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>
This commit is contained in:
cyan
2025-07-29 17:01:33 +03:00
committed by GitHub
co-authored by Konstantin Ullrich
parent 10499814bb
commit e4806ad141
98 changed files with 796 additions and 247 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
PR_test_build:
runs-on: linux-amd64
container:
image: ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly
image: ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-ndkr28-go1.24.1-ruststablenightly
env:
STORE_PASS: test@cake_wallet
KEY_PASS: test@cake_wallet
+1 -1
View File
@@ -9,7 +9,7 @@ jobs:
PR_test_build:
runs-on: linux-amd64
container:
image: ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly
image: ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-ndkr28-go1.24.1-ruststablenightly
env:
STORE_PASS: test@cake_wallet
KEY_PASS: test@cake_wallet
+5 -4
View File
@@ -1,4 +1,4 @@
# docker buildx build --push --pull --platform linux/amd64,linux/arm64 . -f Dockerfile -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.0-go1.24.1-ruststablenightly
# docker buildx build --push --pull --platform linux/amd64,linux/arm64 . -f Dockerfile -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-ndkr28-go1.24.1-ruststablenightly
# Heavily inspired by cirrusci images
# https://github.com/cirruslabs/docker-images-android/blob/master/sdk/tools/Dockerfile
@@ -15,7 +15,7 @@ LABEL org.opencontainers.image.source=https://github.com/cake-tech/cake_wallet
ENV GOLANG_VERSION=1.24.1
# Pin Flutter version to latest known-working version
ENV FLUTTER_VERSION=3.27.0
ENV FLUTTER_VERSION=3.27.4
# Pin Android Studio, platform, and build tools versions to latest known-working version
# Comes from https://developer.android.com/studio/#command-tools
@@ -138,11 +138,12 @@ RUN ARCH=$(uname -m) && \
"build-tools;35.0.0"
# Install extra NDK dependency for sp_scanner
ENV ANDROID_NDK_VERSION=27.2.12479018
ENV ANDROID_NDK_VERSION=28.2.13676358
RUN ARCH=$(uname -m) && \
if [ "$ARCH" != "x86_64" ]; then exit 0; fi \
&& yes | sdkmanager "ndk;$ANDROID_NDK_VERSION" \
"ndk;27.0.12077973"
"ndk;27.0.12077973" \
"ndk;27.2.12479018"
# Install dependencies for tests
# Comes from https://github.com/ReactiveCircus/android-emulator-runner
+1 -1
View File
@@ -94,7 +94,7 @@ android {
}
}
ndkVersion "27.0.12077973"
ndkVersion "28.2.13676358"
}
flutter {
Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

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