mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 20:39:51 +00:00
Merge 8a12e2c2c7
into 1a5601f755
This commit is contained in:
commit
1fc440f53c
45 changed files with 478 additions and 155 deletions
|
@ -55,7 +55,7 @@ jobs:
|
||||||
- name: Flutter action
|
- name: Flutter action
|
||||||
uses: subosito/flutter-action@v1
|
uses: subosito/flutter-action@v1
|
||||||
with:
|
with:
|
||||||
flutter-version: "3.27.0"
|
flutter-version: "3.27.4"
|
||||||
channel: stable
|
channel: stable
|
||||||
|
|
||||||
- name: Install package dependencies
|
- name: Install package dependencies
|
||||||
|
|
2
.github/workflows/pr_test_build_android.yml
vendored
2
.github/workflows/pr_test_build_android.yml
vendored
|
@ -9,7 +9,7 @@ jobs:
|
||||||
PR_test_build:
|
PR_test_build:
|
||||||
runs-on: linux-amd64
|
runs-on: linux-amd64
|
||||||
container:
|
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-go1.24.1-ruststablenightly
|
||||||
env:
|
env:
|
||||||
STORE_PASS: test@cake_wallet
|
STORE_PASS: test@cake_wallet
|
||||||
KEY_PASS: test@cake_wallet
|
KEY_PASS: test@cake_wallet
|
||||||
|
|
2
.github/workflows/pr_test_build_linux.yml
vendored
2
.github/workflows/pr_test_build_linux.yml
vendored
|
@ -9,7 +9,7 @@ jobs:
|
||||||
PR_test_build:
|
PR_test_build:
|
||||||
runs-on: linux-amd64
|
runs-on: linux-amd64
|
||||||
container:
|
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-go1.24.1-ruststablenightly
|
||||||
env:
|
env:
|
||||||
STORE_PASS: test@cake_wallet
|
STORE_PASS: test@cake_wallet
|
||||||
KEY_PASS: test@cake_wallet
|
KEY_PASS: test@cake_wallet
|
||||||
|
|
|
@ -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-go1.24.1-ruststablenightly
|
||||||
|
|
||||||
# Heavily inspired by cirrusci images
|
# Heavily inspired by cirrusci images
|
||||||
# https://github.com/cirruslabs/docker-images-android/blob/master/sdk/tools/Dockerfile
|
# 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
|
ENV GOLANG_VERSION=1.24.1
|
||||||
|
|
||||||
# Pin Flutter version to latest known-working version
|
# 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
|
# Pin Android Studio, platform, and build tools versions to latest known-working version
|
||||||
# Comes from https://developer.android.com/studio/#command-tools
|
# Comes from https://developer.android.com/studio/#command-tools
|
||||||
|
|
|
@ -28,6 +28,16 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
||||||
BitcoinReceivePageOption.p2pkh
|
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 = [
|
static const allLitecoin = [
|
||||||
BitcoinReceivePageOption.p2wpkh,
|
BitcoinReceivePageOption.p2wpkh,
|
||||||
BitcoinReceivePageOption.mweb,
|
BitcoinReceivePageOption.mweb,
|
||||||
|
|
|
@ -24,6 +24,7 @@ import 'package:cw_core/encryption_file_utils.dart';
|
||||||
import 'package:cw_core/payjoin_session.dart';
|
import 'package:cw_core/payjoin_session.dart';
|
||||||
import 'package:cw_core/pending_transaction.dart';
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.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_info.dart';
|
||||||
import 'package:cw_core/wallet_keys_file.dart';
|
import 'package:cw_core/wallet_keys_file.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -32,6 +33,9 @@ import 'package:ledger_bitcoin/ledger_bitcoin.dart';
|
||||||
import 'package:ledger_bitcoin/psbt.dart';
|
import 'package:ledger_bitcoin/psbt.dart';
|
||||||
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
|
||||||
import 'package:mobx/mobx.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';
|
part 'bitcoin_wallet.g.dart';
|
||||||
|
|
||||||
|
@ -352,7 +356,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
as PendingBitcoinTransaction;
|
as PendingBitcoinTransaction;
|
||||||
|
|
||||||
final payjoinUri = credentials.payjoinUri;
|
final payjoinUri = credentials.payjoinUri;
|
||||||
if (payjoinUri == null) return tx;
|
if (payjoinUri == null && !tx.shouldCommitUR()) return tx;
|
||||||
|
|
||||||
final transaction = await buildPsbt(
|
final transaction = await buildPsbt(
|
||||||
utxos: tx.utxos,
|
utxos: tx.utxos,
|
||||||
|
@ -370,14 +374,19 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
outputOrdering: BitcoinOrdering.none,
|
outputOrdering: BitcoinOrdering.none,
|
||||||
enableRBF: true,
|
enableRBF: true,
|
||||||
publicKeys: tx.publicKeys!,
|
publicKeys: tx.publicKeys!,
|
||||||
masterFingerprint: Uint8List(0));
|
masterFingerprint: Uint8List.fromList([0, 0, 0, 0]));
|
||||||
|
|
||||||
final originalPsbt = await signPsbt(
|
if (tx.shouldCommitUR()) {
|
||||||
base64.encode(transaction.asPsbtV0()), getUtxoWithPrivateKeys());
|
tx.unsignedPsbt = transaction.asPsbtV0();
|
||||||
|
return tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
final originalPsbt =
|
||||||
|
await signPsbt(base64.encode(transaction.asPsbtV0()), getUtxoWithPrivateKeys());
|
||||||
|
|
||||||
tx.commitOverride = () async {
|
tx.commitOverride = () async {
|
||||||
final sender = await payjoinManager.initSender(
|
final sender =
|
||||||
payjoinUri, originalPsbt, int.parse(tx.feeRate));
|
await payjoinManager.initSender(payjoinUri!, originalPsbt, int.parse(tx.feeRate));
|
||||||
payjoinManager.spawnNewSender(
|
payjoinManager.spawnNewSender(
|
||||||
sender: sender, pjUrl: payjoinUri, amount: BigInt.from(tx.amount));
|
sender: sender, pjUrl: payjoinUri, amount: BigInt.from(tx.amount));
|
||||||
};
|
};
|
||||||
|
@ -405,6 +414,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
feeRate: "",
|
feeRate: "",
|
||||||
network: network,
|
network: network,
|
||||||
hasChange: true,
|
hasChange: true,
|
||||||
|
isViewOnly: false,
|
||||||
).commit();
|
).commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,6 +440,44 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
||||||
return base64Encode(psbt.asPsbtV0());
|
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
|
@override
|
||||||
Future<String> signMessage(String message, {String? address = null}) async {
|
Future<String> signMessage(String message, {String? address = null}) async {
|
||||||
if (walletInfo.isHardwareWallet) {
|
if (walletInfo.isHardwareWallet) {
|
||||||
|
|
|
@ -54,6 +54,17 @@ class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials {
|
||||||
final String wif;
|
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 {
|
class BitcoinRestoreWalletFromHardware extends WalletCredentials {
|
||||||
BitcoinRestoreWalletFromHardware({
|
BitcoinRestoreWalletFromHardware({
|
||||||
required String name,
|
required String name,
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
class BitcoinWalletKeys {
|
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 wif;
|
||||||
final String privateKey;
|
final String privateKey;
|
||||||
final String publicKey;
|
final String publicKey;
|
||||||
|
final String xpub;
|
||||||
|
|
||||||
|
Map<String, String> toJson() => {
|
||||||
|
'wif': wif,
|
||||||
|
'privateKey': privateKey,
|
||||||
|
'publicKey': publicKey,
|
||||||
|
'xpub': xpub
|
||||||
|
};
|
||||||
}
|
}
|
|
@ -20,7 +20,7 @@ import 'package:bip39/bip39.dart' as bip39;
|
||||||
class BitcoinWalletService extends WalletService<
|
class BitcoinWalletService extends WalletService<
|
||||||
BitcoinNewWalletCredentials,
|
BitcoinNewWalletCredentials,
|
||||||
BitcoinRestoreWalletFromSeedCredentials,
|
BitcoinRestoreWalletFromSeedCredentials,
|
||||||
BitcoinRestoreWalletFromWIFCredentials,
|
BitcoinWalletFromKeysCredentials,
|
||||||
BitcoinRestoreWalletFromHardware> {
|
BitcoinRestoreWalletFromHardware> {
|
||||||
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource,
|
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource,
|
||||||
this.payjoinSessionSource, this.alwaysScan, this.isDirect);
|
this.payjoinSessionSource, this.alwaysScan, this.isDirect);
|
||||||
|
@ -169,9 +169,25 @@ class BitcoinWalletService extends WalletService<
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
|
Future<BitcoinWallet> restoreFromKeys(BitcoinWalletFromKeysCredentials credentials,
|
||||||
{bool? isTestnet}) async =>
|
{bool? isTestnet}) async {
|
||||||
throw UnimplementedError();
|
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
|
@override
|
||||||
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
|
Future<BitcoinWallet> restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials,
|
||||||
|
|
|
@ -280,11 +280,26 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
BitcoinWalletKeys get keys => BitcoinWalletKeys(
|
BitcoinWalletKeys get keys {
|
||||||
wif: WifEncoder.encode(hd.privateKey.raw, netVer: network.wifNetVer),
|
String? wif;
|
||||||
privateKey: hd.privateKey.toHex(),
|
String? privateKey;
|
||||||
publicKey: hd.publicKey.toHex(),
|
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;
|
String _password;
|
||||||
List<BitcoinUnspent> unspentCoins;
|
List<BitcoinUnspent> unspentCoins;
|
||||||
|
@ -666,7 +681,7 @@ abstract class ElectrumWalletBase
|
||||||
);
|
);
|
||||||
spendsSilentPayment = true;
|
spendsSilentPayment = true;
|
||||||
isSilentPayment = true;
|
isSilentPayment = true;
|
||||||
} else if (!isHardwareWallet) {
|
} else if (!isHardwareWallet && keys.privateKey.isNotEmpty) {
|
||||||
privkey =
|
privkey =
|
||||||
generateECPrivate(hd: hd, index: utx.bitcoinAddressRecord.index, network: network);
|
generateECPrivate(hd: hd, index: utx.bitcoinAddressRecord.index, network: network);
|
||||||
}
|
}
|
||||||
|
@ -1104,7 +1119,8 @@ abstract class ElectrumWalletBase
|
||||||
network: network,
|
network: network,
|
||||||
hasChange: estimatedTx.hasChange,
|
hasChange: estimatedTx.hasChange,
|
||||||
isSendAll: estimatedTx.isSendAll,
|
isSendAll: estimatedTx.isSendAll,
|
||||||
hasTaprootInputs: false, // ToDo: (Konsti) Support Taproot
|
hasTaprootInputs: false, // ToDo: (Konsti) Support Taproot,
|
||||||
|
isViewOnly: false,
|
||||||
)..addListener((transaction) async {
|
)..addListener((transaction) async {
|
||||||
transactionHistory.addOne(transaction);
|
transactionHistory.addOne(transaction);
|
||||||
await updateBalance();
|
await updateBalance();
|
||||||
|
@ -1138,6 +1154,7 @@ abstract class ElectrumWalletBase
|
||||||
bool hasTaprootInputs = false;
|
bool hasTaprootInputs = false;
|
||||||
|
|
||||||
final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) {
|
final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) {
|
||||||
|
if (keys.privateKey.isEmpty) return "";
|
||||||
String error = "Cannot find private key.";
|
String error = "Cannot find private key.";
|
||||||
|
|
||||||
ECPrivateInfo? key;
|
ECPrivateInfo? key;
|
||||||
|
@ -1175,18 +1192,21 @@ abstract class ElectrumWalletBase
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return PendingBitcoinTransaction(transaction, type,
|
return PendingBitcoinTransaction(
|
||||||
electrumClient: electrumClient,
|
transaction,
|
||||||
amount: estimatedTx.amount,
|
type,
|
||||||
fee: estimatedTx.fee,
|
electrumClient: electrumClient,
|
||||||
feeRate: feeRateInt.toString(),
|
amount: estimatedTx.amount,
|
||||||
network: network,
|
fee: estimatedTx.fee,
|
||||||
hasChange: estimatedTx.hasChange,
|
feeRate: feeRateInt.toString(),
|
||||||
isSendAll: estimatedTx.isSendAll,
|
network: network,
|
||||||
hasTaprootInputs: hasTaprootInputs,
|
hasChange: estimatedTx.hasChange,
|
||||||
utxos: estimatedTx.utxos,
|
isSendAll: estimatedTx.isSendAll,
|
||||||
publicKeys: estimatedTx.publicKeys)
|
hasTaprootInputs: hasTaprootInputs,
|
||||||
..addListener((transaction) async {
|
utxos: estimatedTx.utxos,
|
||||||
|
publicKeys: estimatedTx.publicKeys,
|
||||||
|
isViewOnly: keys.privateKey.isEmpty,
|
||||||
|
)..addListener((transaction) async {
|
||||||
transactionHistory.addOne(transaction);
|
transactionHistory.addOne(transaction);
|
||||||
if (estimatedTx.spendsSilentPayment) {
|
if (estimatedTx.spendsSilentPayment) {
|
||||||
transactionHistory.transactions.values.forEach((tx) {
|
transactionHistory.transactions.values.forEach((tx) {
|
||||||
|
@ -1847,6 +1867,7 @@ abstract class ElectrumWalletBase
|
||||||
network: network,
|
network: network,
|
||||||
hasChange: changeOutputs.isNotEmpty,
|
hasChange: changeOutputs.isNotEmpty,
|
||||||
feeRate: newFee.toString(),
|
feeRate: newFee.toString(),
|
||||||
|
isViewOnly: keys.privateKey.isEmpty,
|
||||||
)..addListener((transaction) async {
|
)..addListener((transaction) async {
|
||||||
transactionHistory.transactions.values.forEach((tx) {
|
transactionHistory.transactions.values.forEach((tx) {
|
||||||
if (tx.id == hash) {
|
if (tx.id == hash) {
|
||||||
|
|
|
@ -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:cw_bitcoin/electrum_wallet.dart';
|
||||||
import 'package:grpc/grpc.dart';
|
import 'package:grpc/grpc.dart';
|
||||||
import 'package:cw_bitcoin/exceptions.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_core/wallet_type.dart';
|
||||||
import 'package:cw_mweb/cw_mweb.dart';
|
import 'package:cw_mweb/cw_mweb.dart';
|
||||||
import 'package:cw_mweb/mwebd.pb.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 {
|
class PendingBitcoinTransaction with PendingTransaction {
|
||||||
PendingBitcoinTransaction(
|
PendingBitcoinTransaction(
|
||||||
|
@ -28,6 +35,8 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||||
this.utxos = const [],
|
this.utxos = const [],
|
||||||
this.publicKeys,
|
this.publicKeys,
|
||||||
this.commitOverride,
|
this.commitOverride,
|
||||||
|
this.unsignedPsbt,
|
||||||
|
required this.isViewOnly,
|
||||||
}) : _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
|
}) : _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
|
||||||
|
|
||||||
final WalletType type;
|
final WalletType type;
|
||||||
|
@ -40,6 +49,7 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||||
final bool isSendAll;
|
final bool isSendAll;
|
||||||
final bool hasChange;
|
final bool hasChange;
|
||||||
final bool hasTaprootInputs;
|
final bool hasTaprootInputs;
|
||||||
|
final bool isViewOnly;
|
||||||
List<UtxoWithAddress> utxos;
|
List<UtxoWithAddress> utxos;
|
||||||
bool isMweb;
|
bool isMweb;
|
||||||
String? changeAddressOverride;
|
String? changeAddressOverride;
|
||||||
|
@ -49,6 +59,8 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||||
final Map<String, PublicKeyWithDerivationPath>? publicKeys;
|
final Map<String, PublicKeyWithDerivationPath>? publicKeys;
|
||||||
Future<void> Function()? commitOverride;
|
Future<void> Function()? commitOverride;
|
||||||
|
|
||||||
|
Uint8List? unsignedPsbt;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get id => idOverride ?? _tx.txId();
|
String get id => idOverride ?? _tx.txId();
|
||||||
|
|
||||||
|
@ -72,9 +84,11 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||||
try {
|
try {
|
||||||
final change = _tx.outputs.firstWhere((out) => out.isChange);
|
final change = _tx.outputs.firstWhere((out) => out.isChange);
|
||||||
if (changeAddressOverride != null) {
|
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 (_) {
|
} catch (_) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -123,12 +137,14 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||||
|
|
||||||
Future<void> _ltcCommit() async {
|
Future<void> _ltcCommit() async {
|
||||||
try {
|
try {
|
||||||
final resp = await CwMweb.broadcast(BroadcastRequest(rawTx: BytesUtils.fromHexString(hex)));
|
final resp = await CwMweb.broadcast(
|
||||||
|
BroadcastRequest(rawTx: BytesUtils.fromHexString(hex)));
|
||||||
idOverride = resp.txid;
|
idOverride = resp.txid;
|
||||||
} on GrpcError catch (e) {
|
} on GrpcError catch (e) {
|
||||||
throw BitcoinTransactionCommitFailed(errorMessage: e.message);
|
throw BitcoinTransactionCommitFailed(errorMessage: e.message);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw BitcoinTransactionCommitFailed(errorMessage: "Unknown error: ${e.toString()}");
|
throw BitcoinTransactionCommitFailed(
|
||||||
|
errorMessage: "Unknown error: ${e.toString()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +163,8 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||||
_listeners.forEach((listener) => listener(transactionInfo()));
|
_listeners.forEach((listener) => listener(transactionInfo()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void addListener(void Function(ElectrumTransactionInfo transaction) listener) =>
|
void addListener(
|
||||||
|
void Function(ElectrumTransactionInfo transaction) listener) =>
|
||||||
_listeners.add(listener);
|
_listeners.add(listener);
|
||||||
|
|
||||||
ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type,
|
ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type,
|
||||||
|
@ -162,9 +179,40 @@ class PendingBitcoinTransaction with PendingTransaction {
|
||||||
inputAddresses: _tx.inputs.map((input) => input.txId).toList(),
|
inputAddresses: _tx.inputs.map((input) => input.txId).toList(),
|
||||||
outputAddresses: outputAddresses,
|
outputAddresses: outputAddresses,
|
||||||
fee: fee);
|
fee: fee);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String?> commitUR() {
|
bool shouldCommitUR() => isViewOnly;
|
||||||
throw UnimplementedError();
|
|
||||||
|
@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"),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,15 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.11.0"
|
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:
|
bech32:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -588,8 +597,8 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "packages/ledger-bitcoin"
|
path: "packages/ledger-bitcoin"
|
||||||
ref: trunk
|
ref: eab179d487cddda3f647f6608115a89662facde4
|
||||||
resolved-ref: e93254f3ff3f996fb91f65a1e7ceffb9f510b4c8
|
resolved-ref: eab179d487cddda3f647f6608115a89662facde4
|
||||||
url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
|
url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.3"
|
version: "0.0.3"
|
||||||
|
@ -1101,6 +1110,15 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.0"
|
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:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1182,5 +1200,5 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.2"
|
version: "2.2.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.6.0 <4.0.0"
|
dart: ">=3.6.2 <4.0.0"
|
||||||
flutter: ">=3.27.0"
|
flutter: ">=3.27.0"
|
||||||
|
|
|
@ -49,7 +49,7 @@ dependencies:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
|
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
|
||||||
path: packages/ledger-bitcoin
|
path: packages/ledger-bitcoin
|
||||||
ref: trunk
|
ref: eab179d487cddda3f647f6608115a89662facde4
|
||||||
ledger_litecoin:
|
ledger_litecoin:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
|
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
|
||||||
|
@ -58,6 +58,14 @@ dependencies:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/sneurlax/socks_socket
|
url: https://github.com/sneurlax/socks_socket
|
||||||
ref: e6232c53c1595469931ababa878759a067c02e94
|
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:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -86,7 +86,7 @@ class PendingBitcoinCashTransaction with PendingTransaction {
|
||||||
isReplaced: false,
|
isReplaced: false,
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
Future<String?> commitUR() {
|
Future<Map<String, String>> commitUR() {
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,5 +17,5 @@ mixin PendingTransaction {
|
||||||
bool shouldCommitUR() => false;
|
bool shouldCommitUR() => false;
|
||||||
|
|
||||||
Future<void> commit();
|
Future<void> commit();
|
||||||
Future<String?> commitUR();
|
Future<Map<String, String>> commitUR();
|
||||||
}
|
}
|
||||||
|
|
|
@ -829,4 +829,4 @@ packages:
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.6.0 <4.0.0"
|
dart: ">=3.6.0 <4.0.0"
|
||||||
flutter: ">=3.27.0"
|
flutter: ">=3.27.4"
|
||||||
|
|
|
@ -33,7 +33,7 @@ class DecredPendingTransaction with PendingTransaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String?> commitUR() {
|
Future<Map<String, String>> commitUR() {
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ class PendingEVMChainTransaction with PendingTransaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String?> commitUR() {
|
Future<Map<String, String>> commitUR() {
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ class PendingMoneroTransaction with PendingTransaction {
|
||||||
String get feeFormatted => AmountConverter.amountIntToString(
|
String get feeFormatted => AmountConverter.amountIntToString(
|
||||||
CryptoCurrency.xmr, pendingTransactionDescription.fee);
|
CryptoCurrency.xmr, pendingTransactionDescription.fee);
|
||||||
|
|
||||||
|
@override
|
||||||
bool shouldCommitUR() => isViewOnly;
|
bool shouldCommitUR() => isViewOnly;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -64,7 +65,7 @@ class PendingMoneroTransaction with PendingTransaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String?> commitUR() async {
|
Future<Map<String, String>> commitUR() async {
|
||||||
try {
|
try {
|
||||||
final ret = await monero_transaction_history.commitTransactionFromPointerAddress(
|
final ret = await monero_transaction_history.commitTransactionFromPointerAddress(
|
||||||
address: pendingTransactionDescription.pointerAddress,
|
address: pendingTransactionDescription.pointerAddress,
|
||||||
|
@ -74,7 +75,10 @@ class PendingMoneroTransaction with PendingTransaction {
|
||||||
await Future.delayed(const Duration(milliseconds: 250));
|
await Future.delayed(const Duration(milliseconds: 250));
|
||||||
await wallet.fetchTransactions();
|
await wallet.fetchTransactions();
|
||||||
}());
|
}());
|
||||||
return ret;
|
if (ret == null) return {};
|
||||||
|
return {
|
||||||
|
"xmr-txsigned": ret,
|
||||||
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
final message = e.toString();
|
final message = e.toString();
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ class PendingNanoTransaction with PendingTransaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String?> commitUR() {
|
Future<Map<String, String>> commitUR() {
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -978,4 +978,4 @@ packages:
|
||||||
version: "3.1.3"
|
version: "3.1.3"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.6.0 <4.0.0"
|
dart: ">=3.6.0 <4.0.0"
|
||||||
flutter: ">=3.27.0"
|
flutter: ">=3.27.4"
|
||||||
|
|
|
@ -41,7 +41,7 @@ class PendingSolanaTransaction with PendingTransaction {
|
||||||
String get id => '';
|
String get id => '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String?> commitUR() {
|
Future<Map<String, String>> commitUR() {
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ class PendingTronTransaction with PendingTransaction {
|
||||||
String get id => '';
|
String get id => '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String?> commitUR() {
|
Future<Map<String, String>> commitUR() {
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ class PendingWowneroTransaction with PendingTransaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String?> commitUR() {
|
Future<Map<String, String>> commitUR() {
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ class PendingZanoTransaction with PendingTransaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String?> commitUR() {
|
Future<Map<String, String>> commitUR() {
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
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.
|
# NOTE: Replace `main` with the latest release tag available at https://github.com/cake-tech/cake_wallet/releases/latest.
|
||||||
cd cake_wallet
|
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 build -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-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 run -v$(pwd):$(pwd) -w $(pwd) -i --rm ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-go1.24.1-ruststablenightly bash -x << EOF
|
||||||
set -x -e
|
set -x -e
|
||||||
pushd scripts/android
|
pushd scripts/android
|
||||||
source ./app_env.sh cakewallet
|
source ./app_env.sh cakewallet
|
||||||
|
|
|
@ -7,7 +7,7 @@ The following are the system requirements to build Cake Wallet for your iOS devi
|
||||||
```txt
|
```txt
|
||||||
macOS 15.3.1
|
macOS 15.3.1
|
||||||
Xcode 16.2
|
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.
|
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
|
### 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
|
### 4. Installing Rust
|
||||||
|
|
||||||
|
@ -65,7 +65,7 @@ The output of this command should appear like this, indicating successful instal
|
||||||
|
|
||||||
```zsh
|
```zsh
|
||||||
Doctor summary (to see all details, run flutter doctor -v):
|
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)
|
[✓] Xcode - develop for iOS and macOS (Xcode 16.2)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -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
|
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.
|
# NOTE: Replace `main` with the latest release tag available at https://github.com/cake-tech/cake_wallet/releases/latest.
|
||||||
cd cake_wallet
|
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 build -t ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-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 run --privileged -v$(pwd):$(pwd) -w $(pwd) -i --rm ghcr.io/cake-tech/cake_wallet:debian12-flutter3.27.4-go1.24.1-ruststablenightly bash -x << EOF
|
||||||
set -x -e
|
set -x -e
|
||||||
pushd scripts
|
pushd scripts
|
||||||
./gen_android_manifest.sh
|
./gen_android_manifest.sh
|
||||||
|
|
|
@ -7,7 +7,7 @@ The following are the system requirements to build Cake Wallet for your macOS de
|
||||||
```txt
|
```txt
|
||||||
macOS 15.3.1
|
macOS 15.3.1
|
||||||
Xcode 16.2
|
Xcode 16.2
|
||||||
Flutter 3.27.0
|
Flutter 3.27.4
|
||||||
```
|
```
|
||||||
|
|
||||||
### 1. Installing dependencies
|
### 1. Installing dependencies
|
||||||
|
@ -34,9 +34,9 @@ sudo xcodebuild -runFirstLaunch
|
||||||
|
|
||||||
### 3. Installing Flutter
|
### 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
|
### 4. Installing Rust
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ The output of this command should appear like this, indicating successful instal
|
||||||
|
|
||||||
```zsh
|
```zsh
|
||||||
Doctor summary (to see all details, run flutter doctor -v):
|
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)
|
[✓] Xcode - develop for iOS and macOS (Xcode 16.2)
|
||||||
...
|
...
|
||||||
|
|
|
@ -6,18 +6,18 @@ The following are the system requirements to build Cake Wallet for your Windows
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
Windows 10 or later (64-bit), x86-64 based
|
Windows 10 or later (64-bit), x86-64 based
|
||||||
Flutter 3.27.0
|
Flutter 3.27.4
|
||||||
```
|
```
|
||||||
|
|
||||||
### 1. Installing Flutter
|
### 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:
|
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.
|
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
|
### 2. Install Development Tools
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,14 @@ class CWBitcoin extends Bitcoin {
|
||||||
passphrase: passphrase,
|
passphrase: passphrase,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletCredentials createBitcoinWalletFromKeys({
|
||||||
|
required String name,
|
||||||
|
required String password,
|
||||||
|
required String xpub,
|
||||||
|
}) =>
|
||||||
|
BitcoinWalletFromKeysCredentials(name: name, password: password, xpub: xpub);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
WalletCredentials createBitcoinRestoreWalletFromWIFCredentials(
|
WalletCredentials createBitcoinRestoreWalletFromWIFCredentials(
|
||||||
{required String name,
|
{required String name,
|
||||||
|
@ -62,11 +70,7 @@ class CWBitcoin extends Bitcoin {
|
||||||
final bitcoinWallet = wallet as ElectrumWallet;
|
final bitcoinWallet = wallet as ElectrumWallet;
|
||||||
final keys = bitcoinWallet.keys;
|
final keys = bitcoinWallet.keys;
|
||||||
|
|
||||||
return <String, String>{
|
return bitcoinWallet.keys.toJson();
|
||||||
'wif': keys.wif,
|
|
||||||
'privateKey': keys.privateKey,
|
|
||||||
'publicKey': keys.publicKey
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -276,7 +280,14 @@ class CWBitcoin extends Bitcoin {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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
|
@override
|
||||||
List<ReceivePageOption> getLitecoinReceivePageOptions() {
|
List<ReceivePageOption> getLitecoinReceivePageOptions() {
|
||||||
|
@ -719,6 +730,12 @@ class CWBitcoin extends Bitcoin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> commitPsbtUR(Object wallet, List<String> urCodes) {
|
||||||
|
final _wallet = wallet as BitcoinWalletBase;
|
||||||
|
return _wallet.commitPsbtUR(urCodes);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getPayjoinEndpoint(Object wallet) {
|
String getPayjoinEndpoint(Object wallet) {
|
||||||
final _wallet = wallet as ElectrumWallet;
|
final _wallet = wallet as ElectrumWallet;
|
||||||
|
|
|
@ -971,7 +971,7 @@ Future<void> setup({
|
||||||
|
|
||||||
getIt.registerFactory(() => AnimatedURModel(getIt.get<AppStore>()));
|
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));
|
AnimatedURPage(getIt.get<AnimatedURModel>(), urQr: urQr));
|
||||||
|
|
||||||
getIt.registerFactoryParam<ContactViewModel, ContactRecord?, void>(
|
getIt.registerFactoryParam<ContactViewModel, ContactRecord?, void>(
|
||||||
|
|
|
@ -198,14 +198,14 @@ class WalletRestoreFromKeysFormState extends State<WalletRestoreFromKeysForm> {
|
||||||
Widget _restoreFromKeysFormFields() {
|
Widget _restoreFromKeysFormFields() {
|
||||||
// Decred can only restore a view only wallet with an account pubkey. Other
|
// Decred can only restore a view only wallet with an account pubkey. Other
|
||||||
// fields are not used.
|
// fields are not used.
|
||||||
if (widget.walletRestoreViewModel.type == WalletType.decred) {
|
if (widget.walletRestoreViewModel.onlyViewKeyRestore) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
BaseTextFormField(
|
BaseTextFormField(
|
||||||
controller: viewKeyController,
|
controller: viewKeyController,
|
||||||
hintText: S.of(context).view_key_public,
|
hintText: S.of(context).view_key_public,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -253,13 +253,14 @@ class WalletRestoreFromKeysFormState extends State<WalletRestoreFromKeysForm> {
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
BlockchainHeightWidget(
|
if (widget.walletRestoreViewModel.hasBlockchainHeightSelector)
|
||||||
key: blockchainHeightKey,
|
BlockchainHeightWidget(
|
||||||
hasDatePicker: widget.walletRestoreViewModel.type != WalletType.haven,
|
key: blockchainHeightKey,
|
||||||
onHeightChange: (_) => null,
|
hasDatePicker: widget.walletRestoreViewModel.type != WalletType.haven,
|
||||||
onHeightOrDateEntered: widget.onHeightOrDateEntered,
|
onHeightChange: (_) => null,
|
||||||
walletType: widget.walletRestoreViewModel.type,
|
onHeightOrDateEntered: widget.onHeightOrDateEntered,
|
||||||
),
|
walletType: widget.walletRestoreViewModel.type,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -199,7 +199,7 @@ class WalletRestorePage extends BasePage {
|
||||||
credentials['seed'] =
|
credentials['seed'] =
|
||||||
walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.text;
|
walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.text;
|
||||||
|
|
||||||
if (walletRestoreViewModel.hasBlockchainHeightLanguageSelector) {
|
if (walletRestoreViewModel.hasBlockchainHeightSelector) {
|
||||||
credentials['height'] =
|
credentials['height'] =
|
||||||
walletRestoreFromSeedFormKey.currentState!.blockchainHeightKey.currentState?.height ??
|
walletRestoreFromSeedFormKey.currentState!.blockchainHeightKey.currentState?.height ??
|
||||||
-1;
|
-1;
|
||||||
|
@ -219,7 +219,7 @@ class WalletRestorePage extends BasePage {
|
||||||
credentials['name'] =
|
credentials['name'] =
|
||||||
walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
|
walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
|
||||||
credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text;
|
credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text;
|
||||||
if (walletRestoreViewModel.type != WalletType.decred) {
|
if (!walletRestoreViewModel.onlyViewKeyRestore) {
|
||||||
credentials['address'] =
|
credentials['address'] =
|
||||||
walletRestoreFromKeysFormKey.currentState!.addressController.text;
|
walletRestoreFromKeysFormKey.currentState!.addressController.text;
|
||||||
credentials['spendKey'] =
|
credentials['spendKey'] =
|
||||||
|
@ -519,7 +519,7 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onViewKeyEntered: (bool entered) {
|
onViewKeyEntered: (bool entered) {
|
||||||
if (walletRestoreViewModel.type == WalletType.decred) {
|
if (widget.walletRestoreViewModel.onlyViewKeyRestore) {
|
||||||
walletRestoreViewModel.isButtonEnabled = entered;
|
walletRestoreViewModel.isButtonEnabled = entered;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -536,7 +536,7 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
|
||||||
key: widget.walletRestoreFromSeedFormKey,
|
key: widget.walletRestoreFromSeedFormKey,
|
||||||
restoredWallet: walletRestoreViewModel.restoredWallet,
|
restoredWallet: walletRestoreViewModel.restoredWallet,
|
||||||
seedSettingsViewModel: widget.seedSettingsViewModel,
|
seedSettingsViewModel: widget.seedSettingsViewModel,
|
||||||
displayBlockHeightSelector: widget.walletRestoreViewModel.hasBlockchainHeightLanguageSelector,
|
displayBlockHeightSelector: widget.walletRestoreViewModel.hasBlockchainHeightSelector,
|
||||||
displayLanguageSelector: widget.walletRestoreViewModel.hasSeedLanguageSelector,
|
displayLanguageSelector: widget.walletRestoreViewModel.hasSeedLanguageSelector,
|
||||||
type: widget.walletRestoreViewModel.type,
|
type: widget.walletRestoreViewModel.type,
|
||||||
blockHeightFocusNode: widget.blockHeightFocusNode,
|
blockHeightFocusNode: widget.blockHeightFocusNode,
|
||||||
|
@ -563,7 +563,7 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
|
||||||
}
|
}
|
||||||
|
|
||||||
void _validateOnChange({bool isPolyseed = false}) {
|
void _validateOnChange({bool isPolyseed = false}) {
|
||||||
if (!isPolyseed && walletRestoreViewModel.hasBlockchainHeightLanguageSelector) {
|
if (!isPolyseed && walletRestoreViewModel.hasBlockchainHeightSelector) {
|
||||||
final hasHeight = walletRestoreFromSeedFormKey
|
final hasHeight = walletRestoreFromSeedFormKey
|
||||||
.currentState?.blockchainHeightKey.currentState?.restoreHeightController.text.isNotEmpty;
|
.currentState?.blockchainHeightKey.currentState?.restoreHeightController.text.isNotEmpty;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import 'dart:async';
|
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/entities/qr_scanner.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/monero/monero.dart';
|
import 'package:cake_wallet/monero/monero.dart';
|
||||||
|
@ -7,31 +9,33 @@ 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/screens/receive/widgets/qr_image.dart';
|
||||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.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/src/widgets/primary_button.dart';
|
||||||
|
import 'package:cake_wallet/utils/clipboard_util.dart';
|
||||||
|
import 'package:cake_wallet/utils/feature_flag.dart';
|
||||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||||
import 'package:cake_wallet/view_model/animated_ur_model.dart';
|
import 'package:cake_wallet/view_model/animated_ur_model.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
// ur:xmr-txunsigned - unsigned transaction
|
// ur:xmr-txunsigned - unsigned transaction
|
||||||
// should show a scanner afterwards.
|
// should show a scanner afterwards.
|
||||||
|
|
||||||
class AnimatedURPage extends BasePage {
|
class AnimatedURPage extends BasePage {
|
||||||
final bool isAll;
|
final bool isAll;
|
||||||
AnimatedURPage(this.animatedURmodel, {required String urQr, this.isAll = false}) {
|
AnimatedURPage(this.animatedURmodel, {
|
||||||
if (urQr == "export-outputs") {
|
required this.urQr,
|
||||||
this.urQr = monero!.exportOutputsUR(animatedURmodel.wallet, false);
|
this.isAll = false,
|
||||||
} else if (urQr == "export-outputs-all") {
|
});
|
||||||
this.urQr = monero!.exportOutputsUR(animatedURmodel.wallet, true);
|
|
||||||
} else {
|
|
||||||
this.urQr = urQr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
late String urQr;
|
late Map<String, String> urQr;
|
||||||
|
|
||||||
final AnimatedURModel animatedURmodel;
|
final AnimatedURModel animatedURmodel;
|
||||||
|
|
||||||
String get urQrType {
|
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];
|
return first.split('/')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,11 +47,11 @@ class AnimatedURPage extends BasePage {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 64.0),
|
padding: const EdgeInsets.only(top: 64.0),
|
||||||
child: URQR(
|
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),
|
SizedBox(height: 32),
|
||||||
if (urQrType == "ur:xmr-txunsigned" || urQrType == "ur:xmr-output")
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
|
@ -60,8 +64,11 @@ class AnimatedURPage extends BasePage {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: 32),
|
},
|
||||||
if (urQrType == "ur:xmr-output" && !isAll) Padding(
|
|
||||||
|
if (urQrType == "ur:xmr-output" && !isAll) ...{
|
||||||
|
SizedBox(height: 32),
|
||||||
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: double.maxFinite,
|
width: double.maxFinite,
|
||||||
|
@ -73,6 +80,7 @@ class AnimatedURPage extends BasePage {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -81,7 +89,13 @@ class AnimatedURPage extends BasePage {
|
||||||
Navigator.of(context).pushReplacement(
|
Navigator.of(context).pushReplacement(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return AnimatedURPage(animatedURmodel, urQr: "export-outputs-all", isAll: true);
|
return AnimatedURPage(
|
||||||
|
animatedURmodel,
|
||||||
|
urQr: {
|
||||||
|
"export-outputs-all": "export-outputs-all",
|
||||||
|
},
|
||||||
|
isAll: true,
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -106,6 +120,11 @@ class AnimatedURPage extends BasePage {
|
||||||
Navigator.of(context).pop(true);
|
Navigator.of(context).pop(true);
|
||||||
}
|
}
|
||||||
break;
|
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:
|
default:
|
||||||
throw UnimplementedError("unable to handle UR: ${urQrType}");
|
throw UnimplementedError("unable to handle UR: ${urQrType}");
|
||||||
}
|
}
|
||||||
|
@ -124,9 +143,9 @@ class AnimatedURPage extends BasePage {
|
||||||
}
|
}
|
||||||
|
|
||||||
class URQR extends StatefulWidget {
|
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
|
@override
|
||||||
// ignore: library_private_types_in_public_api
|
// ignore: library_private_types_in_public_api
|
||||||
|
@ -160,6 +179,24 @@ class _URQRState extends State<URQR> {
|
||||||
super.dispose();
|
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();
|
||||||
|
selectedInt++;
|
||||||
|
setState(() {
|
||||||
|
nextLabel = keys[(selectedInt) % keys.length];
|
||||||
|
selected = keys[(selectedInt) % keys.length];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
|
@ -168,10 +205,37 @@ class _URQRState extends State<URQR> {
|
||||||
children: [
|
children: [
|
||||||
Center(
|
Center(
|
||||||
child: QrImage(
|
child: QrImage(
|
||||||
data: widget.frames[frame % widget.frames.length], version: -1,
|
data: frames[frame % frames.length],
|
||||||
|
version: -1,
|
||||||
size: 400,
|
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(frames[frame % frames.length]),
|
||||||
|
),
|
||||||
|
}
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,7 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
|
||||||
showLegacySeedTab = widget.walletKeysViewModel.legacySeedSplit.isNotEmpty;
|
showLegacySeedTab = widget.walletKeysViewModel.legacySeedSplit.isNotEmpty;
|
||||||
isLegacySeedOnly = widget.walletKeysViewModel.isLegacySeedOnly;
|
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);
|
_tabController = TabController(length: totalTabs, vsync: this);
|
||||||
}
|
}
|
||||||
|
@ -126,7 +126,7 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
|
||||||
dividerColor: Colors.transparent,
|
dividerColor: Colors.transparent,
|
||||||
padding: EdgeInsets.zero,
|
padding: EdgeInsets.zero,
|
||||||
tabs: [
|
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 (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')),
|
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(
|
child: TabBarView(
|
||||||
controller: _tabController,
|
controller: _tabController,
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
if (_hasSeeds)
|
||||||
padding: const EdgeInsets.only(left: 22, right: 22),
|
Padding(
|
||||||
child: _buildSeedTab(context, false),
|
padding: const EdgeInsets.only(left: 22, right: 22),
|
||||||
),
|
child: _buildSeedTab(context, false),
|
||||||
|
),
|
||||||
if (showKeyTab)
|
if (showKeyTab)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 22, right: 22),
|
padding: const EdgeInsets.only(left: 22, right: 22),
|
||||||
|
|
|
@ -22,7 +22,7 @@ abstract class ReceiveOptionViewModelBase with Store {
|
||||||
switch (walletType) {
|
switch (walletType) {
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
_options = [
|
_options = [
|
||||||
...bitcoin!.getBitcoinReceivePageOptions(),
|
...bitcoin!.getBitcoinReceivePageOptions(_wallet),
|
||||||
...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
|
...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -35,7 +35,7 @@ class RestoredWallet {
|
||||||
|
|
||||||
factory RestoredWallet.fromKey(Map<String, dynamic> json) {
|
factory RestoredWallet.fromKey(Map<String, dynamic> json) {
|
||||||
try {
|
try {
|
||||||
final codeParsed = jsonDecode(json['raw_qr'].toString());
|
final codeParsed = jsonDecode(json['raw_qr'].toString());
|
||||||
if (codeParsed["version"] == 0) {
|
if (codeParsed["version"] == 0) {
|
||||||
json['address'] = codeParsed["primaryAddress"];
|
json['address'] = codeParsed["primaryAddress"];
|
||||||
json['view_key'] = codeParsed["privateViewKey"];
|
json['view_key'] = codeParsed["privateViewKey"];
|
||||||
|
@ -44,6 +44,7 @@ class RestoredWallet {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// fine, we don't care, it is only for monero anyway
|
// fine, we don't care, it is only for monero anyway
|
||||||
}
|
}
|
||||||
|
json['view_key'] ??= json['xpub'];
|
||||||
final height = json['height'] as String?;
|
final height = json['height'] as String?;
|
||||||
return RestoredWallet(
|
return RestoredWallet(
|
||||||
restoreMode: json['mode'] as WalletRestoreMode,
|
restoreMode: json['mode'] as WalletRestoreMode,
|
||||||
|
@ -51,7 +52,7 @@ class RestoredWallet {
|
||||||
address: json['address'] as String?,
|
address: json['address'] as String?,
|
||||||
spendKey: json['spend_key'] as String?,
|
spendKey: json['spend_key'] as String?,
|
||||||
viewKey: json['view_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?,
|
privateKey: json['private_key'] as String?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,8 @@ class WalletRestoreFromQRCode {
|
||||||
|
|
||||||
final extracted = sortedKeys.firstWhereOrNull((key) => code.toLowerCase().contains(key));
|
final extracted = sortedKeys.firstWhereOrNull((key) => code.toLowerCase().contains(key));
|
||||||
|
|
||||||
|
if (code.startsWith("xpub")) return WalletType.bitcoin;
|
||||||
|
|
||||||
if (extracted == null) {
|
if (extracted == null) {
|
||||||
// Special case for view-only monero wallet
|
// Special case for view-only monero wallet
|
||||||
try {
|
try {
|
||||||
|
@ -117,11 +119,15 @@ class WalletRestoreFromQRCode {
|
||||||
|
|
||||||
formattedUri = seedPhrase != null
|
formattedUri = seedPhrase != null
|
||||||
? '$walletType:?seed=$seedPhrase'
|
? '$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 {
|
} else {
|
||||||
final index = code.indexOf(':');
|
final index = code.indexOf(':');
|
||||||
final query = code.substring(index + 1).replaceAll('?', '&');
|
final query = code.substring(index + 1).replaceAll('?', '&');
|
||||||
formattedUri = '$walletType:?$query';
|
formattedUri = code.startsWith('xpub')
|
||||||
|
? '$walletType:?xpub=$code'
|
||||||
|
:'$walletType:?$query';
|
||||||
}
|
}
|
||||||
|
|
||||||
final uri = Uri.parse(formattedUri);
|
final uri = Uri.parse(formattedUri);
|
||||||
|
@ -158,6 +164,10 @@ class WalletRestoreFromQRCode {
|
||||||
throw Exception('Unexpected restore mode: tx_payment_id is invalid');
|
throw Exception('Unexpected restore mode: tx_payment_id is invalid');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (credentials.containsKey("xpub")) {
|
||||||
|
return WalletRestoreMode.keys;
|
||||||
|
}
|
||||||
|
|
||||||
if (credentials['seed'] != null) {
|
if (credentials['seed'] != null) {
|
||||||
final seedValue = credentials['seed'] as String;
|
final seedValue = credentials['seed'] as String;
|
||||||
final words = SeedValidator.getWordList(type: type, language: 'english');
|
final words = SeedValidator.getWordList(type: type, language: 'english');
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/monero/monero.dart';
|
import 'package:cake_wallet/monero/monero.dart';
|
||||||
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.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.bitcoin:
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
case WalletType.bitcoinCash:
|
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.none:
|
||||||
case WalletType.haven:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,17 +32,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||||
WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService,
|
WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService,
|
||||||
Box<WalletInfo> walletInfoSource, SeedSettingsViewModel seedSettingsViewModel,
|
Box<WalletInfo> walletInfoSource, SeedSettingsViewModel seedSettingsViewModel,
|
||||||
{required WalletType type, this.restoredWallet})
|
{required WalletType type, this.restoredWallet})
|
||||||
: hasSeedLanguageSelector =
|
: isButtonEnabled = restoredWallet != null,
|
||||||
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,
|
|
||||||
hasPassphrase = false,
|
hasPassphrase = false,
|
||||||
mode = restoredWallet?.restoreMode ?? WalletRestoreMode.seed,
|
mode = restoredWallet?.restoreMode ?? WalletRestoreMode.seed,
|
||||||
super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel,
|
super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel,
|
||||||
|
@ -60,9 +50,9 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||||
case WalletType.ethereum:
|
case WalletType.ethereum:
|
||||||
case WalletType.polygon:
|
case WalletType.polygon:
|
||||||
case WalletType.decred:
|
case WalletType.decred:
|
||||||
|
case WalletType.bitcoin:
|
||||||
availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys];
|
availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys];
|
||||||
break;
|
break;
|
||||||
case WalletType.bitcoin:
|
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
case WalletType.bitcoinCash:
|
case WalletType.bitcoinCash:
|
||||||
case WalletType.zano:
|
case WalletType.zano:
|
||||||
|
@ -82,9 +72,34 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||||
static const decredSeedMnemonicLength = 15;
|
static const decredSeedMnemonicLength = 15;
|
||||||
|
|
||||||
late List<WalletRestoreMode> availableModes;
|
late List<WalletRestoreMode> availableModes;
|
||||||
final bool hasSeedLanguageSelector;
|
late final bool hasSeedLanguageSelector = [
|
||||||
final bool hasBlockchainHeightLanguageSelector;
|
WalletType.monero,
|
||||||
final bool hasRestoreFromPrivateKey;
|
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 = [
|
||||||
|
WalletType.bitcoin,
|
||||||
|
WalletType.litecoin,
|
||||||
|
WalletType.bitcoinCash,
|
||||||
|
WalletType.decred
|
||||||
|
].contains(type);
|
||||||
|
|
||||||
final RestoredWallet? restoredWallet;
|
final RestoredWallet? restoredWallet;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
|
@ -198,6 +213,13 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||||
final address = options['address'] as String?;
|
final address = options['address'] as String?;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
case WalletType.bitcoin:
|
||||||
|
return bitcoin!.createBitcoinWalletFromKeys(
|
||||||
|
name: name,
|
||||||
|
password: password,
|
||||||
|
xpub: viewKey!,
|
||||||
|
);
|
||||||
|
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
return monero!.createMoneroRestoreWalletFromKeysCredentials(
|
return monero!.createMoneroRestoreWalletFromKeysCredentials(
|
||||||
name: name,
|
name: name,
|
||||||
|
@ -276,8 +298,9 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
String? mnemonic = credentials['seed'] as String?;
|
String? mnemonic = credentials['seed'] as String?;
|
||||||
String? passphrase = credentials['passphrase'] as String?;
|
String? passphrase = credentials['passphrase'] as String?;
|
||||||
|
if (mnemonic == null) break;
|
||||||
return bitcoin!.getDerivationsFromMnemonic(
|
return bitcoin!.getDerivationsFromMnemonic(
|
||||||
mnemonic: mnemonic!,
|
mnemonic: mnemonic,
|
||||||
node: node,
|
node: node,
|
||||||
passphrase: passphrase,
|
passphrase: passphrase,
|
||||||
);
|
);
|
||||||
|
|
|
@ -130,6 +130,14 @@ dependencies:
|
||||||
url: https://github.com/MrCyjaneK/flutter_daemon
|
url: https://github.com/MrCyjaneK/flutter_daemon
|
||||||
ref: c24ee99f2f3070ea02d8108bbdd7727d73f7e5f1
|
ref: c24ee99f2f3070ea02d8108bbdd7727d73f7e5f1
|
||||||
flutter_local_notifications: ^19.0.0
|
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
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -14,7 +14,7 @@ cd "$SCRIPT_DIR"
|
||||||
BUILD_AMD64=false
|
BUILD_AMD64=false
|
||||||
BUILD_ARM64=false
|
BUILD_ARM64=false
|
||||||
APP_TYPE="cakewallet"
|
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-go1.24.1-ruststablenightly"
|
||||||
|
|
||||||
# Parse arguments
|
# Parse arguments
|
||||||
for arg in "$@"
|
for arg in "$@"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
FROM mcr.microsoft.com/windows/servercore:ltsc2022
|
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 GIT_VERSION=2.47.1
|
||||||
ENV VS_INSTALLED_DIR="C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools"
|
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"
|
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"
|
||||||
|
|
|
@ -163,6 +163,7 @@ abstract class Bitcoin {
|
||||||
String? passphrase,
|
String? passphrase,
|
||||||
});
|
});
|
||||||
WalletCredentials createBitcoinRestoreWalletFromWIFCredentials({required String name, required String password, required String wif, WalletInfo? walletInfo});
|
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 createBitcoinNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password, String? passphrase, String? mnemonic});
|
||||||
WalletCredentials createBitcoinHardwareWalletCredentials({required String name, required HardwareAccountData accountData, WalletInfo? walletInfo});
|
WalletCredentials createBitcoinHardwareWalletCredentials({required String name, required HardwareAccountData accountData, WalletInfo? walletInfo});
|
||||||
List<String> getWordList();
|
List<String> getWordList();
|
||||||
|
@ -206,7 +207,7 @@ abstract class Bitcoin {
|
||||||
Map<DerivationType, List<DerivationInfo>> getElectrumDerivations();
|
Map<DerivationType, List<DerivationInfo>> getElectrumDerivations();
|
||||||
Future<void> setAddressType(Object wallet, dynamic option);
|
Future<void> setAddressType(Object wallet, dynamic option);
|
||||||
ReceivePageOption getSelectedAddressType(Object wallet);
|
ReceivePageOption getSelectedAddressType(Object wallet);
|
||||||
List<ReceivePageOption> getBitcoinReceivePageOptions();
|
List<ReceivePageOption> getBitcoinReceivePageOptions(Object wallet);
|
||||||
List<ReceivePageOption> getLitecoinReceivePageOptions();
|
List<ReceivePageOption> getLitecoinReceivePageOptions();
|
||||||
BitcoinAddressType getBitcoinAddressType(ReceivePageOption option);
|
BitcoinAddressType getBitcoinAddressType(ReceivePageOption option);
|
||||||
bool isPayjoinAvailable(Object wallet);
|
bool isPayjoinAvailable(Object wallet);
|
||||||
|
@ -244,6 +245,7 @@ abstract class Bitcoin {
|
||||||
bool getMwebEnabled(Object wallet);
|
bool getMwebEnabled(Object wallet);
|
||||||
String? getUnusedMwebAddress(Object wallet);
|
String? getUnusedMwebAddress(Object wallet);
|
||||||
String? getUnusedSegwitAddress(Object wallet);
|
String? getUnusedSegwitAddress(Object wallet);
|
||||||
|
Future<void> commitPsbtUR(Object wallet, List<String> urCodes);
|
||||||
|
|
||||||
void updatePayjoinState(Object wallet, bool state);
|
void updatePayjoinState(Object wallet, bool state);
|
||||||
String getPayjoinEndpoint(Object wallet);
|
String getPayjoinEndpoint(Object wallet);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue