Add decred (#1938)

* decred: Add decred. (#1322)

* multi: Add initial decred screens. (#1165)

Use a mock libwallet for now.

* cw_decred: add libdcrwallet dependency and link library for android, ios and macos (#1240)

* change cw_decred from package to plugin

* add libdcrwallet dependency and link library for android, ios and macos

* remove spvwallet, make some libdcrwallet fns async, light refactor

* libdcrwallet: use json payload returns

* use specific libwallet commit hash

* decred: fix Rename wallet.

---------

Co-authored-by: JoeGruff <joegruffins@gmail.com>

* decred: Add sync.

* decred: Add send transaction.

* decred: Fix fee estimation.

* decred: List transactions.

* decred: Add rescan.

* decred: Sign message.

* decred: Add new addr and addrs.

* decred: Add change wallet pass.

* decred: Add restore from seed.

* decred: Add watching only wallets.

* decred: Enable mainnet.

* decred: Allow using blank node address.

This allows a persistent peer to be unset, falling back to decred
seeders.

* decred: Rescan from wallet birthday.

* add and update macos build scripts, update build readme, gitignore macos project.pbxproj

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>

* multi: hide decred rescan page if it's not ready

-  move hasRescan method to WalletBase and implement for decred

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>

* cw_decred: fix bug where decred wallets are not loaded after app restart

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>

* add buy and sell for decred via onramp

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>

* bug-fix: account for other send outputs that are part of the same tx

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>

* decred: Return address with no peers.

* decred: Update pubspec.

* decred: Add verify message.

* upgrade hive_generator dep in cw_decred

* decred: Clean up code.

---------

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>
Co-authored-by: Wisdom Arerosuoghene <wisdom.arerosuoghene@gmail.com>
Co-authored-by: Philemon Ukane <ukanephilemon@gmail.com>

* fix extracted addresses not used
fix conflicts with main

* remove print [skip ci]

* minor formatting

* fix initial migration version

* add build decred script to workflow

* install go before build decred
fix switch cases

* trial 2 to fix decred build

* re-install go

* revert build script change

* refactor/clean nodes functions

* Fix address book issue
Fix send ALL (to be continued with the fees point)

* Fix transactions display issues
Add missing file

* Fix unconfirmed balance not displayed
Change Wallet order
Minor cleanup

* Fix workflow

* Fix workflow

* Fix workflow

* test

* hardcode path for now

* fix + cleanup decred build script to work on mac and linux

* Update decred build script

* Run actions on pull requests, extract commit message

* run after checkout

* add safe directory

* Get commit message from base.sha instead of last commit

* base -> head

* Do not merge main branch into pr

* [skip slack] [run tests] clone by sha

* Proper name for decred library in the build script

* Throw an error when ANDROID_HOME or ANDROID_NDK_VERSION is missing

* Fix conflicts with main

* minor code enhancement

* decred: Add used address history.  (#1941)

* decred: Update pubspec.

* decred testnet

* decred: Add used address history.

* decred: Remove default node list.

* populate transaction history before sync begins

* decred: Add some awaits.

* decred: Fix send all.

* decred: Add clang export to build script.

* decred: Update logo colors.

* cleanup cw_decred.dart

* make decred wallet addresses selectable in receive page

* decred: Always set default addr when used.

* decred: Add back default node list.

* decred: Allow creating addresses manually.

---------

Co-authored-by: Wisdom Arerosuoghene <wisdom.arerosuoghene@gmail.com>
Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* minor fixes and cleanup

* minor fix, feel free to test now

* - Fix transaction details
- Fix Nodes
- Add processing sync status

* Add decred info card

* push missing file

* Add missing text for decred info card

* minor: change docs link [skip ci]

* decred: Update derivation info. (#2013)

* decred: Update derivation info.

* decred: Allow unsynced unused addresses.

* decred: Update dcrwallet dep to 4.3.0.

* Merge main and fix conflicts

* Merge main and fix conflicts

* decred: Fix background sync panic. (#2080)

* decred: Run libwallet in isolate. (#2077)

* decred: Fix contact save inquiry. (#2083)

Also fix tx time and the fee shown on pending transactions.

* Disable send button in view only decred wallets

* - Fix frozen coins
- Add URI support
- Fix fees in tx details
- Handle empty coins send
- Handle wallets in address book

* Merge main

* remove print [skip ci]

* Fix restore from QR

* minor improvement for QR restore

* minor fixes [skip ci]

* decred: Get slip44 addrs before sync completes. (#2092)

* - Fix loading wallet more than one time
- Fix minor UI issue

---------

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>
Co-authored-by: JoeGruffins <34998433+JoeGruffins@users.noreply.github.com>
Co-authored-by: Wisdom Arerosuoghene <wisdom.arerosuoghene@gmail.com>
Co-authored-by: Philemon Ukane <ukanephilemon@gmail.com>
Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
This commit is contained in:
Omar Hatem
2025-03-21 04:18:47 +02:00
committed by GitHub
co-authored by JoeGruffins Wisdom Arerosuoghene Philemon Ukane Czarek Nakamoto
parent 52a39e29d4
commit 0ba54fa602
175 changed files with 7145 additions and 1115 deletions
@@ -55,6 +55,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
case WalletType.none:
case WalletType.haven:
case WalletType.zano:
case WalletType.decred:
return false;
}
}
@@ -9,7 +9,6 @@ import 'package:cake_wallet/entities/wallet_list_order_types.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/mobx.dart';
import 'package:collection/collection.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
@@ -43,7 +42,8 @@ abstract class ContactListViewModelBase with Store {
}
}
} else if (info.addresses?.isNotEmpty == true && info.addresses!.length > 1) {
if ([WalletType.monero, WalletType.wownero, WalletType.haven].contains(info.type)) {
if ([WalletType.monero, WalletType.wownero, WalletType.haven, WalletType.decred]
.contains(info.type)) {
final address = info.address;
final name = _createName(info.name, "", key: 0);
walletContacts.add(WalletContact(
@@ -129,11 +129,9 @@ abstract class ContactListViewModelBase with Store {
(element.type == CryptoCurrency.btc || element.type == CryptoCurrency.ltc)) return false;
return element.type == _currency ||
(element.type.tag != null &&
_currency?.tag != null &&
element.type.tag == _currency?.tag) ||
_currency?.toString() == element.type.tag ||
_currency?.tag == element.type.toString();
(element.type.tag != null && _currency.tag != null && element.type.tag == _currency.tag) ||
_currency.toString() == element.type.tag ||
_currency.tag == element.type.toString();
}
void dispose() => _subscription?.cancel();
@@ -62,12 +62,12 @@ abstract class ContactViewModelBase with Store {
return;
}
if (_contact != null && _contact!.original.isInBox) {
_contact?.name = name;
_contact?.address = address;
_contact?.type = currency!;
_contact?.lastChange = now;
await _contact?.save();
if (_contact != null && _contact.original.isInBox) {
_contact.name = name;
_contact.address = address;
_contact.type = currency!;
_contact.lastChange = now;
await _contact.save();
} else {
await _contacts
.add(Contact(name: name, address: address, type: currency!, lastChange: now));
@@ -306,6 +306,7 @@ abstract class BalanceViewModelBase with Store {
case WalletType.monero:
case WalletType.wownero:
case WalletType.zano:
case WalletType.decred:
return true;
default:
return false;
@@ -23,7 +23,6 @@ import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:cake_wallet/store/dashboard/transaction_filter_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/store/yat/yat_store.dart';
import 'package:cake_wallet/utils/mobx.dart';
import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
@@ -169,6 +168,9 @@ abstract class DashboardViewModelBase with Store {
type = appStore.wallet!.type,
transactions = ObservableList<TransactionListItem>(),
wallet = appStore.wallet! {
showDecredInfoCard = wallet.type == WalletType.decred &&
(sharedPreferences.getBool(PreferencesKey.showDecredInfoCard) ?? true);
name = wallet.name;
type = wallet.type;
isShowFirstYatIntroduction = false;
@@ -261,6 +263,7 @@ abstract class DashboardViewModelBase with Store {
reaction((_) => appStore.wallet, (wallet) {
_onWalletChange(wallet);
_checkMweb();
showDecredInfoCard = wallet?.type == WalletType.decred;
});
_transactionDisposer?.reaction.dispose();
@@ -350,6 +353,10 @@ abstract class DashboardViewModelBase with Store {
statusText = S.current.please_try_to_connect_to_another_node;
}
if (status is ProcessingSyncStatus) {
statusText = (status as ProcessingSyncStatus).message ?? S.current.processing;
}
return statusText;
}
@@ -399,13 +406,7 @@ abstract class DashboardViewModelBase with Store {
bool get isTestnet => wallet.type == WalletType.bitcoin && bitcoin!.isTestnet(wallet);
@computed
bool get hasRescan => [
WalletType.bitcoin,
WalletType.monero,
WalletType.litecoin,
WalletType.wownero,
WalletType.haven
].contains(wallet.type);
bool get hasRescan => wallet.hasRescan;
@computed
bool get isMoneroViewOnly {
@@ -490,6 +491,9 @@ abstract class DashboardViewModelBase with Store {
@observable
bool mwebEnabled = false;
@observable
late bool showDecredInfoCard;
@computed
bool get hasEnabledMwebBefore => settingsStore.hasEnabledMwebBefore;
@@ -543,6 +547,12 @@ abstract class DashboardViewModelBase with Store {
bitcoin!.setMwebEnabled(wallet, false);
}
@action
void dismissDecredInfoCard() {
showDecredInfoCard = false;
sharedPreferences.setBool(PreferencesKey.showDecredInfoCard, false);
}
BalanceViewModel balanceViewModel;
AppStore appStore;
@@ -573,6 +583,9 @@ abstract class DashboardViewModelBase with Store {
@computed
bool get isEnabledSwapAction => settingsStore.exchangeStatus != ExchangeApiMode.disabled;
@computed
bool get canSend => wallet.canSend();
@observable
bool hasSwapAction;
@@ -596,21 +609,28 @@ abstract class DashboardViewModelBase with Store {
@computed
bool get hasSignMessages {
if (wallet.isHardwareWallet) return false;
return [
WalletType.monero,
WalletType.litecoin,
WalletType.bitcoin,
WalletType.bitcoinCash,
WalletType.ethereum,
WalletType.polygon,
WalletType.solana,
WalletType.nano,
WalletType.banano,
WalletType.tron,
WalletType.wownero
].contains(wallet.type);
if (wallet.isHardwareWallet) {
return false;
}
switch (wallet.type) {
case WalletType.monero:
case WalletType.litecoin:
case WalletType.bitcoin:
case WalletType.bitcoinCash:
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.solana:
case WalletType.nano:
case WalletType.banano:
case WalletType.tron:
case WalletType.wownero:
case WalletType.decred:
return true;
case WalletType.zano:
case WalletType.haven:
case WalletType.none:
return false;
}
}
bool get showRepWarning {
@@ -214,6 +214,7 @@ abstract class HomeSettingsViewModelBase with Store {
case WalletType.nano:
case WalletType.wownero:
case WalletType.bitcoinCash:
case WalletType.decred:
return false;
}
@@ -14,7 +14,9 @@ abstract class ReceiveOptionViewModelBase with Store {
(_wallet.type == WalletType.bitcoin ||
_wallet.type == WalletType.litecoin
? bitcoin!.getSelectedAddressType(_wallet)
: ReceivePageOption.mainnet),
: (_wallet.type == WalletType.decred && _wallet.isTestnet)
? ReceivePageOption.testnet
: ReceivePageOption.mainnet),
_options = [] {
final walletType = _wallet.type;
switch (walletType) {
@@ -33,6 +35,17 @@ abstract class ReceiveOptionViewModelBase with Store {
case WalletType.haven:
_options = [ReceivePageOption.mainnet];
break;
case WalletType.decred:
if (_wallet.isTestnet) {
_options = [
ReceivePageOption.testnet,
...ReceivePageOptions.where(
(element) => element != ReceivePageOption.mainnet)
];
} else {
_options = ReceivePageOptions;
}
break;
default:
_options = ReceivePageOptions;
}
@@ -1,3 +1,4 @@
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
@@ -82,7 +83,9 @@ class TransactionListItem extends ActionListItem with Keyable {
if (transaction.confirmations <= 0) {
str = S.current.pending;
}
if ((isPegOut || fromPegOut) && transaction.confirmations >= 0 && transaction.confirmations < 6) {
if ((isPegOut || fromPegOut) &&
transaction.confirmations >= 0 &&
transaction.confirmations < 6) {
str = " (${transaction.confirmations}/6)";
}
if (isPegIn) {
@@ -224,7 +227,13 @@ class TransactionListItem extends ActionListItem with Keyable {
cryptoAmount: zano!.formatterIntAmountToDouble(amount: transaction.amount, currency: asset, forFee: false),
price: price);
break;
default:
case WalletType.decred:
amount = calculateFiatAmountRaw(
cryptoAmount: decred!.formatterDecredAmountToDouble(amount: transaction.amount),
price: price);
break;
case WalletType.none:
case WalletType.banano:
break;
}
@@ -23,6 +23,8 @@ import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
@@ -167,7 +169,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
wallet.type == WalletType.bitcoinCash;
bool get hideAddressAfterExchange =>
wallet.type == WalletType.monero || wallet.type == WalletType.wownero;
wallet.type == WalletType.monero ||
wallet.type == WalletType.wownero;
bool _useTorOnly;
final Box<Trade> trades;
@@ -316,8 +319,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
bool get isMoneroWallet => wallet.type == WalletType.monero;
List<CryptoCurrency> receiveCurrencies;
List<CryptoCurrency> depositCurrencies;
@@ -778,6 +779,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
depositCurrency = CryptoCurrency.zano;
receiveCurrency = CryptoCurrency.xmr;
break;
case WalletType.decred:
depositCurrency = CryptoCurrency.dcr;
receiveCurrency = CryptoCurrency.xmr;
break;
case WalletType.none:
break;
}
@@ -63,13 +63,13 @@ abstract class NodeCreateOrEditViewModelBase with Store {
String socksProxyAddress;
@computed
bool get isReady => address.isNotEmpty && port.isNotEmpty;
bool get isReady =>
(address.isNotEmpty && port.isNotEmpty) ||
_walletType == WalletType.decred; // Allow an empty address.
bool get hasAuthCredentials =>
_walletType == WalletType.monero || _walletType == WalletType.wownero || _walletType == WalletType.haven;
bool get hasTestnetSupport => _walletType == WalletType.bitcoin;
bool get hasPathSupport {
switch (_walletType) {
case WalletType.ethereum:
@@ -87,6 +87,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
case WalletType.bitcoinCash:
case WalletType.bitcoin:
case WalletType.zano:
case WalletType.decred:
return false;
}
}
@@ -49,50 +49,10 @@ abstract class NodeListViewModelBase with Store {
await resetToDefault(_nodeSource);
Node node;
switch (_appStore.wallet!.type) {
case WalletType.bitcoin:
if (_appStore.wallet!.isTestnet) {
node = getBitcoinTestnetDefaultElectrumServer(nodes: _nodeSource)!;
} else {
node = getBitcoinDefaultElectrumServer(nodes: _nodeSource)!;
}
break;
case WalletType.monero:
node = getMoneroDefaultNode(nodes: _nodeSource);
break;
case WalletType.litecoin:
node = getLitecoinDefaultElectrumServer(nodes: _nodeSource)!;
break;
case WalletType.haven:
node = getHavenDefaultNode(nodes: _nodeSource)!;
break;
case WalletType.ethereum:
node = getEthereumDefaultNode(nodes: _nodeSource)!;
break;
case WalletType.bitcoinCash:
node = getBitcoinCashDefaultElectrumServer(nodes: _nodeSource)!;
break;
case WalletType.nano:
node = getNanoDefaultNode(nodes: _nodeSource)!;
break;
case WalletType.polygon:
node = getPolygonDefaultNode(nodes: _nodeSource)!;
break;
case WalletType.solana:
node = getSolanaDefaultNode(nodes: _nodeSource)!;
break;
case WalletType.tron:
node = getTronDefaultNode(nodes: _nodeSource)!;
break;
case WalletType.wownero:
node = getWowneroDefaultNode(nodes: _nodeSource);
break;
case WalletType.zano:
node = getZanoDefaultNode(nodes: _nodeSource)!;
break;
default:
throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
if (_appStore.wallet!.type == WalletType.bitcoin && _appStore.wallet!.isTestnet) {
node = getBitcoinTestnetDefaultElectrumServer(nodes: _nodeSource)!;
} else {
node = getDefaultNode(nodes: _nodeSource, type: _appStore.wallet!.type)!;
}
await setAsCurrent(node);
@@ -44,6 +44,9 @@ class WalletRestoreFromQRCode {
'zano': WalletType.zano,
'zano-wallet': WalletType.zano,
'zano_wallet': WalletType.zano,
'decred': WalletType.decred,
'decred-wallet': WalletType.decred,
'decred_wallet': WalletType.decred,
};
static WalletType? _extractWalletType(String code) {
@@ -69,8 +72,12 @@ class WalletRestoreFromQRCode {
}
static String? _extractAddressFromUrl(String rawString, WalletType type) {
return AddressResolver.extractAddressByType(
raw: rawString, type: walletTypeToCryptoCurrency(type));
try {
return AddressResolver.extractAddressByType(
raw: rawString, type: walletTypeToCryptoCurrency(type));
} catch (_) {
return null;
}
}
static String? _extractSeedPhraseFromUrl(String rawString, WalletType walletType) {
@@ -122,7 +129,6 @@ class WalletRestoreFromQRCode {
}
if (queryParameters['address'] == null) {
queryParameters['address'] = _extractAddressFromUrl(code, walletType);
}
Map<String, dynamic> credentials = {'type': walletType, ...queryParameters, 'raw_qr': code};
+8 -1
View File
@@ -1,4 +1,5 @@
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
@@ -88,7 +89,13 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
return transactionPriority == bitcoinCash!.getBitcoinCashTransactionPrioritySlow();
case WalletType.polygon:
return transactionPriority == polygon!.getPolygonTransactionPrioritySlow();
default:
case WalletType.decred:
return transactionPriority == decred!.getDecredTransactionPrioritySlow();
case WalletType.none:
case WalletType.nano:
case WalletType.banano:
case WalletType.solana:
case WalletType.tron:
return false;
}
}
+8
View File
@@ -1,3 +1,4 @@
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
import 'package:cake_wallet/entities/parse_address_from_domain.dart';
@@ -101,6 +102,9 @@ abstract class OutputBase with Store {
case WalletType.bitcoinCash:
_amount = bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount);
break;
case WalletType.decred:
_amount = decred!.formatterStringDoubleToDecredAmount(_cryptoAmount);
break;
case WalletType.haven:
_amount = haven!.formatterMoneroParseAmount(amount: _cryptoAmount);
break;
@@ -188,6 +192,10 @@ abstract class OutputBase with Store {
if (_wallet.type == WalletType.zano) {
return zano!.formatterIntAmountToDouble(amount: fee, currency: cryptoCurrencyHandler(), forFee: true);
}
if (_wallet.type == WalletType.decred) {
return decred!.formatterDecredAmountToDouble(amount: fee);
}
} catch (e) {
printV(e.toString());
}
+48 -47
View File
@@ -6,6 +6,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/wallet_contact.dart';
@@ -251,6 +252,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
wallet.type == WalletType.litecoin ||
wallet.type == WalletType.monero ||
wallet.type == WalletType.wownero ||
wallet.type == WalletType.decred ||
wallet.type == WalletType.bitcoinCash;
@computed
@@ -539,6 +541,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.zano:
return zano!.createZanoTransactionCredentials(
outputs: outputs, priority: priority!, currency: selectedCryptoCurrency);
case WalletType.decred:
this.coinTypeToSpendFrom = UnspentCoinType.any;
return decred!.createDecredTransactionCredentials(outputs, priority!);
default:
throw Exception('Unexpected wallet type: ${wallet.type}');
}
@@ -681,55 +686,51 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
}
}
if (walletType == WalletType.bitcoin ||
walletType == WalletType.litecoin ||
walletType == WalletType.bitcoinCash) {
if (error is TransactionWrongBalanceException) {
if (error.amount != null)
return S.current
.tx_wrong_balance_with_amount_exception(currency.toString(), error.amount.toString());
if (error is TransactionWrongBalanceException) {
if (error.amount != null)
return S.current
.tx_wrong_balance_with_amount_exception(currency.toString(), error.amount.toString());
return S.current.tx_wrong_balance_exception(currency.toString());
}
if (error is TransactionNoInputsException) {
return S.current.tx_not_enough_inputs_exception;
}
if (error is TransactionNoFeeException) {
return S.current.tx_zero_fee_exception;
}
if (error is TransactionNoDustException) {
return S.current.tx_no_dust_exception;
}
if (error is TransactionCommitFailed) {
if (error.errorMessage != null && error.errorMessage!.contains("no peers replied")) {
return S.current.tx_commit_failed_no_peers;
}
return "${S.current.tx_commit_failed}${error.errorMessage != null ? "\n\n${error.errorMessage}" : ""}";
}
if (error is TransactionCommitFailedDustChange) {
return S.current.tx_rejected_dust_change;
}
if (error is TransactionCommitFailedDustOutput) {
return S.current.tx_rejected_dust_output;
}
if (error is TransactionCommitFailedDustOutputSendAll) {
return S.current.tx_rejected_dust_output_send_all;
}
if (error is TransactionCommitFailedVoutNegative) {
return S.current.tx_rejected_vout_negative;
}
if (error is TransactionCommitFailedBIP68Final) {
return S.current.tx_rejected_bip68_final;
}
if (error is TransactionCommitFailedLessThanMin) {
return S.current.fee_less_than_min;
}
if (error is TransactionNoDustOnChangeException) {
return S.current.tx_commit_exception_no_dust_on_change(error.min, error.max);
}
if (error is TransactionInputNotSupported) {
return S.current.tx_invalid_input;
return S.current.tx_wrong_balance_exception(currency.toString());
}
if (error is TransactionNoInputsException) {
return S.current.tx_not_enough_inputs_exception;
}
if (error is TransactionNoFeeException) {
return S.current.tx_zero_fee_exception;
}
if (error is TransactionNoDustException) {
return S.current.tx_no_dust_exception;
}
if (error is TransactionCommitFailed) {
if (error.errorMessage != null && error.errorMessage!.contains("no peers replied")) {
return S.current.tx_commit_failed_no_peers;
}
return "${S.current.tx_commit_failed}${error.errorMessage != null ? "\n\n${error.errorMessage}" : ""}";
}
if (error is TransactionCommitFailedDustChange) {
return S.current.tx_rejected_dust_change;
}
if (error is TransactionCommitFailedDustOutput) {
return S.current.tx_rejected_dust_output;
}
if (error is TransactionCommitFailedDustOutputSendAll) {
return S.current.tx_rejected_dust_output_send_all;
}
if (error is TransactionCommitFailedVoutNegative) {
return S.current.tx_rejected_vout_negative;
}
if (error is TransactionCommitFailedBIP68Final) {
return S.current.tx_rejected_bip68_final;
}
if (error is TransactionCommitFailedLessThanMin) {
return S.current.fee_less_than_min;
}
if (error is TransactionNoDustOnChangeException) {
return S.current.tx_commit_exception_no_dust_on_change(error.min, error.max);
}
if (error is TransactionInputNotSupported) {
return S.current.tx_invalid_input;
}
return errorMessage;
@@ -44,7 +44,8 @@ abstract class PrivacySettingsViewModelBase with Store {
_wallet.type == WalletType.wownero ||
_wallet.type == WalletType.bitcoin ||
_wallet.type == WalletType.litecoin ||
_wallet.type == WalletType.bitcoinCash;
_wallet.type == WalletType.bitcoinCash ||
_wallet.type == WalletType.decred;
bool get isMoneroWallet => _wallet.type == WalletType.monero;
@@ -48,6 +48,7 @@ abstract class TransactionDetailsViewModelBase with Store {
final dateFormat = DateFormatter.withCurrentLocal();
final tx = transactionInfo;
// TODO: can be cleaned further
switch (wallet.type) {
case WalletType.monero:
_addMoneroListItems(tx, dateFormat);
@@ -84,8 +85,12 @@ abstract class TransactionDetailsViewModelBase with Store {
case WalletType.zano:
_addZanoListItems(tx, dateFormat);
break;
default:
case WalletType.decred:
_addDecredListItems(tx, dateFormat);
break;
case WalletType.none:
case WalletType.banano:
break;
}
final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
@@ -186,6 +191,8 @@ abstract class TransactionDetailsViewModelBase with Store {
return 'https://explore.wownero.com/tx/${txId}';
case WalletType.zano:
return 'https://explorer.zano.org/transaction/${txId}';
case WalletType.decred:
return 'https://${wallet.isTestnet ? "testnet" : "dcrdata"}.decred.org/tx/${txId.split(':')[0]}';
case WalletType.none:
return '';
}
@@ -218,6 +225,8 @@ abstract class TransactionDetailsViewModelBase with Store {
return S.current.view_transaction_on + 'Wownero.com';
case WalletType.zano:
return S.current.view_transaction_on + 'explorer.zano.org';
case WalletType.decred:
return S.current.view_transaction_on + 'dcrdata.decred.org';
case WalletType.none:
return '';
}
@@ -672,6 +681,51 @@ abstract class TransactionDetailsViewModelBase with Store {
items.addAll(_items);
}
void _addDecredListItems(TransactionInfo tx, DateFormat dateFormat) {
final _items = [
StandartListItem(
title: S.current.transaction_details_transaction_id,
value: tx.txHash,
key: ValueKey('standard_list_item_transaction_details_id_key'),
),
StandartListItem(
title: S.current.transaction_details_date,
value: dateFormat.format(tx.date),
key: ValueKey('standard_list_item_transaction_details_date_key'),
),
StandartListItem(
title: S.current.transaction_details_height,
value: '${tx.height}',
key: ValueKey('standard_list_item_transaction_details_height_key'),
),
StandartListItem(
title: S.current.transaction_details_amount,
value: tx.amountFormatted(),
key: ValueKey('standard_list_item_transaction_details_amount_key'),
),
if (tx.feeFormatted()?.isNotEmpty ?? false)
StandartListItem(
title: S.current.transaction_details_fee,
value: tx.feeFormatted()!,
key: ValueKey('standard_list_item_transaction_details_fee_key'),
),
if (showRecipientAddress && tx.to != null)
StandartListItem(
title: S.current.transaction_details_recipient_address,
value: tx.to!,
key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
),
if (tx.from != null)
StandartListItem(
title: S.current.transaction_details_source_address,
value: tx.from!,
key: ValueKey('standard_list_item_transaction_details_source_address_key'),
),
];
items.addAll(_items);
}
@action
Future<void> _checkForRBF(TransactionInfo tx) async {
if (wallet.type == WalletType.bitcoin &&
@@ -1,6 +1,7 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/unspent_coin_type.dart';
@@ -92,6 +93,8 @@ abstract class UnspentCoinsListViewModelBase with Store {
return wownero!.formatterWowneroAmountToString(amount: fullBalance);
if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type))
return bitcoin!.formatterBitcoinAmountToString(amount: fullBalance);
if (wallet.type == WalletType.decred)
return decred!.formatterDecredAmountToString(amount: fullBalance);
return '';
}
@@ -105,7 +108,9 @@ abstract class UnspentCoinsListViewModelBase with Store {
if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type)) {
await bitcoin!.updateUnspents(wallet);
}
if (wallet.type == WalletType.decred) {
decred!.updateUnspents(wallet);
}
_updateUnspentCoinsInfo();
}
@@ -119,6 +124,8 @@ abstract class UnspentCoinsListViewModelBase with Store {
case WalletType.litecoin:
case WalletType.bitcoinCash:
return bitcoin!.getUnspents(wallet, coinTypeToSpendFrom: coinTypeToSpendFrom);
case WalletType.decred:
return decred!.getUnspents(wallet);
default:
return List.empty();
}
@@ -5,6 +5,7 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cw_core/wallet_type.dart';
part 'wallet_address_edit_or_create_view_model.g.dart';
@@ -27,13 +28,12 @@ class AddressEditOrCreateStateFailure extends AddressEditOrCreateState {
}
abstract class WalletAddressEditOrCreateViewModelBase with Store {
WalletAddressEditOrCreateViewModelBase(
{required WalletBase wallet, WalletAddressListItem? item})
WalletAddressEditOrCreateViewModelBase({required WalletBase wallet, WalletAddressListItem? item})
: isEdit = item != null,
state = AddressEditOrCreateStateInitial(),
label = item?.name ?? '',
_item = item,
_wallet = wallet;
_wallet = wallet;
@observable
AddressEditOrCreateState state;
@@ -46,7 +46,8 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
final WalletAddressListItem? _item;
final WalletBase _wallet;
bool get isElectrum => _wallet.type == WalletType.bitcoin ||
bool get isElectrum =>
_wallet.type == WalletType.bitcoin ||
_wallet.type == WalletType.bitcoinCash ||
_wallet.type == WalletType.litecoin;
@@ -69,39 +70,46 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
Future<void> _createNew() async {
final wallet = _wallet;
if (isElectrum) await bitcoin!.generateNewAddress(wallet, label);
if (isElectrum) {
await bitcoin!.generateNewAddress(wallet, label);
await wallet.save();
}
if (wallet.type == WalletType.decred) {
await decred!.generateNewAddress(wallet, label);
await wallet.save();
}
if (wallet.type == WalletType.monero) {
await monero
!.getSubaddressList(wallet)
.addSubaddress(
wallet,
accountIndex: monero!.getCurrentAccount(wallet).id,
label: label);
final addr = await monero!.getSubaddressList(wallet).subaddresses.first.address; // first because the order is reversed
await monero!
.getSubaddressList(wallet)
.addSubaddress(wallet, accountIndex: monero!.getCurrentAccount(wallet).id, label: label);
final addr = await monero!
.getSubaddressList(wallet)
.subaddresses
.first
.address; // first because the order is reversed
wallet.walletAddresses.manualAddresses.add(addr);
await wallet.save();
}
if (wallet.type == WalletType.wownero) {
await wownero
!.getSubaddressList(wallet)
.addSubaddress(
wallet,
accountIndex: wownero!.getCurrentAccount(wallet).id,
label: label);
final addr = await wownero!.getSubaddressList(wallet).subaddresses.first.address; // first because the order is reversed
await wownero!
.getSubaddressList(wallet)
.addSubaddress(wallet, accountIndex: wownero!.getCurrentAccount(wallet).id, label: label);
final addr = await wownero!
.getSubaddressList(wallet)
.subaddresses
.first
.address; // first because the order is reversed
wallet.walletAddresses.manualAddresses.add(addr);
await wallet.save();
}
if (wallet.type == WalletType.haven) {
await haven
!.getSubaddressList(wallet)
.addSubaddress(
wallet,
accountIndex: haven!.getCurrentAccount(wallet).id,
label: label);
await haven!
.getSubaddressList(wallet)
.addSubaddress(wallet, accountIndex: haven!.getCurrentAccount(wallet).id, label: label);
await wallet.save();
}
}
@@ -111,6 +119,12 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
if (isElectrum) await bitcoin!.updateAddress(wallet, _item!.address, label);
if (wallet.type == WalletType.decred) {
await decred!.updateAddress(wallet, _item!.address, label);
await wallet.save();
return;
}
final index = _item?.id;
if (index != null) {
if (wallet.type == WalletType.monero) {
@@ -125,9 +139,7 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
}
if (wallet.type == WalletType.haven) {
await haven!.getSubaddressList(wallet).setLabelSubaddress(wallet,
accountIndex: haven!.getCurrentAccount(wallet).id,
addressIndex: index,
label: label);
accountIndex: haven!.getCurrentAccount(wallet).id, addressIndex: index, label: label);
await wallet.save();
}
}
@@ -12,6 +12,7 @@ import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
@@ -222,6 +223,22 @@ class ZanoURI extends PaymentURI {
}
}
class DecredURI extends PaymentURI {
DecredURI({required String amount, required String address})
: super(amount: amount, address: address);
@override
String toString() {
var base = 'decred:' + address;
if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}';
}
return base;
}
}
abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
WalletAddressListViewModelBase({
required AppStore appStore,
@@ -313,7 +330,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
case WalletType.wownero:
return WowneroURI(amount: amount, address: address.address);
case WalletType.zano:
return ZanoURI(amount: amount, address: address.address);
return ZanoURI(amount: amount, address: address.address);
case WalletType.decred:
return DecredURI(amount: amount, address: address.address);
case WalletType.none:
throw Exception('Unexpected type: ${type.toString()}');
}
@@ -468,6 +487,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
}
if (wallet.type == WalletType.decred) {
final addrInfos = decred!.getAddressInfos(wallet);
addrInfos.forEach((info) {
addressList.add(new WalletAddressListItem(isPrimary: false, address: info.address,
name: info.label));
});
}
for (var i = 0; i < addressList.length; i++) {
if (!(addressList[i] is WalletAddressListItem)) continue;
(addressList[i] as WalletAddressListItem).isHidden = wallet.walletAddresses.hiddenAddresses
@@ -561,7 +588,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
WalletType.haven,
WalletType.bitcoinCash,
WalletType.bitcoin,
WalletType.litecoin
WalletType.litecoin,
WalletType.decred
].contains(wallet.type);
@computed
@@ -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';
@@ -155,6 +156,12 @@ abstract class WalletKeysViewModelBase with Store {
),
]);
break;
case WalletType.decred:
final pubkey = decred!.pubkey(_appStore.wallet!);
items.addAll([
StandartListItem(title: S.current.view_key_public, value: pubkey),
]);
break;
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
@@ -248,6 +255,8 @@ abstract class WalletKeysViewModelBase with Store {
return 'wownero-wallet';
case WalletType.zano:
return 'zano-wallet';
case WalletType.decred:
return 'decred-wallet';
default:
throw Exception('Unexpected wallet type: ${_wallet.type.toString()}');
}
+5
View File
@@ -21,6 +21,7 @@ import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/decred/decred.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
@@ -83,6 +84,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
return 25;
case WalletType.zano:
return 26;
case WalletType.decred:
return 15;
}
}
@@ -170,6 +173,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
password: walletPassword,
passphrase: passphrase,
);
case WalletType.decred:
return decred!.createDecredNewWalletCredentials(name: name);
case WalletType.none:
throw Exception('Unexpected type: ${type.toString()}');
}
+20 -4
View File
@@ -11,6 +11,7 @@ import 'package:cake_wallet/polygon/polygon.dart';
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/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';
@@ -58,6 +59,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:
@@ -77,6 +79,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
}
static const moneroSeedMnemonicLength = 25;
static const decredSeedMnemonicLength = 15;
late List<WalletRestoreMode> availableModes;
final bool hasSeedLanguageSelector;
@@ -171,11 +174,18 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
);
case WalletType.zano:
return zano!.createZanoRestoreWalletFromSeedCredentials(
name: name,
password: password,
height: height,
name: name,
password: password,
height: height,
passphrase: passphrase??'',
mnemonic: seed);
mnemonic: seed,
);
case WalletType.decred:
return decred!.createDecredRestoreWalletFromSeedCredentials(
name: name,
mnemonic: seed,
password: password,
);
case WalletType.none:
break;
}
@@ -251,6 +261,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;
}