Merge remote-tracking branch 'origin/main' into electrum-sp-refactors

This commit is contained in:
Rafael Saes 2025-04-14 10:59:24 -03:00
commit 817735f510
98 changed files with 1882 additions and 704 deletions

View file

@ -283,6 +283,9 @@ jobs:
xmessage -timeout 30 "restore_wallet_through_seeds_flow_test" &
rm -rf ~/.local/share/com.example.cake_wallet/ ~/Documents/cake_wallet/ ~/cake_wallet
exec timeout --signal=SIGKILL 900 flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart
- name: Test [cw_monero]
timeout-minutes: 2
run: cd cw_monero && flutter test
- name: Stop screen recording, encrypt and upload
if: always()
run: |

View file

@ -74,9 +74,6 @@ android {
release {
signingConfig signingConfigs.release
shrinkResources false
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {

View file

@ -6,3 +6,4 @@
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
-dontwarn io.flutter.embedding.**
-dontwarn com.google.android.play.core.splitcompat.SplitCompatApplication

View file

@ -1,3 +1,6 @@
UI/UX enhancements
Performance improvements
Monero 12-word seed support (Wallet Groups support as well)
Integrate DFX's OpenCryptoPay
Exchange flow enhancements
Hardware Wallets flow enhancements
Minor UI enhancements
Bug fixes

View file

@ -1,4 +1,6 @@
New App Logo
UI/UX enhancements
Performance improvements
Monero 12-word seed support (Wallet Groups support as well)
Integrate DFX's OpenCryptoPay
Exchange flow enhancements
Hardware Wallets flow enhancements
Minor UI enhancements
Bug fixes

View file

@ -287,7 +287,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet<LitecoinWalletAddresses
try {
await subscribeForStatuses();
} catch (e) {
printV("failed to subcribe for updates: $e");
printV("failed to subscribe for updates: $e");
}
updateFeeRates();
_feeRatesTimer?.cancel();
@ -551,7 +551,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet<LitecoinWalletAddresses
}
_utxoStream = responseStream.listen(
(Utxo sUtxo) async {
// we're processing utxos, so our balance could still be innacurate:
// we're processing utxos, so our balance could still be inaccurate:
if (mwebSyncStatus is! SynchronizingSyncStatus && mwebSyncStatus is! SyncingSyncStatus) {
mwebSyncStatus = SynchronizingSyncStatus();
processingUtxos = true;

View file

@ -247,3 +247,38 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal
'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
}
}
WalletType? cryptoCurrencyToWalletType(CryptoCurrency type) {
switch (type) {
case CryptoCurrency.xmr:
return WalletType.monero;
case CryptoCurrency.btc:
return WalletType.bitcoin;
case CryptoCurrency.ltc:
return WalletType.litecoin;
case CryptoCurrency.xhv:
return WalletType.haven;
case CryptoCurrency.eth:
return WalletType.ethereum;
case CryptoCurrency.bch:
return WalletType.bitcoinCash;
case CryptoCurrency.nano:
return WalletType.nano;
case CryptoCurrency.banano:
return WalletType.banano;
case CryptoCurrency.maticpoly:
return WalletType.polygon;
case CryptoCurrency.sol:
return WalletType.solana;
case CryptoCurrency.trx:
return WalletType.tron;
case CryptoCurrency.wow:
return WalletType.wownero;
case CryptoCurrency.zano:
return WalletType.zano;
case CryptoCurrency.dcr:
return WalletType.decred;
default:
return null;
}
}

View file

@ -710,6 +710,7 @@ abstract class DecredWalletBase extends WalletBase<DecredBalance, DecredTransact
// walletBirthdayBlockHeight checks if the wallet birthday is set and returns
// it. Returns -1 if not.
Future<int> walletBirthdayBlockHeight() async {
try {
final res = await _libwallet.birthState(walletInfo.name);
final decoded = json.decode(res);
// Having these values set indicates that sync has not reached the birthday
@ -718,6 +719,9 @@ abstract class DecredWalletBase extends WalletBase<DecredBalance, DecredTransact
return -1;
}
return decoded["height"] ?? 0;
} on FormatException catch (_) {
return 0;
}
}
Future<bool> verifyMessage(String message, String signature, {String? address = null}) async {

View file

@ -190,7 +190,7 @@ abstract class EVMChainClient {
_sendTransaction = () async => await sendTransaction(signedTransaction);
return PendingEVMChainTransaction(
signedTransaction: signedTransaction,
signedTransaction: prepareSignedTransactionForSending(signedTransaction),
amount: amount.toString(),
fee: gasFee,
sendTransaction: _sendTransaction,

View file

@ -12,3 +12,4 @@ android/.cxx/
macos/cw_monero.podspec
macos/External/
*monero_libwallet2_api_c.*

View file

@ -1,4 +1,7 @@
import 'dart:async';
import 'package:cw_monero/api/wallet.dart';
import 'package:cw_monero/monero_account_list.dart';
import 'package:monero/monero.dart' as monero;
monero.wallet? wptr = null;
@ -15,7 +18,6 @@ monero.WalletListener? getWlptr() {
return _wlptr!;
}
monero.SubaddressAccount? subaddressAccount;
bool isUpdating = false;
@ -51,8 +53,9 @@ void addAccountSync({required String label}) {
}
void setLabelForAccountSync({required int accountIndex, required String label}) {
// TODO(mrcyjanek): this may be wrong function?
monero.Wallet_setSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: 0, label: label);
monero.SubaddressAccount_setLabel(subaddressAccount!, accountIndex: accountIndex, label: label);
MoneroAccountListBase.cachedAccounts[wptr!.address] = [];
refreshAccounts();
}
void _addAccount(String label) => addAccountSync(label: label);
@ -66,10 +69,10 @@ void _setLabelForAccount(Map<String, dynamic> args) {
Future<void> addAccount({required String label}) async {
_addAccount(label);
await store();
unawaited(store());
}
Future<void> setLabelForAccount({required int accountIndex, required String label}) async {
_setLabelForAccount({'accountIndex': accountIndex, 'label': label});
await store();
unawaited(store());
}

View file

@ -185,13 +185,14 @@ Future<PendingTransactionDescription> createTransactionSync(
final rAmt = monero.PendingTransaction_amount(pendingTx);
final rFee = monero.PendingTransaction_fee(pendingTx);
final rHash = monero.PendingTransaction_txid(pendingTx, '');
final rHex = monero.PendingTransaction_hex(pendingTx, '');
final rTxKey = rHash;
return PendingTransactionDescription(
amount: rAmt,
fee: rFee,
hash: rHash,
hex: '',
hex: rHex,
txKey: rTxKey,
pointerAddress: pendingTx.address,
);
@ -234,7 +235,7 @@ Future<PendingTransactionDescription> createTransactionMultDest(
amount: monero.PendingTransaction_amount(txptr),
fee: monero.PendingTransaction_fee(txptr),
hash: monero.PendingTransaction_txid(txptr, ''),
hex: monero.PendingTransaction_txid(txptr, ''),
hex: monero.PendingTransaction_hex(txptr, ''),
txKey: monero.PendingTransaction_txid(txptr, ''),
pointerAddress: txptr.address,
);

View file

@ -62,6 +62,11 @@ String getSeed() {
}
return cakepolyseed;
}
final bip39 = monero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.seed.bip39");
if(bip39.isNotEmpty) return bip39;
final legacy = getSeedLegacy(null);
return legacy;
}

View file

@ -0,0 +1,59 @@
import 'dart:typed_data';
import 'package:bip32/bip32.dart' as bip32;
import 'package:bip39/bip39.dart' as bip39;
import 'package:polyseed/polyseed.dart';
bool isBip39Seed(String mnemonic) => bip39.validateMnemonic(mnemonic);
String getBip39Seed() => bip39.generateMnemonic();
String getLegacySeedFromBip39(String mnemonic,
{int accountIndex = 0, String passphrase = ""}) {
final seed = bip39.mnemonicToSeed(mnemonic, passphrase: passphrase);
final bip32KeyPair =
bip32.BIP32.fromSeed(seed).derivePath("m/44'/128'/$accountIndex'/0/0");
final spendKey = _reduceECKey(bip32KeyPair.privateKey!);
return LegacySeedLang.getByEnglishName("English")
.encodePhrase(spendKey.toHexString());
}
const _ed25519CurveOrder =
"1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED";
Uint8List _reduceECKey(Uint8List buffer) {
final curveOrder = BigInt.parse(_ed25519CurveOrder, radix: 16);
final bigNumber = _readBytes(buffer);
var result = bigNumber % curveOrder;
final resultBuffer = Uint8List(32);
for (var i = 0; i < 32; i++) {
resultBuffer[i] = (result & BigInt.from(0xff)).toInt();
result = result >> 8;
}
return resultBuffer;
}
/// Read BigInt from a little-endian Uint8List
/// From https://github.com/dart-lang/sdk/issues/32803#issuecomment-387405784
BigInt _readBytes(Uint8List bytes) {
BigInt read(int start, int end) {
if (end - start <= 4) {
var result = 0;
for (int i = end - 1; i >= start; i--) {
result = result * 256 + bytes[i];
}
return BigInt.from(result);
}
final mid = start + ((end - start) >> 1);
return read(start, mid) +
read(mid, end) * (BigInt.one << ((mid - start) * 8));
}
return read(0, bytes.length);
}

View file

@ -45,18 +45,18 @@ abstract class MoneroAccountListBase with Store {
}
}
Map<int, List<Account>> _cachedAccounts = {};
static Map<int, List<Account>> cachedAccounts = {};
List<Account> getAll() {
final allAccounts = account_list.getAllAccount();
final currentCount = allAccounts.length;
_cachedAccounts[account_list.wptr!.address] ??= [];
cachedAccounts[account_list.wptr!.address] ??= [];
if (_cachedAccounts[account_list.wptr!.address]!.length == currentCount) {
return _cachedAccounts[account_list.wptr!.address]!;
if (cachedAccounts[account_list.wptr!.address]!.length == currentCount) {
return cachedAccounts[account_list.wptr!.address]!;
}
_cachedAccounts[account_list.wptr!.address] = allAccounts.map((accountRow) {
cachedAccounts[account_list.wptr!.address] = allAccounts.map((accountRow) {
final balance = monero.SubaddressAccountRow_getUnlockedBalance(accountRow);
return Account(
@ -66,7 +66,7 @@ abstract class MoneroAccountListBase with Store {
);
}).toList();
return _cachedAccounts[account_list.wptr!.address]!;
return cachedAccounts[account_list.wptr!.address]!;
}
Future<void> addAccount({required String label}) async {

View file

@ -1,6 +1,7 @@
import 'dart:ffi';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:cw_core/get_height_by_date.dart';
import 'package:cw_core/monero_wallet_utils.dart';
import 'package:cw_core/pathForWallet.dart';
@ -14,26 +15,35 @@ import 'package:cw_core/wallet_type.dart';
import 'package:cw_monero/api/account_list.dart';
import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
import 'package:cw_monero/api/wallet_manager.dart';
import 'package:cw_monero/bip39_seed.dart';
import 'package:cw_monero/ledger.dart';
import 'package:cw_monero/monero_wallet.dart';
import 'package:collection/collection.dart';
import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:monero/monero.dart' as monero;
import 'package:polyseed/polyseed.dart';
enum MoneroSeedType { polyseed, legacy, bip39 }
class MoneroNewWalletCredentials extends WalletCredentials {
MoneroNewWalletCredentials(
{required String name, required this.language, required this.isPolyseed, String? password, this.passphrase})
{required String name,
required this.language,
required this.seedType,
String? password,
this.passphrase,
this.mnemonic})
: super(name: name, password: password);
final String language;
final bool isPolyseed;
final MoneroSeedType seedType;
final String? passphrase;
final String? mnemonic;
}
class MoneroRestoreWalletFromHardwareCredentials extends WalletCredentials {
MoneroRestoreWalletFromHardwareCredentials({required String name,
MoneroRestoreWalletFromHardwareCredentials(
{required String name,
required this.ledgerConnection,
int height = 0,
String? password})
@ -60,7 +70,8 @@ class MoneroWalletLoadingException implements Exception {
}
class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
MoneroRestoreWalletFromKeysCredentials({required String name,
MoneroRestoreWalletFromKeysCredentials(
{required String name,
required String password,
required this.language,
required this.address,
@ -98,26 +109,41 @@ class MoneroWalletService extends WalletService<
WalletType getType() => WalletType.monero;
@override
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials, {bool? isTestnet}) async {
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials,
{bool? isTestnet}) async {
try {
final path = await pathForWallet(name: credentials.name, type: getType());
if (credentials.isPolyseed) {
if (credentials.seedType == MoneroSeedType.bip39) {
return _restoreFromBip39(
path: path,
password: credentials.password!,
mnemonic: credentials.mnemonic ?? getBip39Seed(),
passphrase: credentials.passphrase,
walletInfo: credentials.walletInfo!,
);
}
if (credentials.seedType == MoneroSeedType.polyseed) {
final polyseed = Polyseed.create();
final lang = PolyseedLang.getByEnglishName(credentials.language);
if (credentials.passphrase != null) polyseed.crypt(credentials.passphrase!);
if (credentials.passphrase != null)
polyseed.crypt(credentials.passphrase!);
final heightOverride =
getMoneroHeigthByDate(date: DateTime.now().subtract(Duration(days: 2)));
final heightOverride = getMoneroHeigthByDate(
date: DateTime.now().subtract(Duration(days: 2)));
return _restoreFromPolyseed(
path, credentials.password!, polyseed, credentials.walletInfo!, lang,
return _restoreFromPolyseed(path, credentials.password!, polyseed,
credentials.walletInfo!, lang,
overrideHeight: heightOverride, passphrase: credentials.passphrase);
}
await monero_wallet_manager.createWallet(
path: path, password: credentials.password!, language: credentials.language, passphrase: credentials.passphrase??"");
path: path,
password: credentials.password!,
language: credentials.language,
passphrase: credentials.passphrase ?? "");
final wallet = MoneroWallet(
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
@ -145,7 +171,8 @@ class MoneroWalletService extends WalletService<
}
@override
Future<MoneroWallet> openWallet(String name, String password, {OpenWalletTry openWalletTry = OpenWalletTry.initial}) async {
Future<MoneroWallet> openWallet(String name, String password,
{OpenWalletTry openWalletTry = OpenWalletTry.initial}) async {
try {
final path = await pathForWallet(name: name, type: getType());
@ -303,8 +330,28 @@ class MoneroWalletService extends WalletService<
rethrow;
}
try {
if (isBip39Seed(credentials.mnemonic)) {
final path =
await pathForWallet(name: credentials.name, type: getType());
return _restoreFromBip39(
path: path,
password: credentials.password!,
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
overrideHeight: credentials.height!,
passphrase: credentials.passphrase,
);
}
} catch (e) {
printV("Bip39 restore failed: $e");
rethrow;
}
try {
final path = await pathForWallet(name: credentials.name, type: getType());
monero_wallet_manager.restoreFromSeed(
path: path,
password: credentials.password!,
@ -325,6 +372,52 @@ class MoneroWalletService extends WalletService<
}
}
Future<MoneroWallet> _restoreFromBip39({
required String path,
required String password,
required String mnemonic,
required WalletInfo walletInfo,
String? passphrase,
int? overrideHeight,
}) async {
walletInfo.derivationInfo = DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/44'/128'/0'/0/0",
);
final legacyMnemonic =
getLegacySeedFromBip39(mnemonic, passphrase: passphrase ?? "");
final height =
overrideHeight ?? getMoneroHeigthByDate(date: DateTime.now());
walletInfo.isRecovery = true;
walletInfo.restoreHeight = height;
monero_wallet_manager.restoreFromSeed(
path: path,
password: password,
passphrase: '',
seed: legacyMnemonic,
restoreHeight: height,
);
monero.Wallet_setCacheAttribute(wptr!,
key: "cakewallet.seed.bip39", value: mnemonic);
monero.Wallet_setCacheAttribute(wptr!,
key: "cakewallet.passphrase", value: passphrase ?? '');
monero.Wallet_store(wptr!);
final wallet = MoneroWallet(
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
password: password,
);
await wallet.init();
return wallet;
}
Future<MoneroWallet> restoreFromPolyseed(
MoneroRestoreWalletFromSeedCredentials credentials) async {
try {
@ -344,14 +437,12 @@ class MoneroWalletService extends WalletService<
}
}
Future<MoneroWallet> _restoreFromPolyseed(
String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang,
Future<MoneroWallet> _restoreFromPolyseed(String path, String password,
Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang,
{PolyseedCoin coin = PolyseedCoin.POLYSEED_MONERO,
int? overrideHeight,
String? passphrase}) async {
if (polyseed.isEncrypted == false &&
(passphrase??'') != "") {
if (polyseed.isEncrypted == false && (passphrase ?? '') != "") {
// Fallback to the different passphrase offset method, when a passphrase
// was provided but the polyseed is not encrypted.
monero_wallet_manager.restoreWalletFromPolyseedWithOffset(
@ -437,7 +528,8 @@ class MoneroWalletService extends WalletService<
if (walletFilesExist(path)) await repairOldAndroidWallet(name);
await monero_wallet_manager.openWalletAsync({'path': path, 'password': password});
await monero_wallet_manager
.openWalletAsync({'path': path, 'password': password});
final walletInfo = walletInfoSource.values
.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
final wallet = MoneroWallet(

View file

@ -5,18 +5,23 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8"
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
url: "https://pub.dev"
source: hosted
version: "47.0.0"
version: "76.0.0"
_macros:
dependency: transitive
description: dart
source: sdk
version: "0.3.3"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80"
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
url: "https://pub.dev"
source: hosted
version: "4.7.0"
version: "6.11.0"
args:
dependency: transitive
description:
@ -41,6 +46,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.11.0"
bip32:
dependency: "direct main"
description:
name: bip32
sha256: "54787cd7a111e9d37394aabbf53d1fc5e2e0e0af2cd01c459147a97c0e3f8a97"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
bip39:
dependency: "direct main"
description:
name: bip39
sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc
url: "https://pub.dev"
source: hosted
version: "1.0.6"
blockchain_utils:
dependency: transitive
description:
@ -66,6 +87,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
bs58check:
dependency: transitive
description:
name: bs58check
sha256: c4a164d42b25c2f6bc88a8beccb9fc7d01440f3c60ba23663a20a70faf484ea9
url: "https://pub.dev"
source: hosted
version: "1.0.2"
build:
dependency: transitive
description:
@ -94,10 +123,10 @@ packages:
dependency: "direct dev"
description:
name: build_resolvers
sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6"
sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
url: "https://pub.dev"
source: hosted
version: "2.0.10"
version: "2.4.4"
build_runner:
dependency: "direct dev"
description:
@ -222,10 +251,10 @@ packages:
dependency: transitive
description:
name: dart_style
sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4"
sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820"
url: "https://pub.dev"
source: hosted
version: "2.2.4"
version: "2.3.8"
dbus:
dependency: transitive
description:
@ -348,6 +377,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.6.0"
hex:
dependency: transitive
description:
name: hex
sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a"
url: "https://pub.dev"
source: hosted
version: "0.2.0"
hive:
dependency: transitive
description:
@ -360,10 +397,10 @@ packages:
dependency: "direct dev"
description:
name: hive_generator
sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938"
sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
url: "https://pub.dev"
source: hosted
version: "1.1.3"
version: "2.0.1"
http:
dependency: "direct main"
description:
@ -468,6 +505,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
macros:
dependency: transitive
description:
name: macros
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
url: "https://pub.dev"
source: hosted
version: "0.1.3-main.0"
matcher:
dependency: transitive
description:
@ -512,10 +557,18 @@ packages:
dependency: "direct dev"
description:
name: mobx_codegen
sha256: d4beb9cea4b7b014321235f8fdc7c2193ee0fe1d1198e9da7403f8bc85c4407c
sha256: "990da80722f7d7c0017dec92040b31545d625b15d40204c36a1e63d167c73cdc"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.7.0"
mockito:
dependency: "direct dev"
description:
name: mockito
sha256: f99d8d072e249f719a5531735d146d8cf04c580d93920b04de75bef6dfb2daf6
url: "https://pub.dev"
source: hosted
version: "5.4.5"
monero:
dependency: "direct main"
description:
@ -735,18 +788,18 @@ packages:
dependency: transitive
description:
name: source_gen
sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d"
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
url: "https://pub.dev"
source: hosted
version: "1.2.6"
version: "1.5.0"
source_helper:
dependency: transitive
description:
name: source_helper
sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f"
sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
version: "1.3.5"
source_span:
dependency: transitive
description:
@ -924,5 +977,5 @@ packages:
source: hosted
version: "3.1.3"
sdks:
dart: ">=3.5.0 <4.0.0"
dart: ">=3.6.0 <4.0.0"
flutter: ">=3.24.0"

View file

@ -12,6 +12,8 @@ environment:
dependencies:
flutter:
sdk: flutter
bip39: ^1.0.6
bip32: ^2.0.0
ffi: ^2.0.1
http: ^1.1.0
path_provider: ^2.0.11
@ -36,7 +38,8 @@ dev_dependencies:
build_runner: ^2.4.7
build_resolvers: ^2.0.9
mobx_codegen: ^2.0.7
hive_generator: ^1.1.3
mockito: ^5.4.5
hive_generator: ^2.0.1
dependency_overrides:
watcher: ^1.1.0

View file

@ -0,0 +1,39 @@
import 'package:cw_monero/bip39_seed.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group("Exodus Style bip39", () {
group("Test Wallet 1", () {
final bip39Seed = 'meadow tip best belt boss eyebrow control affair eternal piece very shiver';
final expectedLegacySeed0 = "tasked eight afraid laboratory tail feline rift reinvest vane cafe bailed foggy dormant paper jigsaw king hazard suture king dapper dummy jolted dating dwindling king";
final expectedLegacySeed1 = "palace pairing axes mohawk rekindle excess awful juvenile shipped talent nibs efficient dapper biggest swung fight pact innocent emerge issued titans affair nearby noises emerge";
test("Get legacy Seed from bip39", () {
final legacySeed = getLegacySeedFromBip39(bip39Seed);
expect(legacySeed, expectedLegacySeed0);
});
test("Get legacy Seed from bip39 with account index", () {
final legacySeed = getLegacySeedFromBip39(bip39Seed, accountIndex: 1);
expect(legacySeed, expectedLegacySeed1);
});
});
group("Test Wallet 2", () {
final bip39Seed = "color ranch color remove subway public water embrace before begin liberty fault";
final expectedLegacySeed0 = "somewhere problems gauze gigantic intended foxes upcoming saved waffle pipeline lurk bogeys empty wipeout abbey italics novelty tucks rafts elite lunar obnoxious awful bugs elite";
final expectedLegacySeed1 = "playful toxic wildly eluded mesh fainted february mugged maps repent vigilant hitched seventh threaten clue fetches sample diet number alkaline future cottage tuition vegan alkaline";
test("Get legacy Seed from bip39", () {
final legacySeed = getLegacySeedFromBip39(bip39Seed);
expect(legacySeed, expectedLegacySeed0);
});
test("Get legacy Seed from bip39 with account index", () {
final legacySeed = getLegacySeedFromBip39(bip39Seed, accountIndex: 1);
expect(legacySeed, expectedLegacySeed1);
});
});
});
}

View file

@ -0,0 +1,29 @@
import 'package:mockito/mockito.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
class MockPathProviderPlatform extends Mock
with MockPlatformInterfaceMixin
implements PathProviderPlatform {
Future<String> getTemporaryPath() => throw UnimplementedError();
Future<String> getApplicationSupportPath() => throw UnimplementedError();
Future<String> getLibraryPath() => throw UnimplementedError();
Future<String> getApplicationDocumentsPath() async => "./test/data";
Future<String> getExternalStoragePath() => throw UnimplementedError();
Future<List<String>> getExternalCachePaths() => throw UnimplementedError();
Future<String> getDownloadsPath() => throw UnimplementedError();
@override
Future<String?> getApplicationCachePath() => throw UnimplementedError();
@override
Future<List<String>?> getExternalStoragePaths({StorageDirectory? type}) =>
throw UnimplementedError();
}

View file

@ -0,0 +1,148 @@
import 'dart:io';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_monero/monero_wallet_service.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hive/hive.dart';
import 'package:path_provider_platform_interface/path_provider_platform_interface.dart';
import 'mock/path_provider.dart';
import 'utils/setup_monero_c.dart';
Future<void> main() async {
group("MoneroWalletService Tests", () {
Hive.init('./test/data/db');
late MoneroWalletService walletService;
late File moneroCBinary;
setUpAll(() async {
PathProviderPlatform.instance = MockPathProviderPlatform();
final Box<WalletInfo> walletInfoSource =
await Hive.openBox('testWalletInfo');
final Box<UnspentCoinsInfo> unspentCoinsInfoSource =
await Hive.openBox('testUnspentCoinsInfo');
walletService = MoneroWalletService(walletInfoSource, unspentCoinsInfoSource);
moneroCBinary = getMoneroCBinary().copySync(moneroCBinaryName);
});
tearDownAll(() {
Directory('./test/data').deleteSync(recursive: true);
moneroCBinary.deleteSync();
});
group("Create wallet", () {
test("Create Legacy Wallet", () async {
final credentials = _getTestCreateCredentials(
name: 'Create Wallet LS',
language: 'English',
seedType: MoneroSeedType.legacy);
final wallet = await walletService.create(credentials);
expect(wallet.seed.split(" ").length, 25);
expect(wallet.restoreHeight, greaterThan(3000000));
});
test("Create Polyseed Wallet", () async {
final credentials = _getTestCreateCredentials(
name: 'Create Wallet PS',
language: 'English',
seedType: MoneroSeedType.polyseed);
final wallet = await walletService.create(credentials);
expect(wallet.seed.split(" ").length, 16);
expect(wallet.restoreHeight, greaterThan(3000000));
});
test("Create Bip39 Wallet", () async {
final credentials = _getTestCreateCredentials(
name: 'Create Wallet BS',
language: 'English',
seedType: MoneroSeedType.bip39);
final wallet = await walletService.create(credentials);
expect(wallet.seed.split(" ").length, 12);
expect(wallet.restoreHeight, greaterThan(3000000));
});
});
group("Restore wallet", () {
test('Legacy Seed', () async {
final credentials = _getTestRestoreCredentials(
name: 'Test Wallet LS',
mnemonic:
'ability pockets lordship tomorrow gypsy match neutral uncle avatar betting bicycle junk unzip pyramid lynx mammal edgy empty uneven knowledge juvenile wiring paradise psychic betting',
);
final wallet = await walletService.restoreFromSeed(credentials);
expect(wallet.walletAddresses.primaryAddress,
'48tLyQXpcwt8w6uKHyb5Zs3vdnoDWAEKFQr1c198o7aX9dBzXP3BTSMVsDiuH3ozDCNqwojb4vNeQZf7xg6URimDLaNtGSN');
});
test('Bip39 Seed', () async {
final credentials = _getTestRestoreCredentials(
name: 'Test Wallet BS',
mnemonic:
'color ranch color remove subway public water embrace before begin liberty fault');
final wallet = await walletService.restoreFromSeed(credentials);
expect(wallet.walletAddresses.primaryAddress,
'49MggvPosJugF8Zq7WAKbsSchz6vbyL6YiUxM4ryfGQDXphs6wiWiXLFWCSshnLPcceGTWUaKfWWMHQAAKESV3TQJVQsL9a');
});
});
});
}
MoneroRestoreWalletFromSeedCredentials _getTestRestoreCredentials({
required String name,
required String mnemonic,
}) {
final credentials = MoneroRestoreWalletFromSeedCredentials(
name: name, mnemonic: mnemonic, passphrase: '', password: "test");
credentials.walletInfo = WalletInfo.external(
id: WalletBase.idFor(name, WalletType.monero),
name: name,
type: WalletType.monero,
isRecovery: true,
restoreHeight: credentials.height ?? 0,
date: DateTime.now(),
path: '',
dirPath: '',
address: '',
);
return credentials;
}
MoneroNewWalletCredentials _getTestCreateCredentials({
required String name,
required String language,
required MoneroSeedType seedType,
String? mnemonic,
}) {
final credentials = MoneroNewWalletCredentials(
name: name,
language: language,
seedType: seedType,
password: "test",
mnemonic: mnemonic,
passphrase: '',
);
credentials.walletInfo = WalletInfo.external(
id: WalletBase.idFor(name, WalletType.monero),
name: name,
type: WalletType.monero,
isRecovery: false,
restoreHeight: credentials.height ?? 0,
date: DateTime.now(),
path: '',
dirPath: '',
address: '',
);
return credentials;
}

View file

@ -0,0 +1,16 @@
import 'dart:io';
File getMoneroCBinary() {
if (Platform.isWindows)
return File(
'../scripts/monero_c/release/monero/x86_64-w64-mingw32_libwallet2_api_c.dll');
if (Platform.isMacOS) return File('../macos/monero_libwallet2_api_c.dylib');
return File('../scripts/monero_c/release/monero/x86_64-linux-gnu_libwallet2_api_c.so');
}
String get moneroCBinaryName {
if (Platform.isWindows)
return "monero_libwallet2_api_c.dll";
if (Platform.isMacOS) return "monero_libwallet2_api_c.dylib";
return "/usr/lib/monero_libwallet2_api_c.so";
}

View file

@ -48,8 +48,7 @@ void addAccountSync({required String label}) {
}
void setLabelForAccountSync({required int accountIndex, required String label}) {
// TODO(mrcyjanek): this may be wrong function?
wownero.Wallet_setSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: 0, label: label);
wownero.SubaddressAccount_setLabel(subaddressAccount!, accountIndex: accountIndex, label: label);
}
void _addAccount(String label) => addAccountSync(label: label);

View file

@ -37,6 +37,7 @@ class OnRamperBuyProvider extends BuyProvider {
static const List<CryptoCurrency> _notSupportedCrypto = [];
static const List<FiatCurrency> _notSupportedFiat = [];
static Map<String, dynamic> _onrampMetadata = {};
final SettingsStore _settingsStore;
@ -59,11 +60,8 @@ class OnRamperBuyProvider extends BuyProvider {
Future<List<PaymentMethod>> getAvailablePaymentTypes(
String fiatCurrency, CryptoCurrency cryptoCurrency, bool isBuyAction) async {
final params = {
'fiatCurrency': fiatCurrency,
'type': isBuyAction ? 'buy' : 'sell',
'isRecurringPayment': 'false'
};
final params = {'type': isBuyAction ? 'buy' : 'sell'};
final url = Uri.https(_baseApiUrl, '$supported$paymentTypes/$fiatCurrency', params);
@ -136,16 +134,14 @@ class OnRamperBuyProvider extends BuyProvider {
final actionType = isBuyAction ? 'buy' : 'sell';
final normalizedCryptoCurrency = _getNormalizeCryptoCurrency(cryptoCurrency);
final normalizedCryptoCurrency =
cryptoCurrency.title + _getNormalizeNetwork(cryptoCurrency).toUpperCase();
final params = {
'amount': amount.toString(),
if (paymentMethod != null) 'paymentMethod': paymentMethod,
'clientName': 'CakeWallet',
'type': actionType,
'walletAddress': walletAddress,
'isRecurringPayment': 'false',
'input': 'source',
if (actionType == 'sell') 'type': actionType,
};
log('Onramper: Fetching $actionType quote: ${isBuyAction ? normalizedCryptoCurrency : fiatCurrency.name} -> ${isBuyAction ? fiatCurrency.name : normalizedCryptoCurrency}, amount: $amount, paymentMethod: $paymentMethod');
@ -165,7 +161,7 @@ class OnRamperBuyProvider extends BuyProvider {
List<Quote> validQuotes = [];
final onrampMetadata = await getOnrampMetadata();
if (_onrampMetadata.isEmpty) _onrampMetadata = await getOnrampMetadata();
for (var item in data) {
@ -174,12 +170,12 @@ class OnRamperBuyProvider extends BuyProvider {
final paymentMethod = (item as Map<String, dynamic>)['paymentMethod'] as String;
final rampId = item['ramp'] as String?;
final rampMetaData = onrampMetadata[rampId] as Map<String, dynamic>?;
final rampMetaData = _onrampMetadata[rampId] as Map<String, dynamic>?;
if (rampMetaData == null) continue;
final quote = Quote.fromOnramperJson(
item, isBuyAction, onrampMetadata, _getPaymentTypeByString(paymentMethod));
item, isBuyAction, _onrampMetadata, _getPaymentTypeByString(paymentMethod));
quote.setFiatCurrency = fiatCurrency;
quote.setCryptoCurrency = cryptoCurrency;
validQuotes.add(quote);
@ -206,7 +202,6 @@ class OnRamperBuyProvider extends BuyProvider {
required String cryptoCurrencyAddress,
String? countryCode}) async {
final actionType = isBuyAction ? 'buy' : 'sell';
final prefix = actionType == 'sell' ? actionType + '_' : '';
final primaryColor = getColorStr(Theme.of(context).primaryColor);
final secondaryColor = getColorStr(Theme.of(context).colorScheme.background);
@ -220,18 +215,20 @@ class OnRamperBuyProvider extends BuyProvider {
cardColor = getColorStr(Colors.white);
}
final defaultCrypto = _getNormalizeCryptoCurrency(quote.cryptoCurrency);
final defaultCrypto =
quote.cryptoCurrency.title + _getNormalizeNetwork(quote.cryptoCurrency).toLowerCase();
final paymentMethod = normalizePaymentMethod(quote.paymentType);
final uri = Uri.https(_baseUrl, '', {
'apiKey': _apiKey,
'mode': actionType,
'${prefix}defaultFiat': quote.fiatCurrency.name,
'${prefix}defaultCrypto': defaultCrypto,
'${prefix}defaultAmount': amount.toString(),
if (paymentMethod != null) '${prefix}defaultPaymentMethod': paymentMethod,
'onlyOnramps': quote.rampId,
'txnType': actionType,
'txnFiat': quote.fiatCurrency.name,
'txnCrypto': defaultCrypto,
'txnAmount': amount.toString(),
'skipTransactionScreen': "true",
if (paymentMethod != null) 'txnPaymentMethod': paymentMethod,
'txnOnramp': quote.rampId,
'networkWallets': '${_tagToNetwork(quote.cryptoCurrency.tag ?? quote.cryptoCurrency.title)}:$cryptoCurrencyAddress',
'supportSwap': "false",
'primaryColor': primaryColor,
@ -257,31 +254,29 @@ class OnRamperBuyProvider extends BuyProvider {
String _tagToNetwork(String tag) {
switch (tag) {
case 'OMNI':
case 'BSC':
return tag;
case 'POL':
return 'POLYGON';
case 'ETH':
return 'ETHEREUM';
case 'TRX':
return 'TRON';
case 'SOL':
return 'SOLANA';
case 'ZEC':
return 'ZCASH';
default:
try {
return CryptoCurrency.fromString(tag).fullName!;
} catch (_) {
return tag;
}
}
}
String _getNormalizeCryptoCurrency(Currency currency) {
if (currency is CryptoCurrency) {
if (!mainCurrency.contains(currency)) {
final network = currency.tag == null ? currency.fullName : _tagToNetwork(currency.tag!);
return '${currency.title}_${network?.replaceAll(' ', '')}'.toUpperCase();
}
return currency.title.toUpperCase();
}
return currency.name.toUpperCase();
String _getNormalizeNetwork(CryptoCurrency currency) {
if (mainCurrency.contains(currency)) return '';
if (currency == CryptoCurrency.eos) return '_EOSIO';
if (currency.tag != null) return '_' + _tagToNetwork(currency.tag!);
return '_' + (currency.fullName?.replaceAll(' ', '') ?? currency.title);;
}
String? normalizePaymentMethod(PaymentType paymentType) {

View file

@ -35,9 +35,16 @@ class RobinhoodBuyProvider extends BuyProvider {
static const _baseUrl = 'applink.robinhood.com';
static const _cIdBaseUrl = 'exchange-helper.cakewallet.com';
static const _apiBaseUrl = 'api.robinhood.com';
static const _assetsPath = '/catpay/v1/supported_currencies/';
static const List<CryptoCurrency> _notSupportedCrypto = [];
static const List<FiatCurrency> _notSupportedFiat = [];
static List<CryptoCurrency> _supportedCrypto = [];
static final List<FiatCurrency> _supportedFiat = [FiatCurrency.usd];
static final List<CryptoCurrency> _notSupportedCrypto = [];
static final List<FiatCurrency> _notSupportedFiat =
FiatCurrency.all.where((fiat) => !_supportedFiat.contains(fiat)).toList();
@override
String get title => 'Robinhood Connect';
@ -58,6 +65,38 @@ class RobinhoodBuyProvider extends BuyProvider {
String get _apiSecret => secrets.exchangeHelperApiKey;
Future<List<CryptoCurrency>> getSupportedAssets() async {
final uri = Uri.https(_apiBaseUrl, '$_assetsPath', {'applicationId': _applicationId});
try {
final response = await http.get(uri, headers: {'accept': 'application/json'});
if (response.statusCode == 200) {
final responseData = jsonDecode(response.body) as Map<String, dynamic>;
final pairs = responseData['cryptoCurrencyPairs'] as List<dynamic>;
final supportedAssets = <CryptoCurrency>[];
for (final item in pairs) {
String code = item['assetCurrency']['code'] as String;
if (code == 'AVAX') code = 'AVAXC';
try {
final currency = CryptoCurrency.fromString(code);
supportedAssets.add(currency);
} catch (e) {
log('Robinhood: Unknown asset code "$code" - skipped');
}
}
return supportedAssets;
} else {
return [];
}
} catch (e) {
log('Robinhood: Failed to fetch supported assets: $e');
return [];
}
}
Future<String> getSignature(String message) async {
switch (wallet.type) {
case WalletType.ethereum:
@ -156,6 +195,9 @@ class RobinhoodBuyProvider extends BuyProvider {
String? countryCode}) async {
String? paymentMethod;
if (_supportedCrypto.isEmpty) _supportedCrypto = await getSupportedAssets();
if (_supportedCrypto.isNotEmpty && !(_supportedCrypto.contains(cryptoCurrency))) return null;
if (paymentType != null && paymentType != PaymentType.all) {
paymentMethod = normalizePaymentMethod(paymentType);
if (paymentMethod == null) return null;

View file

@ -0,0 +1,18 @@
class OpenCryptoPayException implements Exception {
final String message;
OpenCryptoPayException([this.message = '']);
@override
String toString() =>
'OpenCryptoPayException${message.isNotEmpty ? ': $message' : ''}';
}
class OpenCryptoPayNotSupportedException extends OpenCryptoPayException {
final String provider;
OpenCryptoPayNotSupportedException(this.provider);
@override
String get message => "$provider does not support Open CryptoPay";
}

View file

@ -0,0 +1,78 @@
import 'dart:convert';
import 'package:bech32/bech32.dart';
Uri decodeLNURL(String encodedUrl) {
Uri decodedUri;
/// The URL doesn't have to be encoded at all as per LUD-17: Protocol schemes and raw (non bech32-encoded) URLs.
/// https://github.com/lnurl/luds/blob/luds/17.md
/// Handle non bech32-encoded LNURL
final lud17prefixes = ['lnurlw', 'lnurlc', 'lnurlp', 'keyauth'];
decodedUri = Uri.parse(encodedUrl);
for (final prefix in lud17prefixes) {
if (decodedUri.scheme.contains(prefix)) {
decodedUri = decodedUri.replace(scheme: prefix);
}
}
if (lud17prefixes.contains(decodedUri.scheme)) {
/// If the non-bech32 LNURL is a Tor address, the port has to be http instead of https for the clearnet LNURL so check if the host ends with '.onion' or '.onion.'
decodedUri = decodedUri.replace(
scheme: decodedUri.host.endsWith('onion') ||
decodedUri.host.endsWith('onion.')
? 'http'
: 'https');
} else {
/// Try to parse the input as a lnUrl. Will throw an error if it fails.
final lnUrl = _findLnUrl(encodedUrl);
/// Decode the lnurl using bech32
final bech32 = const Bech32Codec().decode(lnUrl, lnUrl.length);
decodedUri = Uri.parse(utf8.decode(_convert(bech32.data, 5, 8, false)));
}
return decodedUri;
}
/// Parse and return a given lnurl string if it's valid. Will remove
/// `lightning:` from the beginning of it if present.
String _findLnUrl(String input) {
final res = RegExp(
r',*?((lnurl)([0-9]+[a-z0-9]+))',
).allMatches(input.toLowerCase());
if (res.length == 1) {
return res.first.group(0)!;
} else {
throw ArgumentError('Not a valid lnurl string');
}
}
/// Taken from bech32 (bitcoinjs): https://github.com/bitcoinjs/bech32
List<int> _convert(List<int> data, int inBits, int outBits, bool pad) {
var value = 0;
var bits = 0;
final maxV = (1 << outBits) - 1;
final result = <int>[];
for (final dataValue in data) {
value = (value << inBits) | dataValue;
bits += inBits;
while (bits >= outBits) {
bits -= outBits;
result.add((value >> bits) & maxV);
}
}
if (pad) {
if (bits > 0) result.add((value << (outBits - bits)) & maxV);
} else {
if (bits >= inBits) throw Exception('Excess padding');
if ((value << (outBits - bits)) & maxV > 0) {
throw Exception('Non-zero padding');
}
}
return result;
}

View file

@ -0,0 +1,26 @@
class OpenCryptoPayRequest {
final String receiverName;
final int expiry;
final String callbackUrl;
final String quote;
final Map<String, List<OpenCryptoPayQuoteAsset>> methods;
OpenCryptoPayRequest({
required this.receiverName,
required this.expiry,
required this.callbackUrl,
required this.quote,
required this.methods,
});
}
class OpenCryptoPayQuoteAsset {
final String symbol;
final String amount;
const OpenCryptoPayQuoteAsset(this.symbol, this.amount);
OpenCryptoPayQuoteAsset.fromJson(Map<String, dynamic> json)
: symbol = json['asset'] as String,
amount = json['amount'] as String;
}

View file

@ -0,0 +1,184 @@
import 'dart:convert';
import 'dart:developer';
import 'package:cake_wallet/core/open_crypto_pay/exceptions.dart';
import 'package:cake_wallet/core/open_crypto_pay/lnurl.dart';
import 'package:cake_wallet/core/open_crypto_pay/models.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:http/http.dart';
class OpenCryptoPayService {
static bool isOpenCryptoPayQR(String value) =>
value.toLowerCase().contains("lightning=lnurl") ||
value.toLowerCase().startsWith("lnurl");
final Client _httpClient = Client();
Future<String> commitOpenCryptoPayRequest(
String txHex, {
required String txId,
required OpenCryptoPayRequest request,
required CryptoCurrency asset,
}) async {
final uri = Uri.parse(request.callbackUrl.replaceAll("/cb/", "/tx/"));
final queryParams = Map.of(uri.queryParameters);
queryParams['quote'] = request.quote;
queryParams['asset'] = asset.title;
queryParams['method'] = _getMethod(asset);
queryParams['hex'] = txHex;
queryParams['tx'] = txId;
final response =
await _httpClient.get(Uri.https(uri.authority, uri.path, queryParams));
if (response.statusCode == 200) {
final body = jsonDecode(response.body) as Map;
if (body.keys.contains("txId")) return body["txId"] as String;
throw OpenCryptoPayException(body.toString());
}
throw OpenCryptoPayException(
"Unexpected status code ${response.statusCode} ${response.body}");
}
Future<void> cancelOpenCryptoPayRequest(OpenCryptoPayRequest request) async {
final uri = Uri.parse(request.callbackUrl.replaceAll("/cb/", "/cancel/"));
await _httpClient.delete(uri);
}
Future<OpenCryptoPayRequest> getOpenCryptoPayInvoice(String lnUrl) async {
if (lnUrl.toLowerCase().startsWith("http")) {
final uri = Uri.parse(lnUrl);
final params = uri.queryParameters;
if (!params.containsKey("lightning")) {
throw OpenCryptoPayNotSupportedException(uri.authority);
}
lnUrl = params["lightning"] as String;
}
final url = decodeLNURL(lnUrl);
final params = await _getOpenCryptoPayParams(url);
return OpenCryptoPayRequest(
receiverName: params.$1.displayName ?? "Unknown",
expiry: params.$1.expiration.difference(DateTime.now()).inSeconds,
callbackUrl: params.$1.callbackUrl,
quote: params.$1.id,
methods: params.$2,
);
}
Future<(_OpenCryptoPayQuote, Map<String, List<OpenCryptoPayQuoteAsset>>)>
_getOpenCryptoPayParams(Uri uri) async {
final response = await _httpClient.get(uri);
if (response.statusCode == 200) {
final responseBody = jsonDecode(response.body) as Map;
for (final key in ['callback', 'transferAmounts', 'quote']) {
if (!responseBody.keys.contains(key)) {
throw OpenCryptoPayNotSupportedException(uri.authority);
}
}
final methods = <String, List<OpenCryptoPayQuoteAsset>>{};
for (final transferAmountRaw in responseBody['transferAmounts'] as List) {
final transferAmount = transferAmountRaw as Map;
final method = transferAmount['method'] as String;
methods[method] = [];
for (final assetJson in transferAmount['assets'] as List) {
final asset = OpenCryptoPayQuoteAsset.fromJson(
assetJson as Map<String, dynamic>);
methods[method]?.add(asset);
}
}
log(responseBody.toString());
final quote = _OpenCryptoPayQuote.fromJson(
responseBody['callback'] as String,
responseBody['displayName'] as String?,
responseBody['quote'] as Map<String, dynamic>);
return (quote, methods);
} else {
throw OpenCryptoPayException(
'Failed to get Open CryptoPay Request. Status: ${response.statusCode} ${response.body}');
}
}
Future<Uri> getOpenCryptoPayAddress(
OpenCryptoPayRequest request, CryptoCurrency asset) async {
final uri = Uri.parse(request.callbackUrl);
final queryParams = Map.of(uri.queryParameters);
queryParams['quote'] = request.quote;
queryParams['asset'] = asset.title;
queryParams['method'] = _getMethod(asset);
final response =
await _httpClient.get(Uri.https(uri.authority, uri.path, queryParams));
if (response.statusCode == 200) {
final responseBody = jsonDecode(response.body) as Map;
for (final key in ['expiryDate', 'uri']) {
if (!responseBody.keys.contains(key)) {
throw OpenCryptoPayNotSupportedException(uri.authority);
}
}
final result = Uri.parse(responseBody['uri'] as String);
if (result.queryParameters['amount'] != null) return result;
final newQueryParameters =
Map<String, dynamic>.from(result.queryParameters);
newQueryParameters['amount'] = _getAmountByAsset(request, asset);
return Uri(
scheme: result.scheme,
path: result.path.split("@").first,
queryParameters: newQueryParameters);
} else {
throw OpenCryptoPayException(
'Failed to create Open CryptoPay Request. Status: ${response.statusCode} ${response.body}');
}
}
String _getAmountByAsset(OpenCryptoPayRequest request, CryptoCurrency asset) {
final method = _getMethod(asset);
return request.methods[method]!
.firstWhere((e) => e.symbol.toUpperCase() == asset.title.toUpperCase())
.amount;
}
String _getMethod(CryptoCurrency asset) {
switch (asset.tag) {
case "ETH":
return "Ethereum";
case "POL":
return "Polygon";
default:
return asset.fullName!;
}
}
}
class _OpenCryptoPayQuote {
final String callbackUrl;
final String? displayName;
final String id;
final DateTime expiration;
_OpenCryptoPayQuote(
this.callbackUrl, this.displayName, this.id, this.expiration);
_OpenCryptoPayQuote.fromJson(
this.callbackUrl, this.displayName, Map<String, dynamic> json)
: id = json['id'] as String,
expiration = DateTime.parse(json['expiration'] as String);
}

View file

@ -9,8 +9,50 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl
static List<FiatCurrency> get all => _all.values.toList();
static List<FiatCurrency> get currenciesAvailableToBuyWith =>
[amd, aud, bgn, brl, cad, chf, clp, cop, czk, dkk, egp, eur, gbp, gtq, hkd, hrk, huf, idr, ils, inr, isk, jpy, krw, mad, mxn, myr, ngn, nok, nzd, php, pkr, pln, ron, sek, sgd, thb, twd, usd, vnd, zar, tur,];
static List<FiatCurrency> get currenciesAvailableToBuyWith => [
amd,
aud,
bgn,
brl,
cad,
chf,
clp,
cop,
czk,
dkk,
egp,
eur,
gbp,
gtq,
hkd,
hrk,
huf,
idr,
ils,
inr,
isk,
jpy,
krw,
mad,
mxn,
myr,
ngn,
nok,
nzd,
php,
pkr,
pln,
ron,
sek,
sgd,
thb,
twd,
usd,
vnd,
zar,
tur,
kes,
];
static const amd = FiatCurrency(symbol: 'AMD', countryCode: "arm", fullName: "Armenian Dram");
static const ars = FiatCurrency(symbol: 'ARS', countryCode: "arg", fullName: "Argentine Peso");
@ -62,6 +104,7 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl
static const vnd = FiatCurrency(symbol: 'VND', countryCode: "vnm", fullName: "Vietnamese Dong đồng");
static const zar = FiatCurrency(symbol: 'ZAR', countryCode: "saf", fullName: "South African Rand");
static const tur = FiatCurrency(symbol: 'TRY', countryCode: "tur", fullName: "Turkish Lira");
static const kes = FiatCurrency(symbol: 'KES', countryCode: "ken", fullName: "Kenyan Shillings");
static final _all = {
FiatCurrency.amd.raw: FiatCurrency.amd,
@ -114,6 +157,7 @@ class FiatCurrency extends EnumerableItem<String> with Serializable<String> impl
FiatCurrency.vnd.raw: FiatCurrency.vnd,
FiatCurrency.zar.raw: FiatCurrency.zar,
FiatCurrency.tur.raw: FiatCurrency.tur,
FiatCurrency.kes.raw: FiatCurrency.kes,
};
static FiatCurrency deserialize({required String raw}) => _all[raw] ?? FiatCurrency.usd;

View file

@ -1,17 +1,17 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/enumerable_item.dart';
import 'package:cw_core/wallet_info.dart';
class MoneroSeedType extends EnumerableItem<int> with Serializable<int> {
const MoneroSeedType({required String title, required int raw}) : super(title: title, raw: raw);
static const all = [MoneroSeedType.legacy, MoneroSeedType.polyseed];
static const all = [legacy, polyseed, bip39];
static const defaultSeedType = polyseed;
static const legacy = MoneroSeedType(raw: 0, title: 'Legacy (25 words)');
static const polyseed = MoneroSeedType(raw: 1, title: 'Polyseed (16 words)');
static const wowneroSeed = MoneroSeedType(raw: 2, title: 'Wownero (14 words)');
static const legacy = MoneroSeedType(raw: 0, title: 'Legacy');
static const polyseed = MoneroSeedType(raw: 1, title: 'Polyseed');
static const wowneroSeed = MoneroSeedType(raw: 2, title: 'Wownero');
static const bip39 = MoneroSeedType(raw: 3, title: 'BIP39');
static MoneroSeedType deserialize({required int raw}) {
switch (raw) {
@ -21,24 +21,15 @@ class MoneroSeedType extends EnumerableItem<int> with Serializable<int> {
return polyseed;
case 2:
return wowneroSeed;
case 3:
return bip39;
default:
throw Exception('Unexpected token: $raw for SeedType deserialize');
}
}
@override
String toString() {
switch (this) {
case MoneroSeedType.legacy:
return S.current.seedtype_legacy;
case MoneroSeedType.polyseed:
return S.current.seedtype_polyseed;
case MoneroSeedType.wowneroSeed:
return S.current.seedtype_wownero;
default:
return '';
}
}
String toString() => title;
}
class BitcoinSeedType extends EnumerableItem<int> with Serializable<int> {

View file

@ -252,11 +252,21 @@ class CWMonero extends Monero {
WalletCredentials createMoneroNewWalletCredentials({
required String name,
required String language,
required bool isPolyseed,
required int seedType,
required String? passphrase,
String? password}) =>
String? password,
String? mnemonic,
}) =>
MoneroNewWalletCredentials(
name: name, password: password, language: language, isPolyseed: isPolyseed, passphrase: passphrase);
name: name,
password: password,
language: language,
seedType: seedType == 1
? MoneroSeedType.polyseed
: (seedType == 3 ? MoneroSeedType.bip39 : MoneroSeedType.legacy),
passphrase: passphrase,
mnemonic: mnemonic,
);
@override
Map<String, String> getKeys(Object wallet) {

View file

@ -11,8 +11,8 @@ bool isBIP39Wallet(WalletType walletType) {
case WalletType.bitcoinCash:
case WalletType.nano:
case WalletType.banano:
return true;
case WalletType.monero:
return true;
case WalletType.wownero:
case WalletType.haven:
case WalletType.zano:

View file

@ -381,6 +381,7 @@ class CakePayBuyCardDetailPage extends BasePage {
return ConfirmSendingBottomSheet(
key: ValueKey('send_page_confirm_sending_dialog_key'),
currentTheme: currentTheme,
walletType: cakePayPurchaseViewModel.sendViewModel.walletType,
paymentId: S.of(popupContext).payment_id,
paymentIdValue: order?.orderId,
expirationTime: cakePayPurchaseViewModel.formattedRemainingTime,

View file

@ -12,9 +12,11 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
import 'package:cake_wallet/utils/address_formatter.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -235,7 +237,7 @@ class _ContactPageBodyState extends State<ContactPageBody> with SingleTickerProv
return;
}
final isCopied = await showNameAndAddressDialog(context, contact.name, contact.address);
final isCopied = await DialogService.showNameAndAddressDialog(context, contact);
if (isCopied) {
await Clipboard.setData(ClipboardData(text: contact.address));
@ -280,21 +282,6 @@ class _ContactPageBodyState extends State<ContactPageBody> with SingleTickerProv
? Image.asset(image, height: 24, width: 24)
: const SizedBox(height: 24, width: 24);
}
Future<bool> showNameAndAddressDialog(BuildContext context, String name, String address) async {
return await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: name,
alertContent: address,
rightButtonText: S.of(context).copy,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.of(context).pop(true),
actionLeftButton: () => Navigator.of(context).pop(false));
}) ??
false;
}
}
class ContactListBody extends StatefulWidget {
@ -357,7 +344,7 @@ class _ContactListBodyState extends State<ContactListBody> {
}
final isCopied =
await showNameAndAddressDialog(context, contact.name, contact.address);
await DialogService.showNameAndAddressDialog(context, contact);
if (isCopied) {
await Clipboard.setData(ClipboardData(text: contact.address));
@ -434,7 +421,7 @@ class _ContactListBodyState extends State<ContactListBody> {
),
SlidableAction(
onPressed: (_) async {
final isDelete = await showAlertDialog(context);
final isDelete = await DialogService.showAlertDialog(context);
if (isDelete) {
await widget.contactListViewModel.delete(contact);
@ -494,7 +481,10 @@ class _ContactListBodyState extends State<ContactListBody> {
);
}
Future<bool> showAlertDialog(BuildContext context) async {
}
class DialogService {
static Future<bool> showAlertDialog(BuildContext context) async {
return await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
@ -509,13 +499,25 @@ class _ContactListBodyState extends State<ContactListBody> {
false;
}
Future<bool> showNameAndAddressDialog(BuildContext context, String name, String address) async {
static Future<bool> showNameAndAddressDialog(BuildContext context,ContactBase contact) async {
return await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: name,
alertContent: address,
alertTitle: contact.name,
alertContent: contact.address,
alertContentTextWidget: AddressFormatter.buildSegmentedAddress(
address: contact.address,
textAlign: TextAlign.center,
walletType: cryptoCurrencyToWalletType(contact.type),
evenTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: 'Lato',
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
),
rightButtonText: S.of(context).copy,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.of(context).pop(true),
@ -524,3 +526,4 @@ class _ContactListBodyState extends State<ContactListBody> {
false;
}
}

View file

@ -207,15 +207,15 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
onPressed: () async {
if (_formKey.currentState!.validate() &&
(!_showDisclaimer || _disclaimerChecked)) {
final hasPotentialError = await widget.homeSettingsViewModel
final isWhitelisted = await widget.homeSettingsViewModel
.checkIfTokenIsWhitelisted(_contractAddressController.text);
final hasPotentialError = !isWhitelisted && await widget.homeSettingsViewModel
.checkIfERC20TokenContractAddressIsAPotentialScamAddress(
_contractAddressController.text,
);
final isWhitelisted = await widget.homeSettingsViewModel
.checkIfTokenIsWhitelisted(_contractAddressController.text);
bool isPotentialScam = hasPotentialError;
bool isPotentialScam = hasPotentialError && !isWhitelisted;
final tokenSymbol = _tokenSymbolController.text.toUpperCase();
// check if the token symbol is the same as any of the base currencies symbols (ETH, SOL, POL, TRX, etc):
@ -258,7 +258,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
}
};
if (hasPotentialError) {
if (hasPotentialError && !isWhitelisted) {
showPopUp<void>(
context: context,
builder: (dialogContext) {

View file

@ -264,6 +264,7 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
return ConfirmSendingBottomSheet(
key: ValueKey('exchange_trade_page_confirm_sending_bottom_sheet_key'),
currentTheme: widget.currentTheme,
walletType: widget.exchangeTradeViewModel.sendViewModel.walletType,
titleText: S.of(bottomSheetContext).confirm_transaction,
titleIconPath:
widget.exchangeTradeViewModel.sendViewModel.selectedCryptoCurrency.iconPath,

View file

@ -144,7 +144,9 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
),
);
}),
if (widget.privacySettingsViewModel.isMoneroSeedTypeOptionsEnabled)
if (widget
.privacySettingsViewModel.isMoneroSeedTypeOptionsEnabled &&
!widget.isChildWallet)
Observer(builder: (_) {
return SettingsChoicesCell(
ChoicesListItem<MoneroSeedType>(

View file

@ -21,6 +21,7 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
@ -102,8 +103,8 @@ class _WalletNameFormState extends State<WalletNameForm> {
_stateReaction ??= reaction((_) => _walletNewVM.state, (ExecutionState state) async {
if (state is ExecutedSuccessfullyState) {
if (widget.isChildWallet) {
Navigator.of(navigatorKey.currentContext ?? context)
.pushNamed(Routes.walletGroupExistingSeedDescriptionPage,
Navigator.of(navigatorKey.currentContext ?? context).pushNamed(
Routes.walletGroupExistingSeedDescriptionPage,
arguments: _walletNewVM.seedPhraseWordsLength);
} else {
Navigator.of(navigatorKey.currentContext ?? context)
@ -305,7 +306,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
),
),
),
if (_walletNewVM.hasLanguageSelector) ...[
if (_walletNewVM.showLanguageSelector) ...[
if (_walletNewVM.hasSeedType) ...[
Observer(
builder: (BuildContext build) => Padding(
@ -317,7 +318,11 @@ class _WalletNameFormState extends State<WalletNameForm> {
await showPopUp<void>(
context: context,
builder: (_) => Picker(
items: MoneroSeedType.all,
items: MoneroSeedType.all
.where((e) => // exclude bip39 in case of Wownero
widget._walletNewVM.type != WalletType.wownero ||
e.raw != MoneroSeedType.bip39.raw)
.toList(),
selectedAtIndex: isPolyseed ? 1 : 0,
onItemSelected: _setSeedType,
isSeparated: false,
@ -401,7 +406,10 @@ class _WalletNameFormState extends State<WalletNameForm> {
} else {
await _walletNewVM.create(
options: _walletNewVM.hasLanguageSelector
? [_languageSelectorKey.currentState!.selected, isPolyseed]
? [
_languageSelectorKey.currentState?.selected ?? defaultSeedLanguage,
widget._seedSettingsViewModel.moneroSeedType
]
: null);
}
} catch (e) {

View file

@ -20,6 +20,7 @@ class AddressListPage extends BasePage {
children: <Widget>[
AddressList(
addressListViewModel: addressListViewModel,
currentTheme: currentTheme,
onSelect: (String address) async {
Navigator.of(context).pop(address);
},

View file

@ -110,7 +110,7 @@ class ReceivePage extends BasePage {
isLight: currentTheme.type == ThemeType.light,
),
),
AddressList(addressListViewModel: addressListViewModel),
AddressList(addressListViewModel: addressListViewModel, currentTheme: currentTheme),
Padding(
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
child: Text(

View file

@ -1,7 +1,11 @@
import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/themes/extensions/qr_code_theme.dart';
import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/address_formatter.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:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
@ -9,12 +13,13 @@ import 'package:flutter_slidable/flutter_slidable.dart';
class AddressCell extends StatelessWidget {
AddressCell({
required this.address,
required this.derivationPath,
required this.name,
required this.isCurrent,
required this.isPrimary,
required this.backgroundColor,
required this.textColor,
required this.walletType,
required this.derivationPath,
this.onTap,
this.onEdit,
this.onHide,
@ -32,6 +37,8 @@ class AddressCell extends StatelessWidget {
required bool isCurrent,
required Color backgroundColor,
required Color textColor,
required WalletType walletType,
required ThemeBase currentTheme,
Function(String)? onTap,
bool hasBalance = false,
bool hasReceived = false,
@ -47,6 +54,7 @@ class AddressCell extends StatelessWidget {
isCurrent: isCurrent,
isPrimary: item.isPrimary,
backgroundColor: backgroundColor,
walletType: walletType,
textColor: textColor,
onTap: onTap,
onEdit: onEdit,
@ -67,6 +75,8 @@ class AddressCell extends StatelessWidget {
final bool isPrimary;
final Color backgroundColor;
final Color textColor;
final WalletType walletType;
final ThemeBase currentTheme;
final Function(String)? onTap;
final Function()? onEdit;
final Function()? onHide;
@ -78,21 +88,6 @@ class AddressCell extends StatelessWidget {
final bool hasBalance;
final bool hasReceived;
static const int addressPreviewLength = 8;
String get formattedAddress {
final formatIfCashAddr = address.replaceAll('bitcoincash:', '');
if (formatIfCashAddr.length <= (name.isNotEmpty ? 16 : 43)) {
return formatIfCashAddr;
} else {
return formatIfCashAddr.substring(0, addressPreviewLength) +
'...' +
formatIfCashAddr.substring(
formatIfCashAddr.length - addressPreviewLength, formatIfCashAddr.length);
}
}
@override
Widget build(BuildContext context) {
final Widget cell = InkWell(
@ -146,16 +141,12 @@ class AddressCell extends StatelessWidget {
],
),
Flexible(
child: AutoSizeText(
responsiveLayoutUtil.shouldRenderTabletUI ? address : formattedAddress,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: isChange ? 10 : 14,
color: textColor,
),
),
),
child: AddressFormatter.buildSegmentedAddress(
address: address,
walletType: walletType,
shouldTruncate: name.isNotEmpty || address.length > 43,
evenTextStyle:
TextStyle(fontSize: isChange ? 10 : 14, color: textColor))),
],
),
if (derivationPath.isNotEmpty)

View file

@ -7,6 +7,7 @@ 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/themes/theme_base.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';
@ -18,16 +19,17 @@ import 'package:cw_core/wallet_type.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class AddressList extends StatefulWidget {
const AddressList({
super.key,
required this.addressListViewModel,
required this.currentTheme,
this.onSelect,
});
final WalletAddressListViewModel addressListViewModel;
final ThemeBase currentTheme;
final Function(String)? onSelect;
@override
@ -163,6 +165,8 @@ class _AddressListState extends State<AddressList> {
return AddressCell.fromItem(
item,
isCurrent: isCurrent,
currentTheme: widget.currentTheme,
walletType: widget.addressListViewModel.type,
hasBalance: widget.addressListViewModel.isBalanceAvailable,
hasReceived: widget.addressListViewModel.isReceivedAvailable,
// hasReceived:

View file

@ -5,6 +5,7 @@ import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/address_formatter.dart';
import 'package:cake_wallet/utils/brightness_util.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_bar.dart';
@ -160,16 +161,15 @@ class QRWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Text(
addressUri.address,
child: AddressFormatter.buildSegmentedAddress(
address: addressUri.address,
walletType: addressListViewModel.type,
textAlign: TextAlign.center,
style: TextStyle(
evenTextStyle: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
color:
Theme.of(context).extension<DashboardPageTheme>()!.textColor),
),
),
Theme.of(context).extension<DashboardPageTheme>()!.textColor))),
Padding(
padding: EdgeInsets.only(left: 12),
child: copyImage,

View file

@ -11,13 +11,15 @@ import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
import 'package:cake_wallet/utils/show_pop_up.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/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
import 'package:polyseed/polyseed.dart';
class WalletRestoreFromSeedForm extends StatefulWidget {
WalletRestoreFromSeedForm({Key? key,
WalletRestoreFromSeedForm({
Key? key,
required this.displayLanguageSelector,
required this.displayBlockHeightSelector,
required this.type,
@ -47,20 +49,22 @@ class WalletRestoreFromSeedForm extends StatefulWidget {
@override
WalletRestoreFromSeedFormState createState() =>
WalletRestoreFromSeedFormState('English', displayWalletPassword: displayWalletPassword);
WalletRestoreFromSeedFormState('English',
displayWalletPassword: displayWalletPassword);
}
class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
WalletRestoreFromSeedFormState(this.language, {required bool displayWalletPassword})
WalletRestoreFromSeedFormState(this.language,
{required bool displayWalletPassword})
: seedWidgetStateKey = GlobalKey<SeedWidgetState>(),
blockchainHeightKey = GlobalKey<BlockchainHeightState>(),
formKey = GlobalKey<FormState>(),
languageController = TextEditingController(),
nameTextEditingController = TextEditingController(),
passwordTextEditingController = displayWalletPassword ? TextEditingController() : null,
repeatedPasswordTextEditingController = displayWalletPassword
? TextEditingController()
: null,
passwordTextEditingController =
displayWalletPassword ? TextEditingController() : null,
repeatedPasswordTextEditingController =
displayWalletPassword ? TextEditingController() : null,
seedTypeController = TextEditingController();
final GlobalKey<SeedWidgetState> seedWidgetStateKey;
@ -83,18 +87,21 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
_setLanguageLabel(language);
if (passwordTextEditingController != null) {
passwordListener = () => widget.onPasswordChange?.call(passwordTextEditingController!.text);
passwordListener = () =>
widget.onPasswordChange?.call(passwordTextEditingController!.text);
passwordTextEditingController?.addListener(passwordListener!);
}
if (repeatedPasswordTextEditingController != null) {
repeatedPasswordListener =
() => widget.onRepeatedPasswordChange?.call(repeatedPasswordTextEditingController!.text);
repeatedPasswordTextEditingController?.addListener(repeatedPasswordListener!);
repeatedPasswordListener = () => widget.onRepeatedPasswordChange
?.call(repeatedPasswordTextEditingController!.text);
repeatedPasswordTextEditingController
?.addListener(repeatedPasswordListener!);
}
moneroSeedTypeReaction =
reaction((_) => widget.seedSettingsViewModel.moneroSeedType, (MoneroSeedType item) {
reaction((_) => widget.seedSettingsViewModel.moneroSeedType,
(MoneroSeedType item) {
_setSeedType(item);
_changeLanguage('English');
});
@ -118,16 +125,22 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
}
void onSeedChange(String seed) {
if ((widget.type == WalletType.monero || widget.type == WalletType.wownero) &&
Polyseed.isValidSeed(seed)) {
if ([WalletType.monero, WalletType.wownero].contains(widget.type) &&
(seed.split(" ").length == 12 || Polyseed.isValidSeed(seed))) {
try {
final lang = PolyseedLang.getByPhrase(seed);
if (widget.type == WalletType.monero && seed.split(" ").length == 12) {
_changeSeedType(MoneroSeedType.bip39);
} else {
_changeSeedType(MoneroSeedType.polyseed);
_changeLanguage(lang.nameEnglish);
}
if (widget.type == WalletType.wownero && seed
.split(" ")
.length == 14) {
_changeLanguage(lang.nameEnglish, true);
} catch (e) {
printV(e);
}
}
if (widget.type == WalletType.wownero && seed.split(" ").length == 14) {
_changeSeedType(MoneroSeedType.wowneroSeed);
_changeLanguage("English");
}
@ -147,9 +160,7 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
BaseTextFormField(
key: ValueKey('wallet_restore_from_seed_wallet_name_textfield_key'),
controller: nameTextEditingController,
hintText: S
.of(context)
.wallet_name,
hintText: S.of(context).wallet_name,
suffixIcon: IconButton(
key: ValueKey('wallet_restore_from_seed_wallet_name_refresh_button_key'),
onPressed: () async {
@ -158,17 +169,17 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
setState(() {
nameTextEditingController.text = rName;
nameTextEditingController.selection = TextSelection.fromPosition(
TextPosition(offset: nameTextEditingController.text.length));
nameTextEditingController.selection =
TextSelection.fromPosition(TextPosition(
offset:
nameTextEditingController.text.length));
});
},
icon: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
color: Theme
.of(context)
.hintColor,
color: Theme.of(context).hintColor,
),
width: 34,
height: 34,
@ -194,14 +205,13 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
seedTextFieldKey: ValueKey('wallet_restore_from_seed_wallet_seeds_textfield_key'),
pasteButtonKey: ValueKey('wallet_restore_from_seed_wallet_seeds_paste_button_key'),
),
if (widget.type == WalletType.monero || widget.type == WalletType.wownero)
if ([WalletType.monero, WalletType.wownero].contains(widget.type))
GestureDetector(
key: ValueKey('wallet_restore_from_seed_seedtype_picker_button_key'),
onTap: () async {
await showPopUp<void>(
context: context,
builder: (_) =>
Picker(
builder: (_) => Picker(
items: _getItems(),
selectedAtIndex: isPolyseed
? 1
@ -226,20 +236,16 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
),
),
),
if (widget.displayWalletPassword)
...[BaseTextFormField(
if (widget.displayWalletPassword) ...[
BaseTextFormField(
key: ValueKey('password'),
controller: passwordTextEditingController,
hintText: S
.of(context)
.password,
hintText: S.of(context).password,
obscureText: true),
BaseTextFormField(
key: ValueKey('repeat_wallet_password'),
controller: repeatedPasswordTextEditingController,
hintText: S
.of(context)
.repeat_wallet_password,
hintText: S.of(context).repeat_wallet_password,
obscureText: true)
],
if (widget.displayLanguageSelector)
@ -248,11 +254,12 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
onTap: () async {
await showPopUp<void>(
context: context,
builder: (_) =>
SeedLanguagePicker(
builder: (_) => SeedLanguagePicker(
selected: language,
onItemSelected: _changeLanguage,
seedType: isPolyseed ? MoneroSeedType.polyseed : MoneroSeedType.legacy,
onItemSelected: (lang) =>
_changeLanguage(lang, isPolyseed || isBip39),
seedType:
widget.seedSettingsViewModel.moneroSeedType,
));
},
child: Container(
@ -274,7 +281,8 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
key: blockchainHeightKey,
blockHeightTextFieldKey: ValueKey('wallet_restore_from_seed_blockheight_textfield_key'),
onHeightOrDateEntered: widget.onHeightOrDateEntered,
hasDatePicker: widget.type == WalletType.monero || widget.type == WalletType.wownero,
hasDatePicker:
[WalletType.monero, WalletType.wownero].contains(widget.type),
walletType: widget.type,
),
]));
@ -282,24 +290,25 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
bool get isPolyseed =>
widget.seedSettingsViewModel.moneroSeedType == MoneroSeedType.polyseed &&
(widget.type == WalletType.monero || widget.type == WalletType.wownero);
[WalletType.monero, WalletType.wownero].contains(widget.type);
Widget get expandIcon =>
Container(
bool get isBip39 =>
widget.seedSettingsViewModel.moneroSeedType == MoneroSeedType.bip39 &&
WalletType.monero == widget.type;
Widget get expandIcon => Container(
padding: EdgeInsets.all(18),
width: 24,
height: 24,
child: Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
height: 8,
color: Theme
.of(context)
.hintColor,
color: Theme.of(context).hintColor,
),
);
void _changeLanguage(String language) {
final setLang = isPolyseed
void _changeLanguage(String language, [bool useBip39Wordlist = false]) {
final setLang = useBip39Wordlist
? "POLYSEED_$language"
: seedTypeController.value.text.contains("14")
? "WOWSEED_" + language
@ -312,8 +321,8 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
});
}
void _setLanguageLabel(String language) =>
languageController.text = '${language.replaceAll("POLYSEED_", "")} (Seed language)';
void _setLanguageLabel(String language) => languageController.text =
'${language.replaceAll("POLYSEED_", "")} (Seed language)';
void _changeSeedType(MoneroSeedType item) {
_setSeedType(item);
@ -328,9 +337,17 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
List<MoneroSeedType> _getItems() {
switch (widget.type) {
case WalletType.monero:
return [MoneroSeedType.legacy, MoneroSeedType.polyseed];
return [
MoneroSeedType.legacy,
MoneroSeedType.polyseed,
MoneroSeedType.bip39
];
case WalletType.wownero:
return [MoneroSeedType.legacy, MoneroSeedType.polyseed, MoneroSeedType.wowneroSeed];
return [
MoneroSeedType.legacy,
MoneroSeedType.polyseed,
MoneroSeedType.wowneroSeed
];
default:
return [MoneroSeedType.legacy];
}

View file

@ -531,25 +531,42 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
}
bool _isValidSeed() {
final seedPhrase =
walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.text;
final seedPhrase = walletRestoreFromSeedFormKey
.currentState!.seedWidgetStateKey.currentState!.text;
if (walletRestoreViewModel.isPolyseed(seedPhrase)) return true;
final seedWords = seedPhrase.split(' ');
if (seedWords.length == 14 && walletRestoreViewModel.type == WalletType.wownero) return true;
if (seedWords.length == 26 && walletRestoreViewModel.type == WalletType.zano) return true;
if (seedWords.length == 14 &&
walletRestoreViewModel.type == WalletType.wownero) return true;
if (seedWords.length == 26 &&
walletRestoreViewModel.type == WalletType.zano) return true;
if ((walletRestoreViewModel.type == WalletType.monero ||
walletRestoreViewModel.type == WalletType.wownero ||
walletRestoreViewModel.type == WalletType.haven) &&
seedWords.length != WalletRestoreViewModelBase.moneroSeedMnemonicLength) {
return false;
if (seedWords.length == 12 &&
walletRestoreViewModel.type == WalletType.monero) {
return walletRestoreFromSeedFormKey
.currentState
?.blockchainHeightKey
.currentState
?.restoreHeightController
.text
.isNotEmpty == true;
}
if ([WalletType.monero, WalletType.wownero, WalletType.haven]
.contains(walletRestoreViewModel.type) &&
seedWords.length ==
WalletRestoreViewModelBase.moneroSeedMnemonicLength) {
return true;
}
// bip39:
final validBip39SeedLengths = [12, 18, 24];
final nonBip39WalletTypes = [WalletType.monero, WalletType.wownero, WalletType.haven, WalletType.decred];
final nonBip39WalletTypes = [
WalletType.wownero,
WalletType.haven,
WalletType.decred
];
// if it's a bip39 wallet and the length is not valid return false
if (!nonBip39WalletTypes.contains(walletRestoreViewModel.type) &&
!(validBip39SeedLengths.contains(seedWords.length))) {
@ -562,8 +579,9 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
return false;
}
final words =
walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.words.toSet();
final words = walletRestoreFromSeedFormKey
.currentState!.seedWidgetStateKey.currentState!.words
.toSet();
return seedWords.toSet().difference(words).toSet().isEmpty;
}

View file

@ -547,9 +547,9 @@ class SendPage extends BasePage {
}
if (state is ExecutedSuccessfullyState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
if (context.mounted) {
showModalBottomSheet<void>(
final result = await showModalBottomSheet<bool>(
context: context,
isDismissible: false,
isScrollControlled: true,
@ -558,6 +558,7 @@ class SendPage extends BasePage {
key: ValueKey('send_page_confirm_sending_dialog_key'),
titleText: S.of(bottomSheetContext).confirm_transaction,
currentTheme: currentTheme,
walletType: sendViewModel.walletType,
titleIconPath: sendViewModel.selectedCryptoCurrency.iconPath,
currency: sendViewModel.selectedCryptoCurrency,
amount: S.of(bottomSheetContext).send_amount,
@ -570,13 +571,16 @@ class SendPage extends BasePage {
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmountFormatted,
outputs: sendViewModel.outputs,
onSlideComplete: () async {
Navigator.of(bottomSheetContext).pop();
Navigator.of(bottomSheetContext).pop(true);
sendViewModel.commitTransaction(context);
},
change: sendViewModel.pendingTransaction!.change,
isOpenCryptoPay: sendViewModel.ocpRequest != null,
);
},
);
if (result == null) sendViewModel.dismissTransaction();
}
});
}
@ -600,7 +604,8 @@ class SendPage extends BasePage {
context: context,
isDismissible: false,
builder: (BuildContext bottomSheetContext) {
return showContactSheet
return showContactSheet &&
sendViewModel.ocpRequest == null
? InfoBottomSheet(
currentTheme: currentTheme,
showDontAskMeCheckbox: true,

View file

@ -1,9 +1,12 @@
import 'package:cake_wallet/core/open_crypto_pay/open_cryptopay_service.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/src/screens/receive/widgets/currency_input_field.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cw_core/crypto_currency.dart';
@ -23,9 +26,6 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
import '../../../../themes/extensions/cake_text_theme.dart';
import '../../../../themes/theme_base.dart';
class SendCard extends StatefulWidget {
SendCard({
Key? key,
@ -181,11 +181,16 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
addressKey: ValueKey('send_page_address_textfield_key'),
focusNode: addressFocusNode,
controller: addressController,
onURIScanned: (uri) {
onURIScanned: (uri) async {
if (OpenCryptoPayService.isOpenCryptoPayQR(
uri.toString())) {
sendViewModel.createOpenCryptoPayTransaction(uri.toString());
} else {
final paymentRequest = PaymentRequest.fromUri(uri);
addressController.text = paymentRequest.address;
cryptoAmountController.text = paymentRequest.amount;
noteController.text = paymentRequest.note;
}
},
options: [
AddressTextFieldOption.paste,

View file

@ -190,6 +190,7 @@ class RBFDetailsPage extends BasePage {
key: ValueKey('rbf_confirm_sending_bottom_sheet'),
titleText: S.of(bottomSheetContext).confirm_transaction,
currentTheme: currentTheme,
walletType: transactionDetailsViewModel.sendViewModel.walletType,
titleIconPath: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency.iconPath,
currency: transactionDetailsViewModel.sendViewModel.selectedCryptoCurrency,
amount: S.of(bottomSheetContext).send_amount,

View file

@ -9,8 +9,11 @@ import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.
import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
import 'package:cake_wallet/src/widgets/list_row.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/utils/address_formatter.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -39,13 +42,28 @@ class TransactionDetailsPage extends BasePage {
final item = transactionDetailsViewModel.items[index];
if (item is StandartListItem) {
Widget? addressTextWidget;
if (item.title.toLowerCase() == 'recipient addresses' ||
item.title.toLowerCase() == 'source address') {
addressTextWidget = getFormattedAddress(
context: context,
value: item.value,
walletType: transactionDetailsViewModel.sendViewModel.walletType,
);
}
return GestureDetector(
key: item.key,
onTap: () {
Clipboard.setData(ClipboardData(text: item.value));
showBar<void>(context, S.of(context).transaction_details_copied(item.title));
},
child: ListRow(title: '${item.title}:', value: item.value),
child: ListRow(
title: '${item.title}:',
value: item.value,
textWidget: addressTextWidget,
),
);
}
@ -91,4 +109,80 @@ class TransactionDetailsPage extends BasePage {
],
);
}
Widget getFormattedAddress({
required BuildContext context,
required String value,
required WalletType walletType,
}) {
final textStyle = TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
);
final List<Widget> children = [];
final bool hasDoubleNewline = value.contains('\n\n');
if (hasDoubleNewline) {
final blocks = value
.split('\n\n')
.map((b) => b.trim())
.where((b) => b.isNotEmpty)
.toList();
for (final block in blocks) {
final lines = block
.split('\n')
.map((l) => l.trim())
.where((l) => l.isNotEmpty)
.toList();
if (lines.length > 1) {
children.add(Text(lines.first, style: textStyle));
for (int i = 1; i < lines.length; i++) {
children.add(
AddressFormatter.buildSegmentedAddress(
address: lines[i],
walletType: walletType,
evenTextStyle: textStyle,
),
);
}
} else {
children.add(
AddressFormatter.buildSegmentedAddress(
address: lines.first,
walletType: walletType,
evenTextStyle: textStyle,
),
);
}
children.add(SizedBox(height: 8));
}
} else {
final lines = value
.split('\n')
.map((l) => l.trim())
.where((l) => l.isNotEmpty)
.toList();
bool firstLineIsContactName = (lines.length > 1 && lines.first.length < 20);
int startIndex = 0;
if (firstLineIsContactName) {
children.add(Text(lines.first, style: textStyle));
startIndex = 1;
}
for (int i = startIndex; i < lines.length; i++) {
children.add(
AddressFormatter.buildSegmentedAddress(
address: lines[i],
walletType: walletType,
evenTextStyle: textStyle,
),
);
}
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: children,
);
}
}

View file

@ -6,6 +6,7 @@ class AlertWithTwoActions extends BaseAlertDialog {
AlertWithTwoActions({
required this.alertTitle,
required this.alertContent,
this.alertContentTextWidget,
required this.leftButtonText,
required this.rightButtonText,
required this.actionLeftButton,
@ -21,6 +22,7 @@ class AlertWithTwoActions extends BaseAlertDialog {
final String alertTitle;
final String alertContent;
final Widget? alertContentTextWidget;
final String leftButtonText;
final String rightButtonText;
final VoidCallback actionLeftButton;
@ -38,6 +40,8 @@ class AlertWithTwoActions extends BaseAlertDialog {
@override
String get contentText => alertContent;
@override
Widget? get contentTextWidget => alertContentTextWidget;
@override
String get leftActionButtonText => leftButtonText;
@override
String get rightActionButtonText => rightButtonText;

View file

@ -13,6 +13,8 @@ class BaseAlertDialog extends StatelessWidget {
String get contentText => '';
Widget? get contentTextWidget => null;
String get leftActionButtonText => '';
String get rightActionButtonText => '';
@ -79,6 +81,7 @@ class BaseAlertDialog extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
contentTextWidget ??
Text(
contentText,
textAlign: TextAlign.center,

View file

@ -33,7 +33,7 @@ abstract class BaseBottomSheet extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (titleIconPath != null)
Image.asset(titleIconPath!, height: 24, width: 24)
Image.asset(titleIconPath!, height: 24, width: 24, excludeFromSemantics: true)
else
Container(),
const SizedBox(width: 6),

View file

@ -5,11 +5,12 @@ import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/extensions/filter_theme.dart';
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/address_formatter.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'dart:math' as math;
import 'base_bottom_sheet_widget.dart';
@ -27,7 +28,9 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
final String feeFiatAmount;
final List<Output> outputs;
final VoidCallback onSlideComplete;
final WalletType walletType;
final PendingChange? change;
final bool isOpenCryptoPay;
ConfirmSendingBottomSheet({
required String titleText,
@ -45,7 +48,9 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
required this.feeFiatAmount,
required this.outputs,
required this.onSlideComplete,
required this.walletType,
this.change,
this.isOpenCryptoPay = false,
Key? key,
}) : showScrollbar = outputs.length > 3,
super(titleText: titleText, titleIconPath: titleIconPath);
@ -89,6 +94,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
itemTitle: paymentId!,
currentTheme: currentTheme,
itemTitleTextStyle: itemTitleTextStyle,
walletType: walletType,
isBatchSending: false,
amount: '',
address: paymentIdValue!,
@ -132,11 +138,12 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
final _amount = item.cryptoAmount.replaceAll(',', '.') + ' ${currency.title}';
return isBatchSending || contactName.isNotEmpty
? AddressExpansionTile(
contactType: 'Contact',
contactType: isOpenCryptoPay ? 'Open CryptoPay' : S.of(context).contact,
currentTheme: currentTheme,
name: isBatchSending ? batchContactTitle : contactName,
address: _address,
amount: _amount,
walletType: walletType,
isBatchSending: isBatchSending,
itemTitleTextStyle: itemTitleTextStyle,
itemSubTitleTextStyle: itemSubTitleTextStyle,
@ -144,10 +151,11 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
stealthAddress: item.stealthAddress,
)
: AddressTile(
itemTitle: 'Address',
itemTitle: S.of(context).address,
currentTheme: currentTheme,
itemTitleTextStyle: itemTitleTextStyle,
isBatchSending: isBatchSending,
walletType: walletType,
amount: _amount,
address: _address,
itemSubTitleTextStyle: itemSubTitleTextStyle,
@ -165,6 +173,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
address: change!.address,
amount: change!.amount + ' ${currency.title}',
isBatchSending: true,
walletType: walletType,
itemTitleTextStyle: itemTitleTextStyle,
itemSubTitleTextStyle: itemSubTitleTextStyle,
tileBackgroundColor: tileBackgroundColor,
@ -215,6 +224,7 @@ class ConfirmSendingBottomSheet extends BaseBottomSheet {
onSlideComplete: onSlideComplete,
buttonText: 'Swipe to send',
currentTheme: currentTheme,
accessibleNavigationModeButtonText: S.of(context).send,
),
);
}
@ -240,7 +250,10 @@ class StandardTile extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
return Semantics(
container: true,
label: itemTitle,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
decoration:
BoxDecoration(borderRadius: BorderRadius.circular(10), color: tileBackgroundColor),
@ -259,6 +272,7 @@ class StandardTile extends StatelessWidget {
),
],
),
),
);
}
}
@ -274,6 +288,7 @@ class AddressTile extends StatelessWidget {
required this.address,
required this.itemSubTitleTextStyle,
required this.tileBackgroundColor,
required this.walletType,
});
final String itemTitle;
@ -284,18 +299,10 @@ class AddressTile extends StatelessWidget {
final String address;
final TextStyle itemSubTitleTextStyle;
final Color tileBackgroundColor;
final WalletType walletType;
@override
Widget build(BuildContext context) {
final addressTextStyle = TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color: currentTheme.type == ThemeType.bright
? Theme.of(context).extension<CakeTextTheme>()!.titleColor.withOpacity(0.5)
: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
);
return Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8),
decoration: BoxDecoration(
@ -312,41 +319,19 @@ class AddressTile extends StatelessWidget {
if (isBatchSending) Text(amount, style: itemTitleTextStyle),
],
),
buildSegmentedAddress(
AddressFormatter.buildSegmentedAddress(
address: address,
evenTextStyle: addressTextStyle,
oddTextStyle: itemSubTitleTextStyle,
),
walletType: walletType,
evenTextStyle: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none)),
],
),
);
}
Widget buildSegmentedAddress({
required String address,
int chunkSize = 6,
required TextStyle evenTextStyle,
required TextStyle oddTextStyle,
}) {
final spans = <TextSpan>[];
int index = 0;
for (int i = 0; i < address.length; i += chunkSize) {
final chunk = address.substring(i, math.min(i + chunkSize, address.length));
final style = (index % 2 == 0) ? evenTextStyle : oddTextStyle;
spans.add(
TextSpan(text: '$chunk ', style: style),
);
index++;
}
return RichText(
text: TextSpan(children: spans, style: evenTextStyle),
overflow: TextOverflow.visible,
);
}
}
class AddressExpansionTile extends StatelessWidget {
@ -361,6 +346,7 @@ class AddressExpansionTile extends StatelessWidget {
required this.itemTitleTextStyle,
required this.itemSubTitleTextStyle,
required this.tileBackgroundColor,
required this.walletType,
this.stealthAddress,
});
@ -373,21 +359,15 @@ class AddressExpansionTile extends StatelessWidget {
final TextStyle itemTitleTextStyle;
final TextStyle itemSubTitleTextStyle;
final Color tileBackgroundColor;
final WalletType walletType;
final String? stealthAddress;
@override
Widget build(BuildContext context) {
final addressTextStyle = TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color: currentTheme.type == ThemeType.bright
? Theme.of(context).extension<CakeTextTheme>()!.titleColor.withOpacity(0.5)
: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
);
return Container(
return Semantics(
container: true,
label: name,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(10)),
color: tileBackgroundColor,
@ -403,22 +383,6 @@ class AddressExpansionTile extends StatelessWidget {
visualDensity: VisualDensity.compact,
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(isBatchSending ? name : contactType,
style: itemTitleTextStyle, softWrap: true)),
Text(isBatchSending ? amount : name,
style: TextStyle(
fontSize: 14,
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
)),
],
),
children: [
Row(
children: [
Expanded(
child: Column(
@ -443,36 +407,27 @@ class AddressExpansionTile extends StatelessWidget {
),
],
),
children: [
Row(
children: [
Expanded(
child: AddressFormatter.buildSegmentedAddress(
address: address,
walletType: walletType,
evenTextStyle: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none)),
),
],
),
],
),
),
),
);
}
Widget buildSegmentedAddress({
required String address,
int chunkSize = 6,
required TextStyle evenTextStyle,
required TextStyle oddTextStyle,
}) {
final spans = <TextSpan>[];
int index = 0;
for (int i = 0; i < address.length; i += chunkSize) {
final chunk = address.substring(i, math.min(i + chunkSize, address.length));
final style = (index % 2 == 0) ? evenTextStyle : oddTextStyle;
spans.add(
TextSpan(text: '$chunk ', style: style),
);
index++;
}
return RichText(
text: TextSpan(children: spans, style: evenTextStyle),
overflow: TextOverflow.visible,
),
);
}
}

View file

@ -12,7 +12,8 @@ class ListRow extends StatelessWidget {
this.padding,
this.color,
this.hintTextColor,
this.mainTextColor
this.mainTextColor,
this.textWidget
});
final String title;
@ -24,6 +25,16 @@ class ListRow extends StatelessWidget {
final Color? color;
final Color? hintTextColor;
final Color? mainTextColor;
final Widget? textWidget;
Widget _getTextWidget (BuildContext context) => textWidget ?? Text(
value,
style: TextStyle(
fontSize: valueFontSize,
fontWeight: FontWeight.w500,
color: mainTextColor ?? Theme.of(context).extension<CakeTextTheme>()!.titleColor
),
);
@override
Widget build(BuildContext context) {
@ -49,12 +60,7 @@ class ListRow extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Text(value,
style: TextStyle(
fontSize: valueFontSize,
fontWeight: FontWeight.w500,
color: mainTextColor ?? Theme.of(context).extension<CakeTextTheme>()!.titleColor)),
),
child: _getTextWidget(context)),
image != null
? Padding(
padding: EdgeInsets.only(left: 24),

View file

@ -16,7 +16,7 @@ class SeedLanguagePickerOption {
final List<SeedLanguagePickerOption> seedLanguages = [
SeedLanguagePickerOption('English', S.current.seed_language_english,
Image.asset('assets/images/flags/usa.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed]),
Image.asset('assets/images/flags/usa.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed, MoneroSeedType.bip39]),
SeedLanguagePickerOption('Chinese (Simplified)', S.current.seed_language_chinese,
Image.asset('assets/images/flags/chn.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed]),
SeedLanguagePickerOption('Chinese (Traditional)', S.current.seed_language_chinese_traditional,

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/extensions/filter_theme.dart';
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
@ -12,12 +13,14 @@ class StandardSlideButton extends StatefulWidget {
this.buttonText = '',
this.height = 48.0,
required this.currentTheme,
required this.accessibleNavigationModeButtonText,
}) : super(key: key);
final VoidCallback onSlideComplete;
final String buttonText;
final double height;
final ThemeBase currentTheme;
final String accessibleNavigationModeButtonText;
@override
_StandardSlideButtonState createState() => _StandardSlideButtonState();
@ -28,11 +31,7 @@ class _StandardSlideButtonState extends State<StandardSlideButton> {
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
final double maxWidth = constraints.maxWidth;
const double sideMargin = 4.0;
final double effectiveMaxWidth = maxWidth - 2 * sideMargin;
const double sliderWidth = 42.0;
final bool accessible = MediaQuery.of(context).accessibleNavigation;
final tileBackgroundColor = widget.currentTheme.type == ThemeType.light
? Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor
@ -40,11 +39,22 @@ class _StandardSlideButtonState extends State<StandardSlideButton> {
? Colors.black.withOpacity(0.5)
: Theme.of(context).extension<FilterTheme>()!.buttonColor;
return accessible
? PrimaryButton(
text: widget.accessibleNavigationModeButtonText,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
onPressed: () => widget.onSlideComplete())
: LayoutBuilder(builder: (context, constraints) {
final double maxWidth = constraints.maxWidth;
const double sideMargin = 4.0;
final double effectiveMaxWidth = maxWidth - 2 * sideMargin;
const double sliderWidth = 42.0;
return Container(
height: widget.height,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: tileBackgroundColor),
borderRadius: BorderRadius.circular(10), color: tileBackgroundColor),
child: Stack(
alignment: Alignment.centerLeft,
children: [
@ -83,7 +93,9 @@ class _StandardSlideButtonState extends State<StandardSlideButton> {
),
alignment: Alignment.center,
child: Icon(Icons.arrow_forward,
color: widget.currentTheme.type == ThemeType.bright ? Theme.of(context).extension<CakeMenuTheme>()!.backgroundColor : Theme.of(context).extension<FilterTheme>()!.buttonColor),
color: widget.currentTheme.type == ThemeType.bright
? Theme.of(context).extension<CakeMenuTheme>()!.backgroundColor
: Theme.of(context).extension<FilterTheme>()!.buttonColor),
),
),
)

View file

@ -0,0 +1,161 @@
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/widgets.dart';
import 'dart:math' as math;
class AddressFormatter {
static Widget buildSegmentedAddress({
required String address,
WalletType? walletType,
required TextStyle evenTextStyle,
TextStyle? oddTextStyle,
TextAlign? textAlign,
bool shouldTruncate = false,
}) {
final cleanAddress = address.replaceAll('bitcoincash:', '');
final isMWEB = address.startsWith('ltcmweb');
final chunkSize = walletType != null ? _getChunkSize(walletType) : 4;
if (shouldTruncate) {
return _buildTruncatedAddress(
address: cleanAddress,
isMWEB: isMWEB,
chunkSize: chunkSize,
evenTextStyle: evenTextStyle,
oddTextStyle: oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(150)),
textAlign: textAlign,
);
} else {
return _buildFullSegmentedAddress(
address: cleanAddress,
isMWEB: isMWEB,
chunkSize: chunkSize,
evenTextStyle: evenTextStyle,
oddTextStyle: oddTextStyle ?? evenTextStyle.copyWith(color: evenTextStyle.color!.withAlpha(128)),
textAlign: textAlign,
);
}
}
static Widget _buildFullSegmentedAddress({
required String address,
required bool isMWEB,
required int chunkSize,
required TextStyle evenTextStyle,
required TextStyle oddTextStyle,
TextAlign? textAlign,
}) {
final chunks = <String>[];
if (isMWEB) {
const mwebDisplayPrefix = 'ltcmweb';
chunks.add(mwebDisplayPrefix);
final startIndex = mwebDisplayPrefix.length;
for (int i = startIndex; i < address.length; i += chunkSize) {
final chunk = address.substring(
i,
math.min(i + chunkSize, address.length),
);
chunks.add(chunk);
}
} else {
for (int i = 0; i < address.length; i += chunkSize) {
final chunk = address.substring(
i,
math.min(i + chunkSize, address.length),
);
chunks.add(chunk);
}
}
final spans = <TextSpan>[];
for (int i = 0; i < chunks.length; i++) {
final style = (i % 2 == 0) ? evenTextStyle : oddTextStyle;
spans.add(TextSpan(text: '${chunks[i]} ', style: style));
}
return RichText(
text: TextSpan(children: spans),
textAlign: textAlign ?? TextAlign.start,
overflow: TextOverflow.visible,
);
}
static Widget _buildTruncatedAddress({
required String address,
required bool isMWEB,
required int chunkSize,
required TextStyle evenTextStyle,
required TextStyle oddTextStyle,
TextAlign? textAlign,
}) {
if (isMWEB) {
const fixedPrefix = 'ltcmweb';
final secondChunkStart = fixedPrefix.length;
const chunkSize = 4;
final secondChunk = address.substring(
secondChunkStart,
math.min(secondChunkStart + chunkSize, address.length),
);
final lastChunk = address.substring(address.length - chunkSize);
final spans = <TextSpan>[
TextSpan(text: '$fixedPrefix ', style: evenTextStyle),
TextSpan(text: '$secondChunk ', style: oddTextStyle),
TextSpan(text: '... ', style: oddTextStyle),
TextSpan(text: lastChunk, style: evenTextStyle),
];
return RichText(
text: TextSpan(children: spans),
textAlign: textAlign ?? TextAlign.start,
overflow: TextOverflow.visible,
);
} else {
final int digitCount = chunkSize;
if (address.length <= 2 * digitCount) {
return _buildFullSegmentedAddress(
address: address,
isMWEB: isMWEB,
chunkSize: chunkSize,
evenTextStyle: evenTextStyle,
oddTextStyle: oddTextStyle,
textAlign: textAlign,
);
}
final String firstPart = address.substring(0, digitCount);
final String secondPart =
address.substring(digitCount, digitCount * 2);
final String lastPart =
address.substring(address.length - digitCount);
final spans = <TextSpan>[
TextSpan(text: '$firstPart ', style: evenTextStyle),
TextSpan(text: '$secondPart ', style: oddTextStyle),
TextSpan(text: '... ', style: oddTextStyle),
TextSpan(text: lastPart, style: evenTextStyle),
];
return RichText(
text: TextSpan(children: spans),
textAlign: textAlign ?? TextAlign.start,
overflow: TextOverflow.visible,
);
}
}
static int _getChunkSize(WalletType walletType) {
switch (walletType) {
case WalletType.monero:
case WalletType.wownero:
case WalletType.zano:
return 6;
default:
return 4;
}
}
}

View file

@ -221,7 +221,8 @@ abstract class HomeSettingsViewModelBase with Store {
}
// check if the contractAddress is in the defaultTokenAddresses
bool isInWhitelist = defaultTokenAddresses.any((element) => element == contractAddress);
bool isInWhitelist = defaultTokenAddresses
.any((element) => element.toLowerCase() == contractAddress.toLowerCase());
return isInWhitelist;
}

View file

@ -1,53 +1,57 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/core/amount_validator.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/core/open_crypto_pay/models.dart';
import 'package:cake_wallet/core/open_crypto_pay/open_cryptopay_service.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
import 'package:cake_wallet/entities/contact.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/evm_transaction_error_fees_handler.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/parsed_address.dart';
import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cake_wallet/view_model/send/fees_view_model.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/zano/zano.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/exceptions.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/core/amount_validator.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
import 'package:cake_wallet/entities/parsed_address.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/generated/i18n.dart';
part 'send_view_model.g.dart';
@ -219,7 +223,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
String get balance {
if (walletType == WalletType.litecoin && coinTypeToSpendFrom == UnspentCoinType.mweb) {
return balanceViewModel.balances.values.first.secondAvailableBalance;
} else if (walletType == WalletType.litecoin && coinTypeToSpendFrom == UnspentCoinType.nonMweb) {
} else if (walletType == WalletType.litecoin &&
coinTypeToSpendFrom == UnspentCoinType.nonMweb) {
return balanceViewModel.balances.values.first.availableBalance;
}
return wallet.balance[selectedCryptoCurrency]!.formattedFullAvailableBalance;
@ -390,13 +395,57 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
return conditionsList.contains(true);
}
final _ocpService = OpenCryptoPayService();
@observable
OpenCryptoPayRequest? ocpRequest;
@action
Future<void> dismissTransaction() async {
state = InitialExecutionState();
if (ocpRequest != null) {
clearOutputs();
_ocpService.cancelOpenCryptoPayRequest(ocpRequest!);
ocpRequest = null;
}
}
@action
Future<PendingTransaction?> createOpenCryptoPayTransaction(String uri) async {
state = IsExecutingState();
try {
final originalOCPRequest = await _ocpService.getOpenCryptoPayInvoice(uri.toString());
final paymentUri = await _ocpService.getOpenCryptoPayAddress(
originalOCPRequest,
selectedCryptoCurrency,
);
ocpRequest = originalOCPRequest;
final paymentRequest = PaymentRequest.fromUri(paymentUri);
clearOutputs();
outputs.first.address = paymentRequest.address;
outputs.first.parsedAddress =
ParsedAddress(addresses: [paymentRequest.address], name: ocpRequest!.receiverName);
outputs.first.setCryptoAmount(paymentRequest.amount);
outputs.first.note = ocpRequest!.receiverName;
return createTransaction();
} catch (e) {
printV(e);
state = FailureState(translateErrorMessage(e, walletType, currency));
return null;
}
}
@action
Future<PendingTransaction?> createTransaction({ExchangeProvider? provider}) async {
try {
if (wallet.isHardwareWallet)
state = IsAwaitingDeviceResponseState();
else
state = IsExecutingState();
if (!(state is IsExecutingState)) state = IsExecutingState();
if (wallet.isHardwareWallet) state = IsAwaitingDeviceResponseState();
pendingTransaction = await wallet.createTransaction(_credentials(provider));
@ -476,6 +525,24 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
throw Exception("Pending transaction doesn't exist. It should not be happened.");
}
if (ocpRequest != null) {
state = TransactionCommitting();
if (selectedCryptoCurrency == CryptoCurrency.xmr) {
await pendingTransaction!.commit();
}
await _ocpService.commitOpenCryptoPayRequest(
pendingTransaction!.hex,
txId: pendingTransaction!.id,
request: ocpRequest!,
asset: selectedCryptoCurrency,
);
state = TransactionCommitted();
return;
}
String address = outputs.fold('', (acc, value) {
return value.isParsedAddress
? '$acc${value.address}\n${value.extractedAddress}\n\n'
@ -703,7 +770,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
final parsedErrorMessageResult =
EVMTransactionErrorFeesHandler.parseEthereumFeesErrorMessage(
errorMessage,
_fiatConversationStore.prices[currency]!,
_fiatConversationStore.prices[currency] ?? 0.0,
);
if (parsedErrorMessageResult.error != null) {

View file

@ -6,6 +6,7 @@ import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cake_wallet/wallet_types.g.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
@ -127,12 +128,16 @@ abstract class WalletGroupsDisplayViewModelBase with Store {
bool isNonSeedWallet = wallet.isNonSeedWallet;
bool isNotMoneroBip39Wallet = wallet.type == WalletType.monero &&
wallet.derivationInfo?.derivationType != DerivationType.bip39;
// Exclude if any of these conditions are true
return isNonBIP39Wallet ||
isNanoDerivationType ||
isElectrumDerivationType ||
isSameTypeAsSelectedWallet ||
isNonSeedWallet;
isNonSeedWallet ||
isNotMoneroBip39Wallet;
});
if (shouldExcludeGroup) continue;

View file

@ -63,14 +63,15 @@ abstract class WalletKeysViewModelBase with Store {
String get seed => _wallet.seed != null ? _wallet.seed! : '';
bool get isLegacySeedOnly =>
(_wallet.type == WalletType.monero || _wallet.type == WalletType.wownero) &&
[WalletType.monero, WalletType.wownero].contains(_wallet.type) &&
_wallet.seed != null &&
!Polyseed.isValidSeed(_wallet.seed!);
!(Polyseed.isValidSeed(_wallet.seed!) ||
_wallet.seed!.split(' ').length == 12);
String get legacySeed {
if ((_wallet.type == WalletType.monero || _wallet.type == WalletType.wownero) &&
_wallet.seed != null &&
Polyseed.isValidSeed(_wallet.seed!)) {
(Polyseed.isValidSeed(_wallet.seed!) || _wallet.seed!.split(' ').length == 12)) {
final langName = PolyseedLang.getByPhrase(_wallet.seed!).nameEnglish;
if (_wallet.type == WalletType.monero) {

View file

@ -75,6 +75,9 @@ abstract class WalletListViewModelBase with Store {
@action
Future<void> loadWallet(WalletListItem walletItem) async {
if (walletItem.type == WalletType.haven) {
return;
}
// bool switchingToSameWalletType = walletItem.type == _appStore.wallet?.type;
// await _appStore.wallet?.close(shouldCleanup: !switchingToSameWalletType);
final wallet = await _walletLoadingService.load(walletItem.type, walletItem.name);

View file

@ -49,6 +49,9 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
bool get hasLanguageSelector =>
[WalletType.monero, WalletType.haven, WalletType.wownero].contains(type);
bool get showLanguageSelector =>
newWalletArguments?.mnemonic == null && hasLanguageSelector;
int get seedPhraseWordsLength {
switch (type) {
case WalletType.monero:
@ -81,7 +84,9 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
}
}
bool get hasSeedType => [WalletType.monero, WalletType.wownero].contains(type);
bool get hasSeedType =>
newWalletArguments?.mnemonic == null &&
[WalletType.monero, WalletType.wownero].contains(type);
@override
WalletCredentials getCredentials(dynamic _options) {
@ -96,7 +101,11 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
language: options!.first as String,
password: walletPassword,
passphrase: passphrase,
isPolyseed: options.last as bool);
seedType: newWalletArguments!.mnemonic != null
? MoneroSeedType.bip39.raw
: (options.last as MoneroSeedType).raw,
mnemonic: newWalletArguments!.mnemonic,
);
case WalletType.bitcoin:
case WalletType.litecoin:
return bitcoin!.createBitcoinNewWalletCredentials(
@ -152,7 +161,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
return wownero!.createWowneroNewWalletCredentials(
name: name,
language: options!.first as String,
isPolyseed: options.last as bool,
isPolyseed: (options.last as MoneroSeedType).raw == 1,
password: walletPassword,
passphrase: passphrase,
);

View file

@ -684,9 +684,6 @@
"seedtype": "البذور",
"seedtype_alert_content": "مشاركة البذور مع محافظ أخرى ممكن فقط مع BIP39 Seedtype.",
"seedtype_alert_title": "تنبيه البذور",
"seedtype_legacy": "إرث (25 كلمة)",
"seedtype_polyseed": "بوليسيد (16 كلمة)",
"seedtype_wownero": "Wownero (14 كلمة)",
"select_backup_file": "حدد ملف النسخ الاحتياطي",
"select_buy_provider_notice": "حدد مزود شراء أعلاه. يمكنك تخطي هذه الشاشة عن طريق تعيين مزود شراء الافتراضي في إعدادات التطبيق.",
"select_destination": ".ﻲﻃﺎﻴﺘﺣﻻﺍ ﺦﺴﻨﻟﺍ ﻒﻠﻣ ﺔﻬﺟﻭ ﺪﻳﺪﺤﺗ ءﺎﺟﺮﻟﺍ",

View file

@ -684,9 +684,6 @@
"seedtype": "Семенна тип",
"seedtype_alert_content": "Споделянето на семена с други портфейли е възможно само с BIP39 Seedtype.",
"seedtype_alert_title": "Сигнал за семена",
"seedtype_legacy": "Наследство (25 думи)",
"seedtype_polyseed": "Поли семе (16 думи)",
"seedtype_wownero": "Wownero (14 думи)",
"select_backup_file": "Избор на резервно копие",
"select_buy_provider_notice": "Изберете доставчик на покупка по -горе. Можете да пропуснете този екран, като зададете вашия доставчик по подразбиране по подразбиране в настройките на приложението.",
"select_destination": "Моля, изберете дестинация за архивния файл.",

View file

@ -684,9 +684,6 @@
"seedtype": "SeedType",
"seedtype_alert_content": "Sdílení semen s jinými peněženkami je možné pouze u BIP39 SeedType.",
"seedtype_alert_title": "Upozornění seedtype",
"seedtype_legacy": "Legacy (25 slov)",
"seedtype_polyseed": "Polyseed (16 slov)",
"seedtype_wownero": "Wownero (14 slov)",
"select_backup_file": "Vybrat soubor se zálohou",
"select_buy_provider_notice": "Vyberte výše uvedeného poskytovatele nákupu. Tuto obrazovku můžete přeskočit nastavením výchozího poskytovatele nákupu v nastavení aplikace.",
"select_destination": "Vyberte cíl pro záložní soubor.",

View file

@ -685,9 +685,6 @@
"seedtype": "Seedtyp",
"seedtype_alert_content": "Das Teilen von Seeds mit anderen Wallet ist nur mit bip39 Seedype möglich.",
"seedtype_alert_title": "Seedype-Alarm",
"seedtype_legacy": "Veraltet (25 Wörter)",
"seedtype_polyseed": "Polyseed (16 Wörter)",
"seedtype_wownero": "WOWNO (14 Wörter)",
"select_backup_file": "Sicherungsdatei auswählen",
"select_buy_provider_notice": "Wählen Sie oben einen Anbieter kaufen. Sie können diese Seite überspringen, indem Sie Ihren Standard-Kaufanbieter in den App-Einstellungen festlegen.",
"select_destination": "Bitte wählen Sie das Ziel für die Sicherungsdatei aus.",
@ -701,7 +698,7 @@
"send": "Senden",
"send_address": "${cryptoCurrency}-Adresse",
"send_amount": "Betrag:",
"send_change_to_you": "Verändere dich zu dir:",
"send_change_to_you": "Rückgeld:",
"send_creating_transaction": "Erstelle Transaktion",
"send_error_currency": "Die Währung darf nur Zahlen enthalten",
"send_error_minimum_value": "Der Mindestbetrag ist 0,01",

View file

@ -685,9 +685,6 @@
"seedtype": "Seedtype",
"seedtype_alert_content": "Sharing seeds with other wallets is only possible with BIP39 SeedType.",
"seedtype_alert_title": "SeedType Alert",
"seedtype_legacy": "Legacy (25 words)",
"seedtype_polyseed": "Polyseed (16 words)",
"seedtype_wownero": "Wownero (14 words)",
"select_backup_file": "Select backup file",
"select_buy_provider_notice": "Select a buy provider above. You can skip this screen by setting your default buy provider in app settings.",
"select_destination": "Please select destination for the backup file.",

View file

@ -685,9 +685,6 @@
"seedtype": "Tipos de semillas",
"seedtype_alert_content": "Compartir semillas con otras billeteras solo es posible con semillas bip39 - un tipo específico de semilla.",
"seedtype_alert_title": "Alerta de tipo de semillas",
"seedtype_legacy": "Semilla clásica-legacy (25 palabras)",
"seedtype_polyseed": "Poli-semilla (16 palabras)",
"seedtype_wownero": "Wownero (14 palabras)",
"select_backup_file": "Seleccionar archivo de respaldo",
"select_buy_provider_notice": "Selecciona un proveedor de compra arriba. Puede omitir esta pantalla configurando su proveedor de compra predeterminado en la configuración de la aplicación.",
"select_destination": "Selecciona el destino del archivo de copia de seguridad.",

View file

@ -684,9 +684,6 @@
"seedtype": "Type de graine",
"seedtype_alert_content": "Le partage de graines avec d'autres portefeuilles n'est possible qu'avec le type de graine BIP39.",
"seedtype_alert_title": "Alerte Type de Graine",
"seedtype_legacy": "Legacy (25 words)",
"seedtype_polyseed": "Polyseed (16 mots)",
"seedtype_wownero": "WOWNERO (14 mots)",
"select_backup_file": "Sélectionnez le fichier de sauvegarde",
"select_buy_provider_notice": "Sélectionnez un fournisseur d'achat ci-dessus. Vous pouvez ignorer cet écran en définissant votre fournisseur d'achat par défaut dans les paramètres de l'application.",
"select_destination": "Veuillez sélectionner la destination du fichier de sauvegarde.",

View file

@ -686,9 +686,6 @@
"seedtype": "Seedtype",
"seedtype_alert_content": "Raba tsaba tare da sauran wallets yana yiwuwa ne kawai tare da Bip39 seedtype.",
"seedtype_alert_title": "Seedtype farke",
"seedtype_legacy": "Legacy (25 kalmomi)",
"seedtype_polyseed": "Polyseed (16 kalmomi)",
"seedtype_wownero": "WowRero (kalmomi 14)",
"select_backup_file": "Zaɓi fayil ɗin madadin",
"select_buy_provider_notice": "Zaɓi mai ba da kyauta a sama. Zaka iya tsallake wannan allon ta hanyar saita mai ba da isasshen busasshen mai ba da isasshen busasshiyar saiti.",
"select_destination": "Da fatan za a zaɓi wurin da za a yi wa madadin fayil ɗin.",

View file

@ -686,9 +686,6 @@
"seedtype": "बीज",
"seedtype_alert_content": "अन्य पर्स के साथ बीज साझा करना केवल BIP39 सीडटाइप के साथ संभव है।",
"seedtype_alert_title": "बीजगणित अलर्ट",
"seedtype_legacy": "विरासत (25 शब्द)",
"seedtype_polyseed": "पॉलीसीड (16 शब्द)",
"seedtype_wownero": "Wownero (14 शब्द)",
"select_backup_file": "बैकअप फ़ाइल का चयन करें",
"select_buy_provider_notice": "ऊपर एक खरीद प्रदाता का चयन करें। आप इस स्क्रीन को ऐप सेटिंग्स में अपना डिफ़ॉल्ट बाय प्रदाता सेट करके छोड़ सकते हैं।",
"select_destination": "कृपया बैकअप फ़ाइल के लिए गंतव्य का चयन करें।",

View file

@ -684,9 +684,6 @@
"seedtype": "Sjemenska vrsta",
"seedtype_alert_content": "Dijeljenje sjemena s drugim novčanicima moguće je samo s BIP39 sjemenom.",
"seedtype_alert_title": "Upozorenje o sjemenu",
"seedtype_legacy": "Nasljeđe (25 riječi)",
"seedtype_polyseed": "Poliseed (16 riječi)",
"seedtype_wownero": "WANERO (14 riječi)",
"select_backup_file": "Odaberite datoteku sigurnosne kopije",
"select_buy_provider_notice": "Odaberite gornji davatelj kupnje. Ovaj zaslon možete preskočiti postavljanjem zadanog davatelja usluga kupnje u postavkama aplikacija.",
"select_destination": "Odaberite odredište za datoteku sigurnosne kopije.",

View file

@ -683,9 +683,6 @@
"seedtype": "Սերմի տեսակ",
"seedtype_alert_content": "Այլ դրամապանակներով սերմերի փոխանակումը հնարավոր է միայն BIP39 SEEDTYPE- ով:",
"seedtype_alert_title": "SEEDTYPE ALERT",
"seedtype_legacy": "Legacy (25 բառ)",
"seedtype_polyseed": "Polyseed (16 բառ)",
"seedtype_wownero": "Wownero (14 բառ)",
"select_backup_file": "Ընտրել կրկնօրինակ ֆայլ",
"select_buy_provider_notice": "Ընտրեք գնման մատակարարը վերևում։ Դուք կարող եք բաց թողնել այս էկրանը ձեր լռելայն գնման մատակարարը հավելվածի կարգավորումներում սահմանելով",
"select_destination": "Խնդրում ենք ընտրել կրկնօրինակ ֆայլի նպատակակետը",

View file

@ -687,9 +687,6 @@
"seedtype": "Seedtype",
"seedtype_alert_content": "Berbagi biji dengan dompet lain hanya dimungkinkan dengan BIP39 seedtype.",
"seedtype_alert_title": "Peringatan seedtype",
"seedtype_legacy": "Legacy (25 kata)",
"seedtype_polyseed": "Polyseed (16 kata)",
"seedtype_wownero": "Wownero (14 kata)",
"select_backup_file": "Pilih file cadangan",
"select_buy_provider_notice": "Pilih penyedia beli di atas. Anda dapat melewatkan layar ini dengan mengatur penyedia pembelian default Anda di pengaturan aplikasi.",
"select_destination": "Silakan pilih tujuan untuk file cadangan.",

View file

@ -685,9 +685,6 @@
"seedtype": "Seedtype",
"seedtype_alert_content": "La condivisione di semi con altri portafogli è possibile solo con Bip39 SeedType.",
"seedtype_alert_title": "Avviso seedType",
"seedtype_legacy": "Legacy (25 parole)",
"seedtype_polyseed": "Polyseed (16 parole)",
"seedtype_wownero": "Wownero (14 parole)",
"select_backup_file": "Seleziona file di backup",
"select_buy_provider_notice": "Seleziona un provider di acquisto sopra. È possibile saltare questa schermata impostando il provider di acquisto predefinito nelle impostazioni dell'app.",
"select_destination": "Seleziona la destinazione per il file di backup.",

View file

@ -685,9 +685,6 @@
"seedtype": "SeedType",
"seedtype_alert_content": "他の財布と種子を共有することは、BIP39 SeedTypeでのみ可能です。",
"seedtype_alert_title": "SeedTypeアラート",
"seedtype_legacy": "レガシー25語",
"seedtype_polyseed": "ポリシード16語",
"seedtype_wownero": "wownero14ワード",
"select_backup_file": "バックアップファイルを選択",
"select_buy_provider_notice": "上記の購入プロバイダーを選択してください。デフォルトの購入プロバイダーをアプリ設定で設定して、この画面をスキップできます。",
"select_destination": "バックアップファイルの保存先を選択してください。",

View file

@ -684,9 +684,6 @@
"seedtype": "시드 타입",
"seedtype_alert_content": "다른 지갑과 씨앗을 공유하는 것은 BIP39 SeedType에서만 가능합니다.",
"seedtype_alert_title": "종자 경보",
"seedtype_legacy": "레거시 (25 단어)",
"seedtype_polyseed": "다문 (16 단어)",
"seedtype_wownero": "Wownero (14 단어)",
"select_backup_file": "백업 파일 선택",
"select_buy_provider_notice": "위의 구매 제공자를 선택하십시오. 앱 설정에서 기본 구매 제공자를 설정 하여이 화면을 건너 뛸 수 있습니다.",
"select_destination": "백업 파일의 대상을 선택하십시오.",

View file

@ -684,9 +684,6 @@
"seedtype": "မျိုးပွားခြင်း",
"seedtype_alert_content": "အခြားပိုက်ဆံအိတ်များနှင့်မျိုးစေ့များကိုမျှဝေခြင်းသည် BIP39 sebyspe ဖြင့်သာဖြစ်သည်။",
"seedtype_alert_title": "ပျိုးပင်သတိပေးချက်",
"seedtype_legacy": "အမွေအနှစ် (စကားလုံး 25 လုံး)",
"seedtype_polyseed": "polyseed (စကားလုံး 16 လုံး)",
"seedtype_wownero": "Wownero (စကားလုံး 14 လုံး)",
"select_backup_file": "အရန်ဖိုင်ကို ရွေးပါ။",
"select_buy_provider_notice": "အပေါ်ကဝယ်သူတစ် ဦး ကိုရွေးချယ်ပါ။ သင်၏ default 0 ယ်သူအား app settings တွင် setting လုပ်ခြင်းဖြင့်ဤ screen ကိုကျော်သွားနိုင်သည်။",
"select_destination": "အရန်ဖိုင်အတွက် ဦးတည်ရာကို ရွေးပါ။",

View file

@ -684,9 +684,6 @@
"seedtype": "Zaadtype",
"seedtype_alert_content": "Het delen van zaden met andere portefeuilles is alleen mogelijk met BIP39 SeedType.",
"seedtype_alert_title": "Zaadtype alert",
"seedtype_legacy": "Legacy (25 woorden)",
"seedtype_polyseed": "Polyseed (16 woorden)",
"seedtype_wownero": "WOWNERO (14 woorden)",
"select_backup_file": "Selecteer een back-upbestand",
"select_buy_provider_notice": "Selecteer hierboven een koopprovider. U kunt dit scherm overslaan door uw standaard kopenprovider in te stellen in app -instellingen.",
"select_destination": "Selecteer de bestemming voor het back-upbestand.",

View file

@ -684,9 +684,6 @@
"seedtype": "Sedtype",
"seedtype_alert_content": "Dzielenie się nasionami z innymi portfelami jest możliwe tylko z BIP39 sededType.",
"seedtype_alert_title": "Ustanowienie typu sedype",
"seedtype_legacy": "Legacy (25 słów)",
"seedtype_polyseed": "Polyseed (16 słów)",
"seedtype_wownero": "Wowero (14 słów)",
"select_backup_file": "Wybierz plik kopii zapasowej",
"select_buy_provider_notice": "Wybierz powyższe dostawcę zakupu. Możesz pominąć ten ekran, ustawiając domyślnego dostawcę zakupu w ustawieniach aplikacji.",
"select_destination": "Wybierz miejsce docelowe dla pliku kopii zapasowej.",

View file

@ -688,7 +688,7 @@
"seedtype_alert_title": "Alerta de tipo de seed",
"seedtype_legacy": "Legado (25 palavras)",
"seedtype_polyseed": "Polyseed (16 palavras)",
"seedtype_wownero": "Wowrone (14 palavras)",
"seedtype_wownero": "Wownero (14 palavras)",
"select_backup_file": "Selecione o arquivo de backup",
"select_buy_provider_notice": "Selecione um provedor de compra acima. Você pode pular esta tela definindo seu provedor de compra padrão nas configurações de aplicativos.",
"select_destination": "Selecione o destino para o arquivo de backup.",

View file

@ -685,9 +685,6 @@
"seedtype": "SEEDTYPE",
"seedtype_alert_content": "Обмен семенами с другими кошельками возможно только с BIP39 SeedType.",
"seedtype_alert_title": "SEEDTYPE ALERT",
"seedtype_legacy": "Наследие (25 слов)",
"seedtype_polyseed": "Полиса (16 слов)",
"seedtype_wownero": "Wownero (14 слов)",
"select_backup_file": "Выберите файл резервной копии",
"select_buy_provider_notice": "Выберите поставщика покупки выше. Вы можете пропустить этот экран, установив поставщика покупки по умолчанию в настройках приложения.",
"select_destination": "Пожалуйста, выберите место для файла резервной копии.",

View file

@ -684,9 +684,6 @@
"seedtype": "เมล็ดพันธุ์",
"seedtype_alert_content": "การแบ่งปันเมล็ดกับกระเป๋าเงินอื่น ๆ เป็นไปได้เฉพาะกับ bip39 seedtype",
"seedtype_alert_title": "การแจ้งเตือน seedtype",
"seedtype_legacy": "มรดก (25 คำ)",
"seedtype_polyseed": "โพลีส (16 คำ)",
"seedtype_wownero": "wownero (14 คำ)",
"select_backup_file": "เลือกไฟล์สำรอง",
"select_buy_provider_notice": "เลือกผู้ให้บริการซื้อด้านบน คุณสามารถข้ามหน้าจอนี้ได้โดยการตั้งค่าผู้ให้บริการซื้อเริ่มต้นในการตั้งค่าแอป",
"select_destination": "โปรดเลือกปลายทางสำหรับไฟล์สำรอง",

View file

@ -684,9 +684,6 @@
"seedtype": "Seed type",
"seedtype_alert_content": "Ang pagbabahagi ng mga buto sa iba pang mga pitaka ay posible lamang sa bip39 seedtype.",
"seedtype_alert_title": "Alerto ng Seedtype",
"seedtype_legacy": "Legacy (25 na salita)",
"seedtype_polyseed": "Polyseed (16 na salita)",
"seedtype_wownero": "Wownero (14 na salita)",
"select_backup_file": "Piliin ang backup na file",
"select_buy_provider_notice": "Pumili ng provider ng pagbili sa itaas. Maaari mong laktawan ang screen na ito sa pamamagitan ng pagtatakda ng iyong default na provider ng pagbili sa mga setting ng app.",
"select_destination": "Mangyaring piliin ang patutunguhan para sa backup na file.",

View file

@ -684,9 +684,6 @@
"seedtype": "Tohum",
"seedtype_alert_content": "Tohumları diğer cüzdanlarla paylaşmak sadece BIP39 tohumu ile mümkündür.",
"seedtype_alert_title": "SeedType uyarısı",
"seedtype_legacy": "Miras (25 kelime)",
"seedtype_polyseed": "Polyseed (16 kelime)",
"seedtype_wownero": "Wownero (14 kelime)",
"select_backup_file": "Yedek dosyası seç",
"select_buy_provider_notice": "Yukarıda bir satın alma sağlayıcısı seçin. App ayarlarında varsayılan satın alma sağlayıcınızı ayarlayarak bu ekranı atlayabilirsiniz.",
"select_destination": "Lütfen yedekleme dosyası için hedef seçin.",

View file

@ -685,9 +685,6 @@
"seedtype": "Насіннєвий тип",
"seedtype_alert_content": "Спільний доступ до інших гаманців можливе лише за допомогою BIP39 Seedtype.",
"seedtype_alert_title": "Попередження насінника",
"seedtype_legacy": "Спадщина (25 слів)",
"seedtype_polyseed": "Полісей (16 слів)",
"seedtype_wownero": "Влонеро (14 слів)",
"select_backup_file": "Виберіть файл резервної копії",
"select_buy_provider_notice": "Виберіть постачальника купівлі вище. Ви можете пропустити цей екран, встановивши свого постачальника купівлі за замовчуванням у налаштуваннях додатків.",
"select_destination": "Виберіть місце призначення для файлу резервної копії.",

View file

@ -686,9 +686,6 @@
"seedtype": "سیڈ ٹائپ",
"seedtype_alert_content": "دوسرے بٹوے کے ساتھ بیجوں کا اشتراک صرف BIP39 بیج ٹائپ کے ساتھ ہی ممکن ہے۔",
"seedtype_alert_title": "سیڈ ٹائپ الرٹ",
"seedtype_legacy": "میراث (25 الفاظ)",
"seedtype_polyseed": "پالیسیڈ (16 الفاظ)",
"seedtype_wownero": "واونرو (14 الفاظ)",
"select_backup_file": "بیک اپ فائل کو منتخب کریں۔",
"select_buy_provider_notice": "اوپر خریدنے والا خریدنے والا منتخب کریں۔ آپ ایپ کی ترتیبات میں اپنے پہلے سے طے شدہ خریدنے والے کو ترتیب دے کر اس اسکرین کو چھوڑ سکتے ہیں۔",
"select_destination": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﻝﺰﻨﻣ ﮯﯿﻟ ﮯﮐ ﻞﺋﺎﻓ ﭖﺍ ﮏﯿﺑ ﻡﺮﮐ ﮦﺍﺮﺑ",

View file

@ -682,9 +682,6 @@
"seedtype": "Loại hạt giống",
"seedtype_alert_content": "Chia sẻ hạt giống với ví khác chỉ có thể với BIP39 SeedType.",
"seedtype_alert_title": "Cảnh báo hạt giống",
"seedtype_legacy": "Di sản (25 từ)",
"seedtype_polyseed": "Polyseed (16 từ)",
"seedtype_wownero": "Wownero (14 từ)",
"select_backup_file": "Chọn tệp sao lưu",
"select_buy_provider_notice": "Chọn nhà cung cấp mua ở trên. Bạn có thể bỏ qua màn hình này bằng cách thiết lập nhà cung cấp mua mặc định trong cài đặt ứng dụng.",
"select_destination": "Vui lòng chọn đích cho tệp sao lưu.",

View file

@ -685,9 +685,6 @@
"seedtype": "Irugbin-seetypu",
"seedtype_alert_content": "Pinpin awọn irugbin pẹlu awọn gedo miiran ṣee ṣe pẹlu Bip39 irugbin.",
"seedtype_alert_title": "Ṣajọpọ Seeytype",
"seedtype_legacy": "Legacy (awọn ọrọ 25)",
"seedtype_polyseed": "Polyseed (awọn ọrọ 16)",
"seedtype_wownero": "Wowero (awọn ọrọ 14)",
"select_backup_file": "Select backup file",
"select_buy_provider_notice": "Yan olupese Ra loke. O le skii iboju yii nipa ṣiṣeto olupese rẹ ni awọn eto App.",
"select_destination": "Jọwọ yan ibi ti o nlo fun faili afẹyinti.",

View file

@ -684,9 +684,6 @@
"seedtype": "籽粒",
"seedtype_alert_content": "只有BIP39籽粒可以与其他钱包共享种子。",
"seedtype_alert_title": "籽粒警报",
"seedtype_legacy": "遗产25个单词",
"seedtype_polyseed": "多种物品16个单词",
"seedtype_wownero": "沃恩罗14个单词",
"select_backup_file": "选择备份文件",
"select_buy_provider_notice": "在上面选择买入提供商。您可以通过在应用程序设置中设置默认的购买提供商来跳过此屏幕。",
"select_destination": "请选择备份文件的目的地。",

View file

@ -14,15 +14,15 @@ TYPES=($MONERO_COM $CAKEWALLET)
APP_ANDROID_TYPE=$1
MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="4.25.0"
MONERO_COM_BUILD_NUMBER=118
MONERO_COM_VERSION="4.26.0"
MONERO_COM_BUILD_NUMBER=119
MONERO_COM_BUNDLE_ID="com.monero.app"
MONERO_COM_PACKAGE="com.monero.app"
MONERO_COM_SCHEME="monero.com"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.25.0"
CAKEWALLET_BUILD_NUMBER=256
CAKEWALLET_VERSION="4.26.0"
CAKEWALLET_BUILD_NUMBER=257
CAKEWALLET_BUNDLE_ID="com.cakewallet.cake_wallet"
CAKEWALLET_PACKAGE="com.cakewallet.cake_wallet"
CAKEWALLET_SCHEME="cakewallet"

View file

@ -12,13 +12,13 @@ TYPES=($MONERO_COM $CAKEWALLET)
APP_IOS_TYPE=$1
MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="4.25.0"
MONERO_COM_BUILD_NUMBER=115
MONERO_COM_VERSION="4.26.0"
MONERO_COM_BUILD_NUMBER=116
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.25.0"
CAKEWALLET_BUILD_NUMBER=310
CAKEWALLET_VERSION="4.26.0"
CAKEWALLET_BUILD_NUMBER=311
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"

View file

@ -14,8 +14,8 @@ if [ -n "$1" ]; then
fi
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.25.0"
CAKEWALLET_BUILD_NUMBER=51
CAKEWALLET_VERSION="4.26.0"
CAKEWALLET_BUILD_NUMBER=52
if ! [[ " ${TYPES[*]} " =~ " ${APP_LINUX_TYPE} " ]]; then
echo "Wrong app type."

View file

@ -16,13 +16,13 @@ if [ -n "$1" ]; then
fi
MONERO_COM_NAME="Monero.com"
MONERO_COM_VERSION="4.25.0"
MONERO_COM_BUILD_NUMBER=47
MONERO_COM_VERSION="4.26.0"
MONERO_COM_BUILD_NUMBER=48
MONERO_COM_BUNDLE_ID="com.cakewallet.monero"
CAKEWALLET_NAME="Cake Wallet"
CAKEWALLET_VERSION="4.25.0"
CAKEWALLET_BUILD_NUMBER=109
CAKEWALLET_VERSION="4.26.0"
CAKEWALLET_BUILD_NUMBER=110
CAKEWALLET_BUNDLE_ID="com.fotolockr.cakewallet"
if ! [[ " ${TYPES[*]} " =~ " ${APP_MACOS_TYPE} " ]]; then

View file

@ -1,5 +1,5 @@
#define MyAppName "Cake Wallet"
#define MyAppVersion "4.25.0"
#define MyAppVersion "4.26.0"
#define MyAppPublisher "Cake Labs LLC"
#define MyAppURL "https://cakewallet.com/"
#define MyAppExeName "CakeWallet.exe"

View file

@ -475,7 +475,7 @@ abstract class Monero {
required int height});
WalletCredentials createMoneroRestoreWalletFromSeedCredentials({required String name, required String password, required String passphrase, required int height, required String mnemonic});
WalletCredentials createMoneroRestoreWalletFromHardwareCredentials({required String name, required String password, required int height, required ledger.LedgerConnection ledgerConnection});
WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required bool isPolyseed, required String? passphrase, String? password});
WalletCredentials createMoneroNewWalletCredentials({required String name, required String language, required int seedType, required String? passphrase, String? password, String? mnemonic});
Map<String, String> getKeys(Object wallet);
int? getRestoreHeight(Object wallet);
Object createMoneroTransactionCreationCredentials({required List<Output> outputs, required TransactionPriority priority});