decred: Add watching only wallets.

This commit is contained in:
JoeGruff 2024-03-14 17:36:51 +09:00
parent 2acb68e64c
commit fa8881f438
12 changed files with 132 additions and 22 deletions

View file

@ -57,6 +57,17 @@ void createWalletSync(Map<String, String> args) {
);
}
void createWatchOnlyWallet(String walletName, String datadir, String pubkey) {
final cName = walletName.toCString();
final cDataDir = datadir.toCString();
final cPub = pubkey.toCString();
final cNet = "testnet".toCString();
executePayloadFn(
fn: () => dcrwalletApi.createWatchOnlyWallet(cName, cDataDir, cNet, cPub),
ptrsToFree: [cName, cDataDir, cNet, cPub],
);
}
/// loadWalletAsync calls the libdcrwallet's loadWallet function asynchronously.
Future<void> loadWalletAsync({required String name, required String dataDir}) {
final args = <String, String>{
@ -269,6 +280,15 @@ String? newExternalAddress(String walletName) {
return res.payload;
}
String defaultPubkey(String walletName) {
final cName = walletName.toCString();
final res = executePayloadFn(
fn: () => dcrwalletApi.defaultPubkey(cName),
ptrsToFree: [cName],
);
return res.payload;
}
String addresses(String walletName) {
final cName = walletName.toCString();
final res = executePayloadFn(

View file

@ -13,6 +13,7 @@ import 'package:cw_decred/api/libdcrwallet.dart' as libdcrwallet;
import 'package:cw_decred/transaction_history.dart';
import 'package:cw_decred/wallet_addresses.dart';
import 'package:cw_decred/transaction_priority.dart';
import 'package:cw_decred/wallet_service.dart';
import 'package:cw_decred/balance.dart';
import 'package:cw_decred/transaction_info.dart';
import 'package:cw_core/crypto_currency.dart';
@ -37,6 +38,8 @@ abstract class DecredWalletBase extends WalletBase<DecredBalance,
: _password = password,
this.syncStatus = NotConnectedSyncStatus(),
this.unspentCoinsInfo = unspentCoinsInfo,
this.watchingOnly =
walletInfo.derivationPath == DecredWalletService.pubkeyRestorePath,
this.balance =
ObservableMap.of({CryptoCurrency.dcr: DecredBalance.zero()}),
super(walletInfo) {
@ -50,6 +53,7 @@ abstract class DecredWalletBase extends WalletBase<DecredBalance,
static final defaultFeeRate = 10000;
final String _password;
final idPrefix = "decred_";
bool watchingOnly;
bool connecting = false;
int bestHeight = 0;
String bestHash = "";
@ -73,12 +77,17 @@ abstract class DecredWalletBase extends WalletBase<DecredBalance,
@override
String? get seed {
if (watchingOnly) {
return null;
}
return libdcrwallet.walletSeed(walletInfo.name, _password);
}
@override
Object get keys {
return {};
Object get keys => {};
String get pubkey {
return libdcrwallet.defaultPubkey(walletInfo.name);
}
Future<void> init() async {
@ -240,6 +249,16 @@ abstract class DecredWalletBase extends WalletBase<DecredBalance,
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
if (watchingOnly) {
return DecredPendingTransaction(
txid: "",
amount: 0,
fee: 0,
rawHex: "",
send: () async {
throw "unable to send with watching only wallet";
});
}
final inputs = [];
this.unspentCoinsInfo.values.forEach((unspent) {
if (unspent.isSending) {
@ -417,6 +436,9 @@ abstract class DecredWalletBase extends WalletBase<DecredBalance,
@override
Future<void> changePassword(String password) async {
if (watchingOnly) {
return;
}
return () async {
libdcrwallet.changeWalletPassword(walletInfo.name, _password, password);
}();

View file

@ -18,17 +18,15 @@ class DecredRestoreWalletFromSeedCredentials extends WalletCredentials {
final String mnemonic;
}
class DecredRestoreWalletFromWIFCredentials extends WalletCredentials {
DecredRestoreWalletFromWIFCredentials(
class DecredRestoreWalletFromPubkeyCredentials extends WalletCredentials {
DecredRestoreWalletFromPubkeyCredentials(
{required String name,
required String password,
required this.wif,
required String this.pubkey,
WalletInfo? walletInfo})
: t = throw UnimplementedError(), // TODO: Maybe can be used to create watching only wallets?
super(name: name, password: password, walletInfo: walletInfo);
: super(name: name, password: password, walletInfo: walletInfo);
final String wif;
final void t;
final String pubkey;
}
class DecredRestoreWalletFromHardwareCredentials extends WalletCredentials {

View file

@ -20,6 +20,8 @@ class DecredWalletService extends WalletService<
final Box<WalletInfo> walletInfoSource;
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
final seedRestorePath = "m/44'/42'";
static final pubkeyRestorePath = "m/44'/42'/0'";
static void init() async {
// Use the general path for all dcr wallets as the general log directory.
@ -43,6 +45,7 @@ class DecredWalletService extends WalletService<
dataDir: credentials.walletInfo!.dirPath,
password: credentials.password!,
);
credentials.walletInfo!.derivationPath = seedRestorePath;
final wallet = DecredWallet(credentials.walletInfo!, credentials.password!,
this.unspentCoinsInfoSource);
await wallet.init();
@ -101,12 +104,15 @@ class DecredWalletService extends WalletService<
password: credentials.password!,
mnemonic: credentials.mnemonic,
);
credentials.walletInfo!.derivationPath = seedRestorePath;
final wallet = DecredWallet(credentials.walletInfo!, credentials.password!,
this.unspentCoinsInfoSource);
await wallet.init();
return wallet;
}
// restoreFromKeys only supports restoring a watch only wallet from an account
// pubkey.
@override
Future<DecredWallet> restoreFromKeys(
DecredRestoreWalletFromPubkeyCredentials credentials,

View file

@ -19,6 +19,15 @@ class CWDecred extends Decred {
DecredRestoreWalletFromSeedCredentials(
name: name, mnemonic: mnemonic, password: password);
@override
WalletCredentials createDecredRestoreWalletFromPubkeyCredentials(
{required String name,
required String pubkey,
required String password}) =>
DecredRestoreWalletFromPubkeyCredentials(
name: name, pubkey: pubkey, password: password);
@override
WalletService createDecredWalletService(Box<WalletInfo> walletInfoSource,
Box<UnspentCoinsInfo> unspentCoinSource) {
return DecredWalletService(walletInfoSource, unspentCoinSource);
@ -117,6 +126,12 @@ class CWDecred extends Decred {
return (minutesDiff / 5).toInt();
}
@override
List<String> getDecredWordList() => wordlist;
@override
List<String> getDecredWordList() => wordlist;
@override
String pubkey(Object wallet) {
final decredWallet = wallet as DecredWallet;
return decredWallet.pubkey;
}
}

View file

@ -14,6 +14,7 @@ class WalletRestoreFromKeysFrom extends StatefulWidget {
WalletRestoreFromKeysFrom({
required this.walletRestoreViewModel,
required this.onPrivateKeyChange,
required this.onViewKeyEntered,
required this.displayPrivateKeyField,
required this.onHeightOrDateEntered,
required this.displayWalletPassword,
@ -25,6 +26,7 @@ class WalletRestoreFromKeysFrom extends StatefulWidget {
final Function(bool) onHeightOrDateEntered;
final WalletRestoreViewModel walletRestoreViewModel;
final void Function(String)? onPrivateKeyChange;
final void Function(bool)? onViewKeyEntered;
final bool displayPrivateKeyField;
final bool displayWalletPassword;
final void Function(String)? onPasswordChange;
@ -80,6 +82,10 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
}
widget.onPrivateKeyChange?.call(privateKeyController.text);
});
viewKeyController.addListener(() {
widget.onViewKeyEntered?.call(viewKeyController.text.isNotEmpty);
});
}
@override
@ -168,6 +174,19 @@ class WalletRestoreFromKeysFromState extends State<WalletRestoreFromKeysFrom> {
}
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) {
return Column(
children: [
BaseTextFormField(
controller: viewKeyController,
hintText: S.of(context).view_key_public,
maxLines: null,
)],
);
}
if (widget.displayPrivateKeyField) {
// the term "private key" isn't actually what we're accepting here, and it's confusing to
// users of the nano community, what this form actually accepts (when importing for nano) is a nano seed in it's hex form, referred to in code as a "seed key"

View file

@ -67,6 +67,11 @@ class WalletRestorePage extends BasePage {
walletRestoreViewModel.isButtonEnabled = _isValidSeedKey();
}
},
onViewKeyEntered: (bool entered) {
if (walletRestoreViewModel.type == WalletType.decred) {
walletRestoreViewModel.isButtonEnabled = entered;
}
},
displayPrivateKeyField: walletRestoreViewModel.hasRestoreFromPrivateKey,
displayWalletPassword: walletRestoreViewModel.hasWalletPassword,
onPasswordChange: (String password) => walletRestoreViewModel.walletPassword = password,
@ -336,14 +341,16 @@ class WalletRestorePage extends BasePage {
credentials['name'] =
walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
} else {
credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text;
credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text;
credentials['spendKey'] =
walletRestoreFromKeysFormKey.currentState!.spendKeyController.text;
credentials['height'] =
walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState!.height;
credentials['name'] =
walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text;
if (walletRestoreViewModel.type != WalletType.decred) {
credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text;
credentials['spendKey'] =
walletRestoreFromKeysFormKey.currentState!.spendKeyController.text;
credentials['height'] =
walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState!.height;
}
}
}

View file

@ -12,6 +12,7 @@ import 'package:cw_core/wallet_type.dart';
import 'package:cw_monero/monero_wallet.dart';
import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:polyseed/polyseed.dart';
part 'wallet_keys_view_model.g.dart';
@ -132,6 +133,16 @@ abstract class WalletKeysViewModelBase with Store {
}
}
if (_appStore.wallet!.type == WalletType.decred) {
final seed = _appStore.wallet!.seed;
final pubkey = decred!.pubkey(_appStore.wallet!);
items.addAll([
if (seed != null)
StandartListItem(title: S.current.wallet_seed, value: seed),
StandartListItem(title: S.current.view_key_public, value: pubkey),
]);
}
if (_appStore.wallet!.type == WalletType.haven) {
final keys = haven!.getKeys(_appStore.wallet!);
@ -227,7 +238,6 @@ abstract class WalletKeysViewModelBase with Store {
if (_appStore.wallet!.type == WalletType.bitcoin ||
_appStore.wallet!.type == WalletType.litecoin ||
_appStore.wallet!.type == WalletType.decred ||
_appStore.wallet!.type == WalletType.bitcoinCash) {
// final keys = bitcoin!.getWalletKeys(_appStore.wallet!);

View file

@ -57,6 +57,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
case WalletType.haven:
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.decred:
availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys];
break;
case WalletType.bitcoin:
@ -245,6 +246,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
password: password,
language: 'English',
);
case WalletType.decred:
return decred!.createDecredRestoreWalletFromPubkeyCredentials(
name: name,
password: password,
pubkey: viewKey!,
);
default:
break;
}

View file

@ -4,7 +4,7 @@
CW_DECRED_DIR=${WORKDIR}/cake_wallet/cw_decred
LIBWALLET_PATH="${WORKDIR}/decred/libwallet"
LIBWALLET_URL="https://github.com/decred/libwallet.git"
LIBWALLET_COMMIT="9f39f38b460e2dece5704cbc4aee293c741ee710"
LIBWALLET_VERSION="v1.0.0"
if [ -e $LIBWALLET_PATH ]; then
rm -fr $LIBWALLET_PATH
@ -12,7 +12,7 @@ fi
mkdir -p $LIBWALLET_PATH
git clone $LIBWALLET_URL $LIBWALLET_PATH
cd $LIBWALLET_PATH
git checkout $LIBWALLET_COMMIT
git checkout $LIBWALLET_VERSION
export CPATH="$(clang -v 2>&1 | grep "Selected GCC installation" | rev | cut -d' ' -f1 | rev)/include"

View file

@ -3,7 +3,7 @@
. ./config.sh
LIBWALLET_PATH="${EXTERNAL_IOS_SOURCE_DIR}/libwallet"
LIBWALLET_URL="https://github.com/decred/libwallet.git"
LIBWALLET_COMMIT="9f39f38b460e2dece5704cbc4aee293c741ee710"
LIBWALLET_VERSION="v1.0.0"
if [ -e $LIBWALLET_PATH ]; then
rm -fr $LIBWALLET_PATH
@ -11,7 +11,7 @@ fi
mkdir -p $LIBWALLET_PATH
git clone $LIBWALLET_URL $LIBWALLET_PATH
cd $LIBWALLET_PATH
git checkout $LIBWALLET_COMMIT
git checkout $LIBWALLET_VERSION
SYSROOT=`xcrun --sdk iphoneos --show-sdk-path`
CLANG="clang -isysroot ${SYSROOT}"

View file

@ -1439,6 +1439,10 @@ abstract class Decred {
{required String name,
required String mnemonic,
required String password});
WalletCredentials createDecredRestoreWalletFromPubkeyCredentials(
{required String name,
required String pubkey,
required String password});
WalletService createDecredWalletService(Box<WalletInfo> walletInfoSource,
Box<UnspentCoinsInfo> unspentCoinSource);
@ -1466,6 +1470,8 @@ abstract class Decred {
int heightByDate(DateTime date);
List<String> getDecredWordList();
String pubkey(Object wallet);
}
""";