mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 12:29:51 +00:00
Add decred (#1938)
* decred: Add decred. (#1322) * multi: Add initial decred screens. (#1165) Use a mock libwallet for now. * cw_decred: add libdcrwallet dependency and link library for android, ios and macos (#1240) * change cw_decred from package to plugin * add libdcrwallet dependency and link library for android, ios and macos * remove spvwallet, make some libdcrwallet fns async, light refactor * libdcrwallet: use json payload returns * use specific libwallet commit hash * decred: fix Rename wallet. --------- Co-authored-by: JoeGruff <joegruffins@gmail.com> * decred: Add sync. * decred: Add send transaction. * decred: Fix fee estimation. * decred: List transactions. * decred: Add rescan. * decred: Sign message. * decred: Add new addr and addrs. * decred: Add change wallet pass. * decred: Add restore from seed. * decred: Add watching only wallets. * decred: Enable mainnet. * decred: Allow using blank node address. This allows a persistent peer to be unset, falling back to decred seeders. * decred: Rescan from wallet birthday. * add and update macos build scripts, update build readme, gitignore macos project.pbxproj Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com> * multi: hide decred rescan page if it's not ready - move hasRescan method to WalletBase and implement for decred Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com> * cw_decred: fix bug where decred wallets are not loaded after app restart Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com> * add buy and sell for decred via onramp Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com> * bug-fix: account for other send outputs that are part of the same tx Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com> * decred: Return address with no peers. * decred: Update pubspec. * decred: Add verify message. * upgrade hive_generator dep in cw_decred * decred: Clean up code. --------- Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com> Co-authored-by: Wisdom Arerosuoghene <wisdom.arerosuoghene@gmail.com> Co-authored-by: Philemon Ukane <ukanephilemon@gmail.com> * fix extracted addresses not used fix conflicts with main * remove print [skip ci] * minor formatting * fix initial migration version * add build decred script to workflow * install go before build decred fix switch cases * trial 2 to fix decred build * re-install go * revert build script change * refactor/clean nodes functions * Fix address book issue Fix send ALL (to be continued with the fees point) * Fix transactions display issues Add missing file * Fix unconfirmed balance not displayed Change Wallet order Minor cleanup * Fix workflow * Fix workflow * Fix workflow * test * hardcode path for now * fix + cleanup decred build script to work on mac and linux * Update decred build script * Run actions on pull requests, extract commit message * run after checkout * add safe directory * Get commit message from base.sha instead of last commit * base -> head * Do not merge main branch into pr * [skip slack] [run tests] clone by sha * Proper name for decred library in the build script * Throw an error when ANDROID_HOME or ANDROID_NDK_VERSION is missing * Fix conflicts with main * minor code enhancement * decred: Add used address history. (#1941) * decred: Update pubspec. * decred testnet * decred: Add used address history. * decred: Remove default node list. * populate transaction history before sync begins * decred: Add some awaits. * decred: Fix send all. * decred: Add clang export to build script. * decred: Update logo colors. * cleanup cw_decred.dart * make decred wallet addresses selectable in receive page * decred: Always set default addr when used. * decred: Add back default node list. * decred: Allow creating addresses manually. --------- Co-authored-by: Wisdom Arerosuoghene <wisdom.arerosuoghene@gmail.com> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * minor fixes and cleanup * minor fix, feel free to test now * - Fix transaction details - Fix Nodes - Add processing sync status * Add decred info card * push missing file * Add missing text for decred info card * minor: change docs link [skip ci] * decred: Update derivation info. (#2013) * decred: Update derivation info. * decred: Allow unsynced unused addresses. * decred: Update dcrwallet dep to 4.3.0. * Merge main and fix conflicts * Merge main and fix conflicts * decred: Fix background sync panic. (#2080) * decred: Run libwallet in isolate. (#2077) * decred: Fix contact save inquiry. (#2083) Also fix tx time and the fee shown on pending transactions. * Disable send button in view only decred wallets * - Fix frozen coins - Add URI support - Fix fees in tx details - Handle empty coins send - Handle wallets in address book * Merge main * remove print [skip ci] * Fix restore from QR * minor improvement for QR restore * minor fixes [skip ci] * decred: Get slip44 addrs before sync completes. (#2092) * - Fix loading wallet more than one time - Fix minor UI issue --------- Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com> Co-authored-by: JoeGruffins <34998433+JoeGruffins@users.noreply.github.com> Co-authored-by: Wisdom Arerosuoghene <wisdom.arerosuoghene@gmail.com> Co-authored-by: Philemon Ukane <ukanephilemon@gmail.com> Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
This commit is contained in:
parent
52a39e29d4
commit
0ba54fa602
175 changed files with 7145 additions and 1115 deletions
39
cw_decred/.gitignore
vendored
Normal file
39
cw_decred/.gitignore
vendored
Normal file
|
@ -0,0 +1,39 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# IntelliJ related
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
.idea/
|
||||
|
||||
# The .vscode folder contains launch configuration and tasks you configure in
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
||||
|
||||
android/.externalNativeBuild/
|
||||
android/.cxx/
|
||||
android/libs
|
||||
ios/External/
|
||||
macos/External/
|
||||
|
||||
*libdcrwallet.h
|
||||
libdcrwallet_bindings.dart
|
36
cw_decred/.metadata
Normal file
36
cw_decred/.metadata
Normal file
|
@ -0,0 +1,36 @@
|
|||
# This file tracks properties of this Flutter project.
|
||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||
#
|
||||
# This file should be version controlled.
|
||||
|
||||
version:
|
||||
revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
channel: unknown
|
||||
|
||||
project_type: plugin
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
- platform: android
|
||||
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
- platform: ios
|
||||
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
- platform: macos
|
||||
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
3
cw_decred/CHANGELOG.md
Normal file
3
cw_decred/CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## [0.0.1] - TODO: Add release date.
|
||||
|
||||
* TODO: Describe initial release.
|
1
cw_decred/LICENSE
Normal file
1
cw_decred/LICENSE
Normal file
|
@ -0,0 +1 @@
|
|||
TODO: Add your license here.
|
3
cw_decred/README.md
Normal file
3
cw_decred/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# cw_decred
|
||||
|
||||
TODO: Fill this out.
|
4
cw_decred/analysis_options.yaml
Normal file
4
cw_decred/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
9
cw_decred/android/.gitignore
vendored
Normal file
9
cw_decred/android/.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.cxx
|
59
cw_decred/android/build.gradle
Normal file
59
cw_decred/android/build.gradle
Normal file
|
@ -0,0 +1,59 @@
|
|||
group 'com.cakewallet.cw_decred'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '2.0.21'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.7.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
|
||||
if (project.android.hasProperty("namespace")) {
|
||||
namespace 'com.cakewallet.cw_decred'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '17'
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDirs += 'src/main/kotlin'
|
||||
jniLibs.srcDirs 'libs' // contains libdcrwallet.so shared libraries
|
||||
}
|
||||
}
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
}
|
1
cw_decred/android/settings.gradle
Normal file
1
cw_decred/android/settings.gradle
Normal file
|
@ -0,0 +1 @@
|
|||
rootProject.name = 'cw_decred'
|
4
cw_decred/android/src/main/AndroidManifest.xml
Normal file
4
cw_decred/android/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.cakewallet.cw_decred">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
|
@ -0,0 +1,35 @@
|
|||
package com.cakewallet.cw_decred
|
||||
|
||||
import androidx.annotation.NonNull
|
||||
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||
import io.flutter.plugin.common.MethodChannel.Result
|
||||
|
||||
/** CwDecredPlugin */
|
||||
class CwDecredPlugin: FlutterPlugin, MethodCallHandler {
|
||||
/// The MethodChannel that will the communication between Flutter and native Android
|
||||
///
|
||||
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
|
||||
/// when the Flutter Engine is detached from the Activity
|
||||
private lateinit var channel : MethodChannel
|
||||
|
||||
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "cw_decred")
|
||||
channel.setMethodCallHandler(this)
|
||||
}
|
||||
|
||||
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
|
||||
if (call.method == "getPlatformVersion") {
|
||||
result.success("Android ${android.os.Build.VERSION.RELEASE}")
|
||||
} else {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
channel.setMethodCallHandler(null)
|
||||
}
|
||||
}
|
38
cw_decred/ios/.gitignore
vendored
Normal file
38
cw_decred/ios/.gitignore
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
.idea/
|
||||
.vagrant/
|
||||
.sconsign.dblite
|
||||
.svn/
|
||||
|
||||
.DS_Store
|
||||
*.swp
|
||||
profile
|
||||
|
||||
DerivedData/
|
||||
build/
|
||||
GeneratedPluginRegistrant.h
|
||||
GeneratedPluginRegistrant.m
|
||||
|
||||
.generated/
|
||||
|
||||
*.pbxuser
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.perspectivev3
|
||||
|
||||
!default.pbxuser
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.perspectivev3
|
||||
|
||||
xcuserdata
|
||||
|
||||
*.moved-aside
|
||||
|
||||
*.pyc
|
||||
*sync/
|
||||
Icon?
|
||||
.tags*
|
||||
|
||||
/Flutter/Generated.xcconfig
|
||||
/Flutter/ephemeral/
|
||||
/Flutter/flutter_export_environment.sh
|
0
cw_decred/ios/Assets/.gitkeep
Normal file
0
cw_decred/ios/Assets/.gitkeep
Normal file
19
cw_decred/ios/Classes/CwDecredPlugin.swift
Normal file
19
cw_decred/ios/Classes/CwDecredPlugin.swift
Normal file
|
@ -0,0 +1,19 @@
|
|||
import Flutter
|
||||
import UIKit
|
||||
|
||||
public class CwDecredPlugin: NSObject, FlutterPlugin {
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let channel = FlutterMethodChannel(name: "cw_decred", binaryMessenger: registrar.messenger())
|
||||
let instance = CwDecredPlugin()
|
||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||
}
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
switch call.method {
|
||||
case "getPlatformVersion":
|
||||
result("iOS " + UIDevice.current.systemVersion)
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
}
|
22
cw_decred/ios/cw_decred.podspec
Normal file
22
cw_decred/ios/cw_decred.podspec
Normal file
|
@ -0,0 +1,22 @@
|
|||
#
|
||||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
|
||||
# Run `pod lib lint cw_decred.podspec` to validate before publishing.
|
||||
#
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'cw_decred'
|
||||
s.version = '0.0.1'
|
||||
s.summary = 'Cake Wallet Decred'
|
||||
s.description = 'Cake Wallet wrapper over Decred project'
|
||||
s.homepage = 'http://cakewallet.com'
|
||||
s.license = { :file => '../LICENSE' }
|
||||
s.author = { 'Cake Wallet' => 'support@cakewallet.com' }
|
||||
s.source = { :path => '.' }
|
||||
s.source_files = 'Classes/**/*'
|
||||
s.dependency 'Flutter'
|
||||
s.platform = :ios, '11.0'
|
||||
|
||||
s.vendored_libraries = 'External/lib/libdcrwallet.a'
|
||||
# Flutter.framework does not contain a i386 slice.
|
||||
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', "OTHER_LDFLAGS" => "-force_load $(PODS_TARGET_SRCROOT)/External/lib/libdcrwallet.a -lstdc++" }
|
||||
s.swift_version = '5.0'
|
||||
end
|
26
cw_decred/lib/amount_format.dart
Normal file
26
cw_decred/lib/amount_format.dart
Normal file
|
@ -0,0 +1,26 @@
|
|||
import 'package:intl/intl.dart';
|
||||
import 'package:cw_core/crypto_amount_format.dart';
|
||||
|
||||
const decredAmountLength = 8;
|
||||
const decredAmountDivider = 100000000;
|
||||
final decredAmountFormat = NumberFormat()
|
||||
..maximumFractionDigits = decredAmountLength
|
||||
..minimumFractionDigits = 1;
|
||||
|
||||
String decredAmountToString({required int amount}) =>
|
||||
decredAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: decredAmountDivider));
|
||||
|
||||
double decredAmountToDouble({required int amount}) =>
|
||||
cryptoAmountToDouble(amount: amount, divider: decredAmountDivider);
|
||||
|
||||
int stringDoubleToDecredAmount(String amount) {
|
||||
int result = 0;
|
||||
|
||||
try {
|
||||
result = (double.parse(amount) * decredAmountDivider).round();
|
||||
} catch (e) {
|
||||
result = 0;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
693
cw_decred/lib/api/libdcrwallet.dart
Normal file
693
cw_decred/lib/api/libdcrwallet.dart
Normal file
|
@ -0,0 +1,693 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
import 'dart:isolate';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_decred/api/libdcrwallet_bindings.dart';
|
||||
import 'package:cw_decred/api/util.dart';
|
||||
|
||||
final int ErrCodeNotSynced = 1;
|
||||
|
||||
final String libraryName = Platform.isAndroid || Platform.isLinux // TODO: Linux.
|
||||
? 'libdcrwallet.so'
|
||||
: 'cw_decred.framework/cw_decred';
|
||||
|
||||
class Libwallet {
|
||||
final SendPort _commands;
|
||||
final ReceivePort _responses;
|
||||
final Map<int, Completer<Object?>> _activeRequests = {};
|
||||
int _idCounter = 0;
|
||||
bool _closed = false;
|
||||
|
||||
static Future<Libwallet> spawn() async {
|
||||
// Create a receive port and add its initial message handler.
|
||||
final initPort = RawReceivePort();
|
||||
final connection = Completer<(ReceivePort, SendPort)>.sync();
|
||||
initPort.handler = (initialMessage) {
|
||||
final commandPort = initialMessage as SendPort;
|
||||
connection.complete((
|
||||
ReceivePort.fromRawReceivePort(initPort),
|
||||
commandPort,
|
||||
));
|
||||
};
|
||||
// Spawn the isolate.
|
||||
try {
|
||||
await Isolate.spawn(_startRemoteIsolate, (initPort.sendPort));
|
||||
} on Object {
|
||||
initPort.close();
|
||||
rethrow;
|
||||
}
|
||||
|
||||
final (ReceivePort receivePort, SendPort sendPort) = await connection.future;
|
||||
|
||||
return Libwallet._(receivePort, sendPort);
|
||||
}
|
||||
|
||||
Libwallet._(this._responses, this._commands) {
|
||||
_responses.listen(_handleResponsesFromIsolate);
|
||||
}
|
||||
|
||||
void _handleResponsesFromIsolate(dynamic message) {
|
||||
final (int id, Object? response) = message as (int, Object?);
|
||||
final completer = _activeRequests.remove(id)!;
|
||||
|
||||
if (response is RemoteError) {
|
||||
completer.completeError(response);
|
||||
} else {
|
||||
completer.complete(response);
|
||||
}
|
||||
|
||||
if (_closed && _activeRequests.isEmpty) _responses.close();
|
||||
}
|
||||
|
||||
static void _handleCommandsToIsolate(
|
||||
ReceivePort receivePort,
|
||||
SendPort sendPort,
|
||||
) {
|
||||
final dcrwalletApi = libdcrwallet(DynamicLibrary.open(libraryName));
|
||||
receivePort.listen((message) {
|
||||
if (message == 'shutdown') {
|
||||
receivePort.close();
|
||||
return;
|
||||
}
|
||||
final (int id, Map<String, String> args) = message as (int, Map<String, String>);
|
||||
var res = PayloadResult("", "", 0);
|
||||
final method = args["method"] ?? "";
|
||||
try {
|
||||
switch (method) {
|
||||
case "initlibdcrwallet":
|
||||
final logDir = args["logdir"] ?? "";
|
||||
final cLogDir = logDir.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.initialize(cLogDir),
|
||||
ptrsToFree: [cLogDir],
|
||||
);
|
||||
break;
|
||||
case "createwallet":
|
||||
final config = args["config"] ?? "";
|
||||
final cConfig = config.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.createWallet(cConfig),
|
||||
ptrsToFree: [cConfig],
|
||||
);
|
||||
break;
|
||||
case "createwatchonlywallet":
|
||||
final config = args["config"] ?? "";
|
||||
final cConfig = config.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.createWatchOnlyWallet(cConfig),
|
||||
ptrsToFree: [cConfig],
|
||||
);
|
||||
break;
|
||||
case "loadwallet":
|
||||
final config = args["config"] ?? "";
|
||||
final cConfig = config.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.loadWallet(cConfig),
|
||||
ptrsToFree: [cConfig],
|
||||
);
|
||||
break;
|
||||
case "startsync":
|
||||
final name = args["name"] ?? "";
|
||||
final peers = args["peers"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cPeers = peers.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.syncWallet(cName, cPeers),
|
||||
ptrsToFree: [cName, cPeers],
|
||||
);
|
||||
break;
|
||||
case "closewallet":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.closeWallet(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "changewalletpassword":
|
||||
final name = args["name"] ?? "";
|
||||
final oldPass = args["oldpass"] ?? "";
|
||||
final newPass = args["newpass"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cOldPass = oldPass.toCString();
|
||||
final cNewPass = newPass.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.changePassphrase(cName, cOldPass, cNewPass),
|
||||
ptrsToFree: [cName, cOldPass, cNewPass],
|
||||
);
|
||||
break;
|
||||
case "walletseed":
|
||||
final name = args["name"] ?? "";
|
||||
final pass = args["pass"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cPass = pass.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.walletSeed(cName, cPass),
|
||||
ptrsToFree: [cName, cPass],
|
||||
);
|
||||
break;
|
||||
case "syncstatus":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.syncWalletStatus(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "balance":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.walletBalance(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "estimatefee":
|
||||
final name = args["name"] ?? "";
|
||||
final numBlocks = args["numblocks"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cNumBlocks = numBlocks.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.estimateFee(cName, cNumBlocks),
|
||||
ptrsToFree: [cName, cNumBlocks],
|
||||
);
|
||||
break;
|
||||
case "createsignedtransaction":
|
||||
final name = args["name"] ?? "";
|
||||
final signReq = args["signreq"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cSignReq = signReq.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.createSignedTransaction(cName, cSignReq),
|
||||
ptrsToFree: [cName, cSignReq],
|
||||
);
|
||||
break;
|
||||
case "sendrawtransaction":
|
||||
final name = args["name"] ?? "";
|
||||
final txHex = args["txhex"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cTxHex = txHex.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.sendRawTransaction(cName, cTxHex),
|
||||
ptrsToFree: [cName, cTxHex],
|
||||
);
|
||||
break;
|
||||
case "listtransactions":
|
||||
final name = args["name"] ?? "";
|
||||
final from = args["from"] ?? "";
|
||||
final count = args["count"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cFrom = from.toCString();
|
||||
final cCount = count.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.listTransactions(cName, cFrom, cCount),
|
||||
ptrsToFree: [cName, cFrom, cCount],
|
||||
);
|
||||
break;
|
||||
case "bestblock":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.bestBlock(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "listunspents":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.listUnspents(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "rescanfromheight":
|
||||
final name = args["name"] ?? "";
|
||||
final height = args["height"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cHeight = height.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.rescanFromHeight(cName, cHeight),
|
||||
ptrsToFree: [cName, cHeight],
|
||||
);
|
||||
break;
|
||||
case "signmessage":
|
||||
final name = args["name"] ?? "";
|
||||
final message = args["message"] ?? "";
|
||||
final address = args["address"] ?? "";
|
||||
final pass = args["pass"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cMessage = message.toCString();
|
||||
final cAddress = address.toCString();
|
||||
final cPass = pass.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.signMessage(cName, cMessage, cAddress, cPass),
|
||||
ptrsToFree: [cName, cMessage, cAddress, cPass],
|
||||
);
|
||||
break;
|
||||
case "verifymessage":
|
||||
final name = args["name"] ?? "";
|
||||
final message = args["message"] ?? "";
|
||||
final address = args["address"] ?? "";
|
||||
final sig = args["sig"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cMessage = message.toCString();
|
||||
final cAddress = address.toCString();
|
||||
final cSig = sig.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.verifyMessage(cName, cMessage, cAddress, cSig),
|
||||
ptrsToFree: [cName, cMessage, cAddress, cSig],
|
||||
);
|
||||
break;
|
||||
case "newexternaladdress":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.newExternalAddress(cName),
|
||||
ptrsToFree: [cName],
|
||||
skipErrorCheck: true,
|
||||
);
|
||||
break;
|
||||
case "defaultpubkey":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.defaultPubkey(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "addresses":
|
||||
final name = args["name"] ?? "";
|
||||
final nUsed = args["nused"] ?? "";
|
||||
final nUnused = args["nunused"] ?? "";
|
||||
final cName = name.toCString();
|
||||
final cNUsed = nUsed.toCString();
|
||||
final cNUnused = nUnused.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.addresses(cName, cNUsed, cNUnused),
|
||||
ptrsToFree: [cName, cNUsed, cNUnused],
|
||||
);
|
||||
break;
|
||||
case "birthstate":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
res = executePayloadFn(
|
||||
fn: () => dcrwalletApi.birthState(cName),
|
||||
ptrsToFree: [cName],
|
||||
);
|
||||
break;
|
||||
case "shutdown":
|
||||
final name = args["name"] ?? "";
|
||||
final cName = name.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.shutdown(),
|
||||
ptrsToFree: [],
|
||||
);
|
||||
break;
|
||||
default:
|
||||
res = PayloadResult("", "unknown libwallet method ${method}", 0);
|
||||
}
|
||||
sendPort.send((id, res));
|
||||
} catch (e) {
|
||||
final errMsg = e.toString();
|
||||
printV("decred libwallet returned an error for method ${method}: ${errMsg}");
|
||||
sendPort.send((id, PayloadResult("", errMsg, 0)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void _startRemoteIsolate(SendPort sendPort) {
|
||||
final receivePort = ReceivePort();
|
||||
sendPort.send(receivePort.sendPort);
|
||||
_handleCommandsToIsolate(receivePort, sendPort);
|
||||
}
|
||||
|
||||
// initLibdcrwallet initializes libdcrwallet using the provided logDir and gets
|
||||
// it ready for use. This must be done before attempting to create, load or use
|
||||
// a wallet.
|
||||
Future<void> initLibdcrwallet(String logDir) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "initlibdcrwallet",
|
||||
"logdir": logDir,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future<void> createWallet(String config) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "createwallet",
|
||||
"config": config,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future<void> createWatchOnlyWallet(String config) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "createwatchonlywallet",
|
||||
"config": config,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future<void> loadWallet(String config) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "loadwallet",
|
||||
"config": config,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future<void> startSync(String walletName, String peers) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "startsync",
|
||||
"name": walletName,
|
||||
"peers": peers,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future<void> closeWallet(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "closewallet",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future<String> changeWalletPassword(
|
||||
String walletName, String currentPassword, String newPassword) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "changewalletpassword",
|
||||
"name": walletName,
|
||||
"oldpass": currentPassword,
|
||||
"newpass": newPassword
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String?> walletSeed(String walletName, String walletPassword) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "walletseed",
|
||||
"name": walletName,
|
||||
"pass": walletPassword,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> syncStatus(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "syncstatus",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<Map> balance(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "balance",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return jsonDecode(res.payload);
|
||||
}
|
||||
|
||||
Future<String> estimateFee(String walletName, int numBlocks) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "estimatefee",
|
||||
"name": walletName,
|
||||
"numblocks": numBlocks.toString(),
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> createSignedTransaction(
|
||||
String walletName, String createSignedTransactionReq) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "createsignedtransaction",
|
||||
"name": walletName,
|
||||
"signreq": createSignedTransactionReq,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> sendRawTransaction(String walletName, String txHex) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "sendrawtransaction",
|
||||
"name": walletName,
|
||||
"txhex": txHex,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> listTransactions(String walletName, String from, String count) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "listtransactions",
|
||||
"name": walletName,
|
||||
"from": from,
|
||||
"count": count,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> bestBlock(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "bestblock",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> listUnspents(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "listunspents",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> rescanFromHeight(String walletName, String height) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "rescanfromheight",
|
||||
"name": walletName,
|
||||
"height": height,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> signMessage(
|
||||
String walletName, String message, String address, String walletPass) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "signmessage",
|
||||
"name": walletName,
|
||||
"message": message,
|
||||
"address": address,
|
||||
"pass": walletPass,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> verifyMessage(
|
||||
String walletName, String message, String address, String sig) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "verifymessage",
|
||||
"name": walletName,
|
||||
"message": message,
|
||||
"address": address,
|
||||
"sig": sig,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String?> newExternalAddress(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "newexternaladdress",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
if (res.errCode == ErrCodeNotSynced) {
|
||||
// Wallet is not synced. We do not want to give out a used address so give
|
||||
// nothing.
|
||||
return null;
|
||||
}
|
||||
checkErr(res.err);
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> defaultPubkey(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "defaultpubkey",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> addresses(String walletName, String nUsed, String nUnused) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "addresses",
|
||||
"name": walletName,
|
||||
"nused": nUsed,
|
||||
"nunused": nUnused,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<String> birthState(String walletName) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "birthstate",
|
||||
"name": walletName,
|
||||
};
|
||||
_commands.send((id, req));
|
||||
final res = await completer.future as PayloadResult;
|
||||
return res.payload;
|
||||
}
|
||||
|
||||
Future<void> shutdown() async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "shutdown",
|
||||
};
|
||||
_commands.send((id, req));
|
||||
await completer.future as PayloadResult;
|
||||
}
|
||||
|
||||
void close() {
|
||||
if (!_closed) {
|
||||
_closed = true;
|
||||
_commands.send('shutdown');
|
||||
if (_activeRequests.isEmpty) _responses.close();
|
||||
}
|
||||
}
|
||||
}
|
64
cw_decred/lib/api/util.dart
Normal file
64
cw_decred/lib/api/util.dart
Normal file
|
@ -0,0 +1,64 @@
|
|||
import 'dart:ffi';
|
||||
import 'package:ffi/ffi.dart';
|
||||
import 'dart:convert';
|
||||
|
||||
class PayloadResult {
|
||||
final String payload;
|
||||
final String err;
|
||||
final int errCode;
|
||||
|
||||
const PayloadResult(this.payload, this.err, this.errCode);
|
||||
}
|
||||
|
||||
// Executes the provided fn and converts the string response to a PayloadResult.
|
||||
// Returns payload, error code, and error.
|
||||
PayloadResult executePayloadFn({
|
||||
required Pointer<Char> fn(),
|
||||
required List<Pointer> ptrsToFree,
|
||||
bool skipErrorCheck = false,
|
||||
}) {
|
||||
final jsonStr = fn().toDartString();
|
||||
freePointers(ptrsToFree);
|
||||
if (jsonStr == null) throw Exception("no json return from wallet library");
|
||||
final decoded = json.decode(jsonStr);
|
||||
|
||||
final err = decoded["error"] ?? "";
|
||||
if (!skipErrorCheck) {
|
||||
checkErr(err);
|
||||
}
|
||||
|
||||
final payload = decoded["payload"] ?? "";
|
||||
final errCode = decoded["errorcode"] ?? -1;
|
||||
return new PayloadResult(payload, err, errCode);
|
||||
}
|
||||
|
||||
void freePointers(List<Pointer> ptrsToFree) {
|
||||
for (final ptr in ptrsToFree) {
|
||||
malloc.free(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
void checkErr(String err) {
|
||||
if (err == "") return;
|
||||
throw Exception(err);
|
||||
}
|
||||
|
||||
extension StringUtil on String {
|
||||
Pointer<Char> toCString() => toNativeUtf8().cast<Char>();
|
||||
}
|
||||
|
||||
extension CStringUtil on Pointer<Char> {
|
||||
bool get isNull => address == nullptr.address;
|
||||
|
||||
free() {
|
||||
malloc.free(this);
|
||||
}
|
||||
|
||||
String? toDartString() {
|
||||
if (isNull) return null;
|
||||
|
||||
final str = cast<Utf8>().toDartString();
|
||||
free();
|
||||
return str;
|
||||
}
|
||||
}
|
25
cw_decred/lib/balance.dart
Normal file
25
cw_decred/lib/balance.dart
Normal file
|
@ -0,0 +1,25 @@
|
|||
import 'package:cw_decred/amount_format.dart';
|
||||
import 'package:cw_core/balance.dart';
|
||||
|
||||
class DecredBalance extends Balance {
|
||||
const DecredBalance({required this.confirmed, required this.unconfirmed, required this.frozen})
|
||||
: super(confirmed, unconfirmed);
|
||||
|
||||
factory DecredBalance.zero() => DecredBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
|
||||
|
||||
final int confirmed;
|
||||
final int unconfirmed;
|
||||
final int frozen;
|
||||
|
||||
@override
|
||||
String get formattedAvailableBalance => decredAmountToString(amount: confirmed - frozen);
|
||||
|
||||
@override
|
||||
String get formattedAdditionalBalance => decredAmountToString(amount: unconfirmed);
|
||||
|
||||
@override
|
||||
String get formattedUnAvailableBalance {
|
||||
final frozenFormatted = decredAmountToString(amount: frozen);
|
||||
return frozenFormatted == '0.0' ? '' : frozenFormatted;
|
||||
}
|
||||
}
|
2050
cw_decred/lib/mnemonic.dart
Normal file
2050
cw_decred/lib/mnemonic.dart
Normal file
File diff suppressed because it is too large
Load diff
39
cw_decred/lib/pending_transaction.dart
Normal file
39
cw_decred/lib/pending_transaction.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_decred/amount_format.dart';
|
||||
|
||||
class DecredPendingTransaction with PendingTransaction {
|
||||
DecredPendingTransaction(
|
||||
{required this.txid,
|
||||
required this.amount,
|
||||
required this.fee,
|
||||
required this.rawHex,
|
||||
required this.send});
|
||||
|
||||
final int amount;
|
||||
final int fee;
|
||||
final String txid;
|
||||
final String rawHex;
|
||||
final Future<void> Function() send;
|
||||
|
||||
@override
|
||||
String get id => txid;
|
||||
|
||||
@override
|
||||
String get amountFormatted => decredAmountToString(amount: amount);
|
||||
|
||||
@override
|
||||
String get feeFormatted => decredAmountToString(amount: fee);
|
||||
|
||||
@override
|
||||
String get hex => rawHex;
|
||||
|
||||
@override
|
||||
Future<void> commit() async {
|
||||
return send();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
10
cw_decred/lib/transaction_credentials.dart
Normal file
10
cw_decred/lib/transaction_credentials.dart
Normal file
|
@ -0,0 +1,10 @@
|
|||
import 'package:cw_decred/transaction_priority.dart';
|
||||
import 'package:cw_core/output_info.dart';
|
||||
|
||||
class DecredTransactionCredentials {
|
||||
DecredTransactionCredentials(this.outputs, {required this.priority, this.feeRate});
|
||||
|
||||
final List<OutputInfo> outputs;
|
||||
final DecredTransactionPriority? priority;
|
||||
final int? feeRate;
|
||||
}
|
31
cw_decred/lib/transaction_history.dart
Normal file
31
cw_decred/lib/transaction_history.dart
Normal file
|
@ -0,0 +1,31 @@
|
|||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
|
||||
class DecredTransactionHistory extends TransactionHistoryBase<TransactionInfo> {
|
||||
DecredTransactionHistory() {
|
||||
transactions = ObservableMap<String, TransactionInfo>();
|
||||
}
|
||||
|
||||
@override
|
||||
void addOne(TransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
|
||||
@override
|
||||
void addMany(Map<String, TransactionInfo> transactions) => this.transactions.addAll(transactions);
|
||||
|
||||
@override
|
||||
Future<void> save() async {}
|
||||
|
||||
// update returns true if a known transaction that is not pending was found.
|
||||
bool update(Map<String, TransactionInfo> txs) {
|
||||
var foundOldTx = false;
|
||||
txs.forEach((_, tx) {
|
||||
if (!this.transactions.containsKey(tx.id) || this.transactions[tx.id]!.isPending) {
|
||||
this.transactions[tx.id] = tx;
|
||||
} else {
|
||||
foundOldTx = true;
|
||||
}
|
||||
});
|
||||
return foundOldTx;
|
||||
}
|
||||
}
|
45
cw_decred/lib/transaction_info.dart
Normal file
45
cw_decred/lib/transaction_info.dart
Normal file
|
@ -0,0 +1,45 @@
|
|||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/format_amount.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_decred/amount_format.dart';
|
||||
|
||||
class DecredTransactionInfo extends TransactionInfo {
|
||||
DecredTransactionInfo({
|
||||
required String id,
|
||||
required int amount,
|
||||
required int fee,
|
||||
required TransactionDirection direction,
|
||||
required bool isPending,
|
||||
required DateTime date,
|
||||
required int height,
|
||||
required int confirmations,
|
||||
required String to,
|
||||
}) {
|
||||
this.id = id;
|
||||
this.amount = amount;
|
||||
this.fee = fee;
|
||||
this.height = height;
|
||||
this.direction = direction;
|
||||
this.date = date;
|
||||
this.isPending = isPending;
|
||||
this.confirmations = confirmations;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
String? _fiatAmount;
|
||||
|
||||
@override
|
||||
String amountFormatted() =>
|
||||
'${formatAmount(decredAmountToString(amount: amount))} ${walletTypeToCryptoCurrency(WalletType.decred).title}';
|
||||
|
||||
@override
|
||||
String? feeFormatted() =>
|
||||
'${formatAmount(decredAmountToString(amount: fee ?? 0))} ${walletTypeToCryptoCurrency(WalletType.decred).title}';
|
||||
|
||||
@override
|
||||
String fiatAmount() => _fiatAmount ?? '';
|
||||
|
||||
@override
|
||||
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
||||
}
|
69
cw_decred/lib/transaction_priority.dart
Normal file
69
cw_decred/lib/transaction_priority.dart
Normal file
|
@ -0,0 +1,69 @@
|
|||
import 'package:cw_core/transaction_priority.dart';
|
||||
|
||||
class DecredTransactionPriority extends TransactionPriority {
|
||||
const DecredTransactionPriority({required String title, required int raw})
|
||||
: super(title: title, raw: raw);
|
||||
|
||||
static const List<DecredTransactionPriority> all = [fast, medium, slow];
|
||||
static const DecredTransactionPriority slow = DecredTransactionPriority(title: 'Slow', raw: 0);
|
||||
static const DecredTransactionPriority medium =
|
||||
DecredTransactionPriority(title: 'Medium', raw: 1);
|
||||
static const DecredTransactionPriority fast = DecredTransactionPriority(title: 'Fast', raw: 2);
|
||||
|
||||
static DecredTransactionPriority deserialize({required int raw}) {
|
||||
switch (raw) {
|
||||
case 0:
|
||||
return slow;
|
||||
case 1:
|
||||
return medium;
|
||||
case 2:
|
||||
return fast;
|
||||
default:
|
||||
throw Exception('Unexpected token: $raw for DecredTransactionPriority deserialize');
|
||||
}
|
||||
}
|
||||
|
||||
String get units => 'atom';
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var label = '';
|
||||
|
||||
switch (this) {
|
||||
case DecredTransactionPriority.slow:
|
||||
label = 'Slow ~24hrs'; // '${S.current.transaction_priority_slow} ~24hrs';
|
||||
break;
|
||||
case DecredTransactionPriority.medium:
|
||||
label = 'Medium'; // S.current.transaction_priority_medium;
|
||||
break;
|
||||
case DecredTransactionPriority.fast:
|
||||
label = 'Fast'; // S.current.transaction_priority_fast;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
String labelWithRate(int rate) => '${toString()} ($rate ${units}/byte)';
|
||||
}
|
||||
|
||||
class FeeCache {
|
||||
int _feeRate;
|
||||
DateTime stamp;
|
||||
FeeCache(this._feeRate) : this.stamp = DateTime(0, 0, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
bool isOld() {
|
||||
return this.stamp.add(const Duration(minutes: 30)).isBefore(DateTime.now());
|
||||
}
|
||||
|
||||
void update(int feeRate) {
|
||||
this._feeRate = feeRate;
|
||||
this.stamp = DateTime.now();
|
||||
}
|
||||
|
||||
int feeRate() {
|
||||
return this._feeRate;
|
||||
}
|
||||
}
|
729
cw_decred/lib/wallet.dart
Normal file
729
cw_decred/lib/wallet.dart
Normal file
|
@ -0,0 +1,729 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:cw_core/exceptions.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_decred/pending_transaction.dart';
|
||||
import 'package:cw_decred/transaction_credentials.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
import 'package:cw_decred/api/libdcrwallet.dart';
|
||||
import 'package:cw_decred/transaction_history.dart';
|
||||
import 'package:cw_decred/wallet_addresses.dart';
|
||||
import 'package:cw_decred/transaction_priority.dart';
|
||||
import 'package:cw_decred/wallet_service.dart';
|
||||
import 'package:cw_decred/balance.dart';
|
||||
import 'package:cw_decred/transaction_info.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/unspent_transaction_output.dart';
|
||||
|
||||
part 'wallet.g.dart';
|
||||
|
||||
class DecredWallet = DecredWalletBase with _$DecredWallet;
|
||||
|
||||
abstract class DecredWalletBase
|
||||
extends WalletBase<DecredBalance, DecredTransactionHistory, DecredTransactionInfo> with Store {
|
||||
DecredWalletBase(WalletInfo walletInfo, String password, Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
Libwallet libwallet, Function() closeLibwallet)
|
||||
: _password = password,
|
||||
_libwallet = libwallet,
|
||||
_closeLibwallet = closeLibwallet,
|
||||
this.syncStatus = NotConnectedSyncStatus(),
|
||||
this.unspentCoinsInfo = unspentCoinsInfo,
|
||||
this.watchingOnly =
|
||||
walletInfo.derivationInfo?.derivationPath == DecredWalletService.pubkeyRestorePath ||
|
||||
walletInfo.derivationInfo?.derivationPath ==
|
||||
DecredWalletService.pubkeyRestorePathTestnet,
|
||||
this.balance = ObservableMap.of({CryptoCurrency.dcr: DecredBalance.zero()}),
|
||||
this.isTestnet = walletInfo.derivationInfo?.derivationPath ==
|
||||
DecredWalletService.seedRestorePathTestnet ||
|
||||
walletInfo.derivationInfo?.derivationPath ==
|
||||
DecredWalletService.pubkeyRestorePathTestnet,
|
||||
super(walletInfo) {
|
||||
walletAddresses = DecredWalletAddresses(walletInfo, libwallet);
|
||||
transactionHistory = DecredTransactionHistory();
|
||||
|
||||
reaction((_) => isEnabledAutoGenerateSubaddress, (bool enabled) {
|
||||
this.walletAddresses.isEnabledAutoGenerateSubaddress = enabled;
|
||||
});
|
||||
}
|
||||
|
||||
// NOTE: Hitting this max fee would be unexpected with current on chain use
|
||||
// but this may need to be updated in the future.
|
||||
final maxFeeRate = 100000;
|
||||
|
||||
// syncIntervalSyncing is used up until synced, then transactions are checked
|
||||
// every syncIntervalSynced.
|
||||
final syncIntervalSyncing = 5; // seconds
|
||||
final syncIntervalSynced = 30; // seconds
|
||||
static final defaultFeeRate = 10000;
|
||||
final String _password;
|
||||
final Libwallet _libwallet;
|
||||
final Function() _closeLibwallet;
|
||||
final idPrefix = "decred_";
|
||||
|
||||
// TODO: Encrypt this.
|
||||
var _seed = "";
|
||||
var _pubkey = "";
|
||||
var _unspents = <Unspent>[];
|
||||
|
||||
// synced is used to set the syncTimer interval.
|
||||
bool synced = false;
|
||||
bool watchingOnly;
|
||||
bool connecting = false;
|
||||
String persistantPeer = "default-spv-nodes";
|
||||
FeeCache feeRateFast = FeeCache(defaultFeeRate);
|
||||
FeeCache feeRateMedium = FeeCache(defaultFeeRate);
|
||||
FeeCache feeRateSlow = FeeCache(defaultFeeRate);
|
||||
Timer? syncTimer;
|
||||
Box<UnspentCoinsInfo> unspentCoinsInfo;
|
||||
|
||||
@override
|
||||
@observable
|
||||
bool isEnabledAutoGenerateSubaddress = true;
|
||||
|
||||
@override
|
||||
@observable
|
||||
SyncStatus syncStatus;
|
||||
|
||||
@override
|
||||
@observable
|
||||
late ObservableMap<CryptoCurrency, DecredBalance> balance;
|
||||
|
||||
@override
|
||||
late DecredWalletAddresses walletAddresses;
|
||||
|
||||
@override
|
||||
String? get seed {
|
||||
if (watchingOnly) {
|
||||
return null;
|
||||
}
|
||||
return _seed;
|
||||
}
|
||||
|
||||
@override
|
||||
Object get keys => {};
|
||||
|
||||
@override
|
||||
bool isTestnet;
|
||||
|
||||
String get pubkey {
|
||||
return _pubkey;
|
||||
}
|
||||
|
||||
Future<void> init() async {
|
||||
final getSeed = () async {
|
||||
if (!watchingOnly) {
|
||||
_seed = await _libwallet.walletSeed(walletInfo.name, _password) ?? "";
|
||||
}
|
||||
_pubkey = await _libwallet.defaultPubkey(walletInfo.name);
|
||||
};
|
||||
await Future.wait([
|
||||
updateBalance(),
|
||||
updateTransactionHistory(),
|
||||
walletAddresses.init(),
|
||||
fetchTransactions(),
|
||||
updateFees(),
|
||||
fetchUnspents(),
|
||||
getSeed(),
|
||||
]);
|
||||
}
|
||||
|
||||
Future<void> performBackgroundTasks() async {
|
||||
if (!await checkSync()) {
|
||||
if (synced == true) {
|
||||
synced = false;
|
||||
if (syncTimer != null) {
|
||||
syncTimer!.cancel();
|
||||
}
|
||||
syncTimer = Timer.periodic(
|
||||
Duration(seconds: syncIntervalSyncing), (Timer t) => performBackgroundTasks());
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Set sync check interval lower since we are synced.
|
||||
if (synced == false) {
|
||||
synced = true;
|
||||
if (syncTimer != null) {
|
||||
syncTimer!.cancel();
|
||||
}
|
||||
syncTimer = Timer.periodic(
|
||||
Duration(seconds: syncIntervalSynced), (Timer t) => performBackgroundTasks());
|
||||
}
|
||||
await Future.wait([
|
||||
updateTransactionHistory(),
|
||||
updateFees(),
|
||||
fetchUnspents(),
|
||||
updateBalance(),
|
||||
walletAddresses.updateAddressesInBox(),
|
||||
]);
|
||||
}
|
||||
|
||||
Future<void> updateFees() async {
|
||||
final feeForNb = (int nb) async {
|
||||
try {
|
||||
final feeStr = await _libwallet.estimateFee(walletInfo.name, nb);
|
||||
var fee = int.parse(feeStr);
|
||||
if (fee > maxFeeRate) {
|
||||
throw "dcr fee returned from estimate fee was over max";
|
||||
} else if (fee <= 0) {
|
||||
throw "dcr fee returned from estimate fee was zero";
|
||||
}
|
||||
return fee;
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
return defaultFeeRate;
|
||||
}
|
||||
};
|
||||
if (feeRateSlow.isOld()) {
|
||||
feeRateSlow.update(await feeForNb(4));
|
||||
}
|
||||
if (feeRateMedium.isOld()) {
|
||||
feeRateMedium.update(await feeForNb(2));
|
||||
}
|
||||
if (feeRateFast.isOld()) {
|
||||
feeRateFast.update(await feeForNb(1));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateTransactionHistory() async {
|
||||
// from is the number of transactions skipped from most recent, not block
|
||||
// height.
|
||||
var from = 0;
|
||||
while (true) {
|
||||
// Transactions are returned from newest to oldest. Loop fetching 5 txn
|
||||
// at a time until we find a batch with txn that no longer need to be
|
||||
// updated.
|
||||
final txs = await this.fetchFiveTransactions(from);
|
||||
if (txs.length == 0) {
|
||||
return;
|
||||
}
|
||||
if (this.transactionHistory.update(txs)) {
|
||||
return;
|
||||
}
|
||||
from += 5;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> checkSync() async {
|
||||
final syncStatusJSON = await _libwallet.syncStatus(walletInfo.name);
|
||||
final decoded = json.decode(syncStatusJSON);
|
||||
|
||||
final syncStatusCode = decoded["syncstatuscode"] ?? 0;
|
||||
// final syncStatusStr = decoded["syncstatus"] ?? "";
|
||||
final targetHeight = decoded["targetheight"] ?? 1;
|
||||
final numPeers = decoded["numpeers"] ?? 0;
|
||||
// final cFiltersHeight = decoded["cfiltersheight"] ?? 0;
|
||||
final headersHeight = decoded["headersheight"] ?? 0;
|
||||
final rescanHeight = decoded["rescanheight"] ?? 0;
|
||||
|
||||
if (numPeers == 0) {
|
||||
syncStatus = NotConnectedSyncStatus();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sync codes:
|
||||
// NotStarted = 0
|
||||
// FetchingCFilters = 1
|
||||
// FetchingHeaders = 2
|
||||
// DiscoveringAddrs = 3
|
||||
// Rescanning = 4
|
||||
// Complete = 5
|
||||
|
||||
if (syncStatusCode > 4) {
|
||||
syncStatus = SyncedSyncStatus();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (syncStatusCode == 0) {
|
||||
syncStatus = ConnectedSyncStatus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (syncStatusCode == 1) {
|
||||
syncStatus = SyncingSyncStatus(targetHeight, 0.0);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (syncStatusCode == 2) {
|
||||
final headersProg = headersHeight / targetHeight;
|
||||
// Only allow headers progress to go up half way.
|
||||
syncStatus = SyncingSyncStatus(targetHeight - headersHeight, headersProg);
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: This step takes a while so should really get more info to the UI
|
||||
// that we are discovering addresses.
|
||||
if (syncStatusCode == 3) {
|
||||
// Hover at half.
|
||||
syncStatus = ProcessingSyncStatus();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (syncStatusCode == 4) {
|
||||
// Start at 75%.
|
||||
final rescanProg = rescanHeight / targetHeight / 4;
|
||||
syncStatus = SyncingSyncStatus(targetHeight - rescanHeight, .75 + rescanProg);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> connectToNode({required Node node}) async {
|
||||
if (connecting) {
|
||||
return;
|
||||
}
|
||||
connecting = true;
|
||||
String addr = "default-spv-nodes";
|
||||
if (node.uri.host != addr) {
|
||||
addr = node.uri.host;
|
||||
if (node.uri.port != "") {
|
||||
addr += ":" + node.uri.port.toString();
|
||||
}
|
||||
}
|
||||
if (addr != persistantPeer) {
|
||||
if (syncTimer != null) {
|
||||
syncTimer!.cancel();
|
||||
syncTimer = null;
|
||||
}
|
||||
persistantPeer = addr;
|
||||
await _libwallet.closeWallet(walletInfo.name);
|
||||
final network = isTestnet ? "testnet" : "mainnet";
|
||||
final config = {
|
||||
"name": walletInfo.name,
|
||||
"datadir": walletInfo.dirPath,
|
||||
"net": network,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await _libwallet.loadWallet(jsonEncode(config));
|
||||
}
|
||||
await this._startSync();
|
||||
connecting = false;
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> startSync() async {
|
||||
if (connecting) {
|
||||
return;
|
||||
}
|
||||
connecting = true;
|
||||
await this._startSync();
|
||||
connecting = false;
|
||||
}
|
||||
|
||||
Future<void> _startSync() async {
|
||||
if (syncTimer != null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
syncStatus = ConnectingSyncStatus();
|
||||
await _libwallet.startSync(
|
||||
walletInfo.name,
|
||||
persistantPeer == "default-spv-nodes" ? "" : persistantPeer,
|
||||
);
|
||||
syncTimer = Timer.periodic(
|
||||
Duration(seconds: syncIntervalSyncing), (Timer t) => performBackgroundTasks());
|
||||
} catch (e) {
|
||||
printV(e.toString());
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||
if (watchingOnly) {
|
||||
return DecredPendingTransaction(
|
||||
txid: "",
|
||||
amount: 0,
|
||||
fee: 0,
|
||||
rawHex: "",
|
||||
send: () async {
|
||||
throw "unable to send with watching only wallet";
|
||||
});
|
||||
}
|
||||
var totalIn = 0;
|
||||
final ignoreInputs = [];
|
||||
this.unspentCoinsInfo.values.forEach((unspent) {
|
||||
if (unspent.isFrozen || !unspent.isSending) {
|
||||
final input = {"txid": unspent.hash, "vout": unspent.vout};
|
||||
ignoreInputs.add(input);
|
||||
return;
|
||||
}
|
||||
totalIn += unspent.value;
|
||||
});
|
||||
|
||||
final creds = credentials as DecredTransactionCredentials;
|
||||
var totalAmt = 0;
|
||||
var sendAll = false;
|
||||
final outputs = [];
|
||||
for (final out in creds.outputs) {
|
||||
var amt = 0;
|
||||
if (out.sendAll) {
|
||||
if (creds.outputs.length != 1) {
|
||||
throw "can only send all to one output";
|
||||
}
|
||||
sendAll = true;
|
||||
totalAmt = totalIn;
|
||||
} else if (out.cryptoAmount != null) {
|
||||
final coins = double.parse(out.cryptoAmount!);
|
||||
amt = (coins * 1e8).toInt();
|
||||
}
|
||||
totalAmt += amt;
|
||||
final o = {
|
||||
"address": out.isParsedAddress ? out.extractedAddress! : out.address,
|
||||
"amount": amt
|
||||
};
|
||||
outputs.add(o);
|
||||
}
|
||||
|
||||
// throw exception if no selected coins under coin control
|
||||
// or if the total coins selected, is less than the amount the user wants to spend
|
||||
if (ignoreInputs.length == unspentCoinsInfo.values.length || totalIn < totalAmt) {
|
||||
throw TransactionNoInputsException();
|
||||
}
|
||||
|
||||
// The inputs are always used. Currently we don't have use for this
|
||||
// argument. sendall ingores output value and sends everything.
|
||||
final signReq = {
|
||||
// "inputs": inputs,
|
||||
"ignoreInputs": ignoreInputs,
|
||||
"outputs": outputs,
|
||||
"feerate": creds.feeRate ?? defaultFeeRate,
|
||||
"password": _password,
|
||||
"sendall": sendAll,
|
||||
};
|
||||
final res = await _libwallet.createSignedTransaction(walletInfo.name, jsonEncode(signReq));
|
||||
final decoded = json.decode(res);
|
||||
final signedHex = decoded["signedhex"];
|
||||
final send = () async {
|
||||
await _libwallet.sendRawTransaction(walletInfo.name, signedHex);
|
||||
await updateBalance();
|
||||
};
|
||||
final fee = decoded["fee"] ?? 0;
|
||||
if (sendAll) {
|
||||
totalAmt = (totalAmt - fee).toInt();
|
||||
}
|
||||
return DecredPendingTransaction(
|
||||
txid: decoded["txid"] ?? "", amount: totalAmt, fee: fee, rawHex: signedHex, send: send);
|
||||
}
|
||||
|
||||
int feeRate(TransactionPriority priority) {
|
||||
if (!(priority is DecredTransactionPriority)) {
|
||||
return defaultFeeRate;
|
||||
}
|
||||
final p = priority;
|
||||
switch (p) {
|
||||
case DecredTransactionPriority.slow:
|
||||
return feeRateSlow.feeRate();
|
||||
case DecredTransactionPriority.medium:
|
||||
return feeRateMedium.feeRate();
|
||||
case DecredTransactionPriority.fast:
|
||||
return feeRateFast.feeRate();
|
||||
}
|
||||
return defaultFeeRate;
|
||||
}
|
||||
|
||||
@override
|
||||
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
|
||||
if (priority is DecredTransactionPriority) {
|
||||
final P2PKHOutputSize =
|
||||
36; // 8 bytes value + 2 bytes version + at least 1 byte varint script size + P2PKHPkScriptSize
|
||||
// MsgTxOverhead is 4 bytes version (lower 2 bytes for the real transaction
|
||||
// version and upper 2 bytes for the serialization type) + 4 bytes locktime
|
||||
// + 4 bytes expiry + 3 bytes of varints for the number of transaction
|
||||
// inputs (x2 for witness and prefix) and outputs
|
||||
final MsgTxOverhead = 15;
|
||||
// TxInOverhead is the overhead for a wire.TxIn with a scriptSig length <
|
||||
// 254. prefix (41 bytes) + ValueIn (8 bytes) + BlockHeight (4 bytes) +
|
||||
// BlockIndex (4 bytes) + sig script var int (at least 1 byte)
|
||||
final TxInOverhead = 57;
|
||||
final P2PKHInputSize =
|
||||
TxInOverhead + 109; // TxInOverhead (57) + var int (1) + P2PKHSigScriptSize (108)
|
||||
|
||||
int inputsCount = 1;
|
||||
if (amount != null) {
|
||||
inputsCount += _unspents.where((e) {
|
||||
amount = (amount!) - e.value;
|
||||
return (amount!) > 0;
|
||||
}).length;
|
||||
}
|
||||
|
||||
// Estimate using a transaction consuming inoutsCount and paying to one address with change.
|
||||
return (this.feeRate(priority) / 1000).round() *
|
||||
(MsgTxOverhead + P2PKHInputSize * inputsCount + P2PKHOutputSize * 2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, DecredTransactionInfo>> fetchTransactions() async {
|
||||
return this.fetchFiveTransactions(0);
|
||||
}
|
||||
|
||||
Future<Map<String, DecredTransactionInfo>> fetchFiveTransactions(int from) async {
|
||||
final res = await _libwallet.listTransactions(walletInfo.name, from.toString(), "5");
|
||||
final decoded = json.decode(res);
|
||||
var txs = <String, DecredTransactionInfo>{};
|
||||
for (final d in decoded) {
|
||||
final txid = uniqueTxID(d["txid"] ?? "", d["vout"] ?? 0);
|
||||
var direction = TransactionDirection.outgoing;
|
||||
if (d["category"] == "receive") {
|
||||
direction = TransactionDirection.incoming;
|
||||
}
|
||||
final amountDouble = d["amount"] ?? 0.0;
|
||||
final amount = (amountDouble * 1e8).toInt().abs();
|
||||
final feeDouble = d["fee"] ?? 0.0;
|
||||
final fee = (feeDouble * 1e8).toInt().abs();
|
||||
final confs = d["confirmations"] ?? 0;
|
||||
final sendTime = d["time"] ?? 0;
|
||||
final height = d["height"] ?? 0;
|
||||
final txInfo = DecredTransactionInfo(
|
||||
id: txid,
|
||||
amount: amount,
|
||||
fee: fee,
|
||||
direction: direction,
|
||||
isPending: confs == 0,
|
||||
date: DateTime.fromMillisecondsSinceEpoch(sendTime * 1000, isUtc: false),
|
||||
height: height,
|
||||
confirmations: confs,
|
||||
to: d["address"] ?? "",
|
||||
);
|
||||
txs[txid] = txInfo;
|
||||
}
|
||||
return txs;
|
||||
}
|
||||
|
||||
// uniqueTxID combines the tx id and vout to create a unique id.
|
||||
String uniqueTxID(String id, int vout) {
|
||||
return id + ":" + vout.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> save() async {}
|
||||
|
||||
@override
|
||||
bool get hasRescan => walletBirthdayBlockHeight() != -1;
|
||||
|
||||
@override
|
||||
Future<void> rescan({required int height}) async {
|
||||
// The required height is not used. A birthday time is recorded in the
|
||||
// mnemonic. As long as not private data is imported into the wallet, we
|
||||
// can always rescan from there.
|
||||
var rescanHeight = 0;
|
||||
if (!watchingOnly) {
|
||||
rescanHeight = await walletBirthdayBlockHeight();
|
||||
// Sync has not yet reached the birthday block.
|
||||
if (rescanHeight == -1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await _libwallet.rescanFromHeight(walletInfo.name, rescanHeight.toString());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close({bool shouldCleanup = false}) async {
|
||||
if (syncTimer != null) {
|
||||
syncTimer!.cancel();
|
||||
syncTimer = null;
|
||||
}
|
||||
await _libwallet.closeWallet(walletInfo.name);
|
||||
if (shouldCleanup) {
|
||||
await _libwallet.shutdown();
|
||||
_closeLibwallet();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> changePassword(String password) async {
|
||||
if (watchingOnly) {
|
||||
return;
|
||||
}
|
||||
return () async {
|
||||
await _libwallet.changeWalletPassword(walletInfo.name, _password, password);
|
||||
}();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateBalance() async {
|
||||
final balanceMap = await _libwallet.balance(walletInfo.name);
|
||||
|
||||
var totalFrozen = 0;
|
||||
|
||||
unspentCoinsInfo.values.forEach((info) {
|
||||
_unspents.forEach((element) {
|
||||
if (element.hash == info.hash &&
|
||||
element.vout == info.vout &&
|
||||
info.isFrozen &&
|
||||
element.value == info.value) {
|
||||
totalFrozen += element.value;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
balance[CryptoCurrency.dcr] = DecredBalance(
|
||||
confirmed: balanceMap["confirmed"] ?? 0,
|
||||
unconfirmed: balanceMap["unconfirmed"] ?? 0,
|
||||
frozen: totalFrozen,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => onError;
|
||||
|
||||
Future<void> renameWalletFiles(String newWalletName) async {
|
||||
final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
|
||||
|
||||
final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
|
||||
|
||||
if (File(newDirPath).existsSync()) {
|
||||
throw "wallet already exists at $newDirPath";
|
||||
}
|
||||
|
||||
await Directory(currentDirPath).rename(newDirPath);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> signMessage(String message, {String? address = null}) async {
|
||||
if (watchingOnly) {
|
||||
throw "a watching only wallet cannot sign";
|
||||
}
|
||||
var addr = address;
|
||||
if (addr == null) {
|
||||
addr = walletAddresses.address;
|
||||
}
|
||||
if (addr == "") {
|
||||
throw "unable to get an address from unsynced wallet";
|
||||
}
|
||||
return await _libwallet.signMessage(walletInfo.name, message, addr, _password);
|
||||
}
|
||||
|
||||
Future<void> fetchUnspents() async {
|
||||
final res = await _libwallet.listUnspents(walletInfo.name);
|
||||
final decoded = json.decode(res);
|
||||
var unspents = <Unspent>[];
|
||||
for (final d in decoded) {
|
||||
final spendable = d["spendable"] ?? false;
|
||||
if (!spendable) {
|
||||
continue;
|
||||
}
|
||||
final amountDouble = d["amount"] ?? 0.0;
|
||||
final amount = (amountDouble * 1e8).toInt().abs();
|
||||
final utxo = Unspent(d["address"] ?? "", d["txid"] ?? "", amount, d["vout"] ?? 0, null);
|
||||
utxo.isChange = d["ischange"] ?? false;
|
||||
unspents.add(utxo);
|
||||
}
|
||||
_unspents = unspents;
|
||||
}
|
||||
|
||||
List<Unspent> unspents() {
|
||||
this.updateUnspents(_unspents);
|
||||
return _unspents;
|
||||
}
|
||||
|
||||
void updateUnspents(List<Unspent> unspentCoins) {
|
||||
if (this.unspentCoinsInfo.isEmpty) {
|
||||
unspentCoins.forEach((coin) => this.addCoinInfo(coin));
|
||||
return;
|
||||
}
|
||||
|
||||
if (unspentCoins.isEmpty) {
|
||||
this.unspentCoinsInfo.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
final walletID = idPrefix + walletInfo.name;
|
||||
if (unspentCoins.isNotEmpty) {
|
||||
unspentCoins.forEach((coin) {
|
||||
final coinInfoList = this.unspentCoinsInfo.values.where((element) =>
|
||||
element.walletId == walletID && element.hash == coin.hash && element.vout == coin.vout);
|
||||
|
||||
if (coinInfoList.isEmpty) {
|
||||
this.addCoinInfo(coin);
|
||||
} else {
|
||||
final coinInfo = coinInfoList.first;
|
||||
|
||||
coin.isFrozen = coinInfo.isFrozen;
|
||||
coin.isSending = coinInfo.isSending;
|
||||
coin.note = coinInfo.note;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
final List<dynamic> keys = <dynamic>[];
|
||||
this.unspentCoinsInfo.values.forEach((element) {
|
||||
final existUnspentCoins = unspentCoins.where((coin) => element.hash.contains(coin.hash));
|
||||
|
||||
if (existUnspentCoins.isEmpty) {
|
||||
keys.add(element.key);
|
||||
}
|
||||
});
|
||||
|
||||
if (keys.isNotEmpty) {
|
||||
unspentCoinsInfo.deleteAll(keys);
|
||||
}
|
||||
}
|
||||
|
||||
void addCoinInfo(Unspent coin) {
|
||||
final newInfo = UnspentCoinsInfo(
|
||||
walletId: idPrefix + walletInfo.name,
|
||||
hash: coin.hash,
|
||||
isFrozen: false,
|
||||
isSending: coin.isSending,
|
||||
noteRaw: "",
|
||||
address: coin.address,
|
||||
value: coin.value,
|
||||
vout: coin.vout,
|
||||
isChange: coin.isChange,
|
||||
keyImage: coin.keyImage,
|
||||
);
|
||||
|
||||
unspentCoinsInfo.add(newInfo);
|
||||
}
|
||||
|
||||
// walletBirthdayBlockHeight checks if the wallet birthday is set and returns
|
||||
// it. Returns -1 if not.
|
||||
Future<int> walletBirthdayBlockHeight() async {
|
||||
final res = await _libwallet.birthState(walletInfo.name);
|
||||
final decoded = json.decode(res);
|
||||
// Having these values set indicates that sync has not reached the birthday
|
||||
// yet, so no birthday is set.
|
||||
if (decoded["setfromheight"] == true || decoded["setfromtime"] == true) {
|
||||
return -1;
|
||||
}
|
||||
return decoded["height"] ?? 0;
|
||||
}
|
||||
|
||||
Future<bool> verifyMessage(String message, String signature, {String? address = null}) async {
|
||||
var addr = address;
|
||||
if (addr == null) {
|
||||
throw "an address is required to verify message";
|
||||
}
|
||||
return () async {
|
||||
final verified = await _libwallet.verifyMessage(walletInfo.name, message, addr, signature);
|
||||
if (verified == "true") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
}
|
||||
|
||||
@override
|
||||
String get password => _password;
|
||||
|
||||
@override
|
||||
bool canSend() => seed != null;
|
||||
}
|
137
cw_decred/lib/wallet_addresses.dart
Normal file
137
cw_decred/lib/wallet_addresses.dart
Normal file
|
@ -0,0 +1,137 @@
|
|||
import 'dart:convert';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
import 'package:cw_core/address_info.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_decred/api/libdcrwallet.dart';
|
||||
|
||||
part 'wallet_addresses.g.dart';
|
||||
|
||||
class DecredWalletAddresses = DecredWalletAddressesBase with _$DecredWalletAddresses;
|
||||
|
||||
abstract class DecredWalletAddressesBase extends WalletAddresses with Store {
|
||||
DecredWalletAddressesBase(WalletInfo walletInfo, Libwallet libwallet)
|
||||
: _libwallet = libwallet,
|
||||
super(walletInfo);
|
||||
final Libwallet _libwallet;
|
||||
String currentAddr = '';
|
||||
|
||||
@observable
|
||||
bool isEnabledAutoGenerateSubaddress = true;
|
||||
|
||||
@observable
|
||||
String selectedAddr = '';
|
||||
|
||||
@override
|
||||
@computed
|
||||
String get address {
|
||||
return selectedAddr;
|
||||
}
|
||||
|
||||
@override
|
||||
set address(value) {
|
||||
selectedAddr = value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
if (walletInfo.addresses != null) {
|
||||
addressesMap = walletInfo.addresses!;
|
||||
}
|
||||
if (walletInfo.addressInfos != null) {
|
||||
addressInfos = walletInfo.addressInfos!;
|
||||
}
|
||||
if (walletInfo.usedAddresses != null) {
|
||||
usedAddresses = {...walletInfo.usedAddresses!};
|
||||
}
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAddressesInBox() async {
|
||||
final addrs = await libAddresses();
|
||||
final allAddrs = new List.from(addrs.usedAddrs)..addAll(addrs.unusedAddrs);
|
||||
|
||||
// Add all addresses.
|
||||
allAddrs.forEach((addr) {
|
||||
if (addressesMap.containsKey(addr)) {
|
||||
return;
|
||||
}
|
||||
addressesMap[addr] = "";
|
||||
addressInfos[0] ??= [];
|
||||
addressInfos[0]?.add(AddressInfo(address: addr, label: "", accountIndex: 0));
|
||||
});
|
||||
|
||||
// Add used addresses.
|
||||
addrs.usedAddrs.forEach((addr) {
|
||||
if (!usedAddresses.contains(addr)) {
|
||||
usedAddresses.add(addr);
|
||||
}
|
||||
});
|
||||
|
||||
if (addrs.unusedAddrs.length > 0 && addrs.unusedAddrs[0] != currentAddr) {
|
||||
currentAddr = addrs.unusedAddrs[0];
|
||||
selectedAddr = currentAddr;
|
||||
}
|
||||
|
||||
await saveAddressesInBox();
|
||||
}
|
||||
|
||||
List<AddressInfo> getAddressInfos() {
|
||||
if (addressInfos.containsKey(0)) {
|
||||
return addressInfos[0]!;
|
||||
}
|
||||
return <AddressInfo>[];
|
||||
}
|
||||
|
||||
Future<void> updateAddress(String address, String label) async {
|
||||
if (!addressInfos.containsKey(0)) {
|
||||
return;
|
||||
}
|
||||
addressInfos[0]!.forEach((info) {
|
||||
if (info.address == address) {
|
||||
info.label = label;
|
||||
}
|
||||
});
|
||||
await saveAddressesInBox();
|
||||
}
|
||||
|
||||
Future<LibAddresses> libAddresses() async {
|
||||
final nUsed = "10";
|
||||
var nUnused = "1";
|
||||
if (this.isEnabledAutoGenerateSubaddress) {
|
||||
nUnused = "3";
|
||||
}
|
||||
final res = await _libwallet.addresses(walletInfo.name, nUsed, nUnused);
|
||||
final decoded = json.decode(res);
|
||||
final usedAddrs = List<String>.from(decoded["used"] ?? []);
|
||||
final unusedAddrs = List<String>.from(decoded["unused"] ?? []);
|
||||
// index is the index of the first unused address.
|
||||
final index = decoded["index"] ?? 0;
|
||||
return new LibAddresses(usedAddrs, unusedAddrs, index);
|
||||
}
|
||||
|
||||
Future<void> generateNewAddress(String label) async {
|
||||
// NOTE: This will ignore the gap limit and may cause problems when restoring from seed if too
|
||||
// many addresses are taken and not used.
|
||||
final addr = await _libwallet.newExternalAddress(walletInfo.name) ?? '';
|
||||
if (addr == "") {
|
||||
return;
|
||||
}
|
||||
if (!addressesMap.containsKey(addr)) {
|
||||
addressesMap[addr] = "";
|
||||
addressInfos[0] ??= [];
|
||||
addressInfos[0]?.add(AddressInfo(address: addr, label: label, accountIndex: 0));
|
||||
}
|
||||
selectedAddr = addr;
|
||||
await saveAddressesInBox();
|
||||
}
|
||||
}
|
||||
|
||||
class LibAddresses {
|
||||
final List<String> usedAddrs, unusedAddrs;
|
||||
final int firstUnusedAddrIndex;
|
||||
|
||||
LibAddresses(this.usedAddrs, this.unusedAddrs, this.firstUnusedAddrIndex);
|
||||
}
|
40
cw_decred/lib/wallet_creation_credentials.dart
Normal file
40
cw_decred/lib/wallet_creation_credentials.dart
Normal file
|
@ -0,0 +1,40 @@
|
|||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/hardware/hardware_account_data.dart';
|
||||
|
||||
class DecredNewWalletCredentials extends WalletCredentials {
|
||||
DecredNewWalletCredentials({required String name, WalletInfo? walletInfo})
|
||||
: super(name: name, walletInfo: walletInfo);
|
||||
}
|
||||
|
||||
class DecredRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
DecredRestoreWalletFromSeedCredentials(
|
||||
{required String name,
|
||||
required String password,
|
||||
required this.mnemonic,
|
||||
WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String mnemonic;
|
||||
}
|
||||
|
||||
class DecredRestoreWalletFromPubkeyCredentials extends WalletCredentials {
|
||||
DecredRestoreWalletFromPubkeyCredentials(
|
||||
{required String name,
|
||||
required String password,
|
||||
required String this.pubkey,
|
||||
WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String pubkey;
|
||||
}
|
||||
|
||||
class DecredRestoreWalletFromHardwareCredentials extends WalletCredentials {
|
||||
DecredRestoreWalletFromHardwareCredentials(
|
||||
{required String name, required this.hwAccountData, WalletInfo? walletInfo})
|
||||
: t = throw UnimplementedError(),
|
||||
super(name: name, walletInfo: walletInfo);
|
||||
|
||||
final HardwareAccountData hwAccountData;
|
||||
final void t;
|
||||
}
|
186
cw_decred/lib/wallet_service.dart
Normal file
186
cw_decred/lib/wallet_service.dart
Normal file
|
@ -0,0 +1,186 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:cw_decred/api/libdcrwallet.dart';
|
||||
import 'package:cw_decred/wallet_creation_credentials.dart';
|
||||
import 'package:cw_decred/wallet.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
|
||||
class DecredWalletService extends WalletService<
|
||||
DecredNewWalletCredentials,
|
||||
DecredRestoreWalletFromSeedCredentials,
|
||||
DecredRestoreWalletFromPubkeyCredentials,
|
||||
DecredRestoreWalletFromHardwareCredentials> {
|
||||
DecredWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||
final seedRestorePath = "m/44'/42'";
|
||||
static final seedRestorePathTestnet = "m/44'/1'";
|
||||
static final pubkeyRestorePath = "m/44'/42'/0'";
|
||||
static final pubkeyRestorePathTestnet = "m/44'/1'/0'";
|
||||
final mainnet = "mainnet";
|
||||
final testnet = "testnet";
|
||||
Libwallet? libwallet;
|
||||
|
||||
Future<void> init() async {
|
||||
if (libwallet != null) {
|
||||
return;
|
||||
}
|
||||
libwallet = await Libwallet.spawn();
|
||||
// Use the general path for all dcr wallets as the general log directory.
|
||||
// Individual wallet paths may be removed if the wallet is deleted.
|
||||
final dcrLogDir = await pathForWalletDir(name: '', type: WalletType.decred);
|
||||
libwallet!.initLibdcrwallet(dcrLogDir);
|
||||
}
|
||||
|
||||
void closeLibwallet() {
|
||||
if (libwallet == null) {
|
||||
return;
|
||||
}
|
||||
libwallet!.close();
|
||||
libwallet = null;
|
||||
}
|
||||
|
||||
@override
|
||||
WalletType getType() => WalletType.decred;
|
||||
|
||||
@override
|
||||
Future<bool> isWalletExit(String name) async =>
|
||||
File(await pathForWallet(name: name, type: getType())).existsSync();
|
||||
|
||||
@override
|
||||
Future<DecredWallet> create(DecredNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||
await this.init();
|
||||
final config = {
|
||||
"name": credentials.walletInfo!.name,
|
||||
"datadir": credentials.walletInfo!.dirPath,
|
||||
"pass": credentials.password!,
|
||||
"net": isTestnet == true ? testnet : mainnet,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await libwallet!.createWallet(jsonEncode(config));
|
||||
final di = DerivationInfo(
|
||||
derivationPath: isTestnet == true ? seedRestorePathTestnet : seedRestorePath);
|
||||
credentials.walletInfo!.derivationInfo = di;
|
||||
final wallet = DecredWallet(credentials.walletInfo!, credentials.password!,
|
||||
this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DecredWallet> openWallet(String name, String password) async {
|
||||
final walletInfo = walletInfoSource.values
|
||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
|
||||
final network = walletInfo.derivationInfo?.derivationPath == seedRestorePathTestnet ||
|
||||
walletInfo.derivationInfo?.derivationPath == pubkeyRestorePathTestnet
|
||||
? testnet
|
||||
: mainnet;
|
||||
|
||||
await this.init();
|
||||
final walletDirExists = Directory(walletInfo.dirPath).existsSync();
|
||||
if (!walletDirExists) {
|
||||
walletInfo.dirPath = await pathForWalletDir(name: name, type: getType());
|
||||
}
|
||||
|
||||
final config = {
|
||||
"name": walletInfo.name,
|
||||
"datadir": walletInfo.dirPath,
|
||||
"net": network,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await libwallet!.loadWallet(jsonEncode(config));
|
||||
final wallet =
|
||||
DecredWallet(walletInfo, password, this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String wallet) async {
|
||||
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
||||
final walletInfo = walletInfoSource.values
|
||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
||||
await walletInfoSource.delete(walletInfo.key);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> rename(String currentName, String password, String newName) async {
|
||||
final currentWalletInfo = walletInfoSource.values
|
||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
|
||||
final network = currentWalletInfo.derivationInfo?.derivationPath == seedRestorePathTestnet ||
|
||||
currentWalletInfo.derivationInfo?.derivationPath == pubkeyRestorePathTestnet
|
||||
? testnet
|
||||
: mainnet;
|
||||
final currentWallet = DecredWallet(
|
||||
currentWalletInfo, password, this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
|
||||
final newDirPath = await pathForWalletDir(name: newName, type: getType());
|
||||
final newWalletInfo = currentWalletInfo;
|
||||
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||
newWalletInfo.name = newName;
|
||||
newWalletInfo.dirPath = newDirPath;
|
||||
newWalletInfo.network = network;
|
||||
|
||||
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DecredWallet> restoreFromSeed(DecredRestoreWalletFromSeedCredentials credentials,
|
||||
{bool? isTestnet}) async {
|
||||
await this.init();
|
||||
final config = {
|
||||
"name": credentials.walletInfo!.name,
|
||||
"datadir": credentials.walletInfo!.dirPath,
|
||||
"pass": credentials.password!,
|
||||
"mnemonic": credentials.mnemonic,
|
||||
"net": isTestnet == true ? testnet : mainnet,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await libwallet!.createWallet(jsonEncode(config));
|
||||
final di = DerivationInfo(
|
||||
derivationPath: isTestnet == true ? seedRestorePathTestnet : seedRestorePath);
|
||||
credentials.walletInfo!.derivationInfo = di;
|
||||
final wallet = DecredWallet(credentials.walletInfo!, credentials.password!,
|
||||
this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
// restoreFromKeys only supports restoring a watch only wallet from an account
|
||||
// pubkey.
|
||||
@override
|
||||
Future<DecredWallet> restoreFromKeys(DecredRestoreWalletFromPubkeyCredentials credentials,
|
||||
{bool? isTestnet}) async {
|
||||
await this.init();
|
||||
final config = {
|
||||
"name": credentials.walletInfo!.name,
|
||||
"datadir": credentials.walletInfo!.dirPath,
|
||||
"pubkey": credentials.pubkey,
|
||||
"net": isTestnet == true ? testnet : mainnet,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await libwallet!.createWatchOnlyWallet(jsonEncode(config));
|
||||
final di = DerivationInfo(
|
||||
derivationPath: isTestnet == true ? pubkeyRestorePathTestnet : pubkeyRestorePath);
|
||||
credentials.walletInfo!.derivationInfo = di;
|
||||
final wallet = DecredWallet(credentials.walletInfo!, credentials.password!,
|
||||
this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DecredWallet> restoreFromHardwareWallet(
|
||||
DecredRestoreWalletFromHardwareCredentials credentials) async =>
|
||||
throw UnimplementedError();
|
||||
}
|
19
cw_decred/macos/Classes/CwDecredPlugin.swift
Normal file
19
cw_decred/macos/Classes/CwDecredPlugin.swift
Normal file
|
@ -0,0 +1,19 @@
|
|||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
public class CwDecredPlugin: NSObject, FlutterPlugin {
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let channel = FlutterMethodChannel(name: "cw_decred", binaryMessenger: registrar.messenger)
|
||||
let instance = CwDecredPlugin()
|
||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||
}
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
switch call.method {
|
||||
case "getPlatformVersion":
|
||||
result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString)
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
}
|
22
cw_decred/macos/cw_decred.podspec
Normal file
22
cw_decred/macos/cw_decred.podspec
Normal file
|
@ -0,0 +1,22 @@
|
|||
#
|
||||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
|
||||
# Run `pod lib lint cw_decred.podspec` to validate before publishing.
|
||||
#
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'cw_decred'
|
||||
s.version = '0.0.1'
|
||||
s.summary = 'Cake Wallet Decred'
|
||||
s.description = 'Cake Wallet wrapper over Decred project'
|
||||
s.homepage = 'http://cakewallet.com'
|
||||
s.license = { :file => '../LICENSE' }
|
||||
s.author = { 'Cake Wallet' => 'support@cakewallet.com' }
|
||||
|
||||
s.source = { :path => '.' }
|
||||
s.source_files = 'Classes/**/*'
|
||||
s.dependency 'FlutterMacOS'
|
||||
|
||||
s.platform = :osx, '10.11'
|
||||
s.vendored_libraries = 'External/lib/libdcrwallet.a'
|
||||
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', "OTHER_LDFLAGS" => "-force_load $(PODS_TARGET_SRCROOT)/External/lib/libdcrwallet.a -lstdc++" }
|
||||
s.swift_version = '5.0'
|
||||
end
|
852
cw_decred/pubspec.lock
Normal file
852
cw_decred/pubspec.lock
Normal file
|
@ -0,0 +1,852 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "72.0.0"
|
||||
_macros:
|
||||
dependency: transitive
|
||||
description: dart
|
||||
source: sdk
|
||||
version: "0.3.2"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.7.0"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
asn1lib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: asn1lib
|
||||
sha256: "4bae5ae63e6d6dd17c4aac8086f3dec26c0236f6a0f03416c6c19d830c367cf5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.8"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
blockchain_utils:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: cake-update-v2
|
||||
resolved-ref: "59fdf29d72068e0522a96a8953ed7272833a9f57"
|
||||
url: "https://github.com/cake-tech/blockchain_utils"
|
||||
source: git
|
||||
version: "3.3.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
build:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_daemon
|
||||
sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
build_resolvers:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.13"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.3.2"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_collection
|
||||
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.1"
|
||||
built_value:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: ea90e81dc4a25a043d9bee692d20ed6d1c4a1662a28c03a96417446c093ed6b4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.9.5"
|
||||
cake_backup:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: main
|
||||
resolved-ref: "3aba867dcab6737f6707782f5db15d71f303db38"
|
||||
url: "https://github.com/cake-tech/cake_backup.git"
|
||||
source: git
|
||||
version: "1.0.0+1"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_util
|
||||
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
code_builder:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: code_builder
|
||||
sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.10.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: crypto
|
||||
sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.6"
|
||||
cryptography:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cryptography
|
||||
sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
cupertino_icons:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
cw_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../cw_core"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.7"
|
||||
decimal:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: decimal
|
||||
sha256: "24a261d5d5c87e86c7651c417a5dbdf8bcd7080dd592533910e8d0505a279f21"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.3"
|
||||
encrypt:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: encrypt
|
||||
sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.3"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
ffigen:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: ffigen
|
||||
sha256: "2119b4fe3aad0db94dc9531b90283c4640a6231070e613c400b426a4da08c704"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "16.1.0"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fixnum
|
||||
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_mobx:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_mobx
|
||||
sha256: ba5e93467866a2991259dc51cffd41ef45f695c667c2b8e7b087bf24118b50fe
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
frontend_server_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: glob
|
||||
sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.3"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: graphs
|
||||
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
hive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: hive
|
||||
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.3"
|
||||
hive_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: hive_generator
|
||||
sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_multi_server
|
||||
sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.19.0"
|
||||
io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: io
|
||||
sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.7"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.5"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: logging
|
||||
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
macros:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: macros
|
||||
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2-main.4"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.16+1"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.15.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
mobx:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mobx
|
||||
sha256: bf1a90e5bcfd2851fc6984e20eef69557c65d9e4d0a88f5be4cf72c9819ce6b0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
mobx_codegen:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: mobx_codegen
|
||||
sha256: "990da80722f7d7c0017dec92040b31545d625b15d40204c36a1e63d167c73cdc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nested
|
||||
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
on_chain:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: cake-update-v2
|
||||
resolved-ref: "93440dc5126369b873ca1fccc13c3c1240b1c5c2"
|
||||
url: "https://github.com/cake-tech/on_chain.git"
|
||||
source: git
|
||||
version: "3.7.0"
|
||||
package_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_config
|
||||
sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.0"
|
||||
path_provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.15"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.9.1"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pool
|
||||
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
provider:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: provider
|
||||
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.2"
|
||||
pub_semver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pub_semver
|
||||
sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
pubspec_parse:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pubspec_parse
|
||||
sha256: "81876843eb50dc2e1e5b151792c9a985c5ed2536914115ed04e9c8528f6647b0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: quiver
|
||||
sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
rational:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: rational
|
||||
sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.3"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf
|
||||
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
shelf_web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shelf_web_socket
|
||||
sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
socks5_proxy:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: socks5_proxy
|
||||
sha256: "616818a0ea1064a4823b53c9f7eaf8da64ed82dcd51ed71371c7e54751ed5053"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
source_helper:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_helper
|
||||
sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.5"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.0"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
stream_transform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_transform
|
||||
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timing
|
||||
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
tuple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: tuple
|
||||
sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
unorm_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: unorm_dart
|
||||
sha256: "23d8bf65605401a6a32cff99435fed66ef3dab3ddcad3454059165df46496a3b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.4"
|
||||
watcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: watcher
|
||||
sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
web_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket
|
||||
sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.6"
|
||||
web_socket_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
yaml_edit:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml_edit
|
||||
sha256: fb38626579fb345ad00e674e2af3a5c9b0cc4b9bfb8fd7f7ff322c7c9e62aef5
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
sdks:
|
||||
dart: ">=3.5.0 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
84
cw_decred/pubspec.yaml
Normal file
84
cw_decred/pubspec.yaml
Normal file
|
@ -0,0 +1,84 @@
|
|||
name: cw_decred
|
||||
description: A new Flutter plugin project.
|
||||
version: 0.0.1
|
||||
publish_to: none
|
||||
author: Cake Wallet
|
||||
homepage: https://cakewallet.com
|
||||
|
||||
environment:
|
||||
sdk: '>=3.2.0-0 <4.0.0'
|
||||
flutter: ">=3.19.0"
|
||||
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cw_core:
|
||||
path: ../cw_core
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
build_runner: ^2.1.11
|
||||
build_resolvers: ^2.0.9
|
||||
mobx_codegen: ^2.0.7
|
||||
hive_generator: ^2.0.1
|
||||
ffigen: ^16.1.0
|
||||
|
||||
ffigen:
|
||||
name: libdcrwallet
|
||||
description: Bindings for dcrwallet go library.
|
||||
output: "lib/api/libdcrwallet_bindings.dart"
|
||||
headers:
|
||||
entry-points:
|
||||
- "lib/api/libdcrwallet.h"
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
# The following section is specific to Flutter packages.
|
||||
flutter:
|
||||
# This section identifies this Flutter project as a plugin project.
|
||||
# The androidPackage and pluginClass identifiers should not ordinarily
|
||||
# be modified. They are used by the tooling to maintain consistency when
|
||||
# adding or updating assets for this project.
|
||||
plugin:
|
||||
platforms:
|
||||
android:
|
||||
package: com.cakewallet.cw_decred
|
||||
pluginClass: CwDecredPlugin
|
||||
ios:
|
||||
pluginClass: CwDecredPlugin
|
||||
macos:
|
||||
pluginClass: CwDecredPlugin
|
||||
|
||||
# To add assets to your plugin package, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
#
|
||||
# For details regarding assets in packages, see
|
||||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
#
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||
|
||||
# To add custom fonts to your plugin package, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts in packages, see
|
||||
# https://flutter.dev/custom-fonts/#from-packages
|
Loading…
Add table
Add a link
Reference in a new issue