diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml index 6a7444745..c55148f4c 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -9,7 +9,7 @@ jobs: PR_test_build: runs-on: linux-amd64 container: - image: ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly + image: ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-ndkr28-go1.24.1-ruststablenightly env: STORE_PASS: test@cake_wallet KEY_PASS: test@cake_wallet diff --git a/.github/workflows/pr_test_build_linux.yml b/.github/workflows/pr_test_build_linux.yml index 9a167d0c0..6a5e10ee3 100644 --- a/.github/workflows/pr_test_build_linux.yml +++ b/.github/workflows/pr_test_build_linux.yml @@ -9,7 +9,7 @@ jobs: PR_test_build: runs-on: linux-amd64 container: - image: ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly + image: ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-ndkr28-go1.24.1-ruststablenightly env: STORE_PASS: test@cake_wallet KEY_PASS: test@cake_wallet diff --git a/Dockerfile b/Dockerfile index 151b7af20..f03ba1815 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -# docker buildx build --push --pull --platform linux/amd64,linux/arm64 . -f Dockerfile -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly +# docker buildx build --push --pull --platform linux/amd64,linux/arm64 . -f Dockerfile -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-ndkr28-go1.24.1-ruststablenightly # Heavily inspired by cirrusci images # https://github.com/cirruslabs/docker-images-android/blob/master/sdk/tools/Dockerfile @@ -15,7 +15,7 @@ LABEL org.opencontainers.image.source=https://github.com/cake-tech/cake_wallet ENV GOLANG_VERSION=1.24.1 # Pin Flutter version to latest known-working version -ENV FLUTTER_VERSION=3.27.0 +ENV FLUTTER_VERSION=3.27.4 # Pin Android Studio, platform, and build tools versions to latest known-working version # Comes from https://developer.android.com/studio/#command-tools @@ -138,11 +138,12 @@ RUN ARCH=$(uname -m) && \ "build-tools;35.0.0" # Install extra NDK dependency for sp_scanner -ENV ANDROID_NDK_VERSION=27.2.12479018 +ENV ANDROID_NDK_VERSION=28.2.13676358 RUN ARCH=$(uname -m) && \ if [ "$ARCH" != "x86_64" ]; then exit 0; fi \ && yes | sdkmanager "ndk;$ANDROID_NDK_VERSION" \ - "ndk;27.0.12077973" + "ndk;27.0.12077973" \ + "ndk;27.2.12479018" # Install dependencies for tests # Comes from https://github.com/ReactiveCircus/android-emulator-runner diff --git a/android/app/build.gradle b/android/app/build.gradle index 29ae280f6..0b009085b 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -94,7 +94,7 @@ android { } } - ndkVersion "27.0.12077973" + ndkVersion "28.2.13676358" } flutter { diff --git a/assets/images/restore_backup.png b/assets/images/restore_backup.png new file mode 100644 index 000000000..01157f0e6 Binary files /dev/null and b/assets/images/restore_backup.png differ diff --git a/assets/images/restore_backup_dark.png b/assets/images/restore_backup_dark.png new file mode 100644 index 000000000..7029a86da Binary files /dev/null and b/assets/images/restore_backup_dark.png differ diff --git a/assets/images/restore_cupcake.png b/assets/images/restore_cupcake.png new file mode 100644 index 000000000..b05b3a6a7 Binary files /dev/null and b/assets/images/restore_cupcake.png differ diff --git a/assets/images/restore_cupcake_dark.png b/assets/images/restore_cupcake_dark.png new file mode 100644 index 000000000..48fe965cb Binary files /dev/null and b/assets/images/restore_cupcake_dark.png differ diff --git a/assets/images/restore_hot_wallet.png b/assets/images/restore_hot_wallet.png new file mode 100644 index 000000000..61fefeca5 Binary files /dev/null and b/assets/images/restore_hot_wallet.png differ diff --git a/assets/images/restore_hot_wallet_dark.png b/assets/images/restore_hot_wallet_dark.png new file mode 100644 index 000000000..0790c210a Binary files /dev/null and b/assets/images/restore_hot_wallet_dark.png differ diff --git a/assets/images/restore_hw.png b/assets/images/restore_hw.png new file mode 100644 index 000000000..4636c57e4 Binary files /dev/null and b/assets/images/restore_hw.png differ diff --git a/assets/images/restore_hw_dark.png b/assets/images/restore_hw_dark.png new file mode 100644 index 000000000..d6023d8f1 Binary files /dev/null and b/assets/images/restore_hw_dark.png differ diff --git a/assets/images/restore_qr.png b/assets/images/restore_qr.png index 5f8cffb95..0f99d2f56 100644 Binary files a/assets/images/restore_qr.png and b/assets/images/restore_qr.png differ diff --git a/assets/images/restore_qr_dark.png b/assets/images/restore_qr_dark.png new file mode 100644 index 000000000..cb33cfe17 Binary files /dev/null and b/assets/images/restore_qr_dark.png differ diff --git a/cw_bitcoin/lib/bitcoin_receive_page_option.dart b/cw_bitcoin/lib/bitcoin_receive_page_option.dart index 07083e111..8491ae8e3 100644 --- a/cw_bitcoin/lib/bitcoin_receive_page_option.dart +++ b/cw_bitcoin/lib/bitcoin_receive_page_option.dart @@ -28,6 +28,16 @@ class BitcoinReceivePageOption implements ReceivePageOption { BitcoinReceivePageOption.p2pkh ]; + static const allViewOnly = [ + BitcoinReceivePageOption.p2wpkh, + // TODO: uncomment this after we properly derive keys and not use m/84 for + // all of them (as this breaks cupcake) + // BitcoinReceivePageOption.p2tr, + // BitcoinReceivePageOption.p2wsh, + // BitcoinReceivePageOption.p2sh, + // BitcoinReceivePageOption.p2pkh + ]; + static const allLitecoin = [ BitcoinReceivePageOption.p2wpkh, BitcoinReceivePageOption.mweb, diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index d286df424..a5c209e91 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -21,9 +21,11 @@ import 'package:cw_bitcoin/psbt/v0_deserialize.dart'; import 'package:cw_bitcoin/psbt/v0_finalizer.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/encryption_file_utils.dart'; +import 'package:cw_core/output_info.dart'; import 'package:cw_core/payjoin_session.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; @@ -32,6 +34,9 @@ import 'package:ledger_bitcoin/ledger_bitcoin.dart'; import 'package:ledger_bitcoin/psbt.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:mobx/mobx.dart'; +import 'package:ur/cbor_lite.dart'; +import 'package:ur/ur.dart'; +import 'package:ur/ur_decoder.dart'; part 'bitcoin_wallet.g.dart'; @@ -280,6 +285,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { Future buildPsbt({ required List outputs, + required List cwOutputs, required BigInt fee, required BasedUtxoNetwork network, required List utxos, @@ -308,7 +314,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { } return PSBTTransactionBuild( - inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF) + inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF, cwOutputs: cwOutputs) .psbt; } @@ -318,6 +324,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { required BigInt fee, required BasedUtxoNetwork network, required List utxos, + required List cwOutputs, required Map publicKeys, String? memo, bool enableRBF = false, @@ -331,6 +338,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { fee: fee, network: network, utxos: utxos, + cwOutputs: cwOutputs, publicKeys: publicKeys, masterFingerprint: masterFingerprint, memo: memo, @@ -351,7 +359,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { as PendingBitcoinTransaction; final payjoinUri = credentials.payjoinUri; - if (payjoinUri == null) return tx; + if (payjoinUri == null && !tx.shouldCommitUR()) return tx; final transaction = await buildPsbt( utxos: tx.utxos, @@ -363,20 +371,26 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { isChange: e.isChange, )) .toList(), + cwOutputs: credentials.outputs, fee: BigInt.from(tx.fee), network: network, memo: credentials.outputs.first.memo, outputOrdering: BitcoinOrdering.none, enableRBF: true, publicKeys: tx.publicKeys!, - masterFingerprint: Uint8List(0)); + masterFingerprint: Uint8List.fromList([0, 0, 0, 0])); - final originalPsbt = await signPsbt( - base64.encode(transaction.asPsbtV0()), getUtxoWithPrivateKeys()); + if (tx.shouldCommitUR()) { + tx.unsignedPsbt = transaction.asPsbtV0(); + return tx; + } + + final originalPsbt = + await signPsbt(base64.encode(transaction.asPsbtV0()), getUtxoWithPrivateKeys()); tx.commitOverride = () async { - final sender = await payjoinManager.initSender( - payjoinUri, originalPsbt, int.parse(tx.feeRate)); + final sender = + await payjoinManager.initSender(payjoinUri!, originalPsbt, int.parse(tx.feeRate)); payjoinManager.spawnNewSender( sender: sender, pjUrl: payjoinUri, amount: BigInt.from(tx.amount)); }; @@ -404,6 +418,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { feeRate: "", network: network, hasChange: true, + isViewOnly: false, ).commit(); } @@ -429,6 +444,44 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { return base64Encode(psbt.asPsbtV0()); } + Future commitPsbtUR(List urCodes) async { + if (urCodes.isEmpty) throw Exception("No QR code got scanned"); + bool isUr = urCodes.any((str) { + return str.startsWith("ur:psbt/"); + }); + if (isUr) { + final ur = URDecoder(); + for (final inp in urCodes) { + ur.receivePart(inp); + } + final result = (ur.result as UR); + final cbor = result.cbor; + final cborDecoder = CBORDecoder(cbor); + final out = cborDecoder.decodeBytes(); + final bytes = out.$1; + final base64psbt = base64Encode(bytes); + final psbt = PsbtV2()..deserializeV0(base64Decode(base64psbt)); + + // psbt.finalize(); + final finalized = base64Encode(psbt.serialize()); + await commitPsbt(finalized); + } else { + final btcTx = BtcTransaction.fromRaw(urCodes.first); + + return PendingBitcoinTransaction( + btcTx, + type, + electrumClient: electrumClient, + amount: 0, + fee: 0, + feeRate: "", + network: network, + hasChange: true, + isViewOnly: false, + ).commit(); + } + } + @override Future signMessage(String message, {String? address = null}) async { if (walletInfo.isHardwareWallet) { diff --git a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart index 177d61e87..3d71a0c39 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_creation_credentials.dart @@ -54,6 +54,17 @@ class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials { final String wif; } +class BitcoinWalletFromKeysCredentials extends WalletCredentials { + BitcoinWalletFromKeysCredentials({ + required String name, + required String password, + required this.xpub, + WalletInfo? walletInfo, + }) : super(name: name, password: password, walletInfo: walletInfo); + + final String xpub; +} + class BitcoinRestoreWalletFromHardware extends WalletCredentials { BitcoinRestoreWalletFromHardware({ required String name, diff --git a/cw_bitcoin/lib/bitcoin_wallet_keys.dart b/cw_bitcoin/lib/bitcoin_wallet_keys.dart index 0a4afc10d..4ed0da49c 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_keys.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_keys.dart @@ -1,7 +1,15 @@ class BitcoinWalletKeys { - const BitcoinWalletKeys({required this.wif, required this.privateKey, required this.publicKey}); + const BitcoinWalletKeys({required this.wif, required this.privateKey, required this.publicKey, required this.xpub}); final String wif; final String privateKey; final String publicKey; + final String xpub; + + Map toJson() => { + 'wif': wif, + 'privateKey': privateKey, + 'publicKey': publicKey, + 'xpub': xpub + }; } \ No newline at end of file diff --git a/cw_bitcoin/lib/bitcoin_wallet_service.dart b/cw_bitcoin/lib/bitcoin_wallet_service.dart index 33113d7ef..8d41298ee 100644 --- a/cw_bitcoin/lib/bitcoin_wallet_service.dart +++ b/cw_bitcoin/lib/bitcoin_wallet_service.dart @@ -20,7 +20,7 @@ import 'package:bip39/bip39.dart' as bip39; class BitcoinWalletService extends WalletService< BitcoinNewWalletCredentials, BitcoinRestoreWalletFromSeedCredentials, - BitcoinRestoreWalletFromWIFCredentials, + BitcoinWalletFromKeysCredentials, BitcoinRestoreWalletFromHardware> { BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.payjoinSessionSource, this.isDirect); @@ -165,9 +165,25 @@ class BitcoinWalletService extends WalletService< } @override - Future restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials, - {bool? isTestnet}) async => - throw UnimplementedError(); + Future restoreFromKeys(BitcoinWalletFromKeysCredentials credentials, + {bool? isTestnet}) async { + final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet; + credentials.walletInfo?.network = network.value; + + final wallet = await BitcoinWallet( + password: credentials.password!, + xpub: credentials.xpub, + walletInfo: credentials.walletInfo!, + unspentCoinsInfo: unspentCoinsInfoSource, + networkParam: network, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + payjoinBox: payjoinSessionSource, + ); + + await wallet.save(); + await wallet.init(); + return wallet; + } @override Future restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials, diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index eb03514ae..91736b3c1 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -42,6 +42,7 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/unspent_coin_type.dart'; +import 'package:cw_core/output_info.dart'; import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger; @@ -216,7 +217,7 @@ abstract class ElectrumWalletBase @override bool isTestnet; - bool get hasSilentPaymentsScanning => type == WalletType.bitcoin; + bool get hasSilentPaymentsScanning => type == WalletType.bitcoin && keys.privateKey.isNotEmpty; @observable bool nodeSupportsSilentPayments = true; @@ -282,11 +283,26 @@ abstract class ElectrumWalletBase } @override - BitcoinWalletKeys get keys => BitcoinWalletKeys( - wif: WifEncoder.encode(hd.privateKey.raw, netVer: network.wifNetVer), - privateKey: hd.privateKey.toHex(), - publicKey: hd.publicKey.toHex(), - ); + BitcoinWalletKeys get keys { + String? wif; + String? privateKey; + String? publicKey; + try { + wif = WifEncoder.encode(hd.privateKey.raw, netVer: network.wifNetVer); + } catch (_) {} + try { + privateKey = hd.privateKey.toHex(); + } catch (_) {} + try { + publicKey = hd.publicKey.toHex(); + } catch (_) {} + return BitcoinWalletKeys( + wif: wif ?? '', + privateKey: privateKey ?? '', + publicKey: publicKey ?? '', + xpub: xpub, + ); + } String _password; List unspentCoins; @@ -672,7 +688,7 @@ abstract class ElectrumWalletBase ); spendsSilentPayment = true; isSilentPayment = true; - } else if (!isHardwareWallet) { + } else if (!isHardwareWallet && keys.privateKey.isNotEmpty) { privkey = generateECPrivate(hd: hd, index: utx.bitcoinAddressRecord.index, network: network); } @@ -1120,6 +1136,7 @@ abstract class ElectrumWalletBase memo: estimatedTx.memo, outputOrdering: BitcoinOrdering.none, enableRBF: true, + cwOutputs: transactionCredentials.outputs, ); return PendingBitcoinTransaction( @@ -1132,7 +1149,8 @@ abstract class ElectrumWalletBase network: network, hasChange: estimatedTx.hasChange, isSendAll: estimatedTx.isSendAll, - hasTaprootInputs: false, // ToDo: (Konsti) Support Taproot + hasTaprootInputs: false, // ToDo: (Konsti) Support Taproot, + isViewOnly: false, )..addListener((transaction) async { transactionHistory.addOne(transaction); await updateBalance(); @@ -1166,6 +1184,7 @@ abstract class ElectrumWalletBase bool hasTaprootInputs = false; final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) { + if (keys.privateKey.isEmpty) return ""; String error = "Cannot find private key."; ECPrivateInfo? key; @@ -1203,18 +1222,21 @@ abstract class ElectrumWalletBase } }); - return PendingBitcoinTransaction(transaction, type, - electrumClient: electrumClient, - amount: estimatedTx.amount, - fee: estimatedTx.fee, - feeRate: feeRateInt.toString(), - network: network, - hasChange: estimatedTx.hasChange, - isSendAll: estimatedTx.isSendAll, - hasTaprootInputs: hasTaprootInputs, - utxos: estimatedTx.utxos, - publicKeys: estimatedTx.publicKeys) - ..addListener((transaction) async { + return PendingBitcoinTransaction( + transaction, + type, + electrumClient: electrumClient, + amount: estimatedTx.amount, + fee: estimatedTx.fee, + feeRate: feeRateInt.toString(), + network: network, + hasChange: estimatedTx.hasChange, + isSendAll: estimatedTx.isSendAll, + hasTaprootInputs: hasTaprootInputs, + utxos: estimatedTx.utxos, + publicKeys: estimatedTx.publicKeys, + isViewOnly: keys.privateKey.isEmpty, + )..addListener((transaction) async { transactionHistory.addOne(transaction); if (estimatedTx.spendsSilentPayment) { transactionHistory.transactions.values.forEach((tx) { @@ -1242,6 +1264,7 @@ abstract class ElectrumWalletBase required BigInt fee, required BasedUtxoNetwork network, required List utxos, + required List cwOutputs, required Map publicKeys, String? memo, bool enableRBF = false, @@ -1379,6 +1402,8 @@ abstract class ElectrumWalletBase @action @override Future rescan({required int height, bool? doSingleScan}) async { + if (keys.privateKey.isEmpty) return; + silentPaymentsScanningActive = true; _setListeners(height, doSingleScan: doSingleScan); } @@ -1875,6 +1900,7 @@ abstract class ElectrumWalletBase network: network, hasChange: changeOutputs.isNotEmpty, feeRate: newFee.toString(), + isViewOnly: keys.privateKey.isEmpty, )..addListener((transaction) async { transactionHistory.transactions.values.forEach((tx) { if (tx.id == hash) { diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index d3ba83a61..7b32547b8 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -38,6 +38,7 @@ import 'package:cw_bitcoin/litecoin_wallet_addresses.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_keys_file.dart'; +import 'package:cw_core/output_info.dart'; import 'package:flutter/foundation.dart'; import 'package:grpc/grpc.dart'; import 'package:hive/hive.dart'; @@ -1383,6 +1384,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { required BigInt fee, required BasedUtxoNetwork network, required List utxos, + required List cwOutputs, required Map publicKeys, String? memo, bool enableRBF = false, diff --git a/cw_bitcoin/lib/pending_bitcoin_transaction.dart b/cw_bitcoin/lib/pending_bitcoin_transaction.dart index 43c4b3963..82bcbd79e 100644 --- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart +++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart @@ -1,3 +1,7 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:bbqrdart/bbqrdart.dart'; import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:grpc/grpc.dart'; import 'package:cw_bitcoin/exceptions.dart'; @@ -11,6 +15,9 @@ import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_mweb/cw_mweb.dart'; import 'package:cw_mweb/mwebd.pb.dart'; +import 'package:ur/cbor_lite.dart'; +import 'package:ur/ur.dart'; +import 'package:ur/ur_encoder.dart'; class PendingBitcoinTransaction with PendingTransaction { PendingBitcoinTransaction( @@ -28,6 +35,8 @@ class PendingBitcoinTransaction with PendingTransaction { this.utxos = const [], this.publicKeys, this.commitOverride, + this.unsignedPsbt, + required this.isViewOnly, }) : _listeners = []; final WalletType type; @@ -40,6 +49,7 @@ class PendingBitcoinTransaction with PendingTransaction { final bool isSendAll; final bool hasChange; final bool hasTaprootInputs; + final bool isViewOnly; List utxos; bool isMweb; String? changeAddressOverride; @@ -49,6 +59,8 @@ class PendingBitcoinTransaction with PendingTransaction { final Map? publicKeys; Future Function()? commitOverride; + Uint8List? unsignedPsbt; + @override String get id => idOverride ?? _tx.txId(); @@ -89,9 +101,11 @@ class PendingBitcoinTransaction with PendingTransaction { try { final change = _tx.outputs.firstWhere((out) => out.isChange); if (changeAddressOverride != null) { - return PendingChange(changeAddressOverride!, BtcUtils.fromSatoshi(change.amount)); + return PendingChange( + changeAddressOverride!, BtcUtils.fromSatoshi(change.amount)); } - return PendingChange(change.scriptPubKey.toAddress(), BtcUtils.fromSatoshi(change.amount)); + return PendingChange( + change.scriptPubKey.toAddress(), BtcUtils.fromSatoshi(change.amount)); } catch (_) { return null; } @@ -140,12 +154,14 @@ class PendingBitcoinTransaction with PendingTransaction { Future _ltcCommit() async { try { - final resp = await CwMweb.broadcast(BroadcastRequest(rawTx: BytesUtils.fromHexString(hex))); + final resp = await CwMweb.broadcast( + BroadcastRequest(rawTx: BytesUtils.fromHexString(hex))); idOverride = resp.txid; } on GrpcError catch (e) { throw BitcoinTransactionCommitFailed(errorMessage: e.message); } catch (e) { - throw BitcoinTransactionCommitFailed(errorMessage: "Unknown error: ${e.toString()}"); + throw BitcoinTransactionCommitFailed( + errorMessage: "Unknown error: ${e.toString()}"); } } @@ -164,7 +180,8 @@ class PendingBitcoinTransaction with PendingTransaction { _listeners.forEach((listener) => listener(transactionInfo())); } - void addListener(void Function(ElectrumTransactionInfo transaction) listener) => + void addListener( + void Function(ElectrumTransactionInfo transaction) listener) => _listeners.add(listener); ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type, @@ -179,9 +196,40 @@ class PendingBitcoinTransaction with PendingTransaction { inputAddresses: _tx.inputs.map((input) => input.txId).toList(), outputAddresses: outputAddresses, fee: fee); - + @override - Future commitUR() { - throw UnimplementedError(); + bool shouldCommitUR() => isViewOnly; + + @override + Future> commitUR() { + var sourceBytes = unsignedPsbt!; + var cborEncoder = CBOREncoder(); + cborEncoder.encodeBytes(sourceBytes); + var ur = UR("psbt", cborEncoder.getBytes()); + var urLegacy = UR("crypto-psbt", cborEncoder.getBytes()); + // var ur = UR("psbt", Uint8List.fromList(List.generate(64*1024, (int x) => x % 256))); + var encoded = UREncoder(ur, 120); + var encodedLegacy = UREncoder(urLegacy, 120); + List values = []; + List valuesLegacy = []; + while (!encoded.isComplete) { + values.add(encoded.nextPart()); + valuesLegacy.add(encodedLegacy.nextPart()); + } + + final bbqrObj = BBQRPsbt.fromUint8List(sourceBytes); + List bbqr = [ + bbqrObj.asString(), + ]; + while (!bbqrObj.isDone) { + bbqrObj.next(); + bbqr.add(bbqrObj.asString()); + } + + return Future.value({ + "PSBT (bcur)": values.join("\n"), + "PSBT (bbqr)": bbqr.join("\n"), + "PSBT (bcur legacy)": valuesLegacy.join("\n"), + }); } } diff --git a/cw_bitcoin/lib/psbt/transaction_builder.dart b/cw_bitcoin/lib/psbt/transaction_builder.dart index 8cb979730..4692e0906 100644 --- a/cw_bitcoin/lib/psbt/transaction_builder.dart +++ b/cw_bitcoin/lib/psbt/transaction_builder.dart @@ -1,7 +1,9 @@ +import 'dart:convert'; import 'dart:typed_data'; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:convert/convert.dart'; +import 'package:cw_core/output_info.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:ledger_bitcoin/psbt.dart'; @@ -9,7 +11,7 @@ class PSBTTransactionBuild { final PsbtV2 psbt = PsbtV2(); PSBTTransactionBuild( - {required List inputs, required List outputs, bool enableRBF = true}) { + {required List inputs, required List outputs, required List cwOutputs, bool enableRBF = true}) { psbt.setGlobalTxVersion(2); psbt.setGlobalInputCount(inputs.length); psbt.setGlobalOutputCount(outputs.length); @@ -24,7 +26,7 @@ class PSBTTransactionBuild { psbt.setInputPreviousTxId(i, Uint8List.fromList(hex.decode(input.utxo.txHash).reversed.toList())); psbt.setInputOutputIndex(i, input.utxo.vout); psbt.setInputSequence(i, enableRBF ? 0x1 : 0xffffffff); - + if (input.utxo.isSegwit()) { setInputSegwit(i, input); @@ -43,6 +45,33 @@ class PSBTTransactionBuild { if (output is BitcoinOutput) { psbt.setOutputScript(i, Uint8List.fromList(output.address.toScriptPubKey().toBytes())); psbt.setOutputAmount(i, output.value.toInt()); + if (cwOutputs.isNotEmpty) { + try { + final cwOutput = cwOutputs + .where((e) => [e.address, e.extractedAddress] + .map((e) => e?.toLowerCase()) + .contains(output.address.toAddress().toLowerCase())) + .firstOrNull; + if (cwOutput != null) { + final bip353Name = utf8.encode(cwOutput.extra['bip353_name'] as String); + final bip353Proof = base64.decode(cwOutput.extra['bip353_proof'] as String); + + if (bip353Name.length > 255) { + printV('BIP353 name is too long, skipping'); + continue; + } + final proof = Uint8List.fromList([ + bip353Name.length, + ...bip353Name, + ...bip353Proof, + ]); + + psbt.setOutputDNSSECProof(i, proof); + } + } catch (e) { + printV('Error setting DNSSEC proof: $e'); + } + } } } } diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index c68e850e4..58eb5e7f7 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -46,6 +46,15 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + bbqrdart: + dependency: "direct main" + description: + path: "." + ref: b0f3f72911221bafed0ab77359fccd64bc79d524 + resolved-ref: b0f3f72911221bafed0ab77359fccd64bc79d524 + url: "https://github.com/mrcyjanek/bbqrdart" + source: git + version: "1.0.0" bech32: dependency: "direct main" description: @@ -588,8 +597,8 @@ packages: dependency: "direct main" description: path: "packages/ledger-bitcoin" - ref: trunk - resolved-ref: e93254f3ff3f996fb91f65a1e7ceffb9f510b4c8 + ref: e8ab1a98493dfdd2277edde07a66b8d378ca70f6 + resolved-ref: e8ab1a98493dfdd2277edde07a66b8d378ca70f6 url: "https://github.com/cake-tech/ledger-flutter-plus-plugins" source: git version: "0.0.3" @@ -1101,6 +1110,15 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.0" + ur: + dependency: "direct main" + description: + path: "." + ref: "5738f70d0ec3d50977ac3dd01fed62939600238b" + resolved-ref: "5738f70d0ec3d50977ac3dd01fed62939600238b" + url: "https://github.com/bukata-sa/bc-ur-dart" + source: git + version: "0.1.0" vector_math: dependency: transitive description: @@ -1182,5 +1200,5 @@ packages: source: hosted version: "2.2.2" sdks: - dart: ">=3.6.0 <4.0.0" + dart: ">=3.6.2 <4.0.0" flutter: ">=3.27.0" diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 1fca85c31..062ac025b 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -49,7 +49,7 @@ dependencies: git: url: https://github.com/cake-tech/ledger-flutter-plus-plugins path: packages/ledger-bitcoin - ref: trunk + ref: e8ab1a98493dfdd2277edde07a66b8d378ca70f6 ledger_litecoin: git: url: https://github.com/cake-tech/ledger-flutter-plus-plugins @@ -58,6 +58,14 @@ dependencies: git: url: https://github.com/sneurlax/socks_socket ref: e6232c53c1595469931ababa878759a067c02e94 + ur: + git: + url: https://github.com/bukata-sa/bc-ur-dart + ref: 5738f70d0ec3d50977ac3dd01fed62939600238b + bbqrdart: + git: + url: https://github.com/mrcyjanek/bbqrdart + ref: b0f3f72911221bafed0ab77359fccd64bc79d524 dev_dependencies: flutter_test: diff --git a/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart b/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart index 6f1b6a6f7..24dc1970d 100644 --- a/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart +++ b/cw_bitcoin_cash/lib/src/pending_bitcoin_cash_transaction.dart @@ -89,7 +89,7 @@ class PendingBitcoinCashTransaction with PendingTransaction { isReplaced: false, ); @override - Future commitUR() { + Future> commitUR() { throw UnimplementedError(); } } diff --git a/cw_core/lib/output_info.dart b/cw_core/lib/output_info.dart index 9e3ac4ffc..1db1c2ed6 100644 --- a/cw_core/lib/output_info.dart +++ b/cw_core/lib/output_info.dart @@ -8,7 +8,8 @@ class OutputInfo { this.fiatAmount, this.note, this.extractedAddress, - this.memo}); + this.memo, + this.extra = const {}}); final String? fiatAmount; final String? cryptoAmount; @@ -19,4 +20,5 @@ class OutputInfo { final bool isParsedAddress; final int? formattedCryptoAmount; final String? memo; + final Map extra; } \ No newline at end of file diff --git a/cw_core/lib/pending_transaction.dart b/cw_core/lib/pending_transaction.dart index a06ddfcc8..599047fbd 100644 --- a/cw_core/lib/pending_transaction.dart +++ b/cw_core/lib/pending_transaction.dart @@ -18,5 +18,5 @@ mixin PendingTransaction { bool shouldCommitUR() => false; Future commit(); - Future commitUR(); + Future> commitUR(); } diff --git a/cw_decred/lib/pending_transaction.dart b/cw_decred/lib/pending_transaction.dart index 53699b60d..23c76920c 100644 --- a/cw_decred/lib/pending_transaction.dart +++ b/cw_decred/lib/pending_transaction.dart @@ -36,7 +36,7 @@ class DecredPendingTransaction with PendingTransaction { } @override - Future commitUR() { + Future> commitUR() { throw UnimplementedError(); } } diff --git a/cw_evm/lib/pending_evm_chain_transaction.dart b/cw_evm/lib/pending_evm_chain_transaction.dart index 921a4a849..50213d6a4 100644 --- a/cw_evm/lib/pending_evm_chain_transaction.dart +++ b/cw_evm/lib/pending_evm_chain_transaction.dart @@ -53,7 +53,7 @@ class PendingEVMChainTransaction with PendingTransaction { } @override - Future commitUR() { + Future> commitUR() { throw UnimplementedError(); } } diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 029b642d5..40e544c4a 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -354,14 +354,23 @@ abstract class MoneroWalletBase extends WalletBase exportOutputsUR() { + final str = currentWallet!.exportOutputsUR(all: false); + int status = currentWallet!.status(); if (status != 0) { final err = currentWallet!.errorString(); - throw MoneroTransactionCreationException("unable to export UR: $err"); + throw MoneroTransactionCreationException("unable to export outputs: $err"); } - return str; + final strAll = currentWallet!.exportOutputsUR(all: true); + status = currentWallet!.status(); + if (status != 0) { + final err = currentWallet!.errorString(); + throw MoneroTransactionCreationException("unable to export outputs: $err"); + } + return { + "Outputs (partial)": str, + "Outputs (all)": strAll, + }; } bool needExportOutputs(int amount) { diff --git a/cw_monero/lib/pending_monero_transaction.dart b/cw_monero/lib/pending_monero_transaction.dart index 760c6baa4..e1d191eac 100644 --- a/cw_monero/lib/pending_monero_transaction.dart +++ b/cw_monero/lib/pending_monero_transaction.dart @@ -42,6 +42,7 @@ class PendingMoneroTransaction with PendingTransaction { String get feeFormattedValue => AmountConverter.amountIntToString( CryptoCurrency.xmr, pendingTransactionDescription.fee); + @override bool shouldCommitUR() => isViewOnly; @override @@ -67,7 +68,7 @@ class PendingMoneroTransaction with PendingTransaction { } @override - Future commitUR() async { + Future> commitUR() async { try { final ret = await monero_transaction_history.commitTransactionFromPointerAddress( address: pendingTransactionDescription.pointerAddress, @@ -77,7 +78,10 @@ class PendingMoneroTransaction with PendingTransaction { await Future.delayed(const Duration(milliseconds: 250)); await wallet.fetchTransactions(); }()); - return ret; + if (ret == null) return {}; + return { + "xmr-txsigned": ret, + }; } catch (e) { final message = e.toString(); diff --git a/cw_nano/lib/pending_nano_transaction.dart b/cw_nano/lib/pending_nano_transaction.dart index 1530dcea4..8a759f55b 100644 --- a/cw_nano/lib/pending_nano_transaction.dart +++ b/cw_nano/lib/pending_nano_transaction.dart @@ -42,7 +42,7 @@ class PendingNanoTransaction with PendingTransaction { } @override - Future commitUR() { + Future> commitUR() { throw UnimplementedError(); } } diff --git a/cw_solana/lib/pending_solana_transaction.dart b/cw_solana/lib/pending_solana_transaction.dart index e21b00e86..8ba1c4b9e 100644 --- a/cw_solana/lib/pending_solana_transaction.dart +++ b/cw_solana/lib/pending_solana_transaction.dart @@ -44,7 +44,7 @@ class PendingSolanaTransaction with PendingTransaction { String get id => ''; @override - Future commitUR() { + Future> commitUR() { throw UnimplementedError(); } } diff --git a/cw_tron/lib/pending_tron_transaction.dart b/cw_tron/lib/pending_tron_transaction.dart index c1061db38..b1d601f8e 100644 --- a/cw_tron/lib/pending_tron_transaction.dart +++ b/cw_tron/lib/pending_tron_transaction.dart @@ -35,7 +35,7 @@ class PendingTronTransaction with PendingTransaction { String get id => ''; @override - Future commitUR() { + Future> commitUR() { throw UnimplementedError(); } } diff --git a/cw_wownero/lib/pending_wownero_transaction.dart b/cw_wownero/lib/pending_wownero_transaction.dart index e908e6e2c..b8aa7c4ed 100644 --- a/cw_wownero/lib/pending_wownero_transaction.dart +++ b/cw_wownero/lib/pending_wownero_transaction.dart @@ -55,7 +55,7 @@ class PendingWowneroTransaction with PendingTransaction { } @override - Future commitUR() { + Future> commitUR() { throw UnimplementedError(); } } diff --git a/cw_zano/lib/model/pending_zano_transaction.dart b/cw_zano/lib/model/pending_zano_transaction.dart index fd2ab45fe..415e58303 100644 --- a/cw_zano/lib/model/pending_zano_transaction.dart +++ b/cw_zano/lib/model/pending_zano_transaction.dart @@ -49,7 +49,7 @@ class PendingZanoTransaction with PendingTransaction { } @override - Future commitUR() { + Future> commitUR() { throw UnimplementedError(); } } diff --git a/docs/builds/ANDROID.md b/docs/builds/ANDROID.md index d7bc584ad..2461df4bd 100644 --- a/docs/builds/ANDROID.md +++ b/docs/builds/ANDROID.md @@ -18,8 +18,8 @@ In order to build the latest version of Cake Wallet, simply run the following: git clone --branch main https://github.com/cake-tech/cake_wallet.git # NOTE: Replace `main` with the latest release tag available at https://github.com/cake-tech/cake_wallet/releases/latest. cd cake_wallet -# docker build -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly . # Uncomment to build the docker image yourself instead of pulling it from the registry -docker run -v$(pwd):$(pwd) -w $(pwd) -i --rm ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly bash -x << EOF +# docker build -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-ndkr28-go1.24.1-ruststablenightly . # Uncomment to build the docker image yourself instead of pulling it from the registry +docker run -v$(pwd):$(pwd) -w $(pwd) -i --rm ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-ndkr28-go1.24.1-ruststablenightly bash -x << EOF set -x -e pushd scripts/android source ./app_env.sh cakewallet diff --git a/docs/builds/IOS.md b/docs/builds/IOS.md index 1d23b917c..44abaa805 100644 --- a/docs/builds/IOS.md +++ b/docs/builds/IOS.md @@ -7,7 +7,7 @@ The following are the system requirements to build Cake Wallet for your iOS devi ```txt macOS 15.3.1 Xcode 16.2 -Flutter 3.27.0 +Flutter 3.27.4 ``` NOTE: Newer versions of macOS and Xcode may also work, but have not been confirmed to work by the Cake team. @@ -43,9 +43,9 @@ To enable iOS build support for Xcode, perform the following: ### 3. Installing Flutter -Install Flutter, specifically version `3.27.0` by following the [official docs](https://docs.flutter.dev/get-started/install/macos/desktop?tab=download). +Install Flutter, specifically version `3.27.4` by following the [official docs](https://docs.flutter.dev/get-started/install/macos/desktop?tab=download). -NOTE: as `3.27.0` is not the latest version, you'll need to download it from instead of the link in the docs above. +NOTE: as `3.27.4` is not the latest version, you'll need to download it from instead of the link in the docs above. ### 4. Installing Rust @@ -65,7 +65,7 @@ The output of this command should appear like this, indicating successful instal ```zsh Doctor summary (to see all details, run flutter doctor -v): -[✓] Flutter (Channel stable, 3.27.0, on macOS 15.x.x) +[✓] Flutter (Channel stable, 3.27.4, on macOS 15.x.x) [✓] Xcode - develop for iOS and macOS (Xcode 16.2) ``` diff --git a/docs/builds/LINUX.md b/docs/builds/LINUX.md index 0f438a1d6..7207f125c 100644 --- a/docs/builds/LINUX.md +++ b/docs/builds/LINUX.md @@ -20,8 +20,8 @@ In order to build the latest version of Cake Wallet, simply run the following: git clone --branch main https://github.com/cake-tech/cake_wallet.git # NOTE: Replace `main` with the latest release tag available at https://github.com/cake-tech/cake_wallet/releases/latest. cd cake_wallet -# docker build -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly . # Uncomment to build the docker image yourself instead of pulling it from the registry -docker run --privileged -v$(pwd):$(pwd) -w $(pwd) -i --rm ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly bash -x << EOF +# docker build -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-ndkr28-go1.24.1-ruststablenightly . # Uncomment to build the docker image yourself instead of pulling it from the registry +docker run --privileged -v$(pwd):$(pwd) -w $(pwd) -i --rm ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-ndkr28-go1.24.1-ruststablenightly bash -x << EOF set -x -e pushd scripts ./gen_android_manifest.sh diff --git a/docs/builds/MACOS.md b/docs/builds/MACOS.md index 7e0f39aab..46a9842a4 100644 --- a/docs/builds/MACOS.md +++ b/docs/builds/MACOS.md @@ -7,7 +7,7 @@ The following are the system requirements to build Cake Wallet for your macOS de ```txt macOS 15.3.1 Xcode 16.2 -Flutter 3.27.0 +Flutter 3.27.4 ``` ### 1. Installing dependencies @@ -34,9 +34,9 @@ sudo xcodebuild -runFirstLaunch ### 3. Installing Flutter -Install Flutter, specifically version `3.27.0` by following the [official docs](https://docs.flutter.dev/get-started/install/macos/desktop?tab=download). +Install Flutter, specifically version `3.27.4` by following the [official docs](https://docs.flutter.dev/get-started/install/macos/desktop?tab=download). -NOTE: as `3.27.0` is not the latest version, you'll need to download it from instead of the link in the docs above. +NOTE: as `3.27.4` is not the latest version, you'll need to download it from instead of the link in the docs above. ### 4. Installing Rust @@ -56,7 +56,7 @@ The output of this command should appear like this, indicating successful instal ```zsh Doctor summary (to see all details, run flutter doctor -v): -[✓] Flutter (Channel stable, 3.27.0, on macOS 15.x.x) +[✓] Flutter (Channel stable, 3.27.4, on macOS 15.x.x) ... [✓] Xcode - develop for iOS and macOS (Xcode 16.2) ... diff --git a/docs/builds/WINDOWS.md b/docs/builds/WINDOWS.md index 7a86dac49..4fec78dc0 100644 --- a/docs/builds/WINDOWS.md +++ b/docs/builds/WINDOWS.md @@ -6,18 +6,18 @@ The following are the system requirements to build Cake Wallet for your Windows ```txt Windows 10 or later (64-bit), x86-64 based -Flutter 3.27.0 +Flutter 3.27.4 ``` ### 1. Installing Flutter -Install Flutter, specifically version `3.27.0` by following the [official docs](https://docs.flutter.dev/get-started/install/windows). +Install Flutter, specifically version `3.27.4` by following the [official docs](https://docs.flutter.dev/get-started/install/windows). In order for Flutter to function, you'll also need to enable Developer Mode: Start Menu > search for "Run" > type `ms-settings:developers`, and turn on Developer Mode. -NOTE: as `3.27.0` is not the latest version, you'll need to download it from instead of the link in the docs above. +NOTE: as `3.27.4` is not the latest version, you'll need to download it from instead of the link in the docs above. ### 2. Install Development Tools diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 630474f22..2a189c181 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -18,6 +18,14 @@ class CWBitcoin extends Bitcoin { passphrase: passphrase, ); + @override + WalletCredentials createBitcoinWalletFromKeys({ + required String name, + required String password, + required String xpub, + }) => + BitcoinWalletFromKeysCredentials(name: name, password: password, xpub: xpub); + @override WalletCredentials createBitcoinRestoreWalletFromWIFCredentials( {required String name, @@ -62,11 +70,7 @@ class CWBitcoin extends Bitcoin { final bitcoinWallet = wallet as ElectrumWallet; final keys = bitcoinWallet.keys; - return { - 'wif': keys.wif, - 'privateKey': keys.privateKey, - 'publicKey': keys.publicKey - }; + return bitcoinWallet.keys.toJson(); } @override @@ -126,7 +130,8 @@ class CWBitcoin extends Bitcoin { extractedAddress: out.extractedAddress, isParsedAddress: out.isParsedAddress, formattedCryptoAmount: out.formattedCryptoAmount, - memo: out.memo)) + memo: out.memo, + extra: out.extra)) .toList(), priority: priority as BitcoinTransactionPriority, feeRate: bitcoinFeeRate, @@ -275,7 +280,14 @@ class CWBitcoin extends Bitcoin { } @override - List getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all; + List getBitcoinReceivePageOptions(Object wallet) { + final bitcoinWallet = wallet as ElectrumWallet; + final keys = bitcoinWallet.keys; + if (keys.privateKey.isEmpty) { + return BitcoinReceivePageOption.allViewOnly; + } + return BitcoinReceivePageOption.all; + } @override List getLitecoinReceivePageOptions() { @@ -727,6 +739,12 @@ class CWBitcoin extends Bitcoin { } } + @override + Future commitPsbtUR(Object wallet, List urCodes) { + final _wallet = wallet as BitcoinWalletBase; + return _wallet.commitPsbtUR(urCodes); + } + @override String getPayjoinEndpoint(Object wallet) { final _wallet = wallet as ElectrumWallet; diff --git a/lib/di.dart b/lib/di.dart index 32f7cab0e..7cf32ed0b 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -978,7 +978,7 @@ Future setup({ getIt.registerFactory(() => AnimatedURModel(getIt.get())); - getIt.registerFactoryParam((String urQr, _) => + getIt.registerFactoryParam, void>((Map urQr, _) => AnimatedURPage(getIt.get(), urQr: urQr)); getIt.registerFactoryParam( diff --git a/lib/entities/bip_353_record.dart b/lib/entities/bip_353_record.dart index 8685185da..e8451fd79 100644 --- a/lib/entities/bip_353_record.dart +++ b/lib/entities/bip_353_record.dart @@ -1,3 +1,6 @@ +import 'dart:convert'; +import 'dart:isolate'; + import 'package:basic_utils/basic_utils.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/widgets/alert_with_picker_option.dart'; @@ -5,6 +8,7 @@ import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/utils/print_verbose.dart'; +import 'package:dnssec_proof/dnssec_proof.dart'; import 'package:flutter/material.dart'; class Bip353Record { @@ -22,6 +26,19 @@ class Bip353Record { 'address': 'On-Chain Address', }; + static Future fetchDnsProof(String bip353Name) async { + if (bip353Name.startsWith('₿')) { + bip353Name = bip353Name.substring(1); + } + final parts = bip353Name.split('@'); + if (parts.length != 2) return null; + final userPart = parts[0]; + final domainPart = parts[1]; + final bip353Domain = '$userPart.user._bitcoin-payment.$domainPart.'; + final proof = await Isolate.run(() => DnsProver.getTxtProof(bip353Domain)); + return base64.encode(proof); + } + static Future?> fetchUriByCryptoCurrency( String bip353Name, String asset) async { try { @@ -30,7 +47,10 @@ class Bip353Record { if (parts.length != 2) return null; - final userPart = parts[0]; + String userPart = parts[0]; + if (userPart.startsWith('₿')) { + userPart = userPart.substring(1); + } final domainPart = parts[1]; // 2. Construct the correct subdomain: "user._bitcoin-payment.domain" @@ -42,6 +62,10 @@ class Bip353Record { RRecordType.TXT, dnssec: true, ); + final proof = await fetchDnsProof(bip353Name); + if (proof == null) { + throw Exception('DNSSEC proof not found'); + } if (txtRecords == null) return null; diff --git a/lib/entities/parse_address_from_domain.dart b/lib/entities/parse_address_from_domain.dart index 01e6322cc..fa7184e8e 100644 --- a/lib/entities/parse_address_from_domain.dart +++ b/lib/entities/parse_address_from_domain.dart @@ -344,7 +344,13 @@ class AddressResolver { if (bip353AddressMap != null && bip353AddressMap.isNotEmpty) { final chosenAddress = await Bip353Record.pickBip353AddressChoice(context, text, bip353AddressMap); if (chosenAddress != null) { - return ParsedAddress.fetchBip353AddressAddress(address: chosenAddress, name: text); + try { + final dnsProof = await Bip353Record.fetchDnsProof(text); + return ParsedAddress.fetchBip353AddressAddress(address: chosenAddress, name: text, dnsProof: dnsProof); + } catch (e) { + printV('Bip353Record.fetchBip353AddressAddress error: $e'); + return ParsedAddress.fetchBip353AddressAddress(address: chosenAddress, name: text); + } } } diff --git a/lib/entities/parsed_address.dart b/lib/entities/parsed_address.dart index 1f2529221..74acab80a 100644 --- a/lib/entities/parsed_address.dart +++ b/lib/entities/parsed_address.dart @@ -26,6 +26,7 @@ class ParsedAddress { this.profileImageUrl = '', this.profileName = '', this.parseFrom = ParseFrom.notParsed, + this.bip353DnsProof, }); factory ParsedAddress.fetchEmojiAddress({ @@ -59,11 +60,13 @@ class ParsedAddress { factory ParsedAddress.fetchBip353AddressAddress ({ required String address, required String name, + String? dnsProof, }) { return ParsedAddress( addresses: [address], name: name, parseFrom: ParseFrom.bip353, + bip353DnsProof: dnsProof, ); } @@ -178,4 +181,5 @@ class ParsedAddress { final String profileImageUrl; final String profileName; final ParseFrom parseFrom; + final String? bip353DnsProof; } diff --git a/lib/monero/cw_monero.dart b/lib/monero/cw_monero.dart index 257ff895e..9c51749e8 100644 --- a/lib/monero/cw_monero.dart +++ b/lib/monero/cw_monero.dart @@ -399,9 +399,9 @@ class CWMonero extends Monero { } @override - String exportOutputsUR(Object wallet, bool all) { + Map exportOutputsUR(Object wallet) { final moneroWallet = wallet as MoneroWallet; - return moneroWallet.exportOutputsUR(all); + return moneroWallet.exportOutputsUR(); } @override diff --git a/lib/src/screens/receive/widgets/qr_widget.dart b/lib/src/screens/receive/widgets/qr_widget.dart index 0018e632a..d6d53ee09 100644 --- a/lib/src/screens/receive/widgets/qr_widget.dart +++ b/lib/src/screens/receive/widgets/qr_widget.dart @@ -115,7 +115,8 @@ class QRWidget extends StatelessWidget { ), ), if (addressListViewModel.isPayjoinUnavailable && - !addressListViewModel.isSilentPayments) ...[ + !addressListViewModel.isSilentPayments && + !addressListViewModel.isCupcake) ...[ GestureDetector( onTap: () => _onPayjoinInactivePressed(context), child: Row( diff --git a/lib/src/screens/restore/restore_options_page.dart b/lib/src/screens/restore/restore_options_page.dart index 7f6e1f814..ea1bff6bb 100644 --- a/lib/src/screens/restore/restore_options_page.dart +++ b/lib/src/screens/restore/restore_options_page.dart @@ -6,6 +6,7 @@ import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/option_tile.dart'; import 'package:cake_wallet/utils/device_info.dart'; +import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/utils/permission_handler.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; @@ -53,13 +54,40 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> { return true; } + String get imageRestoreHWPath => + widget.themeType == ThemeType.dark + ? 'assets/images/restore_hw_dark.png' + : 'assets/images/restore_hw.png'; + + String get imageRestoreCupcakePath => + widget.themeType == ThemeType.dark + ? 'assets/images/restore_cupcake_dark.png' + : 'assets/images/restore_cupcake.png'; + + String get imageRestoreQRPath => + widget.themeType == ThemeType.dark + ? 'assets/images/restore_qr_dark.png' + : 'assets/images/restore_qr.png'; + + + String get imageRestoreHotWalletPath => + widget.themeType == ThemeType.dark + ? 'assets/images/restore_hot_wallet_dark.png' + : 'assets/images/restore_hot_wallet.png'; + + + String get imageRestoreBackupPath => + widget.themeType == ThemeType.dark + ? 'assets/images/restore_backup_dark.png' + : 'assets/images/restore_backup.png'; + @override Widget build(BuildContext context) { - final imageColor = Theme.of(context).colorScheme.primary; - final imageLedger = Image.asset('assets/images/hardware_wallet/ledger_nano_x.png', - width: 40, color: imageColor); - final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png', color: imageColor); - final imageBackup = Image.asset('assets/images/backup.png', color: imageColor); + final imageRestoreHW = Image.asset(imageRestoreHWPath, width: 55); + final imageRestoreCupcake = Image.asset(imageRestoreCupcakePath, width: 55); + final imageRestoreQR = Image.asset(imageRestoreQRPath, width: 55); + final imageSeedKeys = Image.asset(imageRestoreHotWalletPath, width: 55); + final imageRestoreBackup = Image.asset(imageRestoreBackupPath, width: 55); return Center( child: Container( @@ -76,15 +104,16 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> { title: S.of(context).restore_title_from_seed_keys, description: S.of(context).restore_description_from_seed_keys, ), - if (widget.isNewInstall) + if (FeatureFlag.hasBitcoinViewOnly && DeviceInfo.instance.isMobile) Padding( padding: EdgeInsets.only(top: 24), child: OptionTile( - key: ValueKey('restore_options_from_backup_button_key'), - onPressed: () => Navigator.pushNamed(context, Routes.restoreFromBackup), - image: imageBackup, - title: S.of(context).restore_title_from_backup, - description: S.of(context).restore_description_from_backup, + key: ValueKey('restore_options_from_cupcake_button_key'), + onPressed: () => _onScanQRCode(context), + image: imageRestoreCupcake, + title: S.of(context).restore_title_from_cupcake, + description: S.of(context).restore_description_from_cupcake, + tag: S.of(context).new_tag, ), ), if (_doesSupportHardwareWallets) @@ -94,25 +123,33 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> { key: ValueKey('restore_options_from_hardware_wallet_button_key'), onPressed: () => Navigator.pushNamed(context, Routes.restoreWalletFromHardwareWallet), - image: imageLedger, + image: imageRestoreHW, title: S.of(context).restore_title_from_hardware_wallet, description: S.of(context).restore_description_from_hardware_wallet, ), ), + if (widget.isNewInstall) + Padding( + padding: EdgeInsets.only(top: 24), + child: OptionTile( + key: ValueKey('restore_options_from_backup_button_key'), + onPressed: () => Navigator.pushNamed(context, Routes.restoreFromBackup), + image: imageRestoreBackup, + title: S.of(context).restore_title_from_backup, + description: S.of(context).restore_description_from_backup, + ), + ), if (DeviceInfo.instance.isMobile) Padding( padding: EdgeInsets.only(top: 24), child: OptionTile( key: ValueKey('restore_options_from_qr_button_key'), onPressed: () => _onScanQRCode(context), - icon: Icon( - Icons.qr_code_rounded, - color: imageColor, - size: 50, - ), + image: imageRestoreQR, title: S.of(context).scan_qr_code, - description: S.of(context).cold_or_recover_wallet), - ) + description: S.of(context).cold_or_recover_wallet, + ), + ), ], ), )), diff --git a/lib/src/screens/restore/wallet_restore_from_keys_form.dart b/lib/src/screens/restore/wallet_restore_from_keys_form.dart index 9ca5c2508..5713ad7d0 100644 --- a/lib/src/screens/restore/wallet_restore_from_keys_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_keys_form.dart @@ -198,14 +198,14 @@ class WalletRestoreFromKeysFormState extends State { Widget _restoreFromKeysFormFields() { // Decred can only restore a view only wallet with an account pubkey. Other // fields are not used. - if (widget.walletRestoreViewModel.type == WalletType.decred) { + if (widget.walletRestoreViewModel.onlyViewKeyRestore) { return Column( children: [ BaseTextFormField( controller: viewKeyController, hintText: S.of(context).view_key_public, maxLines: null, - ) + ), ], ); } @@ -253,13 +253,14 @@ class WalletRestoreFromKeysFormState extends State { maxLines: null, ), ), - BlockchainHeightWidget( - key: blockchainHeightKey, - hasDatePicker: widget.walletRestoreViewModel.type != WalletType.haven, - onHeightChange: (_) => null, - onHeightOrDateEntered: widget.onHeightOrDateEntered, - walletType: widget.walletRestoreViewModel.type, - ), + if (widget.walletRestoreViewModel.hasBlockchainHeightSelector) + BlockchainHeightWidget( + key: blockchainHeightKey, + hasDatePicker: widget.walletRestoreViewModel.type != WalletType.haven, + onHeightChange: (_) => null, + onHeightOrDateEntered: widget.onHeightOrDateEntered, + walletType: widget.walletRestoreViewModel.type, + ), ], ); } diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index dd4a284be..411178c83 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -199,7 +199,7 @@ class WalletRestorePage extends BasePage { credentials['seed'] = walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.text; - if (walletRestoreViewModel.hasBlockchainHeightLanguageSelector) { + if (walletRestoreViewModel.hasBlockchainHeightSelector) { credentials['height'] = walletRestoreFromSeedFormKey.currentState!.blockchainHeightKey.currentState?.height ?? -1; @@ -219,7 +219,7 @@ class WalletRestorePage extends BasePage { credentials['name'] = walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text; credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text; - if (walletRestoreViewModel.type != WalletType.decred) { + if (!walletRestoreViewModel.onlyViewKeyRestore) { credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text; credentials['spendKey'] = @@ -519,7 +519,7 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody> } }, onViewKeyEntered: (bool entered) { - if (walletRestoreViewModel.type == WalletType.decred) { + if (widget.walletRestoreViewModel.onlyViewKeyRestore) { walletRestoreViewModel.isButtonEnabled = entered; } }, @@ -536,7 +536,7 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody> key: widget.walletRestoreFromSeedFormKey, restoredWallet: walletRestoreViewModel.restoredWallet, seedSettingsViewModel: widget.seedSettingsViewModel, - displayBlockHeightSelector: widget.walletRestoreViewModel.hasBlockchainHeightLanguageSelector, + displayBlockHeightSelector: widget.walletRestoreViewModel.hasBlockchainHeightSelector, displayLanguageSelector: widget.walletRestoreViewModel.hasSeedLanguageSelector, type: widget.walletRestoreViewModel.type, blockHeightFocusNode: widget.blockHeightFocusNode, @@ -563,7 +563,7 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody> } void _validateOnChange({bool isPolyseed = false}) { - if (!isPolyseed && walletRestoreViewModel.hasBlockchainHeightLanguageSelector) { + if (!isPolyseed && walletRestoreViewModel.hasBlockchainHeightSelector) { final hasHeight = walletRestoreFromSeedFormKey .currentState?.blockchainHeightKey.currentState?.restoreHeightController.text.isNotEmpty; diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index 32951a7af..e75cba9ed 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -450,7 +450,7 @@ class SendPage extends BasePage { } if (monero!.needExportOutputs(sendViewModel.wallet, amount)) { await Navigator.of(context).pushNamed(Routes.urqrAnimatedPage, - arguments: 'export-outputs'); + arguments: monero!.exportOutputsUR(sendViewModel.wallet)); await Future.delayed( Duration(seconds: 1)); // wait for monero to refresh the state } diff --git a/lib/src/screens/ur/animated_ur_page.dart b/lib/src/screens/ur/animated_ur_page.dart index a4ef3517f..2bd9276b5 100644 --- a/lib/src/screens/ur/animated_ur_page.dart +++ b/lib/src/screens/ur/animated_ur_page.dart @@ -1,5 +1,7 @@ import 'dart:async'; +import 'package:bbqrdart/bbqrdart.dart'; +import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/entities/qr_scanner.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/monero/monero.dart'; @@ -7,31 +9,49 @@ import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/animated_ur_model.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; // ur:xmr-txunsigned - unsigned transaction // should show a scanner afterwards. class AnimatedURPage extends BasePage { final bool isAll; - AnimatedURPage(this.animatedURmodel, {required String urQr, this.isAll = false}) { - if (urQr == "export-outputs") { - this.urQr = monero!.exportOutputsUR(animatedURmodel.wallet, false); - } else if (urQr == "export-outputs-all") { - this.urQr = monero!.exportOutputsUR(animatedURmodel.wallet, true); - } else { - this.urQr = urQr; + AnimatedURPage(this.animatedURmodel, { + required Map this.urQr, + this.isAll = false, + }) { + if (urQr.keys.first.startsWith("export-outputs") && animatedURmodel.wallet.type == WalletType.monero) { + if (animatedURmodel.wallet.type == WalletType.monero) { + urQr = monero!.exportOutputsUR(animatedURmodel.wallet); + } else { + throw UnimplementedError("unable to handle UR: ${urQr.keys.first}"); + } + } + for (var key in urQr.keys) { + if (key.isEmpty || urQr[key]!.isEmpty) { + urQr.remove(key); + continue; + } + urQr[key] = urQr[key]!.trim(); } } - late String urQr; + Map urQr = {}; + final AnimatedURModel animatedURmodel; String get urQrType { - final first = urQr.trim().split("\n")[0]; + if (urQr.values.first.trim().substring(0, 2) == BBQR.header) { + return BBQR.header; + } + if (urQr.isEmpty) return "unknown"; + final first = urQr.values.first.trim().split("\n")[0]; return first.split('/')[0]; } @@ -43,11 +63,11 @@ class AnimatedURPage extends BasePage { Padding( padding: const EdgeInsets.only(top: 64.0), child: URQR( - frames: urQr.trim().split("\n"), + urqr: urQr, ), ), + if (["ur:xmr-txunsigned", "ur:xmr-output", "ur:psbt", BBQR.header].contains(urQrType)) ...{ SizedBox(height: 32), - if (urQrType == "ur:xmr-txunsigned" || urQrType == "ur:xmr-output") Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: SizedBox( @@ -60,30 +80,8 @@ class AnimatedURPage extends BasePage { ), ), ), - SizedBox(height: 32), - if (urQrType == "ur:xmr-output" && !isAll) Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: SizedBox( - width: double.maxFinite, - child: PrimaryButton( - onPressed: () => _exportAll(context), - text: "Export all", - color: Theme.of(context).colorScheme.secondary, - textColor: Theme.of(context).colorScheme.onPrimary, - ), - ), - ), - ], - ); - } - - void _exportAll(BuildContext context) { - Navigator.of(context).pushReplacement( - MaterialPageRoute( - builder: (context) { - return AnimatedURPage(animatedURmodel, urQr: "export-outputs-all", isAll: true); }, - ), + ], ); } @@ -106,6 +104,11 @@ class AnimatedURPage extends BasePage { Navigator.of(context).pop(true); } break; + case "ur:psbt": // psbt + final ur = await presentQRScanner(context); + if (ur == null) return; + await bitcoin!.commitPsbtUR(animatedURmodel.wallet, ur.trim().split("\n")); + Navigator.of(context).pop(true); default: throw UnimplementedError("unable to handle UR: ${urQrType}"); } @@ -124,9 +127,9 @@ class AnimatedURPage extends BasePage { } class URQR extends StatefulWidget { - URQR({super.key, required this.frames}); + URQR({super.key, required this.urqr}); - List frames; + final Map urqr; @override // ignore: library_private_types_in_public_api @@ -160,6 +163,24 @@ class _URQRState extends State { super.dispose(); } + late String selected = (widget.urqr.isEmpty) ? "unknown" : widget.urqr.keys.first; + int selectedInt = 0; + + List get frames { + return widget.urqr[selected]?.split("\n") ?? []; + } + + late String nextLabel = widget.urqr.keys.toList()[(selectedInt + 1) % widget.urqr.length] ; + + void next() { + final keys = widget.urqr.keys.toList(); + setState(() { + selectedInt++; + nextLabel = keys[(selectedInt + 1) % keys.length]; + selected = keys[(selectedInt) % keys.length]; + }); + } + @override Widget build(BuildContext context) { return Column( @@ -168,10 +189,37 @@ class _URQRState extends State { children: [ Center( child: QrImage( - data: widget.frames[frame % widget.frames.length], version: -1, + data: frames[frame % frames.length], + version: -1, size: 400, ), ), + if (widget.urqr.values.length > 1) + SizedBox( + width: double.maxFinite, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: SizedBox( + width: double.maxFinite, + child: PrimaryButton( + onPressed: next, + text: nextLabel, + color: Theme.of(context).colorScheme.primary, + textColor: Theme.of(context).colorScheme.onPrimary, + ), + ), + ), + ), + if (FeatureFlag.hasDevOptions) ...{ + TextButton( + onPressed: () { + Clipboard.setData(ClipboardData(text: """Current frame (${frame % frames.length}): ${frames[frame % frames.length]}, +All frames: + - ${frames.join("\n - ")}""")); + }, + child: Text("[dev] copy debug info"), + ), + } ], ); } diff --git a/lib/src/screens/wallet_keys/wallet_keys_page.dart b/lib/src/screens/wallet_keys/wallet_keys_page.dart index 917891854..6bffd5f12 100644 --- a/lib/src/screens/wallet_keys/wallet_keys_page.dart +++ b/lib/src/screens/wallet_keys/wallet_keys_page.dart @@ -82,7 +82,7 @@ class _WalletKeysPageBodyState extends State showLegacySeedTab = widget.walletKeysViewModel.legacySeedSplit.isNotEmpty; isLegacySeedOnly = widget.walletKeysViewModel.isLegacySeedOnly; - final totalTabs = 1 + (showKeyTab ? 1 : 0) + (showLegacySeedTab ? 1 : 0); + final totalTabs = (_hasSeeds ? 1 : 0) + (showKeyTab ? 1 : 0) + (showLegacySeedTab ? 1 : 0); _tabController = TabController(length: totalTabs, vsync: this); } @@ -126,7 +126,7 @@ class _WalletKeysPageBodyState extends State dividerColor: Colors.transparent, padding: EdgeInsets.zero, tabs: [ - Tab(text: S.of(context).widgets_seed, key: ValueKey('wallet_keys_page_seed')), + if (_hasSeeds) Tab(text: S.of(context).widgets_seed, key: ValueKey('wallet_keys_page_seed')), if (showKeyTab) Tab(text: S.of(context).keys, key: ValueKey('wallet_keys_page_keys'),), if (showLegacySeedTab) Tab(text: S.of(context).legacy, key: ValueKey('wallet_keys_page_seed_legacy')), ], @@ -137,10 +137,11 @@ class _WalletKeysPageBodyState extends State child: TabBarView( controller: _tabController, children: [ - Padding( - padding: const EdgeInsets.only(left: 22, right: 22), - child: _buildSeedTab(context, false), - ), + if (_hasSeeds) + Padding( + padding: const EdgeInsets.only(left: 22, right: 22), + child: _buildSeedTab(context, false), + ), if (showKeyTab) Padding( padding: const EdgeInsets.only(left: 22, right: 22), diff --git a/lib/src/widgets/option_tile.dart b/lib/src/widgets/option_tile.dart index e5e5ba62d..cdc4fb9f4 100644 --- a/lib/src/widgets/option_tile.dart +++ b/lib/src/widgets/option_tile.dart @@ -7,6 +7,7 @@ class OptionTile extends StatelessWidget { this.icon, required this.title, required this.description, + this.tag, super.key, }) : assert(image != null || icon != null); @@ -15,6 +16,7 @@ class OptionTile extends StatelessWidget { final Icon? icon; final String title; final String description; + final String? tag; @override Widget build(BuildContext context) { @@ -36,25 +38,34 @@ class OptionTile extends StatelessWidget { icon ?? image!, Expanded( child: Padding( - padding: EdgeInsets.only(left: 16), + padding: EdgeInsets.only(left: 20), child: Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - title, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: 20, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.onSurface, - ), + Row( + children: [ + ConstrainedBox( + constraints: BoxConstraints(maxWidth: 220), + child: Text(title, style: Theme.of(context).textTheme.titleMedium), + ), + if (tag != null) Container( + decoration: BoxDecoration( + border: Border.all(), + borderRadius: BorderRadius.circular(20), + color: Theme.of(context).colorScheme.onSurfaceVariant + ), + padding: EdgeInsets.symmetric(horizontal: 5), + margin: EdgeInsets.only(left: 5), + child: Text(tag!)) + ], ), Padding( padding: EdgeInsets.only(top: 5), child: Text( description, - style: Theme.of(context).textTheme.bodyMedium!.copyWith( + style: Theme.of(context).textTheme.bodySmall!.copyWith( color: Theme.of(context).colorScheme.onSurfaceVariant, ), ), diff --git a/lib/src/widgets/setting_actions.dart b/lib/src/widgets/setting_actions.dart index e170d61e6..ae0552e2e 100644 --- a/lib/src/widgets/setting_actions.dart +++ b/lib/src/widgets/setting_actions.dart @@ -45,7 +45,7 @@ class SettingActions { name: (context) => S.of(context).export_outputs, image: 'assets/images/monero_menu.png', onTap: (BuildContext context) { - Navigator.of(context).pushNamed(Routes.urqrAnimatedPage, arguments: 'export-outputs'); + Navigator.of(context).pushNamed(Routes.urqrAnimatedPage, arguments: {'export-outputs': 'export-outputs'}); }, ); diff --git a/lib/utils/feature_flag.dart b/lib/utils/feature_flag.dart index 5fc6e54b2..8b2dc94dd 100644 --- a/lib/utils/feature_flag.dart +++ b/lib/utils/feature_flag.dart @@ -9,4 +9,5 @@ class FeatureFlag { static final bool isInAppTorEnabled = (Platform.isAndroid); static const int verificationWordsCount = kDebugMode ? 0 : 2; static const bool hasDevOptions = bool.fromEnvironment('hasDevOptions', defaultValue: kDebugMode); + static const bool hasBitcoinViewOnly = true; } diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index 7d7f43bcd..e1ef832e7 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -536,7 +536,10 @@ abstract class DashboardViewModelBase with Store { } @computed - bool get hasSilentPayments => wallet.type == WalletType.bitcoin && !wallet.isHardwareWallet; + bool get hasSilentPayments => + wallet.type == WalletType.bitcoin && + (bitcoin!.getWalletKeys(wallet)["privateKey"] ?? "").isNotEmpty && + !wallet.isHardwareWallet; @computed bool get showSilentPaymentsCard => hasSilentPayments && settingsStore.silentPaymentsCardDisplay; diff --git a/lib/view_model/dashboard/receive_option_view_model.dart b/lib/view_model/dashboard/receive_option_view_model.dart index f15d7dad6..1ae6851a0 100644 --- a/lib/view_model/dashboard/receive_option_view_model.dart +++ b/lib/view_model/dashboard/receive_option_view_model.dart @@ -22,7 +22,7 @@ abstract class ReceiveOptionViewModelBase with Store { switch (walletType) { case WalletType.bitcoin: _options = [ - ...bitcoin!.getBitcoinReceivePageOptions(), + ...bitcoin!.getBitcoinReceivePageOptions(_wallet), ...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet) ]; break; diff --git a/lib/view_model/restore/restore_wallet.dart b/lib/view_model/restore/restore_wallet.dart index cc3ad4123..153a9960a 100644 --- a/lib/view_model/restore/restore_wallet.dart +++ b/lib/view_model/restore/restore_wallet.dart @@ -35,7 +35,7 @@ class RestoredWallet { factory RestoredWallet.fromKey(Map json) { try { - final codeParsed = jsonDecode(json['raw_qr'].toString()); + final codeParsed = jsonDecode(json['raw_qr'].toString()); if (codeParsed["version"] == 0) { json['address'] = codeParsed["primaryAddress"]; json['view_key'] = codeParsed["privateViewKey"]; @@ -44,6 +44,7 @@ class RestoredWallet { } catch (e) { // fine, we don't care, it is only for monero anyway } + json['view_key'] ??= json['xpub']; final height = json['height'] as String?; return RestoredWallet( restoreMode: json['mode'] as WalletRestoreMode, @@ -51,7 +52,7 @@ class RestoredWallet { address: json['address'] as String?, spendKey: json['spend_key'] as String?, viewKey: json['view_key'] as String?, - height: height != null ? int.tryParse(height)??0 : 0, + height: height != null ? int.tryParse(height) ?? 0 : 0, privateKey: json['private_key'] as String?, ); } diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart index 530ba1725..e522a20b2 100644 --- a/lib/view_model/restore/wallet_restore_from_qr_code.dart +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -54,6 +54,8 @@ class WalletRestoreFromQRCode { final extracted = sortedKeys.firstWhereOrNull((key) => code.toLowerCase().contains(key)); + if (code.startsWith("xpub")) return WalletType.bitcoin; + if (extracted == null) { // Special case for view-only monero wallet try { @@ -117,11 +119,15 @@ class WalletRestoreFromQRCode { formattedUri = seedPhrase != null ? '$walletType:?seed=$seedPhrase' - : throw Exception('Failed to determine valid seed phrase'); + : code.startsWith('xpub') + ? '$walletType:?xpub=$code' + : throw Exception('Failed to determine valid seed phrase'); } else { final index = code.indexOf(':'); final query = code.substring(index + 1).replaceAll('?', '&'); - formattedUri = '$walletType:?$query'; + formattedUri = code.startsWith('xpub') + ? '$walletType:?xpub=$code' + :'$walletType:?$query'; } final uri = Uri.parse(formattedUri); @@ -158,6 +164,10 @@ class WalletRestoreFromQRCode { throw Exception('Unexpected restore mode: tx_payment_id is invalid'); } + if (credentials.containsKey("xpub")) { + return WalletRestoreMode.keys; + } + if (credentials['seed'] != null) { final seedValue = credentials['seed'] as String; final words = SeedValidator.getWordList(type: type, language: 'english'); diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart index 9926f7c8c..f4925a766 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -289,6 +289,15 @@ abstract class OutputBase with Store { } } + Map get extra { + final fields = {}; + if (parsedAddress.parseFrom == ParseFrom.bip353) { + fields['bip353_name'] = parsedAddress.name; + fields['bip353_proof'] = parsedAddress.bip353DnsProof; + } + return fields; + } + void _setCryptoNumMaximumFractionDigits() { var maximumFractionDigits = 0; diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 910f081ee..4cae799b1 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -603,6 +603,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo bool get isSilentPayments => wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet); + @computed + bool get isCupcake => + wallet.type == WalletType.bitcoin && + (bitcoin!.getWalletKeys(wallet)["privateKey"] ?? "").isEmpty; + @computed bool get isAutoGenerateSubaddressEnabled => _settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled && diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index b61615cac..31b4cc53e 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart'; @@ -162,18 +163,21 @@ abstract class WalletKeysViewModelBase with Store { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: + final keys = bitcoin!.getWalletKeys(_appStore.wallet!); + + items.addAll([ + if ((keys['wif']??'').isNotEmpty) + StandartListItem(title: "WIF", value: keys['wif']!), + if ((keys['privateKey']??'').isNotEmpty) + StandartListItem(title: S.current.private_key, value: keys['privateKey']!), + if (keys['publicKey'] != null) + StandartListItem(title: S.current.public_key, value: keys['publicKey']!), + if (keys['xpub'] != null) + StandartListItem(title: "xPub", value: keys['xpub']!), + ]); + break; case WalletType.none: case WalletType.haven: - // final keys = bitcoin!.getWalletKeys(_appStore.wallet!); - // - // items.addAll([ - // if (keys['wif'] != null) - // StandartListItem(title: "WIF", value: keys['wif']!), - // if (keys['privateKey'] != null) - // StandartListItem(title: S.current.private_key, value: keys['privateKey']!), - // if (keys['publicKey'] != null) - // StandartListItem(title: S.current.public_key, value: keys['publicKey']!), - // ]); break; } diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index 6e00ba4cc..3a7485c80 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -11,6 +11,7 @@ import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/decred/decred.dart'; +import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/view_model/restore/restore_mode.dart'; import 'package:cake_wallet/view_model/restore/restore_wallet.dart'; import 'package:cake_wallet/view_model/seed_settings_view_model.dart'; @@ -32,17 +33,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService, Box walletInfoSource, SeedSettingsViewModel seedSettingsViewModel, {required WalletType type, this.restoredWallet}) - : hasSeedLanguageSelector = - type == WalletType.monero || type == WalletType.haven || type == WalletType.wownero, - hasBlockchainHeightLanguageSelector = - type == WalletType.monero || type == WalletType.haven || type == WalletType.wownero, - hasRestoreFromPrivateKey = type == WalletType.ethereum || - type == WalletType.polygon || - type == WalletType.nano || - type == WalletType.banano || - type == WalletType.solana || - type == WalletType.tron, - isButtonEnabled = false, + : isButtonEnabled = restoredWallet != null, hasPassphrase = false, mode = restoredWallet?.restoreMode ?? WalletRestoreMode.seed, super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel, @@ -60,9 +51,9 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { case WalletType.ethereum: case WalletType.polygon: case WalletType.decred: + case WalletType.bitcoin: availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys]; break; - case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: case WalletType.zano: @@ -82,9 +73,32 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { static const decredSeedMnemonicLength = 15; late List availableModes; - final bool hasSeedLanguageSelector; - final bool hasBlockchainHeightLanguageSelector; - final bool hasRestoreFromPrivateKey; + late final bool hasSeedLanguageSelector = [ + WalletType.monero, + WalletType.haven, + WalletType.wownero + ].contains(type); + + late final bool hasBlockchainHeightSelector = [ + WalletType.monero, + WalletType.haven, + WalletType.wownero + ].contains(type); + + late final bool hasRestoreFromPrivateKey = [ + WalletType.ethereum, + WalletType.polygon, + WalletType.nano, + WalletType.banano, + WalletType.solana, + WalletType.tron + ].contains(type); + + late final bool onlyViewKeyRestore = [ + if (FeatureFlag.hasBitcoinViewOnly) WalletType.bitcoin, + WalletType.decred + ].contains(type); + final RestoredWallet? restoredWallet; @observable @@ -198,6 +212,13 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { final address = options['address'] as String?; switch (type) { + case WalletType.bitcoin: + return bitcoin!.createBitcoinWalletFromKeys( + name: name, + password: password, + xpub: viewKey!, + ); + case WalletType.monero: return monero!.createMoneroRestoreWalletFromKeysCredentials( name: name, @@ -276,8 +297,9 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { case WalletType.litecoin: String? mnemonic = credentials['seed'] as String?; String? passphrase = credentials['passphrase'] as String?; + if (mnemonic == null) break; return bitcoin!.getDerivationsFromMnemonic( - mnemonic: mnemonic!, + mnemonic: mnemonic, node: node, passphrase: passphrase, ); diff --git a/pubspec_base.yaml b/pubspec_base.yaml index cb35f2d46..981485ef1 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -130,6 +130,18 @@ dependencies: url: https://github.com/MrCyjaneK/flutter_daemon ref: c24ee99f2f3070ea02d8108bbdd7727d73f7e5f1 flutter_local_notifications: ^19.0.0 + ur: + git: + url: https://github.com/bukata-sa/bc-ur-dart + ref: 5738f70d0ec3d50977ac3dd01fed62939600238b + bbqrdart: + git: + url: https://github.com/mrcyjanek/bbqrdart + ref: b0f3f72911221bafed0ab77359fccd64bc79d524 + dnssec_proof: + git: + url: https://github.com/MrCyjaneK/dnssec_proof.git + ref: a2b0bdac343afc14fc38dfb81f28147eab50be05 dev_dependencies: flutter_test: diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index 4bccebd14..87a0c9035 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "اختر ممثلًا جديدًا", "nanogpt_subtitle": "جميع النماذج الأحدث (GPT-4 ، Claude). \\ nno اشتراك ، ادفع مع Crypto.", "narrow": "ضيق", + "new_tag": "جديد", "new_first_wallet_text": "الحفاظ على سلامة التشفير الخاص بك هو قطعة من الكعكة", "new_node_testing": "تجربة العقدة الجديدة", "new_subaddress_create": "إنشاء", @@ -687,6 +688,7 @@ "restore_bitcoin_description_from_seed": "قم باستعادة محفظتك من كود مكون من 24 كلمة", "restore_bitcoin_title_from_keys": "استعادة من WIF", "restore_description_from_backup": "يمكنك استعادة تطبيق Cake Wallet بالكامل من ملف النسخ الاحتياطي", + "restore_description_from_cupcake": "ارتباط بتطبيقنا المصاحب للهواء ، مثبتًا على هاتف ثانٍ.", "restore_description_from_hardware_wallet": "استعادة من محفظة أجهزة دفتر الأستاذ", "restore_description_from_keys": "قم باستعادة محفظتك من ضغطات المفاتيح المولدة المحفوظة من مفاتيحك الخاصة", "restore_description_from_seed": "قم باستعادة محفظتك من الرمز المكون من 25 كلمة أو 13 كلمة", @@ -701,6 +703,7 @@ "restore_seed_keys_restore": "استعادة السييد / المفاتيح", "restore_spend_key_private": "مفتاح الإنفاق (خاص)", "restore_title_from_backup": "استعادة من النسخة الاحتياطية", + "restore_title_from_cupcake": "تطبيق كب كيك", "restore_title_from_hardware_wallet": "استعادة من محفظة الأجهزة", "restore_title_from_keys": "استعادة من المفاتيح", "restore_title_from_seed": "استعادة من السييد", @@ -1125,4 +1128,4 @@ "you_will_send": "تحويل من", "youCanGoBackToYourDapp": "يمكنك العودة إلى DAPP الخاص بك الآن", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 2384857e6..84ef023c2 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "Изберете нов представител", "nanogpt_subtitle": "Всички най-нови модели (GPT-4, Claude). \\ Nno абонамент, платете с Crypto.", "narrow": "Тесен", + "new_tag": "Ново", "new_first_wallet_text": "Запазването на криптовалутата ви е парче торта", "new_node_testing": "Тестване на нов node", "new_subaddress_create": "Създаване", @@ -687,6 +688,7 @@ "restore_bitcoin_description_from_seed": "Възстановяване на портфейл чрез код от 24 думи", "restore_bitcoin_title_from_keys": "Възстановяване от WIF", "restore_description_from_backup": "Можете да възстановите цялото приложение Cake Wallet от своя резервен файл", + "restore_description_from_cupcake": "Връзка с нашето придружително приложение за въздух, инсталирано на втори телефон.", "restore_description_from_hardware_wallet": "Възстановяване от хардуерния портфейл на главната книга", "restore_description_from_keys": "Възстановяване на портфейл от генерираните от Вашите тайни ключове клавиши", "restore_description_from_seed": "Възстановяване на портфейл от кода от 13 или 25 думи", @@ -701,6 +703,7 @@ "restore_seed_keys_restore": "Възстановяне от Seed/Keys", "restore_spend_key_private": "Spend key (публичен)", "restore_title_from_backup": "Възстановяване от резервно копие", + "restore_title_from_cupcake": "Приложение за кекс", "restore_title_from_hardware_wallet": "Възстановяване от хардуерния портфейл", "restore_title_from_keys": "Възстановяване от keys", "restore_title_from_seed": "Възстановяване от seed", @@ -1125,4 +1128,4 @@ "you_will_send": "Обръщане от", "youCanGoBackToYourDapp": "Можете да се върнете при вашия Dapp сега", "yy": "гг" -} \ No newline at end of file +} diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 6167fa90f..526ccce62 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "Vyberte nového zástupce", "nanogpt_subtitle": "Všechny nejnovější modely (GPT-4, Claude). \\ Nno předplatné, plaťte krypto.", "narrow": "Úzký", + "new_tag": "Nový", "new_first_wallet_text": "Udržování svého kryptového v bezpečí je kus dortu", "new_node_testing": "Testování nového uzlu", "new_subaddress_create": "Vytvořit", @@ -687,6 +688,7 @@ "restore_bitcoin_description_from_seed": "Obnovte svou peněženku pomocí kombinace 24 slov", "restore_bitcoin_title_from_keys": "Obnovit z WIF", "restore_description_from_backup": "Můžete obnovit celou Cake Wallet aplikaci ze souboru se zálohou", + "restore_description_from_cupcake": "Odkaz na naši doprovodnou aplikaci Air-mezerou nainstalovanou na druhém telefonu.", "restore_description_from_hardware_wallet": "Obnovit z peněženky hardwaru knihy", "restore_description_from_keys": "Obnovte svou peněženku pomocí generovaných stisků kláves uložených z vašich soukromých klíčů", "restore_description_from_seed": "Obnovte svou peněženku pomocí kombinace 25, nebo 13 slov", @@ -701,6 +703,7 @@ "restore_seed_keys_restore": "Obnovit ze seedu/klíčů", "restore_spend_key_private": "Klíč pro platby (soukromý)", "restore_title_from_backup": "Obnovit ze zálohy", + "restore_title_from_cupcake": "Cupcake App", "restore_title_from_hardware_wallet": "Obnovit z hardwarové peněženky", "restore_title_from_keys": "Obnovit z klíčů", "restore_title_from_seed": "Obnovit ze seedu", @@ -1125,4 +1128,4 @@ "you_will_send": "Směnit z", "youCanGoBackToYourDapp": "Nyní se můžete vrátit do svého dappu", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index c07a396b8..967dc4183 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "Wählen Sie einen neuen Vertreter aus", "nanogpt_subtitle": "Alle neuesten Modelle (GPT-4, Claude).", "narrow": "Eng", + "new_tag": "Neu", "new_first_wallet_text": "Wenn Sie Ihren Krypto schützen, ist ein Kinderspiel", "new_node_testing": "Neuen Knoten testen", "new_subaddress_create": "Erstellen", @@ -688,6 +689,7 @@ "restore_bitcoin_description_from_seed": "Stellen Sie Ihre Wallet aus dem 24-Wort-Kombinationscode wieder her", "restore_bitcoin_title_from_keys": "Aus WIF wiederherstellen", "restore_description_from_backup": "Sie können die gesamte Cake Wallet-App aus Ihrer Sicherungsdatei wiederherstellen", + "restore_description_from_cupcake": "Links mit unserer air-gapped Begleitapp, die auf einem zweiten Telefon installiert ist.", "restore_description_from_hardware_wallet": "Stellen Sie eine Wallet von Ledger wieder her", "restore_description_from_keys": "Stellen Sie Ihr Wallet aus generierten Tastenanschlägen her, die von Ihren privaten Schlüsseln gespeichert wurden", "restore_description_from_seed": "Stellen Sie Ihre Wallet aus den 25 Wörtern oder dem 13-Wort-Kombinationscode wieder her", @@ -702,6 +704,7 @@ "restore_seed_keys_restore": "Seed/Schlüssel wiederherstellen", "restore_spend_key_private": "Spend Key (geheim)", "restore_title_from_backup": "Aus einer Sicherungsdatei wiederherstellen", + "restore_title_from_cupcake": "Cupcake App", "restore_title_from_hardware_wallet": "Von Hardware-Wallet wiederherstellen", "restore_title_from_keys": "Aus Schlüsseln wiederherstellen", "restore_title_from_seed": "Aus Seed wiederherstellen", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index fdd0ae648..376e70dcc 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "Pick a new representative", "nanogpt_subtitle": "All the newest models (GPT-4, Claude).\\nNo subscription, pay with crypto.", "narrow": "Narrow", + "new_tag": "New", "new_first_wallet_text": "Keeping your crypto safe is a piece of cake", "new_node_testing": "New node testing", "new_subaddress_create": "Create", @@ -688,6 +689,7 @@ "restore_bitcoin_description_from_seed": "Restore your wallet from 24 word combination code", "restore_bitcoin_title_from_keys": "Restore from WIF", "restore_description_from_backup": "You can restore the whole Cake Wallet app from your back-up file", + "restore_description_from_cupcake": "Link with our companion air-gapped app, installed on a second phone.", "restore_description_from_hardware_wallet": "Restore from a Ledger hardware wallet", "restore_description_from_keys": "Restore your wallet from generated keystrokes saved from your private keys", "restore_description_from_seed": "Restore your wallet from either the 25 word or 13 word combination code", @@ -702,6 +704,7 @@ "restore_seed_keys_restore": "Seed/Keys Restore", "restore_spend_key_private": "Spend key (private)", "restore_title_from_backup": "Restore from backup", + "restore_title_from_cupcake": "Cupcake App", "restore_title_from_hardware_wallet": "Restore from hardware wallet", "restore_title_from_keys": "Restore from keys", "restore_title_from_seed": "Restore from seed", @@ -1126,4 +1129,4 @@ "you_will_send": "Convert from", "youCanGoBackToYourDapp": "You can go back to your dApp now", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 57d704700..078454ae4 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "Elija un nuevo representante", "nanogpt_subtitle": "Todos los modelos más nuevos (GPT-4, Claude). \\nSin suscripción, pague con cripto.", "narrow": "Angosto", + "new_tag": "Nuevo", "new_first_wallet_text": "Mantener su criptografía a salvo es un pedazo de pastel", "new_node_testing": "Prueba nuevos nodos", "new_subaddress_create": "Crear", @@ -688,6 +689,7 @@ "restore_bitcoin_description_from_seed": "Restaure su billetera a partir del código de combinación de 24 palabras", "restore_bitcoin_title_from_keys": "Restaurar desde WIF", "restore_description_from_backup": "Puede restaurar toda la aplicación Cake Wallet desde ysu archivo de respaldo", + "restore_description_from_cupcake": "Enlace con nuestra aplicación complementaria de Airpaped, instalada en un segundo teléfono.", "restore_description_from_hardware_wallet": "Restaurar desde una billetera de hardware Ledger", "restore_description_from_keys": "Restaure su billetera de las pulsaciones de teclas generadas guardadas de sus claves privadas", "restore_description_from_seed": "Restaure su billetera desde el código de combinación de 25 palabras i de 13 palabras", @@ -702,6 +704,7 @@ "restore_seed_keys_restore": "Restauración de semillas / llaves", "restore_spend_key_private": "Llave de gasto (privada)", "restore_title_from_backup": "Restaurar desde un archivo de respaldo", + "restore_title_from_cupcake": "Aplicación de cupcakes", "restore_title_from_hardware_wallet": "Restaurar desde una cartera fría", "restore_title_from_keys": "Usando las claves", "restore_title_from_seed": "Usando la semilla", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 6262e179b..7471c2e61 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "Choisissez un nouveau représentant", "nanogpt_subtitle": "Tous les modèles les plus récents (GPT-4, Claude).\nPas d'abonnement, payez avec vos crypto-monnaies.", "narrow": "Rédruit", + "new_tag": "Nouveau", "new_first_wallet_text": "Garder vos crypto-monnaies en sécurité est un jeu d'enfant.", "new_node_testing": "Test du nouveau nœud", "new_subaddress_create": "Créer", @@ -687,6 +688,7 @@ "restore_bitcoin_description_from_seed": "Restaurer votre portefeuille (wallet) à l'aide d'une phrase secrète (seed) de 24 mots", "restore_bitcoin_title_from_keys": "Restaurer à partir de WIF", "restore_description_from_backup": "Vous pouvez restaurer l'intégralité de l'application Cake Wallet depuis un fichier de sauvegarde", + "restore_description_from_cupcake": "Lien avec notre application complémentaire à air, installée sur un deuxième téléphone.", "restore_description_from_hardware_wallet": "Restaurer depuis un portefeuille matériel Ledger", "restore_description_from_keys": "Restaurer votre portefeuille (wallet) d'après les séquences de touches générées d'après vos clefs privées", "restore_description_from_seed": "Restaurer votre portefeuille (wallet) depuis une phrase secrète (seed) de 25 ou 13 mots", @@ -701,6 +703,7 @@ "restore_seed_keys_restore": "Restaurer depuis Phrase secrète (seed)/Clefs", "restore_spend_key_private": "Clef de dépense (spend key) (privée)", "restore_title_from_backup": "Restaurer depuis une sauvegarde", + "restore_title_from_cupcake": "Application de cupcake", "restore_title_from_hardware_wallet": "Restaurer à partir du portefeuille matériel", "restore_title_from_keys": "Restaurer depuis des clefs", "restore_title_from_seed": "Restaurer depuis une phrase secrète (seed)", @@ -1125,4 +1128,4 @@ "you_will_send": "Convertir depuis", "youCanGoBackToYourDapp": "Vous pouvez retourner à votre Dapp maintenant", "yy": "AA" -} \ No newline at end of file +} diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index b7fa3a150..b4bce78ef 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "Dauki sabon wakili", "nanogpt_subtitle": "Duk sabbin samfuran (GPT-4, CLODE). \\ NNO biyan kuɗi, biya tare da crypto.", "narrow": "kunkuntar", + "new_tag": "Sabo", "new_first_wallet_text": "Tsayawa kayatar da lafiya", "new_node_testing": "Sabbin gwajin kumburi", "new_subaddress_create": "Ƙirƙiri", @@ -689,6 +690,7 @@ "restore_bitcoin_description_from_seed": "Dawo da kwalinku daga 24 lambar haɗin kalma", "restore_bitcoin_title_from_keys": "Dawo daga WIF", "restore_description_from_backup": "Kuna iya dawo da duk aikace-aikacen Wallet ɗin Cake daga fayil ɗin ajiyar ku", + "restore_description_from_cupcake": "Haɗi tare da App na Aikace-aikacen Babi na Aikace-aikacen, shigar a kan wayar ta biyu.", "restore_description_from_hardware_wallet": "Maidowa da walatware mai wanki", "restore_description_from_keys": "Maido da walat ɗin ku daga maɓallan maɓalli da aka ƙera da aka ajiye daga maɓallan ku na sirri", "restore_description_from_seed": "Dawo da kwalinku daga 25 ko 13 lambar haɗin kalma", @@ -703,6 +705,7 @@ "restore_seed_keys_restore": "Mayar da iri/Maɓallai", "restore_spend_key_private": "Maɓallin kashewa (key kalmar sirri)", "restore_title_from_backup": "Dawo daga madadin", + "restore_title_from_cupcake": "Appoake App", "restore_title_from_hardware_wallet": "Dawowa daga walatware mai wuya", "restore_title_from_keys": "Dawo daga maɓallai", "restore_title_from_seed": "Maidowa daga iri", @@ -1128,4 +1131,4 @@ "you_will_send": "Maida daga", "youCanGoBackToYourDapp": "Kuna iya komawa zuwa DPP ɗinku yanzu", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index 7f7021821..d282c21d8 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "एक नया प्रतिनिधि चुनें", "nanogpt_subtitle": "सभी नवीनतम मॉडल (GPT-4, क्लाउड)। \\ nno सदस्यता, क्रिप्टो के साथ भुगतान करें।", "narrow": "सँकरा", + "new_tag": "नया", "new_first_wallet_text": "अपने क्रिप्टो को सुरक्षित रखना केक का एक टुकड़ा है", "new_node_testing": "नई नोड परीक्षण", "new_subaddress_create": "सर्जन करना", @@ -689,6 +690,7 @@ "restore_bitcoin_description_from_seed": "24 शब्द संयोजन कोड से अपने वॉलेट को पुनर्स्थापित करें", "restore_bitcoin_title_from_keys": "WIF से पुनर्स्थापित करें", "restore_description_from_backup": "आप से पूरे केक वॉलेट एप्लिकेशन को पुनर्स्थापित कर सकते हैं आपकी बैक-अप फ़ाइल", + "restore_description_from_cupcake": "एक दूसरे फोन पर इंस्टॉल किए गए हमारे साथी एयर-गपड़ी ऐप के साथ लिंक।", "restore_description_from_hardware_wallet": "एक लेजर हार्डवेयर वॉलेट से पुनर्स्थापित करें", "restore_description_from_keys": "अपने वॉलेट को जेनरेट से पुनर्स्थापित करें आपकी निजी कुंजी से कीस्ट्रोक्स सहेजे गए", "restore_description_from_seed": "या तो 25 शब्द से अपने वॉलेट को पुनर्स्थापित करें या 13 शब्द संयोजन कोड", @@ -703,6 +705,7 @@ "restore_seed_keys_restore": "बीज / कुंजी पुनर्स्थापित करें", "restore_spend_key_private": "कुंजी खर्च करें (निजीe)", "restore_title_from_backup": "बैक-अप फ़ाइल से पुनर्स्थापित करें", + "restore_title_from_cupcake": "कपकेक ऐप", "restore_title_from_hardware_wallet": "हार्डवेयर वॉलेट से पुनर्स्थापित करें", "restore_title_from_keys": "कुंजी से पुनर्स्थापित करें", "restore_title_from_seed": "बीज से पुनर्स्थापित करें", @@ -1127,4 +1130,4 @@ "you_will_send": "से रूपांतरित करें", "youCanGoBackToYourDapp": "अब आप अपने DAPP पर वापस जा सकते हैं", "yy": "वाईवाई" -} \ No newline at end of file +} diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 29e079cda..73bd51d1f 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "Odaberite novog predstavnika", "nanogpt_subtitle": "Svi najnoviji modeli (GPT-4, Claude). \\ NNO pretplata, plaćajte kripto.", "narrow": "Usko", + "new_tag": "Novi", "new_first_wallet_text": "Čuvanje kriptovaluta je komad torte", "new_node_testing": "Provjera novog nodea", "new_subaddress_create": "Izradi", @@ -687,6 +688,7 @@ "restore_bitcoin_description_from_seed": "Oporavi novčanik pomoću koda od 12 riječi", "restore_bitcoin_title_from_keys": "Oporavi pomoću formata uvoza novčanika (WIF)", "restore_description_from_backup": "Možete oporaviti cijelu Cake Wallet aplikaciju pomoću vlastite datoteke sa sigurnosnom kopijom", + "restore_description_from_cupcake": "Link s našom pratećom aplikacijom koja se bavi zrakom, instaliranom na drugi telefon.", "restore_description_from_hardware_wallet": "Vratite se iz novčanice od knjige", "restore_description_from_keys": "Oporavi novčanik pomoću generiranih pritisaka na tipke spremljenih od vlastitih privatnih ključeva (keys)", "restore_description_from_seed": "Oporavi novčanik pomoću koda koji sadrži kombinaciju od 25 ili 13 riječi", @@ -701,6 +703,7 @@ "restore_seed_keys_restore": "Oporavak pomoću pristupnog izraza/ključa", "restore_spend_key_private": "Spend key (privatni)", "restore_title_from_backup": "Oporavak pomoću sigurnosne kopije", + "restore_title_from_cupcake": "Cupcake App", "restore_title_from_hardware_wallet": "Vrati iz hardverskog novčanika", "restore_title_from_keys": "Oporavi pomoću ključa", "restore_title_from_seed": "Oporavi pomoću pristupnog izraza", @@ -1125,4 +1128,4 @@ "you_will_send": "Razmijeni iz", "youCanGoBackToYourDapp": "Sada se možete vratiti na svoj dapp", "yy": "GG" -} \ No newline at end of file +} diff --git a/res/values/strings_hy.arb b/res/values/strings_hy.arb index 8ff77aef2..b16f92a48 100644 --- a/res/values/strings_hy.arb +++ b/res/values/strings_hy.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "Ընտրեք նոր ներկայացուցիչ", "nanogpt_subtitle": "Բոլոր ամենանոր մոդելներ (GPT-4, Claude).\\nԱռանց բաժանորդագրության, վճարեք կրիպտոարժույթով", "narrow": "Նեղ", + "new_tag": "Նոր", "new_first_wallet_text": "Ձեր ծպտյալ անվտանգ պահելը տորթի մի կտոր է", "new_node_testing": "Նոր հանգույցի փորձարկում", "new_subaddress_create": "Ստեղծել", @@ -686,6 +687,7 @@ "restore_bitcoin_description_from_seed": "Վերականգնեք ձեր դրամապանակը 24 բառերի համադրությամբ", "restore_bitcoin_title_from_keys": "Վերականգնել WIF-ից", "restore_description_from_backup": "Դուք կարող եք վերականգնել ամբողջ Cake Wallet հավելվածը ձեր կրկնօրինակ ֆայլից", + "restore_description_from_cupcake": "Հղում մեր ուղեկից օդային բացված ծրագրի հետ, տեղադրված երկրորդ հեռախոսով:", "restore_description_from_hardware_wallet": "Վերականգնեք Ledger հարդվերային դրամապանակից", "restore_description_from_keys": "Վերականգնեք ձեր դրամապանակը ձեր գախտնի բանալիների հիման վրա ստեղծված մուտքագրումներից", "restore_description_from_seed": "Վերականգնեք ձեր դրամապպանակը 25 բառերի կամ 13 բառերի համադրությամբ", @@ -700,6 +702,7 @@ "restore_seed_keys_restore": "Սերմ/բանալիներ վերականգնել", "restore_spend_key_private": "Վճարման բանալի (գախտնի)", "restore_title_from_backup": "Վերականգնել կրկնօրինակ ֆայլից", + "restore_title_from_cupcake": "Cupcake ծրագիրը", "restore_title_from_hardware_wallet": "Վերականգնել հարդվերային դրամապանակից", "restore_title_from_keys": "Վերականգնել բանալիներից", "restore_title_from_seed": "Վերականգնել սերմից", @@ -1123,4 +1126,4 @@ "you_will_send": "Փոխանակեք", "youCanGoBackToYourDapp": "Այժմ կարող եք վերադառնալ ձեր DAPP- ին", "yy": "ՏՏ" -} \ No newline at end of file +} diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 90bb88bb9..e7e4eea66 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "Pilih perwakilan baru", "nanogpt_subtitle": "Semua model terbaru (GPT-4, Claude). \\ Nno langganan, bayar dengan crypto.", "narrow": "Sempit", + "new_tag": "Baru", "new_first_wallet_text": "Menjaga crypto Anda aman adalah sepotong kue", "new_node_testing": "Pengujian node baru", "new_subaddress_create": "Buat", @@ -689,6 +690,7 @@ "restore_bitcoin_description_from_seed": "Pulihkan dompet Anda dari kombinasi kode 24 kata", "restore_bitcoin_title_from_keys": "Pulihkan dari WIF", "restore_description_from_backup": "Anda dapat memulihkan seluruh aplikasi Cake Wallet dari file cadangan Anda", + "restore_description_from_cupcake": "Tautkan dengan aplikasi pendamping udara kami, diinstal pada telepon kedua.", "restore_description_from_hardware_wallet": "Kembalikan dari dompet perangkat keras Ledger", "restore_description_from_keys": "Pulihkan dompet Anda dari tombol yang dihasilkan yang disimpan dari kunci pribadi Anda", "restore_description_from_seed": "Pulihkan dompet Anda dari kombinasi kode 25 atau 13 kata", @@ -704,6 +706,7 @@ "restore_seed_keys_restore": "Pulihkan Seed/Kunci", "restore_spend_key_private": "Habiskan kunci (pribadi)", "restore_title_from_backup": "Pulihkan dari cadangan", + "restore_title_from_cupcake": "Aplikasi Cupcake", "restore_title_from_hardware_wallet": "Kembalikan dari dompet perangkat keras", "restore_title_from_keys": "Pulihkan dari kunci", "restore_title_from_seed": "Pulihkan dari seed", @@ -1128,4 +1131,4 @@ "you_will_send": "Konversi dari", "youCanGoBackToYourDapp": "Anda dapat kembali ke dapp Anda sekarang", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index d8590ced3..192824315 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "Scegli un nuovo rappresentante", "nanogpt_subtitle": "Tutti i modelli più recenti (GPT-4, Claude). Nessun abbonamento, paga con cripto.", "narrow": "Stretto", + "new_tag": "Nuovo", "new_first_wallet_text": "Mantenere la tua criptovaluta è un gioco da ragazzi", "new_node_testing": "Test novo nodo", "new_subaddress_create": "Crea", @@ -688,6 +689,7 @@ "restore_bitcoin_description_from_seed": "Recupera il tuo portafoglio da una combinazione di 12 parole", "restore_bitcoin_title_from_keys": "Recupera da WIF", "restore_description_from_backup": "Puoi recuperare l'app Cake Wallet per intero dal tuo file di backup", + "restore_description_from_cupcake": "Collegamento con la nostra app per gap ad aria accompagnata, installata su un secondo telefono.", "restore_description_from_hardware_wallet": "Ripristina da un portafoglio hardware Ledger", "restore_description_from_keys": "Recupera il tuo portafoglio da una sequenza di caratteri generati dalle tue chiavi private", "restore_description_from_seed": "Recupera il tuo portafoglio da una combinazione di 25 o 13 parole", @@ -702,6 +704,7 @@ "restore_seed_keys_restore": "Recupera Seme/Chiavi", "restore_spend_key_private": "Chiave di Spesa (privata)", "restore_title_from_backup": "Recupera da backup", + "restore_title_from_cupcake": "App Cupcake", "restore_title_from_hardware_wallet": "Ripristina dal portafoglio hardware", "restore_title_from_keys": "Recupera dalle chiavi", "restore_title_from_seed": "Recupera dal seme", @@ -1128,4 +1131,4 @@ "you_will_send": "Conveti da", "youCanGoBackToYourDapp": "Puoi tornare al tuo DApp ora", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 0157c2054..6a1785e28 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -523,6 +523,7 @@ "nano_pick_new_rep": "新しい代表者を選びます", "nanogpt_subtitle": "すべての最新モデル(GPT-4、Claude)。\\ nnoサブスクリプション、暗号で支払います。", "narrow": "狭い", + "new_tag": "新しい", "new_first_wallet_text": "暗号を安全に保つことはケーキです", "new_node_testing": "新しいノードのテスト", "new_subaddress_create": "作成する", @@ -688,6 +689,7 @@ "restore_bitcoin_description_from_seed": "24ワードの組み合わせコードからウォレットを復元する", "restore_bitcoin_title_from_keys": "WIFから復元", "restore_description_from_backup": "Cake Walletアプリ全体を復元できますバックアップファイル", + "restore_description_from_cupcake": "2番目の電話にインストールされたコンパニオンギャップアプリにリンクします。", "restore_description_from_hardware_wallet": "元帳ハードウェアウォレットから復元します", "restore_description_from_keys": "生成されたウォレットを復元します秘密鍵から保存されたキーストローク", "restore_description_from_seed": "25ワードからウォレットを復元しますまたは13ワードの組み合わせコード", @@ -702,6 +704,7 @@ "restore_seed_keys_restore": "シード/キーの復元", "restore_spend_key_private": "キーを使う (プライベート)", "restore_title_from_backup": "バックアップファイルから復元する", + "restore_title_from_cupcake": "カップケーキアプリ", "restore_title_from_hardware_wallet": "ハードウェアウォレットから復元します", "restore_title_from_keys": "キーから復元する", "restore_title_from_seed": "シードから復元", @@ -1126,4 +1129,4 @@ "you_will_send": "から変換", "youCanGoBackToYourDapp": "あなたは今あなたのダップに戻ることができます", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index d7138fa9c..bd0c20a91 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "새 대표자 선택", "nanogpt_subtitle": "모든 최신 모델 (GPT-4, Claude).\n구독 없이 암호화폐로 결제.", "narrow": "좁게", + "new_tag": "새로운", "new_first_wallet_text": "암호화폐를 안전하게 보관하는 것은 식은 죽 먹기", "new_node_testing": "새 노드 테스트 중", "new_subaddress_create": "생성", @@ -688,6 +689,7 @@ "restore_bitcoin_description_from_seed": "24단어 조합 코드로 지갑 복구", "restore_bitcoin_title_from_keys": "WIF에서 복구", "restore_description_from_backup": "백업 파일에서 전체 Cake Wallet 앱을 복구할 수 있습니다", + "restore_description_from_cupcake": "두 번째 전화에 설치된 동반자 에어 갭 앱과 링크하십시오.", "restore_description_from_hardware_wallet": "Ledger 하드웨어 지갑에서 복구", "restore_description_from_keys": "개인 키에서 저장된 키 입력으로 지갑 복구", "restore_description_from_seed": "25단어 또는 13단어 조합 코드로 지갑 복구", @@ -702,6 +704,7 @@ "restore_seed_keys_restore": "시드/키 복구", "restore_spend_key_private": "지출 키 (개인)", "restore_title_from_backup": "백업에서 복구", + "restore_title_from_cupcake": "컵 케이크 앱", "restore_title_from_hardware_wallet": "하드웨어 지갑에서 복구", "restore_title_from_keys": "키에서 복구", "restore_title_from_seed": "시드에서 복구", @@ -1126,4 +1129,4 @@ "you_will_send": "전환 출처", "youCanGoBackToYourDapp": "이제 dApp으로 돌아갈 수 있습니다", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index a358f71d0..7c6bd171a 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "အသစ်တစ်ခုကိုရွေးပါ", "nanogpt_subtitle": "အားလုံးနောက်ဆုံးပေါ်မော်ဒယ်များ (GPT-4, Claude) ။ \\ nno subscription, crypto နှင့်အတူပေးဆောင်။", "narrow": "ကျဉ်းသော", + "new_tag": "သစ်သော", "new_first_wallet_text": "သင်၏ Crypto Safe ကိုလုံခြုံအောင်ပြုလုပ်ခြင်းသည်ကိတ်မုန့်တစ်မျိုးဖြစ်သည်", "new_node_testing": "နှာခေါင်း အသစ်စမ်းသပ်ခြင်း။", "new_subaddress_create": "ဖန်တီးပါ။", @@ -687,6 +688,7 @@ "restore_bitcoin_description_from_seed": "24 စကားလုံးပေါင်းစပ်ကုဒ်မှ သင့်ပိုက်ဆံအိတ်ကို ပြန်ယူပါ။", "restore_bitcoin_title_from_keys": "WIF မှ ပြန်လည်ရယူပါ။", "restore_description_from_backup": "သင့်အရန်ဖိုင်မှ Cake Wallet အက်ပ်တစ်ခုလုံးကို သင်ပြန်လည်ရယူနိုင်သည်။", + "restore_description_from_cupcake": "ဒုတိယဖုန်းတွင်တပ်ဆင်ထားသောကျွန်ုပ်တို့၏အဖော် Air-Gensess App နှင့်ချိတ်ဆက်ပါ။", "restore_description_from_hardware_wallet": "Ledger ဟာ့ဒ်ဝဲပိုက်ဆံအိတ်မှ Restore", "restore_description_from_keys": "သင့်ကိုယ်ပိုင်သော့များမှ သိမ်းဆည်းထားသော ထုတ်ပေးထားသော သော့ချက်များမှ သင့်ပိုက်ဆံအိတ်ကို ပြန်လည်ရယူပါ။", "restore_description_from_seed": "25 စကားလုံး သို့မဟုတ် 13 စကားလုံးပေါင်းစပ်ကုဒ်မှ သင့်ပိုက်ဆံအိတ်ကို ပြန်လည်ရယူပါ။", @@ -701,6 +703,7 @@ "restore_seed_keys_restore": "မျိုးစေ့/သော့များ ပြန်လည်ရယူပါ။", "restore_spend_key_private": "သော့သုံးရန် (သီးသန့်)", "restore_title_from_backup": "အရန်သိမ်းခြင်းမှ ပြန်လည်ရယူပါ။", + "restore_title_from_cupcake": "Cupcake app", "restore_title_from_hardware_wallet": "ဟာ့ဒ်ဝဲပိုက်ဆံအိတ်မှ restore", "restore_title_from_keys": "သော့များမှ ပြန်လည်ရယူပါ။", "restore_title_from_seed": "မျိုးစေ့မှပြန်လည်ရယူပါ။", @@ -1125,4 +1128,4 @@ "you_will_send": "မှပြောင်းပါ။", "youCanGoBackToYourDapp": "သငျသညျယခုသင်၏ dapp ကိုပြန်သွားနိုင်ပါတယ်", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 2d760997c..0bbc6449f 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "Kies een nieuwe vertegenwoordiger", "nanogpt_subtitle": "Alle nieuwste modellen (GPT-4, Claude). \\ Nno-abonnement, betalen met crypto.", "narrow": "Smal", + "new_tag": "Nieuw", "new_first_wallet_text": "Je crypto veilig houden is een fluitje van een cent", "new_node_testing": "Nieuwe knooppunttest", "new_subaddress_create": "Creëren", @@ -687,6 +688,7 @@ "restore_bitcoin_description_from_seed": "Herstel uw portemonnee met een combinatiecode van 24 woorden", "restore_bitcoin_title_from_keys": "Herstel van WIF", "restore_description_from_backup": "Je kunt de hele Cake Wallet-app herstellen van uw back-upbestand", + "restore_description_from_cupcake": "Link met onze bijbehorende air-happed-app, geïnstalleerd op een tweede telefoon.", "restore_description_from_hardware_wallet": "Herstel van een grootboekhardware -portemonnee", "restore_description_from_keys": "Herstel uw portemonnee van gegenereerd toetsaanslagen opgeslagen van uw privésleutels", "restore_description_from_seed": "Herstel uw portemonnee van het 25 woord of 13 woord combinatiecode", @@ -701,6 +703,7 @@ "restore_seed_keys_restore": "Zaad / sleutels herstellen", "restore_spend_key_private": "Sleutel uitgeven (privaat)", "restore_title_from_backup": "Herstellen vanuit een back-upbestand", + "restore_title_from_cupcake": "Cupcake -app", "restore_title_from_hardware_wallet": "Herstel van de hardware -portemonnee", "restore_title_from_keys": "Herstel van sleutels", "restore_title_from_seed": "Herstel van zaad", @@ -1126,4 +1129,4 @@ "you_will_send": "Converteren van", "youCanGoBackToYourDapp": "U kunt nu terug naar uw DApp gaan", "yy": "JJ" -} \ No newline at end of file +} diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 7145b7fb1..1f0d22b18 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "Wybierz nowego przedstawiciela", "nanogpt_subtitle": "Wszystkie najnowsze modele (GPT-4, Claude). \\ Nno subskrypcja, płacą za pomocą kryptografii.", "narrow": "Wąski", + "new_tag": "Nowy", "new_first_wallet_text": "Zachowanie bezpieczeństwa kryptograficznego jest kawałkiem ciasta", "new_node_testing": "Testowanie nowych węzłów", "new_subaddress_create": "Stwórz", @@ -687,6 +688,7 @@ "restore_bitcoin_description_from_seed": "Przywróć swój portfel z frazy seed złożonej z 24 słów", "restore_bitcoin_title_from_keys": "Przywróć z klucza prywatnego", "restore_description_from_backup": "Możesz przywrócić całą aplikację Cake Wallet z pliku kopii zapasowej", + "restore_description_from_cupcake": "Połącz z naszą towarzyszącą aplikacją powietrzną, zainstalowaną na drugim telefonie.", "restore_description_from_hardware_wallet": "Przywróć z portfela sprzętowego księgi", "restore_description_from_keys": "Przywróć swój portfel z kluczy prywatnych", "restore_description_from_seed": "Przywróć swój portfel z 25 lub 13-słownej frazy seed", @@ -701,6 +703,7 @@ "restore_seed_keys_restore": "Przywracanie z frazy seed / kluczy", "restore_spend_key_private": "Podaj prywatny klucz wglądu (view key)", "restore_title_from_backup": "Przywróć z pliku kopii zapasowej", + "restore_title_from_cupcake": "Aplikacja Cupcake", "restore_title_from_hardware_wallet": "Przywróć z portfela sprzętowego", "restore_title_from_keys": "Przywróć z kluczy", "restore_title_from_seed": "Przywróć z frazy seed", @@ -1125,4 +1128,4 @@ "you_will_send": "Konwertuj z", "youCanGoBackToYourDapp": "Możesz teraz wrócić do swojego dapp", "yy": "RR" -} \ No newline at end of file +} diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index e87cf566d..4f05d31d8 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -523,6 +523,7 @@ "nano_pick_new_rep": "Escolha um novo representante", "nanogpt_subtitle": "Todos os modelos mais recentes (GPT-4, Claude). \\nSem assinatura, pague com criptomoeda.", "narrow": "Estreito", + "new_tag": "Novo", "new_first_wallet_text": "Manter sua cripto segura é um pedaço de bolo", "new_node_testing": "Teste de novo nó", "new_subaddress_create": "Criar", @@ -689,6 +690,7 @@ "restore_bitcoin_description_from_seed": "Restaure sua carteira a partir de um código de combinação de 24 palavras", "restore_bitcoin_title_from_keys": "Restaurar de WIF", "restore_description_from_backup": "Você pode restaurar todo o aplicativo Cake Wallet de seu arquivo de backup", + "restore_description_from_cupcake": "Link para nosso aplicativo Companion Air-Gapped, instalado em um segundo telefone.", "restore_description_from_hardware_wallet": "Restaurar de uma carteira de hardware do Ledger", "restore_description_from_keys": "Restaure sua carteira a partir de suas chaves privadas", "restore_description_from_seed": "Restaure sua carteira a partir de semente com 25 palavras ou 13 palavras", @@ -703,6 +705,7 @@ "restore_seed_keys_restore": "Restauração com sementes/chaves", "restore_spend_key_private": "Chave de gastos (privada)", "restore_title_from_backup": "Restaurar a partir de um arquivo de backup", + "restore_title_from_cupcake": "Aplicativo de cupcake", "restore_title_from_hardware_wallet": "Restaurar da carteira de hardware", "restore_title_from_keys": "Restaurar a partir de chaves", "restore_title_from_seed": "Restaurar a partir de semente", @@ -1128,4 +1131,4 @@ "you_will_send": "Converter de", "youCanGoBackToYourDapp": "Você pode voltar para o seu dapp agora", "yy": "aa" -} \ No newline at end of file +} diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index fef47e56c..b7b7c0125 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "Выберите нового представителя", "nanogpt_subtitle": "Все новейшие модели (GPT-4, Claude). \\ Nno Подписка, платите с крипто.", "narrow": "Узкий", + "new_tag": "Новый", "new_first_wallet_text": "Сохранение вашего криптографии - это кусок торта", "new_node_testing": "Тестирование новой ноды", "new_subaddress_create": "Создать", @@ -688,6 +689,7 @@ "restore_bitcoin_description_from_seed": "Вы можете восстановить кошелёк используя 24-ти значную мнемоническую фразу", "restore_bitcoin_title_from_keys": "Восстановить с помощью WIF", "restore_description_from_backup": "Вы можете восстановить Cake Wallet из вашего back-up файла", + "restore_description_from_cupcake": "Ссылка с нашим приложением с авиакомпаниями, установленным на втором телефоне.", "restore_description_from_hardware_wallet": "Восстановите из оборудования", "restore_description_from_keys": "Вы можете восстановить кошелёк с помощью приватных ключей", "restore_description_from_seed": "Вы можете восстановить кошелёк используя 25-ти значную мнемоническую фразу", @@ -702,6 +704,7 @@ "restore_seed_keys_restore": "Восстановить из мнемонической фразы/ключей", "restore_spend_key_private": "Приватный ключ траты", "restore_title_from_backup": "Восстановить из back-up файла", + "restore_title_from_cupcake": "Приложение кекса", "restore_title_from_hardware_wallet": "Восстановить из аппаратного кошелька", "restore_title_from_keys": "Восстановить с помощью ключей", "restore_title_from_seed": "Восстановить из мнемонической фразы", @@ -1126,4 +1129,4 @@ "you_will_send": "Конвертировать из", "youCanGoBackToYourDapp": "Вы можете вернуться к своему даппу сейчас", "yy": "ГГ" -} \ No newline at end of file +} diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index d228cd7e6..df26901b4 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "เลือกตัวแทนใหม่", "nanogpt_subtitle": "รุ่นใหม่ล่าสุดทั้งหมด (GPT-4, Claude). การสมัครสมาชิก \\ nno, จ่ายด้วย crypto", "narrow": "แคบ", + "new_tag": "ใหม่", "new_first_wallet_text": "การรักษา crypto ของคุณให้ปลอดภัยเป็นเค้กชิ้นหนึ่ง", "new_node_testing": "การทดสอบโหนดใหม่", "new_subaddress_create": "สร้าง", @@ -687,6 +688,7 @@ "restore_bitcoin_description_from_seed": "กู้กระเป๋าของคุณจากรหัสผสมของ 24 คำ", "restore_bitcoin_title_from_keys": "กู้จาก WIF", "restore_description_from_backup": "คุณสามารถกู้แอพ Cake Wallet ทั้งหมดจากไฟล์สำรองข้อมูลของคุณ", + "restore_description_from_cupcake": "เชื่อมโยงกับแอพที่มี Gapped Air-Gapped ของเราติดตั้งบนโทรศัพท์เครื่องที่สอง", "restore_description_from_hardware_wallet": "กู้คืนจากกระเป๋าเงินฮาร์ดแวร์บัญชีแยกประเภท", "restore_description_from_keys": "กู้กระเป๋าของคุณจากการกดปุ่มที่สร้างขึ้นจาก private keys ของคุณที่บันทึกไว้", "restore_description_from_seed": "กู้กระเป๋าของคุณจากรหัสผสมของ 25 คำหรือ 13 คำ", @@ -701,6 +703,7 @@ "restore_seed_keys_restore": "กู้จาก Seed/Keys", "restore_spend_key_private": "คีย์สำหรับใช้ (ส่วนตัว)", "restore_title_from_backup": "กู้จากการสำรองข้อมูล", + "restore_title_from_cupcake": "แอพคัพเค้ก", "restore_title_from_hardware_wallet": "กู้คืนจากกระเป๋าเงินฮาร์ดแวร์", "restore_title_from_keys": "กู้จาก keys", "restore_title_from_seed": "กู้จาก seed", @@ -1125,4 +1128,4 @@ "you_will_send": "แปลงจาก", "youCanGoBackToYourDapp": "คุณสามารถกลับไปที่ dapp ของคุณได้ทันที", "yy": "ปี" -} \ No newline at end of file +} diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index b29463aa1..8246f8574 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "Pumili ng isang bagong representative", "nanogpt_subtitle": "Ang lahat ng mga pinakabagong modelo (GPT-4, Claude). \nNo subscription, magbayad gamit ang crypto.", "narrow": "Makitid", + "new_tag": "Bago", "new_first_wallet_text": "Ang pagpapanatiling ligtas sa iyong crypto ay isang piraso ng cake", "new_node_testing": "Bagong node testing", "new_subaddress_create": "Lumikha", @@ -687,6 +688,7 @@ "restore_bitcoin_description_from_seed": "Ibalik ang iyong wallet mula sa 24 na salita na seed", "restore_bitcoin_title_from_keys": "Ibalik mula sa WIF", "restore_description_from_backup": "Maari mong ibalik ang buong Cake Wallet app sa iyong backup file", + "restore_description_from_cupcake": "Mag-link sa aming kasamang naka-air-gapped app, na naka-install sa isang pangalawang telepono.", "restore_description_from_hardware_wallet": "Ibalik mula sa isang Ledger hardware wallet", "restore_description_from_keys": "Ibalik ang iyong wallet mula sa nabuong mga keystrokes na na-save mula sa iyong mga private key", "restore_description_from_seed": "Ibalik ang iyong wallet mula sa alinman sa 25 na salita o 13 na salita na seed", @@ -701,6 +703,7 @@ "restore_seed_keys_restore": "Ibalik mula sa Seed/Keys", "restore_spend_key_private": "Spend key (private)", "restore_title_from_backup": "Ibalik mula sa backup", + "restore_title_from_cupcake": "Cupcake app", "restore_title_from_hardware_wallet": "Ibalik mula sa hardware wallet", "restore_title_from_keys": "Ibalik mula sa keys", "restore_title_from_seed": "Ibalik mula sa seed", @@ -1125,4 +1128,4 @@ "you_will_send": "I-convert mula sa", "youCanGoBackToYourDapp": "Maaari kang bumalik sa iyong dapp ngayon", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index e3688198f..297e35640 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "Yeni bir temsilci seçin", "nanogpt_subtitle": "En yeni modeller (GPT-4, Claude). \\ Nno aboneliği, kripto ile ödeme yapın.", "narrow": "Dar", + "new_tag": "Yeni", "new_first_wallet_text": "Kripto'nuzu güvende tutmak bir parça kek", "new_node_testing": "Yeni düğüm test ediliyor", "new_subaddress_create": "Oluştur", @@ -687,6 +688,7 @@ "restore_bitcoin_description_from_seed": "Cüzdanınızı 24 kelimelik kombinasyon kodundan geri yükle", "restore_bitcoin_title_from_keys": "WIF'den geri yükle", "restore_description_from_backup": "Yedek dosyandan tüm Cake Wallet uygulamasını geri döndürebilirsin", + "restore_description_from_cupcake": "İkinci bir telefona yüklü eşlik eden hava kaplı uygulamamızla bağlantı kurun.", "restore_description_from_hardware_wallet": "Bir defter donanım cüzdanından geri yükleyin", "restore_description_from_keys": "Cüzdanınızı özel anahtarlarınızdan kaydedilen oluşturulmuş tuş vuruşlarından geri yükleyin", "restore_description_from_seed": "Cüzdanınızı 25 veya 13 kelimelik kombinasyon kodundan geri döndürün", @@ -701,6 +703,7 @@ "restore_seed_keys_restore": "Tohumu/Anahtarları Geri Döndür", "restore_spend_key_private": "Harcama anahtarı (Özel)", "restore_title_from_backup": "Yedekten geri döndür", + "restore_title_from_cupcake": "Cupcake uygulaması", "restore_title_from_hardware_wallet": "Donanım cüzdanından geri yükleme", "restore_title_from_keys": "Anahtarlardan geri döndür", "restore_title_from_seed": "Tohumdan geri döndür", @@ -1125,4 +1128,4 @@ "you_will_send": "Biçiminden dönüştür:", "youCanGoBackToYourDapp": "Şimdi Dapp'ınıza geri dönebilirsin", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index d9fe0391a..bc72ec0f6 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "Виберіть нового представника", "nanogpt_subtitle": "Усі найновіші моделі (GPT-4, Claude). \\ Nno підписка, оплата криптовалютою.", "narrow": "вузькі", + "new_tag": "Новачок", "new_first_wallet_text": "Зберігання вашої криптовалюти це просто як шматок торта", "new_node_testing": "Тестування нового вузла", "new_subaddress_create": "Створити", @@ -688,6 +689,7 @@ "restore_bitcoin_description_from_seed": "Ви можете відновити гаманець використовуючи 24-ти слівну мнемонічну фразу", "restore_bitcoin_title_from_keys": "Відновити за допомогою WIF", "restore_description_from_backup": "Ви можете відновити Cake Wallet з вашого резервного файлу", + "restore_description_from_cupcake": "Зв'язок з нашим супутничим додатком, встановленим на другому телефоні.", "restore_description_from_hardware_wallet": "Відновлення з апаратного гаманця", "restore_description_from_keys": "Ви можете відновити гаманець за допомогою приватних ключів", "restore_description_from_seed": "Ви можете відновити гаманець використовуючи 25-ти слівну мнемонічну фразу", @@ -702,6 +704,7 @@ "restore_seed_keys_restore": "Відновити за допомогою мнемонічної фрази/ключів", "restore_spend_key_private": "Приватний ключ витрати", "restore_title_from_backup": "Відновити із резервного файлу", + "restore_title_from_cupcake": "Додаток для кексів", "restore_title_from_hardware_wallet": "Відновити з апаратного гаманця", "restore_title_from_keys": "Відновити за допомогою ключів", "restore_title_from_seed": "Відновити з мнемонічної фрази", @@ -1126,4 +1129,4 @@ "you_will_send": "Конвертувати з", "youCanGoBackToYourDapp": "Ви можете повернутися до свого DAPP зараз", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index f265bd74e..3cd375b27 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "ایک نیا نمائندہ منتخب کریں", "nanogpt_subtitle": "تمام تازہ ترین ماڈل (GPT-4 ، کلاڈ)۔ n n no سبسکرپشن ، کریپٹو کے ساتھ ادائیگی کریں۔", "narrow": "تنگ", + "new_tag": "نیا", "new_first_wallet_text": "اپنے کریپٹو کو محفوظ رکھنا کیک کا ایک ٹکڑا ہے", "new_node_testing": "نیا نوڈ ٹیسٹنگ", "new_subaddress_create": "بنانا", @@ -689,6 +690,7 @@ "restore_bitcoin_description_from_seed": "24 الفاظ کے مجموعہ کوڈ سے اپنے بٹوے کو بحال کریں۔", "restore_bitcoin_title_from_keys": "WIF سے بحال کریں۔", "restore_description_from_backup": "آپ اپنی بیک اپ فائل سے پوری کیک والیٹ ایپ کو بحال کر سکتے ہیں۔", + "restore_description_from_cupcake": "دوسرے فون پر نصب ، ہمارے ساتھی ایئر گیپڈ ایپ کے ساتھ لنک کریں۔", "restore_description_from_hardware_wallet": "لیجر ہارڈ ویئر پرس سے بحال کریں", "restore_description_from_keys": "اپنے بٹوے کو اپنی نجی کلیدوں سے محفوظ کردہ کی اسٹروکس سے بحال کریں۔", "restore_description_from_seed": "اپنے بٹوے کو 25 لفظ یا 13 الفاظ کے مجموعہ کوڈ سے بحال کریں۔", @@ -703,6 +705,7 @@ "restore_seed_keys_restore": "بیج/کیز کی بحالی", "restore_spend_key_private": "خرچ کی کلید (نجی)", "restore_title_from_backup": "بیک اپ سے بحال کریں۔", + "restore_title_from_cupcake": "کپ کیک ایپ", "restore_title_from_hardware_wallet": "ہارڈ ویئر پرس سے بحال کریں", "restore_title_from_keys": "چابیاں سے بحال کریں۔", "restore_title_from_seed": "بیج سے بحال کریں۔", @@ -1127,4 +1130,4 @@ "you_will_send": "سے تبدیل کریں۔", "youCanGoBackToYourDapp": "اب آپ اپنے ڈی اے پی پی پر واپس جاسکتے ہیں", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_vi.arb b/res/values/strings_vi.arb index ca6b70e0a..02e16c254 100644 --- a/res/values/strings_vi.arb +++ b/res/values/strings_vi.arb @@ -521,6 +521,7 @@ "nano_pick_new_rep": "Chọn đại diện mới", "nanogpt_subtitle": "Tất cả các mẫu mới nhất (GPT-4, Claude).\\nKhông cần đăng ký, thanh toán bằng tiền điện tử.", "narrow": "Hẹp", + "new_tag": "Mới", "new_first_wallet_text": "Giữ tiền điện tử của bạn an toàn là một miếng bánh", "new_node_testing": "Đang thử nghiệm nút mới", "new_subaddress_create": "Tạo", @@ -685,6 +686,7 @@ "restore_bitcoin_description_from_seed": "Khôi phục ví của bạn từ mã kết hợp 24 từ", "restore_bitcoin_title_from_keys": "Khôi phục từ WIF", "restore_description_from_backup": "Bạn có thể khôi phục toàn bộ ứng dụng Cake Wallet từ tệp sao lưu của bạn", + "restore_description_from_cupcake": "Liên kết với Ứng dụng Air Air của chúng tôi, được cài đặt trên điện thoại thứ hai.", "restore_description_from_hardware_wallet": "Khôi phục từ ví phần cứng Ledger", "restore_description_from_keys": "Khôi phục ví của bạn từ các thao tác nhập được lưu từ khóa riêng của bạn", "restore_description_from_seed": "Khôi phục ví của bạn từ mã kết hợp 25 từ hoặc 13 từ", @@ -699,6 +701,7 @@ "restore_seed_keys_restore": "Khôi phục hạt giống/khóa", "restore_spend_key_private": "Khóa chi tiêu (riêng tư)", "restore_title_from_backup": "Khôi phục từ sao lưu", + "restore_title_from_cupcake": "Ứng dụng Cupcake", "restore_title_from_hardware_wallet": "Khôi phục từ ví phần cứng", "restore_title_from_keys": "Khôi phục từ khóa", "restore_title_from_seed": "Khôi phục từ hạt giống", @@ -1122,4 +1125,4 @@ "you_will_send": "Chuyển đổi từ", "youCanGoBackToYourDapp": "Bạn có thể quay lại DAPP của mình ngay bây giờ", "yy": "YY" -} \ No newline at end of file +} diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index e06500ed2..0863ae65c 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -523,6 +523,7 @@ "nano_pick_new_rep": "Mu aṣoju tuntun kan", "nanogpt_subtitle": "Gbogbo awọn awoṣe tuntun (GPT-4, Claude). \\ Nno alabapin kan, sanwo pẹlu Crypto.", "narrow": "Taara", + "new_tag": "Tuntun", "new_first_wallet_text": "Tọju ẹrọ ti o ni aabo rẹ jẹ nkan ti akara oyinbo kan", "new_node_testing": "A ń dán apẹka títun wò", "new_subaddress_create": "Ṣe é", @@ -688,6 +689,7 @@ "restore_bitcoin_description_from_seed": "Mú àpamọ́wọ́ yín padà láti àkànpọ̀ ọlọ́rọ̀ ẹ̀ẹ̀mẹrinlélógun", "restore_bitcoin_title_from_keys": "Mú padà láti WIF", "restore_description_from_backup": "Ẹ lè fi ẹ̀dà nípamọ́ yín mú odindi Cake Wallet áàpù padà.", + "restore_description_from_cupcake": "Ọna asopọ pẹlu Ohun elo Wa-ilẹ wa, ti o fi sori foonu keji.", "restore_description_from_hardware_wallet": "Mu pada kuro ninu apamọwọ ohun elo ti o yanilenu", "restore_description_from_keys": "Mú àpamọ́wọ́ yín padà láti àwọn àtẹ̀ nípamọ́ láti àwọn kọ́kọ́rọ́ àdáni yín", "restore_description_from_seed": "Ẹ mú àpamọ́wọ́ yín padà láti àkànpọ̀ ọlọ́rọ̀ ẹ̀ẹ̀marùndínlọgbọ̀n tàbí ti mẹ́talá.", @@ -702,6 +704,7 @@ "restore_seed_keys_restore": "Mú hóró/kọ́kọ́rọ́ padà", "restore_spend_key_private": "kọ́kọ́rọ́ àdáni fún níná", "restore_title_from_backup": "Fi ẹ̀dà nípamọ́ mú padà", + "restore_title_from_cupcake": "Ohun elo app", "restore_title_from_hardware_wallet": "Mu pada kuro ninu apamọwọ ohun elo", "restore_title_from_keys": "Fi kọ́kọ́rọ́ ṣẹ̀dá", "restore_title_from_seed": "Fi hóró mú padà", @@ -1126,4 +1129,4 @@ "you_will_send": "Ṣe pàṣípààrọ̀ láti", "youCanGoBackToYourDapp": "O le pada si tapla rẹ bayi", "yy": "Ọd" -} \ No newline at end of file +} diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index 4a2a01161..308a20453 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -522,6 +522,7 @@ "nano_pick_new_rep": "选择新代表", "nanogpt_subtitle": "所有最新型号(GPT-4,Claude)。\\ nno订阅,用加密货币付款。", "narrow": "狭窄的", + "new_tag": "新的", "new_first_wallet_text": "保持加密货币是一件小菜一碟", "new_node_testing": "新节点测试", "new_subaddress_create": "创建", @@ -687,6 +688,7 @@ "restore_bitcoin_description_from_seed": "从24个文字的组成码恢复您的钱包", "restore_bitcoin_title_from_keys": "从WIF还原", "restore_description_from_backup": "您可以从还原整个Cake Wallet应用您的备份文件", + "restore_description_from_cupcake": "与我们的配套空调应用程序链接,安装在第二台手机上。", "restore_description_from_hardware_wallet": "从分类帐硬件钱包还原", "restore_description_from_keys": "使用私钥恢复钱包", "restore_description_from_seed": "从25个字中恢复您的钱包或13个字的组合码", @@ -701,6 +703,7 @@ "restore_seed_keys_restore": "种子/密钥还原", "restore_spend_key_private": "Spend私钥", "restore_title_from_backup": "从备份文件还原", + "restore_title_from_cupcake": "蛋糕应用", "restore_title_from_hardware_wallet": "从硬件钱包还原", "restore_title_from_keys": "从密钥还原", "restore_title_from_seed": "从种子还原", @@ -1125,4 +1128,4 @@ "you_will_send": "转换自", "youCanGoBackToYourDapp": "您现在可以回到DAPP", "yy": "YY" -} \ No newline at end of file +} diff --git a/scripts/linux/build_cake_release.sh b/scripts/linux/build_cake_release.sh index 16f228d2e..88264c3dc 100755 --- a/scripts/linux/build_cake_release.sh +++ b/scripts/linux/build_cake_release.sh @@ -14,7 +14,7 @@ cd "$SCRIPT_DIR" BUILD_AMD64=false BUILD_ARM64=false APP_TYPE="cakewallet" -DOCKER_IMAGE="ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly" +DOCKER_IMAGE="ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-ndkr28-go1.24.1-ruststablenightly" # Parse arguments for arg in "$@" diff --git a/scripts/windows/Dockerfile.windows b/scripts/windows/Dockerfile.windows index a534be5e4..893009b8f 100644 --- a/scripts/windows/Dockerfile.windows +++ b/scripts/windows/Dockerfile.windows @@ -4,7 +4,7 @@ FROM mcr.microsoft.com/windows/servercore:ltsc2022 -ENV FLUTTER_VERSION=3.27.0 +ENV FLUTTER_VERSION=3.27.4 ENV GIT_VERSION=2.47.1 ENV VS_INSTALLED_DIR="C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools" ENV PATH="C:\Users\ContainerAdministrator\.cargo\bin;C:\ProgramData\chocolatey\bin;C:\flutter\flutter\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Users\ContainerAdministrator\AppData\Local\Microsoft\WindowsApps" diff --git a/tool/configure.dart b/tool/configure.dart index dc3f594cf..39b52c771 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -163,6 +163,7 @@ abstract class Bitcoin { String? passphrase, }); WalletCredentials createBitcoinRestoreWalletFromWIFCredentials({required String name, required String password, required String wif, WalletInfo? walletInfo}); + WalletCredentials createBitcoinWalletFromKeys({required String name, required String password, required String xpub}); WalletCredentials createBitcoinNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? passphrase, String? mnemonic}); WalletCredentials createBitcoinHardwareWalletCredentials({required String name, required HardwareAccountData accountData, WalletInfo? walletInfo}); List getWordList(); @@ -206,7 +207,7 @@ abstract class Bitcoin { Map> getElectrumDerivations(); Future setAddressType(Object wallet, dynamic option); ReceivePageOption getSelectedAddressType(Object wallet); - List getBitcoinReceivePageOptions(); + List getBitcoinReceivePageOptions(Object wallet); List getLitecoinReceivePageOptions(); BitcoinAddressType getBitcoinAddressType(ReceivePageOption option); bool isPayjoinAvailable(Object wallet); @@ -246,6 +247,7 @@ abstract class Bitcoin { bool getMwebEnabled(Object wallet); String? getUnusedMwebAddress(Object wallet); String? getUnusedSegwitAddress(Object wallet); + Future commitPsbtUR(Object wallet, List urCodes); void updatePayjoinState(Object wallet, bool state); String getPayjoinEndpoint(Object wallet); @@ -401,7 +403,7 @@ abstract class Monero { Future commitTransactionUR(Object wallet, String ur); - String exportOutputsUR(Object wallet, bool all); + Map exportOutputsUR(Object wallet); bool needExportOutputs(Object wallet, int amount);