mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 19:49:22 +00:00
Configure wallet types for the app
This commit is contained in:
parent
dd784a7829
commit
e6b1da376d
224 changed files with 903 additions and 23743 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -113,3 +113,6 @@ ios/build
|
||||||
shared_external/**
|
shared_external/**
|
||||||
cw_shared_external/**
|
cw_shared_external/**
|
||||||
cw_haven/**
|
cw_haven/**
|
||||||
|
|
||||||
|
lib/bitcoin/bitcoin.dart
|
||||||
|
lib/monero/monero.dart
|
|
@ -2,10 +2,7 @@ analyzer:
|
||||||
strong-mode:
|
strong-mode:
|
||||||
implicit-casts: false
|
implicit-casts: false
|
||||||
implicit-dynamic: false
|
implicit-dynamic: false
|
||||||
exclude:
|
exclude: [build/**, lib/generated/*.dart, lib/**.g.dart, cw_monero/ios/External/**, cw_shared_external/**, shared_external/**]
|
||||||
- "build/**"
|
|
||||||
- "lib/generated/*.dart"
|
|
||||||
- "**.g.dart"
|
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
rules:
|
rules:
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
import 'package:cw_monero/signatures.dart';
|
|
||||||
import 'package:cw_monero/types.dart';
|
|
||||||
import 'package:cw_monero/monero_api.dart';
|
|
||||||
import 'package:cw_monero/structs/account_row.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:cw_monero/wallet.dart';
|
|
||||||
|
|
||||||
final accountSizeNative = moneroApi
|
|
||||||
.lookup<NativeFunction<account_size>>('account_size')
|
|
||||||
.asFunction<SubaddressSize>();
|
|
||||||
|
|
||||||
final accountRefreshNative = moneroApi
|
|
||||||
.lookup<NativeFunction<account_refresh>>('account_refresh')
|
|
||||||
.asFunction<AccountRefresh>();
|
|
||||||
|
|
||||||
final accountGetAllNative = moneroApi
|
|
||||||
.lookup<NativeFunction<account_get_all>>('account_get_all')
|
|
||||||
.asFunction<AccountGetAll>();
|
|
||||||
|
|
||||||
final accountAddNewNative = moneroApi
|
|
||||||
.lookup<NativeFunction<account_add_new>>('account_add_row')
|
|
||||||
.asFunction<AccountAddNew>();
|
|
||||||
|
|
||||||
final accountSetLabelNative = moneroApi
|
|
||||||
.lookup<NativeFunction<account_set_label>>('account_set_label_row')
|
|
||||||
.asFunction<AccountSetLabel>();
|
|
||||||
|
|
||||||
bool isUpdating = false;
|
|
||||||
|
|
||||||
void refreshAccounts() {
|
|
||||||
try {
|
|
||||||
isUpdating = true;
|
|
||||||
accountRefreshNative();
|
|
||||||
isUpdating = false;
|
|
||||||
} catch (e) {
|
|
||||||
isUpdating = false;
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<AccountRow> getAllAccount() {
|
|
||||||
final size = accountSizeNative();
|
|
||||||
final accountAddressesPointer = accountGetAllNative();
|
|
||||||
final accountAddresses = accountAddressesPointer.asTypedList(size);
|
|
||||||
|
|
||||||
return accountAddresses
|
|
||||||
.map((addr) => Pointer<AccountRow>.fromAddress(addr).ref)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
void addAccountSync({String label}) {
|
|
||||||
final labelPointer = Utf8.toUtf8(label);
|
|
||||||
accountAddNewNative(labelPointer);
|
|
||||||
free(labelPointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setLabelForAccountSync({int accountIndex, String label}) {
|
|
||||||
final labelPointer = Utf8.toUtf8(label);
|
|
||||||
accountSetLabelNative(accountIndex, labelPointer);
|
|
||||||
free(labelPointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _addAccount(String label) => addAccountSync(label: label);
|
|
||||||
|
|
||||||
void _setLabelForAccount(Map<String, dynamic> args) {
|
|
||||||
final label = args['label'] as String;
|
|
||||||
final accountIndex = args['accountIndex'] as int;
|
|
||||||
|
|
||||||
setLabelForAccountSync(label: label, accountIndex: accountIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> addAccount({String label}) async {
|
|
||||||
await compute(_addAccount, label);
|
|
||||||
await store();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setLabelForAccount({int accountIndex, String label}) async {
|
|
||||||
await compute(
|
|
||||||
_setLabelForAccount, {'accountIndex': accountIndex, 'label': label});
|
|
||||||
await store();
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
|
|
||||||
String convertUTF8ToString({Pointer<Utf8> pointer}) {
|
|
||||||
final str = Utf8.fromUtf8(pointer);
|
|
||||||
free(pointer);
|
|
||||||
return str;
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
class ConnectionToNodeException implements Exception {
|
|
||||||
ConnectionToNodeException({this.message});
|
|
||||||
|
|
||||||
final String message;
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
class CreationTransactionException implements Exception {
|
|
||||||
CreationTransactionException({this.message});
|
|
||||||
|
|
||||||
final String message;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => message;
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
class SetupWalletException implements Exception {
|
|
||||||
SetupWalletException({this.message});
|
|
||||||
|
|
||||||
final String message;
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
class WalletCreationException implements Exception {
|
|
||||||
WalletCreationException({this.message});
|
|
||||||
|
|
||||||
final String message;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => message;
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
class WalletOpeningException implements Exception {
|
|
||||||
WalletOpeningException({this.message});
|
|
||||||
|
|
||||||
final String message;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => message;
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
class WalletRestoreFromKeysException implements Exception {
|
|
||||||
WalletRestoreFromKeysException({this.message});
|
|
||||||
|
|
||||||
final String message;
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
class WalletRestoreFromSeedException implements Exception {
|
|
||||||
WalletRestoreFromSeedException({this.message});
|
|
||||||
|
|
||||||
final String message;
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
final DynamicLibrary moneroApi = Platform.isAndroid
|
|
||||||
? DynamicLibrary.open("libcw_monero.so")
|
|
||||||
: DynamicLibrary.open("cw_monero.framework/cw_monero");
|
|
|
@ -1,8 +0,0 @@
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
class MoneroOutput {
|
|
||||||
MoneroOutput({@required this.address, @required this.amount});
|
|
||||||
|
|
||||||
final String address;
|
|
||||||
final String amount;
|
|
||||||
}
|
|
|
@ -1,122 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'package:cw_monero/structs/pending_transaction.dart';
|
|
||||||
import 'package:cw_monero/structs/ut8_box.dart';
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
|
|
||||||
typedef create_wallet = Int8 Function(
|
|
||||||
Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, Int32, Pointer<Utf8>);
|
|
||||||
|
|
||||||
typedef restore_wallet_from_seed = Int8 Function(
|
|
||||||
Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, Int32, Int64, Pointer<Utf8>);
|
|
||||||
|
|
||||||
typedef restore_wallet_from_keys = Int8 Function(Pointer<Utf8>, Pointer<Utf8>,
|
|
||||||
Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, Int32, Int64, Pointer<Utf8>);
|
|
||||||
|
|
||||||
typedef is_wallet_exist = Int8 Function(Pointer<Utf8>);
|
|
||||||
|
|
||||||
typedef load_wallet = Int8 Function(Pointer<Utf8>, Pointer<Utf8>, Int8);
|
|
||||||
|
|
||||||
typedef error_string = Pointer<Utf8> Function();
|
|
||||||
|
|
||||||
typedef get_filename = Pointer<Utf8> Function();
|
|
||||||
|
|
||||||
typedef get_seed = Pointer<Utf8> Function();
|
|
||||||
|
|
||||||
typedef get_address = Pointer<Utf8> Function(Int32, Int32);
|
|
||||||
|
|
||||||
typedef get_full_balanace = Int64 Function(Int32);
|
|
||||||
|
|
||||||
typedef get_unlocked_balanace = Int64 Function(Int32);
|
|
||||||
|
|
||||||
typedef get_current_height = Int64 Function();
|
|
||||||
|
|
||||||
typedef get_node_height = Int64 Function();
|
|
||||||
|
|
||||||
typedef is_connected = Int8 Function();
|
|
||||||
|
|
||||||
typedef setup_node = Int8 Function(
|
|
||||||
Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, Int8, Int8, Pointer<Utf8>);
|
|
||||||
|
|
||||||
typedef start_refresh = Void Function();
|
|
||||||
|
|
||||||
typedef connect_to_node = Int8 Function();
|
|
||||||
|
|
||||||
typedef set_refresh_from_block_height = Void Function(Int64);
|
|
||||||
|
|
||||||
typedef set_recovering_from_seed = Void Function(Int8);
|
|
||||||
|
|
||||||
typedef store_c = Void Function(Pointer<Utf8>);
|
|
||||||
|
|
||||||
typedef set_listener = Void Function();
|
|
||||||
|
|
||||||
typedef get_syncing_height = Int64 Function();
|
|
||||||
|
|
||||||
typedef is_needed_to_refresh = Int8 Function();
|
|
||||||
|
|
||||||
typedef is_new_transaction_exist = Int8 Function();
|
|
||||||
|
|
||||||
typedef subaddrress_size = Int32 Function();
|
|
||||||
|
|
||||||
typedef subaddrress_refresh = Void Function(Int32);
|
|
||||||
|
|
||||||
typedef subaddress_get_all = Pointer<Int64> Function();
|
|
||||||
|
|
||||||
typedef subaddress_add_new = Void Function(
|
|
||||||
Int32 accountIndex, Pointer<Utf8> label);
|
|
||||||
|
|
||||||
typedef subaddress_set_label = Void Function(
|
|
||||||
Int32 accountIndex, Int32 addressIndex, Pointer<Utf8> label);
|
|
||||||
|
|
||||||
typedef account_size = Int32 Function();
|
|
||||||
|
|
||||||
typedef account_refresh = Void Function();
|
|
||||||
|
|
||||||
typedef account_get_all = Pointer<Int64> Function();
|
|
||||||
|
|
||||||
typedef account_add_new = Void Function(Pointer<Utf8> label);
|
|
||||||
|
|
||||||
typedef account_set_label = Void Function(
|
|
||||||
Int32 accountIndex, Pointer<Utf8> label);
|
|
||||||
|
|
||||||
typedef transactions_refresh = Void Function();
|
|
||||||
|
|
||||||
typedef get_tx_key = Pointer<Utf8> Function(Pointer<Utf8> txId);
|
|
||||||
|
|
||||||
typedef transactions_count = Int64 Function();
|
|
||||||
|
|
||||||
typedef transactions_get_all = Pointer<Int64> Function();
|
|
||||||
|
|
||||||
typedef transaction_create = Int8 Function(
|
|
||||||
Pointer<Utf8> address,
|
|
||||||
Pointer<Utf8> paymentId,
|
|
||||||
Pointer<Utf8> amount,
|
|
||||||
Int8 priorityRaw,
|
|
||||||
Int32 subaddrAccount,
|
|
||||||
Pointer<Utf8Box> error,
|
|
||||||
Pointer<PendingTransactionRaw> pendingTransaction);
|
|
||||||
|
|
||||||
typedef transaction_create_mult_dest = Int8 Function(
|
|
||||||
Pointer<Pointer<Utf8>> addresses,
|
|
||||||
Pointer<Utf8> paymentId,
|
|
||||||
Pointer<Pointer<Utf8>> amounts,
|
|
||||||
Int32 size,
|
|
||||||
Int8 priorityRaw,
|
|
||||||
Int32 subaddrAccount,
|
|
||||||
Pointer<Utf8Box> error,
|
|
||||||
Pointer<PendingTransactionRaw> pendingTransaction);
|
|
||||||
|
|
||||||
typedef transaction_commit = Int8 Function(Pointer<PendingTransactionRaw>, Pointer<Utf8Box>);
|
|
||||||
|
|
||||||
typedef secret_view_key = Pointer<Utf8> Function();
|
|
||||||
|
|
||||||
typedef public_view_key = Pointer<Utf8> Function();
|
|
||||||
|
|
||||||
typedef secret_spend_key = Pointer<Utf8> Function();
|
|
||||||
|
|
||||||
typedef public_spend_key = Pointer<Utf8> Function();
|
|
||||||
|
|
||||||
typedef close_current_wallet = Void Function();
|
|
||||||
|
|
||||||
typedef on_startup = Void Function();
|
|
||||||
|
|
||||||
typedef rescan_blockchain = Void Function();
|
|
|
@ -1,11 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
|
|
||||||
class AccountRow extends Struct {
|
|
||||||
@Int64()
|
|
||||||
int id;
|
|
||||||
Pointer<Utf8> label;
|
|
||||||
|
|
||||||
String getLabel() => Utf8.fromUtf8(label);
|
|
||||||
int getId() => id;
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
|
|
||||||
class PendingTransactionRaw extends Struct {
|
|
||||||
@Int64()
|
|
||||||
int amount;
|
|
||||||
|
|
||||||
@Int64()
|
|
||||||
int fee;
|
|
||||||
|
|
||||||
Pointer<Utf8> hash;
|
|
||||||
|
|
||||||
String getHash() => Utf8.fromUtf8(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
class PendingTransactionDescription {
|
|
||||||
PendingTransactionDescription({this.amount, this.fee, this.hash, this.pointerAddress});
|
|
||||||
|
|
||||||
final int amount;
|
|
||||||
final int fee;
|
|
||||||
final String hash;
|
|
||||||
final int pointerAddress;
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
|
|
||||||
class SubaddressRow extends Struct {
|
|
||||||
@Int64()
|
|
||||||
int id;
|
|
||||||
Pointer<Utf8> address;
|
|
||||||
Pointer<Utf8> label;
|
|
||||||
|
|
||||||
String getLabel() => Utf8.fromUtf8(label);
|
|
||||||
String getAddress() => Utf8.fromUtf8(address);
|
|
||||||
int getId() => id;
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
|
|
||||||
class TransactionInfoRow extends Struct {
|
|
||||||
@Uint64()
|
|
||||||
int amount;
|
|
||||||
|
|
||||||
@Uint64()
|
|
||||||
int fee;
|
|
||||||
|
|
||||||
@Uint64()
|
|
||||||
int blockHeight;
|
|
||||||
|
|
||||||
@Uint64()
|
|
||||||
int confirmations;
|
|
||||||
|
|
||||||
@Uint32()
|
|
||||||
int subaddrAccount;
|
|
||||||
|
|
||||||
@Int8()
|
|
||||||
int direction;
|
|
||||||
|
|
||||||
@Int8()
|
|
||||||
int isPending;
|
|
||||||
|
|
||||||
@Uint32()
|
|
||||||
int subaddrIndex;
|
|
||||||
|
|
||||||
Pointer<Utf8> hash;
|
|
||||||
|
|
||||||
Pointer<Utf8> paymentId;
|
|
||||||
|
|
||||||
@Int64()
|
|
||||||
int datetime;
|
|
||||||
|
|
||||||
int getDatetime() => datetime;
|
|
||||||
int getAmount() => amount >= 0 ? amount : amount * -1;
|
|
||||||
bool getIsPending() => isPending != 0;
|
|
||||||
String getHash() => Utf8.fromUtf8(hash);
|
|
||||||
String getPaymentId() => Utf8.fromUtf8(paymentId);
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
|
|
||||||
class Utf8Box extends Struct {
|
|
||||||
Pointer<Utf8> value;
|
|
||||||
|
|
||||||
String getValue() => Utf8.fromUtf8(value);
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:cw_monero/signatures.dart';
|
|
||||||
import 'package:cw_monero/types.dart';
|
|
||||||
import 'package:cw_monero/monero_api.dart';
|
|
||||||
import 'package:cw_monero/structs/subaddress_row.dart';
|
|
||||||
import 'package:cw_monero/wallet.dart';
|
|
||||||
|
|
||||||
final subaddressSizeNative = moneroApi
|
|
||||||
.lookup<NativeFunction<subaddrress_size>>('subaddrress_size')
|
|
||||||
.asFunction<SubaddressSize>();
|
|
||||||
|
|
||||||
final subaddressRefreshNative = moneroApi
|
|
||||||
.lookup<NativeFunction<subaddrress_refresh>>('subaddress_refresh')
|
|
||||||
.asFunction<SubaddressRefresh>();
|
|
||||||
|
|
||||||
final subaddrressGetAllNative = moneroApi
|
|
||||||
.lookup<NativeFunction<subaddress_get_all>>('subaddrress_get_all')
|
|
||||||
.asFunction<SubaddressGetAll>();
|
|
||||||
|
|
||||||
final subaddrressAddNewNative = moneroApi
|
|
||||||
.lookup<NativeFunction<subaddress_add_new>>('subaddress_add_row')
|
|
||||||
.asFunction<SubaddressAddNew>();
|
|
||||||
|
|
||||||
final subaddrressSetLabelNative = moneroApi
|
|
||||||
.lookup<NativeFunction<subaddress_set_label>>('subaddress_set_label')
|
|
||||||
.asFunction<SubaddressSetLabel>();
|
|
||||||
|
|
||||||
bool isUpdating = false;
|
|
||||||
|
|
||||||
void refreshSubaddresses({@required int accountIndex}) {
|
|
||||||
try {
|
|
||||||
isUpdating = true;
|
|
||||||
subaddressRefreshNative(accountIndex);
|
|
||||||
isUpdating = false;
|
|
||||||
} catch (e) {
|
|
||||||
isUpdating = false;
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<SubaddressRow> getAllSubaddresses() {
|
|
||||||
final size = subaddressSizeNative();
|
|
||||||
final subaddressAddressesPointer = subaddrressGetAllNative();
|
|
||||||
final subaddressAddresses = subaddressAddressesPointer.asTypedList(size);
|
|
||||||
|
|
||||||
return subaddressAddresses
|
|
||||||
.map((addr) => Pointer<SubaddressRow>.fromAddress(addr).ref)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
void addSubaddressSync({int accountIndex, String label}) {
|
|
||||||
final labelPointer = Utf8.toUtf8(label);
|
|
||||||
subaddrressAddNewNative(accountIndex, labelPointer);
|
|
||||||
free(labelPointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setLabelForSubaddressSync(
|
|
||||||
{int accountIndex, int addressIndex, String label}) {
|
|
||||||
final labelPointer = Utf8.toUtf8(label);
|
|
||||||
|
|
||||||
subaddrressSetLabelNative(accountIndex, addressIndex, labelPointer);
|
|
||||||
free(labelPointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _addSubaddress(Map<String, dynamic> args) {
|
|
||||||
final label = args['label'] as String;
|
|
||||||
final accountIndex = args['accountIndex'] as int;
|
|
||||||
|
|
||||||
addSubaddressSync(accountIndex: accountIndex, label: label);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _setLabelForSubaddress(Map<String, dynamic> args) {
|
|
||||||
final label = args['label'] as String;
|
|
||||||
final accountIndex = args['accountIndex'] as int;
|
|
||||||
final addressIndex = args['addressIndex'] as int;
|
|
||||||
|
|
||||||
setLabelForSubaddressSync(
|
|
||||||
accountIndex: accountIndex, addressIndex: addressIndex, label: label);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future addSubaddress({int accountIndex, String label}) async {
|
|
||||||
await compute<Map<String, Object>, void>(
|
|
||||||
_addSubaddress, {'accountIndex': accountIndex, 'label': label});
|
|
||||||
await store();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future setLabelForSubaddress(
|
|
||||||
{int accountIndex, int addressIndex, String label}) async {
|
|
||||||
await compute<Map<String, Object>, void>(_setLabelForSubaddress, {
|
|
||||||
'accountIndex': accountIndex,
|
|
||||||
'addressIndex': addressIndex,
|
|
||||||
'label': label
|
|
||||||
});
|
|
||||||
await store();
|
|
||||||
}
|
|
|
@ -1,230 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'package:cw_monero/convert_utf8_to_string.dart';
|
|
||||||
import 'package:cw_monero/monero_output.dart';
|
|
||||||
import 'package:cw_monero/structs/ut8_box.dart';
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:cw_monero/signatures.dart';
|
|
||||||
import 'package:cw_monero/types.dart';
|
|
||||||
import 'package:cw_monero/monero_api.dart';
|
|
||||||
import 'package:cw_monero/structs/transaction_info_row.dart';
|
|
||||||
import 'package:cw_monero/structs/pending_transaction.dart';
|
|
||||||
import 'package:cw_monero/exceptions/creation_transaction_exception.dart';
|
|
||||||
|
|
||||||
final transactionsRefreshNative = moneroApi
|
|
||||||
.lookup<NativeFunction<transactions_refresh>>('transactions_refresh')
|
|
||||||
.asFunction<TransactionsRefresh>();
|
|
||||||
|
|
||||||
final transactionsCountNative = moneroApi
|
|
||||||
.lookup<NativeFunction<transactions_count>>('transactions_count')
|
|
||||||
.asFunction<TransactionsCount>();
|
|
||||||
|
|
||||||
final transactionsGetAllNative = moneroApi
|
|
||||||
.lookup<NativeFunction<transactions_get_all>>('transactions_get_all')
|
|
||||||
.asFunction<TransactionsGetAll>();
|
|
||||||
|
|
||||||
final transactionCreateNative = moneroApi
|
|
||||||
.lookup<NativeFunction<transaction_create>>('transaction_create')
|
|
||||||
.asFunction<TransactionCreate>();
|
|
||||||
|
|
||||||
final transactionCreateMultDestNative = moneroApi
|
|
||||||
.lookup<NativeFunction<transaction_create_mult_dest>>('transaction_create_mult_dest')
|
|
||||||
.asFunction<TransactionCreateMultDest>();
|
|
||||||
|
|
||||||
final transactionCommitNative = moneroApi
|
|
||||||
.lookup<NativeFunction<transaction_commit>>('transaction_commit')
|
|
||||||
.asFunction<TransactionCommit>();
|
|
||||||
|
|
||||||
final getTxKeyNative = moneroApi
|
|
||||||
.lookup<NativeFunction<get_tx_key>>('get_tx_key')
|
|
||||||
.asFunction<GetTxKey>();
|
|
||||||
|
|
||||||
String getTxKey(String txId) {
|
|
||||||
final txIdPointer = Utf8.toUtf8(txId);
|
|
||||||
final keyPointer = getTxKeyNative(txIdPointer);
|
|
||||||
|
|
||||||
free(txIdPointer);
|
|
||||||
|
|
||||||
if (keyPointer != null) {
|
|
||||||
return convertUTF8ToString(pointer: keyPointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void refreshTransactions() => transactionsRefreshNative();
|
|
||||||
|
|
||||||
int countOfTransactions() => transactionsCountNative();
|
|
||||||
|
|
||||||
List<TransactionInfoRow> getAllTransations() {
|
|
||||||
final size = transactionsCountNative();
|
|
||||||
final transactionsPointer = transactionsGetAllNative();
|
|
||||||
final transactionsAddresses = transactionsPointer.asTypedList(size);
|
|
||||||
|
|
||||||
return transactionsAddresses
|
|
||||||
.map((addr) => Pointer<TransactionInfoRow>.fromAddress(addr).ref)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
PendingTransactionDescription createTransactionSync(
|
|
||||||
{String address,
|
|
||||||
String paymentId,
|
|
||||||
String amount,
|
|
||||||
int priorityRaw,
|
|
||||||
int accountIndex = 0}) {
|
|
||||||
final addressPointer = Utf8.toUtf8(address);
|
|
||||||
final paymentIdPointer = Utf8.toUtf8(paymentId);
|
|
||||||
final amountPointer = amount != null ? Utf8.toUtf8(amount) : nullptr;
|
|
||||||
final errorMessagePointer = allocate<Utf8Box>();
|
|
||||||
final pendingTransactionRawPointer = allocate<PendingTransactionRaw>();
|
|
||||||
final created = transactionCreateNative(
|
|
||||||
addressPointer,
|
|
||||||
paymentIdPointer,
|
|
||||||
amountPointer,
|
|
||||||
priorityRaw,
|
|
||||||
accountIndex,
|
|
||||||
errorMessagePointer,
|
|
||||||
pendingTransactionRawPointer) !=
|
|
||||||
0;
|
|
||||||
|
|
||||||
free(addressPointer);
|
|
||||||
free(paymentIdPointer);
|
|
||||||
|
|
||||||
if (amountPointer != nullptr) {
|
|
||||||
free(amountPointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!created) {
|
|
||||||
final message = errorMessagePointer.ref.getValue();
|
|
||||||
free(errorMessagePointer);
|
|
||||||
throw CreationTransactionException(message: message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return PendingTransactionDescription(
|
|
||||||
amount: pendingTransactionRawPointer.ref.amount,
|
|
||||||
fee: pendingTransactionRawPointer.ref.fee,
|
|
||||||
hash: pendingTransactionRawPointer.ref.getHash(),
|
|
||||||
pointerAddress: pendingTransactionRawPointer.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
PendingTransactionDescription createTransactionMultDestSync(
|
|
||||||
{List<MoneroOutput> outputs,
|
|
||||||
String paymentId,
|
|
||||||
int priorityRaw,
|
|
||||||
int accountIndex = 0}) {
|
|
||||||
final int size = outputs.length;
|
|
||||||
final List<Pointer<Utf8>> addressesPointers = outputs.map((output) =>
|
|
||||||
Utf8.toUtf8(output.address)).toList();
|
|
||||||
final Pointer<Pointer<Utf8>> addressesPointerPointer = allocate(count: size);
|
|
||||||
final List<Pointer<Utf8>> amountsPointers = outputs.map((output) =>
|
|
||||||
Utf8.toUtf8(output.amount)).toList();
|
|
||||||
final Pointer<Pointer<Utf8>> amountsPointerPointer = allocate(count: size);
|
|
||||||
|
|
||||||
for (int i = 0; i < size; i++) {
|
|
||||||
addressesPointerPointer[i] = addressesPointers[i];
|
|
||||||
amountsPointerPointer[i] = amountsPointers[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
final paymentIdPointer = Utf8.toUtf8(paymentId);
|
|
||||||
final errorMessagePointer = allocate<Utf8Box>();
|
|
||||||
final pendingTransactionRawPointer = allocate<PendingTransactionRaw>();
|
|
||||||
final created = transactionCreateMultDestNative(
|
|
||||||
addressesPointerPointer,
|
|
||||||
paymentIdPointer,
|
|
||||||
amountsPointerPointer,
|
|
||||||
size,
|
|
||||||
priorityRaw,
|
|
||||||
accountIndex,
|
|
||||||
errorMessagePointer,
|
|
||||||
pendingTransactionRawPointer) !=
|
|
||||||
0;
|
|
||||||
|
|
||||||
free(addressesPointerPointer);
|
|
||||||
free(amountsPointerPointer);
|
|
||||||
|
|
||||||
addressesPointers.forEach((element) => free(element));
|
|
||||||
amountsPointers.forEach((element) => free(element));
|
|
||||||
|
|
||||||
free(paymentIdPointer);
|
|
||||||
|
|
||||||
if (!created) {
|
|
||||||
final message = errorMessagePointer.ref.getValue();
|
|
||||||
free(errorMessagePointer);
|
|
||||||
throw CreationTransactionException(message: message);
|
|
||||||
}
|
|
||||||
|
|
||||||
return PendingTransactionDescription(
|
|
||||||
amount: pendingTransactionRawPointer.ref.amount,
|
|
||||||
fee: pendingTransactionRawPointer.ref.fee,
|
|
||||||
hash: pendingTransactionRawPointer.ref.getHash(),
|
|
||||||
pointerAddress: pendingTransactionRawPointer.address);
|
|
||||||
}
|
|
||||||
|
|
||||||
void commitTransactionFromPointerAddress({int address}) => commitTransaction(
|
|
||||||
transactionPointer: Pointer<PendingTransactionRaw>.fromAddress(address));
|
|
||||||
|
|
||||||
void commitTransaction({Pointer<PendingTransactionRaw> transactionPointer}) {
|
|
||||||
final errorMessagePointer = allocate<Utf8Box>();
|
|
||||||
final isCommited =
|
|
||||||
transactionCommitNative(transactionPointer, errorMessagePointer) != 0;
|
|
||||||
|
|
||||||
if (!isCommited) {
|
|
||||||
final message = errorMessagePointer.ref.getValue();
|
|
||||||
free(errorMessagePointer);
|
|
||||||
throw CreationTransactionException(message: message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PendingTransactionDescription _createTransactionSync(Map args) {
|
|
||||||
final address = args['address'] as String;
|
|
||||||
final paymentId = args['paymentId'] as String;
|
|
||||||
final amount = args['amount'] as String;
|
|
||||||
final priorityRaw = args['priorityRaw'] as int;
|
|
||||||
final accountIndex = args['accountIndex'] as int;
|
|
||||||
|
|
||||||
return createTransactionSync(
|
|
||||||
address: address,
|
|
||||||
paymentId: paymentId,
|
|
||||||
amount: amount,
|
|
||||||
priorityRaw: priorityRaw,
|
|
||||||
accountIndex: accountIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
PendingTransactionDescription _createTransactionMultDestSync(Map args) {
|
|
||||||
final outputs = args['outputs'] as List<MoneroOutput>;
|
|
||||||
final paymentId = args['paymentId'] as String;
|
|
||||||
final priorityRaw = args['priorityRaw'] as int;
|
|
||||||
final accountIndex = args['accountIndex'] as int;
|
|
||||||
|
|
||||||
return createTransactionMultDestSync(
|
|
||||||
outputs: outputs,
|
|
||||||
paymentId: paymentId,
|
|
||||||
priorityRaw: priorityRaw,
|
|
||||||
accountIndex: accountIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<PendingTransactionDescription> createTransaction(
|
|
||||||
{String address,
|
|
||||||
String paymentId = '',
|
|
||||||
String amount,
|
|
||||||
int priorityRaw,
|
|
||||||
int accountIndex = 0}) =>
|
|
||||||
compute(_createTransactionSync, {
|
|
||||||
'address': address,
|
|
||||||
'paymentId': paymentId,
|
|
||||||
'amount': amount,
|
|
||||||
'priorityRaw': priorityRaw,
|
|
||||||
'accountIndex': accountIndex
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<PendingTransactionDescription> createTransactionMultDest(
|
|
||||||
{List<MoneroOutput> outputs,
|
|
||||||
String paymentId = '',
|
|
||||||
int priorityRaw,
|
|
||||||
int accountIndex = 0}) =>
|
|
||||||
compute(_createTransactionMultDestSync, {
|
|
||||||
'outputs': outputs,
|
|
||||||
'paymentId': paymentId,
|
|
||||||
'priorityRaw': priorityRaw,
|
|
||||||
'accountIndex': accountIndex
|
|
||||||
});
|
|
|
@ -1,120 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'package:cw_monero/structs/pending_transaction.dart';
|
|
||||||
import 'package:cw_monero/structs/ut8_box.dart';
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
|
|
||||||
typedef CreateWallet = int Function(
|
|
||||||
Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, int, Pointer<Utf8>);
|
|
||||||
|
|
||||||
typedef RestoreWalletFromSeed = int Function(
|
|
||||||
Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, int, int, Pointer<Utf8>);
|
|
||||||
|
|
||||||
typedef RestoreWalletFromKeys = int Function(Pointer<Utf8>, Pointer<Utf8>,
|
|
||||||
Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, int, int, Pointer<Utf8>);
|
|
||||||
|
|
||||||
typedef IsWalletExist = int Function(Pointer<Utf8>);
|
|
||||||
|
|
||||||
typedef LoadWallet = int Function(Pointer<Utf8>, Pointer<Utf8>, int);
|
|
||||||
|
|
||||||
typedef ErrorString = Pointer<Utf8> Function();
|
|
||||||
|
|
||||||
typedef GetFilename = Pointer<Utf8> Function();
|
|
||||||
|
|
||||||
typedef GetSeed = Pointer<Utf8> Function();
|
|
||||||
|
|
||||||
typedef GetAddress = Pointer<Utf8> Function(int, int);
|
|
||||||
|
|
||||||
typedef GetFullBalance = int Function(int);
|
|
||||||
|
|
||||||
typedef GetUnlockedBalance = int Function(int);
|
|
||||||
|
|
||||||
typedef GetCurrentHeight = int Function();
|
|
||||||
|
|
||||||
typedef GetNodeHeight = int Function();
|
|
||||||
|
|
||||||
typedef IsConnected = int Function();
|
|
||||||
|
|
||||||
typedef SetupNode = int Function(
|
|
||||||
Pointer<Utf8>, Pointer<Utf8>, Pointer<Utf8>, int, int, Pointer<Utf8>);
|
|
||||||
|
|
||||||
typedef StartRefresh = void Function();
|
|
||||||
|
|
||||||
typedef ConnectToNode = int Function();
|
|
||||||
|
|
||||||
typedef SetRefreshFromBlockHeight = void Function(int);
|
|
||||||
|
|
||||||
typedef SetRecoveringFromSeed = void Function(int);
|
|
||||||
|
|
||||||
typedef Store = void Function(Pointer<Utf8>);
|
|
||||||
|
|
||||||
typedef SetListener = void Function();
|
|
||||||
|
|
||||||
typedef GetSyncingHeight = int Function();
|
|
||||||
|
|
||||||
typedef IsNeededToRefresh = int Function();
|
|
||||||
|
|
||||||
typedef IsNewTransactionExist = int Function();
|
|
||||||
|
|
||||||
typedef SubaddressSize = int Function();
|
|
||||||
|
|
||||||
typedef SubaddressRefresh = void Function(int);
|
|
||||||
|
|
||||||
typedef SubaddressGetAll = Pointer<Int64> Function();
|
|
||||||
|
|
||||||
typedef SubaddressAddNew = void Function(int accountIndex, Pointer<Utf8> label);
|
|
||||||
|
|
||||||
typedef SubaddressSetLabel = void Function(
|
|
||||||
int accountIndex, int addressIndex, Pointer<Utf8> label);
|
|
||||||
|
|
||||||
typedef AccountSize = int Function();
|
|
||||||
|
|
||||||
typedef AccountRefresh = void Function();
|
|
||||||
|
|
||||||
typedef AccountGetAll = Pointer<Int64> Function();
|
|
||||||
|
|
||||||
typedef AccountAddNew = void Function(Pointer<Utf8> label);
|
|
||||||
|
|
||||||
typedef AccountSetLabel = void Function(int accountIndex, Pointer<Utf8> label);
|
|
||||||
|
|
||||||
typedef TransactionsRefresh = void Function();
|
|
||||||
|
|
||||||
typedef GetTxKey = Pointer<Utf8> Function(Pointer<Utf8> txId);
|
|
||||||
|
|
||||||
typedef TransactionsCount = int Function();
|
|
||||||
|
|
||||||
typedef TransactionsGetAll = Pointer<Int64> Function();
|
|
||||||
|
|
||||||
typedef TransactionCreate = int Function(
|
|
||||||
Pointer<Utf8> address,
|
|
||||||
Pointer<Utf8> paymentId,
|
|
||||||
Pointer<Utf8> amount,
|
|
||||||
int priorityRaw,
|
|
||||||
int subaddrAccount,
|
|
||||||
Pointer<Utf8Box> error,
|
|
||||||
Pointer<PendingTransactionRaw> pendingTransaction);
|
|
||||||
|
|
||||||
typedef TransactionCreateMultDest = int Function(
|
|
||||||
Pointer<Pointer<Utf8>> addresses,
|
|
||||||
Pointer<Utf8> paymentId,
|
|
||||||
Pointer<Pointer<Utf8>> amounts,
|
|
||||||
int size,
|
|
||||||
int priorityRaw,
|
|
||||||
int subaddrAccount,
|
|
||||||
Pointer<Utf8Box> error,
|
|
||||||
Pointer<PendingTransactionRaw> pendingTransaction);
|
|
||||||
|
|
||||||
typedef TransactionCommit = int Function(Pointer<PendingTransactionRaw>, Pointer<Utf8Box>);
|
|
||||||
|
|
||||||
typedef SecretViewKey = Pointer<Utf8> Function();
|
|
||||||
|
|
||||||
typedef PublicViewKey = Pointer<Utf8> Function();
|
|
||||||
|
|
||||||
typedef SecretSpendKey = Pointer<Utf8> Function();
|
|
||||||
|
|
||||||
typedef PublicSpendKey = Pointer<Utf8> Function();
|
|
||||||
|
|
||||||
typedef CloseCurrentWallet = void Function();
|
|
||||||
|
|
||||||
typedef OnStartup = void Function();
|
|
||||||
|
|
||||||
typedef RescanBlockchainAsync = void Function();
|
|
|
@ -1,329 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:ffi';
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
import 'package:cw_monero/convert_utf8_to_string.dart';
|
|
||||||
import 'package:cw_monero/signatures.dart';
|
|
||||||
import 'package:cw_monero/types.dart';
|
|
||||||
import 'package:cw_monero/monero_api.dart';
|
|
||||||
import 'package:cw_monero/exceptions/setup_wallet_exception.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
int _boolToInt(bool value) => value ? 1 : 0;
|
|
||||||
|
|
||||||
final getFileNameNative = moneroApi
|
|
||||||
.lookup<NativeFunction<get_filename>>('get_filename')
|
|
||||||
.asFunction<GetFilename>();
|
|
||||||
|
|
||||||
final getSeedNative =
|
|
||||||
moneroApi.lookup<NativeFunction<get_seed>>('seed').asFunction<GetSeed>();
|
|
||||||
|
|
||||||
final getAddressNative = moneroApi
|
|
||||||
.lookup<NativeFunction<get_address>>('get_address')
|
|
||||||
.asFunction<GetAddress>();
|
|
||||||
|
|
||||||
final getFullBalanceNative = moneroApi
|
|
||||||
.lookup<NativeFunction<get_full_balanace>>('get_full_balance')
|
|
||||||
.asFunction<GetFullBalance>();
|
|
||||||
|
|
||||||
final getUnlockedBalanceNative = moneroApi
|
|
||||||
.lookup<NativeFunction<get_unlocked_balanace>>('get_unlocked_balance')
|
|
||||||
.asFunction<GetUnlockedBalance>();
|
|
||||||
|
|
||||||
final getCurrentHeightNative = moneroApi
|
|
||||||
.lookup<NativeFunction<get_current_height>>('get_current_height')
|
|
||||||
.asFunction<GetCurrentHeight>();
|
|
||||||
|
|
||||||
final getNodeHeightNative = moneroApi
|
|
||||||
.lookup<NativeFunction<get_node_height>>('get_node_height')
|
|
||||||
.asFunction<GetNodeHeight>();
|
|
||||||
|
|
||||||
final isConnectedNative = moneroApi
|
|
||||||
.lookup<NativeFunction<is_connected>>('is_connected')
|
|
||||||
.asFunction<IsConnected>();
|
|
||||||
|
|
||||||
final setupNodeNative = moneroApi
|
|
||||||
.lookup<NativeFunction<setup_node>>('setup_node')
|
|
||||||
.asFunction<SetupNode>();
|
|
||||||
|
|
||||||
final startRefreshNative = moneroApi
|
|
||||||
.lookup<NativeFunction<start_refresh>>('start_refresh')
|
|
||||||
.asFunction<StartRefresh>();
|
|
||||||
|
|
||||||
final connecToNodeNative = moneroApi
|
|
||||||
.lookup<NativeFunction<connect_to_node>>('connect_to_node')
|
|
||||||
.asFunction<ConnectToNode>();
|
|
||||||
|
|
||||||
final setRefreshFromBlockHeightNative = moneroApi
|
|
||||||
.lookup<NativeFunction<set_refresh_from_block_height>>(
|
|
||||||
'set_refresh_from_block_height')
|
|
||||||
.asFunction<SetRefreshFromBlockHeight>();
|
|
||||||
|
|
||||||
final setRecoveringFromSeedNative = moneroApi
|
|
||||||
.lookup<NativeFunction<set_recovering_from_seed>>(
|
|
||||||
'set_recovering_from_seed')
|
|
||||||
.asFunction<SetRecoveringFromSeed>();
|
|
||||||
|
|
||||||
final storeNative =
|
|
||||||
moneroApi.lookup<NativeFunction<store_c>>('store').asFunction<Store>();
|
|
||||||
|
|
||||||
final setListenerNative = moneroApi
|
|
||||||
.lookup<NativeFunction<set_listener>>('set_listener')
|
|
||||||
.asFunction<SetListener>();
|
|
||||||
|
|
||||||
final getSyncingHeightNative = moneroApi
|
|
||||||
.lookup<NativeFunction<get_syncing_height>>('get_syncing_height')
|
|
||||||
.asFunction<GetSyncingHeight>();
|
|
||||||
|
|
||||||
final isNeededToRefreshNative = moneroApi
|
|
||||||
.lookup<NativeFunction<is_needed_to_refresh>>('is_needed_to_refresh')
|
|
||||||
.asFunction<IsNeededToRefresh>();
|
|
||||||
|
|
||||||
final isNewTransactionExistNative = moneroApi
|
|
||||||
.lookup<NativeFunction<is_new_transaction_exist>>(
|
|
||||||
'is_new_transaction_exist')
|
|
||||||
.asFunction<IsNewTransactionExist>();
|
|
||||||
|
|
||||||
final getSecretViewKeyNative = moneroApi
|
|
||||||
.lookup<NativeFunction<secret_view_key>>('secret_view_key')
|
|
||||||
.asFunction<SecretViewKey>();
|
|
||||||
|
|
||||||
final getPublicViewKeyNative = moneroApi
|
|
||||||
.lookup<NativeFunction<public_view_key>>('public_view_key')
|
|
||||||
.asFunction<PublicViewKey>();
|
|
||||||
|
|
||||||
final getSecretSpendKeyNative = moneroApi
|
|
||||||
.lookup<NativeFunction<secret_spend_key>>('secret_spend_key')
|
|
||||||
.asFunction<SecretSpendKey>();
|
|
||||||
|
|
||||||
final getPublicSpendKeyNative = moneroApi
|
|
||||||
.lookup<NativeFunction<secret_view_key>>('public_spend_key')
|
|
||||||
.asFunction<PublicSpendKey>();
|
|
||||||
|
|
||||||
final closeCurrentWalletNative = moneroApi
|
|
||||||
.lookup<NativeFunction<close_current_wallet>>('close_current_wallet')
|
|
||||||
.asFunction<CloseCurrentWallet>();
|
|
||||||
|
|
||||||
final onStartupNative = moneroApi
|
|
||||||
.lookup<NativeFunction<on_startup>>('on_startup')
|
|
||||||
.asFunction<OnStartup>();
|
|
||||||
|
|
||||||
final rescanBlockchainAsyncNative = moneroApi
|
|
||||||
.lookup<NativeFunction<rescan_blockchain>>('rescan_blockchain')
|
|
||||||
.asFunction<RescanBlockchainAsync>();
|
|
||||||
|
|
||||||
int getSyncingHeight() => getSyncingHeightNative();
|
|
||||||
|
|
||||||
bool isNeededToRefresh() => isNeededToRefreshNative() != 0;
|
|
||||||
|
|
||||||
bool isNewTransactionExist() => isNewTransactionExistNative() != 0;
|
|
||||||
|
|
||||||
String getFilename() => convertUTF8ToString(pointer: getFileNameNative());
|
|
||||||
|
|
||||||
String getSeed() => convertUTF8ToString(pointer: getSeedNative());
|
|
||||||
|
|
||||||
String getAddress({int accountIndex = 0, int addressIndex = 0}) =>
|
|
||||||
convertUTF8ToString(pointer: getAddressNative(accountIndex, addressIndex));
|
|
||||||
|
|
||||||
int getFullBalance({int accountIndex = 0}) =>
|
|
||||||
getFullBalanceNative(accountIndex);
|
|
||||||
|
|
||||||
int getUnlockedBalance({int accountIndex = 0}) =>
|
|
||||||
getUnlockedBalanceNative(accountIndex);
|
|
||||||
|
|
||||||
int getCurrentHeight() => getCurrentHeightNative();
|
|
||||||
|
|
||||||
int getNodeHeightSync() => getNodeHeightNative();
|
|
||||||
|
|
||||||
bool isConnectedSync() => isConnectedNative() != 0;
|
|
||||||
|
|
||||||
bool setupNodeSync(
|
|
||||||
{String address,
|
|
||||||
String login,
|
|
||||||
String password,
|
|
||||||
bool useSSL = false,
|
|
||||||
bool isLightWallet = false}) {
|
|
||||||
final addressPointer = Utf8.toUtf8(address);
|
|
||||||
Pointer<Utf8> loginPointer;
|
|
||||||
Pointer<Utf8> passwordPointer;
|
|
||||||
|
|
||||||
if (login != null) {
|
|
||||||
loginPointer = Utf8.toUtf8(login);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (password != null) {
|
|
||||||
passwordPointer = Utf8.toUtf8(password);
|
|
||||||
}
|
|
||||||
|
|
||||||
final errorMessagePointer = allocate<Utf8>();
|
|
||||||
final isSetupNode = setupNodeNative(
|
|
||||||
addressPointer,
|
|
||||||
loginPointer,
|
|
||||||
passwordPointer,
|
|
||||||
_boolToInt(useSSL),
|
|
||||||
_boolToInt(isLightWallet),
|
|
||||||
errorMessagePointer) !=
|
|
||||||
0;
|
|
||||||
|
|
||||||
free(addressPointer);
|
|
||||||
free(loginPointer);
|
|
||||||
free(passwordPointer);
|
|
||||||
|
|
||||||
if (!isSetupNode) {
|
|
||||||
throw SetupWalletException(
|
|
||||||
message: convertUTF8ToString(pointer: errorMessagePointer));
|
|
||||||
}
|
|
||||||
|
|
||||||
return isSetupNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
void startRefreshSync() => startRefreshNative();
|
|
||||||
|
|
||||||
Future<bool> connectToNode() async => connecToNodeNative() != 0;
|
|
||||||
|
|
||||||
void setRefreshFromBlockHeight({int height}) =>
|
|
||||||
setRefreshFromBlockHeightNative(height);
|
|
||||||
|
|
||||||
void setRecoveringFromSeed({bool isRecovery}) =>
|
|
||||||
setRecoveringFromSeedNative(_boolToInt(isRecovery));
|
|
||||||
|
|
||||||
void storeSync() {
|
|
||||||
final pathPointer = Utf8.toUtf8('');
|
|
||||||
storeNative(pathPointer);
|
|
||||||
free(pathPointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void closeCurrentWallet() => closeCurrentWalletNative();
|
|
||||||
|
|
||||||
String getSecretViewKey() =>
|
|
||||||
convertUTF8ToString(pointer: getSecretViewKeyNative());
|
|
||||||
|
|
||||||
String getPublicViewKey() =>
|
|
||||||
convertUTF8ToString(pointer: getPublicViewKeyNative());
|
|
||||||
|
|
||||||
String getSecretSpendKey() =>
|
|
||||||
convertUTF8ToString(pointer: getSecretSpendKeyNative());
|
|
||||||
|
|
||||||
String getPublicSpendKey() =>
|
|
||||||
convertUTF8ToString(pointer: getPublicSpendKeyNative());
|
|
||||||
|
|
||||||
class SyncListener {
|
|
||||||
SyncListener(this.onNewBlock, this.onNewTransaction) {
|
|
||||||
_cachedBlockchainHeight = 0;
|
|
||||||
_lastKnownBlockHeight = 0;
|
|
||||||
_initialSyncHeight = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Function(int, int, double) onNewBlock;
|
|
||||||
void Function() onNewTransaction;
|
|
||||||
|
|
||||||
Timer _updateSyncInfoTimer;
|
|
||||||
int _cachedBlockchainHeight;
|
|
||||||
int _lastKnownBlockHeight;
|
|
||||||
int _initialSyncHeight;
|
|
||||||
|
|
||||||
Future<int> getNodeHeightOrUpdate(int baseHeight) async {
|
|
||||||
if (_cachedBlockchainHeight < baseHeight || _cachedBlockchainHeight == 0) {
|
|
||||||
_cachedBlockchainHeight = await getNodeHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
return _cachedBlockchainHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
void start() {
|
|
||||||
_cachedBlockchainHeight = 0;
|
|
||||||
_lastKnownBlockHeight = 0;
|
|
||||||
_initialSyncHeight = 0;
|
|
||||||
_updateSyncInfoTimer ??=
|
|
||||||
Timer.periodic(Duration(milliseconds: 1200), (_) async {
|
|
||||||
if (isNewTransactionExist()) {
|
|
||||||
onNewTransaction?.call();
|
|
||||||
}
|
|
||||||
|
|
||||||
var syncHeight = getSyncingHeight();
|
|
||||||
|
|
||||||
if (syncHeight <= 0) {
|
|
||||||
syncHeight = getCurrentHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_initialSyncHeight <= 0) {
|
|
||||||
_initialSyncHeight = syncHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
final bchHeight = await getNodeHeightOrUpdate(syncHeight);
|
|
||||||
|
|
||||||
if (_lastKnownBlockHeight == syncHeight || syncHeight == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_lastKnownBlockHeight = syncHeight;
|
|
||||||
final track = bchHeight - _initialSyncHeight;
|
|
||||||
final diff = track - (bchHeight - syncHeight);
|
|
||||||
final ptc = diff <= 0 ? 0.0 : diff / track;
|
|
||||||
final left = bchHeight - syncHeight;
|
|
||||||
|
|
||||||
if (syncHeight < 0 || left < 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Actual new height; 2. Blocks left to finish; 3. Progress in percents;
|
|
||||||
onNewBlock?.call(syncHeight, left, ptc);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void stop() => _updateSyncInfoTimer?.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
SyncListener setListeners(void Function(int, int, double) onNewBlock,
|
|
||||||
void Function() onNewTransaction) {
|
|
||||||
final listener = SyncListener(onNewBlock, onNewTransaction);
|
|
||||||
setListenerNative();
|
|
||||||
return listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
void onStartup() => onStartupNative();
|
|
||||||
|
|
||||||
void _storeSync(Object _) => storeSync();
|
|
||||||
|
|
||||||
bool _setupNodeSync(Map args) {
|
|
||||||
final address = args['address'] as String;
|
|
||||||
final login = (args['login'] ?? '') as String;
|
|
||||||
final password = (args['password'] ?? '') as String;
|
|
||||||
final useSSL = args['useSSL'] as bool;
|
|
||||||
final isLightWallet = args['isLightWallet'] as bool;
|
|
||||||
|
|
||||||
return setupNodeSync(
|
|
||||||
address: address,
|
|
||||||
login: login,
|
|
||||||
password: password,
|
|
||||||
useSSL: useSSL,
|
|
||||||
isLightWallet: isLightWallet);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isConnected(Object _) => isConnectedSync();
|
|
||||||
|
|
||||||
int _getNodeHeight(Object _) => getNodeHeightSync();
|
|
||||||
|
|
||||||
void startRefresh() => startRefreshSync();
|
|
||||||
|
|
||||||
Future setupNode(
|
|
||||||
{String address,
|
|
||||||
String login,
|
|
||||||
String password,
|
|
||||||
bool useSSL = false,
|
|
||||||
bool isLightWallet = false}) =>
|
|
||||||
compute<Map<String, Object>, void>(_setupNodeSync, {
|
|
||||||
'address': address,
|
|
||||||
'login': login,
|
|
||||||
'password': password,
|
|
||||||
'useSSL': useSSL,
|
|
||||||
'isLightWallet': isLightWallet
|
|
||||||
});
|
|
||||||
|
|
||||||
Future store() => compute<int, void>(_storeSync, 0);
|
|
||||||
|
|
||||||
Future<bool> isConnected() => compute(_isConnected, 0);
|
|
||||||
|
|
||||||
Future<int> getNodeHeight() => compute(_getNodeHeight, 0);
|
|
||||||
|
|
||||||
void rescanBlockchainAsync() => rescanBlockchainAsyncNative();
|
|
|
@ -1,248 +0,0 @@
|
||||||
import 'dart:ffi';
|
|
||||||
import 'package:ffi/ffi.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:cw_monero/convert_utf8_to_string.dart';
|
|
||||||
import 'package:cw_monero/signatures.dart';
|
|
||||||
import 'package:cw_monero/types.dart';
|
|
||||||
import 'package:cw_monero/monero_api.dart';
|
|
||||||
import 'package:cw_monero/wallet.dart';
|
|
||||||
import 'package:cw_monero/exceptions/wallet_opening_exception.dart';
|
|
||||||
import 'package:cw_monero/exceptions/wallet_creation_exception.dart';
|
|
||||||
import 'package:cw_monero/exceptions/wallet_restore_from_keys_exception.dart';
|
|
||||||
import 'package:cw_monero/exceptions/wallet_restore_from_seed_exception.dart';
|
|
||||||
|
|
||||||
final createWalletNative = moneroApi
|
|
||||||
.lookup<NativeFunction<create_wallet>>('create_wallet')
|
|
||||||
.asFunction<CreateWallet>();
|
|
||||||
|
|
||||||
final restoreWalletFromSeedNative = moneroApi
|
|
||||||
.lookup<NativeFunction<restore_wallet_from_seed>>(
|
|
||||||
'restore_wallet_from_seed')
|
|
||||||
.asFunction<RestoreWalletFromSeed>();
|
|
||||||
|
|
||||||
final restoreWalletFromKeysNative = moneroApi
|
|
||||||
.lookup<NativeFunction<restore_wallet_from_keys>>(
|
|
||||||
'restore_wallet_from_keys')
|
|
||||||
.asFunction<RestoreWalletFromKeys>();
|
|
||||||
|
|
||||||
final isWalletExistNative = moneroApi
|
|
||||||
.lookup<NativeFunction<is_wallet_exist>>('is_wallet_exist')
|
|
||||||
.asFunction<IsWalletExist>();
|
|
||||||
|
|
||||||
final loadWalletNative = moneroApi
|
|
||||||
.lookup<NativeFunction<load_wallet>>('load_wallet')
|
|
||||||
.asFunction<LoadWallet>();
|
|
||||||
|
|
||||||
final errorStringNative = moneroApi
|
|
||||||
.lookup<NativeFunction<error_string>>('error_string')
|
|
||||||
.asFunction<ErrorString>();
|
|
||||||
|
|
||||||
void createWalletSync(
|
|
||||||
{String path, String password, String language, int nettype = 0}) {
|
|
||||||
final pathPointer = Utf8.toUtf8(path);
|
|
||||||
final passwordPointer = Utf8.toUtf8(password);
|
|
||||||
final languagePointer = Utf8.toUtf8(language);
|
|
||||||
final errorMessagePointer = allocate<Utf8>();
|
|
||||||
final isWalletCreated = createWalletNative(pathPointer, passwordPointer,
|
|
||||||
languagePointer, nettype, errorMessagePointer) !=
|
|
||||||
0;
|
|
||||||
|
|
||||||
free(pathPointer);
|
|
||||||
free(passwordPointer);
|
|
||||||
free(languagePointer);
|
|
||||||
|
|
||||||
if (!isWalletCreated) {
|
|
||||||
throw WalletCreationException(
|
|
||||||
message: convertUTF8ToString(pointer: errorMessagePointer));
|
|
||||||
}
|
|
||||||
|
|
||||||
// setupNodeSync(address: "node.moneroworld.com:18089");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isWalletExistSync({String path}) {
|
|
||||||
final pathPointer = Utf8.toUtf8(path);
|
|
||||||
final isExist = isWalletExistNative(pathPointer) != 0;
|
|
||||||
|
|
||||||
free(pathPointer);
|
|
||||||
|
|
||||||
return isExist;
|
|
||||||
}
|
|
||||||
|
|
||||||
void restoreWalletFromSeedSync(
|
|
||||||
{String path,
|
|
||||||
String password,
|
|
||||||
String seed,
|
|
||||||
int nettype = 0,
|
|
||||||
int restoreHeight = 0}) {
|
|
||||||
final pathPointer = Utf8.toUtf8(path);
|
|
||||||
final passwordPointer = Utf8.toUtf8(password);
|
|
||||||
final seedPointer = Utf8.toUtf8(seed);
|
|
||||||
final errorMessagePointer = allocate<Utf8>();
|
|
||||||
final isWalletRestored = restoreWalletFromSeedNative(
|
|
||||||
pathPointer,
|
|
||||||
passwordPointer,
|
|
||||||
seedPointer,
|
|
||||||
nettype,
|
|
||||||
restoreHeight,
|
|
||||||
errorMessagePointer) !=
|
|
||||||
0;
|
|
||||||
|
|
||||||
free(pathPointer);
|
|
||||||
free(passwordPointer);
|
|
||||||
free(seedPointer);
|
|
||||||
|
|
||||||
if (!isWalletRestored) {
|
|
||||||
throw WalletRestoreFromSeedException(
|
|
||||||
message: convertUTF8ToString(pointer: errorMessagePointer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void restoreWalletFromKeysSync(
|
|
||||||
{String path,
|
|
||||||
String password,
|
|
||||||
String language,
|
|
||||||
String address,
|
|
||||||
String viewKey,
|
|
||||||
String spendKey,
|
|
||||||
int nettype = 0,
|
|
||||||
int restoreHeight = 0}) {
|
|
||||||
final pathPointer = Utf8.toUtf8(path);
|
|
||||||
final passwordPointer = Utf8.toUtf8(password);
|
|
||||||
final languagePointer = Utf8.toUtf8(language);
|
|
||||||
final addressPointer = Utf8.toUtf8(address);
|
|
||||||
final viewKeyPointer = Utf8.toUtf8(viewKey);
|
|
||||||
final spendKeyPointer = Utf8.toUtf8(spendKey);
|
|
||||||
final errorMessagePointer = allocate<Utf8>();
|
|
||||||
final isWalletRestored = restoreWalletFromKeysNative(
|
|
||||||
pathPointer,
|
|
||||||
passwordPointer,
|
|
||||||
languagePointer,
|
|
||||||
addressPointer,
|
|
||||||
viewKeyPointer,
|
|
||||||
spendKeyPointer,
|
|
||||||
nettype,
|
|
||||||
restoreHeight,
|
|
||||||
errorMessagePointer) !=
|
|
||||||
0;
|
|
||||||
|
|
||||||
free(pathPointer);
|
|
||||||
free(passwordPointer);
|
|
||||||
free(languagePointer);
|
|
||||||
free(addressPointer);
|
|
||||||
free(viewKeyPointer);
|
|
||||||
free(spendKeyPointer);
|
|
||||||
|
|
||||||
if (!isWalletRestored) {
|
|
||||||
throw WalletRestoreFromKeysException(
|
|
||||||
message: convertUTF8ToString(pointer: errorMessagePointer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void loadWallet({String path, String password, int nettype = 0}) {
|
|
||||||
final pathPointer = Utf8.toUtf8(path);
|
|
||||||
final passwordPointer = Utf8.toUtf8(password);
|
|
||||||
final loaded = loadWalletNative(pathPointer, passwordPointer, nettype) != 0;
|
|
||||||
free(pathPointer);
|
|
||||||
free(passwordPointer);
|
|
||||||
|
|
||||||
if (!loaded) {
|
|
||||||
throw WalletOpeningException(
|
|
||||||
message: convertUTF8ToString(pointer: errorStringNative()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _createWallet(Map<String, dynamic> args) {
|
|
||||||
final path = args['path'] as String;
|
|
||||||
final password = args['password'] as String;
|
|
||||||
final language = args['language'] as String;
|
|
||||||
|
|
||||||
createWalletSync(path: path, password: password, language: language);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _restoreFromSeed(Map<String, dynamic> args) {
|
|
||||||
final path = args['path'] as String;
|
|
||||||
final password = args['password'] as String;
|
|
||||||
final seed = args['seed'] as String;
|
|
||||||
final restoreHeight = args['restoreHeight'] as int;
|
|
||||||
|
|
||||||
restoreWalletFromSeedSync(
|
|
||||||
path: path, password: password, seed: seed, restoreHeight: restoreHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _restoreFromKeys(Map<String, dynamic> args) {
|
|
||||||
final path = args['path'] as String;
|
|
||||||
final password = args['password'] as String;
|
|
||||||
final language = args['language'] as String;
|
|
||||||
final restoreHeight = args['restoreHeight'] as int;
|
|
||||||
final address = args['address'] as String;
|
|
||||||
final viewKey = args['viewKey'] as String;
|
|
||||||
final spendKey = args['spendKey'] as String;
|
|
||||||
|
|
||||||
restoreWalletFromKeysSync(
|
|
||||||
path: path,
|
|
||||||
password: password,
|
|
||||||
language: language,
|
|
||||||
restoreHeight: restoreHeight,
|
|
||||||
address: address,
|
|
||||||
viewKey: viewKey,
|
|
||||||
spendKey: spendKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _openWallet(Map<String, String> args) async =>
|
|
||||||
loadWallet(path: args['path'], password: args['password']);
|
|
||||||
|
|
||||||
bool _isWalletExist(String path) => isWalletExistSync(path: path);
|
|
||||||
|
|
||||||
void openWallet({String path, String password, int nettype = 0}) async =>
|
|
||||||
loadWallet(path: path, password: password, nettype: nettype);
|
|
||||||
|
|
||||||
Future<void> openWalletAsync(Map<String, String> args) async =>
|
|
||||||
compute(_openWallet, args);
|
|
||||||
|
|
||||||
Future<void> createWallet(
|
|
||||||
{String path,
|
|
||||||
String password,
|
|
||||||
String language,
|
|
||||||
int nettype = 0}) async =>
|
|
||||||
compute(_createWallet, {
|
|
||||||
'path': path,
|
|
||||||
'password': password,
|
|
||||||
'language': language,
|
|
||||||
'nettype': nettype
|
|
||||||
});
|
|
||||||
|
|
||||||
Future restoreFromSeed(
|
|
||||||
{String path,
|
|
||||||
String password,
|
|
||||||
String seed,
|
|
||||||
int nettype = 0,
|
|
||||||
int restoreHeight = 0}) async =>
|
|
||||||
compute<Map<String, Object>, void>(_restoreFromSeed, {
|
|
||||||
'path': path,
|
|
||||||
'password': password,
|
|
||||||
'seed': seed,
|
|
||||||
'nettype': nettype,
|
|
||||||
'restoreHeight': restoreHeight
|
|
||||||
});
|
|
||||||
|
|
||||||
Future restoreFromKeys(
|
|
||||||
{String path,
|
|
||||||
String password,
|
|
||||||
String language,
|
|
||||||
String address,
|
|
||||||
String viewKey,
|
|
||||||
String spendKey,
|
|
||||||
int nettype = 0,
|
|
||||||
int restoreHeight = 0}) async =>
|
|
||||||
compute<Map<String, Object>, void>(_restoreFromKeys, {
|
|
||||||
'path': path,
|
|
||||||
'password': password,
|
|
||||||
'language': language,
|
|
||||||
'address': address,
|
|
||||||
'viewKey': viewKey,
|
|
||||||
'spendKey': spendKey,
|
|
||||||
'nettype': nettype,
|
|
||||||
'restoreHeight': restoreHeight
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<bool> isWalletExist({String path}) => compute(_isWalletExist, path);
|
|
|
@ -1,6 +1,27 @@
|
||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "14.0.0"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.41.2"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
async:
|
async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -15,6 +36,62 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
|
build:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.2"
|
||||||
|
build_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_config
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.6"
|
||||||
|
build_daemon:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_daemon
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.10"
|
||||||
|
build_resolvers:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: build_resolvers
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.3"
|
||||||
|
build_runner:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: build_runner
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.11.5"
|
||||||
|
build_runner_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_runner_core
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.10"
|
||||||
|
built_collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: built_collection
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "5.1.1"
|
||||||
|
built_value:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: built_value
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "8.1.3"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -29,6 +106,20 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
|
checked_yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: checked_yaml
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
|
cli_util:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_util
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.5"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -36,6 +127,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
code_builder:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: code_builder
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.7.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -43,6 +141,41 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.15.0"
|
version: "1.15.0"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
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
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.12"
|
||||||
|
dartx:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dartx
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.0"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -57,16 +190,121 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.3"
|
version: "0.1.3"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.2"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_mobx:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_mobx
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0+2"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
|
graphs:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: graphs
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0"
|
||||||
|
hive:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hive
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.0-nullsafety.2"
|
||||||
|
hive_generator:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: hive_generator
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.2"
|
||||||
|
http:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.2"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.4"
|
||||||
|
intl:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: intl
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.17.0"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.5"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.3"
|
||||||
|
json_annotation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: json_annotation
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.1"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -81,6 +319,34 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
|
mobx:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: mobx
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1+4"
|
||||||
|
mobx_codegen:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: mobx_codegen
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.3"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -95,6 +361,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
pedantic:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pedantic
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.11.1"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -102,11 +375,53 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.1"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.0"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
pubspec_parse:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pubspec_parse
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.8"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.9"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.4+1"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.99"
|
version: "0.0.99"
|
||||||
|
source_gen:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_gen
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.10+3"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -128,6 +443,13 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
|
stream_transform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_transform
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
string_scanner:
|
string_scanner:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -149,6 +471,20 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.19"
|
version: "0.2.19"
|
||||||
|
time:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: time
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
timing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: timing
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.1+3"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -163,6 +499,27 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.12.0-0.0 <3.0.0"
|
dart: ">=2.12.0 <3.0.0"
|
||||||
flutter: ">=0.1.4"
|
flutter: ">=1.17.0"
|
||||||
|
|
|
@ -12,10 +12,20 @@ dependencies:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
ffi: ^0.1.3
|
ffi: ^0.1.3
|
||||||
path_provider: ^1.4.0
|
path_provider: ^1.4.0
|
||||||
|
http: ^0.12.0+2
|
||||||
|
mobx: ^1.2.1+2
|
||||||
|
flutter_mobx: ^1.1.0+2
|
||||||
|
intl: ^0.17.0
|
||||||
|
cw_core:
|
||||||
|
path: ../cw_core
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
build_runner: ^1.10.3
|
||||||
|
build_resolvers: ^1.3.10
|
||||||
|
mobx_codegen: ^1.1.0+1
|
||||||
|
hive_generator: ^0.8.1
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# following page: https://dart.dev/tools/pub/pubspec
|
||||||
|
|
|
@ -196,4 +196,4 @@ SPEC CHECKSUMS:
|
||||||
|
|
||||||
PODFILE CHECKSUM: bc2591d23316907c9c90ca1cd2fce063fd866508
|
PODFILE CHECKSUM: bc2591d23316907c9c90ca1cd2fce063fd866508
|
||||||
|
|
||||||
COCOAPODS: 1.9.3
|
COCOAPODS: 1.10.2
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
|
||||||
import 'package:bs58check/bs58check.dart' as bs58check;
|
|
||||||
import 'package:bitcoin_flutter/src/utils/constants/op.dart';
|
|
||||||
import 'package:bitcoin_flutter/src/utils/script.dart' as bscript;
|
|
||||||
import 'package:bitcoin_flutter/src/address.dart';
|
|
||||||
|
|
||||||
Uint8List p2shAddressToOutputScript(String address) {
|
|
||||||
final decodeBase58 = bs58check.decode(address);
|
|
||||||
final hash = decodeBase58.sublist(1);
|
|
||||||
return bscript.compile(<dynamic>[OPS['OP_HASH160'], hash, OPS['OP_EQUAL']]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Uint8List addressToOutputScript(
|
|
||||||
String address, bitcoin.NetworkType networkType) {
|
|
||||||
try {
|
|
||||||
// FIXME: improve validation for p2sh addresses
|
|
||||||
// 3 for bitcoin
|
|
||||||
// m for litecoin
|
|
||||||
if (address.startsWith('3') || address.toLowerCase().startsWith('m')) {
|
|
||||||
return p2shAddressToOutputScript(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Address.addressToOutputScript(address, networkType);
|
|
||||||
} catch (err) {
|
|
||||||
print(err);
|
|
||||||
return Uint8List(0);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
class BitcoinAddressRecord {
|
|
||||||
BitcoinAddressRecord(this.address, {this.index, bool isHidden})
|
|
||||||
: _isHidden = isHidden;
|
|
||||||
|
|
||||||
factory BitcoinAddressRecord.fromJSON(String jsonSource) {
|
|
||||||
final decoded = json.decode(jsonSource) as Map;
|
|
||||||
|
|
||||||
return BitcoinAddressRecord(decoded['address'] as String,
|
|
||||||
index: decoded['index'] as int, isHidden: decoded['isHidden'] as bool);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object o) =>
|
|
||||||
o is BitcoinAddressRecord && address == o.address;
|
|
||||||
|
|
||||||
final String address;
|
|
||||||
bool get isHidden => _isHidden ?? false;
|
|
||||||
int index;
|
|
||||||
final bool _isHidden;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => address.hashCode;
|
|
||||||
|
|
||||||
String toJSON() =>
|
|
||||||
json.encode({'address': address, 'index': index, 'isHidden': isHidden});
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:cake_wallet/entities/crypto_amount_format.dart';
|
|
||||||
|
|
||||||
const bitcoinAmountLength = 8;
|
|
||||||
const bitcoinAmountDivider = 100000000;
|
|
||||||
final bitcoinAmountFormat = NumberFormat()
|
|
||||||
..maximumFractionDigits = bitcoinAmountLength
|
|
||||||
..minimumFractionDigits = 1;
|
|
||||||
|
|
||||||
String bitcoinAmountToString({int amount}) => bitcoinAmountFormat.format(
|
|
||||||
cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider));
|
|
||||||
|
|
||||||
double bitcoinAmountToDouble({int amount}) =>
|
|
||||||
cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider);
|
|
||||||
|
|
||||||
int stringDoubleToBitcoinAmount(String amount) {
|
|
||||||
int result = 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
result = (double.parse(amount) * bitcoinAmountDivider).toInt();
|
|
||||||
} catch (e) {
|
|
||||||
result = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
class BitcoinCommitTransactionException implements Exception {
|
|
||||||
@override
|
|
||||||
String toString() => 'Transaction commit is failed.';
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,5 +0,0 @@
|
||||||
class BitcoinMnemonicIsIncorrectException implements Exception {
|
|
||||||
@override
|
|
||||||
String toString() =>
|
|
||||||
'Bitcoin mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
|
|
||||||
import 'package:cake_wallet/view_model/send/output.dart';
|
|
||||||
|
|
||||||
class BitcoinTransactionCredentials {
|
|
||||||
BitcoinTransactionCredentials(this.outputs, this.priority);
|
|
||||||
|
|
||||||
final List<Output> outputs;
|
|
||||||
BitcoinTransactionPriority priority;
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
class BitcoinTransactionNoInputsException implements Exception {
|
|
||||||
@override
|
|
||||||
String toString() => 'Not enough inputs available';
|
|
||||||
}
|
|
|
@ -1,103 +0,0 @@
|
||||||
import 'package:cake_wallet/entities/transaction_priority.dart';
|
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
|
||||||
|
|
||||||
class BitcoinTransactionPriority extends TransactionPriority {
|
|
||||||
const BitcoinTransactionPriority({String title, int raw})
|
|
||||||
: super(title: title, raw: raw);
|
|
||||||
|
|
||||||
static const List<BitcoinTransactionPriority> all = [fast, medium, slow];
|
|
||||||
static const BitcoinTransactionPriority slow =
|
|
||||||
BitcoinTransactionPriority(title: 'Slow', raw: 0);
|
|
||||||
static const BitcoinTransactionPriority medium =
|
|
||||||
BitcoinTransactionPriority(title: 'Medium', raw: 1);
|
|
||||||
static const BitcoinTransactionPriority fast =
|
|
||||||
BitcoinTransactionPriority(title: 'Fast', raw: 2);
|
|
||||||
|
|
||||||
static BitcoinTransactionPriority deserialize({int raw}) {
|
|
||||||
switch (raw) {
|
|
||||||
case 0:
|
|
||||||
return slow;
|
|
||||||
case 1:
|
|
||||||
return medium;
|
|
||||||
case 2:
|
|
||||||
return fast;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String get units => 'sat';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
var label = '';
|
|
||||||
|
|
||||||
switch (this) {
|
|
||||||
case BitcoinTransactionPriority.slow:
|
|
||||||
label = '${S.current.transaction_priority_slow} ~24hrs';
|
|
||||||
break;
|
|
||||||
case BitcoinTransactionPriority.medium:
|
|
||||||
label = S.current.transaction_priority_medium;
|
|
||||||
break;
|
|
||||||
case BitcoinTransactionPriority.fast:
|
|
||||||
label = S.current.transaction_priority_fast;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
String labelWithRate(int rate) => '${toString()} ($rate ${units}/byte)';
|
|
||||||
}
|
|
||||||
|
|
||||||
class LitecoinTransactionPriority extends BitcoinTransactionPriority {
|
|
||||||
const LitecoinTransactionPriority({String title, int raw})
|
|
||||||
: super(title: title, raw: raw);
|
|
||||||
|
|
||||||
static const List<LitecoinTransactionPriority> all = [fast, medium, slow];
|
|
||||||
static const LitecoinTransactionPriority slow =
|
|
||||||
LitecoinTransactionPriority(title: 'Slow', raw: 0);
|
|
||||||
static const LitecoinTransactionPriority medium =
|
|
||||||
LitecoinTransactionPriority(title: 'Medium', raw: 1);
|
|
||||||
static const LitecoinTransactionPriority fast =
|
|
||||||
LitecoinTransactionPriority(title: 'Fast', raw: 2);
|
|
||||||
|
|
||||||
static LitecoinTransactionPriority deserialize({int raw}) {
|
|
||||||
switch (raw) {
|
|
||||||
case 0:
|
|
||||||
return slow;
|
|
||||||
case 1:
|
|
||||||
return medium;
|
|
||||||
case 2:
|
|
||||||
return fast;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get units => 'Latoshi';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
var label = '';
|
|
||||||
|
|
||||||
switch (this) {
|
|
||||||
case LitecoinTransactionPriority.slow:
|
|
||||||
label = S.current.transaction_priority_slow;
|
|
||||||
break;
|
|
||||||
case LitecoinTransactionPriority.medium:
|
|
||||||
label = S.current.transaction_priority_medium;
|
|
||||||
break;
|
|
||||||
case LitecoinTransactionPriority.fast:
|
|
||||||
label = S.current.transaction_priority_fast;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
|
||||||
|
|
||||||
class BitcoinTransactionWrongBalanceException implements Exception {
|
|
||||||
BitcoinTransactionWrongBalanceException(this.currency);
|
|
||||||
|
|
||||||
final CryptoCurrency currency;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'Wrong balance. Not enough ${currency.title} on your balance.';
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
|
||||||
|
|
||||||
class BitcoinUnspent {
|
|
||||||
BitcoinUnspent(this.address, this.hash, this.value, this.vout)
|
|
||||||
: isSending = true,
|
|
||||||
isFrozen = false,
|
|
||||||
note = '';
|
|
||||||
|
|
||||||
factory BitcoinUnspent.fromJSON(
|
|
||||||
BitcoinAddressRecord address, Map<String, dynamic> json) =>
|
|
||||||
BitcoinUnspent(address, json['tx_hash'] as String, json['value'] as int,
|
|
||||||
json['tx_pos'] as int);
|
|
||||||
|
|
||||||
final BitcoinAddressRecord address;
|
|
||||||
final String hash;
|
|
||||||
final int value;
|
|
||||||
final int vout;
|
|
||||||
|
|
||||||
bool get isP2wpkh =>
|
|
||||||
address.address.startsWith('bc') || address.address.startsWith('ltc');
|
|
||||||
bool isSending;
|
|
||||||
bool isFrozen;
|
|
||||||
String note;
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum_wallet_snapshot.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum_balance.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_addresses.dart';
|
|
||||||
|
|
||||||
part 'bitcoin_wallet.g.dart';
|
|
||||||
|
|
||||||
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
|
|
||||||
|
|
||||||
abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|
||||||
BitcoinWalletBase(
|
|
||||||
{@required String mnemonic,
|
|
||||||
@required String password,
|
|
||||||
@required WalletInfo walletInfo,
|
|
||||||
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
|
||||||
List<BitcoinAddressRecord> initialAddresses,
|
|
||||||
ElectrumBalance initialBalance,
|
|
||||||
int accountIndex = 0})
|
|
||||||
: super(
|
|
||||||
mnemonic: mnemonic,
|
|
||||||
password: password,
|
|
||||||
walletInfo: walletInfo,
|
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
|
||||||
networkType: bitcoin.bitcoin,
|
|
||||||
initialAddresses: initialAddresses,
|
|
||||||
initialBalance: initialBalance) {
|
|
||||||
walletAddresses = BitcoinWalletAddresses(
|
|
||||||
walletInfo,
|
|
||||||
initialAddresses: initialAddresses,
|
|
||||||
accountIndex: accountIndex,
|
|
||||||
mainHd: hd,
|
|
||||||
sideHd: bitcoin.HDWallet.fromSeed(
|
|
||||||
mnemonicToSeedBytes(mnemonic), network: networkType)
|
|
||||||
.derivePath("m/0'/1"),
|
|
||||||
networkType: networkType);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<BitcoinWallet> open({
|
|
||||||
@required String name,
|
|
||||||
@required WalletInfo walletInfo,
|
|
||||||
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
|
||||||
@required String password,
|
|
||||||
}) async {
|
|
||||||
final snp = ElectrumWallletSnapshot(name, walletInfo.type, password);
|
|
||||||
await snp.load();
|
|
||||||
return BitcoinWallet(
|
|
||||||
mnemonic: snp.mnemonic,
|
|
||||||
password: password,
|
|
||||||
walletInfo: walletInfo,
|
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
|
||||||
initialAddresses: snp.addresses,
|
|
||||||
initialBalance: snp.balance,
|
|
||||||
accountIndex: snp.accountIndex);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
|
||||||
import 'package:cake_wallet/bitcoin/utils.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum_wallet_addresses.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
|
||||||
|
|
||||||
part 'bitcoin_wallet_addresses.g.dart';
|
|
||||||
|
|
||||||
class BitcoinWalletAddresses = BitcoinWalletAddressesBase
|
|
||||||
with _$BitcoinWalletAddresses;
|
|
||||||
|
|
||||||
abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses
|
|
||||||
with Store {
|
|
||||||
BitcoinWalletAddressesBase(
|
|
||||||
WalletInfo walletInfo,
|
|
||||||
{@required List<BitcoinAddressRecord> initialAddresses,
|
|
||||||
int accountIndex = 0,
|
|
||||||
@required bitcoin.HDWallet mainHd,
|
|
||||||
@required bitcoin.HDWallet sideHd,
|
|
||||||
@required this.networkType})
|
|
||||||
: super(
|
|
||||||
walletInfo,
|
|
||||||
initialAddresses: initialAddresses,
|
|
||||||
accountIndex: accountIndex,
|
|
||||||
mainHd: mainHd,
|
|
||||||
sideHd: sideHd);
|
|
||||||
|
|
||||||
bitcoin.NetworkType networkType;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String getAddress({@required int index, @required bitcoin.HDWallet hd}) =>
|
|
||||||
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
|
||||||
|
|
||||||
class BitcoinNewWalletCredentials extends WalletCredentials {
|
|
||||||
BitcoinNewWalletCredentials({String name, WalletInfo walletInfo})
|
|
||||||
: super(name: name, walletInfo: walletInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials {
|
|
||||||
BitcoinRestoreWalletFromSeedCredentials(
|
|
||||||
{String name, String password, this.mnemonic, WalletInfo walletInfo})
|
|
||||||
: super(name: name, password: password, walletInfo: walletInfo);
|
|
||||||
|
|
||||||
final String mnemonic;
|
|
||||||
}
|
|
||||||
|
|
||||||
class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials {
|
|
||||||
BitcoinRestoreWalletFromWIFCredentials(
|
|
||||||
{String name, String password, this.wif, WalletInfo walletInfo})
|
|
||||||
: super(name: name, password: password, walletInfo: walletInfo);
|
|
||||||
|
|
||||||
final String wif;
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
class BitcoinWalletKeys {
|
|
||||||
const BitcoinWalletKeys({@required this.wif, @required this.privateKey, @required this.publicKey});
|
|
||||||
|
|
||||||
final String wif;
|
|
||||||
final String privateKey;
|
|
||||||
final String publicKey;
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
|
||||||
import 'package:cake_wallet/core/wallet_service.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
|
||||||
import 'package:cake_wallet/entities/pathForWallet.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
|
|
||||||
class BitcoinWalletService extends WalletService<
|
|
||||||
BitcoinNewWalletCredentials,
|
|
||||||
BitcoinRestoreWalletFromSeedCredentials,
|
|
||||||
BitcoinRestoreWalletFromWIFCredentials> {
|
|
||||||
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
|
||||||
|
|
||||||
final Box<WalletInfo> walletInfoSource;
|
|
||||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
|
||||||
|
|
||||||
@override
|
|
||||||
WalletType getType() => WalletType.bitcoin;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials) async {
|
|
||||||
final wallet = BitcoinWallet(
|
|
||||||
mnemonic: await generateMnemonic(),
|
|
||||||
password: credentials.password,
|
|
||||||
walletInfo: credentials.walletInfo,
|
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
|
||||||
await wallet.save();
|
|
||||||
await wallet.init();
|
|
||||||
return wallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> isWalletExit(String name) async =>
|
|
||||||
File(await pathForWallet(name: name, type: getType())).existsSync();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<BitcoinWallet> openWallet(String name, String password) async {
|
|
||||||
final walletInfo = walletInfoSource.values.firstWhere(
|
|
||||||
(info) => info.id == WalletBase.idFor(name, getType()),
|
|
||||||
orElse: () => null);
|
|
||||||
final wallet = await BitcoinWalletBase.open(
|
|
||||||
password: password, name: name, walletInfo: walletInfo,
|
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
|
||||||
await wallet.init();
|
|
||||||
return wallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> remove(String wallet) async =>
|
|
||||||
File(await pathForWalletDir(name: wallet, type: WalletType.bitcoin))
|
|
||||||
.delete(recursive: true);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<BitcoinWallet> restoreFromKeys(
|
|
||||||
BitcoinRestoreWalletFromWIFCredentials credentials) async =>
|
|
||||||
throw UnimplementedError();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<BitcoinWallet> restoreFromSeed(
|
|
||||||
BitcoinRestoreWalletFromSeedCredentials credentials) async {
|
|
||||||
if (!validateMnemonic(credentials.mnemonic)) {
|
|
||||||
throw BitcoinMnemonicIsIncorrectException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final wallet = BitcoinWallet(
|
|
||||||
password: credentials.password,
|
|
||||||
mnemonic: credentials.mnemonic,
|
|
||||||
walletInfo: credentials.walletInfo,
|
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
|
||||||
await wallet.save();
|
|
||||||
await wallet.init();
|
|
||||||
return wallet;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,444 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/script_hash.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:rxdart/rxdart.dart';
|
|
||||||
|
|
||||||
String jsonrpcparams(List<Object> params) {
|
|
||||||
final _params = params?.map((val) => '"${val.toString()}"')?.join(',');
|
|
||||||
return '[$_params]';
|
|
||||||
}
|
|
||||||
|
|
||||||
String jsonrpc(
|
|
||||||
{String method, List<Object> params, int id, double version = 2.0}) =>
|
|
||||||
'{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n';
|
|
||||||
|
|
||||||
class SocketTask {
|
|
||||||
SocketTask({this.completer, this.isSubscription, this.subject});
|
|
||||||
|
|
||||||
final Completer completer;
|
|
||||||
final BehaviorSubject subject;
|
|
||||||
final bool isSubscription;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ElectrumClient {
|
|
||||||
ElectrumClient()
|
|
||||||
: _id = 0,
|
|
||||||
_isConnected = false,
|
|
||||||
_tasks = {},
|
|
||||||
unterminatedString = '';
|
|
||||||
|
|
||||||
static const connectionTimeout = Duration(seconds: 5);
|
|
||||||
static const aliveTimerDuration = Duration(seconds: 2);
|
|
||||||
|
|
||||||
bool get isConnected => _isConnected;
|
|
||||||
Socket socket;
|
|
||||||
void Function(bool) onConnectionStatusChange;
|
|
||||||
int _id;
|
|
||||||
final Map<String, SocketTask> _tasks;
|
|
||||||
bool _isConnected;
|
|
||||||
Timer _aliveTimer;
|
|
||||||
String unterminatedString;
|
|
||||||
|
|
||||||
Future<void> connectToUri(Uri uri) async =>
|
|
||||||
await connect(host: uri.host, port: uri.port);
|
|
||||||
|
|
||||||
Future<void> connect({@required String host, @required int port}) async {
|
|
||||||
try {
|
|
||||||
await socket?.close();
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
socket = await SecureSocket.connect(host, port,
|
|
||||||
timeout: connectionTimeout, onBadCertificate: (_) => true);
|
|
||||||
_setIsConnected(true);
|
|
||||||
|
|
||||||
socket.listen((Uint8List event) {
|
|
||||||
try {
|
|
||||||
final response =
|
|
||||||
json.decode(utf8.decode(event.toList())) as Map<String, Object>;
|
|
||||||
_handleResponse(response);
|
|
||||||
} on FormatException catch (e) {
|
|
||||||
final msg = e.message.toLowerCase();
|
|
||||||
|
|
||||||
if (e.source is String) {
|
|
||||||
unterminatedString += e.source as String;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msg.contains("not a subtype of type")) {
|
|
||||||
unterminatedString += e.source as String;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isJSONStringCorrect(unterminatedString)) {
|
|
||||||
final response =
|
|
||||||
json.decode(unterminatedString) as Map<String, Object>;
|
|
||||||
_handleResponse(response);
|
|
||||||
unterminatedString = '';
|
|
||||||
}
|
|
||||||
} on TypeError catch (e) {
|
|
||||||
if (!e.toString().contains('Map<String, Object>')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final source = utf8.decode(event.toList());
|
|
||||||
unterminatedString += source;
|
|
||||||
|
|
||||||
if (isJSONStringCorrect(unterminatedString)) {
|
|
||||||
final response =
|
|
||||||
json.decode(unterminatedString) as Map<String, Object>;
|
|
||||||
_handleResponse(response);
|
|
||||||
unterminatedString = null;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print(e.toString());
|
|
||||||
}
|
|
||||||
}, onError: (Object error) {
|
|
||||||
print(error.toString());
|
|
||||||
_setIsConnected(false);
|
|
||||||
}, onDone: () {
|
|
||||||
_setIsConnected(false);
|
|
||||||
});
|
|
||||||
keepAlive();
|
|
||||||
}
|
|
||||||
|
|
||||||
void keepAlive() {
|
|
||||||
_aliveTimer?.cancel();
|
|
||||||
_aliveTimer = Timer.periodic(aliveTimerDuration, (_) async => ping());
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> ping() async {
|
|
||||||
try {
|
|
||||||
await callWithTimeout(method: 'server.ping');
|
|
||||||
_setIsConnected(true);
|
|
||||||
} on RequestFailedTimeoutException catch (_) {
|
|
||||||
_setIsConnected(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<String>> version() =>
|
|
||||||
call(method: 'server.version').then((dynamic result) {
|
|
||||||
if (result is List) {
|
|
||||||
return result.map((dynamic val) => val.toString()).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<Map<String, Object>> getBalance(String scriptHash) =>
|
|
||||||
call(method: 'blockchain.scripthash.get_balance', params: [scriptHash])
|
|
||||||
.then((dynamic result) {
|
|
||||||
if (result is Map<String, Object>) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <String, Object>{};
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<List<Map<String, dynamic>>> getHistory(String scriptHash) =>
|
|
||||||
call(method: 'blockchain.scripthash.get_history', params: [scriptHash])
|
|
||||||
.then((dynamic result) {
|
|
||||||
if (result is List) {
|
|
||||||
return result.map((dynamic val) {
|
|
||||||
if (val is Map<String, Object>) {
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <String, Object>{};
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<List<Map<String, dynamic>>> getListUnspentWithAddress(
|
|
||||||
String address, NetworkType networkType) =>
|
|
||||||
call(
|
|
||||||
method: 'blockchain.scripthash.listunspent',
|
|
||||||
params: [scriptHash(address, networkType: networkType)])
|
|
||||||
.then((dynamic result) {
|
|
||||||
if (result is List) {
|
|
||||||
return result.map((dynamic val) {
|
|
||||||
if (val is Map<String, Object>) {
|
|
||||||
val['address'] = address;
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <String, Object>{};
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<List<Map<String, dynamic>>> getListUnspent(String scriptHash) =>
|
|
||||||
call(method: 'blockchain.scripthash.listunspent', params: [scriptHash])
|
|
||||||
.then((dynamic result) {
|
|
||||||
if (result is List) {
|
|
||||||
return result.map((dynamic val) {
|
|
||||||
if (val is Map<String, Object>) {
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <String, Object>{};
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<List<Map<String, dynamic>>> getMempool(String scriptHash) =>
|
|
||||||
call(method: 'blockchain.scripthash.get_mempool', params: [scriptHash])
|
|
||||||
.then((dynamic result) {
|
|
||||||
if (result is List) {
|
|
||||||
return result.map((dynamic val) {
|
|
||||||
if (val is Map<String, Object>) {
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <String, Object>{};
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<Map<String, Object>> getTransactionRaw(
|
|
||||||
{@required String hash}) async =>
|
|
||||||
call(method: 'blockchain.transaction.get', params: [hash, true])
|
|
||||||
.then((dynamic result) {
|
|
||||||
if (result is Map<String, Object>) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <String, Object>{};
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<Map<String, Object>> getTransactionExpanded(
|
|
||||||
{@required String hash}) async {
|
|
||||||
try {
|
|
||||||
final originalTx = await getTransactionRaw(hash: hash);
|
|
||||||
final vins = originalTx['vin'] as List<Object>;
|
|
||||||
|
|
||||||
for (dynamic vin in vins) {
|
|
||||||
if (vin is Map<String, Object>) {
|
|
||||||
vin['tx'] = await getTransactionRaw(hash: vin['txid'] as String);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return originalTx;
|
|
||||||
} catch (_) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> broadcastTransaction(
|
|
||||||
{@required String transactionRaw}) async =>
|
|
||||||
call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
|
|
||||||
.then((dynamic result) {
|
|
||||||
if (result is String) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<Map<String, dynamic>> getMerkle(
|
|
||||||
{@required String hash, @required int height}) async =>
|
|
||||||
await call(
|
|
||||||
method: 'blockchain.transaction.get_merkle',
|
|
||||||
params: [hash, height]) as Map<String, dynamic>;
|
|
||||||
|
|
||||||
Future<Map<String, dynamic>> getHeader({@required int height}) async =>
|
|
||||||
await call(method: 'blockchain.block.get_header', params: [height])
|
|
||||||
as Map<String, dynamic>;
|
|
||||||
|
|
||||||
Future<double> estimatefee({@required int p}) =>
|
|
||||||
call(method: 'blockchain.estimatefee', params: [p])
|
|
||||||
.then((dynamic result) {
|
|
||||||
if (result is double) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result is String) {
|
|
||||||
return double.parse(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<List<List<int>>> feeHistogram() =>
|
|
||||||
call(method: 'mempool.get_fee_histogram').then((dynamic result) {
|
|
||||||
if (result is List) {
|
|
||||||
return result.map((dynamic e) {
|
|
||||||
if (e is List) {
|
|
||||||
return e.map((dynamic ee) => ee is int ? ee : null).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<List<int>> feeRates() async {
|
|
||||||
try {
|
|
||||||
final topDoubleString = await estimatefee(p: 1);
|
|
||||||
final middleDoubleString = await estimatefee(p: 20);
|
|
||||||
final bottomDoubleString = await estimatefee(p: 100);
|
|
||||||
final top =
|
|
||||||
(stringDoubleToBitcoinAmount(topDoubleString.toString()) / 1000)
|
|
||||||
.round();
|
|
||||||
final middle =
|
|
||||||
(stringDoubleToBitcoinAmount(middleDoubleString.toString()) / 1000)
|
|
||||||
.round();
|
|
||||||
final bottom =
|
|
||||||
(stringDoubleToBitcoinAmount(bottomDoubleString.toString()) / 1000)
|
|
||||||
.round();
|
|
||||||
|
|
||||||
return [bottom, middle, top];
|
|
||||||
} catch (_) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BehaviorSubject<Object> scripthashUpdate(String scripthash) {
|
|
||||||
_id += 1;
|
|
||||||
return subscribe<Object>(
|
|
||||||
id: 'blockchain.scripthash.subscribe:$scripthash',
|
|
||||||
method: 'blockchain.scripthash.subscribe',
|
|
||||||
params: [scripthash]);
|
|
||||||
}
|
|
||||||
|
|
||||||
BehaviorSubject<T> subscribe<T>(
|
|
||||||
{@required String id,
|
|
||||||
@required String method,
|
|
||||||
List<Object> params = const []}) {
|
|
||||||
try {
|
|
||||||
final subscription = BehaviorSubject<T>();
|
|
||||||
_regisrySubscription(id, subscription);
|
|
||||||
socket.write(jsonrpc(method: method, id: _id, params: params));
|
|
||||||
|
|
||||||
return subscription;
|
|
||||||
} catch(e) {
|
|
||||||
print(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> call({String method, List<Object> params = const []}) async {
|
|
||||||
final completer = Completer<dynamic>();
|
|
||||||
_id += 1;
|
|
||||||
final id = _id;
|
|
||||||
_registryTask(id, completer);
|
|
||||||
socket.write(jsonrpc(method: method, id: id, params: params));
|
|
||||||
|
|
||||||
return completer.future;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<dynamic> callWithTimeout(
|
|
||||||
{String method,
|
|
||||||
List<Object> params = const [],
|
|
||||||
int timeout = 2000}) async {
|
|
||||||
try {
|
|
||||||
final completer = Completer<dynamic>();
|
|
||||||
_id += 1;
|
|
||||||
final id = _id;
|
|
||||||
_registryTask(id, completer);
|
|
||||||
socket.write(jsonrpc(method: method, id: id, params: params));
|
|
||||||
Timer(Duration(milliseconds: timeout), () {
|
|
||||||
if (!completer.isCompleted) {
|
|
||||||
completer.completeError(RequestFailedTimeoutException(method, id));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return completer.future;
|
|
||||||
} catch(e) {
|
|
||||||
print(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> close() async {
|
|
||||||
_aliveTimer.cancel();
|
|
||||||
await socket.close();
|
|
||||||
onConnectionStatusChange = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _registryTask(int id, Completer completer) => _tasks[id.toString()] =
|
|
||||||
SocketTask(completer: completer, isSubscription: false);
|
|
||||||
|
|
||||||
void _regisrySubscription(String id, BehaviorSubject subject) =>
|
|
||||||
_tasks[id] = SocketTask(subject: subject, isSubscription: true);
|
|
||||||
|
|
||||||
void _finish(String id, Object data) {
|
|
||||||
if (_tasks[id] == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(_tasks[id]?.completer?.isCompleted ?? false)) {
|
|
||||||
_tasks[id]?.completer?.complete(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(_tasks[id]?.isSubscription ?? false)) {
|
|
||||||
_tasks[id] = null;
|
|
||||||
} else {
|
|
||||||
_tasks[id].subject.add(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _methodHandler(
|
|
||||||
{@required String method, @required Map<String, Object> request}) {
|
|
||||||
switch (method) {
|
|
||||||
case 'blockchain.scripthash.subscribe':
|
|
||||||
final params = request['params'] as List<dynamic>;
|
|
||||||
final scripthash = params.first as String;
|
|
||||||
final id = 'blockchain.scripthash.subscribe:$scripthash';
|
|
||||||
|
|
||||||
_tasks[id]?.subject?.add(params.last);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _setIsConnected(bool isConnected) {
|
|
||||||
if (_isConnected != isConnected) {
|
|
||||||
onConnectionStatusChange?.call(isConnected);
|
|
||||||
}
|
|
||||||
|
|
||||||
_isConnected = isConnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleResponse(Map<String, Object> response) {
|
|
||||||
final method = response['method'];
|
|
||||||
final id = response['id'] as String;
|
|
||||||
final result = response['result'];
|
|
||||||
|
|
||||||
if (method is String) {
|
|
||||||
_methodHandler(method: method, request: response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_finish(id, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: move me
|
|
||||||
bool isJSONStringCorrect(String source) {
|
|
||||||
try {
|
|
||||||
json.decode(source);
|
|
||||||
return true;
|
|
||||||
} catch (_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RequestFailedTimeoutException implements Exception {
|
|
||||||
RequestFailedTimeoutException(this.method, this.id);
|
|
||||||
|
|
||||||
final String method;
|
|
||||||
final int id;
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
|
||||||
import 'package:cake_wallet/entities/balance.dart';
|
|
||||||
|
|
||||||
class ElectrumBalance extends Balance {
|
|
||||||
const ElectrumBalance({@required this.confirmed, @required this.unconfirmed})
|
|
||||||
: super(confirmed, unconfirmed);
|
|
||||||
|
|
||||||
factory ElectrumBalance.fromJSON(String jsonSource) {
|
|
||||||
if (jsonSource == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final decoded = json.decode(jsonSource) as Map;
|
|
||||||
|
|
||||||
return ElectrumBalance(
|
|
||||||
confirmed: decoded['confirmed'] as int ?? 0,
|
|
||||||
unconfirmed: decoded['unconfirmed'] as int ?? 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
final int confirmed;
|
|
||||||
final int unconfirmed;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get formattedAvailableBalance =>
|
|
||||||
bitcoinAmountToString(amount: confirmed);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get formattedAdditionalBalance =>
|
|
||||||
bitcoinAmountToString(amount: unconfirmed);
|
|
||||||
|
|
||||||
String toJSON() =>
|
|
||||||
json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed});
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:cake_wallet/entities/pathForWallet.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
|
||||||
import 'package:cake_wallet/core/transaction_history.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/file.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart';
|
|
||||||
|
|
||||||
part 'electrum_transaction_history.g.dart';
|
|
||||||
|
|
||||||
const _transactionsHistoryFileName = 'transactions.json';
|
|
||||||
|
|
||||||
class ElectrumTransactionHistory = ElectrumTransactionHistoryBase
|
|
||||||
with _$ElectrumTransactionHistory;
|
|
||||||
|
|
||||||
abstract class ElectrumTransactionHistoryBase
|
|
||||||
extends TransactionHistoryBase<ElectrumTransactionInfo> with Store {
|
|
||||||
ElectrumTransactionHistoryBase(
|
|
||||||
{@required this.walletInfo, @required String password})
|
|
||||||
: _password = password,
|
|
||||||
_height = 0 {
|
|
||||||
transactions = ObservableMap<String, ElectrumTransactionInfo>();
|
|
||||||
}
|
|
||||||
|
|
||||||
final WalletInfo walletInfo;
|
|
||||||
final String _password;
|
|
||||||
int _height;
|
|
||||||
|
|
||||||
Future<void> init() async => await _load();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void addOne(ElectrumTransactionInfo transaction) =>
|
|
||||||
transactions[transaction.id] = transaction;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void addMany(Map<String, ElectrumTransactionInfo> transactions) =>
|
|
||||||
transactions.forEach((_, tx) => _updateOrInsert(tx));
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> save() async {
|
|
||||||
try {
|
|
||||||
final dirPath =
|
|
||||||
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
|
||||||
final path = '$dirPath/$_transactionsHistoryFileName';
|
|
||||||
final data =
|
|
||||||
json.encode({'height': _height, 'transactions': transactions});
|
|
||||||
await writeData(path: path, password: _password, data: data);
|
|
||||||
} catch (e) {
|
|
||||||
print('Error while save bitcoin transaction history: ${e.toString()}');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Map<String, Object>> _read() async {
|
|
||||||
final dirPath =
|
|
||||||
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
|
||||||
final path = '$dirPath/$_transactionsHistoryFileName';
|
|
||||||
final content = await read(path: path, password: _password);
|
|
||||||
return json.decode(content) as Map<String, Object>;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _load() async {
|
|
||||||
try {
|
|
||||||
final content = await _read();
|
|
||||||
final txs = content['transactions'] as Map<String, Object> ?? {};
|
|
||||||
|
|
||||||
txs.entries.forEach((entry) {
|
|
||||||
final val = entry.value;
|
|
||||||
|
|
||||||
if (val is Map<String, Object>) {
|
|
||||||
final tx = ElectrumTransactionInfo.fromJson(val, walletInfo.type);
|
|
||||||
_updateOrInsert(tx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
_height = content['height'] as int;
|
|
||||||
} catch (e) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateOrInsert(ElectrumTransactionInfo transaction) {
|
|
||||||
if (transaction.id == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transactions[transaction.id] == null) {
|
|
||||||
transactions[transaction.id] = transaction;
|
|
||||||
} else {
|
|
||||||
final originalTx = transactions[transaction.id];
|
|
||||||
originalTx.confirmations = transaction.confirmations;
|
|
||||||
originalTx.amount = transaction.amount;
|
|
||||||
originalTx.height = transaction.height;
|
|
||||||
originalTx.date ??= transaction.date;
|
|
||||||
originalTx.isPending = transaction.isPending;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,178 +0,0 @@
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
|
||||||
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
|
||||||
import 'package:cake_wallet/entities/transaction_direction.dart';
|
|
||||||
import 'package:cake_wallet/entities/transaction_info.dart';
|
|
||||||
import 'package:cake_wallet/entities/format_amount.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
|
||||||
|
|
||||||
class ElectrumTransactionInfo extends TransactionInfo {
|
|
||||||
ElectrumTransactionInfo(this.type,
|
|
||||||
{@required String id,
|
|
||||||
@required int height,
|
|
||||||
@required int amount,
|
|
||||||
@required int fee,
|
|
||||||
@required TransactionDirection direction,
|
|
||||||
@required bool isPending,
|
|
||||||
@required DateTime date,
|
|
||||||
@required int confirmations}) {
|
|
||||||
this.id = id;
|
|
||||||
this.height = height;
|
|
||||||
this.amount = amount;
|
|
||||||
this.fee = fee;
|
|
||||||
this.direction = direction;
|
|
||||||
this.date = date;
|
|
||||||
this.isPending = isPending;
|
|
||||||
this.confirmations = confirmations;
|
|
||||||
}
|
|
||||||
|
|
||||||
factory ElectrumTransactionInfo.fromElectrumVerbose(
|
|
||||||
Map<String, Object> obj, WalletType type,
|
|
||||||
{@required List<BitcoinAddressRecord> addresses, @required int height}) {
|
|
||||||
final addressesSet = addresses.map((addr) => addr.address).toSet();
|
|
||||||
final id = obj['txid'] as String;
|
|
||||||
final vins = obj['vin'] as List<Object> ?? [];
|
|
||||||
final vout = (obj['vout'] as List<Object> ?? []);
|
|
||||||
final date = obj['time'] is int
|
|
||||||
? DateTime.fromMillisecondsSinceEpoch((obj['time'] as int) * 1000)
|
|
||||||
: DateTime.now();
|
|
||||||
final confirmations = obj['confirmations'] as int ?? 0;
|
|
||||||
var direction = TransactionDirection.incoming;
|
|
||||||
var inputsAmount = 0;
|
|
||||||
var amount = 0;
|
|
||||||
var totalOutAmount = 0;
|
|
||||||
|
|
||||||
for (dynamic vin in vins) {
|
|
||||||
final vout = vin['vout'] as int;
|
|
||||||
final out = vin['tx']['vout'][vout] as Map;
|
|
||||||
final outAddresses =
|
|
||||||
(out['scriptPubKey']['addresses'] as List<Object>)?.toSet();
|
|
||||||
inputsAmount +=
|
|
||||||
stringDoubleToBitcoinAmount((out['value'] as double ?? 0).toString());
|
|
||||||
|
|
||||||
if (outAddresses?.intersection(addressesSet)?.isNotEmpty ?? false) {
|
|
||||||
direction = TransactionDirection.outgoing;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (dynamic out in vout) {
|
|
||||||
final outAddresses =
|
|
||||||
out['scriptPubKey']['addresses'] as List<Object> ?? [];
|
|
||||||
final ntrs = outAddresses.toSet().intersection(addressesSet);
|
|
||||||
final value = stringDoubleToBitcoinAmount(
|
|
||||||
(out['value'] as double ?? 0.0).toString());
|
|
||||||
totalOutAmount += value;
|
|
||||||
|
|
||||||
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||
|
|
||||||
(direction == TransactionDirection.outgoing && ntrs.isEmpty)) {
|
|
||||||
amount += value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final fee = inputsAmount - totalOutAmount;
|
|
||||||
|
|
||||||
return ElectrumTransactionInfo(type,
|
|
||||||
id: id,
|
|
||||||
height: height,
|
|
||||||
isPending: false,
|
|
||||||
fee: fee,
|
|
||||||
direction: direction,
|
|
||||||
amount: amount,
|
|
||||||
date: date,
|
|
||||||
confirmations: confirmations);
|
|
||||||
}
|
|
||||||
|
|
||||||
factory ElectrumTransactionInfo.fromHexAndHeader(WalletType type, String hex,
|
|
||||||
{List<String> addresses, int height, int timestamp, int confirmations}) {
|
|
||||||
final tx = bitcoin.Transaction.fromHex(hex);
|
|
||||||
var exist = false;
|
|
||||||
var amount = 0;
|
|
||||||
|
|
||||||
if (addresses != null) {
|
|
||||||
tx.outs.forEach((out) {
|
|
||||||
try {
|
|
||||||
final p2pkh = bitcoin.P2PKH(
|
|
||||||
data: PaymentData(output: out.script), network: bitcoin.bitcoin);
|
|
||||||
exist = addresses.contains(p2pkh.data.address);
|
|
||||||
|
|
||||||
if (exist) {
|
|
||||||
amount += out.value;
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
final date = timestamp != null
|
|
||||||
? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000)
|
|
||||||
: DateTime.now();
|
|
||||||
|
|
||||||
return ElectrumTransactionInfo(type,
|
|
||||||
id: tx.getId(),
|
|
||||||
height: height,
|
|
||||||
isPending: false,
|
|
||||||
fee: null,
|
|
||||||
direction: TransactionDirection.incoming,
|
|
||||||
amount: amount,
|
|
||||||
date: date,
|
|
||||||
confirmations: confirmations);
|
|
||||||
}
|
|
||||||
|
|
||||||
factory ElectrumTransactionInfo.fromJson(
|
|
||||||
Map<String, dynamic> data, WalletType type) {
|
|
||||||
return ElectrumTransactionInfo(type,
|
|
||||||
id: data['id'] as String,
|
|
||||||
height: data['height'] as int,
|
|
||||||
amount: data['amount'] as int,
|
|
||||||
fee: data['fee'] as int,
|
|
||||||
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
|
||||||
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
|
|
||||||
isPending: data['isPending'] as bool,
|
|
||||||
confirmations: data['confirmations'] as int);
|
|
||||||
}
|
|
||||||
|
|
||||||
final WalletType type;
|
|
||||||
|
|
||||||
String _fiatAmount;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String amountFormatted() =>
|
|
||||||
'${formatAmount(bitcoinAmountToString(amount: amount))} ${walletTypeToCryptoCurrency(type).title}';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String feeFormatted() => fee != null
|
|
||||||
? '${formatAmount(bitcoinAmountToString(amount: fee))} ${walletTypeToCryptoCurrency(type).title}'
|
|
||||||
: '';
|
|
||||||
|
|
||||||
@override
|
|
||||||
String fiatAmount() => _fiatAmount ?? '';
|
|
||||||
|
|
||||||
@override
|
|
||||||
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
|
||||||
|
|
||||||
ElectrumTransactionInfo updated(ElectrumTransactionInfo info) {
|
|
||||||
return ElectrumTransactionInfo(info.type,
|
|
||||||
id: id,
|
|
||||||
height: info.height,
|
|
||||||
amount: info.amount,
|
|
||||||
fee: info.fee,
|
|
||||||
direction: direction ?? info.direction,
|
|
||||||
date: date ?? info.date,
|
|
||||||
isPending: isPending ?? info.isPending,
|
|
||||||
confirmations: info.confirmations);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final m = <String, dynamic>{};
|
|
||||||
m['id'] = id;
|
|
||||||
m['height'] = height;
|
|
||||||
m['amount'] = amount;
|
|
||||||
m['direction'] = direction.index;
|
|
||||||
m['date'] = date.millisecondsSinceEpoch;
|
|
||||||
m['isPending'] = isPending;
|
|
||||||
m['confirmations'] = confirmations;
|
|
||||||
m['fee'] = fee;
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,548 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum_wallet_addresses.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
|
||||||
import 'package:rxdart/subjects.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart';
|
|
||||||
import 'package:cake_wallet/entities/pathForWallet.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/address_to_output_script.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum_balance.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_credentials.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum_transaction_history.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_no_inputs_exception.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_wrong_balance_exception.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_unspent.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_keys.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/file.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/pending_bitcoin_transaction.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/script_hash.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/utils.dart';
|
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
|
||||||
import 'package:cake_wallet/entities/node.dart';
|
|
||||||
import 'package:cake_wallet/entities/sync_status.dart';
|
|
||||||
import 'package:cake_wallet/entities/transaction_priority.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum.dart';
|
|
||||||
|
|
||||||
part 'electrum_wallet.g.dart';
|
|
||||||
|
|
||||||
class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet;
|
|
||||||
|
|
||||||
abstract class ElectrumWalletBase extends WalletBase<ElectrumBalance,
|
|
||||||
ElectrumTransactionHistory, ElectrumTransactionInfo> with Store {
|
|
||||||
ElectrumWalletBase(
|
|
||||||
{@required String password,
|
|
||||||
@required WalletInfo walletInfo,
|
|
||||||
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
|
||||||
@required List<BitcoinAddressRecord> initialAddresses,
|
|
||||||
@required this.networkType,
|
|
||||||
@required this.mnemonic,
|
|
||||||
ElectrumClient electrumClient,
|
|
||||||
ElectrumBalance initialBalance})
|
|
||||||
: balance = initialBalance ??
|
|
||||||
const ElectrumBalance(confirmed: 0, unconfirmed: 0),
|
|
||||||
hd = bitcoin.HDWallet.fromSeed(mnemonicToSeedBytes(mnemonic),
|
|
||||||
network: networkType)
|
|
||||||
.derivePath("m/0'/0"),
|
|
||||||
syncStatus = NotConnectedSyncStatus(),
|
|
||||||
_password = password,
|
|
||||||
_feeRates = <int>[],
|
|
||||||
_isTransactionUpdating = false,
|
|
||||||
super(walletInfo) {
|
|
||||||
this.electrumClient = electrumClient ?? ElectrumClient();
|
|
||||||
this.walletInfo = walletInfo;
|
|
||||||
this.unspentCoinsInfo = unspentCoinsInfo;
|
|
||||||
transactionHistory =
|
|
||||||
ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
|
|
||||||
unspentCoins = [];
|
|
||||||
_scripthashesUpdateSubject = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
|
|
||||||
inputsCount * 146 + outputsCounts * 33 + 8;
|
|
||||||
|
|
||||||
final bitcoin.HDWallet hd;
|
|
||||||
final String mnemonic;
|
|
||||||
|
|
||||||
ElectrumClient electrumClient;
|
|
||||||
Box<UnspentCoinsInfo> unspentCoinsInfo;
|
|
||||||
|
|
||||||
@override
|
|
||||||
ElectrumWalletAddresses walletAddresses;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@observable
|
|
||||||
ElectrumBalance balance;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@observable
|
|
||||||
SyncStatus syncStatus;
|
|
||||||
|
|
||||||
List<String> get scriptHashes => walletAddresses.addresses
|
|
||||||
.map((addr) => scriptHash(addr.address, networkType: networkType))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
List<String> get publicScriptHashes => walletAddresses.addresses
|
|
||||||
.where((addr) => !addr.isHidden)
|
|
||||||
.map((addr) => scriptHash(addr.address, networkType: networkType))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
String get xpub => hd.base58;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get seed => mnemonic;
|
|
||||||
|
|
||||||
bitcoin.NetworkType networkType;
|
|
||||||
|
|
||||||
@override
|
|
||||||
BitcoinWalletKeys get keys => BitcoinWalletKeys(
|
|
||||||
wif: hd.wif, privateKey: hd.privKey, publicKey: hd.pubKey);
|
|
||||||
|
|
||||||
final String _password;
|
|
||||||
List<BitcoinUnspent> unspentCoins;
|
|
||||||
List<int> _feeRates;
|
|
||||||
Map<String, BehaviorSubject<Object>> _scripthashesUpdateSubject;
|
|
||||||
bool _isTransactionUpdating;
|
|
||||||
|
|
||||||
Future<void> init() async {
|
|
||||||
await walletAddresses.init();
|
|
||||||
await transactionHistory.init();
|
|
||||||
await save();
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
@override
|
|
||||||
Future<void> startSync() async {
|
|
||||||
try {
|
|
||||||
syncStatus = StartingSyncStatus();
|
|
||||||
await updateTransactions();
|
|
||||||
_subscribeForUpdates();
|
|
||||||
await _updateBalance();
|
|
||||||
await updateUnspent();
|
|
||||||
_feeRates = await electrumClient.feeRates();
|
|
||||||
|
|
||||||
Timer.periodic(const Duration(minutes: 1),
|
|
||||||
(timer) async => _feeRates = await electrumClient.feeRates());
|
|
||||||
|
|
||||||
syncStatus = SyncedSyncStatus();
|
|
||||||
} catch (e) {
|
|
||||||
print(e.toString());
|
|
||||||
syncStatus = FailedSyncStatus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
@override
|
|
||||||
Future<void> connectToNode({@required Node node}) async {
|
|
||||||
try {
|
|
||||||
syncStatus = ConnectingSyncStatus();
|
|
||||||
await electrumClient.connectToUri(node.uri);
|
|
||||||
electrumClient.onConnectionStatusChange = (bool isConnected) {
|
|
||||||
if (!isConnected) {
|
|
||||||
syncStatus = LostConnectionSyncStatus();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
syncStatus = ConnectedSyncStatus();
|
|
||||||
} catch (e) {
|
|
||||||
print(e.toString());
|
|
||||||
syncStatus = FailedSyncStatus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<PendingBitcoinTransaction> createTransaction(
|
|
||||||
Object credentials) async {
|
|
||||||
const minAmount = 546;
|
|
||||||
final transactionCredentials = credentials as BitcoinTransactionCredentials;
|
|
||||||
final inputs = <BitcoinUnspent>[];
|
|
||||||
final outputs = transactionCredentials.outputs;
|
|
||||||
final hasMultiDestination = outputs.length > 1;
|
|
||||||
var allInputsAmount = 0;
|
|
||||||
|
|
||||||
if (unspentCoins.isEmpty) {
|
|
||||||
await updateUnspent();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (final utx in unspentCoins) {
|
|
||||||
if (utx.isSending) {
|
|
||||||
allInputsAmount += utx.value;
|
|
||||||
inputs.add(utx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inputs.isEmpty) {
|
|
||||||
throw BitcoinTransactionNoInputsException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final allAmountFee = feeAmountForPriority(
|
|
||||||
transactionCredentials.priority, inputs.length, outputs.length);
|
|
||||||
final allAmount = allInputsAmount - allAmountFee;
|
|
||||||
|
|
||||||
var credentialsAmount = 0;
|
|
||||||
var amount = 0;
|
|
||||||
var fee = 0;
|
|
||||||
|
|
||||||
if (hasMultiDestination) {
|
|
||||||
if (outputs.any((item) => item.sendAll
|
|
||||||
|| item.formattedCryptoAmount <= 0)) {
|
|
||||||
throw BitcoinTransactionWrongBalanceException(currency);
|
|
||||||
}
|
|
||||||
|
|
||||||
credentialsAmount = outputs.fold(0, (acc, value) =>
|
|
||||||
acc + value.formattedCryptoAmount);
|
|
||||||
|
|
||||||
if (allAmount - credentialsAmount < minAmount) {
|
|
||||||
throw BitcoinTransactionWrongBalanceException(currency);
|
|
||||||
}
|
|
||||||
|
|
||||||
amount = credentialsAmount;
|
|
||||||
|
|
||||||
fee = calculateEstimatedFee(transactionCredentials.priority, amount,
|
|
||||||
outputsCount: outputs.length + 1);
|
|
||||||
} else {
|
|
||||||
final output = outputs.first;
|
|
||||||
|
|
||||||
credentialsAmount = !output.sendAll
|
|
||||||
? output.formattedCryptoAmount
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
if (credentialsAmount > allAmount) {
|
|
||||||
throw BitcoinTransactionWrongBalanceException(currency);
|
|
||||||
}
|
|
||||||
|
|
||||||
amount = output.sendAll || allAmount - credentialsAmount < minAmount
|
|
||||||
? allAmount
|
|
||||||
: credentialsAmount;
|
|
||||||
|
|
||||||
fee = output.sendAll || amount == allAmount
|
|
||||||
? allAmountFee
|
|
||||||
: calculateEstimatedFee(transactionCredentials.priority, amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fee == 0) {
|
|
||||||
throw BitcoinTransactionWrongBalanceException(currency);
|
|
||||||
}
|
|
||||||
|
|
||||||
final totalAmount = amount + fee;
|
|
||||||
|
|
||||||
if (totalAmount > balance.confirmed || totalAmount > allInputsAmount) {
|
|
||||||
throw BitcoinTransactionWrongBalanceException(currency);
|
|
||||||
}
|
|
||||||
|
|
||||||
final txb = bitcoin.TransactionBuilder(network: networkType);
|
|
||||||
final changeAddress = walletAddresses.address;
|
|
||||||
var leftAmount = totalAmount;
|
|
||||||
var totalInputAmount = 0;
|
|
||||||
|
|
||||||
inputs.clear();
|
|
||||||
|
|
||||||
for (final utx in unspentCoins) {
|
|
||||||
if (utx.isSending) {
|
|
||||||
leftAmount = leftAmount - utx.value;
|
|
||||||
totalInputAmount += utx.value;
|
|
||||||
inputs.add(utx);
|
|
||||||
|
|
||||||
if (leftAmount <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inputs.isEmpty) {
|
|
||||||
throw BitcoinTransactionNoInputsException();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (amount <= 0 || totalInputAmount < totalAmount) {
|
|
||||||
throw BitcoinTransactionWrongBalanceException(currency);
|
|
||||||
}
|
|
||||||
|
|
||||||
txb.setVersion(1);
|
|
||||||
|
|
||||||
inputs.forEach((input) {
|
|
||||||
if (input.isP2wpkh) {
|
|
||||||
final p2wpkh = bitcoin
|
|
||||||
.P2WPKH(
|
|
||||||
data: generatePaymentData(hd: hd, index: input.address.index),
|
|
||||||
network: networkType)
|
|
||||||
.data;
|
|
||||||
|
|
||||||
txb.addInput(input.hash, input.vout, null, p2wpkh.output);
|
|
||||||
} else {
|
|
||||||
txb.addInput(input.hash, input.vout);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
outputs.forEach((item) {
|
|
||||||
final outputAmount = hasMultiDestination
|
|
||||||
? item.formattedCryptoAmount
|
|
||||||
: amount;
|
|
||||||
|
|
||||||
final outputAddress = item.isParsedAddress
|
|
||||||
? item.extractedAddress
|
|
||||||
: item.address;
|
|
||||||
|
|
||||||
txb.addOutput(
|
|
||||||
addressToOutputScript(outputAddress, networkType),
|
|
||||||
outputAmount);
|
|
||||||
});
|
|
||||||
|
|
||||||
final estimatedSize =
|
|
||||||
estimatedTransactionSize(inputs.length, outputs.length + 1);
|
|
||||||
final feeAmount = feeRate(transactionCredentials.priority) * estimatedSize;
|
|
||||||
final changeValue = totalInputAmount - amount - feeAmount;
|
|
||||||
|
|
||||||
if (changeValue > minAmount) {
|
|
||||||
txb.addOutput(changeAddress, changeValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < inputs.length; i++) {
|
|
||||||
final input = inputs[i];
|
|
||||||
final keyPair = generateKeyPair(
|
|
||||||
hd: hd, index: input.address.index, network: networkType);
|
|
||||||
final witnessValue = input.isP2wpkh ? input.value : null;
|
|
||||||
|
|
||||||
txb.sign(vin: i, keyPair: keyPair, witnessValue: witnessValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
return PendingBitcoinTransaction(txb.build(), type,
|
|
||||||
electrumClient: electrumClient, amount: amount, fee: fee)
|
|
||||||
..addListener((transaction) async {
|
|
||||||
transactionHistory.addOne(transaction);
|
|
||||||
await _updateBalance();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
String toJSON() => json.encode({
|
|
||||||
'mnemonic': mnemonic,
|
|
||||||
'account_index': walletAddresses.accountIndex.toString(),
|
|
||||||
'addresses': walletAddresses.addresses.map((addr) => addr.toJSON()).toList(),
|
|
||||||
'balance': balance?.toJSON()
|
|
||||||
});
|
|
||||||
|
|
||||||
int feeRate(TransactionPriority priority) {
|
|
||||||
if (priority is BitcoinTransactionPriority) {
|
|
||||||
return _feeRates[priority.raw];
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int feeAmountForPriority(BitcoinTransactionPriority priority, int inputsCount,
|
|
||||||
int outputsCount) =>
|
|
||||||
feeRate(priority) * estimatedTransactionSize(inputsCount, outputsCount);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int calculateEstimatedFee(TransactionPriority priority, int amount,
|
|
||||||
{int outputsCount}) {
|
|
||||||
if (priority is BitcoinTransactionPriority) {
|
|
||||||
int inputsCount = 0;
|
|
||||||
|
|
||||||
if (amount != null) {
|
|
||||||
int totalValue = 0;
|
|
||||||
|
|
||||||
for (final input in unspentCoins) {
|
|
||||||
if (totalValue >= amount) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.isSending) {
|
|
||||||
totalValue += input.value;
|
|
||||||
inputsCount += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (totalValue < amount) return 0;
|
|
||||||
} else {
|
|
||||||
for (final input in unspentCoins) {
|
|
||||||
if (input.isSending) {
|
|
||||||
inputsCount += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If send all, then we have no change value
|
|
||||||
final _outputsCount = outputsCount ?? (amount != null ? 2 : 1);
|
|
||||||
|
|
||||||
return feeAmountForPriority(
|
|
||||||
priority, inputsCount, _outputsCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> save() async {
|
|
||||||
final path = await makePath();
|
|
||||||
await write(path: path, password: _password, data: toJSON());
|
|
||||||
await transactionHistory.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
bitcoin.ECPair keyPairFor({@required int index}) =>
|
|
||||||
generateKeyPair(hd: hd, index: index, network: networkType);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> rescan({int height}) async => throw UnimplementedError();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() async {
|
|
||||||
try {
|
|
||||||
await electrumClient?.close();
|
|
||||||
} catch (_) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> makePath() async =>
|
|
||||||
pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
|
||||||
|
|
||||||
Future<void> updateUnspent() async {
|
|
||||||
final unspent = await Future.wait(walletAddresses
|
|
||||||
.addresses.map((address) => electrumClient
|
|
||||||
.getListUnspentWithAddress(address.address, networkType)
|
|
||||||
.then((unspent) => unspent
|
|
||||||
.map((unspent) => BitcoinUnspent.fromJSON(address, unspent)))));
|
|
||||||
unspentCoins = unspent.expand((e) => e).toList();
|
|
||||||
|
|
||||||
if (unspentCoinsInfo.isEmpty) {
|
|
||||||
unspentCoins.forEach((coin) => _addCoinInfo(coin));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unspentCoins.isNotEmpty) {
|
|
||||||
unspentCoins.forEach((coin) {
|
|
||||||
final coinInfoList = unspentCoinsInfo.values.where((element) =>
|
|
||||||
element.walletId.contains(id) && element.hash.contains(coin.hash));
|
|
||||||
|
|
||||||
if (coinInfoList.isNotEmpty) {
|
|
||||||
final coinInfo = coinInfoList.first;
|
|
||||||
|
|
||||||
coin.isFrozen = coinInfo.isFrozen;
|
|
||||||
coin.isSending = coinInfo.isSending;
|
|
||||||
coin.note = coinInfo.note;
|
|
||||||
} else {
|
|
||||||
_addCoinInfo(coin);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await _refreshUnspentCoinsInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _addCoinInfo(BitcoinUnspent coin) async {
|
|
||||||
final newInfo = UnspentCoinsInfo(
|
|
||||||
walletId: id,
|
|
||||||
hash: coin.hash,
|
|
||||||
isFrozen: coin.isFrozen,
|
|
||||||
isSending: coin.isSending,
|
|
||||||
note: coin.note
|
|
||||||
);
|
|
||||||
|
|
||||||
await unspentCoinsInfo.add(newInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _refreshUnspentCoinsInfo() async {
|
|
||||||
try {
|
|
||||||
final List<dynamic> keys = <dynamic>[];
|
|
||||||
final currentWalletUnspentCoins = unspentCoinsInfo.values
|
|
||||||
.where((element) => element.walletId.contains(id));
|
|
||||||
|
|
||||||
if (currentWalletUnspentCoins.isNotEmpty) {
|
|
||||||
currentWalletUnspentCoins.forEach((element) {
|
|
||||||
final existUnspentCoins = unspentCoins
|
|
||||||
?.where((coin) => element.hash.contains(coin?.hash));
|
|
||||||
|
|
||||||
if (existUnspentCoins?.isEmpty ?? true) {
|
|
||||||
keys.add(element.key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keys.isNotEmpty) {
|
|
||||||
await unspentCoinsInfo.deleteAll(keys);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
print(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ElectrumTransactionInfo> fetchTransactionInfo(
|
|
||||||
{@required String hash, @required int height}) async {
|
|
||||||
final tx = await electrumClient.getTransactionExpanded(hash: hash);
|
|
||||||
return ElectrumTransactionInfo.fromElectrumVerbose(tx, walletInfo.type,
|
|
||||||
height: height, addresses: walletAddresses.addresses);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
|
|
||||||
final histories =
|
|
||||||
publicScriptHashes.map((scriptHash) => electrumClient.getHistory(scriptHash));
|
|
||||||
final _historiesWithDetails = await Future.wait(histories)
|
|
||||||
.then((histories) => histories.expand((i) => i).toList())
|
|
||||||
.then((histories) => histories.map((tx) => fetchTransactionInfo(
|
|
||||||
hash: tx['tx_hash'] as String, height: tx['height'] as int)));
|
|
||||||
final historiesWithDetails = await Future.wait(_historiesWithDetails);
|
|
||||||
|
|
||||||
return historiesWithDetails.fold<Map<String, ElectrumTransactionInfo>>(
|
|
||||||
<String, ElectrumTransactionInfo>{}, (acc, tx) {
|
|
||||||
acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx;
|
|
||||||
return acc;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> updateTransactions() async {
|
|
||||||
try {
|
|
||||||
if (_isTransactionUpdating) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_isTransactionUpdating = true;
|
|
||||||
final transactions = await fetchTransactions();
|
|
||||||
transactionHistory.addMany(transactions);
|
|
||||||
await transactionHistory.save();
|
|
||||||
_isTransactionUpdating = false;
|
|
||||||
} catch (e) {
|
|
||||||
print(e);
|
|
||||||
_isTransactionUpdating = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _subscribeForUpdates() {
|
|
||||||
scriptHashes.forEach((sh) async {
|
|
||||||
await _scripthashesUpdateSubject[sh]?.close();
|
|
||||||
_scripthashesUpdateSubject[sh] = electrumClient.scripthashUpdate(sh);
|
|
||||||
_scripthashesUpdateSubject[sh].listen((event) async {
|
|
||||||
try {
|
|
||||||
await _updateBalance();
|
|
||||||
await updateUnspent();
|
|
||||||
await updateTransactions();
|
|
||||||
} catch (e) {
|
|
||||||
print(e.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ElectrumBalance> _fetchBalances() async {
|
|
||||||
final balances = await Future.wait(
|
|
||||||
scriptHashes.map((sh) => electrumClient.getBalance(sh)));
|
|
||||||
final balance = balances.fold(
|
|
||||||
ElectrumBalance(confirmed: 0, unconfirmed: 0),
|
|
||||||
(ElectrumBalance acc, val) => ElectrumBalance(
|
|
||||||
confirmed: (val['confirmed'] as int ?? 0) + (acc.confirmed ?? 0),
|
|
||||||
unconfirmed:
|
|
||||||
(val['unconfirmed'] as int ?? 0) + (acc.unconfirmed ?? 0)));
|
|
||||||
|
|
||||||
return balance;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _updateBalance() async {
|
|
||||||
balance = await _fetchBalances();
|
|
||||||
await save();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,132 +0,0 @@
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_addresses.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
|
||||||
|
|
||||||
part 'electrum_wallet_addresses.g.dart';
|
|
||||||
|
|
||||||
class ElectrumWalletAddresses = ElectrumWalletAddressesBase
|
|
||||||
with _$ElectrumWalletAddresses;
|
|
||||||
|
|
||||||
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|
||||||
ElectrumWalletAddressesBase(WalletInfo walletInfo,
|
|
||||||
{@required List<BitcoinAddressRecord> initialAddresses,
|
|
||||||
int accountIndex = 0,
|
|
||||||
this.mainHd,
|
|
||||||
this.sideHd})
|
|
||||||
: super(walletInfo) {
|
|
||||||
this.accountIndex = accountIndex;
|
|
||||||
addresses = ObservableList<BitcoinAddressRecord>.of(
|
|
||||||
(initialAddresses ?? []).toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
static const regularAddressesCount = 22;
|
|
||||||
static const hiddenAddressesCount = 17;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@observable
|
|
||||||
String address;
|
|
||||||
|
|
||||||
bitcoin.HDWallet mainHd;
|
|
||||||
bitcoin.HDWallet sideHd;
|
|
||||||
|
|
||||||
ObservableList<BitcoinAddressRecord> addresses;
|
|
||||||
|
|
||||||
int accountIndex;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> init() async {
|
|
||||||
await generateAddresses();
|
|
||||||
address = addresses[accountIndex].address;
|
|
||||||
await updateAddressesInBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
@action
|
|
||||||
Future<void> nextAddress() async {
|
|
||||||
accountIndex += 1;
|
|
||||||
|
|
||||||
if (accountIndex >= addresses.length) {
|
|
||||||
accountIndex = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
address = addresses[accountIndex].address;
|
|
||||||
|
|
||||||
await updateAddressesInBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> generateAddresses() async {
|
|
||||||
final regularAddresses = <BitcoinAddressRecord>[];
|
|
||||||
final hiddenAddresses = <BitcoinAddressRecord>[];
|
|
||||||
|
|
||||||
addresses.forEach((addr) {
|
|
||||||
if (addr.isHidden) {
|
|
||||||
hiddenAddresses.add(addr);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
regularAddresses.add(addr);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (regularAddresses.length < regularAddressesCount) {
|
|
||||||
final addressesCount = regularAddressesCount - regularAddresses.length;
|
|
||||||
await generateNewAddresses(addressesCount,
|
|
||||||
startIndex: regularAddresses.length, hd: mainHd, isHidden: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hiddenAddresses.length < hiddenAddressesCount) {
|
|
||||||
final addressesCount = hiddenAddressesCount - hiddenAddresses.length;
|
|
||||||
await generateNewAddresses(addressesCount,
|
|
||||||
startIndex: hiddenAddresses.length, hd: sideHd, isHidden: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<BitcoinAddressRecord> generateNewAddress(
|
|
||||||
{bool isHidden = false, bitcoin.HDWallet hd}) async {
|
|
||||||
accountIndex += 1;
|
|
||||||
final address = BitcoinAddressRecord(
|
|
||||||
getAddress(index: accountIndex, hd: hd),
|
|
||||||
index: accountIndex,
|
|
||||||
isHidden: isHidden);
|
|
||||||
addresses.add(address);
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<BitcoinAddressRecord>> generateNewAddresses(int count,
|
|
||||||
{int startIndex = 0, bitcoin.HDWallet hd, bool isHidden = false}) async {
|
|
||||||
final list = <BitcoinAddressRecord>[];
|
|
||||||
|
|
||||||
for (var i = startIndex; i < count + startIndex; i++) {
|
|
||||||
final address = BitcoinAddressRecord(getAddress(index: i, hd: hd),
|
|
||||||
index: i, isHidden: isHidden);
|
|
||||||
list.add(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
addresses.addAll(list);
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Future<void> updateAddress(String address) async {
|
|
||||||
for (final addr in addresses) {
|
|
||||||
if (addr.address == address) {
|
|
||||||
await save();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
String getAddress({@required int index, @required bitcoin.HDWallet hd}) => '';
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> updateAddressesInBox() async {
|
|
||||||
try {
|
|
||||||
addressesMap.clear();
|
|
||||||
addressesMap[address] = '';
|
|
||||||
|
|
||||||
await saveAddressesInBox();
|
|
||||||
} catch (e) {
|
|
||||||
print(e.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum_balance.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/file.dart';
|
|
||||||
import 'package:cake_wallet/entities/pathForWallet.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
|
||||||
|
|
||||||
class ElectrumWallletSnapshot {
|
|
||||||
ElectrumWallletSnapshot(this.name, this.type, this.password);
|
|
||||||
|
|
||||||
final String name;
|
|
||||||
final String password;
|
|
||||||
final WalletType type;
|
|
||||||
|
|
||||||
String mnemonic;
|
|
||||||
List<BitcoinAddressRecord> addresses;
|
|
||||||
ElectrumBalance balance;
|
|
||||||
int accountIndex;
|
|
||||||
|
|
||||||
Future<void> load() async {
|
|
||||||
try {
|
|
||||||
final path = await pathForWallet(name: name, type: type);
|
|
||||||
final jsonSource = await read(path: path, password: password);
|
|
||||||
final data = json.decode(jsonSource) as Map;
|
|
||||||
final addressesTmp = data['addresses'] as List ?? <Object>[];
|
|
||||||
mnemonic = data['mnemonic'] as String;
|
|
||||||
addresses = addressesTmp
|
|
||||||
.whereType<String>()
|
|
||||||
.map((addr) => BitcoinAddressRecord.fromJSON(addr))
|
|
||||||
.toList();
|
|
||||||
balance = ElectrumBalance.fromJSON(data['balance'] as String) ??
|
|
||||||
ElectrumBalance(confirmed: 0, unconfirmed: 0);
|
|
||||||
accountIndex = 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
accountIndex = int.parse(data['account_index'] as String);
|
|
||||||
} catch (_) {}
|
|
||||||
} catch (e) {
|
|
||||||
print(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'package:cake_wallet/bitcoin/key.dart';
|
|
||||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
Future<void> write(
|
|
||||||
{@required String path,
|
|
||||||
@required String password,
|
|
||||||
@required String data}) async {
|
|
||||||
final keys = extractKeys(password);
|
|
||||||
final key = encrypt.Key.fromBase64(keys.first);
|
|
||||||
final iv = encrypt.IV.fromBase64(keys.last);
|
|
||||||
final encrypted = await encode(key: key, iv: iv, data: data);
|
|
||||||
final f = File(path);
|
|
||||||
f.writeAsStringSync(encrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> writeData(
|
|
||||||
{@required String path,
|
|
||||||
@required String password,
|
|
||||||
@required String data}) async {
|
|
||||||
final keys = extractKeys(password);
|
|
||||||
final key = encrypt.Key.fromBase64(keys.first);
|
|
||||||
final iv = encrypt.IV.fromBase64(keys.last);
|
|
||||||
final encrypted = await encode(key: key, iv: iv, data: data);
|
|
||||||
final f = File(path);
|
|
||||||
f.writeAsStringSync(encrypted);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> read({@required String path, @required String password}) async {
|
|
||||||
final file = File(path);
|
|
||||||
|
|
||||||
if (!file.existsSync()) {
|
|
||||||
file.createSync();
|
|
||||||
}
|
|
||||||
|
|
||||||
final encrypted = file.readAsStringSync();
|
|
||||||
|
|
||||||
return decode(password: password, data: encrypted);
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
|
||||||
|
|
||||||
const ivEncodedStringLength = 12;
|
|
||||||
|
|
||||||
String generateKey() {
|
|
||||||
final key = encrypt.Key.fromSecureRandom(512);
|
|
||||||
final iv = encrypt.IV.fromSecureRandom(8);
|
|
||||||
|
|
||||||
return key.base64 + iv.base64;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> extractKeys(String key) {
|
|
||||||
final _key = key.substring(0, key.length - ivEncodedStringLength);
|
|
||||||
final iv = key.substring(key.length - ivEncodedStringLength);
|
|
||||||
|
|
||||||
return [_key, iv];
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> encode({encrypt.Key key, encrypt.IV iv, String data}) async {
|
|
||||||
final encrypter = encrypt.Encrypter(encrypt.Salsa20(key));
|
|
||||||
final encrypted = encrypter.encrypt(data, iv: iv);
|
|
||||||
|
|
||||||
return encrypted.base64;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String> decode({String password, String data}) async {
|
|
||||||
final keys = extractKeys(password);
|
|
||||||
final key = encrypt.Key.fromBase64(keys.first);
|
|
||||||
final iv = encrypt.IV.fromBase64(keys.last);
|
|
||||||
final encrypter = encrypt.Encrypter(encrypt.Salsa20(key));
|
|
||||||
final encrypted = encrypter.decrypt64(data, iv: iv);
|
|
||||||
|
|
||||||
return encrypted;
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
|
|
||||||
|
|
||||||
final litecoinNetwork = NetworkType(
|
|
||||||
messagePrefix: '\x19Litecoin Signed Message:\n',
|
|
||||||
bech32: 'ltc',
|
|
||||||
bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4),
|
|
||||||
pubKeyHash: 0x30,
|
|
||||||
scriptHash: 0x32,
|
|
||||||
wif: 0xb0);
|
|
|
@ -1,82 +0,0 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/litecoin_wallet_addresses.dart';
|
|
||||||
import 'package:cake_wallet/entities/transaction_priority.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum_wallet_snapshot.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum_wallet.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum_balance.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/litecoin_network.dart';
|
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
|
||||||
|
|
||||||
part 'litecoin_wallet.g.dart';
|
|
||||||
|
|
||||||
class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet;
|
|
||||||
|
|
||||||
abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|
||||||
LitecoinWalletBase(
|
|
||||||
{@required String mnemonic,
|
|
||||||
@required String password,
|
|
||||||
@required WalletInfo walletInfo,
|
|
||||||
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
|
||||||
List<BitcoinAddressRecord> initialAddresses,
|
|
||||||
ElectrumBalance initialBalance,
|
|
||||||
int accountIndex = 0})
|
|
||||||
: super(
|
|
||||||
mnemonic: mnemonic,
|
|
||||||
password: password,
|
|
||||||
walletInfo: walletInfo,
|
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
|
||||||
networkType: litecoinNetwork,
|
|
||||||
initialAddresses: initialAddresses,
|
|
||||||
initialBalance: initialBalance) {
|
|
||||||
walletAddresses = LitecoinWalletAddresses(
|
|
||||||
walletInfo,
|
|
||||||
initialAddresses: initialAddresses,
|
|
||||||
accountIndex: accountIndex,
|
|
||||||
mainHd: hd,
|
|
||||||
sideHd: bitcoin.HDWallet
|
|
||||||
.fromSeed(mnemonicToSeedBytes(mnemonic), network: networkType)
|
|
||||||
.derivePath("m/0'/1"),
|
|
||||||
networkType: networkType,);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<LitecoinWallet> open({
|
|
||||||
@required String name,
|
|
||||||
@required WalletInfo walletInfo,
|
|
||||||
@required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
|
||||||
@required String password,
|
|
||||||
}) async {
|
|
||||||
final snp = ElectrumWallletSnapshot(name, walletInfo.type, password);
|
|
||||||
await snp.load();
|
|
||||||
return LitecoinWallet(
|
|
||||||
mnemonic: snp.mnemonic,
|
|
||||||
password: password,
|
|
||||||
walletInfo: walletInfo,
|
|
||||||
unspentCoinsInfo: unspentCoinsInfo,
|
|
||||||
initialAddresses: snp.addresses,
|
|
||||||
initialBalance: snp.balance,
|
|
||||||
accountIndex: snp.accountIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int feeRate(TransactionPriority priority) {
|
|
||||||
if (priority is LitecoinTransactionPriority) {
|
|
||||||
switch (priority) {
|
|
||||||
case LitecoinTransactionPriority.slow:
|
|
||||||
return 1;
|
|
||||||
case LitecoinTransactionPriority.medium:
|
|
||||||
return 2;
|
|
||||||
case LitecoinTransactionPriority.fast:
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/utils.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum_wallet_addresses.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
|
||||||
|
|
||||||
part 'litecoin_wallet_addresses.g.dart';
|
|
||||||
|
|
||||||
class LitecoinWalletAddresses = LitecoinWalletAddressesBase
|
|
||||||
with _$LitecoinWalletAddresses;
|
|
||||||
|
|
||||||
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
|
|
||||||
with Store {
|
|
||||||
LitecoinWalletAddressesBase(
|
|
||||||
WalletInfo walletInfo,
|
|
||||||
{@required List<BitcoinAddressRecord> initialAddresses,
|
|
||||||
int accountIndex = 0,
|
|
||||||
@required bitcoin.HDWallet mainHd,
|
|
||||||
@required bitcoin.HDWallet sideHd,
|
|
||||||
@required this.networkType})
|
|
||||||
: super(
|
|
||||||
walletInfo,
|
|
||||||
initialAddresses: initialAddresses,
|
|
||||||
accountIndex: accountIndex,
|
|
||||||
mainHd: mainHd,
|
|
||||||
sideHd: sideHd);
|
|
||||||
|
|
||||||
bitcoin.NetworkType networkType;
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
String getAddress({@required int index, @required bitcoin.HDWallet hd}) =>
|
|
||||||
generateP2WPKHAddress(hd: hd, index: index, networkType: networkType);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> generateAddresses() async {
|
|
||||||
if (addresses.length < 33) {
|
|
||||||
final addressesCount = 22 - addresses.length;
|
|
||||||
await generateNewAddresses(addressesCount,
|
|
||||||
hd: mainHd, startIndex: addresses.length);
|
|
||||||
await generateNewAddresses(11,
|
|
||||||
startIndex: 0, hd: sideHd, isHidden: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/litecoin_wallet.dart';
|
|
||||||
import 'package:cake_wallet/core/wallet_service.dart';
|
|
||||||
import 'package:cake_wallet/entities/pathForWallet.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
|
||||||
|
|
||||||
class LitecoinWalletService extends WalletService<
|
|
||||||
BitcoinNewWalletCredentials,
|
|
||||||
BitcoinRestoreWalletFromSeedCredentials,
|
|
||||||
BitcoinRestoreWalletFromWIFCredentials> {
|
|
||||||
LitecoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
|
||||||
|
|
||||||
final Box<WalletInfo> walletInfoSource;
|
|
||||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
|
||||||
|
|
||||||
@override
|
|
||||||
WalletType getType() => WalletType.litecoin;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials) async {
|
|
||||||
final wallet = LitecoinWallet(
|
|
||||||
mnemonic: await generateMnemonic(),
|
|
||||||
password: credentials.password,
|
|
||||||
walletInfo: credentials.walletInfo,
|
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
|
||||||
await wallet.save();
|
|
||||||
await wallet.init();
|
|
||||||
|
|
||||||
return wallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> isWalletExit(String name) async =>
|
|
||||||
File(await pathForWallet(name: name, type: getType())).existsSync();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<LitecoinWallet> openWallet(String name, String password) async {
|
|
||||||
final walletInfo = walletInfoSource.values.firstWhere(
|
|
||||||
(info) => info.id == WalletBase.idFor(name, getType()),
|
|
||||||
orElse: () => null);
|
|
||||||
final wallet = await LitecoinWalletBase.open(
|
|
||||||
password: password, name: name, walletInfo: walletInfo,
|
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
|
||||||
await wallet.init();
|
|
||||||
return wallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> remove(String wallet) async =>
|
|
||||||
File(await pathForWalletDir(name: wallet, type: getType()))
|
|
||||||
.delete(recursive: true);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<LitecoinWallet> restoreFromKeys(
|
|
||||||
BitcoinRestoreWalletFromWIFCredentials credentials) async =>
|
|
||||||
throw UnimplementedError();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<LitecoinWallet> restoreFromSeed(
|
|
||||||
BitcoinRestoreWalletFromSeedCredentials credentials) async {
|
|
||||||
if (!validateMnemonic(credentials.mnemonic)) {
|
|
||||||
throw BitcoinMnemonicIsIncorrectException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final wallet = LitecoinWallet(
|
|
||||||
password: credentials.password,
|
|
||||||
mnemonic: credentials.mnemonic,
|
|
||||||
walletInfo: credentials.walletInfo,
|
|
||||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
|
||||||
await wallet.save();
|
|
||||||
await wallet.init();
|
|
||||||
return wallet;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_commit_transaction_exception.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
|
||||||
import 'package:cake_wallet/core/pending_transaction.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/electrum_transaction_info.dart';
|
|
||||||
import 'package:cake_wallet/entities/transaction_direction.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
|
||||||
|
|
||||||
class PendingBitcoinTransaction with PendingTransaction {
|
|
||||||
PendingBitcoinTransaction(this._tx, this.type,
|
|
||||||
{@required this.electrumClient,
|
|
||||||
@required this.amount,
|
|
||||||
@required this.fee})
|
|
||||||
: _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
|
|
||||||
|
|
||||||
final WalletType type;
|
|
||||||
final bitcoin.Transaction _tx;
|
|
||||||
final ElectrumClient electrumClient;
|
|
||||||
final int amount;
|
|
||||||
final int fee;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get id => _tx.getId();
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get amountFormatted => bitcoinAmountToString(amount: amount);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get feeFormatted => bitcoinAmountToString(amount: fee);
|
|
||||||
|
|
||||||
final List<void Function(ElectrumTransactionInfo transaction)> _listeners;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> commit() async {
|
|
||||||
final result =
|
|
||||||
await electrumClient.broadcastTransaction(transactionRaw: _tx.toHex());
|
|
||||||
|
|
||||||
if (result.isEmpty) {
|
|
||||||
throw BitcoinCommitTransactionException();
|
|
||||||
}
|
|
||||||
|
|
||||||
_listeners?.forEach((listener) => listener(transactionInfo()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void addListener(
|
|
||||||
void Function(ElectrumTransactionInfo transaction) listener) =>
|
|
||||||
_listeners.add(listener);
|
|
||||||
|
|
||||||
ElectrumTransactionInfo transactionInfo() => ElectrumTransactionInfo(type,
|
|
||||||
id: id,
|
|
||||||
height: 0,
|
|
||||||
amount: amount,
|
|
||||||
direction: TransactionDirection.outgoing,
|
|
||||||
date: DateTime.now(),
|
|
||||||
isPending: true,
|
|
||||||
confirmations: 0,
|
|
||||||
fee: fee);
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
|
||||||
import 'package:crypto/crypto.dart';
|
|
||||||
|
|
||||||
String scriptHash(String address, {@required bitcoin.NetworkType networkType}) {
|
|
||||||
final outputScript =
|
|
||||||
bitcoin.Address.addressToOutputScript(address, networkType);
|
|
||||||
final parts = sha256.convert(outputScript).toString().split('');
|
|
||||||
var res = '';
|
|
||||||
|
|
||||||
for (var i = parts.length - 1; i >= 0; i--) {
|
|
||||||
final char = parts[i];
|
|
||||||
i--;
|
|
||||||
final nextChar = parts[i];
|
|
||||||
res += nextChar;
|
|
||||||
res += char;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
|
|
||||||
part 'unspent_coins_info.g.dart';
|
|
||||||
|
|
||||||
@HiveType(typeId: UnspentCoinsInfo.typeId)
|
|
||||||
class UnspentCoinsInfo extends HiveObject {
|
|
||||||
UnspentCoinsInfo({
|
|
||||||
this.walletId,
|
|
||||||
this.hash,
|
|
||||||
this.isFrozen,
|
|
||||||
this.isSending,
|
|
||||||
this.note});
|
|
||||||
|
|
||||||
static const typeId = 9;
|
|
||||||
static const boxName = 'Unspent';
|
|
||||||
static const boxKey = 'unspentBoxKey';
|
|
||||||
|
|
||||||
@HiveField(0)
|
|
||||||
String walletId;
|
|
||||||
|
|
||||||
@HiveField(1)
|
|
||||||
String hash;
|
|
||||||
|
|
||||||
@HiveField(2)
|
|
||||||
bool isFrozen;
|
|
||||||
|
|
||||||
@HiveField(3)
|
|
||||||
bool isSending;
|
|
||||||
|
|
||||||
@HiveField(4)
|
|
||||||
String note;
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
import 'dart:typed_data';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
|
|
||||||
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
|
||||||
import 'package:hex/hex.dart';
|
|
||||||
|
|
||||||
bitcoin.PaymentData generatePaymentData(
|
|
||||||
{@required bitcoin.HDWallet hd, @required int index}) =>
|
|
||||||
PaymentData(
|
|
||||||
pubkey: Uint8List.fromList(HEX.decode(hd.derive(index).pubKey)));
|
|
||||||
|
|
||||||
bitcoin.ECPair generateKeyPair(
|
|
||||||
{@required bitcoin.HDWallet hd,
|
|
||||||
@required int index,
|
|
||||||
bitcoin.NetworkType network}) =>
|
|
||||||
bitcoin.ECPair.fromWIF(hd.derive(index).wif, network: network);
|
|
||||||
|
|
||||||
String generateP2WPKHAddress(
|
|
||||||
{@required bitcoin.HDWallet hd,
|
|
||||||
@required int index,
|
|
||||||
bitcoin.NetworkType networkType}) =>
|
|
||||||
bitcoin
|
|
||||||
.P2WPKH(
|
|
||||||
data: PaymentData(
|
|
||||||
pubkey:
|
|
||||||
Uint8List.fromList(HEX.decode(hd.derive(index).pubKey))),
|
|
||||||
network: networkType)
|
|
||||||
.data
|
|
||||||
.address;
|
|
||||||
|
|
||||||
String generateP2WPKHAddressByPath(
|
|
||||||
{@required bitcoin.HDWallet hd,
|
|
||||||
@required String path,
|
|
||||||
bitcoin.NetworkType networkType}) =>
|
|
||||||
bitcoin
|
|
||||||
.P2WPKH(
|
|
||||||
data: PaymentData(
|
|
||||||
pubkey:
|
|
||||||
Uint8List.fromList(HEX.decode(hd.derivePath(path).pubKey))),
|
|
||||||
network: networkType)
|
|
||||||
.data
|
|
||||||
.address;
|
|
||||||
|
|
||||||
String generateP2PKHAddress(
|
|
||||||
{@required bitcoin.HDWallet hd,
|
|
||||||
@required int index,
|
|
||||||
bitcoin.NetworkType networkType}) =>
|
|
||||||
bitcoin
|
|
||||||
.P2PKH(
|
|
||||||
data: PaymentData(
|
|
||||||
pubkey:
|
|
||||||
Uint8List.fromList(HEX.decode(hd.derive(index).pubKey))),
|
|
||||||
network: networkType)
|
|
||||||
.data
|
|
||||||
.address;
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:cake_wallet/buy/buy_amount.dart';
|
import 'package:cake_wallet/buy/buy_amount.dart';
|
||||||
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
||||||
import 'package:cake_wallet/buy/order.dart';
|
import 'package:cake_wallet/buy/order.dart';
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
abstract class BuyProvider {
|
abstract class BuyProvider {
|
||||||
BuyProvider({this.wallet, this.isTestEnvironment});
|
BuyProvider({this.wallet, this.isTestEnvironment});
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:cake_wallet/entities/enumerable_item.dart';
|
import 'package:cw_core/enumerable_item.dart';
|
||||||
|
|
||||||
class BuyProviderDescription extends EnumerableItem<int>
|
class BuyProviderDescription extends EnumerableItem<int>
|
||||||
with Serializable<int> {
|
with Serializable<int> {
|
||||||
|
|
|
@ -6,11 +6,11 @@ import 'package:cake_wallet/buy/buy_amount.dart';
|
||||||
import 'package:cake_wallet/buy/buy_provider.dart';
|
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||||
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
||||||
import 'package:cake_wallet/buy/order.dart';
|
import 'package:cake_wallet/buy/order.dart';
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
|
||||||
class MoonPaySellProvider {
|
class MoonPaySellProvider {
|
||||||
MoonPaySellProvider({this.isTest = false})
|
MoonPaySellProvider({this.isTest = false})
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||||
import 'package:cake_wallet/entities/format_amount.dart';
|
import 'package:cw_core/format_amount.dart';
|
||||||
|
|
||||||
part 'order.g.dart';
|
part 'order.g.dart';
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@ import 'package:cake_wallet/buy/buy_amount.dart';
|
||||||
import 'package:cake_wallet/buy/buy_provider.dart';
|
import 'package:cake_wallet/buy/buy_provider.dart';
|
||||||
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
import 'package:cake_wallet/buy/buy_provider_description.dart';
|
||||||
import 'package:cake_wallet/buy/order.dart';
|
import 'package:cake_wallet/buy/order.dart';
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:cake_wallet/core/validator.dart';
|
import 'package:cake_wallet/core/validator.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
class AddressLabelValidator extends TextValidator {
|
class AddressLabelValidator extends TextValidator {
|
||||||
AddressLabelValidator({WalletType type})
|
AddressLabelValidator({WalletType type})
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/core/validator.dart';
|
import 'package:cake_wallet/core/validator.dart';
|
||||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
|
||||||
class AddressValidator extends TextValidator {
|
class AddressValidator extends TextValidator {
|
||||||
AddressValidator({@required CryptoCurrency type})
|
AddressValidator({@required CryptoCurrency type})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
|
||||||
class AmountConverter {
|
class AmountConverter {
|
||||||
static const _moneroAmountLength = 12;
|
static const _moneroAmountLength = 12;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:cake_wallet/core/validator.dart';
|
import 'package:cake_wallet/core/validator.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
class AmountValidator extends TextValidator {
|
class AmountValidator extends TextValidator {
|
||||||
AmountValidator({WalletType type, bool isAutovalidate = false})
|
AmountValidator({WalletType type, bool isAutovalidate = false})
|
||||||
|
|
|
@ -12,7 +12,7 @@ import 'package:cake_wallet/core/key_service.dart';
|
||||||
import 'package:cake_wallet/entities/encrypt.dart';
|
import 'package:cake_wallet/entities/encrypt.dart';
|
||||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||||
import 'package:cake_wallet/entities/secret_store_key.dart';
|
import 'package:cake_wallet/entities/secret_store_key.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||||
|
|
||||||
class BackupService {
|
class BackupService {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
import 'package:cake_wallet/bitcoin/key.dart';
|
import 'package:cw_core/key.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
String generateWalletPassword(WalletType type) {
|
String generateWalletPassword(WalletType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
const bitcoinMnemonicLength = 12;
|
const bitcoinMnemonicLength = 12;
|
||||||
const moneroMnemonicLength = 25;
|
const moneroMnemonicLength = 25;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/core/validator.dart';
|
import 'package:cake_wallet/core/validator.dart';
|
||||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
|
||||||
class MoneroLabelValidator extends TextValidator {
|
class MoneroLabelValidator extends TextValidator {
|
||||||
MoneroLabelValidator({@required CryptoCurrency type})
|
MoneroLabelValidator({@required CryptoCurrency type})
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
mixin PendingTransaction {
|
|
||||||
String get id;
|
|
||||||
String get amountFormatted;
|
|
||||||
String get feeFormatted;
|
|
||||||
|
|
||||||
Future<void> commit();
|
|
||||||
}
|
|
|
@ -1,15 +1,8 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart' as bitcoin_electrum;
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
import 'package:cake_wallet/core/validator.dart';
|
import 'package:cake_wallet/core/validator.dart';
|
||||||
import 'package:cake_wallet/entities/mnemonic_item.dart';
|
import 'package:cake_wallet/entities/mnemonic_item.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cake_wallet/monero/mnemonics/chinese_simplified.dart';
|
import 'package:cake_wallet/monero/monero.dart';
|
||||||
import 'package:cake_wallet/monero/mnemonics/dutch.dart';
|
|
||||||
import 'package:cake_wallet/monero/mnemonics/english.dart';
|
|
||||||
import 'package:cake_wallet/monero/mnemonics/german.dart';
|
|
||||||
import 'package:cake_wallet/monero/mnemonics/japanese.dart';
|
|
||||||
import 'package:cake_wallet/monero/mnemonics/portuguese.dart';
|
|
||||||
import 'package:cake_wallet/monero/mnemonics/russian.dart';
|
|
||||||
import 'package:cake_wallet/monero/mnemonics/spanish.dart';
|
|
||||||
import 'package:cake_wallet/utils/language_list.dart';
|
import 'package:cake_wallet/utils/language_list.dart';
|
||||||
|
|
||||||
class SeedValidator extends Validator<MnemonicItem> {
|
class SeedValidator extends Validator<MnemonicItem> {
|
||||||
|
@ -27,46 +20,15 @@ class SeedValidator extends Validator<MnemonicItem> {
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
return getBitcoinWordList(language);
|
return getBitcoinWordList(language);
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
return getMoneroWordList(language);
|
return monero.getMoneroWordList(language);
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<String> getMoneroWordList(String language) {
|
|
||||||
switch (language) {
|
|
||||||
case LanguageList.english:
|
|
||||||
return EnglishMnemonics.words;
|
|
||||||
break;
|
|
||||||
case LanguageList.chineseSimplified:
|
|
||||||
return ChineseSimplifiedMnemonics.words;
|
|
||||||
break;
|
|
||||||
case LanguageList.dutch:
|
|
||||||
return DutchMnemonics.words;
|
|
||||||
break;
|
|
||||||
case LanguageList.german:
|
|
||||||
return GermanMnemonics.words;
|
|
||||||
break;
|
|
||||||
case LanguageList.japanese:
|
|
||||||
return JapaneseMnemonics.words;
|
|
||||||
break;
|
|
||||||
case LanguageList.portuguese:
|
|
||||||
return PortugueseMnemonics.words;
|
|
||||||
break;
|
|
||||||
case LanguageList.russian:
|
|
||||||
return RussianMnemonics.words;
|
|
||||||
break;
|
|
||||||
case LanguageList.spanish:
|
|
||||||
return SpanishMnemonics.words;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return EnglishMnemonics.words;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<String> getBitcoinWordList(String language) {
|
static List<String> getBitcoinWordList(String language) {
|
||||||
assert(language.toLowerCase() == LanguageList.english.toLowerCase());
|
assert(language.toLowerCase() == LanguageList.english.toLowerCase());
|
||||||
return bitcoin_electrum.englishWordlist;
|
return bitcoin.getWordList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:mobx/mobx.dart';
|
|
||||||
import 'package:cake_wallet/entities/transaction_info.dart';
|
|
||||||
|
|
||||||
abstract class TransactionHistoryBase<TransactionType extends TransactionInfo> {
|
|
||||||
TransactionHistoryBase();
|
|
||||||
// : _isUpdating = false;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
ObservableMap<String, TransactionType> transactions;
|
|
||||||
|
|
||||||
Future<void> save();
|
|
||||||
|
|
||||||
void addOne(TransactionType transaction);
|
|
||||||
|
|
||||||
void addMany(Map<String, TransactionType> transactions);
|
|
||||||
|
|
||||||
// bool _isUpdating;
|
|
||||||
|
|
||||||
// @action
|
|
||||||
// Future<void> update() async {
|
|
||||||
// if (_isUpdating) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// try {
|
|
||||||
// _isUpdating = true;
|
|
||||||
// final _transactions = await fetchTransactions();
|
|
||||||
// transactions.keys
|
|
||||||
// .toSet()
|
|
||||||
// .difference(_transactions.keys.toSet())
|
|
||||||
// .forEach((k) => transactions.remove(k));
|
|
||||||
// _transactions.forEach((key, value) => transactions[key] = value);
|
|
||||||
// _isUpdating = false;
|
|
||||||
// } catch (e) {
|
|
||||||
// _isUpdating = false;
|
|
||||||
// rethrow;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void updateAsync({void Function() onFinished}) {
|
|
||||||
// fetchTransactionsAsync(
|
|
||||||
// (transaction) => transactions[transaction.id] = transaction,
|
|
||||||
// onFinished: onFinished);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void fetchTransactionsAsync(
|
|
||||||
// void Function(TransactionType transaction) onTransactionLoaded,
|
|
||||||
// {void Function() onFinished});
|
|
||||||
|
|
||||||
// Future<Map<String, TransactionType>> fetchTransactions();
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
import 'package:cake_wallet/entities/balance.dart';
|
|
||||||
import 'package:cake_wallet/entities/transaction_info.dart';
|
|
||||||
import 'package:cake_wallet/entities/transaction_priority.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_addresses.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
|
||||||
import 'package:cake_wallet/core/pending_transaction.dart';
|
|
||||||
import 'package:cake_wallet/core/transaction_history.dart';
|
|
||||||
import 'package:cake_wallet/entities/currency_for_wallet_type.dart';
|
|
||||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
|
||||||
import 'package:cake_wallet/entities/sync_status.dart';
|
|
||||||
import 'package:cake_wallet/entities/node.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
|
||||||
|
|
||||||
abstract class WalletBase<
|
|
||||||
BalanceType extends Balance,
|
|
||||||
HistoryType extends TransactionHistoryBase,
|
|
||||||
TransactionType extends TransactionInfo> {
|
|
||||||
WalletBase(this.walletInfo);
|
|
||||||
|
|
||||||
static String idFor(String name, WalletType type) =>
|
|
||||||
walletTypeToString(type).toLowerCase() + '_' + name;
|
|
||||||
|
|
||||||
WalletInfo walletInfo;
|
|
||||||
|
|
||||||
WalletType get type => walletInfo.type;
|
|
||||||
|
|
||||||
CryptoCurrency get currency => currencyForWalletType(type);
|
|
||||||
|
|
||||||
String get id => walletInfo.id;
|
|
||||||
|
|
||||||
String get name => walletInfo.name;
|
|
||||||
|
|
||||||
//String get address;
|
|
||||||
|
|
||||||
//set address(String address);
|
|
||||||
|
|
||||||
BalanceType get balance;
|
|
||||||
|
|
||||||
SyncStatus get syncStatus;
|
|
||||||
|
|
||||||
set syncStatus(SyncStatus status);
|
|
||||||
|
|
||||||
String get seed;
|
|
||||||
|
|
||||||
Object get keys;
|
|
||||||
|
|
||||||
WalletAddresses get walletAddresses;
|
|
||||||
|
|
||||||
HistoryType transactionHistory;
|
|
||||||
|
|
||||||
Future<void> connectToNode({@required Node node});
|
|
||||||
|
|
||||||
Future<void> startSync();
|
|
||||||
|
|
||||||
Future<PendingTransaction> createTransaction(Object credentials);
|
|
||||||
|
|
||||||
int calculateEstimatedFee(TransactionPriority priority, int amount);
|
|
||||||
|
|
||||||
// void fetchTransactionsAsync(
|
|
||||||
// void Function(TransactionType transaction) onTransactionLoaded,
|
|
||||||
// {void Function() onFinished});
|
|
||||||
|
|
||||||
Future<Map<String, TransactionType>> fetchTransactions();
|
|
||||||
|
|
||||||
Future<void> save();
|
|
||||||
|
|
||||||
Future<void> rescan({int height});
|
|
||||||
|
|
||||||
void close();
|
|
||||||
}
|
|
|
@ -3,11 +3,11 @@ import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:cake_wallet/core/key_service.dart';
|
import 'package:cake_wallet/core/key_service.dart';
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
import 'package:cw_core/wallet_base.dart';
|
||||||
import 'package:cake_wallet/core/generate_wallet_password.dart';
|
import 'package:cake_wallet/core/generate_wallet_password.dart';
|
||||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
import 'package:cw_core/wallet_credentials.dart';
|
||||||
import 'package:cake_wallet/core/wallet_service.dart';
|
import 'package:cw_core/wallet_service.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
class WalletCreationService {
|
class WalletCreationService {
|
||||||
WalletCreationService(
|
WalletCreationService(
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
|
||||||
|
|
||||||
abstract class WalletCredentials {
|
|
||||||
WalletCredentials({this.name, this.password, this.height, this.walletInfo});
|
|
||||||
|
|
||||||
final String name;
|
|
||||||
final int height;
|
|
||||||
String password;
|
|
||||||
WalletInfo walletInfo;
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
import 'package:cake_wallet/core/wallet_base.dart';
|
|
||||||
import 'package:cake_wallet/core/wallet_credentials.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
|
||||||
|
|
||||||
abstract class WalletService<N extends WalletCredentials,
|
|
||||||
RFS extends WalletCredentials, RFK extends WalletCredentials> {
|
|
||||||
WalletType getType();
|
|
||||||
|
|
||||||
Future<WalletBase> create(N credentials);
|
|
||||||
|
|
||||||
Future<WalletBase> restoreFromSeed(RFS credentials);
|
|
||||||
|
|
||||||
Future<WalletBase> restoreFromKeys(RFK credentials);
|
|
||||||
|
|
||||||
Future<WalletBase> openWallet(String name, String password);
|
|
||||||
|
|
||||||
Future<bool> isWalletExit(String name);
|
|
||||||
|
|
||||||
Future<void> remove(String wallet);
|
|
||||||
}
|
|
36
lib/di.dart
36
lib/di.dart
|
@ -1,16 +1,15 @@
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
|
import 'package:cake_wallet/monero/monero.dart';
|
||||||
import 'package:cake_wallet/bitcoin/litecoin_wallet_service.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
import 'package:cake_wallet/bitcoin/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
import 'package:cake_wallet/core/backup_service.dart';
|
import 'package:cake_wallet/core/backup_service.dart';
|
||||||
import 'package:cake_wallet/core/wallet_service.dart';
|
import 'package:cw_core/wallet_service.dart';
|
||||||
import 'package:cake_wallet/entities/biometric_auth.dart';
|
import 'package:cake_wallet/entities/biometric_auth.dart';
|
||||||
import 'package:cake_wallet/entities/contact_record.dart';
|
import 'package:cake_wallet/entities/contact_record.dart';
|
||||||
import 'package:cake_wallet/buy/order.dart';
|
import 'package:cake_wallet/buy/order.dart';
|
||||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||||
import 'package:cake_wallet/entities/transaction_info.dart';
|
import 'package:cw_core/transaction_info.dart';
|
||||||
import 'package:cake_wallet/monero/monero_wallet_service.dart';
|
|
||||||
import 'package:cake_wallet/entities/contact.dart';
|
import 'package:cake_wallet/entities/contact.dart';
|
||||||
import 'package:cake_wallet/entities/node.dart';
|
import 'package:cw_core/node.dart';
|
||||||
import 'package:cake_wallet/exchange/trade.dart';
|
import 'package:cake_wallet/exchange/trade.dart';
|
||||||
import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
|
import 'package:cake_wallet/reactions/on_authentication_state_change.dart';
|
||||||
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
|
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
|
||||||
|
@ -51,8 +50,7 @@ import 'package:cake_wallet/store/secret_store.dart';
|
||||||
import 'package:cake_wallet/store/settings_store.dart';
|
import 'package:cake_wallet/store/settings_store.dart';
|
||||||
import 'package:cake_wallet/core/auth_service.dart';
|
import 'package:cake_wallet/core/auth_service.dart';
|
||||||
import 'package:cake_wallet/core/key_service.dart';
|
import 'package:cake_wallet/core/key_service.dart';
|
||||||
import 'package:cake_wallet/monero/monero_wallet.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
|
||||||
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
|
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart';
|
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||||
|
@ -108,7 +106,7 @@ import 'package:cake_wallet/view_model/wallet_restoration_from_seed_vm.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_restoration_from_keys_vm.dart';
|
import 'package:cake_wallet/view_model/wallet_restoration_from_keys_vm.dart';
|
||||||
import 'package:cake_wallet/core/wallet_creation_service.dart';
|
import 'package:cake_wallet/core/wallet_creation_service.dart';
|
||||||
import 'package:cake_wallet/store/app_store.dart';
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
|
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
|
||||||
import 'package:cake_wallet/store/authentication_store.dart';
|
import 'package:cake_wallet/store/authentication_store.dart';
|
||||||
import 'package:cake_wallet/store/dashboard/trades_store.dart';
|
import 'package:cake_wallet/store/dashboard/trades_store.dart';
|
||||||
|
@ -354,7 +352,7 @@ Future setup(
|
||||||
getIt.registerFactory(() {
|
getIt.registerFactory(() {
|
||||||
final wallet = getIt.get<AppStore>().wallet;
|
final wallet = getIt.get<AppStore>().wallet;
|
||||||
|
|
||||||
if (wallet is MoneroWallet) {
|
if (wallet.type == WalletType.monero) {
|
||||||
return MoneroAccountListViewModel(wallet);
|
return MoneroAccountListViewModel(wallet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,7 +381,7 @@ Future setup(
|
||||||
getIt.registerFactoryParam<MoneroAccountEditOrCreateViewModel,
|
getIt.registerFactoryParam<MoneroAccountEditOrCreateViewModel,
|
||||||
AccountListItem, void>(
|
AccountListItem, void>(
|
||||||
(AccountListItem account, _) => MoneroAccountEditOrCreateViewModel(
|
(AccountListItem account, _) => MoneroAccountEditOrCreateViewModel(
|
||||||
(getIt.get<AppStore>().wallet as MoneroWallet).walletAddresses.accountList,
|
monero.getAccountList(getIt.get<AppStore>().wallet),
|
||||||
wallet: getIt.get<AppStore>().wallet,
|
wallet: getIt.get<AppStore>().wallet,
|
||||||
accountListItem: account));
|
accountListItem: account));
|
||||||
|
|
||||||
|
@ -467,23 +465,15 @@ Future setup(
|
||||||
getIt.registerFactory(
|
getIt.registerFactory(
|
||||||
() => ExchangeTemplatePage(getIt.get<ExchangeViewModel>()));
|
() => ExchangeTemplatePage(getIt.get<ExchangeViewModel>()));
|
||||||
|
|
||||||
getIt.registerFactory(() => MoneroWalletService(_walletInfoSource));
|
|
||||||
|
|
||||||
getIt.registerFactory(() =>
|
|
||||||
BitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource));
|
|
||||||
|
|
||||||
getIt.registerFactory(() =>
|
|
||||||
LitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource));
|
|
||||||
|
|
||||||
getIt.registerFactoryParam<WalletService, WalletType, void>(
|
getIt.registerFactoryParam<WalletService, WalletType, void>(
|
||||||
(WalletType param1, __) {
|
(WalletType param1, __) {
|
||||||
switch (param1) {
|
switch (param1) {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
return getIt.get<MoneroWalletService>();
|
return monero.createMoneroWalletService(_walletInfoSource);
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
return getIt.get<BitcoinWalletService>();
|
return bitcoin.createBitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
return getIt.get<LitecoinWalletService>();
|
return bitcoin.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
abstract class Balance {
|
|
||||||
const Balance(this.available, this.additional);
|
|
||||||
|
|
||||||
final int available;
|
|
||||||
|
|
||||||
final int additional;
|
|
||||||
|
|
||||||
String get formattedAvailableBalance;
|
|
||||||
|
|
||||||
String get formattedAdditionalBalance;
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/entities/enumerable_item.dart';
|
import 'package:cw_core/enumerable_item.dart';
|
||||||
|
|
||||||
class BalanceDisplayMode extends EnumerableItem<int> with Serializable<int> {
|
class BalanceDisplayMode extends EnumerableItem<int> with Serializable<int> {
|
||||||
const BalanceDisplayMode({@required String title, @required int raw})
|
const BalanceDisplayMode({@required String title, @required int raw})
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import 'package:cake_wallet/entities/monero_transaction_priority.dart';
|
|
||||||
|
|
||||||
double calculateEstimatedFee({MoneroTransactionPriority priority}) {
|
|
||||||
if (priority == MoneroTransactionPriority.slow) {
|
|
||||||
return 0.00002459;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (priority == MoneroTransactionPriority.regular) {
|
|
||||||
return 0.00012305;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (priority == MoneroTransactionPriority.medium) {
|
|
||||||
return 0.00024503;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (priority == MoneroTransactionPriority.fast) {
|
|
||||||
return 0.00061453;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (priority == MoneroTransactionPriority.fastest) {
|
|
||||||
return 0.0260216;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
|
@ -1,7 +1,8 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cake_wallet/utils/mobx.dart';
|
import 'package:cake_wallet/utils/mobx.dart';
|
||||||
|
import 'package:cw_core/keyable.dart';
|
||||||
|
|
||||||
part 'contact.g.dart';
|
part 'contact.g.dart';
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
|
||||||
abstract class ContactBase {
|
abstract class ContactBase {
|
||||||
String name;
|
String name;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// import 'package:hive/hive.dart';
|
// import 'package:hive/hive.dart';
|
||||||
// import 'package:mobx/mobx.dart';
|
// import 'package:mobx/mobx.dart';
|
||||||
// import 'package:cake_wallet/entities/contact.dart';
|
// import 'package:cake_wallet/entities/contact.dart';
|
||||||
// import 'package:cake_wallet/entities/crypto_currency.dart';
|
// import 'package:cw_core/crypto_currency.dart';
|
||||||
|
|
||||||
// part 'contact_model.g.dart';
|
// part 'contact_model.g.dart';
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/entities/contact.dart';
|
import 'package:cake_wallet/entities/contact.dart';
|
||||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cake_wallet/entities/record.dart';
|
import 'package:cake_wallet/entities/record.dart';
|
||||||
import 'package:cake_wallet/entities/contact_base.dart';
|
import 'package:cake_wallet/entities/contact_base.dart';
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
double cryptoAmountToDouble({num amount, num divider}) => amount / divider;
|
|
|
@ -1,125 +0,0 @@
|
||||||
import 'package:cake_wallet/entities/enumerable_item.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
|
|
||||||
part 'crypto_currency.g.dart';
|
|
||||||
|
|
||||||
@HiveType(typeId: 0)
|
|
||||||
class CryptoCurrency extends EnumerableItem<int> with Serializable<int> {
|
|
||||||
const CryptoCurrency({final String title, final int raw})
|
|
||||||
: super(title: title, raw: raw);
|
|
||||||
|
|
||||||
static const all = [
|
|
||||||
CryptoCurrency.xmr,
|
|
||||||
CryptoCurrency.ada,
|
|
||||||
CryptoCurrency.bch,
|
|
||||||
CryptoCurrency.bnb,
|
|
||||||
CryptoCurrency.btc,
|
|
||||||
CryptoCurrency.dai,
|
|
||||||
CryptoCurrency.dash,
|
|
||||||
CryptoCurrency.eos,
|
|
||||||
CryptoCurrency.eth,
|
|
||||||
CryptoCurrency.ltc,
|
|
||||||
CryptoCurrency.trx,
|
|
||||||
CryptoCurrency.usdt,
|
|
||||||
CryptoCurrency.usdterc20,
|
|
||||||
CryptoCurrency.xlm,
|
|
||||||
CryptoCurrency.xrp
|
|
||||||
];
|
|
||||||
static const xmr = CryptoCurrency(title: 'XMR', raw: 0);
|
|
||||||
static const ada = CryptoCurrency(title: 'ADA', raw: 1);
|
|
||||||
static const bch = CryptoCurrency(title: 'BCH', raw: 2);
|
|
||||||
static const bnb = CryptoCurrency(title: 'BNB BEP2', raw: 3);
|
|
||||||
static const btc = CryptoCurrency(title: 'BTC', raw: 4);
|
|
||||||
static const dai = CryptoCurrency(title: 'DAI', raw: 5);
|
|
||||||
static const dash = CryptoCurrency(title: 'DASH', raw: 6);
|
|
||||||
static const eos = CryptoCurrency(title: 'EOS', raw: 7);
|
|
||||||
static const eth = CryptoCurrency(title: 'ETH', raw: 8);
|
|
||||||
static const ltc = CryptoCurrency(title: 'LTC', raw: 9);
|
|
||||||
static const nano = CryptoCurrency(title: 'NANO', raw: 10);
|
|
||||||
static const trx = CryptoCurrency(title: 'TRX', raw: 11);
|
|
||||||
static const usdt = CryptoCurrency(title: 'USDT', raw: 12);
|
|
||||||
static const usdterc20 = CryptoCurrency(title: 'USDTERC20', raw: 13);
|
|
||||||
static const xlm = CryptoCurrency(title: 'XLM', raw: 14);
|
|
||||||
static const xrp = CryptoCurrency(title: 'XRP', raw: 15);
|
|
||||||
|
|
||||||
static CryptoCurrency deserialize({int raw}) {
|
|
||||||
switch (raw) {
|
|
||||||
case 0:
|
|
||||||
return CryptoCurrency.xmr;
|
|
||||||
case 1:
|
|
||||||
return CryptoCurrency.ada;
|
|
||||||
case 2:
|
|
||||||
return CryptoCurrency.bch;
|
|
||||||
case 3:
|
|
||||||
return CryptoCurrency.bnb;
|
|
||||||
case 4:
|
|
||||||
return CryptoCurrency.btc;
|
|
||||||
case 5:
|
|
||||||
return CryptoCurrency.dai;
|
|
||||||
case 6:
|
|
||||||
return CryptoCurrency.dash;
|
|
||||||
case 7:
|
|
||||||
return CryptoCurrency.eos;
|
|
||||||
case 8:
|
|
||||||
return CryptoCurrency.eth;
|
|
||||||
case 9:
|
|
||||||
return CryptoCurrency.ltc;
|
|
||||||
case 10:
|
|
||||||
return CryptoCurrency.nano;
|
|
||||||
case 11:
|
|
||||||
return CryptoCurrency.trx;
|
|
||||||
case 12:
|
|
||||||
return CryptoCurrency.usdt;
|
|
||||||
case 13:
|
|
||||||
return CryptoCurrency.usdterc20;
|
|
||||||
case 14:
|
|
||||||
return CryptoCurrency.xlm;
|
|
||||||
case 15:
|
|
||||||
return CryptoCurrency.xrp;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static CryptoCurrency fromString(String raw) {
|
|
||||||
switch (raw.toLowerCase()) {
|
|
||||||
case 'xmr':
|
|
||||||
return CryptoCurrency.xmr;
|
|
||||||
case 'ada':
|
|
||||||
return CryptoCurrency.ada;
|
|
||||||
case 'bch':
|
|
||||||
return CryptoCurrency.bch;
|
|
||||||
case 'bnbmainnet':
|
|
||||||
return CryptoCurrency.bnb;
|
|
||||||
case 'btc':
|
|
||||||
return CryptoCurrency.btc;
|
|
||||||
case 'dai':
|
|
||||||
return CryptoCurrency.dai;
|
|
||||||
case 'dash':
|
|
||||||
return CryptoCurrency.dash;
|
|
||||||
case 'eos':
|
|
||||||
return CryptoCurrency.eos;
|
|
||||||
case 'eth':
|
|
||||||
return CryptoCurrency.eth;
|
|
||||||
case 'ltc':
|
|
||||||
return CryptoCurrency.ltc;
|
|
||||||
case 'nano':
|
|
||||||
return CryptoCurrency.nano;
|
|
||||||
case 'trx':
|
|
||||||
return CryptoCurrency.trx;
|
|
||||||
case 'usdt':
|
|
||||||
return CryptoCurrency.usdt;
|
|
||||||
case 'usdterc20':
|
|
||||||
return CryptoCurrency.usdterc20;
|
|
||||||
case 'xlm':
|
|
||||||
return CryptoCurrency.xlm;
|
|
||||||
case 'xrp':
|
|
||||||
return CryptoCurrency.xrp;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => title;
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
|
||||||
|
|
||||||
CryptoCurrency currencyForWalletType(WalletType type) {
|
|
||||||
switch (type) {
|
|
||||||
case WalletType.bitcoin:
|
|
||||||
return CryptoCurrency.btc;
|
|
||||||
case WalletType.monero:
|
|
||||||
return CryptoCurrency.xmr;
|
|
||||||
case WalletType.litecoin:
|
|
||||||
return CryptoCurrency.ltc;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +1,21 @@
|
||||||
import 'dart:io' show File, Platform;
|
import 'dart:io' show File, Platform;
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_priority.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||||
import 'package:cake_wallet/entities/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cake_wallet/entities/secret_store_key.dart';
|
import 'package:cake_wallet/entities/secret_store_key.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cake_wallet/entities/node.dart';
|
import 'package:cw_core/node.dart';
|
||||||
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
import 'package:cake_wallet/entities/balance_display_mode.dart';
|
||||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||||
import 'package:cake_wallet/entities/node_list.dart';
|
import 'package:cake_wallet/entities/node_list.dart';
|
||||||
import 'package:cake_wallet/entities/monero_transaction_priority.dart';
|
import 'package:cake_wallet/monero/monero.dart';
|
||||||
import 'package:cake_wallet/entities/contact.dart';
|
import 'package:cake_wallet/entities/contact.dart';
|
||||||
import 'package:cake_wallet/entities/fs_migration.dart';
|
import 'package:cake_wallet/entities/fs_migration.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cake_wallet/exchange/trade.dart';
|
import 'package:cake_wallet/exchange/trade.dart';
|
||||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ Future defaultSettingsMigration(
|
||||||
FiatCurrency.usd.toString());
|
FiatCurrency.usd.toString());
|
||||||
await sharedPreferences.setInt(
|
await sharedPreferences.setInt(
|
||||||
PreferencesKey.currentTransactionPriorityKeyLegacy,
|
PreferencesKey.currentTransactionPriorityKeyLegacy,
|
||||||
MoneroTransactionPriority.standard.raw);
|
monero.getDefaultTransactionPriority().raw);
|
||||||
await sharedPreferences.setInt(
|
await sharedPreferences.setInt(
|
||||||
PreferencesKey.currentBalanceDisplayModeKey,
|
PreferencesKey.currentBalanceDisplayModeKey,
|
||||||
BalanceDisplayMode.availableBalance.raw);
|
BalanceDisplayMode.availableBalance.raw);
|
||||||
|
@ -308,7 +308,7 @@ Future<void> changeTransactionPriorityAndFeeRateKeys(
|
||||||
await sharedPreferences.setInt(
|
await sharedPreferences.setInt(
|
||||||
PreferencesKey.moneroTransactionPriority, legacyTransactionPriority);
|
PreferencesKey.moneroTransactionPriority, legacyTransactionPriority);
|
||||||
await sharedPreferences.setInt(PreferencesKey.bitcoinTransactionPriority,
|
await sharedPreferences.setInt(PreferencesKey.bitcoinTransactionPriority,
|
||||||
BitcoinTransactionPriority.medium.serialize());
|
bitcoin.getMediumTransactionPriority().serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> changeDefaultMoneroNode(
|
Future<void> changeDefaultMoneroNode(
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
abstract class EnumerableItem<T> {
|
|
||||||
const EnumerableItem({@required this.title, @required this.raw});
|
|
||||||
|
|
||||||
final T raw;
|
|
||||||
final String title;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => title;
|
|
||||||
}
|
|
||||||
|
|
||||||
mixin Serializable<T> on EnumerableItem<T> {
|
|
||||||
static Serializable deserialize<T>({T raw}) => null;
|
|
||||||
T serialize() => raw;
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:cake_wallet/entities/enumerable_item.dart';
|
import 'package:cw_core/enumerable_item.dart';
|
||||||
|
|
||||||
class FiatCurrency extends EnumerableItem<String> with Serializable<String> {
|
class FiatCurrency extends EnumerableItem<String> with Serializable<String> {
|
||||||
const FiatCurrency({String symbol}) : super(title: symbol, raw: symbol);
|
const FiatCurrency({String symbol}) : super(title: symbol, raw: symbol);
|
||||||
|
|
|
@ -6,15 +6,15 @@ import 'package:hive/hive.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:cake_wallet/core/key_service.dart';
|
import 'package:cake_wallet/core/key_service.dart';
|
||||||
import 'package:cake_wallet/entities/contact.dart';
|
import 'package:cake_wallet/entities/contact.dart';
|
||||||
import 'package:cake_wallet/entities/crypto_currency.dart';
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
import 'package:cake_wallet/entities/encrypt.dart';
|
import 'package:cake_wallet/entities/encrypt.dart';
|
||||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||||
import 'package:cake_wallet/entities/ios_legacy_helper.dart'
|
import 'package:cake_wallet/entities/ios_legacy_helper.dart'
|
||||||
as ios_legacy_helper;
|
as ios_legacy_helper;
|
||||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||||
import 'package:cake_wallet/entities/secret_store_key.dart';
|
import 'package:cake_wallet/entities/secret_store_key.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_info.dart';
|
import 'package:cw_core/wallet_info.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||||
import 'package:cake_wallet/exchange/trade.dart';
|
import 'package:cake_wallet/exchange/trade.dart';
|
||||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||||
|
|
|
@ -2,9 +2,9 @@ import 'package:cake_wallet/di.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:cake_wallet/store/app_store.dart';
|
import 'package:cake_wallet/store/app_store.dart';
|
||||||
import 'package:cake_wallet/core/key_service.dart';
|
import 'package:cake_wallet/core/key_service.dart';
|
||||||
import 'package:cake_wallet/core/wallet_service.dart';
|
import 'package:cw_core/wallet_service.dart';
|
||||||
import 'package:cake_wallet/entities/preferences_key.dart';
|
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
Future<void> loadCurrentWallet() async {
|
Future<void> loadCurrentWallet() async {
|
||||||
final appStore = getIt.get<AppStore>();
|
final appStore = getIt.get<AppStore>();
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
import 'package:cake_wallet/entities/transaction_priority.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
|
||||||
import 'package:cake_wallet/entities/enumerable_item.dart';
|
|
||||||
|
|
||||||
class MoneroTransactionPriority extends TransactionPriority {
|
|
||||||
const MoneroTransactionPriority({String title, int raw})
|
|
||||||
: super(title: title, raw: raw);
|
|
||||||
|
|
||||||
static const all = [
|
|
||||||
MoneroTransactionPriority.slow,
|
|
||||||
MoneroTransactionPriority.regular,
|
|
||||||
MoneroTransactionPriority.medium,
|
|
||||||
MoneroTransactionPriority.fast,
|
|
||||||
MoneroTransactionPriority.fastest
|
|
||||||
];
|
|
||||||
static const slow = MoneroTransactionPriority(title: 'Slow', raw: 0);
|
|
||||||
static const regular = MoneroTransactionPriority(title: 'Regular', raw: 1);
|
|
||||||
static const medium = MoneroTransactionPriority(title: 'Medium', raw: 2);
|
|
||||||
static const fast = MoneroTransactionPriority(title: 'Fast', raw: 3);
|
|
||||||
static const fastest = MoneroTransactionPriority(title: 'Fastest', raw: 4);
|
|
||||||
static const standard = slow;
|
|
||||||
|
|
||||||
|
|
||||||
static List<MoneroTransactionPriority> forWalletType(WalletType type) {
|
|
||||||
switch (type) {
|
|
||||||
case WalletType.monero:
|
|
||||||
return MoneroTransactionPriority.all;
|
|
||||||
case WalletType.bitcoin:
|
|
||||||
return [
|
|
||||||
MoneroTransactionPriority.slow,
|
|
||||||
MoneroTransactionPriority.regular,
|
|
||||||
MoneroTransactionPriority.fast
|
|
||||||
];
|
|
||||||
default:
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static MoneroTransactionPriority deserialize({int raw}) {
|
|
||||||
switch (raw) {
|
|
||||||
case 0:
|
|
||||||
return slow;
|
|
||||||
case 1:
|
|
||||||
return regular;
|
|
||||||
case 2:
|
|
||||||
return medium;
|
|
||||||
case 3:
|
|
||||||
return fast;
|
|
||||||
case 4:
|
|
||||||
return fastest;
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
switch (this) {
|
|
||||||
case MoneroTransactionPriority.slow:
|
|
||||||
return S.current.transaction_priority_slow;
|
|
||||||
case MoneroTransactionPriority.regular:
|
|
||||||
return S.current.transaction_priority_regular;
|
|
||||||
case MoneroTransactionPriority.medium:
|
|
||||||
return S.current.transaction_priority_medium;
|
|
||||||
case MoneroTransactionPriority.fast:
|
|
||||||
return S.current.transaction_priority_fast;
|
|
||||||
case MoneroTransactionPriority.fastest:
|
|
||||||
return S.current.transaction_priority_fastest;
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,131 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:cake_wallet/utils/mobx.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'package:http/http.dart' as http;
|
|
||||||
import 'package:hive/hive.dart';
|
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
|
||||||
import 'package:cake_wallet/entities/digest_request.dart';
|
|
||||||
|
|
||||||
part 'node.g.dart';
|
|
||||||
|
|
||||||
Uri createUriFromElectrumAddress(String address) =>
|
|
||||||
Uri.tryParse('tcp://$address');
|
|
||||||
|
|
||||||
@HiveType(typeId: Node.typeId)
|
|
||||||
class Node extends HiveObject with Keyable {
|
|
||||||
Node(
|
|
||||||
{@required String uri,
|
|
||||||
@required WalletType type,
|
|
||||||
this.login,
|
|
||||||
this.password,
|
|
||||||
this.useSSL}) {
|
|
||||||
uriRaw = uri;
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node.fromMap(Map map)
|
|
||||||
: uriRaw = map['uri'] as String ?? '',
|
|
||||||
login = map['login'] as String,
|
|
||||||
password = map['password'] as String,
|
|
||||||
typeRaw = map['typeRaw'] as int,
|
|
||||||
useSSL = map['useSSL'] as bool;
|
|
||||||
|
|
||||||
static const typeId = 1;
|
|
||||||
static const boxName = 'Nodes';
|
|
||||||
|
|
||||||
@HiveField(0)
|
|
||||||
String uriRaw;
|
|
||||||
|
|
||||||
@HiveField(1)
|
|
||||||
String login;
|
|
||||||
|
|
||||||
@HiveField(2)
|
|
||||||
String password;
|
|
||||||
|
|
||||||
@HiveField(3)
|
|
||||||
int typeRaw;
|
|
||||||
|
|
||||||
@HiveField(4)
|
|
||||||
bool useSSL;
|
|
||||||
|
|
||||||
bool get isSSL => useSSL ?? false;
|
|
||||||
|
|
||||||
Uri get uri {
|
|
||||||
switch (type) {
|
|
||||||
case WalletType.monero:
|
|
||||||
return Uri.http(uriRaw, '');
|
|
||||||
case WalletType.bitcoin:
|
|
||||||
return createUriFromElectrumAddress(uriRaw);
|
|
||||||
case WalletType.litecoin:
|
|
||||||
return createUriFromElectrumAddress(uriRaw);
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
dynamic get keyIndex {
|
|
||||||
_keyIndex ??= key;
|
|
||||||
return _keyIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
WalletType get type => deserializeFromInt(typeRaw);
|
|
||||||
|
|
||||||
set type(WalletType type) => typeRaw = serializeToInt(type);
|
|
||||||
|
|
||||||
dynamic _keyIndex;
|
|
||||||
|
|
||||||
Future<bool> requestNode() async {
|
|
||||||
try {
|
|
||||||
switch (type) {
|
|
||||||
case WalletType.monero:
|
|
||||||
return requestMoneroNode();
|
|
||||||
case WalletType.bitcoin:
|
|
||||||
return requestElectrumServer();
|
|
||||||
case WalletType.litecoin:
|
|
||||||
return requestElectrumServer();
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> requestMoneroNode() async {
|
|
||||||
try {
|
|
||||||
Map<String, dynamic> resBody;
|
|
||||||
|
|
||||||
if (login != null && password != null) {
|
|
||||||
final digestRequest = DigestRequest();
|
|
||||||
final response = await digestRequest.request(
|
|
||||||
uri: uri.toString(), login: login, password: password);
|
|
||||||
resBody = response.data as Map<String, dynamic>;
|
|
||||||
} else {
|
|
||||||
final rpcUri = Uri.http(uri.authority, '/json_rpc');
|
|
||||||
final headers = {'Content-type': 'application/json'};
|
|
||||||
final body =
|
|
||||||
json.encode({'jsonrpc': '2.0', 'id': '0', 'method': 'get_info'});
|
|
||||||
final response =
|
|
||||||
await http.post(rpcUri.toString(), headers: headers, body: body);
|
|
||||||
resBody = json.decode(response.body) as Map<String, dynamic>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !(resBody['result']['offline'] as bool);
|
|
||||||
} catch (_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> requestElectrumServer() async {
|
|
||||||
try {
|
|
||||||
await SecureSocket.connect(uri.host, uri.port,
|
|
||||||
timeout: Duration(seconds: 5), onBadCertificate: (_) => true);
|
|
||||||
return true;
|
|
||||||
} catch (_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import "package:yaml/yaml.dart";
|
import "package:yaml/yaml.dart";
|
||||||
import 'package:cake_wallet/entities/node.dart';
|
import 'package:cw_core/node.dart';
|
||||||
import 'package:cake_wallet/entities/wallet_type.dart';
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
|
||||||
Future<List<Node>> loadDefaultNodes() async {
|
Future<List<Node>> loadDefaultNodes() async {
|
||||||
final nodesRaw = await rootBundle.loadString('assets/node_list.yml');
|
final nodesRaw = await rootBundle.loadString('assets/node_list.yml');
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue