CW948: feat: bitcoin view-only (#2261)

* feat: bitcoin view-only * fix: add UR to cw_bitcoin, fix payjoin issue * feat: add unsignedPsbt to PendingBitcoinTransaction * wip * feat: xpub restore from bitcoin URI QR * feat(dev): debug UR codes [skip ci] * fix: master fingerprint default in create bitcoin transaction * fix: handle empty private key in bitcoin transaction building * fix: show success dialog after btc ur transaction * fix(ui): remove SP from UI in view only wallet * fix(cw_bitcoin): tx sending on non-airgap * fix(cw_bitcoin): disable all non-segwit addresses * feat(ur): support BBQR for coldcard qr * flutter 3.27.0 -> 3.27.4 * feat: raw xpub qr support for coldcard q * fix: update ledger_bitcoin fix: update switching button * fix: restore button not showing up after scanning * feat: commit raw tx (from coldcard Q) * feat: legacy crypto-psbt support for seed signer * chore: address comments from review * fix(cw_bitcoin): disable sp and payjoin for cupcake * feat: enable DNSSEC BIP353 in PSBT (#2397) * feat: enable DNSSEC BIP353 in PSBT * chore: put bitcoin view only behind a featureflag * fix: update Monero wallet to work with new internal URQR return format * feat: add "Restore from Cupcake App" option with localized strings * feat: enable DNSSEC BIP353 in PSBT using dnssec-prover (#2401) * fix: use dnssec-prover chore: bump ndk to r28c * fix: validate DNSSEC proof before updating the UI * fix: rename button to copy debug info to not hide actions on URQR widget --------- Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>

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