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>

Omar Hatem committed Mar 21, 2025 at 04:18 UTC 0ba54fa60283b34f03ddac061911c84bd342ef89
175 files changed +7143 -1113
.github/workflows/no_print_in_dart.yaml
+1 -3
@@ -1,8 +1,6 @@
1 name: No print statements in dart files
2
3 -on:
4 - pull_request:
5 - branches: [main]
3 +on: [pull_request]
4
5 jobs:
6 PR_test_build:
.github/workflows/pr_test_build_android.yml
+10 -2
@@ -47,6 +47,7 @@ jobs:
47 echo "message<<EOF" >> $GITHUB_ENV
48 echo "$FULL_MESSAGE" >> $GITHUB_ENV
49 echo "EOF" >> $GITHUB_ENV
50 +
51 - name: Add secrets
52 run: |
53 touch lib/.secrets.g.dart
@@ -243,6 +244,13 @@ jobs:
244 ./build_mwebd.sh --dont-install
245 popd
246
247 + - name: Build Decred
248 + run: |
249 + set -x -e
250 + pushd scripts/android
251 + ./build_decred.sh
252 + popd
253 +
254 - name: Build generated code
255 run: |
256 ./model_generator.sh async
@@ -281,7 +289,7 @@ jobs:
289 set -x
290 apk_file=$(ls build/app/outputs/flutter-apk/test-apk/${BRANCH_NAME}.apk || exit 1)
291 echo "APK_FILE=$apk_file" >> $GITHUB_ENV
284 -
292 +
293 - name: Upload artifact to slack
294 if: ${{ !contains(env.message, 'skip slack') }}
295 continue-on-error: true
@@ -294,7 +302,7 @@ jobs:
302
303 - name: cleanup
304 run: rm -rf build/app/outputs/flutter-apk/test-apk/
297 -
305 +
306 - name: Upload Artifact to github
307 uses: actions/upload-artifact@v4
308 with:
.github/workflows/pr_test_build_linux.yml
+1
@@ -32,6 +32,7 @@ jobs:
32 - uses: actions/checkout@v4
33 with:
34 ref: ${{ github.event.pull_request.head.sha }}
35 + repository: ${{ github.event.pull_request.head.repo.full_name }}
36 - name: configure git
37 run: |
38 git config --global --add safe.directory '*'
.gitignore
+3
@@ -9,6 +9,7 @@
9 .history
10 .svn/
11 .fvm/
12 +.fvmrc
13
14 # IntelliJ related
15 *.iml
@@ -138,6 +139,7 @@ lib/solana/solana.dart
139 lib/tron/tron.dart
140 lib/wownero/wownero.dart
141 lib/zano/zano.dart
142 +lib/decred/decred.dart
143
144 ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
145 ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
@@ -171,6 +173,7 @@ macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
173 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
174 macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
175 macos/Runner/Configs/AppInfo.xcconfig
176 +macos/Runner.xcodeproj/project.pbxproj
177
178
179 integration_test/playground.dart
android/app/src/main/AndroidManifestBase.xml
+3
@@ -92,6 +92,9 @@
92 <data android:scheme="zano" />
93 <data android:scheme="zano-wallet" />
94 <data android:scheme="zano_wallet" />
95 + <data android:scheme="decred" />
96 + <data android:scheme="decred-wallet" />
97 + <data android:scheme="decred_wallet" />
98 </intent-filter>
99 <!-- nano-gpt link scheme -->
100 <intent-filter android:autoVerify="true">
assets/decred_node_list.yml new
+6
@@ -0,0 +1,6 @@
1 +-
2 + uri: default-spv-nodes
3 + is_default: true
4 +-
5 + uri: dcrd.sethforprivacy.com:9108
6 + useSSL: true
\ No newline at end of file
assets/images/2.0x/decred.png
Binary files /dev/null and b/assets/images/2.0x/decred.png differ
assets/images/2.0x/decred_menu.png
Binary files /dev/null and b/assets/images/2.0x/decred_menu.png differ
assets/images/3.0x/decred.png
Binary files /dev/null and b/assets/images/3.0x/decred.png differ
assets/images/3.0x/decred_menu.png
Binary files /dev/null and b/assets/images/3.0x/decred_menu.png differ
assets/images/dcr_icon.png
Binary files a/assets/images/dcr_icon.png and b/assets/images/dcr_icon.png differ
assets/images/decred.png
Binary files /dev/null and b/assets/images/decred.png differ
assets/images/decred_icon.png
Binary files /dev/null and b/assets/images/decred_icon.png differ
assets/images/decred_menu.png
Binary files /dev/null and b/assets/images/decred_menu.png differ
cw_bitcoin/lib/bitcoin_wallet.dart
+3
@@ -92,6 +92,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
92 });
93 }
94
95 + @override
96 + bool get hasRescan => true;
97 +
98 static Future<BitcoinWallet> create({
99 required String mnemonic,
100 required String password,
cw_bitcoin/lib/litecoin_wallet.dart
+3 -1
@@ -1,6 +1,5 @@
1 import 'dart:async';
2 import 'dart:convert';
3 -import 'dart:typed_data';
3
4 import 'package:convert/convert.dart' as convert;
5 import 'dart:math';
@@ -156,6 +155,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
155 @observable
156 SyncStatus mwebSyncStatus = NotConnectedSyncStatus();
157
158 + @override
159 + bool get hasRescan => true;
160 +
161 List<int> get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
162 List<int> get spendSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
163
cw_core/lib/amount_converter.dart
+1
@@ -29,6 +29,7 @@ class AmountConverter {
29 case CryptoCurrency.btc:
30 case CryptoCurrency.bch:
31 case CryptoCurrency.ltc:
32 + case CryptoCurrency.dcr:
33 return _bitcoinAmountToString(amount);
34 case CryptoCurrency.xhv:
35 case CryptoCurrency.xag:
cw_core/lib/currency_for_wallet_type.dart
+6 -1
@@ -32,9 +32,10 @@ CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) {
32 return CryptoCurrency.wow;
33 case WalletType.zano:
34 return CryptoCurrency.zano;
35 + case WalletType.decred:
36 + return CryptoCurrency.dcr;
37 case WalletType.none:
38 throw Exception(
37 -
39 'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
40 }
41 }
@@ -65,6 +66,10 @@ WalletType? walletTypeForCurrency(CryptoCurrency currency) {
66 return WalletType.tron;
67 case CryptoCurrency.wow:
68 return WalletType.wownero;
69 + case CryptoCurrency.zano:
70 + return WalletType.zano;
71 + case CryptoCurrency.dcr:
72 + return WalletType.decred;
73 default:
74 return null;
75 }
cw_core/lib/node.dart
+18
@@ -103,6 +103,7 @@ class Node extends HiveObject with Keyable {
103 case WalletType.solana:
104 case WalletType.tron:
105 case WalletType.zano:
106 + case WalletType.decred:
107 return Uri.parse(
108 "http${isSSL ? "s" : ""}://$uriRaw${path!.startsWith("/") || path!.isEmpty ? path : "/$path"}");
109 case WalletType.none:
@@ -167,6 +168,8 @@ class Node extends HiveObject with Keyable {
168 return requestElectrumServer();
169 case WalletType.zano:
170 return requestZanoNode();
171 + case WalletType.decred:
172 + return requestDecredNode();
173 case WalletType.none:
174 return false;
175 }
@@ -355,6 +358,21 @@ class Node extends HiveObject with Keyable {
358 return false;
359 }
360 }
361 +
362 + Future<bool> requestDecredNode() async {
363 + if (uri.host == "default-spv-nodes") {
364 + // Just show default port as ok. The wallet will connect to a list of known
365 + // nodes automatically.
366 + return true;
367 + }
368 + try {
369 + final socket = await Socket.connect(uri.host, uri.port, timeout: Duration(seconds: 5));
370 + socket.destroy();
371 + return true;
372 + } catch (_) {
373 + return false;
374 + }
375 + }
376 }
377
378 /// https://github.com/ManyMath/digest_auth/
cw_core/lib/receive_page_option.dart
+1
@@ -2,6 +2,7 @@ import 'package:cw_core/enumerate.dart';
2
3 class ReceivePageOption implements Enumerate {
4 static const mainnet = ReceivePageOption._('mainnet');
5 + static const testnet = ReceivePageOption._('testnet');
6 static const anonPayInvoice = ReceivePageOption._('anonPayInvoice');
7 static const anonPayDonationLink = ReceivePageOption._('anonPayDonationLink');
8
cw_core/lib/sync_status.dart
+10
@@ -34,6 +34,16 @@ class SyncingSyncStatus extends SyncStatus {
34 }
35 }
36
37 +class ProcessingSyncStatus extends SyncStatus {
38 + final String? message;
39 +
40 + ProcessingSyncStatus({this.message});
41 +
42 + @override
43 + double progress() => 0.99;
44 +
45 +}
46 +
47 class SyncedSyncStatus extends SyncStatus {
48 @override
49 double progress() => 1.0;
cw_core/lib/wallet_base.dart
+4
@@ -60,6 +60,8 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
60
61 bool get isHardwareWallet => walletInfo.isHardwareWallet;
62
63 + bool get hasRescan => false;
64 +
65 Future<void> connectToNode({required Node node});
66
67 // 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
102 Future<bool> verifyMessage(String message, String signature, {String? address = null});
103
104 bool isTestnet = false;
105 +
106 + bool canSend() => true;
107 }
cw_core/lib/wallet_type.dart
+14 -1
@@ -17,6 +17,7 @@ const walletTypes = [
17 WalletType.solana,
18 WalletType.tron,
19 WalletType.zano,
20 + WalletType.decred,
21 ];
22
23 @HiveType(typeId: WALLET_TYPE_TYPE_ID)
@@ -60,9 +61,11 @@ enum WalletType {
61 @HiveField(12)
62 wownero,
63
63 - @HiveField(13)
64 + @HiveField(13)
65 zano,
66
67 + @HiveField(14)
68 + decred
69 }
70
71 int serializeToInt(WalletType type) {
@@ -93,6 +96,8 @@ int serializeToInt(WalletType type) {
96 return 11;
97 case WalletType.zano:
98 return 12;
99 + case WalletType.decred:
100 + return 13;
101 case WalletType.none:
102 return -1;
103 }
@@ -126,6 +131,8 @@ WalletType deserializeFromInt(int raw) {
131 return WalletType.wownero;
132 case 12:
133 return WalletType.zano;
134 + case 13:
135 + return WalletType.decred;
136 default:
137 throw Exception(
138 'Unexpected token: $raw for WalletType deserializeFromInt');
@@ -160,6 +167,8 @@ String walletTypeToString(WalletType type) {
167 return 'Wownero';
168 case WalletType.zano:
169 return 'Zano';
170 + case WalletType.decred:
171 + return 'Decred';
172 case WalletType.none:
173 return '';
174 }
@@ -193,6 +202,8 @@ String walletTypeToDisplayName(WalletType type) {
202 return 'Wownero (WOW)';
203 case WalletType.zano:
204 return 'Zano (ZANO)';
205 + case WalletType.decred:
206 + return 'Decred (DCR)';
207 case WalletType.none:
208 return '';
209 }
@@ -229,6 +240,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal
240 return CryptoCurrency.wow;
241 case WalletType.zano:
242 return CryptoCurrency.zano;
243 + case WalletType.decred:
244 + return CryptoCurrency.dcr;
245 case WalletType.none:
246 throw Exception(
247 'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
cw_core/pubspec.lock
+18
@@ -46,6 +46,15 @@ packages:
46 url: "https://pub.dev"
47 source: hosted
48 version: "2.11.0"
49 + blockchain_utils:
50 + dependency: transitive
51 + description:
52 + path: "."
53 + ref: cake-update-v2
54 + resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57"
55 + url: "https://github.com/cake-tech/blockchain_utils"
56 + source: git
57 + version: "3.3.0"
58 boolean_selector:
59 dependency: transitive
60 description:
@@ -465,6 +474,15 @@ packages:
474 url: "https://pub.dev"
475 source: hosted
476 version: "1.0.0"
477 + on_chain:
478 + dependency: "direct main"
479 + description:
480 + path: "."
481 + ref: cake-update-v2
482 + resolved-ref: "93440dc5126369b873ca1fccc13c3c1240b1c5c2"
483 + url: "https://github.com/cake-tech/on_chain.git"
484 + source: git
485 + version: "3.7.0"
486 package_config:
487 dependency: transitive
488 description:
cw_decred/.gitignore new
+39
@@ -0,0 +1,39 @@
1 +# Miscellaneous
2 +*.class
3 +*.log
4 +*.pyc
5 +*.swp
6 +.DS_Store
7 +.atom/
8 +.buildlog/
9 +.history
10 +.svn/
11 +migrate_working_dir/
12 +
13 +# IntelliJ related
14 +*.iml
15 +*.ipr
16 +*.iws
17 +.idea/
18 +
19 +# The .vscode folder contains launch configuration and tasks you configure in
20 +# VS Code which you may wish to be included in version control, so this line
21 +# is commented out by default.
22 +#.vscode/
23 +
24 +# Flutter/Dart/Pub related
25 +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
26 +/pubspec.lock
27 +**/doc/api/
28 +.dart_tool/
29 +.packages
30 +build/
31 +
32 +android/.externalNativeBuild/
33 +android/.cxx/
34 +android/libs
35 +ios/External/
36 +macos/External/
37 +
38 +*libdcrwallet.h
39 +libdcrwallet_bindings.dart
cw_decred/.metadata new
+36
@@ -0,0 +1,36 @@
1 +# This file tracks properties of this Flutter project.
2 +# Used by Flutter tool to assess capabilities and perform upgrades etc.
3 +#
4 +# This file should be version controlled.
5 +
6 +version:
7 + revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
8 + channel: unknown
9 +
10 +project_type: plugin
11 +
12 +# Tracks metadata for the flutter migrate command
13 +migration:
14 + platforms:
15 + - platform: root
16 + create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
17 + base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
18 + - platform: android
19 + create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
20 + base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
21 + - platform: ios
22 + create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
23 + base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
24 + - platform: macos
25 + create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
26 + base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
27 +
28 + # User provided section
29 +
30 + # List of Local paths (relative to this file) that should be
31 + # ignored by the migrate tool.
32 + #
33 + # Files that are not part of the templates will be ignored by default.
34 + unmanaged_files:
35 + - 'lib/main.dart'
36 + - 'ios/Runner.xcodeproj/project.pbxproj'
cw_decred/CHANGELOG.md new
+3
@@ -0,0 +1,3 @@
1 +## [0.0.1] - TODO: Add release date.
2 +
3 +* TODO: Describe initial release.
cw_decred/LICENSE new
+1
@@ -0,0 +1 @@
1 +TODO: Add your license here.
cw_decred/README.md new
+3
@@ -0,0 +1,3 @@
1 +# cw_decred
2 +
3 +TODO: Fill this out.
cw_decred/analysis_options.yaml new
+4
@@ -0,0 +1,4 @@
1 +include: package:flutter_lints/flutter.yaml
2 +
3 +# Additional information about this file can be found at
4 +# https://dart.dev/guides/language/analysis-options
cw_decred/android/.gitignore new
+9
@@ -0,0 +1,9 @@
1 +*.iml
2 +.gradle
3 +/local.properties
4 +/.idea/workspace.xml
5 +/.idea/libraries
6 +.DS_Store
7 +/build
8 +/captures
9 +.cxx
cw_decred/android/build.gradle new
+59
@@ -0,0 +1,59 @@
1 +group 'com.cakewallet.cw_decred'
2 +version '1.0-SNAPSHOT'
3 +
4 +buildscript {
5 + ext.kotlin_version = '2.0.21'
6 + repositories {
7 + google()
8 + mavenCentral()
9 + }
10 +
11 + dependencies {
12 + classpath 'com.android.tools.build:gradle:8.7.1'
13 + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14 + }
15 +}
16 +
17 +rootProject.allprojects {
18 + repositories {
19 + google()
20 + mavenCentral()
21 + }
22 +}
23 +
24 +apply plugin: 'com.android.library'
25 +apply plugin: 'kotlin-android'
26 +
27 +android {
28 + compileSdkVersion 33
29 +
30 + if (project.android.hasProperty("namespace")) {
31 + namespace 'com.cakewallet.cw_decred'
32 + }
33 +
34 + compileOptions {
35 + sourceCompatibility JavaVersion.VERSION_17
36 + targetCompatibility JavaVersion.VERSION_17
37 + }
38 +
39 + kotlinOptions {
40 + jvmTarget = '17'
41 + }
42 + sourceSets {
43 + main {
44 + java.srcDirs += 'src/main/kotlin'
45 + jniLibs.srcDirs 'libs' // contains libdcrwallet.so shared libraries
46 + }
47 + }
48 + defaultConfig {
49 + minSdkVersion 21
50 + }
51 + externalNativeBuild {
52 + cmake {
53 + }
54 + }
55 +}
56 +
57 +dependencies {
58 + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
59 +}
cw_decred/android/settings.gradle new
+1
@@ -0,0 +1 @@
1 +rootProject.name = 'cw_decred'
cw_decred/android/src/main/AndroidManifest.xml new
+4
@@ -0,0 +1,4 @@
1 +<manifest xmlns:android="http://schemas.android.com/apk/res/android"
2 + package="com.cakewallet.cw_decred">
3 + <uses-permission android:name="android.permission.INTERNET"/>
4 +</manifest>
cw_decred/android/src/main/kotlin/com/cakewallet/cw_decred/CwDecredPlugin.kt new
+35
@@ -0,0 +1,35 @@
1 +package com.cakewallet.cw_decred
2 +
3 +import androidx.annotation.NonNull
4 +
5 +import io.flutter.embedding.engine.plugins.FlutterPlugin
6 +import io.flutter.plugin.common.MethodCall
7 +import io.flutter.plugin.common.MethodChannel
8 +import io.flutter.plugin.common.MethodChannel.MethodCallHandler
9 +import io.flutter.plugin.common.MethodChannel.Result
10 +
11 +/** CwDecredPlugin */
12 +class CwDecredPlugin: FlutterPlugin, MethodCallHandler {
13 + /// The MethodChannel that will the communication between Flutter and native Android
14 + ///
15 + /// This local reference serves to register the plugin with the Flutter Engine and unregister it
16 + /// when the Flutter Engine is detached from the Activity
17 + private lateinit var channel : MethodChannel
18 +
19 + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
20 + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "cw_decred")
21 + channel.setMethodCallHandler(this)
22 + }
23 +
24 + override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
25 + if (call.method == "getPlatformVersion") {
26 + result.success("Android ${android.os.Build.VERSION.RELEASE}")
27 + } else {
28 + result.notImplemented()
29 + }
30 + }
31 +
32 + override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
33 + channel.setMethodCallHandler(null)
34 + }
35 +}
cw_decred/ios/.gitignore new
+38
@@ -0,0 +1,38 @@
1 +.idea/
2 +.vagrant/
3 +.sconsign.dblite
4 +.svn/
5 +
6 +.DS_Store
7 +*.swp
8 +profile
9 +
10 +DerivedData/
11 +build/
12 +GeneratedPluginRegistrant.h
13 +GeneratedPluginRegistrant.m
14 +
15 +.generated/
16 +
17 +*.pbxuser
18 +*.mode1v3
19 +*.mode2v3
20 +*.perspectivev3
21 +
22 +!default.pbxuser
23 +!default.mode1v3
24 +!default.mode2v3
25 +!default.perspectivev3
26 +
27 +xcuserdata
28 +
29 +*.moved-aside
30 +
31 +*.pyc
32 +*sync/
33 +Icon?
34 +.tags*
35 +
36 +/Flutter/Generated.xcconfig
37 +/Flutter/ephemeral/
38 +/Flutter/flutter_export_environment.sh
\ No newline at end of file
cw_decred/ios/Assets/.gitkeep
cw_decred/ios/Classes/CwDecredPlugin.swift new
+19
@@ -0,0 +1,19 @@
1 +import Flutter
2 +import UIKit
3 +
4 +public class CwDecredPlugin: NSObject, FlutterPlugin {
5 + public static func register(with registrar: FlutterPluginRegistrar) {
6 + let channel = FlutterMethodChannel(name: "cw_decred", binaryMessenger: registrar.messenger())
7 + let instance = CwDecredPlugin()
8 + registrar.addMethodCallDelegate(instance, channel: channel)
9 + }
10 +
11 + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
12 + switch call.method {
13 + case "getPlatformVersion":
14 + result("iOS " + UIDevice.current.systemVersion)
15 + default:
16 + result(FlutterMethodNotImplemented)
17 + }
18 + }
19 +}
cw_decred/ios/cw_decred.podspec new
+22
@@ -0,0 +1,22 @@
1 +#
2 +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
3 +# Run `pod lib lint cw_decred.podspec` to validate before publishing.
4 +#
5 +Pod::Spec.new do |s|
6 + s.name = 'cw_decred'
7 + s.version = '0.0.1'
8 + s.summary = 'Cake Wallet Decred'
9 + s.description = 'Cake Wallet wrapper over Decred project'
10 + s.homepage = 'http://cakewallet.com'
11 + s.license = { :file => '../LICENSE' }
12 + s.author = { 'Cake Wallet' => 'support@cakewallet.com' }
13 + s.source = { :path => '.' }
14 + s.source_files = 'Classes/**/*'
15 + s.dependency 'Flutter'
16 + s.platform = :ios, '11.0'
17 +
18 + s.vendored_libraries = 'External/lib/libdcrwallet.a'
19 + # Flutter.framework does not contain a i386 slice.
20 + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', "OTHER_LDFLAGS" => "-force_load $(PODS_TARGET_SRCROOT)/External/lib/libdcrwallet.a -lstdc++" }
21 + s.swift_version = '5.0'
22 +end
cw_decred/lib/amount_format.dart new
+26
@@ -0,0 +1,26 @@
1 +import 'package:intl/intl.dart';
2 +import 'package:cw_core/crypto_amount_format.dart';
3 +
4 +const decredAmountLength = 8;
5 +const decredAmountDivider = 100000000;
6 +final decredAmountFormat = NumberFormat()
7 + ..maximumFractionDigits = decredAmountLength
8 + ..minimumFractionDigits = 1;
9 +
10 +String decredAmountToString({required int amount}) =>
11 + decredAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: decredAmountDivider));
12 +
13 +double decredAmountToDouble({required int amount}) =>
14 + cryptoAmountToDouble(amount: amount, divider: decredAmountDivider);
15 +
16 +int stringDoubleToDecredAmount(String amount) {
17 + int result = 0;
18 +
19 + try {
20 + result = (double.parse(amount) * decredAmountDivider).round();
21 + } catch (e) {
22 + result = 0;
23 + }
24 +
25 + return result;
26 +}
cw_decred/lib/api/libdcrwallet.dart new
+693
@@ -0,0 +1,693 @@
1 +import 'dart:convert';
2 +import 'dart:ffi';
3 +import 'dart:io';
4 +import 'dart:async';
5 +import 'dart:isolate';
6 +import 'package:flutter/foundation.dart';
7 +import 'package:cw_core/utils/print_verbose.dart';
8 +import 'package:cw_decred/api/libdcrwallet_bindings.dart';
9 +import 'package:cw_decred/api/util.dart';
10 +
11 +final int ErrCodeNotSynced = 1;
12 +
13 +final String libraryName = Platform.isAndroid || Platform.isLinux // TODO: Linux.
14 + ? 'libdcrwallet.so'
15 + : 'cw_decred.framework/cw_decred';
16 +
17 +class Libwallet {
18 + final SendPort _commands;
19 + final ReceivePort _responses;
20 + final Map<int, Completer<Object?>> _activeRequests = {};
21 + int _idCounter = 0;
22 + bool _closed = false;
23 +
24 + static Future<Libwallet> spawn() async {
25 + // Create a receive port and add its initial message handler.
26 + final initPort = RawReceivePort();
27 + final connection = Completer<(ReceivePort, SendPort)>.sync();
28 + initPort.handler = (initialMessage) {
29 + final commandPort = initialMessage as SendPort;
30 + connection.complete((
31 + ReceivePort.fromRawReceivePort(initPort),
32 + commandPort,
33 + ));
34 + };
35 + // Spawn the isolate.
36 + try {
37 + await Isolate.spawn(_startRemoteIsolate, (initPort.sendPort));
38 + } on Object {
39 + initPort.close();
40 + rethrow;
41 + }
42 +
43 + final (ReceivePort receivePort, SendPort sendPort) = await connection.future;
44 +
45 + return Libwallet._(receivePort, sendPort);
46 + }
47 +
48 + Libwallet._(this._responses, this._commands) {
49 + _responses.listen(_handleResponsesFromIsolate);
50 + }
51 +
52 + void _handleResponsesFromIsolate(dynamic message) {
53 + final (int id, Object? response) = message as (int, Object?);
54 + final completer = _activeRequests.remove(id)!;
55 +
56 + if (response is RemoteError) {
57 + completer.completeError(response);
58 + } else {
59 + completer.complete(response);
60 + }
61 +
62 + if (_closed && _activeRequests.isEmpty) _responses.close();
63 + }
64 +
65 + static void _handleCommandsToIsolate(
66 + ReceivePort receivePort,
67 + SendPort sendPort,
68 + ) {
69 + final dcrwalletApi = libdcrwallet(DynamicLibrary.open(libraryName));
70 + receivePort.listen((message) {
71 + if (message == 'shutdown') {
72 + receivePort.close();
73 + return;
74 + }
75 + final (int id, Map<String, String> args) = message as (int, Map<String, String>);
76 + var res = PayloadResult("", "", 0);
77 + final method = args["method"] ?? "";
78 + try {
79 + switch (method) {
80 + case "initlibdcrwallet":
81 + final logDir = args["logdir"] ?? "";
82 + final cLogDir = logDir.toCString();
83 + executePayloadFn(
84 + fn: () => dcrwalletApi.initialize(cLogDir),
85 + ptrsToFree: [cLogDir],
86 + );
87 + break;
88 + case "createwallet":
89 + final config = args["config"] ?? "";
90 + final cConfig = config.toCString();
91 + executePayloadFn(
92 + fn: () => dcrwalletApi.createWallet(cConfig),
93 + ptrsToFree: [cConfig],
94 + );
95 + break;
96 + case "createwatchonlywallet":
97 + final config = args["config"] ?? "";
98 + final cConfig = config.toCString();
99 + executePayloadFn(
100 + fn: () => dcrwalletApi.createWatchOnlyWallet(cConfig),
101 + ptrsToFree: [cConfig],
102 + );
103 + break;
104 + case "loadwallet":
105 + final config = args["config"] ?? "";
106 + final cConfig = config.toCString();
107 + executePayloadFn(
108 + fn: () => dcrwalletApi.loadWallet(cConfig),
109 + ptrsToFree: [cConfig],
110 + );
111 + break;
112 + case "startsync":
113 + final name = args["name"] ?? "";
114 + final peers = args["peers"] ?? "";
115 + final cName = name.toCString();
116 + final cPeers = peers.toCString();
117 + executePayloadFn(
118 + fn: () => dcrwalletApi.syncWallet(cName, cPeers),
119 + ptrsToFree: [cName, cPeers],
120 + );
121 + break;
122 + case "closewallet":
123 + final name = args["name"] ?? "";
124 + final cName = name.toCString();
125 + executePayloadFn(
126 + fn: () => dcrwalletApi.closeWallet(cName),
127 + ptrsToFree: [cName],
128 + );
129 + break;
130 + case "changewalletpassword":
131 + final name = args["name"] ?? "";
132 + final oldPass = args["oldpass"] ?? "";
133 + final newPass = args["newpass"] ?? "";
134 + final cName = name.toCString();
135 + final cOldPass = oldPass.toCString();
136 + final cNewPass = newPass.toCString();
137 + res = executePayloadFn(
138 + fn: () => dcrwalletApi.changePassphrase(cName, cOldPass, cNewPass),
139 + ptrsToFree: [cName, cOldPass, cNewPass],
140 + );
141 + break;
142 + case "walletseed":
143 + final name = args["name"] ?? "";
144 + final pass = args["pass"] ?? "";
145 + final cName = name.toCString();
146 + final cPass = pass.toCString();
147 + res = executePayloadFn(
148 + fn: () => dcrwalletApi.walletSeed(cName, cPass),
149 + ptrsToFree: [cName, cPass],
150 + );
151 + break;
152 + case "syncstatus":
153 + final name = args["name"] ?? "";
154 + final cName = name.toCString();
155 + res = executePayloadFn(
156 + fn: () => dcrwalletApi.syncWalletStatus(cName),
157 + ptrsToFree: [cName],
158 + );
159 + break;
160 + case "balance":
161 + final name = args["name"] ?? "";
162 + final cName = name.toCString();
163 + res = executePayloadFn(
164 + fn: () => dcrwalletApi.walletBalance(cName),
165 + ptrsToFree: [cName],
166 + );
167 + break;
168 + case "estimatefee":
169 + final name = args["name"] ?? "";
170 + final numBlocks = args["numblocks"] ?? "";
171 + final cName = name.toCString();
172 + final cNumBlocks = numBlocks.toCString();
173 + res = executePayloadFn(
174 + fn: () => dcrwalletApi.estimateFee(cName, cNumBlocks),
175 + ptrsToFree: [cName, cNumBlocks],
176 + );
177 + break;
178 + case "createsignedtransaction":
179 + final name = args["name"] ?? "";
180 + final signReq = args["signreq"] ?? "";
181 + final cName = name.toCString();
182 + final cSignReq = signReq.toCString();
183 + res = executePayloadFn(
184 + fn: () => dcrwalletApi.createSignedTransaction(cName, cSignReq),
185 + ptrsToFree: [cName, cSignReq],
186 + );
187 + break;
188 + case "sendrawtransaction":
189 + final name = args["name"] ?? "";
190 + final txHex = args["txhex"] ?? "";
191 + final cName = name.toCString();
192 + final cTxHex = txHex.toCString();
193 + res = executePayloadFn(
194 + fn: () => dcrwalletApi.sendRawTransaction(cName, cTxHex),
195 + ptrsToFree: [cName, cTxHex],
196 + );
197 + break;
198 + case "listtransactions":
199 + final name = args["name"] ?? "";
200 + final from = args["from"] ?? "";
201 + final count = args["count"] ?? "";
202 + final cName = name.toCString();
203 + final cFrom = from.toCString();
204 + final cCount = count.toCString();
205 + res = executePayloadFn(
206 + fn: () => dcrwalletApi.listTransactions(cName, cFrom, cCount),
207 + ptrsToFree: [cName, cFrom, cCount],
208 + );
209 + break;
210 + case "bestblock":
211 + final name = args["name"] ?? "";
212 + final cName = name.toCString();
213 + res = executePayloadFn(
214 + fn: () => dcrwalletApi.bestBlock(cName),
215 + ptrsToFree: [cName],
216 + );
217 + break;
218 + case "listunspents":
219 + final name = args["name"] ?? "";
220 + final cName = name.toCString();
221 + res = executePayloadFn(
222 + fn: () => dcrwalletApi.listUnspents(cName),
223 + ptrsToFree: [cName],
224 + );
225 + break;
226 + case "rescanfromheight":
227 + final name = args["name"] ?? "";
228 + final height = args["height"] ?? "";
229 + final cName = name.toCString();
230 + final cHeight = height.toCString();
231 + res = executePayloadFn(
232 + fn: () => dcrwalletApi.rescanFromHeight(cName, cHeight),
233 + ptrsToFree: [cName, cHeight],
234 + );
235 + break;
236 + case "signmessage":
237 + final name = args["name"] ?? "";
238 + final message = args["message"] ?? "";
239 + final address = args["address"] ?? "";
240 + final pass = args["pass"] ?? "";
241 + final cName = name.toCString();
242 + final cMessage = message.toCString();
243 + final cAddress = address.toCString();
244 + final cPass = pass.toCString();
245 + res = executePayloadFn(
246 + fn: () => dcrwalletApi.signMessage(cName, cMessage, cAddress, cPass),
247 + ptrsToFree: [cName, cMessage, cAddress, cPass],
248 + );
249 + break;
250 + case "verifymessage":
251 + final name = args["name"] ?? "";
252 + final message = args["message"] ?? "";
253 + final address = args["address"] ?? "";
254 + final sig = args["sig"] ?? "";
255 + final cName = name.toCString();
256 + final cMessage = message.toCString();
257 + final cAddress = address.toCString();
258 + final cSig = sig.toCString();
259 + res = executePayloadFn(
260 + fn: () => dcrwalletApi.verifyMessage(cName, cMessage, cAddress, cSig),
261 + ptrsToFree: [cName, cMessage, cAddress, cSig],
262 + );
263 + break;
264 + case "newexternaladdress":
265 + final name = args["name"] ?? "";
266 + final cName = name.toCString();
267 + res = executePayloadFn(
268 + fn: () => dcrwalletApi.newExternalAddress(cName),
269 + ptrsToFree: [cName],
270 + skipErrorCheck: true,
271 + );
272 + break;
273 + case "defaultpubkey":
274 + final name = args["name"] ?? "";
275 + final cName = name.toCString();
276 + res = executePayloadFn(
277 + fn: () => dcrwalletApi.defaultPubkey(cName),
278 + ptrsToFree: [cName],
279 + );
280 + break;
281 + case "addresses":
282 + final name = args["name"] ?? "";
283 + final nUsed = args["nused"] ?? "";
284 + final nUnused = args["nunused"] ?? "";
285 + final cName = name.toCString();
286 + final cNUsed = nUsed.toCString();
287 + final cNUnused = nUnused.toCString();
288 + res = executePayloadFn(
289 + fn: () => dcrwalletApi.addresses(cName, cNUsed, cNUnused),
290 + ptrsToFree: [cName, cNUsed, cNUnused],
291 + );
292 + break;
293 + case "birthstate":
294 + final name = args["name"] ?? "";
295 + final cName = name.toCString();
296 + res = executePayloadFn(
297 + fn: () => dcrwalletApi.birthState(cName),
298 + ptrsToFree: [cName],
299 + );
300 + break;
301 + case "shutdown":
302 + final name = args["name"] ?? "";
303 + final cName = name.toCString();
304 + executePayloadFn(
305 + fn: () => dcrwalletApi.shutdown(),
306 + ptrsToFree: [],
307 + );
308 + break;
309 + default:
310 + res = PayloadResult("", "unknown libwallet method ${method}", 0);
311 + }
312 + sendPort.send((id, res));
313 + } catch (e) {
314 + final errMsg = e.toString();
315 + printV("decred libwallet returned an error for method ${method}: ${errMsg}");
316 + sendPort.send((id, PayloadResult("", errMsg, 0)));
317 + }
318 + });
319 + }
320 +
321 + static void _startRemoteIsolate(SendPort sendPort) {
322 + final receivePort = ReceivePort();
323 + sendPort.send(receivePort.sendPort);
324 + _handleCommandsToIsolate(receivePort, sendPort);
325 + }
326 +
327 + // initLibdcrwallet initializes libdcrwallet using the provided logDir and gets
328 + // it ready for use. This must be done before attempting to create, load or use
329 + // a wallet.
330 + Future<void> initLibdcrwallet(String logDir) async {
331 + if (_closed) throw StateError('Closed');
332 + final completer = Completer<Object?>.sync();
333 + final id = _idCounter++;
334 + _activeRequests[id] = completer;
335 + final req = {
336 + "method": "initlibdcrwallet",
337 + "logdir": logDir,
338 + };
339 + _commands.send((id, req));
340 + await completer.future;
341 + }
342 +
343 + Future<void> createWallet(String config) async {
344 + if (_closed) throw StateError('Closed');
345 + final completer = Completer<Object?>.sync();
346 + final id = _idCounter++;
347 + _activeRequests[id] = completer;
348 + final req = {
349 + "method": "createwallet",
350 + "config": config,
351 + };
352 + _commands.send((id, req));
353 + await completer.future;
354 + }
355 +
356 + Future<void> createWatchOnlyWallet(String config) async {
357 + if (_closed) throw StateError('Closed');
358 + final completer = Completer<Object?>.sync();
359 + final id = _idCounter++;
360 + _activeRequests[id] = completer;
361 + final req = {
362 + "method": "createwatchonlywallet",
363 + "config": config,
364 + };
365 + _commands.send((id, req));
366 + await completer.future;
367 + }
368 +
369 + Future<void> loadWallet(String config) async {
370 + if (_closed) throw StateError('Closed');
371 + final completer = Completer<Object?>.sync();
372 + final id = _idCounter++;
373 + _activeRequests[id] = completer;
374 + final req = {
375 + "method": "loadwallet",
376 + "config": config,
377 + };
378 + _commands.send((id, req));
379 + await completer.future;
380 + }
381 +
382 + Future<void> startSync(String walletName, String peers) async {
383 + if (_closed) throw StateError('Closed');
384 + final completer = Completer<Object?>.sync();
385 + final id = _idCounter++;
386 + _activeRequests[id] = completer;
387 + final req = {
388 + "method": "startsync",
389 + "name": walletName,
390 + "peers": peers,
391 + };
392 + _commands.send((id, req));
393 + await completer.future;
394 + }
395 +
396 + Future<void> closeWallet(String walletName) async {
397 + if (_closed) throw StateError('Closed');
398 + final completer = Completer<Object?>.sync();
399 + final id = _idCounter++;
400 + _activeRequests[id] = completer;
401 + final req = {
402 + "method": "closewallet",
403 + "name": walletName,
404 + };
405 + _commands.send((id, req));
406 + await completer.future;
407 + }
408 +
409 + Future<String> changeWalletPassword(
410 + String walletName, String currentPassword, String newPassword) async {
411 + if (_closed) throw StateError('Closed');
412 + final completer = Completer<Object?>.sync();
413 + final id = _idCounter++;
414 + _activeRequests[id] = completer;
415 + final req = {
416 + "method": "changewalletpassword",
417 + "name": walletName,
418 + "oldpass": currentPassword,
419 + "newpass": newPassword
420 + };
421 + _commands.send((id, req));
422 + final res = await completer.future as PayloadResult;
423 + return res.payload;
424 + }
425 +
426 + Future<String?> walletSeed(String walletName, String walletPassword) async {
427 + if (_closed) throw StateError('Closed');
428 + final completer = Completer<Object?>.sync();
429 + final id = _idCounter++;
430 + _activeRequests[id] = completer;
431 + final req = {
432 + "method": "walletseed",
433 + "name": walletName,
434 + "pass": walletPassword,
435 + };
436 + _commands.send((id, req));
437 + final res = await completer.future as PayloadResult;
438 + return res.payload;
439 + }
440 +
441 + Future<String> syncStatus(String walletName) async {
442 + if (_closed) throw StateError('Closed');
443 + final completer = Completer<Object?>.sync();
444 + final id = _idCounter++;
445 + _activeRequests[id] = completer;
446 + final req = {
447 + "method": "syncstatus",
448 + "name": walletName,
449 + };
450 + _commands.send((id, req));
451 + final res = await completer.future as PayloadResult;
452 + return res.payload;
453 + }
454 +
455 + Future<Map> balance(String walletName) async {
456 + if (_closed) throw StateError('Closed');
457 + final completer = Completer<Object?>.sync();
458 + final id = _idCounter++;
459 + _activeRequests[id] = completer;
460 + final req = {
461 + "method": "balance",
462 + "name": walletName,
463 + };
464 + _commands.send((id, req));
465 + final res = await completer.future as PayloadResult;
466 + return jsonDecode(res.payload);
467 + }
468 +
469 + Future<String> estimateFee(String walletName, int numBlocks) async {
470 + if (_closed) throw StateError('Closed');
471 + final completer = Completer<Object?>.sync();
472 + final id = _idCounter++;
473 + _activeRequests[id] = completer;
474 + final req = {
475 + "method": "estimatefee",
476 + "name": walletName,
477 + "numblocks": numBlocks.toString(),
478 + };
479 + _commands.send((id, req));
480 + final res = await completer.future as PayloadResult;
481 + return res.payload;
482 + }
483 +
484 + Future<String> createSignedTransaction(
485 + String walletName, String createSignedTransactionReq) async {
486 + if (_closed) throw StateError('Closed');
487 + final completer = Completer<Object?>.sync();
488 + final id = _idCounter++;
489 + _activeRequests[id] = completer;
490 + final req = {
491 + "method": "createsignedtransaction",
492 + "name": walletName,
493 + "signreq": createSignedTransactionReq,
494 + };
495 + _commands.send((id, req));
496 + final res = await completer.future as PayloadResult;
497 + return res.payload;
498 + }
499 +
500 + Future<String> sendRawTransaction(String walletName, String txHex) async {
501 + if (_closed) throw StateError('Closed');
502 + final completer = Completer<Object?>.sync();
503 + final id = _idCounter++;
504 + _activeRequests[id] = completer;
505 + final req = {
506 + "method": "sendrawtransaction",
507 + "name": walletName,
508 + "txhex": txHex,
509 + };
510 + _commands.send((id, req));
511 + final res = await completer.future as PayloadResult;
512 + return res.payload;
513 + }
514 +
515 + Future<String> listTransactions(String walletName, String from, String count) async {
516 + if (_closed) throw StateError('Closed');
517 + final completer = Completer<Object?>.sync();
518 + final id = _idCounter++;
519 + _activeRequests[id] = completer;
520 + final req = {
521 + "method": "listtransactions",
522 + "name": walletName,
523 + "from": from,
524 + "count": count,
525 + };
526 + _commands.send((id, req));
527 + final res = await completer.future as PayloadResult;
528 + return res.payload;
529 + }
530 +
531 + Future<String> bestBlock(String walletName) async {
532 + if (_closed) throw StateError('Closed');
533 + final completer = Completer<Object?>.sync();
534 + final id = _idCounter++;
535 + _activeRequests[id] = completer;
536 + final req = {
537 + "method": "bestblock",
538 + "name": walletName,
539 + };
540 + _commands.send((id, req));
541 + final res = await completer.future as PayloadResult;
542 + return res.payload;
543 + }
544 +
545 + Future<String> listUnspents(String walletName) async {
546 + if (_closed) throw StateError('Closed');
547 + final completer = Completer<Object?>.sync();
548 + final id = _idCounter++;
549 + _activeRequests[id] = completer;
550 + final req = {
551 + "method": "listunspents",
552 + "name": walletName,
553 + };
554 + _commands.send((id, req));
555 + final res = await completer.future as PayloadResult;
556 + return res.payload;
557 + }
558 +
559 + Future<String> rescanFromHeight(String walletName, String height) async {
560 + if (_closed) throw StateError('Closed');
561 + final completer = Completer<Object?>.sync();
562 + final id = _idCounter++;
563 + _activeRequests[id] = completer;
564 + final req = {
565 + "method": "rescanfromheight",
566 + "name": walletName,
567 + "height": height,
568 + };
569 + _commands.send((id, req));
570 + final res = await completer.future as PayloadResult;
571 + return res.payload;
572 + }
573 +
574 + Future<String> signMessage(
575 + String walletName, String message, String address, String walletPass) async {
576 + if (_closed) throw StateError('Closed');
577 + final completer = Completer<Object?>.sync();
578 + final id = _idCounter++;
579 + _activeRequests[id] = completer;
580 + final req = {
581 + "method": "signmessage",
582 + "name": walletName,
583 + "message": message,
584 + "address": address,
585 + "pass": walletPass,
586 + };
587 + _commands.send((id, req));
588 + final res = await completer.future as PayloadResult;
589 + return res.payload;
590 + }
591 +
592 + Future<String> verifyMessage(
593 + String walletName, String message, String address, String sig) async {
594 + if (_closed) throw StateError('Closed');
595 + final completer = Completer<Object?>.sync();
596 + final id = _idCounter++;
597 + _activeRequests[id] = completer;
598 + final req = {
599 + "method": "verifymessage",
600 + "name": walletName,
601 + "message": message,
602 + "address": address,
603 + "sig": sig,
604 + };
605 + _commands.send((id, req));
606 + final res = await completer.future as PayloadResult;
607 + return res.payload;
608 + }
609 +
610 + Future<String?> newExternalAddress(String walletName) async {
611 + if (_closed) throw StateError('Closed');
612 + final completer = Completer<Object?>.sync();
613 + final id = _idCounter++;
614 + _activeRequests[id] = completer;
615 + final req = {
616 + "method": "newexternaladdress",
617 + "name": walletName,
618 + };
619 + _commands.send((id, req));
620 + final res = await completer.future as PayloadResult;
621 + if (res.errCode == ErrCodeNotSynced) {
622 + // Wallet is not synced. We do not want to give out a used address so give
623 + // nothing.
624 + return null;
625 + }
626 + checkErr(res.err);
627 + return res.payload;
628 + }
629 +
630 + Future<String> defaultPubkey(String walletName) async {
631 + if (_closed) throw StateError('Closed');
632 + final completer = Completer<Object?>.sync();
633 + final id = _idCounter++;
634 + _activeRequests[id] = completer;
635 + final req = {
636 + "method": "defaultpubkey",
637 + "name": walletName,
638 + };
639 + _commands.send((id, req));
640 + final res = await completer.future as PayloadResult;
641 + return res.payload;
642 + }
643 +
644 + Future<String> addresses(String walletName, String nUsed, String nUnused) async {
645 + if (_closed) throw StateError('Closed');
646 + final completer = Completer<Object?>.sync();
647 + final id = _idCounter++;
648 + _activeRequests[id] = completer;
649 + final req = {
650 + "method": "addresses",
651 + "name": walletName,
652 + "nused": nUsed,
653 + "nunused": nUnused,
654 + };
655 + _commands.send((id, req));
656 + final res = await completer.future as PayloadResult;
657 + return res.payload;
658 + }
659 +
660 + Future<String> birthState(String walletName) async {
661 + if (_closed) throw StateError('Closed');
662 + final completer = Completer<Object?>.sync();
663 + final id = _idCounter++;
664 + _activeRequests[id] = completer;
665 + final req = {
666 + "method": "birthstate",
667 + "name": walletName,
668 + };
669 + _commands.send((id, req));
670 + final res = await completer.future as PayloadResult;
671 + return res.payload;
672 + }
673 +
674 + Future<void> shutdown() async {
675 + if (_closed) throw StateError('Closed');
676 + final completer = Completer<Object?>.sync();
677 + final id = _idCounter++;
678 + _activeRequests[id] = completer;
679 + final req = {
680 + "method": "shutdown",
681 + };
682 + _commands.send((id, req));
683 + await completer.future as PayloadResult;
684 + }
685 +
686 + void close() {
687 + if (!_closed) {
688 + _closed = true;
689 + _commands.send('shutdown');
690 + if (_activeRequests.isEmpty) _responses.close();
691 + }
692 + }
693 +}
cw_decred/lib/api/util.dart new
+64
@@ -0,0 +1,64 @@
1 +import 'dart:ffi';
2 +import 'package:ffi/ffi.dart';
3 +import 'dart:convert';
4 +
5 +class PayloadResult {
6 + final String payload;
7 + final String err;
8 + final int errCode;
9 +
10 + const PayloadResult(this.payload, this.err, this.errCode);
11 +}
12 +
13 +// Executes the provided fn and converts the string response to a PayloadResult.
14 +// Returns payload, error code, and error.
15 +PayloadResult executePayloadFn({
16 + required Pointer<Char> fn(),
17 + required List<Pointer> ptrsToFree,
18 + bool skipErrorCheck = false,
19 +}) {
20 + final jsonStr = fn().toDartString();
21 + freePointers(ptrsToFree);
22 + if (jsonStr == null) throw Exception("no json return from wallet library");
23 + final decoded = json.decode(jsonStr);
24 +
25 + final err = decoded["error"] ?? "";
26 + if (!skipErrorCheck) {
27 + checkErr(err);
28 + }
29 +
30 + final payload = decoded["payload"] ?? "";
31 + final errCode = decoded["errorcode"] ?? -1;
32 + return new PayloadResult(payload, err, errCode);
33 +}
34 +
35 +void freePointers(List<Pointer> ptrsToFree) {
36 + for (final ptr in ptrsToFree) {
37 + malloc.free(ptr);
38 + }
39 +}
40 +
41 +void checkErr(String err) {
42 + if (err == "") return;
43 + throw Exception(err);
44 +}
45 +
46 +extension StringUtil on String {
47 + Pointer<Char> toCString() => toNativeUtf8().cast<Char>();
48 +}
49 +
50 +extension CStringUtil on Pointer<Char> {
51 + bool get isNull => address == nullptr.address;
52 +
53 + free() {
54 + malloc.free(this);
55 + }
56 +
57 + String? toDartString() {
58 + if (isNull) return null;
59 +
60 + final str = cast<Utf8>().toDartString();
61 + free();
62 + return str;
63 + }
64 +}
cw_decred/lib/balance.dart new
+25
@@ -0,0 +1,25 @@
1 +import 'package:cw_decred/amount_format.dart';
2 +import 'package:cw_core/balance.dart';
3 +
4 +class DecredBalance extends Balance {
5 + const DecredBalance({required this.confirmed, required this.unconfirmed, required this.frozen})
6 + : super(confirmed, unconfirmed);
7 +
8 + factory DecredBalance.zero() => DecredBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
9 +
10 + final int confirmed;
11 + final int unconfirmed;
12 + final int frozen;
13 +
14 + @override
15 + String get formattedAvailableBalance => decredAmountToString(amount: confirmed - frozen);
16 +
17 + @override
18 + String get formattedAdditionalBalance => decredAmountToString(amount: unconfirmed);
19 +
20 + @override
21 + String get formattedUnAvailableBalance {
22 + final frozenFormatted = decredAmountToString(amount: frozen);
23 + return frozenFormatted == '0.0' ? '' : frozenFormatted;
24 + }
25 +}
cw_decred/lib/mnemonic.dart new
+2050
@@ -0,0 +1,2050 @@
1 +final wordlist = <String>[
2 + "abandon",
3 + "ability",
4 + "able",
5 + "about",
6 + "above",
7 + "absent",
8 + "absorb",
9 + "abstract",
10 + "absurd",
11 + "abuse",
12 + "access",
13 + "accident",
14 + "account",
15 + "accuse",
16 + "achieve",
17 + "acid",
18 + "acoustic",
19 + "acquire",
20 + "across",
21 + "act",
22 + "action",
23 + "actor",
24 + "actress",
25 + "actual",
26 + "adapt",
27 + "add",
28 + "addict",
29 + "address",
30 + "adjust",
31 + "admit",
32 + "adult",
33 + "advance",
34 + "advice",
35 + "aerobic",
36 + "affair",
37 + "afford",
38 + "afraid",
39 + "again",
40 + "age",
41 + "agent",
42 + "agree",
43 + "ahead",
44 + "aim",
45 + "air",
46 + "airport",
47 + "aisle",
48 + "alarm",
49 + "album",
50 + "alcohol",
51 + "alert",
52 + "alien",
53 + "all",
54 + "alley",
55 + "allow",
56 + "almost",
57 + "alone",
58 + "alpha",
59 + "already",
60 + "also",
61 + "alter",
62 + "always",
63 + "amateur",
64 + "amazing",
65 + "among",
66 + "amount",
67 + "amused",
68 + "analyst",
69 + "anchor",
70 + "ancient",
71 + "anger",
72 + "angle",
73 + "angry",
74 + "animal",
75 + "ankle",
76 + "announce",
77 + "annual",
78 + "another",
79 + "answer",
80 + "antenna",
81 + "antique",
82 + "anxiety",
83 + "any",
84 + "apart",
85 + "apology",
86 + "appear",
87 + "apple",
88 + "approve",
89 + "april",
90 + "arch",
91 + "arctic",
92 + "area",
93 + "arena",
94 + "argue",
95 + "arm",
96 + "armed",
97 + "armor",
98 + "army",
99 + "around",
100 + "arrange",
101 + "arrest",
102 + "arrive",
103 + "arrow",
104 + "art",
105 + "artefact",
106 + "artist",
107 + "artwork",
108 + "ask",
109 + "aspect",
110 + "assault",
111 + "asset",
112 + "assist",
113 + "assume",
114 + "asthma",
115 + "athlete",
116 + "atom",
117 + "attack",
118 + "attend",
119 + "attitude",
120 + "attract",
121 + "auction",
122 + "audit",
123 + "august",
124 + "aunt",
125 + "author",
126 + "auto",
127 + "autumn",
128 + "average",
129 + "avocado",
130 + "avoid",
131 + "awake",
132 + "aware",
133 + "away",
134 + "awesome",
135 + "awful",
136 + "awkward",
137 + "axis",
138 + "baby",
139 + "bachelor",
140 + "bacon",
141 + "badge",
142 + "bag",
143 + "balance",
144 + "balcony",
145 + "ball",
146 + "bamboo",
147 + "banana",
148 + "banner",
149 + "bar",
150 + "barely",
151 + "bargain",
152 + "barrel",
153 + "base",
154 + "basic",
155 + "basket",
156 + "battle",
157 + "beach",
158 + "bean",
159 + "beauty",
160 + "because",
161 + "become",
162 + "beef",
163 + "before",
164 + "begin",
165 + "behave",
166 + "behind",
167 + "believe",
168 + "below",
169 + "belt",
170 + "bench",
171 + "benefit",
172 + "best",
173 + "betray",
174 + "better",
175 + "between",
176 + "beyond",
177 + "bicycle",
178 + "bid",
179 + "bike",
180 + "bind",
181 + "biology",
182 + "bird",
183 + "birth",
184 + "bitter",
185 + "black",
186 + "blade",
187 + "blame",
188 + "blanket",
189 + "blast",
190 + "bleak",
191 + "bless",
192 + "blind",
193 + "blood",
194 + "blossom",
195 + "blouse",
196 + "blue",
197 + "blur",
198 + "blush",
199 + "board",
200 + "boat",
201 + "body",
202 + "boil",
203 + "bomb",
204 + "bone",
205 + "bonus",
206 + "book",
207 + "boost",
208 + "border",
209 + "boring",
210 + "borrow",
211 + "boss",
212 + "bottom",
213 + "bounce",
214 + "box",
215 + "boy",
216 + "bracket",
217 + "brain",
218 + "brand",
219 + "brass",
220 + "brave",
221 + "bread",
222 + "breeze",
223 + "brick",
224 + "bridge",
225 + "brief",
226 + "bright",
227 + "bring",
228 + "brisk",
229 + "broccoli",
230 + "broken",
231 + "bronze",
232 + "broom",
233 + "brother",
234 + "brown",
235 + "brush",
236 + "bubble",
237 + "buddy",
238 + "budget",
239 + "buffalo",
240 + "build",
241 + "bulb",
242 + "bulk",
243 + "bullet",
244 + "bundle",
245 + "bunker",
246 + "burden",
247 + "burger",
248 + "burst",
249 + "bus",
250 + "business",
251 + "busy",
252 + "butter",
253 + "buyer",
254 + "buzz",
255 + "cabbage",
256 + "cabin",
257 + "cable",
258 + "cactus",
259 + "cage",
260 + "cake",
261 + "call",
262 + "calm",
263 + "camera",
264 + "camp",
265 + "can",
266 + "canal",
267 + "cancel",
268 + "candy",
269 + "cannon",
270 + "canoe",
271 + "canvas",
272 + "canyon",
273 + "capable",
274 + "capital",
275 + "captain",
276 + "car",
277 + "carbon",
278 + "card",
279 + "cargo",
280 + "carpet",
281 + "carry",
282 + "cart",
283 + "case",
284 + "cash",
285 + "casino",
286 + "castle",
287 + "casual",
288 + "cat",
289 + "catalog",
290 + "catch",
291 + "category",
292 + "cattle",
293 + "caught",
294 + "cause",
295 + "caution",
296 + "cave",
297 + "ceiling",
298 + "celery",
299 + "cement",
300 + "census",
301 + "century",
302 + "cereal",
303 + "certain",
304 + "chair",
305 + "chalk",
306 + "champion",
307 + "change",
308 + "chaos",
309 + "chapter",
310 + "charge",
311 + "chase",
312 + "chat",
313 + "cheap",
314 + "check",
315 + "cheese",
316 + "chef",
317 + "cherry",
318 + "chest",
319 + "chicken",
320 + "chief",
321 + "child",
322 + "chimney",
323 + "choice",
324 + "choose",
325 + "chronic",
326 + "chuckle",
327 + "chunk",
328 + "churn",
329 + "cigar",
330 + "cinnamon",
331 + "circle",
332 + "citizen",
333 + "city",
334 + "civil",
335 + "claim",
336 + "clap",
337 + "clarify",
338 + "claw",
339 + "clay",
340 + "clean",
341 + "clerk",
342 + "clever",
343 + "click",
344 + "client",
345 + "cliff",
346 + "climb",
347 + "clinic",
348 + "clip",
349 + "clock",
350 + "clog",
351 + "close",
352 + "cloth",
353 + "cloud",
354 + "clown",
355 + "club",
356 + "clump",
357 + "cluster",
358 + "clutch",
359 + "coach",
360 + "coast",
361 + "coconut",
362 + "code",
363 + "coffee",
364 + "coil",
365 + "coin",
366 + "collect",
367 + "color",
368 + "column",
369 + "combine",
370 + "come",
371 + "comfort",
372 + "comic",
373 + "common",
374 + "company",
375 + "concert",
376 + "conduct",
377 + "confirm",
378 + "congress",
379 + "connect",
380 + "consider",
381 + "control",
382 + "convince",
383 + "cook",
384 + "cool",
385 + "copper",
386 + "copy",
387 + "coral",
388 + "core",
389 + "corn",
390 + "correct",
391 + "cost",
392 + "cotton",
393 + "couch",
394 + "country",
395 + "couple",
396 + "course",
397 + "cousin",
398 + "cover",
399 + "coyote",
400 + "crack",
401 + "cradle",
402 + "craft",
403 + "cram",
404 + "crane",
405 + "crash",
406 + "crater",
407 + "crawl",
408 + "crazy",
409 + "cream",
410 + "credit",
411 + "creek",
412 + "crew",
413 + "cricket",
414 + "crime",
415 + "crisp",
416 + "critic",
417 + "crop",
418 + "cross",
419 + "crouch",
420 + "crowd",
421 + "crucial",
422 + "cruel",
423 + "cruise",
424 + "crumble",
425 + "crunch",
426 + "crush",
427 + "cry",
428 + "crystal",
429 + "cube",
430 + "culture",
431 + "cup",
432 + "cupboard",
433 + "curious",
434 + "current",
435 + "curtain",
436 + "curve",
437 + "cushion",
438 + "custom",
439 + "cute",
440 + "cycle",
441 + "dad",
442 + "damage",
443 + "damp",
444 + "dance",
445 + "danger",
446 + "daring",
447 + "dash",
448 + "daughter",
449 + "dawn",
450 + "day",
451 + "deal",
452 + "debate",
453 + "debris",
454 + "decade",
455 + "december",
456 + "decide",
457 + "decline",
458 + "decorate",
459 + "decrease",
460 + "deer",
461 + "defense",
462 + "define",
463 + "defy",
464 + "degree",
465 + "delay",
466 + "deliver",
467 + "demand",
468 + "demise",
469 + "denial",
470 + "dentist",
471 + "deny",
472 + "depart",
473 + "depend",
474 + "deposit",
475 + "depth",
476 + "deputy",
477 + "derive",
478 + "describe",
479 + "desert",
480 + "design",
481 + "desk",
482 + "despair",
483 + "destroy",
484 + "detail",
485 + "detect",
486 + "develop",
487 + "device",
488 + "devote",
489 + "diagram",
490 + "dial",
491 + "diamond",
492 + "diary",
493 + "dice",
494 + "diesel",
495 + "diet",
496 + "differ",
497 + "digital",
498 + "dignity",
499 + "dilemma",
500 + "dinner",
501 + "dinosaur",
502 + "direct",
503 + "dirt",
504 + "disagree",
505 + "discover",
506 + "disease",
507 + "dish",
508 + "dismiss",
509 + "disorder",
510 + "display",
511 + "distance",
512 + "divert",
513 + "divide",
514 + "divorce",
515 + "dizzy",
516 + "doctor",
517 + "document",
518 + "dog",
519 + "doll",
520 + "dolphin",
521 + "domain",
522 + "donate",
523 + "donkey",
524 + "donor",
525 + "door",
526 + "dose",
527 + "double",
528 + "dove",
529 + "draft",
530 + "dragon",
531 + "drama",
532 + "drastic",
533 + "draw",
534 + "dream",
535 + "dress",
536 + "drift",
537 + "drill",
538 + "drink",
539 + "drip",
540 + "drive",
541 + "drop",
542 + "drum",
543 + "dry",
544 + "duck",
545 + "dumb",
546 + "dune",
547 + "during",
548 + "dust",
549 + "dutch",
550 + "duty",
551 + "dwarf",
552 + "dynamic",
553 + "eager",
554 + "eagle",
555 + "early",
556 + "earn",
557 + "earth",
558 + "easily",
559 + "east",
560 + "easy",
561 + "echo",
562 + "ecology",
563 + "economy",
564 + "edge",
565 + "edit",
566 + "educate",
567 + "effort",
568 + "egg",
569 + "eight",
570 + "either",
571 + "elbow",
572 + "elder",
573 + "electric",
574 + "elegant",
575 + "element",
576 + "elephant",
577 + "elevator",
578 + "elite",
579 + "else",
580 + "embark",
581 + "embody",
582 + "embrace",
583 + "emerge",
584 + "emotion",
585 + "employ",
586 + "empower",
587 + "empty",
588 + "enable",
589 + "enact",
590 + "end",
591 + "endless",
592 + "endorse",
593 + "enemy",
594 + "energy",
595 + "enforce",
596 + "engage",
597 + "engine",
598 + "enhance",
599 + "enjoy",
600 + "enlist",
601 + "enough",
602 + "enrich",
603 + "enroll",
604 + "ensure",
605 + "enter",
606 + "entire",
607 + "entry",
608 + "envelope",
609 + "episode",
610 + "equal",
611 + "equip",
612 + "era",
613 + "erase",
614 + "erode",
615 + "erosion",
616 + "error",
617 + "erupt",
618 + "escape",
619 + "essay",
620 + "essence",
621 + "estate",
622 + "eternal",
623 + "ethics",
624 + "evidence",
625 + "evil",
626 + "evoke",
627 + "evolve",
628 + "exact",
629 + "example",
630 + "excess",
631 + "exchange",
632 + "excite",
633 + "exclude",
634 + "excuse",
635 + "execute",
636 + "exercise",
637 + "exhaust",
638 + "exhibit",
639 + "exile",
640 + "exist",
641 + "exit",
642 + "exotic",
643 + "expand",
644 + "expect",
645 + "expire",
646 + "explain",
647 + "expose",
648 + "express",
649 + "extend",
650 + "extra",
651 + "eye",
652 + "eyebrow",
653 + "fabric",
654 + "face",
655 + "faculty",
656 + "fade",
657 + "faint",
658 + "faith",
659 + "fall",
660 + "false",
661 + "fame",
662 + "family",
663 + "famous",
664 + "fan",
665 + "fancy",
666 + "fantasy",
667 + "farm",
668 + "fashion",
669 + "fat",
670 + "fatal",
671 + "father",
672 + "fatigue",
673 + "fault",
674 + "favorite",
675 + "feature",
676 + "february",
677 + "federal",
678 + "fee",
679 + "feed",
680 + "feel",
681 + "female",
682 + "fence",
683 + "festival",
684 + "fetch",
685 + "fever",
686 + "few",
687 + "fiber",
688 + "fiction",
689 + "field",
690 + "figure",
691 + "file",
692 + "film",
693 + "filter",
694 + "final",
695 + "find",
696 + "fine",
697 + "finger",
698 + "finish",
699 + "fire",
700 + "firm",
701 + "first",
702 + "fiscal",
703 + "fish",
704 + "fit",
705 + "fitness",
706 + "fix",
707 + "flag",
708 + "flame",
709 + "flash",
710 + "flat",
711 + "flavor",
712 + "flee",
713 + "flight",
714 + "flip",
715 + "float",
716 + "flock",
717 + "floor",
718 + "flower",
719 + "fluid",
720 + "flush",
721 + "fly",
722 + "foam",
723 + "focus",
724 + "fog",
725 + "foil",
726 + "fold",
727 + "follow",
728 + "food",
729 + "foot",
730 + "force",
731 + "forest",
732 + "forget",
733 + "fork",
734 + "fortune",
735 + "forum",
736 + "forward",
737 + "fossil",
738 + "foster",
739 + "found",
740 + "fox",
741 + "fragile",
742 + "frame",
743 + "frequent",
744 + "fresh",
745 + "friend",
746 + "fringe",
747 + "frog",
748 + "front",
749 + "frost",
750 + "frown",
751 + "frozen",
752 + "fruit",
753 + "fuel",
754 + "fun",
755 + "funny",
756 + "furnace",
757 + "fury",
758 + "future",
759 + "gadget",
760 + "gain",
761 + "galaxy",
762 + "gallery",
763 + "game",
764 + "gap",
765 + "garage",
766 + "garbage",
767 + "garden",
768 + "garlic",
769 + "garment",
770 + "gas",
771 + "gasp",
772 + "gate",
773 + "gather",
774 + "gauge",
775 + "gaze",
776 + "general",
777 + "genius",
778 + "genre",
779 + "gentle",
780 + "genuine",
781 + "gesture",
782 + "ghost",
783 + "giant",
784 + "gift",
785 + "giggle",
786 + "ginger",
787 + "giraffe",
788 + "girl",
789 + "give",
790 + "glad",
791 + "glance",
792 + "glare",
793 + "glass",
794 + "glide",
795 + "glimpse",
796 + "globe",
797 + "gloom",
798 + "glory",
799 + "glove",
800 + "glow",
801 + "glue",
802 + "goat",
803 + "goddess",
804 + "gold",
805 + "good",
806 + "goose",
807 + "gorilla",
808 + "gospel",
809 + "gossip",
810 + "govern",
811 + "gown",
812 + "grab",
813 + "grace",
814 + "grain",
815 + "grant",
816 + "grape",
817 + "grass",
818 + "gravity",
819 + "great",
820 + "green",
821 + "grid",
822 + "grief",
823 + "grit",
824 + "grocery",
825 + "group",
826 + "grow",
827 + "grunt",
828 + "guard",
829 + "guess",
830 + "guide",
831 + "guilt",
832 + "guitar",
833 + "gun",
834 + "gym",
835 + "habit",
836 + "hair",
837 + "half",
838 + "hammer",
839 + "hamster",
840 + "hand",
841 + "happy",
842 + "harbor",
843 + "hard",
844 + "harsh",
845 + "harvest",
846 + "hat",
847 + "have",
848 + "hawk",
849 + "hazard",
850 + "head",
851 + "health",
852 + "heart",
853 + "heavy",
854 + "hedgehog",
855 + "height",
856 + "hello",
857 + "helmet",
858 + "help",
859 + "hen",
860 + "hero",
861 + "hidden",
862 + "high",
863 + "hill",
864 + "hint",
865 + "hip",
866 + "hire",
867 + "history",
868 + "hobby",
869 + "hockey",
870 + "hold",
871 + "hole",
872 + "holiday",
873 + "hollow",
874 + "home",
875 + "honey",
876 + "hood",
877 + "hope",
878 + "horn",
879 + "horror",
880 + "horse",
881 + "hospital",
882 + "host",
883 + "hotel",
884 + "hour",
885 + "hover",
886 + "hub",
887 + "huge",
888 + "human",
889 + "humble",
890 + "humor",
891 + "hundred",
892 + "hungry",
893 + "hunt",
894 + "hurdle",
895 + "hurry",
896 + "hurt",
897 + "husband",
898 + "hybrid",
899 + "ice",
900 + "icon",
901 + "idea",
902 + "identify",
903 + "idle",
904 + "ignore",
905 + "ill",
906 + "illegal",
907 + "illness",
908 + "image",
909 + "imitate",
910 + "immense",
911 + "immune",
912 + "impact",
913 + "impose",
914 + "improve",
915 + "impulse",
916 + "inch",
917 + "include",
918 + "income",
919 + "increase",
920 + "index",
921 + "indicate",
922 + "indoor",
923 + "industry",
924 + "infant",
925 + "inflict",
926 + "inform",
927 + "inhale",
928 + "inherit",
929 + "initial",
930 + "inject",
931 + "injury",
932 + "inmate",
933 + "inner",
934 + "innocent",
935 + "input",
936 + "inquiry",
937 + "insane",
938 + "insect",
939 + "inside",
940 + "inspire",
941 + "install",
942 + "intact",
943 + "interest",
944 + "into",
945 + "invest",
946 + "invite",
947 + "involve",
948 + "iron",
949 + "island",
950 + "isolate",
951 + "issue",
952 + "item",
953 + "ivory",
954 + "jacket",
955 + "jaguar",
956 + "jar",
957 + "jazz",
958 + "jealous",
959 + "jeans",
960 + "jelly",
961 + "jewel",
962 + "job",
963 + "join",
964 + "joke",
965 + "journey",
966 + "joy",
967 + "judge",
968 + "juice",
969 + "jump",
970 + "jungle",
971 + "junior",
972 + "junk",
973 + "just",
974 + "kangaroo",
975 + "keen",
976 + "keep",
977 + "ketchup",
978 + "key",
979 + "kick",
980 + "kid",
981 + "kidney",
982 + "kind",
983 + "kingdom",
984 + "kiss",
985 + "kit",
986 + "kitchen",
987 + "kite",
988 + "kitten",
989 + "kiwi",
990 + "knee",
991 + "knife",
992 + "knock",
993 + "know",
994 + "lab",
995 + "label",
996 + "labor",
997 + "ladder",
998 + "lady",
999 + "lake",
1000 + "lamp",
1001 + "language",
1002 + "laptop",
1003 + "large",
1004 + "later",
1005 + "latin",
1006 + "laugh",
1007 + "laundry",
1008 + "lava",
1009 + "law",
1010 + "lawn",
1011 + "lawsuit",
1012 + "layer",
1013 + "lazy",
1014 + "leader",
1015 + "leaf",
1016 + "learn",
1017 + "leave",
1018 + "lecture",
1019 + "left",
1020 + "leg",
1021 + "legal",
1022 + "legend",
1023 + "leisure",
1024 + "lemon",
1025 + "lend",
1026 + "length",
1027 + "lens",
1028 + "leopard",
1029 + "lesson",
1030 + "letter",
1031 + "level",
1032 + "liar",
1033 + "liberty",
1034 + "library",
1035 + "license",
1036 + "life",
1037 + "lift",
1038 + "light",
1039 + "like",
1040 + "limb",
1041 + "limit",
1042 + "link",
1043 + "lion",
1044 + "liquid",
1045 + "list",
1046 + "little",
1047 + "live",
1048 + "lizard",
1049 + "load",
1050 + "loan",
1051 + "lobster",
1052 + "local",
1053 + "lock",
1054 + "logic",
1055 + "lonely",
1056 + "long",
1057 + "loop",
1058 + "lottery",
1059 + "loud",
1060 + "lounge",
1061 + "love",
1062 + "loyal",
1063 + "lucky",
1064 + "luggage",
1065 + "lumber",
1066 + "lunar",
1067 + "lunch",
1068 + "luxury",
1069 + "lyrics",
1070 + "machine",
1071 + "mad",
1072 + "magic",
1073 + "magnet",
1074 + "maid",
1075 + "mail",
1076 + "main",
1077 + "major",
1078 + "make",
1079 + "mammal",
1080 + "man",
1081 + "manage",
1082 + "mandate",
1083 + "mango",
1084 + "mansion",
1085 + "manual",
1086 + "maple",
1087 + "marble",
1088 + "march",
1089 + "margin",
1090 + "marine",
1091 + "market",
1092 + "marriage",
1093 + "mask",
1094 + "mass",
1095 + "master",
1096 + "match",
1097 + "material",
1098 + "math",
1099 + "matrix",
1100 + "matter",
1101 + "maximum",
1102 + "maze",
1103 + "meadow",
1104 + "mean",
1105 + "measure",
1106 + "meat",
1107 + "mechanic",
1108 + "medal",
1109 + "media",
1110 + "melody",
1111 + "melt",
1112 + "member",
1113 + "memory",
1114 + "mention",
1115 + "menu",
1116 + "mercy",
1117 + "merge",
1118 + "merit",
1119 + "merry",
1120 + "mesh",
1121 + "message",
1122 + "metal",
1123 + "method",
1124 + "middle",
1125 + "midnight",
1126 + "milk",
1127 + "million",
1128 + "mimic",
1129 + "mind",
1130 + "minimum",
1131 + "minor",
1132 + "minute",
1133 + "miracle",
1134 + "mirror",
1135 + "misery",
1136 + "miss",
1137 + "mistake",
1138 + "mix",
1139 + "mixed",
1140 + "mixture",
1141 + "mobile",
1142 + "model",
1143 + "modify",
1144 + "mom",
1145 + "moment",
1146 + "monitor",
1147 + "monkey",
1148 + "monster",
1149 + "month",
1150 + "moon",
1151 + "moral",
1152 + "more",
1153 + "morning",
1154 + "mosquito",
1155 + "mother",
1156 + "motion",
1157 + "motor",
1158 + "mountain",
1159 + "mouse",
1160 + "move",
1161 + "movie",
1162 + "much",
1163 + "muffin",
1164 + "mule",
1165 + "multiply",
1166 + "muscle",
1167 + "museum",
1168 + "mushroom",
1169 + "music",
1170 + "must",
1171 + "mutual",
1172 + "myself",
1173 + "mystery",
1174 + "myth",
1175 + "naive",
1176 + "name",
1177 + "napkin",
1178 + "narrow",
1179 + "nasty",
1180 + "nation",
1181 + "nature",
1182 + "near",
1183 + "neck",
1184 + "need",
1185 + "negative",
1186 + "neglect",
1187 + "neither",
1188 + "nephew",
1189 + "nerve",
1190 + "nest",
1191 + "net",
1192 + "network",
1193 + "neutral",
1194 + "never",
1195 + "news",
1196 + "next",
1197 + "nice",
1198 + "night",
1199 + "noble",
1200 + "noise",
1201 + "nominee",
1202 + "noodle",
1203 + "normal",
1204 + "north",
1205 + "nose",
1206 + "notable",
1207 + "note",
1208 + "nothing",
1209 + "notice",
1210 + "novel",
1211 + "now",
1212 + "nuclear",
1213 + "number",
1214 + "nurse",
1215 + "nut",
1216 + "oak",
1217 + "obey",
1218 + "object",
1219 + "oblige",
1220 + "obscure",
1221 + "observe",
1222 + "obtain",
1223 + "obvious",
1224 + "occur",
1225 + "ocean",
1226 + "october",
1227 + "odor",
1228 + "off",
1229 + "offer",
1230 + "office",
1231 + "often",
1232 + "oil",
1233 + "okay",
1234 + "old",
1235 + "olive",
1236 + "olympic",
1237 + "omit",
1238 + "once",
1239 + "one",
1240 + "onion",
1241 + "online",
1242 + "only",
1243 + "open",
1244 + "opera",
1245 + "opinion",
1246 + "oppose",
1247 + "option",
1248 + "orange",
1249 + "orbit",
1250 + "orchard",
1251 + "order",
1252 + "ordinary",
1253 + "organ",
1254 + "orient",
1255 + "original",
1256 + "orphan",
1257 + "ostrich",
1258 + "other",
1259 + "outdoor",
1260 + "outer",
1261 + "output",
1262 + "outside",
1263 + "oval",
1264 + "oven",
1265 + "over",
1266 + "own",
1267 + "owner",
1268 + "oxygen",
1269 + "oyster",
1270 + "ozone",
1271 + "pact",
1272 + "paddle",
1273 + "page",
1274 + "pair",
1275 + "palace",
1276 + "palm",
1277 + "panda",
1278 + "panel",
1279 + "panic",
1280 + "panther",
1281 + "paper",
1282 + "parade",
1283 + "parent",
1284 + "park",
1285 + "parrot",
1286 + "party",
1287 + "pass",
1288 + "patch",
1289 + "path",
1290 + "patient",
1291 + "patrol",
1292 + "pattern",
1293 + "pause",
1294 + "pave",
1295 + "payment",
1296 + "peace",
1297 + "peanut",
1298 + "pear",
1299 + "peasant",
1300 + "pelican",
1301 + "pen",
1302 + "penalty",
1303 + "pencil",
1304 + "people",
1305 + "pepper",
1306 + "perfect",
1307 + "permit",
1308 + "person",
1309 + "pet",
1310 + "phone",
1311 + "photo",
1312 + "phrase",
1313 + "physical",
1314 + "piano",
1315 + "picnic",
1316 + "picture",
1317 + "piece",
1318 + "pig",
1319 + "pigeon",
1320 + "pill",
1321 + "pilot",
1322 + "pink",
1323 + "pioneer",
1324 + "pipe",
1325 + "pistol",
1326 + "pitch",
1327 + "pizza",
1328 + "place",
1329 + "planet",
1330 + "plastic",
1331 + "plate",
1332 + "play",
1333 + "please",
1334 + "pledge",
1335 + "pluck",
1336 + "plug",
1337 + "plunge",
1338 + "poem",
1339 + "poet",
1340 + "point",
1341 + "polar",
1342 + "pole",
1343 + "police",
1344 + "pond",
1345 + "pony",
1346 + "pool",
1347 + "popular",
1348 + "portion",
1349 + "position",
1350 + "possible",
1351 + "post",
1352 + "potato",
1353 + "pottery",
1354 + "poverty",
1355 + "powder",
1356 + "power",
1357 + "practice",
1358 + "praise",
1359 + "predict",
1360 + "prefer",
1361 + "prepare",
1362 + "present",
1363 + "pretty",
1364 + "prevent",
1365 + "price",
1366 + "pride",
1367 + "primary",
1368 + "print",
1369 + "priority",
1370 + "prison",
1371 + "private",
1372 + "prize",
1373 + "problem",
1374 + "process",
1375 + "produce",
1376 + "profit",
1377 + "program",
1378 + "project",
1379 + "promote",
1380 + "proof",
1381 + "property",
1382 + "prosper",
1383 + "protect",
1384 + "proud",
1385 + "provide",
1386 + "public",
1387 + "pudding",
1388 + "pull",
1389 + "pulp",
1390 + "pulse",
1391 + "pumpkin",
1392 + "punch",
1393 + "pupil",
1394 + "puppy",
1395 + "purchase",
1396 + "purity",
1397 + "purpose",
1398 + "purse",
1399 + "push",
1400 + "put",
1401 + "puzzle",
1402 + "pyramid",
1403 + "quality",
1404 + "quantum",
1405 + "quarter",
1406 + "question",
1407 + "quick",
1408 + "quit",
1409 + "quiz",
1410 + "quote",
1411 + "rabbit",
1412 + "raccoon",
1413 + "race",
1414 + "rack",
1415 + "radar",
1416 + "radio",
1417 + "rail",
1418 + "rain",
1419 + "raise",
1420 + "rally",
1421 + "ramp",
1422 + "ranch",
1423 + "random",
1424 + "range",
1425 + "rapid",
1426 + "rare",
1427 + "rate",
1428 + "rather",
1429 + "raven",
1430 + "raw",
1431 + "razor",
1432 + "ready",
1433 + "real",
1434 + "reason",
1435 + "rebel",
1436 + "rebuild",
1437 + "recall",
1438 + "receive",
1439 + "recipe",
1440 + "record",
1441 + "recycle",
1442 + "reduce",
1443 + "reflect",
1444 + "reform",
1445 + "refuse",
1446 + "region",
1447 + "regret",
1448 + "regular",
1449 + "reject",
1450 + "relax",
1451 + "release",
1452 + "relief",
1453 + "rely",
1454 + "remain",
1455 + "remember",
1456 + "remind",
1457 + "remove",
1458 + "render",
1459 + "renew",
1460 + "rent",
1461 + "reopen",
1462 + "repair",
1463 + "repeat",
1464 + "replace",
1465 + "report",
1466 + "require",
1467 + "rescue",
1468 + "resemble",
1469 + "resist",
1470 + "resource",
1471 + "response",
1472 + "result",
1473 + "retire",
1474 + "retreat",
1475 + "return",
1476 + "reunion",
1477 + "reveal",
1478 + "review",
1479 + "reward",
1480 + "rhythm",
1481 + "rib",
1482 + "ribbon",
1483 + "rice",
1484 + "rich",
1485 + "ride",
1486 + "ridge",
1487 + "rifle",
1488 + "right",
1489 + "rigid",
1490 + "ring",
1491 + "riot",
1492 + "ripple",
1493 + "risk",
1494 + "ritual",
1495 + "rival",
1496 + "river",
1497 + "road",
1498 + "roast",
1499 + "robot",
1500 + "robust",
1501 + "rocket",
1502 + "romance",
1503 + "roof",
1504 + "rookie",
1505 + "room",
1506 + "rose",
1507 + "rotate",
1508 + "rough",
1509 + "round",
1510 + "route",
1511 + "royal",
1512 + "rubber",
1513 + "rude",
1514 + "rug",
1515 + "rule",
1516 + "run",
1517 + "runway",
1518 + "rural",
1519 + "sad",
1520 + "saddle",
1521 + "sadness",
1522 + "safe",
1523 + "sail",
1524 + "salad",
1525 + "salmon",
1526 + "salon",
1527 + "salt",
1528 + "salute",
1529 + "same",
1530 + "sample",
1531 + "sand",
1532 + "satisfy",
1533 + "satoshi",
1534 + "sauce",
1535 + "sausage",
1536 + "save",
1537 + "say",
1538 + "scale",
1539 + "scan",
1540 + "scare",
1541 + "scatter",
1542 + "scene",
1543 + "scheme",
1544 + "school",
1545 + "science",
1546 + "scissors",
1547 + "scorpion",
1548 + "scout",
1549 + "scrap",
1550 + "screen",
1551 + "script",
1552 + "scrub",
1553 + "sea",
1554 + "search",
1555 + "season",
1556 + "seat",
1557 + "second",
1558 + "secret",
1559 + "section",
1560 + "security",
1561 + "seed",
1562 + "seek",
1563 + "segment",
1564 + "select",
1565 + "sell",
1566 + "seminar",
1567 + "senior",
1568 + "sense",
1569 + "sentence",
1570 + "series",
1571 + "service",
1572 + "session",
1573 + "settle",
1574 + "setup",
1575 + "seven",
1576 + "shadow",
1577 + "shaft",
1578 + "shallow",
1579 + "share",
1580 + "shed",
1581 + "shell",
1582 + "sheriff",
1583 + "shield",
1584 + "shift",
1585 + "shine",
1586 + "ship",
1587 + "shiver",
1588 + "shock",
1589 + "shoe",
1590 + "shoot",
1591 + "shop",
1592 + "short",
1593 + "shoulder",
1594 + "shove",
1595 + "shrimp",
1596 + "shrug",
1597 + "shuffle",
1598 + "shy",
1599 + "sibling",
1600 + "sick",
1601 + "side",
1602 + "siege",
1603 + "sight",
1604 + "sign",
1605 + "silent",
1606 + "silk",
1607 + "silly",
1608 + "silver",
1609 + "similar",
1610 + "simple",
1611 + "since",
1612 + "sing",
1613 + "siren",
1614 + "sister",
1615 + "situate",
1616 + "six",
1617 + "size",
1618 + "skate",
1619 + "sketch",
1620 + "ski",
1621 + "skill",
1622 + "skin",
1623 + "skirt",
1624 + "skull",
1625 + "slab",
1626 + "slam",
1627 + "sleep",
1628 + "slender",
1629 + "slice",
1630 + "slide",
1631 + "slight",
1632 + "slim",
1633 + "slogan",
1634 + "slot",
1635 + "slow",
1636 + "slush",
1637 + "small",
1638 + "smart",
1639 + "smile",
1640 + "smoke",
1641 + "smooth",
1642 + "snack",
1643 + "snake",
1644 + "snap",
1645 + "sniff",
1646 + "snow",
1647 + "soap",
1648 + "soccer",
1649 + "social",
1650 + "sock",
1651 + "soda",
1652 + "soft",
1653 + "solar",
1654 + "soldier",
1655 + "solid",
1656 + "solution",
1657 + "solve",
1658 + "someone",
1659 + "song",
1660 + "soon",
1661 + "sorry",
1662 + "sort",
1663 + "soul",
1664 + "sound",
1665 + "soup",
1666 + "source",
1667 + "south",
1668 + "space",
1669 + "spare",
1670 + "spatial",
1671 + "spawn",
1672 + "speak",
1673 + "special",
1674 + "speed",
1675 + "spell",
1676 + "spend",
1677 + "sphere",
1678 + "spice",
1679 + "spider",
1680 + "spike",
1681 + "spin",
1682 + "spirit",
1683 + "split",
1684 + "spoil",
1685 + "sponsor",
1686 + "spoon",
1687 + "sport",
1688 + "spot",
1689 + "spray",
1690 + "spread",
1691 + "spring",
1692 + "spy",
1693 + "square",
1694 + "squeeze",
1695 + "squirrel",
1696 + "stable",
1697 + "stadium",
1698 + "staff",
1699 + "stage",
1700 + "stairs",
1701 + "stamp",
1702 + "stand",
1703 + "start",
1704 + "state",
1705 + "stay",
1706 + "steak",
1707 + "steel",
1708 + "stem",
1709 + "step",
1710 + "stereo",
1711 + "stick",
1712 + "still",
1713 + "sting",
1714 + "stock",
1715 + "stomach",
1716 + "stone",
1717 + "stool",
1718 + "story",
1719 + "stove",
1720 + "strategy",
1721 + "street",
1722 + "strike",
1723 + "strong",
1724 + "struggle",
1725 + "student",
1726 + "stuff",
1727 + "stumble",
1728 + "style",
1729 + "subject",
1730 + "submit",
1731 + "subway",
1732 + "success",
1733 + "such",
1734 + "sudden",
1735 + "suffer",
1736 + "sugar",
1737 + "suggest",
1738 + "suit",
1739 + "summer",
1740 + "sun",
1741 + "sunny",
1742 + "sunset",
1743 + "super",
1744 + "supply",
1745 + "supreme",
1746 + "sure",
1747 + "surface",
1748 + "surge",
1749 + "surprise",
1750 + "surround",
1751 + "survey",
1752 + "suspect",
1753 + "sustain",
1754 + "swallow",
1755 + "swamp",
1756 + "swap",
1757 + "swarm",
1758 + "swear",
1759 + "sweet",
1760 + "swift",
1761 + "swim",
1762 + "swing",
1763 + "switch",
1764 + "sword",
1765 + "symbol",
1766 + "symptom",
1767 + "syrup",
1768 + "system",
1769 + "table",
1770 + "tackle",
1771 + "tag",
1772 + "tail",
1773 + "talent",
1774 + "talk",
1775 + "tank",
1776 + "tape",
1777 + "target",
1778 + "task",
1779 + "taste",
1780 + "tattoo",
1781 + "taxi",
1782 + "teach",
1783 + "team",
1784 + "tell",
1785 + "ten",
1786 + "tenant",
1787 + "tennis",
1788 + "tent",
1789 + "term",
1790 + "test",
1791 + "text",
1792 + "thank",
1793 + "that",
1794 + "theme",
1795 + "then",
1796 + "theory",
1797 + "there",
1798 + "they",
1799 + "thing",
1800 + "this",
1801 + "thought",
1802 + "three",
1803 + "thrive",
1804 + "throw",
1805 + "thumb",
1806 + "thunder",
1807 + "ticket",
1808 + "tide",
1809 + "tiger",
1810 + "tilt",
1811 + "timber",
1812 + "time",
1813 + "tiny",
1814 + "tip",
1815 + "tired",
1816 + "tissue",
1817 + "title",
1818 + "toast",
1819 + "tobacco",
1820 + "today",
1821 + "toddler",
1822 + "toe",
1823 + "together",
1824 + "toilet",
1825 + "token",
1826 + "tomato",
1827 + "tomorrow",
1828 + "tone",
1829 + "tongue",
1830 + "tonight",
1831 + "tool",
1832 + "tooth",
1833 + "top",
1834 + "topic",
1835 + "topple",
1836 + "torch",
1837 + "tornado",
1838 + "tortoise",
1839 + "toss",
1840 + "total",
1841 + "tourist",
1842 + "toward",
1843 + "tower",
1844 + "town",
1845 + "toy",
1846 + "track",
1847 + "trade",
1848 + "traffic",
1849 + "tragic",
1850 + "train",
1851 + "transfer",
1852 + "trap",
1853 + "trash",
1854 + "travel",
1855 + "tray",
1856 + "treat",
1857 + "tree",
1858 + "trend",
1859 + "trial",
1860 + "tribe",
1861 + "trick",
1862 + "trigger",
1863 + "trim",
1864 + "trip",
1865 + "trophy",
1866 + "trouble",
1867 + "truck",
1868 + "true",
1869 + "truly",
1870 + "trumpet",
1871 + "trust",
1872 + "truth",
1873 + "try",
1874 + "tube",
1875 + "tuition",
1876 + "tumble",
1877 + "tuna",
1878 + "tunnel",
1879 + "turkey",
1880 + "turn",
1881 + "turtle",
1882 + "twelve",
1883 + "twenty",
1884 + "twice",
1885 + "twin",
1886 + "twist",
1887 + "two",
1888 + "type",
1889 + "typical",
1890 + "ugly",
1891 + "umbrella",
1892 + "unable",
1893 + "unaware",
1894 + "uncle",
1895 + "uncover",
1896 + "under",
1897 + "undo",
1898 + "unfair",
1899 + "unfold",
1900 + "unhappy",
1901 + "uniform",
1902 + "unique",
1903 + "unit",
1904 + "universe",
1905 + "unknown",
1906 + "unlock",
1907 + "until",
1908 + "unusual",
1909 + "unveil",
1910 + "update",
1911 + "upgrade",
1912 + "uphold",
1913 + "upon",
1914 + "upper",
1915 + "upset",
1916 + "urban",
1917 + "urge",
1918 + "usage",
1919 + "use",
1920 + "used",
1921 + "useful",
1922 + "useless",
1923 + "usual",
1924 + "utility",
1925 + "vacant",
1926 + "vacuum",
1927 + "vague",
1928 + "valid",
1929 + "valley",
1930 + "valve",
1931 + "van",
1932 + "vanish",
1933 + "vapor",
1934 + "various",
1935 + "vast",
1936 + "vault",
1937 + "vehicle",
1938 + "velvet",
1939 + "vendor",
1940 + "venture",
1941 + "venue",
1942 + "verb",
1943 + "verify",
1944 + "version",
1945 + "very",
1946 + "vessel",
1947 + "veteran",
1948 + "viable",
1949 + "vibrant",
1950 + "vicious",
1951 + "victory",
1952 + "video",
1953 + "view",
1954 + "village",
1955 + "vintage",
1956 + "violin",
1957 + "virtual",
1958 + "virus",
1959 + "visa",
1960 + "visit",
1961 + "visual",
1962 + "vital",
1963 + "vivid",
1964 + "vocal",
1965 + "voice",
1966 + "void",
1967 + "volcano",
1968 + "volume",
1969 + "vote",
1970 + "voyage",
1971 + "wage",
1972 + "wagon",
1973 + "wait",
1974 + "walk",
1975 + "wall",
1976 + "walnut",
1977 + "want",
1978 + "warfare",
1979 + "warm",
1980 + "warrior",
1981 + "wash",
1982 + "wasp",
1983 + "waste",
1984 + "water",
1985 + "wave",
1986 + "way",
1987 + "wealth",
1988 + "weapon",
1989 + "wear",
1990 + "weasel",
1991 + "weather",
1992 + "web",
1993 + "wedding",
1994 + "weekend",
1995 + "weird",
1996 + "welcome",
1997 + "west",
1998 + "wet",
1999 + "whale",
2000 + "what",
2001 + "wheat",
2002 + "wheel",
2003 + "when",
2004 + "where",
2005 + "whip",
2006 + "whisper",
2007 + "wide",
2008 + "width",
2009 + "wife",
2010 + "wild",
2011 + "will",
2012 + "win",
2013 + "window",
2014 + "wine",
2015 + "wing",
2016 + "wink",
2017 + "winner",
2018 + "winter",
2019 + "wire",
2020 + "wisdom",
2021 + "wise",
2022 + "wish",
2023 + "witness",
2024 + "wolf",
2025 + "woman",
2026 + "wonder",
2027 + "wood",
2028 + "wool",
2029 + "word",
2030 + "work",
2031 + "world",
2032 + "worry",
2033 + "worth",
2034 + "wrap",
2035 + "wreck",
2036 + "wrestle",
2037 + "wrist",
2038 + "write",
2039 + "wrong",
2040 + "yard",
2041 + "year",
2042 + "yellow",
2043 + "you",
2044 + "young",
2045 + "youth",
2046 + "zebra",
2047 + "zero",
2048 + "zone",
2049 + "zoo",
2050 +];
cw_decred/lib/pending_transaction.dart new
+39
@@ -0,0 +1,39 @@
1 +import 'package:cw_core/pending_transaction.dart';
2 +import 'package:cw_decred/amount_format.dart';
3 +
4 +class DecredPendingTransaction with PendingTransaction {
5 + DecredPendingTransaction(
6 + {required this.txid,
7 + required this.amount,
8 + required this.fee,
9 + required this.rawHex,
10 + required this.send});
11 +
12 + final int amount;
13 + final int fee;
14 + final String txid;
15 + final String rawHex;
16 + final Future<void> Function() send;
17 +
18 + @override
19 + String get id => txid;
20 +
21 + @override
22 + String get amountFormatted => decredAmountToString(amount: amount);
23 +
24 + @override
25 + String get feeFormatted => decredAmountToString(amount: fee);
26 +
27 + @override
28 + String get hex => rawHex;
29 +
30 + @override
31 + Future<void> commit() async {
32 + return send();
33 + }
34 +
35 + @override
36 + Future<String?> commitUR() {
37 + throw UnimplementedError();
38 + }
39 +}
cw_decred/lib/transaction_credentials.dart new
+10
@@ -0,0 +1,10 @@
1 +import 'package:cw_decred/transaction_priority.dart';
2 +import 'package:cw_core/output_info.dart';
3 +
4 +class DecredTransactionCredentials {
5 + DecredTransactionCredentials(this.outputs, {required this.priority, this.feeRate});
6 +
7 + final List<OutputInfo> outputs;
8 + final DecredTransactionPriority? priority;
9 + final int? feeRate;
10 +}
cw_decred/lib/transaction_history.dart new
+31
@@ -0,0 +1,31 @@
1 +import 'package:mobx/mobx.dart';
2 +import 'package:cw_core/transaction_info.dart';
3 +import 'package:cw_core/transaction_history.dart';
4 +
5 +class DecredTransactionHistory extends TransactionHistoryBase<TransactionInfo> {
6 + DecredTransactionHistory() {
7 + transactions = ObservableMap<String, TransactionInfo>();
8 + }
9 +
10 + @override
11 + void addOne(TransactionInfo transaction) => transactions[transaction.id] = transaction;
12 +
13 + @override
14 + void addMany(Map<String, TransactionInfo> transactions) => this.transactions.addAll(transactions);
15 +
16 + @override
17 + Future<void> save() async {}
18 +
19 + // update returns true if a known transaction that is not pending was found.
20 + bool update(Map<String, TransactionInfo> txs) {
21 + var foundOldTx = false;
22 + txs.forEach((_, tx) {
23 + if (!this.transactions.containsKey(tx.id) || this.transactions[tx.id]!.isPending) {
24 + this.transactions[tx.id] = tx;
25 + } else {
26 + foundOldTx = true;
27 + }
28 + });
29 + return foundOldTx;
30 + }
31 +}
cw_decred/lib/transaction_info.dart new
+45
@@ -0,0 +1,45 @@
1 +import 'package:cw_core/transaction_direction.dart';
2 +import 'package:cw_core/transaction_info.dart';
3 +import 'package:cw_core/format_amount.dart';
4 +import 'package:cw_core/wallet_type.dart';
5 +import 'package:cw_decred/amount_format.dart';
6 +
7 +class DecredTransactionInfo extends TransactionInfo {
8 + DecredTransactionInfo({
9 + required String id,
10 + required int amount,
11 + required int fee,
12 + required TransactionDirection direction,
13 + required bool isPending,
14 + required DateTime date,
15 + required int height,
16 + required int confirmations,
17 + required String to,
18 + }) {
19 + this.id = id;
20 + this.amount = amount;
21 + this.fee = fee;
22 + this.height = height;
23 + this.direction = direction;
24 + this.date = date;
25 + this.isPending = isPending;
26 + this.confirmations = confirmations;
27 + this.to = to;
28 + }
29 +
30 + String? _fiatAmount;
31 +
32 + @override
33 + String amountFormatted() =>
34 + '${formatAmount(decredAmountToString(amount: amount))} ${walletTypeToCryptoCurrency(WalletType.decred).title}';
35 +
36 + @override
37 + String? feeFormatted() =>
38 + '${formatAmount(decredAmountToString(amount: fee ?? 0))} ${walletTypeToCryptoCurrency(WalletType.decred).title}';
39 +
40 + @override
41 + String fiatAmount() => _fiatAmount ?? '';
42 +
43 + @override
44 + void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
45 +}
cw_decred/lib/transaction_priority.dart new
+69
@@ -0,0 +1,69 @@
1 +import 'package:cw_core/transaction_priority.dart';
2 +
3 +class DecredTransactionPriority extends TransactionPriority {
4 + const DecredTransactionPriority({required String title, required int raw})
5 + : super(title: title, raw: raw);
6 +
7 + static const List<DecredTransactionPriority> all = [fast, medium, slow];
8 + static const DecredTransactionPriority slow = DecredTransactionPriority(title: 'Slow', raw: 0);
9 + static const DecredTransactionPriority medium =
10 + DecredTransactionPriority(title: 'Medium', raw: 1);
11 + static const DecredTransactionPriority fast = DecredTransactionPriority(title: 'Fast', raw: 2);
12 +
13 + static DecredTransactionPriority deserialize({required int raw}) {
14 + switch (raw) {
15 + case 0:
16 + return slow;
17 + case 1:
18 + return medium;
19 + case 2:
20 + return fast;
21 + default:
22 + throw Exception('Unexpected token: $raw for DecredTransactionPriority deserialize');
23 + }
24 + }
25 +
26 + String get units => 'atom';
27 +
28 + @override
29 + String toString() {
30 + var label = '';
31 +
32 + switch (this) {
33 + case DecredTransactionPriority.slow:
34 + label = 'Slow ~24hrs'; // '${S.current.transaction_priority_slow} ~24hrs';
35 + break;
36 + case DecredTransactionPriority.medium:
37 + label = 'Medium'; // S.current.transaction_priority_medium;
38 + break;
39 + case DecredTransactionPriority.fast:
40 + label = 'Fast'; // S.current.transaction_priority_fast;
41 + break;
42 + default:
43 + break;
44 + }
45 +
46 + return label;
47 + }
48 +
49 + String labelWithRate(int rate) => '${toString()} ($rate ${units}/byte)';
50 +}
51 +
52 +class FeeCache {
53 + int _feeRate;
54 + DateTime stamp;
55 + FeeCache(this._feeRate) : this.stamp = DateTime(0, 0, 0, 0, 0, 0, 0, 0);
56 +
57 + bool isOld() {
58 + return this.stamp.add(const Duration(minutes: 30)).isBefore(DateTime.now());
59 + }
60 +
61 + void update(int feeRate) {
62 + this._feeRate = feeRate;
63 + this.stamp = DateTime.now();
64 + }
65 +
66 + int feeRate() {
67 + return this._feeRate;
68 + }
69 +}
cw_decred/lib/wallet.dart new
+729
@@ -0,0 +1,729 @@
1 +import 'dart:async';
2 +import 'dart:convert';
3 +import 'dart:io';
4 +import 'package:cw_core/exceptions.dart';
5 +import 'package:cw_core/transaction_direction.dart';
6 +import 'package:cw_core/utils/print_verbose.dart';
7 +import 'package:cw_decred/pending_transaction.dart';
8 +import 'package:cw_decred/transaction_credentials.dart';
9 +import 'package:flutter/foundation.dart';
10 +import 'package:mobx/mobx.dart';
11 +import 'package:hive/hive.dart';
12 +
13 +import 'package:cw_decred/api/libdcrwallet.dart';
14 +import 'package:cw_decred/transaction_history.dart';
15 +import 'package:cw_decred/wallet_addresses.dart';
16 +import 'package:cw_decred/transaction_priority.dart';
17 +import 'package:cw_decred/wallet_service.dart';
18 +import 'package:cw_decred/balance.dart';
19 +import 'package:cw_decred/transaction_info.dart';
20 +import 'package:cw_core/crypto_currency.dart';
21 +import 'package:cw_core/wallet_info.dart';
22 +import 'package:cw_core/wallet_base.dart';
23 +import 'package:cw_core/transaction_priority.dart';
24 +import 'package:cw_core/pending_transaction.dart';
25 +import 'package:cw_core/pathForWallet.dart';
26 +import 'package:cw_core/sync_status.dart';
27 +import 'package:cw_core/node.dart';
28 +import 'package:cw_core/unspent_coins_info.dart';
29 +import 'package:cw_core/unspent_transaction_output.dart';
30 +
31 +part 'wallet.g.dart';
32 +
33 +class DecredWallet = DecredWalletBase with _$DecredWallet;
34 +
35 +abstract class DecredWalletBase
36 + extends WalletBase<DecredBalance, DecredTransactionHistory, DecredTransactionInfo> with Store {
37 + DecredWalletBase(WalletInfo walletInfo, String password, Box<UnspentCoinsInfo> unspentCoinsInfo,
38 + Libwallet libwallet, Function() closeLibwallet)
39 + : _password = password,
40 + _libwallet = libwallet,
41 + _closeLibwallet = closeLibwallet,
42 + this.syncStatus = NotConnectedSyncStatus(),
43 + this.unspentCoinsInfo = unspentCoinsInfo,
44 + this.watchingOnly =
45 + walletInfo.derivationInfo?.derivationPath == DecredWalletService.pubkeyRestorePath ||
46 + walletInfo.derivationInfo?.derivationPath ==
47 + DecredWalletService.pubkeyRestorePathTestnet,
48 + this.balance = ObservableMap.of({CryptoCurrency.dcr: DecredBalance.zero()}),
49 + this.isTestnet = walletInfo.derivationInfo?.derivationPath ==
50 + DecredWalletService.seedRestorePathTestnet ||
51 + walletInfo.derivationInfo?.derivationPath ==
52 + DecredWalletService.pubkeyRestorePathTestnet,
53 + super(walletInfo) {
54 + walletAddresses = DecredWalletAddresses(walletInfo, libwallet);
55 + transactionHistory = DecredTransactionHistory();
56 +
57 + reaction((_) => isEnabledAutoGenerateSubaddress, (bool enabled) {
58 + this.walletAddresses.isEnabledAutoGenerateSubaddress = enabled;
59 + });
60 + }
61 +
62 + // NOTE: Hitting this max fee would be unexpected with current on chain use
63 + // but this may need to be updated in the future.
64 + final maxFeeRate = 100000;
65 +
66 + // syncIntervalSyncing is used up until synced, then transactions are checked
67 + // every syncIntervalSynced.
68 + final syncIntervalSyncing = 5; // seconds
69 + final syncIntervalSynced = 30; // seconds
70 + static final defaultFeeRate = 10000;
71 + final String _password;
72 + final Libwallet _libwallet;
73 + final Function() _closeLibwallet;
74 + final idPrefix = "decred_";
75 +
76 + // TODO: Encrypt this.
77 + var _seed = "";
78 + var _pubkey = "";
79 + var _unspents = <Unspent>[];
80 +
81 + // synced is used to set the syncTimer interval.
82 + bool synced = false;
83 + bool watchingOnly;
84 + bool connecting = false;
85 + String persistantPeer = "default-spv-nodes";
86 + FeeCache feeRateFast = FeeCache(defaultFeeRate);
87 + FeeCache feeRateMedium = FeeCache(defaultFeeRate);
88 + FeeCache feeRateSlow = FeeCache(defaultFeeRate);
89 + Timer? syncTimer;
90 + Box<UnspentCoinsInfo> unspentCoinsInfo;
91 +
92 + @override
93 + @observable
94 + bool isEnabledAutoGenerateSubaddress = true;
95 +
96 + @override
97 + @observable
98 + SyncStatus syncStatus;
99 +
100 + @override
101 + @observable
102 + late ObservableMap<CryptoCurrency, DecredBalance> balance;
103 +
104 + @override
105 + late DecredWalletAddresses walletAddresses;
106 +
107 + @override
108 + String? get seed {
109 + if (watchingOnly) {
110 + return null;
111 + }
112 + return _seed;
113 + }
114 +
115 + @override
116 + Object get keys => {};
117 +
118 + @override
119 + bool isTestnet;
120 +
121 + String get pubkey {
122 + return _pubkey;
123 + }
124 +
125 + Future<void> init() async {
126 + final getSeed = () async {
127 + if (!watchingOnly) {
128 + _seed = await _libwallet.walletSeed(walletInfo.name, _password) ?? "";
129 + }
130 + _pubkey = await _libwallet.defaultPubkey(walletInfo.name);
131 + };
132 + await Future.wait([
133 + updateBalance(),
134 + updateTransactionHistory(),
135 + walletAddresses.init(),
136 + fetchTransactions(),
137 + updateFees(),
138 + fetchUnspents(),
139 + getSeed(),
140 + ]);
141 + }
142 +
143 + Future<void> performBackgroundTasks() async {
144 + if (!await checkSync()) {
145 + if (synced == true) {
146 + synced = false;
147 + if (syncTimer != null) {
148 + syncTimer!.cancel();
149 + }
150 + syncTimer = Timer.periodic(
151 + Duration(seconds: syncIntervalSyncing), (Timer t) => performBackgroundTasks());
152 + }
153 + return;
154 + }
155 + // Set sync check interval lower since we are synced.
156 + if (synced == false) {
157 + synced = true;
158 + if (syncTimer != null) {
159 + syncTimer!.cancel();
160 + }
161 + syncTimer = Timer.periodic(
162 + Duration(seconds: syncIntervalSynced), (Timer t) => performBackgroundTasks());
163 + }
164 + await Future.wait([
165 + updateTransactionHistory(),
166 + updateFees(),
167 + fetchUnspents(),
168 + updateBalance(),
169 + walletAddresses.updateAddressesInBox(),
170 + ]);
171 + }
172 +
173 + Future<void> updateFees() async {
174 + final feeForNb = (int nb) async {
175 + try {
176 + final feeStr = await _libwallet.estimateFee(walletInfo.name, nb);
177 + var fee = int.parse(feeStr);
178 + if (fee > maxFeeRate) {
179 + throw "dcr fee returned from estimate fee was over max";
180 + } else if (fee <= 0) {
181 + throw "dcr fee returned from estimate fee was zero";
182 + }
183 + return fee;
184 + } catch (e) {
185 + printV(e);
186 + return defaultFeeRate;
187 + }
188 + };
189 + if (feeRateSlow.isOld()) {
190 + feeRateSlow.update(await feeForNb(4));
191 + }
192 + if (feeRateMedium.isOld()) {
193 + feeRateMedium.update(await feeForNb(2));
194 + }
195 + if (feeRateFast.isOld()) {
196 + feeRateFast.update(await feeForNb(1));
197 + }
198 + }
199 +
200 + Future<void> updateTransactionHistory() async {
201 + // from is the number of transactions skipped from most recent, not block
202 + // height.
203 + var from = 0;
204 + while (true) {
205 + // Transactions are returned from newest to oldest. Loop fetching 5 txn
206 + // at a time until we find a batch with txn that no longer need to be
207 + // updated.
208 + final txs = await this.fetchFiveTransactions(from);
209 + if (txs.length == 0) {
210 + return;
211 + }
212 + if (this.transactionHistory.update(txs)) {
213 + return;
214 + }
215 + from += 5;
216 + }
217 + }
218 +
219 + Future<bool> checkSync() async {
220 + final syncStatusJSON = await _libwallet.syncStatus(walletInfo.name);
221 + final decoded = json.decode(syncStatusJSON);
222 +
223 + final syncStatusCode = decoded["syncstatuscode"] ?? 0;
224 + // final syncStatusStr = decoded["syncstatus"] ?? "";
225 + final targetHeight = decoded["targetheight"] ?? 1;
226 + final numPeers = decoded["numpeers"] ?? 0;
227 + // final cFiltersHeight = decoded["cfiltersheight"] ?? 0;
228 + final headersHeight = decoded["headersheight"] ?? 0;
229 + final rescanHeight = decoded["rescanheight"] ?? 0;
230 +
231 + if (numPeers == 0) {
232 + syncStatus = NotConnectedSyncStatus();
233 + return false;
234 + }
235 +
236 + // Sync codes:
237 + // NotStarted = 0
238 + // FetchingCFilters = 1
239 + // FetchingHeaders = 2
240 + // DiscoveringAddrs = 3
241 + // Rescanning = 4
242 + // Complete = 5
243 +
244 + if (syncStatusCode > 4) {
245 + syncStatus = SyncedSyncStatus();
246 + return true;
247 + }
248 +
249 + if (syncStatusCode == 0) {
250 + syncStatus = ConnectedSyncStatus();
251 + return false;
252 + }
253 +
254 + if (syncStatusCode == 1) {
255 + syncStatus = SyncingSyncStatus(targetHeight, 0.0);
256 + return false;
257 + }
258 +
259 + if (syncStatusCode == 2) {
260 + final headersProg = headersHeight / targetHeight;
261 + // Only allow headers progress to go up half way.
262 + syncStatus = SyncingSyncStatus(targetHeight - headersHeight, headersProg);
263 + return false;
264 + }
265 +
266 + // TODO: This step takes a while so should really get more info to the UI
267 + // that we are discovering addresses.
268 + if (syncStatusCode == 3) {
269 + // Hover at half.
270 + syncStatus = ProcessingSyncStatus();
271 + return false;
272 + }
273 +
274 + if (syncStatusCode == 4) {
275 + // Start at 75%.
276 + final rescanProg = rescanHeight / targetHeight / 4;
277 + syncStatus = SyncingSyncStatus(targetHeight - rescanHeight, .75 + rescanProg);
278 + return false;
279 + }
280 + return false;
281 + }
282 +
283 + @action
284 + @override
285 + Future<void> connectToNode({required Node node}) async {
286 + if (connecting) {
287 + return;
288 + }
289 + connecting = true;
290 + String addr = "default-spv-nodes";
291 + if (node.uri.host != addr) {
292 + addr = node.uri.host;
293 + if (node.uri.port != "") {
294 + addr += ":" + node.uri.port.toString();
295 + }
296 + }
297 + if (addr != persistantPeer) {
298 + if (syncTimer != null) {
299 + syncTimer!.cancel();
300 + syncTimer = null;
301 + }
302 + persistantPeer = addr;
303 + await _libwallet.closeWallet(walletInfo.name);
304 + final network = isTestnet ? "testnet" : "mainnet";
305 + final config = {
306 + "name": walletInfo.name,
307 + "datadir": walletInfo.dirPath,
308 + "net": network,
309 + "unsyncedaddrs": true,
310 + };
311 + await _libwallet.loadWallet(jsonEncode(config));
312 + }
313 + await this._startSync();
314 + connecting = false;
315 + }
316 +
317 + @action
318 + @override
319 + Future<void> startSync() async {
320 + if (connecting) {
321 + return;
322 + }
323 + connecting = true;
324 + await this._startSync();
325 + connecting = false;
326 + }
327 +
328 + Future<void> _startSync() async {
329 + if (syncTimer != null) {
330 + return;
331 + }
332 + try {
333 + syncStatus = ConnectingSyncStatus();
334 + await _libwallet.startSync(
335 + walletInfo.name,
336 + persistantPeer == "default-spv-nodes" ? "" : persistantPeer,
337 + );
338 + syncTimer = Timer.periodic(
339 + Duration(seconds: syncIntervalSyncing), (Timer t) => performBackgroundTasks());
340 + } catch (e) {
341 + printV(e.toString());
342 + syncStatus = FailedSyncStatus();
343 + }
344 + }
345 +
346 + @override
347 + Future<PendingTransaction> createTransaction(Object credentials) async {
348 + if (watchingOnly) {
349 + return DecredPendingTransaction(
350 + txid: "",
351 + amount: 0,
352 + fee: 0,
353 + rawHex: "",
354 + send: () async {
355 + throw "unable to send with watching only wallet";
356 + });
357 + }
358 + var totalIn = 0;
359 + final ignoreInputs = [];
360 + this.unspentCoinsInfo.values.forEach((unspent) {
361 + if (unspent.isFrozen || !unspent.isSending) {
362 + final input = {"txid": unspent.hash, "vout": unspent.vout};
363 + ignoreInputs.add(input);
364 + return;
365 + }
366 + totalIn += unspent.value;
367 + });
368 +
369 + final creds = credentials as DecredTransactionCredentials;
370 + var totalAmt = 0;
371 + var sendAll = false;
372 + final outputs = [];
373 + for (final out in creds.outputs) {
374 + var amt = 0;
375 + if (out.sendAll) {
376 + if (creds.outputs.length != 1) {
377 + throw "can only send all to one output";
378 + }
379 + sendAll = true;
380 + totalAmt = totalIn;
381 + } else if (out.cryptoAmount != null) {
382 + final coins = double.parse(out.cryptoAmount!);
383 + amt = (coins * 1e8).toInt();
384 + }
385 + totalAmt += amt;
386 + final o = {
387 + "address": out.isParsedAddress ? out.extractedAddress! : out.address,
388 + "amount": amt
389 + };
390 + outputs.add(o);
391 + }
392 +
393 + // throw exception if no selected coins under coin control
394 + // or if the total coins selected, is less than the amount the user wants to spend
395 + if (ignoreInputs.length == unspentCoinsInfo.values.length || totalIn < totalAmt) {
396 + throw TransactionNoInputsException();
397 + }
398 +
399 + // The inputs are always used. Currently we don't have use for this
400 + // argument. sendall ingores output value and sends everything.
401 + final signReq = {
402 + // "inputs": inputs,
403 + "ignoreInputs": ignoreInputs,
404 + "outputs": outputs,
405 + "feerate": creds.feeRate ?? defaultFeeRate,
406 + "password": _password,
407 + "sendall": sendAll,
408 + };
409 + final res = await _libwallet.createSignedTransaction(walletInfo.name, jsonEncode(signReq));
410 + final decoded = json.decode(res);
411 + final signedHex = decoded["signedhex"];
412 + final send = () async {
413 + await _libwallet.sendRawTransaction(walletInfo.name, signedHex);
414 + await updateBalance();
415 + };
416 + final fee = decoded["fee"] ?? 0;
417 + if (sendAll) {
418 + totalAmt = (totalAmt - fee).toInt();
419 + }
420 + return DecredPendingTransaction(
421 + txid: decoded["txid"] ?? "", amount: totalAmt, fee: fee, rawHex: signedHex, send: send);
422 + }
423 +
424 + int feeRate(TransactionPriority priority) {
425 + if (!(priority is DecredTransactionPriority)) {
426 + return defaultFeeRate;
427 + }
428 + final p = priority;
429 + switch (p) {
430 + case DecredTransactionPriority.slow:
431 + return feeRateSlow.feeRate();
432 + case DecredTransactionPriority.medium:
433 + return feeRateMedium.feeRate();
434 + case DecredTransactionPriority.fast:
435 + return feeRateFast.feeRate();
436 + }
437 + return defaultFeeRate;
438 + }
439 +
440 + @override
441 + int calculateEstimatedFee(TransactionPriority priority, int? amount) {
442 + if (priority is DecredTransactionPriority) {
443 + final P2PKHOutputSize =
444 + 36; // 8 bytes value + 2 bytes version + at least 1 byte varint script size + P2PKHPkScriptSize
445 + // MsgTxOverhead is 4 bytes version (lower 2 bytes for the real transaction
446 + // version and upper 2 bytes for the serialization type) + 4 bytes locktime
447 + // + 4 bytes expiry + 3 bytes of varints for the number of transaction
448 + // inputs (x2 for witness and prefix) and outputs
449 + final MsgTxOverhead = 15;
450 + // TxInOverhead is the overhead for a wire.TxIn with a scriptSig length <
451 + // 254. prefix (41 bytes) + ValueIn (8 bytes) + BlockHeight (4 bytes) +
452 + // BlockIndex (4 bytes) + sig script var int (at least 1 byte)
453 + final TxInOverhead = 57;
454 + final P2PKHInputSize =
455 + TxInOverhead + 109; // TxInOverhead (57) + var int (1) + P2PKHSigScriptSize (108)
456 +
457 + int inputsCount = 1;
458 + if (amount != null) {
459 + inputsCount += _unspents.where((e) {
460 + amount = (amount!) - e.value;
461 + return (amount!) > 0;
462 + }).length;
463 + }
464 +
465 + // Estimate using a transaction consuming inoutsCount and paying to one address with change.
466 + return (this.feeRate(priority) / 1000).round() *
467 + (MsgTxOverhead + P2PKHInputSize * inputsCount + P2PKHOutputSize * 2);
468 + }
469 + return 0;
470 + }
471 +
472 + @override
473 + Future<Map<String, DecredTransactionInfo>> fetchTransactions() async {
474 + return this.fetchFiveTransactions(0);
475 + }
476 +
477 + Future<Map<String, DecredTransactionInfo>> fetchFiveTransactions(int from) async {
478 + final res = await _libwallet.listTransactions(walletInfo.name, from.toString(), "5");
479 + final decoded = json.decode(res);
480 + var txs = <String, DecredTransactionInfo>{};
481 + for (final d in decoded) {
482 + final txid = uniqueTxID(d["txid"] ?? "", d["vout"] ?? 0);
483 + var direction = TransactionDirection.outgoing;
484 + if (d["category"] == "receive") {
485 + direction = TransactionDirection.incoming;
486 + }
487 + final amountDouble = d["amount"] ?? 0.0;
488 + final amount = (amountDouble * 1e8).toInt().abs();
489 + final feeDouble = d["fee"] ?? 0.0;
490 + final fee = (feeDouble * 1e8).toInt().abs();
491 + final confs = d["confirmations"] ?? 0;
492 + final sendTime = d["time"] ?? 0;
493 + final height = d["height"] ?? 0;
494 + final txInfo = DecredTransactionInfo(
495 + id: txid,
496 + amount: amount,
497 + fee: fee,
498 + direction: direction,
499 + isPending: confs == 0,
500 + date: DateTime.fromMillisecondsSinceEpoch(sendTime * 1000, isUtc: false),
501 + height: height,
502 + confirmations: confs,
503 + to: d["address"] ?? "",
504 + );
505 + txs[txid] = txInfo;
506 + }
507 + return txs;
508 + }
509 +
510 + // uniqueTxID combines the tx id and vout to create a unique id.
511 + String uniqueTxID(String id, int vout) {
512 + return id + ":" + vout.toString();
513 + }
514 +
515 + @override
516 + Future<void> save() async {}
517 +
518 + @override
519 + bool get hasRescan => walletBirthdayBlockHeight() != -1;
520 +
521 + @override
522 + Future<void> rescan({required int height}) async {
523 + // The required height is not used. A birthday time is recorded in the
524 + // mnemonic. As long as not private data is imported into the wallet, we
525 + // can always rescan from there.
526 + var rescanHeight = 0;
527 + if (!watchingOnly) {
528 + rescanHeight = await walletBirthdayBlockHeight();
529 + // Sync has not yet reached the birthday block.
530 + if (rescanHeight == -1) {
531 + return;
532 + }
533 + }
534 + await _libwallet.rescanFromHeight(walletInfo.name, rescanHeight.toString());
535 + }
536 +
537 + @override
538 + Future<void> close({bool shouldCleanup = false}) async {
539 + if (syncTimer != null) {
540 + syncTimer!.cancel();
541 + syncTimer = null;
542 + }
543 + await _libwallet.closeWallet(walletInfo.name);
544 + if (shouldCleanup) {
545 + await _libwallet.shutdown();
546 + _closeLibwallet();
547 + }
548 + }
549 +
550 + @override
551 + Future<void> changePassword(String password) async {
552 + if (watchingOnly) {
553 + return;
554 + }
555 + return () async {
556 + await _libwallet.changeWalletPassword(walletInfo.name, _password, password);
557 + }();
558 + }
559 +
560 + @override
561 + Future<void> updateBalance() async {
562 + final balanceMap = await _libwallet.balance(walletInfo.name);
563 +
564 + var totalFrozen = 0;
565 +
566 + unspentCoinsInfo.values.forEach((info) {
567 + _unspents.forEach((element) {
568 + if (element.hash == info.hash &&
569 + element.vout == info.vout &&
570 + info.isFrozen &&
571 + element.value == info.value) {
572 + totalFrozen += element.value;
573 + }
574 + });
575 + });
576 +
577 + balance[CryptoCurrency.dcr] = DecredBalance(
578 + confirmed: balanceMap["confirmed"] ?? 0,
579 + unconfirmed: balanceMap["unconfirmed"] ?? 0,
580 + frozen: totalFrozen,
581 + );
582 + }
583 +
584 + @override
585 + void setExceptionHandler(void Function(FlutterErrorDetails) onError) => onError;
586 +
587 + Future<void> renameWalletFiles(String newWalletName) async {
588 + final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
589 +
590 + final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
591 +
592 + if (File(newDirPath).existsSync()) {
593 + throw "wallet already exists at $newDirPath";
594 + }
595 +
596 + await Directory(currentDirPath).rename(newDirPath);
597 + }
598 +
599 + @override
600 + Future<String> signMessage(String message, {String? address = null}) async {
601 + if (watchingOnly) {
602 + throw "a watching only wallet cannot sign";
603 + }
604 + var addr = address;
605 + if (addr == null) {
606 + addr = walletAddresses.address;
607 + }
608 + if (addr == "") {
609 + throw "unable to get an address from unsynced wallet";
610 + }
611 + return await _libwallet.signMessage(walletInfo.name, message, addr, _password);
612 + }
613 +
614 + Future<void> fetchUnspents() async {
615 + final res = await _libwallet.listUnspents(walletInfo.name);
616 + final decoded = json.decode(res);
617 + var unspents = <Unspent>[];
618 + for (final d in decoded) {
619 + final spendable = d["spendable"] ?? false;
620 + if (!spendable) {
621 + continue;
622 + }
623 + final amountDouble = d["amount"] ?? 0.0;
624 + final amount = (amountDouble * 1e8).toInt().abs();
625 + final utxo = Unspent(d["address"] ?? "", d["txid"] ?? "", amount, d["vout"] ?? 0, null);
626 + utxo.isChange = d["ischange"] ?? false;
627 + unspents.add(utxo);
628 + }
629 + _unspents = unspents;
630 + }
631 +
632 + List<Unspent> unspents() {
633 + this.updateUnspents(_unspents);
634 + return _unspents;
635 + }
636 +
637 + void updateUnspents(List<Unspent> unspentCoins) {
638 + if (this.unspentCoinsInfo.isEmpty) {
639 + unspentCoins.forEach((coin) => this.addCoinInfo(coin));
640 + return;
641 + }
642 +
643 + if (unspentCoins.isEmpty) {
644 + this.unspentCoinsInfo.clear();
645 + return;
646 + }
647 +
648 + final walletID = idPrefix + walletInfo.name;
649 + if (unspentCoins.isNotEmpty) {
650 + unspentCoins.forEach((coin) {
651 + final coinInfoList = this.unspentCoinsInfo.values.where((element) =>
652 + element.walletId == walletID && element.hash == coin.hash && element.vout == coin.vout);
653 +
654 + if (coinInfoList.isEmpty) {
655 + this.addCoinInfo(coin);
656 + } else {
657 + final coinInfo = coinInfoList.first;
658 +
659 + coin.isFrozen = coinInfo.isFrozen;
660 + coin.isSending = coinInfo.isSending;
661 + coin.note = coinInfo.note;
662 + }
663 + });
664 + }
665 +
666 + final List<dynamic> keys = <dynamic>[];
667 + this.unspentCoinsInfo.values.forEach((element) {
668 + final existUnspentCoins = unspentCoins.where((coin) => element.hash.contains(coin.hash));
669 +
670 + if (existUnspentCoins.isEmpty) {
671 + keys.add(element.key);
672 + }
673 + });
674 +
675 + if (keys.isNotEmpty) {
676 + unspentCoinsInfo.deleteAll(keys);
677 + }
678 + }
679 +
680 + void addCoinInfo(Unspent coin) {
681 + final newInfo = UnspentCoinsInfo(
682 + walletId: idPrefix + walletInfo.name,
683 + hash: coin.hash,
684 + isFrozen: false,
685 + isSending: coin.isSending,
686 + noteRaw: "",
687 + address: coin.address,
688 + value: coin.value,
689 + vout: coin.vout,
690 + isChange: coin.isChange,
691 + keyImage: coin.keyImage,
692 + );
693 +
694 + unspentCoinsInfo.add(newInfo);
695 + }
696 +
697 + // walletBirthdayBlockHeight checks if the wallet birthday is set and returns
698 + // it. Returns -1 if not.
699 + Future<int> walletBirthdayBlockHeight() async {
700 + final res = await _libwallet.birthState(walletInfo.name);
701 + final decoded = json.decode(res);
702 + // Having these values set indicates that sync has not reached the birthday
703 + // yet, so no birthday is set.
704 + if (decoded["setfromheight"] == true || decoded["setfromtime"] == true) {
705 + return -1;
706 + }
707 + return decoded["height"] ?? 0;
708 + }
709 +
710 + Future<bool> verifyMessage(String message, String signature, {String? address = null}) async {
711 + var addr = address;
712 + if (addr == null) {
713 + throw "an address is required to verify message";
714 + }
715 + return () async {
716 + final verified = await _libwallet.verifyMessage(walletInfo.name, message, addr, signature);
717 + if (verified == "true") {
718 + return true;
719 + }
720 + return false;
721 + }();
722 + }
723 +
724 + @override
725 + String get password => _password;
726 +
727 + @override
728 + bool canSend() => seed != null;
729 +}
cw_decred/lib/wallet_addresses.dart new
+137
@@ -0,0 +1,137 @@
1 +import 'dart:convert';
2 +import 'package:mobx/mobx.dart';
3 +
4 +import 'package:cw_core/address_info.dart';
5 +import 'package:cw_core/wallet_addresses.dart';
6 +import 'package:cw_core/wallet_info.dart';
7 +import 'package:cw_decred/api/libdcrwallet.dart';
8 +
9 +part 'wallet_addresses.g.dart';
10 +
11 +class DecredWalletAddresses = DecredWalletAddressesBase with _$DecredWalletAddresses;
12 +
13 +abstract class DecredWalletAddressesBase extends WalletAddresses with Store {
14 + DecredWalletAddressesBase(WalletInfo walletInfo, Libwallet libwallet)
15 + : _libwallet = libwallet,
16 + super(walletInfo);
17 + final Libwallet _libwallet;
18 + String currentAddr = '';
19 +
20 + @observable
21 + bool isEnabledAutoGenerateSubaddress = true;
22 +
23 + @observable
24 + String selectedAddr = '';
25 +
26 + @override
27 + @computed
28 + String get address {
29 + return selectedAddr;
30 + }
31 +
32 + @override
33 + set address(value) {
34 + selectedAddr = value;
35 + }
36 +
37 + @override
38 + Future<void> init() async {
39 + if (walletInfo.addresses != null) {
40 + addressesMap = walletInfo.addresses!;
41 + }
42 + if (walletInfo.addressInfos != null) {
43 + addressInfos = walletInfo.addressInfos!;
44 + }
45 + if (walletInfo.usedAddresses != null) {
46 + usedAddresses = {...walletInfo.usedAddresses!};
47 + }
48 + await updateAddressesInBox();
49 + }
50 +
51 + @override
52 + Future<void> updateAddressesInBox() async {
53 + final addrs = await libAddresses();
54 + final allAddrs = new List.from(addrs.usedAddrs)..addAll(addrs.unusedAddrs);
55 +
56 + // Add all addresses.
57 + allAddrs.forEach((addr) {
58 + if (addressesMap.containsKey(addr)) {
59 + return;
60 + }
61 + addressesMap[addr] = "";
62 + addressInfos[0] ??= [];
63 + addressInfos[0]?.add(AddressInfo(address: addr, label: "", accountIndex: 0));
64 + });
65 +
66 + // Add used addresses.
67 + addrs.usedAddrs.forEach((addr) {
68 + if (!usedAddresses.contains(addr)) {
69 + usedAddresses.add(addr);
70 + }
71 + });
72 +
73 + if (addrs.unusedAddrs.length > 0 && addrs.unusedAddrs[0] != currentAddr) {
74 + currentAddr = addrs.unusedAddrs[0];
75 + selectedAddr = currentAddr;
76 + }
77 +
78 + await saveAddressesInBox();
79 + }
80 +
81 + List<AddressInfo> getAddressInfos() {
82 + if (addressInfos.containsKey(0)) {
83 + return addressInfos[0]!;
84 + }
85 + return <AddressInfo>[];
86 + }
87 +
88 + Future<void> updateAddress(String address, String label) async {
89 + if (!addressInfos.containsKey(0)) {
90 + return;
91 + }
92 + addressInfos[0]!.forEach((info) {
93 + if (info.address == address) {
94 + info.label = label;
95 + }
96 + });
97 + await saveAddressesInBox();
98 + }
99 +
100 + Future<LibAddresses> libAddresses() async {
101 + final nUsed = "10";
102 + var nUnused = "1";
103 + if (this.isEnabledAutoGenerateSubaddress) {
104 + nUnused = "3";
105 + }
106 + final res = await _libwallet.addresses(walletInfo.name, nUsed, nUnused);
107 + final decoded = json.decode(res);
108 + final usedAddrs = List<String>.from(decoded["used"] ?? []);
109 + final unusedAddrs = List<String>.from(decoded["unused"] ?? []);
110 + // index is the index of the first unused address.
111 + final index = decoded["index"] ?? 0;
112 + return new LibAddresses(usedAddrs, unusedAddrs, index);
113 + }
114 +
115 + Future<void> generateNewAddress(String label) async {
116 + // NOTE: This will ignore the gap limit and may cause problems when restoring from seed if too
117 + // many addresses are taken and not used.
118 + final addr = await _libwallet.newExternalAddress(walletInfo.name) ?? '';
119 + if (addr == "") {
120 + return;
121 + }
122 + if (!addressesMap.containsKey(addr)) {
123 + addressesMap[addr] = "";
124 + addressInfos[0] ??= [];
125 + addressInfos[0]?.add(AddressInfo(address: addr, label: label, accountIndex: 0));
126 + }
127 + selectedAddr = addr;
128 + await saveAddressesInBox();
129 + }
130 +}
131 +
132 +class LibAddresses {
133 + final List<String> usedAddrs, unusedAddrs;
134 + final int firstUnusedAddrIndex;
135 +
136 + LibAddresses(this.usedAddrs, this.unusedAddrs, this.firstUnusedAddrIndex);
137 +}
cw_decred/lib/wallet_creation_credentials.dart new
+40
@@ -0,0 +1,40 @@
1 +import 'package:cw_core/wallet_credentials.dart';
2 +import 'package:cw_core/wallet_info.dart';
3 +import 'package:cw_core/hardware/hardware_account_data.dart';
4 +
5 +class DecredNewWalletCredentials extends WalletCredentials {
6 + DecredNewWalletCredentials({required String name, WalletInfo? walletInfo})
7 + : super(name: name, walletInfo: walletInfo);
8 +}
9 +
10 +class DecredRestoreWalletFromSeedCredentials extends WalletCredentials {
11 + DecredRestoreWalletFromSeedCredentials(
12 + {required String name,
13 + required String password,
14 + required this.mnemonic,
15 + WalletInfo? walletInfo})
16 + : super(name: name, password: password, walletInfo: walletInfo);
17 +
18 + final String mnemonic;
19 +}
20 +
21 +class DecredRestoreWalletFromPubkeyCredentials extends WalletCredentials {
22 + DecredRestoreWalletFromPubkeyCredentials(
23 + {required String name,
24 + required String password,
25 + required String this.pubkey,
26 + WalletInfo? walletInfo})
27 + : super(name: name, password: password, walletInfo: walletInfo);
28 +
29 + final String pubkey;
30 +}
31 +
32 +class DecredRestoreWalletFromHardwareCredentials extends WalletCredentials {
33 + DecredRestoreWalletFromHardwareCredentials(
34 + {required String name, required this.hwAccountData, WalletInfo? walletInfo})
35 + : t = throw UnimplementedError(),
36 + super(name: name, walletInfo: walletInfo);
37 +
38 + final HardwareAccountData hwAccountData;
39 + final void t;
40 +}
cw_decred/lib/wallet_service.dart new
+186
@@ -0,0 +1,186 @@
1 +import 'dart:convert';
2 +import 'dart:io';
3 +import 'package:cw_decred/api/libdcrwallet.dart';
4 +import 'package:cw_decred/wallet_creation_credentials.dart';
5 +import 'package:cw_decred/wallet.dart';
6 +import 'package:cw_core/wallet_base.dart';
7 +import 'package:cw_core/wallet_service.dart';
8 +import 'package:cw_core/pathForWallet.dart';
9 +import 'package:cw_core/wallet_info.dart';
10 +import 'package:cw_core/wallet_type.dart';
11 +import 'package:hive/hive.dart';
12 +import 'package:collection/collection.dart';
13 +import 'package:cw_core/unspent_coins_info.dart';
14 +
15 +class DecredWalletService extends WalletService<
16 + DecredNewWalletCredentials,
17 + DecredRestoreWalletFromSeedCredentials,
18 + DecredRestoreWalletFromPubkeyCredentials,
19 + DecredRestoreWalletFromHardwareCredentials> {
20 + DecredWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
21 +
22 + final Box<WalletInfo> walletInfoSource;
23 + final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
24 + final seedRestorePath = "m/44'/42'";
25 + static final seedRestorePathTestnet = "m/44'/1'";
26 + static final pubkeyRestorePath = "m/44'/42'/0'";
27 + static final pubkeyRestorePathTestnet = "m/44'/1'/0'";
28 + final mainnet = "mainnet";
29 + final testnet = "testnet";
30 + Libwallet? libwallet;
31 +
32 + Future<void> init() async {
33 + if (libwallet != null) {
34 + return;
35 + }
36 + libwallet = await Libwallet.spawn();
37 + // Use the general path for all dcr wallets as the general log directory.
38 + // Individual wallet paths may be removed if the wallet is deleted.
39 + final dcrLogDir = await pathForWalletDir(name: '', type: WalletType.decred);
40 + libwallet!.initLibdcrwallet(dcrLogDir);
41 + }
42 +
43 + void closeLibwallet() {
44 + if (libwallet == null) {
45 + return;
46 + }
47 + libwallet!.close();
48 + libwallet = null;
49 + }
50 +
51 + @override
52 + WalletType getType() => WalletType.decred;
53 +
54 + @override
55 + Future<bool> isWalletExit(String name) async =>
56 + File(await pathForWallet(name: name, type: getType())).existsSync();
57 +
58 + @override
59 + Future<DecredWallet> create(DecredNewWalletCredentials credentials, {bool? isTestnet}) async {
60 + await this.init();
61 + final config = {
62 + "name": credentials.walletInfo!.name,
63 + "datadir": credentials.walletInfo!.dirPath,
64 + "pass": credentials.password!,
65 + "net": isTestnet == true ? testnet : mainnet,
66 + "unsyncedaddrs": true,
67 + };
68 + await libwallet!.createWallet(jsonEncode(config));
69 + final di = DerivationInfo(
70 + derivationPath: isTestnet == true ? seedRestorePathTestnet : seedRestorePath);
71 + credentials.walletInfo!.derivationInfo = di;
72 + final wallet = DecredWallet(credentials.walletInfo!, credentials.password!,
73 + this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
74 + await wallet.init();
75 + return wallet;
76 + }
77 +
78 + @override
79 + Future<DecredWallet> openWallet(String name, String password) async {
80 + final walletInfo = walletInfoSource.values
81 + .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
82 + final network = walletInfo.derivationInfo?.derivationPath == seedRestorePathTestnet ||
83 + walletInfo.derivationInfo?.derivationPath == pubkeyRestorePathTestnet
84 + ? testnet
85 + : mainnet;
86 +
87 + await this.init();
88 + final walletDirExists = Directory(walletInfo.dirPath).existsSync();
89 + if (!walletDirExists) {
90 + walletInfo.dirPath = await pathForWalletDir(name: name, type: getType());
91 + }
92 +
93 + final config = {
94 + "name": walletInfo.name,
95 + "datadir": walletInfo.dirPath,
96 + "net": network,
97 + "unsyncedaddrs": true,
98 + };
99 + await libwallet!.loadWallet(jsonEncode(config));
100 + final wallet =
101 + DecredWallet(walletInfo, password, this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
102 + await wallet.init();
103 + return wallet;
104 + }
105 +
106 + @override
107 + Future<void> remove(String wallet) async {
108 + File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
109 + final walletInfo = walletInfoSource.values
110 + .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
111 + await walletInfoSource.delete(walletInfo.key);
112 + }
113 +
114 + @override
115 + Future<void> rename(String currentName, String password, String newName) async {
116 + final currentWalletInfo = walletInfoSource.values
117 + .firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
118 + final network = currentWalletInfo.derivationInfo?.derivationPath == seedRestorePathTestnet ||
119 + currentWalletInfo.derivationInfo?.derivationPath == pubkeyRestorePathTestnet
120 + ? testnet
121 + : mainnet;
122 + final currentWallet = DecredWallet(
123 + currentWalletInfo, password, this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
124 +
125 + await currentWallet.renameWalletFiles(newName);
126 +
127 + final newDirPath = await pathForWalletDir(name: newName, type: getType());
128 + final newWalletInfo = currentWalletInfo;
129 + newWalletInfo.id = WalletBase.idFor(newName, getType());
130 + newWalletInfo.name = newName;
131 + newWalletInfo.dirPath = newDirPath;
132 + newWalletInfo.network = network;
133 +
134 + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
135 + }
136 +
137 + @override
138 + Future<DecredWallet> restoreFromSeed(DecredRestoreWalletFromSeedCredentials credentials,
139 + {bool? isTestnet}) async {
140 + await this.init();
141 + final config = {
142 + "name": credentials.walletInfo!.name,
143 + "datadir": credentials.walletInfo!.dirPath,
144 + "pass": credentials.password!,
145 + "mnemonic": credentials.mnemonic,
146 + "net": isTestnet == true ? testnet : mainnet,
147 + "unsyncedaddrs": true,
148 + };
149 + await libwallet!.createWallet(jsonEncode(config));
150 + final di = DerivationInfo(
151 + derivationPath: isTestnet == true ? seedRestorePathTestnet : seedRestorePath);
152 + credentials.walletInfo!.derivationInfo = di;
153 + final wallet = DecredWallet(credentials.walletInfo!, credentials.password!,
154 + this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
155 + await wallet.init();
156 + return wallet;
157 + }
158 +
159 + // restoreFromKeys only supports restoring a watch only wallet from an account
160 + // pubkey.
161 + @override
162 + Future<DecredWallet> restoreFromKeys(DecredRestoreWalletFromPubkeyCredentials credentials,
163 + {bool? isTestnet}) async {
164 + await this.init();
165 + final config = {
166 + "name": credentials.walletInfo!.name,
167 + "datadir": credentials.walletInfo!.dirPath,
168 + "pubkey": credentials.pubkey,
169 + "net": isTestnet == true ? testnet : mainnet,
170 + "unsyncedaddrs": true,
171 + };
172 + await libwallet!.createWatchOnlyWallet(jsonEncode(config));
173 + final di = DerivationInfo(
174 + derivationPath: isTestnet == true ? pubkeyRestorePathTestnet : pubkeyRestorePath);
175 + credentials.walletInfo!.derivationInfo = di;
176 + final wallet = DecredWallet(credentials.walletInfo!, credentials.password!,
177 + this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
178 + await wallet.init();
179 + return wallet;
180 + }
181 +
182 + @override
183 + Future<DecredWallet> restoreFromHardwareWallet(
184 + DecredRestoreWalletFromHardwareCredentials credentials) async =>
185 + throw UnimplementedError();
186 +}
cw_decred/macos/Classes/CwDecredPlugin.swift new
+19
@@ -0,0 +1,19 @@
1 +import Cocoa
2 +import FlutterMacOS
3 +
4 +public class CwDecredPlugin: NSObject, FlutterPlugin {
5 + public static func register(with registrar: FlutterPluginRegistrar) {
6 + let channel = FlutterMethodChannel(name: "cw_decred", binaryMessenger: registrar.messenger)
7 + let instance = CwDecredPlugin()
8 + registrar.addMethodCallDelegate(instance, channel: channel)
9 + }
10 +
11 + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
12 + switch call.method {
13 + case "getPlatformVersion":
14 + result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString)
15 + default:
16 + result(FlutterMethodNotImplemented)
17 + }
18 + }
19 +}
cw_decred/macos/cw_decred.podspec new
+22
@@ -0,0 +1,22 @@
1 +#
2 +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
3 +# Run `pod lib lint cw_decred.podspec` to validate before publishing.
4 +#
5 +Pod::Spec.new do |s|
6 + s.name = 'cw_decred'
7 + s.version = '0.0.1'
8 + s.summary = 'Cake Wallet Decred'
9 + s.description = 'Cake Wallet wrapper over Decred project'
10 + s.homepage = 'http://cakewallet.com'
11 + s.license = { :file => '../LICENSE' }
12 + s.author = { 'Cake Wallet' => 'support@cakewallet.com' }
13 +
14 + s.source = { :path => '.' }
15 + s.source_files = 'Classes/**/*'
16 + s.dependency 'FlutterMacOS'
17 +
18 + s.platform = :osx, '10.11'
19 + s.vendored_libraries = 'External/lib/libdcrwallet.a'
20 + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', "OTHER_LDFLAGS" => "-force_load $(PODS_TARGET_SRCROOT)/External/lib/libdcrwallet.a -lstdc++" }
21 + s.swift_version = '5.0'
22 +end
cw_decred/pubspec.lock new
+852
@@ -0,0 +1,852 @@
1 +# Generated by pub
2 +# See https://dart.dev/tools/pub/glossary#lockfile
3 +packages:
4 + _fe_analyzer_shared:
5 + dependency: transitive
6 + description:
7 + name: _fe_analyzer_shared
8 + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
9 + url: "https://pub.dev"
10 + source: hosted
11 + version: "72.0.0"
12 + _macros:
13 + dependency: transitive
14 + description: dart
15 + source: sdk
16 + version: "0.3.2"
17 + analyzer:
18 + dependency: transitive
19 + description:
20 + name: analyzer
21 + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
22 + url: "https://pub.dev"
23 + source: hosted
24 + version: "6.7.0"
25 + args:
26 + dependency: transitive
27 + description:
28 + name: args
29 + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
30 + url: "https://pub.dev"
31 + source: hosted
32 + version: "2.6.0"
33 + asn1lib:
34 + dependency: transitive
35 + description:
36 + name: asn1lib
37 + sha256: "4bae5ae63e6d6dd17c4aac8086f3dec26c0236f6a0f03416c6c19d830c367cf5"
38 + url: "https://pub.dev"
39 + source: hosted
40 + version: "1.5.8"
41 + async:
42 + dependency: transitive
43 + description:
44 + name: async
45 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
46 + url: "https://pub.dev"
47 + source: hosted
48 + version: "2.11.0"
49 + blockchain_utils:
50 + dependency: transitive
51 + description:
52 + path: "."
53 + ref: cake-update-v2
54 + resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57"
55 + url: "https://github.com/cake-tech/blockchain_utils"
56 + source: git
57 + version: "3.3.0"
58 + boolean_selector:
59 + dependency: transitive
60 + description:
61 + name: boolean_selector
62 + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
63 + url: "https://pub.dev"
64 + source: hosted
65 + version: "2.1.1"
66 + build:
67 + dependency: transitive
68 + description:
69 + name: build
70 + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
71 + url: "https://pub.dev"
72 + source: hosted
73 + version: "2.4.1"
74 + build_config:
75 + dependency: transitive
76 + description:
77 + name: build_config
78 + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
79 + url: "https://pub.dev"
80 + source: hosted
81 + version: "1.1.1"
82 + build_daemon:
83 + dependency: transitive
84 + description:
85 + name: build_daemon
86 + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9"
87 + url: "https://pub.dev"
88 + source: hosted
89 + version: "4.0.2"
90 + build_resolvers:
91 + dependency: "direct dev"
92 + description:
93 + name: build_resolvers
94 + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
95 + url: "https://pub.dev"
96 + source: hosted
97 + version: "2.4.2"
98 + build_runner:
99 + dependency: "direct dev"
100 + description:
101 + name: build_runner
102 + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
103 + url: "https://pub.dev"
104 + source: hosted
105 + version: "2.4.13"
106 + build_runner_core:
107 + dependency: transitive
108 + description:
109 + name: build_runner_core
110 + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
111 + url: "https://pub.dev"
112 + source: hosted
113 + version: "7.3.2"
114 + built_collection:
115 + dependency: transitive
116 + description:
117 + name: built_collection
118 + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
119 + url: "https://pub.dev"
120 + source: hosted
121 + version: "5.1.1"
122 + built_value:
123 + dependency: transitive
124 + description:
125 + name: built_value
126 + sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4
127 + url: "https://pub.dev"
128 + source: hosted
129 + version: "8.9.5"
130 + cake_backup:
131 + dependency: transitive
132 + description:
133 + path: "."
134 + ref: main
135 + resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38"
136 + url: "https://github.com/cake-tech/cake_backup.git"
137 + source: git
138 + version: "1.0.0+1"
139 + characters:
140 + dependency: transitive
141 + description:
142 + name: characters
143 + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
144 + url: "https://pub.dev"
145 + source: hosted
146 + version: "1.3.0"
147 + checked_yaml:
148 + dependency: transitive
149 + description:
150 + name: checked_yaml
151 + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
152 + url: "https://pub.dev"
153 + source: hosted
154 + version: "2.0.3"
155 + cli_util:
156 + dependency: transitive
157 + description:
158 + name: cli_util
159 + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
160 + url: "https://pub.dev"
161 + source: hosted
162 + version: "0.4.2"
163 + clock:
164 + dependency: transitive
165 + description:
166 + name: clock
167 + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
168 + url: "https://pub.dev"
169 + source: hosted
170 + version: "1.1.1"
171 + code_builder:
172 + dependency: transitive
173 + description:
174 + name: code_builder
175 + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
176 + url: "https://pub.dev"
177 + source: hosted
178 + version: "4.10.1"
179 + collection:
180 + dependency: transitive
181 + description:
182 + name: collection
183 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
184 + url: "https://pub.dev"
185 + source: hosted
186 + version: "1.18.0"
187 + convert:
188 + dependency: transitive
189 + description:
190 + name: convert
191 + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
192 + url: "https://pub.dev"
193 + source: hosted
194 + version: "3.1.2"
195 + crypto:
196 + dependency: transitive
197 + description:
198 + name: crypto
199 + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
200 + url: "https://pub.dev"
201 + source: hosted
202 + version: "3.0.6"
203 + cryptography:
204 + dependency: transitive
205 + description:
206 + name: cryptography
207 + sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05
208 + url: "https://pub.dev"
209 + source: hosted
210 + version: "2.7.0"
211 + cupertino_icons:
212 + dependency: transitive
213 + description:
214 + name: cupertino_icons
215 + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
216 + url: "https://pub.dev"
217 + source: hosted
218 + version: "1.0.8"
219 + cw_core:
220 + dependency: "direct main"
221 + description:
222 + path: "../cw_core"
223 + relative: true
224 + source: path
225 + version: "0.0.1"
226 + dart_style:
227 + dependency: transitive
228 + description:
229 + name: dart_style
230 + sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab"
231 + url: "https://pub.dev"
232 + source: hosted
233 + version: "2.3.7"
234 + decimal:
235 + dependency: transitive
236 + description:
237 + name: decimal
238 + sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
239 + url: "https://pub.dev"
240 + source: hosted
241 + version: "2.3.3"
242 + encrypt:
243 + dependency: transitive
244 + description:
245 + name: encrypt
246 + sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
247 + url: "https://pub.dev"
248 + source: hosted
249 + version: "5.0.3"
250 + fake_async:
251 + dependency: transitive
252 + description:
253 + name: fake_async
254 + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
255 + url: "https://pub.dev"
256 + source: hosted
257 + version: "1.3.1"
258 + ffi:
259 + dependency: transitive
260 + description:
261 + name: ffi
262 + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
263 + url: "https://pub.dev"
264 + source: hosted
265 + version: "2.1.3"
266 + ffigen:
267 + dependency: "direct dev"
268 + description:
269 + name: ffigen
270 + sha256: "2119b4fe3aad0db94dc9531b90283c4640a6231070e613c400b426a4da08c704"
271 + url: "https://pub.dev"
272 + source: hosted
273 + version: "16.1.0"
274 + file:
275 + dependency: transitive
276 + description:
277 + name: file
278 + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
279 + url: "https://pub.dev"
280 + source: hosted
281 + version: "7.0.1"
282 + fixnum:
283 + dependency: transitive
284 + description:
285 + name: fixnum
286 + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
287 + url: "https://pub.dev"
288 + source: hosted
289 + version: "1.1.1"
290 + flutter:
291 + dependency: "direct main"
292 + description: flutter
293 + source: sdk
294 + version: "0.0.0"
295 + flutter_mobx:
296 + dependency: transitive
297 + description:
298 + name: flutter_mobx
299 + sha256: ba5e93467866a2991259dc51cffd41ef45f695c667c2b8e7b087bf24118b50fe
300 + url: "https://pub.dev"
301 + source: hosted
302 + version: "2.3.0"
303 + flutter_test:
304 + dependency: "direct dev"
305 + description: flutter
306 + source: sdk
307 + version: "0.0.0"
308 + frontend_server_client:
309 + dependency: transitive
310 + description:
311 + name: frontend_server_client
312 + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
313 + url: "https://pub.dev"
314 + source: hosted
315 + version: "4.0.0"
316 + glob:
317 + dependency: transitive
318 + description:
319 + name: glob
320 + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
321 + url: "https://pub.dev"
322 + source: hosted
323 + version: "2.1.3"
324 + graphs:
325 + dependency: transitive
326 + description:
327 + name: graphs
328 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
329 + url: "https://pub.dev"
330 + source: hosted
331 + version: "2.3.2"
332 + hive:
333 + dependency: transitive
334 + description:
335 + name: hive
336 + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
337 + url: "https://pub.dev"
338 + source: hosted
339 + version: "2.2.3"
340 + hive_generator:
341 + dependency: "direct dev"
342 + description:
343 + name: hive_generator
344 + sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
345 + url: "https://pub.dev"
346 + source: hosted
347 + version: "2.0.1"
348 + http:
349 + dependency: transitive
350 + description:
351 + name: http
352 + sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
353 + url: "https://pub.dev"
354 + source: hosted
355 + version: "1.3.0"
356 + http_multi_server:
357 + dependency: transitive
358 + description:
359 + name: http_multi_server
360 + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
361 + url: "https://pub.dev"
362 + source: hosted
363 + version: "3.2.2"
364 + http_parser:
365 + dependency: transitive
366 + description:
367 + name: http_parser
368 + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
369 + url: "https://pub.dev"
370 + source: hosted
371 + version: "4.0.2"
372 + intl:
373 + dependency: transitive
374 + description:
375 + name: intl
376 + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
377 + url: "https://pub.dev"
378 + source: hosted
379 + version: "0.19.0"
380 + io:
381 + dependency: transitive
382 + description:
383 + name: io
384 + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
385 + url: "https://pub.dev"
386 + source: hosted
387 + version: "1.0.5"
388 + js:
389 + dependency: transitive
390 + description:
391 + name: js
392 + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
393 + url: "https://pub.dev"
394 + source: hosted
395 + version: "0.6.7"
396 + json_annotation:
397 + dependency: transitive
398 + description:
399 + name: json_annotation
400 + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
401 + url: "https://pub.dev"
402 + source: hosted
403 + version: "4.9.0"
404 + leak_tracker:
405 + dependency: transitive
406 + description:
407 + name: leak_tracker
408 + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
409 + url: "https://pub.dev"
410 + source: hosted
411 + version: "10.0.5"
412 + leak_tracker_flutter_testing:
413 + dependency: transitive
414 + description:
415 + name: leak_tracker_flutter_testing
416 + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
417 + url: "https://pub.dev"
418 + source: hosted
419 + version: "3.0.5"
420 + leak_tracker_testing:
421 + dependency: transitive
422 + description:
423 + name: leak_tracker_testing
424 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
425 + url: "https://pub.dev"
426 + source: hosted
427 + version: "3.0.1"
428 + logging:
429 + dependency: transitive
430 + description:
431 + name: logging
432 + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
433 + url: "https://pub.dev"
434 + source: hosted
435 + version: "1.3.0"
436 + macros:
437 + dependency: transitive
438 + description:
439 + name: macros
440 + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
441 + url: "https://pub.dev"
442 + source: hosted
443 + version: "0.1.2-main.4"
444 + matcher:
445 + dependency: transitive
446 + description:
447 + name: matcher
448 + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
449 + url: "https://pub.dev"
450 + source: hosted
451 + version: "0.12.16+1"
452 + material_color_utilities:
453 + dependency: transitive
454 + description:
455 + name: material_color_utilities
456 + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
457 + url: "https://pub.dev"
458 + source: hosted
459 + version: "0.11.1"
460 + meta:
461 + dependency: transitive
462 + description:
463 + name: meta
464 + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
465 + url: "https://pub.dev"
466 + source: hosted
467 + version: "1.15.0"
468 + mime:
469 + dependency: transitive
470 + description:
471 + name: mime
472 + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
473 + url: "https://pub.dev"
474 + source: hosted
475 + version: "2.0.0"
476 + mobx:
477 + dependency: transitive
478 + description:
479 + name: mobx
480 + sha256: bf1a90e5bcfd2851fc6984e20eef69557c65d9e4d0a88f5be4cf72c9819ce6b0
481 + url: "https://pub.dev"
482 + source: hosted
483 + version: "2.5.0"
484 + mobx_codegen:
485 + dependency: "direct dev"
486 + description:
487 + name: mobx_codegen
488 + sha256: "990da80722f7d7c0017dec92040b31545d625b15d40204c36a1e63d167c73cdc"
489 + url: "https://pub.dev"
490 + source: hosted
491 + version: "2.7.0"
492 + nested:
493 + dependency: transitive
494 + description:
495 + name: nested
496 + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
497 + url: "https://pub.dev"
498 + source: hosted
499 + version: "1.0.0"
500 + on_chain:
501 + dependency: transitive
502 + description:
503 + path: "."
504 + ref: cake-update-v2
505 + resolved-ref: "93440dc5126369b873ca1fccc13c3c1240b1c5c2"
506 + url: "https://github.com/cake-tech/on_chain.git"
507 + source: git
508 + version: "3.7.0"
509 + package_config:
510 + dependency: transitive
511 + description:
512 + name: package_config
513 + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67"
514 + url: "https://pub.dev"
515 + source: hosted
516 + version: "2.1.1"
517 + path:
518 + dependency: transitive
519 + description:
520 + name: path
521 + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
522 + url: "https://pub.dev"
523 + source: hosted
524 + version: "1.9.0"
525 + path_provider:
526 + dependency: transitive
527 + description:
528 + name: path_provider
529 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
530 + url: "https://pub.dev"
531 + source: hosted
532 + version: "2.1.5"
533 + path_provider_android:
534 + dependency: transitive
535 + description:
536 + name: path_provider_android
537 + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2"
538 + url: "https://pub.dev"
539 + source: hosted
540 + version: "2.2.15"
541 + path_provider_foundation:
542 + dependency: transitive
543 + description:
544 + name: path_provider_foundation
545 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
546 + url: "https://pub.dev"
547 + source: hosted
548 + version: "2.4.1"
549 + path_provider_linux:
550 + dependency: transitive
551 + description:
552 + name: path_provider_linux
553 + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
554 + url: "https://pub.dev"
555 + source: hosted
556 + version: "2.2.1"
557 + path_provider_platform_interface:
558 + dependency: transitive
559 + description:
560 + name: path_provider_platform_interface
561 + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
562 + url: "https://pub.dev"
563 + source: hosted
564 + version: "2.1.2"
565 + path_provider_windows:
566 + dependency: transitive
567 + description:
568 + name: path_provider_windows
569 + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
570 + url: "https://pub.dev"
571 + source: hosted
572 + version: "2.3.0"
573 + platform:
574 + dependency: transitive
575 + description:
576 + name: platform
577 + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
578 + url: "https://pub.dev"
579 + source: hosted
580 + version: "3.1.6"
581 + plugin_platform_interface:
582 + dependency: transitive
583 + description:
584 + name: plugin_platform_interface
585 + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
586 + url: "https://pub.dev"
587 + source: hosted
588 + version: "2.1.8"
589 + pointycastle:
590 + dependency: transitive
591 + description:
592 + name: pointycastle
593 + sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
594 + url: "https://pub.dev"
595 + source: hosted
596 + version: "3.9.1"
597 + pool:
598 + dependency: transitive
599 + description:
600 + name: pool
601 + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
602 + url: "https://pub.dev"
603 + source: hosted
604 + version: "1.5.1"
605 + provider:
606 + dependency: transitive
607 + description:
608 + name: provider
609 + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
610 + url: "https://pub.dev"
611 + source: hosted
612 + version: "6.1.2"
613 + pub_semver:
614 + dependency: transitive
615 + description:
616 + name: pub_semver
617 + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
618 + url: "https://pub.dev"
619 + source: hosted
620 + version: "2.2.0"
621 + pubspec_parse:
622 + dependency: transitive
623 + description:
624 + name: pubspec_parse
625 + sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0"
626 + url: "https://pub.dev"
627 + source: hosted
628 + version: "1.4.0"
629 + quiver:
630 + dependency: transitive
631 + description:
632 + name: quiver
633 + sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
634 + url: "https://pub.dev"
635 + source: hosted
636 + version: "3.2.2"
637 + rational:
638 + dependency: transitive
639 + description:
640 + name: rational
641 + sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
642 + url: "https://pub.dev"
643 + source: hosted
644 + version: "2.2.3"
645 + shelf:
646 + dependency: transitive
647 + description:
648 + name: shelf
649 + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
650 + url: "https://pub.dev"
651 + source: hosted
652 + version: "1.4.1"
653 + shelf_web_socket:
654 + dependency: transitive
655 + description:
656 + name: shelf_web_socket
657 + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
658 + url: "https://pub.dev"
659 + source: hosted
660 + version: "2.0.1"
661 + sky_engine:
662 + dependency: transitive
663 + description: flutter
664 + source: sdk
665 + version: "0.0.99"
666 + socks5_proxy:
667 + dependency: transitive
668 + description:
669 + name: socks5_proxy
670 + sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053"
671 + url: "https://pub.dev"
672 + source: hosted
673 + version: "1.0.6"
674 + source_gen:
675 + dependency: transitive
676 + description:
677 + name: source_gen
678 + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
679 + url: "https://pub.dev"
680 + source: hosted
681 + version: "1.5.0"
682 + source_helper:
683 + dependency: transitive
684 + description:
685 + name: source_helper
686 + sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
687 + url: "https://pub.dev"
688 + source: hosted
689 + version: "1.3.5"
690 + source_span:
691 + dependency: transitive
692 + description:
693 + name: source_span
694 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
695 + url: "https://pub.dev"
696 + source: hosted
697 + version: "1.10.0"
698 + stack_trace:
699 + dependency: transitive
700 + description:
701 + name: stack_trace
702 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
703 + url: "https://pub.dev"
704 + source: hosted
705 + version: "1.11.1"
706 + stream_channel:
707 + dependency: transitive
708 + description:
709 + name: stream_channel
710 + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
711 + url: "https://pub.dev"
712 + source: hosted
713 + version: "2.1.2"
714 + stream_transform:
715 + dependency: transitive
716 + description:
717 + name: stream_transform
718 + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
719 + url: "https://pub.dev"
720 + source: hosted
721 + version: "2.1.1"
722 + string_scanner:
723 + dependency: transitive
724 + description:
725 + name: string_scanner
726 + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
727 + url: "https://pub.dev"
728 + source: hosted
729 + version: "1.2.0"
730 + term_glyph:
731 + dependency: transitive
732 + description:
733 + name: term_glyph
734 + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
735 + url: "https://pub.dev"
736 + source: hosted
737 + version: "1.2.1"
738 + test_api:
739 + dependency: transitive
740 + description:
741 + name: test_api
742 + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
743 + url: "https://pub.dev"
744 + source: hosted
745 + version: "0.7.2"
746 + timing:
747 + dependency: transitive
748 + description:
749 + name: timing
750 + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
751 + url: "https://pub.dev"
752 + source: hosted
753 + version: "1.0.2"
754 + tuple:
755 + dependency: transitive
756 + description:
757 + name: tuple
758 + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
759 + url: "https://pub.dev"
760 + source: hosted
761 + version: "2.0.2"
762 + typed_data:
763 + dependency: transitive
764 + description:
765 + name: typed_data
766 + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
767 + url: "https://pub.dev"
768 + source: hosted
769 + version: "1.4.0"
770 + unorm_dart:
771 + dependency: transitive
772 + description:
773 + name: unorm_dart
774 + sha256: "23d8bf65605401a6a32cff99435fed66ef3dab3ddcad3454059165df46496a3b"
775 + url: "https://pub.dev"
776 + source: hosted
777 + version: "0.3.0"
778 + vector_math:
779 + dependency: transitive
780 + description:
781 + name: vector_math
782 + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
783 + url: "https://pub.dev"
784 + source: hosted
785 + version: "2.1.4"
786 + vm_service:
787 + dependency: transitive
788 + description:
789 + name: vm_service
790 + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
791 + url: "https://pub.dev"
792 + source: hosted
793 + version: "14.2.4"
794 + watcher:
795 + dependency: transitive
796 + description:
797 + name: watcher
798 + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
799 + url: "https://pub.dev"
800 + source: hosted
801 + version: "1.1.1"
802 + web:
803 + dependency: transitive
804 + description:
805 + name: web
806 + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
807 + url: "https://pub.dev"
808 + source: hosted
809 + version: "1.1.1"
810 + web_socket:
811 + dependency: transitive
812 + description:
813 + name: web_socket
814 + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
815 + url: "https://pub.dev"
816 + source: hosted
817 + version: "0.1.6"
818 + web_socket_channel:
819 + dependency: transitive
820 + description:
821 + name: web_socket_channel
822 + sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5"
823 + url: "https://pub.dev"
824 + source: hosted
825 + version: "3.0.2"
826 + xdg_directories:
827 + dependency: transitive
828 + description:
829 + name: xdg_directories
830 + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
831 + url: "https://pub.dev"
832 + source: hosted
833 + version: "1.1.0"
834 + yaml:
835 + dependency: transitive
836 + description:
837 + name: yaml
838 + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
839 + url: "https://pub.dev"
840 + source: hosted
841 + version: "3.1.3"
842 + yaml_edit:
843 + dependency: transitive
844 + description:
845 + name: yaml_edit
846 + sha256: fb38626579fb345ad00e674e2af3a5c9b0cc4b9bfb8fd7f7ff322c7c9e62aef5
847 + url: "https://pub.dev"
848 + source: hosted
849 + version: "2.2.2"
850 +sdks:
851 + dart: ">=3.5.0 <4.0.0"
852 + flutter: ">=3.24.0"
cw_decred/pubspec.yaml new
+84
@@ -0,0 +1,84 @@
1 +name: cw_decred
2 +description: A new Flutter plugin project.
3 +version: 0.0.1
4 +publish_to: none
5 +author: Cake Wallet
6 +homepage: https://cakewallet.com
7 +
8 +environment:
9 + sdk: '>=3.2.0-0 <4.0.0'
10 + flutter: ">=3.19.0"
11 +
12 +
13 +dependencies:
14 + flutter:
15 + sdk: flutter
16 + cw_core:
17 + path: ../cw_core
18 +
19 +dev_dependencies:
20 + flutter_test:
21 + sdk: flutter
22 + build_runner: ^2.1.11
23 + build_resolvers: ^2.0.9
24 + mobx_codegen: ^2.0.7
25 + hive_generator: ^2.0.1
26 + ffigen: ^16.1.0
27 +
28 +ffigen:
29 + name: libdcrwallet
30 + description: Bindings for dcrwallet go library.
31 + output: "lib/api/libdcrwallet_bindings.dart"
32 + headers:
33 + entry-points:
34 + - "lib/api/libdcrwallet.h"
35 +
36 +# For information on the generic Dart part of this file, see the
37 +# following page: https://dart.dev/tools/pub/pubspec
38 +
39 +# The following section is specific to Flutter packages.
40 +flutter:
41 + # This section identifies this Flutter project as a plugin project.
42 + # The androidPackage and pluginClass identifiers should not ordinarily
43 + # be modified. They are used by the tooling to maintain consistency when
44 + # adding or updating assets for this project.
45 + plugin:
46 + platforms:
47 + android:
48 + package: com.cakewallet.cw_decred
49 + pluginClass: CwDecredPlugin
50 + ios:
51 + pluginClass: CwDecredPlugin
52 + macos:
53 + pluginClass: CwDecredPlugin
54 +
55 + # To add assets to your plugin package, add an assets section, like this:
56 + # assets:
57 + # - images/a_dot_burr.jpeg
58 + # - images/a_dot_ham.jpeg
59 + #
60 + # For details regarding assets in packages, see
61 + # https://flutter.dev/assets-and-images/#from-packages
62 + #
63 + # An image asset can refer to one or more resolution-specific "variants", see
64 + # https://flutter.dev/assets-and-images/#resolution-aware
65 +
66 + # To add custom fonts to your plugin package, add a fonts section here,
67 + # in this "flutter" section. Each entry in this list should have a
68 + # "family" key with the font family name, and a "fonts" key with a
69 + # list giving the asset and other descriptors for the font. For
70 + # example:
71 + # fonts:
72 + # - family: Schyler
73 + # fonts:
74 + # - asset: fonts/Schyler-Regular.ttf
75 + # - asset: fonts/Schyler-Italic.ttf
76 + # style: italic
77 + # - family: Trajan Pro
78 + # fonts:
79 + # - asset: fonts/TrajanPro.ttf
80 + # - asset: fonts/TrajanPro_Bold.ttf
81 + # weight: 700
82 + #
83 + # For details regarding fonts in packages, see
84 + # https://flutter.dev/custom-fonts/#from-packages
cw_monero/lib/monero_wallet.dart
+3
@@ -115,6 +115,9 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
115 @observable
116 ObservableMap<CryptoCurrency, MoneroBalance> balance;
117
118 + @override
119 + bool get hasRescan => true;
120 +
121 @override
122 String get seed => monero_wallet.getSeed();
123 String seedLegacy(String? language) => monero_wallet.getSeedLegacy(language);
cw_wownero/lib/wownero_wallet.dart
+3
@@ -123,6 +123,9 @@ abstract class WowneroWalletBase
123
124 String _password;
125
126 + @override
127 + bool get hasRescan => true;
128 +
129 @override
130 MoneroWalletKeys get keys => MoneroWalletKeys(
131 primaryAddress: wownero_wallet.getAddress(accountIndex: 0, addressIndex: 0),
ios/Podfile.lock
+43
@@ -3,8 +3,39 @@ PODS:
3 - Flutter
4 - ReachabilitySwift
5 - CryptoSwift (1.8.3)
6 + - cw_haven (0.0.1):
7 + - cw_haven/Boost (= 0.0.1)
8 + - cw_haven/Haven (= 0.0.1)
9 + - cw_haven/OpenSSL (= 0.0.1)
10 + - cw_haven/Sodium (= 0.0.1)
11 + - cw_shared_external
12 + - Flutter
13 + - cw_haven/Boost (0.0.1):
14 + - cw_shared_external
15 + - Flutter
16 + - cw_haven/Haven (0.0.1):
17 + - cw_shared_external
18 + - Flutter
19 + - cw_haven/OpenSSL (0.0.1):
20 + - cw_shared_external
21 + - Flutter
22 + - cw_haven/Sodium (0.0.1):
23 + - cw_shared_external
24 + - Flutter
25 - cw_mweb (0.0.1):
26 - Flutter
27 + - cw_decred (0.0.1):
28 + - cw_shared_external (0.0.1):
29 + - cw_shared_external/Boost (= 0.0.1)
30 + - cw_shared_external/OpenSSL (= 0.0.1)
31 + - cw_shared_external/Sodium (= 0.0.1)
32 + - Flutter
33 + - cw_shared_external/Boost (0.0.1):
34 + - Flutter
35 + - cw_shared_external/OpenSSL (0.0.1):
36 + - Flutter
37 + - cw_shared_external/Sodium (0.0.1):
38 + - Flutter
39 - device_display_brightness (0.0.1):
40 - Flutter
41 - device_info_plus (0.0.1):
@@ -106,7 +137,10 @@ PODS:
137 DEPENDENCIES:
138 - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
139 - CryptoSwift
140 + - cw_haven (from `.symlinks/plugins/cw_haven/ios`)
141 - cw_mweb (from `.symlinks/plugins/cw_mweb/ios`)
142 + - cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`)
143 + - cw_decred (from `.symlinks/plugins/cw_decred/ios`)
144 - device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`)
145 - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
146 - devicelocale (from `.symlinks/plugins/devicelocale/ios`)
@@ -147,8 +181,14 @@ SPEC REPOS:
181 EXTERNAL SOURCES:
182 connectivity_plus:
183 :path: ".symlinks/plugins/connectivity_plus/ios"
184 + cw_haven:
185 + :path: ".symlinks/plugins/cw_haven/ios"
186 cw_mweb:
187 :path: ".symlinks/plugins/cw_mweb/ios"
188 + cw_shared_external:
189 + :path: ".symlinks/plugins/cw_shared_external/ios"
190 + cw_decred:
191 + :path: ".symlinks/plugins/cw_decred/ios"
192 device_display_brightness:
193 :path: ".symlinks/plugins/device_display_brightness/ios"
194 device_info_plus:
@@ -203,7 +243,10 @@ EXTERNAL SOURCES:
243 SPEC CHECKSUMS:
244 connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
245 CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483
246 + cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a
247 cw_mweb: 22cd01dfb8ad2d39b15332006f22046aaa8352a3
248 + cw_shared_external: 2972d872b8917603478117c9957dfca611845a92
249 + cw_decred: 9c0e1df74745b51a1289ec5e91fb9e24b68fa14a
250 device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7
251 device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
252 devicelocale: 35ba84dc7f45f527c3001535d8c8d104edd5d926
ios/Runner/InfoBase.plist
+40
@@ -242,6 +242,46 @@
242 <string>wownero-wallet</string>
243 </array>
244 </dict>
245 + <dict>
246 + <key>CFBundleTypeRole</key>
247 + <string>Viewer</string>
248 + <key>CFBundleURLName</key>
249 + <string>zano</string>
250 + <key>CFBundleURLSchemes</key>
251 + <array>
252 + <string>zano</string>
253 + </array>
254 + </dict>
255 + <dict>
256 + <key>CFBundleTypeRole</key>
257 + <string>Viewer</string>
258 + <key>CFBundleURLName</key>
259 + <string>zano-wallet</string>
260 + <key>CFBundleURLSchemes</key>
261 + <array>
262 + <string>zano-wallet</string>
263 + </array>
264 + </dict>
265 + <dict>
266 + <key>CFBundleTypeRole</key>
267 + <string>Viewer</string>
268 + <key>CFBundleURLName</key>
269 + <string>decred</string>
270 + <key>CFBundleURLSchemes</key>
271 + <array>
272 + <string>decred</string>
273 + </array>
274 + </dict>
275 + <dict>
276 + <key>CFBundleTypeRole</key>
277 + <string>Viewer</string>
278 + <key>CFBundleURLName</key>
279 + <string>decred-wallet</string>
280 + <key>CFBundleURLSchemes</key>
281 + <array>
282 + <string>decred-wallet</string>
283 + </array>
284 + </dict>
285 </array>
286 <key>CFBundleVersion</key>
287 <string>$(CURRENT_PROJECT_VERSION)</string>
lib/core/address_validator.dart
+1 -1
@@ -117,7 +117,7 @@ class AddressValidator extends TextValidator {
117 case CryptoCurrency.zec:
118 pattern = 't1[0-9a-zA-Z]{33}|t3[0-9a-zA-Z]{33}';
119 case CryptoCurrency.dcr:
120 - pattern = 'D[ksecS]([0-9a-zA-Z])+';
120 + pattern = '(D|T|S)[ksecS]([0-9a-zA-Z])+';
121 case CryptoCurrency.rvn:
122 pattern = '[Rr]([1-9a-km-zA-HJ-NP-Z]){33}';
123 case CryptoCurrency.near:
lib/core/node_address_validator.dart
+11
@@ -17,3 +17,14 @@ class NodePathValidator extends TextValidator {
17 isAutovalidate: true,
18 );
19 }
20 +
21 +// NodeAddressValidatorDecredBlankException allows decred to send a blank ip
22 +// address which effectively clears the current set persistant peer.
23 +class NodeAddressValidatorDecredBlankException extends TextValidator {
24 + NodeAddressValidatorDecredBlankException()
25 + : super(
26 + errorMessage: S.current.error_text_node_address,
27 + isAutovalidate: true,
28 + pattern:
29 + '^((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.\-]+\$');
30 +}
lib/core/seed_validator.dart
+3
@@ -10,6 +10,7 @@ import 'package:cake_wallet/solana/solana.dart';
10 import 'package:cake_wallet/tron/tron.dart';
11 import 'package:cake_wallet/wownero/wownero.dart';
12 import 'package:cake_wallet/zano/zano.dart';
13 +import 'package:cake_wallet/decred/decred.dart';
14 import 'package:cake_wallet/utils/language_list.dart';
15 import 'package:cw_core/wallet_type.dart';
16
@@ -50,6 +51,8 @@ class SeedValidator extends Validator<MnemonicItem> {
51 return wownero!.getWowneroWordList(language);
52 case WalletType.zano:
53 return zano!.getWordList(language);
54 + case WalletType.decred:
55 + return decred!.getDecredWordList();
56 case WalletType.none:
57 return [];
58 }
lib/core/sync_status_title.dart
+4
@@ -63,5 +63,9 @@ String syncStatusTitle(SyncStatus syncStatus) {
63 return S.current.sync_status_attempting_scan;
64 }
65
66 + if (syncStatus is ProcessingSyncStatus) {
67 + return syncStatus.message ?? S.current.processing;
68 + }
69 +
70 return '';
71 }
lib/core/wallet_creation_service.dart
+1
@@ -90,6 +90,7 @@ class WalletCreationService {
90 case WalletType.nano:
91 case WalletType.banano:
92 case WalletType.zano:
93 + case WalletType.decred:
94 return false;
95 }
96 }
lib/decred/cw_decred.dart new
+114
@@ -0,0 +1,114 @@
1 +part of 'decred.dart';
2 +
3 +class CWDecred extends Decred {
4 + CWDecred() {}
5 +
6 + @override
7 + WalletCredentials createDecredNewWalletCredentials(
8 + {required String name, WalletInfo? walletInfo}) =>
9 + DecredNewWalletCredentials(name: name, walletInfo: walletInfo);
10 +
11 + @override
12 + WalletCredentials createDecredRestoreWalletFromSeedCredentials(
13 + {required String name, required String mnemonic, required String password}) =>
14 + DecredRestoreWalletFromSeedCredentials(name: name, mnemonic: mnemonic, password: password);
15 +
16 + @override
17 + WalletCredentials createDecredRestoreWalletFromPubkeyCredentials(
18 + {required String name, required String pubkey, required String password}) =>
19 + DecredRestoreWalletFromPubkeyCredentials(name: name, pubkey: pubkey, password: password);
20 +
21 + @override
22 + WalletService createDecredWalletService(
23 + Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
24 + return DecredWalletService(walletInfoSource, unspentCoinSource);
25 + }
26 +
27 + @override
28 + List<TransactionPriority> getTransactionPriorities() => DecredTransactionPriority.all;
29 +
30 + @override
31 + TransactionPriority getDecredTransactionPriorityMedium() => DecredTransactionPriority.medium;
32 +
33 + @override
34 + TransactionPriority getDecredTransactionPrioritySlow() => DecredTransactionPriority.slow;
35 +
36 + @override
37 + TransactionPriority deserializeDecredTransactionPriority(int raw) =>
38 + DecredTransactionPriority.deserialize(raw: raw);
39 +
40 + @override
41 + Object createDecredTransactionCredentials(List<Output> outputs, TransactionPriority priority) =>
42 + DecredTransactionCredentials(
43 + outputs
44 + .map((out) => OutputInfo(
45 + fiatAmount: out.fiatAmount,
46 + cryptoAmount: out.cryptoAmount,
47 + address: out.address,
48 + note: out.note,
49 + sendAll: out.sendAll,
50 + extractedAddress: out.extractedAddress,
51 + isParsedAddress: out.isParsedAddress,
52 + formattedCryptoAmount: out.formattedCryptoAmount))
53 + .toList(),
54 + priority: priority as DecredTransactionPriority);
55 +
56 + List<AddressInfo> getAddressInfos(Object wallet) {
57 + final decredWallet = wallet as DecredWallet;
58 + return decredWallet.walletAddresses.getAddressInfos();
59 + }
60 +
61 + @override
62 + Future<void> updateAddress(Object wallet, String address, String label) async {
63 + final decredWallet = wallet as DecredWallet;
64 + await decredWallet.walletAddresses.updateAddress(address, label);
65 + }
66 +
67 + @override
68 + Future<void> generateNewAddress(Object wallet, String label) async {
69 + final decredWallet = wallet as DecredWallet;
70 + await decredWallet.walletAddresses.generateNewAddress(label);
71 + }
72 +
73 + @override
74 + String formatterDecredAmountToString({required int amount}) =>
75 + decredAmountToString(amount: amount);
76 +
77 + @override
78 + double formatterDecredAmountToDouble({required int amount}) =>
79 + decredAmountToDouble(amount: amount);
80 +
81 + @override
82 + int formatterStringDoubleToDecredAmount(String amount) => stringDoubleToDecredAmount(amount);
83 +
84 + @override
85 + List<Unspent> getUnspents(Object wallet) {
86 + final decredWallet = wallet as DecredWallet;
87 + return decredWallet.unspents();
88 + }
89 +
90 + @override
91 + void updateUnspents(Object wallet) {
92 + final decredWallet = wallet as DecredWallet;
93 + decredWallet.unspents();
94 + }
95 +
96 + @override
97 + int heightByDate(DateTime date) {
98 + final genesisBlocktime = DateTime.fromMillisecondsSinceEpoch(1454954400 * 1000);
99 + final minutesDiff = date.difference(genesisBlocktime).inMinutes;
100 + // Decred has five minute blocks on mainnet.
101 + // NOTE: This is off by about a day but is currently unused by decred as we
102 + // rescan from the wallet birthday.
103 + return minutesDiff ~/ 5;
104 + }
105 +
106 + @override
107 + List<String> getDecredWordList() => wordlist;
108 +
109 + @override
110 + String pubkey(Object wallet) {
111 + final decredWallet = wallet as DecredWallet;
112 + return decredWallet.pubkey;
113 + }
114 +}
lib/di.dart
+5 -1
@@ -69,6 +69,7 @@ import 'package:cake_wallet/haven/haven.dart';
69 import 'package:cake_wallet/monero/monero.dart';
70 import 'package:cake_wallet/nano/nano.dart';
71 import 'package:cake_wallet/polygon/polygon.dart';
72 +import 'package:cake_wallet/decred/decred.dart';
73 import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
74 import 'package:cake_wallet/routes.dart';
75 import 'package:cake_wallet/solana/solana.dart';
@@ -990,7 +991,8 @@ Future<void> setup({
991 (Node? editingNode, bool? isSelected) => NodeCreateOrEditPage(
992 nodeCreateOrEditViewModel: getIt.get<NodeCreateOrEditViewModel>(param2: false),
993 editingNode: editingNode,
993 - isSelected: isSelected));
994 + isSelected: isSelected,
995 + type: getIt.get<AppStore>().wallet!.type));
996
997 getIt.registerFactoryParam<PowNodeCreateOrEditPage, Node?, bool?>(
998 (Node? editingNode, bool? isSelected) => PowNodeCreateOrEditPage(
@@ -1115,6 +1117,8 @@ Future<void> setup({
1117 return wownero!.createWowneroWalletService(_walletInfoSource, _unspentCoinsInfoSource);
1118 case WalletType.zano:
1119 return zano!.createZanoWalletService(_walletInfoSource);
1120 + case WalletType.decred:
1121 + return decred!.createDecredWalletService(_walletInfoSource, _unspentCoinsInfoSource);
1122 case WalletType.none:
1123 throw Exception('Unexpected token: ${param1.toString()} for generating of WalletService');
1124 }
lib/entities/default_settings_migration.dart
+267 -584
@@ -46,6 +46,7 @@ const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002';
46 const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568';
47 const zanoDefaultNodeUri = 'zano.cakewallet.com:11211';
48 const moneroWorldNodeUri = '.moneroworld.com';
49 +const decredDefaultUri = "default-spv-nodes";
50
51 Future<void> defaultSettingsMigration(
52 {required int version,
@@ -96,30 +97,67 @@ Future<void> defaultSettingsMigration(
97 PreferencesKey.currentBalanceDisplayModeKey, BalanceDisplayMode.availableBalance.raw);
98 await sharedPreferences.setBool('save_recipient_address', true);
99 await resetToDefault(nodes);
99 - await changeMoneroCurrentNodeToDefault(
100 - sharedPreferences: sharedPreferences, nodes: nodes);
101 - await changeBitcoinCurrentElectrumServerToDefault(
102 - sharedPreferences: sharedPreferences, nodes: nodes);
103 - await changeLitecoinCurrentElectrumServerToDefault(
104 - sharedPreferences: sharedPreferences, nodes: nodes);
105 - await changeHavenCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
106 - await changeBitcoinCashCurrentNodeToDefault(
107 - sharedPreferences: sharedPreferences, nodes: nodes);
100
101 + await _changeDefaultNode(
102 + nodes: nodes,
103 + sharedPreferences: sharedPreferences,
104 + type: WalletType.monero,
105 + currentNodePreferenceKey: PreferencesKey.currentNodeIdKey,
106 + useSSL: true,
107 + trusted: true,
108 + );
109 + await _changeDefaultNode(
110 + nodes: nodes,
111 + sharedPreferences: sharedPreferences,
112 + type: WalletType.bitcoin,
113 + currentNodePreferenceKey: PreferencesKey.currentBitcoinElectrumSererIdKey,
114 + useSSL: true,
115 + );
116 + await _changeDefaultNode(
117 + nodes: nodes,
118 + sharedPreferences: sharedPreferences,
119 + type: WalletType.litecoin,
120 + currentNodePreferenceKey: PreferencesKey.currentLitecoinElectrumSererIdKey,
121 + useSSL: true,
122 + );
123 + await _changeDefaultNode(
124 + nodes: nodes,
125 + sharedPreferences: sharedPreferences,
126 + type: WalletType.haven,
127 + currentNodePreferenceKey: PreferencesKey.currentHavenNodeIdKey,
128 + );
129 break;
130 case 2:
131 await replaceNodesMigration(nodes: nodes);
112 - await replaceDefaultNode(sharedPreferences: sharedPreferences, nodes: nodes);
113 -
132 + await _changeDefaultNode(
133 + nodes: nodes,
134 + sharedPreferences: sharedPreferences,
135 + type: WalletType.monero,
136 + newDefaultUri: newCakeWalletMoneroUri,
137 + currentNodePreferenceKey: PreferencesKey.currentNodeIdKey,
138 + useSSL: true,
139 + trusted: true,
140 + oldUri: [
141 + 'xmr-node-uk.cakewallet.com:18081',
142 + 'eu-node.cakewallet.io:18081',
143 + 'node.cakewallet.io:18081'
144 + ],
145 + );
146 break;
147 case 3:
148 await updateNodeTypes(nodes: nodes);
117 - await addBitcoinElectrumServerList(nodes: nodes);
149 + await addWalletNodeList(nodes: nodes, type: WalletType.bitcoin);
150
151 break;
152 case 4:
121 - await changeBitcoinCurrentElectrumServerToDefault(
122 - sharedPreferences: sharedPreferences, nodes: nodes);
153 + await _changeDefaultNode(
154 + nodes: nodes,
155 + sharedPreferences: sharedPreferences,
156 + type: WalletType.bitcoin,
157 + newDefaultUri: newCakeWalletBitcoinUri,
158 + currentNodePreferenceKey: PreferencesKey.currentBitcoinElectrumSererIdKey,
159 + useSSL: true,
160 + );
161 break;
162
163 case 5:
@@ -139,7 +177,15 @@ Future<void> defaultSettingsMigration(
177 break;
178
179 case 11:
142 - await changeDefaultMoneroNode(nodes, sharedPreferences);
180 + await _changeDefaultNode(
181 + nodes: nodes,
182 + sharedPreferences: sharedPreferences,
183 + type: WalletType.monero,
184 + newDefaultUri: newCakeWalletMoneroUri,
185 + currentNodePreferenceKey: PreferencesKey.currentNodeIdKey,
186 + trusted: true,
187 + oldUri: ['.cakewallet.com'],
188 + );
189 break;
190
191 case 12:
@@ -151,28 +197,38 @@ Future<void> defaultSettingsMigration(
197 break;
198
199 case 15:
154 - await addLitecoinElectrumServerList(nodes: nodes);
155 - await changeLitecoinCurrentElectrumServerToDefault(
156 - sharedPreferences: sharedPreferences, nodes: nodes);
200 + await addWalletNodeList(nodes: nodes, type: WalletType.litecoin);
201 + await _changeDefaultNode(
202 + nodes: nodes,
203 + sharedPreferences: sharedPreferences,
204 + type: WalletType.litecoin,
205 + currentNodePreferenceKey: PreferencesKey.currentLitecoinElectrumSererIdKey,
206 + );
207 await checkCurrentNodes(nodes, powNodes, sharedPreferences);
208 break;
209
210 case 16:
161 - await addHavenNodeList(nodes: nodes);
162 - await changeHavenCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
211 + await addWalletNodeList(nodes: nodes, type: WalletType.haven);
212 + await _changeDefaultNode(
213 + nodes: nodes,
214 + sharedPreferences: sharedPreferences,
215 + type: WalletType.haven,
216 + currentNodePreferenceKey: PreferencesKey.currentHavenNodeIdKey,
217 + );
218 await checkCurrentNodes(nodes, powNodes, sharedPreferences);
219 break;
220
221 case 17:
167 - await changeDefaultHavenNode(nodes);
222 + await _changeDefaultNode(
223 + nodes: nodes,
224 + sharedPreferences: sharedPreferences,
225 + type: WalletType.haven,
226 + currentNodePreferenceKey: PreferencesKey.currentHavenNodeIdKey,
227 + );
228 break;
229
230 case 18:
171 - await updateWalletTypeNodesWithNewNode(
172 - nodes: nodes,
173 - newNodeUri: "cakexmrl7bonq7ovjka5kuwuyd3f7qnkz6z6s6dmsy3uckwra7bvggyd.onion:18081",
174 - type: WalletType.monero,
175 - );
231 + addWalletNodeList(nodes: nodes, type: WalletType.monero);
232 break;
233
234 case 19:
@@ -182,47 +238,82 @@ Future<void> defaultSettingsMigration(
238 await migrateExchangeStatus(sharedPreferences);
239 break;
240 case 21:
185 - await addEthereumNodeList(nodes: nodes);
186 - await changeEthereumCurrentNodeToDefault(
187 - sharedPreferences: sharedPreferences, nodes: nodes);
241 + await addWalletNodeList(nodes: nodes, type: WalletType.ethereum);
242 + await _changeDefaultNode(
243 + nodes: nodes,
244 + sharedPreferences: sharedPreferences,
245 + type: WalletType.ethereum,
246 + currentNodePreferenceKey: PreferencesKey.currentEthereumNodeIdKey,
247 + );
248 break;
249 case 22:
190 - await addNanoNodeList(nodes: nodes);
250 + await addWalletNodeList(nodes: nodes, type: WalletType.nano);
251 await addNanoPowNodeList(nodes: powNodes);
192 - await changeNanoCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
193 - await changeNanoCurrentPowNodeToDefault(
194 - sharedPreferences: sharedPreferences, nodes: powNodes);
252 + await _changeDefaultNode(
253 + nodes: nodes,
254 + sharedPreferences: sharedPreferences,
255 + type: WalletType.nano,
256 + currentNodePreferenceKey: PreferencesKey.currentNanoNodeIdKey,
257 + );
258 + await _changeDefaultNode(
259 + nodes: powNodes,
260 + sharedPreferences: sharedPreferences,
261 + type: WalletType.nano,
262 + currentNodePreferenceKey: PreferencesKey.currentNanoPowNodeIdKey,
263 + newDefaultUri: nanoDefaultPowNodeUri,
264 + );
265 break;
266 case 23:
197 - await addBitcoinCashElectrumServerList(nodes: nodes);
198 - await changeBitcoinCurrentElectrumServerToDefault(
199 - sharedPreferences: sharedPreferences, nodes: nodes);
267 + await addWalletNodeList(nodes: nodes, type: WalletType.bitcoinCash);
268 + await _changeDefaultNode(
269 + nodes: nodes,
270 + sharedPreferences: sharedPreferences,
271 + type: WalletType.bitcoinCash,
272 + currentNodePreferenceKey: PreferencesKey.currentBitcoinCashNodeIdKey,
273 + );
274 break;
275 case 24:
202 - await addPolygonNodeList(nodes: nodes);
203 - await changePolygonCurrentNodeToDefault(
204 - sharedPreferences: sharedPreferences, nodes: nodes);
276 + await addWalletNodeList(nodes: nodes, type: WalletType.polygon);
277 + await _changeDefaultNode(
278 + nodes: nodes,
279 + sharedPreferences: sharedPreferences,
280 + type: WalletType.polygon,
281 + currentNodePreferenceKey: PreferencesKey.currentPolygonNodeIdKey,
282 + );
283 break;
284 case 25:
285 await rewriteSecureStoragePin(secureStorage: secureStorage);
286 break;
287 case 26:
288
211 - /// commented out as it was a probable cause for some users to have white screen issues
212 - /// maybe due to multiple access on Secure Storage at once
213 - /// or long await time on start of the app
214 - // await insecureStorageMigration(secureStorage: secureStorage, sharedPreferences: sharedPreferences);
289 + /// commented out as it was a probable cause for some users to have white screen issues
290 + /// maybe due to multiple access on Secure Storage at once
291 + /// or long await time on start of the app
292 + // await insecureStorageMigration(secureStorage: secureStorage, sharedPreferences: sharedPreferences);
293 + break;
294 case 27:
216 - await addSolanaNodeList(nodes: nodes);
217 - await changeSolanaCurrentNodeToDefault(
218 - sharedPreferences: sharedPreferences, nodes: nodes);
295 + await addWalletNodeList(nodes: nodes, type: WalletType.solana);
296 + await _changeDefaultNode(
297 + nodes: nodes,
298 + sharedPreferences: sharedPreferences,
299 + type: WalletType.solana,
300 + currentNodePreferenceKey: PreferencesKey.currentSolanaNodeIdKey,
301 + );
302 break;
303
304 case 28:
305 await _updateMoneroPriority(sharedPreferences);
306 break;
307 case 29:
225 - await changeDefaultBitcoinNode(nodes, sharedPreferences);
308 + await _changeDefaultNode(
309 + nodes: nodes,
310 + sharedPreferences: sharedPreferences,
311 + type: WalletType.bitcoin,
312 + newDefaultUri: newCakeWalletBitcoinUri,
313 + currentNodePreferenceKey: PreferencesKey.currentBitcoinElectrumSererIdKey,
314 + useSSL: true,
315 + oldUri: ['.cakewallet.com'],
316 + );
317 break;
318 case 30:
319 await disableServiceStatusFiatDisabled(sharedPreferences);
@@ -234,28 +325,50 @@ Future<void> defaultSettingsMigration(
325 await updateBtcNanoWalletInfos(walletInfoSource);
326 break;
327 case 33:
237 - await addTronNodeList(nodes: nodes);
238 - await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
328 + await addWalletNodeList(nodes: nodes, type: WalletType.tron);
329 + await _changeDefaultNode(
330 + nodes: nodes,
331 + sharedPreferences: sharedPreferences,
332 + type: WalletType.tron,
333 + currentNodePreferenceKey: PreferencesKey.currentTronNodeIdKey,
334 + );
335 break;
336 case 34:
241 - await _addElectRsNode(nodes, sharedPreferences);
337 + addWalletNodeList(nodes: nodes, type: WalletType.bitcoin);
338 case 35:
243 - await _switchElectRsNode(nodes, sharedPreferences);
339 + await _changeDefaultNode(
340 + nodes: nodes,
341 + sharedPreferences: sharedPreferences,
342 + type: WalletType.bitcoin,
343 + newDefaultUri: newCakeWalletBitcoinUri,
344 + currentNodePreferenceKey: PreferencesKey.currentBitcoinElectrumSererIdKey,
345 + useSSL: true,
346 + oldUri: ['electrs.cakewallet.com'],
347 + );
348 break;
349 case 36:
246 - await addWowneroNodeList(nodes: nodes);
350 + await addWalletNodeList(nodes: nodes, type: WalletType.wownero);
351 await changeWowneroCurrentNodeToDefault(
352 sharedPreferences: sharedPreferences, nodes: nodes);
353 break;
354 case 37:
251 - await replaceTronDefaultNode(sharedPreferences: sharedPreferences, nodes: nodes);
355 + // removed as it would be replaced again anyway
356 + // await replaceTronDefaultNode(sharedPreferences: sharedPreferences, nodes: nodes);
357 break;
358 case 38:
359 await fixBtcDerivationPaths(walletInfoSource);
360 break;
361 case 39:
362 _fixNodesUseSSLFlag(nodes);
258 - await changeDefaultNanoNode(nodes, sharedPreferences);
363 + await _changeDefaultNode(
364 + nodes: nodes,
365 + sharedPreferences: sharedPreferences,
366 + type: WalletType.nano,
367 + newDefaultUri: nanoDefaultNodeUri,
368 + currentNodePreferenceKey: PreferencesKey.currentNanoNodeIdKey,
369 + useSSL: true,
370 + oldUri: ['rpc.nano.to'],
371 + );
372 break;
373 case 40:
374 await removeMoneroWorld(sharedPreferences: sharedPreferences, nodes: nodes);
@@ -266,8 +379,8 @@ Future<void> defaultSettingsMigration(
379 providerName: "SwapTrade",
380 enabled: false,
381 );
269 - await _addSethNode(nodes, sharedPreferences);
270 - await updateTronNodesWithNowNodes(sharedPreferences: sharedPreferences, nodes: nodes);
382 + addWalletNodeList(nodes: nodes, type: WalletType.bitcoin);
383 + addWalletNodeList(nodes: nodes, type: WalletType.tron);
384 break;
385 case 42:
386 _fixNodesUseSSLFlag(nodes);
@@ -312,19 +425,8 @@ Future<void> defaultSettingsMigration(
425 case 45:
426 await _backupHavenSeeds(havenSeedStore);
427
315 - updateWalletTypeNodesWithNewNode(
316 - newNodeUri: 'matic.nownodes.io',
317 - nodes: nodes,
318 - type: WalletType.polygon,
319 - useSSL: true,
320 - );
321 - updateWalletTypeNodesWithNewNode(
322 - newNodeUri: 'eth.nownodes.io',
323 - nodes: nodes,
324 - type: WalletType.ethereum,
325 - useSSL: true,
326 - );
327 -
428 + addWalletNodeList(nodes: nodes, type: WalletType.polygon);
429 + addWalletNodeList(nodes: nodes, type: WalletType.ethereum);
430 _changeDefaultNode(
431 nodes: nodes,
432 sharedPreferences: sharedPreferences,
@@ -349,18 +451,7 @@ Future<void> defaultSettingsMigration(
451 break;
452 case 46:
453 await _fixNodesUseSSLFlag(nodes);
352 - await updateWalletTypeNodesWithNewNode(
353 - newNodeUri: 'litecoin.stackwallet.com:20063',
354 - nodes: nodes,
355 - type: WalletType.litecoin,
356 - useSSL: true,
357 - );
358 - await updateWalletTypeNodesWithNewNode(
359 - newNodeUri: 'electrum-ltc.bysh.me:50002',
360 - nodes: nodes,
361 - type: WalletType.litecoin,
362 - useSSL: true,
363 - );
454 + await addWalletNodeList(nodes: nodes, type: WalletType.litecoin);
455 await _changeDefaultNode(
456 nodes: nodes,
457 sharedPreferences: sharedPreferences,
@@ -386,10 +477,14 @@ Future<void> defaultSettingsMigration(
477 newUri: "polygon-bor-rpc.publicnode.com",
478 useSSL: true,
479 );
389 - break;
480 case 47:
391 - await addZanoNodeList(nodes: nodes);
392 - await changeZanoCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
481 + await addWalletNodeList(nodes: nodes, type: WalletType.zano);
482 + await _changeDefaultNode(
483 + nodes: nodes,
484 + sharedPreferences: sharedPreferences,
485 + type: WalletType.zano,
486 + currentNodePreferenceKey: PreferencesKey.currentZanoNodeIdKey,
487 + );
488 _changeExchangeProviderAvailability(
489 sharedPreferences,
490 providerName: "SimpleSwap",
@@ -400,8 +495,17 @@ Future<void> defaultSettingsMigration(
495 providerName: "SwapTrade",
496 enabled: false,
497 );
403 - break;
498 + break;
499 case 48:
500 + await addWalletNodeList(nodes: nodes, type: WalletType.decred);
501 + await _changeDefaultNode(
502 + nodes: nodes,
503 + sharedPreferences: sharedPreferences,
504 + type: WalletType.decred,
505 + currentNodePreferenceKey: PreferencesKey.currentDecredNodeIdKey,
506 + );
507 + break;
508 + case 49:
509 _changeExchangeProviderAvailability(
510 sharedPreferences,
511 providerName: "SwapTrade",
@@ -414,8 +518,9 @@ Future<void> defaultSettingsMigration(
518
519 await sharedPreferences.setInt(
520 PreferencesKey.currentDefaultSettingsMigrationVersion, version);
417 - } catch (e) {
521 + } catch (e, s) {
522 printV('Migration error: ${e.toString()}');
523 + printV('Migration error: ${s}');
524 }
525 });
526
@@ -455,17 +560,24 @@ Future<void> _changeDefaultNode({
560 required Box<Node> nodes,
561 required SharedPreferences sharedPreferences,
562 required WalletType type,
458 - required String newDefaultUri,
563 required String currentNodePreferenceKey,
460 - required bool useSSL,
461 - required List<String>
462 - oldUri, // leave empty if you want to force replace the node regardless of the user's current node
564 + bool useSSL = true,
565 + bool trusted = false,
566 + String? newDefaultUri, // ignore, if you want to use the default node uri
567 + List<String>?
568 + oldUri, // ignore, if you want to force replace the node regardless of the user's current node
569 }) async {
570 final currentNodeId = sharedPreferences.getInt(currentNodePreferenceKey);
465 - final currentNode = nodes.values.firstWhere((node) => node.key == currentNodeId);
466 - final shouldReplace = oldUri.any((e) => currentNode.uriRaw.contains(e));
571 + final bool shouldReplace;
572 + if (currentNodeId == null) {
573 + shouldReplace = true;
574 + } else {
575 + final currentNode = nodes.values.firstWhere((node) => node.key == currentNodeId);
576 + shouldReplace = oldUri?.any((e) => currentNode.uriRaw.contains(e)) ?? true;
577 + }
578
579 if (shouldReplace) {
580 + newDefaultUri ??= _getDefaultNodeUri(type);
581 var newNodeId =
582 nodes.values.firstWhereOrNull((element) => element.uriRaw == newDefaultUri)?.key;
583
@@ -475,6 +587,7 @@ Future<void> _changeDefaultNode({
587 uri: newDefaultUri,
588 type: type,
589 useSSL: useSSL,
590 + trusted: trusted,
591 );
592
593 await nodes.add(newNode);
@@ -485,23 +598,38 @@ Future<void> _changeDefaultNode({
598 }
599 }
600
488 -/// Generic function for adding a new Node for a Wallet Type.
489 -Future<void> updateWalletTypeNodesWithNewNode({
490 - required Box<Node> nodes,
491 - required WalletType type,
492 - required String newNodeUri,
493 - bool? useSSL,
494 -}) async {
495 - // If it already exists in the box of nodes, no need to add it annymore.
496 - if (nodes.values.any((node) => node.uriRaw == newNodeUri)) return;
497 -
498 - await nodes.add(
499 - Node(
500 - uri: newNodeUri,
501 - type: type,
502 - useSSL: useSSL,
503 - ),
504 - );
601 +String _getDefaultNodeUri(WalletType type) {
602 + switch (type) {
603 + case WalletType.monero:
604 + return newCakeWalletMoneroUri;
605 + case WalletType.bitcoin:
606 + return newCakeWalletBitcoinUri;
607 + case WalletType.litecoin:
608 + return cakeWalletLitecoinElectrumUri;
609 + case WalletType.haven:
610 + return havenDefaultNodeUri;
611 + case WalletType.ethereum:
612 + return ethereumDefaultNodeUri;
613 + case WalletType.nano:
614 + return nanoDefaultNodeUri;
615 + case WalletType.bitcoinCash:
616 + return cakeWalletBitcoinCashDefaultNodeUri;
617 + case WalletType.polygon:
618 + return polygonDefaultNodeUri;
619 + case WalletType.solana:
620 + return solanaDefaultNodeUri;
621 + case WalletType.tron:
622 + return tronDefaultNodeUri;
623 + case WalletType.wownero:
624 + return wowneroDefaultNodeUri;
625 + case WalletType.zano:
626 + return zanoDefaultNodeUri;
627 + case WalletType.decred:
628 + return decredDefaultUri;
629 + case WalletType.banano:
630 + case WalletType.none:
631 + return '';
632 + }
633 }
634
635 void _changeExchangeProviderAvailability(SharedPreferences sharedPreferences,
@@ -533,7 +661,7 @@ Future<void> _fixNodesUseSSLFlag(Box<Node> nodes) async {
661 }
662
663 Future<void> updateNanoNodeList({required Box<Node> nodes}) async {
536 - final nodeList = await loadDefaultNanoNodes();
664 + final nodeList = await loadDefaultNodes(WalletType.nano);
665 var listOfNewEndpoints = <String>[
666 "app.natrium.io",
667 "rainstorm.city",
@@ -682,87 +810,16 @@ Future<void> replaceNodesMigration({required Box<Node> nodes}) async {
810 });
811 }
812
685 -Future<void> changeMoneroCurrentNodeToDefault(
686 - {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
687 - final node = getMoneroDefaultNode(nodes: nodes);
688 - final nodeId = node.key as int? ?? 0; // 0 - England
689 -
690 - await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, nodeId);
691 -}
692 -
693 -Node? getBitcoinDefaultElectrumServer({required Box<Node> nodes}) {
694 - return nodes.values
695 - .firstWhereOrNull((Node node) => node.uriRaw == cakeWalletBitcoinElectrumUri) ??
696 - nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoin);
697 -}
698 -
813 Node? getBitcoinTestnetDefaultElectrumServer({required Box<Node> nodes}) {
814 return nodes.values
815 .firstWhereOrNull((Node node) => node.uriRaw == publicBitcoinTestnetElectrumUri) ??
816 nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoin);
817 }
818
705 -Node? getLitecoinDefaultElectrumServer({required Box<Node> nodes}) {
706 - return nodes.values
707 - .firstWhereOrNull((Node node) => node.uriRaw == cakeWalletLitecoinElectrumUri) ??
708 - nodes.values.firstWhereOrNull((node) => node.type == WalletType.litecoin);
709 -}
710 -
711 -Node? getHavenDefaultNode({required Box<Node> nodes}) {
712 - return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == havenDefaultNodeUri) ??
713 - nodes.values.firstWhereOrNull((node) => node.type == WalletType.haven);
714 -}
715 -
716 -Node? getEthereumDefaultNode({required Box<Node> nodes}) {
717 - return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == ethereumDefaultNodeUri) ??
718 - nodes.values.firstWhereOrNull((node) => node.type == WalletType.ethereum);
719 -}
720 -
721 -Node? getPolygonDefaultNode({required Box<Node> nodes}) {
722 - return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == polygonDefaultNodeUri) ??
723 - nodes.values.firstWhereOrNull((node) => node.type == WalletType.polygon);
724 -}
725 -
726 -Node? getNanoDefaultNode({required Box<Node> nodes}) {
727 - return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultNodeUri) ??
728 - nodes.values.firstWhereOrNull((node) => node.type == WalletType.nano);
729 -}
730 -
731 -Node? getNanoDefaultPowNode({required Box<Node> nodes}) {
732 - return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultPowNodeUri) ??
733 - nodes.values.firstWhereOrNull((node) => (node.type == WalletType.nano));
734 -}
735 -
736 -Node? getBitcoinCashDefaultElectrumServer({required Box<Node> nodes}) {
737 - return nodes.values
738 - .firstWhereOrNull((Node node) => node.uriRaw == cakeWalletBitcoinCashDefaultNodeUri) ??
739 - nodes.values.firstWhereOrNull((node) => node.type == WalletType.bitcoinCash);
740 -}
741 -
742 -Node? getZanoDefaultNode({required Box<Node> nodes}) {
743 - return nodes.values.firstWhereOrNull(
744 - (Node node) => node.uriRaw == zanoDefaultNodeUri)
745 - ?? nodes.values.firstWhereOrNull((node) => node.type == WalletType.zano);
746 -}
747 -
748 -Node getMoneroDefaultNode({required Box<Node> nodes}) {
749 - var nodeUri = newCakeWalletMoneroUri;
750 -
751 - try {
752 - return nodes.values.firstWhere((Node node) => node.uriRaw == nodeUri);
753 - } catch (_) {
754 - return nodes.values.first;
755 - }
756 -}
757 -
758 -Node? getSolanaDefaultNode({required Box<Node> nodes}) {
759 - return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == solanaDefaultNodeUri) ??
760 - nodes.values.firstWhereOrNull((node) => node.type == WalletType.solana);
761 -}
762 -
763 -Node? getTronDefaultNode({required Box<Node> nodes}) {
764 - return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == tronDefaultNodeUri) ??
765 - nodes.values.firstWhereOrNull((node) => node.type == WalletType.tron);
819 +Node? getDefaultNode({required Box<Node> nodes, required WalletType type}) {
820 + final defaultUri = _getDefaultNodeUri(type);
821 + return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == defaultUri) ??
822 + nodes.values.firstWhereOrNull((node) => node.type == type);
823 }
824
825 Node getWowneroDefaultNode({required Box<Node> nodes}) {
@@ -899,63 +956,6 @@ Future<void> rewriteSecureStoragePin({required SecureStorage secureStorage}) asy
956 );
957 }
958
902 -Future<void> changeBitcoinCurrentElectrumServerToDefault(
903 - {required SharedPreferences sharedPreferences,
904 - required Box<Node> nodes,
905 - bool? isTestnet}) async {
906 - Node? server;
907 - if (isTestnet == true) {
908 - server = getBitcoinTestnetDefaultElectrumServer(nodes: nodes);
909 - } else {
910 - server = getBitcoinDefaultElectrumServer(nodes: nodes);
911 - }
912 - final serverId = server?.key as int? ?? 0;
913 -
914 - await sharedPreferences.setInt(PreferencesKey.currentBitcoinElectrumSererIdKey, serverId);
915 -}
916 -
917 -Future<void> changeLitecoinCurrentElectrumServerToDefault(
918 - {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
919 - final server = getLitecoinDefaultElectrumServer(nodes: nodes);
920 - final serverId = server?.key as int? ?? 0;
921 -
922 - await sharedPreferences.setInt(PreferencesKey.currentLitecoinElectrumSererIdKey, serverId);
923 -}
924 -
925 -Future<void> changeBitcoinCashCurrentNodeToDefault(
926 - {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
927 - final server = getBitcoinCashDefaultElectrumServer(nodes: nodes);
928 - final serverId = server?.key as int? ?? 0;
929 -
930 - await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, serverId);
931 -}
932 -
933 -Future<void> changeHavenCurrentNodeToDefault(
934 - {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
935 - final node = getHavenDefaultNode(nodes: nodes);
936 - final nodeId = node?.key as int? ?? 0;
937 -
938 - await sharedPreferences.setInt(PreferencesKey.currentHavenNodeIdKey, nodeId);
939 -}
940 -
941 -Future<void> replaceDefaultNode(
942 - {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
943 - const nodesForReplace = <String>[
944 - 'xmr-node-uk.cakewallet.com:18081',
945 - 'eu-node.cakewallet.io:18081',
946 - 'node.cakewallet.io:18081'
947 - ];
948 - final currentNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
949 - final currentNode = nodes.values.firstWhereOrNull((Node node) => node.key == currentNodeId);
950 - final needToReplace = currentNode == null ? true : nodesForReplace.contains(currentNode.uriRaw);
951 -
952 - if (!needToReplace) {
953 - return;
954 - }
955 -
956 - await changeMoneroCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
957 -}
958 -
959 Future<void> updateNodeTypes({required Box<Node> nodes}) async {
960 nodes.values.forEach((node) async {
961 if (node.type == null) {
@@ -965,42 +965,6 @@ Future<void> updateNodeTypes({required Box<Node> nodes}) async {
965 });
966 }
967
968 -Future<void> addBitcoinElectrumServerList({required Box<Node> nodes}) async {
969 - final serverList = await loadBitcoinElectrumServerList();
970 - for (var node in serverList) {
971 - if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
972 - await nodes.add(node);
973 - }
974 - }
975 -}
976 -
977 -Future<void> addLitecoinElectrumServerList({required Box<Node> nodes}) async {
978 - final serverList = await loadLitecoinElectrumServerList();
979 - for (var node in serverList) {
980 - if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
981 - await nodes.add(node);
982 - }
983 - }
984 -}
985 -
986 -Future<void> addBitcoinCashElectrumServerList({required Box<Node> nodes}) async {
987 - final serverList = await loadBitcoinCashElectrumServerList();
988 - for (var node in serverList) {
989 - if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
990 - await nodes.add(node);
991 - }
992 - }
993 -}
994 -
995 -Future<void> addHavenNodeList({required Box<Node> nodes}) async {
996 - final nodeList = await loadDefaultHavenNodes();
997 - for (var node in nodeList) {
998 - if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
999 - await nodes.add(node);
1000 - }
1001 - }
1002 -}
1003 -
968 Future<void> addAddressesForMoneroWallets(Box<WalletInfo> walletInfoSource) async {
969 final moneroWalletsInfo = walletInfoSource.values.where((info) => info.type == WalletType.monero);
970 moneroWalletsInfo.forEach((info) async {
@@ -1050,31 +1014,6 @@ Future<void> changeTransactionPriorityAndFeeRateKeys(SharedPreferences sharedPre
1014 bitcoin!.getMediumTransactionPriority().serialize());
1015 }
1016
1053 -Future<void> changeDefaultMoneroNode(
1054 - Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
1055 - const cakeWalletMoneroNodeUriPattern = '.cakewallet.com';
1056 - final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
1057 - final currentMoneroNode = nodeSource.values.firstWhere((node) => node.key == currentMoneroNodeId);
1058 - final needToReplaceCurrentMoneroNode =
1059 - currentMoneroNode.uri.toString().contains(cakeWalletMoneroNodeUriPattern);
1060 -
1061 - nodeSource.values.forEach((node) async {
1062 - if (node.type == WalletType.monero &&
1063 - node.uri.toString().contains(cakeWalletMoneroNodeUriPattern)) {
1064 - await node.delete();
1065 - }
1066 - });
1067 -
1068 - final newCakeWalletNode =
1069 - Node(uri: newCakeWalletMoneroUri, type: WalletType.monero, trusted: true);
1070 -
1071 - await nodeSource.add(newCakeWalletNode);
1072 -
1073 - if (needToReplaceCurrentMoneroNode) {
1074 - await sharedPreferences.setInt(PreferencesKey.currentNodeIdKey, newCakeWalletNode.key as int);
1075 - }
1076 -}
1077 -
1017 Future<void> fixBtcDerivationPaths(Box<WalletInfo> walletsInfoSource) async {
1018 for (WalletInfo walletInfo in walletsInfoSource.values) {
1019 if (walletInfo.type == WalletType.bitcoin ||
@@ -1102,128 +1041,6 @@ Future<void> updateBtcNanoWalletInfos(Box<WalletInfo> walletsInfoSource) async {
1041 }
1042 }
1043
1105 -Future<void> changeDefaultNanoNode(
1106 - Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
1107 - const oldNanoNodeUriPattern = 'rpc.nano.to';
1108 - final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
1109 - final currentNanoNode = nodeSource.values.firstWhere((node) => node.key == currentNanoNodeId);
1110 -
1111 - final newCakeWalletNode = Node(
1112 - uri: nanoDefaultNodeUri,
1113 - type: WalletType.nano,
1114 - useSSL: true,
1115 - );
1116 -
1117 - await nodeSource.add(newCakeWalletNode);
1118 -
1119 - if (currentNanoNode.uri.toString().contains(oldNanoNodeUriPattern)) {
1120 - await sharedPreferences.setInt(
1121 - PreferencesKey.currentNanoNodeIdKey, newCakeWalletNode.key as int);
1122 - }
1123 -}
1124 -
1125 -Future<void> changeDefaultBitcoinNode(
1126 - Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
1127 - const cakeWalletBitcoinNodeUriPattern = '.cakewallet.com';
1128 - final currentBitcoinNodeId =
1129 - sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
1130 - final currentBitcoinNode =
1131 - nodeSource.values.firstWhere((node) => node.key == currentBitcoinNodeId);
1132 - final needToReplaceCurrentBitcoinNode =
1133 - currentBitcoinNode.uri.toString().contains(cakeWalletBitcoinNodeUriPattern);
1134 -
1135 - final newCakeWalletBitcoinNode =
1136 - Node(uri: newCakeWalletBitcoinUri, type: WalletType.bitcoin, useSSL: true);
1137 -
1138 - if (!nodeSource.values.any((element) => element.uriRaw == newCakeWalletBitcoinUri)) {
1139 - await nodeSource.add(newCakeWalletBitcoinNode);
1140 - }
1141 -
1142 - if (needToReplaceCurrentBitcoinNode) {
1143 - await sharedPreferences.setInt(
1144 - PreferencesKey.currentBitcoinElectrumSererIdKey, newCakeWalletBitcoinNode.key as int);
1145 - }
1146 -}
1147 -
1148 -Future<void> _addSethNode(Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
1149 - _addBitcoinNode(
1150 - nodeSource: nodeSource,
1151 - sharedPreferences: sharedPreferences,
1152 - nodeUri: "fulcrum.sethforprivacy.com:50002",
1153 - useSSL: false,
1154 - );
1155 -}
1156 -
1157 -Future<void> _addElectRsNode(Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
1158 - _addBitcoinNode(
1159 - nodeSource: nodeSource,
1160 - sharedPreferences: sharedPreferences,
1161 - nodeUri: cakeWalletSilentPaymentsElectrsUri,
1162 - );
1163 -}
1164 -
1165 -Future<void> _addBitcoinNode({
1166 - required Box<Node> nodeSource,
1167 - required SharedPreferences sharedPreferences,
1168 - required String nodeUri,
1169 - bool replaceExisting = false,
1170 - bool useSSL = false,
1171 -}) async {
1172 - bool isNodeExists = nodeSource.values.any((element) => element.uriRaw == nodeUri);
1173 - if (isNodeExists) {
1174 - return;
1175 - }
1176 - const cakeWalletBitcoinNodeUriPattern = '.cakewallet.com';
1177 - final currentBitcoinNodeId =
1178 - sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
1179 - final currentBitcoinNode =
1180 - nodeSource.values.firstWhere((node) => node.key == currentBitcoinNodeId);
1181 - final needToReplaceCurrentBitcoinNode =
1182 - currentBitcoinNode.uri.toString().contains(cakeWalletBitcoinNodeUriPattern);
1183 -
1184 - final newElectRsBitcoinNode = Node(uri: nodeUri, type: WalletType.bitcoin, useSSL: useSSL);
1185 -
1186 - await nodeSource.add(newElectRsBitcoinNode);
1187 -
1188 - if (needToReplaceCurrentBitcoinNode && replaceExisting) {
1189 - await sharedPreferences.setInt(
1190 - PreferencesKey.currentBitcoinElectrumSererIdKey, newElectRsBitcoinNode.key as int);
1191 - }
1192 -}
1193 -
1194 -Future<void> _switchElectRsNode(Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
1195 - final currentBitcoinNodeId =
1196 - sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
1197 - final currentBitcoinNode =
1198 - nodeSource.values.firstWhere((node) => node.key == currentBitcoinNodeId);
1199 - final needToReplaceCurrentBitcoinNode =
1200 - currentBitcoinNode.uri.toString().contains('electrs.cakewallet.com');
1201 -
1202 - if (!needToReplaceCurrentBitcoinNode) return;
1203 -
1204 - final btcElectrumNode = nodeSource.values.firstWhereOrNull(
1205 - (node) => node.uri.toString().contains('btc-electrum.cakewallet.com'),
1206 - );
1207 -
1208 - if (btcElectrumNode == null) {
1209 - final newBtcElectrumBitcoinNode = Node(
1210 - uri: newCakeWalletBitcoinUri,
1211 - type: WalletType.bitcoin,
1212 - useSSL: false,
1213 - );
1214 - await nodeSource.add(newBtcElectrumBitcoinNode);
1215 - await sharedPreferences.setInt(
1216 - PreferencesKey.currentBitcoinElectrumSererIdKey,
1217 - newBtcElectrumBitcoinNode.key as int,
1218 - );
1219 - } else {
1220 - await sharedPreferences.setInt(
1221 - PreferencesKey.currentBitcoinElectrumSererIdKey,
1222 - btcElectrumNode.key as int,
1223 - );
1224 - }
1225 -}
1226 -
1044 Future<void> checkCurrentNodes(
1045 Box<Node> nodeSource, Box<Node> powNodeSource, SharedPreferences sharedPreferences) async {
1046 final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
@@ -1236,6 +1053,7 @@ Future<void> checkCurrentNodes(
1053 final currentPolygonNodeId = sharedPreferences.getInt(PreferencesKey.currentPolygonNodeIdKey);
1054 final currentNanoNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoNodeIdKey);
1055 final currentNanoPowNodeId = sharedPreferences.getInt(PreferencesKey.currentNanoPowNodeIdKey);
1056 + final currentDecredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey);
1057 final currentBitcoinCashNodeId =
1058 sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey);
1059 final currentSolanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey);
@@ -1256,6 +1074,8 @@ Future<void> checkCurrentNodes(
1074 nodeSource.values.firstWhereOrNull((node) => node.key == currentPolygonNodeId);
1075 final currentNanoNodeServer =
1076 nodeSource.values.firstWhereOrNull((node) => node.key == currentNanoNodeId);
1077 + final currentDecredNodeServer =
1078 + nodeSource.values.firstWhereOrNull((node) => node.key == currentDecredNodeId);
1079 final currentNanoPowNodeServer =
1080 powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId);
1081 final currentBitcoinCashNodeServer =
@@ -1266,7 +1086,8 @@ Future<void> checkCurrentNodes(
1086 nodeSource.values.firstWhereOrNull((node) => node.key == currentTronNodeId);
1087 final currentWowneroNodeServer =
1088 nodeSource.values.firstWhereOrNull((node) => node.key == currentWowneroNodeId);
1269 - final currentZanoNode = nodeSource.values.firstWhereOrNull((node) => node.key == currentZanoNodeId);
1089 + final currentZanoNode =
1090 + nodeSource.values.firstWhereOrNull((node) => node.key == currentZanoNodeId);
1091
1092 if (currentMoneroNode == null) {
1093 final newCakeWalletNode = Node(uri: newCakeWalletMoneroUri, type: WalletType.monero);
@@ -1357,6 +1178,12 @@ Future<void> checkCurrentNodes(
1178 await nodeSource.add(node);
1179 await sharedPreferences.setInt(PreferencesKey.currentZanoNodeIdKey, node.key as int);
1180 }
1181 +
1182 + if (currentDecredNodeServer == null) {
1183 + final node = Node(uri: decredDefaultUri, type: WalletType.decred);
1184 + await nodeSource.add(node);
1185 + await sharedPreferences.setInt(PreferencesKey.currentDecredNodeIdKey, node.key as int);
1186 + }
1187 }
1188
1189 Future<void> resetBitcoinElectrumServer(
@@ -1385,15 +1212,6 @@ Future<void> resetBitcoinElectrumServer(
1212 await oldElectrumServer?.delete();
1213 }
1214
1388 -Future<void> changeDefaultHavenNode(Box<Node> nodeSource) async {
1389 - const previousHavenDefaultNodeUri = 'vault.havenprotocol.org:443';
1390 - final havenNodes = nodeSource.values.where((node) => node.uriRaw == previousHavenDefaultNodeUri);
1391 - havenNodes.forEach((node) async {
1392 - node.uriRaw = havenDefaultNodeUri;
1393 - await node.save();
1394 - });
1395 -}
1396 -
1215 Future<void> migrateExchangeStatus(SharedPreferences sharedPreferences) async {
1216 final isExchangeDisabled = sharedPreferences.getBool(PreferencesKey.disableExchangeKey);
1217 if (isExchangeDisabled == null) {
@@ -1406,65 +1224,14 @@ Future<void> migrateExchangeStatus(SharedPreferences sharedPreferences) async {
1224 await sharedPreferences.remove(PreferencesKey.disableExchangeKey);
1225 }
1226
1409 -Future<void> addEthereumNodeList({required Box<Node> nodes}) async {
1410 - final nodeList = await loadDefaultEthereumNodes();
1411 - for (var node in nodeList) {
1412 - if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
1413 - await nodes.add(node);
1414 - }
1415 - }
1416 -}
1417 -
1418 -Future<void> changeEthereumCurrentNodeToDefault(
1419 - {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
1420 - final node = getEthereumDefaultNode(nodes: nodes);
1421 - final nodeId = node?.key as int? ?? 0;
1422 -
1423 - await sharedPreferences.setInt(PreferencesKey.currentEthereumNodeIdKey, nodeId);
1424 -}
1425 -
1426 -Future<void> addWowneroNodeList({required Box<Node> nodes}) async {
1427 - final nodeList = await loadDefaultWowneroNodes();
1428 - for (var node in nodeList) {
1429 - if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
1430 - await nodes.add(node);
1431 - }
1432 - }
1433 -}
1434 -
1435 -Future<void> addZanoNodeList({required Box<Node> nodes}) async {
1436 - final nodeList = await loadDefaultZanoNodes();
1437 - for (var node in nodeList) {
1438 - if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
1439 - await nodes.add(node);
1440 - }
1441 - }
1442 -}
1443 -
1227 Future<void> changeWowneroCurrentNodeToDefault(
1228 {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
1229 final node = getWowneroDefaultNode(nodes: nodes);
1447 - final nodeId = node?.key as int? ?? 0;
1230 + final nodeId = node.key as int? ?? 0;
1231
1232 await sharedPreferences.setInt(PreferencesKey.currentWowneroNodeIdKey, nodeId);
1233 }
1234
1452 -Future<void> changeZanoCurrentNodeToDefault(
1453 - {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
1454 - final node = getZanoDefaultNode(nodes: nodes);
1455 - final nodeId = node?.key as int? ?? 0;
1456 - await sharedPreferences.setInt(PreferencesKey.currentZanoNodeIdKey, nodeId);
1457 -}
1458 -
1459 -Future<void> addNanoNodeList({required Box<Node> nodes}) async {
1460 - final nodeList = await loadDefaultNanoNodes();
1461 - for (var node in nodeList) {
1462 - if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
1463 - await nodes.add(node);
1464 - }
1465 - }
1466 -}
1467 -
1235 Future<void> addNanoPowNodeList({required Box<Node> nodes}) async {
1236 final nodeList = await loadDefaultNanoPowNodes();
1237 for (var node in nodeList) {
@@ -1474,57 +1241,13 @@ Future<void> addNanoPowNodeList({required Box<Node> nodes}) async {
1241 }
1242 }
1243
1477 -Future<void> changeNanoCurrentNodeToDefault(
1478 - {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
1479 - final node = getNanoDefaultNode(nodes: nodes);
1480 - final nodeId = node?.key as int? ?? 0;
1481 -
1482 - await sharedPreferences.setInt(PreferencesKey.currentNanoNodeIdKey, nodeId);
1483 -}
1484 -
1485 -Future<void> changeNanoCurrentPowNodeToDefault(
1486 - {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
1487 - final node = getNanoDefaultPowNode(nodes: nodes);
1488 - final nodeId = node?.key as int? ?? 0;
1489 - await sharedPreferences.setInt(PreferencesKey.currentNanoPowNodeIdKey, nodeId);
1490 -}
1491 -
1492 -Future<void> addPolygonNodeList({required Box<Node> nodes}) async {
1493 - final nodeList = await loadDefaultPolygonNodes();
1494 - for (var node in nodeList) {
1495 - if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
1496 - await nodes.add(node);
1497 - }
1498 - }
1499 -}
1500 -
1501 -Future<void> changePolygonCurrentNodeToDefault(
1502 - {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
1503 - final node = getPolygonDefaultNode(nodes: nodes);
1504 - final nodeId = node?.key as int? ?? 0;
1505 -
1506 - await sharedPreferences.setInt(PreferencesKey.currentPolygonNodeIdKey, nodeId);
1507 -}
1508 -
1509 -Future<void> addSolanaNodeList({required Box<Node> nodes}) async {
1510 - final nodeList = await loadDefaultSolanaNodes();
1511 - for (var node in nodeList) {
1512 - if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
1513 - await nodes.add(node);
1514 - }
1515 - }
1516 -}
1517 -
1518 -Future<void> changeSolanaCurrentNodeToDefault(
1519 - {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
1520 - final node = getSolanaDefaultNode(nodes: nodes);
1521 - final nodeId = node?.key as int? ?? 0;
1522 -
1523 - await sharedPreferences.setInt(PreferencesKey.currentSolanaNodeIdKey, nodeId);
1244 +Node? getNanoDefaultPowNode({required Box<Node> nodes}) {
1245 + return nodes.values.firstWhereOrNull((Node node) => node.uriRaw == nanoDefaultPowNodeUri) ??
1246 + nodes.values.firstWhereOrNull((node) => (node.type == WalletType.nano));
1247 }
1248
1526 -Future<void> addTronNodeList({required Box<Node> nodes}) async {
1527 - final nodeList = await loadDefaultTronNodes();
1249 +Future<void> addWalletNodeList({required Box<Node> nodes, required WalletType type}) async {
1250 + final List<Node> nodeList = await loadDefaultNodes(type);
1251 for (var node in nodeList) {
1252 if (nodes.values.firstWhereOrNull((element) => element.uriRaw == node.uriRaw) == null) {
1253 await nodes.add(node);
@@ -1532,40 +1255,6 @@ Future<void> addTronNodeList({required Box<Node> nodes}) async {
1255 }
1256 }
1257
1535 -Future<void> changeTronCurrentNodeToDefault(
1536 - {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
1537 - final node = getTronDefaultNode(nodes: nodes);
1538 - final nodeId = node?.key as int? ?? 0;
1539 -
1540 - await sharedPreferences.setInt(PreferencesKey.currentTronNodeIdKey, nodeId);
1541 -}
1542 -
1543 -Future<void> replaceTronDefaultNode({
1544 - required SharedPreferences sharedPreferences,
1545 - required Box<Node> nodes,
1546 -}) async {
1547 - // Get the currently active node
1548 - final currentTronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
1549 - final currentTronNode =
1550 - nodes.values.firstWhereOrNull((Node node) => node.key == currentTronNodeId);
1551 -
1552 - //Confirm if this node is part of the default nodes from CakeWallet
1553 - final tronDefaultNodeList = [
1554 - 'tron-rpc.publicnode.com:443',
1555 - 'api.trongrid.io',
1556 - ];
1557 - bool needsToBeReplaced =
1558 - currentTronNode == null ? true : tronDefaultNodeList.contains(currentTronNode.uriRaw);
1559 -
1560 - // If it's a custom node, return. We don't want to switch users from their custom nodes
1561 - if (!needsToBeReplaced) {
1562 - return;
1563 - }
1564 -
1565 - // If it's not, we switch user to the new default node: NowNodes
1566 - await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
1567 -}
1568 -
1258 Future<void> removeMoneroWorld(
1259 {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
1260 const cakeWalletMoneroNodeUriPattern = '.moneroworld.com';
@@ -1582,19 +1271,13 @@ Future<void> removeMoneroWorld(
1271 });
1272
1273 if (needToReplaceCurrentMoneroNode) {
1585 - await changeMoneroCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
1274 + await _changeDefaultNode(
1275 + nodes: nodes,
1276 + sharedPreferences: sharedPreferences,
1277 + type: WalletType.monero,
1278 + newDefaultUri: newCakeWalletMoneroUri,
1279 + currentNodePreferenceKey: PreferencesKey.currentNodeIdKey,
1280 + trusted: true,
1281 + );
1282 }
1283 }
1588 -
1589 -Future<void> updateTronNodesWithNowNodes({
1590 - required SharedPreferences sharedPreferences,
1591 - required Box<Node> nodes,
1592 -}) async {
1593 - final tronNowNodesUri = 'trx.nownodes.io';
1594 -
1595 - if (nodes.values.any((node) => node.uriRaw == tronNowNodesUri)) return;
1596 -
1597 - await nodes.add(Node(uri: tronNowNodesUri, type: WalletType.tron));
1598 -
1599 - await replaceTronDefaultNode(sharedPreferences: sharedPreferences, nodes: nodes);
1600 -}
lib/entities/main_actions.dart
+1
@@ -59,6 +59,7 @@ class MainActions {
59 static MainActions sendAction = MainActions._(
60 name: (context) => S.of(context).send,
61 image: 'assets/images/upload.png',
62 + isEnabled: (viewModel) => viewModel.canSend,
63 onTap: (BuildContext context, DashboardViewModel viewModel) async {
64 Navigator.pushNamed(context, Routes.send);
65 },
lib/entities/node_list.dart
+64 -194
@@ -4,111 +4,62 @@ import "package:yaml/yaml.dart";
4 import 'package:cw_core/node.dart';
5 import 'package:cw_core/wallet_type.dart';
6
7 -Future<List<Node>> loadDefaultNodes() async {
8 - final nodesRaw = await rootBundle.loadString('assets/node_list.yml');
9 - final loadedNodes = loadYaml(nodesRaw) as YamlList;
10 - final nodes = <Node>[];
11 -
12 - for (final raw in loadedNodes) {
13 - if (raw is Map) {
14 - final node = Node.fromMap(Map<String, Object>.from(raw));
15 - node.type = WalletType.monero;
16 - nodes.add(node);
17 - }
18 - }
19 -
20 - return nodes;
21 -}
22 -
23 -Future<List<Node>> loadBitcoinElectrumServerList() async {
24 - final serverListRaw = await rootBundle.loadString('assets/bitcoin_electrum_server_list.yml');
25 - final loadedServerList = loadYaml(serverListRaw) as YamlList;
26 - final serverList = <Node>[];
27 -
28 - for (final raw in loadedServerList) {
29 - if (raw is Map) {
30 - final node = Node.fromMap(Map<String, Object>.from(raw));
31 - node.type = WalletType.bitcoin;
32 - serverList.add(node);
33 - }
34 - }
35 -
36 - return serverList;
37 -}
38 -
39 -Future<List<Node>> loadLitecoinElectrumServerList() async {
40 - final serverListRaw = await rootBundle.loadString('assets/litecoin_electrum_server_list.yml');
41 - final loadedServerList = loadYaml(serverListRaw) as YamlList;
42 - final serverList = <Node>[];
43 -
44 - for (final raw in loadedServerList) {
45 - if (raw is Map) {
46 - final node = Node.fromMap(Map<String, Object>.from(raw));
47 - node.type = WalletType.litecoin;
48 - serverList.add(node);
49 - }
50 - }
51 -
52 - return serverList;
53 -}
54 -
55 -Future<List<Node>> loadDefaultHavenNodes() async {
56 - final nodesRaw = await rootBundle.loadString('assets/haven_node_list.yml');
57 - final loadedNodes = loadYaml(nodesRaw) as YamlList;
58 - final nodes = <Node>[];
59 -
60 - for (final raw in loadedNodes) {
61 - if (raw is Map) {
62 - final node = Node.fromMap(Map<String, Object>.from(raw));
63 - node.type = WalletType.haven;
64 - nodes.add(node);
65 - }
7 +Future<List<Node>> loadDefaultNodes(WalletType type) async {
8 + String path;
9 + switch (type) {
10 + case WalletType.monero:
11 + path = 'assets/node_list.yml';
12 + break;
13 + case WalletType.bitcoin:
14 + path = 'assets/bitcoin_electrum_server_list.yml';
15 + break;
16 + case WalletType.litecoin:
17 + path = 'assets/litecoin_electrum_server_list.yml';
18 + break;
19 + case WalletType.haven:
20 + path = 'assets/haven_node_list.yml';
21 + break;
22 + case WalletType.ethereum:
23 + path = 'assets/ethereum_server_list.yml';
24 + break;
25 + case WalletType.nano:
26 + path = 'assets/nano_node_list.yml';
27 + break;
28 + case WalletType.bitcoinCash:
29 + path = 'assets/bitcoin_cash_electrum_server_list.yml';
30 + break;
31 + case WalletType.polygon:
32 + path = 'assets/polygon_node_list.yml';
33 + break;
34 + case WalletType.solana:
35 + path = 'assets/solana_node_list.yml';
36 + break;
37 + case WalletType.tron:
38 + path = 'assets/tron_node_list.yml';
39 + break;
40 + case WalletType.wownero:
41 + path = 'assets/wownero_node_list.yml';
42 + break;
43 + case WalletType.zano:
44 + path = 'assets/zano_node_list.yml';
45 + break;
46 + case WalletType.decred:
47 + path = 'assets/decred_node_list.yml';
48 + break;
49 + case WalletType.banano:
50 + case WalletType.none:
51 + path = '';
52 + break;
53 }
54
68 - return nodes;
69 -}
70 -
71 -Future<List<Node>> loadDefaultEthereumNodes() async {
72 - final nodesRaw = await rootBundle.loadString('assets/ethereum_server_list.yml');
55 + final nodesRaw = await rootBundle.loadString(path);
56 final loadedNodes = loadYaml(nodesRaw) as YamlList;
57 final nodes = <Node>[];
58
59 for (final raw in loadedNodes) {
60 if (raw is Map) {
61 final node = Node.fromMap(Map<String, Object>.from(raw));
79 - node.type = WalletType.ethereum;
80 - nodes.add(node);
81 - }
82 - }
83 -
84 - return nodes;
85 -}
86 -
87 -Future<List<Node>> loadBitcoinCashElectrumServerList() async {
88 - final serverListRaw = await rootBundle.loadString('assets/bitcoin_cash_electrum_server_list.yml');
89 - final loadedServerList = loadYaml(serverListRaw) as YamlList;
90 - final serverList = <Node>[];
91 -
92 - for (final raw in loadedServerList) {
93 - if (raw is Map) {
94 - final node = Node.fromMap(Map<String, Object>.from(raw));
95 - node.type = WalletType.bitcoinCash;
96 - serverList.add(node);
97 - }
98 - }
99 -
100 - return serverList;
101 -}
102 -
103 -Future<List<Node>> loadDefaultNanoNodes() async {
104 - final nodesRaw = await rootBundle.loadString('assets/nano_node_list.yml');
105 - final loadedNodes = loadYaml(nodesRaw) as YamlList;
106 - final nodes = <Node>[];
107 -
108 - for (final raw in loadedNodes) {
109 - if (raw is Map) {
110 - final node = Node.fromMap(Map<String, Object>.from(raw));
111 - node.type = WalletType.nano;
62 + node.type = type;
63 nodes.add(node);
64 }
65 }
@@ -132,103 +83,19 @@ Future<List<Node>> loadDefaultNanoPowNodes() async {
83 return nodes;
84 }
85
135 -Future<List<Node>> loadDefaultPolygonNodes() async {
136 - final nodesRaw = await rootBundle.loadString('assets/polygon_node_list.yml');
137 - final loadedNodes = loadYaml(nodesRaw) as YamlList;
138 - final nodes = <Node>[];
139 -
140 - for (final raw in loadedNodes) {
141 - if (raw is Map) {
142 - final node = Node.fromMap(Map<String, Object>.from(raw));
143 -
144 - node.type = WalletType.polygon;
145 - nodes.add(node);
146 - }
147 - }
148 -
149 - return nodes;
150 -}
151 -
152 -Future<List<Node>> loadDefaultSolanaNodes() async {
153 - final nodesRaw = await rootBundle.loadString('assets/solana_node_list.yml');
154 - final loadedNodes = loadYaml(nodesRaw) as YamlList;
155 - final nodes = <Node>[];
156 -
157 - for (final raw in loadedNodes) {
158 - if (raw is Map) {
159 - final node = Node.fromMap(Map<String, Object>.from(raw));
160 -
161 - node.type = WalletType.solana;
162 - nodes.add(node);
163 - }
164 - }
165 -
166 - return nodes;
167 -}
168 -
169 -Future<List<Node>> loadDefaultTronNodes() async {
170 - final nodesRaw = await rootBundle.loadString('assets/tron_node_list.yml');
171 - final loadedNodes = loadYaml(nodesRaw) as YamlList;
172 - final nodes = <Node>[];
173 -
174 - for (final raw in loadedNodes) {
175 - if (raw is Map) {
176 - final node = Node.fromMap(Map<String, Object>.from(raw));
177 -
178 - node.type = WalletType.tron;
179 - nodes.add(node);
180 - }
181 - }
182 -
183 - return nodes;
184 -}
185 -
186 -Future<List<Node>> loadDefaultWowneroNodes() async {
187 - final nodesRaw = await rootBundle.loadString('assets/wownero_node_list.yml');
188 - final loadedNodes = loadYaml(nodesRaw) as YamlList;
189 - final nodes = <Node>[];
190 -
191 - for (final raw in loadedNodes) {
192 - if (raw is Map) {
193 - final node = Node.fromMap(Map<String, Object>.from(raw));
194 -
195 - node.type = WalletType.wownero;
196 - nodes.add(node);
197 - }
198 - }
199 -
200 - return nodes;
201 -}
202 -
203 -Future<List<Node>> loadDefaultZanoNodes() async {
204 - final nodesRaw = await rootBundle.loadString('assets/zano_node_list.yml');
205 - final loadedNodes = loadYaml(nodesRaw) as YamlList;
206 - final nodes = <Node>[];
207 -
208 - for (final raw in loadedNodes) {
209 - if (raw is Map) {
210 - final node = Node.fromMap(Map<String, Object>.from(raw));
211 -
212 - node.type = WalletType.zano;
213 - nodes.add(node);
214 - }
215 - }
216 -
217 - return nodes;
218 -}
219 -
86 Future<void> resetToDefault(Box<Node> nodeSource) async {
221 - final moneroNodes = await loadDefaultNodes();
222 - final bitcoinElectrumServerList = await loadBitcoinElectrumServerList();
223 - final litecoinElectrumServerList = await loadLitecoinElectrumServerList();
224 - final bitcoinCashElectrumServerList = await loadBitcoinCashElectrumServerList();
225 - final havenNodes = await loadDefaultHavenNodes();
226 - final ethereumNodes = await loadDefaultEthereumNodes();
227 - final nanoNodes = await loadDefaultNanoNodes();
228 - final polygonNodes = await loadDefaultPolygonNodes();
229 - final solanaNodes = await loadDefaultSolanaNodes();
230 - final tronNodes = await loadDefaultTronNodes();
231 - final zanoNodes = await loadDefaultZanoNodes();
87 + final moneroNodes = await loadDefaultNodes(WalletType.monero);
88 + final bitcoinElectrumServerList = await loadDefaultNodes(WalletType.bitcoin);
89 + final litecoinElectrumServerList = await loadDefaultNodes(WalletType.litecoin);
90 + final bitcoinCashElectrumServerList = await loadDefaultNodes(WalletType.bitcoinCash);
91 + final havenNodes = await loadDefaultNodes(WalletType.haven);
92 + final ethereumNodes = await loadDefaultNodes(WalletType.ethereum);
93 + final nanoNodes = await loadDefaultNodes(WalletType.nano);
94 + final polygonNodes = await loadDefaultNodes(WalletType.polygon);
95 + final solanaNodes = await loadDefaultNodes(WalletType.solana);
96 + final tronNodes = await loadDefaultNodes(WalletType.tron);
97 + final decredNodes = await loadDefaultNodes(WalletType.decred);
98 + final zanoNodes = await loadDefaultNodes(WalletType.zano);
99
100 final nodes = moneroNodes +
101 bitcoinElectrumServerList +
@@ -238,7 +105,10 @@ Future<void> resetToDefault(Box<Node> nodeSource) async {
105 bitcoinCashElectrumServerList +
106 nanoNodes +
107 polygonNodes +
241 - solanaNodes + tronNodes + zanoNodes;
108 + solanaNodes +
109 + tronNodes +
110 + zanoNodes +
111 + decredNodes;
112
113 await nodeSource.clear();
114 await nodeSource.addAll(nodes);
lib/entities/preferences_key.dart
+3
@@ -10,6 +10,7 @@ class PreferencesKey {
10 static const currentPolygonNodeIdKey = 'current_node_id_matic';
11 static const currentNanoNodeIdKey = 'current_node_id_nano';
12 static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow';
13 + static const currentDecredNodeIdKey = 'current_node_id_decred';
14 static const currentBananoNodeIdKey = 'current_node_id_banano';
15 static const currentBananoPowNodeIdKey = 'current_node_id_banano_pow';
16 static const currentFiatCurrencyKey = 'current_fiat_currency';
@@ -48,6 +49,7 @@ class PreferencesKey {
49 static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
50 static const zanoTransactionPriority = 'current_fee_priority_zano';
51 static const wowneroTransactionPriority = 'current_fee_priority_wownero';
52 + static const decredTransactionPriority = 'current_fee_priority_decred';
53 static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
54 static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay';
55 static const silentPaymentsAlwaysScan = 'silentPaymentsAlwaysScan';
@@ -81,6 +83,7 @@ class PreferencesKey {
83 static const lookupsENS = 'looks_up_ens';
84 static const lookupsWellKnown = 'looks_up_well_known';
85 static const showCameraConsent = 'show_camera_consent';
86 + static const showDecredInfoCard = 'show_decred_info_card';
87
88 static String moneroWalletUpdateV1Key(String name) =>
89 '${PreferencesKey.moneroWalletPasswordUpdateV1Base}_${name}';
lib/entities/priority_for_wallet_type.dart
+3
@@ -6,6 +6,7 @@ import 'package:cake_wallet/monero/monero.dart';
6 import 'package:cake_wallet/polygon/polygon.dart';
7 import 'package:cake_wallet/wownero/wownero.dart';
8 import 'package:cake_wallet/zano/zano.dart';
9 +import 'package:cake_wallet/decred/decred.dart';
10 import 'package:cw_core/transaction_priority.dart';
11 import 'package:cw_core/wallet_type.dart';
12
@@ -35,6 +36,8 @@ List<TransactionPriority> priorityForWalletType(WalletType type) {
36 return [];
37 case WalletType.zano:
38 return zano!.getTransactionPriorities();
39 + case WalletType.decred:
40 + return decred!.getTransactionPriorities();
41 default:
42 return [];
43 }
lib/entities/provider_types.dart
+2
@@ -82,6 +82,7 @@ class ProvidersHelper {
82 ProviderType.moonpay,
83 ProviderType.kriptonim
84 ];
85 + case WalletType.decred:
86 case WalletType.none:
87 case WalletType.haven:
88 case WalletType.zano:
@@ -113,6 +114,7 @@ class ProvidersHelper {
114 ];
115 case WalletType.monero:
116 return [ProviderType.dfx];
117 + case WalletType.decred:
118 case WalletType.nano:
119 case WalletType.banano:
120 case WalletType.none:
lib/main.dart
+1 -1
@@ -215,7 +215,7 @@ Future<void> initializeAppConfigs() async {
215 secureStorage: secureStorage,
216 anonpayInvoiceInfo: anonpayInvoiceInfo,
217 havenSeedStore: havenSeedStore,
218 - initialMigrationVersion: 48,
218 + initialMigrationVersion: 49,
219 );
220 }
221
lib/reactions/bip39_wallet_utils.dart
+1
@@ -16,6 +16,7 @@ bool isBIP39Wallet(WalletType walletType) {
16 case WalletType.wownero:
17 case WalletType.haven:
18 case WalletType.zano:
19 + case WalletType.decred:
20 case WalletType.none:
21 return false;
22 }
lib/reactions/check_connection.dart
+3
@@ -18,6 +18,9 @@ void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore
18 if (wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus) {
19 return;
20 }
21 + if (wallet.type == WalletType.decred && wallet.syncStatus is ProcessingSyncStatus) {
22 + return;
23 + }
24
25 try {
26 final connectivityResult = await (Connectivity().checkConnectivity());
lib/reactions/fiat_rate_update.dart
-1
@@ -10,7 +10,6 @@ import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
10 import 'package:cake_wallet/store/settings_store.dart';
11 import 'package:cake_wallet/tron/tron.dart';
12 import 'package:cw_core/crypto_currency.dart';
13 -import 'package:cw_core/erc20_token.dart';
13 import 'package:cw_core/utils/print_verbose.dart';
14 import 'package:cw_core/wallet_type.dart';
15 import 'package:mobx/mobx.dart';
lib/reactions/on_current_wallet_change.dart
+2 -1
@@ -74,7 +74,8 @@ void startCurrentWalletChangeReaction(
74 wallet.type == WalletType.wownero ||
75 wallet.type == WalletType.bitcoin ||
76 wallet.type == WalletType.litecoin ||
77 - wallet.type == WalletType.bitcoinCash) {
77 + wallet.type == WalletType.bitcoinCash ||
78 + wallet.type == WalletType.decred) {
79 _setAutoGenerateSubaddressStatus(wallet, settingsStore);
80 }
81
lib/reactions/on_wallet_sync_status_change.dart
+1 -1
@@ -25,7 +25,7 @@ void startWalletSyncStatusChangeReaction(
25 await updateHavenRate(fiatConversionStore);
26 }
27 }
28 - if (status is SyncingSyncStatus) {
28 + if (status is SyncingSyncStatus || status is ProcessingSyncStatus) {
29 await WakelockPlus.enable();
30 }
31 if (status is SyncedSyncStatus || status is FailedSyncStatus) {
lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart
+3
@@ -47,6 +47,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
47 final tronIcon = Image.asset('assets/images/trx_icon.png', height: 24, width: 24);
48 final wowneroIcon = Image.asset('assets/images/wownero_icon.png', height: 24, width: 24);
49 final zanoIcon = Image.asset('assets/images/zano_icon.png', height: 24, width: 24);
50 + final decredIcon = Image.asset('assets/images/decred_icon.png', height: 24, width: 24);
51 final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
52
53 Image _newWalletImage(BuildContext context) => Image.asset(
@@ -181,6 +182,8 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
182 return tronIcon;
183 case WalletType.zano:
184 return zanoIcon;
185 + case WalletType.decred:
186 + return decredIcon;
187 default:
188 return nonWalletTypeIcon;
189 }
lib/src/screens/dashboard/pages/balance/crypto_balance_widget.dart
+38 -68
@@ -3,6 +3,7 @@ import 'package:cake_wallet/generated/i18n.dart';
3 import 'package:cake_wallet/routes.dart';
4 import 'package:cake_wallet/src/screens/dashboard/pages/balance/balance_row_widget.dart';
5 import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart';
6 +import 'package:cake_wallet/src/screens/dashboard/widgets/info_card.dart';
7 import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
8 import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
9 import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
@@ -280,81 +281,50 @@ class CryptoBalanceWidget extends StatelessWidget {
281 SizedBox(height: 16),
282 Padding(
283 padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
283 - child: DashBoardRoundedCardWidget(
284 - marginV: 0,
285 - marginH: 0,
286 - customBorder: 30,
284 + child: InfoCard(
285 title: S.of(context).litecoin_mweb,
288 - subTitle: S.of(context).litecoin_mweb_description,
289 - hint: Column(
290 - crossAxisAlignment: CrossAxisAlignment.start,
291 - children: [
292 - GestureDetector(
293 - behavior: HitTestBehavior.opaque,
294 - onTap: () => launchUrl(
295 - Uri.parse("https://docs.cakewallet.com/cryptos/litecoin/#mweb"),
296 - mode: LaunchMode.externalApplication,
297 - ),
298 - child: Text(
299 - S.of(context).learn_more,
300 - style: TextStyle(
301 - fontSize: 12,
302 - fontFamily: 'Lato',
303 - fontWeight: FontWeight.w400,
304 - color:
305 - Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
306 - height: 1,
307 - ),
308 - softWrap: true,
309 - ),
310 - ),
311 - SizedBox(height: 8),
312 - Row(
313 - children: [
314 - Expanded(
315 - child: ElevatedButton(
316 - onPressed: () => _dismissMweb(context),
317 - style: ElevatedButton.styleFrom(
318 - backgroundColor: Theme.of(context).primaryColor,
319 - ),
320 - child: Text(
321 - S.of(context).litecoin_mweb_dismiss,
322 - style: TextStyle(color: Colors.white),
323 - ),
324 - ),
325 - ),
326 - const SizedBox(width: 8),
327 - Expanded(
328 - child: ElevatedButton(
329 - onPressed: () => _enableMweb(context),
330 - style: ElevatedButton.styleFrom(
331 - backgroundColor: Colors.white,
332 - foregroundColor: Colors.black,
333 - ),
334 - child: Text(
335 - S.of(context).enable,
336 - maxLines: 1,
337 - ),
338 - ),
339 - ),
340 - ],
341 - ),
342 - ],
343 - ),
344 - icon: Container(
345 - decoration: BoxDecoration(
346 - color: Colors.white,
347 - shape: BoxShape.circle,
286 + description: S.of(context).litecoin_mweb_description,
287 + leftButtonTitle: S.of(context).litecoin_mweb_dismiss,
288 + rightButtonTitle: S.of(context).enable,
289 + image: 'assets/images/mweb_logo.png',
290 + leftButtonAction: () => _dismissMweb(context),
291 + rightButtonAction: () => _enableMweb(context),
292 + hintWidget: GestureDetector(
293 + behavior: HitTestBehavior.opaque,
294 + onTap: () => launchUrl(
295 + Uri.parse("https://docs.cakewallet.com/cryptos/litecoin/#mweb"),
296 + mode: LaunchMode.externalApplication,
297 ),
349 - child: ImageIcon(
350 - AssetImage('assets/images/mweb_logo.png'),
351 - color: Color.fromARGB(255, 11, 70, 129),
352 - size: 40,
298 + child: Text(
299 + S.of(context).learn_more,
300 + style: TextStyle(
301 + fontSize: 12,
302 + fontFamily: 'Lato',
303 + fontWeight: FontWeight.w400,
304 + color: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
305 + height: 1,
306 + ),
307 + softWrap: true,
308 ),
309 ),
310 ),
311 ),
312 ],
313 + if (dashboardViewModel.showDecredInfoCard) ...[
314 + SizedBox(height: 10),
315 + Padding(
316 + padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
317 + child: InfoCard(
318 + title: S.of(context).decred_info_title,
319 + description: S.of(context).decred_info_card_details,
320 + image: 'assets/images/dcr_icon.png',
321 + leftButtonTitle: S.of(context).litecoin_mweb_dismiss,
322 + rightButtonTitle: S.of(context).learn_more,
323 + leftButtonAction: () => dashboardViewModel.dismissDecredInfoCard(),
324 + rightButtonAction: () => launchUrl(Uri.parse("https://docs.cakewallet.com/cryptos/decred/#spv-sync")),
325 + ),
326 + ),
327 + ],
328 ],
329 );
330 }),
lib/src/screens/dashboard/pages/navigation_dock.dart
+4 -2
@@ -75,8 +75,10 @@ class NavigationDock extends StatelessWidget {
75 .labelTextColor,
76 ),
77 title: action.name(context),
78 - onClick: () async =>
79 - await action.onTap(context, dashboardViewModel),
78 + onClick: (action.isEnabled?.call(dashboardViewModel) ?? true)
79 + ? () async =>
80 + await action.onTap(context, dashboardViewModel)
81 + : null,
82 textColor: action.isEnabled?.call(dashboardViewModel) ?? true
83 ? null
84 : Theme.of(context)
lib/src/screens/dashboard/widgets/info_card.dart new
+88
@@ -0,0 +1,88 @@
1 +import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
2 +import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
3 +import 'package:flutter/material.dart';
4 +
5 +class InfoCard extends StatelessWidget {
6 + final String leftButtonTitle;
7 + final String rightButtonTitle;
8 + final String title;
9 + final String description;
10 + final String image;
11 +
12 + final Function() leftButtonAction;
13 + final Function() rightButtonAction;
14 +
15 + final Widget? hintWidget;
16 +
17 + const InfoCard({
18 + Key? key,
19 + required this.title,
20 + required this.description,
21 + required this.leftButtonTitle,
22 + required this.rightButtonTitle,
23 + required this.leftButtonAction,
24 + required this.rightButtonAction,
25 + required this.image,
26 + this.hintWidget,
27 + }) : super(key: key);
28 +
29 + @override
30 + Widget build(BuildContext context) {
31 + return DashBoardRoundedCardWidget(
32 + marginH: 0,
33 + marginV: 0,
34 + customBorder: 30,
35 + title: title,
36 + subTitle: description,
37 + hint: Column(
38 + crossAxisAlignment: CrossAxisAlignment.start,
39 + children: [
40 + if (hintWidget != null) hintWidget!,
41 + if (hintWidget != null) SizedBox(height: 8),
42 + Row(
43 + children: [
44 + Expanded(
45 + child: ElevatedButton(
46 + onPressed: leftButtonAction,
47 + style: ElevatedButton.styleFrom(
48 + backgroundColor: Theme.of(context).primaryColor,
49 + ),
50 + child: Text(
51 + leftButtonTitle,
52 + style: TextStyle(color: Colors.white),
53 + ),
54 + ),
55 + ),
56 + const SizedBox(width: 8),
57 + Expanded(
58 + child: ElevatedButton(
59 + onPressed: rightButtonAction,
60 + style: ElevatedButton.styleFrom(
61 + backgroundColor: Colors.white,
62 + foregroundColor: Colors.black,
63 + ),
64 + child: Text(
65 + rightButtonTitle,
66 + maxLines: 1,
67 + ),
68 + ),
69 + ),
70 + ],
71 + ),
72 + ],
73 + ),
74 + onTap: () => {},
75 + icon: Container(
76 + decoration: BoxDecoration(
77 + color: Colors.white,
78 + shape: BoxShape.circle,
79 + ),
80 + child: CakeImageWidget(
81 + imageUrl: image,
82 + height: 40,
83 + width: 40,
84 + ),
85 + ),
86 + );
87 + }
88 +}
lib/src/screens/dashboard/widgets/menu_widget.dart
+5 -1
@@ -38,7 +38,8 @@ class MenuWidgetState extends State<MenuWidget> {
38 this.solanaIcon = Image.asset('assets/images/sol_icon.png'),
39 this.tronIcon = Image.asset('assets/images/trx_icon.png'),
40 this.wowneroIcon = Image.asset('assets/images/wownero_icon.png'),
41 - this.zanoIcon = Image.asset('assets/images/zano_icon.png');
41 + this.zanoIcon = Image.asset('assets/images/zano_icon.png'),
42 + this.decredIcon = Image.asset('assets/images/decred_menu.png');
43
44 final largeScreen = 731;
45
@@ -64,6 +65,7 @@ class MenuWidgetState extends State<MenuWidget> {
65 Image tronIcon;
66 Image wowneroIcon;
67 Image zanoIcon;
68 + Image decredIcon;
69
70 @override
71 void initState() {
@@ -250,6 +252,8 @@ class MenuWidgetState extends State<MenuWidget> {
252 return wowneroIcon;
253 case WalletType.zano:
254 return zanoIcon;
255 + case WalletType.decred:
256 + return decredIcon;
257 default:
258 throw Exception('No icon for ${type.toString()}');
259 }
lib/src/screens/dashboard/widgets/sync_indicator.dart
+2 -2
@@ -22,8 +22,8 @@ class SyncIndicator extends StatelessWidget {
22 builder: (_) {
23 final syncIndicatorWidth = 237.0;
24 final status = dashboardViewModel.status;
25 - final statusText = status != null ? syncStatusTitle(status) : '';
26 - final progress = status != null ? status.progress() : 0.0;
25 + final statusText = syncStatusTitle(status);
26 + final progress = status.progress();
27 final indicatorOffset = progress * syncIndicatorWidth;
28 final indicatorWidth = progress < 1
29 ? indicatorOffset > 0 ? indicatorOffset : 0.0
lib/src/screens/new_wallet/advanced_privacy_settings_page.dart
+5 -2
@@ -274,7 +274,8 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
274 ],
275 );
276 }),
277 - if (widget.privacySettingsViewModel.type == WalletType.bitcoin)
277 + if (widget.privacySettingsViewModel.type == WalletType.bitcoin ||
278 + widget.privacySettingsViewModel.type == WalletType.decred)
279 Builder(builder: (_) {
280 final val = testnetValue ?? false;
281 return SettingsSwitcherCell(
@@ -301,7 +302,9 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
302
303 widget.nodeViewModel.save();
304 }
304 - if (testnetValue == true) {
305 + if (testnetValue == true &&
306 + widget.privacySettingsViewModel.type ==
307 + WalletType.bitcoin) {
308 // TODO: add type (mainnet/testnet) to Node class so when switching wallets the node can be switched to a matching type
309 // 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
310 widget.nodeViewModel.address = publicBitcoinTestnetElectrumAddress;
lib/src/screens/nodes/node_create_or_edit_page.dart
+4 -1
@@ -5,6 +5,7 @@ import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
5 import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
6 import 'package:cake_wallet/utils/show_pop_up.dart';
7 import 'package:cw_core/node.dart';
8 +import 'package:cw_core/wallet_type.dart';
9 import 'package:flutter/material.dart';
10 import 'package:flutter_mobx/flutter_mobx.dart';
11 import 'package:mobx/mobx.dart';
@@ -15,7 +16,7 @@ import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
16 import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
17
18 class NodeCreateOrEditPage extends BasePage {
18 - NodeCreateOrEditPage({required this.nodeCreateOrEditViewModel,this.editingNode, this.isSelected})
19 + NodeCreateOrEditPage({required this.nodeCreateOrEditViewModel,this.editingNode, this.isSelected, this.type})
20 : _formKey = GlobalKey<FormState>(),
21 _addressController = TextEditingController(),
22 _pathController = TextEditingController(),
@@ -86,6 +87,7 @@ class NodeCreateOrEditPage extends BasePage {
87 final NodeCreateOrEditViewModel nodeCreateOrEditViewModel;
88 final Node? editingNode;
89 final bool? isSelected;
90 + final WalletType? type;
91
92 @override
93 Widget body(BuildContext context) {
@@ -130,6 +132,7 @@ class NodeCreateOrEditPage extends BasePage {
132 formKey: _formKey,
133 nodeViewModel: nodeCreateOrEditViewModel,
134 editingNode: editingNode,
135 + type: type,
136 ),
137 bottomSectionPadding: EdgeInsets.only(bottom: 24),
138 bottomSection: Observer(
lib/src/screens/nodes/widgets/node_form.dart
+4 -1
@@ -5,6 +5,7 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
5 import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
6 import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
7 import 'package:cw_core/node.dart';
8 +import 'package:cw_core/wallet_type.dart';
9 import 'package:flutter/material.dart';
10 import 'package:flutter_mobx/flutter_mobx.dart';
11 import 'package:cake_wallet/generated/i18n.dart';
@@ -15,6 +16,7 @@ class NodeForm extends StatelessWidget {
16 required this.nodeViewModel,
17 required this.formKey,
18 this.editingNode,
19 + this.type,
20 }) : _addressController = TextEditingController(text: editingNode?.uri.host.toString()),
21 _pathController = TextEditingController(text: editingNode?.path.toString()),
22 _portController = TextEditingController(text: editingNode?.uri.port.toString()),
@@ -76,6 +78,7 @@ class NodeForm extends StatelessWidget {
78 final NodeCreateOrEditViewModel nodeViewModel;
79 final GlobalKey<FormState> formKey;
80 final Node? editingNode;
81 + final WalletType? type;
82
83 final TextEditingController _addressController;
84 final TextEditingController _pathController;
@@ -96,7 +99,7 @@ class NodeForm extends StatelessWidget {
99 child: BaseTextFormField(
100 controller: _addressController,
101 hintText: S.of(context).node_address,
99 - validator: NodeAddressValidator(),
102 + validator: type == WalletType.decred ? NodeAddressValidatorDecredBlankException() : NodeAddressValidator(),
103 ),
104 )
105 ],
lib/src/screens/rescan/rescan_page.dart
+54 -17
@@ -9,41 +9,47 @@ import 'package:cake_wallet/src/screens/base_page.dart';
9 import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
10 import 'package:cake_wallet/src/widgets/primary_button.dart';
11 import 'package:cake_wallet/generated/i18n.dart';
12 +import 'package:cw_core/wallet_type.dart';
13
14 class RescanPage extends BasePage {
15 RescanPage(this._rescanViewModel)
16 : _blockchainHeightWidgetKey = GlobalKey<BlockchainHeightState>();
17
18 @override
18 - String get title =>
19 - _rescanViewModel.isSilentPaymentsScan ? S.current.silent_payments_scanning : S.current.rescan;
19 + String get title => _rescanViewModel.isSilentPaymentsScan
20 + ? S.current.silent_payments_scanning
21 + : S.current.rescan;
22 final GlobalKey<BlockchainHeightState> _blockchainHeightWidgetKey;
23 final RescanViewModel _rescanViewModel;
24
25 @override
26 Widget body(BuildContext context) {
25 - return GestureDetector(
26 - behavior: HitTestBehavior.opaque,
27 - onTap: () => FocusScope.of(context).unfocus(),
28 - child: Padding(
27 + Widget child;
28 + if (_rescanViewModel.wallet.type != WalletType.decred) {
29 + child = Padding(
30 padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
30 - child: Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
31 + child:
32 + Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
33 Observer(
34 builder: (_) => BlockchainHeightWidget(
35 key: _blockchainHeightWidgetKey,
34 - onHeightOrDateEntered: (value) => _rescanViewModel.isButtonEnabled = value,
36 + onHeightOrDateEntered: (value) =>
37 + _rescanViewModel.isButtonEnabled = value,
38 isSilentPaymentsScan: _rescanViewModel.isSilentPaymentsScan,
39 isMwebScan: _rescanViewModel.isMwebScan,
40 doSingleScan: _rescanViewModel.doSingleScan,
38 - hasDatePicker: !_rescanViewModel.isMwebScan,// disable date picker for mweb for now
39 - toggleSingleScan: () =>
40 - _rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan,
41 + hasDatePicker: !_rescanViewModel
42 + .isMwebScan, // disable date picker for mweb for now
43 + toggleSingleScan: () => _rescanViewModel.doSingleScan =
44 + !_rescanViewModel.doSingleScan,
45 walletType: _rescanViewModel.wallet.type,
42 - bitcoinMempoolAPIEnabled: _rescanViewModel.isBitcoinMempoolAPIEnabled,
46 + bitcoinMempoolAPIEnabled:
47 + _rescanViewModel.isBitcoinMempoolAPIEnabled,
48 )),
49 Observer(
50 builder: (_) => LoadingPrimaryButton(
46 - isLoading: _rescanViewModel.state == RescanWalletState.rescaning,
51 + isLoading:
52 + _rescanViewModel.state == RescanWalletState.rescaning,
53 text: S.of(context).rescan,
54 onPressed: () async {
55 if (_rescanViewModel.isSilentPaymentsScan) {
@@ -51,7 +57,8 @@ class RescanPage extends BasePage {
57 }
58
59 _rescanViewModel.rescanCurrentWallet(
54 - restoreHeight: _blockchainHeightWidgetKey.currentState!.height);
60 + restoreHeight:
61 + _blockchainHeightWidgetKey.currentState!.height);
62
63 Navigator.of(context).pop();
64 },
@@ -60,7 +67,35 @@ class RescanPage extends BasePage {
67 isDisabled: !_rescanViewModel.isButtonEnabled,
68 ))
69 ]),
63 - ),
70 + );
71 + } else {
72 + child = Center(
73 + child: Padding(
74 + padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
75 + child: Column(
76 + mainAxisAlignment: MainAxisAlignment.spaceBetween,
77 + children: [
78 + Spacer(),
79 + Observer(
80 + builder: (_) => LoadingPrimaryButton(
81 + isLoading: _rescanViewModel.state ==
82 + RescanWalletState.rescaning,
83 + text: S.of(context).rescan,
84 + onPressed: () async {
85 + await _rescanViewModel.rescanCurrentWallet(
86 + restoreHeight: 0);
87 + Navigator.of(context).pop();
88 + },
89 + color: Theme.of(context).primaryColor,
90 + textColor: Colors.white,
91 + ))
92 + ]),
93 + ));
94 + }
95 + return GestureDetector(
96 + behavior: HitTestBehavior.opaque,
97 + onTap: () => FocusScope.of(context).unfocus(),
98 + child: child,
99 );
100 }
101
@@ -70,14 +105,16 @@ class RescanPage extends BasePage {
105 Navigator.of(context).pop();
106
107 final needsToSwitch =
73 - await bitcoin!.getNodeIsElectrsSPEnabled(_rescanViewModel.wallet) == false;
108 + await bitcoin!.getNodeIsElectrsSPEnabled(_rescanViewModel.wallet) ==
109 + false;
110
111 if (needsToSwitch) {
112 return showPopUp<void>(
113 context: navigatorKey.currentState!.context,
114 builder: (BuildContext _dialogContext) => AlertWithTwoActions(
115 alertTitle: S.of(_dialogContext).change_current_node_title,
80 - alertContent: S.of(_dialogContext).confirm_silent_payments_switch_node,
116 + alertContent:
117 + S.of(_dialogContext).confirm_silent_payments_switch_node,
118 rightButtonText: S.of(_dialogContext).confirm,
119 leftButtonText: S.of(_dialogContext).cancel,
120 actionRightButton: () async {
lib/src/screens/restore/wallet_restore_from_keys_form.dart
+19
@@ -15,6 +15,7 @@ class WalletRestoreFromKeysForm extends StatefulWidget {
15 WalletRestoreFromKeysForm({
16 required this.walletRestoreViewModel,
17 required this.onPrivateKeyChange,
18 + required this.onViewKeyEntered,
19 required this.displayPrivateKeyField,
20 required this.onHeightOrDateEntered,
21 required this.displayWalletPassword,
@@ -27,6 +28,7 @@ class WalletRestoreFromKeysForm extends StatefulWidget {
28 final Function(bool) onHeightOrDateEntered;
29 final WalletRestoreViewModel walletRestoreViewModel;
30 final void Function(String)? onPrivateKeyChange;
31 + final void Function(bool)? onViewKeyEntered;
32 final bool displayPrivateKeyField;
33 final bool displayWalletPassword;
34 final RestoredWallet? restoredWallet;
@@ -97,6 +99,10 @@ class WalletRestoreFromKeysFormState extends State<WalletRestoreFromKeysForm> {
99 blockchainHeightKey.currentState?.restoreHeightController.text = widget.restoredWallet!.height.toString();
100 }
101 });
102 +
103 + viewKeyController.addListener(() {
104 + widget.onViewKeyEntered?.call(viewKeyController.text.isNotEmpty);
105 + });
106 }
107
108 @override
@@ -187,6 +193,19 @@ class WalletRestoreFromKeysFormState extends State<WalletRestoreFromKeysForm> {
193 }
194
195 Widget _restoreFromKeysFormFields() {
196 + // Decred can only restore a view only wallet with an account pubkey. Other
197 + // fields are not used.
198 + if (widget.walletRestoreViewModel.type == WalletType.decred) {
199 + return Column(
200 + children: [
201 + BaseTextFormField(
202 + controller: viewKeyController,
203 + hintText: S.of(context).view_key_public,
204 + maxLines: null,
205 + )],
206 + );
207 + }
208 +
209 if (widget.displayPrivateKeyField) {
210 // the term "private key" isn't actually what we're accepting here, and it's confusing to
211 // 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"
lib/src/screens/restore/wallet_restore_page.dart
+20 -7
@@ -168,14 +168,16 @@ class WalletRestorePage extends BasePage {
168 credentials['name'] =
169 walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
170 } else {
171 - credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text;
172 - credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text;
173 - credentials['spendKey'] =
174 - walletRestoreFromKeysFormKey.currentState!.spendKeyController.text;
175 - credentials['height'] =
176 - walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState!.height;
171 credentials['name'] =
172 walletRestoreFromKeysFormKey.currentState!.nameTextEditingController.text;
173 + credentials['viewKey'] = walletRestoreFromKeysFormKey.currentState!.viewKeyController.text;
174 + if (walletRestoreViewModel.type != WalletType.decred) {
175 + credentials['address'] = walletRestoreFromKeysFormKey.currentState!.addressController.text;
176 + credentials['spendKey'] =
177 + walletRestoreFromKeysFormKey.currentState!.spendKeyController.text;
178 + credentials['height'] =
179 + walletRestoreFromKeysFormKey.currentState!.blockchainHeightKey.currentState!.height;
180 + }
181 }
182 }
183
@@ -467,6 +469,11 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
469 widget.walletRestoreViewModel.isButtonEnabled = _isValidSeedKey();
470 }
471 },
472 + onViewKeyEntered: (bool entered) {
473 + if (walletRestoreViewModel.type == WalletType.decred) {
474 + walletRestoreViewModel.isButtonEnabled = entered;
475 + }
476 + },
477 onPasswordChange: (String password) =>
478 widget.walletRestoreViewModel.walletPassword = password,
479 onRepeatedPasswordChange: (String repeatedPassword) =>
@@ -538,13 +545,19 @@ class _WalletRestorePageBodyState extends State<_WalletRestorePageBody>
545
546 // bip39:
547 final validBip39SeedLengths = [12, 18, 24];
541 - final nonBip39WalletTypes = [WalletType.monero, WalletType.wownero, WalletType.haven];
548 + final nonBip39WalletTypes = [WalletType.monero, WalletType.wownero, WalletType.haven, WalletType.decred];
549 // if it's a bip39 wallet and the length is not valid return false
550 if (!nonBip39WalletTypes.contains(walletRestoreViewModel.type) &&
551 !(validBip39SeedLengths.contains(seedWords.length))) {
552 return false;
553 }
554
555 + if ((walletRestoreViewModel.type == WalletType.decred) &&
556 + seedWords.length !=
557 + WalletRestoreViewModelBase.decredSeedMnemonicLength) {
558 + return false;
559 + }
560 +
561 final words =
562 walletRestoreFromSeedFormKey.currentState!.seedWidgetStateKey.currentState!.words.toSet();
563 return seedWords.toSet().difference(words).toSet().isEmpty;
lib/src/screens/wallet_keys/wallet_keys_page.dart
+17 -13
@@ -72,6 +72,10 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
72 late bool showLegacySeedTab;
73 late bool isLegacySeedOnly;
74
75 + bool get _hasSeeds =>
76 + widget.walletKeysViewModel.legacySeedSplit.length > 10 ||
77 + widget.walletKeysViewModel.seedSplit.length > 10;
78 +
79 @override
80 void initState() {
81 super.initState();
@@ -160,11 +164,10 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
164 Widget _buildSeedTab(BuildContext context, bool isLegacySeed) {
165 return Column(
166 children: [
163 - if (isLegacySeedOnly || isLegacySeed)
164 - ...[
165 - _buildHeightBox(),
166 - const SizedBox(height: 20),
167 - ],
167 + if (isLegacySeedOnly || isLegacySeed) ...[
168 + _buildHeightBox(),
169 + const SizedBox(height: 20),
170 + ],
171 (_buildPassphraseBox() ?? Container()),
172 if (widget.walletKeysViewModel.passphrase.isNotEmpty) const SizedBox(height: 20),
173 Expanded(
@@ -175,13 +178,14 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
178 ),
179 ),
180 const SizedBox(height: 10),
178 - _buildBottomActionPanel(
179 - titleForClipboard: S.of(context).wallet_seed.toLowerCase(),
180 - dataToCopy: isLegacySeed
181 - ? widget.walletKeysViewModel.legacySeed
182 - : widget.walletKeysViewModel.seed,
183 - onShowQR: () async => _showQR(context),
184 - ),
181 + if (_hasSeeds)
182 + _buildBottomActionPanel(
183 + titleForClipboard: S.of(context).wallet_seed.toLowerCase(),
184 + dataToCopy: isLegacySeed
185 + ? widget.walletKeysViewModel.legacySeed
186 + : widget.walletKeysViewModel.seed,
187 + onShowQR: () async => _showQR(context),
188 + ),
189 ],
190 );
191 }
@@ -326,7 +330,7 @@ class _WalletKeysPageBodyState extends State<WalletKeysPageBody>
330 ),
331 );
332 }
329 -
333 +
334 Widget _buildBottomActionPanel({
335 required String titleForClipboard,
336 required String dataToCopy,
lib/src/screens/wallet_list/wallet_list_page.dart
+14 -1
@@ -122,6 +122,7 @@ class WalletListBodyState extends State<WalletListBody> {
122 final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
123 final tBitcoinIcon = Image.asset('assets/images/tbtc.png', height: 24, width: 24);
124 final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
125 + final decredIcon = Image.asset('assets/images/decred_icon.png', height: 24, width: 24);
126 final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
127 final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
128 final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
@@ -136,6 +137,8 @@ class WalletListBodyState extends State<WalletListBody> {
137 final double tileHeight = 60;
138 Flushbar<void>? _progressBar;
139
140 + bool _loadingWallet = false;
141 +
142 @override
143 Widget build(BuildContext context) {
144 final newWalletImage = Image.asset('assets/images/new_wallet.png',
@@ -480,6 +483,10 @@ class WalletListBodyState extends State<WalletListBody> {
483 }
484
485 Future<void> _loadWallet(WalletListItem wallet) async {
486 + if (_loadingWallet) return;
487 +
488 + _loadingWallet = true;
489 +
490 if (SettingsStoreBase.walletPasswordDirectInput) {
491 Navigator.of(context).pushNamed(Routes.walletUnlockLoadable,
492 arguments: WalletUnlockArguments(
@@ -492,13 +499,17 @@ class WalletListBodyState extends State<WalletListBody> {
499 },
500 walletName: wallet.name,
501 walletType: wallet.type));
502 + _loadingWallet = false;
503 return;
504 }
505
506 await widget.authService.authenticateAction(
507 context,
508 onAuthSuccess: (isAuthenticatedSuccessfully) async {
501 - if (!isAuthenticatedSuccessfully) return;
509 + if (!isAuthenticatedSuccessfully) {
510 + _loadingWallet = false;
511 + return;
512 + }
513
514 try {
515 final requireHardwareWalletConnection = widget.walletListViewModel
@@ -555,6 +566,8 @@ class WalletListBodyState extends State<WalletListBody> {
566 .of(context)
567 .wallet_list_failed_to_load(wallet.name, e.toString()));
568 }
569 + } finally {
570 + _loadingWallet = false;
571 }
572 },
573 conditionToDetermineIfToUse2FA:
lib/src/widgets/blockchain_height_widget.dart
+6 -1
@@ -9,6 +9,9 @@ import 'package:intl/intl.dart';
9 import 'package:cake_wallet/generated/i18n.dart';
10 import 'package:cake_wallet/monero/monero.dart';
11 import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
12 +import 'package:cake_wallet/decred/decred.dart';
13 +import 'package:cw_core/wallet_type.dart';
14 +
15
16 class BlockchainHeightWidget extends StatefulWidget {
17 BlockchainHeightWidget({
@@ -183,7 +186,9 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
186 bitcoinMempoolAPIEnabled: await widget.bitcoinMempoolAPIEnabled,
187 );
188 } else {
186 - if (widget.walletType == WalletType.monero) {
189 + if (widget.walletType == WalletType.decred) {
190 + height = decred!.heightByDate(date);
191 + } else if (widget.walletType == WalletType.monero) {
192 height = monero!.getHeightByDate(date: date);
193 } else {
194 assert(widget.walletType == WalletType.wownero,
lib/src/widgets/seed_widget.dart
+3
@@ -65,6 +65,9 @@ class SeedWidgetState extends State<SeedWidget> {
65 });
66 widget.onSeedChange?.call(text);
67 });
68 + Future.delayed(Duration.zero, () {
69 + widget.onSeedChange?.call(text);
70 + });
71 }
72
73 void changeSeedLanguage(String language) {
lib/store/settings_store.dart
+36 -2
@@ -2,6 +2,7 @@ import 'dart:convert';
2 import 'dart:io';
3
4 import 'package:cake_wallet/bitcoin/bitcoin.dart';
5 +import 'package:cake_wallet/decred/decred.dart';
6 import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
7 import 'package:cake_wallet/core/secure_storage.dart';
8 import 'package:cake_wallet/di.dart';
@@ -139,6 +140,7 @@ abstract class SettingsStoreBase with Store {
140 TransactionPriority? initialPolygonTransactionPriority,
141 TransactionPriority? initialBitcoinCashTransactionPriority,
142 TransactionPriority? initialZanoTransactionPriority,
143 + TransactionPriority? initialDecredTransactionPriority,
144 Country? initialCakePayCountry})
145 : nodes = ObservableMap<WalletType, Node>.of(nodes),
146 powNodes = ObservableMap<WalletType, Node>.of(powNodes),
@@ -225,6 +227,9 @@ abstract class SettingsStoreBase with Store {
227 if (initialZanoTransactionPriority != null) {
228 priority[WalletType.zano] = initialZanoTransactionPriority;
229 }
230 + if (initialDecredTransactionPriority != null) {
231 + priority[WalletType.decred] = initialDecredTransactionPriority;
232 + }
233
234 if (initialCakePayCountry != null) {
235 selectedCakePayCountry = initialCakePayCountry;
@@ -280,6 +285,9 @@ abstract class SettingsStoreBase with Store {
285 case WalletType.zano:
286 key = PreferencesKey.zanoTransactionPriority;
287 break;
288 + case WalletType.decred:
289 + key = PreferencesKey.decredTransactionPriority;
290 + break;
291 default:
292 key = null;
293 }
@@ -898,6 +906,7 @@ abstract class SettingsStoreBase with Store {
906 TransactionPriority? bitcoinCashTransactionPriority;
907 TransactionPriority? wowneroTransactionPriority;
908 TransactionPriority? zanoTransactionPriority;
909 + TransactionPriority? decredTransactionPriority;
910
911 if (sharedPreferences.getInt(PreferencesKey.havenTransactionPriority) != null) {
912 havenTransactionPriority = monero?.deserializeMoneroTransactionPriority(
@@ -927,6 +936,10 @@ abstract class SettingsStoreBase with Store {
936 zanoTransactionPriority = monero?.deserializeMoneroTransactionPriority(
937 raw: sharedPreferences.getInt(PreferencesKey.zanoTransactionPriority)!);
938 }
939 + if (sharedPreferences.getInt(PreferencesKey.decredTransactionPriority) != null) {
940 + decredTransactionPriority = decred?.deserializeDecredTransactionPriority(
941 + sharedPreferences.getInt(PreferencesKey.decredTransactionPriority)!);
942 + }
943
944 moneroTransactionPriority ??= monero?.getDefaultTransactionPriority();
945 bitcoinTransactionPriority ??= bitcoin?.getMediumTransactionPriority();
@@ -935,6 +948,7 @@ abstract class SettingsStoreBase with Store {
948 ethereumTransactionPriority ??= ethereum?.getDefaultTransactionPriority();
949 bitcoinCashTransactionPriority ??= bitcoinCash?.getDefaultTransactionPriority();
950 wowneroTransactionPriority ??= wownero?.getDefaultTransactionPriority();
951 + decredTransactionPriority ??= decred?.getDecredTransactionPriorityMedium();
952 polygonTransactionPriority ??= polygon?.getDefaultTransactionPriority();
953 zanoTransactionPriority ??= zano?.getDefaultTransactionPriority();
954
@@ -1038,7 +1052,7 @@ abstract class SettingsStoreBase with Store {
1052 final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
1053 final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
1054 final zanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey);
1041 -
1055 + final decredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey);
1056 final moneroNode = nodeSource.get(nodeId);
1057 final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
1058 final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
@@ -1047,6 +1061,7 @@ abstract class SettingsStoreBase with Store {
1061 final polygonNode = nodeSource.get(polygonNodeId);
1062 final bitcoinCashElectrumServer = nodeSource.get(bitcoinCashElectrumServerId);
1063 final nanoNode = nodeSource.get(nanoNodeId);
1064 + final decredNode = nodeSource.get(decredNodeId);
1065 final nanoPowNode = powNodeSource.get(nanoPowNodeId);
1066 final solanaNode = nodeSource.get(solanaNodeId);
1067 final tronNode = nodeSource.get(tronNodeId);
@@ -1137,6 +1152,10 @@ abstract class SettingsStoreBase with Store {
1152 nodes[WalletType.zano] = zanoNode;
1153 }
1154
1155 + if (decredNode != null) {
1156 + nodes[WalletType.decred] = decredNode;
1157 + }
1158 +
1159 final savedSyncMode = SyncMode.all.firstWhere((element) {
1160 return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 0);
1161 });
@@ -1304,6 +1323,7 @@ abstract class SettingsStoreBase with Store {
1323 initialHavenTransactionPriority: havenTransactionPriority,
1324 initialLitecoinTransactionPriority: litecoinTransactionPriority,
1325 initialBitcoinCashTransactionPriority: bitcoinCashTransactionPriority,
1326 + initialDecredTransactionPriority: decredTransactionPriority,
1327 initialShouldRequireTOTP2FAForAccessingWallet: shouldRequireTOTP2FAForAccessingWallet,
1328 initialShouldRequireTOTP2FAForSendsToContact: shouldRequireTOTP2FAForSendsToContact,
1329 initialShouldRequireTOTP2FAForSendsToNonContact: shouldRequireTOTP2FAForSendsToNonContact,
@@ -1378,6 +1398,11 @@ abstract class SettingsStoreBase with Store {
1398 priority[WalletType.zano] = zano!.deserializeMoneroTransactionPriority(
1399 raw: sharedPreferences.getInt(PreferencesKey.zanoTransactionPriority)!);
1400 }
1401 + if (decred != null &&
1402 + sharedPreferences.getInt(PreferencesKey.decredTransactionPriority) != null) {
1403 + priority[WalletType.decred] = decred!.deserializeDecredTransactionPriority(
1404 + sharedPreferences.getInt(PreferencesKey.decredTransactionPriority)!);
1405 + }
1406
1407 final generateSubaddresses =
1408 sharedPreferences.getInt(PreferencesKey.autoGenerateSubaddressStatusKey);
@@ -1489,6 +1514,7 @@ abstract class SettingsStoreBase with Store {
1514 final tronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey);
1515 final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey);
1516 final zanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey);
1517 + final decredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey);
1518 final moneroNode = nodeSource.get(nodeId);
1519 final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId);
1520 final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId);
@@ -1496,11 +1522,12 @@ abstract class SettingsStoreBase with Store {
1522 final ethereumNode = nodeSource.get(ethereumNodeId);
1523 final polygonNode = nodeSource.get(polygonNodeId);
1524 final bitcoinCashNode = nodeSource.get(bitcoinCashElectrumServerId);
1499 - final nanoNode = nodeSource.get(nanoNodeId);
1525 + final nanoNode = nodeSource.get(nanoNodeId);
1526 final solanaNode = nodeSource.get(solanaNodeId);
1527 final tronNode = nodeSource.get(tronNodeId);
1528 final wowneroNode = nodeSource.get(wowneroNodeId);
1529 final zanoNode = nodeSource.get(zanoNodeId);
1530 + final decredNode = nodeSource.get(decredNodeId);
1531
1532 if (moneroNode != null) {
1533 nodes[WalletType.monero] = moneroNode;
@@ -1551,6 +1578,10 @@ abstract class SettingsStoreBase with Store {
1578 nodes[WalletType.zano] = zanoNode;
1579 }
1580
1581 + if (decredNode != null) {
1582 + nodes[WalletType.decred] = decredNode;
1583 + }
1584 +
1585 // MIGRATED:
1586
1587 useTOTP2FA = await SecureKey.getBool(
@@ -1687,6 +1718,9 @@ abstract class SettingsStoreBase with Store {
1718 case WalletType.wownero:
1719 await _sharedPreferences.setInt(PreferencesKey.currentWowneroNodeIdKey, node.key as int);
1720 break;
1721 + case WalletType.decred:
1722 + await _sharedPreferences.setInt(PreferencesKey.currentDecredNodeIdKey, node.key as int);
1723 + break;
1724 case WalletType.zano:
1725 await _sharedPreferences.setInt(PreferencesKey.currentZanoNodeIdKey, node.key as int);
1726 default:
lib/view_model/advanced_privacy_settings_view_model.dart
+1
@@ -55,6 +55,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
55 case WalletType.none:
56 case WalletType.haven:
57 case WalletType.zano:
58 + case WalletType.decred:
59 return false;
60 }
61 }
lib/view_model/contact_list/contact_list_view_model.dart
+5 -7
@@ -9,7 +9,6 @@ import 'package:cake_wallet/entities/wallet_list_order_types.dart';
9 import 'package:cake_wallet/generated/i18n.dart';
10 import 'package:cake_wallet/store/settings_store.dart';
11 import 'package:cake_wallet/utils/mobx.dart';
12 -import 'package:collection/collection.dart';
12 import 'package:cw_core/crypto_currency.dart';
13 import 'package:cw_core/wallet_info.dart';
14 import 'package:cw_core/wallet_type.dart';
@@ -43,7 +42,8 @@ abstract class ContactListViewModelBase with Store {
42 }
43 }
44 } else if (info.addresses?.isNotEmpty == true && info.addresses!.length > 1) {
46 - if ([WalletType.monero, WalletType.wownero, WalletType.haven].contains(info.type)) {
45 + if ([WalletType.monero, WalletType.wownero, WalletType.haven, WalletType.decred]
46 + .contains(info.type)) {
47 final address = info.address;
48 final name = _createName(info.name, "", key: 0);
49 walletContacts.add(WalletContact(
@@ -129,11 +129,9 @@ abstract class ContactListViewModelBase with Store {
129 (element.type == CryptoCurrency.btc || element.type == CryptoCurrency.ltc)) return false;
130
131 return element.type == _currency ||
132 - (element.type.tag != null &&
133 - _currency?.tag != null &&
134 - element.type.tag == _currency?.tag) ||
135 - _currency?.toString() == element.type.tag ||
136 - _currency?.tag == element.type.toString();
132 + (element.type.tag != null && _currency.tag != null && element.type.tag == _currency.tag) ||
133 + _currency.toString() == element.type.tag ||
134 + _currency.tag == element.type.toString();
135 }
136
137 void dispose() => _subscription?.cancel();
lib/view_model/contact_list/contact_view_model.dart
+6 -6
@@ -62,12 +62,12 @@ abstract class ContactViewModelBase with Store {
62 return;
63 }
64
65 - if (_contact != null && _contact!.original.isInBox) {
66 - _contact?.name = name;
67 - _contact?.address = address;
68 - _contact?.type = currency!;
69 - _contact?.lastChange = now;
70 - await _contact?.save();
65 + if (_contact != null && _contact.original.isInBox) {
66 + _contact.name = name;
67 + _contact.address = address;
68 + _contact.type = currency!;
69 + _contact.lastChange = now;
70 + await _contact.save();
71 } else {
72 await _contacts
73 .add(Contact(name: name, address: address, type: currency!, lastChange: now));
lib/view_model/dashboard/balance_view_model.dart
+1
@@ -306,6 +306,7 @@ abstract class BalanceViewModelBase with Store {
306 case WalletType.monero:
307 case WalletType.wownero:
308 case WalletType.zano:
309 + case WalletType.decred:
310 return true;
311 default:
312 return false;
lib/view_model/dashboard/dashboard_view_model.dart
+43 -23
@@ -23,7 +23,6 @@ import 'package:cake_wallet/store/dashboard/trades_store.dart';
23 import 'package:cake_wallet/store/dashboard/transaction_filter_store.dart';
24 import 'package:cake_wallet/store/settings_store.dart';
25 import 'package:cake_wallet/store/yat/yat_store.dart';
26 -import 'package:cake_wallet/utils/mobx.dart';
26 import 'package:cake_wallet/view_model/dashboard/action_list_item.dart';
27 import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
28 import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
@@ -169,6 +168,9 @@ abstract class DashboardViewModelBase with Store {
168 type = appStore.wallet!.type,
169 transactions = ObservableList<TransactionListItem>(),
170 wallet = appStore.wallet! {
171 + showDecredInfoCard = wallet.type == WalletType.decred &&
172 + (sharedPreferences.getBool(PreferencesKey.showDecredInfoCard) ?? true);
173 +
174 name = wallet.name;
175 type = wallet.type;
176 isShowFirstYatIntroduction = false;
@@ -261,6 +263,7 @@ abstract class DashboardViewModelBase with Store {
263 reaction((_) => appStore.wallet, (wallet) {
264 _onWalletChange(wallet);
265 _checkMweb();
266 + showDecredInfoCard = wallet?.type == WalletType.decred;
267 });
268
269 _transactionDisposer?.reaction.dispose();
@@ -350,6 +353,10 @@ abstract class DashboardViewModelBase with Store {
353 statusText = S.current.please_try_to_connect_to_another_node;
354 }
355
356 + if (status is ProcessingSyncStatus) {
357 + statusText = (status as ProcessingSyncStatus).message ?? S.current.processing;
358 + }
359 +
360 return statusText;
361 }
362
@@ -399,13 +406,7 @@ abstract class DashboardViewModelBase with Store {
406 bool get isTestnet => wallet.type == WalletType.bitcoin && bitcoin!.isTestnet(wallet);
407
408 @computed
402 - bool get hasRescan => [
403 - WalletType.bitcoin,
404 - WalletType.monero,
405 - WalletType.litecoin,
406 - WalletType.wownero,
407 - WalletType.haven
408 - ].contains(wallet.type);
409 + bool get hasRescan => wallet.hasRescan;
410
411 @computed
412 bool get isMoneroViewOnly {
@@ -490,6 +491,9 @@ abstract class DashboardViewModelBase with Store {
491 @observable
492 bool mwebEnabled = false;
493
494 + @observable
495 + late bool showDecredInfoCard;
496 +
497 @computed
498 bool get hasEnabledMwebBefore => settingsStore.hasEnabledMwebBefore;
499
@@ -543,6 +547,12 @@ abstract class DashboardViewModelBase with Store {
547 bitcoin!.setMwebEnabled(wallet, false);
548 }
549
550 + @action
551 + void dismissDecredInfoCard() {
552 + showDecredInfoCard = false;
553 + sharedPreferences.setBool(PreferencesKey.showDecredInfoCard, false);
554 + }
555 +
556 BalanceViewModel balanceViewModel;
557
558 AppStore appStore;
@@ -573,6 +583,9 @@ abstract class DashboardViewModelBase with Store {
583 @computed
584 bool get isEnabledSwapAction => settingsStore.exchangeStatus != ExchangeApiMode.disabled;
585
586 + @computed
587 + bool get canSend => wallet.canSend();
588 +
589 @observable
590 bool hasSwapAction;
591
@@ -596,21 +609,28 @@ abstract class DashboardViewModelBase with Store {
609
610 @computed
611 bool get hasSignMessages {
599 - if (wallet.isHardwareWallet) return false;
600 -
601 - return [
602 - WalletType.monero,
603 - WalletType.litecoin,
604 - WalletType.bitcoin,
605 - WalletType.bitcoinCash,
606 - WalletType.ethereum,
607 - WalletType.polygon,
608 - WalletType.solana,
609 - WalletType.nano,
610 - WalletType.banano,
611 - WalletType.tron,
612 - WalletType.wownero
613 - ].contains(wallet.type);
612 + if (wallet.isHardwareWallet) {
613 + return false;
614 + }
615 + switch (wallet.type) {
616 + case WalletType.monero:
617 + case WalletType.litecoin:
618 + case WalletType.bitcoin:
619 + case WalletType.bitcoinCash:
620 + case WalletType.ethereum:
621 + case WalletType.polygon:
622 + case WalletType.solana:
623 + case WalletType.nano:
624 + case WalletType.banano:
625 + case WalletType.tron:
626 + case WalletType.wownero:
627 + case WalletType.decred:
628 + return true;
629 + case WalletType.zano:
630 + case WalletType.haven:
631 + case WalletType.none:
632 + return false;
633 + }
634 }
635
636 bool get showRepWarning {
lib/view_model/dashboard/home_settings_view_model.dart
+1
@@ -214,6 +214,7 @@ abstract class HomeSettingsViewModelBase with Store {
214 case WalletType.nano:
215 case WalletType.wownero:
216 case WalletType.bitcoinCash:
217 + case WalletType.decred:
218 return false;
219 }
220
lib/view_model/dashboard/receive_option_view_model.dart
+14 -1
@@ -14,7 +14,9 @@ abstract class ReceiveOptionViewModelBase with Store {
14 (_wallet.type == WalletType.bitcoin ||
15 _wallet.type == WalletType.litecoin
16 ? bitcoin!.getSelectedAddressType(_wallet)
17 - : ReceivePageOption.mainnet),
17 + : (_wallet.type == WalletType.decred && _wallet.isTestnet)
18 + ? ReceivePageOption.testnet
19 + : ReceivePageOption.mainnet),
20 _options = [] {
21 final walletType = _wallet.type;
22 switch (walletType) {
@@ -33,6 +35,17 @@ abstract class ReceiveOptionViewModelBase with Store {
35 case WalletType.haven:
36 _options = [ReceivePageOption.mainnet];
37 break;
38 + case WalletType.decred:
39 + if (_wallet.isTestnet) {
40 + _options = [
41 + ReceivePageOption.testnet,
42 + ...ReceivePageOptions.where(
43 + (element) => element != ReceivePageOption.mainnet)
44 + ];
45 + } else {
46 + _options = ReceivePageOptions;
47 + }
48 + break;
49 default:
50 _options = ReceivePageOptions;
51 }
lib/view_model/dashboard/transaction_list_item.dart
+11 -2
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/decred/decred.dart';
2 import 'package:cake_wallet/entities/balance_display_mode.dart';
3 import 'package:cake_wallet/entities/fiat_currency.dart';
4 import 'package:cake_wallet/ethereum/ethereum.dart';
@@ -82,7 +83,9 @@ class TransactionListItem extends ActionListItem with Keyable {
83 if (transaction.confirmations <= 0) {
84 str = S.current.pending;
85 }
85 - if ((isPegOut || fromPegOut) && transaction.confirmations >= 0 && transaction.confirmations < 6) {
86 + if ((isPegOut || fromPegOut) &&
87 + transaction.confirmations >= 0 &&
88 + transaction.confirmations < 6) {
89 str = " (${transaction.confirmations}/6)";
90 }
91 if (isPegIn) {
@@ -224,7 +227,13 @@ class TransactionListItem extends ActionListItem with Keyable {
227 cryptoAmount: zano!.formatterIntAmountToDouble(amount: transaction.amount, currency: asset, forFee: false),
228 price: price);
229 break;
227 - default:
230 + case WalletType.decred:
231 + amount = calculateFiatAmountRaw(
232 + cryptoAmount: decred!.formatterDecredAmountToDouble(amount: transaction.amount),
233 + price: price);
234 + break;
235 + case WalletType.none:
236 + case WalletType.banano:
237 break;
238 }
239
lib/view_model/exchange/exchange_view_model.dart
+8 -3
@@ -23,6 +23,8 @@ import 'package:shared_preferences/shared_preferences.dart';
23
24 import 'package:cake_wallet/.secrets.g.dart' as secrets;
25 import 'package:cake_wallet/bitcoin/bitcoin.dart';
26 +import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
27 +import 'package:cake_wallet/decred/decred.dart';
28 import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
29 import 'package:cake_wallet/entities/exchange_api_mode.dart';
30 import 'package:cake_wallet/entities/preferences_key.dart';
@@ -167,7 +169,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
169 wallet.type == WalletType.bitcoinCash;
170
171 bool get hideAddressAfterExchange =>
170 - wallet.type == WalletType.monero || wallet.type == WalletType.wownero;
172 + wallet.type == WalletType.monero ||
173 + wallet.type == WalletType.wownero;
174
175 bool _useTorOnly;
176 final Box<Trade> trades;
@@ -316,8 +319,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
319
320 bool get isMoneroWallet => wallet.type == WalletType.monero;
321
319 -
320 -
322 List<CryptoCurrency> receiveCurrencies;
323
324 List<CryptoCurrency> depositCurrencies;
@@ -778,6 +779,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
779 depositCurrency = CryptoCurrency.zano;
780 receiveCurrency = CryptoCurrency.xmr;
781 break;
782 + case WalletType.decred:
783 + depositCurrency = CryptoCurrency.dcr;
784 + receiveCurrency = CryptoCurrency.xmr;
785 + break;
786 case WalletType.none:
787 break;
788 }
lib/view_model/node_list/node_create_or_edit_view_model.dart
+4 -3
@@ -63,13 +63,13 @@ abstract class NodeCreateOrEditViewModelBase with Store {
63 String socksProxyAddress;
64
65 @computed
66 - bool get isReady => address.isNotEmpty && port.isNotEmpty;
66 + bool get isReady =>
67 + (address.isNotEmpty && port.isNotEmpty) ||
68 + _walletType == WalletType.decred; // Allow an empty address.
69
70 bool get hasAuthCredentials =>
71 _walletType == WalletType.monero || _walletType == WalletType.wownero || _walletType == WalletType.haven;
72
71 - bool get hasTestnetSupport => _walletType == WalletType.bitcoin;
72 -
73 bool get hasPathSupport {
74 switch (_walletType) {
75 case WalletType.ethereum:
@@ -87,6 +87,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
87 case WalletType.bitcoinCash:
88 case WalletType.bitcoin:
89 case WalletType.zano:
90 + case WalletType.decred:
91 return false;
92 }
93 }
lib/view_model/node_list/node_list_view_model.dart
+4 -44
@@ -49,50 +49,10 @@ abstract class NodeListViewModelBase with Store {
49 await resetToDefault(_nodeSource);
50
51 Node node;
52 -
53 - switch (_appStore.wallet!.type) {
54 - case WalletType.bitcoin:
55 - if (_appStore.wallet!.isTestnet) {
56 - node = getBitcoinTestnetDefaultElectrumServer(nodes: _nodeSource)!;
57 - } else {
58 - node = getBitcoinDefaultElectrumServer(nodes: _nodeSource)!;
59 - }
60 - break;
61 - case WalletType.monero:
62 - node = getMoneroDefaultNode(nodes: _nodeSource);
63 - break;
64 - case WalletType.litecoin:
65 - node = getLitecoinDefaultElectrumServer(nodes: _nodeSource)!;
66 - break;
67 - case WalletType.haven:
68 - node = getHavenDefaultNode(nodes: _nodeSource)!;
69 - break;
70 - case WalletType.ethereum:
71 - node = getEthereumDefaultNode(nodes: _nodeSource)!;
72 - break;
73 - case WalletType.bitcoinCash:
74 - node = getBitcoinCashDefaultElectrumServer(nodes: _nodeSource)!;
75 - break;
76 - case WalletType.nano:
77 - node = getNanoDefaultNode(nodes: _nodeSource)!;
78 - break;
79 - case WalletType.polygon:
80 - node = getPolygonDefaultNode(nodes: _nodeSource)!;
81 - break;
82 - case WalletType.solana:
83 - node = getSolanaDefaultNode(nodes: _nodeSource)!;
84 - break;
85 - case WalletType.tron:
86 - node = getTronDefaultNode(nodes: _nodeSource)!;
87 - break;
88 - case WalletType.wownero:
89 - node = getWowneroDefaultNode(nodes: _nodeSource);
90 - break;
91 - case WalletType.zano:
92 - node = getZanoDefaultNode(nodes: _nodeSource)!;
93 - break;
94 - default:
95 - throw Exception('Unexpected wallet type: ${_appStore.wallet!.type}');
52 + if (_appStore.wallet!.type == WalletType.bitcoin && _appStore.wallet!.isTestnet) {
53 + node = getBitcoinTestnetDefaultElectrumServer(nodes: _nodeSource)!;
54 + } else {
55 + node = getDefaultNode(nodes: _nodeSource, type: _appStore.wallet!.type)!;
56 }
57
58 await setAsCurrent(node);
lib/view_model/restore/wallet_restore_from_qr_code.dart
+9 -3
@@ -44,6 +44,9 @@ class WalletRestoreFromQRCode {
44 'zano': WalletType.zano,
45 'zano-wallet': WalletType.zano,
46 'zano_wallet': WalletType.zano,
47 + 'decred': WalletType.decred,
48 + 'decred-wallet': WalletType.decred,
49 + 'decred_wallet': WalletType.decred,
50 };
51
52 static WalletType? _extractWalletType(String code) {
@@ -69,8 +72,12 @@ class WalletRestoreFromQRCode {
72 }
73
74 static String? _extractAddressFromUrl(String rawString, WalletType type) {
72 - return AddressResolver.extractAddressByType(
73 - raw: rawString, type: walletTypeToCryptoCurrency(type));
75 + try {
76 + return AddressResolver.extractAddressByType(
77 + raw: rawString, type: walletTypeToCryptoCurrency(type));
78 + } catch (_) {
79 + return null;
80 + }
81 }
82
83 static String? _extractSeedPhraseFromUrl(String rawString, WalletType walletType) {
@@ -122,7 +129,6 @@ class WalletRestoreFromQRCode {
129 }
130 if (queryParameters['address'] == null) {
131 queryParameters['address'] = _extractAddressFromUrl(code, walletType);
125 -
132 }
133
134 Map<String, dynamic> credentials = {'type': walletType, ...queryParameters, 'raw_qr': code};
lib/view_model/send/fees_view_model.dart
+8 -1
@@ -1,4 +1,5 @@
1 import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
2 +import 'package:cake_wallet/decred/decred.dart';
3 import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
4 import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
5 import 'package:cake_wallet/ethereum/ethereum.dart';
@@ -88,7 +89,13 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor
89 return transactionPriority == bitcoinCash!.getBitcoinCashTransactionPrioritySlow();
90 case WalletType.polygon:
91 return transactionPriority == polygon!.getPolygonTransactionPrioritySlow();
91 - default:
92 + case WalletType.decred:
93 + return transactionPriority == decred!.getDecredTransactionPrioritySlow();
94 + case WalletType.none:
95 + case WalletType.nano:
96 + case WalletType.banano:
97 + case WalletType.solana:
98 + case WalletType.tron:
99 return false;
100 }
101 }
lib/view_model/send/output.dart
+8
@@ -1,3 +1,4 @@
1 +import 'package:cake_wallet/decred/decred.dart';
2 import 'package:cake_wallet/di.dart';
3 import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
4 import 'package:cake_wallet/entities/parse_address_from_domain.dart';
@@ -101,6 +102,9 @@ abstract class OutputBase with Store {
102 case WalletType.bitcoinCash:
103 _amount = bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount);
104 break;
105 + case WalletType.decred:
106 + _amount = decred!.formatterStringDoubleToDecredAmount(_cryptoAmount);
107 + break;
108 case WalletType.haven:
109 _amount = haven!.formatterMoneroParseAmount(amount: _cryptoAmount);
110 break;
@@ -188,6 +192,10 @@ abstract class OutputBase with Store {
192 if (_wallet.type == WalletType.zano) {
193 return zano!.formatterIntAmountToDouble(amount: fee, currency: cryptoCurrencyHandler(), forFee: true);
194 }
195 +
196 + if (_wallet.type == WalletType.decred) {
197 + return decred!.formatterDecredAmountToDouble(amount: fee);
198 + }
199 } catch (e) {
200 printV(e.toString());
201 }
lib/view_model/send/send_view_model.dart
+48 -47
@@ -6,6 +6,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart';
6 import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
7 import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
8 import 'package:cake_wallet/nano/nano.dart';
9 +import 'package:cake_wallet/decred/decred.dart';
10 import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
11 import 'package:cake_wallet/entities/contact_record.dart';
12 import 'package:cake_wallet/entities/wallet_contact.dart';
@@ -251,6 +252,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
252 wallet.type == WalletType.litecoin ||
253 wallet.type == WalletType.monero ||
254 wallet.type == WalletType.wownero ||
255 + wallet.type == WalletType.decred ||
256 wallet.type == WalletType.bitcoinCash;
257
258 @computed
@@ -539,6 +541,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
541 case WalletType.zano:
542 return zano!.createZanoTransactionCredentials(
543 outputs: outputs, priority: priority!, currency: selectedCryptoCurrency);
544 + case WalletType.decred:
545 + this.coinTypeToSpendFrom = UnspentCoinType.any;
546 + return decred!.createDecredTransactionCredentials(outputs, priority!);
547 default:
548 throw Exception('Unexpected wallet type: ${wallet.type}');
549 }
@@ -681,55 +686,51 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
686 }
687 }
688
684 - if (walletType == WalletType.bitcoin ||
685 - walletType == WalletType.litecoin ||
686 - walletType == WalletType.bitcoinCash) {
687 - if (error is TransactionWrongBalanceException) {
688 - if (error.amount != null)
689 - return S.current
690 - .tx_wrong_balance_with_amount_exception(currency.toString(), error.amount.toString());
689 + if (error is TransactionWrongBalanceException) {
690 + if (error.amount != null)
691 + return S.current
692 + .tx_wrong_balance_with_amount_exception(currency.toString(), error.amount.toString());
693
692 - return S.current.tx_wrong_balance_exception(currency.toString());
693 - }
694 - if (error is TransactionNoInputsException) {
695 - return S.current.tx_not_enough_inputs_exception;
696 - }
697 - if (error is TransactionNoFeeException) {
698 - return S.current.tx_zero_fee_exception;
699 - }
700 - if (error is TransactionNoDustException) {
701 - return S.current.tx_no_dust_exception;
702 - }
703 - if (error is TransactionCommitFailed) {
704 - if (error.errorMessage != null && error.errorMessage!.contains("no peers replied")) {
705 - return S.current.tx_commit_failed_no_peers;
706 - }
707 - return "${S.current.tx_commit_failed}${error.errorMessage != null ? "\n\n${error.errorMessage}" : ""}";
708 - }
709 - if (error is TransactionCommitFailedDustChange) {
710 - return S.current.tx_rejected_dust_change;
711 - }
712 - if (error is TransactionCommitFailedDustOutput) {
713 - return S.current.tx_rejected_dust_output;
714 - }
715 - if (error is TransactionCommitFailedDustOutputSendAll) {
716 - return S.current.tx_rejected_dust_output_send_all;
717 - }
718 - if (error is TransactionCommitFailedVoutNegative) {
719 - return S.current.tx_rejected_vout_negative;
720 - }
721 - if (error is TransactionCommitFailedBIP68Final) {
722 - return S.current.tx_rejected_bip68_final;
723 - }
724 - if (error is TransactionCommitFailedLessThanMin) {
725 - return S.current.fee_less_than_min;
726 - }
727 - if (error is TransactionNoDustOnChangeException) {
728 - return S.current.tx_commit_exception_no_dust_on_change(error.min, error.max);
729 - }
730 - if (error is TransactionInputNotSupported) {
731 - return S.current.tx_invalid_input;
694 + return S.current.tx_wrong_balance_exception(currency.toString());
695 + }
696 + if (error is TransactionNoInputsException) {
697 + return S.current.tx_not_enough_inputs_exception;
698 + }
699 + if (error is TransactionNoFeeException) {
700 + return S.current.tx_zero_fee_exception;
701 + }
702 + if (error is TransactionNoDustException) {
703 + return S.current.tx_no_dust_exception;
704 + }
705 + if (error is TransactionCommitFailed) {
706 + if (error.errorMessage != null && error.errorMessage!.contains("no peers replied")) {
707 + return S.current.tx_commit_failed_no_peers;
708 }
709 + return "${S.current.tx_commit_failed}${error.errorMessage != null ? "\n\n${error.errorMessage}" : ""}";
710 + }
711 + if (error is TransactionCommitFailedDustChange) {
712 + return S.current.tx_rejected_dust_change;
713 + }
714 + if (error is TransactionCommitFailedDustOutput) {
715 + return S.current.tx_rejected_dust_output;
716 + }
717 + if (error is TransactionCommitFailedDustOutputSendAll) {
718 + return S.current.tx_rejected_dust_output_send_all;
719 + }
720 + if (error is TransactionCommitFailedVoutNegative) {
721 + return S.current.tx_rejected_vout_negative;
722 + }
723 + if (error is TransactionCommitFailedBIP68Final) {
724 + return S.current.tx_rejected_bip68_final;
725 + }
726 + if (error is TransactionCommitFailedLessThanMin) {
727 + return S.current.fee_less_than_min;
728 + }
729 + if (error is TransactionNoDustOnChangeException) {
730 + return S.current.tx_commit_exception_no_dust_on_change(error.min, error.max);
731 + }
732 + if (error is TransactionInputNotSupported) {
733 + return S.current.tx_invalid_input;
734 }
735
736 return errorMessage;
lib/view_model/settings/privacy_settings_view_model.dart
+2 -1
@@ -44,7 +44,8 @@ abstract class PrivacySettingsViewModelBase with Store {
44 _wallet.type == WalletType.wownero ||
45 _wallet.type == WalletType.bitcoin ||
46 _wallet.type == WalletType.litecoin ||
47 - _wallet.type == WalletType.bitcoinCash;
47 + _wallet.type == WalletType.bitcoinCash ||
48 + _wallet.type == WalletType.decred;
49
50 bool get isMoneroWallet => _wallet.type == WalletType.monero;
51
lib/view_model/transaction_details_view_model.dart
+55 -1
@@ -48,6 +48,7 @@ abstract class TransactionDetailsViewModelBase with Store {
48 final dateFormat = DateFormatter.withCurrentLocal();
49 final tx = transactionInfo;
50
51 + // TODO: can be cleaned further
52 switch (wallet.type) {
53 case WalletType.monero:
54 _addMoneroListItems(tx, dateFormat);
@@ -84,8 +85,12 @@ abstract class TransactionDetailsViewModelBase with Store {
85 case WalletType.zano:
86 _addZanoListItems(tx, dateFormat);
87 break;
87 - default:
88 + case WalletType.decred:
89 + _addDecredListItems(tx, dateFormat);
90 break;
91 + case WalletType.none:
92 + case WalletType.banano:
93 + break;
94 }
95
96 final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
@@ -186,6 +191,8 @@ abstract class TransactionDetailsViewModelBase with Store {
191 return 'https://explore.wownero.com/tx/${txId}';
192 case WalletType.zano:
193 return 'https://explorer.zano.org/transaction/${txId}';
194 + case WalletType.decred:
195 + return 'https://${wallet.isTestnet ? "testnet" : "dcrdata"}.decred.org/tx/${txId.split(':')[0]}';
196 case WalletType.none:
197 return '';
198 }
@@ -218,6 +225,8 @@ abstract class TransactionDetailsViewModelBase with Store {
225 return S.current.view_transaction_on + 'Wownero.com';
226 case WalletType.zano:
227 return S.current.view_transaction_on + 'explorer.zano.org';
228 + case WalletType.decred:
229 + return S.current.view_transaction_on + 'dcrdata.decred.org';
230 case WalletType.none:
231 return '';
232 }
@@ -672,6 +681,51 @@ abstract class TransactionDetailsViewModelBase with Store {
681 items.addAll(_items);
682 }
683
684 + void _addDecredListItems(TransactionInfo tx, DateFormat dateFormat) {
685 + final _items = [
686 + StandartListItem(
687 + title: S.current.transaction_details_transaction_id,
688 + value: tx.txHash,
689 + key: ValueKey('standard_list_item_transaction_details_id_key'),
690 + ),
691 + StandartListItem(
692 + title: S.current.transaction_details_date,
693 + value: dateFormat.format(tx.date),
694 + key: ValueKey('standard_list_item_transaction_details_date_key'),
695 + ),
696 + StandartListItem(
697 + title: S.current.transaction_details_height,
698 + value: '${tx.height}',
699 + key: ValueKey('standard_list_item_transaction_details_height_key'),
700 + ),
701 + StandartListItem(
702 + title: S.current.transaction_details_amount,
703 + value: tx.amountFormatted(),
704 + key: ValueKey('standard_list_item_transaction_details_amount_key'),
705 + ),
706 + if (tx.feeFormatted()?.isNotEmpty ?? false)
707 + StandartListItem(
708 + title: S.current.transaction_details_fee,
709 + value: tx.feeFormatted()!,
710 + key: ValueKey('standard_list_item_transaction_details_fee_key'),
711 + ),
712 + if (showRecipientAddress && tx.to != null)
713 + StandartListItem(
714 + title: S.current.transaction_details_recipient_address,
715 + value: tx.to!,
716 + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'),
717 + ),
718 + if (tx.from != null)
719 + StandartListItem(
720 + title: S.current.transaction_details_source_address,
721 + value: tx.from!,
722 + key: ValueKey('standard_list_item_transaction_details_source_address_key'),
723 + ),
724 + ];
725 +
726 + items.addAll(_items);
727 + }
728 +
729 @action
730 Future<void> _checkForRBF(TransactionInfo tx) async {
731 if (wallet.type == WalletType.bitcoin &&
lib/view_model/unspent_coins/unspent_coins_list_view_model.dart
+8 -1
@@ -1,6 +1,7 @@
1 import 'package:cake_wallet/bitcoin/bitcoin.dart';
2 import 'package:cake_wallet/monero/monero.dart';
3 import 'package:cake_wallet/utils/exception_handler.dart';
4 +import 'package:cake_wallet/decred/decred.dart';
5 import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
6 import 'package:cake_wallet/wownero/wownero.dart';
7 import 'package:cw_core/unspent_coin_type.dart';
@@ -92,6 +93,8 @@ abstract class UnspentCoinsListViewModelBase with Store {
93 return wownero!.formatterWowneroAmountToString(amount: fullBalance);
94 if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type))
95 return bitcoin!.formatterBitcoinAmountToString(amount: fullBalance);
96 + if (wallet.type == WalletType.decred)
97 + return decred!.formatterDecredAmountToString(amount: fullBalance);
98 return '';
99 }
100
@@ -105,7 +108,9 @@ abstract class UnspentCoinsListViewModelBase with Store {
108 if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type)) {
109 await bitcoin!.updateUnspents(wallet);
110 }
108 -
111 + if (wallet.type == WalletType.decred) {
112 + decred!.updateUnspents(wallet);
113 + }
114 _updateUnspentCoinsInfo();
115 }
116
@@ -119,6 +124,8 @@ abstract class UnspentCoinsListViewModelBase with Store {
124 case WalletType.litecoin:
125 case WalletType.bitcoinCash:
126 return bitcoin!.getUnspents(wallet, coinTypeToSpendFrom: coinTypeToSpendFrom);
127 + case WalletType.decred:
128 + return decred!.getUnspents(wallet);
129 default:
130 return List.empty();
131 }
lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart
+40 -28
@@ -5,6 +5,7 @@ import 'package:cw_core/wallet_base.dart';
5 import 'package:cake_wallet/bitcoin/bitcoin.dart';
6 import 'package:cake_wallet/monero/monero.dart';
7 import 'package:cake_wallet/haven/haven.dart';
8 +import 'package:cake_wallet/decred/decred.dart';
9 import 'package:cw_core/wallet_type.dart';
10
11 part 'wallet_address_edit_or_create_view_model.g.dart';
@@ -27,13 +28,12 @@ class AddressEditOrCreateStateFailure extends AddressEditOrCreateState {
28 }
29
30 abstract class WalletAddressEditOrCreateViewModelBase with Store {
30 - WalletAddressEditOrCreateViewModelBase(
31 - {required WalletBase wallet, WalletAddressListItem? item})
31 + WalletAddressEditOrCreateViewModelBase({required WalletBase wallet, WalletAddressListItem? item})
32 : isEdit = item != null,
33 state = AddressEditOrCreateStateInitial(),
34 label = item?.name ?? '',
35 _item = item,
36 - _wallet = wallet;
36 + _wallet = wallet;
37
38 @observable
39 AddressEditOrCreateState state;
@@ -46,7 +46,8 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
46 final WalletAddressListItem? _item;
47 final WalletBase _wallet;
48
49 - bool get isElectrum => _wallet.type == WalletType.bitcoin ||
49 + bool get isElectrum =>
50 + _wallet.type == WalletType.bitcoin ||
51 _wallet.type == WalletType.bitcoinCash ||
52 _wallet.type == WalletType.litecoin;
53
@@ -69,39 +70,46 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
70 Future<void> _createNew() async {
71 final wallet = _wallet;
72
72 - if (isElectrum) await bitcoin!.generateNewAddress(wallet, label);
73 + if (isElectrum) {
74 + await bitcoin!.generateNewAddress(wallet, label);
75 + await wallet.save();
76 + }
77 +
78 + if (wallet.type == WalletType.decred) {
79 + await decred!.generateNewAddress(wallet, label);
80 + await wallet.save();
81 + }
82
83 if (wallet.type == WalletType.monero) {
75 - await monero
76 - !.getSubaddressList(wallet)
77 - .addSubaddress(
78 - wallet,
79 - accountIndex: monero!.getCurrentAccount(wallet).id,
80 - label: label);
81 - final addr = await monero!.getSubaddressList(wallet).subaddresses.first.address; // first because the order is reversed
84 + await monero!
85 + .getSubaddressList(wallet)
86 + .addSubaddress(wallet, accountIndex: monero!.getCurrentAccount(wallet).id, label: label);
87 + final addr = await monero!
88 + .getSubaddressList(wallet)
89 + .subaddresses
90 + .first
91 + .address; // first because the order is reversed
92 wallet.walletAddresses.manualAddresses.add(addr);
93 await wallet.save();
94 }
95
96 if (wallet.type == WalletType.wownero) {
87 - await wownero
88 - !.getSubaddressList(wallet)
89 - .addSubaddress(
90 - wallet,
91 - accountIndex: wownero!.getCurrentAccount(wallet).id,
92 - label: label);
93 - final addr = await wownero!.getSubaddressList(wallet).subaddresses.first.address; // first because the order is reversed
97 + await wownero!
98 + .getSubaddressList(wallet)
99 + .addSubaddress(wallet, accountIndex: wownero!.getCurrentAccount(wallet).id, label: label);
100 + final addr = await wownero!
101 + .getSubaddressList(wallet)
102 + .subaddresses
103 + .first
104 + .address; // first because the order is reversed
105 wallet.walletAddresses.manualAddresses.add(addr);
106 await wallet.save();
107 }
108
109 if (wallet.type == WalletType.haven) {
99 - await haven
100 - !.getSubaddressList(wallet)
101 - .addSubaddress(
102 - wallet,
103 - accountIndex: haven!.getCurrentAccount(wallet).id,
104 - label: label);
110 + await haven!
111 + .getSubaddressList(wallet)
112 + .addSubaddress(wallet, accountIndex: haven!.getCurrentAccount(wallet).id, label: label);
113 await wallet.save();
114 }
115 }
@@ -111,6 +119,12 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
119
120 if (isElectrum) await bitcoin!.updateAddress(wallet, _item!.address, label);
121
122 + if (wallet.type == WalletType.decred) {
123 + await decred!.updateAddress(wallet, _item!.address, label);
124 + await wallet.save();
125 + return;
126 + }
127 +
128 final index = _item?.id;
129 if (index != null) {
130 if (wallet.type == WalletType.monero) {
@@ -125,9 +139,7 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
139 }
140 if (wallet.type == WalletType.haven) {
141 await haven!.getSubaddressList(wallet).setLabelSubaddress(wallet,
128 - accountIndex: haven!.getCurrentAccount(wallet).id,
129 - addressIndex: index,
130 - label: label);
142 + accountIndex: haven!.getCurrentAccount(wallet).id, addressIndex: index, label: label);
143 await wallet.save();
144 }
145 }
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+30 -2
@@ -12,6 +12,7 @@ import 'package:cake_wallet/haven/haven.dart';
12 import 'package:cake_wallet/monero/monero.dart';
13 import 'package:cake_wallet/polygon/polygon.dart';
14 import 'package:cake_wallet/solana/solana.dart';
15 +import 'package:cake_wallet/decred/decred.dart';
16 import 'package:cake_wallet/store/app_store.dart';
17 import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
18 import 'package:cake_wallet/store/settings_store.dart';
@@ -222,6 +223,22 @@ class ZanoURI extends PaymentURI {
223 }
224 }
225
226 +class DecredURI extends PaymentURI {
227 + DecredURI({required String amount, required String address})
228 + : super(amount: amount, address: address);
229 +
230 + @override
231 + String toString() {
232 + var base = 'decred:' + address;
233 +
234 + if (amount.isNotEmpty) {
235 + base += '?amount=${amount.replaceAll(',', '.')}';
236 + }
237 +
238 + return base;
239 + }
240 +}
241 +
242 abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store {
243 WalletAddressListViewModelBase({
244 required AppStore appStore,
@@ -313,7 +330,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
330 case WalletType.wownero:
331 return WowneroURI(amount: amount, address: address.address);
332 case WalletType.zano:
316 - return ZanoURI(amount: amount, address: address.address);
333 + return ZanoURI(amount: amount, address: address.address);
334 + case WalletType.decred:
335 + return DecredURI(amount: amount, address: address.address);
336 case WalletType.none:
337 throw Exception('Unexpected type: ${type.toString()}');
338 }
@@ -468,6 +487,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
487 addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
488 }
489
490 + if (wallet.type == WalletType.decred) {
491 + final addrInfos = decred!.getAddressInfos(wallet);
492 + addrInfos.forEach((info) {
493 + addressList.add(new WalletAddressListItem(isPrimary: false, address: info.address,
494 + name: info.label));
495 + });
496 + }
497 +
498 for (var i = 0; i < addressList.length; i++) {
499 if (!(addressList[i] is WalletAddressListItem)) continue;
500 (addressList[i] as WalletAddressListItem).isHidden = wallet.walletAddresses.hiddenAddresses
@@ -561,7 +588,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
588 WalletType.haven,
589 WalletType.bitcoinCash,
590 WalletType.bitcoin,
564 - WalletType.litecoin
591 + WalletType.litecoin,
592 + WalletType.decred
593 ].contains(wallet.type);
594
595 @computed
lib/view_model/wallet_keys_view_model.dart
+9
@@ -12,6 +12,7 @@ import 'package:cw_core/wallet_type.dart';
12 import 'package:cw_monero/monero_wallet.dart';
13 import 'package:flutter/foundation.dart';
14 import 'package:mobx/mobx.dart';
15 +import 'package:cake_wallet/decred/decred.dart';
16 import 'package:polyseed/polyseed.dart';
17
18 part 'wallet_keys_view_model.g.dart';
@@ -155,6 +156,12 @@ abstract class WalletKeysViewModelBase with Store {
156 ),
157 ]);
158 break;
159 + case WalletType.decred:
160 + final pubkey = decred!.pubkey(_appStore.wallet!);
161 + items.addAll([
162 + StandartListItem(title: S.current.view_key_public, value: pubkey),
163 + ]);
164 + break;
165 case WalletType.bitcoin:
166 case WalletType.litecoin:
167 case WalletType.bitcoinCash:
@@ -248,6 +255,8 @@ abstract class WalletKeysViewModelBase with Store {
255 return 'wownero-wallet';
256 case WalletType.zano:
257 return 'zano-wallet';
258 + case WalletType.decred:
259 + return 'decred-wallet';
260 default:
261 throw Exception('Unexpected wallet type: ${_wallet.type.toString()}');
262 }
lib/view_model/wallet_new_vm.dart
+5
@@ -21,6 +21,7 @@ import 'package:cake_wallet/tron/tron.dart';
21 import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
22 import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
23 import 'package:cake_wallet/wownero/wownero.dart';
24 +import 'package:cake_wallet/decred/decred.dart';
25 import 'package:cw_core/wallet_base.dart';
26 import 'package:cw_core/wallet_credentials.dart';
27 import 'package:cw_core/wallet_info.dart';
@@ -83,6 +84,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
84 return 25;
85 case WalletType.zano:
86 return 26;
87 + case WalletType.decred:
88 + return 15;
89 }
90 }
91
@@ -170,6 +173,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
173 password: walletPassword,
174 passphrase: passphrase,
175 );
176 + case WalletType.decred:
177 + return decred!.createDecredNewWalletCredentials(name: name);
178 case WalletType.none:
179 throw Exception('Unexpected type: ${type.toString()}');
180 }
lib/view_model/wallet_restore_view_model.dart
+20 -4
@@ -11,6 +11,7 @@ import 'package:cake_wallet/polygon/polygon.dart';
11 import 'package:cake_wallet/solana/solana.dart';
12 import 'package:cake_wallet/store/app_store.dart';
13 import 'package:cake_wallet/tron/tron.dart';
14 +import 'package:cake_wallet/decred/decred.dart';
15 import 'package:cake_wallet/view_model/restore/restore_mode.dart';
16 import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
17 import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
@@ -58,6 +59,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
59 case WalletType.haven:
60 case WalletType.ethereum:
61 case WalletType.polygon:
62 + case WalletType.decred:
63 availableModes = [WalletRestoreMode.seed, WalletRestoreMode.keys];
64 break;
65 case WalletType.bitcoin:
@@ -77,6 +79,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
79 }
80
81 static const moneroSeedMnemonicLength = 25;
82 + static const decredSeedMnemonicLength = 15;
83
84 late List<WalletRestoreMode> availableModes;
85 final bool hasSeedLanguageSelector;
@@ -171,11 +174,18 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
174 );
175 case WalletType.zano:
176 return zano!.createZanoRestoreWalletFromSeedCredentials(
174 - name: name,
175 - password: password,
176 - height: height,
177 + name: name,
178 + password: password,
179 + height: height,
180 passphrase: passphrase??'',
178 - mnemonic: seed);
181 + mnemonic: seed,
182 + );
183 + case WalletType.decred:
184 + return decred!.createDecredRestoreWalletFromSeedCredentials(
185 + name: name,
186 + mnemonic: seed,
187 + password: password,
188 + );
189 case WalletType.none:
190 break;
191 }
@@ -251,6 +261,12 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
261 password: password,
262 language: 'English',
263 );
264 + case WalletType.decred:
265 + return decred!.createDecredRestoreWalletFromPubkeyCredentials(
266 + name: name,
267 + password: password,
268 + pubkey: viewKey!,
269 + );
270 default:
271 break;
272 }
macos/Flutter/GeneratedPluginRegistrant.swift
+2
@@ -6,6 +6,7 @@ import FlutterMacOS
6 import Foundation
7
8 import connectivity_plus
9 +import cw_decred
10 import cw_mweb
11 import device_info_plus
12 import devicelocale
@@ -24,6 +25,7 @@ import wakelock_plus
25
26 func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
27 ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
28 + CwDecredPlugin.register(with: registry.registrar(forPlugin: "CwDecredPlugin"))
29 CwMwebPlugin.register(with: registry.registrar(forPlugin: "CwMwebPlugin"))
30 DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
31 DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin"))
macos/Podfile.lock
+8
@@ -3,6 +3,7 @@ PODS:
3 - FlutterMacOS
4 - ReachabilitySwift
5 - cw_mweb (0.0.1):
6 + - cw_decred (0.0.1):
7 - FlutterMacOS
8 - device_info_plus (0.0.1):
9 - FlutterMacOS
@@ -45,6 +46,7 @@ PODS:
46 DEPENDENCIES:
47 - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`)
48 - cw_mweb (from `Flutter/ephemeral/.symlinks/plugins/cw_mweb/macos`)
49 + - cw_decred (from `Flutter/ephemeral/.symlinks/plugins/cw_decred/macos`)
50 - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`)
51 - devicelocale (from `Flutter/ephemeral/.symlinks/plugins/devicelocale/macos`)
52 - fast_scanner (from `Flutter/ephemeral/.symlinks/plugins/fast_scanner/macos`)
@@ -72,6 +74,10 @@ EXTERNAL SOURCES:
74 :path: Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos
75 cw_mweb:
76 :path: Flutter/ephemeral/.symlinks/plugins/cw_mweb/macos
77 + cw_decred:
78 + :path: Flutter/ephemeral/.symlinks/plugins/cw_decred/macos
79 + cw_monero:
80 + :path: Flutter/ephemeral/.symlinks/plugins/cw_monero/macos
81 device_info_plus:
82 :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos
83 devicelocale:
@@ -108,6 +114,8 @@ EXTERNAL SOURCES:
114 SPEC CHECKSUMS:
115 connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747
116 cw_mweb: 7440b12ead811dda972a9918442ea2a458e8742c
117 + cw_monero: ec03de55a19c4a2b174ea687e0f4202edc716fa4
118 + cw_decred: 0c93fbeb31bd97a6ad4ec5680960af0943bfca78
119 device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
120 devicelocale: 9f0f36ac651cabae2c33f32dcff4f32b61c38225
121 fast_scanner: d31bae07e2653403a69dac99fb710c1722b16a97
model_generator.sh
+4 -4
@@ -1,22 +1,22 @@
1 #!/bin/bash
2 set -x -e
3
4 -for cwcoin in cw_{core,evm,monero,bitcoin,haven,nano,bitcoin_cash,solana,tron,wownero,zano}
4 +for cwcoin in cw_{core,evm,monero,bitcoin,haven,nano,bitcoin_cash,solana,tron,wownero,zano,decred}
5 do
6 if [[ "x$1" == "xasync" ]];
7 then
8 bash -c "cd $cwcoin; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .." &
9 else
10 - bash -c "cd $cwcoin; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd .."
10 + cd $cwcoin; flutter pub get; dart run build_runner build --delete-conflicting-outputs; cd ..
11 fi
12 done
13 for cwcoin in cw_{polygon,ethereum,mweb};
14 -do
14 +do
15 if [[ "x$1" == "xasync" ]];
16 then
17 bash -c "cd $cwcoin; flutter pub get; cd .." &
18 else
19 - bash -c "cd $cwcoin; flutter pub get; cd .."
19 + cd $cwcoin; flutter pub get; cd ..
20 fi
21 done
22
pubspec_base.yaml
+1
@@ -189,6 +189,7 @@ flutter:
189 - assets/tron_node_list.yml
190 - assets/wownero_node_list.yml
191 - assets/zano_node_list.yml
192 + - assets/decred_node_list.yml
193 - assets/text/
194 - assets/faq/
195 - assets/animation/
res/values/strings_ar.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "يخضع تخزين واستخدام رقم بطاقة الدفع الخاصة بك (وبيانات الاعتماد المقابلة لرقم بطاقة الدفع الخاصة بك) في هذه المحفظة الرقمية لشروط وأحكام اتفاقية حامل البطاقة المعمول بها مع جهة إصدار بطاقة الدفع ، كما هو معمول به من وقت لآخر.",
209 "decimal_places_error": "عدد كبير جدًا من المنازل العشرية",
210 "decimals_cannot_be_zero": "الرمز العشري لا يمكن أن يكون الصفر.",
211 + "decred_info_card_details": "يستخدم Decred طريقة متزامنة لا مركزية وحفاظ على الخصوصية المعروفة باسم \"SPV\" ، والتي تستغرق وقتًا أطول من محفظة Bitcoin العادية. لمعرفة المزيد ، انقر أدناه.",
212 + "decred_info_title": "التزامن في Decred",
213 "default_buy_provider": "مزود شراء الافتراضي",
214 "default_sell_provider": "ﻲﺿﺍﺮﺘﻓﻻﺍ ﻊﻴﺒﻟﺍ ﺩﻭﺰﻣ",
215 "delete": "حذف",
@@ -546,6 +548,7 @@
548 "proceed_after_one_minute": "إذا لم تستمر الشاشة بعد دقيقة واحدة ، فتحقق من بريدك الإلكتروني.",
549 "proceed_on_device": "تابع جهازك",
550 "proceed_on_device_description": "يرجى اتباع الإرشادات المطلوبة على محفظة الأجهزة الخاصة بك",
551 + "processing": "يعالج",
552 "profile": "حساب تعريفي",
553 "provider_error": "خطأ ${provider}",
554 "public_key": "مفتاح عمومي",
res/values/strings_bg.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "Съхранението и използването на данните от вашата платежна карта в този дигитален портфейл подлежат на условията на съответното съгласие за картодържец от издателя на картата.",
209 "decimal_places_error": "Твърде много знаци след десетичната запетая",
210 "decimals_cannot_be_zero": "Десетичната точка не може да бъде нула.",
211 + "decred_info_card_details": "DeCred използва децентрализиран и консервиращ метод за синхронизиране, известен като „SPV“, който отнема повече време от нормалния портфейл с биткойн. За да научите повече, докоснете по -долу.",
212 + "decred_info_title": "Синхронизация в Decred",
213 "default_buy_provider": "Доставчик по подразбиране купува",
214 "default_sell_provider": "Доставчик за продажба по подразбиране",
215 "delete": "Изтрий",
@@ -546,6 +548,7 @@
548 "proceed_after_one_minute": "Ако процесът продължи повече от 1 минута, проверете своя имейл.",
549 "proceed_on_device": "Продължете на вашето устройство",
550 "proceed_on_device_description": "Моля, следвайте инструкциите, подканени на вашия хардуер",
551 + "processing": "Обработка",
552 "profile": "Профил",
553 "provider_error": "Грешка на ${provider} ",
554 "public_key": "Публичен ключ",
res/values/strings_cs.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "Uložení a použití vašeho čísla platební karty (a přihlašovací údaje k vašemu číslu karty) v této digitální peněžence se řídí Obchodními podmínkami smlouvy příslušného držitele karty s vydavatelem karty (v jejich nejaktuálnější verzi).",
209 "decimal_places_error": "Příliš mnoho desetinných míst",
210 "decimals_cannot_be_zero": "Desetinná desetinná škola nemůže být nulová.",
211 + "decred_info_card_details": "Decred používá decentralizovanou a synchronizační metodu zachovávající soukromí známou jako „SPV“, která trvá déle než normální bitcoinová peněženka. Chcete -li se dozvědět více, klepněte na níže.",
212 + "decred_info_title": "Synchronizace v Decred",
213 "default_buy_provider": "Výchozí poskytovatel nákupu",
214 "default_sell_provider": "Výchozí poskytovatel prodeje",
215 "delete": "Smazat",
@@ -546,6 +548,7 @@
548 "proceed_after_one_minute": "Pokud proces nepokročí během 1 minuty, zkontrolujte svůj e-mail.",
549 "proceed_on_device": "Pokračujte ve svém zařízení",
550 "proceed_on_device_description": "Postupujte podle pokynů na výzvu na vaší hardwarové peněžence",
551 + "processing": "Zpracování",
552 "profile": "Profil",
553 "provider_error": "${provider} chyba",
554 "public_key": "Veřejný klíč",
res/values/strings_de.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "Die Speicherung und Nutzung Ihrer Zahlungskartennummer (und Ihrer Zahlungskartennummer entsprechenden Anmeldeinformationen) in dieser digitalen Wallet unterliegt den Allgemeinen Geschäftsbedingungen des geltenden Karteninhabervertrags mit dem Zahlungskartenaussteller, gültig ab von Zeit zu Zeit.",
209 "decimal_places_error": "Zu viele Nachkommastellen",
210 "decimals_cannot_be_zero": "Token-Dezimalzahl kann nicht Null sein.",
211 + "decred_info_card_details": "Decred verwendet eine dezentrale und für Privatsphäre erziehende Synchronisation, die als „SPV“ bezeichnet wird und länger als eine normale Bitcoin-Brieftasche dauert. Um mehr zu erfahren, tippen Sie unten.",
212 + "decred_info_title": "Synchronisation in Decred",
213 "default_buy_provider": "Standard-Kaufanbieter",
214 "default_sell_provider": "Standard-Verkaufsanbieter",
215 "delete": "Löschen",
@@ -547,6 +549,7 @@
549 "proceed_after_one_minute": "Wenn der Bildschirm nach 1 Minute nicht weitergeht, überprüfen Sie bitte Ihre E-Mail.",
550 "proceed_on_device": "Fahren Sie auf Ihrem Gerät fort",
551 "proceed_on_device_description": "Bitte befolgen Sie die Anweisungen, die auf Ihrer Hardware-Wallet angezeigt werden",
552 + "processing": "Verarbeitung",
553 "profile": "Profil",
554 "provider_error": "${provider}-Fehler",
555 "public_key": "Öffentlicher Schlüssel",
res/values/strings_en.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "The storage and usage of your payment card number (and credentials corresponding to your payment card number) in this digital wallet are subject to the Terms and Conditions of the applicable cardholder agreement with the payment card issuer, as in effect from time to time.",
209 "decimal_places_error": "Too many decimal places",
210 "decimals_cannot_be_zero": "Token decimal cannot be zero.",
211 + "decred_info_card_details": "Decred uses a decentralized and privacy-preserving sync method known as “SPV”, which takes longer than a normal Bitcoin wallet. To learn more, tap below.",
212 + "decred_info_title": "Synchronization in Decred",
213 "default_buy_provider": "Default Buy Provider",
214 "default_sell_provider": "Default Sell Provider",
215 "delete": "Delete",
@@ -547,6 +549,7 @@
549 "proceed_after_one_minute": "If the screen doesn’t proceed after 1 minute, check your email.",
550 "proceed_on_device": "Proceed on your device",
551 "proceed_on_device_description": "Please follow the instructions prompted on your hardware wallet",
552 + "processing": "Processing",
553 "profile": "Profile",
554 "provider_error": "${provider} error",
555 "public_key": "Public key",
res/values/strings_es.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "El almacenamiento y el uso de su número de tarjeta de pago (y las credenciales correspondientes a su número de tarjeta de pago) en esta billetera digital están sujetos a los Términos y condiciones del acuerdo del titular de la tarjeta aplicable con el emisor de la tarjeta de pago, en vigor desde tiempo al tiempo.",
209 "decimal_places_error": "Demasiados lugares decimales",
210 "decimals_cannot_be_zero": "Token Decimal no puede ser cero.",
211 + "decred_info_card_details": "Decred utiliza un método de sincronización descentralizado y que presenta la privacidad conocido como \"SPV\", que lleva más tiempo que una billetera Bitcoin normal. Para obtener más información, toque a continuación.",
212 + "decred_info_title": "Sincronización en Decred",
213 "default_buy_provider": "Proveedor de compra predeterminado",
214 "default_sell_provider": "Proveedor de venta predeterminado",
215 "delete": "Borrar",
@@ -547,6 +549,7 @@
549 "proceed_after_one_minute": "Si la pantalla no continúa después de 1 minuto, revisa tu correo electrónico.",
550 "proceed_on_device": "Continúa con tu dispositivo",
551 "proceed_on_device_description": "Sigue las instrucciones solicitadas en su billetera de hardware",
552 + "processing": "Tratamiento",
553 "profile": "Perfil",
554 "provider_error": "${provider} error",
555 "public_key": "Clave pública",
res/values/strings_fr.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "Le stockage et l'utilisation de votre numéro de carte de paiement (et des informations d'identification correspondant à votre numéro de carte de paiement) dans ce portefeuille (wallet) numérique peuvent être soumis aux conditions générales de l'accord du titulaire de carte parfois en vigueur avec l'émetteur de la carte de paiement.",
209 "decimal_places_error": "Trop de décimales",
210 "decimals_cannot_be_zero": "La décimale du jeton ne peut pas être nulle.",
211 + "decred_info_card_details": "Decred utilise une méthode de synchronisation décentralisée et préservée de confidentialité connue sous le nom de «SPV», qui prend plus de temps qu'un portefeuille Bitcoin normal. Pour en savoir plus, appuyez sur ci-dessous.",
212 + "decred_info_title": "Synchronisation dans Decred",
213 "default_buy_provider": "Fournisseur d'achat par défaut",
214 "default_sell_provider": "Fournisseur de vente par défaut",
215 "delete": "Effacer",
@@ -546,6 +548,7 @@
548 "proceed_after_one_minute": "Si l'écran ne s'affiche pas après 1 minute, vérifiez vos e-mails.",
549 "proceed_on_device": "Continuez sur votre appareil",
550 "proceed_on_device_description": "Veuillez suivre les instructions affichées sur votre portefeuille physique.",
551 + "processing": "Traitement",
552 "profile": "Profil",
553 "provider_error": "Erreur de ${provider}",
554 "public_key": "Clef publique",
res/values/strings_ha.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "Adana da amfani da lambar katin kuɗin ku (da takaddun shaida masu dacewa da lambar katin kuɗin ku) a cikin wannan walat ɗin dijital suna ƙarƙashin Sharuɗɗa da Sharuɗɗa na yarjejeniya mai amfani da katin tare da mai fitar da katin biyan kuɗi, kamar yadda yake aiki daga lokaci zuwa lokaci.",
209 "decimal_places_error": "Wadannan suna da tsawon harsuna",
210 "decimals_cannot_be_zero": "Alamar alama ba zata iya zama sifili ba.",
211 + "decred_info_card_details": "An yanke amfani da shi da ingantaccen tsari da kuma tsarin adana Siscyc na sirri da aka sani da \"SPV\", wanda ke ɗaukar tsayi fiye da walatic na al'ada. Don ƙarin koyo, matsa ƙasa.",
212 + "decred_info_title": "Aiki tare a Decred",
213 "default_buy_provider": "Tsohuwar Siyarwa",
214 "default_sell_provider": "Tsohuwar Mai Bayar Siyarwa",
215 "delete": "Share",
@@ -548,6 +550,7 @@
550 "proceed_after_one_minute": "Idan allon bai ci gaba ba bayan minti 1, duba imel ɗin ku.",
551 "proceed_on_device": "Ci gaba akan na'urarka",
552 "proceed_on_device_description": "Da fatan za a bi umarnin akan walatware",
553 + "processing": "Aiki",
554 "profile": "Rabin fuska",
555 "provider_error": "${provider} kuskure",
556 "public_key": "Maɓallin jama'a",
res/values/strings_hi.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "इस डिजिटल वॉलेट में आपके भुगतान कार्ड नंबर (और आपके भुगतान कार्ड नंबर से संबंधित क्रेडेंशियल) का भंडारण और उपयोग भुगतान कार्ड जारीकर्ता के साथ लागू कार्डधारक समझौते के नियमों और शर्तों के अधीन है, जैसा कि प्रभावी है समय - समय पर।",
209 "decimal_places_error": "बहुत अधिक दशमलव स्थान",
210 "decimals_cannot_be_zero": "टोकन दशमलव शून्य नहीं हो सकता।",
211 + "decred_info_card_details": "डिक्रेड एक विकेन्द्रीकृत और गोपनीयता-संरक्षण सिंक विधि का उपयोग करता है जिसे \"एसपीवी\" के रूप में जाना जाता है, जो एक सामान्य बिटकॉइन वॉलेट से अधिक समय लेता है। अधिक जानने के लिए, नीचे टैप करें।",
212 + "decred_info_title": "डिक्रेड में सिंक्रनाइज़ेशन",
213 "default_buy_provider": "डिफ़ॉल्ट खरीद प्रदाता",
214 "default_sell_provider": "डिफ़ॉल्ट विक्रय प्रदाता",
215 "delete": "हटाएं",
@@ -547,6 +549,7 @@
549 "proceed_after_one_minute": "यदि 1 मिनट के बाद भी स्क्रीन आगे नहीं बढ़ती है, तो अपना ईमेल देखें।",
550 "proceed_on_device": "अपने डिवाइस पर आगे बढ़ें",
551 "proceed_on_device_description": "कृपया अपने हार्डवेयर वॉलेट पर दिए गए निर्देशों का पालन करें",
552 + "processing": "प्रसंस्करण",
553 "profile": "प्रोफ़ाइल",
554 "provider_error": "${provider} त्रुटि",
555 "public_key": "सार्वजनिक कुंजी",
res/values/strings_hr.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "Pohranjivanje i korištenje broja vaše platne kartice (i vjerodajnica koje odgovaraju broju vaše platne kartice) u ovom digitalnom novčaniku podliježu Uvjetima i odredbama važećeg ugovora vlasnika kartice s izdavateljem platne kartice, koji su na snazi ​​od S vremena na vrijeme.",
209 "decimal_places_error": "Previše decimalnih mjesta",
210 "decimals_cannot_be_zero": "Token Decimal ne može biti nula.",
211 + "decred_info_card_details": "DECRED koristi decentraliziranu metodu sinkronizacije za očuvanje privatnosti poznatu kao \"SPV\", koja traje duže od normalnog Bitcoin novčanika. Da biste saznali više, dodirnite u nastavku.",
212 + "decred_info_title": "Sinkronizacija u DECRED",
213 "default_buy_provider": "Zadani davatelj kupnje",
214 "default_sell_provider": "Zadani dobavljač prodaje",
215 "delete": "Izbriši",
@@ -546,6 +548,7 @@
548 "proceed_after_one_minute": "Ako se zaslon ne nastavi nakon 1 minute, provjerite svoju e-poštu.",
549 "proceed_on_device": "Nastavite na svom uređaju",
550 "proceed_on_device_description": "Slijedite upute zatražene na vašem hardverskom novčaniku",
551 + "processing": "Obrada",
552 "profile": "Profil",
553 "provider_error": "${provider} greška",
554 "public_key": "Javni ključ",
res/values/strings_hy.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "Ձեր վճարային քարտի համարի (և ձեր վճարային քարտի համարի համապատասխան վկայականներ) պահպանումն ու օգտագործումը այս թվային դրամապանակում ենթակա են վճարային քարտ թողարկող կողմի գործող պայմանների և պայմանագրի",
209 "decimal_places_error": "Խմբակային տեղերի սխալ",
210 "decimals_cannot_be_zero": "Խմբակային տեղերը չեն կարող լինել զրո",
211 + "decred_info_card_details": "DECREDE- ն օգտագործում է ապակենտրոնացված եւ գաղտնիության պահպանման համաժամացման համաժամացման մեթոդը, որը հայտնի է որպես «SPV», որը տեւում է ավելի երկար, քան նորմալ Bitcoin դրամապանակը: Ավելին իմանալու համար հպեք ստորեւ:",
212 + "decred_info_title": "Համաժամացումը DEPRED- ում",
213 "default_buy_provider": "Լռելյայն գնման մատակարար",
214 "default_sell_provider": "Լռելյայն վաճառքի մատակարար",
215 "delete": "Ջնջել",
@@ -545,6 +547,7 @@
547 "proceed_after_one_minute": "Եթե էկրանը 1 րոպեից ավել չի անցնում, ստուգեք ձեր էլեկտրոնային փոստը",
548 "proceed_on_device": "Շարունակեք ձեր սարքի վրա",
549 "proceed_on_device_description": "Խնդրում ենք հետևել ձեր սարքի վրա ցուցադրվող հրահանգներին",
550 + "processing": "Վերամշակում",
551 "profile": "Պրոֆիլ",
552 "provider_error": "${provider} սխալ",
553 "public_key": "Հանրային բանալի",
res/values/strings_id.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "Penyimpanan dan penggunaan nomor kartu pembayaran Anda (dan kredensial yang sesuai dengan nomor kartu pembayaran Anda) dalam dompet digital ini tertakluk pada Syarat dan Ketentuan persetujuan pemegang kartu yang berlaku dengan penerbit kartu pembayaran, seperti yang berlaku dari waktu ke waktu.",
209 "decimal_places_error": "Terlalu banyak tempat desimal",
210 "decimals_cannot_be_zero": "Token desimal tidak bisa nol.",
211 + "decred_info_card_details": "Decred menggunakan metode sinkronisasi yang terdesentralisasi dan mempertahankan privasi yang dikenal sebagai \"SPV\", yang membutuhkan waktu lebih lama dari dompet Bitcoin normal. Untuk mempelajari lebih lanjut, ketuk di bawah ini.",
212 + "decred_info_title": "Sinkronisasi dalam dekred",
213 "default_buy_provider": "Penyedia beli default",
214 "default_sell_provider": "Penyedia Penjualan Default",
215 "delete": "Hapus",
@@ -548,6 +550,7 @@
550 "proceed_after_one_minute": "Jika layar tidak bergerak setelah 1 menit, periksa email Anda.",
551 "proceed_on_device": "Lanjutkan di perangkat Anda",
552 "proceed_on_device_description": "Harap ikuti instruksi yang diminta di dompet perangkat keras Anda",
553 + "processing": "Pengolahan",
554 "profile": "Profil",
555 "provider_error": "${provider} error",
556 "public_key": "Kunci publik",
res/values/strings_it.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "L'archiviazione e l'utilizzo del numero della carta di pagamento (e delle credenziali corrispondenti al numero della carta di pagamento) in questo portafoglio digitale sono soggetti ai Termini e condizioni del contratto applicabile con il titolare della carta con l'emittente della carta di pagamento, come in vigore di tanto in tanto.",
209 "decimal_places_error": "Troppe cifre decimali",
210 "decimals_cannot_be_zero": "Il decimale token non può essere zero.",
211 + "decred_info_card_details": "Decred utilizza un metodo di sincronizzazione decentralizzato e di conservazione della privacy noto come \"SPV\", che richiede più tempo di un normale portafoglio Bitcoin. Per saperne di più, tocca sotto.",
212 + "decred_info_title": "Sincronizzazione in decred",
213 "default_buy_provider": "Provider di acquisto predefinito",
214 "default_sell_provider": "Provider di vendita predefinito",
215 "delete": "Elimina",
@@ -547,6 +549,7 @@
549 "proceed_after_one_minute": "Se la schermata non procede dopo 1 minuto, controlla la tua email.",
550 "proceed_on_device": "Procedi sul tuo dispositivo",
551 "proceed_on_device_description": "Segui le istruzioni richieste sul portafoglio hardware",
552 + "processing": "Elaborazione",
553 "profile": "Profilo",
554 "provider_error": "${provider} errore",
555 "public_key": "Chiave pubblica",
res/values/strings_ja.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "このデジタルウォレットでの支払いカード番号(および支払いカード番号に対応する資格情報)の保存と使用には、支払いカード発行者との該当するカード所有者契約の利用規約が適用されます。時々。",
209 "decimal_places_error": "小数点以下の桁数が多すぎる",
210 "decimals_cannot_be_zero": "トークン小数はゼロにすることはできません。",
211 + "decred_info_card_details": "Decredは、「SPV」と呼ばれる分散型およびプライバシーを摂取する同期メソッドを使用します。これには、通常のビットコインウォレットよりも時間がかかります。詳細については、以下をタップしてください。",
212 + "decred_info_title": "デコロードの同期",
213 "default_buy_provider": "デフォルトの購入プロバイダー",
214 "default_sell_provider": "デフォルトの販売プロバイダー",
215 "delete": "削除する",
@@ -547,6 +549,7 @@
549 "proceed_after_one_minute": "1分経っても画面が進まない場合は、メールを確認してください。",
550 "proceed_on_device": "デバイスに進みます",
551 "proceed_on_device_description": "ハードウェアウォレットにプロンプ​​トされた指示に従ってください",
552 + "processing": "処理",
553 "profile": "プロフィール",
554 "provider_error": "${provider} エラー",
555 "public_key": "公開鍵",
res/values/strings_ko.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "이 디지털 지갑에 있는 귀하의 지불 카드 번호(및 귀하의 지불 카드 번호에 해당하는 자격 증명)의 저장 및 사용은 부터 발효되는 지불 카드 발행자와의 해당 카드 소지자 계약의 이용 약관을 따릅니다. 수시로.",
209 "decimal_places_error": "소수점 이하 자릿수가 너무 많습니다.",
210 "decimals_cannot_be_zero": "토큰 소수점은 0이 될 수 없습니다.",
211 + "decred_info_card_details": "Decred는 정상적인 비트 코인 지갑보다 더 오래 걸리는 \"SPV\"로 알려진 분산 및 개인 정보 보호 동기화 방법을 사용합니다. 자세한 내용은 아래를 누릅니다.",
212 + "decred_info_title": "Decred의 동기화",
213 "default_buy_provider": "기본 구매 제공자",
214 "default_sell_provider": "기본 판매 공급자",
215 "delete": "지우다",
@@ -546,6 +548,7 @@
548 "proceed_after_one_minute": "1분 후에도 화면이 진행되지 않으면 이메일을 확인하세요.",
549 "proceed_on_device": "장치를 진행하십시오",
550 "proceed_on_device_description": "하드웨어 지갑에 표시된 지침을 따르십시오",
551 + "processing": "처리",
552 "profile": "프로필",
553 "provider_error": "${provider} 오류",
554 "public_key": "공개 키",
res/values/strings_my.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "ဤဒစ်ဂျစ်တယ်ပိုက်ဆံအိတ်ရှိ သင့်ငွေပေးချေမှုကတ်နံပါတ် (နှင့် သင့်ငွေပေးချေကတ်နံပါတ်နှင့် သက်ဆိုင်သောအထောက်အထားများ) ၏ သိုလှောင်မှုနှင့် အသုံးပြုမှုသည် အချိန်အခါနှင့်အမျှ သက်ရောက်မှုရှိသကဲ့သို့ ကတ်ကိုင်ဆောင်ထားသူ၏ သဘောတူညီချက်၏ စည်းကမ်းသတ်မှတ်ချက်များနှင့် ကိုက်ညီပါသည်။",
209 "decimal_places_error": "ဒဿမနေရာများ များလွန်းသည်။",
210 "decimals_cannot_be_zero": "တိုကင်ဒ decimal မသုညမဖြစ်နိုင်ပါ။",
211 + "decred_info_card_details": "ပုံမှန် bitcoin ပိုက်ဆံအိတ်ထက်ပိုရှည်သော \"SPV\" ဟုလူသိများသောဗဟိုချုပ်ကိုင်မှုလျှော့ချခြင်းနှင့်လုံခြုံမှုထိန်းသိမ်းခြင်းကိုထိန်းသိမ်းထားသည့်ထပ်တူပြုခြင်းနည်းလမ်းကိုအသုံးပြုသည်။ ပိုမိုလေ့လာရန်အောက်ပါကိုအသာပုတ်ပါ။",
212 + "decred_info_title": "ဒီဇင်ဘာလ၌ထပ်တူပြုခြင်း",
213 "default_buy_provider": "Default Provider ကိုဝယ်ပါ",
214 "default_sell_provider": "ပုံသေရောင်းချပေးသူ",
215 "delete": "ဖျက်ပါ။",
@@ -546,6 +548,7 @@
548 "proceed_after_one_minute": "မျက်နှာပြင်သည် ၁ မိနစ်အကြာတွင် ဆက်လက်မလုပ်ဆောင်ပါက သင့်အီးမေးလ်ကို စစ်ဆေးပါ။",
549 "proceed_on_device": "သင့်စက်ပေါ်တွင်ဆက်လက်ဆောင်ရွက်ပါ",
550 "proceed_on_device_description": "သင်၏ hardware ပိုက်ဆံအိတ်ပေါ်ရှိညွှန်ကြားချက်များကိုလိုက်နာပါ",
551 + "processing": "လုပ်ကိုင်ခြင်း",
552 "profile": "ကိုယ်ရေးအကျဉ်း",
553 "provider_error": "${provider} အမှား",
554 "public_key": "အများသူငှာသော့",
res/values/strings_nl.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "De opslag en het gebruik van uw betaalkaartnummer (en inloggegevens die overeenkomen met uw betaalkaartnummer) in deze digitale portemonnee zijn onderworpen aan de Algemene voorwaarden van de toepasselijke kaarthouderovereenkomst met de uitgever van de betaalkaart, zoals van kracht vanaf tijd tot tijd.",
209 "decimal_places_error": "Te veel decimalen",
210 "decimals_cannot_be_zero": "Token decimaal kan niet nul zijn.",
211 + "decred_info_card_details": "Decred maakt gebruik van een gedecentraliseerde en privacy-behouds-synchronisatiemethode bekend als \"SPV\", die langer duurt dan een normale Bitcoin-portemonnee. Voor meer informatie, tik hieronder.",
212 + "decred_info_title": "Synchronisatie in Decred",
213 "default_buy_provider": "Standaard Koopprovider",
214 "default_sell_provider": "Standaard verkoopaanbieder",
215 "delete": "Delete",
@@ -546,6 +548,7 @@
548 "proceed_after_one_minute": "Als het scherm na 1 minuut niet verder gaat, controleer dan uw e-mail.",
549 "proceed_on_device": "Ga verder met uw apparaat",
550 "proceed_on_device_description": "Volg de instructies die zijn aangevraagd op uw hardware -portemonnee",
551 + "processing": "Verwerking",
552 "profile": "Profiel",
553 "provider_error": "${provider} fout",
554 "public_key": "Publieke sleutel",
res/values/strings_pl.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "Przechowywanie i używanie numeru karty płatniczej (oraz danych uwierzytelniających odpowiadających numerowi karty płatniczej) w tym portfelu cyfrowym podlega Warunkom odpowiedniej umowy posiadacza karty z wydawcą karty płatniczej, zgodnie z obowiązującym od od czasu do czasu.",
209 "decimal_places_error": "Za dużo miejsc dziesiętnych",
210 "decimals_cannot_be_zero": "Token dziesiętny nie może być zerowy.",
211 + "decred_info_card_details": "Decred używa zdecentralizowanej i zachowującej prywatność metodę synchronizacji znanej jako „SPV”, która trwa dłużej niż normalny portfel bitcoin. Aby dowiedzieć się więcej, dotknij poniżej.",
212 + "decred_info_title": "Synchronizacja w dekred",
213 "default_buy_provider": "Domyślny dostawca zakupu",
214 "default_sell_provider": "Domyślny dostawca sprzedaży",
215 "delete": "Skasuj",
@@ -546,6 +548,7 @@
548 "proceed_after_one_minute": "Jeśli ekran nie przejdzie dalej po 1 minucie, sprawdź pocztę.",
549 "proceed_on_device": "Kontynuuj swoje urządzenie",
550 "proceed_on_device_description": "Postępuj zgodnie z instrukcjami wyświetlonymi w portfelu sprzętowym",
551 + "processing": "Przetwarzanie",
552 "profile": "Profil",
553 "provider_error": "${provider} pomyłka",
554 "public_key": "Klucz publiczny",
res/values/strings_pt.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "O armazenamento e uso do número do cartão de pagamento (e credenciais correspondentes ao número do cartão de pagamento) nesta carteira digital estão sujeitos aos Termos e Condições do contrato do titular do cartão aplicável com o emissor do cartão de pagamento, em vigor a partir de tempo ao tempo.",
209 "decimal_places_error": "Muitas casas decimais",
210 "decimals_cannot_be_zero": "Decimal de token não pode ser zero.",
211 + "decred_info_card_details": "O Decred usa um método de sincronização descentralizado e de preservação de privacidade, conhecido como \"SPV\", que leva mais tempo do que uma carteira normal de Bitcoin. Para saber mais, toque abaixo.",
212 + "decred_info_title": "Sincronização em Decred",
213 "default_buy_provider": "Provedor de compra padrão",
214 "default_sell_provider": "Provedor de venda padrão",
215 "delete": "Excluir",
@@ -548,6 +550,7 @@
550 "proceed_after_one_minute": "Se a tela não prosseguir após 1 minuto, verifique seu e-mail.",
551 "proceed_on_device": "Prossiga no seu dispositivo",
552 "proceed_on_device_description": "Siga as instruções solicitadas em sua carteira de hardware",
553 + "processing": "Processamento",
554 "profile": "Perfil",
555 "provider_error": "${provider} erro",
556 "public_key": "Chave pública",
res/values/strings_ru.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "Хранение и использование номера вашей платежной карты (и учетных данных, соответствующих номеру вашей платежной карты) в этом цифровом кошельке регулируются положениями и условиями применимого соглашения держателя карты с эмитентом платежной карты, действующим с время от времени.",
209 "decimal_places_error": "Слишком много десятичных знаков",
210 "decimals_cannot_be_zero": "Десятичный токен не может быть нулевым.",
211 + "decred_info_card_details": "DepRed использует децентрализованный метод синхронизации и сохраняющего конфиденциальность, известный как «SPV», который занимает больше времени, чем обычный биткойн-кошелек. Чтобы узнать больше, нажмите ниже.",
212 + "decred_info_title": "Синхронизация в декорации",
213 "default_buy_provider": "По умолчанию поставщик покупки",
214 "default_sell_provider": "Поставщик продаж по умолчанию",
215 "delete": "Удалить",
@@ -547,6 +549,7 @@
549 "proceed_after_one_minute": "Если через 1 минуту экран не отображается, проверьте свою электронную почту.",
550 "proceed_on_device": "Пройдите на свое устройство",
551 "proceed_on_device_description": "Пожалуйста, следуйте инструкциям, представленным на вашем аппаратном кошельке",
552 + "processing": "Обработка",
553 "profile": "Профиль",
554 "provider_error": "${provider} ошибка",
555 "public_key": "Публичный ключ",
res/values/strings_th.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "การเก็บรักษาและใช้หมายเลขบัตรจ่ายเงิน (และข้อมูลประจำตัวที่เกี่ยวข้องกับหมายเลขบัตรจ่ายเงิน) ในกระเป๋าดิจิทัลนี้ จะต้องยึดถือข้อกำหนดและเงื่อนไขของข้อตกลงผู้ใช้บัตรของผู้ถือบัตรที่เกี่ยวข้องกับบัตรผู้ถือบัตร ซึ่งจะมีผลตั้งแต่เวลานั้น",
209 "decimal_places_error": "ทศนิยมมากเกินไป",
210 "decimals_cannot_be_zero": "ทศนิยมโทเค็นไม่สามารถเป็นศูนย์ได้",
211 + "decred_info_card_details": "Decred ใช้วิธีการซิงค์การกระจายอำนาจและความเป็นส่วนตัวที่เรียกว่า \"SPV\" ซึ่งใช้เวลานานกว่ากระเป๋าเงิน bitcoin ปกติ หากต้องการเรียนรู้เพิ่มเติมแตะด้านล่าง",
212 + "decred_info_title": "การซิงโครไนซ์ใน decred",
213 "default_buy_provider": "ผู้ให้บริการซื้อเริ่มต้น",
214 "default_sell_provider": "ผู้ให้บริการการขายเริ่มต้น",
215 "delete": "ลบ",
@@ -546,6 +548,7 @@
548 "proceed_after_one_minute": "หากหน้าจอไม่ดำเนินการหลังจาก 1 นาทีโปรดตรวจสอบอีเมลของคุณ",
549 "proceed_on_device": "ดำเนินการบนอุปกรณ์ของคุณ",
550 "proceed_on_device_description": "โปรดทำตามคำแนะนำที่ได้รับแจ้งไว้ในกระเป๋าเงินฮาร์ดแวร์ของคุณ",
551 + "processing": "กำลังประมวลผล",
552 "profile": "ประวัติโดยย่อ",
553 "provider_error": "ข้อผิดพลาด ${provider}",
554 "public_key": "คีย์สาธารณะ",
res/values/strings_tl.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "Ang pag-iimbak at paggamit ng iyong numero sa card (at mga kredensyal na nauugnay sa numero ng iyong card sa pagbabayad) sa pagbabayad sa digital wallet na ito ay napapailalim sa mga tuntunin at kundisyon ng naaangkop na kasunduan sa may-ari ng card kasama ang nagbigay ng card ng pagbabayad, na may bisa sa pana-panahon.",
209 "decimal_places_error": "Masyadong maraming mga lugar na desimal",
210 "decimals_cannot_be_zero": "Ang token decimal ay hindi maaaring maging zero.",
211 + "decred_info_card_details": "Ang DECRED ay gumagamit ng isang desentralisado at privacy-pagpapanatili ng pamamaraan ng pag-sync na kilala bilang \"SPV\", na mas matagal kaysa sa isang normal na pitaka ng bitcoin. Upang malaman ang higit pa, mag -tap sa ibaba.",
212 + "decred_info_title": "Pag -synchronise sa Decred",
213 "default_buy_provider": "Default na Buy Provider",
214 "default_sell_provider": "Default na Sell Provider",
215 "delete": "Tanggalin",
@@ -546,6 +548,7 @@
548 "proceed_after_one_minute": "Kung ang screen ay hindi magpapatuloy pagkatapos ng 1 minuto, suriin ang iyong email.",
549 "proceed_on_device": "Magpatuloy sa iyong hardware wallet",
550 "proceed_on_device_description": "Mangyaring sundin ang mga tagubilin na sinenyasan sa iyong hardware wallet",
551 + "processing": "Pagproseso",
552 "profile": "Profile",
553 "provider_error": "${provider} error",
554 "public_key": "Public key",
res/values/strings_tr.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "Ödeme kartı numaranızın (ve kart numaranıza karşılık gelen kimlik bilgilerinin) bu dijital cüzdanda saklanması ve kullanılması, zaman zaman yürürlükte olan ödeme kartı veren kuruluşla yapılan ilgili kart sahibi sözleşmesinin Hüküm ve Koşullarına tabidir.",
209 "decimal_places_error": "Çok fazla ondalık basamak",
210 "decimals_cannot_be_zero": "Token oncial sıfır olamaz.",
211 + "decred_info_card_details": "Decred, normal bir bitcoin cüzdanından daha uzun süren “SPV” olarak bilinen merkezi olmayan ve gizliliği koruyan bir senkronizasyon yöntemi kullanır. Daha fazla bilgi edinmek için aşağıya dokunun.",
212 + "decred_info_title": "Senkronizasyon Dekred",
213 "default_buy_provider": "Varsayılan Satın Alma Sağlayıcısı",
214 "default_sell_provider": "Varsayılan Satış Sağlayıcısı",
215 "delete": "Sil",
@@ -546,6 +548,7 @@
548 "proceed_after_one_minute": "Ekran 1 dakika sonra ilerlemezse, e-postanızı kontrol edin.",
549 "proceed_on_device": "Cihazınıza devam edin",
550 "proceed_on_device_description": "Lütfen donanım cüzdanınızda istenen talimatları izleyin",
551 + "processing": "İşleme",
552 "profile": "Profil",
553 "provider_error": "${provider} hatası",
554 "public_key": "Genel Anahtar",
res/values/strings_uk.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "Зберігання та використання номера вашої платіжної картки (та облікових даних, які відповідають номеру вашої платіжної картки) у цьому цифровому гаманці регулюються Умовами відповідної угоди власника картки з емітентом платіжної картки, що діє з час від часу.",
209 "decimal_places_error": "Забагато знаків після коми",
210 "decimals_cannot_be_zero": "Десятковий знак не може бути нульовим.",
211 + "decred_info_card_details": "Decred використовує децентралізований метод синхронізації, що зберігає конфіденційність, відомий як \"SPV\", який займає більше часу, ніж звичайний гаманець Bitcoin. Щоб дізнатися більше, торкніться нижче.",
212 + "decred_info_title": "Синхронізація в Decred",
213 "default_buy_provider": "Постачальник покупки за замовчуванням",
214 "default_sell_provider": "Постачальник продажу за замовчуванням",
215 "delete": "Видалити",
@@ -546,6 +548,7 @@
548 "proceed_after_one_minute": "Якщо екран не продовжується через 1 хвилину, перевірте свою електронну пошту.",
549 "proceed_on_device": "Продовжуйте свій пристрій",
550 "proceed_on_device_description": "Будь ласка, дотримуйтесь інструкцій, підказаних на вашому апаратному гаманці",
551 + "processing": "Обробка",
552 "profile": "Профіль",
553 "provider_error": "${provider} помилка",
554 "public_key": "Публічний ключ",
res/values/strings_ur.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "اس ڈیجیٹل والیٹ میں آپ کے ادائیگی کارڈ نمبر (اور آپ کے ادائیگی کارڈ نمبر سے متعلقہ اسناد) کا ذخیرہ اور استعمال ادائیگی کارڈ جاری کنندہ کے ساتھ قابل اطلاق کارڈ ہولڈر کے معاہدے کی شرائط و ضوابط کے ساتھ مشروط ہے، جیسا کہ وقتاً فوقتاً نافذ ہوتا ہے۔",
209 "decimal_places_error": "بہت زیادہ اعشاریہ جگہیں۔",
210 "decimals_cannot_be_zero": "ٹوکن اعشاریہ صفر نہیں ہوسکتا۔",
211 + "decred_info_card_details": "ڈیکریڈ ایک विकेंद्रीकृत اور رازداری سے محفوظ رکھنے والا مطابقت پذیری کا طریقہ استعمال کرتا ہے جسے \"ایس پی وی\" کہا جاتا ہے ، جو عام بٹ کوائن پرس سے زیادہ وقت لگتا ہے۔ مزید جاننے کے لئے ، نیچے ٹیپ کریں۔",
212 + "decred_info_title": "فیصلہ شدہ میں ہم آہنگی",
213 "default_buy_provider": "پہلے سے طے شدہ خریدنے والا",
214 "default_sell_provider": " ﮦﺪﻨﻨﮐ ﻢﮨﺍﺮﻓ ﻞﯿﺳ ﭧﻟﺎﻔﯾﮈ",
215 "delete": "حذف کریں۔",
@@ -548,6 +550,7 @@
550 "proceed_after_one_minute": "اگر اسکرین 1 منٹ کے بعد آگے نہیں بڑھتی ہے تو اپنا ای میل چیک کریں۔",
551 "proceed_on_device": "اپنے آلے پر آگے بڑھیں",
552 "proceed_on_device_description": "براہ کرم اپنے ہارڈ ویئر پرس پر آنے والی ہدایات پر عمل کریں",
553 + "processing": "پروسیسنگ",
554 "profile": "پروفائل",
555 "provider_error": "${provider} خرابی۔",
556 "public_key": "عوامی کلید",
res/values/strings_vi.arb
+3
@@ -207,6 +207,8 @@
207 "debit_card_terms": "Việc lưu trữ và sử dụng số thẻ thanh toán của bạn (và thông tin xác thực tương ứng với số thẻ thanh toán của bạn) trong ví điện tử này phải tuân theo Điều khoản và Điều kiện của thỏa thuận chủ thẻ hiện hành với tổ chức phát hành thẻ thanh toán, theo thời gian.",
208 "decimal_places_error": "Quá nhiều chữ số thập phân",
209 "decimals_cannot_be_zero": "Chữ số thập phân không thể là số không.",
210 + "decred_info_card_details": "Decred sử dụng một phương pháp đồng bộ hóa bảo tồn và bảo tồn quyền riêng tư được gọi là SPV SPV, mất nhiều thời gian hơn ví Bitcoin bình thường. Để tìm hiểu thêm, nhấn bên dưới.",
211 + "decred_info_title": "Đồng bộ hóa trong Decred",
212 "default_buy_provider": "Nhà cung cấp Mua mặc định",
213 "default_sell_provider": "Nhà cung cấp Bán mặc định",
214 "delete": "Xóa",
@@ -544,6 +546,7 @@
546 "proceed_after_one_minute": "Nếu màn hình không tiếp tục sau 1 phút, hãy kiểm tra email của bạn.",
547 "proceed_on_device": "Tiếp tục trên thiết bị của bạn",
548 "proceed_on_device_description": "Vui lòng làm theo các hướng dẫn được nhắc trên ví phần cứng của bạn",
549 + "processing": "Xử lý",
550 "profile": "Hồ sơ",
551 "provider_error": "Lỗi ${provider}",
552 "public_key": "Khóa công khai",
res/values/strings_yo.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "Òfin ti olùṣe àjọrò káàdì ìrajà bójú irú ọ̀nà t'á pamọ́ àti a lo òǹkà ti káàdì ìrajà yín (àti ọ̀rọ̀ ìdánimọ̀ tí káàdì náà) nínú àpamọ́wọ́ yìí.",
209 "decimal_places_error": "Oọ̀rọ̀ ayipada ti o wa ni o dara julọ",
210 "decimals_cannot_be_zero": "Token eleemel ko le jẹ odo.",
211 + "decred_info_card_details": "Devend nlo ọna ti o ni itọju ati ti itọju-itọju-itọju ti o mọ bi \"SPV\", eyiti o gba to gun ju apamọwọ Bitcoin deede. Lati kọ ẹkọ diẹ sii, tẹ ni isalẹ.",
212 + "decred_info_title": "Imuṣiṣẹpọ ni devitered",
213 "default_buy_provider": "Aiyipada Ra Olupese",
214 "default_sell_provider": "Aiyipada Olupese Tita",
215 "delete": "Pa á",
@@ -547,6 +549,7 @@
549 "proceed_after_one_minute": "Tí aṣàfihàn kò bá tẹ̀síwájú l'áàárín ìṣẹ́jú kan, ẹ tọ́ ímeèlì yín wò.",
550 "proceed_on_device": "Tẹsiwaju lori ẹrọ rẹ",
551 "proceed_on_device_description": "Jọwọ tẹle awọn ilana ti a ṣe lori apamọwọ ohun elo rẹ",
552 + "processing": "Iṣaayan",
553 "profile": "profaili",
554 "provider_error": "Àṣìṣe ${provider}",
555 "public_key": "Kọ́kọ́rọ́ tó kò àdáni",
res/values/strings_zh.arb
+3
@@ -208,6 +208,8 @@
208 "debit_card_terms": "您的支付卡号(以及与您的支付卡号对应的凭证)在此数字钱包中的存储和使用受适用的持卡人与支付卡发卡机构签订的协议的条款和条件的约束,自时不时。",
209 "decimal_places_error": "小数位太多",
210 "decimals_cannot_be_zero": "代币十进制不能为零。",
211 + "decred_info_card_details": "DECRED使用称为“ SPV”的分散和隐私的同步方法,该方法比普通的比特币钱包更长。要了解更多信息,请点击下面。",
212 + "decred_info_title": "在Decred中同步",
213 "default_buy_provider": "默认购买提供商",
214 "default_sell_provider": "默认销售提供商",
215 "delete": "删除",
@@ -546,6 +548,7 @@
548 "proceed_after_one_minute": "如果屏幕在 1 分钟后没有继续,请检查您的电子邮件。",
549 "proceed_on_device": "在设备上继续",
550 "proceed_on_device_description": "请按照您的硬件钱包上提示的说明进行操作",
551 + "processing": "加工",
552 "profile": "轮廓",
553 "provider_error": "${provider} 错误",
554 "public_key": "公钥",
scripts/android/.gitignore
+2 -1
@@ -1 +1,2 @@
1 -mwebd
\ No newline at end of file
1 +mwebd
2 +decred
\ No newline at end of file
scripts/android/build_all.sh
+2 -1
@@ -11,6 +11,7 @@ case $APP_ANDROID_TYPE in
11 "monero.com") $DIR/build_monero_all.sh ;;
12 "cakewallet") $DIR/build_monero_all.sh
13 $DIR/build_haven_all.sh
14 - $DIR/build_mwebd.sh ;;
14 + $DIR/build_mwebd.sh
15 + $DIR/build_decred.sh ;;
16 "haven") $DIR/build_haven_all.sh ;;
17 esac
scripts/android/build_decred.sh new
+84
@@ -0,0 +1,84 @@
1 +#!/bin/sh
2 +
3 +set -e
4 +cd "$(dirname "$0")"
5 +# . ./config.sh
6 +
7 +CW_DECRED_DIR=$(realpath ../..)/cw_decred
8 +LIBWALLET_PATH="${PWD}/decred/libwallet"
9 +LIBWALLET_URL="https://github.com/decred/libwallet.git"
10 +LIBWALLET_VERSION="87b2769538db3065b334d247b25774593fc6443d"
11 +
12 +if [ -e $LIBWALLET_PATH ]; then
13 + rm -fr $LIBWALLET_PATH/{*,.*} || true
14 +fi
15 +mkdir -p $LIBWALLET_PATH || true
16 +
17 +git clone $LIBWALLET_URL $LIBWALLET_PATH
18 +cd $LIBWALLET_PATH
19 +git checkout $LIBWALLET_VERSION
20 +
21 +if [[ "x$ANDROID_HOME" == "x" ]];
22 +then
23 + echo "ANDROID_HOME is missing, please declare it before building (on macos it is usually $HOME/Library/Android/sdk)"
24 + echo "echo > ~/.zprofile"
25 + echo "echo 'export ANDROID_HOME=\"\$HOME/Library/Android/sdk\" > ~/.zprofile"
26 + exit 1
27 +fi
28 +
29 +if [[ "x$ANDROID_NDK_VERSION" == "x" ]];
30 +then
31 + echo "ANDROID_NDK_VERSION is missing, please declare it before building"
32 + echo "You have these versions installed on your system currently:"
33 + ls ${ANDROID_HOME}/ndk/ | cat | awk '{ print "- " $1 }'
34 + echo "echo > ~/.zprofile"
35 + echo "echo 'export ANDROID_NDK_CERSION=..... > ~/.zprofile"
36 + exit 1
37 +fi
38 +
39 +export NDK_BIN_PATH="${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}/toolchains/llvm/prebuilt/$(uname | tr '[:upper:]' '[:lower:]')-x86_64/bin"
40 +export ANDROID_API_VERSION=21
41 +# export CPATH="$(clang -v 2>&1 | grep "Selected GCC installation" | rev | cut -d' ' -f1 | rev)/include"
42 +
43 +for arch in "aarch" "aarch64" "x86_64"
44 +do
45 + TRIPLET=""
46 + TARGET=""
47 + ARCH_ABI=""
48 +
49 + case $arch in
50 + "aarch")
51 + TRIPLET="armv7a-linux-androideabi"
52 + TARGET="arm"
53 + ARCH_ABI="armeabi-v7a";;
54 + "aarch64")
55 + TRIPLET="aarch64-linux-android"
56 + TARGET="arm64"
57 + ARCH_ABI="arm64-v8a";;
58 + "x86_64")
59 + TRIPLET="x86_64-linux-android"
60 + TARGET="amd64"
61 + ARCH_ABI="x86_64";;
62 + *)
63 + echo "Unknown arch: $arch"
64 + exit 1;;
65 + esac
66 +
67 + # PATH="${TOOLCHAIN_BASE_DIR}_${arch}/bin:${ORIGINAL_PATH}"
68 + if [ -e ./build ]; then
69 + rm -fr ./build
70 + fi
71 +
72 + CLANG_PATH="${NDK_BIN_PATH}/${TRIPLET}${ANDROID_API_VERSION}-clang"
73 + CGO_ENABLED=1 GOOS=android GOARCH=${TARGET} CC=${CLANG_PATH} CXX=${CLANG_PATH}++ \
74 + go build -v -buildmode=c-shared -o ./build/${TRIPLET}-libdcrwallet.so ./cgo
75 +
76 + DEST_LIB_DIR=${CW_DECRED_DIR}/android/libs/${ARCH_ABI}
77 + mkdir -p $DEST_LIB_DIR
78 + cp ${LIBWALLET_PATH}/build/${TRIPLET}-libdcrwallet.so $DEST_LIB_DIR/libdcrwallet.so
79 +done
80 +
81 +HEADER_DIR=$CW_DECRED_DIR/lib/api
82 +cp ${LIBWALLET_PATH}/build/${TRIPLET}-libdcrwallet.h $HEADER_DIR/libdcrwallet.h
83 +cd $CW_DECRED_DIR
84 +dart run ffigen
scripts/android/pubspec_gen.sh
+2 -2
@@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in
10 CONFIG_ARGS="--monero"
11 ;;
12 $CAKEWALLET)
13 - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano"
13 + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred"
14 if [ "$CW_WITH_HAVEN" = true ];then
15 CONFIG_ARGS="$CONFIG_ARGS --haven"
16 fi
@@ -26,4 +26,4 @@ flutter pub get
26 dart run tool/generate_pubspec.dart
27 flutter pub get
28 dart run tool/configure.dart $CONFIG_ARGS
29 -cd scripts/android
\ No newline at end of file
29 +cd scripts/android
scripts/ios/app_config.sh
+4 -4
@@ -27,18 +27,18 @@ universal_sed "s/PRODUCT_BUNDLE_IDENTIFIER = .*;/PRODUCT_BUNDLE_IDENTIFIER = $AP
27 CONFIG_ARGS=""
28
29 case $APP_IOS_TYPE in
30 - $MONERO_COM)
30 + $MONERO_COM)
31 CONFIG_ARGS="--monero"
32 ;;
33 +
34 $CAKEWALLET)
34 - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano"
35 + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred"
36 if [ "$CW_WITH_HAVEN" = true ];then
37 CONFIG_ARGS="$CONFIG_ARGS --haven"
38 fi
39 ;;
39 - $HAVEN)
40 -
40
41 + $HAVEN)
42 CONFIG_ARGS="--haven"
43 ;;
44 esac
scripts/ios/build_all.sh
+1 -1
@@ -9,6 +9,6 @@ DIR=$(dirname "$0")
9
10 case $APP_IOS_TYPE in
11 "monero.com") $DIR/build_monero_all.sh ;;
12 - "cakewallet") $DIR/build_monero_all.sh && $DIR/build_haven.sh && $DIR/build_mwebd.sh ;;
12 + "cakewallet") $DIR/build_monero_all.sh && $DIR/build_haven.sh && $DIR/build_mwebd.sh && $DIR/build_decred.sh ;;
13 "haven") $DIR/build_haven_all.sh ;;
14 esac
scripts/ios/build_decred.sh new
+35
@@ -0,0 +1,35 @@
1 +#!/bin/sh
2 +set -e
3 +. ./config.sh
4 +LIBWALLET_PATH="${EXTERNAL_IOS_SOURCE_DIR}/libwallet"
5 +LIBWALLET_URL="https://github.com/decred/libwallet.git"
6 +LIBWALLET_VERSION="87b2769538db3065b334d247b25774593fc6443d"
7 +
8 +if [ -e $LIBWALLET_PATH ]; then
9 + rm -fr $LIBWALLET_PATH
10 +fi
11 +mkdir -p $LIBWALLET_PATH
12 +git clone $LIBWALLET_URL $LIBWALLET_PATH
13 +cd $LIBWALLET_PATH
14 +git checkout $LIBWALLET_VERSION
15 +
16 +SYSROOT=`xcrun --sdk iphoneos --show-sdk-path`
17 +CLANG="clang -target arm64-apple-ios -isysroot ${SYSROOT}"
18 +CLANGXX="clang++ -target arm64-apple-ios -isysroot ${SYSROOT}"
19 +
20 +if [ -e ./build ]; then
21 + rm -fr ./build
22 +fi
23 +CGO_ENABLED=1 GOOS=ios GOARCH=arm64 CC=$CLANG CXX=$CLANGXX \
24 +go build -v -buildmode=c-archive -o ./build/libdcrwallet.a ./cgo || exit 1
25 +
26 +CW_DECRED_DIR=${CW_ROOT}/cw_decred
27 +HEADER_DIR=$CW_DECRED_DIR/lib/api
28 +mv ${LIBWALLET_PATH}/build/libdcrwallet.h $HEADER_DIR
29 +
30 +DEST_LIB_DIR=${CW_DECRED_DIR}/ios/External/lib
31 +mkdir -p $DEST_LIB_DIR
32 +mv ${LIBWALLET_PATH}/build/libdcrwallet.a $DEST_LIB_DIR
33 +
34 +cd $CW_DECRED_DIR
35 +dart run ffigen
scripts/ios/build_zmq.sh
+1 -1
@@ -1,4 +1,4 @@
1 -g#!/bin/sh
1 +#!/bin/sh
2
3 . ./config.sh
4
scripts/macos/app_config.sh
+1 -1
@@ -36,7 +36,7 @@ case $APP_MACOS_TYPE in
36 $MONERO_COM)
37 CONFIG_ARGS="--monero";;
38 $CAKEWALLET)
39 - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero";; #--haven
39 + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --decred";; #--haven
40 esac
41
42 cp -rf pubspec_description.yaml pubspec.yaml
scripts/macos/build_all.sh
+1 -1
@@ -1,3 +1,3 @@
1 #!/bin/sh
2
3 -./build_monero_all.sh universal
\ No newline at end of file
3 +./build_monero_all.sh universal && $DIR/build_decred.sh
scripts/macos/build_boost_arm64.sh
+1
@@ -1,4 +1,5 @@
1 #!/bin/sh
2
3 +. ./config.sh
4 . ./build_boost_common.sh
5 build_boost_arm64
\ No newline at end of file
scripts/macos/build_boost_common.sh
+6 -2
@@ -35,6 +35,10 @@ BOOST_B2_LINKFLAGS_X86_64="-arch x86_64"
35 BOOST_B2_BUILD_DIR_X86_64=macos-x86_64
36
37 build_boost_init_common() {
38 + echo "
39 + ============================ BOOST ============================
40 + "
41 +
42 CXXFLAGS=$1
43 CFLAGS=$2
44 LINKFLAGS=$3
@@ -157,8 +161,8 @@ build_boost_compile_universal() {
161 build_boost_install_common() {
162 ARCH=$1
163 LIB_DIR=""
160 - mkdir $EXTERNAL_MACOS_LIB_DIR
161 - mkdir $EXTERNAL_MACOS_INCLUDE_DIR
164 + mkdir -p $EXTERNAL_MACOS_LIB_DIR
165 + mkdir -p $EXTERNAL_MACOS_INCLUDE_DIR
166
167 case $ARCH in
168 arm64) LIB_DIR="${BOOST_B2_BUILD_DIR_ARM_64}/stage/lib";;
scripts/macos/build_decred.sh new
+34
@@ -0,0 +1,34 @@
1 +#!/bin/sh
2 +
3 +. ./config.sh
4 +
5 +LIBWALLET_PATH="${EXTERNAL_MACOS_SOURCE_DIR}/libwallet"
6 +LIBWALLET_URL="https://github.com/decred/libwallet.git"
7 +LIBWALLET_VERSION="87b2769538db3065b334d247b25774593fc6443d"
8 +
9 +echo "======================= DECRED LIBWALLET ========================="
10 +
11 +echo "Cloning DECRED LIBWALLET from - $LIBWALLET_URL"
12 +if [ -e $LIBWALLET_PATH ]; then
13 + rm -fr $LIBWALLET_PATH
14 +fi
15 +mkdir -p $LIBWALLET_PATH
16 +git clone $LIBWALLET_URL $LIBWALLET_PATH
17 +cd $LIBWALLET_PATH
18 +git checkout $LIBWALLET_VERSION
19 +
20 +if [ -e ./build ]; then
21 + rm -fr ./build
22 +fi
23 +go build -buildmode=c-archive -o ./build/libdcrwallet.a ./cgo
24 +
25 +CW_DECRED_DIR=${CW_ROOT}/cw_decred
26 +HEADER_DIR=$CW_DECRED_DIR/lib/api
27 +mv ${LIBWALLET_PATH}/build/libdcrwallet.h $HEADER_DIR
28 +
29 +DEST_LIB_DIR=${CW_DECRED_DIR}/macos/External/lib
30 +mkdir -p $DEST_LIB_DIR
31 +mv ${LIBWALLET_PATH}/build/libdcrwallet.a $DEST_LIB_DIR
32 +
33 +cd $CW_DECRED_DIR
34 +dart run ffigen
scripts/macos/build_expat.sh
+4
@@ -6,6 +6,10 @@ EXPAT_VERSION=R_2_4_8
6 EXPAT_HASH="3bab6c09bbe8bf42d84b81563ddbcf4cca4be838"
7 EXPAT_SRC_DIR=${EXTERNAL_MACOS_SOURCE_DIR}/libexpat
8
9 +echo "
10 +============================ EXPAT ============================
11 +"
12 +
13 git clone https://github.com/libexpat/libexpat.git -b ${EXPAT_VERSION} ${EXPAT_SRC_DIR}
14 cd $EXPAT_SRC_DIR
15 test `git rev-parse HEAD` = ${EXPAT_HASH} || exit 1
scripts/macos/build_monero.sh
+4
@@ -11,6 +11,10 @@ DEST_LIB_DIR=${EXTERNAL_MACOS_LIB_DIR}/monero
11 DEST_INCLUDE_DIR=${EXTERNAL_MACOS_INCLUDE_DIR}/monero
12 ARCH=`uname -m`
13
14 +echo "
15 +============================ MONERO ============================
16 +"
17 +
18 echo "Cloning monero from - $MONERO_URL to - $MONERO_DIR_PATH"
19 git clone $MONERO_URL $MONERO_DIR_PATH
20 cd $MONERO_DIR_PATH
scripts/macos/build_openssl_arm64.sh
+1
@@ -1,4 +1,5 @@
1 #!/bin/sh
2
3 +. ./config.sh
4 . ./build_openssl_common.sh
5 build_openssl_arm64
\ No newline at end of file
scripts/macos/build_openssl_common.sh
+3 -1
@@ -13,7 +13,9 @@ build_openssl_init_common() {
13 # Use 1.1.1s because of https://github.com/openssl/openssl/issues/18720
14 OPENSSL_VERSION="1.1.1s"
15
16 - echo "============================ OpenSSL ============================"
16 + echo "
17 + ============================ OPENSSL ============================
18 + "
19
20 cd $EXTERNAL_MACOS_SOURCE_DIR
21 curl -O https://www.openssl.org/source/openssl-$OPENSSL_VERSION.tar.gz
scripts/macos/build_sodium.sh
+3 -1
@@ -5,7 +5,9 @@
5 SODIUM_PATH="${EXTERNAL_MACOS_SOURCE_DIR}/libsodium"
6 SODIUM_URL="https://github.com/jedisct1/libsodium.git"
7
8 -echo "============================ SODIUM ============================"
8 +echo "
9 +============================ SODIUM ============================
10 +"
11
12 echo "Cloning SODIUM from - $SODIUM_URL"
13 git clone $SODIUM_URL $SODIUM_PATH --branch stable
scripts/macos/build_unbound.sh
+4 -1
@@ -7,7 +7,10 @@ UNBOUND_HASH="cbed768b8ff9bfcf11089a5f1699b7e5707f1ea5"
7 UNBOUND_URL="https://www.nlnetlabs.nl/downloads/unbound/unbound-${UNBOUND_VERSION}.tar.gz"
8 UNBOUND_DIR_PATH="${EXTERNAL_MACOS_SOURCE_DIR}/unbound-1.16.2"
9
10 -echo "============================ Unbound ============================"
10 +echo "
11 +============================ Unbound ============================
12 +"
13 +
14 rm -rf ${UNBOUND_DIR_PATH}
15 git clone https://github.com/NLnetLabs/unbound.git -b ${UNBOUND_VERSION} ${UNBOUND_DIR_PATH}
16 cd $UNBOUND_DIR_PATH
scripts/macos/build_zmq.sh
+3 -1
@@ -5,7 +5,9 @@
5 ZMQ_PATH="${EXTERNAL_MACOS_SOURCE_DIR}/libzmq"
6 ZMQ_URL="https://github.com/zeromq/libzmq.git"
7
8 -echo "============================ ZMQ ============================"
8 +echo "
9 +============================ ZMQ ============================
10 +"
11
12 echo "Cloning ZMQ from - $ZMQ_URL"
13 git clone $ZMQ_URL $ZMQ_PATH
scripts/macos/gen.sh new
+6
@@ -0,0 +1,6 @@
1 +#!/bin/bash
2 +
3 +. ./gen_common.sh
4 +
5 +ARCH=`uname -m`
6 +gen $ARCH
\ No newline at end of file
scripts/macos/gen_common.sh
+3 -1
@@ -15,6 +15,8 @@ gen_podspec() {
15 gen_project() {
16 ARCH=$1
17 CW_DIR="`pwd`/../../macos/Runner.xcodeproj"
18 + BASE_FILENAME="project_base.pbxproj"
19 + BASE_FILE_PATH="${CW_DIR}/${BASE_FILENAME}"
20 DEFAULT_FILENAME="project.pbxproj"
21 DEFAULT_FILE_PATH="${CW_DIR}/${DEFAULT_FILENAME}"
22 universal_sed "s/ARCHS =.*/ARCHS = \"${ARCH}\";/g" $DEFAULT_FILE_PATH
@@ -24,4 +26,4 @@ gen() {
26 ARCH=$1
27 gen_podspec "${ARCH}"
28 gen_project "${ARCH}"
27 -}
\ No newline at end of file
29 +}
tool/configure.dart
+105 -3
@@ -11,6 +11,7 @@ const solanaOutputPath = 'lib/solana/solana.dart';
11 const tronOutputPath = 'lib/tron/tron.dart';
12 const wowneroOutputPath = 'lib/wownero/wownero.dart';
13 const zanoOutputPath = 'lib/zano/zano.dart';
14 +const decredOutputPath = 'lib/decred/decred.dart';
15 const walletTypesPath = 'lib/wallet_types.g.dart';
16 const secureStoragePath = 'lib/core/secure_storage.dart';
17 const pubspecDefaultPath = 'pubspec_default.yaml';
@@ -30,6 +31,7 @@ Future<void> main(List<String> args) async {
31 final hasTron = args.contains('${prefix}tron');
32 final hasWownero = args.contains('${prefix}wownero');
33 final hasZano = args.contains('${prefix}zano');
34 + final hasDecred = args.contains('${prefix}decred');
35 final excludeFlutterSecureStorage = args.contains('${prefix}excludeFlutterSecureStorage');
36
37 await generateBitcoin(hasBitcoin);
@@ -44,6 +46,7 @@ Future<void> main(List<String> args) async {
46 await generateWownero(hasWownero);
47 await generateZano(hasZano);
48 // await generateBanano(hasEthereum);
49 + await generateDecred(hasDecred);
50
51 await generatePubspec(
52 hasMonero: hasMonero,
@@ -59,6 +62,7 @@ Future<void> main(List<String> args) async {
62 hasTron: hasTron,
63 hasWownero: hasWownero,
64 hasZano: hasZano,
65 + hasDecred: hasDecred,
66 );
67 await generateWalletTypes(
68 hasMonero: hasMonero,
@@ -73,6 +77,7 @@ Future<void> main(List<String> args) async {
77 hasTron: hasTron,
78 hasWownero: hasWownero,
79 hasZano: hasZano,
80 + hasDecred: hasDecred,
81 );
82 await injectSecureStorage(!excludeFlutterSecureStorage);
83 }
@@ -1093,12 +1098,15 @@ abstract class BitcoinCash {
1098 """;
1099
1100 const bitcoinCashEmptyDefinition = 'BitcoinCash? bitcoinCash;\n';
1096 - const bitcoinCashCWDefinition = 'BitcoinCash? bitcoinCash = CWBitcoinCash();\n';
1101 + const bitcoinCashCWDefinition =
1102 + 'BitcoinCash? bitcoinCash = CWBitcoinCash();\n';
1103
1104 final output = '$bitcoinCashCommonHeaders\n' +
1105 (hasImplementation ? '$bitcoinCashCWHeaders\n' : '\n') +
1106 (hasImplementation ? '$bitcoinCashCwPart\n\n' : '\n') +
1101 - (hasImplementation ? bitcoinCashCWDefinition : bitcoinCashEmptyDefinition) +
1107 + (hasImplementation
1108 + ? bitcoinCashCWDefinition
1109 + : bitcoinCashEmptyDefinition) +
1110 '\n' +
1111 bitcoinCashContent;
1112
@@ -1233,7 +1241,8 @@ abstract class NanoUtil {
1241 """;
1242
1243 const nanoEmptyDefinition = 'Nano? nano;\nNanoUtil? nanoUtil;\n';
1236 - const nanoCWDefinition = 'Nano? nano = CWNano();\nNanoUtil? nanoUtil = CWNanoUtil();\n';
1244 + const nanoCWDefinition =
1245 + 'Nano? nano = CWNano();\nNanoUtil? nanoUtil = CWNanoUtil();\n';
1246
1247 final output = '$nanoCommonHeaders\n' +
1248 (hasImplementation ? '$nanoCWHeaders\n' : '\n') +
@@ -1482,6 +1491,85 @@ abstract class Zano {
1491 await outputFile.writeAsString(output);
1492 }
1493
1494 +Future<void> generateDecred(bool hasImplementation) async {
1495 + final outputFile = File(decredOutputPath);
1496 + const decredCommonHeaders = """
1497 +import 'package:cw_core/wallet_credentials.dart';
1498 +import 'package:cw_core/address_info.dart';
1499 +import 'package:cw_core/wallet_info.dart';
1500 +import 'package:cw_core/transaction_priority.dart';
1501 +import 'package:cw_core/output_info.dart';
1502 +import 'package:cw_core/wallet_service.dart';
1503 +import 'package:cw_core/unspent_transaction_output.dart';
1504 +import 'package:cw_core/unspent_coins_info.dart';
1505 +import 'package:cake_wallet/view_model/send/output.dart';
1506 +import 'package:hive/hive.dart';
1507 +""";
1508 + const decredCWHeaders = """
1509 +import 'package:cw_decred/transaction_priority.dart';
1510 +import 'package:cw_decred/wallet.dart';
1511 +import 'package:cw_decred/wallet_service.dart';
1512 +import 'package:cw_decred/wallet_creation_credentials.dart';
1513 +import 'package:cw_decred/amount_format.dart';
1514 +import 'package:cw_decred/transaction_credentials.dart';
1515 +import 'package:cw_decred/mnemonic.dart';
1516 +""";
1517 + const decredCwPart = "part 'cw_decred.dart';";
1518 + const decredContent = """
1519 +
1520 +abstract class Decred {
1521 + WalletCredentials createDecredNewWalletCredentials(
1522 + {required String name, WalletInfo? walletInfo});
1523 + WalletCredentials createDecredRestoreWalletFromSeedCredentials(
1524 + {required String name, required String mnemonic, required String password});
1525 + WalletCredentials createDecredRestoreWalletFromPubkeyCredentials(
1526 + {required String name, required String pubkey, required String password});
1527 + WalletService createDecredWalletService(
1528 + Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource);
1529 +
1530 + List<TransactionPriority> getTransactionPriorities();
1531 + TransactionPriority getDecredTransactionPriorityMedium();
1532 + TransactionPriority getDecredTransactionPrioritySlow();
1533 + TransactionPriority deserializeDecredTransactionPriority(int raw);
1534 +
1535 + Object createDecredTransactionCredentials(List<Output> outputs, TransactionPriority priority);
1536 +
1537 + List<AddressInfo> getAddressInfos(Object wallet);
1538 + Future<void> updateAddress(Object wallet, String address, String label);
1539 + Future<void> generateNewAddress(Object wallet, String label);
1540 +
1541 + String formatterDecredAmountToString({required int amount});
1542 + double formatterDecredAmountToDouble({required int amount});
1543 + int formatterStringDoubleToDecredAmount(String amount);
1544 +
1545 + List<Unspent> getUnspents(Object wallet);
1546 + void updateUnspents(Object wallet);
1547 +
1548 + int heightByDate(DateTime date);
1549 +
1550 + List<String> getDecredWordList();
1551 +
1552 + String pubkey(Object wallet);
1553 +}
1554 +""";
1555 +
1556 + const decredEmptyDefinition = 'Decred? decred;\n';
1557 + const decredCWDefinition = 'Decred? decred = CWDecred();\n';
1558 +
1559 + final output = '$decredCommonHeaders\n' +
1560 + (hasImplementation ? '$decredCWHeaders\n' : '\n') +
1561 + (hasImplementation ? '$decredCwPart\n\n' : '\n') +
1562 + (hasImplementation ? decredCWDefinition : decredEmptyDefinition) +
1563 + '\n' +
1564 + decredContent;
1565 +
1566 + if (outputFile.existsSync()) {
1567 + await outputFile.delete();
1568 + }
1569 +
1570 + await outputFile.writeAsString(output);
1571 +}
1572 +
1573 Future<void> generatePubspec({
1574 required bool hasMonero,
1575 required bool hasBitcoin,
@@ -1496,6 +1584,7 @@ Future<void> generatePubspec({
1584 required bool hasTron,
1585 required bool hasWownero,
1586 required bool hasZano,
1587 + required bool hasDecred,
1588 }) async {
1589 const cwCore = """
1590 cw_core:
@@ -1564,6 +1653,10 @@ Future<void> generatePubspec({
1653 cw_zano:
1654 path: ./cw_zano
1655 """;
1656 + const cwDecred = """
1657 + cw_decred:
1658 + path: ./cw_decred
1659 + """;
1660 final inputFile = File(pubspecOutputPath);
1661 final inputText = await inputFile.readAsString();
1662 final inputLines = inputText.split('\n');
@@ -1613,6 +1706,10 @@ Future<void> generatePubspec({
1706 output += '\n$cwSharedExternal\n$cwHaven';
1707 }
1708
1709 + if (hasDecred) {
1710 + output += '\n$cwDecred';
1711 + }
1712 +
1713 if (hasFlutterSecureStorage) {
1714 output += '\n$flutterSecureStorage\n';
1715 }
@@ -1654,6 +1751,7 @@ Future<void> generateWalletTypes({
1751 required bool hasTron,
1752 required bool hasWownero,
1753 required bool hasZano,
1754 + required bool hasDecred,
1755 }) async {
1756 final walletTypesFile = File(walletTypesPath);
1757
@@ -1709,6 +1807,10 @@ Future<void> generateWalletTypes({
1807 outputContent += '\tWalletType.banano,\n';
1808 }
1809
1810 + if (hasDecred) {
1811 + outputContent += '\tWalletType.decred,\n';
1812 + }
1813 +
1814 if (hasWownero) {
1815 outputContent += '\tWalletType.wownero,\n';
1816 }