mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-17 05:41:29 +02:00
Delete cw_zano / cw_tron / cw_solana / cw_decred packages + all references
These chains were already disabled in availableWalletTypes (none of them
were getting created), but the source dirs and the codegen scaffolding
for them still lived in tree (~20K lines of dead Dart).
Surgery:
- Delete cw_decred/ cw_solana/ cw_tron/ cw_zano/ source trees
- Strip 5 generate*() function bodies from tool/configure.dart
(generateSolana, generateTron, generateZano, generateDecred,
generateZcash) plus their top-level imports, args, calls,
signatures, and conditional pubspec/walletTypes injection blocks.
configure.dart shrinks from 2134 to 1561 lines and dart analyze
is clean.
- Strip solana/tron secrets config + output paths from
tool/import_secrets_config.dart
- Delete scripts/{android,ios,macos}/build_decred.sh,
scripts/ios/build_zano{,_all}.sh
- Patch scripts/{android,ios,macos}/build_all.sh,
scripts/android/copy_monero_deps.sh, scripts/ios/setup.sh,
scripts/ios/gen_framework.sh to drop decred/zano steps
- Patch scripts/android/docker/build.sh + Dockerfile.final to drop the
decred Docker stage; delete scripts/android/docker/Dockerfile.decred
- Update .github/workflows/build-linux.yml comments to reflect the
remaining cw_* module list (evm, nano, bitcoin)
Note: WalletType.{solana,tron,zano,decred,zcash} enum values still live
in cw_core/lib/wallet_type.dart and there are dead switch cases in
lib/di.dart and lib/new-ui/pages/*.dart that reference them. Those
paths are unreachable (the enum values never get assigned to any wallet)
but cleaning them up is a separate hygiene pass.
This commit is contained in:
@@ -50,9 +50,9 @@ jobs:
|
||||
# IMPORTANT: don't pre-write any of the tool/.*secrets-config.json
|
||||
# files. The generator at tool/generate_secrets_config.dart has an
|
||||
# early-return if tool/.secrets-config.json already exists (lines
|
||||
# 57-63), which then ALSO skips creating the per-module configs (evm,
|
||||
# solana, nano, tron, bitcoin) — every cw_* module then fails to
|
||||
# compile with "Undefined name secrets.xxx" for dozens of keys.
|
||||
# 57-63), which then ALSO skips creating the per-module configs
|
||||
# (evm, nano, bitcoin) — every cw_* module then fails to compile
|
||||
# with "Undefined name secrets.xxx" for dozens of keys.
|
||||
#
|
||||
# Instead: run the generator first so it creates all configs from the
|
||||
# full SecretKey list (with empty defaults), then sed-inject Trocador
|
||||
@@ -93,7 +93,8 @@ jobs:
|
||||
# ---- Native crypto cores (monero_c prebuilt bundle) ------------------
|
||||
# Skips the full simplybs toolchain bootstrap. The bundle has libmonero,
|
||||
# libwownero, AND libzano .so files; CMake only installs the ones we
|
||||
# ship (monero + wownero).
|
||||
# ship (monero + wownero) — libzano is dropped at install time even
|
||||
# though monero_c builds it.
|
||||
- name: Fetch prebuilt monero_c .so bundle
|
||||
run: |
|
||||
set -x -e
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
# 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
|
||||
@@ -1,36 +0,0 @@
|
||||
# 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'
|
||||
@@ -1,3 +0,0 @@
|
||||
## [0.0.1] - TODO: Add release date.
|
||||
|
||||
* TODO: Describe initial release.
|
||||
@@ -1 +0,0 @@
|
||||
TODO: Add your license here.
|
||||
@@ -1,47 +0,0 @@
|
||||
# cw_decred
|
||||
|
||||
Decred wallet module that bridges to the native `libdcrwallet` via FFI. Provides high‑level methods to create/load wallets, sync, query balances/transactions, build and broadcast transactions, and sign/verify messages.
|
||||
|
||||
## Features
|
||||
|
||||
- FFI bindings to `libdcrwallet` with an isolate‑based request/response model.
|
||||
- Initialize, create, load, close wallets; watch‑only creation.
|
||||
- Start sync with optional peer list; query sync status and best block.
|
||||
- Query balances, list transactions and unspents, rescan from height.
|
||||
- Create signed transactions and broadcast raw transactions.
|
||||
- Export wallet seed; change wallet password.
|
||||
- Address management (new external address, default pubkey, address lists).
|
||||
- Message signing and verification.
|
||||
|
||||
## Getting started
|
||||
|
||||
Ensure the platform library is available:
|
||||
|
||||
- Android/Linux: `libdcrwallet.so`
|
||||
- Apple: embedded `cw_decred.framework/cw_decred`
|
||||
|
||||
Initialize and load a wallet:
|
||||
|
||||
```dart
|
||||
final lib = await Libwallet.spawn();
|
||||
await lib.initLibdcrwallet('', 'info');
|
||||
await lib.loadWallet(jsonEncode({ /* libdcrwallet config */ }));
|
||||
await lib.startSync('wallet.db', '');
|
||||
final status = await lib.syncStatus('wallet.db');
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Create, sign, and broadcast a transaction:
|
||||
|
||||
```dart
|
||||
final signed = await lib.createSignedTransaction('wallet.db', jsonEncode({
|
||||
// inputs/outputs and policy for libdcrwallet
|
||||
}));
|
||||
final txid = await lib.sendRawTransaction('wallet.db', signed);
|
||||
```
|
||||
|
||||
## Additional information
|
||||
|
||||
- See `lib/api/` for the isolate wrapper (`libdcrwallet.dart`) and low‑level bindings.
|
||||
- Errors are surfaced via the `PayloadResult` struct; some calls support `throwOnError` in higher‑level wrappers.
|
||||
@@ -1,4 +0,0 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
@@ -1,9 +0,0 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.cxx
|
||||
@@ -1,59 +0,0 @@
|
||||
group 'com.cakewallet.cw_decred'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '2.0.21'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:8.7.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 33
|
||||
|
||||
if (project.android.hasProperty("namespace")) {
|
||||
namespace 'com.cakewallet.cw_decred'
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_17
|
||||
targetCompatibility JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '17'
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
java.srcDirs += 'src/main/kotlin'
|
||||
jniLibs.srcDirs 'libs' // contains libdcrwallet.so shared libraries
|
||||
}
|
||||
}
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
rootProject.name = 'cw_decred'
|
||||
@@ -1,4 +0,0 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.cakewallet.cw_decred">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
@@ -1,35 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
description: This file stores settings for Dart & Flutter DevTools.
|
||||
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||
extensions:
|
||||
@@ -1,38 +0,0 @@
|
||||
.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
|
||||
@@ -1,19 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
@@ -1,26 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,702 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi';
|
||||
import 'dart:io';
|
||||
import 'dart:async';
|
||||
import 'dart:isolate';
|
||||
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 level = args["level"] ?? "";
|
||||
final cLogDir = logDir.toCString();
|
||||
final cLevel = level.toCString();
|
||||
executePayloadFn(
|
||||
fn: () => dcrwalletApi.initialize(cLogDir, cLevel),
|
||||
ptrsToFree: [cLogDir, cLevel],
|
||||
);
|
||||
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. An empty string can be used to log to stdout and create no log files.
|
||||
Future<void> initLibdcrwallet(String logDir, String level) async {
|
||||
if (_closed) throw StateError('Closed');
|
||||
final completer = Completer<Object?>.sync();
|
||||
final id = _idCounter++;
|
||||
_activeRequests[id] = completer;
|
||||
final req = {
|
||||
"method": "initlibdcrwallet",
|
||||
"logdir": logDir,
|
||||
"level": level,
|
||||
};
|
||||
_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, {bool throwOnError = false}) 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;
|
||||
try {
|
||||
return jsonDecode(res.payload);
|
||||
} catch (_) {
|
||||
if (throwOnError) {
|
||||
rethrow;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import 'package:cw_decred/amount_format.dart';
|
||||
import 'package:cw_core/balance.dart';
|
||||
|
||||
class DecredBalance extends Balance {
|
||||
DecredBalance({required this.confirmed, required this.unconfirmed, required int frozen})
|
||||
: _frozen = frozen, super.fromInt(confirmed, unconfirmed);
|
||||
|
||||
factory DecredBalance.zero() => DecredBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
|
||||
|
||||
final int confirmed;
|
||||
final int unconfirmed;
|
||||
final int _frozen;
|
||||
BigInt get frozen => BigInt.from(_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;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,42 +0,0 @@
|
||||
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 => "$feeFormattedValue DCR";
|
||||
|
||||
@override
|
||||
String get feeFormattedValue => decredAmountToString(amount: fee);
|
||||
|
||||
@override
|
||||
String get hex => rawHex;
|
||||
|
||||
@override
|
||||
Future<void> commit() async {
|
||||
return send();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, String>> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
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);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:flutter/foundation.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:
|
||||
if (kDebugMode) {
|
||||
throw Exception('Unexpected token: $raw for DecredTransactionPriority deserialize');
|
||||
}
|
||||
return medium;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,770 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:cw_core/exceptions.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_decred/amount_format.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/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, DerivationInfo derivationInfo, String password, Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
Libwallet libwallet, Function() closeLibwallet)
|
||||
: _password = password,
|
||||
_libwallet = libwallet,
|
||||
_closeLibwallet = closeLibwallet,
|
||||
this.syncStatus = NotConnectedSyncStatus(),
|
||||
this.unspentCoinsInfo = unspentCoinsInfo,
|
||||
this.watchingOnly =
|
||||
derivationInfo.derivationPath == DecredWalletService.pubkeyRestorePath ||
|
||||
derivationInfo.derivationPath ==
|
||||
DecredWalletService.pubkeyRestorePathTestnet,
|
||||
this.balance = ObservableMap.of({CryptoCurrency.dcr: DecredBalance.zero()}),
|
||||
this.isTestnet = derivationInfo.derivationPath ==
|
||||
DecredWalletService.seedRestorePathTestnet ||
|
||||
derivationInfo.derivationPath ==
|
||||
DecredWalletService.pubkeyRestorePathTestnet,
|
||||
super(walletInfo, derivationInfo) {
|
||||
walletAddresses = DecredWalletAddresses(walletInfo, libwallet, isTestnet);
|
||||
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;
|
||||
}
|
||||
|
||||
@override
|
||||
String formatCryptoAmount(String amount) => decredAmountToString(amount: int.parse(amount));
|
||||
|
||||
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.isEmpty ? "{}" : 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 dirPath = await pathForWalletDir(name: walletInfo.name, type: WalletType.decred);
|
||||
final config = {
|
||||
"name": walletInfo.name,
|
||||
"datadir": 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).round();
|
||||
}
|
||||
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).round();
|
||||
}
|
||||
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 {
|
||||
try {
|
||||
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).round().abs();
|
||||
final feeDouble = d["fee"] ?? 0.0;
|
||||
final fee = (feeDouble * 1e8).round().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;
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
Future<bool> checkNodeHealth() async => await checkSync();
|
||||
|
||||
@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";
|
||||
}
|
||||
|
||||
final sourceDir = Directory(currentDirPath);
|
||||
final targetDir = Directory(newDirPath);
|
||||
|
||||
if (!targetDir.existsSync()) {
|
||||
await targetDir.create(recursive: true);
|
||||
}
|
||||
|
||||
await for (final entity in sourceDir.list(recursive: true)) {
|
||||
final relativePath = entity.path.substring(sourceDir.path.length + 1);
|
||||
final targetPath = p.join(targetDir.path, relativePath);
|
||||
|
||||
if (entity is File) {
|
||||
await entity.rename(targetPath);
|
||||
} else if (entity is Directory) {
|
||||
await Directory(targetPath).create(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
await sourceDir.delete(recursive: true);
|
||||
}
|
||||
|
||||
@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 {
|
||||
try {
|
||||
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).round().abs();
|
||||
final utxo = Unspent(d["address"] ?? "", d["txid"] ?? "", amount, d["vout"] ?? 0, null);
|
||||
utxo.isChange = d["ischange"] ?? false;
|
||||
unspents.add(utxo);
|
||||
}
|
||||
_unspents = unspents;
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
try {
|
||||
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;
|
||||
} on FormatException catch (_) {
|
||||
return 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;
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'package:cw_core/payment_uris.dart';
|
||||
import 'package:cw_core/receive_page_option.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:mobx/mobx.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(super.walletInfo, this._libwallet, super.isTestnet);
|
||||
|
||||
final Libwallet _libwallet;
|
||||
String _currentAddr = '';
|
||||
|
||||
@observable
|
||||
bool isEnabledAutoGenerateSubaddress = true;
|
||||
|
||||
@observable
|
||||
String selectedAddr = '';
|
||||
|
||||
@override
|
||||
@computed
|
||||
String get address => selectedAddr;
|
||||
|
||||
@override
|
||||
set address(value) => selectedAddr = value;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
addressesMap = await walletInfo.getAddresses();
|
||||
addressInfos = await walletInfo.getAddressInfos();
|
||||
usedAddresses = await walletInfo.getUsedAddresses();
|
||||
manualAddresses = await walletInfo.getManualAddresses();
|
||||
hiddenAddresses = await walletInfo.getHiddenAddresses();
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAddressesInBox() async {
|
||||
final addrs = await _libAddresses();
|
||||
final allAddrs = 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(
|
||||
WalletInfoAddressInfo(
|
||||
walletInfoId: walletInfo.internalId,
|
||||
mapKey: 0,
|
||||
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<WalletInfoAddressInfo> getAddressInfos() {
|
||||
if (addressInfos.containsKey(0)) return addressInfos[0]!;
|
||||
|
||||
return <WalletInfoAddressInfo>[];
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
try {
|
||||
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 _LibAddresses(usedAddrs, unusedAddrs, index);
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
return _LibAddresses([], [], 0);
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
WalletInfoAddressInfo(
|
||||
walletInfoId: walletInfo.internalId,
|
||||
mapKey: 0,
|
||||
address: addr,
|
||||
label: label,
|
||||
accountIndex: 0,
|
||||
),
|
||||
);
|
||||
}
|
||||
selectedAddr = addr;
|
||||
await saveAddressesInBox();
|
||||
}
|
||||
|
||||
@override
|
||||
List<ReceivePageOption> get receivePageOptions => isTestnet
|
||||
? [
|
||||
ReceivePageOption.testnet,
|
||||
...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
|
||||
]
|
||||
: ReceivePageOptions;
|
||||
|
||||
@override
|
||||
PaymentURI getPaymentUri(String amount) => DecredURI(address: address, amount: amount);
|
||||
}
|
||||
|
||||
class _LibAddresses {
|
||||
final List<String> usedAddrs, unusedAddrs;
|
||||
final int firstUnusedAddrIndex;
|
||||
|
||||
_LibAddresses(this.usedAddrs, this.unusedAddrs, this.firstUnusedAddrIndex);
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
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:path/path.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.unspentCoinsInfoSource);
|
||||
|
||||
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";
|
||||
static Libwallet? libwallet;
|
||||
|
||||
Future<void> init() async {
|
||||
if (libwallet != null) {
|
||||
return;
|
||||
}
|
||||
libwallet = await Libwallet.spawn();
|
||||
// Init logging with no directory to force printing to stdout and only
|
||||
// print ERROR level logs.
|
||||
libwallet!.initLibdcrwallet("", "err");
|
||||
}
|
||||
|
||||
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 dirPath = await pathForWalletDir(name: credentials.walletInfo!.name, type: getType());
|
||||
final network = isTestnet == true ? testnet : mainnet;
|
||||
final config = {
|
||||
"name": credentials.walletInfo!.name,
|
||||
"datadir": dirPath,
|
||||
"pass": credentials.password!,
|
||||
"net": network,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await libwallet!.createWallet(jsonEncode(config));
|
||||
final di = await credentials.walletInfo!.getDerivationInfo();
|
||||
di.derivationPath = isTestnet == true ? seedRestorePathTestnet : seedRestorePath;
|
||||
await di.save();
|
||||
credentials.walletInfo!.save();
|
||||
credentials.walletInfo!.network = network;
|
||||
// ios will move our wallet directory when updating. Since we must
|
||||
// recalculate the new path every time we open the wallet, ensure this path
|
||||
// is not used. An older wallet will have a directory here which is a
|
||||
// condition for moving the wallet when opening, so this must be kept blank
|
||||
// going forward.
|
||||
credentials.walletInfo!.dirPath = "";
|
||||
credentials.walletInfo!.path = "";
|
||||
final wallet = DecredWallet(credentials.walletInfo!, di, credentials.password!,
|
||||
this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
void copyDirectorySync(Directory source, Directory destination) {
|
||||
/// create destination folder if not exist
|
||||
if (!destination.existsSync()) {
|
||||
destination.createSync(recursive: true);
|
||||
}
|
||||
|
||||
/// get all files from source (recursive: false is important here)
|
||||
source.listSync(recursive: false).forEach((entity) {
|
||||
final newPath = destination.path + Platform.pathSeparator + basename(entity.path);
|
||||
if (entity is File) {
|
||||
entity.rename(newPath);
|
||||
} else if (entity is Directory) {
|
||||
copyDirectorySync(entity, Directory(newPath));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> moveWallet(String fromPath, String toPath) async {
|
||||
final oldWalletDir = new Directory(fromPath);
|
||||
final newWalletDir = new Directory(toPath);
|
||||
copyDirectorySync(oldWalletDir, newWalletDir);
|
||||
// It would be ideal to delete the old directory here, but ios will error
|
||||
// sometimes with "OS Error: No such file or directory, errno = 2" even
|
||||
// after checking if it exists.
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DecredWallet> openWallet(String name, String password) async {
|
||||
final walletInfo = await WalletInfo.get(name, getType());
|
||||
if (walletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
final di = await walletInfo.getDerivationInfo();
|
||||
if (walletInfo.network == null || walletInfo.network == "") {
|
||||
walletInfo.network = di.derivationPath == seedRestorePathTestnet ||
|
||||
di.derivationPath == pubkeyRestorePathTestnet
|
||||
? testnet
|
||||
: mainnet;
|
||||
walletInfo.save();
|
||||
}
|
||||
|
||||
await this.init();
|
||||
|
||||
// Cake wallet version 4.27.0 and earlier gave a wallet dir that did not
|
||||
// match the name. Move those to the correct place.
|
||||
final dirPath = await pathForWalletDir(name: name, type: getType());
|
||||
if (walletInfo.path != "") {
|
||||
// On ios the stored dir no longer exists. We can only trust the basename.
|
||||
// dirPath may already be updated and lost the basename, so look at path.
|
||||
final randomBasename = basename(walletInfo.path);
|
||||
final oldDir = await pathForWalletDir(name: randomBasename, type: getType());
|
||||
if (oldDir != dirPath) {
|
||||
await this.moveWallet(oldDir, dirPath);
|
||||
}
|
||||
// Clear the path so this does not trigger again.
|
||||
walletInfo.dirPath = "";
|
||||
walletInfo.path = "";
|
||||
await walletInfo.save();
|
||||
}
|
||||
|
||||
final config = {
|
||||
"name": name,
|
||||
"datadir": dirPath,
|
||||
"net": walletInfo.network,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await libwallet!.loadWallet(jsonEncode(config));
|
||||
final wallet =
|
||||
DecredWallet(walletInfo, di, 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 = await WalletInfo.get(wallet, getType());
|
||||
if (walletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
await WalletInfo.delete(walletInfo);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> rename(String currentName, String password, String newName) async {
|
||||
final currentWalletInfo = await WalletInfo.get(currentName, getType());
|
||||
if (currentWalletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
final di = await currentWalletInfo.getDerivationInfo();
|
||||
final network = di.derivationPath == seedRestorePathTestnet ||
|
||||
di.derivationPath == pubkeyRestorePathTestnet
|
||||
? testnet
|
||||
: mainnet;
|
||||
currentWalletInfo.network = network;
|
||||
currentWalletInfo.save();
|
||||
if (libwallet == null) {
|
||||
libwallet = await Libwallet.spawn();
|
||||
libwallet!.initLibdcrwallet("", "err");
|
||||
}
|
||||
final currentWallet = DecredWallet(
|
||||
currentWalletInfo, di, password, this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
|
||||
final newWalletInfo = currentWalletInfo;
|
||||
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||
newWalletInfo.name = newName;
|
||||
newWalletInfo.dirPath = "";
|
||||
newWalletInfo.path = "";
|
||||
|
||||
await newWalletInfo.save();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DecredWallet> restoreFromSeed(DecredRestoreWalletFromSeedCredentials credentials,
|
||||
{bool? isTestnet}) async {
|
||||
await this.init();
|
||||
final network = isTestnet == true ? testnet : mainnet;
|
||||
final dirPath = await pathForWalletDir(name: credentials.walletInfo!.name, type: getType());
|
||||
final config = {
|
||||
"name": credentials.walletInfo!.name,
|
||||
"datadir": dirPath,
|
||||
"pass": credentials.password!,
|
||||
"mnemonic": credentials.mnemonic,
|
||||
"net": network,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await libwallet!.createWallet(jsonEncode(config));
|
||||
final di = await credentials.walletInfo!.getDerivationInfo();
|
||||
di.derivationPath = isTestnet == true ? seedRestorePathTestnet : seedRestorePath;
|
||||
await di.save();
|
||||
credentials.walletInfo!.network = network;
|
||||
credentials.walletInfo!.dirPath = "";
|
||||
credentials.walletInfo!.path = "";
|
||||
final wallet = DecredWallet(credentials.walletInfo!, di, 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 network = isTestnet == true ? testnet : mainnet;
|
||||
final dirPath = await pathForWalletDir(name: credentials.walletInfo!.name, type: getType());
|
||||
final config = {
|
||||
"name": credentials.walletInfo!.name,
|
||||
"datadir": dirPath,
|
||||
"pubkey": credentials.pubkey,
|
||||
"net": network,
|
||||
"unsyncedaddrs": true,
|
||||
};
|
||||
await libwallet!.createWatchOnlyWallet(jsonEncode(config));
|
||||
final di = await credentials.walletInfo!.getDerivationInfo();
|
||||
di.derivationPath = isTestnet == true ? pubkeyRestorePathTestnet : pubkeyRestorePath;
|
||||
await di.save();
|
||||
credentials.walletInfo!.network = network;
|
||||
credentials.walletInfo!.dirPath = "";
|
||||
credentials.walletInfo!.path = "";
|
||||
final wallet = DecredWallet(credentials.walletInfo!, di, credentials.password!,
|
||||
this.unspentCoinsInfoSource, libwallet!, closeLibwallet);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<DecredWallet> restoreFromHardwareWallet(
|
||||
DecredRestoreWalletFromHardwareCredentials credentials) async =>
|
||||
throw UnimplementedError();
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
@@ -1,950 +0,0 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
_fe_analyzer_shared:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "76.0.0"
|
||||
_macros:
|
||||
dependency: transitive
|
||||
description: dart
|
||||
source: sdk
|
||||
version: "0.3.3"
|
||||
analyzer:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.11.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: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.0"
|
||||
bech32:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: "05755063b593aa6cca0a4820a318e0ce17de6192"
|
||||
url: "https://github.com/cake-tech/bech32.git"
|
||||
source: git
|
||||
version: "0.2.2"
|
||||
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: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
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: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.4"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.15"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.0"
|
||||
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: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.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: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
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: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
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: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
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: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.9"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.9"
|
||||
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: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3-main.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
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: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.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: "096865a8c6b89c260beadfec04f7e184c40a3273"
|
||||
resolved-ref: "096865a8c6b89c260beadfec04f7e184c40a3273"
|
||||
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: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
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.0"
|
||||
socks5_proxy:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
|
||||
resolved-ref: "27ad7c2efae8d7460325c74b90f660085cbd0685"
|
||||
url: "https://github.com/LacticWhale/socks_dart"
|
||||
source: git
|
||||
version: "2.1.0"
|
||||
socks_socket:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "."
|
||||
ref: e6232c53c1595469931ababa878759a067c02e94
|
||||
resolved-ref: e6232c53c1595469931ababa878759a067c02e94
|
||||
url: "https://github.com/sneurlax/socks_socket"
|
||||
source: git
|
||||
version: "1.1.1"
|
||||
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: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
sqflite:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
sqflite_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_android
|
||||
sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.4+6"
|
||||
sqflite_common_ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common_ffi
|
||||
sha256: "883dd810b2b49e6e8c3b980df1829ef550a94e3f87deab5d864917d27ca6bf36"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.4+4"
|
||||
sqflite_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_darwin
|
||||
sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1+1"
|
||||
sqflite_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_platform_interface
|
||||
sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
sqlite3:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlite3
|
||||
sha256: f393d92c71bdcc118d6203d07c991b9be0f84b1a6f89dd4f7eed348131329924
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.9.0"
|
||||
sqlite3_flutter_libs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqlite3_flutter_libs
|
||||
sha256: "69c80d812ef2500202ebd22002cbfc1b6565e9ff56b2f971e757fac5d42294df"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.40"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
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: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
synchronized:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: synchronized
|
||||
sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.0+3"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
timing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: timing
|
||||
sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
torch_dart:
|
||||
dependency: transitive
|
||||
description:
|
||||
path: "../scripts/torch_dart"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
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: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.0.0"
|
||||
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.7.0-0 <4.0.0"
|
||||
flutter: ">=3.24.0"
|
||||
@@ -1,83 +0,0 @@
|
||||
name: cw_decred
|
||||
description: A new Flutter plugin project.
|
||||
version: 0.0.1
|
||||
publish_to: none
|
||||
author: Hash Wallet
|
||||
homepage: https://github.com/Such-Software/hash-wallet
|
||||
|
||||
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.4.15
|
||||
build_resolvers: ^2.4.4
|
||||
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
|
||||
@@ -1,30 +0,0 @@
|
||||
# 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/
|
||||
@@ -1,10 +0,0 @@
|
||||
# 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 and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
channel: stable
|
||||
|
||||
project_type: package
|
||||
@@ -1,3 +0,0 @@
|
||||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
||||
@@ -1 +0,0 @@
|
||||
TODO: Add your license here.
|
||||
@@ -1,63 +0,0 @@
|
||||
## cw_solana
|
||||
|
||||
Solana wallet module for Cake Wallet. Provides native SOL and SPL token support built on `on_chain/solana` with high-throughput RPC usage and safe transaction parsing.
|
||||
|
||||
### Features
|
||||
|
||||
- Connect to Solana RPC (Ankr/Chainstack/custom) via `SolanaRPC` over HTTP.
|
||||
- Fetch SOL balances and aggregate SPL token balances across accounts.
|
||||
- Parse and stream native and SPL token transactions (filters ATA-only and spam-like micro txs).
|
||||
- Estimate fees per compiled message and enforce rent-exemption checks.
|
||||
- Create/sign/broadcast SOL and SPL transfers; auto-create recipient ATA when necessary.
|
||||
- Manage SPL tokens; fetch on-chain metadata (symbol/name) for unknown mints.
|
||||
- Sign and verify messages.
|
||||
- Node health checks for SOL and a known SPL token (USDC).
|
||||
|
||||
### Getting started
|
||||
|
||||
If you use hosted RPC providers, add a secrets file for keys (optional unless using those hosts):
|
||||
|
||||
```dart
|
||||
// cw_solana/lib/.secrets.g.dart (DO NOT COMMIT)
|
||||
const String ankrApiKey = 'YOUR_ANKR_KEY';
|
||||
const String chainStackApiKey = 'YOUR_CHAINSTACK_KEY';
|
||||
```
|
||||
|
||||
Connect and sync:
|
||||
|
||||
```dart
|
||||
final service = SolanaWalletService(walletInfoBox, true);
|
||||
final wallet = await service.create(SolanaNewWalletCredentials(name: 'My SOL', password: 'secret'));
|
||||
await wallet.connectToNode(node: Node(uriRaw: 'api.mainnet-beta.solana.com', isSSL: true));
|
||||
await wallet.startSync();
|
||||
final sol = wallet.balance[CryptoCurrency.sol]?.balance;
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
Send SOL:
|
||||
|
||||
```dart
|
||||
final pending = await wallet.createTransaction(
|
||||
SolanaTransactionCredentials.single(
|
||||
address: 'SoL...',
|
||||
cryptoAmount: '0.05',
|
||||
currency: CryptoCurrency.sol,
|
||||
),
|
||||
);
|
||||
final sig = await pending.commit();
|
||||
```
|
||||
|
||||
Add an SPL token by mint:
|
||||
|
||||
```dart
|
||||
final token = await wallet.getSPLToken('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v'); // USDC
|
||||
if (token != null) {
|
||||
await wallet.addSPLToken(token);
|
||||
}
|
||||
```
|
||||
|
||||
### Additional information
|
||||
|
||||
- When using `rpc.ankr.com` or `solana-mainnet.core.chainstack.com`, the client reads API keys from `.secrets.g.dart`.
|
||||
- See `lib/` for APIs: `SolanaWalletClient`, `SolanaWallet`, `SolanaWalletService`, and credential types.
|
||||
@@ -1,4 +0,0 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
@@ -1,7 +0,0 @@
|
||||
library cw_solana;
|
||||
|
||||
/// A Calculator.
|
||||
class Calculator {
|
||||
/// Returns [value] plus 1.
|
||||
int addOne(int value) => value + 1;
|
||||
}
|
||||
@@ -1,703 +0,0 @@
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/spl_token.dart';
|
||||
|
||||
class DefaultSPLTokens {
|
||||
final List<SPLToken> _defaultTokens = [
|
||||
SPLToken(
|
||||
name: 'USDT Tether',
|
||||
symbol: 'USDT',
|
||||
mintAddress: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
|
||||
decimal: 6,
|
||||
mint: 'usdtsol',
|
||||
enabled: true,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'USD Coin',
|
||||
symbol: 'USDC',
|
||||
mintAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
|
||||
decimal: 6,
|
||||
mint: 'usdcsol',
|
||||
enabled: true,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Bonk',
|
||||
symbol: 'Bonk',
|
||||
mintAddress: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263',
|
||||
decimal: 5,
|
||||
mint: 'Bonk',
|
||||
iconPath: 'assets/images/bonk_icon.png',
|
||||
enabled: false,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Raydium',
|
||||
symbol: 'RAY',
|
||||
mintAddress: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R',
|
||||
decimal: 6,
|
||||
mint: 'ray',
|
||||
iconPath: 'assets/images/ray_icon.png',
|
||||
enabled: false,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Wrapped Ethereum (Sollet)',
|
||||
symbol: 'soETH',
|
||||
mintAddress: '2FPyTwcZLUg1MDrwsyoP4D6s1tM7hAkHYRjkNb5w6Pxk',
|
||||
decimal: 6,
|
||||
mint: 'soEth',
|
||||
iconPath: 'assets/new-ui/crypto_full_icons/ethereum.svg',
|
||||
enabled: false,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Wrapped SOL',
|
||||
symbol: 'WSOL',
|
||||
mintAddress: 'So11111111111111111111111111111111111111112',
|
||||
decimal: 9,
|
||||
mint: 'WSOL',
|
||||
iconPath: 'assets/new-ui/crypto_full_icons/solana.svg',
|
||||
enabled: false,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Wrapped Bitcoin (Sollet)',
|
||||
symbol: 'BTC',
|
||||
mintAddress: '9n4nbM75f5Ui33ZbPYXn59EwSgE8CGsHtAeTH5YFeJ9E',
|
||||
decimal: 6,
|
||||
mint: 'btcsol',
|
||||
iconPath: 'assets/new-ui/crypto_full_icons/bitcoin.svg',
|
||||
enabled: false,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Helium Network Token',
|
||||
symbol: 'HNT',
|
||||
mintAddress: 'hntyVP6YFm1Hg25TN9WGLqM12b8TQmcknKrdu1oxWux',
|
||||
decimal: 8,
|
||||
mint: 'hnt',
|
||||
iconPath: 'assets/images/hnt_icon.png',
|
||||
enabled: false,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Pyth Network',
|
||||
symbol: 'PYTH',
|
||||
mintAddress: 'HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3',
|
||||
decimal: 6,
|
||||
mint: 'pyth',
|
||||
enabled: false,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'GMT',
|
||||
symbol: 'GMT',
|
||||
mintAddress: '7i5KKsX2weiTkry7jA4ZwSuXGhs5eJBEjY8vVxR4pfRx',
|
||||
decimal: 6,
|
||||
mint: 'ray',
|
||||
iconPath: 'assets/images/gmt_icon.png',
|
||||
enabled: false,
|
||||
),
|
||||
SPLToken(
|
||||
name: 'AvocadoCoin',
|
||||
symbol: 'AVDO',
|
||||
mintAddress: 'EE5L8cMU4itTsCSuor7NLK6RZx6JhsBe8GGV3oaAHm3P',
|
||||
decimal: 8,
|
||||
mint: 'avdo',
|
||||
iconPath: 'assets/images/avdo_icon.png',
|
||||
enabled: false,
|
||||
),
|
||||
SPLToken(
|
||||
name: "Tether Gold",
|
||||
symbol: "XAUT0",
|
||||
mintAddress: "AymATz4TCL9sWNEEV9Kvyz45CHVhDZ6kUgjTJPzLpU9P",
|
||||
decimal: 6,
|
||||
mint: 'xaut0',
|
||||
enabled: false,
|
||||
iconPath: "assets/images/xau_sol.png",
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Abbott xStock',
|
||||
symbol: 'ABTx',
|
||||
mintAddress: 'XsHtf5RpxsQ7jeJ9ivNewouZKJHbPxhPoEy6yYvULr7',
|
||||
decimal: 8,
|
||||
mint: 'abtx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/abtx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'AbbVie xStock',
|
||||
symbol: 'ABBVx',
|
||||
mintAddress: 'XswbinNKyPmzTa5CskMbCPvMW6G5CMnZXZEeQSSQoie',
|
||||
decimal: 8,
|
||||
mint: 'abbvx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/abbv.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Accenture xStock',
|
||||
symbol: 'ACNx',
|
||||
mintAddress: 'Xs5UJzmCRQ8DWZjskExdSQDnbE6iLkRu2jjrRAB1JSU',
|
||||
decimal: 8,
|
||||
mint: 'acnx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/acnx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Alphabet xStock',
|
||||
symbol: 'GOOGLx',
|
||||
mintAddress: 'XsCPL9dNWBMvFtTmwcCA5v3xWPSMEBCszbQdiLLq6aN',
|
||||
decimal: 8,
|
||||
mint: 'googlx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/googlx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Amazon xStock',
|
||||
symbol: 'AMZNx',
|
||||
mintAddress: 'Xs3eBt7uRfJX8QUs4suhyU8p2M6DoUDrJyWBa8LLZsg',
|
||||
decimal: 8,
|
||||
mint: 'amznx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/amznx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Amber xStock',
|
||||
symbol: 'AMBRx',
|
||||
mintAddress: 'XsaQTCgebC2KPbf27KUhdv5JFvHhQ4GDAPURwrEhAzb',
|
||||
decimal: 8,
|
||||
mint: 'ambrx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/ambrx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Apple xStock',
|
||||
symbol: 'AAPLx',
|
||||
mintAddress: 'XsbEhLAtcf6HdfpFZ5xEMdqW8nfAvcsP5bdudRLJzJp',
|
||||
decimal: 8,
|
||||
mint: 'aaplx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/apple.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'AppLovin xStock',
|
||||
symbol: 'APPx',
|
||||
mintAddress: 'XsPdAVBi8Zc1xvv53k4JcMrQaEDTgkGqKYeh7AYgPHV',
|
||||
decimal: 8,
|
||||
mint: 'appx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/appx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'AstraZeneca xStock',
|
||||
symbol: 'AZNx',
|
||||
mintAddress: 'Xs3ZFkPYT2BN7qBMqf1j1bfTeTm1rFzEFSsQ1z3wAKU',
|
||||
decimal: 8,
|
||||
mint: 'aznx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/aznx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Bank of America xStock',
|
||||
symbol: 'BACx',
|
||||
mintAddress: 'XswsQk4duEQmCbGzfqUUWYmi7pV7xpJ9eEmLHXCaEQP',
|
||||
decimal: 8,
|
||||
mint: 'bacx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/bacx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Berkshire Hathaway xStock',
|
||||
symbol: 'BRK.Bx',
|
||||
mintAddress: 'Xs6B6zawENwAbWVi7w92rjazLuAr5Az59qgWKcNb45x',
|
||||
decimal: 8,
|
||||
mint: 'brkbx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/brkbx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Broadcom xStock',
|
||||
symbol: 'AVGOx',
|
||||
mintAddress: 'XsgSaSvNSqLTtFuyWPBhK9196Xb9Bbdyjj4fH3cPJGo',
|
||||
decimal: 8,
|
||||
mint: 'avgox',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/avgox.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Chevron xStock',
|
||||
symbol: 'CVXx',
|
||||
mintAddress: 'XsNNMt7WTNA2sV3jrb1NNfNgapxRF5i4i6GcnTRRHts',
|
||||
decimal: 8,
|
||||
mint: 'cvxx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/cvxx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Circle xStock',
|
||||
symbol: 'CRCLx',
|
||||
mintAddress: 'XsueG8BtpquVJX9LVLLEGuViXUungE6WmK5YZ3p3bd1',
|
||||
decimal: 8,
|
||||
mint: 'crclx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/crclx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Cisco xStock',
|
||||
symbol: 'CSCOx',
|
||||
mintAddress: 'Xsr3pdLQyXvDJBFgpR5nexCEZwXvigb8wbPYp4YoNFf',
|
||||
decimal: 8,
|
||||
mint: 'cscx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/cscox.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Coca-Cola xStock',
|
||||
symbol: 'KOx',
|
||||
mintAddress: 'XsaBXg8dU5cPM6ehmVctMkVqoiRG2ZjMo1cyBJ3AykQ',
|
||||
decimal: 8,
|
||||
mint: 'kox',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/kox.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Coinbase xStock',
|
||||
symbol: 'COINx',
|
||||
mintAddress: 'Xs7ZdzSHLU9ftNJsii5fCeJhoRWSC32SQGzGQtePxNu',
|
||||
decimal: 8,
|
||||
mint: 'coinx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/coinx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Comcast xStock',
|
||||
symbol: 'CMCSAx',
|
||||
mintAddress: 'XsvKCaNsxg2GN8jjUmq71qukMJr7Q1c5R2Mk9P8kcS8',
|
||||
decimal: 8,
|
||||
mint: 'cmcsax',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/cmcsax.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'CrowdStrike xStock',
|
||||
symbol: 'CRWDx',
|
||||
mintAddress: 'Xs7xXqkcK7K8urEqGg52SECi79dRp2cEKKuYjUePYDw',
|
||||
decimal: 8,
|
||||
mint: 'crwdx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/crwdx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Danaher xStock',
|
||||
symbol: 'DHRx',
|
||||
mintAddress: 'Xseo8tgCZfkHxWS9xbFYeKFyMSbWEvZGFV1Gh53GtCV',
|
||||
decimal: 8,
|
||||
mint: 'dhrx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/dhrx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'DFDV xStock',
|
||||
symbol: 'DFDVx',
|
||||
mintAddress: 'Xs2yquAgsHByNzx68WJC55WHjHBvG9JsMB7CWjTLyPy',
|
||||
decimal: 8,
|
||||
mint: 'dfdvx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/dfdvx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Eli Lilly xStock',
|
||||
symbol: 'LLYx',
|
||||
mintAddress: 'Xsnuv4omNoHozR6EEW5mXkw8Nrny5rB3jVfLqi6gKMH',
|
||||
decimal: 8,
|
||||
mint: 'llyx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/llyx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Exxon Mobil xStock',
|
||||
symbol: 'XOMx',
|
||||
mintAddress: 'XsaHND8sHyfMfsWPj6kSdd5VwvCayZvjYgKmmcNL5qh',
|
||||
decimal: 8,
|
||||
mint: 'xomx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/xomx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Gamestop xStock',
|
||||
symbol: 'GMEx',
|
||||
mintAddress: 'Xsf9mBktVB9BSU5kf4nHxPq5hCBJ2j2ui3ecFGxPRGc',
|
||||
decimal: 8,
|
||||
mint: 'gmex',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/gmex.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Gold xStock',
|
||||
symbol: 'GLDx',
|
||||
mintAddress: 'Xsv9hRk1z5ystj9MhnA7Lq4vjSsLwzL2nxrwmwtD3re',
|
||||
decimal: 8,
|
||||
mint: 'gldx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/gldx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Goldman Sachs xStock',
|
||||
symbol: 'GSx',
|
||||
mintAddress: 'XsgaUyp4jd1fNBCxgtTKkW64xnnhQcvgaxzsbAq5ZD1',
|
||||
decimal: 8,
|
||||
mint: 'gsx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/gsx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Home Depot xStock',
|
||||
symbol: 'HDx',
|
||||
mintAddress: 'XszjVtyhowGjSC5odCqBpW1CtXXwXjYokymrk7fGKD3',
|
||||
decimal: 8,
|
||||
mint: 'hdx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/hdx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Honeywell xStock',
|
||||
symbol: 'HONx',
|
||||
mintAddress: 'XsRbLZthfABAPAfumWNEJhPyiKDW6TvDVeAeW7oKqA2',
|
||||
decimal: 8,
|
||||
mint: 'honx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/honx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Intel xStock',
|
||||
symbol: 'INTCx',
|
||||
mintAddress: 'XshPgPdXFRWB8tP1j82rebb2Q9rPgGX37RuqzohmArM',
|
||||
decimal: 8,
|
||||
mint: 'intcx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/intcx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'International Business Machines xStock',
|
||||
symbol: 'IBMx',
|
||||
mintAddress: 'XspwhyYPdWVM8XBHZnpS9hgyag9MKjLRyE3tVfmCbSr',
|
||||
decimal: 8,
|
||||
mint: 'ibmx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/ibmx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Johnson & Johnson xStock',
|
||||
symbol: 'JNJx',
|
||||
mintAddress: 'XsGVi5eo1Dh2zUpic4qACcjuWGjNv8GCt3dm5XcX6Dn',
|
||||
decimal: 8,
|
||||
mint: 'jnjx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/jnjx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'JPMorgan Chase xStock',
|
||||
symbol: 'JPMx',
|
||||
mintAddress: 'XsMAqkcKsUewDrzVkait4e5u4y8REgtyS7jWgCpLV2C',
|
||||
decimal: 8,
|
||||
mint: 'jpmx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/jpmx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Linde xStock',
|
||||
symbol: 'LINx',
|
||||
mintAddress: 'XsSr8anD1hkvNMu8XQiVcmiaTP7XGvYu7Q58LdmtE8Z',
|
||||
decimal: 8,
|
||||
mint: 'linx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/linx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Marvell xStock',
|
||||
symbol: 'MRVLx',
|
||||
mintAddress: 'XsuxRGDzbLjnJ72v74b7p9VY6N66uYgTCyfwwRjVCJA',
|
||||
decimal: 8,
|
||||
mint: 'mrvlx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/mrvlx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Mastercard xStock',
|
||||
symbol: 'MAx',
|
||||
mintAddress: 'XsApJFV9MAktqnAc6jqzsHVujxkGm9xcSUffaBoYLKC',
|
||||
decimal: 8,
|
||||
mint: 'max',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/mastercard.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'McDonald\'s xStock',
|
||||
symbol: 'MCDx',
|
||||
mintAddress: 'XsqE9cRRpzxcGKDXj1BJ7Xmg4GRhZoyY1KpmGSxAWT2',
|
||||
decimal: 8,
|
||||
mint: 'mcdx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/mcdonalds.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Medtronic xStock',
|
||||
symbol: 'MDTx',
|
||||
mintAddress: 'XsDgw22qRLTv5Uwuzn6T63cW69exG41T6gwQhEK22u2',
|
||||
decimal: 8,
|
||||
mint: 'mdtx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/mdtx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Merck xStock',
|
||||
symbol: 'MRKx',
|
||||
mintAddress: 'XsnQnU7AdbRZYe2akqqpibDdXjkieGFfSkbkjX1Sd1X',
|
||||
decimal: 8,
|
||||
mint: 'mrkx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/mrkx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Meta xStock',
|
||||
symbol: 'METAx',
|
||||
mintAddress: 'Xsa62P5mvPszXL1krVUnU5ar38bBSVcWAB6fmPCo5Zu',
|
||||
decimal: 8,
|
||||
mint: 'metax',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/metax.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Microsoft xStock',
|
||||
symbol: 'MSFTx',
|
||||
mintAddress: 'XspzcW1PRtgf6Wj92HCiZdjzKCyFekVD8P5Ueh3dRMX',
|
||||
decimal: 8,
|
||||
mint: 'msftx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/msftx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'MicroStrategy xStock',
|
||||
symbol: 'MSTRx',
|
||||
mintAddress: 'XsP7xzNPvEHS1m6qfanPUGjNmdnmsLKEoNAnHjdxxyZ',
|
||||
decimal: 8,
|
||||
mint: 'mstrx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/mstrx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Nasdaq xStock',
|
||||
symbol: 'QQQx',
|
||||
mintAddress: 'Xs8S1uUs1zvS2p7iwtsG3b6fkhpvmwz4GYU3gWAmWHZ',
|
||||
decimal: 8,
|
||||
mint: 'qqqx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/qqqx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Netflix xStock',
|
||||
symbol: 'NFLXx',
|
||||
mintAddress: 'XsEH7wWfJJu2ZT3UCFeVfALnVA6CP5ur7Ee11KmzVpL',
|
||||
decimal: 8,
|
||||
mint: 'nflxx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/nflxx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Novo Nordisk xStock',
|
||||
symbol: 'NVOx',
|
||||
mintAddress: 'XsfAzPzYrYjd4Dpa9BU3cusBsvWfVB9gBcyGC87S57n',
|
||||
decimal: 8,
|
||||
mint: 'nvox',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/nvox.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'NVIDIA xStock',
|
||||
symbol: 'NVDAx',
|
||||
mintAddress: 'Xsc9qvGR1efVDFGLrVsmkzv3qi45LTBjeUKSPmx9qEh',
|
||||
decimal: 8,
|
||||
mint: 'nvdax',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/nvdax.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'OPEN xStock',
|
||||
symbol: 'OPENx',
|
||||
mintAddress: 'XsGtpmjhmC8kyjVSWL4VicGu36ceq9u55PTgF8bhGv6',
|
||||
decimal: 8,
|
||||
mint: 'openx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/openx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Oracle xStock',
|
||||
symbol: 'ORCLx',
|
||||
mintAddress: 'XsjFwUPiLofddX5cWFHW35GCbXcSu1BCUGfxoQAQjeL',
|
||||
decimal: 8,
|
||||
mint: 'orclx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/orclx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Palantir xStock',
|
||||
symbol: 'PLTRx',
|
||||
mintAddress: 'XsoBhf2ufR8fTyNSjqfU71DYGaE6Z3SUGAidpzriAA4',
|
||||
decimal: 8,
|
||||
mint: 'pltrx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/pltrx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'PepsiCo xStock',
|
||||
symbol: 'PEPx',
|
||||
mintAddress: 'Xsv99frTRUeornyvCfvhnDesQDWuvns1M852Pez91vF',
|
||||
decimal: 8,
|
||||
mint: 'pepx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/pepx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Pfizer xStock',
|
||||
symbol: 'PFEx',
|
||||
mintAddress: 'XsAtbqkAP1HJxy7hFDeq7ok6yM43DQ9mQ1Rh861X8rw',
|
||||
decimal: 8,
|
||||
mint: 'pfex',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/pfex.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Philip Morris xStock',
|
||||
symbol: 'PMx',
|
||||
mintAddress: 'Xsba6tUnSjDae2VcopDB6FGGDaxRrewFCDa5hKn5vT3',
|
||||
decimal: 8,
|
||||
mint: 'pmx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/pmx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Procter & Gamble xStock',
|
||||
symbol: 'PGx',
|
||||
mintAddress: 'XsYdjDjNUygZ7yGKfQaB6TxLh2gC6RRjzLtLAGJrhzV',
|
||||
decimal: 8,
|
||||
mint: 'pgx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/pgx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Robinhood xStock',
|
||||
symbol: 'HOODx',
|
||||
mintAddress: 'XsvNBAYkrDRNhA7wPHQfX3ZUXZyZLdnCQDfHZ56bzpg',
|
||||
decimal: 8,
|
||||
mint: 'hoodx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/hoodx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Salesforce xStock',
|
||||
symbol: 'CRMx',
|
||||
mintAddress: 'XsczbcQ3zfcgAEt9qHQES8pxKAVG5rujPSHQEXi4kaN',
|
||||
decimal: 8,
|
||||
mint: 'crmx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/crmx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'SP500 xStock',
|
||||
symbol: 'SPYx',
|
||||
mintAddress: 'XsoCS1TfEyfFhfvj8EtZ528L3CaKBDBRqRapnBbDF2W',
|
||||
decimal: 8,
|
||||
mint: 'spyx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/spyx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'TBLL xStock',
|
||||
symbol: 'TBLLx',
|
||||
mintAddress: 'XsqBC5tcVQLYt8wqGCHRnAUUecbRYXoJCReD6w7QEKp',
|
||||
decimal: 8,
|
||||
mint: 'tbllx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/tbllx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Tesla xStock',
|
||||
symbol: 'TSLAx',
|
||||
mintAddress: 'XsDoVfqeBukxuZHWhdvWHBhgEHjGNst4MLodqsJHzoB',
|
||||
decimal: 8,
|
||||
mint: 'tslax',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/tslax.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Thermo Fisher xStock',
|
||||
symbol: 'TMOx',
|
||||
mintAddress: 'Xs8drBWy3Sd5QY3aifG9kt9KFs2K3PGZmx7jWrsrk57',
|
||||
decimal: 8,
|
||||
mint: 'tmox',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/tmox.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'TON xStock',
|
||||
symbol: 'TONXx',
|
||||
mintAddress: 'XscE4GUcsYhcyZu5ATiGUMmhxYa1D5fwbpJw4K6K4dp',
|
||||
decimal: 8,
|
||||
mint: 'tonxx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/tonxx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'TQQQ xStock',
|
||||
symbol: 'TQQQx',
|
||||
mintAddress: 'XsjQP3iMAaQ3kQScQKthQpx9ALRbjKAjQtHg6TFomoc',
|
||||
decimal: 8,
|
||||
mint: 'tqqqx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/tqqqx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'UnitedHealth xStock',
|
||||
symbol: 'UNHx',
|
||||
mintAddress: 'XszvaiXGPwvk2nwb3o9C1CX4K6zH8sez11E6uyup6fe',
|
||||
decimal: 8,
|
||||
mint: 'unhx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/unhx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Vanguard xStock',
|
||||
symbol: 'VTIx',
|
||||
mintAddress: 'XsssYEQjzxBCFgvYFFNuhJFBeHNdLWYeUSP8F45cDr9',
|
||||
decimal: 8,
|
||||
mint: 'vtix',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/vtix.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Visa xStock',
|
||||
symbol: 'Vx',
|
||||
mintAddress: 'XsqgsbXwWogGJsNcVZ3TyVouy2MbTkfCFhCGGGcQZ2p',
|
||||
decimal: 8,
|
||||
mint: 'vx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/vx.webp',
|
||||
),
|
||||
SPLToken(
|
||||
name: 'Walmart xStock',
|
||||
symbol: 'WMTx',
|
||||
mintAddress: 'Xs151QeqTCiuKtinzfRATnUESM2xTU6V9Wy8Vy538ci',
|
||||
decimal: 8,
|
||||
mint: 'wmtx',
|
||||
enabled: false,
|
||||
iconPath: 'assets/images/stocks/wmtx.webp',
|
||||
),
|
||||
];
|
||||
|
||||
List<SPLToken> get initialSPLTokens => _defaultTokens.map((token) {
|
||||
String? iconPath;
|
||||
if (token.iconPath?.isEmpty ?? true) {
|
||||
try {
|
||||
iconPath = CryptoCurrency.all
|
||||
.firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
|
||||
.iconPath;
|
||||
} catch (_) {}
|
||||
} else {
|
||||
iconPath = token.iconPath;
|
||||
}
|
||||
|
||||
return SPLToken.copyWith(token, icon: iconPath, tag: 'SOL');
|
||||
}).toList();
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
|
||||
class PendingSolanaTransaction with PendingTransaction {
|
||||
final double amount;
|
||||
final String serializedTransaction;
|
||||
final String destinationAddress;
|
||||
final Function sendTransaction;
|
||||
final double fee;
|
||||
String? _sig;
|
||||
|
||||
PendingSolanaTransaction({
|
||||
required this.fee,
|
||||
required this.amount,
|
||||
required this.serializedTransaction,
|
||||
required this.destinationAddress,
|
||||
required this.sendTransaction,
|
||||
});
|
||||
|
||||
@override
|
||||
String get amountFormatted {
|
||||
String stringifiedAmount = amount.toString();
|
||||
|
||||
if (stringifiedAmount.toString().length >= 6) {
|
||||
stringifiedAmount = stringifiedAmount.substring(0, 6);
|
||||
}
|
||||
|
||||
return stringifiedAmount;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> commit() async {
|
||||
_sig = await sendTransaction();
|
||||
}
|
||||
|
||||
@override
|
||||
String get feeFormatted => "$feeFormattedValue SOL";
|
||||
|
||||
@override
|
||||
String get feeFormattedValue => fee.toString();
|
||||
|
||||
@override
|
||||
String get hex => serializedTransaction;
|
||||
|
||||
@override
|
||||
String get id => _sig ?? '';
|
||||
|
||||
@override
|
||||
Future<Map<String, String>> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cw_core/balance.dart';
|
||||
|
||||
class SolanaBalance extends Balance {
|
||||
SolanaBalance(this.balance, bool isToken) : super(
|
||||
BigInt.from(int.tryParse(balance.toStringAsFixed(isToken ? 6 : 9).replaceFirst(".", "")) ?? 0),
|
||||
BigInt.from(int.tryParse(balance.toStringAsFixed(isToken ? 6 : 9).replaceFirst(".", "")) ?? 0));
|
||||
|
||||
// Using raw amount from RPC to avoid decimals mismatch for SPL tokens.
|
||||
SolanaBalance.forToken(BigInt rawAmount, double uiAmount)
|
||||
: balance = uiAmount,
|
||||
super(rawAmount, rawAmount);
|
||||
|
||||
final double balance;
|
||||
|
||||
String get formattedAdditionalBalance => _balanceFormatted();
|
||||
|
||||
String get formattedAvailableBalance => _balanceFormatted();
|
||||
|
||||
String _balanceFormatted() {
|
||||
String stringBalance = balance.toString();
|
||||
if (stringBalance.toString().length >= 12) {
|
||||
stringBalance = stringBalance.substring(0, 12);
|
||||
}
|
||||
return stringBalance;
|
||||
}
|
||||
|
||||
static SolanaBalance? fromJSON(String? jsonSource, bool isToken) {
|
||||
if (jsonSource == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final decoded = json.decode(jsonSource) as Map;
|
||||
|
||||
try {
|
||||
return SolanaBalance(decoded['balance'], isToken);
|
||||
} catch (e) {
|
||||
return SolanaBalance(0.0, isToken);
|
||||
}
|
||||
}
|
||||
|
||||
String toJSON() => json.encode({'balance': balance.toString()});
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,38 +0,0 @@
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/exceptions.dart';
|
||||
|
||||
class SolanaTransactionCreationException implements Exception {
|
||||
final String exceptionMessage;
|
||||
|
||||
SolanaTransactionCreationException(CryptoCurrency currency)
|
||||
: exceptionMessage = 'Error creating ${currency.title} transaction.';
|
||||
|
||||
@override
|
||||
String toString() => exceptionMessage;
|
||||
}
|
||||
|
||||
class SolanaTransactionWrongBalanceException implements Exception {
|
||||
final String exceptionMessage;
|
||||
|
||||
SolanaTransactionWrongBalanceException(CryptoCurrency currency)
|
||||
: exceptionMessage = 'Wrong balance. Not enough ${currency.title} on your balance.';
|
||||
|
||||
@override
|
||||
String toString() => exceptionMessage;
|
||||
}
|
||||
|
||||
class SolanaSignNativeTokenTransactionRentException
|
||||
extends SignNativeTokenTransactionRentException {}
|
||||
|
||||
class SolanaCreateAssociatedTokenAccountException extends CreateAssociatedTokenAccountException {
|
||||
SolanaCreateAssociatedTokenAccountException(super.errorMessage);
|
||||
}
|
||||
|
||||
class SolanaSignSPLTokenTransactionRentException extends SignSPLTokenTransactionRentException {}
|
||||
|
||||
class SolanaNoAssociatedTokenAccountException extends NoAssociatedTokenAccountException {
|
||||
SolanaNoAssociatedTokenAccountException(this.account, this.mint);
|
||||
|
||||
final String account;
|
||||
final String mint;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +0,0 @@
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/output_info.dart';
|
||||
|
||||
class SolanaTransactionCredentials {
|
||||
SolanaTransactionCredentials(
|
||||
this.outputs, {
|
||||
required this.currency,
|
||||
});
|
||||
|
||||
final List<OutputInfo> outputs;
|
||||
final CryptoCurrency currency;
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:core';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_solana/solana_transaction_info.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
|
||||
part 'solana_transaction_history.g.dart';
|
||||
|
||||
const transactionsHistoryFileName = 'solana_transactions.json';
|
||||
|
||||
class SolanaTransactionHistory = SolanaTransactionHistoryBase with _$SolanaTransactionHistory;
|
||||
|
||||
abstract class SolanaTransactionHistoryBase extends TransactionHistoryBase<SolanaTransactionInfo>
|
||||
with Store {
|
||||
SolanaTransactionHistoryBase(
|
||||
{required this.walletInfo, required String password, required this.encryptionFileUtils})
|
||||
: _password = password {
|
||||
transactions = ObservableMap<String, SolanaTransactionInfo>();
|
||||
}
|
||||
|
||||
final WalletInfo walletInfo;
|
||||
final EncryptionFileUtils encryptionFileUtils;
|
||||
String _password;
|
||||
|
||||
Future<void> init() async {
|
||||
clear();
|
||||
await _load();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
try {
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
final path = '$dirPath/$transactionsHistoryFileName';
|
||||
final transactionMaps = transactions.map((key, value) => MapEntry(key, value.toJson()));
|
||||
final data = json.encode({'transactions': transactionMaps});
|
||||
await encryptionFileUtils.write(path: path, password: _password, data: data);
|
||||
} catch (e, s) {
|
||||
printV('Error while saving solana transaction history: ${e.toString()}');
|
||||
printV(s);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void addOne(SolanaTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
|
||||
@override
|
||||
void addMany(Map<String, SolanaTransactionInfo> transactions) =>
|
||||
this.transactions.addAll(transactions);
|
||||
|
||||
Future<Map<String, dynamic>> _read() async {
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
final path = '$dirPath/$transactionsHistoryFileName';
|
||||
final content = await encryptionFileUtils.read(path: path, password: _password);
|
||||
if (content.isEmpty) {
|
||||
return {};
|
||||
}
|
||||
return json.decode(content) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
try {
|
||||
final content = await _read();
|
||||
final txs = content['transactions'] as Map<String, dynamic>? ?? {};
|
||||
|
||||
txs.entries.forEach((entry) {
|
||||
final val = entry.value;
|
||||
|
||||
if (val is Map<String, dynamic>) {
|
||||
final tx = SolanaTransactionInfo.fromJson(val);
|
||||
_update(tx);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
printV(e);
|
||||
}
|
||||
}
|
||||
|
||||
void _update(SolanaTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import 'package:cw_core/format_amount.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
|
||||
class SolanaTransactionInfo extends TransactionInfo {
|
||||
SolanaTransactionInfo({
|
||||
required this.id,
|
||||
required this.blockTime,
|
||||
required this.to,
|
||||
required this.from,
|
||||
required this.direction,
|
||||
required this.solAmount,
|
||||
this.tokenSymbol = "SOL",
|
||||
required this.isPending,
|
||||
required this.txFee,
|
||||
}) : amount = solAmount.toInt();
|
||||
|
||||
final String id;
|
||||
final String? to;
|
||||
final String? from;
|
||||
final int amount;
|
||||
final bool isPending;
|
||||
final double solAmount;
|
||||
final String tokenSymbol;
|
||||
final DateTime blockTime;
|
||||
final double txFee;
|
||||
final TransactionDirection direction;
|
||||
|
||||
String? _fiatAmount;
|
||||
|
||||
@override
|
||||
DateTime get date => blockTime;
|
||||
|
||||
@override
|
||||
String amountFormatted() {
|
||||
String stringBalance = solAmount.toString();
|
||||
if (stringBalance.toString().length >= 12) {
|
||||
stringBalance = stringBalance.substring(0, 12);
|
||||
}
|
||||
return '$stringBalance $tokenSymbol';
|
||||
}
|
||||
|
||||
@override
|
||||
String fiatAmount() => _fiatAmount ?? '';
|
||||
|
||||
@override
|
||||
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
||||
|
||||
@override
|
||||
String feeFormatted() => '${txFee.toString()} SOL';
|
||||
|
||||
factory SolanaTransactionInfo.fromJson(Map<String, dynamic> data) {
|
||||
return SolanaTransactionInfo(
|
||||
id: data['id'] as String,
|
||||
solAmount: data['solAmount'],
|
||||
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||
blockTime: DateTime.fromMillisecondsSinceEpoch(data['blockTime'] as int),
|
||||
isPending: data['isPending'] as bool,
|
||||
tokenSymbol: data['tokenSymbol'] as String,
|
||||
to: data['to'],
|
||||
from: data['from'],
|
||||
txFee: data['txFee'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'solAmount': solAmount,
|
||||
'direction': direction.index,
|
||||
'blockTime': blockTime.millisecondsSinceEpoch,
|
||||
'isPending': isPending,
|
||||
'tokenSymbol': tokenSymbol,
|
||||
'to': to,
|
||||
'from': from,
|
||||
'txFee': txFee,
|
||||
};
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
class SolanaTransactionModel {
|
||||
final String id;
|
||||
|
||||
final String from;
|
||||
|
||||
final String to;
|
||||
|
||||
final double amount;
|
||||
|
||||
// If this is an outgoing transaction
|
||||
final bool isOutgoingTx;
|
||||
|
||||
// The Program ID of this transaction, e.g, System Program, Token Program...
|
||||
final String programId;
|
||||
|
||||
// The DateTime from the UNIX timestamp of the block where the transaction was included
|
||||
final DateTime blockTime;
|
||||
|
||||
// The Transaction fee
|
||||
final double fee;
|
||||
|
||||
// The token symbol
|
||||
final String tokenSymbol;
|
||||
|
||||
SolanaTransactionModel({
|
||||
required this.id,
|
||||
required this.to,
|
||||
required this.from,
|
||||
required this.amount,
|
||||
required this.programId,
|
||||
required int blockTimeInInt,
|
||||
this.isOutgoingTx = false,
|
||||
required this.tokenSymbol,
|
||||
required this.fee,
|
||||
}) : blockTime = DateTime.fromMillisecondsSinceEpoch(blockTimeInInt * 1000);
|
||||
|
||||
factory SolanaTransactionModel.fromJson(Map<String, dynamic> json) => SolanaTransactionModel(
|
||||
id: json['id'],
|
||||
blockTimeInInt: int.parse(json["timeStamp"]) * 1000,
|
||||
from: json["from"],
|
||||
to: json["to"],
|
||||
amount: double.parse(json["value"]),
|
||||
programId: json["programId"],
|
||||
fee: json['fee'],
|
||||
tokenSymbol: json['tokenSymbol'],
|
||||
);
|
||||
}
|
||||
@@ -1,845 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_keys_file.dart';
|
||||
import 'package:cw_solana/default_spl_tokens.dart';
|
||||
import 'package:cw_solana/solana_balance.dart';
|
||||
import 'package:cw_solana/solana_client.dart';
|
||||
import 'package:cw_solana/solana_exceptions.dart';
|
||||
import 'package:cw_solana/solana_transaction_credentials.dart';
|
||||
import 'package:cw_solana/solana_transaction_history.dart';
|
||||
import 'package:cw_solana/solana_transaction_info.dart';
|
||||
import 'package:cw_solana/solana_transaction_model.dart';
|
||||
import 'package:cw_solana/solana_wallet_addresses.dart';
|
||||
import 'package:cw_core/spl_token.dart';
|
||||
import 'package:hex/hex.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:on_chain/solana/solana.dart' hide Store;
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
|
||||
part 'solana_wallet.g.dart';
|
||||
|
||||
class SolanaWallet = SolanaWalletBase with _$SolanaWallet;
|
||||
|
||||
abstract class SolanaWalletBase
|
||||
extends WalletBase<SolanaBalance, SolanaTransactionHistory, SolanaTransactionInfo>
|
||||
with Store, WalletKeysFile {
|
||||
SolanaWalletBase({
|
||||
required WalletInfo walletInfo,
|
||||
required DerivationInfo derivationInfo,
|
||||
String? mnemonic,
|
||||
String? privateKey,
|
||||
required String password,
|
||||
SolanaBalance? initialBalance,
|
||||
required this.encryptionFileUtils,
|
||||
this.passphrase,
|
||||
}) : syncStatus = const NotConnectedSyncStatus(),
|
||||
_password = password,
|
||||
_mnemonic = mnemonic,
|
||||
_hexPrivateKey = privateKey,
|
||||
_client = SolanaWalletClient(),
|
||||
walletAddresses = SolanaWalletAddresses(walletInfo),
|
||||
balance = ObservableMap<CryptoCurrency, SolanaBalance>.of(
|
||||
{CryptoCurrency.sol: initialBalance ?? SolanaBalance(BigInt.zero.toDouble(), false)}),
|
||||
super(walletInfo, derivationInfo) {
|
||||
this.walletInfo = walletInfo;
|
||||
transactionHistory = SolanaTransactionHistory(
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
);
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(SPLToken.typeId)) {
|
||||
CakeHive.registerAdapter(SPLTokenAdapter());
|
||||
}
|
||||
|
||||
_sharedPrefs.complete(SharedPreferences.getInstance());
|
||||
}
|
||||
|
||||
final String _password;
|
||||
final String? _mnemonic;
|
||||
final String? _hexPrivateKey;
|
||||
final EncryptionFileUtils encryptionFileUtils;
|
||||
|
||||
late final SolanaWalletClient _client;
|
||||
|
||||
SolanaWalletClient get client => _client;
|
||||
|
||||
@observable
|
||||
double? estimatedFee;
|
||||
|
||||
Timer? _transactionsUpdateTimer;
|
||||
|
||||
late final Box<SPLToken> splTokensBox;
|
||||
|
||||
@override
|
||||
WalletAddresses walletAddresses;
|
||||
|
||||
@override
|
||||
@observable
|
||||
SyncStatus syncStatus;
|
||||
|
||||
@override
|
||||
@observable
|
||||
ObservableMap<CryptoCurrency, SolanaBalance> balance =
|
||||
ObservableMap<CryptoCurrency, SolanaBalance>();
|
||||
|
||||
final Completer<SharedPreferences> _sharedPrefs = Completer();
|
||||
|
||||
@override
|
||||
Object get keys => throw UnimplementedError("keys");
|
||||
|
||||
late final SolanaPrivateKey _solanaPrivateKey;
|
||||
|
||||
late final SolanaPublicKey _solanaPublicKey;
|
||||
|
||||
SolanaPublicKey get solanaPublicKey => _solanaPublicKey;
|
||||
|
||||
SolanaPrivateKey get solanaPrivateKey => _solanaPrivateKey;
|
||||
|
||||
String get solanaAddress => _solanaPublicKey.toAddress().address;
|
||||
|
||||
@override
|
||||
String? get seed => _mnemonic;
|
||||
|
||||
@override
|
||||
String get privateKey => _solanaPrivateKey.seedHex();
|
||||
|
||||
@override
|
||||
WalletKeysData get walletKeysData => WalletKeysData(
|
||||
mnemonic: _mnemonic,
|
||||
privateKey: privateKey,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
Future<void> init() async {
|
||||
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${SPLToken.boxName}";
|
||||
|
||||
splTokensBox = await CakeHive.openBox<SPLToken>(boxName);
|
||||
|
||||
// Create the privatekey using either the mnemonic or the privateKey
|
||||
_solanaPrivateKey = await getPrivateKey(
|
||||
mnemonic: _mnemonic,
|
||||
privateKey: _hexPrivateKey,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
// Extract the public key and wallet address
|
||||
_solanaPublicKey = _solanaPrivateKey.publicKey();
|
||||
|
||||
walletInfo.address = _solanaPublicKey.toAddress().address;
|
||||
|
||||
await walletAddresses.init();
|
||||
await transactionHistory.init();
|
||||
|
||||
await save();
|
||||
}
|
||||
|
||||
Future<SolanaPrivateKey> getPrivateKey({
|
||||
String? mnemonic,
|
||||
String? privateKey,
|
||||
String? passphrase,
|
||||
}) async {
|
||||
assert(mnemonic != null || privateKey != null);
|
||||
|
||||
if (mnemonic != null) {
|
||||
final seed = bip39.mnemonicToSeed(mnemonic, passphrase: passphrase ?? '');
|
||||
|
||||
// Derive a Solana private key from the seed
|
||||
final bip44 = Bip44.fromSeed(seed, Bip44Coins.solana);
|
||||
|
||||
final childKey = bip44.deriveDefaultPath.change(Bip44Changes.chainExt);
|
||||
|
||||
return SolanaPrivateKey.fromSeed(childKey.privateKey.raw);
|
||||
}
|
||||
|
||||
try {
|
||||
final keypairBytes = Base58Decoder.decode(privateKey!);
|
||||
return SolanaPrivateKey.fromBytes(keypairBytes);
|
||||
} catch (_) {
|
||||
final privateKeyBytes = HEX.decode(privateKey!);
|
||||
return SolanaPrivateKey.fromSeed(privateKeyBytes);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0;
|
||||
|
||||
@override
|
||||
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
|
||||
|
||||
@override
|
||||
Future<void> close({bool shouldCleanup = false}) async {
|
||||
_client.stop();
|
||||
_transactionsUpdateTimer?.cancel();
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> connectToNode({required Node node}) async {
|
||||
try {
|
||||
syncStatus = ConnectingSyncStatus();
|
||||
|
||||
final isConnected = _client.connect(node);
|
||||
|
||||
if (!isConnected) {
|
||||
throw Exception("Solana Node connection failed");
|
||||
}
|
||||
|
||||
_setTransactionUpdateTimer();
|
||||
|
||||
syncStatus = ConnectedSyncStatus();
|
||||
} catch (e) {
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getEstimatedFees() async {
|
||||
try {
|
||||
estimatedFee = await _client.getEstimatedFee(_solanaPublicKey, Commitment.confirmed);
|
||||
printV(estimatedFee.toString());
|
||||
} catch (e) {
|
||||
estimatedFee = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||
final solCredentials = credentials as SolanaTransactionCredentials;
|
||||
|
||||
final outputs = solCredentials.outputs;
|
||||
|
||||
final hasMultiDestination = outputs.length > 1;
|
||||
|
||||
await updateTokenBalance();
|
||||
|
||||
final transactionCurrency = balance.keys.firstWhere(
|
||||
(currency) =>
|
||||
currency.title == credentials.currency.title &&
|
||||
currency.tag == credentials.currency.tag,
|
||||
orElse: () => throw Exception(
|
||||
'Currency ${credentials.currency.title} ${credentials.currency.tag} is not accessible in the wallet, try to enable it first.'));
|
||||
|
||||
final walletBalanceForCurrency = balance[transactionCurrency]!.balance;
|
||||
|
||||
final solBalance = balance[CryptoCurrency.sol]!.balance;
|
||||
|
||||
double totalAmount = 0.0;
|
||||
|
||||
bool isSendAll = false;
|
||||
|
||||
if (hasMultiDestination) {
|
||||
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
|
||||
throw SolanaTransactionWrongBalanceException(transactionCurrency);
|
||||
}
|
||||
|
||||
final totalAmountFromCredentials =
|
||||
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0));
|
||||
|
||||
totalAmount = totalAmountFromCredentials.toDouble();
|
||||
|
||||
if (walletBalanceForCurrency < totalAmount) {
|
||||
throw SolanaTransactionWrongBalanceException(transactionCurrency);
|
||||
}
|
||||
} else {
|
||||
final output = outputs.first;
|
||||
|
||||
isSendAll = output.sendAll;
|
||||
|
||||
if (isSendAll) {
|
||||
totalAmount = walletBalanceForCurrency;
|
||||
} else {
|
||||
final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0');
|
||||
|
||||
totalAmount = totalOriginalAmount;
|
||||
}
|
||||
|
||||
if (walletBalanceForCurrency < totalAmount) {
|
||||
throw SolanaTransactionWrongBalanceException(transactionCurrency);
|
||||
}
|
||||
}
|
||||
|
||||
String? tokenMint;
|
||||
// Token Mint is only needed for transactions that are not native tokens(non-SOL transactions)
|
||||
if (transactionCurrency.title != CryptoCurrency.sol.title) {
|
||||
tokenMint = (transactionCurrency as SPLToken).mintAddress;
|
||||
}
|
||||
|
||||
final pendingSolanaTransaction = await _client.signSolanaTransaction(
|
||||
tokenMint: tokenMint,
|
||||
tokenTitle: transactionCurrency.title,
|
||||
inputAmount: totalAmount,
|
||||
ownerPrivateKey: _solanaPrivateKey,
|
||||
tokenDecimals: transactionCurrency.decimals,
|
||||
destinationAddress: solCredentials.outputs.first.isParsedAddress
|
||||
? solCredentials.outputs.first.extractedAddress!
|
||||
: solCredentials.outputs.first.address,
|
||||
isSendAll: isSendAll,
|
||||
solBalance: solBalance,
|
||||
);
|
||||
|
||||
return pendingSolanaTransaction;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, SolanaTransactionInfo>> fetchTransactions() async => {};
|
||||
|
||||
@override
|
||||
Future<void> updateTransactionsHistory({List<String>? specificTokenMints}) async {
|
||||
await Future.wait([
|
||||
_updateNativeSOLTransactions(),
|
||||
updateSPLTokenTransactions(specificMints: specificTokenMints),
|
||||
]);
|
||||
}
|
||||
|
||||
/// Polls for a specific transaction by signature with exponential backoff
|
||||
/// I'm using this in case we make the call to fetch the transaction and it has not finished its confirmations on the solana network and been indexed by the node networks we use.
|
||||
Future<void> pollForTransaction({
|
||||
required String signature,
|
||||
Duration initialDelay = const Duration(seconds: 1),
|
||||
int maxRetries = 5,
|
||||
}) async {
|
||||
final walletAddress = _solanaPublicKey.toAddress().address;
|
||||
|
||||
for (int i = 0; i < maxRetries; i++) {
|
||||
await Future.delayed(initialDelay * (i + 1));
|
||||
|
||||
try {
|
||||
final result = await _client.fetchTransactionBySignature(
|
||||
signature: signature,
|
||||
walletAddress: walletAddress,
|
||||
);
|
||||
|
||||
if (result != null && result.transactions.isNotEmpty) {
|
||||
await addTransactionsToTransactionHistory(result.transactions);
|
||||
|
||||
// Update only the tokens involved in this transaction
|
||||
if (result.tokenMints.isNotEmpty) {
|
||||
await Future.wait([
|
||||
updateSPLTokenTransactions(specificMints: result.tokenMints),
|
||||
updateTokenBalance(tokenMints: result.tokenMints),
|
||||
]);
|
||||
} else {
|
||||
// If no token mints, still update SOL balance
|
||||
await updateTokenBalance(tokenMints: []);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
printV('Error polling for transaction (attempt ${i + 1}/$maxRetries): $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to full refresh if not found after max retries
|
||||
printV('Transaction not found after $maxRetries attempts, falling back to full refresh');
|
||||
await updateTransactionsHistory();
|
||||
}
|
||||
|
||||
void updateTransactions(List<SolanaTransactionModel> updatedTx) {
|
||||
addTransactionsToTransactionHistory(updatedTx);
|
||||
}
|
||||
|
||||
/// Fetches the native SOL transactions linked to the wallet Public Key
|
||||
Future<void> _updateNativeSOLTransactions() async {
|
||||
final transactions =
|
||||
await _client.fetchTransactions(_solanaPublicKey.toAddress(), onUpdate: updateTransactions);
|
||||
|
||||
await addTransactionsToTransactionHistory(transactions);
|
||||
}
|
||||
|
||||
Future<void> updateSPLTokenTransactions({List<String>? specificMints}) async {
|
||||
final allTokens = balance.keys.whereType<SPLToken>().toList(growable: false);
|
||||
|
||||
// Filter to specific mints if provided
|
||||
final tokens = specificMints != null
|
||||
? allTokens.where((t) => specificMints.contains(t.mintAddress)).toList(growable: false)
|
||||
: allTokens;
|
||||
|
||||
if (tokens.isEmpty) return;
|
||||
|
||||
const int batchSize = 5;
|
||||
|
||||
for (var i = 0; i < tokens.length; i += batchSize) {
|
||||
final batch = tokens.sublist(
|
||||
i,
|
||||
i + batchSize > tokens.length ? tokens.length : i + batchSize,
|
||||
);
|
||||
final results = await Future.wait(
|
||||
batch.map((token) async {
|
||||
try {
|
||||
return await _client.getSPLTokenTransfers(
|
||||
mintAddress: token.mintAddress,
|
||||
splTokenSymbol: token.symbol,
|
||||
splTokenDecimal: token.decimal,
|
||||
privateKey: _solanaPrivateKey,
|
||||
onUpdate: updateTransactions,
|
||||
);
|
||||
} catch (_) {
|
||||
return <SolanaTransactionModel>[];
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
for (final list in results) {
|
||||
await addTransactionsToTransactionHistory(list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addTransactionsToTransactionHistory(
|
||||
List<SolanaTransactionModel> transactions,
|
||||
) async {
|
||||
final Map<String, SolanaTransactionInfo> result = {};
|
||||
|
||||
for (var transactionModel in transactions) {
|
||||
result[transactionModel.id] = SolanaTransactionInfo(
|
||||
id: transactionModel.id,
|
||||
to: transactionModel.to,
|
||||
from: transactionModel.from,
|
||||
blockTime: transactionModel.blockTime,
|
||||
direction: transactionModel.isOutgoingTx
|
||||
? TransactionDirection.outgoing
|
||||
: TransactionDirection.incoming,
|
||||
solAmount: transactionModel.amount,
|
||||
isPending: false,
|
||||
txFee: transactionModel.fee,
|
||||
tokenSymbol: transactionModel.tokenSymbol,
|
||||
);
|
||||
}
|
||||
|
||||
transactionHistory.addMany(result);
|
||||
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> rescan({required int height}) => throw UnimplementedError("rescan");
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
|
||||
await saveKeysFile(_password, encryptionFileUtils);
|
||||
saveKeysFile(_password, encryptionFileUtils, true);
|
||||
}
|
||||
|
||||
await walletAddresses.updateAddressesInBox();
|
||||
final path = await makePath();
|
||||
await encryptionFileUtils.write(path: path, password: _password, data: toJSON());
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> startSync() async {
|
||||
try {
|
||||
syncStatus = AttemptingSyncStatus();
|
||||
|
||||
// Verify node health before attempting to sync
|
||||
final isHealthy = await checkNodeHealth();
|
||||
if (!isHealthy) {
|
||||
syncStatus = FailedSyncStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
await Future.wait([
|
||||
updateTokenBalance(),
|
||||
_updateNativeSOLTransactions(),
|
||||
updateSPLTokenTransactions(),
|
||||
_getEstimatedFees(),
|
||||
]);
|
||||
|
||||
syncStatus = SyncedSyncStatus();
|
||||
} catch (e) {
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
}
|
||||
|
||||
String toJSON() => json.encode({
|
||||
'mnemonic': _mnemonic,
|
||||
'private_key': _hexPrivateKey,
|
||||
'balance': balance[currency]!.toJSON(),
|
||||
'passphrase': passphrase,
|
||||
});
|
||||
|
||||
static Future<SolanaWallet> open({
|
||||
required String name,
|
||||
required String password,
|
||||
required WalletInfo walletInfo,
|
||||
required EncryptionFileUtils encryptionFileUtils,
|
||||
}) async {
|
||||
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||
|
||||
Map<String, dynamic>? data;
|
||||
try {
|
||||
final jsonSource = await encryptionFileUtils.read(path: path, password: password);
|
||||
|
||||
data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
if (!hasKeysFile) rethrow;
|
||||
}
|
||||
|
||||
final balance = SolanaBalance.fromJSON(data?['balance'] as String?, false) ?? SolanaBalance(0.0, false);
|
||||
|
||||
final WalletKeysData keysData;
|
||||
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||
if (!hasKeysFile) {
|
||||
final mnemonic = data!['mnemonic'] as String?;
|
||||
final privateKey = data['private_key'] as String?;
|
||||
final passphrase = data['passphrase'] as String?;
|
||||
|
||||
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey, passphrase: passphrase);
|
||||
} else {
|
||||
keysData = await WalletKeysFile.readKeysFile(
|
||||
name,
|
||||
walletInfo.type,
|
||||
password,
|
||||
encryptionFileUtils,
|
||||
);
|
||||
}
|
||||
|
||||
final derivationInfo = await walletInfo.getDerivationInfo();
|
||||
|
||||
return SolanaWallet(
|
||||
walletInfo: walletInfo,
|
||||
derivationInfo: derivationInfo,
|
||||
password: password,
|
||||
passphrase: keysData.passphrase,
|
||||
mnemonic: keysData.mnemonic,
|
||||
privateKey: keysData.privateKey,
|
||||
initialBalance: balance,
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> updateTokenBalance({List<String>? tokenMints}) async {
|
||||
// Fetch SOL and SPL token balances in parallel for better performance
|
||||
await Future.wait([
|
||||
_fetchSOLBalance().then((solBalance) {
|
||||
balance[CryptoCurrency.sol] = solBalance;
|
||||
}),
|
||||
_updateSplTokenBalancesInternal(tokenMints: tokenMints),
|
||||
]);
|
||||
|
||||
await save();
|
||||
}
|
||||
|
||||
Future<SolanaBalance> _fetchSOLBalance() async {
|
||||
final balance = await _client.getBalance(solanaAddress);
|
||||
|
||||
return SolanaBalance(balance, false);
|
||||
}
|
||||
|
||||
/// Internal helper to update SPL token balances.
|
||||
/// When [tokenMints] is null or empty, updates all enabled tokens.
|
||||
Future<void> _updateSplTokenBalancesInternal({
|
||||
List<String>? tokenMints,
|
||||
}) async {
|
||||
// Remove disabled tokens first to keep state clean
|
||||
for (var token in splTokensBox.values.where((t) => !t.enabled)) {
|
||||
balance.remove(token);
|
||||
}
|
||||
|
||||
final enabledTokens = splTokensBox.values.where((t) => t.enabled).toList(growable: false);
|
||||
if (enabledTokens.isEmpty) return;
|
||||
|
||||
final tokens = tokenMints == null || tokenMints.isEmpty
|
||||
? enabledTokens
|
||||
: enabledTokens.where((t) => tokenMints.contains(t.mintAddress)).toList(growable: false);
|
||||
|
||||
if (tokens.isEmpty) return;
|
||||
|
||||
const int batchSize = 5;
|
||||
|
||||
for (var i = 0; i < tokens.length; i += batchSize) {
|
||||
final batch = tokens.sublist(
|
||||
i,
|
||||
i + batchSize > tokens.length ? tokens.length : i + batchSize,
|
||||
);
|
||||
|
||||
final results = await Future.wait(batch.map((token) async {
|
||||
try {
|
||||
final fetched = await _client.getSplTokenBalance(token.mintAddress, solanaAddress);
|
||||
return MapEntry(token, fetched);
|
||||
} catch (e) {
|
||||
printV('Error fetching spl token (${token.symbol}) balance ${e.toString()}');
|
||||
return MapEntry<SPLToken, SolanaBalance?>(token, null);
|
||||
}
|
||||
}));
|
||||
|
||||
for (final entry in results) {
|
||||
final token = entry.key;
|
||||
final fetchedBalance = entry.value;
|
||||
final currentBalance = balance[token] ??
|
||||
SolanaBalance.forToken(BigInt.zero, 0.0);
|
||||
balance[token] = fetchedBalance ?? currentBalance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void>? updateBalance() async => await updateTokenBalance();
|
||||
|
||||
@override
|
||||
Future<bool> checkNodeHealth() async {
|
||||
try {
|
||||
// Check native balance
|
||||
await _client.getBalance(solanaAddress, throwOnError: true);
|
||||
|
||||
// Check USDC token balance
|
||||
const usdcMintAddress = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
||||
await _client.getSplTokenBalance(usdcMintAddress, solanaAddress, throwOnError: true);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
List<SPLToken> get splTokenCurrencies => splTokensBox.values.toList();
|
||||
|
||||
void addInitialTokens() {
|
||||
final initialSPLTokens = DefaultSPLTokens().initialSPLTokens;
|
||||
|
||||
for (var token in initialSPLTokens) {
|
||||
if (!splTokensBox.containsKey(token.mintAddress)) {
|
||||
splTokensBox.put(token.mintAddress, token);
|
||||
} else {
|
||||
// update existing token
|
||||
final existingToken = splTokensBox.get(token.mintAddress);
|
||||
splTokensBox.put(
|
||||
token.mintAddress, SPLToken.copyWith(token, enabled: existingToken!.enabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<SolanaMoralisDiscoveryResult> discoverTokensFromMoralis() async {
|
||||
try {
|
||||
if (!splTokensBox.isOpen) return SolanaMoralisDiscoveryResult.empty;
|
||||
|
||||
final address = walletAddresses.address;
|
||||
if (address.isEmpty) return SolanaMoralisDiscoveryResult.empty;
|
||||
|
||||
final walletTokens = await _client.fetchWalletTokensFromMoralis(address);
|
||||
if (walletTokens.isEmpty) return SolanaMoralisDiscoveryResult.empty;
|
||||
|
||||
final existingMints = {
|
||||
for (final token in splTokensBox.values) token.mintAddress: token,
|
||||
};
|
||||
|
||||
final defaultMints = DefaultSPLTokens().initialSPLTokens.map((t) => t.mintAddress).toSet();
|
||||
|
||||
final newTokens = <DiscoveredSPLToken>[];
|
||||
|
||||
for (final moralisToken in walletTokens) {
|
||||
final mint = moralisToken.mint;
|
||||
|
||||
final existingToken = existingMints[mint];
|
||||
if (existingToken != null) {
|
||||
if (defaultMints.contains(mint) && !existingToken.enabled) {
|
||||
existingToken.enabled = true;
|
||||
await existingToken.save();
|
||||
await addSPLToken(existingToken);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
final tokenInfo = await _client.fetchSPLTokenInfo(mint);
|
||||
if (tokenInfo == null) continue;
|
||||
|
||||
final discoveredToken = SPLToken(
|
||||
name: tokenInfo.name,
|
||||
symbol: tokenInfo.symbol,
|
||||
mintAddress: mint,
|
||||
decimal: moralisToken.decimals,
|
||||
mint: tokenInfo.mint,
|
||||
iconPath: tokenInfo.iconPath,
|
||||
tag: 'SOL',
|
||||
);
|
||||
|
||||
newTokens.add(
|
||||
DiscoveredSPLToken(
|
||||
token: discoveredToken,
|
||||
balance: moralisToken.amount,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SolanaMoralisDiscoveryResult(newTokens: newTokens);
|
||||
} catch (e) {
|
||||
printV('Error discovering SPL tokens from Moralis: ${e.toString()}');
|
||||
return SolanaMoralisDiscoveryResult.empty;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addSPLToken(SPLToken token) async {
|
||||
await splTokensBox.put(token.mintAddress, token);
|
||||
|
||||
if (token.enabled) {
|
||||
final tokenBalance = await _client.getSplTokenBalance(token.mintAddress, solanaAddress) ??
|
||||
balance[token] ??
|
||||
SolanaBalance.forToken(BigInt.zero, 0.0);
|
||||
|
||||
balance[token] = tokenBalance;
|
||||
} else {
|
||||
balance.remove(token);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteSPLToken(SPLToken token) async {
|
||||
if (splTokensBox.isOpen) {
|
||||
await splTokensBox.delete(token.mintAddress);
|
||||
}
|
||||
|
||||
balance.remove(token);
|
||||
await _removeTokenTransactionsInHistory(token);
|
||||
updateTokenBalance();
|
||||
}
|
||||
|
||||
Future<void> _removeTokenTransactionsInHistory(SPLToken token) async {
|
||||
transactionHistory.transactions.removeWhere((key, value) => value.tokenSymbol == token.title);
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
Future<SPLToken?> getSPLToken(String mintAddress) async {
|
||||
try {
|
||||
return await _client.fetchSPLTokenInfo(mintAddress);
|
||||
} catch (e, s) {
|
||||
printV('Error fetching token: ${e.toString()}, ${s.toString()}');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> renameWalletFiles(String newWalletName) async {
|
||||
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
|
||||
final currentWalletFile = File(currentWalletPath);
|
||||
|
||||
final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
|
||||
final currentTransactionsFile = File('$currentDirPath/$transactionsHistoryFileName');
|
||||
|
||||
// Copies current wallet files into new wallet name's dir and files
|
||||
if (currentWalletFile.existsSync()) {
|
||||
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
|
||||
await currentWalletFile.copy(newWalletPath);
|
||||
}
|
||||
if (currentTransactionsFile.existsSync()) {
|
||||
final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
|
||||
await currentTransactionsFile.copy('$newDirPath/$transactionsHistoryFileName');
|
||||
}
|
||||
|
||||
// Delete old name's dir and files
|
||||
await Directory(currentDirPath).delete(recursive: true);
|
||||
}
|
||||
|
||||
void _setTransactionUpdateTimer() {
|
||||
if (_transactionsUpdateTimer?.isActive ?? false) {
|
||||
_transactionsUpdateTimer!.cancel();
|
||||
}
|
||||
|
||||
_transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 30), (_) {
|
||||
updateTokenBalance();
|
||||
_updateNativeSOLTransactions();
|
||||
updateSPLTokenTransactions();
|
||||
_getEstimatedFees();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> signMessage(String message, {String? address}) async {
|
||||
// Convert the message to bytes
|
||||
final messageBytes = utf8.encode(message);
|
||||
|
||||
// Sign the message bytes with the wallet's private key
|
||||
final signature = (_solanaPrivateKey.sign(messageBytes));
|
||||
|
||||
return Base58Encoder.encode(signature);
|
||||
}
|
||||
|
||||
List<List<int>> bytesFromSigString(String signatureString) {
|
||||
final regex = RegExp(r'Signature\(\[(.+)\], publicKey: (.+)\)');
|
||||
final match = regex.firstMatch(signatureString);
|
||||
|
||||
if (match != null) {
|
||||
final bytesString = match.group(1)!;
|
||||
final base58EncodedPublicKeyString = match.group(2)!;
|
||||
final sigBytes = bytesString.split(', ').map(int.parse).toList();
|
||||
|
||||
List<int> pubKeyBytes = SolAddrDecoder().decodeAddr(base58EncodedPublicKeyString);
|
||||
|
||||
return [sigBytes, pubKeyBytes];
|
||||
} else {
|
||||
throw const FormatException('Invalid Signature string format');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> verifyMessage(String message, String signature, {String? address}) async {
|
||||
String signatureString = utf8.decode(HEX.decode(signature));
|
||||
|
||||
List<List<int>> bytes = bytesFromSigString(signatureString);
|
||||
|
||||
final messageBytes = utf8.encode(message);
|
||||
final sigBytes = bytes[0];
|
||||
final pubKeyBytes = bytes[1];
|
||||
|
||||
if (address == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// make sure the address derived from the public key provided matches the one we expect
|
||||
final pub = SolanaPublicKey.fromBytes(pubKeyBytes);
|
||||
if (address != pub.toAddress().address) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return pub.verify(
|
||||
message: messageBytes,
|
||||
signature: sigBytes,
|
||||
);
|
||||
}
|
||||
|
||||
SolanaRPC? get solanaProvider => _client.getSolanaProvider;
|
||||
|
||||
@override
|
||||
String get password => _password;
|
||||
|
||||
@override
|
||||
final String? passphrase;
|
||||
}
|
||||
|
||||
class DiscoveredSPLToken {
|
||||
final SPLToken token;
|
||||
final double balance;
|
||||
|
||||
const DiscoveredSPLToken({
|
||||
required this.token,
|
||||
required this.balance,
|
||||
});
|
||||
}
|
||||
|
||||
class SolanaMoralisDiscoveryResult {
|
||||
final List<DiscoveredSPLToken> newTokens;
|
||||
|
||||
const SolanaMoralisDiscoveryResult({required this.newTokens});
|
||||
|
||||
static const SolanaMoralisDiscoveryResult empty = SolanaMoralisDiscoveryResult(newTokens: []);
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import 'package:cw_core/payment_uris.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'solana_wallet_addresses.g.dart';
|
||||
|
||||
class SolanaWalletAddresses = SolanaWalletAddressesBase with _$SolanaWalletAddresses;
|
||||
|
||||
abstract class SolanaWalletAddressesBase extends WalletAddresses with Store {
|
||||
SolanaWalletAddressesBase(WalletInfo walletInfo)
|
||||
: address = '',
|
||||
super(walletInfo);
|
||||
|
||||
@override
|
||||
String address;
|
||||
|
||||
@override
|
||||
String get primaryAddress => address;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
address = walletInfo.address;
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAddressesInBox() async {
|
||||
try {
|
||||
addressesMap.clear();
|
||||
addressesMap[address] = '';
|
||||
await saveAddressesInBox();
|
||||
} catch (e) {
|
||||
printV(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
PaymentURI getPaymentUri(String amount) => SolanaURI(address: address, amount: amount);
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
|
||||
class SolanaNewWalletCredentials extends WalletCredentials {
|
||||
SolanaNewWalletCredentials({
|
||||
required String name,
|
||||
WalletInfo? walletInfo,
|
||||
String? password,
|
||||
this.mnemonic,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
final String? mnemonic;
|
||||
}
|
||||
|
||||
class SolanaRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
SolanaRestoreWalletFromSeedCredentials({
|
||||
required String name,
|
||||
required String password,
|
||||
required this.mnemonic,
|
||||
WalletInfo? walletInfo,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
final String mnemonic;
|
||||
}
|
||||
|
||||
class SolanaRestoreWalletFromPrivateKey extends WalletCredentials {
|
||||
SolanaRestoreWalletFromPrivateKey(
|
||||
{required String name,
|
||||
required String password,
|
||||
required this.privateKey,
|
||||
WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String privateKey;
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:cw_core/balance.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_solana/solana_mnemonics.dart';
|
||||
import 'package:cw_solana/solana_wallet.dart';
|
||||
import 'package:cw_solana/solana_wallet_creation_credentials.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
class SolanaWalletService extends WalletService<SolanaNewWalletCredentials,
|
||||
SolanaRestoreWalletFromSeedCredentials, SolanaRestoreWalletFromPrivateKey, SolanaNewWalletCredentials> {
|
||||
SolanaWalletService(this.isDirect);
|
||||
|
||||
final bool isDirect;
|
||||
|
||||
@override
|
||||
Future<SolanaWallet> create(SolanaNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||
|
||||
final mnemonic = credentials.mnemonic ?? bip39.generateMnemonic(strength: strength);
|
||||
|
||||
final wallet = SolanaWallet(
|
||||
walletInfo: credentials.walletInfo!,
|
||||
derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
|
||||
mnemonic: mnemonic,
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
WalletType getType() => WalletType.solana;
|
||||
|
||||
@override
|
||||
Future<bool> isWalletExit(String name) async =>
|
||||
File(await pathForWallet(name: name, type: getType())).existsSync();
|
||||
|
||||
@override
|
||||
Future<SolanaWallet> openWallet(String name, String password) async {
|
||||
final walletInfo = await WalletInfo.get(name, getType());
|
||||
if (walletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
|
||||
try {
|
||||
final wallet = await SolanaWalletBase.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
saveBackup(name);
|
||||
return wallet;
|
||||
} catch (_) {
|
||||
await restoreWalletFilesFromBackup(name);
|
||||
|
||||
final wallet = await SolanaWalletBase.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String wallet) async {
|
||||
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
||||
final walletInfo = await WalletInfo.get(wallet, getType());
|
||||
if (walletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
await WalletInfo.delete(walletInfo);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SolanaWallet> restoreFromKeys(SolanaRestoreWalletFromPrivateKey credentials,
|
||||
{bool? isTestnet}) async {
|
||||
final wallet = SolanaWallet(
|
||||
password: credentials.password!,
|
||||
privateKey: credentials.privateKey,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<SolanaWallet> restoreFromSeed(SolanaRestoreWalletFromSeedCredentials credentials,
|
||||
{bool? isTestnet}) async {
|
||||
if (!bip39.validateMnemonic(credentials.mnemonic)) {
|
||||
throw SolanaMnemonicIsIncorrectException();
|
||||
}
|
||||
|
||||
final wallet = SolanaWallet(
|
||||
password: credentials.password!,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
|
||||
passphrase: credentials.passphrase,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> rename(String currentName, String password, String newName) async {
|
||||
final currentWalletInfo = await WalletInfo.get(currentName, getType());
|
||||
if (currentWalletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
final currentWallet = await SolanaWalletBase.open(
|
||||
password: password,
|
||||
name: currentName,
|
||||
walletInfo: currentWalletInfo,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
await saveBackup(newName);
|
||||
|
||||
final newWalletInfo = currentWalletInfo;
|
||||
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||
newWalletInfo.name = newName;
|
||||
|
||||
await newWalletInfo.save();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>> restoreFromHardwareWallet(SolanaNewWalletCredentials credentials) {
|
||||
// TODO: implement restoreFromHardwareWallet
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
name: cw_solana
|
||||
description: A new Flutter package project.
|
||||
version: 0.0.1
|
||||
publish_to: none
|
||||
homepage: https://github.com/Such-Software/hash-wallet
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.6 <4.0.0'
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cw_core:
|
||||
path: ../cw_core
|
||||
http: ^1.1.0
|
||||
hive: ^2.2.3
|
||||
bip39: ^1.0.6
|
||||
mobx: ^2.3.0+1
|
||||
shared_preferences: ^2.0.15
|
||||
bip32: ^2.0.0
|
||||
hex: ^0.2.0
|
||||
on_chain:
|
||||
git:
|
||||
url: https://github.com/cake-tech/on_chain.git
|
||||
ref: 096865a8c6b89c260beadfec04f7e184c40a3273
|
||||
blockchain_utils:
|
||||
git:
|
||||
url: https://github.com/cake-tech/blockchain_utils
|
||||
ref: cake-update-v2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^2.0.0
|
||||
build_runner: ^2.4.15
|
||||
mobx_codegen: ^2.0.7
|
||||
hive_generator: ^2.0.1
|
||||
|
||||
dependency_overrides:
|
||||
watcher: ^1.1.0
|
||||
|
||||
flutter:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
@@ -1,12 +0,0 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:cw_solana/cw_solana.dart';
|
||||
|
||||
void main() {
|
||||
test('adds one to input values', () {
|
||||
final calculator = Calculator();
|
||||
expect(calculator.addOne(2), 3);
|
||||
expect(calculator.addOne(-7), -6);
|
||||
expect(calculator.addOne(0), 1);
|
||||
});
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
# 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/
|
||||
@@ -1,10 +0,0 @@
|
||||
# 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 and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
channel: stable
|
||||
|
||||
project_type: package
|
||||
@@ -1,3 +0,0 @@
|
||||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
||||
@@ -1 +0,0 @@
|
||||
TODO: Add your license here.
|
||||
@@ -1,63 +0,0 @@
|
||||
## cw_tron
|
||||
|
||||
TRON wallet module for Cake Wallet. Implements TRX and TRC-20 token support on top of `on_chain` (Tron), with transaction history and fee estimation via TronGrid.
|
||||
|
||||
### Features
|
||||
|
||||
- Connect to TRON nodes over HTTP(S) via `TronProvider`/`TronHTTPProvider`.
|
||||
- Fetch TRX balance and TRC-20 token balances.
|
||||
- Estimate fees using bandwidth/energy and memo fee; accounts for available account resources.
|
||||
- Create and sign TRX and TRC-20 transfers (supports send-all and optional memo).
|
||||
- Broadcast signed transactions.
|
||||
- Load account history (TRX and TRC-20), filtering spam and TRC10-only events.
|
||||
- Manage TRC-20 tokens: add/remove tokens and fetch token metadata.
|
||||
- Sign/verify messages.
|
||||
- Node health checks for both native and token balance endpoints.
|
||||
|
||||
### Getting started
|
||||
|
||||
Provide a TRON RPC endpoint and a TronGrid API key. Add a secrets file:
|
||||
|
||||
```dart
|
||||
// cw_tron/lib/.secrets.g.dart (DO NOT COMMIT)
|
||||
const String tronGridApiKey = 'YOUR_TRONGRID_API_KEY';
|
||||
```
|
||||
|
||||
Basic connect and sync:
|
||||
|
||||
```dart
|
||||
final service = TronWalletService(walletInfoBox, client: TronClient(), isDirect: true);
|
||||
final wallet = await service.create(TronNewWalletCredentials(name: 'My TRON', password: 'secret'));
|
||||
await wallet.connectToNode(node: Node(uriRaw: 'api.trongrid.io', isSSL: true));
|
||||
await wallet.startSync();
|
||||
final trxBalance = wallet.balance[CryptoCurrency.trx];
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
Send TRX:
|
||||
|
||||
```dart
|
||||
final pending = await wallet.createTransaction(
|
||||
TronTransactionCredentials.single(
|
||||
address: 'T...',
|
||||
cryptoAmount: '1.5',
|
||||
currency: CryptoCurrency.trx,
|
||||
),
|
||||
);
|
||||
final txHash = await pending.commit();
|
||||
```
|
||||
|
||||
Add USDT (TRC-20):
|
||||
|
||||
```dart
|
||||
final usdt = await wallet.getTronToken('TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t');
|
||||
if (usdt != null) {
|
||||
await wallet.addTronToken(usdt);
|
||||
}
|
||||
```
|
||||
|
||||
### Additional information
|
||||
|
||||
- History and token queries use TronGrid; set `tronGridApiKey`.
|
||||
- See `lib/` for APIs: `TronClient`, `TronWallet`, `TronWalletService`, and credential types.
|
||||
@@ -1,4 +0,0 @@
|
||||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
@@ -1,7 +0,0 @@
|
||||
library cw_tron;
|
||||
|
||||
/// A Calculator.
|
||||
class Calculator {
|
||||
/// Returns [value] plus 1.
|
||||
int addOne(int value) => value + 1;
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/tron_token.dart';
|
||||
|
||||
class DefaultTronTokens {
|
||||
final List<TronToken> _defaultTokens = [
|
||||
TronToken(
|
||||
name: "Tether USD",
|
||||
symbol: "USDT",
|
||||
contractAddress: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
|
||||
decimal: 6,
|
||||
enabled: true,
|
||||
),
|
||||
TronToken(
|
||||
name: "USD Coin",
|
||||
symbol: "USDC",
|
||||
contractAddress: "TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8",
|
||||
decimal: 6,
|
||||
enabled: true,
|
||||
),
|
||||
TronToken(
|
||||
name: "Bitcoin",
|
||||
symbol: "BTC",
|
||||
contractAddress: "TN3W4H6rK2ce4vX9YnFQHwKENnHjoxb3m9",
|
||||
decimal: 8,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "Ethereum",
|
||||
symbol: "ETH",
|
||||
contractAddress: "TRFe3hT5oYhjSZ6f3ji5FJ7YCfrkWnHRvh",
|
||||
decimal: 18,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "Wrapped BTC",
|
||||
symbol: "WBTC",
|
||||
contractAddress: "TXpw8XeWYeTUd4quDskoUqeQPowRh4jY65",
|
||||
decimal: 8,
|
||||
enabled: true,
|
||||
),
|
||||
TronToken(
|
||||
name: "Dogecoin",
|
||||
symbol: "DOGE",
|
||||
contractAddress: "THbVQp8kMjStKNnf2iCY6NEzThKMK5aBHg",
|
||||
decimal: 8,
|
||||
enabled: true,
|
||||
),
|
||||
TronToken(
|
||||
name: "JUST Stablecoin",
|
||||
symbol: "USDJ",
|
||||
contractAddress: "TMwFHYXLJaRUPeW6421aqXL4ZEzPRFGkGT",
|
||||
decimal: 18,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "SUN",
|
||||
symbol: "SUN",
|
||||
contractAddress: "TSSMHYeV2uE9qYH95DqyoCuNCzEL1NvU3S",
|
||||
decimal: 18,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "Wrapped TRX",
|
||||
symbol: "WTRX",
|
||||
contractAddress: "TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR",
|
||||
decimal: 6,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "BitTorent",
|
||||
symbol: "BTT",
|
||||
contractAddress: "TAFjULxiVgT4qWk6UZwjqwZXTSaGaqnVp4",
|
||||
decimal: 18,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "BUSD Token",
|
||||
symbol: "BUSD",
|
||||
contractAddress: "TMz2SWatiAtZVVcH2ebpsbVtYwUPT9EdjH",
|
||||
decimal: 18,
|
||||
enabled: false,
|
||||
),
|
||||
TronToken(
|
||||
name: "HTX",
|
||||
symbol: "HTX",
|
||||
contractAddress: "TUPM7K8REVzD2UdV4R5fe5M8XbnR2DdoJ6",
|
||||
decimal: 18,
|
||||
enabled: false,
|
||||
),
|
||||
];
|
||||
|
||||
List<TronToken> get initialTronTokens => _defaultTokens.map((token) {
|
||||
String? iconPath;
|
||||
if (token.iconPath?.isEmpty ?? true) {
|
||||
try {
|
||||
iconPath = CryptoCurrency.all
|
||||
.firstWhere((element) =>
|
||||
element.title.toUpperCase() == token.symbol.split(".").first.toUpperCase())
|
||||
.iconPath;
|
||||
} catch (_) {}
|
||||
} else {
|
||||
iconPath = token.iconPath;
|
||||
}
|
||||
|
||||
return TronToken.copyWith(token, icon: iconPath, tag: 'TRX');
|
||||
}).toList();
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
|
||||
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:web3dart/crypto.dart';
|
||||
|
||||
class PendingTronTransaction with PendingTransaction {
|
||||
final Function sendTransaction;
|
||||
final List<int> signedTransaction;
|
||||
final String fee;
|
||||
final String amount;
|
||||
|
||||
PendingTronTransaction({
|
||||
required this.sendTransaction,
|
||||
required this.signedTransaction,
|
||||
required this.fee,
|
||||
required this.amount,
|
||||
});
|
||||
|
||||
@override
|
||||
String get amountFormatted => amount;
|
||||
|
||||
@override
|
||||
Future<void> commit() async => await sendTransaction();
|
||||
|
||||
@override
|
||||
String get feeFormatted => "$feeFormattedValue TRX";
|
||||
|
||||
@override
|
||||
String get feeFormattedValue => fee;
|
||||
|
||||
@override
|
||||
String get hex => bytesToHex(signedTransaction);
|
||||
|
||||
@override
|
||||
String get id => '';
|
||||
|
||||
@override
|
||||
Future<Map<String, String>> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -1,436 +0,0 @@
|
||||
final trc20Abi = [
|
||||
{"inputs": [], "stateMutability": "nonpayable", "type": "constructor"},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": true, "internalType": "address", "name": "owner", "type": "address"},
|
||||
{"indexed": true, "internalType": "address", "name": "spender", "type": "address"},
|
||||
{"indexed": false, "internalType": "uint256", "name": "value", "type": "uint256"}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": false, "internalType": "uint256", "name": "total", "type": "uint256"},
|
||||
{"indexed": true, "internalType": "uint16", "name": "order_id", "type": "uint16"},
|
||||
{"indexed": true, "internalType": "address", "name": "buyer", "type": "address"},
|
||||
{"indexed": true, "internalType": "address", "name": "seller", "type": "address"},
|
||||
{"indexed": false, "internalType": "address", "name": "contract_address", "type": "address"}
|
||||
],
|
||||
"name": "OrderPaid",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": true, "internalType": "address", "name": "previousOwner", "type": "address"},
|
||||
{"indexed": true, "internalType": "address", "name": "newOwner", "type": "address"}
|
||||
],
|
||||
"name": "OwnershipTransferred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": false, "internalType": "address", "name": "token", "type": "address"},
|
||||
{"indexed": false, "internalType": "bool", "name": "active", "type": "bool"}
|
||||
],
|
||||
"name": "TokenUpdate",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": true, "internalType": "address", "name": "from", "type": "address"},
|
||||
{"indexed": true, "internalType": "address", "name": "to", "type": "address"},
|
||||
{"indexed": false, "internalType": "uint256", "name": "value", "type": "uint256"}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": false, "internalType": "string", "name": "username", "type": "string"},
|
||||
{"indexed": true, "internalType": "address", "name": "seller", "type": "address"}
|
||||
],
|
||||
"name": "UserRegistred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": true, "internalType": "uint16", "name": "order_id", "type": "uint16"},
|
||||
{"indexed": true, "internalType": "address", "name": "buyer", "type": "address"},
|
||||
{"indexed": false, "internalType": "address", "name": "seller", "type": "address"}
|
||||
],
|
||||
"name": "WBuyer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{"indexed": true, "internalType": "uint16", "name": "order_id", "type": "uint16"},
|
||||
{"indexed": true, "internalType": "address", "name": "seller", "type": "address"},
|
||||
{"indexed": false, "internalType": "address", "name": "buyer", "type": "address"}
|
||||
],
|
||||
"name": "WSeller",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "CONTRACTPERCENTAGE",
|
||||
"outputs": [
|
||||
{"internalType": "uint8", "name": "", "type": "uint8"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "uint16", "name": "order_id", "type": "uint16"},
|
||||
{"internalType": "uint256", "name": "order_total", "type": "uint256"},
|
||||
{"internalType": "address", "name": "contractAddress", "type": "address"},
|
||||
{"internalType": "address", "name": "seller", "type": "address"}
|
||||
],
|
||||
"name": "PayWithTokens",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "TOKENINCREAMENT",
|
||||
"outputs": [
|
||||
{"internalType": "uint16", "name": "", "type": "uint16"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "", "type": "address"}
|
||||
],
|
||||
"name": "_signer",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "", "type": "address"}
|
||||
],
|
||||
"name": "_tokens",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "active", "type": "bool"},
|
||||
{"internalType": "uint16", "name": "token", "type": "uint16"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "", "type": "address"}
|
||||
],
|
||||
"name": "_users",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "active", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "owner", "type": "address"},
|
||||
{"internalType": "address", "name": "spender", "type": "address"}
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [
|
||||
{"internalType": "uint256", "name": "", "type": "uint256"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "spender", "type": "address"},
|
||||
{"internalType": "uint256", "name": "amount", "type": "uint256"}
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "account", "type": "address"}
|
||||
],
|
||||
"name": "balanceOf",
|
||||
"outputs": [
|
||||
{"internalType": "uint256", "name": "", "type": "uint256"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "token", "type": "address"}
|
||||
],
|
||||
"name": "balanceOfContract",
|
||||
"outputs": [
|
||||
{"internalType": "uint256", "name": "", "type": "uint256"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "uint256", "name": "amount", "type": "uint256"}
|
||||
],
|
||||
"name": "burn",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "account", "type": "address"},
|
||||
{"internalType": "uint256", "name": "amount", "type": "uint256"}
|
||||
],
|
||||
"name": "burnFrom",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "uint256", "name": "value", "type": "uint256"},
|
||||
{"internalType": "address", "name": "_contractAddress", "type": "address"}
|
||||
],
|
||||
"name": "contractWithdraw",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [
|
||||
{"internalType": "uint8", "name": "", "type": "uint8"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "spender", "type": "address"},
|
||||
{"internalType": "uint256", "name": "subtractedValue", "type": "uint256"}
|
||||
],
|
||||
"name": "decreaseAllowance",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "spender", "type": "address"},
|
||||
{"internalType": "uint256", "name": "addedValue", "type": "uint256"}
|
||||
],
|
||||
"name": "increaseAllowance",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "to", "type": "address"},
|
||||
{"internalType": "uint256", "name": "amount", "type": "uint256"}
|
||||
],
|
||||
"name": "mint",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [
|
||||
{"internalType": "string", "name": "", "type": "string"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "owner",
|
||||
"outputs": [
|
||||
{"internalType": "address", "name": "", "type": "address"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "token", "type": "address"},
|
||||
{"internalType": "uint256", "name": "value", "type": "uint256"}
|
||||
],
|
||||
"name": "payToContract",
|
||||
"outputs": [],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "uint16", "name": "order_id", "type": "uint16"},
|
||||
{"internalType": "address", "name": "seller", "type": "address"}
|
||||
],
|
||||
"name": "payWithNativeToken",
|
||||
"outputs": [],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "string", "name": "username", "type": "string"}
|
||||
],
|
||||
"name": "regiserUser",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "renounceOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "uint16", "name": "id", "type": "uint16"},
|
||||
{"internalType": "address", "name": "buyer", "type": "address"},
|
||||
{"internalType": "address", "name": "seller", "type": "address"}
|
||||
],
|
||||
"name": "selectOrder",
|
||||
"outputs": [
|
||||
{"internalType": "uint232", "name": "", "type": "uint232"},
|
||||
{"internalType": "uint16", "name": "", "type": "uint16"},
|
||||
{"internalType": "uint8", "name": "", "type": "uint8"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [
|
||||
{"internalType": "string", "name": "", "type": "string"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "signer", "type": "address"}
|
||||
],
|
||||
"name": "toggleSigner",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "tokenAddress", "type": "address"}
|
||||
],
|
||||
"name": "toggleToken",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [
|
||||
{"internalType": "uint256", "name": "", "type": "uint256"}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "to", "type": "address"},
|
||||
{"internalType": "uint256", "name": "amount", "type": "uint256"}
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "from", "type": "address"},
|
||||
{"internalType": "address", "name": "to", "type": "address"},
|
||||
{"internalType": "uint256", "name": "amount", "type": "uint256"}
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [
|
||||
{"internalType": "bool", "name": "", "type": "bool"}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "newOwner", "type": "address"}
|
||||
],
|
||||
"name": "transferOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "uint8", "name": "newPercentage", "type": "uint8"}
|
||||
],
|
||||
"name": "updateContractPercentage",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address[]", "name": "buyer", "type": "address[]"},
|
||||
{"internalType": "bytes[]", "name": "signature", "type": "bytes[]"},
|
||||
{"internalType": "uint16[]", "name": "order_id", "type": "uint16[]"},
|
||||
{"internalType": "address", "name": "contractAddress", "type": "address"}
|
||||
],
|
||||
"name": "widthrawForSellers",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{"internalType": "address", "name": "seller", "type": "address"},
|
||||
{"internalType": "bytes", "name": "signature", "type": "bytes"},
|
||||
{"internalType": "uint16", "name": "order_id", "type": "uint16"},
|
||||
{"internalType": "address", "name": "contractAddress", "type": "address"}
|
||||
],
|
||||
"name": "widthrowForBuyers",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
];
|
||||
@@ -1,25 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cw_core/balance.dart';
|
||||
|
||||
class TronBalance extends Balance {
|
||||
TronBalance(this.balance) : super(balance, balance);
|
||||
|
||||
final BigInt balance;
|
||||
|
||||
String toJSON() => json.encode({ 'balance': balance.toString() });
|
||||
|
||||
static TronBalance? fromJSON(String? jsonSource) {
|
||||
if (jsonSource == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final decoded = json.decode(jsonSource) as Map;
|
||||
|
||||
try {
|
||||
return TronBalance(BigInt.parse(decoded['balance']));
|
||||
} catch (e) {
|
||||
return TronBalance(BigInt.zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,568 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import 'package:cw_tron/pending_tron_transaction.dart';
|
||||
import 'package:cw_tron/tron_abi.dart';
|
||||
import 'package:cw_tron/tron_balance.dart';
|
||||
import 'package:cw_tron/tron_http_provider.dart';
|
||||
import 'package:cw_core/tron_token.dart';
|
||||
import 'package:cw_tron/tron_transaction_model.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '.secrets.g.dart' as secrets;
|
||||
import 'package:on_chain/on_chain.dart';
|
||||
|
||||
class TronClient {
|
||||
late final client = ProxyWrapper().getHttpIOClient();
|
||||
|
||||
TronProvider? _provider;
|
||||
// This is an internal tracker, so we don't have to "refetch".
|
||||
int _nativeTxEstimatedFee = 0;
|
||||
|
||||
Future<List<TronTransactionModel>> fetchTransactions(String address,
|
||||
{String? contractAddress}) async {
|
||||
try {
|
||||
final response = await client.get(
|
||||
Uri.https(
|
||||
"api.trongrid.io",
|
||||
"/v1/accounts/$address/transactions",
|
||||
{
|
||||
"only_confirmed": "true",
|
||||
"limit": "200",
|
||||
},
|
||||
),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'TRON-PRO-API-KEY': secrets.tronGridApiKey,
|
||||
},
|
||||
);
|
||||
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (response.statusCode >= 200 &&
|
||||
response.statusCode < 300 &&
|
||||
jsonResponse['status'] != false) {
|
||||
return (jsonResponse['data'] as List).map((e) {
|
||||
return TronTransactionModel.fromJson(e as Map<String, dynamic>);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch (e, s) {
|
||||
log('Error getting tx: ${e.toString()}\n ${s.toString()}');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<TronTRC20TransactionModel>> fetchTrc20ExcludedTransactions(String address) async {
|
||||
try {
|
||||
final response = await client.get(
|
||||
Uri.https(
|
||||
"api.trongrid.io",
|
||||
"/v1/accounts/$address/transactions/trc20",
|
||||
{
|
||||
"only_confirmed": "true",
|
||||
"limit": "200",
|
||||
},
|
||||
),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'TRON-PRO-API-KEY': secrets.tronGridApiKey,
|
||||
},
|
||||
);
|
||||
final jsonResponse = json.decode(response.body) as Map<String, dynamic>;
|
||||
|
||||
if (response.statusCode >= 200 &&
|
||||
response.statusCode < 300 &&
|
||||
jsonResponse['status'] != false) {
|
||||
return (jsonResponse['data'] as List).map((e) {
|
||||
return TronTRC20TransactionModel.fromJson(e as Map<String, dynamic>);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch (e, s) {
|
||||
log('Error getting trc20 tx: ${e.toString()}\n ${s.toString()}');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
bool connect(Node node) {
|
||||
try {
|
||||
final formattedUrl = '${node.isSSL ? 'https' : 'http'}://${node.uriRaw}';
|
||||
_provider = TronProvider(TronHTTPProvider(url: formattedUrl));
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<BigInt> getBalance(TronAddress address, {bool throwOnError = false}) async {
|
||||
try {
|
||||
final accountDetails = await _provider!.request(TronRequestGetAccount(address: address));
|
||||
|
||||
return accountDetails?.balance ?? BigInt.zero;
|
||||
} catch (_) {
|
||||
if (throwOnError) {
|
||||
rethrow;
|
||||
}
|
||||
return BigInt.zero;
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> getFeeLimit(
|
||||
TransactionRaw rawTransaction,
|
||||
TronAddress address,
|
||||
TronAddress receiverAddress, {
|
||||
int energyUsed = 0,
|
||||
bool isEstimatedFeeFlow = false,
|
||||
}) async {
|
||||
try {
|
||||
// Get the tron chain parameters.
|
||||
final chainParams = await _provider!.request(TronRequestGetChainParameters());
|
||||
|
||||
final bandWidthInSun = chainParams.getTransactionFee!;
|
||||
log('BandWidth In Sun: $bandWidthInSun');
|
||||
|
||||
final energyInSun = chainParams.getEnergyFee!;
|
||||
log('Energy In Sun: $energyInSun');
|
||||
|
||||
final fakeTransaction = Transaction(
|
||||
rawData: rawTransaction,
|
||||
signature: [Uint8List(65)],
|
||||
);
|
||||
|
||||
// Calculate the total size of the fake transaction, considering the required network overhead.
|
||||
final transactionSize = fakeTransaction.length + 64;
|
||||
|
||||
// Assign the calculated size to the variable representing the required bandwidth.
|
||||
int neededBandWidth = transactionSize;
|
||||
log('Initial Needed Bandwidth: $neededBandWidth');
|
||||
|
||||
int neededEnergy = energyUsed;
|
||||
log('Initial Needed Energy: $neededEnergy');
|
||||
|
||||
// Fetch account resources to assess the available bandwidth and energy
|
||||
final accountResource =
|
||||
await _provider!.request(TronRequestGetAccountResource(address: address));
|
||||
|
||||
neededEnergy -= accountResource.howManyEnergy.toInt();
|
||||
log('Account resource energy: ${accountResource.howManyEnergy.toInt()}');
|
||||
log('Needed Energy after deducting from account resource energy: $neededEnergy');
|
||||
|
||||
// Deduct the bandwidth from the account's available bandwidth.
|
||||
final BigInt accountBandWidth = accountResource.howManyBandwIth;
|
||||
log('Account resource bandwidth: ${accountResource.howManyBandwIth.toInt()}');
|
||||
|
||||
if (accountBandWidth >= BigInt.from(neededBandWidth) && !isEstimatedFeeFlow) {
|
||||
log('Account has more bandwidth than required');
|
||||
neededBandWidth = 0;
|
||||
}
|
||||
|
||||
if (neededEnergy < 0) {
|
||||
neededEnergy = 0;
|
||||
}
|
||||
|
||||
final energyBurn = neededEnergy * energyInSun.toInt();
|
||||
log('Energy Burn: $energyBurn');
|
||||
|
||||
final bandWidthBurn = neededBandWidth * bandWidthInSun;
|
||||
log('Bandwidth Burn: $bandWidthBurn');
|
||||
|
||||
int totalBurn = energyBurn + bandWidthBurn;
|
||||
log('Total Burn: $totalBurn');
|
||||
|
||||
/// If there is a note (memo), calculate the memo fee.
|
||||
if (rawTransaction.data != null) {
|
||||
totalBurn += chainParams.getMemoFee!;
|
||||
}
|
||||
|
||||
log('Final total burn: $totalBurn');
|
||||
|
||||
return totalBurn;
|
||||
} catch (_) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> getEstimatedFee(TronAddress ownerAddress) async {
|
||||
const constantAmount = '1000';
|
||||
// Fetch the latest Tron block
|
||||
final block = await _provider!.request(TronRequestGetNowBlock());
|
||||
|
||||
// Create the transfer contract
|
||||
final contract = TransferContract(
|
||||
amount: TronHelper.toSun(constantAmount),
|
||||
ownerAddress: ownerAddress,
|
||||
toAddress: ownerAddress,
|
||||
);
|
||||
|
||||
// Prepare the contract parameter for the transaction.
|
||||
final parameter = Any(typeUrl: contract.typeURL, value: contract);
|
||||
|
||||
// Create a TransactionContract object with the contract type and parameter.
|
||||
final transactionContract =
|
||||
TransactionContract(type: contract.contractType, parameter: parameter);
|
||||
|
||||
// Set the transaction expiration time (maximum 24 hours)
|
||||
final expireTime = DateTime.now().add(const Duration(minutes: 30));
|
||||
|
||||
// Create a raw transaction
|
||||
TransactionRaw rawTransaction = TransactionRaw(
|
||||
refBlockBytes: block.blockHeader.rawData.refBlockBytes,
|
||||
refBlockHash: block.blockHeader.rawData.refBlockHash,
|
||||
expiration: BigInt.from(expireTime.millisecondsSinceEpoch),
|
||||
contract: [transactionContract],
|
||||
timestamp: block.blockHeader.rawData.timestamp,
|
||||
);
|
||||
|
||||
final estimatedFee = await getFeeLimit(
|
||||
rawTransaction,
|
||||
ownerAddress,
|
||||
ownerAddress,
|
||||
isEstimatedFeeFlow: true,
|
||||
);
|
||||
|
||||
_nativeTxEstimatedFee = estimatedFee;
|
||||
|
||||
return estimatedFee;
|
||||
}
|
||||
|
||||
Future<int> getTRCEstimatedFee(TronAddress ownerAddress) async {
|
||||
String contractAddress = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t';
|
||||
String constantAmount =
|
||||
'0'; // We're using 0 as the base amount here as we get an error when balance is zero i.e for new wallets.
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final function = contract.functionFromName("transfer");
|
||||
|
||||
/// address /// amount
|
||||
final transferparams = [
|
||||
ownerAddress,
|
||||
TronHelper.toSun(constantAmount),
|
||||
];
|
||||
|
||||
final contractAddr = TronAddress(contractAddress);
|
||||
|
||||
final request = await _provider!.request(
|
||||
TronRequestTriggerConstantContract(
|
||||
ownerAddress: ownerAddress,
|
||||
contractAddress: contractAddr,
|
||||
data: function.encodeHex(transferparams),
|
||||
),
|
||||
);
|
||||
|
||||
if (!request.isSuccess) {
|
||||
log("Tron TRC20 error: ${request.error} \n ${request.respose}");
|
||||
}
|
||||
|
||||
final feeLimit = await getFeeLimit(
|
||||
request.transactionRaw!,
|
||||
ownerAddress,
|
||||
ownerAddress,
|
||||
energyUsed: request.energyUsed ?? 0,
|
||||
isEstimatedFeeFlow: true,
|
||||
);
|
||||
return feeLimit;
|
||||
}
|
||||
|
||||
Future<PendingTronTransaction> signTransaction({
|
||||
required TronPrivateKey ownerPrivKey,
|
||||
required String toAddress,
|
||||
required String amount,
|
||||
required CryptoCurrency currency,
|
||||
required BigInt tronBalance,
|
||||
required bool sendAll,
|
||||
}) async {
|
||||
// Get the owner tron address from the key
|
||||
final ownerAddress = ownerPrivKey.publicKey().toAddress();
|
||||
|
||||
// Define the receiving Tron address for the transaction.
|
||||
final receiverAddress = TronAddress(toAddress);
|
||||
|
||||
bool isNativeTransaction = currency == CryptoCurrency.trx;
|
||||
|
||||
String totalAmount;
|
||||
TransactionRaw rawTransaction;
|
||||
if (isNativeTransaction) {
|
||||
if (sendAll) {
|
||||
final accountResource =
|
||||
await _provider!.request(TronRequestGetAccountResource(address: ownerAddress));
|
||||
|
||||
final availableBandWidth = accountResource.howManyBandwIth.toInt();
|
||||
|
||||
// 269 is the current middle ground for bandwidth per transaction
|
||||
if (availableBandWidth >= 269) {
|
||||
totalAmount = amount;
|
||||
} else {
|
||||
final amountInSun = TronHelper.toSun(amount).toInt();
|
||||
|
||||
// 5000 added here is a buffer since we're working with "estimated" value of the fee.
|
||||
final result = amountInSun - (_nativeTxEstimatedFee + 5000);
|
||||
|
||||
totalAmount = TronHelper.fromSun(BigInt.from(result));
|
||||
}
|
||||
} else {
|
||||
totalAmount = amount;
|
||||
}
|
||||
rawTransaction = await _signNativeTransaction(
|
||||
ownerAddress,
|
||||
receiverAddress,
|
||||
totalAmount,
|
||||
tronBalance,
|
||||
sendAll,
|
||||
);
|
||||
} else {
|
||||
final tokenAddress = (currency as TronToken).contractAddress;
|
||||
totalAmount = amount;
|
||||
rawTransaction = await _signTrcTokenTransaction(
|
||||
ownerAddress,
|
||||
receiverAddress,
|
||||
totalAmount,
|
||||
tokenAddress,
|
||||
tronBalance,
|
||||
);
|
||||
}
|
||||
|
||||
final signature = ownerPrivKey.sign(rawTransaction.toBuffer());
|
||||
|
||||
sendTx() async => await sendTransaction(
|
||||
rawTransaction: rawTransaction,
|
||||
signature: signature,
|
||||
);
|
||||
|
||||
return PendingTronTransaction(
|
||||
signedTransaction: signature,
|
||||
amount: totalAmount,
|
||||
fee: TronHelper.fromSun(rawTransaction.feeLimit ?? BigInt.zero),
|
||||
sendTransaction: sendTx,
|
||||
);
|
||||
}
|
||||
|
||||
Future<TransactionRaw> _signNativeTransaction(
|
||||
TronAddress ownerAddress,
|
||||
TronAddress receiverAddress,
|
||||
String amount,
|
||||
BigInt tronBalance,
|
||||
bool sendAll,
|
||||
) async {
|
||||
// This is introduce to server as a limit in cases where feeLimit is 0
|
||||
// The transaction signing will fail if the feeLimit is explicitly 0.
|
||||
int defaultFeeLimit = 269000;
|
||||
|
||||
final block = await _provider!.request(TronRequestGetNowBlock());
|
||||
// Create the transfer contract
|
||||
final contract = TransferContract(
|
||||
amount: TronHelper.toSun(amount),
|
||||
ownerAddress: ownerAddress,
|
||||
toAddress: receiverAddress,
|
||||
);
|
||||
|
||||
// Prepare the contract parameter for the transaction.
|
||||
final parameter = Any(typeUrl: contract.typeURL, value: contract);
|
||||
|
||||
// Create a TransactionContract object with the contract type and parameter.
|
||||
final transactionContract =
|
||||
TransactionContract(type: contract.contractType, parameter: parameter);
|
||||
|
||||
// Set the transaction expiration time (maximum 24 hours)
|
||||
final expireTime = DateTime.now().add(const Duration(minutes: 30));
|
||||
|
||||
// Create a raw transaction
|
||||
TransactionRaw rawTransaction = TransactionRaw(
|
||||
refBlockBytes: block.blockHeader.rawData.refBlockBytes,
|
||||
refBlockHash: block.blockHeader.rawData.refBlockHash,
|
||||
expiration: BigInt.from(expireTime.millisecondsSinceEpoch),
|
||||
contract: [transactionContract],
|
||||
timestamp: block.blockHeader.rawData.timestamp,
|
||||
);
|
||||
|
||||
final feeLimit = await getFeeLimit(rawTransaction, ownerAddress, receiverAddress);
|
||||
final feeLimitToUse = feeLimit != 0 ? feeLimit : defaultFeeLimit;
|
||||
final tronBalanceInt = tronBalance.toInt();
|
||||
|
||||
if (feeLimit > tronBalanceInt) {
|
||||
final feeInTrx = TronHelper.fromSun(BigInt.parse(feeLimit.toString()));
|
||||
throw Exception(
|
||||
'You don\'t have enough TRX to cover the transaction fee for this transaction. Please top up.\nTransaction fee: $feeInTrx TRX',
|
||||
);
|
||||
}
|
||||
|
||||
rawTransaction = rawTransaction.copyWith(
|
||||
feeLimit: BigInt.from(feeLimitToUse),
|
||||
);
|
||||
|
||||
return rawTransaction;
|
||||
}
|
||||
|
||||
Future<TransactionRaw> _signTrcTokenTransaction(
|
||||
TronAddress ownerAddress,
|
||||
TronAddress receiverAddress,
|
||||
String amount,
|
||||
String contractAddress,
|
||||
BigInt tronBalance,
|
||||
) async {
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final function = contract.functionFromName("transfer");
|
||||
|
||||
/// address /// amount
|
||||
final transferparams = [
|
||||
receiverAddress,
|
||||
TronHelper.toSun(amount),
|
||||
];
|
||||
|
||||
final contractAddr = TronAddress(contractAddress);
|
||||
|
||||
final request = await _provider!.request(
|
||||
TronRequestTriggerConstantContract(
|
||||
ownerAddress: ownerAddress,
|
||||
contractAddress: contractAddr,
|
||||
data: function.encodeHex(transferparams),
|
||||
),
|
||||
);
|
||||
|
||||
if (!request.isSuccess) {
|
||||
log("Tron TRC20 error: ${request.error} \n ${request.respose}");
|
||||
throw Exception(
|
||||
'An error occurred while creating the transfer request. Please try again.',
|
||||
);
|
||||
}
|
||||
|
||||
final feeLimit = await getFeeLimit(
|
||||
request.transactionRaw!,
|
||||
ownerAddress,
|
||||
receiverAddress,
|
||||
energyUsed: request.energyUsed ?? 0,
|
||||
);
|
||||
|
||||
final tronBalanceInt = tronBalance.toInt();
|
||||
|
||||
if (feeLimit > tronBalanceInt) {
|
||||
final feeInTrx = TronHelper.fromSun(BigInt.parse(feeLimit.toString()));
|
||||
throw Exception(
|
||||
'You don\'t have enough TRX to cover the transaction fee for this transaction. Please top up. Transaction fee: $feeInTrx TRX',
|
||||
);
|
||||
}
|
||||
|
||||
final rawTransaction = request.transactionRaw!.copyWith(
|
||||
feeLimit: BigInt.from(feeLimit),
|
||||
);
|
||||
|
||||
return rawTransaction;
|
||||
}
|
||||
|
||||
Future<String> sendTransaction({
|
||||
required TransactionRaw rawTransaction,
|
||||
required List<int> signature,
|
||||
}) async {
|
||||
try {
|
||||
final transaction = Transaction(rawData: rawTransaction, signature: [signature]);
|
||||
|
||||
final raw = BytesUtils.toHexString(transaction.toBuffer());
|
||||
|
||||
final txBroadcastResult = await _provider!.request(TronRequestBroadcastHex(transaction: raw));
|
||||
|
||||
if (txBroadcastResult.isSuccess) {
|
||||
return txBroadcastResult.txId!;
|
||||
} else {
|
||||
throw Exception(txBroadcastResult.error);
|
||||
}
|
||||
} catch (e) {
|
||||
log('Send block Exception: ${e.toString()}');
|
||||
throw Exception(e);
|
||||
}
|
||||
}
|
||||
|
||||
Future<TronBalance> fetchTronTokenBalances(String userAddress, String contractAddress, {bool throwOnError = false}) async {
|
||||
try {
|
||||
final ownerAddress = TronAddress(userAddress);
|
||||
|
||||
final tokenAddress = TronAddress(contractAddress);
|
||||
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final function = contract.functionFromName("balanceOf");
|
||||
|
||||
final request = await _provider!.request(
|
||||
TronRequestTriggerConstantContract.fromMethod(
|
||||
ownerAddress: ownerAddress,
|
||||
contractAddress: tokenAddress,
|
||||
function: function,
|
||||
params: [ownerAddress],
|
||||
),
|
||||
);
|
||||
|
||||
final outputResult = request.outputResult?.first ?? BigInt.zero;
|
||||
|
||||
return TronBalance(outputResult);
|
||||
} catch (_) {
|
||||
if (throwOnError) {
|
||||
rethrow;
|
||||
}
|
||||
return TronBalance(BigInt.zero);
|
||||
}
|
||||
}
|
||||
|
||||
Future<TronToken?> getTronToken(String contractAddress, String userAddress) async {
|
||||
try {
|
||||
final tokenAddress = TronAddress(contractAddress);
|
||||
|
||||
final ownerAddress = TronAddress(userAddress);
|
||||
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final name =
|
||||
(await getTokenDetail(contract, "name", ownerAddress, tokenAddress) as String?) ?? '';
|
||||
|
||||
final symbol =
|
||||
(await getTokenDetail(contract, "symbol", ownerAddress, tokenAddress) as String?) ?? '';
|
||||
|
||||
final decimal =
|
||||
(await getTokenDetail(contract, "decimals", ownerAddress, tokenAddress) as BigInt?) ??
|
||||
BigInt.zero;
|
||||
|
||||
return TronToken(
|
||||
name: name,
|
||||
symbol: symbol,
|
||||
contractAddress: contractAddress,
|
||||
decimal: decimal.toInt(),
|
||||
);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<dynamic> getTokenDetail(
|
||||
ContractABI contract,
|
||||
String functionName,
|
||||
TronAddress ownerAddress,
|
||||
TronAddress tokenAddress,
|
||||
) async {
|
||||
final function = contract.functionFromName(functionName);
|
||||
|
||||
try {
|
||||
final request = await _provider!.request(
|
||||
TronRequestTriggerConstantContract.fromMethod(
|
||||
ownerAddress: ownerAddress,
|
||||
contractAddress: tokenAddress,
|
||||
function: function,
|
||||
params: [],
|
||||
),
|
||||
);
|
||||
|
||||
final outputResult = request.outputResult?.first;
|
||||
|
||||
return outputResult;
|
||||
} catch (_) {
|
||||
log('Erorr fetching detail: ${_.toString()}');
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
class TronMnemonicIsIncorrectException implements Exception {
|
||||
@override
|
||||
String toString() =>
|
||||
'Tron mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
|
||||
}
|
||||
class TronTransactionCreationException implements Exception {
|
||||
final String exceptionMessage;
|
||||
|
||||
TronTransactionCreationException(CryptoCurrency currency)
|
||||
: exceptionMessage = 'Wrong balance. Not enough ${currency.title} on your balance.';
|
||||
|
||||
@override
|
||||
String toString() => exceptionMessage;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import 'package:on_chain/tron/tron.dart';
|
||||
import '.secrets.g.dart' as secrets;
|
||||
|
||||
class TronHTTPProvider implements TronServiceProvider {
|
||||
TronHTTPProvider(
|
||||
{required this.url,
|
||||
this.defaultRequestTimeout = const Duration(seconds: 30)});
|
||||
|
||||
@override
|
||||
final String url;
|
||||
late final client = ProxyWrapper().getHttpIOClient();
|
||||
final Duration defaultRequestTimeout;
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> get(TronRequestDetails params,
|
||||
[Duration? timeout]) async {
|
||||
final response = await client.get(Uri.parse(params.url(url)), headers: {
|
||||
'Content-Type': 'application/json',
|
||||
if (url.contains("trongrid")) 'TRON-PRO-API-KEY': secrets.tronGridApiKey,
|
||||
if (url.contains("nownodes")) 'api-key': secrets.tronNowNodesApiKey,
|
||||
}).timeout(timeout ?? defaultRequestTimeout);
|
||||
final data = json.decode(response.body) as Map<String, dynamic>;
|
||||
return data;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, dynamic>> post(TronRequestDetails params,
|
||||
[Duration? timeout]) async {
|
||||
final response = await client
|
||||
.post(Uri.parse(params.url(url)),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
if (url.contains("trongrid"))
|
||||
'TRON-PRO-API-KEY': secrets.tronGridApiKey,
|
||||
if (url.contains("nownodes"))
|
||||
'api-key': secrets.tronNowNodesApiKey,
|
||||
},
|
||||
body: params.toRequestBody())
|
||||
.timeout(timeout ?? defaultRequestTimeout);
|
||||
final data = json.decode(response.body) as Map<String, dynamic>;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/output_info.dart';
|
||||
|
||||
class TronTransactionCredentials {
|
||||
TronTransactionCredentials(
|
||||
this.outputs, {
|
||||
required this.currency,
|
||||
});
|
||||
|
||||
final List<OutputInfo> outputs;
|
||||
final CryptoCurrency currency;
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:core';
|
||||
import 'dart:developer';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:cw_tron/tron_transaction_info.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
|
||||
part 'tron_transaction_history.g.dart';
|
||||
|
||||
class TronTransactionHistory = TronTransactionHistoryBase with _$TronTransactionHistory;
|
||||
|
||||
abstract class TronTransactionHistoryBase extends TransactionHistoryBase<TronTransactionInfo>
|
||||
with Store {
|
||||
TronTransactionHistoryBase(
|
||||
{required this.walletInfo, required String password, required this.encryptionFileUtils})
|
||||
: _password = password {
|
||||
transactions = ObservableMap<String, TronTransactionInfo>();
|
||||
}
|
||||
|
||||
String _password;
|
||||
|
||||
final WalletInfo walletInfo;
|
||||
final EncryptionFileUtils encryptionFileUtils;
|
||||
|
||||
Future<void> init() async {
|
||||
clear();
|
||||
await _load();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
String transactionsHistoryFileNameForWallet = 'tron_transactions.json';
|
||||
try {
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
String path = '$dirPath/$transactionsHistoryFileNameForWallet';
|
||||
final transactionMaps = transactions.map((key, value) => MapEntry(key, value.toJson()));
|
||||
final data = json.encode({'transactions': transactionMaps});
|
||||
await encryptionFileUtils.write(path: path, password: _password, data: data);
|
||||
} catch (e, s) {
|
||||
log('Error while saving ${walletInfo.type.name} transaction history: ${e.toString()}');
|
||||
log(s.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void addOne(TronTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
|
||||
@override
|
||||
void addMany(Map<String, TronTransactionInfo> transactions) =>
|
||||
this.transactions.addAll(transactions);
|
||||
|
||||
Future<Map<String, dynamic>> _read() async {
|
||||
String transactionsHistoryFileNameForWallet = 'tron_transactions.json';
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
String path = '$dirPath/$transactionsHistoryFileNameForWallet';
|
||||
final content = await encryptionFileUtils.read(path: path, password: _password);
|
||||
if (content.isEmpty) {
|
||||
return {};
|
||||
}
|
||||
return json.decode(content) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
try {
|
||||
final content = await _read();
|
||||
final txs = content['transactions'] as Map<String, dynamic>? ?? {};
|
||||
|
||||
for (var entry in txs.entries) {
|
||||
final val = entry.value;
|
||||
|
||||
if (val is Map<String, dynamic>) {
|
||||
final tx = TronTransactionInfo.fromJson(val);
|
||||
_update(tx);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
void _update(TronTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
import 'package:cw_core/format_amount.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:on_chain/on_chain.dart' as onchain;
|
||||
import 'package:on_chain/tron/tron.dart';
|
||||
|
||||
class TronTransactionInfo extends TransactionInfo {
|
||||
TronTransactionInfo({
|
||||
required this.id,
|
||||
required this.tronAmount,
|
||||
required this.txFee,
|
||||
required this.direction,
|
||||
required this.blockTime,
|
||||
required this.to,
|
||||
required this.from,
|
||||
required this.isPending,
|
||||
this.tokenSymbol = 'TRX',
|
||||
}) : amount = tronAmount.toInt();
|
||||
|
||||
final String id;
|
||||
final String? to;
|
||||
final String? from;
|
||||
final int amount;
|
||||
final BigInt tronAmount;
|
||||
final String tokenSymbol;
|
||||
final DateTime blockTime;
|
||||
final bool isPending;
|
||||
final int? txFee;
|
||||
final TransactionDirection direction;
|
||||
|
||||
factory TronTransactionInfo.fromJson(Map<String, dynamic> data) {
|
||||
return TronTransactionInfo(
|
||||
id: data['id'] as String,
|
||||
tronAmount: BigInt.parse(data['tronAmount']),
|
||||
txFee: data['txFee'],
|
||||
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||
blockTime: DateTime.fromMillisecondsSinceEpoch(data['blockTime'] as int),
|
||||
tokenSymbol: data['tokenSymbol'] as String,
|
||||
to: data['to'],
|
||||
from: data['from'],
|
||||
isPending: data['isPending'],
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'tronAmount': tronAmount.toString(),
|
||||
'txFee': txFee,
|
||||
'direction': direction.index,
|
||||
'blockTime': blockTime.millisecondsSinceEpoch,
|
||||
'tokenSymbol': tokenSymbol,
|
||||
'to': to,
|
||||
'from': from,
|
||||
'isPending': isPending,
|
||||
};
|
||||
|
||||
@override
|
||||
DateTime get date => blockTime;
|
||||
|
||||
String? _fiatAmount;
|
||||
|
||||
@override
|
||||
String amountFormatted() {
|
||||
String formattedAmount = _rawAmountAsString(tronAmount);
|
||||
|
||||
return '$formattedAmount $tokenSymbol';
|
||||
}
|
||||
|
||||
@override
|
||||
String fiatAmount() => _fiatAmount ?? '';
|
||||
|
||||
@override
|
||||
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
||||
|
||||
@override
|
||||
String feeFormatted() {
|
||||
final formattedFee = onchain.TronHelper.fromSun(BigInt.from(txFee ?? 0));
|
||||
|
||||
return '$formattedFee TRX';
|
||||
}
|
||||
|
||||
String _rawAmountAsString(BigInt amount) {
|
||||
String formattedAmount = TronHelper.fromSun(amount);
|
||||
|
||||
if (formattedAmount.length >= 8) {
|
||||
formattedAmount = formattedAmount.substring(0, 8);
|
||||
}
|
||||
|
||||
return formattedAmount;
|
||||
}
|
||||
|
||||
String rawTronAmount() => _rawAmountAsString(tronAmount);
|
||||
}
|
||||
@@ -1,205 +0,0 @@
|
||||
import 'package:blockchain_utils/hex/hex.dart';
|
||||
import 'package:on_chain/on_chain.dart';
|
||||
|
||||
class TronTRC20TransactionModel extends TronTransactionModel {
|
||||
String? transactionId;
|
||||
|
||||
String? tokenSymbol;
|
||||
|
||||
int? timestamp;
|
||||
|
||||
@override
|
||||
String? from;
|
||||
|
||||
@override
|
||||
String? to;
|
||||
|
||||
String? value;
|
||||
|
||||
@override
|
||||
String get hash => transactionId!;
|
||||
|
||||
@override
|
||||
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp ?? 0);
|
||||
|
||||
@override
|
||||
BigInt? get amount => BigInt.parse(value ?? '0');
|
||||
|
||||
@override
|
||||
int? get fee => 0;
|
||||
|
||||
TronTRC20TransactionModel({
|
||||
this.transactionId,
|
||||
this.tokenSymbol,
|
||||
this.timestamp,
|
||||
this.from,
|
||||
this.to,
|
||||
this.value,
|
||||
});
|
||||
|
||||
TronTRC20TransactionModel.fromJson(Map<String, dynamic> json) {
|
||||
transactionId = json['transaction_id'];
|
||||
tokenSymbol = json['token_info'] != null ? json['token_info']['symbol'] : null;
|
||||
timestamp = json['block_timestamp'];
|
||||
from = json['from'];
|
||||
to = json['to'];
|
||||
value = json['value'];
|
||||
}
|
||||
}
|
||||
|
||||
class TronTransactionModel {
|
||||
List<Ret>? ret;
|
||||
String? txID;
|
||||
int? blockTimestamp;
|
||||
List<Contract>? contracts;
|
||||
|
||||
/// Getters to extract out the needed/useful information directly from the model params
|
||||
/// Without having to go through extra steps in the methods that use this model.
|
||||
bool get isError {
|
||||
if (ret?.first.contractRet == null) return true;
|
||||
|
||||
return ret?.first.contractRet != "SUCCESS";
|
||||
}
|
||||
|
||||
String get hash => txID!;
|
||||
|
||||
DateTime get date => DateTime.fromMillisecondsSinceEpoch(blockTimestamp ?? 0);
|
||||
|
||||
String? get from => contracts?.first.parameter?.value?.ownerAddress;
|
||||
|
||||
String? get to => contracts?.first.parameter?.value?.receiverAddress;
|
||||
|
||||
BigInt? get amount => contracts?.first.parameter?.value?.txAmount;
|
||||
|
||||
int? get fee => ret?.first.fee;
|
||||
|
||||
String? get contractAddress => contracts?.first.parameter?.value?.contractAddress;
|
||||
|
||||
TronTransactionModel({
|
||||
this.ret,
|
||||
this.txID,
|
||||
this.blockTimestamp,
|
||||
this.contracts,
|
||||
});
|
||||
|
||||
TronTransactionModel.fromJson(Map<String, dynamic> json) {
|
||||
if (json['ret'] != null) {
|
||||
ret = <Ret>[];
|
||||
json['ret'].forEach((v) {
|
||||
ret!.add(Ret.fromJson(v));
|
||||
});
|
||||
}
|
||||
txID = json['txID'];
|
||||
blockTimestamp = json['block_timestamp'];
|
||||
contracts = json['raw_data'] != null
|
||||
? (json['raw_data']['contract'] as List)
|
||||
.map((e) => Contract.fromJson(e as Map<String, dynamic>))
|
||||
.toList()
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
class Ret {
|
||||
String? contractRet;
|
||||
int? fee;
|
||||
|
||||
Ret({this.contractRet, this.fee});
|
||||
|
||||
Ret.fromJson(Map<String, dynamic> json) {
|
||||
contractRet = json['contractRet'];
|
||||
fee = json['fee'];
|
||||
}
|
||||
}
|
||||
|
||||
class Contract {
|
||||
Parameter? parameter;
|
||||
String? type;
|
||||
|
||||
Contract({this.parameter, this.type});
|
||||
|
||||
Contract.fromJson(Map<String, dynamic> json) {
|
||||
parameter = json['parameter'] != null ? Parameter.fromJson(json['parameter']) : null;
|
||||
type = json['type'];
|
||||
}
|
||||
}
|
||||
|
||||
class Parameter {
|
||||
Value? value;
|
||||
String? typeUrl;
|
||||
|
||||
Parameter({this.value, this.typeUrl});
|
||||
|
||||
Parameter.fromJson(Map<String, dynamic> json) {
|
||||
value = json['value'] != null ? Value.fromJson(json['value']) : null;
|
||||
typeUrl = json['type_url'];
|
||||
}
|
||||
}
|
||||
|
||||
class Value {
|
||||
String? data;
|
||||
String? ownerAddress;
|
||||
String? contractAddress;
|
||||
int? amount;
|
||||
String? toAddress;
|
||||
String? assetName;
|
||||
|
||||
//Getters to extract address for tron transactions
|
||||
/// If the contract address is null, it returns the toAddress
|
||||
/// If it's not null, it decodes the data field and gets the receiver address.
|
||||
String? get receiverAddress {
|
||||
if (contractAddress == null) return toAddress;
|
||||
|
||||
if (data == null) return null;
|
||||
|
||||
return _decodeAddressFromEncodedDataField(data!);
|
||||
}
|
||||
|
||||
//Getters to extract amount for tron transactions
|
||||
/// If the contract address is null, it returns the amount
|
||||
/// If it's not null, it decodes the data field and gets the tx amount.
|
||||
BigInt? get txAmount {
|
||||
if (contractAddress == null) return BigInt.from(amount ?? 0);
|
||||
|
||||
if (data == null) return null;
|
||||
|
||||
return _decodeAmountInvolvedFromEncodedDataField(data!);
|
||||
}
|
||||
|
||||
Value(
|
||||
{this.data,
|
||||
this.ownerAddress,
|
||||
this.contractAddress,
|
||||
this.amount,
|
||||
this.toAddress,
|
||||
this.assetName});
|
||||
|
||||
Value.fromJson(Map<String, dynamic> json) {
|
||||
data = json['data'];
|
||||
ownerAddress = json['owner_address'];
|
||||
contractAddress = json['contract_address'];
|
||||
amount = json['amount'];
|
||||
toAddress = json['to_address'];
|
||||
assetName = json['asset_name'];
|
||||
}
|
||||
|
||||
/// To get the address from the encoded data field
|
||||
String _decodeAddressFromEncodedDataField(String output) {
|
||||
// To get the receiver address from the encoded params
|
||||
output = output.replaceFirst('0x', '').substring(8);
|
||||
final abiCoder = ABICoder.fromType('address');
|
||||
final decoded = abiCoder.decode(AbiParameter.bytes, hex.decode(output));
|
||||
final tronAddress = TronAddress.fromEthAddress((decoded.result as ETHAddress).toBytes());
|
||||
|
||||
return tronAddress.toString();
|
||||
}
|
||||
|
||||
/// To get the amount from the encoded data field
|
||||
BigInt _decodeAmountInvolvedFromEncodedDataField(String output) {
|
||||
output = output.replaceFirst('0x', '').substring(72);
|
||||
final amountAbiCoder = ABICoder.fromType('uint256');
|
||||
final decodedA = amountAbiCoder.decode(AbiParameter.uint256, hex.decode(output));
|
||||
final amount = decodedA.result as BigInt;
|
||||
|
||||
return amount;
|
||||
}
|
||||
}
|
||||
@@ -1,676 +0,0 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
import 'package:cw_core/transaction_priority.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_keys_file.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_tron/default_tron_tokens.dart';
|
||||
import 'package:cw_tron/tron_abi.dart';
|
||||
import 'package:cw_tron/tron_balance.dart';
|
||||
import 'package:cw_tron/tron_client.dart';
|
||||
import 'package:cw_tron/tron_exception.dart';
|
||||
import 'package:cw_core/tron_token.dart';
|
||||
import 'package:cw_tron/tron_transaction_credentials.dart';
|
||||
import 'package:cw_tron/tron_transaction_history.dart';
|
||||
import 'package:cw_tron/tron_transaction_info.dart';
|
||||
import 'package:cw_tron/tron_wallet_addresses.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:on_chain/on_chain.dart';
|
||||
|
||||
part 'tron_wallet.g.dart';
|
||||
|
||||
class TronWallet = TronWalletBase with _$TronWallet;
|
||||
|
||||
abstract class TronWalletBase
|
||||
extends WalletBase<TronBalance, TronTransactionHistory, TronTransactionInfo>
|
||||
with Store, WalletKeysFile {
|
||||
TronWalletBase({
|
||||
required WalletInfo walletInfo,
|
||||
required DerivationInfo derivationInfo,
|
||||
String? mnemonic,
|
||||
String? privateKey,
|
||||
required String password,
|
||||
TronBalance? initialBalance,
|
||||
required this.encryptionFileUtils,
|
||||
this.passphrase,
|
||||
}) : syncStatus = const NotConnectedSyncStatus(),
|
||||
_password = password,
|
||||
_mnemonic = mnemonic,
|
||||
_hexPrivateKey = privateKey,
|
||||
_client = TronClient(),
|
||||
walletAddresses = TronWalletAddresses(walletInfo),
|
||||
balance = ObservableMap<CryptoCurrency, TronBalance>.of(
|
||||
{CryptoCurrency.trx: initialBalance ?? TronBalance(BigInt.zero)},
|
||||
),
|
||||
super(walletInfo, derivationInfo) {
|
||||
this.walletInfo = walletInfo;
|
||||
transactionHistory = TronTransactionHistory(
|
||||
walletInfo: walletInfo, password: password, encryptionFileUtils: encryptionFileUtils);
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(TronToken.typeId)) {
|
||||
CakeHive.registerAdapter(TronTokenAdapter());
|
||||
}
|
||||
}
|
||||
|
||||
final String? _mnemonic;
|
||||
final String? _hexPrivateKey;
|
||||
final String _password;
|
||||
final EncryptionFileUtils encryptionFileUtils;
|
||||
|
||||
late final Box<TronToken> tronTokensBox;
|
||||
|
||||
late final TronPrivateKey _tronPrivateKey;
|
||||
|
||||
late final TronPublicKey _tronPublicKey;
|
||||
|
||||
TronPublicKey get tronPublicKey => _tronPublicKey;
|
||||
|
||||
TronPrivateKey get tronPrivateKey => _tronPrivateKey;
|
||||
|
||||
late String _tronAddress;
|
||||
|
||||
late final TronClient _client;
|
||||
|
||||
Timer? _transactionsUpdateTimer;
|
||||
|
||||
@override
|
||||
WalletAddresses walletAddresses;
|
||||
|
||||
@observable
|
||||
String? nativeTxEstimatedFee;
|
||||
|
||||
@observable
|
||||
String? trc20EstimatedFee;
|
||||
|
||||
@override
|
||||
@observable
|
||||
SyncStatus syncStatus;
|
||||
|
||||
@override
|
||||
@observable
|
||||
late ObservableMap<CryptoCurrency, TronBalance> balance;
|
||||
|
||||
Future<void> init() async {
|
||||
await initTronTokensBox();
|
||||
|
||||
await walletAddresses.init();
|
||||
await transactionHistory.init();
|
||||
_tronPrivateKey = await getPrivateKey(
|
||||
mnemonic: _mnemonic,
|
||||
privateKey: _hexPrivateKey,
|
||||
password: _password,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
_tronPublicKey = _tronPrivateKey.publicKey();
|
||||
|
||||
_tronAddress = _tronPublicKey.toAddress().toString();
|
||||
|
||||
walletAddresses.address = _tronAddress;
|
||||
|
||||
await save();
|
||||
}
|
||||
|
||||
static Future<TronWallet> open({
|
||||
required String name,
|
||||
required String password,
|
||||
required WalletInfo walletInfo,
|
||||
required EncryptionFileUtils encryptionFileUtils,
|
||||
}) async {
|
||||
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||
final path = await pathForWallet(name: name, type: walletInfo.type);
|
||||
|
||||
Map<String, dynamic>? data;
|
||||
try {
|
||||
final jsonSource = await encryptionFileUtils.read(path: path, password: password);
|
||||
|
||||
data = json.decode(jsonSource) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
if (!hasKeysFile) rethrow;
|
||||
}
|
||||
|
||||
final balance = TronBalance.fromJSON(data?['balance'] as String?) ?? TronBalance(BigInt.zero);
|
||||
|
||||
final WalletKeysData keysData;
|
||||
// Migrate wallet from the old scheme to then new .keys file scheme
|
||||
if (!hasKeysFile) {
|
||||
final mnemonic = data!['mnemonic'] as String?;
|
||||
final privateKey = data['private_key'] as String?;
|
||||
final passphrase = data['passphrase'] as String?;
|
||||
|
||||
keysData = WalletKeysData(mnemonic: mnemonic, privateKey: privateKey, passphrase: passphrase);
|
||||
} else {
|
||||
keysData = await WalletKeysFile.readKeysFile(
|
||||
name,
|
||||
walletInfo.type,
|
||||
password,
|
||||
encryptionFileUtils,
|
||||
);
|
||||
}
|
||||
|
||||
final derivationInfo = await walletInfo.getDerivationInfo();
|
||||
|
||||
return TronWallet(
|
||||
walletInfo: walletInfo,
|
||||
derivationInfo: derivationInfo,
|
||||
password: password,
|
||||
mnemonic: keysData.mnemonic,
|
||||
privateKey: keysData.privateKey,
|
||||
passphrase: keysData.passphrase,
|
||||
initialBalance: balance,
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
);
|
||||
}
|
||||
|
||||
void addInitialTokens() {
|
||||
final initialTronTokens = DefaultTronTokens().initialTronTokens;
|
||||
|
||||
for (var token in initialTronTokens) {
|
||||
if (!tronTokensBox.containsKey(token.contractAddress)) {
|
||||
tronTokensBox.put(token.contractAddress, token);
|
||||
} else {
|
||||
// update existing token
|
||||
final existingToken = tronTokensBox.get(token.contractAddress);
|
||||
tronTokensBox.put(
|
||||
token.contractAddress, TronToken.copyWith(token, enabled: existingToken!.enabled));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initTronTokensBox() async {
|
||||
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${TronToken.boxName}";
|
||||
|
||||
tronTokensBox = await CakeHive.openBox<TronToken>(boxName);
|
||||
}
|
||||
|
||||
String idFor(String name, WalletType type) => '${walletTypeToString(type).toLowerCase()}_$name';
|
||||
|
||||
Future<TronPrivateKey> getPrivateKey({
|
||||
String? mnemonic,
|
||||
String? privateKey,
|
||||
required String password,
|
||||
String? passphrase,
|
||||
}) async {
|
||||
assert(mnemonic != null || privateKey != null);
|
||||
|
||||
if (privateKey != null) return TronPrivateKey(privateKey);
|
||||
|
||||
final seed = bip39.mnemonicToSeed(mnemonic!, passphrase: passphrase ?? '');
|
||||
|
||||
// Derive a TRON private key from the seed
|
||||
final bip44 = Bip44.fromSeed(seed, Bip44Coins.tron);
|
||||
|
||||
final childKey = bip44.deriveDefaultPath;
|
||||
|
||||
return TronPrivateKey.fromBytes(childKey.privateKey.raw);
|
||||
}
|
||||
|
||||
@override
|
||||
int calculateEstimatedFee(TransactionPriority priority, int? amount) => 0;
|
||||
|
||||
@override
|
||||
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
|
||||
|
||||
@override
|
||||
Future<void> close({bool shouldCleanup = false}) async => _transactionsUpdateTimer?.cancel();
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> connectToNode({required Node node}) async {
|
||||
try {
|
||||
syncStatus = ConnectingSyncStatus();
|
||||
|
||||
final isConnected = _client.connect(node);
|
||||
|
||||
if (!isConnected) {
|
||||
throw Exception("${walletInfo.type.name.toUpperCase()} Node connection failed");
|
||||
}
|
||||
|
||||
_getEstimatedFees();
|
||||
_setTransactionUpdateTimer();
|
||||
|
||||
syncStatus = ConnectedSyncStatus();
|
||||
} catch (e) {
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getEstimatedFees() async {
|
||||
final nativeFee = await _getNativeTxFee();
|
||||
nativeTxEstimatedFee = TronHelper.fromSun(BigInt.from(nativeFee));
|
||||
|
||||
final trc20Fee = await _getTrc20TxFee();
|
||||
trc20EstimatedFee = TronHelper.fromSun(BigInt.from(trc20Fee));
|
||||
|
||||
log('Native Estimated Fee: $nativeTxEstimatedFee');
|
||||
log('TRC20 Estimated Fee: $trc20EstimatedFee');
|
||||
}
|
||||
|
||||
Future<int> _getNativeTxFee() async {
|
||||
try {
|
||||
final fee = await _client.getEstimatedFee(_tronPublicKey.toAddress());
|
||||
return fee;
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> _getTrc20TxFee() async {
|
||||
try {
|
||||
final trc20fee = await _client.getTRCEstimatedFee(_tronPublicKey.toAddress());
|
||||
return trc20fee;
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> startSync() async {
|
||||
try {
|
||||
syncStatus = AttemptingSyncStatus();
|
||||
|
||||
// Verify node health before attempting to sync
|
||||
final isHealthy = await checkNodeHealth();
|
||||
if (!isHealthy) {
|
||||
syncStatus = FailedSyncStatus();
|
||||
return;
|
||||
}
|
||||
await _updateBalance();
|
||||
await fetchTransactions();
|
||||
fetchTrc20ExcludedTransactions();
|
||||
|
||||
syncStatus = SyncedSyncStatus();
|
||||
} catch (e) {
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||
final tronCredentials = credentials as TronTransactionCredentials;
|
||||
|
||||
final outputs = tronCredentials.outputs;
|
||||
|
||||
final hasMultiDestination = outputs.length > 1;
|
||||
|
||||
final transactionCurrency = balance.keys.firstWhere(
|
||||
(currency) =>
|
||||
currency.title == tronCredentials.currency.title &&
|
||||
currency.tag == tronCredentials.currency.tag,
|
||||
orElse: () => throw Exception(
|
||||
'Currency ${tronCredentials.currency.title} ${tronCredentials.currency.tag} is not accessible in the wallet, try to enable it first.'));
|
||||
|
||||
final walletBalanceForCurrency = balance[transactionCurrency]!.balance;
|
||||
|
||||
BigInt totalAmount = BigInt.zero;
|
||||
bool shouldSendAll = false;
|
||||
if (hasMultiDestination) {
|
||||
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
|
||||
throw TronTransactionCreationException(transactionCurrency);
|
||||
}
|
||||
|
||||
final totalAmountFromCredentials =
|
||||
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0));
|
||||
|
||||
totalAmount = BigInt.from(totalAmountFromCredentials);
|
||||
|
||||
if (walletBalanceForCurrency < totalAmount) {
|
||||
throw TronTransactionCreationException(transactionCurrency);
|
||||
}
|
||||
} else {
|
||||
final output = outputs.first;
|
||||
|
||||
shouldSendAll = output.sendAll;
|
||||
|
||||
if (shouldSendAll) {
|
||||
totalAmount = walletBalanceForCurrency;
|
||||
} else {
|
||||
final totalOriginalAmount = double.parse(output.cryptoAmount ?? '0.0');
|
||||
totalAmount = TronHelper.toSun(totalOriginalAmount.toString());
|
||||
}
|
||||
|
||||
if (walletBalanceForCurrency < totalAmount || totalAmount < BigInt.zero) {
|
||||
throw TronTransactionCreationException(transactionCurrency);
|
||||
}
|
||||
}
|
||||
|
||||
final tronBalance = balance[CryptoCurrency.trx]?.balance ?? BigInt.zero;
|
||||
|
||||
final pendingTransaction = await _client.signTransaction(
|
||||
ownerPrivKey: _tronPrivateKey,
|
||||
toAddress: tronCredentials.outputs.first.isParsedAddress
|
||||
? tronCredentials.outputs.first.extractedAddress!
|
||||
: tronCredentials.outputs.first.address,
|
||||
amount: TronHelper.fromSun(totalAmount),
|
||||
currency: transactionCurrency,
|
||||
tronBalance: tronBalance,
|
||||
sendAll: shouldSendAll,
|
||||
);
|
||||
|
||||
return pendingTransaction;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateTransactionsHistory() async {
|
||||
await Future.wait([
|
||||
fetchTransactions(),
|
||||
fetchTrc20ExcludedTransactions(),
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, TronTransactionInfo>> fetchTransactions() async {
|
||||
final address = _tronAddress;
|
||||
|
||||
final transactions = await _client.fetchTransactions(address);
|
||||
|
||||
final Map<String, TronTransactionInfo> result = {};
|
||||
|
||||
final contract = ContractABI.fromJson(trc20Abi, isTron: true);
|
||||
|
||||
final ownerAddress = TronAddress(_tronAddress);
|
||||
|
||||
for (var transactionModel in transactions) {
|
||||
if (transactionModel.isError) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Filter out spam transaactions that involve receiving TRC10 assets transaction, we deal with TRX and TRC20 transactions
|
||||
if (transactionModel.contracts?.first.type == "TransferAssetContract") {
|
||||
continue;
|
||||
}
|
||||
|
||||
String? tokenSymbol;
|
||||
if (transactionModel.contractAddress != null) {
|
||||
final tokenAddress = TronAddress(transactionModel.contractAddress!);
|
||||
|
||||
tokenSymbol = (await _client.getTokenDetail(
|
||||
contract,
|
||||
"symbol",
|
||||
ownerAddress,
|
||||
tokenAddress,
|
||||
) as String?) ??
|
||||
'';
|
||||
}
|
||||
|
||||
result[transactionModel.hash] = TronTransactionInfo(
|
||||
id: transactionModel.hash,
|
||||
tronAmount: transactionModel.amount ?? BigInt.zero,
|
||||
direction: TronAddress(transactionModel.from!, visible: false).toAddress() == address
|
||||
? TransactionDirection.outgoing
|
||||
: TransactionDirection.incoming,
|
||||
blockTime: transactionModel.date,
|
||||
txFee: transactionModel.fee,
|
||||
tokenSymbol: tokenSymbol ?? "TRX",
|
||||
to: transactionModel.to,
|
||||
from: transactionModel.from,
|
||||
isPending: false,
|
||||
);
|
||||
}
|
||||
|
||||
transactionHistory.addMany(result);
|
||||
|
||||
await transactionHistory.save();
|
||||
|
||||
return transactionHistory.transactions;
|
||||
}
|
||||
|
||||
Future<void> fetchTrc20ExcludedTransactions() async {
|
||||
final address = _tronAddress;
|
||||
|
||||
final transactions = await _client.fetchTrc20ExcludedTransactions(address);
|
||||
|
||||
final Map<String, TronTransactionInfo> result = {};
|
||||
|
||||
for (var transactionModel in transactions) {
|
||||
if (transactionHistory.transactions.containsKey(transactionModel.hash)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result[transactionModel.hash] = TronTransactionInfo(
|
||||
id: transactionModel.hash,
|
||||
tronAmount: transactionModel.amount ?? BigInt.zero,
|
||||
direction: transactionModel.from! == address
|
||||
? TransactionDirection.outgoing
|
||||
: TransactionDirection.incoming,
|
||||
blockTime: transactionModel.date,
|
||||
txFee: transactionModel.fee,
|
||||
tokenSymbol: transactionModel.tokenSymbol ?? "TRX",
|
||||
to: transactionModel.to,
|
||||
from: transactionModel.from,
|
||||
isPending: false,
|
||||
);
|
||||
}
|
||||
|
||||
transactionHistory.addMany(result);
|
||||
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
@override
|
||||
Object get keys => throw UnimplementedError("keys");
|
||||
|
||||
@override
|
||||
Future<void> rescan({required int height}) => throw UnimplementedError("rescan");
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
if (!(await WalletKeysFile.hasKeysFile(walletInfo.name, walletInfo.type))) {
|
||||
await saveKeysFile(_password, encryptionFileUtils);
|
||||
saveKeysFile(_password, encryptionFileUtils, true);
|
||||
}
|
||||
|
||||
await walletAddresses.updateAddressesInBox();
|
||||
final path = await makePath();
|
||||
await encryptionFileUtils.write(path: path, password: _password, data: toJSON());
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
@override
|
||||
String? get seed => _mnemonic;
|
||||
|
||||
@override
|
||||
String get privateKey => _tronPrivateKey.toHex();
|
||||
|
||||
@override
|
||||
WalletKeysData get walletKeysData => WalletKeysData(
|
||||
mnemonic: _mnemonic,
|
||||
privateKey: privateKey,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
String toJSON() => json.encode({
|
||||
'mnemonic': _mnemonic,
|
||||
'private_key': privateKey,
|
||||
'balance': balance[currency]!.toJSON(),
|
||||
'passphrase': passphrase,
|
||||
});
|
||||
|
||||
Future<void> _updateBalance() async {
|
||||
balance[currency] = await _fetchTronBalance();
|
||||
|
||||
await _fetchTronTokenBalances();
|
||||
await save();
|
||||
}
|
||||
|
||||
Future<TronBalance> _fetchTronBalance() async {
|
||||
final balance = await _client.getBalance(_tronPublicKey.toAddress());
|
||||
return TronBalance(balance);
|
||||
}
|
||||
|
||||
Future<void> _fetchTronTokenBalances() async {
|
||||
for (var token in tronTokensBox.values) {
|
||||
try {
|
||||
if (token.enabled) {
|
||||
balance[token] = await _client.fetchTronTokenBalances(
|
||||
_tronAddress,
|
||||
token.contractAddress,
|
||||
);
|
||||
} else {
|
||||
balance.remove(token);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void>? updateBalance() async => await _updateBalance();
|
||||
|
||||
@override
|
||||
Future<bool> checkNodeHealth() async {
|
||||
try {
|
||||
// Check native balance
|
||||
await _client.getBalance(_tronPublicKey.toAddress(), throwOnError: true);
|
||||
|
||||
// Check USDT token balance
|
||||
const usdtContractAddress = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t";
|
||||
await _client.fetchTronTokenBalances(_tronAddress, usdtContractAddress, throwOnError: true);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
List<TronToken> get tronTokenCurrencies => tronTokensBox.values.toList();
|
||||
|
||||
Future<void> addTronToken(TronToken token) async {
|
||||
String? iconPath;
|
||||
if ((token.iconPath == null || token.iconPath!.isEmpty) && !token.isPotentialScam) {
|
||||
try {
|
||||
iconPath = CryptoCurrency.all
|
||||
.firstWhere((element) => element.title.toUpperCase() == token.symbol.toUpperCase())
|
||||
.iconPath;
|
||||
} catch (_) {}
|
||||
} else if (!token.isPotentialScam) {
|
||||
iconPath = token.iconPath;
|
||||
}
|
||||
|
||||
final newToken = TronToken(
|
||||
name: token.name,
|
||||
symbol: token.symbol,
|
||||
contractAddress: token.contractAddress,
|
||||
decimal: token.decimal,
|
||||
enabled: token.enabled,
|
||||
tag: token.tag ?? "TRX",
|
||||
iconPath: iconPath,
|
||||
isPotentialScam: token.isPotentialScam,
|
||||
);
|
||||
|
||||
await tronTokensBox.put(newToken.contractAddress, newToken);
|
||||
|
||||
if (newToken.enabled) {
|
||||
balance[newToken] = await _client.fetchTronTokenBalances(
|
||||
_tronAddress,
|
||||
newToken.contractAddress,
|
||||
);
|
||||
} else {
|
||||
balance.remove(newToken);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> deleteTronToken(TronToken token) async {
|
||||
if (tronTokensBox.isOpen) {
|
||||
await tronTokensBox.delete(token.contractAddress);
|
||||
}
|
||||
|
||||
balance.remove(token);
|
||||
await _removeTokenTransactionsInHistory(token);
|
||||
_updateBalance();
|
||||
}
|
||||
|
||||
Future<void> _removeTokenTransactionsInHistory(TronToken token) async {
|
||||
transactionHistory.transactions.removeWhere((key, value) => value.tokenSymbol == token.title);
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
Future<TronToken?> getTronToken(String contractAddress) async =>
|
||||
await _client.getTronToken(contractAddress, _tronAddress);
|
||||
|
||||
@override
|
||||
Future<void> renameWalletFiles(String newWalletName) async {
|
||||
const transactionHistoryFileNameForWallet = 'tron_transactions.json';
|
||||
|
||||
final currentWalletPath = await pathForWallet(name: walletInfo.name, type: type);
|
||||
final currentWalletFile = File(currentWalletPath);
|
||||
|
||||
final currentDirPath = await pathForWalletDir(name: walletInfo.name, type: type);
|
||||
final currentTransactionsFile = File('$currentDirPath/$transactionHistoryFileNameForWallet');
|
||||
|
||||
// Copies current wallet files into new wallet name's dir and files
|
||||
if (currentWalletFile.existsSync()) {
|
||||
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
|
||||
await currentWalletFile.copy(newWalletPath);
|
||||
}
|
||||
if (currentTransactionsFile.existsSync()) {
|
||||
final newDirPath = await pathForWalletDir(name: newWalletName, type: type);
|
||||
await currentTransactionsFile.copy('$newDirPath/$transactionHistoryFileNameForWallet');
|
||||
}
|
||||
|
||||
// Delete old name's dir and files
|
||||
await Directory(currentDirPath).delete(recursive: true);
|
||||
}
|
||||
|
||||
void _setTransactionUpdateTimer() {
|
||||
if (_transactionsUpdateTimer?.isActive ?? false) {
|
||||
_transactionsUpdateTimer!.cancel();
|
||||
}
|
||||
|
||||
_transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 30), (_) async {
|
||||
_updateBalance();
|
||||
await fetchTransactions();
|
||||
fetchTrc20ExcludedTransactions();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> signMessage(String message, {String? address}) async {
|
||||
return _tronPrivateKey.signPersonalMessage(ascii.encode(message));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> verifyMessage(String message, String signature, {String? address}) async {
|
||||
if (address == null) {
|
||||
return false;
|
||||
}
|
||||
TronPublicKey pubKey = TronPublicKey.fromPersonalSignature(ascii.encode(message), signature)!;
|
||||
return pubKey.toAddress().toString() == address;
|
||||
}
|
||||
|
||||
String getTronBase58AddressFromHex(String hexAddress) => TronAddress(hexAddress).toAddress();
|
||||
|
||||
void updateScanProviderUsageState(bool isEnabled) {
|
||||
if (isEnabled) {
|
||||
fetchTransactions();
|
||||
fetchTrc20ExcludedTransactions();
|
||||
_setTransactionUpdateTimer();
|
||||
} else {
|
||||
_transactionsUpdateTimer?.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String get password => _password;
|
||||
|
||||
@override
|
||||
final String? passphrase;
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:cw_core/payment_uris.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'tron_wallet_addresses.g.dart';
|
||||
|
||||
class TronWalletAddresses = TronWalletAddressesBase with _$TronWalletAddresses;
|
||||
|
||||
abstract class TronWalletAddressesBase extends WalletAddresses with Store {
|
||||
TronWalletAddressesBase(WalletInfo walletInfo)
|
||||
: address = '',
|
||||
super(walletInfo);
|
||||
|
||||
@override
|
||||
@observable
|
||||
String address;
|
||||
|
||||
@override
|
||||
String get primaryAddress => address;
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
address = walletInfo.address;
|
||||
await updateAddressesInBox();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAddressesInBox() async {
|
||||
try {
|
||||
addressesMap.clear();
|
||||
addressesMap[address] = '';
|
||||
await saveAddressesInBox();
|
||||
} catch (e) {
|
||||
log(e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
PaymentURI getPaymentUri(String amount) => TronURI(amount: amount, address: address);
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
|
||||
class TronNewWalletCredentials extends WalletCredentials {
|
||||
TronNewWalletCredentials({
|
||||
required String name,
|
||||
WalletInfo? walletInfo,
|
||||
String? password,
|
||||
this.mnemonic,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
password: password,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
final String? mnemonic;
|
||||
}
|
||||
|
||||
class TronRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||
TronRestoreWalletFromSeedCredentials({
|
||||
required String name,
|
||||
required String password,
|
||||
required this.mnemonic,
|
||||
WalletInfo? walletInfo,
|
||||
String? passphrase,
|
||||
}) : super(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
passphrase: passphrase,
|
||||
);
|
||||
|
||||
final String mnemonic;
|
||||
}
|
||||
|
||||
class TronRestoreWalletFromPrivateKey extends WalletCredentials {
|
||||
TronRestoreWalletFromPrivateKey(
|
||||
{required String name,
|
||||
required String password,
|
||||
required this.privateKey,
|
||||
WalletInfo? walletInfo})
|
||||
: super(name: name, password: password, walletInfo: walletInfo);
|
||||
|
||||
final String privateKey;
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:cw_core/balance.dart';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:cw_core/pathForWallet.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
import 'package:cw_core/transaction_info.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_tron/tron_client.dart';
|
||||
import 'package:cw_tron/tron_exception.dart';
|
||||
import 'package:cw_tron/tron_wallet.dart';
|
||||
import 'package:cw_tron/tron_wallet_creation_credentials.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
class TronWalletService extends WalletService<
|
||||
TronNewWalletCredentials,
|
||||
TronRestoreWalletFromSeedCredentials,
|
||||
TronRestoreWalletFromPrivateKey,
|
||||
TronNewWalletCredentials> {
|
||||
TronWalletService({required this.client, required this.isDirect});
|
||||
|
||||
late TronClient client;
|
||||
|
||||
final bool isDirect;
|
||||
|
||||
@override
|
||||
WalletType getType() => WalletType.tron;
|
||||
|
||||
@override
|
||||
Future<TronWallet> create(TronNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
|
||||
|
||||
final mnemonic = credentials.mnemonic ?? bip39.generateMnemonic(strength: strength);
|
||||
|
||||
final wallet = TronWallet(
|
||||
walletInfo: credentials.walletInfo!,
|
||||
derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
|
||||
mnemonic: mnemonic,
|
||||
password: credentials.password!,
|
||||
passphrase: credentials.passphrase,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TronWallet> openWallet(String name, String password) async {
|
||||
final walletInfo = await WalletInfo.get(name, getType());
|
||||
if (walletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
|
||||
try {
|
||||
final wallet = await TronWalletBase.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
saveBackup(name);
|
||||
return wallet;
|
||||
} catch (_) {
|
||||
await restoreWalletFilesFromBackup(name);
|
||||
|
||||
final wallet = await TronWalletBase.open(
|
||||
name: name,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
return wallet;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TronWallet> restoreFromKeys(
|
||||
TronRestoreWalletFromPrivateKey credentials, {
|
||||
bool? isTestnet,
|
||||
}) async {
|
||||
final wallet = TronWallet(
|
||||
password: credentials.password!,
|
||||
privateKey: credentials.privateKey,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TronWallet> restoreFromSeed(
|
||||
TronRestoreWalletFromSeedCredentials credentials, {
|
||||
bool? isTestnet,
|
||||
}) async {
|
||||
if (!bip39.validateMnemonic(credentials.mnemonic)) {
|
||||
throw TronMnemonicIsIncorrectException();
|
||||
}
|
||||
|
||||
final wallet = TronWallet(
|
||||
password: credentials.password!,
|
||||
mnemonic: credentials.mnemonic,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
derivationInfo: await credentials.walletInfo!.getDerivationInfo(),
|
||||
passphrase: credentials.passphrase,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await wallet.init();
|
||||
wallet.addInitialTokens();
|
||||
await wallet.save();
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> rename(String currentName, String password, String newName) async {
|
||||
final currentWalletInfo = await WalletInfo.get(currentName, getType());
|
||||
if (currentWalletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
final currentWallet = await TronWalletBase.open(
|
||||
password: password,
|
||||
name: currentName,
|
||||
walletInfo: currentWalletInfo,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
await saveBackup(newName);
|
||||
|
||||
final newWalletInfo = currentWalletInfo;
|
||||
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||
newWalletInfo.name = newName;
|
||||
|
||||
await newWalletInfo.save();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isWalletExit(String name) async =>
|
||||
File(await pathForWallet(name: name, type: getType())).existsSync();
|
||||
|
||||
@override
|
||||
Future<void> remove(String wallet) async {
|
||||
File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true);
|
||||
final walletInfo = await WalletInfo.get(wallet, getType());
|
||||
if (walletInfo == null) {
|
||||
throw Exception('Wallet not found');
|
||||
}
|
||||
await WalletInfo.delete(walletInfo);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo>>
|
||||
restoreFromHardwareWallet(TronNewWalletCredentials credentials) {
|
||||
// TODO: implement restoreFromHardwareWallet
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
name: cw_tron
|
||||
description: A new Flutter package project.
|
||||
version: 0.0.1
|
||||
publish_to: none
|
||||
homepage: https://github.com/Such-Software/hash-wallet
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.6 <4.0.0'
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cw_core:
|
||||
path: ../cw_core
|
||||
cw_evm:
|
||||
path: ../cw_evm
|
||||
on_chain:
|
||||
git:
|
||||
url: https://github.com/cake-tech/on_chain.git
|
||||
ref: 096865a8c6b89c260beadfec04f7e184c40a3273
|
||||
blockchain_utils:
|
||||
git:
|
||||
url: https://github.com/cake-tech/blockchain_utils
|
||||
ref: cake-update-v2
|
||||
mobx: ^2.3.0+1
|
||||
bip39: ^1.0.6
|
||||
hive: ^2.2.3
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^2.0.0
|
||||
build_runner: ^2.4.15
|
||||
mobx_codegen: ^2.1.1
|
||||
hive_generator: ^2.0.1
|
||||
flutter:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
@@ -1,12 +0,0 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:cw_tron/cw_tron.dart';
|
||||
|
||||
void main() {
|
||||
test('adds one to input values', () {
|
||||
final calculator = Calculator();
|
||||
expect(calculator.addOne(2), 3);
|
||||
expect(calculator.addOne(-7), -6);
|
||||
expect(calculator.addOne(0), 1);
|
||||
});
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
.DS_Store
|
||||
.dart_tool/
|
||||
|
||||
.packages
|
||||
.pub/
|
||||
|
||||
build/
|
||||
@@ -1,10 +0,0 @@
|
||||
# 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 and should not be manually edited.
|
||||
|
||||
version:
|
||||
revision: 4d7946a68d26794349189cf21b3f68cc6fe61dcb
|
||||
channel: stable
|
||||
|
||||
project_type: plugin
|
||||
@@ -1,3 +0,0 @@
|
||||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
||||
@@ -1 +0,0 @@
|
||||
TODO: Add your license here.
|
||||
@@ -1,23 +0,0 @@
|
||||
# cw_zano
|
||||
|
||||
Zano wallet module for Cake Wallet. Provides a Dart wrapper around the Zano wallet API with typed models and transaction helpers.
|
||||
|
||||
## Features
|
||||
|
||||
- Wallet lifecycle and status queries via `ZanoWalletApi`.
|
||||
- Typed models for balances, transfers, recent history, and wallet info.
|
||||
- Build and submit transfers; pending transaction modeling.
|
||||
- Address and asset utilities, formatter helpers.
|
||||
|
||||
## Usage
|
||||
|
||||
See `lib/zano_wallet_api.dart` and `lib/zano_wallet.dart` for the high-level API. Typical flow:
|
||||
|
||||
```dart
|
||||
final api = ZanoWalletApi();
|
||||
final info = await api.getWalletInfo();
|
||||
final balance = await api.getBalance();
|
||||
// create transfer params and broadcast
|
||||
```
|
||||
|
||||
Consult `lib/api/model/` for full set of supported request/response models.
|
||||
@@ -1,6 +0,0 @@
|
||||
class Consts {
|
||||
static const errorWrongSeed = 'WRONG_SEED';
|
||||
static const errorAlreadyExists = 'ALREADY_EXISTS';
|
||||
static const errorWalletWrongId = 'WALLET_WRONG_ID';
|
||||
static const errorBusy = 'BUSY';
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
class AssetIdParams {
|
||||
final String assetId;
|
||||
|
||||
AssetIdParams({required this.assetId});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'asset_id': assetId,
|
||||
};
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import 'package:cw_core/zano_asset.dart';
|
||||
import 'package:cw_zano/model/zano_asset.dart';
|
||||
import 'package:cw_zano/zano_formatter.dart';
|
||||
|
||||
class Balance {
|
||||
final ZanoAsset assetInfo;
|
||||
final BigInt awaitingIn;
|
||||
final BigInt awaitingOut;
|
||||
final BigInt total;
|
||||
final BigInt unlocked;
|
||||
|
||||
Balance(
|
||||
{required this.assetInfo,
|
||||
required this.awaitingIn,
|
||||
required this.awaitingOut,
|
||||
required this.total,
|
||||
required this.unlocked});
|
||||
|
||||
String get assetId => assetInfo.assetId;
|
||||
|
||||
@override
|
||||
String toString() => '$assetInfo: $total/$unlocked';
|
||||
|
||||
factory Balance.fromJson(Map<String, dynamic> json) => Balance(
|
||||
assetInfo:
|
||||
ZanoAsset.fromJson(json['asset_info'] as Map<String, dynamic>? ?? {}),
|
||||
awaitingIn: ZanoFormatter.bigIntFromDynamic(json['awaiting_in']),
|
||||
awaitingOut: ZanoFormatter.bigIntFromDynamic(json['awaiting_out']),
|
||||
total: ZanoFormatter.bigIntFromDynamic(json['total']),
|
||||
unlocked: ZanoFormatter.bigIntFromDynamic(json['unlocked']),
|
||||
);
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import 'package:cw_zano/api/model/recent_history.dart';
|
||||
import 'package:cw_zano/api/model/wi.dart';
|
||||
import 'package:cw_zano/zano_wallet.dart';
|
||||
|
||||
class CreateWalletResult {
|
||||
final String name;
|
||||
final String pass;
|
||||
final RecentHistory recentHistory;
|
||||
final bool recovered;
|
||||
final int walletFileSize;
|
||||
final int walletId;
|
||||
final int walletLocalBcSize;
|
||||
final Wi wi;
|
||||
final String privateSpendKey;
|
||||
final String privateViewKey;
|
||||
final String publicSpendKey;
|
||||
final String publicViewKey;
|
||||
|
||||
CreateWalletResult(
|
||||
{required this.name,
|
||||
required this.pass,
|
||||
required this.recentHistory,
|
||||
required this.recovered,
|
||||
required this.walletFileSize,
|
||||
required this.walletId,
|
||||
required this.walletLocalBcSize,
|
||||
required this.wi,
|
||||
required this.privateSpendKey,
|
||||
required this.privateViewKey,
|
||||
required this.publicSpendKey,
|
||||
required this.publicViewKey});
|
||||
|
||||
factory CreateWalletResult.fromJson(Map<String, dynamic> json) =>
|
||||
CreateWalletResult(
|
||||
name: json['name'] as String? ?? '',
|
||||
pass: json['pass'] as String? ?? '',
|
||||
recentHistory: RecentHistory.fromJson(
|
||||
json['recent_history'] as Map<String, dynamic>? ?? {}),
|
||||
recovered: json['recovered'] as bool? ?? false,
|
||||
walletFileSize: json['wallet_file_size'] as int? ?? 0,
|
||||
walletId: json['wallet_id'] as int? ?? 0,
|
||||
walletLocalBcSize: json['wallet_local_bc_size'] as int? ?? 0,
|
||||
wi: Wi.fromJson(json['wi'] as Map<String, dynamic>? ?? {}),
|
||||
privateSpendKey: json['private_spend_key'] as String? ?? '',
|
||||
privateViewKey: json['private_view_key'] as String? ?? '',
|
||||
publicSpendKey: json['public_spend_key'] as String? ?? '',
|
||||
publicViewKey: json['public_view_key'] as String? ?? '',
|
||||
);
|
||||
Future<String> seed(ZanoWalletBase api) {
|
||||
return api.getSeed();
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
class Destination {
|
||||
final BigInt amount; // transferred as string
|
||||
final String address;
|
||||
final String assetId;
|
||||
|
||||
Destination(
|
||||
{required this.amount, required this.address, required this.assetId});
|
||||
|
||||
factory Destination.fromJson(Map<String, dynamic> json) => Destination(
|
||||
amount: BigInt.parse(json['amount'] as String? ?? '0'),
|
||||
address: json['address'] as String? ?? '',
|
||||
assetId: json['asset_id'] as String? ?? '',
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'amount': amount.toString(),
|
||||
'address': address,
|
||||
'asset_id': assetId,
|
||||
};
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import 'package:cw_zano/api/model/receive.dart';
|
||||
|
||||
class EmployedEntries {
|
||||
final List<Receive> receive;
|
||||
final List<Receive> send;
|
||||
|
||||
EmployedEntries({required this.receive, required this.send});
|
||||
|
||||
factory EmployedEntries.fromJson(Map<String, dynamic> json) =>
|
||||
EmployedEntries(
|
||||
receive: json['receive'] == null ? [] : (json['receive'] as List<dynamic>)
|
||||
.map((e) => Receive.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
send: json['spent'] == null ? [] : (json['spent'] as List<dynamic>)
|
||||
.map((e) => Receive.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
class GetAddressInfoResult {
|
||||
final bool valid;
|
||||
final bool auditable;
|
||||
final bool paymentId;
|
||||
final bool wrap;
|
||||
|
||||
GetAddressInfoResult(
|
||||
{required this.valid, required this.auditable, required this.paymentId, required this.wrap});
|
||||
|
||||
factory GetAddressInfoResult.fromJson(Map<String, dynamic> json) => GetAddressInfoResult(
|
||||
valid: json['valid'] as bool? ?? false,
|
||||
auditable: json['auditable'] as bool? ?? false,
|
||||
paymentId: json['payment_id'] as bool? ?? false,
|
||||
wrap: json['wrap'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
class GetRecentTxsAndInfoParams {
|
||||
final int offset;
|
||||
final int count;
|
||||
final bool updateProvisionInfo;
|
||||
|
||||
GetRecentTxsAndInfoParams({required this.offset, required this.count, this.updateProvisionInfo = true});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'offset': offset,
|
||||
'count': count,
|
||||
'update_provision_info': updateProvisionInfo,
|
||||
'order': 'FROM_BEGIN_TO_END',
|
||||
};
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import 'package:cw_zano/api/model/transfer.dart';
|
||||
|
||||
class GetRecentTxsAndInfoResult {
|
||||
final List<Transfer> transfers;
|
||||
final int lastItemIndex;
|
||||
final int totalTransfers;
|
||||
|
||||
GetRecentTxsAndInfoResult({required this.transfers, required this.lastItemIndex, required this.totalTransfers});
|
||||
|
||||
GetRecentTxsAndInfoResult.empty(): this.transfers = [], this.lastItemIndex = 0, this.totalTransfers = 0;
|
||||
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
import 'package:cw_zano/api/model/wi.dart';
|
||||
import 'package:cw_zano/api/model/wi_extended.dart';
|
||||
|
||||
class GetWalletInfoResult {
|
||||
final Wi wi;
|
||||
final WiExtended wiExtended;
|
||||
|
||||
GetWalletInfoResult({required this.wi, required this.wiExtended});
|
||||
|
||||
factory GetWalletInfoResult.fromJson(Map<String, dynamic> json) => GetWalletInfoResult(
|
||||
wi: Wi.fromJson(json['wi'] as Map<String, dynamic>? ?? {}),
|
||||
wiExtended: WiExtended.fromJson(json['wi_extended'] as Map<String, dynamic>? ?? {}),
|
||||
);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
class GetWalletStatusResult {
|
||||
final int currentDaemonHeight;
|
||||
final int currentWalletHeight;
|
||||
final bool isDaemonConnected;
|
||||
final bool isInLongRefresh;
|
||||
final int progress;
|
||||
final int walletState;
|
||||
|
||||
GetWalletStatusResult(
|
||||
{required this.currentDaemonHeight,
|
||||
required this.currentWalletHeight,
|
||||
required this.isDaemonConnected,
|
||||
required this.isInLongRefresh,
|
||||
required this.progress,
|
||||
required this.walletState});
|
||||
|
||||
factory GetWalletStatusResult.fromJson(Map<String, dynamic> json) =>
|
||||
GetWalletStatusResult(
|
||||
currentDaemonHeight: json['current_daemon_height'] as int? ?? 0,
|
||||
currentWalletHeight: json['current_wallet_height'] as int? ?? 0,
|
||||
isDaemonConnected: json['is_daemon_connected'] as bool? ?? false,
|
||||
isInLongRefresh: json['is_in_long_refresh'] as bool? ?? false,
|
||||
progress: json['progress'] as int? ?? 0,
|
||||
walletState: json['wallet_state'] as int? ?? 0,
|
||||
);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class ProxyToDaemonParams {
|
||||
final String body;
|
||||
final String uri;
|
||||
|
||||
ProxyToDaemonParams({required this.body, required this.uri});
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'base64_body': base64Encode(utf8.encode(body)),
|
||||
'uri': uri,
|
||||
};
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class ProxyToDaemonResult {
|
||||
final String body;
|
||||
final int responseCode;
|
||||
|
||||
ProxyToDaemonResult({required this.body, required this.responseCode});
|
||||
|
||||
factory ProxyToDaemonResult.fromJson(Map<String, dynamic> json) => ProxyToDaemonResult(
|
||||
body: utf8.decode(base64Decode(json['base64_body'] as String? ?? '')),
|
||||
responseCode: json['response_code'] as int? ?? 0,
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user