mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 12:29:51 +00:00
Merge remote-tracking branch 'origin/main' into electrum-sp-refactors
This commit is contained in:
commit
817735f510
98 changed files with 1882 additions and 704 deletions
3
.github/workflows/pr_test_build_linux.yml
vendored
3
.github/workflows/pr_test_build_linux.yml
vendored
|
@ -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: |
|
||||
|
|
|
@ -74,9 +74,6 @@ android {
|
|||
release {
|
||||
signingConfig signingConfigs.release
|
||||
|
||||
shrinkResources false
|
||||
minifyEnabled false
|
||||
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
|
|
1
android/app/proguard-rules.pro
vendored
1
android/app/proguard-rules.pro
vendored
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
1
cw_monero/.gitignore
vendored
1
cw_monero/.gitignore
vendored
|
@ -12,3 +12,4 @@ android/.cxx/
|
|||
|
||||
macos/cw_monero.podspec
|
||||
macos/External/
|
||||
*monero_libwallet2_api_c.*
|
|
@ -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());
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
59
cw_monero/lib/bip39_seed.dart
Normal file
59
cw_monero/lib/bip39_seed.dart
Normal 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);
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
39
cw_monero/test/bip39_seed_test.dart
Normal file
39
cw_monero/test/bip39_seed_test.dart
Normal 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);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
29
cw_monero/test/mock/path_provider.dart
Normal file
29
cw_monero/test/mock/path_provider.dart
Normal 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();
|
||||
}
|
148
cw_monero/test/monero_wallet_service_test.dart
Normal file
148
cw_monero/test/monero_wallet_service_test.dart
Normal 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;
|
||||
}
|
16
cw_monero/test/utils/setup_monero_c.dart
Normal file
16
cw_monero/test/utils/setup_monero_c.dart
Normal 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";
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
18
lib/core/open_crypto_pay/exceptions.dart
Normal file
18
lib/core/open_crypto_pay/exceptions.dart
Normal 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";
|
||||
}
|
78
lib/core/open_crypto_pay/lnurl.dart
Normal file
78
lib/core/open_crypto_pay/lnurl.dart
Normal 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;
|
||||
}
|
26
lib/core/open_crypto_pay/models.dart
Normal file
26
lib/core/open_crypto_pay/models.dart
Normal 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;
|
||||
}
|
184
lib/core/open_crypto_pay/open_cryptopay_service.dart
Normal file
184
lib/core/open_crypto_pay/open_cryptopay_service.dart
Normal 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);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>(
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -20,6 +20,7 @@ class AddressListPage extends BasePage {
|
|||
children: <Widget>[
|
||||
AddressList(
|
||||
addressListViewModel: addressListViewModel,
|
||||
currentTheme: currentTheme,
|
||||
onSelect: (String address) async {
|
||||
Navigator.of(context).pop(address);
|
||||
},
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
161
lib/utils/address_formatter.dart
Normal file
161
lib/utils/address_formatter.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
|
|
@ -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": ".ﻲﻃﺎﻴﺘﺣﻻﺍ ﺦﺴﻨﻟﺍ ﻒﻠﻣ ﺔﻬﺟﻭ ﺪﻳﺪﺤﺗ ءﺎﺟﺮﻟﺍ",
|
||||
|
|
|
@ -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": "Моля, изберете дестинация за архивния файл.",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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": "कृपया बैकअप फ़ाइल के लिए गंतव्य का चयन करें।",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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": "Խնդրում ենք ընտրել կրկնօրինակ ֆայլի նպատակակետը",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -685,9 +685,6 @@
|
|||
"seedtype": "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": "バックアップファイルの保存先を選択してください。",
|
||||
|
|
|
@ -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": "백업 파일의 대상을 선택하십시오.",
|
||||
|
|
|
@ -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": "အရန်ဖိုင်အတွက် ဦးတည်ရာကို ရွေးပါ။",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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": "Пожалуйста, выберите место для файла резервной копии.",
|
||||
|
|
|
@ -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": "โปรดเลือกปลายทางสำหรับไฟล์สำรอง",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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": "Виберіть місце призначення для файлу резервної копії.",
|
||||
|
|
|
@ -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": "۔ﮟﯾﺮﮐ ﺏﺎﺨﺘﻧﺍ ﺎﮐ ﻝﺰﻨﻣ ﮯﯿﻟ ﮯﮐ ﻞﺋﺎﻓ ﭖﺍ ﮏﯿﺑ ﻡﺮﮐ ﮦﺍﺮﺑ",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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": "请选择备份文件的目的地。",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue