Add decred (#1938)

* decred: Add decred. (#1322)

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

Use a mock libwallet for now.

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

* change cw_decred from package to plugin

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

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

* libdcrwallet: use json payload returns

* use specific libwallet commit hash

* decred: fix Rename wallet.

---------

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

* decred: Add sync.

* decred: Add send transaction.

* decred: Fix fee estimation.

* decred: List transactions.

* decred: Add rescan.

* decred: Sign message.

* decred: Add new addr and addrs.

* decred: Add change wallet pass.

* decred: Add restore from seed.

* decred: Add watching only wallets.

* decred: Enable mainnet.

* decred: Allow using blank node address.

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

* decred: Rescan from wallet birthday.

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

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

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

-  move hasRescan method to WalletBase and implement for decred

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

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

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

* add buy and sell for decred via onramp

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

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

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

* decred: Return address with no peers.

* decred: Update pubspec.

* decred: Add verify message.

* upgrade hive_generator dep in cw_decred

* decred: Clean up code.

---------

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

* fix extracted addresses not used
fix conflicts with main

* remove print [skip ci]

* minor formatting

* fix initial migration version

* add build decred script to workflow

* install go before build decred
fix switch cases

* trial 2 to fix decred build

* re-install go

* revert build script change

* refactor/clean nodes functions

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

* Fix transactions display issues
Add missing file

* Fix unconfirmed balance not displayed
Change Wallet order
Minor cleanup

* Fix workflow

* Fix workflow

* Fix workflow

* test

* hardcode path for now

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

* Update decred build script

* Run actions on pull requests, extract commit message

* run after checkout

* add safe directory

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

* base -> head

* Do not merge main branch into pr

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

* Proper name for decred library in the build script

* Throw an error when ANDROID_HOME or ANDROID_NDK_VERSION is missing

* Fix conflicts with main

* minor code enhancement

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

* decred: Update pubspec.

* decred testnet

* decred: Add used address history.

* decred: Remove default node list.

* populate transaction history before sync begins

* decred: Add some awaits.

* decred: Fix send all.

* decred: Add clang export to build script.

* decred: Update logo colors.

* cleanup cw_decred.dart

* make decred wallet addresses selectable in receive page

* decred: Always set default addr when used.

* decred: Add back default node list.

* decred: Allow creating addresses manually.

---------

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

* minor fixes and cleanup

* minor fix, feel free to test now

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

* Add decred info card

* push missing file

* Add missing text for decred info card

* minor: change docs link [skip ci]

* decred: Update derivation info. (#2013)

* decred: Update derivation info.

* decred: Allow unsynced unused addresses.

* decred: Update dcrwallet dep to 4.3.0.

* Merge main and fix conflicts

* Merge main and fix conflicts

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

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

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

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

* Disable send button in view only decred wallets

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

* Merge main

* remove print [skip ci]

* Fix restore from QR

* minor improvement for QR restore

* minor fixes [skip ci]

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

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

---------

Signed-off-by: Philemon Ukane <ukanephilemon@gmail.com>
Co-authored-by: JoeGruffins <34998433+JoeGruffins@users.noreply.github.com>
Co-authored-by: Wisdom Arerosuoghene <wisdom.arerosuoghene@gmail.com>
Co-authored-by: Philemon Ukane <ukanephilemon@gmail.com>
Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
This commit is contained in:
Omar Hatem 2025-03-21 04:18:47 +02:00 committed by GitHub
parent 52a39e29d4
commit 0ba54fa602
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
175 changed files with 7145 additions and 1115 deletions

39
cw_decred/.gitignore vendored Normal file
View 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
View 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
View file

@ -0,0 +1,3 @@
## [0.0.1] - TODO: Add release date.
* TODO: Describe initial release.

1
cw_decred/LICENSE Normal file
View file

@ -0,0 +1 @@
TODO: Add your license here.

3
cw_decred/README.md Normal file
View file

@ -0,0 +1,3 @@
# cw_decred
TODO: Fill this out.

View 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
View file

@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx

View 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"
}

View file

@ -0,0 +1 @@
rootProject.name = 'cw_decred'

View 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>

View file

@ -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
View 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

View file

View 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)
}
}
}

View 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

View 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;
}

View 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();
}
}
}

View 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;
}
}

View 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

File diff suppressed because it is too large Load diff

View 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();
}
}

View 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;
}

View 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;
}
}

View 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);
}

View 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
View 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;
}

View 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);
}

View 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;
}

View 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();
}

View 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)
}
}
}

View 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
View 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
View 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