Move bitcoin and monero parts into self modules.

This commit is contained in:
M 2021-12-24 14:52:08 +02:00
parent e6b1da376d
commit 4535a1aaa8
126 changed files with 25452 additions and 0 deletions

74
cw_bitcoin/.gitignore vendored Normal file
View file

@ -0,0 +1,74 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3

10
cw_bitcoin/.metadata Normal file
View file

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: b1395592de68cc8ac4522094ae59956dd21a91db
channel: stable
project_type: package

3
cw_bitcoin/CHANGELOG.md Normal file
View file

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

1
cw_bitcoin/LICENSE Normal file
View file

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

14
cw_bitcoin/README.md Normal file
View file

@ -0,0 +1,14 @@
# cw_bitcoin
A new Flutter package project.
## Getting Started
This project is a starting point for a Dart
[package](https://flutter.dev/developing-packages/),
a library module containing code that can be shared easily across
multiple Flutter or Dart projects.
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View file

@ -0,0 +1,29 @@
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);
}
}

View file

@ -0,0 +1,28 @@
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});
}

View file

@ -0,0 +1,28 @@
import 'dart:math';
import 'package:intl/intl.dart';
import 'package:cw_core/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;
}

View file

@ -0,0 +1,4 @@
class BitcoinCommitTransactionException implements Exception {
@override
String toString() => 'Transaction commit is failed.';
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,5 @@
class BitcoinMnemonicIsIncorrectException implements Exception {
@override
String toString() =>
'Bitcoin mnemonic has incorrect format. Mnemonic should contain 12 or 24 words separated by space.';
}

View file

@ -0,0 +1,9 @@
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_core/output_info.dart';
class BitcoinTransactionCredentials {
BitcoinTransactionCredentials(this.outputs, this.priority);
final List<OutputInfo> outputs;
BitcoinTransactionPriority priority;
}

View file

@ -0,0 +1,4 @@
class BitcoinTransactionNoInputsException implements Exception {
@override
String toString() => 'Not enough inputs available';
}

View file

@ -0,0 +1,103 @@
import 'package:cw_core/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;
}
}

View file

@ -0,0 +1,10 @@
import 'package:cw_core/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.';
}

View file

@ -0,0 +1,24 @@
import 'package:cw_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;
}

View file

@ -0,0 +1,63 @@
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_core/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:cw_bitcoin/electrum_wallet_snapshot.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_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);
}
}

View file

@ -0,0 +1,35 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_core/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);
}

View file

@ -0,0 +1,23 @@
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/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;
}

View file

@ -0,0 +1,9 @@
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;
}

View file

@ -0,0 +1,80 @@
import 'dart:io';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
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;
}
}

View file

@ -0,0 +1,7 @@
library cw_bitcoin;
/// A Calculator.
class Calculator {
/// Returns [value] plus 1.
int addOne(int value) => value + 1;
}

View file

@ -0,0 +1,444 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:bitcoin_flutter/bitcoin_flutter.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_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;
}

View file

@ -0,0 +1,35 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/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});
}

View file

@ -0,0 +1,98 @@
import 'dart:convert';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_bitcoin/file.dart';
import 'package:cw_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;
}
}
}

View file

@ -0,0 +1,178 @@
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:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/wallet_type.dart';
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;
}
}

View file

@ -0,0 +1,548 @@
import 'dart:async';
import 'dart:convert';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:hive/hive.dart';
import 'package:cw_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:cw_bitcoin/electrum_transaction_info.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_bitcoin/address_to_output_script.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_bitcoin/electrum_transaction_history.dart';
import 'package:cw_bitcoin/bitcoin_transaction_no_inputs_exception.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_bitcoin/bitcoin_transaction_wrong_balance_exception.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_bitcoin/bitcoin_wallet_keys.dart';
import 'package:cw_bitcoin/file.dart';
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
import 'package:cw_bitcoin/script_hash.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_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();
}
}

View file

@ -0,0 +1,132 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/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());
}
}
}

View file

@ -0,0 +1,42 @@
import 'dart:convert';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/file.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/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);
}
}
}

40
cw_bitcoin/lib/file.dart Normal file
View file

@ -0,0 +1,40 @@
import 'dart:io';
import 'package:cw_core/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);
}

View file

@ -0,0 +1,9 @@
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);

View file

@ -0,0 +1,82 @@
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_bitcoin/litecoin_wallet_addresses.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_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;
}
}

View file

@ -0,0 +1,48 @@
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
import 'package:cw_core/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);
}
}
}

View file

@ -0,0 +1,81 @@
import 'dart:io';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:hive/hive.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
import 'package:cw_bitcoin/litecoin_wallet.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_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;
}
}

View file

@ -0,0 +1,60 @@
import 'package:cw_bitcoin/bitcoin_commit_transaction_exception.dart';
import 'package:flutter/foundation.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_bitcoin/electrum.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_bitcoin/electrum_transaction_info.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/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);
}

View file

@ -0,0 +1,20 @@
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;
}

55
cw_bitcoin/lib/utils.dart Normal file
View file

@ -0,0 +1,55 @@
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;

581
cw_bitcoin/pubspec.lock Normal file
View file

@ -0,0 +1,581 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
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:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.5.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
code_builder:
dependency: transitive
description:
name: code_builder
url: "https://pub.dartlang.org"
source: hosted
version: "3.7.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
ffi:
dependency: transitive
description:
name: ffi
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.2"
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:
dependency: "direct main"
description: flutter
source: sdk
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:
dependency: "direct dev"
description: flutter
source: sdk
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:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.10"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
path_provider:
dependency: "direct main"
description:
name: path_provider
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.28"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+2"
path_provider_macos:
dependency: transitive
description:
name: path_provider_macos
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.4+8"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.5"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.11.1"
platform:
dependency: transitive
description:
name: platform
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
pool:
dependency: transitive
description:
name: pool
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.0"
process:
dependency: transitive
description:
name: process
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.3"
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:
dependency: transitive
description: flutter
source: sdk
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:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
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"
win32:
dependency: transitive
description:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.2"
yaml:
dependency: transitive
description:
name: yaml
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
sdks:
dart: ">=2.12.0 <3.0.0"
flutter: ">=1.20.0"

65
cw_bitcoin/pubspec.yaml Normal file
View file

@ -0,0 +1,65 @@
name: cw_bitcoin
description: A new Flutter package project.
version: 0.0.1
author:
homepage:
environment:
sdk: ">=2.7.0 <3.0.0"
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
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:
flutter_test:
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
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# To add assets to your package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/assets-and-images/#from-packages
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# To add custom fonts to your package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/custom-fonts/#from-packages

View file

@ -0,0 +1,12 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:cw_bitcoin/cw_bitcoin.dart';
void main() {
test('adds one to input values', () {
final calculator = Calculator();
expect(calculator.addOne(2), 3);
expect(calculator.addOne(-7), -6);
expect(calculator.addOne(0), 1);
});
}

74
cw_core/.gitignore vendored Normal file
View file

@ -0,0 +1,74 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3

10
cw_core/.metadata Normal file
View file

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: b1395592de68cc8ac4522094ae59956dd21a91db
channel: stable
project_type: package

3
cw_core/CHANGELOG.md Normal file
View file

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

1
cw_core/LICENSE Normal file
View file

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

14
cw_core/README.md Normal file
View file

@ -0,0 +1,14 @@
# cw_core
A new Flutter package project.
## Getting Started
This project is a starting point for a Dart
[package](https://flutter.dev/developing-packages/),
a library module containing code that can be shared easily across
multiple Flutter or Dart projects.
For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

11
cw_core/lib/balance.dart Normal file
View file

@ -0,0 +1,11 @@
abstract class Balance {
const Balance(this.available, this.additional);
final int available;
final int additional;
String get formattedAvailableBalance;
String get formattedAdditionalBalance;
}

View file

@ -0,0 +1 @@
double cryptoAmountToDouble({num amount, num divider}) => amount / divider;

View file

@ -0,0 +1,125 @@
import 'package:cw_core/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;
}

View file

@ -0,0 +1,15 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/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;
}
}

View file

@ -0,0 +1,16 @@
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;
}

View file

@ -0,0 +1,8 @@
String formatAmount(String amount) {
if ((!amount.contains('.'))&&(!amount.contains(','))) {
return amount + '.00';
} else if ((amount.endsWith('.'))||(amount.endsWith(','))) {
return amount + '00';
}
return amount;
}

34
cw_core/lib/key.dart Normal file
View file

@ -0,0 +1,34 @@
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;
}

3
cw_core/lib/keyable.dart Normal file
View file

@ -0,0 +1,3 @@
mixin Keyable {
dynamic keyIndex;
}

132
cw_core/lib/node.dart Normal file
View file

@ -0,0 +1,132 @@
import 'dart:io';
import 'package:cw_core/keyable.dart';
import 'package:flutter/foundation.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:hive/hive.dart';
import 'package:cw_core/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 {
return false;
//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;
}
}
}

View file

@ -0,0 +1,20 @@
class OutputInfo {
const OutputInfo(
{this.fiatAmount,
this.cryptoAmount,
this.address,
this.note,
this.sendAll,
this.extractedAddress,
this.isParsedAddress,
this.formattedCryptoAmount});
final String fiatAmount;
final String cryptoAmount;
final String address;
final String note;
final String extractedAddress;
final bool sendAll;
final bool isParsedAddress;
final int formattedCryptoAmount;
}

View file

@ -0,0 +1,3 @@
bool parseBoolFromString(String string) {
return string.toString() == 'true';
}

View file

@ -0,0 +1,28 @@
import 'dart:io';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
Future<String> pathForWalletDir({@required String name, @required WalletType type}) async {
final root = await getApplicationDocumentsDirectory();
final prefix = walletTypeToString(type).toLowerCase();
final walletsDir = Directory('${root.path}/wallets');
final walletDire = Directory('${walletsDir.path}/$prefix/$name');
if (!walletDire.existsSync()) {
walletDire.createSync(recursive: true);
}
return walletDire.path;
}
Future<String> pathForWallet({@required String name, @required WalletType type}) async =>
await pathForWalletDir(name: name, type: type)
.then((path) => path + '/$name');
Future<String> outdatedAndroidPathForWalletDir({String name}) async {
final directory = await getApplicationDocumentsDirectory();
final pathDir = directory.path + '/$name';
return pathDir;
}

View file

@ -0,0 +1,7 @@
mixin PendingTransaction {
String get id;
String get amountFormatted;
String get feeFormatted;
Future<void> commit();
}

View file

@ -0,0 +1,54 @@
abstract class SyncStatus {
const SyncStatus();
double progress();
}
class SyncingSyncStatus extends SyncStatus {
SyncingSyncStatus(this.blocksLeft, this.ptc);
final double ptc;
final int blocksLeft;
@override
double progress() => ptc;
@override
String toString() => '$blocksLeft';
}
class SyncedSyncStatus extends SyncStatus {
@override
double progress() => 1.0;
}
class NotConnectedSyncStatus extends SyncStatus {
const NotConnectedSyncStatus();
@override
double progress() => 0.0;
}
class StartingSyncStatus extends SyncStatus {
@override
double progress() => 0.0;
}
class FailedSyncStatus extends SyncStatus {
@override
double progress() => 1.0;
}
class ConnectingSyncStatus extends SyncStatus {
@override
double progress() => 0.0;
}
class ConnectedSyncStatus extends SyncStatus {
@override
double progress() => 0.0;
}
class LostConnectionSyncStatus extends SyncStatus {
@override
double progress() => 1.0;
}

View file

@ -0,0 +1,17 @@
enum TransactionDirection { incoming, outgoing }
TransactionDirection parseTransactionDirectionFromInt(int raw) {
switch (raw) {
case 0: return TransactionDirection.incoming;
case 1: return TransactionDirection.outgoing;
default: return null;
}
}
TransactionDirection parseTransactionDirectionFromNumber(String raw) {
switch (raw) {
case "0": return TransactionDirection.incoming;
case "1": return TransactionDirection.outgoing;
default: return null;
}
}

View file

@ -0,0 +1,52 @@
import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/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();
}

View file

@ -0,0 +1,21 @@
import 'package:cw_core/transaction_direction.dart';
import 'package:cake_wallet/utils/mobx.dart';
import 'package:cw_core/keyable.dart';
abstract class TransactionInfo extends Object with Keyable {
String id;
int amount;
int fee;
TransactionDirection direction;
bool isPending;
DateTime date;
int height;
int confirmations;
String amountFormatted();
String fiatAmount();
String feeFormatted();
void changeFiatAmount(String amount);
@override
dynamic get keyIndex => id;
}

View file

@ -0,0 +1,6 @@
import 'package:cw_core/enumerable_item.dart';
abstract class TransactionPriority extends EnumerableItem<int>
with Serializable<int> {
const TransactionPriority({String title, int raw}) : super(title: title, raw: raw);
}

View file

@ -0,0 +1,32 @@
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;
}

View file

@ -0,0 +1,36 @@
import 'package:cw_core/wallet_info.dart';
abstract class WalletAddresses {
WalletAddresses(this.walletInfo) {
addressesMap = {};
}
final WalletInfo walletInfo;
String get address;
set address(String address);
Map<String, String> addressesMap;
Future<void> init();
Future<void> updateAddressesInBox();
Future<void> saveAddressesInBox() async {
try {
if (walletInfo == null) {
return;
}
walletInfo.address = address;
walletInfo.addresses = addressesMap;
if (walletInfo.isInBox) {
await walletInfo.save();
}
} catch (e) {
print(e.toString());
}
}
}

View file

@ -0,0 +1,71 @@
import 'package:cw_core/balance.dart';
import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_addresses.dart';
import 'package:flutter/foundation.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/transaction_history.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/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();
}

View file

@ -0,0 +1,10 @@
import 'package:cw_core/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;
}

View file

@ -0,0 +1,85 @@
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:cw_core/wallet_type.dart';
import 'dart:async';
part 'wallet_info.g.dart';
@HiveType(typeId: WalletInfo.typeId)
class WalletInfo extends HiveObject {
WalletInfo(this.id, this.name, this.type, this.isRecovery, this.restoreHeight,
this.timestamp, this.dirPath, this.path, this.address, this.yatEid,
this.yatLastUsedAddressRaw)
: _yatLastUsedAddressController = StreamController<String>.broadcast();
factory WalletInfo.external(
{@required String id,
@required String name,
@required WalletType type,
@required bool isRecovery,
@required int restoreHeight,
@required DateTime date,
@required String dirPath,
@required String path,
@required String address,
String yatEid ='',
String yatLastUsedAddressRaw = ''}) {
return WalletInfo(id, name, type, isRecovery, restoreHeight,
date.millisecondsSinceEpoch ?? 0, dirPath, path, address,
yatEid, yatLastUsedAddressRaw);
}
static const typeId = 4;
static const boxName = 'WalletInfo';
@HiveField(0)
String id;
@HiveField(1)
String name;
@HiveField(2)
WalletType type;
@HiveField(3)
bool isRecovery;
@HiveField(4)
int restoreHeight;
@HiveField(5)
int timestamp;
@HiveField(6)
String dirPath;
@HiveField(7)
String path;
@HiveField(8)
String address;
@HiveField(10)
Map<String, String> addresses;
@HiveField(11)
String yatEid;
@HiveField(12)
String yatLastUsedAddressRaw;
String get yatLastUsedAddress => yatLastUsedAddressRaw;
set yatLastUsedAddress(String address) {
yatLastUsedAddressRaw = address;
_yatLastUsedAddressController.add(address);
}
String get yatEmojiId => yatEid ?? '';
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp);
Stream<String> get yatLastUsedAddressStream => _yatLastUsedAddressController.stream;
StreamController<String> _yatLastUsedAddressController;
}

View file

@ -0,0 +1,20 @@
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/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);
}

View file

@ -0,0 +1,91 @@
import 'package:cw_core/crypto_currency.dart';
import 'package:hive/hive.dart';
part 'wallet_type.g.dart';
const walletTypes = [
WalletType.monero,
WalletType.bitcoin,
WalletType.litecoin
];
const walletTypeTypeId = 5;
@HiveType(typeId: walletTypeTypeId)
enum WalletType {
@HiveField(0)
monero,
@HiveField(1)
none,
@HiveField(2)
bitcoin,
@HiveField(3)
litecoin
}
int serializeToInt(WalletType type) {
switch (type) {
case WalletType.monero:
return 0;
case WalletType.bitcoin:
return 1;
case WalletType.litecoin:
return 2;
default:
return -1;
}
}
WalletType deserializeFromInt(int raw) {
switch (raw) {
case 0:
return WalletType.monero;
case 1:
return WalletType.bitcoin;
case 2:
return WalletType.litecoin;
default:
return null;
}
}
String walletTypeToString(WalletType type) {
switch (type) {
case WalletType.monero:
return 'Monero';
case WalletType.bitcoin:
return 'Bitcoin';
case WalletType.litecoin:
return 'Litecoin';
default:
return '';
}
}
String walletTypeToDisplayName(WalletType type) {
switch (type) {
case WalletType.monero:
return 'Monero';
case WalletType.bitcoin:
return 'Bitcoin (Electrum)';
case WalletType.litecoin:
return 'Litecoin (Electrum)';
default:
return '';
}
}
CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
switch (type) {
case WalletType.monero:
return CryptoCurrency.xmr;
case WalletType.bitcoin:
return CryptoCurrency.btc;
case WalletType.litecoin:
return CryptoCurrency.ltc;
default:
return null;
}
}

574
cw_core/pubspec.lock Normal file
View file

@ -0,0 +1,574 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
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:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.5.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
code_builder:
dependency: transitive
description:
name: code_builder
url: "https://pub.dartlang.org"
source: hosted
version: "3.7.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
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"
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:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
ffi:
dependency: transitive
description:
name: ffi
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.2"
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:
dependency: "direct main"
description: flutter
source: sdk
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:
dependency: "direct dev"
description: flutter
source: sdk
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:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.10"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
path_provider:
dependency: "direct main"
description:
name: path_provider
url: "https://pub.dartlang.org"
source: hosted
version: "1.6.28"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.1+2"
path_provider_macos:
dependency: transitive
description:
name: path_provider_macos
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.4+8"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
version: "0.0.5"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.11.1"
platform:
dependency: transitive
description:
name: platform
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
pool:
dependency: transitive
description:
name: pool
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.0"
process:
dependency: transitive
description:
name: process
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.3"
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:
dependency: transitive
description: flutter
source: sdk
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:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
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:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
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"
win32:
dependency: transitive
description:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.2"
yaml:
dependency: transitive
description:
name: yaml
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
sdks:
dart: ">=2.12.0 <3.0.0"
flutter: ">=1.20.0"

63
cw_core/pubspec.yaml Normal file
View file

@ -0,0 +1,63 @@
name: cw_core
description: A new Flutter package project.
version: 0.0.1
author:
homepage:
environment:
sdk: ">=2.7.0 <3.0.0"
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
http: ^0.12.0+2
path_provider: ^1.3.0
mobx: ^1.2.1+2
flutter_mobx: ^1.1.0+2
intl: ^0.17.0
dev_dependencies:
flutter_test:
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
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter:
# To add assets to your package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/assets-and-images/#from-packages
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# To add custom fonts to your package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/custom-fonts/#from-packages

View file

@ -0,0 +1,16 @@
import 'package:cw_monero/api/structs/account_row.dart';
class Account {
Account({this.id, this.label});
Account.fromMap(Map map)
: this.id = map['id'] == null ? 0 : int.parse(map['id'] as String),
this.label = (map['label'] ?? '') as String;
Account.fromRow(AccountRow row)
: this.id = row.getId(),
this.label = row.getLabel();
final int id;
final String label;
}

View file

@ -0,0 +1,83 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:cw_monero/api/signatures.dart';
import 'package:cw_monero/api/types.dart';
import 'package:cw_monero/api/monero_api.dart';
import 'package:cw_monero/api/structs/account_row.dart';
import 'package:flutter/foundation.dart';
import 'package:cw_monero/api/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();
}

View file

@ -0,0 +1,8 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
String convertUTF8ToString({Pointer<Utf8> pointer}) {
final str = Utf8.fromUtf8(pointer);
free(pointer);
return str;
}

View file

@ -0,0 +1,5 @@
class ConnectionToNodeException implements Exception {
ConnectionToNodeException({this.message});
final String message;
}

View file

@ -0,0 +1,8 @@
class CreationTransactionException implements Exception {
CreationTransactionException({this.message});
final String message;
@override
String toString() => message;
}

View file

@ -0,0 +1,5 @@
class SetupWalletException implements Exception {
SetupWalletException({this.message});
final String message;
}

View file

@ -0,0 +1,8 @@
class WalletCreationException implements Exception {
WalletCreationException({this.message});
final String message;
@override
String toString() => message;
}

View file

@ -0,0 +1,8 @@
class WalletOpeningException implements Exception {
WalletOpeningException({this.message});
final String message;
@override
String toString() => message;
}

View file

@ -0,0 +1,5 @@
class WalletRestoreFromKeysException implements Exception {
WalletRestoreFromKeysException({this.message});
final String message;
}

View file

@ -0,0 +1,5 @@
class WalletRestoreFromSeedException implements Exception {
WalletRestoreFromSeedException({this.message});
final String message;
}

View file

@ -0,0 +1,6 @@
import 'dart:ffi';
import 'dart:io';
final DynamicLibrary moneroApi = Platform.isAndroid
? DynamicLibrary.open("libcw_monero.so")
: DynamicLibrary.open("cw_monero.framework/cw_monero");

View file

@ -0,0 +1,8 @@
import 'package:flutter/foundation.dart';
class MoneroOutput {
MoneroOutput({@required this.address, @required this.amount});
final String address;
final String amount;
}

View file

@ -0,0 +1,122 @@
import 'dart:ffi';
import 'package:cw_monero/api/structs/pending_transaction.dart';
import 'package:cw_monero/api/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();

View file

@ -0,0 +1,11 @@
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;
}

View file

@ -0,0 +1,23 @@
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;
}

View file

@ -0,0 +1,13 @@
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;
}

View file

@ -0,0 +1,41 @@
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);
}

View file

@ -0,0 +1,8 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
class Utf8Box extends Struct {
Pointer<Utf8> value;
String getValue() => Utf8.fromUtf8(value);
}

View file

@ -0,0 +1,97 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart';
import 'package:cw_monero/api/signatures.dart';
import 'package:cw_monero/api/types.dart';
import 'package:cw_monero/api/monero_api.dart';
import 'package:cw_monero/api/structs/subaddress_row.dart';
import 'package:cw_monero/api/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();
}

View file

@ -0,0 +1,230 @@
import 'dart:ffi';
import 'package:cw_monero/api/convert_utf8_to_string.dart';
import 'package:cw_monero/api/monero_output.dart';
import 'package:cw_monero/api/structs/ut8_box.dart';
import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart';
import 'package:cw_monero/api/signatures.dart';
import 'package:cw_monero/api/types.dart';
import 'package:cw_monero/api/monero_api.dart';
import 'package:cw_monero/api/structs/transaction_info_row.dart';
import 'package:cw_monero/api/structs/pending_transaction.dart';
import 'package:cw_monero/api/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
});

View file

@ -0,0 +1,120 @@
import 'dart:ffi';
import 'package:cw_monero/api/structs/pending_transaction.dart';
import 'package:cw_monero/api/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();

View file

@ -0,0 +1,329 @@
import 'dart:async';
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:cw_monero/api/convert_utf8_to_string.dart';
import 'package:cw_monero/api/signatures.dart';
import 'package:cw_monero/api/types.dart';
import 'package:cw_monero/api/monero_api.dart';
import 'package:cw_monero/api/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();

View file

@ -0,0 +1,248 @@
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart';
import 'package:cw_monero/api/convert_utf8_to_string.dart';
import 'package:cw_monero/api/signatures.dart';
import 'package:cw_monero/api/types.dart';
import 'package:cw_monero/api/monero_api.dart';
import 'package:cw_monero/api/wallet.dart';
import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
import 'package:cw_monero/api/exceptions/wallet_creation_exception.dart';
import 'package:cw_monero/api/exceptions/wallet_restore_from_keys_exception.dart';
import 'package:cw_monero/api/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);

View file

@ -0,0 +1,120 @@
import 'package:intl/intl.dart';
// FIXME: Hardcoded values; Works only for monero
final dateFormat = DateFormat('yyyy-MM');
final dates = {
"2014-5": 18844,
"2014-6": 65406,
"2014-7": 108882,
"2014-8": 153594,
"2014-9": 198072,
"2014-10": 241088,
"2014-11": 285305,
"2014-12": 328069,
"2015-1": 372369,
"2015-2": 416505,
"2015-3": 456631,
"2015-4": 501084,
"2015-5": 543973,
"2015-6": 588326,
"2015-7": 631187,
"2015-8": 675484,
"2015-9": 719725,
"2015-10": 762463,
"2015-11": 806528,
"2015-12": 849041,
"2016-1": 892866,
"2016-2": 936736,
"2016-3": 977691,
"2016-4": 1015848,
"2016-5": 1037417,
"2016-6": 1059651,
"2016-7": 1081269,
"2016-8": 1103630,
"2016-9": 1125983,
"2016-10": 1147617,
"2016-11": 1169779,
"2016-12": 1191402,
"2017-1": 1213861,
"2017-2": 1236197,
"2017-3": 1256358,
"2017-4": 1278622,
"2017-5": 1300239,
"2017-6": 1322564,
"2017-7": 1344225,
"2017-8": 1366664,
"2017-9": 1389113,
"2017-10": 1410738,
"2017-11": 1433039,
"2017-12": 1454639,
"2018-1": 1477201,
"2018-2": 1499599,
"2018-3": 1519796,
"2018-4": 1542067,
"2018-5": 1562861,
"2018-6": 1585135,
"2018-7": 1606715,
"2018-8": 1629017,
"2018-9": 1651347,
"2018-10": 1673031,
"2018-11": 1695128,
"2018-12": 1716687,
"2019-1": 1738923,
"2019-2": 1761435,
"2019-3": 1781681,
"2019-4": 1803081,
"2019-5": 1824671,
"2019-6": 1847005,
"2019-7": 1868590,
"2019-8": 1890552,
"2019-9": 1912212,
"2019-10": 1932200,
"2019-11": 1957040,
"2019-12": 1978090,
"2020-1": 2001290,
"2020-2": 2022688,
"2020-3": 2043987,
"2020-4": 2066536,
"2020-5": 2090797,
"2020-6": 2111633,
"2020-7": 2131433,
"2020-8": 2153983,
"2020-9": 2176466,
"2020-10": 2198453,
"2020-11": 2220000
};
int getHeigthByDate({DateTime date}) {
final raw = '${date.year}' + '-' + '${date.month}';
final lastHeight = dates.values.last;
int startHeight;
int endHeight;
int height = 0;
try {
if ((dates[raw] == null)||(dates[raw] == lastHeight)) {
startHeight = dates.values.toList()[dates.length - 2];
endHeight = dates.values.toList()[dates.length - 1];
final heightPerDay = (endHeight - startHeight) / 31;
final endDateRaw = dates.keys.toList()[dates.length - 1].split('-');
final endYear = int.parse(endDateRaw[0]);
final endMonth = int.parse(endDateRaw[1]);
final endDate = DateTime(endYear, endMonth);
final differenceInDays = date.difference(endDate).inDays;
final daysHeight = (differenceInDays * heightPerDay).round();
height = endHeight + daysHeight;
} else {
startHeight = dates[raw];
final index = dates.values.toList().indexOf(startHeight);
endHeight = dates.values.toList()[index + 1];
final heightPerDay = ((endHeight - startHeight) / 31).round();
final daysHeight = (date.day - 1) * heightPerDay;
height = startHeight + daysHeight - heightPerDay;
}
} catch (e) {
print(e.toString());
}
return height;
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more