mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-03-25 08:39:06 +00:00
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:
parent
52a39e29d4
commit
0ba54fa602
175 changed files with 7145 additions and 1115 deletions
.github/workflows
.gitignoreandroid/app/src/main
assets
decred_node_list.yml
images
cw_bitcoin/lib
cw_core
lib
amount_converter.dartcurrency_for_wallet_type.dartnode.dartreceive_page_option.dartsync_status.dartwallet_base.dartwallet_type.dart
pubspec.lockcw_decred
.gitignore.metadataCHANGELOG.mdLICENSEREADME.mdanalysis_options.yaml
android
ios
lib
amount_format.dart
api
balance.dartmnemonic.dartpending_transaction.darttransaction_credentials.darttransaction_history.darttransaction_info.darttransaction_priority.dartwallet.dartwallet_addresses.dartwallet_creation_credentials.dartwallet_service.dartmacos
pubspec.lockpubspec.yamlcw_monero/lib
cw_wownero/lib
ios
lib
core
address_validator.dartnode_address_validator.dartseed_validator.dartsync_status_title.dartwallet_creation_service.dart
decred
di.dartentities
default_settings_migration.dartmain_actions.dartnode_list.dartpreferences_key.dartpriority_for_wallet_type.dartprovider_types.dart
main.dartreactions
bip39_wallet_utils.dartcheck_connection.dartfiat_rate_update.darton_current_wallet_change.darton_wallet_sync_status_change.dart
src
screens
dashboard
desktop_widgets
pages
widgets
new_wallet
nodes
rescan
restore
wallet_keys
wallet_list
widgets
store
view_model
4
.github/workflows/no_print_in_dart.yaml
vendored
4
.github/workflows/no_print_in_dart.yaml
vendored
|
@ -1,8 +1,6 @@
|
|||
name: No print statements in dart files
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
PR_test_build:
|
||||
|
|
12
.github/workflows/pr_test_build_android.yml
vendored
12
.github/workflows/pr_test_build_android.yml
vendored
|
@ -47,6 +47,7 @@ jobs:
|
|||
echo "message<<EOF" >> $GITHUB_ENV
|
||||
echo "$FULL_MESSAGE" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
|
||||
- name: Add secrets
|
||||
run: |
|
||||
touch lib/.secrets.g.dart
|
||||
|
@ -243,6 +244,13 @@ jobs:
|
|||
./build_mwebd.sh --dont-install
|
||||
popd
|
||||
|
||||
- name: Build Decred
|
||||
run: |
|
||||
set -x -e
|
||||
pushd scripts/android
|
||||
./build_decred.sh
|
||||
popd
|
||||
|
||||
- name: Build generated code
|
||||
run: |
|
||||
./model_generator.sh async
|
||||
|
@ -281,7 +289,7 @@ jobs:
|
|||
set -x
|
||||
apk_file=$(ls build/app/outputs/flutter-apk/test-apk/${BRANCH_NAME}.apk || exit 1)
|
||||
echo "APK_FILE=$apk_file" >> $GITHUB_ENV
|
||||
|
||||
|
||||
- name: Upload artifact to slack
|
||||
if: ${{ !contains(env.message, 'skip slack') }}
|
||||
continue-on-error: true
|
||||
|
@ -294,7 +302,7 @@ jobs:
|
|||
|
||||
- name: cleanup
|
||||
run: rm -rf build/app/outputs/flutter-apk/test-apk/
|
||||
|
||||
|
||||
- name: Upload Artifact to github
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
|
1
.github/workflows/pr_test_build_linux.yml
vendored
1
.github/workflows/pr_test_build_linux.yml
vendored
|
@ -32,6 +32,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
- name: configure git
|
||||
run: |
|
||||
git config --global --add safe.directory '*'
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -9,6 +9,7 @@
|
|||
.history
|
||||
.svn/
|
||||
.fvm/
|
||||
.fvmrc
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
|
@ -138,6 +139,7 @@ lib/solana/solana.dart
|
|||
lib/tron/tron.dart
|
||||
lib/wownero/wownero.dart
|
||||
lib/zano/zano.dart
|
||||
lib/decred/decred.dart
|
||||
|
||||
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
|
||||
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
|
||||
|
@ -171,6 +173,7 @@ macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
|
|||
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
|
||||
macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
|
||||
macos/Runner/Configs/AppInfo.xcconfig
|
||||
macos/Runner.xcodeproj/project.pbxproj
|
||||
|
||||
|
||||
integration_test/playground.dart
|
||||
|
|
|
@ -92,6 +92,9 @@
|
|||
<data android:scheme="zano" />
|
||||
<data android:scheme="zano-wallet" />
|
||||
<data android:scheme="zano_wallet" />
|
||||
<data android:scheme="decred" />
|
||||
<data android:scheme="decred-wallet" />
|
||||
<data android:scheme="decred_wallet" />
|
||||
</intent-filter>
|
||||
<!-- nano-gpt link scheme -->
|
||||
<intent-filter android:autoVerify="true">
|
||||
|
|
6
assets/decred_node_list.yml
Normal file
6
assets/decred_node_list.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
-
|
||||
uri: default-spv-nodes
|
||||
is_default: true
|
||||
-
|
||||
uri: dcrd.sethforprivacy.com:9108
|
||||
useSSL: true
|
BIN
assets/images/2.0x/decred.png
Normal file
BIN
assets/images/2.0x/decred.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 2.1 KiB |
BIN
assets/images/2.0x/decred_menu.png
Normal file
BIN
assets/images/2.0x/decred_menu.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.8 KiB |
BIN
assets/images/3.0x/decred.png
Normal file
BIN
assets/images/3.0x/decred.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 2.8 KiB |
BIN
assets/images/3.0x/decred_menu.png
Normal file
BIN
assets/images/3.0x/decred_menu.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.4 KiB |
Binary file not shown.
Before ![]() (image error) Size: 115 KiB After ![]() (image error) Size: 89 KiB ![]() ![]() |
BIN
assets/images/decred.png
Normal file
BIN
assets/images/decred.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.2 KiB |
BIN
assets/images/decred_icon.png
Normal file
BIN
assets/images/decred_icon.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.5 KiB |
BIN
assets/images/decred_menu.png
Normal file
BIN
assets/images/decred_menu.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.4 KiB |
|
@ -92,6 +92,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
});
|
||||
}
|
||||
|
||||
@override
|
||||
bool get hasRescan => true;
|
||||
|
||||
static Future<BitcoinWallet> create({
|
||||
required String mnemonic,
|
||||
required String password,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:convert/convert.dart' as convert;
|
||||
import 'dart:math';
|
||||
|
@ -156,6 +155,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
@observable
|
||||
SyncStatus mwebSyncStatus = NotConnectedSyncStatus();
|
||||
|
||||
@override
|
||||
bool get hasRescan => true;
|
||||
|
||||
List<int> get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
|
||||
List<int> get spendSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ class AmountConverter {
|
|||
case CryptoCurrency.btc:
|
||||
case CryptoCurrency.bch:
|
||||
case CryptoCurrency.ltc:
|
||||
case CryptoCurrency.dcr:
|
||||
return _bitcoinAmountToString(amount);
|
||||
case CryptoCurrency.xhv:
|
||||
case CryptoCurrency.xag:
|
||||
|
|
|
@ -32,9 +32,10 @@ CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) {
|
|||
return CryptoCurrency.wow;
|
||||
case WalletType.zano:
|
||||
return CryptoCurrency.zano;
|
||||
case WalletType.decred:
|
||||
return CryptoCurrency.dcr;
|
||||
case WalletType.none:
|
||||
throw Exception(
|
||||
|
||||
'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
|
||||
}
|
||||
}
|
||||
|
@ -65,6 +66,10 @@ WalletType? walletTypeForCurrency(CryptoCurrency currency) {
|
|||
return WalletType.tron;
|
||||
case CryptoCurrency.wow:
|
||||
return WalletType.wownero;
|
||||
case CryptoCurrency.zano:
|
||||
return WalletType.zano;
|
||||
case CryptoCurrency.dcr:
|
||||
return WalletType.decred;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -103,6 +103,7 @@ class Node extends HiveObject with Keyable {
|
|||
case WalletType.solana:
|
||||
case WalletType.tron:
|
||||
case WalletType.zano:
|
||||
case WalletType.decred:
|
||||
return Uri.parse(
|
||||
"http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") || path!.isEmpty ? path : "/$path"}");
|
||||
case WalletType.none:
|
||||
|
@ -167,6 +168,8 @@ class Node extends HiveObject with Keyable {
|
|||
return requestElectrumServer();
|
||||
case WalletType.zano:
|
||||
return requestZanoNode();
|
||||
case WalletType.decred:
|
||||
return requestDecredNode();
|
||||
case WalletType.none:
|
||||
return false;
|
||||
}
|
||||
|
@ -355,6 +358,21 @@ class Node extends HiveObject with Keyable {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> requestDecredNode() async {
|
||||
if (uri.host == "default-spv-nodes") {
|
||||
// Just show default port as ok. The wallet will connect to a list of known
|
||||
// nodes automatically.
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
final socket = await Socket.connect(uri.host, uri.port, timeout: Duration(seconds: 5));
|
||||
socket.destroy();
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// https://github.com/ManyMath/digest_auth/
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'package:cw_core/enumerate.dart';
|
|||
|
||||
class ReceivePageOption implements Enumerate {
|
||||
static const mainnet = ReceivePageOption._('mainnet');
|
||||
static const testnet = ReceivePageOption._('testnet');
|
||||
static const anonPayInvoice = ReceivePageOption._('anonPayInvoice');
|
||||
static const anonPayDonationLink = ReceivePageOption._('anonPayDonationLink');
|
||||
|
||||
|
|
|
@ -34,6 +34,16 @@ class SyncingSyncStatus extends SyncStatus {
|
|||
}
|
||||
}
|
||||
|
||||
class ProcessingSyncStatus extends SyncStatus {
|
||||
final String? message;
|
||||
|
||||
ProcessingSyncStatus({this.message});
|
||||
|
||||
@override
|
||||
double progress() => 0.99;
|
||||
|
||||
}
|
||||
|
||||
class SyncedSyncStatus extends SyncStatus {
|
||||
@override
|
||||
double progress() => 1.0;
|
||||
|
|
|
@ -60,6 +60,8 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
|
|||
|
||||
bool get isHardwareWallet => walletInfo.isHardwareWallet;
|
||||
|
||||
bool get hasRescan => false;
|
||||
|
||||
Future<void> connectToNode({required Node node});
|
||||
|
||||
// there is a default definition here because only coins with a pow node (nano based) need to override this
|
||||
|
@ -100,4 +102,6 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
|
|||
Future<bool> verifyMessage(String message, String signature, {String? address = null});
|
||||
|
||||
bool isTestnet = false;
|
||||
|
||||
bool canSend() => true;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ const walletTypes = [
|
|||
WalletType.solana,
|
||||
WalletType.tron,
|
||||
WalletType.zano,
|
||||
WalletType.decred,
|
||||
];
|
||||
|
||||
@HiveType(typeId: WALLET_TYPE_TYPE_ID)
|
||||
|
@ -60,9 +61,11 @@ enum WalletType {
|
|||
@HiveField(12)
|
||||
wownero,
|
||||
|
||||
@HiveField(13)
|
||||
@HiveField(13)
|
||||
zano,
|
||||
|
||||
@HiveField(14)
|
||||
decred
|
||||
}
|
||||
|
||||
int serializeToInt(WalletType type) {
|
||||
|
@ -93,6 +96,8 @@ int serializeToInt(WalletType type) {
|
|||
return 11;
|
||||
case WalletType.zano:
|
||||
return 12;
|
||||
case WalletType.decred:
|
||||
return 13;
|
||||
case WalletType.none:
|
||||
return -1;
|
||||
}
|
||||
|
@ -126,6 +131,8 @@ WalletType deserializeFromInt(int raw) {
|
|||
return WalletType.wownero;
|
||||
case 12:
|
||||
return WalletType.zano;
|
||||
case 13:
|
||||
return WalletType.decred;
|
||||
default:
|
||||
throw Exception(
|
||||
'Unexpected token: $raw for WalletType deserializeFromInt');
|
||||
|
@ -160,6 +167,8 @@ String walletTypeToString(WalletType type) {
|
|||
return 'Wownero';
|
||||
case WalletType.zano:
|
||||
return 'Zano';
|
||||
case WalletType.decred:
|
||||
return 'Decred';
|
||||
case WalletType.none:
|
||||
return '';
|
||||
}
|
||||
|
@ -193,6 +202,8 @@ String walletTypeToDisplayName(WalletType type) {
|
|||
return 'Wownero (WOW)';
|
||||
case WalletType.zano:
|
||||
return 'Zano (ZANO)';
|
||||
case WalletType.decred:
|
||||
return 'Decred (DCR)';
|
||||
case WalletType.none:
|
||||
return '';
|
||||
}
|
||||
|
@ -229,6 +240,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal
|
|||
return CryptoCurrency.wow;
|
||||
case WalletType.zano:
|
||||
return CryptoCurrency.zano;
|
||||
case WalletType.decred:
|
||||
return CryptoCurrency.dcr;
|
||||
case WalletType.none:
|
||||
throw Exception(
|
||||
'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
|
||||
|
|
|
@ -46,6 +46,15 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
blockchain_utils:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: cake-update-v2
|
||||
resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57"
|
||||
url: "https://github.com/cake-tech/blockchain_utils"
|
||||
source: git
|
||||
version: "3.3.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -465,6 +474,15 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
on_chain:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: cake-update-v2
|
||||
resolved-ref: "93440dc5126369b873ca1fccc13c3c1240b1c5c2"
|
||||
url: "https://github.com/cake-tech/on_chain.git"
|
||||
source: git
|
||||
version: "3.7.0"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
39
cw_decred/.gitignore
vendored
Normal file
39
cw_decred/.gitignore
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
|
||||
android/.externalNativeBuild/
|
||||
android/.cxx/
|
||||
android/libs
|
||||
ios/External/
|
||||
macos/External/
|
||||
|
||||
*libdcrwallet.h
|
||||
libdcrwallet_bindings.dart
|
36
cw_decred/.metadata
Normal file
36
cw_decred/.metadata
Normal file
|
@ -0,0 +1,36 @@
|
|||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled.
|
||||
|
||||
version:
|
||||
revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
channel: unknown
|
||||
|
||||
project_type: plugin
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
- platform: android
|
||||
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
- platform: ios
|
||||
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
- platform: macos
|
||||
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
3
cw_decred/CHANGELOG.md
Normal file
3
cw_decred/CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## [0.0.1] - TODO: Add release date.
|
||||
|
||||
* TODO: Describe initial release.
|
1
cw_decred/LICENSE
Normal file
1
cw_decred/LICENSE
Normal file
|
@ -0,0 +1 @@
|
|||
TODO: Add your license here.
|
3
cw_decred/README.md
Normal file
3
cw_decred/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# cw_decred
|
||||
|
||||
TODO: Fill this out.
|
4
cw_decred/analysis_options.yaml
Normal file
4
cw_decred/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
9
cw_decred/android/.gitignore
vendored
Normal file
9
cw_decred/android/.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.cxx
|
59
cw_decred/android/build.gradle
Normal file
59
cw_decred/android/build.gradle
Normal file
|
@ -0,0 +1,59 @@
|
|||
group 'com.cakewallet.cw_decred'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '2.0.21'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.7.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
|
||||
if (project.android.hasProperty("namespace")) {
|
||||
namespace 'com.cakewallet.cw_decred'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '17'
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDirs += 'src/main/kotlin'
|
||||
jniLibs.srcDirs 'libs' // contains libdcrwallet.so shared libraries
|
||||
}
|
||||
}
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
}
|
1
cw_decred/android/settings.gradle
Normal file
1
cw_decred/android/settings.gradle
Normal file
|
@ -0,0 +1 @@
|
|||
rootProject.name = 'cw_decred'
|
4
cw_decred/android/src/main/AndroidManifest.xml
Normal file
4
cw_decred/android/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.cakewallet.cw_decred">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
|
@ -0,0 +1,35 @@
|
|||
package com.cakewallet.cw_decred
|
||||
|
||||
import androidx.annotation.NonNull
|
||||
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||
import io.flutter.plugin.common.MethodChannel.Result
|
||||
|
||||
/** CwDecredPlugin */
|
||||
class CwDecredPlugin: FlutterPlugin, MethodCallHandler {
|
||||
/// The MethodChannel that will the communication between Flutter and native Android
|
||||
///
|
||||
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
|
||||
/// when the Flutter Engine is detached from the Activity
|
||||
private lateinit var channel : MethodChannel
|
||||
|
||||
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "cw_decred")
|
||||
channel.setMethodCallHandler(this)
|
||||
}
|
||||
|
||||
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
|
||||
if (call.method == "getPlatformVersion") {
|
||||
result.success("Android ${android.os.Build.VERSION.RELEASE}")
|
||||
} else {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
channel.setMethodCallHandler(null)
|
||||
}
|
||||
}
|
38
cw_decred/ios/.gitignore
vendored
Normal file
38
cw_decred/ios/.gitignore
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
.idea/
|
||||
.vagrant/
|
||||
.sconsign.dblite
|
||||
.svn/
|
||||
|
||||
.DS_Store
|
||||
*.swp
|
||||
profile
|
||||
|
||||
DerivedData/
|
||||
build/
|
||||
GeneratedPluginRegistrant.h
|
||||
GeneratedPluginRegistrant.m
|
||||
|
||||
.generated/
|
||||
|
||||
*.pbxuser
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.perspectivev3
|
||||
|
||||
!default.pbxuser
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.perspectivev3
|
||||
|
||||
xcuserdata
|
||||
|
||||
*.moved-aside
|
||||
|
||||
*.pyc
|
||||
*sync/
|
||||
Icon?
|
||||
.tags*
|
||||
|
||||
/Flutter/Generated.xcconfig
|
||||
/Flutter/ephemeral/
|
||||
/Flutter/flutter_export_environment.sh
|
0
cw_decred/ios/Assets/.gitkeep
Normal file
0
cw_decred/ios/Assets/.gitkeep
Normal file
19
cw_decred/ios/Classes/CwDecredPlugin.swift
Normal file
19
cw_decred/ios/Classes/CwDecredPlugin.swift
Normal file
|
@ -0,0 +1,19 @@
|
|||
import Flutter
|
||||
import UIKit
|
||||
|
||||
public class CwDecredPlugin: NSObject, FlutterPlugin {
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let channel = FlutterMethodChannel(name: "cw_decred", binaryMessenger: registrar.messenger())
|
||||
let instance = CwDecredPlugin()
|
||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||
}
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
switch call.method {
|
||||
case "getPlatformVersion":
|
||||
result("iOS " + UIDevice.current.systemVersion)
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
}
|
22
cw_decred/ios/cw_decred.podspec
Normal file
22
cw_decred/ios/cw_decred.podspec
Normal file
|
@ -0,0 +1,22 @@
|
|||
#
|
||||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
|
||||
# Run `pod lib lint cw_decred.podspec` to validate before publishing.
|
||||
#
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'cw_decred'
|
||||
s.version = '0.0.1'
|
||||
s.summary = 'Cake Wallet Decred'
|
||||
s.description = 'Cake Wallet wrapper over Decred project'
|
||||
s.homepage = 'http://cakewallet.com'
|
||||
s.license = { :file => '../LICENSE' }
|
||||
s.author = { 'Cake Wallet' => 'support@cakewallet.com' }
|
||||
s.source = { :path => '.' }
|
||||
s.source_files = 'Classes/**/*'
|
||||
s.dependency 'Flutter'
|
||||
s.platform = :ios, '11.0'
|
||||
|
||||
s.vendored_libraries = 'External/lib/libdcrwallet.a'
|
||||
# Flutter.framework does not contain a i386 slice.
|
||||
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', "OTHER_LDFLAGS" => "-force_load $(PODS_TARGET_SRCROOT)/External/lib/libdcrwallet.a -lstdc++" }
|
||||
s.swift_version = '5.0'
|
||||
end
|
26
cw_decred/lib/amount_format.dart
Normal file
26
cw_decred/lib/amount_format.dart
Normal file
|
@ -0,0 +1,26 @@
|
|||
import 'package:intl/intl.dart';
|
||||
import 'package:cw_core/crypto_amount_format.dart';
|
||||
|
||||
const decredAmountLength = 8;
|
||||
const decredAmountDivider = 100000000;
|
||||
final decredAmountFormat = NumberFormat()
|
||||
..maximumFractionDigits = decredAmountLength
|
||||
..minimumFractionDigits = 1;
|
||||
|
||||
String decredAmountToString({required int amount}) =>
|
||||
decredAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: decredAmountDivider));
|
||||
|
||||
double decredAmountToDouble({required int amount}) =>
|
||||
cryptoAmountToDouble(amount: amount, divider: decredAmountDivider);
|
||||
|
||||
int stringDoubleToDecredAmount(String amount) {
|
||||
int result = 0;
|
||||
|
||||
try {
|
||||
result = (double.parse(amount) * decredAmountDivider).round();
|
||||
} catch (e) {
|
||||
result = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
693
cw_decred/lib/api/libdcrwallet.dart
Normal file
693
cw_decred/lib/api/libdcrwallet.dart
Normal file
|
@ -0,0 +1,693 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
import 'dart:isolate';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_decred/api/libdcrwallet_bindings.dart';
|
||||
import 'package:cw_decred/api/util.dart';
|
||||
|
||||
final int ErrCodeNotSynced = 1;
|
||||
|
||||
final String libraryName = Platform.isAndroid || Platform.isLinux // TODO: Linux.
|
||||
? 'libdcrwallet.so'
|
||||
: 'cw_decred.framework/cw_decred';
|
||||
|
||||
class Libwallet {
|
||||
final SendPort _commands;
|
||||
final ReceivePort _responses;
|
||||
final Map<int, Completer<Object?>> _activeRequests = {};
|
||||
int _idCounter = 0;
|
||||
bool _closed = false;
|
||||
|
||||
static Future<Libwallet> spawn() async {
|
||||
// Create a receive port and add its initial message handler.
|
||||
final initPort = RawReceivePort();
|
||||
final connection = Completer<(ReceivePort, SendPort)>.sync();
|
||||
initPort.handler = (initialMessage) {
|
||||
final commandPort = initialMessage as SendPort;
|
||||
connection.complete((
|
||||
ReceivePort.fromRawReceivePort(initPort),
|
||||
commandPort,
|
||||
));
|
||||
};
|
||||
// Spawn the isolate.
|
||||
try {
|
||||
await Isolate.spawn(_startRemoteIsolate, (initPort.sendPort));
|
||||
} on Object {
|
||||
initPort.close();
|
||||
rethrow;
|
||||
}
|
||||
|
||||
final (ReceivePort receivePort, SendPort sendPort) = await connection.future;
|
||||
|
||||
return Libwallet._(receivePort, sendPort);
|
||||
}
|
||||
|
||||
Libwallet._(this._responses, this._commands) {
|
||||
_responses.listen(_handleResponsesFromIsolate);
|
||||
}
|
||||
|
||||
void _handleResponsesFromIsolate(dynamic message) {
|
||||
final (int id, Object? response) = message as (int, Object?);
|
||||
final completer = _activeRequests.remove(id)!;
|
||||
|
||||
if (response is RemoteError) {
|
||||
completer.completeError(response);
|
||||
} else {
|
||||
completer.complete(response);
|
||||
}
|
||||
|
||||
if (_closed && _activeRequests.isEmpty) _responses.close();
|
||||
}
|
||||
|
||||
static void _handleCommandsToIsolate(
|
||||
ReceivePort receivePort,
|
||||
SendPort sendPort,
|
||||
) {
|
||||
final dcrwalletApi = libdcrwallet(DynamicLibrary.open(libraryName));
|
||||
receivePort.listen((message) {
|
||||
if (message == 'shutdown') {
|
||||
receivePort.close();
|
||||
return;
|
||||
}
|
||||
final (int id, Map<String, String> args) = message as (int, Map<String, String>);
|
||||
var res = PayloadResult("", "", 0);
|
||||
final method = args["method"] ?? "";
|
||||
try {
|
||||
switch (method) {
|
||||
case "initlibdcrwallet":
|
||||
final logDir = args["logdir"] ?? "";
|
||||
final cLogDir = logDir.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.initialize(cLogDir),
|
||||
ptrsToFree: [cLogDir],
|
||||
);
|
||||
break;
|
||||
case "createwallet":
|
||||
final config = args["config"] ?? "";
|
||||
final cConfig = config.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.createWallet(cConfig),
|
||||
ptrsToFree: [cConfig],
|
||||
);
|
||||
break;
|
||||
case "createwatchonlywallet":
|
||||
final config = args["config"] ?? "";
|
||||
final cConfig = config.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.createWatchOnlyWallet(cConfig),
|
||||
ptrsToFree: [cConfig],
|
||||
);
|
||||
break;
|
||||
case "loadwallet":
|
||||
final config = args["config"] ?? "";
|
||||
final cConfig = config.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.loadWallet(cConfig),
|
||||
ptrsToFree: [cConfig],
|
||||
);
|
||||
break;
|
||||
case "startsync":
|
||||
final name = args["name"] ?? "";
|
||||
final peers = args["peers"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cPeers = peers.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.syncWallet(cName, cPeers),
|
||||
ptrsToFree: [cName, cPeers],
|
||||
);
|
||||
break;
|
||||
case "closewallet":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.closeWallet(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "changewalletpassword":
|
||||
final name = args["name"] ?? "";
|
||||
final oldPass = args["oldpass"] ?? "";
|
||||
final newPass = args["newpass"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cOldPass = oldPass.toCString();
|
||||
final cNewPass = newPass.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.changePassphrase(cName, cOldPass, cNewPass),
|
||||
ptrsToFree: [cName, cOldPass, cNewPass],
|
||||
);
|
||||
break;
|
||||
case "walletseed":
|
||||
final name = args["name"] ?? "";
|
||||
final pass = args["pass"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cPass = pass.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.walletSeed(cName, cPass),
|
||||
ptrsToFree: [cName, cPass],
|
||||
);
|
||||
break;
|
||||
case "syncstatus":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.syncWalletStatus(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "balance":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.walletBalance(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "estimatefee":
|
||||
final name = args["name"] ?? "";
|
||||
final numBlocks = args["numblocks"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cNumBlocks = numBlocks.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.estimateFee(cName, cNumBlocks),
|
||||
ptrsToFree: [cName, cNumBlocks],
|
||||
);
|
||||
break;
|
||||
case "createsignedtransaction":
|
||||
final name = args["name"] ?? "";
|
||||
final signReq = args["signreq"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cSignReq = signReq.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.createSignedTransaction(cName, cSignReq),
|
||||
ptrsToFree: [cName, cSignReq],
|
||||
);
|
||||
break;
|
||||
case "sendrawtransaction":
|
||||
final name = args["name"] ?? "";
|
||||
final txHex = args["txhex"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cTxHex = txHex.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.sendRawTransaction(cName, cTxHex),
|
||||
ptrsToFree: [cName, cTxHex],
|
||||
);
|
||||
break;
|
||||
case "listtransactions":
|
||||
final name = args["name"] ?? "";
|
||||
final from = args["from"] ?? "";
|
||||
final count = args["count"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cFrom = from.toCString();
|
||||
final cCount = count.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.listTransactions(cName, cFrom, cCount),
|
||||
ptrsToFree: [cName, cFrom, cCount],
|
||||
);
|
||||
break;
|
||||
case "bestblock":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.bestBlock(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "listunspents":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.listUnspents(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "rescanfromheight":
|
||||
final name = args["name"] ?? "";
|
||||
final height = args["height"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cHeight = height.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.rescanFromHeight(cName, cHeight),
|
||||
ptrsToFree: [cName, cHeight],
|
||||
);
|
||||
break;
|
||||
case "signmessage":
|
||||
final name = args["name"] ?? "";
|
||||
final message = args["message"] ?? "";
|
||||
final address = args["address"] ?? "";
|
||||
final pass = args["pass"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cMessage = message.toCString();
|
||||
final cAddress = address.toCString();
|
||||
final cPass = pass.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.signMessage(cName, cMessage, cAddress, cPass),
|
||||
ptrsToFree: [cName, cMessage, cAddress, cPass],
|
||||
);
|
||||
break;
|
||||
case "verifymessage":
|
||||
final name = args["name"] ?? "";
|
||||
final message = args["message"] ?? "";
|
||||
final address = args["address"] ?? "";
|
||||
final sig = args["sig"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cMessage = message.toCString();
|
||||
final cAddress = address.toCString();
|
||||
final cSig = sig.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.verifyMessage(cName, cMessage, cAddress, cSig),
|
||||
ptrsToFree: [cName, cMessage, cAddress, cSig],
|
||||
);
|
||||
break;
|
||||
case "newexternaladdress":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.newExternalAddress(cName),
|
||||
ptrsToFree: [cName],
|
||||
skipErrorCheck: true,
|
||||
);
|
||||
break;
|
||||
case "defaultpubkey":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.defaultPubkey(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "addresses":
|
||||
final name = args["name"] ?? "";
|
||||
final nUsed = args["nused"] ?? "";
|
||||
final nUnused = args["nunused"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cNUsed = nUsed.toCString();
|
||||
final cNUnused = nUnused.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.addresses(cName, cNUsed, cNUnused),
|
||||
ptrsToFree: [cName, cNUsed, cNUnused],
|
||||
);
|
||||
break;
|
||||
case "birthstate":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.birthState(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "shutdown":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.shutdown(),
|
||||
ptrsToFree: [],
|
||||
);
|
||||
break;
|
||||
default:
|
||||
res = PayloadResult("", "unknown libwallet method ${method}", 0);
|
||||
}
|
||||
sendPort.send((id, res));
|
||||
} catch (e) {
|
||||
final errMsg = e.toString();
|
||||
printV("decred libwallet returned an error for method ${method}: ${errMsg}");
|
||||
sendPort.send((id, PayloadResult("", errMsg, 0)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void _startRemoteIsolate(SendPort sendPort) {
|
||||
final receivePort = ReceivePort();
|
||||
sendPort.send(receivePort.sendPort);
|
||||
_handleCommandsToIsolate(receivePort, sendPort);
|
||||
}
|
||||
|
||||
// initLibdcrwallet initializes libdcrwallet using the provided logDir and gets
|
||||
// it ready for use. This must be done before attempting to create, load or use
|
||||
// a wallet.
|
||||
Future<void> initLibdcrwallet(String logDir) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "initlibdcrwallet",
|
||||
"logdir": logDir,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future<void> createWallet(String config) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "createwallet",
|
||||
"config": config,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future<void> createWatchOnlyWallet(String config) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "createwatchonlywallet",
|
||||
"config": config,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future<void> loadWallet(String config) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "loadwallet",
|
||||
"config": config,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future<void> startSync(String walletName, String peers) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "startsync",
|
||||
"name": walletName,
|
||||
"peers": peers,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future<void> closeWallet(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "closewallet",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future<String> changeWalletPassword(
|
||||
String walletName, String currentPassword, String newPassword) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "changewalletpassword",
|
||||
"name": walletName,
|
||||
"oldpass": currentPassword,
|
||||
"newpass": newPassword
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String?> walletSeed(String walletName, String walletPassword) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "walletseed",
|
||||
"name": walletName,
|
||||
"pass": walletPassword,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> syncStatus(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "syncstatus",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<Map> balance(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "balance",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return jsonDecode(res.payload);
|
||||
}
|
||||
|
||||
Future<String> estimateFee(String walletName, int numBlocks) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "estimatefee",
|
||||
"name": walletName,
|
||||
"numblocks": numBlocks.toString(),
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> createSignedTransaction(
|
||||
String walletName, String createSignedTransactionReq) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "createsignedtransaction",
|
||||
"name": walletName,
|
||||
"signreq": createSignedTransactionReq,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> sendRawTransaction(String walletName, String txHex) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "sendrawtransaction",
|
||||
"name": walletName,
|
||||
"txhex": txHex,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> listTransactions(String walletName, String from, String count) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "listtransactions",
|
||||
"name": walletName,
|
||||
"from": from,
|
||||
"count": count,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> bestBlock(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "bestblock",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> listUnspents(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "listunspents",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> rescanFromHeight(String walletName, String height) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "rescanfromheight",
|
||||
"name": walletName,
|
||||
"height": height,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> signMessage(
|
||||
String walletName, String message, String address, String walletPass) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "signmessage",
|
||||
"name": walletName,
|
||||
"message": message,
|
||||
"address": address,
|
||||
"pass": walletPass,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> verifyMessage(
|
||||
String walletName, String message, String address, String sig) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "verifymessage",
|
||||
"name": walletName,
|
||||
"message": message,
|
||||
"address": address,
|
||||
"sig": sig,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String?> newExternalAddress(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "newexternaladdress",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
if (res.errCode == ErrCodeNotSynced) {
|
||||
// Wallet is not synced. We do not want to give out a used address so give
|
||||
// nothing.
|
||||
return null;
|
||||
}
|
||||
checkErr(res.err);
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> defaultPubkey(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "defaultpubkey",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> addresses(String walletName, String nUsed, String nUnused) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "addresses",
|
||||
"name": walletName,
|
||||
"nused": nUsed,
|
||||
"nunused": nUnused,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> birthState(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "birthstate",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<void> shutdown() async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "shutdown",
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future as PayloadResult;
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (!_closed) {
|
||||
_closed = true;
|
||||
_commands.send('shutdown');
|
||||
if (_activeRequests.isEmpty) _responses.close();
|
||||
}
|
||||
}
|
||||
}
|
64
cw_decred/lib/api/util.dart
Normal file
64
cw_decred/lib/api/util.dart
Normal file
|
@ -0,0 +1,64 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
class PayloadResult {
|
||||
final String payload;
|
||||
final String err;
|
||||
final int errCode;
|
||||
|
||||
const PayloadResult(this.payload, this.err, this.errCode);
|
||||
}
|
||||
|
||||
// Executes the provided fn and converts the string response to a PayloadResult.
|
||||
// Returns payload, error code, and error.
|
||||
PayloadResult executePayloadFn({
|
||||
required Pointer<Char> fn(),
|
||||
required List<Pointer> ptrsToFree,
|
||||
bool skipErrorCheck = false,
|
||||
}) {
|
||||
final jsonStr = fn().toDartString();
|
||||
freePointers(ptrsToFree);
|
||||
if (jsonStr == null) throw Exception("no json return from wallet library");
|
||||
final decoded = json.decode(jsonStr);
|
||||
|
||||
final err = decoded["error"] ?? "";
|
||||
if (!skipErrorCheck) {
|
||||
checkErr(err);
|
||||
}
|
||||
|
||||
final payload = decoded["payload"] ?? "";
|
||||
final errCode = decoded["errorcode"] ?? -1;
|
||||
return new PayloadResult(payload, err, errCode);
|
||||
}
|
||||
|
||||
void freePointers(List<Pointer> ptrsToFree) {
|
||||
for (final ptr in ptrsToFree) {
|
||||
malloc.free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
void checkErr(String err) {
|
||||
if (err == "") return;
|
||||
throw Exception(err);
|
||||
}
|
||||
|
||||
extension StringUtil on String {
|
||||
Pointer<Char> toCString() => toNativeUtf8().cast<Char>();
|
||||
}
|
||||
|
||||
extension CStringUtil on Pointer<Char> {
|
||||
bool get isNull => address == nullptr.address;
|
||||
|
||||
free() {
|
||||
malloc.free(this);
|
||||
}
|
||||
|
||||
String? toDartString() {
|
||||
if (isNull) return null;
|
||||
|
||||
final str = cast<Utf8>().toDartString();
|
||||
free();
|
||||
return str;
|
||||
}
|
||||
}
|
25
cw_decred/lib/balance.dart
Normal file
25
cw_decred/lib/balance.dart
Normal file
|
@ -0,0 +1,25 @@
|
|||
import 'package:cw_decred/amount_format.dart';
|
||||
import 'package:cw_core/balance.dart';
|
||||
|
||||
class DecredBalance extends Balance {
|
||||
const DecredBalance({required this.confirmed, required this.unconfirmed, required this.frozen})
|
||||
: super(confirmed, unconfirmed);
|
||||
|
||||
factory DecredBalance.zero() => DecredBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
|
||||
|
||||
final int confirmed;
|
||||
final int unconfirmed;
|
||||
final int frozen;
|
||||
|
||||
@override
|
||||
String get formattedAvailableBalance => decredAmountToString(amount: confirmed - frozen);
|
||||
|
||||
@override
|
||||
String get formattedAdditionalBalance => decredAmountToString(amount: unconfirmed);
|
||||
|
||||
@override
|
||||
String get formattedUnAvailableBalance {
|
||||
final frozenFormatted = decredAmountToString(amount: frozen);
|
||||
return frozenFormatted == '0.0' ? '' : frozenFormatted;
|
||||
}
|
||||
}
|
2050
cw_decred/lib/mnemonic.dart
Normal file
2050
cw_decred/lib/mnemonic.dart
Normal file
File diff suppressed because it is too large
Load diff
39
cw_decred/lib/pending_transaction.dart
Normal file
39
cw_decred/lib/pending_transaction.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_decred/amount_format.dart';
|
||||
|
||||
class DecredPendingTransaction with PendingTransaction {
|
||||
DecredPendingTransaction(
|
||||
{required this.txid,
|
||||
required this.amount,
|
||||
required this.fee,
|
||||
required this.rawHex,
|
||||
required this.send});
|
||||
|
||||
final int amount;
|
||||
final int fee;
|
||||
final String txid;
|
||||
final String rawHex;
|
||||
final Future<void> Function() send;
|
||||
|
||||
@override
|
||||
String get id => txid;
|
||||
|
||||
@override
|
||||
String get amountFormatted => decredAmountToString(amount: amount);
|
||||
|
||||
@override
|
||||
String get feeFormatted => decredAmountToString(amount: fee);
|
||||
|
||||
@override
|
||||
String get hex => rawHex;
|
||||
|
||||
@override
|
||||
Future<void> commit() async {
|
||||
return send();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
10
cw_decred/lib/transaction_credentials.dart
Normal file
10
cw_decred/lib/transaction_credentials.dart
Normal file
|
@ -0,0 +1,10 @@
|
|||
import 'package:cw_decred/transaction_priority.dart';
|
||||
import 'package:cw_core/output_info.dart';
|
||||
|
||||
class DecredTransactionCredentials {
|
||||
DecredTransactionCredentials(this.outputs, {required this.priority, this.feeRate});
|
||||
|
||||
final List<OutputInfo> outputs;
|
||||
final DecredTransactionPriority? priority;
|
||||
final int? feeRate;
|
||||
}
|
31
cw_decred/lib/transaction_history.dart
Normal file
31
cw_decred/lib/transaction_history.dart
Normal file
|
@ -0,0 +1,31 @@
|
|||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
|
||||
class DecredTransactionHistory extends TransactionHistoryBase<TransactionInfo> {
|
||||
DecredTransactionHistory() {
|
||||
transactions = ObservableMap<String, TransactionInfo>();
|
||||
}
|
||||
|
||||
@override
|
||||
void addOne(TransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
|
||||
@override
|
||||
void addMany(Map<String, TransactionInfo> transactions) => this.transactions.addAll(transactions);
|
||||
|
||||
@override
|
||||
Future<void> save() async {}
|
||||
|
||||
// update returns true if a known transaction that is not pending was found.
|
||||
bool update(Map<String, TransactionInfo> txs) {
|
||||
var foundOldTx = false;
|
||||
txs.forEach((_, tx) {
|
||||
if (!this.transactions.containsKey(tx.id) || this.transactions[tx.id]!.isPending) {
|
||||
this.transactions[tx.id] = tx;
|
||||
} else {
|
||||
foundOldTx = true;
|
||||
}
|
||||
});
|
||||
return foundOldTx;
|
||||
}
|
||||
}
|
45
cw_decred/lib/transaction_info.dart
Normal file
45
cw_decred/lib/transaction_info.dart
Normal file
|
@ -0,0 +1,45 @@
|
|||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/format_amount.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_decred/amount_format.dart';
|
||||
|
||||
class DecredTransactionInfo extends TransactionInfo {
|
||||
DecredTransactionInfo({
|
||||
required String id,
|
||||
required int amount,
|
||||
required int fee,
|
||||
required TransactionDirection direction,
|
||||
required bool isPending,
|
||||
required DateTime date,
|
||||
required int height,
|
||||
required int confirmations,
|
||||
required String to,
|
||||
}) {
|
||||
this.id = id;
|
||||
this.amount = amount;
|
||||
this.fee = fee;
|
||||
this.height = height;
|
||||
this.direction = direction;
|
||||
this.date = date;
|
||||
this.isPending = isPending;
|
||||
this.confirmations = confirmations;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
String? _fiatAmount;
|
||||
|
||||
@override
|
||||
String amountFormatted() =>
|
||||
'${formatAmount(decredAmountToString(amount: amount))} ${walletTypeToCryptoCurrency(WalletType.decred).title}';
|
||||
|
||||
@override
|
||||
String? feeFormatted() =>
|
||||
'${formatAmount(decredAmountToString(amount: fee ?? 0))} ${walletTypeToCryptoCurrency(WalletType.decred).title}';
|
||||
|
||||
@override
|
||||
String fiatAmount() => _fiatAmount ?? '';
|
||||
|
||||
@override
|
||||
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
||||
}
|
69
cw_decred/lib/transaction_priority.dart
Normal file
69
cw_decred/lib/transaction_priority.dart
Normal file
|
@ -0,0 +1,69 @@
|
|||
import 'package:cw_core/transaction_priority.dart';
|
||||
|
||||
class DecredTransactionPriority extends TransactionPriority {
|
||||
const DecredTransactionPriority({required String title, required int raw})
|
||||
: super(title: title, raw: raw);
|
||||
|
||||
static const List<DecredTransactionPriority> all = [fast, medium, slow];
|
||||
static const DecredTransactionPriority slow = DecredTransactionPriority(title: 'Slow', raw: 0);
|
||||
static const DecredTransactionPriority medium =
|
||||
DecredTransactionPriority(title: 'Medium', raw: 1);
|
||||
static const DecredTransactionPriority fast = DecredTransactionPriority(title: 'Fast', raw: 2);
|
||||
|
||||
static DecredTransactionPriority deserialize({required int raw}) {
|
||||
switch (raw) {
|
||||
case 0:
|
||||
return slow;
|
||||
case 1:
|
||||
return medium;
|
||||
case 2:
|
||||
return fast;
|
||||
default:
|
||||
throw Exception('Unexpected token: $raw for DecredTransactionPriority deserialize');
|
||||
}
|
||||
}
|
||||
|
||||
String get units => 'atom';
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var label = '';
|
||||
|
||||
switch (this) {
|
||||
case DecredTransactionPriority.slow:
|
||||
label = 'Slow ~24hrs'; // '${S.current.transaction_priority_slow} ~24hrs';
|
||||
break;
|
||||
case DecredTransactionPriority.medium:
|
||||
label = 'Medium'; // S.current.transaction_priority_medium;
|
||||
break;
|
||||
case DecredTransactionPriority.fast:
|
||||
label = 'Fast'; // S.current.transaction_priority_fast;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
String labelWithRate(int rate) => '${toString()} ($rate ${units}/byte)';
|
||||
}
|
||||
|
||||
class FeeCache {
|
||||
int _feeRate;
|
||||
DateTime stamp;
|
||||
FeeCache(this._feeRate) : this.stamp = DateTime(0, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
bool isOld() {
|
||||
return this.stamp.add(const Duration(minutes: 30)).isBefore(DateTime.now());
|
||||
}
|
||||
|
||||
void update(int feeRate) {
|
||||
this._feeRate = feeRate;
|
||||
this.stamp = DateTime.now();
|
||||
}
|
||||
|
||||
int feeRate() {
|
||||
return this._feeRate;
|
||||
}
|
||||
}
|
729
cw_decred/lib/wallet.dart
Normal file
729
cw_decred/lib/wallet.dart
Normal file
|
@ -0,0 +1,729 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:cw_core/exceptions.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_decred/pending_transaction.dart';
|
||||
import 'package:cw_decred/transaction_credentials.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
import 'package:cw_decred/api/libdcrwallet.dart';
|
||||
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';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/unspent_transaction_output.dart';
|
||||
|
||||
part 'wallet.g.dart';
|
||||
|
||||
class DecredWallet = DecredWalletBase with _$DecredWallet;
|
||||
|
||||
abstract class DecredWalletBase
|
||||
extends WalletBase<DecredBalance, DecredTransactionHistory, DecredTransactionInfo> with Store {
|
||||
DecredWalletBase(WalletInfo walletInfo, String password, Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
Libwallet libwallet, Function() closeLibwallet)
|
||||
: _password = password,
|
||||
_libwallet = libwallet,
|
||||
_closeLibwallet = closeLibwallet,
|
||||
this.syncStatus = NotConnectedSyncStatus(),
|
||||
this.unspentCoinsInfo = unspentCoinsInfo,
|
||||
this.watchingOnly =
|
||||
walletInfo.derivationInfo?.derivationPath == DecredWalletService.pubkeyRestorePath ||
|
||||
walletInfo.derivationInfo?.derivationPath ==
|
||||
DecredWalletService.pubkeyRestorePathTestnet,
|
||||
this.balance = ObservableMap.of({CryptoCurrency.dcr: DecredBalance.zero()}),
|
||||
this.isTestnet = walletInfo.derivationInfo?.derivationPath ==
|
||||
DecredWalletService.seedRestorePathTestnet ||
|
||||
walletInfo.derivationInfo?.derivationPath ==
|
||||
DecredWalletService.pubkeyRestorePathTestnet,
|
||||
super(walletInfo) {
|
||||
walletAddresses = DecredWalletAddresses(walletInfo, libwallet);
|
||||
transactionHistory = DecredTransactionHistory();
|
||||
|
||||
reaction((_) => isEnabledAutoGenerateSubaddress, (bool enabled) {
|
||||
this.walletAddresses.isEnabledAutoGenerateSubaddress = enabled;
|
||||
});
|
||||
}
|
||||
|
||||
// NOTE: Hitting this max fee would be unexpected with current on chain use
|
||||
// but this may need to be updated in the future.
|
||||
final maxFeeRate = 100000;
|
||||
|
||||
// syncIntervalSyncing is used up until synced, then transactions are checked
|
||||
// every syncIntervalSynced.
|
||||
final syncIntervalSyncing = 5; // seconds
|
||||
final syncIntervalSynced = 30; // seconds
|
||||
static final defaultFeeRate = 10000;
|
||||
final String _password;
|
||||
final Libwallet _libwallet;
|
||||
final Function() _closeLibwallet;
|
||||
final idPrefix = "decred_";
|
||||
|
||||
// TODO: Encrypt this.
|
||||
var _seed = "";
|
||||
var _pubkey = "";
|
||||
var _unspents = <Unspent>[];
|
||||
|
||||
// synced is used to set the syncTimer interval.
|
||||
bool synced = false;
|
||||
bool watchingOnly;
|
||||
bool connecting = false;
|
||||
String persistantPeer = "default-spv-nodes";
|
||||
FeeCache feeRateFast = FeeCache(defaultFeeRate);
|
||||
FeeCache feeRateMedium = FeeCache(defaultFeeRate);
|
||||
FeeCache feeRateSlow = FeeCache(defaultFeeRate);
|
||||
Timer? syncTimer;
|
||||
Box<UnspentCoinsInfo> unspentCoinsInfo;
|
||||
|
||||
@override
|
||||
@observable
|
||||
bool isEnabledAutoGenerateSubaddress = true;
|
||||
|
||||
@override
|
||||
@observable
|
||||
SyncStatus syncStatus;
|
||||
|
||||
@override
|
||||
@observable
|
||||
late ObservableMap<CryptoCurrency, DecredBalance> balance;
|
||||
|
||||
@override
|
||||
late DecredWalletAddresses walletAddresses;
|
||||
|
||||
@override
|
||||
String? get seed {
|
||||
if (watchingOnly) {
|
||||
return null;
|
||||
}
|
||||
return _seed;
|
||||
}
|
||||
|
||||
@override
|
||||
Object get keys => {};
|
||||
|
||||
@override
|
||||
bool isTestnet;
|
||||
|
||||
String get pubkey {
|
||||
return _pubkey;
|
||||
}
|
||||
|
||||
Future<void> init() async {
|
||||
final getSeed = () async {
|
||||
if (!watchingOnly) {
|
||||
_seed = await _libwallet.walletSeed(walletInfo.name, _password) ?? "";
|
||||
}
|
||||
_pubkey = await _libwallet.defaultPubkey(walletInfo.name);
|
||||
};
|
||||
await Future.wait([
|
||||
updateBalance(),
|
||||
updateTransactionHistory(),
|
||||
walletAddresses.init(),
|
||||
fetchTransactions(),
|
||||
updateFees(),
|
||||
fetchUnspents(),
|
||||
getSeed(),
|
||||
]);
|
||||
}
|
||||
|
||||
Future<void> performBackgroundTasks() async {
|
||||
if (!await checkSync()) {
|
||||
if (synced == true) {
|
||||
synced = false;
|
||||
if (syncTimer != null) {
|
||||
syncTimer!.cancel();
|
||||
}
|
||||
syncTimer = Timer.periodic(
|
||||
Duration(seconds: syncIntervalSyncing), (Timer t) => performBackgroundTasks());
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Set sync check interval lower since we are synced.
|
||||
if (synced == false) {
|
||||
synced = true;
|
||||
if (syncTimer != null) {
|
||||
syncTimer!.cancel();
|
||||
}
|
||||
syncTimer = Timer.periodic(
|
||||
Duration(seconds: syncIntervalSynced), (Timer t) => performBackgroundTasks());
|
||||
}
|
||||
await Future.wait([
|
||||
updateTransactionHistory(),
|
||||
updateFees(),
|
||||
fetchUnspents(),
|
||||
updateBalance(),
|
||||
walletAddresses.updateAddressesInBox(),
|
||||
]);
|
||||
}
|
||||
|
||||
Future<void> updateFees() async {
|
||||
final feeForNb = (int nb) async {
|
||||
try {
|
||||
final feeStr = await _libwallet.estimateFee(walletInfo.name, nb);
|
||||
var fee = int.parse(feeStr);
|
||||
if (fee > maxFeeRate) {
|
||||
throw "dcr fee returned from estimate fee was over max";
|
||||
} else if (fee <= 0) {
|
||||
throw "dcr fee returned from estimate fee was zero";
|
||||
}
|
||||
return fee;
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
return defaultFeeRate;
|
||||
}
|
||||
};
|
||||
if (feeRateSlow.isOld()) {
|
||||
feeRateSlow.update(await feeForNb(4));
|
||||
}
|
||||
if (feeRateMedium.isOld()) {
|
||||
feeRateMedium.update(await feeForNb(2));
|
||||
}
|
||||
if (feeRateFast.isOld()) {
|
||||
feeRateFast.update(await feeForNb(1));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateTransactionHistory() async {
|
||||
// from is the number of transactions skipped from most recent, not block
|
||||
// height.
|
||||
var from = 0;
|
||||
while (true) {
|
||||
// Transactions are returned from newest to oldest. Loop fetching 5 txn
|
||||
// at a time until we find a batch with txn that no longer need to be
|
||||
// updated.
|
||||
final txs = await this.fetchFiveTransactions(from);
|
||||
if (txs.length == 0) {
|
||||
return;
|
||||
}
|
||||
if (this.transactionHistory.update(txs)) {
|
||||
return;
|
||||
}
|
||||
from += 5;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> checkSync() async {
|
||||
final syncStatusJSON = await _libwallet.syncStatus(walletInfo.name);
|
||||
final decoded = json.decode(syncStatusJSON);
|
||||
|
||||
final syncStatusCode = decoded["syncstatuscode"] ?? 0;
|
||||
// final syncStatusStr = decoded["syncstatus"] ?? "";
|
||||
final targetHeight = decoded["targetheight"] ?? 1;
|
||||
final numPeers = decoded["numpeers"] ?? 0;
|
||||
// final cFiltersHeight = decoded["cfiltersheight"] ?? 0;
|
||||
final headersHeight = decoded["headersheight"] ?? 0;
|
||||
final rescanHeight = decoded["rescanheight"] ?? 0;
|
||||
|
||||
if (numPeers == 0) {
|
||||
syncStatus = NotConnectedSyncStatus();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sync codes:
|
||||
// NotStarted = 0
|
||||
// FetchingCFilters = 1
|
||||
// FetchingHeaders = 2
|
||||
// DiscoveringAddrs = 3
|
||||
// Rescanning = 4
|
||||
// Complete = 5
|
||||
|
||||
if (syncStatusCode > 4) {
|
||||
syncStatus = SyncedSyncStatus();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (syncStatusCode == 0) {
|
||||
syncStatus = ConnectedSyncStatus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (syncStatusCode == 1) {
|
||||
syncStatus = SyncingSyncStatus(targetHeight, 0.0);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (syncStatusCode == 2) {
|
||||
final headersProg = headersHeight / targetHeight;
|
||||
// Only allow headers progress to go up half way.
|
||||
syncStatus = SyncingSyncStatus(targetHeight - headersHeight, headersProg);
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: This step takes a while so should really get more info to the UI
|
||||
// that we are discovering addresses.
|
||||
if (syncStatusCode == 3) {
|
||||
// Hover at half.
|
||||
syncStatus = ProcessingSyncStatus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (syncStatusCode == 4) {
|
||||
// Start at 75%.
|
||||
final rescanProg = rescanHeight / targetHeight / 4;
|
||||
syncStatus = SyncingSyncStatus(targetHeight - rescanHeight, .75 + rescanProg);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> connectToNode({required Node node}) async {
|
||||
if (connecting) {
|
||||
return;
|
||||
}
|
||||
connecting = true;
|
||||
String addr = "default-spv-nodes";
|
||||
if (node.uri.host != addr) {
|
||||
addr = node.uri.host;
|
||||
if (node.uri.port != "") {
|
||||
addr += ":" + node.uri.port.toString();
|
||||
}
|
||||
}
|
||||
if (addr != persistantPeer) {
|
||||
if (syncTimer != null) {
|
||||
syncTimer!.cancel();
|
||||
syncTimer = null;
|
||||
}
|
||||
persistantPeer = addr;
|
||||
await _libwallet.closeWallet(walletInfo.name);
|
||||
final network = isTestnet ? "testnet" : "mainnet";
|
||||
final config = {
|
||||
"name": walletInfo.name,
|
||||
"datadir": walletInfo.dirPath,
|
||||
"net": network,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await _libwallet.loadWallet(jsonEncode(config));
|
||||
}
|
||||
await this._startSync();
|
||||
connecting = false;
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> startSync() async {
|
||||
if (connecting) {
|
||||
return;
|
||||
}
|
||||
connecting = true;
|
||||
await this._startSync();
|
||||
connecting = false;
|
||||
}
|
||||
|
||||
Future<void> _startSync() async {
|
||||
if (syncTimer != null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
syncStatus = ConnectingSyncStatus();
|
||||
await _libwallet.startSync(
|
||||
walletInfo.name,
|
||||
persistantPeer == "default-spv-nodes" ? "" : persistantPeer,
|
||||
);
|
||||
syncTimer = Timer.periodic(
|
||||
Duration(seconds: syncIntervalSyncing), (Timer t) => performBackgroundTasks());
|
||||
} catch (e) {
|
||||
printV(e.toString());
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@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";
|
||||
});
|
||||
}
|
||||
var totalIn = 0;
|
||||
final ignoreInputs = [];
|
||||
this.unspentCoinsInfo.values.forEach((unspent) {
|
||||
if (unspent.isFrozen || !unspent.isSending) {
|
||||
final input = {"txid": unspent.hash, "vout": unspent.vout};
|
||||
ignoreInputs.add(input);
|
||||
return;
|
||||
}
|
||||
totalIn += unspent.value;
|
||||
});
|
||||
|
||||
final creds = credentials as DecredTransactionCredentials;
|
||||
var totalAmt = 0;
|
||||
var sendAll = false;
|
||||
final outputs = [];
|
||||
for (final out in creds.outputs) {
|
||||
var amt = 0;
|
||||
if (out.sendAll) {
|
||||
if (creds.outputs.length != 1) {
|
||||
throw "can only send all to one output";
|
||||
}
|
||||
sendAll = true;
|
||||
totalAmt = totalIn;
|
||||
} else if (out.cryptoAmount != null) {
|
||||
final coins = double.parse(out.cryptoAmount!);
|
||||
amt = (coins * 1e8).toInt();
|
||||
}
|
||||
totalAmt += amt;
|
||||
final o = {
|
||||
"address": out.isParsedAddress ? out.extractedAddress! : out.address,
|
||||
"amount": amt
|
||||
};
|
||||
outputs.add(o);
|
||||
}
|
||||
|
||||
// throw exception if no selected coins under coin control
|
||||
// or if the total coins selected, is less than the amount the user wants to spend
|
||||
if (ignoreInputs.length == unspentCoinsInfo.values.length || totalIn < totalAmt) {
|
||||
throw TransactionNoInputsException();
|
||||
}
|
||||
|
||||
// The inputs are always used. Currently we don't have use for this
|
||||
// argument. sendall ingores output value and sends everything.
|
||||
final signReq = {
|
||||
// "inputs": inputs,
|
||||
"ignoreInputs": ignoreInputs,
|
||||
"outputs": outputs,
|
||||
"feerate": creds.feeRate ?? defaultFeeRate,
|
||||
"password": _password,
|
||||
"sendall": sendAll,
|
||||
};
|
||||
final res = await _libwallet.createSignedTransaction(walletInfo.name, jsonEncode(signReq));
|
||||
final decoded = json.decode(res);
|
||||
final signedHex = decoded["signedhex"];
|
||||
final send = () async {
|
||||
await _libwallet.sendRawTransaction(walletInfo.name, signedHex);
|
||||
await updateBalance();
|
||||
};
|
||||
final fee = decoded["fee"] ?? 0;
|
||||
if (sendAll) {
|
||||
totalAmt = (totalAmt - fee).toInt();
|
||||
}
|
||||
return DecredPendingTransaction(
|
||||
txid: decoded["txid"] ?? "", amount: totalAmt, fee: fee, rawHex: signedHex, send: send);
|
||||
}
|
||||
|
||||
int feeRate(TransactionPriority priority) {
|
||||
if (!(priority is DecredTransactionPriority)) {
|
||||
return defaultFeeRate;
|
||||
}
|
||||
final p = priority;
|
||||
switch (p) {
|
||||
case DecredTransactionPriority.slow:
|
||||
return feeRateSlow.feeRate();
|
||||
case DecredTransactionPriority.medium:
|
||||
return feeRateMedium.feeRate();
|
||||
case DecredTransactionPriority.fast:
|
||||
return feeRateFast.feeRate();
|
||||
}
|
||||
return defaultFeeRate;
|
||||
}
|
||||
|
||||
@override
|
||||
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
|
||||
if (priority is DecredTransactionPriority) {
|
||||
final P2PKHOutputSize =
|
||||
36; // 8 bytes value + 2 bytes version + at least 1 byte varint script size + P2PKHPkScriptSize
|
||||
// MsgTxOverhead is 4 bytes version (lower 2 bytes for the real transaction
|
||||
// version and upper 2 bytes for the serialization type) + 4 bytes locktime
|
||||
// + 4 bytes expiry + 3 bytes of varints for the number of transaction
|
||||
// inputs (x2 for witness and prefix) and outputs
|
||||
final MsgTxOverhead = 15;
|
||||
// TxInOverhead is the overhead for a wire.TxIn with a scriptSig length <
|
||||
// 254. prefix (41 bytes) + ValueIn (8 bytes) + BlockHeight (4 bytes) +
|
||||
// BlockIndex (4 bytes) + sig script var int (at least 1 byte)
|
||||
final TxInOverhead = 57;
|
||||
final P2PKHInputSize =
|
||||
TxInOverhead + 109; // TxInOverhead (57) + var int (1) + P2PKHSigScriptSize (108)
|
||||
|
||||
int inputsCount = 1;
|
||||
if (amount != null) {
|
||||
inputsCount += _unspents.where((e) {
|
||||
amount = (amount!) - e.value;
|
||||
return (amount!) > 0;
|
||||
}).length;
|
||||
}
|
||||
|
||||
// Estimate using a transaction consuming inoutsCount and paying to one address with change.
|
||||
return (this.feeRate(priority) / 1000).round() *
|
||||
(MsgTxOverhead + P2PKHInputSize * inputsCount + P2PKHOutputSize * 2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, DecredTransactionInfo>> fetchTransactions() async {
|
||||
return this.fetchFiveTransactions(0);
|
||||
}
|
||||
|
||||
Future<Map<String, DecredTransactionInfo>> fetchFiveTransactions(int from) async {
|
||||
final res = await _libwallet.listTransactions(walletInfo.name, from.toString(), "5");
|
||||
final decoded = json.decode(res);
|
||||
var txs = <String, DecredTransactionInfo>{};
|
||||
for (final d in decoded) {
|
||||
final txid = uniqueTxID(d["txid"] ?? "", d["vout"] ?? 0);
|
||||
var direction = TransactionDirection.outgoing;
|
||||
if (d["category"] == "receive") {
|
||||
direction = TransactionDirection.incoming;
|
||||
}
|
||||
final amountDouble = d["amount"] ?? 0.0;
|
||||
final amount = (amountDouble * 1e8).toInt().abs();
|
||||
final feeDouble = d["fee"] ?? 0.0;
|
||||
final fee = (feeDouble * 1e8).toInt().abs();
|
||||
final confs = d["confirmations"] ?? 0;
|
||||
final sendTime = d["time"] ?? 0;
|
||||
final height = d["height"] ?? 0;
|
||||
final txInfo = DecredTransactionInfo(
|
||||
id: txid,
|
||||
amount: amount,
|
||||
fee: fee,
|
||||
direction: direction,
|
||||
isPending: confs == 0,
|
||||
date: DateTime.fromMillisecondsSinceEpoch(sendTime * 1000, isUtc: false),
|
||||
height: height,
|
||||
confirmations: confs,
|
||||
to: d["address"] ?? "",
|
||||
);
|
||||
txs[txid] = txInfo;
|
||||
}
|
||||
return txs;
|
||||
}
|
||||
|
||||
// uniqueTxID combines the tx id and vout to create a unique id.
|
||||
String uniqueTxID(String id, int vout) {
|
||||
return id + ":" + vout.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> save() async {}
|
||||
|
||||
@override
|
||||
bool get hasRescan => walletBirthdayBlockHeight() != -1;
|
||||
|
||||
@override
|
||||
Future<void> rescan({required int height}) async {
|
||||
// The required height is not used. A birthday time is recorded in the
|
||||
// mnemonic. As long as not private data is imported into the wallet, we
|
||||
// can always rescan from there.
|
||||
var rescanHeight = 0;
|
||||
if (!watchingOnly) {
|
||||
rescanHeight = await walletBirthdayBlockHeight();
|
||||
// Sync has not yet reached the birthday block.
|
||||
if (rescanHeight == -1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await _libwallet.rescanFromHeight(walletInfo.name, rescanHeight.toString());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close({bool shouldCleanup = false}) async {
|
||||
if (syncTimer != null) {
|
||||
syncTimer!.cancel();
|
||||
syncTimer = null;
|
||||
}
|
||||
await _libwallet.closeWallet(walletInfo.name);
|
||||
if (shouldCleanup) {
|
||||
await _libwallet.shutdown();
|
||||
_closeLibwallet();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> changePassword(String password) async {
|
||||
if (watchingOnly) {
|
||||
return;
|
||||
}
|
||||
return () async {
|
||||
await _libwallet.changeWalletPassword(walletInfo.name, _password, password);
|
||||
}();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateBalance() async {
|
||||
final balanceMap = await _libwallet.balance(walletInfo.name);
|
||||
|
||||
var totalFrozen = 0;
|
||||
|
||||
unspentCoinsInfo.values.forEach((info) {
|
||||
_unspents.forEach((element) {
|
||||
if (element.hash == info.hash &&
|
||||
element.vout == info.vout &&
|
||||
info.isFrozen &&
|
||||
element.value == info.value) {
|
||||
totalFrozen += element.value;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
balance[CryptoCurrency.dcr] = DecredBalance(
|
||||
confirmed: balanceMap["confirmed"] ?? 0,
|
||||
unconfirmed: balanceMap["unconfirmed"] ?? 0,
|
||||
frozen: totalFrozen,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => onError;
|
||||
|
||||
Future<void> renameWalletFiles(String newWalletName) async {
|
||||
final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
|
||||
|
||||
final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
|
||||
|
||||
if (File(newDirPath).existsSync()) {
|
||||
throw "wallet already exists at $newDirPath";
|
||||
}
|
||||
|
||||
await Directory(currentDirPath).rename(newDirPath);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> signMessage(String message, {String? address = null}) async {
|
||||
if (watchingOnly) {
|
||||
throw "a watching only wallet cannot sign";
|
||||
}
|
||||
var addr = address;
|
||||
if (addr == null) {
|
||||
addr = walletAddresses.address;
|
||||
}
|
||||
if (addr == "") {
|
||||
throw "unable to get an address from unsynced wallet";
|
||||
}
|
||||
return await _libwallet.signMessage(walletInfo.name, message, addr, _password);
|
||||
}
|
||||
|
||||
Future<void> fetchUnspents() async {
|
||||
final res = await _libwallet.listUnspents(walletInfo.name);
|
||||
final decoded = json.decode(res);
|
||||
var unspents = <Unspent>[];
|
||||
for (final d in decoded) {
|
||||
final spendable = d["spendable"] ?? false;
|
||||
if (!spendable) {
|
||||
continue;
|
||||
}
|
||||
final amountDouble = d["amount"] ?? 0.0;
|
||||
final amount = (amountDouble * 1e8).toInt().abs();
|
||||
final utxo = Unspent(d["address"] ?? "", d["txid"] ?? "", amount, d["vout"] ?? 0, null);
|
||||
utxo.isChange = d["ischange"] ?? false;
|
||||
unspents.add(utxo);
|
||||
}
|
||||
_unspents = unspents;
|
||||
}
|
||||
|
||||
List<Unspent> unspents() {
|
||||
this.updateUnspents(_unspents);
|
||||
return _unspents;
|
||||
}
|
||||
|
||||
void updateUnspents(List<Unspent> unspentCoins) {
|
||||
if (this.unspentCoinsInfo.isEmpty) {
|
||||
unspentCoins.forEach((coin) => this.addCoinInfo(coin));
|
||||
return;
|
||||
}
|
||||
|
||||
if (unspentCoins.isEmpty) {
|
||||
this.unspentCoinsInfo.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
final walletID = idPrefix + walletInfo.name;
|
||||
if (unspentCoins.isNotEmpty) {
|
||||
unspentCoins.forEach((coin) {
|
||||
final coinInfoList = this.unspentCoinsInfo.values.where((element) =>
|
||||
element.walletId == walletID && element.hash == coin.hash && element.vout == coin.vout);
|
||||
|
||||
if (coinInfoList.isEmpty) {
|
||||
this.addCoinInfo(coin);
|
||||
} else {
|
||||
final coinInfo = coinInfoList.first;
|
||||
|
||||
coin.isFrozen = coinInfo.isFrozen;
|
||||
coin.isSending = coinInfo.isSending;
|
||||
coin.note = coinInfo.note;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final List<dynamic> keys = <dynamic>[];
|
||||
this.unspentCoinsInfo.values.forEach((element) {
|
||||
final existUnspentCoins = unspentCoins.where((coin) => element.hash.contains(coin.hash));
|
||||
|
||||
if (existUnspentCoins.isEmpty) {
|
||||
keys.add(element.key);
|
||||
}
|
||||
});
|
||||
|
||||
if (keys.isNotEmpty) {
|
||||
unspentCoinsInfo.deleteAll(keys);
|
||||
}
|
||||
}
|
||||
|
||||
void addCoinInfo(Unspent coin) {
|
||||
final newInfo = UnspentCoinsInfo(
|
||||
walletId: idPrefix + walletInfo.name,
|
||||
hash: coin.hash,
|
||||
isFrozen: false,
|
||||
isSending: coin.isSending,
|
||||
noteRaw: "",
|
||||
address: coin.address,
|
||||
value: coin.value,
|
||||
vout: coin.vout,
|
||||
isChange: coin.isChange,
|
||||
keyImage: coin.keyImage,
|
||||
);
|
||||
|
||||
unspentCoinsInfo.add(newInfo);
|
||||
}
|
||||
|
||||
// walletBirthdayBlockHeight checks if the wallet birthday is set and returns
|
||||
// it. Returns -1 if not.
|
||||
Future<int> walletBirthdayBlockHeight() async {
|
||||
final res = await _libwallet.birthState(walletInfo.name);
|
||||
final decoded = json.decode(res);
|
||||
// Having these values set indicates that sync has not reached the birthday
|
||||
// yet, so no birthday is set.
|
||||
if (decoded["setfromheight"] == true || decoded["setfromtime"] == true) {
|
||||
return -1;
|
||||
}
|
||||
return decoded["height"] ?? 0;
|
||||
}
|
||||
|
||||
Future<bool> verifyMessage(String message, String signature, {String? address = null}) async {
|
||||
var addr = address;
|
||||
if (addr == null) {
|
||||
throw "an address is required to verify message";
|
||||
}
|
||||
return () async {
|
||||
final verified = await _libwallet.verifyMessage(walletInfo.name, message, addr, signature);
|
||||
if (verified == "true") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
}
|
||||
|
||||
@override
|
||||
String get password => _password;
|
||||
|
||||
@override
|
||||
bool canSend() => seed != null;
|
||||
}
|
137
cw_decred/lib/wallet_addresses.dart
Normal file
137
cw_decred/lib/wallet_addresses.dart
Normal file
|
@ -0,0 +1,137 @@
|
|||
import 'dart:convert';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
import 'package:cw_core/address_info.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_decred/api/libdcrwallet.dart';
|
||||
|
||||
part 'wallet_addresses.g.dart';
|
||||
|
||||
class DecredWalletAddresses = DecredWalletAddressesBase with _$DecredWalletAddresses;
|
||||
|
||||
abstract class DecredWalletAddressesBase extends WalletAddresses with Store {
|
||||
DecredWalletAddressesBase(WalletInfo walletInfo, Libwallet libwallet)
|
||||
: _libwallet = libwallet,
|
||||
super(walletInfo);
|
||||
final Libwallet _libwallet;
|
||||
String currentAddr = '';
|
||||
|
||||
@observable
|
||||
bool isEnabledAutoGenerateSubaddress = true;
|
||||
|
||||
@observable
|
||||
String selectedAddr = '';
|
||||
|
||||
@override
|
||||
@computed
|
||||
String get address {
|
||||
return selectedAddr;
|
||||
}
|
||||
|
||||
@override
|
||||
set address(value) {
|
||||
selectedAddr = value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
if (walletInfo.addresses != null) {
|
||||
addressesMap = walletInfo.addresses!;
|
||||
}
|
||||
if (walletInfo.addressInfos != null) {
|
||||
addressInfos = walletInfo.addressInfos!;
|
||||
}
|
||||
if (walletInfo.usedAddresses != null) {
|
||||
usedAddresses = {...walletInfo.usedAddresses!};
|
||||
}
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAddressesInBox() async {
|
||||
final addrs = await libAddresses();
|
||||
final allAddrs = new List.from(addrs.usedAddrs)..addAll(addrs.unusedAddrs);
|
||||
|
||||
// Add all addresses.
|
||||
allAddrs.forEach((addr) {
|
||||
if (addressesMap.containsKey(addr)) {
|
||||
return;
|
||||
}
|
||||
addressesMap[addr] = "";
|
||||
addressInfos[0] ??= [];
|
||||
addressInfos[0]?.add(AddressInfo(address: addr, label: "", accountIndex: 0));
|
||||
});
|
||||
|
||||
// Add used addresses.
|
||||
addrs.usedAddrs.forEach((addr) {
|
||||
if (!usedAddresses.contains(addr)) {
|
||||
usedAddresses.add(addr);
|
||||
}
|
||||
});
|
||||
|
||||
if (addrs.unusedAddrs.length > 0 && addrs.unusedAddrs[0] != currentAddr) {
|
||||
currentAddr = addrs.unusedAddrs[0];
|
||||
selectedAddr = currentAddr;
|
||||
}
|
||||
|
||||
await saveAddressesInBox();
|
||||
}
|
||||
|
||||
List<AddressInfo> getAddressInfos() {
|
||||
if (addressInfos.containsKey(0)) {
|
||||
return addressInfos[0]!;
|
||||
}
|
||||
return <AddressInfo>[];
|
||||
}
|
||||
|
||||
Future<void> updateAddress(String address, String label) async {
|
||||
if (!addressInfos.containsKey(0)) {
|
||||
return;
|
||||
}
|
||||
addressInfos[0]!.forEach((info) {
|
||||
if (info.address == address) {
|
||||
info.label = label;
|
||||
}
|
||||
});
|
||||
await saveAddressesInBox();
|
||||
}
|
||||
|
||||
Future<LibAddresses> libAddresses() async {
|
||||
final nUsed = "10";
|
||||
var nUnused = "1";
|
||||
if (this.isEnabledAutoGenerateSubaddress) {
|
||||
nUnused = "3";
|
||||
}
|
||||
final res = await _libwallet.addresses(walletInfo.name, nUsed, nUnused);
|
||||
final decoded = json.decode(res);
|
||||
final usedAddrs = List<String>.from(decoded["used"] ?? []);
|
||||
final unusedAddrs = List<String>.from(decoded["unused"] ?? []);
|
||||
// index is the index of the first unused address.
|
||||
final index = decoded["index"] ?? 0;
|
||||
return new LibAddresses(usedAddrs, unusedAddrs, index);
|
||||
}
|
||||
|
||||
Future<void> generateNewAddress(String label) async {
|
||||
// NOTE: This will ignore the gap limit and may cause problems when restoring from seed if too
|
||||
// many addresses are taken and not used.
|
||||
final addr = await _libwallet.newExternalAddress(walletInfo.name) ?? '';
|
||||
if (addr == "") {
|
||||
return;
|
||||
}
|
||||
if (!addressesMap.containsKey(addr)) {
|
||||
addressesMap[addr] = "";
|
||||
addressInfos[0] ??= [];
|
||||
addressInfos[0]?.add(AddressInfo(address: addr, label: label, accountIndex: 0));
|
||||
}
|
||||
selectedAddr = addr;
|
||||
await saveAddressesInBox();
|
||||
}
|
||||
}
|
||||
|
||||
class LibAddresses {
|
||||
final List<String> usedAddrs, unusedAddrs;
|
||||
final int firstUnusedAddrIndex;
|
||||
|
||||
LibAddresses(this.usedAddrs, this.unusedAddrs, this.firstUnusedAddrIndex);
|
||||
}
|
40
cw_decred/lib/wallet_creation_credentials.dart
Normal file
40
cw_decred/lib/wallet_creation_credentials.dart
Normal file
|
@ -0,0 +1,40 @@
|
|||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/hardware/hardware_account_data.dart';
|
||||
|
||||
class DecredNewWalletCredentials extends WalletCredentials {
|
||||
DecredNewWalletCredentials({required String name, WalletInfo? walletInfo})
|
||||
: super(name: name, walletInfo: walletInfo);
|
||||
}
|
||||
|
||||
class DecredRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
DecredRestoreWalletFromSeedCredentials(
|
||||
{required String name,
|
||||
required String password,
|
||||
required this.mnemonic,
|
||||
WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String mnemonic;
|
||||
}
|
||||
|
||||
class DecredRestoreWalletFromPubkeyCredentials extends WalletCredentials {
|
||||
DecredRestoreWalletFromPubkeyCredentials(
|
||||
{required String name,
|
||||
required String password,
|
||||
required String this.pubkey,
|
||||
WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String pubkey;
|
||||
}
|
||||
|
||||
class DecredRestoreWalletFromHardwareCredentials extends WalletCredentials {
|
||||
DecredRestoreWalletFromHardwareCredentials(
|
||||
{required String name, required this.hwAccountData, WalletInfo? walletInfo})
|
||||
: t = throw UnimplementedError(),
|
||||
super(name: name, walletInfo: walletInfo);
|
||||
|
||||
final HardwareAccountData hwAccountData;
|
||||
final void t;
|
||||
}
|
186
cw_decred/lib/wallet_service.dart
Normal file
186
cw_decred/lib/wallet_service.dart
Normal file
|
@ -0,0 +1,186 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:cw_decred/api/libdcrwallet.dart';
|
||||
import 'package:cw_decred/wallet_creation_credentials.dart';
|
||||
import 'package:cw_decred/wallet.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
|
||||
class DecredWalletService extends WalletService<
|
||||
DecredNewWalletCredentials,
|
||||
DecredRestoreWalletFromSeedCredentials,
|
||||
DecredRestoreWalletFromPubkeyCredentials,
|
||||
DecredRestoreWalletFromHardwareCredentials> {
|
||||
DecredWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||
final seedRestorePath = "m/44'/42'";
|
||||
static final seedRestorePathTestnet = "m/44'/1'";
|
||||
static final pubkeyRestorePath = "m/44'/42'/0'";
|
||||
static final pubkeyRestorePathTestnet = "m/44'/1'/0'";
|
||||
final mainnet = "mainnet";
|
||||
final testnet = "testnet";
|
||||
Libwallet? libwallet;
|
||||
|
||||
Future<void> init() async {
|
||||
if (libwallet != null) {
|
||||
return;
|
||||
}
|
||||
libwallet = await Libwallet.spawn();
|
||||
// Use the general path for all dcr wallets as the general log directory.
|
||||
// Individual wallet paths may be removed if the wallet is deleted.
|
||||
final dcrLogDir = await pathForWalletDir(name: '', type: WalletType.decred);
|
||||
libwallet!.initLibdcrwallet(dcrLogDir);
|
||||
}
|
||||
|
||||
void closeLibwallet() {
|
||||
if (libwallet == null) {
|
||||
return;
|
||||
}
|
||||
libwallet!.close();
|
||||
libwallet = null;
|
||||
}
|
||||
|
||||
@override
|
||||
WalletType getType() => WalletType.decred;
|
||||
|
||||
@override
|
||||
Future<bool> isWalletExit(String name) async =>
|
||||
File(await pathForWallet(name: name, type: getType())).existsSync();
|
||||
|
||||
@override
|
||||
Future<DecredWallet> create(DecredNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||
await this.init();
|
||||
final config = {
|
||||
"name": credentials.walletInfo!.name,
|
||||
"datadir": credentials.walletInfo!.dirPath,
|
||||
"pass": credentials.password!,
|
||||
"net": isTestnet == true ? testnet : mainnet,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await libwallet!.createWallet(jsonEncode(config));
|
||||
final di = DerivationInfo(
|
||||
derivationPath: isTestnet == true ? seedRestorePathTestnet : seedRestorePath);
|
||||
credentials.walletInfo!.derivationInfo = di;
|
||||
final wallet = DecredWallet(credentials.walletInfo!, credentials.password!,
|
||||
this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DecredWallet> openWallet(String name, String password) async {
|
||||
final walletInfo = walletInfoSource.values
|
||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
|
||||
final network = walletInfo.derivationInfo?.derivationPath == seedRestorePathTestnet ||
|
||||
walletInfo.derivationInfo?.derivationPath == pubkeyRestorePathTestnet
|
||||
? testnet
|
||||
: mainnet;
|
||||
|
||||
await this.init();
|
||||
final walletDirExists = Directory(walletInfo.dirPath).existsSync();
|
||||
if (!walletDirExists) {
|
||||
walletInfo.dirPath = await pathForWalletDir(name: name, type: getType());
|
||||
}
|
||||
|
||||
final config = {
|
||||
"name": walletInfo.name,
|
||||
"datadir": walletInfo.dirPath,
|
||||
"net": network,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await libwallet!.loadWallet(jsonEncode(config));
|
||||
final wallet =
|
||||
DecredWallet(walletInfo, password, this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String wallet) async {
|
||||
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
||||
final walletInfo = walletInfoSource.values
|
||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
||||
await walletInfoSource.delete(walletInfo.key);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> rename(String currentName, String password, String newName) async {
|
||||
final currentWalletInfo = walletInfoSource.values
|
||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
|
||||
final network = currentWalletInfo.derivationInfo?.derivationPath == seedRestorePathTestnet ||
|
||||
currentWalletInfo.derivationInfo?.derivationPath == pubkeyRestorePathTestnet
|
||||
? testnet
|
||||
: mainnet;
|
||||
final currentWallet = DecredWallet(
|
||||
currentWalletInfo, password, this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
|
||||
final newDirPath = await pathForWalletDir(name: newName, type: getType());
|
||||
final newWalletInfo = currentWalletInfo;
|
||||
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||
newWalletInfo.name = newName;
|
||||
newWalletInfo.dirPath = newDirPath;
|
||||
newWalletInfo.network = network;
|
||||
|
||||
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DecredWallet> restoreFromSeed(DecredRestoreWalletFromSeedCredentials credentials,
|
||||
{bool? isTestnet}) async {
|
||||
await this.init();
|
||||
final config = {
|
||||
"name": credentials.walletInfo!.name,
|
||||
"datadir": credentials.walletInfo!.dirPath,
|
||||
"pass": credentials.password!,
|
||||
"mnemonic": credentials.mnemonic,
|
||||
"net": isTestnet == true ? testnet : mainnet,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await libwallet!.createWallet(jsonEncode(config));
|
||||
final di = DerivationInfo(
|
||||
derivationPath: isTestnet == true ? seedRestorePathTestnet : seedRestorePath);
|
||||
credentials.walletInfo!.derivationInfo = di;
|
||||
final wallet = DecredWallet(credentials.walletInfo!, credentials.password!,
|
||||
this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
// restoreFromKeys only supports restoring a watch only wallet from an account
|
||||
// pubkey.
|
||||
@override
|
||||
Future<DecredWallet> restoreFromKeys(DecredRestoreWalletFromPubkeyCredentials credentials,
|
||||
{bool? isTestnet}) async {
|
||||
await this.init();
|
||||
final config = {
|
||||
"name": credentials.walletInfo!.name,
|
||||
"datadir": credentials.walletInfo!.dirPath,
|
||||
"pubkey": credentials.pubkey,
|
||||
"net": isTestnet == true ? testnet : mainnet,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await libwallet!.createWatchOnlyWallet(jsonEncode(config));
|
||||
final di = DerivationInfo(
|
||||
derivationPath: isTestnet == true ? pubkeyRestorePathTestnet : pubkeyRestorePath);
|
||||
credentials.walletInfo!.derivationInfo = di;
|
||||
final wallet = DecredWallet(credentials.walletInfo!, credentials.password!,
|
||||
this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DecredWallet> restoreFromHardwareWallet(
|
||||
DecredRestoreWalletFromHardwareCredentials credentials) async =>
|
||||
throw UnimplementedError();
|
||||
}
|
19
cw_decred/macos/Classes/CwDecredPlugin.swift
Normal file
19
cw_decred/macos/Classes/CwDecredPlugin.swift
Normal file
|
@ -0,0 +1,19 @@
|
|||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
public class CwDecredPlugin: NSObject, FlutterPlugin {
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let channel = FlutterMethodChannel(name: "cw_decred", binaryMessenger: registrar.messenger)
|
||||
let instance = CwDecredPlugin()
|
||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||
}
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
switch call.method {
|
||||
case "getPlatformVersion":
|
||||
result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString)
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
}
|
22
cw_decred/macos/cw_decred.podspec
Normal file
22
cw_decred/macos/cw_decred.podspec
Normal file
|
@ -0,0 +1,22 @@
|
|||
#
|
||||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
|
||||
# Run `pod lib lint cw_decred.podspec` to validate before publishing.
|
||||
#
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'cw_decred'
|
||||
s.version = '0.0.1'
|
||||
s.summary = 'Cake Wallet Decred'
|
||||
s.description = 'Cake Wallet wrapper over Decred project'
|
||||
s.homepage = 'http://cakewallet.com'
|
||||
s.license = { :file => '../LICENSE' }
|
||||
s.author = { 'Cake Wallet' => 'support@cakewallet.com' }
|
||||
|
||||
s.source = { :path => '.' }
|
||||
s.source_files = 'Classes/**/*'
|
||||
s.dependency 'FlutterMacOS'
|
||||
|
||||
s.platform = :osx, '10.11'
|
||||
s.vendored_libraries = 'External/lib/libdcrwallet.a'
|
||||
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', "OTHER_LDFLAGS" => "-force_load $(PODS_TARGET_SRCROOT)/External/lib/libdcrwallet.a -lstdc++" }
|
||||
s.swift_version = '5.0'
|
||||
end
|
852
cw_decred/pubspec.lock
Normal file
852
cw_decred/pubspec.lock
Normal file
|
@ -0,0 +1,852 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "72.0.0"
|
||||
_macros:
|
||||
dependency: transitive
|
||||
description: dart
|
||||
source: sdk
|
||||
version: "0.3.2"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.7.0"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
asn1lib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: asn1lib
|
||||
sha256: "4bae5ae63e6d6dd17c4aac8086f3dec26c0236f6a0f03416c6c19d830c367cf5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.8"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
blockchain_utils:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: cake-update-v2
|
||||
resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57"
|
||||
url: "https://github.com/cake-tech/blockchain_utils"
|
||||
source: git
|
||||
version: "3.3.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
build:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
build_resolvers:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.13"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.3.2"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_collection
|
||||
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
built_value:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.9.5"
|
||||
cake_backup:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: main
|
||||
resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38"
|
||||
url: "https://github.com/cake-tech/cake_backup.git"
|
||||
source: git
|
||||
version: "1.0.0+1"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_util
|
||||
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.10.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
cryptography:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cryptography
|
||||
sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
cupertino_icons:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
cw_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../cw_core"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.7"
|
||||
decimal:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: decimal
|
||||
sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
encrypt:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: encrypt
|
||||
sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.3"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
ffigen:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: ffigen
|
||||
sha256: "2119b4fe3aad0db94dc9531b90283c4640a6231070e613c400b426a4da08c704"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "16.1.0"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_mobx:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_mobx
|
||||
sha256: ba5e93467866a2991259dc51cffd41ef45f695c667c2b8e7b087bf24118b50fe
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: graphs
|
||||
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
hive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hive
|
||||
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.3"
|
||||
hive_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: hive_generator
|
||||
sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.7"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.5"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
macros:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: macros
|
||||
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2-main.4"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.16+1"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
mobx:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mobx
|
||||
sha256: bf1a90e5bcfd2851fc6984e20eef69557c65d9e4d0a88f5be4cf72c9819ce6b0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
mobx_codegen:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: mobx_codegen
|
||||
sha256: "990da80722f7d7c0017dec92040b31545d625b15d40204c36a1e63d167c73cdc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nested
|
||||
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
on_chain:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: cake-update-v2
|
||||
resolved-ref: "93440dc5126369b873ca1fccc13c3c1240b1c5c2"
|
||||
url: "https://github.com/cake-tech/on_chain.git"
|
||||
source: git
|
||||
version: "3.7.0"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.15"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.9.1"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: provider
|
||||
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.2"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: quiver
|
||||
sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
rational:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: rational
|
||||
sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.3"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
socks5_proxy:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: socks5_proxy
|
||||
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
source_helper:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_helper
|
||||
sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.5"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_transform
|
||||
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timing
|
||||
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
tuple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: tuple
|
||||
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
unorm_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: unorm_dart
|
||||
sha256: "23d8bf65605401a6a32cff99435fed66ef3dab3ddcad3454059165df46496a3b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.4"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket
|
||||
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.6"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
yaml_edit:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml_edit
|
||||
sha256: fb38626579fb345ad00e674e2af3a5c9b0cc4b9bfb8fd7f7ff322c7c9e62aef5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
sdks:
|
||||
dart: ">=3.5.0 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
84
cw_decred/pubspec.yaml
Normal file
84
cw_decred/pubspec.yaml
Normal file
|
@ -0,0 +1,84 @@
|
|||
name: cw_decred
|
||||
description: A new Flutter plugin project.
|
||||
version: 0.0.1
|
||||
publish_to: none
|
||||
author: Cake Wallet
|
||||
homepage: https://cakewallet.com
|
||||
|
||||
environment:
|
||||
sdk: '>=3.2.0-0 <4.0.0'
|
||||
flutter: ">=3.19.0"
|
||||
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cw_core:
|
||||
path: ../cw_core
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
build_runner: ^2.1.11
|
||||
build_resolvers: ^2.0.9
|
||||
mobx_codegen: ^2.0.7
|
||||
hive_generator: ^2.0.1
|
||||
ffigen: ^16.1.0
|
||||
|
||||
ffigen:
|
||||
name: libdcrwallet
|
||||
description: Bindings for dcrwallet go library.
|
||||
output: "lib/api/libdcrwallet_bindings.dart"
|
||||
headers:
|
||||
entry-points:
|
||||
- "lib/api/libdcrwallet.h"
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
# This section identifies this Flutter project as a plugin project.
|
||||
# The androidPackage and pluginClass identifiers should not ordinarily
|
||||
# be modified. They are used by the tooling to maintain consistency when
|
||||
# adding or updating assets for this project.
|
||||
plugin:
|
||||
platforms:
|
||||
android:
|
||||
package: com.cakewallet.cw_decred
|
||||
pluginClass: CwDecredPlugin
|
||||
ios:
|
||||
pluginClass: CwDecredPlugin
|
||||
macos:
|
||||
pluginClass: CwDecredPlugin
|
||||
|
||||
# To add assets to your plugin package, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
#
|
||||
# For details regarding assets in packages, see
|
||||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
#
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||
|
||||
# To add custom fonts to your plugin package, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts in packages, see
|
||||
# https://flutter.dev/custom-fonts/#from-packages
|
|
@ -115,6 +115,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
@observable
|
||||
ObservableMap<CryptoCurrency, MoneroBalance> balance;
|
||||
|
||||
@override
|
||||
bool get hasRescan => true;
|
||||
|
||||
@override
|
||||
String get seed => monero_wallet.getSeed();
|
||||
String seedLegacy(String? language) => monero_wallet.getSeedLegacy(language);
|
||||
|
|
|
@ -123,6 +123,9 @@ abstract class WowneroWalletBase
|
|||
|
||||
String _password;
|
||||
|
||||
@override
|
||||
bool get hasRescan => true;
|
||||
|
||||
@override
|
||||
MoneroWalletKeys get keys => MoneroWalletKeys(
|
||||
primaryAddress: wownero_wallet.getAddress(accountIndex: 0, addressIndex: 0),
|
||||
|
|
|
@ -3,8 +3,39 @@ PODS:
|
|||
- Flutter
|
||||
- ReachabilitySwift
|
||||
- CryptoSwift (1.8.3)
|
||||
- cw_haven (0.0.1):
|
||||
- cw_haven/Boost (= 0.0.1)
|
||||
- cw_haven/Haven (= 0.0.1)
|
||||
- cw_haven/OpenSSL (= 0.0.1)
|
||||
- cw_haven/Sodium (= 0.0.1)
|
||||
- cw_shared_external
|
||||
- Flutter
|
||||
- cw_haven/Boost (0.0.1):
|
||||
- cw_shared_external
|
||||
- Flutter
|
||||
- cw_haven/Haven (0.0.1):
|
||||
- cw_shared_external
|
||||
- Flutter
|
||||
- cw_haven/OpenSSL (0.0.1):
|
||||
- cw_shared_external
|
||||
- Flutter
|
||||
- cw_haven/Sodium (0.0.1):
|
||||
- cw_shared_external
|
||||
- Flutter
|
||||
- cw_mweb (0.0.1):
|
||||
- Flutter
|
||||
- cw_decred (0.0.1):
|
||||
- cw_shared_external (0.0.1):
|
||||
- cw_shared_external/Boost (= 0.0.1)
|
||||
- cw_shared_external/OpenSSL (= 0.0.1)
|
||||
- cw_shared_external/Sodium (= 0.0.1)
|
||||
- Flutter
|
||||
- cw_shared_external/Boost (0.0.1):
|
||||
- Flutter
|
||||
- cw_shared_external/OpenSSL (0.0.1):
|
||||
- Flutter
|
||||
- cw_shared_external/Sodium (0.0.1):
|
||||
- Flutter
|
||||
- device_display_brightness (0.0.1):
|
||||
- Flutter
|
||||
- device_info_plus (0.0.1):
|
||||
|
@ -106,7 +137,10 @@ PODS:
|
|||
DEPENDENCIES:
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- CryptoSwift
|
||||
- cw_haven (from `.symlinks/plugins/cw_haven/ios`)
|
||||
- cw_mweb (from `.symlinks/plugins/cw_mweb/ios`)
|
||||
- cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`)
|
||||
- cw_decred (from `.symlinks/plugins/cw_decred/ios`)
|
||||
- device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- devicelocale (from `.symlinks/plugins/devicelocale/ios`)
|
||||
|
@ -147,8 +181,14 @@ SPEC REPOS:
|
|||
EXTERNAL SOURCES:
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
cw_haven:
|
||||
:path: ".symlinks/plugins/cw_haven/ios"
|
||||
cw_mweb:
|
||||
:path: ".symlinks/plugins/cw_mweb/ios"
|
||||
cw_shared_external:
|
||||
:path: ".symlinks/plugins/cw_shared_external/ios"
|
||||
cw_decred:
|
||||
:path: ".symlinks/plugins/cw_decred/ios"
|
||||
device_display_brightness:
|
||||
:path: ".symlinks/plugins/device_display_brightness/ios"
|
||||
device_info_plus:
|
||||
|
@ -203,7 +243,10 @@ EXTERNAL SOURCES:
|
|||
SPEC CHECKSUMS:
|
||||
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
|
||||
CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483
|
||||
cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a
|
||||
cw_mweb: 22cd01dfb8ad2d39b15332006f22046aaa8352a3
|
||||
cw_shared_external: 2972d872b8917603478117c9957dfca611845a92
|
||||
cw_decred: 9c0e1df74745b51a1289ec5e91fb9e24b68fa14a
|
||||
device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7
|
||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
||||
devicelocale: 35ba84dc7f45f527c3001535d8c8d104edd5d926
|
||||
|
|
|
@ -242,6 +242,46 @@
|
|||
<string>wownero-wallet</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>zano</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>zano</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>zano-wallet</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>zano-wallet</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>decred</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>decred</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>decred-wallet</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>decred-wallet</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
|
|
|
@ -117,7 +117,7 @@ class AddressValidator extends TextValidator {
|
|||
case CryptoCurrency.zec:
|
||||
pattern = 't1[0-9a-zA-Z]{33}|t3[0-9a-zA-Z]{33}';
|
||||
case CryptoCurrency.dcr:
|
||||
pattern = 'D[ksecS]([0-9a-zA-Z])+';
|
||||
pattern = '(D|T|S)[ksecS]([0-9a-zA-Z])+';
|
||||
case CryptoCurrency.rvn:
|
||||
pattern = '[Rr]([1-9a-km-zA-HJ-NP-Z]){33}';
|
||||
case CryptoCurrency.near:
|
||||
|
|
|
@ -17,3 +17,14 @@ class NodePathValidator extends TextValidator {
|
|||
isAutovalidate: true,
|
||||
);
|
||||
}
|
||||
|
||||
// NodeAddressValidatorDecredBlankException allows decred to send a blank ip
|
||||
// address which effectively clears the current set persistant peer.
|
||||
class NodeAddressValidatorDecredBlankException extends TextValidator {
|
||||
NodeAddressValidatorDecredBlankException()
|
||||
: super(
|
||||
errorMessage: S.current.error_text_node_address,
|
||||
isAutovalidate: true,
|
||||
pattern:
|
||||
'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\$|^[0-9a-zA-Z.\-]+\$');
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import 'package:cake_wallet/solana/solana.dart';
|
|||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cake_wallet/wownero/wownero.dart';
|
||||
import 'package:cake_wallet/zano/zano.dart';
|
||||
import 'package:cake_wallet/decred/decred.dart';
|
||||
import 'package:cake_wallet/utils/language_list.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
|
@ -50,6 +51,8 @@ class SeedValidator extends Validator<MnemonicItem> {
|
|||
return wownero!.getWowneroWordList(language);
|
||||
case WalletType.zano:
|
||||
return zano!.getWordList(language);
|
||||
case WalletType.decred:
|
||||
return decred!.getDecredWordList();
|
||||
case WalletType.none:
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -63,5 +63,9 @@ String syncStatusTitle(SyncStatus syncStatus) {
|
|||
return S.current.sync_status_attempting_scan;
|
||||
}
|
||||
|
||||
if (syncStatus is ProcessingSyncStatus) {
|
||||
return syncStatus.message ?? S.current.processing;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@ class WalletCreationService {
|
|||
case WalletType.nano:
|
||||
case WalletType.banano:
|
||||
case WalletType.zano:
|
||||
case WalletType.decred:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
114
lib/decred/cw_decred.dart
Normal file
114
lib/decred/cw_decred.dart
Normal file
|
@ -0,0 +1,114 @@
|
|||
part of 'decred.dart';
|
||||
|
||||
class CWDecred extends Decred {
|
||||
CWDecred() {}
|
||||
|
||||
@override
|
||||
WalletCredentials createDecredNewWalletCredentials(
|
||||
{required String name, WalletInfo? walletInfo}) =>
|
||||
DecredNewWalletCredentials(name: name, walletInfo: walletInfo);
|
||||
|
||||
@override
|
||||
WalletCredentials createDecredRestoreWalletFromSeedCredentials(
|
||||
{required String name, required String mnemonic, required String password}) =>
|
||||
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);
|
||||
}
|
||||
|
||||
@override
|
||||
List<TransactionPriority> getTransactionPriorities() => DecredTransactionPriority.all;
|
||||
|
||||
@override
|
||||
TransactionPriority getDecredTransactionPriorityMedium() => DecredTransactionPriority.medium;
|
||||
|
||||
@override
|
||||
TransactionPriority getDecredTransactionPrioritySlow() => DecredTransactionPriority.slow;
|
||||
|
||||
@override
|
||||
TransactionPriority deserializeDecredTransactionPriority(int raw) =>
|
||||
DecredTransactionPriority.deserialize(raw: raw);
|
||||
|
||||
@override
|
||||
Object createDecredTransactionCredentials(List<Output> outputs, TransactionPriority priority) =>
|
||||
DecredTransactionCredentials(
|
||||
outputs
|
||||
.map((out) => OutputInfo(
|
||||
fiatAmount: out.fiatAmount,
|
||||
cryptoAmount: out.cryptoAmount,
|
||||
address: out.address,
|
||||
note: out.note,
|
||||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount))
|
||||
.toList(),
|
||||
priority: priority as DecredTransactionPriority);
|
||||
|
||||
List<AddressInfo> getAddressInfos(Object wallet) {
|
||||
final decredWallet = wallet as DecredWallet;
|
||||
return decredWallet.walletAddresses.getAddressInfos();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAddress(Object wallet, String address, String label) async {
|
||||
final decredWallet = wallet as DecredWallet;
|
||||
await decredWallet.walletAddresses.updateAddress(address, label);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> generateNewAddress(Object wallet, String label) async {
|
||||
final decredWallet = wallet as DecredWallet;
|
||||
await decredWallet.walletAddresses.generateNewAddress(label);
|
||||
}
|
||||
|
||||
@override
|
||||
String formatterDecredAmountToString({required int amount}) =>
|
||||
decredAmountToString(amount: amount);
|
||||
|
||||
@override
|
||||
double formatterDecredAmountToDouble({required int amount}) =>
|
||||
decredAmountToDouble(amount: amount);
|
||||
|
||||
@override
|
||||
int formatterStringDoubleToDecredAmount(String amount) => stringDoubleToDecredAmount(amount);
|
||||
|
||||
@override
|
||||
List<Unspent> getUnspents(Object wallet) {
|
||||
final decredWallet = wallet as DecredWallet;
|
||||
return decredWallet.unspents();
|
||||
}
|
||||
|
||||
@override
|
||||
void updateUnspents(Object wallet) {
|
||||
final decredWallet = wallet as DecredWallet;
|
||||
decredWallet.unspents();
|
||||
}
|
||||
|
||||
@override
|
||||
int heightByDate(DateTime date) {
|
||||
final genesisBlocktime = DateTime.fromMillisecondsSinceEpoch(1454954400 * 1000);
|
||||
final minutesDiff = date.difference(genesisBlocktime).inMinutes;
|
||||
// Decred has five minute blocks on mainnet.
|
||||
// NOTE: This is off by about a day but is currently unused by decred as we
|
||||
// rescan from the wallet birthday.
|
||||
return minutesDiff ~/ 5;
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> getDecredWordList() => wordlist;
|
||||
|
||||
@override
|
||||
String pubkey(Object wallet) {
|
||||
final decredWallet = wallet as DecredWallet;
|
||||
return decredWallet.pubkey;
|
||||
}
|
||||
}
|
|
@ -69,6 +69,7 @@ import 'package:cake_wallet/haven/haven.dart';
|
|||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/nano/nano.dart';
|
||||
import 'package:cake_wallet/polygon/polygon.dart';
|
||||
import 'package:cake_wallet/decred/decred.dart';
|
||||
import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
|
@ -990,7 +991,8 @@ Future<void> setup({
|
|||
(Node? editingNode, bool? isSelected) => NodeCreateOrEditPage(
|
||||
nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(param2: false),
|
||||
editingNode: editingNode,
|
||||
isSelected: isSelected));
|
||||
isSelected: isSelected,
|
||||
type: getIt.get<AppStore>().wallet!.type));
|
||||
|
||||
getIt.registerFactoryParam<PowNodeCreateOrEditPage, Node?, bool?>(
|
||||
(Node? editingNode, bool? isSelected) => PowNodeCreateOrEditPage(
|
||||
|
@ -1115,6 +1117,8 @@ Future<void> setup({
|
|||
return wownero!.createWowneroWalletService(_walletInfoSource, _unspentCoinsInfoSource);
|
||||
case WalletType.zano:
|
||||
return zano!.createZanoWalletService(_walletInfoSource);
|
||||
case WalletType.decred:
|
||||
return decred!.createDecredWalletService(_walletInfoSource, _unspentCoinsInfoSource);
|
||||
case WalletType.none:
|
||||
throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -59,6 +59,7 @@ class MainActions {
|
|||
static MainActions sendAction = MainActions._(
|
||||
name: (context) => S.of(context).send,
|
||||
image: 'assets/images/upload.png',
|
||||
isEnabled: (viewModel) => viewModel.canSend,
|
||||
onTap: (BuildContext context, DashboardViewModel viewModel) async {
|
||||
Navigator.pushNamed(context, Routes.send);
|
||||
},
|
||||
|
|
|
@ -4,111 +4,62 @@ import "package:yaml/yaml.dart";
|
|||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
Future<List<Node>> loadDefaultNodes() async {
|
||||
final nodesRaw = await rootBundle.loadString('assets/node_list.yml');
|
||||
Future<List<Node>> loadDefaultNodes(WalletType type) async {
|
||||
String path;
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
path = 'assets/node_list.yml';
|
||||
break;
|
||||
case WalletType.bitcoin:
|
||||
path = 'assets/bitcoin_electrum_server_list.yml';
|
||||
break;
|
||||
case WalletType.litecoin:
|
||||
path = 'assets/litecoin_electrum_server_list.yml';
|
||||
break;
|
||||
case WalletType.haven:
|
||||
path = 'assets/haven_node_list.yml';
|
||||
break;
|
||||
case WalletType.ethereum:
|
||||
path = 'assets/ethereum_server_list.yml';
|
||||
break;
|
||||
case WalletType.nano:
|
||||
path = 'assets/nano_node_list.yml';
|
||||
break;
|
||||
case WalletType.bitcoinCash:
|
||||
path = 'assets/bitcoin_cash_electrum_server_list.yml';
|
||||
break;
|
||||
case WalletType.polygon:
|
||||
path = 'assets/polygon_node_list.yml';
|
||||
break;
|
||||
case WalletType.solana:
|
||||
path = 'assets/solana_node_list.yml';
|
||||
break;
|
||||
case WalletType.tron:
|
||||
path = 'assets/tron_node_list.yml';
|
||||
break;
|
||||
case WalletType.wownero:
|
||||
path = 'assets/wownero_node_list.yml';
|
||||
break;
|
||||
case WalletType.zano:
|
||||
path = 'assets/zano_node_list.yml';
|
||||
break;
|
||||
case WalletType.decred:
|
||||
path = 'assets/decred_node_list.yml';
|
||||
break;
|
||||
case WalletType.banano:
|
||||
case WalletType.none:
|
||||
path = '';
|
||||
break;
|
||||
}
|
||||
|
||||
final nodesRaw = await rootBundle.loadString(path);
|
||||
final loadedNodes = loadYaml(nodesRaw) as YamlList;
|
||||
final nodes = <Node>[];
|
||||
|
||||
for (final raw in loadedNodes) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
node.type = WalletType.monero;
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
Future<List<Node>> loadBitcoinElectrumServerList() async {
|
||||
final serverListRaw = await rootBundle.loadString('assets/bitcoin_electrum_server_list.yml');
|
||||
final loadedServerList = loadYaml(serverListRaw) as YamlList;
|
||||
final serverList = <Node>[];
|
||||
|
||||
for (final raw in loadedServerList) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
node.type = WalletType.bitcoin;
|
||||
serverList.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
return serverList;
|
||||
}
|
||||
|
||||
Future<List<Node>> loadLitecoinElectrumServerList() async {
|
||||
final serverListRaw = await rootBundle.loadString('assets/litecoin_electrum_server_list.yml');
|
||||
final loadedServerList = loadYaml(serverListRaw) as YamlList;
|
||||
final serverList = <Node>[];
|
||||
|
||||
for (final raw in loadedServerList) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
node.type = WalletType.litecoin;
|
||||
serverList.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
return serverList;
|
||||
}
|
||||
|
||||
Future<List<Node>> loadDefaultHavenNodes() async {
|
||||
final nodesRaw = await rootBundle.loadString('assets/haven_node_list.yml');
|
||||
final loadedNodes = loadYaml(nodesRaw) as YamlList;
|
||||
final nodes = <Node>[];
|
||||
|
||||
for (final raw in loadedNodes) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
node.type = WalletType.haven;
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
Future<List<Node>> loadDefaultEthereumNodes() async {
|
||||
final nodesRaw = await rootBundle.loadString('assets/ethereum_server_list.yml');
|
||||
final loadedNodes = loadYaml(nodesRaw) as YamlList;
|
||||
final nodes = <Node>[];
|
||||
|
||||
for (final raw in loadedNodes) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
node.type = WalletType.ethereum;
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
Future<List<Node>> loadBitcoinCashElectrumServerList() async {
|
||||
final serverListRaw = await rootBundle.loadString('assets/bitcoin_cash_electrum_server_list.yml');
|
||||
final loadedServerList = loadYaml(serverListRaw) as YamlList;
|
||||
final serverList = <Node>[];
|
||||
|
||||
for (final raw in loadedServerList) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
node.type = WalletType.bitcoinCash;
|
||||
serverList.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
return serverList;
|
||||
}
|
||||
|
||||
Future<List<Node>> loadDefaultNanoNodes() async {
|
||||
final nodesRaw = await rootBundle.loadString('assets/nano_node_list.yml');
|
||||
final loadedNodes = loadYaml(nodesRaw) as YamlList;
|
||||
final nodes = <Node>[];
|
||||
|
||||
for (final raw in loadedNodes) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
node.type = WalletType.nano;
|
||||
node.type = type;
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
|
@ -132,103 +83,19 @@ Future<List<Node>> loadDefaultNanoPowNodes() async {
|
|||
return nodes;
|
||||
}
|
||||
|
||||
Future<List<Node>> loadDefaultPolygonNodes() async {
|
||||
final nodesRaw = await rootBundle.loadString('assets/polygon_node_list.yml');
|
||||
final loadedNodes = loadYaml(nodesRaw) as YamlList;
|
||||
final nodes = <Node>[];
|
||||
|
||||
for (final raw in loadedNodes) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
|
||||
node.type = WalletType.polygon;
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
Future<List<Node>> loadDefaultSolanaNodes() async {
|
||||
final nodesRaw = await rootBundle.loadString('assets/solana_node_list.yml');
|
||||
final loadedNodes = loadYaml(nodesRaw) as YamlList;
|
||||
final nodes = <Node>[];
|
||||
|
||||
for (final raw in loadedNodes) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
|
||||
node.type = WalletType.solana;
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
Future<List<Node>> loadDefaultTronNodes() async {
|
||||
final nodesRaw = await rootBundle.loadString('assets/tron_node_list.yml');
|
||||
final loadedNodes = loadYaml(nodesRaw) as YamlList;
|
||||
final nodes = <Node>[];
|
||||
|
||||
for (final raw in loadedNodes) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
|
||||
node.type = WalletType.tron;
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
Future<List<Node>> loadDefaultWowneroNodes() async {
|
||||
final nodesRaw = await rootBundle.loadString('assets/wownero_node_list.yml');
|
||||
final loadedNodes = loadYaml(nodesRaw) as YamlList;
|
||||
final nodes = <Node>[];
|
||||
|
||||
for (final raw in loadedNodes) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
|
||||
node.type = WalletType.wownero;
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
Future<List<Node>> loadDefaultZanoNodes() async {
|
||||
final nodesRaw = await rootBundle.loadString('assets/zano_node_list.yml');
|
||||
final loadedNodes = loadYaml(nodesRaw) as YamlList;
|
||||
final nodes = <Node>[];
|
||||
|
||||
for (final raw in loadedNodes) {
|
||||
if (raw is Map) {
|
||||
final node = Node.fromMap(Map<String, Object>.from(raw));
|
||||
|
||||
node.type = WalletType.zano;
|
||||
nodes.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
Future<void> resetToDefault(Box<Node> nodeSource) async {
|
||||
final moneroNodes = await loadDefaultNodes();
|
||||
final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
|
||||
final litecoinElectrumServerList = await loadLitecoinElectrumServerList();
|
||||
final bitcoinCashElectrumServerList = await loadBitcoinCashElectrumServerList();
|
||||
final havenNodes = await loadDefaultHavenNodes();
|
||||
final ethereumNodes = await loadDefaultEthereumNodes();
|
||||
final nanoNodes = await loadDefaultNanoNodes();
|
||||
final polygonNodes = await loadDefaultPolygonNodes();
|
||||
final solanaNodes = await loadDefaultSolanaNodes();
|
||||
final tronNodes = await loadDefaultTronNodes();
|
||||
final zanoNodes = await loadDefaultZanoNodes();
|
||||
final moneroNodes = await loadDefaultNodes(WalletType.monero);
|
||||
final bitcoinElectrumServerList = await loadDefaultNodes(WalletType.bitcoin);
|
||||
final litecoinElectrumServerList = await loadDefaultNodes(WalletType.litecoin);
|
||||
final bitcoinCashElectrumServerList = await loadDefaultNodes(WalletType.bitcoinCash);
|
||||
final havenNodes = await loadDefaultNodes(WalletType.haven);
|
||||
final ethereumNodes = await loadDefaultNodes(WalletType.ethereum);
|
||||
final nanoNodes = await loadDefaultNodes(WalletType.nano);
|
||||
final polygonNodes = await loadDefaultNodes(WalletType.polygon);
|
||||
final solanaNodes = await loadDefaultNodes(WalletType.solana);
|
||||
final tronNodes = await loadDefaultNodes(WalletType.tron);
|
||||
final decredNodes = await loadDefaultNodes(WalletType.decred);
|
||||
final zanoNodes = await loadDefaultNodes(WalletType.zano);
|
||||
|
||||
final nodes = moneroNodes +
|
||||
bitcoinElectrumServerList +
|
||||
|
@ -238,7 +105,10 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
|
|||
bitcoinCashElectrumServerList +
|
||||
nanoNodes +
|
||||
polygonNodes +
|
||||
solanaNodes + tronNodes + zanoNodes;
|
||||
solanaNodes +
|
||||
tronNodes +
|
||||
zanoNodes +
|
||||
decredNodes;
|
||||
|
||||
await nodeSource.clear();
|
||||
await nodeSource.addAll(nodes);
|
||||
|
|
|
@ -10,6 +10,7 @@ class PreferencesKey {
|
|||
static const currentPolygonNodeIdKey = 'current_node_id_matic';
|
||||
static const currentNanoNodeIdKey = 'current_node_id_nano';
|
||||
static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow';
|
||||
static const currentDecredNodeIdKey = 'current_node_id_decred';
|
||||
static const currentBananoNodeIdKey = 'current_node_id_banano';
|
||||
static const currentBananoPowNodeIdKey = 'current_node_id_banano_pow';
|
||||
static const currentFiatCurrencyKey = 'current_fiat_currency';
|
||||
|
@ -48,6 +49,7 @@ class PreferencesKey {
|
|||
static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
|
||||
static const zanoTransactionPriority = 'current_fee_priority_zano';
|
||||
static const wowneroTransactionPriority = 'current_fee_priority_wownero';
|
||||
static const decredTransactionPriority = 'current_fee_priority_decred';
|
||||
static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
|
||||
static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay';
|
||||
static const silentPaymentsAlwaysScan = 'silentPaymentsAlwaysScan';
|
||||
|
@ -81,6 +83,7 @@ class PreferencesKey {
|
|||
static const lookupsENS = 'looks_up_ens';
|
||||
static const lookupsWellKnown = 'looks_up_well_known';
|
||||
static const showCameraConsent = 'show_camera_consent';
|
||||
static const showDecredInfoCard = 'show_decred_info_card';
|
||||
|
||||
static String moneroWalletUpdateV1Key(String name) =>
|
||||
'${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:cake_wallet/monero/monero.dart';
|
|||
import 'package:cake_wallet/polygon/polygon.dart';
|
||||
import 'package:cake_wallet/wownero/wownero.dart';
|
||||
import 'package:cake_wallet/zano/zano.dart';
|
||||
import 'package:cake_wallet/decred/decred.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
|
@ -35,6 +36,8 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
|
|||
return [];
|
||||
case WalletType.zano:
|
||||
return zano!.getTransactionPriorities();
|
||||
case WalletType.decred:
|
||||
return decred!.getTransactionPriorities();
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -82,6 +82,7 @@ class ProvidersHelper {
|
|||
ProviderType.moonpay,
|
||||
ProviderType.kriptonim
|
||||
];
|
||||
case WalletType.decred:
|
||||
case WalletType.none:
|
||||
case WalletType.haven:
|
||||
case WalletType.zano:
|
||||
|
@ -113,6 +114,7 @@ class ProvidersHelper {
|
|||
];
|
||||
case WalletType.monero:
|
||||
return [ProviderType.dfx];
|
||||
case WalletType.decred:
|
||||
case WalletType.nano:
|
||||
case WalletType.banano:
|
||||
case WalletType.none:
|
||||
|
|
|
@ -215,7 +215,7 @@ Future<void> initializeAppConfigs() async {
|
|||
secureStorage: secureStorage,
|
||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||
havenSeedStore: havenSeedStore,
|
||||
initialMigrationVersion: 48,
|
||||
initialMigrationVersion: 49,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ bool isBIP39Wallet(WalletType walletType) {
|
|||
case WalletType.wownero:
|
||||
case WalletType.haven:
|
||||
case WalletType.zano:
|
||||
case WalletType.decred:
|
||||
case WalletType.none:
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,9 @@ void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore
|
|||
if (wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus) {
|
||||
return;
|
||||
}
|
||||
if (wallet.type == WalletType.decred && wallet.syncStatus is ProcessingSyncStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final connectivityResult = await (Connectivity().checkConnectivity());
|
||||
|
|
|
@ -10,7 +10,6 @@ import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
|||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cake_wallet/tron/tron.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
|
|
@ -74,7 +74,8 @@ void startCurrentWalletChangeReaction(
|
|||
wallet.type == WalletType.wownero ||
|
||||
wallet.type == WalletType.bitcoin ||
|
||||
wallet.type == WalletType.litecoin ||
|
||||
wallet.type == WalletType.bitcoinCash) {
|
||||
wallet.type == WalletType.bitcoinCash ||
|
||||
wallet.type == WalletType.decred) {
|
||||
_setAutoGenerateSubaddressStatus(wallet, settingsStore);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ void startWalletSyncStatusChangeReaction(
|
|||
await updateHavenRate(fiatConversionStore);
|
||||
}
|
||||
}
|
||||
if (status is SyncingSyncStatus) {
|
||||
if (status is SyncingSyncStatus || status is ProcessingSyncStatus) {
|
||||
await WakelockPlus.enable();
|
||||
}
|
||||
if (status is SyncedSyncStatus || status is FailedSyncStatus) {
|
||||
|
|
|
@ -47,6 +47,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
|||
final tronIcon = Image.asset('assets/images/trx_icon.png', height: 24, width: 24);
|
||||
final wowneroIcon = Image.asset('assets/images/wownero_icon.png', height: 24, width: 24);
|
||||
final zanoIcon = Image.asset('assets/images/zano_icon.png', height: 24, width: 24);
|
||||
final decredIcon = Image.asset('assets/images/decred_icon.png', height: 24, width: 24);
|
||||
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
|
||||
|
||||
Image _newWalletImage(BuildContext context) => Image.asset(
|
||||
|
@ -181,6 +182,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
|||
return tronIcon;
|
||||
case WalletType.zano:
|
||||
return zanoIcon;
|
||||
case WalletType.decred:
|
||||
return decredIcon;
|
||||
default:
|
||||
return nonWalletTypeIcon;
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'package:cake_wallet/generated/i18n.dart';
|
|||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_row_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/info_card.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
|
||||
|
@ -280,81 +281,50 @@ class CryptoBalanceWidget extends StatelessWidget {
|
|||
SizedBox(height: 16),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
|
||||
child: DashBoardRoundedCardWidget(
|
||||
marginV: 0,
|
||||
marginH: 0,
|
||||
customBorder: 30,
|
||||
child: InfoCard(
|
||||
title: S.of(context).litecoin_mweb,
|
||||
subTitle: S.of(context).litecoin_mweb_description,
|
||||
hint: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => launchUrl(
|
||||
Uri.parse("https://docs.cakewallet.com/cryptos/litecoin/#mweb"),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
child: Text(
|
||||
S.of(context).learn_more,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color:
|
||||
Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
|
||||
height: 1,
|
||||
),
|
||||
softWrap: true,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () => _dismissMweb(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
child: Text(
|
||||
S.of(context).litecoin_mweb_dismiss,
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: () => _enableMweb(context),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black,
|
||||
),
|
||||
child: Text(
|
||||
S.of(context).enable,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
icon: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
description: S.of(context).litecoin_mweb_description,
|
||||
leftButtonTitle: S.of(context).litecoin_mweb_dismiss,
|
||||
rightButtonTitle: S.of(context).enable,
|
||||
image: 'assets/images/mweb_logo.png',
|
||||
leftButtonAction: () => _dismissMweb(context),
|
||||
rightButtonAction: () => _enableMweb(context),
|
||||
hintWidget: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => launchUrl(
|
||||
Uri.parse("https://docs.cakewallet.com/cryptos/litecoin/#mweb"),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
child: ImageIcon(
|
||||
AssetImage('assets/images/mweb_logo.png'),
|
||||
color: Color.fromARGB(255, 11, 70, 129),
|
||||
size: 40,
|
||||
child: Text(
|
||||
S.of(context).learn_more,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
|
||||
height: 1,
|
||||
),
|
||||
softWrap: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (dashboardViewModel.showDecredInfoCard) ...[
|
||||
SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
|
||||
child: InfoCard(
|
||||
title: S.of(context).decred_info_title,
|
||||
description: S.of(context).decred_info_card_details,
|
||||
image: 'assets/images/dcr_icon.png',
|
||||
leftButtonTitle: S.of(context).litecoin_mweb_dismiss,
|
||||
rightButtonTitle: S.of(context).learn_more,
|
||||
leftButtonAction: () => dashboardViewModel.dismissDecredInfoCard(),
|
||||
rightButtonAction: () => launchUrl(Uri.parse("https://docs.cakewallet.com/cryptos/decred/#spv-sync")),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}),
|
||||
|
|
|
@ -75,8 +75,10 @@ class NavigationDock extends StatelessWidget {
|
|||
.labelTextColor,
|
||||
),
|
||||
title: action.name(context),
|
||||
onClick: () async =>
|
||||
await action.onTap(context, dashboardViewModel),
|
||||
onClick: (action.isEnabled?.call(dashboardViewModel) ?? true)
|
||||
? () async =>
|
||||
await action.onTap(context, dashboardViewModel)
|
||||
: null,
|
||||
textColor: action.isEnabled?.call(dashboardViewModel) ?? true
|
||||
? null
|
||||
: Theme.of(context)
|
||||
|
|
88
lib/src/screens/dashboard/widgets/info_card.dart
Normal file
88
lib/src/screens/dashboard/widgets/info_card.dart
Normal file
|
@ -0,0 +1,88 @@
|
|||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class InfoCard extends StatelessWidget {
|
||||
final String leftButtonTitle;
|
||||
final String rightButtonTitle;
|
||||
final String title;
|
||||
final String description;
|
||||
final String image;
|
||||
|
||||
final Function() leftButtonAction;
|
||||
final Function() rightButtonAction;
|
||||
|
||||
final Widget? hintWidget;
|
||||
|
||||
const InfoCard({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.description,
|
||||
required this.leftButtonTitle,
|
||||
required this.rightButtonTitle,
|
||||
required this.leftButtonAction,
|
||||
required this.rightButtonAction,
|
||||
required this.image,
|
||||
this.hintWidget,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DashBoardRoundedCardWidget(
|
||||
marginH: 0,
|
||||
marginV: 0,
|
||||
customBorder: 30,
|
||||
title: title,
|
||||
subTitle: description,
|
||||
hint: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (hintWidget != null) hintWidget!,
|
||||
if (hintWidget != null) SizedBox(height: 8),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: leftButtonAction,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
child: Text(
|
||||
leftButtonTitle,
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: rightButtonAction,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: Colors.white,
|
||||
foregroundColor: Colors.black,
|
||||
),
|
||||
child: Text(
|
||||
rightButtonTitle,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () => {},
|
||||
icon: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: image,
|
||||
height: 40,
|
||||
width: 40,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -38,7 +38,8 @@ class MenuWidgetState extends State<MenuWidget> {
|
|||
this.solanaIcon = Image.asset('assets/images/sol_icon.png'),
|
||||
this.tronIcon = Image.asset('assets/images/trx_icon.png'),
|
||||
this.wowneroIcon = Image.asset('assets/images/wownero_icon.png'),
|
||||
this.zanoIcon = Image.asset('assets/images/zano_icon.png');
|
||||
this.zanoIcon = Image.asset('assets/images/zano_icon.png'),
|
||||
this.decredIcon = Image.asset('assets/images/decred_menu.png');
|
||||
|
||||
final largeScreen = 731;
|
||||
|
||||
|
@ -64,6 +65,7 @@ class MenuWidgetState extends State<MenuWidget> {
|
|||
Image tronIcon;
|
||||
Image wowneroIcon;
|
||||
Image zanoIcon;
|
||||
Image decredIcon;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -250,6 +252,8 @@ class MenuWidgetState extends State<MenuWidget> {
|
|||
return wowneroIcon;
|
||||
case WalletType.zano:
|
||||
return zanoIcon;
|
||||
case WalletType.decred:
|
||||
return decredIcon;
|
||||
default:
|
||||
throw Exception('No icon for ${type.toString()}');
|
||||
}
|
||||
|
|
|
@ -22,8 +22,8 @@ class SyncIndicator extends StatelessWidget {
|
|||
builder: (_) {
|
||||
final syncIndicatorWidth = 237.0;
|
||||
final status = dashboardViewModel.status;
|
||||
final statusText = status != null ? syncStatusTitle(status) : '';
|
||||
final progress = status != null ? status.progress() : 0.0;
|
||||
final statusText = syncStatusTitle(status);
|
||||
final progress = status.progress();
|
||||
final indicatorOffset = progress * syncIndicatorWidth;
|
||||
final indicatorWidth = progress < 1
|
||||
? indicatorOffset > 0 ? indicatorOffset : 0.0
|
||||
|
|
|
@ -274,7 +274,8 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
|
|||
],
|
||||
);
|
||||
}),
|
||||
if (widget.privacySettingsViewModel.type == WalletType.bitcoin)
|
||||
if (widget.privacySettingsViewModel.type == WalletType.bitcoin ||
|
||||
widget.privacySettingsViewModel.type == WalletType.decred)
|
||||
Builder(builder: (_) {
|
||||
final val = testnetValue ?? false;
|
||||
return SettingsSwitcherCell(
|
||||
|
@ -301,7 +302,9 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
|
|||
|
||||
widget.nodeViewModel.save();
|
||||
}
|
||||
if (testnetValue == true) {
|
||||
if (testnetValue == true &&
|
||||
widget.privacySettingsViewModel.type ==
|
||||
WalletType.bitcoin) {
|
||||
// TODO: add type (mainnet/testnet) to Node class so when switching wallets the node can be switched to a matching type
|
||||
// Currently this is so you can create a working testnet wallet but you need to keep switching back the node if you use multiple wallets at once
|
||||
widget.nodeViewModel.address = publicBitcoinTestnetElectrumAddress;
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
|||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
@ -15,7 +16,7 @@ import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
|||
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
|
||||
|
||||
class NodeCreateOrEditPage extends BasePage {
|
||||
NodeCreateOrEditPage({required this.nodeCreateOrEditViewModel,this.editingNode, this.isSelected})
|
||||
NodeCreateOrEditPage({required this.nodeCreateOrEditViewModel,this.editingNode, this.isSelected, this.type})
|
||||
: _formKey = GlobalKey<FormState>(),
|
||||
_addressController = TextEditingController(),
|
||||
_pathController = TextEditingController(),
|
||||
|
@ -86,6 +87,7 @@ class NodeCreateOrEditPage extends BasePage {
|
|||
final NodeCreateOrEditViewModel nodeCreateOrEditViewModel;
|
||||
final Node? editingNode;
|
||||
final bool? isSelected;
|
||||
final WalletType? type;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
|
@ -130,6 +132,7 @@ class NodeCreateOrEditPage extends BasePage {
|
|||
formKey: _formKey,
|
||||
nodeViewModel: nodeCreateOrEditViewModel,
|
||||
editingNode: editingNode,
|
||||
type: type,
|
||||
),
|
||||
bottomSectionPadding: EdgeInsets.only(bottom: 24),
|
||||
bottomSection: Observer(
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
|||
import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
@ -15,6 +16,7 @@ class NodeForm extends StatelessWidget {
|
|||
required this.nodeViewModel,
|
||||
required this.formKey,
|
||||
this.editingNode,
|
||||
this.type,
|
||||
}) : _addressController = TextEditingController(text: editingNode?.uri.host.toString()),
|
||||
_pathController = TextEditingController(text: editingNode?.path.toString()),
|
||||
_portController = TextEditingController(text: editingNode?.uri.port.toString()),
|
||||
|
@ -76,6 +78,7 @@ class NodeForm extends StatelessWidget {
|
|||
final NodeCreateOrEditViewModel nodeViewModel;
|
||||
final GlobalKey<FormState> formKey;
|
||||
final Node? editingNode;
|
||||
final WalletType? type;
|
||||
|
||||
final TextEditingController _addressController;
|
||||
final TextEditingController _pathController;
|
||||
|
@ -96,7 +99,7 @@ class NodeForm extends StatelessWidget {
|
|||
child: BaseTextFormField(
|
||||
controller: _addressController,
|
||||
hintText: S.of(context).node_address,
|
||||
validator: NodeAddressValidator(),
|
||||
validator: type == WalletType.decred ? NodeAddressValidatorDecredBlankException() : NodeAddressValidator(),
|
||||
),
|
||||
)
|
||||
],
|
||||
|
|
|
@ -9,41 +9,47 @@ import 'package:cake_wallet/src/screens/base_page.dart';
|
|||
import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
class RescanPage extends BasePage {
|
||||
RescanPage(this._rescanViewModel)
|
||||
: _blockchainHeightWidgetKey = GlobalKey<BlockchainHeightState>();
|
||||
|
||||
@override
|
||||
String get title =>
|
||||
_rescanViewModel.isSilentPaymentsScan ? S.current.silent_payments_scanning : S.current.rescan;
|
||||
String get title => _rescanViewModel.isSilentPaymentsScan
|
||||
? S.current.silent_payments_scanning
|
||||
: S.current.rescan;
|
||||
final GlobalKey<BlockchainHeightState> _blockchainHeightWidgetKey;
|
||||
final RescanViewModel _rescanViewModel;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => FocusScope.of(context).unfocus(),
|
||||
child: Padding(
|
||||
Widget child;
|
||||
if (_rescanViewModel.wallet.type != WalletType.decred) {
|
||||
child = Padding(
|
||||
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||
child:
|
||||
Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||
Observer(
|
||||
builder: (_) => BlockchainHeightWidget(
|
||||
key: _blockchainHeightWidgetKey,
|
||||
onHeightOrDateEntered: (value) => _rescanViewModel.isButtonEnabled = value,
|
||||
onHeightOrDateEntered: (value) =>
|
||||
_rescanViewModel.isButtonEnabled = value,
|
||||
isSilentPaymentsScan: _rescanViewModel.isSilentPaymentsScan,
|
||||
isMwebScan: _rescanViewModel.isMwebScan,
|
||||
doSingleScan: _rescanViewModel.doSingleScan,
|
||||
hasDatePicker: !_rescanViewModel.isMwebScan,// disable date picker for mweb for now
|
||||
toggleSingleScan: () =>
|
||||
_rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan,
|
||||
hasDatePicker: !_rescanViewModel
|
||||
.isMwebScan, // disable date picker for mweb for now
|
||||
toggleSingleScan: () => _rescanViewModel.doSingleScan =
|
||||
!_rescanViewModel.doSingleScan,
|
||||
walletType: _rescanViewModel.wallet.type,
|
||||
bitcoinMempoolAPIEnabled: _rescanViewModel.isBitcoinMempoolAPIEnabled,
|
||||
bitcoinMempoolAPIEnabled:
|
||||
_rescanViewModel.isBitcoinMempoolAPIEnabled,
|
||||
)),
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
isLoading: _rescanViewModel.state == RescanWalletState.rescaning,
|
||||
isLoading:
|
||||
_rescanViewModel.state == RescanWalletState.rescaning,
|
||||
text: S.of(context).rescan,
|
||||
onPressed: () async {
|
||||
if (_rescanViewModel.isSilentPaymentsScan) {
|
||||
|
@ -51,7 +57,8 @@ class RescanPage extends BasePage {
|
|||
}
|
||||
|
||||
_rescanViewModel.rescanCurrentWallet(
|
||||
restoreHeight: _blockchainHeightWidgetKey.currentState!.height);
|
||||
restoreHeight:
|
||||
_blockchainHeightWidgetKey.currentState!.height);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
|
@ -60,7 +67,35 @@ class RescanPage extends BasePage {
|
|||
isDisabled: !_rescanViewModel.isButtonEnabled,
|
||||
))
|
||||
]),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
child = Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Spacer(),
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
isLoading: _rescanViewModel.state ==
|
||||
RescanWalletState.rescaning,
|
||||
text: S.of(context).rescan,
|
||||
onPressed: () async {
|
||||
await _rescanViewModel.rescanCurrentWallet(
|
||||
restoreHeight: 0);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
color: Theme.of(context).primaryColor,
|
||||
textColor: Colors.white,
|
||||
))
|
||||
]),
|
||||
));
|
||||
}
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => FocusScope.of(context).unfocus(),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -70,14 +105,16 @@ class RescanPage extends BasePage {
|
|||
Navigator.of(context).pop();
|
||||
|
||||
final needsToSwitch =
|
||||
await bitcoin!.getNodeIsElectrsSPEnabled(_rescanViewModel.wallet) == false;
|
||||
await bitcoin!.getNodeIsElectrsSPEnabled(_rescanViewModel.wallet) ==
|
||||
false;
|
||||
|
||||
if (needsToSwitch) {
|
||||
return showPopUp<void>(
|
||||
context: navigatorKey.currentState!.context,
|
||||
builder: (BuildContext _dialogContext) => AlertWithTwoActions(
|
||||
alertTitle: S.of(_dialogContext).change_current_node_title,
|
||||
alertContent: S.of(_dialogContext).confirm_silent_payments_switch_node,
|
||||
alertContent:
|
||||
S.of(_dialogContext).confirm_silent_payments_switch_node,
|
||||
rightButtonText: S.of(_dialogContext).confirm,
|
||||
leftButtonText: S.of(_dialogContext).cancel,
|
||||
actionRightButton: () async {
|
||||
|
|
|
@ -15,6 +15,7 @@ class WalletRestoreFromKeysForm extends StatefulWidget {
|
|||
WalletRestoreFromKeysForm({
|
||||
required this.walletRestoreViewModel,
|
||||
required this.onPrivateKeyChange,
|
||||
required this.onViewKeyEntered,
|
||||
required this.displayPrivateKeyField,
|
||||
required this.onHeightOrDateEntered,
|
||||
required this.displayWalletPassword,
|
||||
|
@ -27,6 +28,7 @@ class WalletRestoreFromKeysForm 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 RestoredWallet? restoredWallet;
|
||||
|
@ -97,6 +99,10 @@ class WalletRestoreFromKeysFormState extends State<WalletRestoreFromKeysForm> {
|
|||
blockchainHeightKey.currentState?.restoreHeightController.text = widget.restoredWallet!.height.toString();
|
||||
}
|
||||
});
|
||||
|
||||
viewKeyController.addListener(() {
|
||||
widget.onViewKeyEntered?.call(viewKeyController.text.isNotEmpty);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -187,6 +193,19 @@ class WalletRestoreFromKeysFormState extends State<WalletRestoreFromKeysForm> {
|
|||
}
|
||||
|
||||
Widget _restoreFromKeysFormFields() {
|
||||
// Decred can only restore a view only wallet with an account pubkey. Other
|
||||
// fields are not used.
|
||||
if (widget.walletRestoreViewModel.type == WalletType.decred) {
|
||||
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"
|
||||
|
|
|
@ -168,14 +168,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -467,6 +469,11 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
|
|||
widget.walletRestoreViewModel.isButtonEnabled = _isValidSeedKey();
|
||||
}
|
||||
},
|
||||
onViewKeyEntered: (bool entered) {
|
||||
if (walletRestoreViewModel.type == WalletType.decred) {
|
||||
walletRestoreViewModel.isButtonEnabled = entered;
|
||||
}
|
||||
},
|
||||
onPasswordChange: (String password) =>
|
||||
widget.walletRestoreViewModel.walletPassword = password,
|
||||
onRepeatedPasswordChange: (String repeatedPassword) =>
|
||||
|
@ -538,13 +545,19 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
|
|||
|
||||
// bip39:
|
||||
final validBip39SeedLengths = [12, 18, 24];
|
||||
final nonBip39WalletTypes = [WalletType.monero, WalletType.wownero, WalletType.haven];
|
||||
final nonBip39WalletTypes = [WalletType.monero, WalletType.wownero, WalletType.haven, WalletType.decred];
|
||||
// if it's a bip39 wallet and the length is not valid return false
|
||||
if (!nonBip39WalletTypes.contains(walletRestoreViewModel.type) &&
|
||||
!(validBip39SeedLengths.contains(seedWords.length))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((walletRestoreViewModel.type == WalletType.decred) &&
|
||||
seedWords.length !=
|
||||
WalletRestoreViewModelBase.decredSeedMnemonicLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final words =
|
||||
walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.words.toSet();
|
||||
return seedWords.toSet().difference(words).toSet().isEmpty;
|
||||
|
|
|
@ -72,6 +72,10 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
|
|||
late bool showLegacySeedTab;
|
||||
late bool isLegacySeedOnly;
|
||||
|
||||
bool get _hasSeeds =>
|
||||
widget.walletKeysViewModel.legacySeedSplit.length > 10 ||
|
||||
widget.walletKeysViewModel.seedSplit.length > 10;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
@ -160,11 +164,10 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
|
|||
Widget _buildSeedTab(BuildContext context, bool isLegacySeed) {
|
||||
return Column(
|
||||
children: [
|
||||
if (isLegacySeedOnly || isLegacySeed)
|
||||
...[
|
||||
_buildHeightBox(),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
if (isLegacySeedOnly || isLegacySeed) ...[
|
||||
_buildHeightBox(),
|
||||
const SizedBox(height: 20),
|
||||
],
|
||||
(_buildPassphraseBox() ?? Container()),
|
||||
if (widget.walletKeysViewModel.passphrase.isNotEmpty) const SizedBox(height: 20),
|
||||
Expanded(
|
||||
|
@ -175,13 +178,14 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
|
|||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
_buildBottomActionPanel(
|
||||
titleForClipboard: S.of(context).wallet_seed.toLowerCase(),
|
||||
dataToCopy: isLegacySeed
|
||||
? widget.walletKeysViewModel.legacySeed
|
||||
: widget.walletKeysViewModel.seed,
|
||||
onShowQR: () async => _showQR(context),
|
||||
),
|
||||
if (_hasSeeds)
|
||||
_buildBottomActionPanel(
|
||||
titleForClipboard: S.of(context).wallet_seed.toLowerCase(),
|
||||
dataToCopy: isLegacySeed
|
||||
? widget.walletKeysViewModel.legacySeed
|
||||
: widget.walletKeysViewModel.seed,
|
||||
onShowQR: () async => _showQR(context),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -326,7 +330,7 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Widget _buildBottomActionPanel({
|
||||
required String titleForClipboard,
|
||||
required String dataToCopy,
|
||||
|
|
|
@ -122,6 +122,7 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
|
||||
final tBitcoinIcon = Image.asset('assets/images/tbtc.png', height: 24, width: 24);
|
||||
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
|
||||
final decredIcon = Image.asset('assets/images/decred_icon.png', height: 24, width: 24);
|
||||
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
|
||||
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
|
||||
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
|
||||
|
@ -136,6 +137,8 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
final double tileHeight = 60;
|
||||
Flushbar<void>? _progressBar;
|
||||
|
||||
bool _loadingWallet = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final newWalletImage = Image.asset('assets/images/new_wallet.png',
|
||||
|
@ -480,6 +483,10 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
}
|
||||
|
||||
Future<void> _loadWallet(WalletListItem wallet) async {
|
||||
if (_loadingWallet) return;
|
||||
|
||||
_loadingWallet = true;
|
||||
|
||||
if (SettingsStoreBase.walletPasswordDirectInput) {
|
||||
Navigator.of(context).pushNamed(Routes.walletUnlockLoadable,
|
||||
arguments: WalletUnlockArguments(
|
||||
|
@ -492,13 +499,17 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
},
|
||||
walletName: wallet.name,
|
||||
walletType: wallet.type));
|
||||
_loadingWallet = false;
|
||||
return;
|
||||
}
|
||||
|
||||
await widget.authService.authenticateAction(
|
||||
context,
|
||||
onAuthSuccess: (isAuthenticatedSuccessfully) async {
|
||||
if (!isAuthenticatedSuccessfully) return;
|
||||
if (!isAuthenticatedSuccessfully) {
|
||||
_loadingWallet = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final requireHardwareWalletConnection = widget.walletListViewModel
|
||||
|
@ -555,6 +566,8 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
.of(context)
|
||||
.wallet_list_failed_to_load(wallet.name, e.toString()));
|
||||
}
|
||||
} finally {
|
||||
_loadingWallet = false;
|
||||
}
|
||||
},
|
||||
conditionToDetermineIfToUse2FA:
|
||||
|
|
|
@ -9,6 +9,9 @@ import 'package:intl/intl.dart';
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/decred/decred.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
|
||||
class BlockchainHeightWidget extends StatefulWidget {
|
||||
BlockchainHeightWidget({
|
||||
|
@ -183,7 +186,9 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
|||
bitcoinMempoolAPIEnabled: await widget.bitcoinMempoolAPIEnabled,
|
||||
);
|
||||
} else {
|
||||
if (widget.walletType == WalletType.monero) {
|
||||
if (widget.walletType == WalletType.decred) {
|
||||
height = decred!.heightByDate(date);
|
||||
} else if (widget.walletType == WalletType.monero) {
|
||||
height = monero!.getHeightByDate(date: date);
|
||||
} else {
|
||||
assert(widget.walletType == WalletType.wownero,
|
||||
|
|
|
@ -65,6 +65,9 @@ class SeedWidgetState extends State<SeedWidget> {
|
|||
});
|
||||
widget.onSeedChange?.call(text);
|
||||
});
|
||||
Future.delayed(Duration.zero, () {
|
||||
widget.onSeedChange?.call(text);
|
||||
});
|
||||
}
|
||||
|
||||
void changeSeedLanguage(String language) {
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/decred/decred.dart';
|
||||
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||
import 'package:cake_wallet/core/secure_storage.dart';
|
||||
import 'package:cake_wallet/di.dart';
|
||||
|
@ -139,6 +140,7 @@ abstract class SettingsStoreBase with Store {
|
|||
TransactionPriority? initialPolygonTransactionPriority,
|
||||
TransactionPriority? initialBitcoinCashTransactionPriority,
|
||||
TransactionPriority? initialZanoTransactionPriority,
|
||||
TransactionPriority? initialDecredTransactionPriority,
|
||||
Country? initialCakePayCountry})
|
||||
: nodes = ObservableMap<WalletType, Node>.of(nodes),
|
||||
powNodes = ObservableMap<WalletType, Node>.of(powNodes),
|
||||
|
@ -225,6 +227,9 @@ abstract class SettingsStoreBase with Store {
|
|||
if (initialZanoTransactionPriority != null) {
|
||||
priority[WalletType.zano] = initialZanoTransactionPriority;
|
||||
}
|
||||
if (initialDecredTransactionPriority != null) {
|
||||
priority[WalletType.decred] = initialDecredTransactionPriority;
|
||||
}
|
||||
|
||||
if (initialCakePayCountry != null) {
|
||||
selectedCakePayCountry = initialCakePayCountry;
|
||||
|
@ -280,6 +285,9 @@ abstract class SettingsStoreBase with Store {
|
|||
case WalletType.zano:
|
||||
key = PreferencesKey.zanoTransactionPriority;
|
||||
break;
|
||||
case WalletType.decred:
|
||||
key = PreferencesKey.decredTransactionPriority;
|
||||
break;
|
||||
default:
|
||||
key = null;
|
||||
}
|
||||
|
@ -898,6 +906,7 @@ abstract class SettingsStoreBase with Store {
|
|||
TransactionPriority? bitcoinCashTransactionPriority;
|
||||
TransactionPriority? wowneroTransactionPriority;
|
||||
TransactionPriority? zanoTransactionPriority;
|
||||
TransactionPriority? decredTransactionPriority;
|
||||
|
||||
if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) {
|
||||
havenTransactionPriority = monero?.deserializeMoneroTransactionPriority(
|
||||
|
@ -927,6 +936,10 @@ abstract class SettingsStoreBase with Store {
|
|||
zanoTransactionPriority = monero?.deserializeMoneroTransactionPriority(
|
||||
raw: sharedPreferences.getInt(PreferencesKey.zanoTransactionPriority)!);
|
||||
}
|
||||
if (sharedPreferences.getInt(PreferencesKey.decredTransactionPriority) != null) {
|
||||
decredTransactionPriority = decred?.deserializeDecredTransactionPriority(
|
||||
sharedPreferences.getInt(PreferencesKey.decredTransactionPriority)!);
|
||||
}
|
||||
|
||||
moneroTransactionPriority ??= monero?.getDefaultTransactionPriority();
|
||||
bitcoinTransactionPriority ??= bitcoin?.getMediumTransactionPriority();
|
||||
|
@ -935,6 +948,7 @@ abstract class SettingsStoreBase with Store {
|
|||
ethereumTransactionPriority ??= ethereum?.getDefaultTransactionPriority();
|
||||
bitcoinCashTransactionPriority ??= bitcoinCash?.getDefaultTransactionPriority();
|
||||
wowneroTransactionPriority ??= wownero?.getDefaultTransactionPriority();
|
||||
decredTransactionPriority ??= decred?.getDecredTransactionPriorityMedium();
|
||||
polygonTransactionPriority ??= polygon?.getDefaultTransactionPriority();
|
||||
zanoTransactionPriority ??= zano?.getDefaultTransactionPriority();
|
||||
|
||||
|
@ -1038,7 +1052,7 @@ abstract class SettingsStoreBase with Store {
|
|||
final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
|
||||
final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
|
||||
final zanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey);
|
||||
|
||||
final decredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey);
|
||||
final moneroNode = nodeSource.get(nodeId);
|
||||
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
|
||||
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
|
||||
|
@ -1047,6 +1061,7 @@ abstract class SettingsStoreBase with Store {
|
|||
final polygonNode = nodeSource.get(polygonNodeId);
|
||||
final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId);
|
||||
final nanoNode = nodeSource.get(nanoNodeId);
|
||||
final decredNode = nodeSource.get(decredNodeId);
|
||||
final nanoPowNode = powNodeSource.get(nanoPowNodeId);
|
||||
final solanaNode = nodeSource.get(solanaNodeId);
|
||||
final tronNode = nodeSource.get(tronNodeId);
|
||||
|
@ -1137,6 +1152,10 @@ abstract class SettingsStoreBase with Store {
|
|||
nodes[WalletType.zano] = zanoNode;
|
||||
}
|
||||
|
||||
if (decredNode != null) {
|
||||
nodes[WalletType.decred] = decredNode;
|
||||
}
|
||||
|
||||
final savedSyncMode = SyncMode.all.firstWhere((element) {
|
||||
return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 0);
|
||||
});
|
||||
|
@ -1304,6 +1323,7 @@ abstract class SettingsStoreBase with Store {
|
|||
initialHavenTransactionPriority: havenTransactionPriority,
|
||||
initialLitecoinTransactionPriority: litecoinTransactionPriority,
|
||||
initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority,
|
||||
initialDecredTransactionPriority: decredTransactionPriority,
|
||||
initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet,
|
||||
initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact,
|
||||
initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact,
|
||||
|
@ -1378,6 +1398,11 @@ abstract class SettingsStoreBase with Store {
|
|||
priority[WalletType.zano] = zano!.deserializeMoneroTransactionPriority(
|
||||
raw: sharedPreferences.getInt(PreferencesKey.zanoTransactionPriority)!);
|
||||
}
|
||||
if (decred != null &&
|
||||
sharedPreferences.getInt(PreferencesKey.decredTransactionPriority) != null) {
|
||||
priority[WalletType.decred] = decred!.deserializeDecredTransactionPriority(
|
||||
sharedPreferences.getInt(PreferencesKey.decredTransactionPriority)!);
|
||||
}
|
||||
|
||||
final generateSubaddresses =
|
||||
sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
|
||||
|
@ -1489,6 +1514,7 @@ abstract class SettingsStoreBase with Store {
|
|||
final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
|
||||
final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
|
||||
final zanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey);
|
||||
final decredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey);
|
||||
final moneroNode = nodeSource.get(nodeId);
|
||||
final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
|
||||
final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
|
||||
|
@ -1496,11 +1522,12 @@ abstract class SettingsStoreBase with Store {
|
|||
final ethereumNode = nodeSource.get(ethereumNodeId);
|
||||
final polygonNode = nodeSource.get(polygonNodeId);
|
||||
final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId);
|
||||
final nanoNode = nodeSource.get(nanoNodeId);
|
||||
final nanoNode = nodeSource.get(nanoNodeId);
|
||||
final solanaNode = nodeSource.get(solanaNodeId);
|
||||
final tronNode = nodeSource.get(tronNodeId);
|
||||
final wowneroNode = nodeSource.get(wowneroNodeId);
|
||||
final zanoNode = nodeSource.get(zanoNodeId);
|
||||
final decredNode = nodeSource.get(decredNodeId);
|
||||
|
||||
if (moneroNode != null) {
|
||||
nodes[WalletType.monero] = moneroNode;
|
||||
|
@ -1551,6 +1578,10 @@ abstract class SettingsStoreBase with Store {
|
|||
nodes[WalletType.zano] = zanoNode;
|
||||
}
|
||||
|
||||
if (decredNode != null) {
|
||||
nodes[WalletType.decred] = decredNode;
|
||||
}
|
||||
|
||||
// MIGRATED:
|
||||
|
||||
useTOTP2FA = await SecureKey.getBool(
|
||||
|
@ -1687,6 +1718,9 @@ abstract class SettingsStoreBase with Store {
|
|||
case WalletType.wownero:
|
||||
await _sharedPreferences.setInt(PreferencesKey.currentWowneroNodeIdKey, node.key as int);
|
||||
break;
|
||||
case WalletType.decred:
|
||||
await _sharedPreferences.setInt(PreferencesKey.currentDecredNodeIdKey, node.key as int);
|
||||
break;
|
||||
case WalletType.zano:
|
||||
await _sharedPreferences.setInt(PreferencesKey.currentZanoNodeIdKey, node.key as int);
|
||||
default:
|
||||
|
|
|
@ -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));
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue