mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-01-22 10:45:08 +00:00
Add initial wownero
This commit is contained in:
parent
f2bd5e52b0
commit
603bf7b9d4
77 changed files with 22691 additions and 144 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -136,6 +136,7 @@ lib/nano/nano.dart
|
||||||
lib/polygon/polygon.dart
|
lib/polygon/polygon.dart
|
||||||
lib/solana/solana.dart
|
lib/solana/solana.dart
|
||||||
lib/tron/tron.dart
|
lib/tron/tron.dart
|
||||||
|
lib/wownero/wownero.dart
|
||||||
|
|
||||||
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
|
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_180.png
|
||||||
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
|
ios/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_120.png
|
||||||
|
|
|
@ -30,5 +30,6 @@ fi
|
||||||
source ./app_env.sh cakewallet
|
source ./app_env.sh cakewallet
|
||||||
./app_config.sh
|
./app_config.sh
|
||||||
cd ../.. && flutter pub get
|
cd ../.. && flutter pub get
|
||||||
flutter packages pub run tool/generate_localization.dart
|
#flutter packages pub run tool/generate_localization.dart
|
||||||
./model_generator.sh
|
./model_generator.sh
|
||||||
|
#cd macos && pod install
|
|
@ -4,10 +4,8 @@ import 'package:cw_core/crypto_currency.dart';
|
||||||
class AmountConverter {
|
class AmountConverter {
|
||||||
static const _moneroAmountLength = 12;
|
static const _moneroAmountLength = 12;
|
||||||
static const _moneroAmountDivider = 1000000000000;
|
static const _moneroAmountDivider = 1000000000000;
|
||||||
static const _litecoinAmountDivider = 100000000;
|
static const _wowneroAmountLength = 11;
|
||||||
static const _ethereumAmountDivider = 1000000000000000000;
|
static const _wowneroAmountDivider = 100000000000;
|
||||||
static const _dashAmountDivider = 100000000;
|
|
||||||
static const _bitcoinCashAmountDivider = 100000000;
|
|
||||||
static const _bitcoinAmountDivider = 100000000;
|
static const _bitcoinAmountDivider = 100000000;
|
||||||
static const _bitcoinAmountLength = 8;
|
static const _bitcoinAmountLength = 8;
|
||||||
static final _bitcoinAmountFormat = NumberFormat()
|
static final _bitcoinAmountFormat = NumberFormat()
|
||||||
|
@ -16,69 +14,16 @@ class AmountConverter {
|
||||||
static final _moneroAmountFormat = NumberFormat()
|
static final _moneroAmountFormat = NumberFormat()
|
||||||
..maximumFractionDigits = _moneroAmountLength
|
..maximumFractionDigits = _moneroAmountLength
|
||||||
..minimumFractionDigits = 1;
|
..minimumFractionDigits = 1;
|
||||||
|
static final _wowneroAmountFormat = NumberFormat()
|
||||||
static double amountIntToDouble(CryptoCurrency cryptoCurrency, int amount) {
|
..maximumFractionDigits = _wowneroAmountLength
|
||||||
switch (cryptoCurrency) {
|
..minimumFractionDigits = 1;
|
||||||
case CryptoCurrency.xmr:
|
|
||||||
return _moneroAmountToDouble(amount);
|
|
||||||
case CryptoCurrency.btc:
|
|
||||||
return _bitcoinAmountToDouble(amount);
|
|
||||||
case CryptoCurrency.bch:
|
|
||||||
return _bitcoinCashAmountToDouble(amount);
|
|
||||||
case CryptoCurrency.dash:
|
|
||||||
return _dashAmountToDouble(amount);
|
|
||||||
case CryptoCurrency.eth:
|
|
||||||
return _ethereumAmountToDouble(amount);
|
|
||||||
case CryptoCurrency.ltc:
|
|
||||||
return _litecoinAmountToDouble(amount);
|
|
||||||
case CryptoCurrency.xhv:
|
|
||||||
case CryptoCurrency.xag:
|
|
||||||
case CryptoCurrency.xau:
|
|
||||||
case CryptoCurrency.xaud:
|
|
||||||
case CryptoCurrency.xbtc:
|
|
||||||
case CryptoCurrency.xcad:
|
|
||||||
case CryptoCurrency.xchf:
|
|
||||||
case CryptoCurrency.xcny:
|
|
||||||
case CryptoCurrency.xeur:
|
|
||||||
case CryptoCurrency.xgbp:
|
|
||||||
case CryptoCurrency.xjpy:
|
|
||||||
case CryptoCurrency.xnok:
|
|
||||||
case CryptoCurrency.xnzd:
|
|
||||||
case CryptoCurrency.xusd:
|
|
||||||
return _moneroAmountToDouble(amount);
|
|
||||||
default:
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int amountStringToInt(CryptoCurrency cryptoCurrency, String amount) {
|
|
||||||
switch (cryptoCurrency) {
|
|
||||||
case CryptoCurrency.xmr:
|
|
||||||
return _moneroParseAmount(amount);
|
|
||||||
case CryptoCurrency.xhv:
|
|
||||||
case CryptoCurrency.xag:
|
|
||||||
case CryptoCurrency.xau:
|
|
||||||
case CryptoCurrency.xaud:
|
|
||||||
case CryptoCurrency.xbtc:
|
|
||||||
case CryptoCurrency.xcad:
|
|
||||||
case CryptoCurrency.xchf:
|
|
||||||
case CryptoCurrency.xcny:
|
|
||||||
case CryptoCurrency.xeur:
|
|
||||||
case CryptoCurrency.xgbp:
|
|
||||||
case CryptoCurrency.xjpy:
|
|
||||||
case CryptoCurrency.xnok:
|
|
||||||
case CryptoCurrency.xnzd:
|
|
||||||
case CryptoCurrency.xusd:
|
|
||||||
return _moneroParseAmount(amount);
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static String amountIntToString(CryptoCurrency cryptoCurrency, int amount) {
|
static String amountIntToString(CryptoCurrency cryptoCurrency, int amount) {
|
||||||
switch (cryptoCurrency) {
|
switch (cryptoCurrency) {
|
||||||
case CryptoCurrency.xmr:
|
case CryptoCurrency.xmr:
|
||||||
return _moneroAmountToString(amount);
|
return _moneroAmountToString(amount);
|
||||||
|
case CryptoCurrency.wow:
|
||||||
|
return _wowneroAmountToString(amount);
|
||||||
case CryptoCurrency.btc:
|
case CryptoCurrency.btc:
|
||||||
case CryptoCurrency.bch:
|
case CryptoCurrency.bch:
|
||||||
case CryptoCurrency.ltc:
|
case CryptoCurrency.ltc:
|
||||||
|
@ -106,34 +51,12 @@ class AmountConverter {
|
||||||
static double cryptoAmountToDouble({required num amount, required num divider}) =>
|
static double cryptoAmountToDouble({required num amount, required num divider}) =>
|
||||||
amount / divider;
|
amount / divider;
|
||||||
|
|
||||||
static String _moneroAmountToString(int amount) => _moneroAmountFormat.format(
|
static String _moneroAmountToString(int amount) => _moneroAmountFormat
|
||||||
cryptoAmountToDouble(amount: amount, divider: _moneroAmountDivider));
|
.format(cryptoAmountToDouble(amount: amount, divider: _moneroAmountDivider));
|
||||||
|
|
||||||
static double _moneroAmountToDouble(int amount) =>
|
static String _bitcoinAmountToString(int amount) => _bitcoinAmountFormat
|
||||||
cryptoAmountToDouble(amount: amount, divider: _moneroAmountDivider);
|
.format(cryptoAmountToDouble(amount: amount, divider: _bitcoinAmountDivider));
|
||||||
|
|
||||||
static int _moneroParseAmount(String amount) =>
|
static String _wowneroAmountToString(int amount) => _wowneroAmountFormat
|
||||||
_moneroAmountFormat.parse(amount).toInt();
|
.format(cryptoAmountToDouble(amount: amount, divider: _wowneroAmountDivider));
|
||||||
|
|
||||||
static String _bitcoinAmountToString(int amount) =>
|
|
||||||
_bitcoinAmountFormat.format(
|
|
||||||
cryptoAmountToDouble(amount: amount, divider: _bitcoinAmountDivider));
|
|
||||||
|
|
||||||
static double _bitcoinAmountToDouble(int amount) =>
|
|
||||||
cryptoAmountToDouble(amount: amount, divider: _bitcoinAmountDivider);
|
|
||||||
|
|
||||||
static int _doubleToBitcoinAmount(double amount) =>
|
|
||||||
(amount * _bitcoinAmountDivider).toInt();
|
|
||||||
|
|
||||||
static double _bitcoinCashAmountToDouble(int amount) =>
|
|
||||||
cryptoAmountToDouble(amount: amount, divider: _bitcoinCashAmountDivider);
|
|
||||||
|
|
||||||
static double _dashAmountToDouble(int amount) =>
|
|
||||||
cryptoAmountToDouble(amount: amount, divider: _dashAmountDivider);
|
|
||||||
|
|
||||||
static double _ethereumAmountToDouble(num amount) =>
|
|
||||||
cryptoAmountToDouble(amount: amount, divider: _ethereumAmountDivider);
|
|
||||||
|
|
||||||
static double _litecoinAmountToDouble(int amount) =>
|
|
||||||
cryptoAmountToDouble(amount: amount, divider: _litecoinAmountDivider);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -221,6 +221,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
||||||
static const usdtSol = CryptoCurrency(title: 'USDT', tag: 'SOL', fullName: 'USDT Tether', raw: 91, name: 'usdtsol', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
|
static const usdtSol = CryptoCurrency(title: 'USDT', tag: 'SOL', fullName: 'USDT Tether', raw: 91, name: 'usdtsol', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
|
||||||
static const usdcTrc20 = CryptoCurrency(title: 'USDC', tag: 'TRX', fullName: 'USDC Coin', raw: 92, name: 'usdctrc20', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
|
static const usdcTrc20 = CryptoCurrency(title: 'USDC', tag: 'TRX', fullName: 'USDC Coin', raw: 92, name: 'usdctrc20', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
|
||||||
static const tbtc = CryptoCurrency(title: 'tBTC', fullName: 'Testnet Bitcoin', raw: 93, name: 'tbtc', iconPath: 'assets/images/tbtc.png', decimals: 8);
|
static const tbtc = CryptoCurrency(title: 'tBTC', fullName: 'Testnet Bitcoin', raw: 93, name: 'tbtc', iconPath: 'assets/images/tbtc.png', decimals: 8);
|
||||||
|
static const wow = CryptoCurrency(title: 'WOW', fullName: 'Wownero', raw: 94, name: 'wow', iconPath: 'assets/images/wownero_icon.png', decimals: 11);
|
||||||
|
|
||||||
|
|
||||||
static final Map<int, CryptoCurrency> _rawCurrencyMap =
|
static final Map<int, CryptoCurrency> _rawCurrencyMap =
|
||||||
|
|
|
@ -28,7 +28,9 @@ CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) {
|
||||||
return CryptoCurrency.sol;
|
return CryptoCurrency.sol;
|
||||||
case WalletType.tron:
|
case WalletType.tron:
|
||||||
return CryptoCurrency.trx;
|
return CryptoCurrency.trx;
|
||||||
default:
|
case WalletType.wownero:
|
||||||
|
return CryptoCurrency.wow;
|
||||||
|
case WalletType.none:
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
|
'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType');
|
||||||
}
|
}
|
||||||
|
|
|
@ -296,3 +296,81 @@ DateTime getDateByBitcoinHeight(int height) {
|
||||||
|
|
||||||
return estimatedDate;
|
return estimatedDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: enhance all of this global const lists
|
||||||
|
const wowDates = {
|
||||||
|
"2023-12": 583048,
|
||||||
|
"2023-11": 575048,
|
||||||
|
"2023-10": 566048,
|
||||||
|
"2023-09": 558048,
|
||||||
|
"2023-08": 549048,
|
||||||
|
"2023-07": 540048,
|
||||||
|
"2023-06": 532048,
|
||||||
|
"2023-05": 523048,
|
||||||
|
"2023-04": 514048,
|
||||||
|
"2023-03": 505048,
|
||||||
|
"2023-02": 497048,
|
||||||
|
"2023-01": 488048,
|
||||||
|
"2022-12": 479048,
|
||||||
|
"2022-11": 471048,
|
||||||
|
"2022-10": 462048,
|
||||||
|
"2022-09": 453048,
|
||||||
|
"2022-08": 444048,
|
||||||
|
"2022-07": 435048,
|
||||||
|
"2022-06": 427048,
|
||||||
|
"2022-05": 418048,
|
||||||
|
"2022-04": 410048,
|
||||||
|
"2022-03": 401048,
|
||||||
|
"2022-02": 393048,
|
||||||
|
"2022-01": 384048,
|
||||||
|
"2021-12": 375048,
|
||||||
|
"2021-11": 367048,
|
||||||
|
"2021-10": 358048,
|
||||||
|
"2021-09": 349048,
|
||||||
|
"2021-08": 340048,
|
||||||
|
"2021-07": 331048,
|
||||||
|
"2021-06": 322048,
|
||||||
|
"2021-05": 313048,
|
||||||
|
"2021-04": 305048,
|
||||||
|
"2021-03": 295048,
|
||||||
|
"2021-02": 287048,
|
||||||
|
"2021-01": 279148,
|
||||||
|
"2020-10": 252000,
|
||||||
|
"2020-09": 243000,
|
||||||
|
"2020-08": 234000,
|
||||||
|
"2020-07": 225000,
|
||||||
|
"2020-06": 217500,
|
||||||
|
"2020-05": 208500,
|
||||||
|
"2020-04": 199500,
|
||||||
|
"2020-03": 190500,
|
||||||
|
"2020-02": 183000,
|
||||||
|
"2020-01": 174000,
|
||||||
|
"2019-12": 165000,
|
||||||
|
"2019-11": 156000,
|
||||||
|
"2019-10": 147000,
|
||||||
|
"2019-09": 138000,
|
||||||
|
"2019-08": 129000,
|
||||||
|
"2019-07": 120000,
|
||||||
|
"2019-06": 112500,
|
||||||
|
"2019-05": 103500,
|
||||||
|
"2019-04": 94500,
|
||||||
|
"2019-03": 85500,
|
||||||
|
"2019-02": 79500,
|
||||||
|
"2019-01": 73500,
|
||||||
|
"2018-12": 67500,
|
||||||
|
"2018-11": 61500,
|
||||||
|
"2018-10": 52500,
|
||||||
|
"2018-09": 45000,
|
||||||
|
"2018-08": 36000,
|
||||||
|
"2018-07": 27000,
|
||||||
|
"2018-06": 18000,
|
||||||
|
"2018-05": 9000,
|
||||||
|
"2018-04": 1
|
||||||
|
};
|
||||||
|
|
||||||
|
int getWowneroHeightByDate({required DateTime date}) {
|
||||||
|
String closestKey =
|
||||||
|
wowDates.keys.firstWhere((key) => formatMapKey(key).isBefore(date), orElse: () => '');
|
||||||
|
|
||||||
|
return wowDates[closestKey] ?? 0;
|
||||||
|
}
|
|
@ -79,6 +79,7 @@ class Node extends HiveObject with Keyable {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
|
case WalletType.wownero:
|
||||||
return Uri.http(uriRaw, '');
|
return Uri.http(uriRaw, '');
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
|
@ -96,7 +97,7 @@ class Node extends HiveObject with Keyable {
|
||||||
case WalletType.solana:
|
case WalletType.solana:
|
||||||
case WalletType.tron:
|
case WalletType.tron:
|
||||||
return Uri.https(uriRaw, path ?? '');
|
return Uri.https(uriRaw, path ?? '');
|
||||||
default:
|
case WalletType.none:
|
||||||
throw Exception('Unexpected type ${type.toString()} for Node uri');
|
throw Exception('Unexpected type ${type.toString()} for Node uri');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,6 +144,7 @@ class Node extends HiveObject with Keyable {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
|
case WalletType.wownero:
|
||||||
return requestMoneroNode();
|
return requestMoneroNode();
|
||||||
case WalletType.nano:
|
case WalletType.nano:
|
||||||
case WalletType.banano:
|
case WalletType.banano:
|
||||||
|
@ -155,7 +157,7 @@ class Node extends HiveObject with Keyable {
|
||||||
case WalletType.solana:
|
case WalletType.solana:
|
||||||
case WalletType.tron:
|
case WalletType.tron:
|
||||||
return requestElectrumServer();
|
return requestElectrumServer();
|
||||||
default:
|
case WalletType.none:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
|
|
@ -54,7 +54,10 @@ enum WalletType {
|
||||||
solana,
|
solana,
|
||||||
|
|
||||||
@HiveField(11)
|
@HiveField(11)
|
||||||
tron
|
tron,
|
||||||
|
|
||||||
|
@HiveField(12)
|
||||||
|
wownero,
|
||||||
}
|
}
|
||||||
|
|
||||||
int serializeToInt(WalletType type) {
|
int serializeToInt(WalletType type) {
|
||||||
|
@ -81,7 +84,9 @@ int serializeToInt(WalletType type) {
|
||||||
return 9;
|
return 9;
|
||||||
case WalletType.tron:
|
case WalletType.tron:
|
||||||
return 10;
|
return 10;
|
||||||
default:
|
case WalletType.wownero:
|
||||||
|
return 11;
|
||||||
|
case WalletType.none:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,6 +115,8 @@ WalletType deserializeFromInt(int raw) {
|
||||||
return WalletType.solana;
|
return WalletType.solana;
|
||||||
case 10:
|
case 10:
|
||||||
return WalletType.tron;
|
return WalletType.tron;
|
||||||
|
case 11:
|
||||||
|
return WalletType.wownero;
|
||||||
default:
|
default:
|
||||||
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
|
throw Exception('Unexpected token: $raw for WalletType deserializeFromInt');
|
||||||
}
|
}
|
||||||
|
@ -139,7 +146,9 @@ String walletTypeToString(WalletType type) {
|
||||||
return 'Solana';
|
return 'Solana';
|
||||||
case WalletType.tron:
|
case WalletType.tron:
|
||||||
return 'Tron';
|
return 'Tron';
|
||||||
default:
|
case WalletType.wownero:
|
||||||
|
return 'Wownero';
|
||||||
|
case WalletType.none:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,7 +177,9 @@ String walletTypeToDisplayName(WalletType type) {
|
||||||
return 'Solana (SOL)';
|
return 'Solana (SOL)';
|
||||||
case WalletType.tron:
|
case WalletType.tron:
|
||||||
return 'Tron (TRX)';
|
return 'Tron (TRX)';
|
||||||
default:
|
case WalletType.wownero:
|
||||||
|
return 'Wownero (WOW)';
|
||||||
|
case WalletType.none:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -200,7 +211,9 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal
|
||||||
return CryptoCurrency.sol;
|
return CryptoCurrency.sol;
|
||||||
case WalletType.tron:
|
case WalletType.tron:
|
||||||
return CryptoCurrency.trx;
|
return CryptoCurrency.trx;
|
||||||
default:
|
case WalletType.wownero:
|
||||||
|
return CryptoCurrency.wow;
|
||||||
|
case WalletType.none:
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
|
'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency');
|
||||||
}
|
}
|
||||||
|
|
18
cw_core/lib/wownero_amount_format.dart
Normal file
18
cw_core/lib/wownero_amount_format.dart
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:cw_core/crypto_amount_format.dart';
|
||||||
|
|
||||||
|
const wowneroAmountLength = 11;
|
||||||
|
const wowneroAmountDivider = 100000000000;
|
||||||
|
final wowneroAmountFormat = NumberFormat()
|
||||||
|
..maximumFractionDigits = wowneroAmountLength
|
||||||
|
..minimumFractionDigits = 1;
|
||||||
|
|
||||||
|
String wowneroAmountToString({required int amount}) => wowneroAmountFormat
|
||||||
|
.format(cryptoAmountToDouble(amount: amount, divider: wowneroAmountDivider))
|
||||||
|
.replaceAll(',', '');
|
||||||
|
|
||||||
|
double wowneroAmountToDouble({required int amount}) =>
|
||||||
|
cryptoAmountToDouble(amount: amount, divider: wowneroAmountDivider);
|
||||||
|
|
||||||
|
int wowneroParseAmount({required String amount}) =>
|
||||||
|
(double.parse(amount) * wowneroAmountDivider).round();
|
38
cw_core/lib/wownero_balance.dart
Normal file
38
cw_core/lib/wownero_balance.dart
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import 'package:cw_core/balance.dart';
|
||||||
|
import 'package:cw_core/wownero_amount_format.dart';
|
||||||
|
|
||||||
|
class WowneroBalance extends Balance {
|
||||||
|
WowneroBalance({required this.fullBalance, required this.unlockedBalance, this.frozenBalance = 0})
|
||||||
|
: formattedFullBalance = wowneroAmountToString(amount: fullBalance),
|
||||||
|
formattedUnlockedBalance = wowneroAmountToString(amount: unlockedBalance - frozenBalance),
|
||||||
|
formattedLockedBalance =
|
||||||
|
wowneroAmountToString(amount: frozenBalance + fullBalance - unlockedBalance),
|
||||||
|
super(unlockedBalance, fullBalance);
|
||||||
|
|
||||||
|
WowneroBalance.fromString(
|
||||||
|
{required this.formattedFullBalance,
|
||||||
|
required this.formattedUnlockedBalance,
|
||||||
|
this.formattedLockedBalance = '0.0'})
|
||||||
|
: fullBalance = wowneroParseAmount(amount: formattedFullBalance),
|
||||||
|
unlockedBalance = wowneroParseAmount(amount: formattedUnlockedBalance),
|
||||||
|
frozenBalance = wowneroParseAmount(amount: formattedLockedBalance),
|
||||||
|
super(wowneroParseAmount(amount: formattedUnlockedBalance),
|
||||||
|
wowneroParseAmount(amount: formattedFullBalance));
|
||||||
|
|
||||||
|
final int fullBalance;
|
||||||
|
final int unlockedBalance;
|
||||||
|
final int frozenBalance;
|
||||||
|
final String formattedFullBalance;
|
||||||
|
final String formattedUnlockedBalance;
|
||||||
|
final String formattedLockedBalance;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get formattedUnAvailableBalance =>
|
||||||
|
formattedLockedBalance == '0.0' ? '' : formattedLockedBalance;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get formattedAvailableBalance => formattedUnlockedBalance;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get formattedAdditionalBalance => formattedFullBalance;
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
|
||||||
import 'package:cw_core/monero_wallet_utils.dart';
|
import 'package:cw_core/monero_wallet_utils.dart';
|
||||||
import 'package:cw_core/pathForWallet.dart';
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
import 'package:cw_core/unspent_coins_info.dart';
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
|
|
|
@ -438,8 +438,8 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "6a17a405a1a260fa228b2f4fc94044088a4335ac"
|
ref: "200ad221441c6481439c966288b6e8834769ed21"
|
||||||
resolved-ref: "6a17a405a1a260fa228b2f4fc94044088a4335ac"
|
resolved-ref: "200ad221441c6481439c966288b6e8834769ed21"
|
||||||
url: "https://git.mrcyjanek.net/mrcyjanek/monero.dart"
|
url: "https://git.mrcyjanek.net/mrcyjanek/monero.dart"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
|
5
cw_wownero/.gitignore
vendored
Normal file
5
cw_wownero/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.DS_Store
|
||||||
|
.dart_tool/
|
||||||
|
|
||||||
|
.packages
|
||||||
|
.pub/
|
30
cw_wownero/.metadata
Normal file
30
cw_wownero/.metadata
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
project_type: plugin
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||||
|
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||||
|
- platform: macos
|
||||||
|
create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||||
|
base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
3
cw_wownero/CHANGELOG.md
Normal file
3
cw_wownero/CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
## 0.0.1
|
||||||
|
|
||||||
|
* TODO: Describe initial release.
|
21
cw_wownero/LICENSE
Normal file
21
cw_wownero/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Cake Technologies LLC
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
5
cw_wownero/README.md
Normal file
5
cw_wownero/README.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# cw_wownero
|
||||||
|
|
||||||
|
This project is part of Cake Wallet app.
|
||||||
|
|
||||||
|
Copyright (c) 2020 Cake Technologies LLC.
|
4
cw_wownero/analysis_options.yaml
Normal file
4
cw_wownero/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
73
cw_wownero/lib/api/account_list.dart
Normal file
73
cw_wownero/lib/api/account_list.dart
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import 'package:cw_wownero/api/wallet.dart';
|
||||||
|
import 'package:monero/wownero.dart' as wownero;
|
||||||
|
|
||||||
|
wownero.wallet? wptr = null;
|
||||||
|
|
||||||
|
int _wlptrForW = 0;
|
||||||
|
wownero.WalletListener? _wlptr = null;
|
||||||
|
|
||||||
|
wownero.WalletListener getWlptr() {
|
||||||
|
if (wptr!.address == _wlptrForW) return _wlptr!;
|
||||||
|
_wlptrForW = wptr!.address;
|
||||||
|
_wlptr = wownero.WOWNERO_cw_getWalletListener(wptr!);
|
||||||
|
return _wlptr!;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
wownero.SubaddressAccount? subaddressAccount;
|
||||||
|
|
||||||
|
bool isUpdating = false;
|
||||||
|
|
||||||
|
void refreshAccounts() {
|
||||||
|
try {
|
||||||
|
isUpdating = true;
|
||||||
|
subaddressAccount = wownero.Wallet_subaddressAccount(wptr!);
|
||||||
|
wownero.SubaddressAccount_refresh(subaddressAccount!);
|
||||||
|
isUpdating = false;
|
||||||
|
} catch (e) {
|
||||||
|
isUpdating = false;
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<wownero.SubaddressAccountRow> getAllAccount() {
|
||||||
|
// final size = wownero.Wallet_numSubaddressAccounts(wptr!);
|
||||||
|
refreshAccounts();
|
||||||
|
int size = wownero.SubaddressAccount_getAll_size(subaddressAccount!);
|
||||||
|
print("size: $size");
|
||||||
|
if (size == 0) {
|
||||||
|
wownero.Wallet_addSubaddressAccount(wptr!);
|
||||||
|
return getAllAccount();
|
||||||
|
}
|
||||||
|
return List.generate(size, (index) {
|
||||||
|
return wownero.SubaddressAccount_getAll_byIndex(subaddressAccount!, index: index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void addAccountSync({required String label}) {
|
||||||
|
wownero.Wallet_addSubaddressAccount(wptr!, label: label);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLabelForAccountSync({required int accountIndex, required String label}) {
|
||||||
|
// TODO(mrcyjanek): this may be wrong function?
|
||||||
|
wownero.Wallet_setSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: 0, label: label);
|
||||||
|
}
|
||||||
|
|
||||||
|
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({required String label}) async {
|
||||||
|
_addAccount(label);
|
||||||
|
await store();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setLabelForAccount({required int accountIndex, required String label}) async {
|
||||||
|
_setLabelForAccount({'accountIndex': accountIndex, 'label': label});
|
||||||
|
await store();
|
||||||
|
}
|
17
cw_wownero/lib/api/coins_info.dart
Normal file
17
cw_wownero/lib/api/coins_info.dart
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:cw_wownero/api/account_list.dart';
|
||||||
|
import 'package:monero/wownero.dart' as wownero;
|
||||||
|
|
||||||
|
wownero.Coins? coins = null;
|
||||||
|
|
||||||
|
void refreshCoins(int accountIndex) {
|
||||||
|
coins = wownero.Wallet_coins(wptr!);
|
||||||
|
wownero.Coins_refresh(coins!);
|
||||||
|
}
|
||||||
|
|
||||||
|
int countOfCoins() => wownero.Coins_count(coins!);
|
||||||
|
|
||||||
|
wownero.CoinsInfo getCoin(int index) => wownero.Coins_coin(coins!, index);
|
||||||
|
|
||||||
|
void freezeCoin(int index) => wownero.Coins_setFrozen(coins!, index: index);
|
||||||
|
|
||||||
|
void thawCoin(int index) => wownero.Coins_thaw(coins!, index: index);
|
|
@ -0,0 +1,5 @@
|
||||||
|
class ConnectionToNodeException implements Exception {
|
||||||
|
ConnectionToNodeException({required this.message});
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
class CreationTransactionException implements Exception {
|
||||||
|
CreationTransactionException({required this.message});
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => message;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
class SetupWalletException implements Exception {
|
||||||
|
SetupWalletException({required this.message});
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
class WalletCreationException implements Exception {
|
||||||
|
WalletCreationException({required this.message});
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => message;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
class WalletOpeningException implements Exception {
|
||||||
|
WalletOpeningException({required this.message});
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => message;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
class WalletRestoreFromKeysException implements Exception {
|
||||||
|
WalletRestoreFromKeysException({required this.message});
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
class WalletRestoreFromSeedException implements Exception {
|
||||||
|
WalletRestoreFromSeedException({required this.message});
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
}
|
12
cw_wownero/lib/api/structs/account_row.dart
Normal file
12
cw_wownero/lib/api/structs/account_row.dart
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import 'dart:ffi';
|
||||||
|
import 'package:ffi/ffi.dart';
|
||||||
|
|
||||||
|
class AccountRow extends Struct {
|
||||||
|
@Int64()
|
||||||
|
external int id;
|
||||||
|
|
||||||
|
external Pointer<Utf8> label;
|
||||||
|
|
||||||
|
String getLabel() => label.toDartString();
|
||||||
|
int getId() => id;
|
||||||
|
}
|
73
cw_wownero/lib/api/structs/coins_info_row.dart
Normal file
73
cw_wownero/lib/api/structs/coins_info_row.dart
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import 'dart:ffi';
|
||||||
|
import 'package:ffi/ffi.dart';
|
||||||
|
|
||||||
|
class CoinsInfoRow extends Struct {
|
||||||
|
@Int64()
|
||||||
|
external int blockHeight;
|
||||||
|
|
||||||
|
external Pointer<Utf8> hash;
|
||||||
|
|
||||||
|
@Uint64()
|
||||||
|
external int internalOutputIndex;
|
||||||
|
|
||||||
|
@Uint64()
|
||||||
|
external int globalOutputIndex;
|
||||||
|
|
||||||
|
@Int8()
|
||||||
|
external int spent;
|
||||||
|
|
||||||
|
@Int8()
|
||||||
|
external int frozen;
|
||||||
|
|
||||||
|
@Uint64()
|
||||||
|
external int spentHeight;
|
||||||
|
|
||||||
|
@Uint64()
|
||||||
|
external int amount;
|
||||||
|
|
||||||
|
@Int8()
|
||||||
|
external int rct;
|
||||||
|
|
||||||
|
@Int8()
|
||||||
|
external int keyImageKnown;
|
||||||
|
|
||||||
|
@Uint64()
|
||||||
|
external int pkIndex;
|
||||||
|
|
||||||
|
@Uint32()
|
||||||
|
external int subaddrIndex;
|
||||||
|
|
||||||
|
@Uint32()
|
||||||
|
external int subaddrAccount;
|
||||||
|
|
||||||
|
external Pointer<Utf8> address;
|
||||||
|
|
||||||
|
external Pointer<Utf8> addressLabel;
|
||||||
|
|
||||||
|
external Pointer<Utf8> keyImage;
|
||||||
|
|
||||||
|
@Uint64()
|
||||||
|
external int unlockTime;
|
||||||
|
|
||||||
|
@Int8()
|
||||||
|
external int unlocked;
|
||||||
|
|
||||||
|
external Pointer<Utf8> pubKey;
|
||||||
|
|
||||||
|
@Int8()
|
||||||
|
external int coinbase;
|
||||||
|
|
||||||
|
external Pointer<Utf8> description;
|
||||||
|
|
||||||
|
String getHash() => hash.toDartString();
|
||||||
|
|
||||||
|
String getAddress() => address.toDartString();
|
||||||
|
|
||||||
|
String getAddressLabel() => addressLabel.toDartString();
|
||||||
|
|
||||||
|
String getKeyImage() => keyImage.toDartString();
|
||||||
|
|
||||||
|
String getPubKey() => pubKey.toDartString();
|
||||||
|
|
||||||
|
String getDescription() => description.toDartString();
|
||||||
|
}
|
17
cw_wownero/lib/api/structs/pending_transaction.dart
Normal file
17
cw_wownero/lib/api/structs/pending_transaction.dart
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
class PendingTransactionDescription {
|
||||||
|
PendingTransactionDescription({
|
||||||
|
required this.amount,
|
||||||
|
required this.fee,
|
||||||
|
required this.hash,
|
||||||
|
required this.hex,
|
||||||
|
required this.txKey,
|
||||||
|
required this.pointerAddress});
|
||||||
|
|
||||||
|
final int amount;
|
||||||
|
final int fee;
|
||||||
|
final String hash;
|
||||||
|
final String hex;
|
||||||
|
final String txKey;
|
||||||
|
final int pointerAddress;
|
||||||
|
}
|
15
cw_wownero/lib/api/structs/subaddress_row.dart
Normal file
15
cw_wownero/lib/api/structs/subaddress_row.dart
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
import 'dart:ffi';
|
||||||
|
import 'package:ffi/ffi.dart';
|
||||||
|
|
||||||
|
class SubaddressRow extends Struct {
|
||||||
|
@Int64()
|
||||||
|
external int id;
|
||||||
|
|
||||||
|
external Pointer<Utf8> address;
|
||||||
|
|
||||||
|
external Pointer<Utf8> label;
|
||||||
|
|
||||||
|
String getLabel() => label.toDartString();
|
||||||
|
String getAddress() => address.toDartString();
|
||||||
|
int getId() => id;
|
||||||
|
}
|
41
cw_wownero/lib/api/structs/transaction_info_row.dart
Normal file
41
cw_wownero/lib/api/structs/transaction_info_row.dart
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import 'dart:ffi';
|
||||||
|
import 'package:ffi/ffi.dart';
|
||||||
|
|
||||||
|
class TransactionInfoRow extends Struct {
|
||||||
|
@Uint64()
|
||||||
|
external int amount;
|
||||||
|
|
||||||
|
@Uint64()
|
||||||
|
external int fee;
|
||||||
|
|
||||||
|
@Uint64()
|
||||||
|
external int blockHeight;
|
||||||
|
|
||||||
|
@Uint64()
|
||||||
|
external int confirmations;
|
||||||
|
|
||||||
|
@Uint32()
|
||||||
|
external int subaddrAccount;
|
||||||
|
|
||||||
|
@Int8()
|
||||||
|
external int direction;
|
||||||
|
|
||||||
|
@Int8()
|
||||||
|
external int isPending;
|
||||||
|
|
||||||
|
@Uint32()
|
||||||
|
external int subaddrIndex;
|
||||||
|
|
||||||
|
external Pointer<Utf8> hash;
|
||||||
|
|
||||||
|
external Pointer<Utf8> paymentId;
|
||||||
|
|
||||||
|
@Int64()
|
||||||
|
external int datetime;
|
||||||
|
|
||||||
|
int getDatetime() => datetime;
|
||||||
|
int getAmount() => amount >= 0 ? amount : amount * -1;
|
||||||
|
bool getIsPending() => isPending != 0;
|
||||||
|
String getHash() => hash.toDartString();
|
||||||
|
String getPaymentId() => paymentId.toDartString();
|
||||||
|
}
|
8
cw_wownero/lib/api/structs/ut8_box.dart
Normal file
8
cw_wownero/lib/api/structs/ut8_box.dart
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import 'dart:ffi';
|
||||||
|
import 'package:ffi/ffi.dart';
|
||||||
|
|
||||||
|
class Utf8Box extends Struct {
|
||||||
|
external Pointer<Utf8> value;
|
||||||
|
|
||||||
|
String getValue() => value.toDartString();
|
||||||
|
}
|
91
cw_wownero/lib/api/subaddress_list.dart
Normal file
91
cw_wownero/lib/api/subaddress_list.dart
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import 'package:cw_wownero/api/account_list.dart';
|
||||||
|
import 'package:cw_wownero/api/wallet.dart';
|
||||||
|
import 'package:monero/wownero.dart' as wownero;
|
||||||
|
|
||||||
|
bool isUpdating = false;
|
||||||
|
|
||||||
|
class SubaddressInfoMetadata {
|
||||||
|
SubaddressInfoMetadata({
|
||||||
|
required this.accountIndex,
|
||||||
|
});
|
||||||
|
int accountIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
SubaddressInfoMetadata? subaddress = null;
|
||||||
|
|
||||||
|
void refreshSubaddresses({required int accountIndex}) {
|
||||||
|
try {
|
||||||
|
isUpdating = true;
|
||||||
|
subaddress = SubaddressInfoMetadata(accountIndex: accountIndex);
|
||||||
|
isUpdating = false;
|
||||||
|
} catch (e) {
|
||||||
|
isUpdating = false;
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Subaddress {
|
||||||
|
Subaddress({
|
||||||
|
required this.addressIndex,
|
||||||
|
required this.accountIndex,
|
||||||
|
});
|
||||||
|
String get address => wownero.Wallet_address(
|
||||||
|
wptr!,
|
||||||
|
accountIndex: accountIndex,
|
||||||
|
addressIndex: addressIndex,
|
||||||
|
);
|
||||||
|
final int addressIndex;
|
||||||
|
final int accountIndex;
|
||||||
|
String get label => wownero.Wallet_getSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Subaddress> getAllSubaddresses() {
|
||||||
|
final size = wownero.Wallet_numSubaddresses(wptr!, accountIndex: subaddress!.accountIndex);
|
||||||
|
return List.generate(size, (index) {
|
||||||
|
return Subaddress(
|
||||||
|
accountIndex: subaddress!.accountIndex,
|
||||||
|
addressIndex: index,
|
||||||
|
);
|
||||||
|
}).reversed.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void addSubaddressSync({required int accountIndex, required String label}) {
|
||||||
|
wownero.Wallet_addSubaddress(wptr!, accountIndex: accountIndex, label: label);
|
||||||
|
refreshSubaddresses(accountIndex: accountIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setLabelForSubaddressSync(
|
||||||
|
{required int accountIndex, required int addressIndex, required String label}) {
|
||||||
|
wownero.Wallet_setSubaddressLabel(wptr!, accountIndex: accountIndex, addressIndex: addressIndex, label: label);
|
||||||
|
}
|
||||||
|
|
||||||
|
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<void> addSubaddress({required int accountIndex, required String label}) async {
|
||||||
|
_addSubaddress({'accountIndex': accountIndex, 'label': label});
|
||||||
|
await store();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setLabelForSubaddress(
|
||||||
|
{required int accountIndex, required int addressIndex, required String label}) async {
|
||||||
|
_setLabelForSubaddress({
|
||||||
|
'accountIndex': accountIndex,
|
||||||
|
'addressIndex': addressIndex,
|
||||||
|
'label': label
|
||||||
|
});
|
||||||
|
await store();
|
||||||
|
}
|
278
cw_wownero/lib/api/transaction_history.dart
Normal file
278
cw_wownero/lib/api/transaction_history.dart
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
import 'dart:ffi';
|
||||||
|
import 'dart:isolate';
|
||||||
|
|
||||||
|
import 'package:cw_wownero/api/account_list.dart';
|
||||||
|
import 'package:cw_wownero/api/exceptions/creation_transaction_exception.dart';
|
||||||
|
import 'package:cw_wownero/api/wownero_output.dart';
|
||||||
|
import 'package:cw_wownero/api/structs/pending_transaction.dart';
|
||||||
|
import 'package:ffi/ffi.dart';
|
||||||
|
import 'package:monero/wownero.dart' as wownero;
|
||||||
|
import 'package:monero/src/generated_bindings_wownero.g.dart' as wownero_gen;
|
||||||
|
|
||||||
|
|
||||||
|
String getTxKey(String txId) {
|
||||||
|
return wownero.Wallet_getTxKey(wptr!, txid: txId);
|
||||||
|
}
|
||||||
|
|
||||||
|
wownero.TransactionHistory? txhistory;
|
||||||
|
|
||||||
|
void refreshTransactions() {
|
||||||
|
txhistory = wownero.Wallet_history(wptr!);
|
||||||
|
wownero.TransactionHistory_refresh(txhistory!);
|
||||||
|
}
|
||||||
|
|
||||||
|
int countOfTransactions() => wownero.TransactionHistory_count(txhistory!);
|
||||||
|
|
||||||
|
List<Transaction> getAllTransactions() {
|
||||||
|
final size = countOfTransactions();
|
||||||
|
|
||||||
|
return List.generate(size, (index) => Transaction(txInfo: wownero.TransactionHistory_transaction(txhistory!, index: index)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(mrcyjanek): ...
|
||||||
|
Transaction getTransaction(String txId) {
|
||||||
|
return Transaction(txInfo: wownero.TransactionHistory_transactionById(txhistory!, txid: txId));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<PendingTransactionDescription> createTransactionSync(
|
||||||
|
{required String address,
|
||||||
|
required String paymentId,
|
||||||
|
required int priorityRaw,
|
||||||
|
String? amount,
|
||||||
|
int accountIndex = 0,
|
||||||
|
List<String> preferredInputs = const []}) async {
|
||||||
|
|
||||||
|
final amt = amount == null ? 0 : wownero.Wallet_amountFromString(amount);
|
||||||
|
|
||||||
|
final address_ = address.toNativeUtf8();
|
||||||
|
final paymentId_ = paymentId.toNativeUtf8();
|
||||||
|
final preferredInputs_ = preferredInputs.join(wownero.defaultSeparatorStr).toNativeUtf8();
|
||||||
|
|
||||||
|
final waddr = wptr!.address;
|
||||||
|
final addraddr = address_.address;
|
||||||
|
final paymentIdAddr = paymentId_.address;
|
||||||
|
final preferredInputsAddr = preferredInputs_.address;
|
||||||
|
final spaddr = wownero.defaultSeparator.address;
|
||||||
|
final pendingTx = Pointer<Void>.fromAddress(await Isolate.run(() {
|
||||||
|
final tx = wownero_gen.WowneroC(DynamicLibrary.open(wownero.libPath)).WOWNERO_Wallet_createTransaction(
|
||||||
|
Pointer.fromAddress(waddr),
|
||||||
|
Pointer.fromAddress(addraddr).cast(),
|
||||||
|
Pointer.fromAddress(paymentIdAddr).cast(),
|
||||||
|
amt,
|
||||||
|
1,
|
||||||
|
priorityRaw,
|
||||||
|
accountIndex,
|
||||||
|
Pointer.fromAddress(preferredInputsAddr).cast(),
|
||||||
|
Pointer.fromAddress(spaddr),
|
||||||
|
);
|
||||||
|
return tx.address;
|
||||||
|
}));
|
||||||
|
calloc.free(address_);
|
||||||
|
calloc.free(paymentId_);
|
||||||
|
calloc.free(preferredInputs_);
|
||||||
|
final String? error = (() {
|
||||||
|
final status = wownero.PendingTransaction_status(pendingTx);
|
||||||
|
if (status == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return wownero.PendingTransaction_errorString(pendingTx);
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
final message = error;
|
||||||
|
throw CreationTransactionException(message: message);
|
||||||
|
}
|
||||||
|
|
||||||
|
final rAmt = wownero.PendingTransaction_amount(pendingTx);
|
||||||
|
final rFee = wownero.PendingTransaction_fee(pendingTx);
|
||||||
|
final rHash = wownero.PendingTransaction_txid(pendingTx, '');
|
||||||
|
final rTxKey = rHash;
|
||||||
|
|
||||||
|
return PendingTransactionDescription(
|
||||||
|
amount: rAmt,
|
||||||
|
fee: rFee,
|
||||||
|
hash: rHash,
|
||||||
|
hex: '',
|
||||||
|
txKey: rTxKey,
|
||||||
|
pointerAddress: pendingTx.address,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingTransactionDescription createTransactionMultDestSync(
|
||||||
|
{required List<WowneroOutput> outputs,
|
||||||
|
required String paymentId,
|
||||||
|
required int priorityRaw,
|
||||||
|
int accountIndex = 0,
|
||||||
|
List<String> preferredInputs = const []}) {
|
||||||
|
|
||||||
|
final txptr = wownero.Wallet_createTransactionMultDest(
|
||||||
|
wptr!,
|
||||||
|
dstAddr: outputs.map((e) => e.address).toList(),
|
||||||
|
isSweepAll: false,
|
||||||
|
amounts: outputs.map((e) => wownero.Wallet_amountFromString(e.amount)).toList(),
|
||||||
|
mixinCount: 0,
|
||||||
|
pendingTransactionPriority: priorityRaw,
|
||||||
|
subaddr_account: accountIndex,
|
||||||
|
);
|
||||||
|
if (wownero.PendingTransaction_status(txptr) != 0) {
|
||||||
|
throw CreationTransactionException(message: wownero.PendingTransaction_errorString(txptr));
|
||||||
|
}
|
||||||
|
return PendingTransactionDescription(
|
||||||
|
amount: wownero.PendingTransaction_amount(txptr),
|
||||||
|
fee: wownero.PendingTransaction_fee(txptr),
|
||||||
|
hash: wownero.PendingTransaction_txid(txptr, ''),
|
||||||
|
hex: wownero.PendingTransaction_txid(txptr, ''),
|
||||||
|
txKey: wownero.PendingTransaction_txid(txptr, ''),
|
||||||
|
pointerAddress: txptr.address,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void commitTransactionFromPointerAddress({required int address}) =>
|
||||||
|
commitTransaction(transactionPointer: wownero.PendingTransaction.fromAddress(address));
|
||||||
|
|
||||||
|
void commitTransaction({required wownero.PendingTransaction transactionPointer}) {
|
||||||
|
|
||||||
|
final txCommit = wownero.PendingTransaction_commit(transactionPointer, filename: '', overwrite: false);
|
||||||
|
|
||||||
|
final String? error = (() {
|
||||||
|
final status = wownero.PendingTransaction_status(transactionPointer.cast());
|
||||||
|
if (status == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return wownero.Wallet_errorString(wptr!);
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
throw CreationTransactionException(message: error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<PendingTransactionDescription> _createTransactionSync(Map args) async {
|
||||||
|
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;
|
||||||
|
final preferredInputs = args['preferredInputs'] as List<String>;
|
||||||
|
|
||||||
|
return createTransactionSync(
|
||||||
|
address: address,
|
||||||
|
paymentId: paymentId,
|
||||||
|
amount: amount,
|
||||||
|
priorityRaw: priorityRaw,
|
||||||
|
accountIndex: accountIndex,
|
||||||
|
preferredInputs: preferredInputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingTransactionDescription _createTransactionMultDestSync(Map args) {
|
||||||
|
final outputs = args['outputs'] as List<WowneroOutput>;
|
||||||
|
final paymentId = args['paymentId'] as String;
|
||||||
|
final priorityRaw = args['priorityRaw'] as int;
|
||||||
|
final accountIndex = args['accountIndex'] as int;
|
||||||
|
final preferredInputs = args['preferredInputs'] as List<String>;
|
||||||
|
|
||||||
|
return createTransactionMultDestSync(
|
||||||
|
outputs: outputs,
|
||||||
|
paymentId: paymentId,
|
||||||
|
priorityRaw: priorityRaw,
|
||||||
|
accountIndex: accountIndex,
|
||||||
|
preferredInputs: preferredInputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<PendingTransactionDescription> createTransaction(
|
||||||
|
{required String address,
|
||||||
|
required int priorityRaw,
|
||||||
|
String? amount,
|
||||||
|
String paymentId = '',
|
||||||
|
int accountIndex = 0,
|
||||||
|
List<String> preferredInputs = const []}) async =>
|
||||||
|
_createTransactionSync({
|
||||||
|
'address': address,
|
||||||
|
'paymentId': paymentId,
|
||||||
|
'amount': amount,
|
||||||
|
'priorityRaw': priorityRaw,
|
||||||
|
'accountIndex': accountIndex,
|
||||||
|
'preferredInputs': preferredInputs
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<PendingTransactionDescription> createTransactionMultDest(
|
||||||
|
{required List<WowneroOutput> outputs,
|
||||||
|
required int priorityRaw,
|
||||||
|
String paymentId = '',
|
||||||
|
int accountIndex = 0,
|
||||||
|
List<String> preferredInputs = const []}) async =>
|
||||||
|
_createTransactionMultDestSync({
|
||||||
|
'outputs': outputs,
|
||||||
|
'paymentId': paymentId,
|
||||||
|
'priorityRaw': priorityRaw,
|
||||||
|
'accountIndex': accountIndex,
|
||||||
|
'preferredInputs': preferredInputs
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
class Transaction {
|
||||||
|
final String displayLabel;
|
||||||
|
String subaddressLabel = wownero.Wallet_getSubaddressLabel(wptr!, accountIndex: 0, addressIndex: 0);
|
||||||
|
late final String address = wownero.Wallet_address(
|
||||||
|
wptr!,
|
||||||
|
accountIndex: 0,
|
||||||
|
addressIndex: 0,
|
||||||
|
);
|
||||||
|
final String description;
|
||||||
|
final int fee;
|
||||||
|
final int confirmations;
|
||||||
|
late final bool isPending = confirmations < 10;
|
||||||
|
final int blockheight;
|
||||||
|
final int addressIndex = 0;
|
||||||
|
final int accountIndex;
|
||||||
|
final String paymentId;
|
||||||
|
final int amount;
|
||||||
|
final bool isSpend;
|
||||||
|
late DateTime timeStamp;
|
||||||
|
late final bool isConfirmed = !isPending;
|
||||||
|
final String hash;
|
||||||
|
final String key;
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
"displayLabel": displayLabel,
|
||||||
|
"subaddressLabel": subaddressLabel,
|
||||||
|
"address": address,
|
||||||
|
"description": description,
|
||||||
|
"fee": fee,
|
||||||
|
"confirmations": confirmations,
|
||||||
|
"isPending": isPending,
|
||||||
|
"blockheight": blockheight,
|
||||||
|
"accountIndex": accountIndex,
|
||||||
|
"addressIndex": addressIndex,
|
||||||
|
"paymentId": paymentId,
|
||||||
|
"amount": amount,
|
||||||
|
"isSpend": isSpend,
|
||||||
|
"timeStamp": timeStamp.toIso8601String(),
|
||||||
|
"isConfirmed": isConfirmed,
|
||||||
|
"hash": hash,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// S finalubAddress? subAddress;
|
||||||
|
// List<Transfer> transfers = [];
|
||||||
|
// final int txIndex;
|
||||||
|
final wownero.TransactionInfo txInfo;
|
||||||
|
Transaction({
|
||||||
|
required this.txInfo,
|
||||||
|
}) : displayLabel = wownero.TransactionInfo_label(txInfo),
|
||||||
|
hash = wownero.TransactionInfo_hash(txInfo),
|
||||||
|
timeStamp = DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
wownero.TransactionInfo_timestamp(txInfo) * 1000,
|
||||||
|
),
|
||||||
|
isSpend = wownero.TransactionInfo_direction(txInfo) ==
|
||||||
|
wownero.TransactionInfo_Direction.Out,
|
||||||
|
amount = wownero.TransactionInfo_amount(txInfo),
|
||||||
|
paymentId = wownero.TransactionInfo_paymentId(txInfo),
|
||||||
|
accountIndex = wownero.TransactionInfo_subaddrAccount(txInfo),
|
||||||
|
blockheight = wownero.TransactionInfo_blockHeight(txInfo),
|
||||||
|
confirmations = wownero.TransactionInfo_confirmations(txInfo),
|
||||||
|
fee = wownero.TransactionInfo_fee(txInfo),
|
||||||
|
description = wownero.TransactionInfo_description(txInfo),
|
||||||
|
key = wownero.Wallet_getTxKey(wptr!, txid: wownero.TransactionInfo_hash(txInfo));
|
||||||
|
}
|
284
cw_wownero/lib/api/wallet.dart
Normal file
284
cw_wownero/lib/api/wallet.dart
Normal file
|
@ -0,0 +1,284 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:ffi';
|
||||||
|
import 'dart:isolate';
|
||||||
|
|
||||||
|
import 'package:cw_wownero/api/account_list.dart';
|
||||||
|
import 'package:cw_wownero/api/exceptions/setup_wallet_exception.dart';
|
||||||
|
import 'package:monero/wownero.dart' as wownero;
|
||||||
|
import 'package:mutex/mutex.dart';
|
||||||
|
|
||||||
|
int getSyncingHeight() {
|
||||||
|
// final height = wownero.WOWNERO_cw_WalletListener_height(getWlptr());
|
||||||
|
final h2 = wownero.Wallet_blockChainHeight(wptr!);
|
||||||
|
// print("height: $height / $h2");
|
||||||
|
return h2;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isNeededToRefresh() {
|
||||||
|
final ret = wownero.WOWNERO_cw_WalletListener_isNeedToRefresh(getWlptr());
|
||||||
|
wownero.WOWNERO_cw_WalletListener_resetNeedToRefresh(getWlptr());
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isNewTransactionExist() {
|
||||||
|
final ret = wownero.WOWNERO_cw_WalletListener_isNewTransactionExist(getWlptr());
|
||||||
|
wownero.WOWNERO_cw_WalletListener_resetIsNewTransactionExist(getWlptr());
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getFilename() => wownero.Wallet_filename(wptr!);
|
||||||
|
|
||||||
|
String getSeed() {
|
||||||
|
// wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
|
||||||
|
final cakepolyseed =
|
||||||
|
wownero.Wallet_getCacheAttribute(wptr!, key: "cakewallet.seed");
|
||||||
|
if (cakepolyseed != "") {
|
||||||
|
return cakepolyseed;
|
||||||
|
}
|
||||||
|
final polyseed = wownero.Wallet_getPolyseed(wptr!, passphrase: '');
|
||||||
|
if (polyseed != "") {
|
||||||
|
return polyseed;
|
||||||
|
}
|
||||||
|
final legacy = wownero.Wallet_seed(wptr!, seedOffset: '');
|
||||||
|
return legacy;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getAddress({int accountIndex = 0, int addressIndex = 1}) =>
|
||||||
|
wownero.Wallet_address(wptr!,
|
||||||
|
accountIndex: accountIndex, addressIndex: addressIndex);
|
||||||
|
|
||||||
|
int getFullBalance({int accountIndex = 0}) =>
|
||||||
|
wownero.Wallet_balance(wptr!, accountIndex: accountIndex);
|
||||||
|
|
||||||
|
int getUnlockedBalance({int accountIndex = 0}) =>
|
||||||
|
wownero.Wallet_unlockedBalance(wptr!, accountIndex: accountIndex);
|
||||||
|
|
||||||
|
int getCurrentHeight() => wownero.Wallet_blockChainHeight(wptr!);
|
||||||
|
|
||||||
|
int getNodeHeightSync() => wownero.Wallet_daemonBlockChainHeight(wptr!);
|
||||||
|
|
||||||
|
bool isConnectedSync() => wownero.Wallet_connected(wptr!) != 0;
|
||||||
|
|
||||||
|
Future<bool> setupNodeSync(
|
||||||
|
{required String address,
|
||||||
|
String? login,
|
||||||
|
String? password,
|
||||||
|
bool useSSL = false,
|
||||||
|
bool isLightWallet = false,
|
||||||
|
String? socksProxyAddress}) async {
|
||||||
|
print('''
|
||||||
|
{
|
||||||
|
wptr!,
|
||||||
|
daemonAddress: $address,
|
||||||
|
useSsl: $useSSL,
|
||||||
|
proxyAddress: $socksProxyAddress ?? '',
|
||||||
|
daemonUsername: $login ?? '',
|
||||||
|
daemonPassword: $password ?? ''
|
||||||
|
}
|
||||||
|
''');
|
||||||
|
final addr = wptr!.address;
|
||||||
|
await Isolate.run(() {
|
||||||
|
wownero.Wallet_init(Pointer.fromAddress(addr),
|
||||||
|
daemonAddress: address,
|
||||||
|
useSsl: useSSL,
|
||||||
|
proxyAddress: socksProxyAddress ?? '',
|
||||||
|
daemonUsername: login ?? '',
|
||||||
|
daemonPassword: password ?? '');
|
||||||
|
});
|
||||||
|
// wownero.Wallet_init3(wptr!, argv0: '', defaultLogBaseName: 'wowneroc', console: true);
|
||||||
|
|
||||||
|
final status = wownero.Wallet_status(wptr!);
|
||||||
|
|
||||||
|
if (status != 0) {
|
||||||
|
final error = wownero.Wallet_errorString(wptr!);
|
||||||
|
print("error: $error");
|
||||||
|
throw SetupWalletException(message: error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return status == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startRefreshSync() {
|
||||||
|
wownero.Wallet_refreshAsync(wptr!);
|
||||||
|
wownero.Wallet_startRefresh(wptr!);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> connectToNode() async {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRefreshFromBlockHeight({required int height}) =>
|
||||||
|
wownero.Wallet_setRefreshFromBlockHeight(wptr!,
|
||||||
|
refresh_from_block_height: height);
|
||||||
|
|
||||||
|
void setRecoveringFromSeed({required bool isRecovery}) =>
|
||||||
|
wownero.Wallet_setRecoveringFromSeed(wptr!, recoveringFromSeed: isRecovery);
|
||||||
|
|
||||||
|
final storeMutex = Mutex();
|
||||||
|
void storeSync() async {
|
||||||
|
await storeMutex.acquire();
|
||||||
|
final addr = wptr!.address;
|
||||||
|
Isolate.run(() {
|
||||||
|
wownero.Wallet_store(Pointer.fromAddress(addr));
|
||||||
|
});
|
||||||
|
storeMutex.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setPasswordSync(String password) {
|
||||||
|
wownero.Wallet_setPassword(wptr!, password: password);
|
||||||
|
|
||||||
|
final status = wownero.Wallet_status(wptr!);
|
||||||
|
if (status != 0) {
|
||||||
|
throw Exception(wownero.Wallet_errorString(wptr!));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void closeCurrentWallet() {
|
||||||
|
wownero.Wallet_stop(wptr!);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getSecretViewKey() => wownero.Wallet_secretViewKey(wptr!);
|
||||||
|
|
||||||
|
String getPublicViewKey() => wownero.Wallet_publicViewKey(wptr!);
|
||||||
|
|
||||||
|
String getSecretSpendKey() => wownero.Wallet_secretSpendKey(wptr!);
|
||||||
|
|
||||||
|
String getPublicSpendKey() => wownero.Wallet_publicSpendKey(wptr!);
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
var syncHeight = getSyncingHeight();
|
||||||
|
|
||||||
|
if (syncHeight <= 0) {
|
||||||
|
syncHeight = getCurrentHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_initialSyncHeight <= 0) {
|
||||||
|
_initialSyncHeight = syncHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
final bchHeight = await getNodeHeightOrUpdate(syncHeight);
|
||||||
|
|
||||||
|
if (_lastKnownBlockHeight == syncHeight) {
|
||||||
|
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() {}
|
||||||
|
|
||||||
|
void _storeSync(Object _) => storeSync();
|
||||||
|
|
||||||
|
Future<bool> _setupNodeSync(Map<String, Object?> args) async {
|
||||||
|
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;
|
||||||
|
final socksProxyAddress = (args['socksProxyAddress'] ?? '') as String;
|
||||||
|
|
||||||
|
return setupNodeSync(
|
||||||
|
address: address,
|
||||||
|
login: login,
|
||||||
|
password: password,
|
||||||
|
useSSL: useSSL,
|
||||||
|
isLightWallet: isLightWallet,
|
||||||
|
socksProxyAddress: socksProxyAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isConnected(Object _) => isConnectedSync();
|
||||||
|
|
||||||
|
int _getNodeHeight(Object _) => getNodeHeightSync();
|
||||||
|
|
||||||
|
void startRefresh() => startRefreshSync();
|
||||||
|
|
||||||
|
Future<void> setupNode(
|
||||||
|
{required String address,
|
||||||
|
String? login,
|
||||||
|
String? password,
|
||||||
|
bool useSSL = false,
|
||||||
|
String? socksProxyAddress,
|
||||||
|
bool isLightWallet = false}) async =>
|
||||||
|
_setupNodeSync({
|
||||||
|
'address': address,
|
||||||
|
'login': login,
|
||||||
|
'password': password,
|
||||||
|
'useSSL': useSSL,
|
||||||
|
'isLightWallet': isLightWallet,
|
||||||
|
'socksProxyAddress': socksProxyAddress
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> store() async => _storeSync(0);
|
||||||
|
|
||||||
|
Future<bool> isConnected() async => _isConnected(0);
|
||||||
|
|
||||||
|
Future<int> getNodeHeight() async => _getNodeHeight(0);
|
||||||
|
|
||||||
|
void rescanBlockchainAsync() => wownero.Wallet_rescanBlockchainAsync(wptr!);
|
||||||
|
|
||||||
|
String getSubaddressLabel(int accountIndex, int addressIndex) {
|
||||||
|
return wownero.Wallet_getSubaddressLabel(wptr!,
|
||||||
|
accountIndex: accountIndex, addressIndex: addressIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future setTrustedDaemon(bool trusted) async =>
|
||||||
|
wownero.Wallet_setTrustedDaemon(wptr!, arg: trusted);
|
||||||
|
|
||||||
|
Future<bool> trustedDaemon() async => wownero.Wallet_trustedDaemon(wptr!);
|
||||||
|
|
||||||
|
String signMessage(String message, {String address = ""}) {
|
||||||
|
return wownero.Wallet_signMessage(wptr!, message: message, address: address);
|
||||||
|
}
|
346
cw_wownero/lib/api/wallet_manager.dart
Normal file
346
cw_wownero/lib/api/wallet_manager.dart
Normal file
|
@ -0,0 +1,346 @@
|
||||||
|
import 'dart:ffi';
|
||||||
|
import 'dart:isolate';
|
||||||
|
|
||||||
|
import 'package:cw_wownero/api/account_list.dart';
|
||||||
|
import 'package:cw_wownero/api/exceptions/wallet_creation_exception.dart';
|
||||||
|
import 'package:cw_wownero/api/exceptions/wallet_opening_exception.dart';
|
||||||
|
import 'package:cw_wownero/api/exceptions/wallet_restore_from_keys_exception.dart';
|
||||||
|
import 'package:cw_wownero/api/exceptions/wallet_restore_from_seed_exception.dart';
|
||||||
|
import 'package:cw_wownero/api/wallet.dart';
|
||||||
|
import 'package:monero/wownero.dart' as wownero;
|
||||||
|
|
||||||
|
wownero.WalletManager? _wmPtr;
|
||||||
|
final wownero.WalletManager wmPtr = Pointer.fromAddress((() {
|
||||||
|
try {
|
||||||
|
// Problems with the wallet? Crashes? Lags? this will print all calls to xmr
|
||||||
|
// codebase, so it will be easier to debug what happens. At least easier
|
||||||
|
// than plugging gdb in. Especially on windows/android.
|
||||||
|
wownero.printStarts = false;
|
||||||
|
_wmPtr ??= wownero.WalletManagerFactory_getWalletManager();
|
||||||
|
print("ptr: $_wmPtr");
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
return _wmPtr!.address;
|
||||||
|
})());
|
||||||
|
|
||||||
|
void createWalletSync(
|
||||||
|
{required String path,
|
||||||
|
required String password,
|
||||||
|
required String language,
|
||||||
|
int nettype = 0}) {
|
||||||
|
wptr = wownero.WalletManager_createWallet(wmPtr,
|
||||||
|
path: path, password: password, language: language, networkType: 0);
|
||||||
|
|
||||||
|
final status = wownero.Wallet_status(wptr!);
|
||||||
|
if (status != 0) {
|
||||||
|
throw WalletCreationException(message: wownero.Wallet_errorString(wptr!));
|
||||||
|
}
|
||||||
|
wownero.Wallet_store(wptr!, path: path);
|
||||||
|
openedWalletsByPath[path] = wptr!;
|
||||||
|
|
||||||
|
// is the line below needed?
|
||||||
|
// setupNodeSync(address: "node.wowneroworld.com:18089");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isWalletExistSync({required String path}) {
|
||||||
|
return wownero.WalletManager_walletExists(wmPtr, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void restoreWalletFromSeedSync(
|
||||||
|
{required String path,
|
||||||
|
required String password,
|
||||||
|
required String seed,
|
||||||
|
int nettype = 0,
|
||||||
|
int restoreHeight = 0}) {
|
||||||
|
wptr = wownero.WalletManager_recoveryWallet(
|
||||||
|
wmPtr,
|
||||||
|
path: path,
|
||||||
|
password: password,
|
||||||
|
mnemonic: seed,
|
||||||
|
restoreHeight: restoreHeight,
|
||||||
|
seedOffset: '',
|
||||||
|
networkType: 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
final status = wownero.Wallet_status(wptr!);
|
||||||
|
|
||||||
|
if (status != 0) {
|
||||||
|
final error = wownero.Wallet_errorString(wptr!);
|
||||||
|
throw WalletRestoreFromSeedException(message: error);
|
||||||
|
}
|
||||||
|
|
||||||
|
openedWalletsByPath[path] = wptr!;
|
||||||
|
}
|
||||||
|
|
||||||
|
void restoreWalletFromKeysSync(
|
||||||
|
{required String path,
|
||||||
|
required String password,
|
||||||
|
required String language,
|
||||||
|
required String address,
|
||||||
|
required String viewKey,
|
||||||
|
required String spendKey,
|
||||||
|
int nettype = 0,
|
||||||
|
int restoreHeight = 0}) {
|
||||||
|
wptr = wownero.WalletManager_createWalletFromKeys(
|
||||||
|
wmPtr,
|
||||||
|
path: path,
|
||||||
|
password: password,
|
||||||
|
restoreHeight: restoreHeight,
|
||||||
|
addressString: address,
|
||||||
|
viewKeyString: viewKey,
|
||||||
|
spendKeyString: spendKey,
|
||||||
|
nettype: 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
final status = wownero.Wallet_status(wptr!);
|
||||||
|
if (status != 0) {
|
||||||
|
throw WalletRestoreFromKeysException(
|
||||||
|
message: wownero.Wallet_errorString(wptr!));
|
||||||
|
}
|
||||||
|
|
||||||
|
openedWalletsByPath[path] = wptr!;
|
||||||
|
}
|
||||||
|
|
||||||
|
void restoreWalletFromSpendKeySync(
|
||||||
|
{required String path,
|
||||||
|
required String password,
|
||||||
|
required String seed,
|
||||||
|
required String language,
|
||||||
|
required String spendKey,
|
||||||
|
int nettype = 0,
|
||||||
|
int restoreHeight = 0}) {
|
||||||
|
// wptr = wownero.WalletManager_createWalletFromKeys(
|
||||||
|
// wmPtr,
|
||||||
|
// path: path,
|
||||||
|
// password: password,
|
||||||
|
// restoreHeight: restoreHeight,
|
||||||
|
// addressString: '',
|
||||||
|
// spendKeyString: spendKey,
|
||||||
|
// viewKeyString: '',
|
||||||
|
// nettype: 0,
|
||||||
|
// );
|
||||||
|
|
||||||
|
wptr = wownero.WalletManager_createDeterministicWalletFromSpendKey(
|
||||||
|
wmPtr,
|
||||||
|
path: path,
|
||||||
|
password: password,
|
||||||
|
language: language,
|
||||||
|
spendKeyString: spendKey,
|
||||||
|
newWallet: true, // TODO(mrcyjanek): safe to remove
|
||||||
|
restoreHeight: restoreHeight,
|
||||||
|
);
|
||||||
|
|
||||||
|
final status = wownero.Wallet_status(wptr!);
|
||||||
|
|
||||||
|
if (status != 0) {
|
||||||
|
final err = wownero.Wallet_errorString(wptr!);
|
||||||
|
print("err: $err");
|
||||||
|
throw WalletRestoreFromKeysException(message: err);
|
||||||
|
}
|
||||||
|
|
||||||
|
wownero.Wallet_setCacheAttribute(wptr!, key: "cakewallet.seed", value: seed);
|
||||||
|
|
||||||
|
storeSync();
|
||||||
|
|
||||||
|
openedWalletsByPath[path] = wptr!;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _lastOpenedWallet = "";
|
||||||
|
|
||||||
|
// void restoreWowneroWalletFromDevice(
|
||||||
|
// {required String path,
|
||||||
|
// required String password,
|
||||||
|
// required String deviceName,
|
||||||
|
// int nettype = 0,
|
||||||
|
// int restoreHeight = 0}) {
|
||||||
|
//
|
||||||
|
// final pathPointer = path.toNativeUtf8();
|
||||||
|
// final passwordPointer = password.toNativeUtf8();
|
||||||
|
// final deviceNamePointer = deviceName.toNativeUtf8();
|
||||||
|
// final errorMessagePointer = ''.toNativeUtf8();
|
||||||
|
//
|
||||||
|
// final isWalletRestored = restoreWalletFromDeviceNative(
|
||||||
|
// pathPointer,
|
||||||
|
// passwordPointer,
|
||||||
|
// deviceNamePointer,
|
||||||
|
// nettype,
|
||||||
|
// restoreHeight,
|
||||||
|
// errorMessagePointer) != 0;
|
||||||
|
//
|
||||||
|
// calloc.free(pathPointer);
|
||||||
|
// calloc.free(passwordPointer);
|
||||||
|
//
|
||||||
|
// storeSync();
|
||||||
|
//
|
||||||
|
// if (!isWalletRestored) {
|
||||||
|
// throw WalletRestoreFromKeysException(
|
||||||
|
// message: convertUTF8ToString(pointer: errorMessagePointer));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
Map<String, wownero.wallet> openedWalletsByPath = {};
|
||||||
|
|
||||||
|
void loadWallet(
|
||||||
|
{required String path, required String password, int nettype = 0}) {
|
||||||
|
if (openedWalletsByPath[path] != null) {
|
||||||
|
wptr = openedWalletsByPath[path]!;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (wptr == null || path != _lastOpenedWallet) {
|
||||||
|
if (wptr != null) {
|
||||||
|
final addr = wptr!.address;
|
||||||
|
Isolate.run(() {
|
||||||
|
wownero.Wallet_store(Pointer.fromAddress(addr));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
wptr = wownero.WalletManager_openWallet(wmPtr,
|
||||||
|
path: path, password: password);
|
||||||
|
openedWalletsByPath[path] = wptr!;
|
||||||
|
_lastOpenedWallet = path;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
final status = wownero.Wallet_status(wptr!);
|
||||||
|
if (status != 0) {
|
||||||
|
final err = wownero.Wallet_errorString(wptr!);
|
||||||
|
print(err);
|
||||||
|
throw WalletOpeningException(message: err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _restoreFromSpendKey(Map<String, dynamic> args) {
|
||||||
|
final path = args['path'] as String;
|
||||||
|
final password = args['password'] as String;
|
||||||
|
final seed = args['seed'] as String;
|
||||||
|
final language = args['language'] as String;
|
||||||
|
final spendKey = args['spendKey'] as String;
|
||||||
|
final restoreHeight = args['restoreHeight'] as int;
|
||||||
|
|
||||||
|
restoreWalletFromSpendKeySync(
|
||||||
|
path: path,
|
||||||
|
password: password,
|
||||||
|
seed: seed,
|
||||||
|
language: language,
|
||||||
|
restoreHeight: restoreHeight,
|
||||||
|
spendKey: spendKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _openWallet(Map<String, String> args) async => loadWallet(
|
||||||
|
path: args['path'] as String, password: args['password'] as String);
|
||||||
|
|
||||||
|
Future<bool> _isWalletExist(String path) async => isWalletExistSync(path: path);
|
||||||
|
|
||||||
|
void openWallet(
|
||||||
|
{required String path,
|
||||||
|
required String password,
|
||||||
|
int nettype = 0}) async =>
|
||||||
|
loadWallet(path: path, password: password, nettype: nettype);
|
||||||
|
|
||||||
|
Future<void> openWalletAsync(Map<String, String> args) async =>
|
||||||
|
_openWallet(args);
|
||||||
|
|
||||||
|
Future<void> createWallet(
|
||||||
|
{required String path,
|
||||||
|
required String password,
|
||||||
|
required String language,
|
||||||
|
int nettype = 0}) async =>
|
||||||
|
_createWallet({
|
||||||
|
'path': path,
|
||||||
|
'password': password,
|
||||||
|
'language': language,
|
||||||
|
'nettype': nettype
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> restoreFromSeed(
|
||||||
|
{required String path,
|
||||||
|
required String password,
|
||||||
|
required String seed,
|
||||||
|
int nettype = 0,
|
||||||
|
int restoreHeight = 0}) async =>
|
||||||
|
_restoreFromSeed({
|
||||||
|
'path': path,
|
||||||
|
'password': password,
|
||||||
|
'seed': seed,
|
||||||
|
'nettype': nettype,
|
||||||
|
'restoreHeight': restoreHeight
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> restoreFromKeys(
|
||||||
|
{required String path,
|
||||||
|
required String password,
|
||||||
|
required String language,
|
||||||
|
required String address,
|
||||||
|
required String viewKey,
|
||||||
|
required String spendKey,
|
||||||
|
int nettype = 0,
|
||||||
|
int restoreHeight = 0}) async =>
|
||||||
|
_restoreFromKeys({
|
||||||
|
'path': path,
|
||||||
|
'password': password,
|
||||||
|
'language': language,
|
||||||
|
'address': address,
|
||||||
|
'viewKey': viewKey,
|
||||||
|
'spendKey': spendKey,
|
||||||
|
'nettype': nettype,
|
||||||
|
'restoreHeight': restoreHeight
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> restoreFromSpendKey(
|
||||||
|
{required String path,
|
||||||
|
required String password,
|
||||||
|
required String seed,
|
||||||
|
required String language,
|
||||||
|
required String spendKey,
|
||||||
|
int nettype = 0,
|
||||||
|
int restoreHeight = 0}) async =>
|
||||||
|
_restoreFromSpendKey({
|
||||||
|
'path': path,
|
||||||
|
'password': password,
|
||||||
|
'seed': seed,
|
||||||
|
'language': language,
|
||||||
|
'spendKey': spendKey,
|
||||||
|
'nettype': nettype,
|
||||||
|
'restoreHeight': restoreHeight
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<bool> isWalletExist({required String path}) => _isWalletExist(path);
|
6
cw_wownero/lib/api/wownero_output.dart
Normal file
6
cw_wownero/lib/api/wownero_output.dart
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
class WowneroOutput {
|
||||||
|
WowneroOutput({required this.address, required this.amount});
|
||||||
|
|
||||||
|
final String address;
|
||||||
|
final String amount;
|
||||||
|
}
|
8
cw_wownero/lib/cw_wownero.dart
Normal file
8
cw_wownero/lib/cw_wownero.dart
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
|
||||||
|
import 'cw_wownero_platform_interface.dart';
|
||||||
|
|
||||||
|
class CwWownero {
|
||||||
|
Future<String?> getPlatformVersion() {
|
||||||
|
return CwWowneroPlatform.instance.getPlatformVersion();
|
||||||
|
}
|
||||||
|
}
|
17
cw_wownero/lib/cw_wownero_method_channel.dart
Normal file
17
cw_wownero/lib/cw_wownero_method_channel.dart
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import 'cw_wownero_platform_interface.dart';
|
||||||
|
|
||||||
|
/// An implementation of [CwWowneroPlatform] that uses method channels.
|
||||||
|
class MethodChannelCwWownero extends CwWowneroPlatform {
|
||||||
|
/// The method channel used to interact with the native platform.
|
||||||
|
@visibleForTesting
|
||||||
|
final methodChannel = const MethodChannel('cw_wownero');
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> getPlatformVersion() async {
|
||||||
|
final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
}
|
29
cw_wownero/lib/cw_wownero_platform_interface.dart
Normal file
29
cw_wownero/lib/cw_wownero_platform_interface.dart
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||||
|
|
||||||
|
import 'cw_wownero_method_channel.dart';
|
||||||
|
|
||||||
|
abstract class CwWowneroPlatform extends PlatformInterface {
|
||||||
|
/// Constructs a CwWowneroPlatform.
|
||||||
|
CwWowneroPlatform() : super(token: _token);
|
||||||
|
|
||||||
|
static final Object _token = Object();
|
||||||
|
|
||||||
|
static CwWowneroPlatform _instance = MethodChannelCwWownero();
|
||||||
|
|
||||||
|
/// The default instance of [CwWowneroPlatform] to use.
|
||||||
|
///
|
||||||
|
/// Defaults to [MethodChannelCwWownero].
|
||||||
|
static CwWowneroPlatform get instance => _instance;
|
||||||
|
|
||||||
|
/// Platform-specific implementations should set this with their own
|
||||||
|
/// platform-specific class that extends [CwWowneroPlatform] when
|
||||||
|
/// they register themselves.
|
||||||
|
static set instance(CwWowneroPlatform instance) {
|
||||||
|
PlatformInterface.verifyToken(instance, _token);
|
||||||
|
_instance = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<String?> getPlatformVersion() {
|
||||||
|
throw UnimplementedError('platformVersion() has not been implemented.');
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
class WowneroTransactionCreationException implements Exception {
|
||||||
|
WowneroTransactionCreationException(this.message);
|
||||||
|
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => message;
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
class WowneroTransactionNoInputsException implements Exception {
|
||||||
|
WowneroTransactionNoInputsException(this.inputsSize);
|
||||||
|
|
||||||
|
int inputsSize;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'Not enough inputs ($inputsSize) selected. Please select more under Coin Control';
|
||||||
|
}
|
1630
cw_wownero/lib/mnemonics/chinese_simplified.dart
Normal file
1630
cw_wownero/lib/mnemonics/chinese_simplified.dart
Normal file
File diff suppressed because it is too large
Load diff
1630
cw_wownero/lib/mnemonics/dutch.dart
Normal file
1630
cw_wownero/lib/mnemonics/dutch.dart
Normal file
File diff suppressed because it is too large
Load diff
1630
cw_wownero/lib/mnemonics/english.dart
Normal file
1630
cw_wownero/lib/mnemonics/english.dart
Normal file
File diff suppressed because it is too large
Load diff
1630
cw_wownero/lib/mnemonics/french.dart
Normal file
1630
cw_wownero/lib/mnemonics/french.dart
Normal file
File diff suppressed because it is too large
Load diff
1630
cw_wownero/lib/mnemonics/german.dart
Normal file
1630
cw_wownero/lib/mnemonics/german.dart
Normal file
File diff suppressed because it is too large
Load diff
1630
cw_wownero/lib/mnemonics/italian.dart
Normal file
1630
cw_wownero/lib/mnemonics/italian.dart
Normal file
File diff suppressed because it is too large
Load diff
1630
cw_wownero/lib/mnemonics/japanese.dart
Normal file
1630
cw_wownero/lib/mnemonics/japanese.dart
Normal file
File diff suppressed because it is too large
Load diff
1630
cw_wownero/lib/mnemonics/portuguese.dart
Normal file
1630
cw_wownero/lib/mnemonics/portuguese.dart
Normal file
File diff suppressed because it is too large
Load diff
1630
cw_wownero/lib/mnemonics/russian.dart
Normal file
1630
cw_wownero/lib/mnemonics/russian.dart
Normal file
File diff suppressed because it is too large
Load diff
1630
cw_wownero/lib/mnemonics/spanish.dart
Normal file
1630
cw_wownero/lib/mnemonics/spanish.dart
Normal file
File diff suppressed because it is too large
Load diff
1689
cw_wownero/lib/mywownero.dart
Normal file
1689
cw_wownero/lib/mywownero.dart
Normal file
File diff suppressed because it is too large
Load diff
53
cw_wownero/lib/pending_wownero_transaction.dart
Normal file
53
cw_wownero/lib/pending_wownero_transaction.dart
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
import 'package:cw_wownero/api/structs/pending_transaction.dart';
|
||||||
|
import 'package:cw_wownero/api/transaction_history.dart'
|
||||||
|
as wownero_transaction_history;
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/amount_converter.dart';
|
||||||
|
|
||||||
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
|
|
||||||
|
class DoubleSpendException implements Exception {
|
||||||
|
DoubleSpendException();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'This transaction cannot be committed. This can be due to many reasons including the wallet not being synced, there is not enough XMR in your available balance, or previous transactions are not yet fully processed.';
|
||||||
|
}
|
||||||
|
|
||||||
|
class PendingWowneroTransaction with PendingTransaction {
|
||||||
|
PendingWowneroTransaction(this.pendingTransactionDescription);
|
||||||
|
|
||||||
|
final PendingTransactionDescription pendingTransactionDescription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get id => pendingTransactionDescription.hash;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get hex => pendingTransactionDescription.hex;
|
||||||
|
|
||||||
|
String get txKey => pendingTransactionDescription.txKey;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get amountFormatted => AmountConverter.amountIntToString(
|
||||||
|
CryptoCurrency.xmr, pendingTransactionDescription.amount);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get feeFormatted => AmountConverter.amountIntToString(
|
||||||
|
CryptoCurrency.xmr, pendingTransactionDescription.fee);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> commit() async {
|
||||||
|
try {
|
||||||
|
wownero_transaction_history.commitTransactionFromPointerAddress(
|
||||||
|
address: pendingTransactionDescription.pointerAddress);
|
||||||
|
} catch (e) {
|
||||||
|
final message = e.toString();
|
||||||
|
|
||||||
|
if (message.contains('Reason: double spend')) {
|
||||||
|
throw DoubleSpendException();
|
||||||
|
}
|
||||||
|
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
81
cw_wownero/lib/wownero_account_list.dart
Normal file
81
cw_wownero/lib/wownero_account_list.dart
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
import 'package:cw_core/wownero_amount_format.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:cw_core/account.dart';
|
||||||
|
import 'package:cw_wownero/api/account_list.dart' as account_list;
|
||||||
|
import 'package:monero/wownero.dart' as wownero;
|
||||||
|
|
||||||
|
part 'wownero_account_list.g.dart';
|
||||||
|
|
||||||
|
class WowneroAccountList = WowneroAccountListBase with _$WowneroAccountList;
|
||||||
|
|
||||||
|
abstract class WowneroAccountListBase with Store {
|
||||||
|
WowneroAccountListBase()
|
||||||
|
: accounts = ObservableList<Account>(),
|
||||||
|
_isRefreshing = false,
|
||||||
|
_isUpdating = false {
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
@observable
|
||||||
|
ObservableList<Account> accounts;
|
||||||
|
bool _isRefreshing;
|
||||||
|
bool _isUpdating;
|
||||||
|
|
||||||
|
void update() async {
|
||||||
|
if (_isUpdating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_isUpdating = true;
|
||||||
|
refresh();
|
||||||
|
final accounts = getAll();
|
||||||
|
|
||||||
|
if (accounts.isNotEmpty) {
|
||||||
|
this.accounts.clear();
|
||||||
|
this.accounts.addAll(accounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
_isUpdating = false;
|
||||||
|
} catch (e) {
|
||||||
|
_isUpdating = false;
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Account> getAll() => account_list.getAllAccount().map((accountRow) {
|
||||||
|
final balance = wownero.SubaddressAccountRow_getUnlockedBalance(accountRow);
|
||||||
|
|
||||||
|
return Account(
|
||||||
|
id: wownero.SubaddressAccountRow_getRowId(accountRow),
|
||||||
|
label: wownero.SubaddressAccountRow_getLabel(accountRow),
|
||||||
|
balance: wowneroAmountToString(amount: wownero.Wallet_amountFromString(balance)),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
Future<void> addAccount({required String label}) async {
|
||||||
|
await account_list.addAccount(label: label);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setLabelAccount({required int accountIndex, required String label}) async {
|
||||||
|
await account_list.setLabelForAccount(accountIndex: accountIndex, label: label);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void refresh() {
|
||||||
|
if (_isRefreshing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_isRefreshing = true;
|
||||||
|
account_list.refreshAccounts();
|
||||||
|
_isRefreshing = false;
|
||||||
|
} catch (e) {
|
||||||
|
_isRefreshing = false;
|
||||||
|
print(e);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
162
cw_wownero/lib/wownero_subaddress_list.dart
Normal file
162
cw_wownero/lib/wownero_subaddress_list.dart
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
import 'package:cw_core/subaddress.dart';
|
||||||
|
import 'package:cw_wownero/api/coins_info.dart';
|
||||||
|
import 'package:cw_wownero/api/subaddress_list.dart' as subaddress_list;
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
part 'wownero_subaddress_list.g.dart';
|
||||||
|
|
||||||
|
class WowneroSubaddressList = WowneroSubaddressListBase with _$WowneroSubaddressList;
|
||||||
|
|
||||||
|
abstract class WowneroSubaddressListBase with Store {
|
||||||
|
WowneroSubaddressListBase()
|
||||||
|
: _isRefreshing = false,
|
||||||
|
_isUpdating = false,
|
||||||
|
subaddresses = ObservableList<Subaddress>();
|
||||||
|
|
||||||
|
final List<String> _usedAddresses = [];
|
||||||
|
|
||||||
|
@observable
|
||||||
|
ObservableList<Subaddress> subaddresses;
|
||||||
|
|
||||||
|
bool _isRefreshing;
|
||||||
|
bool _isUpdating;
|
||||||
|
|
||||||
|
void update({required int accountIndex}) {
|
||||||
|
refreshCoins(accountIndex);
|
||||||
|
|
||||||
|
if (_isUpdating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_isUpdating = true;
|
||||||
|
refresh(accountIndex: accountIndex);
|
||||||
|
subaddresses.clear();
|
||||||
|
subaddresses.addAll(getAll());
|
||||||
|
_isUpdating = false;
|
||||||
|
} catch (e) {
|
||||||
|
_isUpdating = false;
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Subaddress> getAll() {
|
||||||
|
var subaddresses = subaddress_list.getAllSubaddresses();
|
||||||
|
|
||||||
|
if (subaddresses.length > 2) {
|
||||||
|
final primary = subaddresses.first;
|
||||||
|
final rest = subaddresses.sublist(1).reversed;
|
||||||
|
subaddresses = [primary] + rest.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return subaddresses.map((s) {
|
||||||
|
final address = s.address;
|
||||||
|
final label = s.label;
|
||||||
|
final id = s.addressIndex;
|
||||||
|
final hasDefaultAddressName =
|
||||||
|
label.toLowerCase() == 'Primary account'.toLowerCase() ||
|
||||||
|
label.toLowerCase() == 'Untitled account'.toLowerCase();
|
||||||
|
final isPrimaryAddress = id == 0 && hasDefaultAddressName;
|
||||||
|
return Subaddress(
|
||||||
|
id: id,
|
||||||
|
address: address,
|
||||||
|
label: isPrimaryAddress
|
||||||
|
? 'Primary address'
|
||||||
|
: hasDefaultAddressName
|
||||||
|
? ''
|
||||||
|
: label);
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> addSubaddress({required int accountIndex, required String label}) async {
|
||||||
|
await subaddress_list.addSubaddress(accountIndex: accountIndex, label: label);
|
||||||
|
update(accountIndex: accountIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> setLabelSubaddress(
|
||||||
|
{required int accountIndex, required int addressIndex, required String label}) async {
|
||||||
|
await subaddress_list.setLabelForSubaddress(
|
||||||
|
accountIndex: accountIndex, addressIndex: addressIndex, label: label);
|
||||||
|
update(accountIndex: accountIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void refresh({required int accountIndex}) {
|
||||||
|
if (_isRefreshing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_isRefreshing = true;
|
||||||
|
subaddress_list.refreshSubaddresses(accountIndex: accountIndex);
|
||||||
|
_isRefreshing = false;
|
||||||
|
} on PlatformException catch (e) {
|
||||||
|
_isRefreshing = false;
|
||||||
|
print(e);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateWithAutoGenerate({
|
||||||
|
required int accountIndex,
|
||||||
|
required String defaultLabel,
|
||||||
|
required List<String> usedAddresses,
|
||||||
|
}) async {
|
||||||
|
_usedAddresses.addAll(usedAddresses);
|
||||||
|
if (_isUpdating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_isUpdating = true;
|
||||||
|
refresh(accountIndex: accountIndex);
|
||||||
|
subaddresses.clear();
|
||||||
|
final newSubAddresses =
|
||||||
|
await _getAllUnusedAddresses(accountIndex: accountIndex, label: defaultLabel);
|
||||||
|
subaddresses.addAll(newSubAddresses);
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
} finally {
|
||||||
|
_isUpdating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Subaddress>> _getAllUnusedAddresses(
|
||||||
|
{required int accountIndex, required String label}) async {
|
||||||
|
final allAddresses = subaddress_list.getAllSubaddresses();
|
||||||
|
final lastAddress = allAddresses.last.address;
|
||||||
|
if (allAddresses.isEmpty || _usedAddresses.contains(lastAddress)) {
|
||||||
|
final isAddressUnused = await _newSubaddress(accountIndex: accountIndex, label: label);
|
||||||
|
if (!isAddressUnused) {
|
||||||
|
return await _getAllUnusedAddresses(accountIndex: accountIndex, label: label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allAddresses
|
||||||
|
.map((s) {
|
||||||
|
final id = s.addressIndex;
|
||||||
|
final address = s.address;
|
||||||
|
final label = s.label;
|
||||||
|
return Subaddress(
|
||||||
|
id: id,
|
||||||
|
address: address,
|
||||||
|
label: id == 0 &&
|
||||||
|
label.toLowerCase() == 'Primary account'.toLowerCase()
|
||||||
|
? 'Primary address'
|
||||||
|
: label);
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _newSubaddress({required int accountIndex, required String label}) async {
|
||||||
|
await subaddress_list.addSubaddress(accountIndex: accountIndex, label: label);
|
||||||
|
|
||||||
|
return subaddress_list
|
||||||
|
.getAllSubaddresses()
|
||||||
|
.where((s) {
|
||||||
|
final address = s.address;
|
||||||
|
return !_usedAddresses.contains(address);
|
||||||
|
})
|
||||||
|
.isNotEmpty;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
import 'package:cw_core/monero_transaction_priority.dart';
|
||||||
|
import 'package:cw_core/output_info.dart';
|
||||||
|
|
||||||
|
class WowneroTransactionCreationCredentials {
|
||||||
|
WowneroTransactionCreationCredentials({required this.outputs, required this.priority});
|
||||||
|
|
||||||
|
final List<OutputInfo> outputs;
|
||||||
|
final MoneroTransactionPriority priority;
|
||||||
|
}
|
28
cw_wownero/lib/wownero_transaction_history.dart
Normal file
28
cw_wownero/lib/wownero_transaction_history.dart
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import 'dart:core';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:cw_core/transaction_history.dart';
|
||||||
|
import 'package:cw_wownero/wownero_transaction_info.dart';
|
||||||
|
|
||||||
|
part 'wownero_transaction_history.g.dart';
|
||||||
|
|
||||||
|
class WowneroTransactionHistory = WowneroTransactionHistoryBase
|
||||||
|
with _$WowneroTransactionHistory;
|
||||||
|
|
||||||
|
abstract class WowneroTransactionHistoryBase
|
||||||
|
extends TransactionHistoryBase<WowneroTransactionInfo> with Store {
|
||||||
|
WowneroTransactionHistoryBase() {
|
||||||
|
transactions = ObservableMap<String, WowneroTransactionInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> save() async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addOne(WowneroTransactionInfo transaction) =>
|
||||||
|
transactions[transaction.id] = transaction;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addMany(Map<String, WowneroTransactionInfo> transactions) =>
|
||||||
|
this.transactions.addAll(transactions);
|
||||||
|
|
||||||
|
}
|
82
cw_wownero/lib/wownero_transaction_info.dart
Normal file
82
cw_wownero/lib/wownero_transaction_info.dart
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import 'package:cw_core/transaction_info.dart';
|
||||||
|
import 'package:cw_core/wownero_amount_format.dart';
|
||||||
|
import 'package:cw_wownero/api/structs/transaction_info_row.dart';
|
||||||
|
import 'package:cw_core/parseBoolFromString.dart';
|
||||||
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
|
import 'package:cw_core/format_amount.dart';
|
||||||
|
import 'package:cw_wownero/api/transaction_history.dart';
|
||||||
|
|
||||||
|
class WowneroTransactionInfo extends TransactionInfo {
|
||||||
|
WowneroTransactionInfo(this.id, this.height, this.direction, this.date,
|
||||||
|
this.isPending, this.amount, this.accountIndex, this.addressIndex, this.fee,
|
||||||
|
this.confirmations);
|
||||||
|
|
||||||
|
WowneroTransactionInfo.fromMap(Map<String, Object?> map)
|
||||||
|
: id = (map['hash'] ?? '') as String,
|
||||||
|
height = (map['height'] ?? 0) as int,
|
||||||
|
direction = map['direction'] != null
|
||||||
|
? parseTransactionDirectionFromNumber(map['direction'] as String)
|
||||||
|
: TransactionDirection.incoming,
|
||||||
|
date = DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
(int.tryParse(map['timestamp'] as String? ?? '') ?? 0) * 1000),
|
||||||
|
isPending = parseBoolFromString(map['isPending'] as String),
|
||||||
|
amount = map['amount'] as int,
|
||||||
|
accountIndex = int.parse(map['accountIndex'] as String),
|
||||||
|
addressIndex = map['addressIndex'] as int,
|
||||||
|
confirmations = map['confirmations'] as int,
|
||||||
|
key = getTxKey((map['hash'] ?? '') as String),
|
||||||
|
fee = map['fee'] as int? ?? 0 {
|
||||||
|
additionalInfo = <String, dynamic>{
|
||||||
|
'key': key,
|
||||||
|
'accountIndex': accountIndex,
|
||||||
|
'addressIndex': addressIndex
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
WowneroTransactionInfo.fromRow(TransactionInfoRow row)
|
||||||
|
: id = row.getHash(),
|
||||||
|
height = row.blockHeight,
|
||||||
|
direction = parseTransactionDirectionFromInt(row.direction),
|
||||||
|
date = DateTime.fromMillisecondsSinceEpoch(row.getDatetime() * 1000),
|
||||||
|
isPending = row.isPending != 0,
|
||||||
|
amount = row.getAmount(),
|
||||||
|
accountIndex = row.subaddrAccount,
|
||||||
|
addressIndex = row.subaddrIndex,
|
||||||
|
confirmations = row.confirmations,
|
||||||
|
key = getTxKey(row.getHash()),
|
||||||
|
fee = row.fee {
|
||||||
|
additionalInfo = <String, dynamic>{
|
||||||
|
'key': key,
|
||||||
|
'accountIndex': accountIndex,
|
||||||
|
'addressIndex': addressIndex
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
final String id;
|
||||||
|
final int height;
|
||||||
|
final TransactionDirection direction;
|
||||||
|
final DateTime date;
|
||||||
|
final int accountIndex;
|
||||||
|
final bool isPending;
|
||||||
|
final int amount;
|
||||||
|
final int fee;
|
||||||
|
final int addressIndex;
|
||||||
|
final int confirmations;
|
||||||
|
String? recipientAddress;
|
||||||
|
String? key;
|
||||||
|
String? _fiatAmount;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String amountFormatted() =>
|
||||||
|
'${formatAmount(wowneroAmountToString(amount: amount))} XMR';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String fiatAmount() => _fiatAmount ?? '';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String feeFormatted() =>
|
||||||
|
'${formatAmount(wowneroAmountToString(amount: fee))} XMR';
|
||||||
|
}
|
20
cw_wownero/lib/wownero_unspent.dart
Normal file
20
cw_wownero/lib/wownero_unspent.dart
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import 'package:cw_core/unspent_transaction_output.dart';
|
||||||
|
import 'package:cw_wownero/api/structs/coins_info_row.dart';
|
||||||
|
|
||||||
|
class WowneroUnspent extends Unspent {
|
||||||
|
WowneroUnspent(
|
||||||
|
String address, String hash, String keyImage, int value, bool isFrozen, this.isUnlocked)
|
||||||
|
: super(address, hash, value, 0, keyImage) {
|
||||||
|
this.isFrozen = isFrozen;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory WowneroUnspent.fromCoinsInfoRow(CoinsInfoRow coinsInfoRow) => WowneroUnspent(
|
||||||
|
coinsInfoRow.getAddress(),
|
||||||
|
coinsInfoRow.getHash(),
|
||||||
|
coinsInfoRow.getKeyImage(),
|
||||||
|
coinsInfoRow.amount,
|
||||||
|
coinsInfoRow.frozen == 1,
|
||||||
|
coinsInfoRow.unlocked == 1);
|
||||||
|
|
||||||
|
final bool isUnlocked;
|
||||||
|
}
|
742
cw_wownero/lib/wownero_wallet.dart
Normal file
742
cw_wownero/lib/wownero_wallet.dart
Normal file
|
@ -0,0 +1,742 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:ffi';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:isolate';
|
||||||
|
|
||||||
|
import 'package:cw_core/account.dart';
|
||||||
|
import 'package:cw_core/crypto_currency.dart';
|
||||||
|
import 'package:cw_core/wownero_amount_format.dart';
|
||||||
|
import 'package:cw_core/wownero_balance.dart';
|
||||||
|
import 'package:cw_core/monero_transaction_priority.dart';
|
||||||
|
import 'package:cw_core/monero_wallet_keys.dart';
|
||||||
|
import 'package:cw_core/monero_wallet_utils.dart';
|
||||||
|
import 'package:cw_core/node.dart';
|
||||||
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
|
import 'package:cw_core/pending_transaction.dart';
|
||||||
|
import 'package:cw_core/sync_status.dart';
|
||||||
|
import 'package:cw_core/transaction_direction.dart';
|
||||||
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
|
import 'package:cw_core/wallet_base.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_wownero/api/coins_info.dart';
|
||||||
|
import 'package:cw_wownero/api/wownero_output.dart';
|
||||||
|
import 'package:cw_wownero/api/structs/pending_transaction.dart';
|
||||||
|
import 'package:cw_wownero/api/transaction_history.dart' as transaction_history;
|
||||||
|
import 'package:cw_wownero/api/wallet.dart' as wownero_wallet;
|
||||||
|
import 'package:cw_wownero/api/wallet_manager.dart';
|
||||||
|
import 'package:cw_wownero/exceptions/wownero_transaction_creation_exception.dart';
|
||||||
|
import 'package:cw_wownero/exceptions/wownero_transaction_no_inputs_exception.dart';
|
||||||
|
import 'package:cw_wownero/wownero_transaction_creation_credentials.dart';
|
||||||
|
import 'package:cw_wownero/wownero_transaction_history.dart';
|
||||||
|
import 'package:cw_wownero/wownero_transaction_info.dart';
|
||||||
|
import 'package:cw_wownero/wownero_unspent.dart';
|
||||||
|
import 'package:cw_wownero/wownero_wallet_addresses.dart';
|
||||||
|
import 'package:cw_wownero/pending_wownero_transaction.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:monero/wownero.dart' as wownero;
|
||||||
|
|
||||||
|
part 'wownero_wallet.g.dart';
|
||||||
|
|
||||||
|
const wowneroBlockSize = 1000;
|
||||||
|
// not sure if this should just be 0 but setting it higher feels safer / should catch more cases:
|
||||||
|
const MIN_RESTORE_HEIGHT = 1000;
|
||||||
|
|
||||||
|
class WowneroWallet = WowneroWalletBase with _$WowneroWallet;
|
||||||
|
|
||||||
|
abstract class WowneroWalletBase
|
||||||
|
extends WalletBase<WowneroBalance, WowneroTransactionHistory, WowneroTransactionInfo> with Store {
|
||||||
|
WowneroWalletBase(
|
||||||
|
{required WalletInfo walletInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo})
|
||||||
|
: balance = ObservableMap<CryptoCurrency, WowneroBalance>.of({
|
||||||
|
CryptoCurrency.xmr: WowneroBalance(
|
||||||
|
fullBalance: wownero_wallet.getFullBalance(accountIndex: 0),
|
||||||
|
unlockedBalance: wownero_wallet.getFullBalance(accountIndex: 0))
|
||||||
|
}),
|
||||||
|
_isTransactionUpdating = false,
|
||||||
|
_hasSyncAfterStartup = false,
|
||||||
|
isEnabledAutoGenerateSubaddress = false,
|
||||||
|
syncStatus = NotConnectedSyncStatus(),
|
||||||
|
unspentCoins = [],
|
||||||
|
this.unspentCoinsInfo = unspentCoinsInfo,
|
||||||
|
super(walletInfo) {
|
||||||
|
transactionHistory = WowneroTransactionHistory();
|
||||||
|
walletAddresses = WowneroWalletAddresses(walletInfo, transactionHistory);
|
||||||
|
|
||||||
|
_onAccountChangeReaction = reaction((_) => walletAddresses.account, (Account? account) {
|
||||||
|
if (account == null) return;
|
||||||
|
|
||||||
|
balance = ObservableMap<CryptoCurrency, WowneroBalance>.of(<CryptoCurrency, WowneroBalance>{
|
||||||
|
currency: WowneroBalance(
|
||||||
|
fullBalance: wownero_wallet.getFullBalance(accountIndex: account.id),
|
||||||
|
unlockedBalance: wownero_wallet.getUnlockedBalance(accountIndex: account.id))
|
||||||
|
});
|
||||||
|
_updateSubAddress(isEnabledAutoGenerateSubaddress, account: account);
|
||||||
|
_askForUpdateTransactionHistory();
|
||||||
|
});
|
||||||
|
|
||||||
|
reaction((_) => isEnabledAutoGenerateSubaddress, (bool enabled) {
|
||||||
|
_updateSubAddress(enabled, account: walletAddresses.account);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static const int _autoSaveInterval = 30;
|
||||||
|
|
||||||
|
Box<UnspentCoinsInfo> unspentCoinsInfo;
|
||||||
|
|
||||||
|
void Function(FlutterErrorDetails)? onError;
|
||||||
|
|
||||||
|
@override
|
||||||
|
late WowneroWalletAddresses walletAddresses;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@observable
|
||||||
|
bool isEnabledAutoGenerateSubaddress;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@observable
|
||||||
|
SyncStatus syncStatus;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@observable
|
||||||
|
ObservableMap<CryptoCurrency, WowneroBalance> balance;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get seed => wownero_wallet.getSeed();
|
||||||
|
|
||||||
|
@override
|
||||||
|
MoneroWalletKeys get keys => MoneroWalletKeys(
|
||||||
|
privateSpendKey: wownero_wallet.getSecretSpendKey(),
|
||||||
|
privateViewKey: wownero_wallet.getSecretViewKey(),
|
||||||
|
publicSpendKey: wownero_wallet.getPublicSpendKey(),
|
||||||
|
publicViewKey: wownero_wallet.getPublicViewKey());
|
||||||
|
|
||||||
|
wownero_wallet.SyncListener? _listener;
|
||||||
|
ReactionDisposer? _onAccountChangeReaction;
|
||||||
|
bool _isTransactionUpdating;
|
||||||
|
bool _hasSyncAfterStartup;
|
||||||
|
Timer? _autoSaveTimer;
|
||||||
|
List<WowneroUnspent> unspentCoins;
|
||||||
|
|
||||||
|
Future<void> init() async {
|
||||||
|
await walletAddresses.init();
|
||||||
|
balance = ObservableMap<CryptoCurrency, WowneroBalance>.of(<CryptoCurrency, WowneroBalance>{
|
||||||
|
currency: WowneroBalance(
|
||||||
|
fullBalance: wownero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id),
|
||||||
|
unlockedBalance:
|
||||||
|
wownero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id))
|
||||||
|
});
|
||||||
|
_setListeners();
|
||||||
|
await updateTransactions();
|
||||||
|
|
||||||
|
if (walletInfo.isRecovery) {
|
||||||
|
wownero_wallet.setRecoveringFromSeed(isRecovery: walletInfo.isRecovery);
|
||||||
|
|
||||||
|
if (wownero_wallet.getCurrentHeight() <= 1) {
|
||||||
|
wownero_wallet.setRefreshFromBlockHeight(height: walletInfo.restoreHeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_autoSaveTimer =
|
||||||
|
Timer.periodic(Duration(seconds: _autoSaveInterval), (_) async => await save());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void>? updateBalance() => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void close() async {
|
||||||
|
_listener?.stop();
|
||||||
|
_onAccountChangeReaction?.reaction.dispose();
|
||||||
|
_autoSaveTimer?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> connectToNode({required Node node}) async {
|
||||||
|
try {
|
||||||
|
syncStatus = ConnectingSyncStatus();
|
||||||
|
await wownero_wallet.setupNode(
|
||||||
|
address: node.uri.toString(),
|
||||||
|
login: node.login,
|
||||||
|
password: node.password,
|
||||||
|
useSSL: node.isSSL,
|
||||||
|
isLightWallet: false,
|
||||||
|
// FIXME: hardcoded value
|
||||||
|
socksProxyAddress: node.socksProxyAddress);
|
||||||
|
|
||||||
|
wownero_wallet.setTrustedDaemon(node.trusted);
|
||||||
|
syncStatus = ConnectedSyncStatus();
|
||||||
|
} catch (e) {
|
||||||
|
syncStatus = FailedSyncStatus();
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> startSync() async {
|
||||||
|
try {
|
||||||
|
_setInitialHeight();
|
||||||
|
} catch (_) {
|
||||||
|
// our restore height wasn't correct, so lets see if using the backup works:
|
||||||
|
try {
|
||||||
|
await resetCache(name);
|
||||||
|
_setInitialHeight();
|
||||||
|
} catch (e) {
|
||||||
|
// we still couldn't get a valid height from the backup?!:
|
||||||
|
// try to use the date instead:
|
||||||
|
try {
|
||||||
|
_setHeightFromDate();
|
||||||
|
} catch (_) {
|
||||||
|
// we still couldn't get a valid sync height :/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
syncStatus = AttemptingSyncStatus();
|
||||||
|
wownero_wallet.startRefresh();
|
||||||
|
_setListeners();
|
||||||
|
_listener?.start();
|
||||||
|
} catch (e) {
|
||||||
|
syncStatus = FailedSyncStatus();
|
||||||
|
print(e);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||||
|
final _credentials = credentials as WowneroTransactionCreationCredentials;
|
||||||
|
final inputs = <String>[];
|
||||||
|
final outputs = _credentials.outputs;
|
||||||
|
final hasMultiDestination = outputs.length > 1;
|
||||||
|
final unlockedBalance =
|
||||||
|
wownero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id);
|
||||||
|
var allInputsAmount = 0;
|
||||||
|
|
||||||
|
PendingTransactionDescription pendingTransactionDescription;
|
||||||
|
|
||||||
|
if (!(syncStatus is SyncedSyncStatus)) {
|
||||||
|
throw WowneroTransactionCreationException('The wallet is not synced.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unspentCoins.isEmpty) {
|
||||||
|
await updateUnspent();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final utx in unspentCoins) {
|
||||||
|
if (utx.isSending) {
|
||||||
|
allInputsAmount += utx.value;
|
||||||
|
inputs.add(utx.keyImage!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final spendAllCoins = inputs.length == unspentCoins.length;
|
||||||
|
|
||||||
|
if (hasMultiDestination) {
|
||||||
|
if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) {
|
||||||
|
throw WowneroTransactionCreationException('You do not have enough XMR to send this amount.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final int totalAmount =
|
||||||
|
outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0));
|
||||||
|
|
||||||
|
final estimatedFee = calculateEstimatedFee(_credentials.priority, totalAmount);
|
||||||
|
if (unlockedBalance < totalAmount) {
|
||||||
|
throw WowneroTransactionCreationException('You do not have enough XMR to send this amount.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!spendAllCoins && (allInputsAmount < totalAmount + estimatedFee)) {
|
||||||
|
throw WowneroTransactionNoInputsException(inputs.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
final wowneroOutputs = outputs.map((output) {
|
||||||
|
final outputAddress = output.isParsedAddress ? output.extractedAddress : output.address;
|
||||||
|
|
||||||
|
return WowneroOutput(
|
||||||
|
address: outputAddress!, amount: output.cryptoAmount!.replaceAll(',', '.'));
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
pendingTransactionDescription = await transaction_history.createTransactionMultDest(
|
||||||
|
outputs: wowneroOutputs,
|
||||||
|
priorityRaw: _credentials.priority.serialize(),
|
||||||
|
accountIndex: walletAddresses.account!.id,
|
||||||
|
preferredInputs: inputs);
|
||||||
|
} else {
|
||||||
|
final output = outputs.first;
|
||||||
|
final address = output.isParsedAddress ? output.extractedAddress : output.address;
|
||||||
|
final amount = output.sendAll ? null : output.cryptoAmount!.replaceAll(',', '.');
|
||||||
|
final formattedAmount = output.sendAll ? null : output.formattedCryptoAmount;
|
||||||
|
|
||||||
|
if ((formattedAmount != null && unlockedBalance < formattedAmount) ||
|
||||||
|
(formattedAmount == null && unlockedBalance <= 0)) {
|
||||||
|
final formattedBalance = wowneroAmountToString(amount: unlockedBalance);
|
||||||
|
|
||||||
|
throw WowneroTransactionCreationException(
|
||||||
|
'You do not have enough unlocked balance. Unlocked: $formattedBalance. Transaction amount: ${output.cryptoAmount}.');
|
||||||
|
}
|
||||||
|
|
||||||
|
final estimatedFee = calculateEstimatedFee(_credentials.priority, formattedAmount);
|
||||||
|
if (!spendAllCoins &&
|
||||||
|
((formattedAmount != null && allInputsAmount < (formattedAmount + estimatedFee)) ||
|
||||||
|
formattedAmount == null)) {
|
||||||
|
throw WowneroTransactionNoInputsException(inputs.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
pendingTransactionDescription = await transaction_history.createTransaction(
|
||||||
|
address: address!,
|
||||||
|
amount: amount,
|
||||||
|
priorityRaw: _credentials.priority.serialize(),
|
||||||
|
accountIndex: walletAddresses.account!.id,
|
||||||
|
preferredInputs: inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PendingWowneroTransaction(pendingTransactionDescription);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int calculateEstimatedFee(TransactionPriority priority, int? amount) {
|
||||||
|
// FIXME: hardcoded value;
|
||||||
|
|
||||||
|
if (priority is MoneroTransactionPriority) {
|
||||||
|
switch (priority) {
|
||||||
|
case MoneroTransactionPriority.slow:
|
||||||
|
return 24590000;
|
||||||
|
case MoneroTransactionPriority.automatic:
|
||||||
|
return 123050000;
|
||||||
|
case MoneroTransactionPriority.medium:
|
||||||
|
return 245029999;
|
||||||
|
case MoneroTransactionPriority.fast:
|
||||||
|
return 614530000;
|
||||||
|
case MoneroTransactionPriority.fastest:
|
||||||
|
return 26021600000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> save() async {
|
||||||
|
await walletAddresses.updateUsedSubaddress();
|
||||||
|
|
||||||
|
if (isEnabledAutoGenerateSubaddress) {
|
||||||
|
walletAddresses.updateUnusedSubaddress(
|
||||||
|
accountIndex: walletAddresses.account?.id ?? 0,
|
||||||
|
defaultLabel: walletAddresses.account?.label ?? '');
|
||||||
|
}
|
||||||
|
|
||||||
|
await walletAddresses.updateAddressesInBox();
|
||||||
|
await wownero_wallet.store();
|
||||||
|
try {
|
||||||
|
await backupWalletFiles(name);
|
||||||
|
} catch (e) {
|
||||||
|
print("¯\\_(ツ)_/¯");
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> renameWalletFiles(String newWalletName) async {
|
||||||
|
final currentWalletDirPath = await pathForWalletDir(name: name, type: type);
|
||||||
|
if (openedWalletsByPath["$currentWalletDirPath/$name"] != null) {
|
||||||
|
// NOTE: this is realistically only required on windows.
|
||||||
|
print("closing wallet");
|
||||||
|
final wmaddr = wmPtr.address;
|
||||||
|
final waddr = openedWalletsByPath["$currentWalletDirPath/$name"]!.address;
|
||||||
|
await Isolate.run(() {
|
||||||
|
wownero.WalletManager_closeWallet(
|
||||||
|
Pointer.fromAddress(wmaddr),
|
||||||
|
Pointer.fromAddress(waddr),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
openedWalletsByPath.remove("$currentWalletDirPath/$name");
|
||||||
|
print("wallet closed");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// -- rename the waller folder --
|
||||||
|
final currentWalletDir = Directory(await pathForWalletDir(name: name, type: type));
|
||||||
|
final newWalletDirPath = await pathForWalletDir(name: newWalletName, type: type);
|
||||||
|
await currentWalletDir.rename(newWalletDirPath);
|
||||||
|
|
||||||
|
// -- use new waller folder to rename files with old names still --
|
||||||
|
final renamedWalletPath = newWalletDirPath + '/$name';
|
||||||
|
|
||||||
|
final currentCacheFile = File(renamedWalletPath);
|
||||||
|
final currentKeysFile = File('$renamedWalletPath.keys');
|
||||||
|
final currentAddressListFile = File('$renamedWalletPath.address.txt');
|
||||||
|
|
||||||
|
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
|
||||||
|
|
||||||
|
if (currentCacheFile.existsSync()) {
|
||||||
|
await currentCacheFile.rename(newWalletPath);
|
||||||
|
}
|
||||||
|
if (currentKeysFile.existsSync()) {
|
||||||
|
await currentKeysFile.rename('$newWalletPath.keys');
|
||||||
|
}
|
||||||
|
if (currentAddressListFile.existsSync()) {
|
||||||
|
await currentAddressListFile.rename('$newWalletPath.address.txt');
|
||||||
|
}
|
||||||
|
|
||||||
|
await backupWalletFiles(newWalletName);
|
||||||
|
} catch (e) {
|
||||||
|
final currentWalletPath = await pathForWallet(name: name, type: type);
|
||||||
|
|
||||||
|
final currentCacheFile = File(currentWalletPath);
|
||||||
|
final currentKeysFile = File('$currentWalletPath.keys');
|
||||||
|
final currentAddressListFile = File('$currentWalletPath.address.txt');
|
||||||
|
|
||||||
|
final newWalletPath = await pathForWallet(name: newWalletName, type: type);
|
||||||
|
|
||||||
|
// Copies current wallet files into new wallet name's dir and files
|
||||||
|
if (currentCacheFile.existsSync()) {
|
||||||
|
await currentCacheFile.copy(newWalletPath);
|
||||||
|
}
|
||||||
|
if (currentKeysFile.existsSync()) {
|
||||||
|
await currentKeysFile.copy('$newWalletPath.keys');
|
||||||
|
}
|
||||||
|
if (currentAddressListFile.existsSync()) {
|
||||||
|
await currentAddressListFile.copy('$newWalletPath.address.txt');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete old name's dir and files
|
||||||
|
await Directory(currentWalletDirPath).delete(recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> changePassword(String password) async => wownero_wallet.setPasswordSync(password);
|
||||||
|
|
||||||
|
Future<int> getNodeHeight() async => wownero_wallet.getNodeHeight();
|
||||||
|
|
||||||
|
Future<bool> isConnected() async => wownero_wallet.isConnected();
|
||||||
|
|
||||||
|
Future<void> setAsRecovered() async {
|
||||||
|
walletInfo.isRecovery = false;
|
||||||
|
await walletInfo.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> rescan({required int height}) async {
|
||||||
|
walletInfo.restoreHeight = height;
|
||||||
|
walletInfo.isRecovery = true;
|
||||||
|
wownero_wallet.setRefreshFromBlockHeight(height: height);
|
||||||
|
wownero_wallet.rescanBlockchainAsync();
|
||||||
|
await startSync();
|
||||||
|
_askForUpdateBalance();
|
||||||
|
walletAddresses.accountList.update();
|
||||||
|
await _askForUpdateTransactionHistory();
|
||||||
|
await save();
|
||||||
|
await walletInfo.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateUnspent() async {
|
||||||
|
try {
|
||||||
|
refreshCoins(walletAddresses.account!.id);
|
||||||
|
|
||||||
|
unspentCoins.clear();
|
||||||
|
|
||||||
|
final coinCount = countOfCoins();
|
||||||
|
for (var i = 0; i < coinCount; i++) {
|
||||||
|
final coin = getCoin(i);
|
||||||
|
final coinSpent = wownero.CoinsInfo_spent(coin);
|
||||||
|
if (coinSpent == 0) {
|
||||||
|
final unspent = WowneroUnspent(
|
||||||
|
wownero.CoinsInfo_address(coin),
|
||||||
|
wownero.CoinsInfo_hash(coin),
|
||||||
|
wownero.CoinsInfo_keyImage(coin),
|
||||||
|
wownero.CoinsInfo_amount(coin),
|
||||||
|
wownero.CoinsInfo_frozen(coin),
|
||||||
|
wownero.CoinsInfo_unlocked(coin),
|
||||||
|
);
|
||||||
|
if (unspent.hash.isNotEmpty) {
|
||||||
|
unspent.isChange = transaction_history.getTransaction(unspent.hash) == 1;
|
||||||
|
}
|
||||||
|
unspentCoins.add(unspent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.accountIndex == walletAddresses.account!.id &&
|
||||||
|
element.keyImage!.contains(coin.keyImage!));
|
||||||
|
|
||||||
|
if (coinInfoList.isNotEmpty) {
|
||||||
|
final coinInfo = coinInfoList.first;
|
||||||
|
|
||||||
|
coin.isFrozen = coinInfo.isFrozen;
|
||||||
|
coin.isSending = coinInfo.isSending;
|
||||||
|
coin.note = coinInfo.note;
|
||||||
|
} else {
|
||||||
|
_addCoinInfo(coin);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await _refreshUnspentCoinsInfo();
|
||||||
|
_askForUpdateBalance();
|
||||||
|
} catch (e, s) {
|
||||||
|
print(e.toString());
|
||||||
|
onError?.call(FlutterErrorDetails(
|
||||||
|
exception: e,
|
||||||
|
stack: s,
|
||||||
|
library: this.runtimeType.toString(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _addCoinInfo(WowneroUnspent coin) async {
|
||||||
|
final newInfo = UnspentCoinsInfo(
|
||||||
|
walletId: id,
|
||||||
|
hash: coin.hash,
|
||||||
|
isFrozen: coin.isFrozen,
|
||||||
|
isSending: coin.isSending,
|
||||||
|
noteRaw: coin.note,
|
||||||
|
address: coin.address,
|
||||||
|
value: coin.value,
|
||||||
|
vout: 0,
|
||||||
|
keyImage: coin.keyImage,
|
||||||
|
isChange: coin.isChange,
|
||||||
|
accountIndex: walletAddresses.account!.id);
|
||||||
|
|
||||||
|
await unspentCoinsInfo.add(newInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _refreshUnspentCoinsInfo() async {
|
||||||
|
try {
|
||||||
|
final List<dynamic> keys = <dynamic>[];
|
||||||
|
final currentWalletUnspentCoins = unspentCoinsInfo.values.where((element) =>
|
||||||
|
element.walletId.contains(id) && element.accountIndex == walletAddresses.account!.id);
|
||||||
|
|
||||||
|
if (currentWalletUnspentCoins.isNotEmpty) {
|
||||||
|
currentWalletUnspentCoins.forEach((element) {
|
||||||
|
final existUnspentCoins =
|
||||||
|
unspentCoins.where((coin) => element.keyImage!.contains(coin.keyImage!));
|
||||||
|
|
||||||
|
if (existUnspentCoins.isEmpty) {
|
||||||
|
keys.add(element.key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keys.isNotEmpty) {
|
||||||
|
await unspentCoinsInfo.deleteAll(keys);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getTransactionAddress(int accountIndex, int addressIndex) =>
|
||||||
|
wownero_wallet.getAddress(accountIndex: accountIndex, addressIndex: addressIndex);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, WowneroTransactionInfo>> fetchTransactions() async {
|
||||||
|
transaction_history.refreshTransactions();
|
||||||
|
return _getAllTransactionsOfAccount(walletAddresses.account?.id)
|
||||||
|
.fold<Map<String, WowneroTransactionInfo>>(<String, WowneroTransactionInfo>{},
|
||||||
|
(Map<String, WowneroTransactionInfo> acc, WowneroTransactionInfo tx) {
|
||||||
|
acc[tx.id] = tx;
|
||||||
|
return acc;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateTransactions() async {
|
||||||
|
try {
|
||||||
|
if (_isTransactionUpdating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_isTransactionUpdating = true;
|
||||||
|
transactionHistory.clear();
|
||||||
|
final transactions = await fetchTransactions();
|
||||||
|
transactionHistory.addMany(transactions);
|
||||||
|
await transactionHistory.save();
|
||||||
|
_isTransactionUpdating = false;
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
_isTransactionUpdating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getSubaddressLabel(int accountIndex, int addressIndex) =>
|
||||||
|
wownero_wallet.getSubaddressLabel(accountIndex, addressIndex);
|
||||||
|
|
||||||
|
List<WowneroTransactionInfo> _getAllTransactionsOfAccount(int? accountIndex) => transaction_history
|
||||||
|
.getAllTransactions()
|
||||||
|
.map((row) => WowneroTransactionInfo(
|
||||||
|
row.hash,
|
||||||
|
row.blockheight,
|
||||||
|
row.isSpend ? TransactionDirection.outgoing : TransactionDirection.incoming,
|
||||||
|
row.timeStamp,
|
||||||
|
row.isPending,
|
||||||
|
row.amount,
|
||||||
|
row.accountIndex,
|
||||||
|
0,
|
||||||
|
row.fee,
|
||||||
|
row.confirmations,
|
||||||
|
|
||||||
|
)..additionalInfo = <String, dynamic>{
|
||||||
|
'key': row.key,
|
||||||
|
'accountIndex': row.accountIndex,
|
||||||
|
'addressIndex': row.addressIndex
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.where((element) => element.accountIndex == (accountIndex ?? 0))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
void _setListeners() {
|
||||||
|
_listener?.stop();
|
||||||
|
_listener = wownero_wallet.setListeners(_onNewBlock, _onNewTransaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the height is correct:
|
||||||
|
void _setInitialHeight() {
|
||||||
|
if (walletInfo.isRecovery) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final height = wownero_wallet.getCurrentHeight();
|
||||||
|
|
||||||
|
if (height > MIN_RESTORE_HEIGHT) {
|
||||||
|
// the restore height is probably correct, so we do nothing:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exception("height isn't > $MIN_RESTORE_HEIGHT!");
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setHeightFromDate() {
|
||||||
|
if (walletInfo.isRecovery) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int height = 0;
|
||||||
|
try {
|
||||||
|
height = _getHeightByDate(walletInfo.date);
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
wownero_wallet.setRecoveringFromSeed(isRecovery: true);
|
||||||
|
wownero_wallet.setRefreshFromBlockHeight(height: height);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _getHeightDistance(DateTime date) {
|
||||||
|
final distance = DateTime.now().millisecondsSinceEpoch - date.millisecondsSinceEpoch;
|
||||||
|
final daysTmp = (distance / 86400).round();
|
||||||
|
final days = daysTmp < 1 ? 1 : daysTmp;
|
||||||
|
|
||||||
|
return days * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _getHeightByDate(DateTime date) {
|
||||||
|
final nodeHeight = wownero_wallet.getNodeHeightSync();
|
||||||
|
final heightDistance = _getHeightDistance(date);
|
||||||
|
|
||||||
|
if (nodeHeight <= 0) {
|
||||||
|
// the node returned 0 (an error state)
|
||||||
|
throw Exception("nodeHeight is <= 0!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeHeight - heightDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _askForUpdateBalance() {
|
||||||
|
final unlockedBalance = _getUnlockedBalance();
|
||||||
|
final fullBalance = _getFullBalance();
|
||||||
|
final frozenBalance = _getFrozenBalance();
|
||||||
|
|
||||||
|
if (balance[currency]!.fullBalance != fullBalance ||
|
||||||
|
balance[currency]!.unlockedBalance != unlockedBalance ||
|
||||||
|
balance[currency]!.frozenBalance != frozenBalance) {
|
||||||
|
balance[currency] = WowneroBalance(
|
||||||
|
fullBalance: fullBalance, unlockedBalance: unlockedBalance, frozenBalance: frozenBalance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _askForUpdateTransactionHistory() async => await updateTransactions();
|
||||||
|
|
||||||
|
int _getFullBalance() => wownero_wallet.getFullBalance(accountIndex: walletAddresses.account!.id);
|
||||||
|
|
||||||
|
int _getUnlockedBalance() =>
|
||||||
|
wownero_wallet.getUnlockedBalance(accountIndex: walletAddresses.account!.id);
|
||||||
|
|
||||||
|
int _getFrozenBalance() {
|
||||||
|
var frozenBalance = 0;
|
||||||
|
|
||||||
|
for (var coin in unspentCoinsInfo.values.where((element) =>
|
||||||
|
element.walletId == id && element.accountIndex == walletAddresses.account!.id)) {
|
||||||
|
if (coin.isFrozen) frozenBalance += coin.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return frozenBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onNewBlock(int height, int blocksLeft, double ptc) async {
|
||||||
|
try {
|
||||||
|
if (walletInfo.isRecovery) {
|
||||||
|
await _askForUpdateTransactionHistory();
|
||||||
|
_askForUpdateBalance();
|
||||||
|
walletAddresses.accountList.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blocksLeft < 100) {
|
||||||
|
await _askForUpdateTransactionHistory();
|
||||||
|
_askForUpdateBalance();
|
||||||
|
walletAddresses.accountList.update();
|
||||||
|
syncStatus = SyncedSyncStatus();
|
||||||
|
|
||||||
|
if (!_hasSyncAfterStartup) {
|
||||||
|
_hasSyncAfterStartup = true;
|
||||||
|
await save();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (walletInfo.isRecovery) {
|
||||||
|
await setAsRecovered();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
syncStatus = SyncingSyncStatus(blocksLeft, ptc);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onNewTransaction() async {
|
||||||
|
try {
|
||||||
|
await _askForUpdateTransactionHistory();
|
||||||
|
_askForUpdateBalance();
|
||||||
|
await Future<void>.delayed(Duration(seconds: 1));
|
||||||
|
} catch (e) {
|
||||||
|
print(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateSubAddress(bool enableAutoGenerate, {Account? account}) {
|
||||||
|
if (enableAutoGenerate) {
|
||||||
|
walletAddresses.updateUnusedSubaddress(
|
||||||
|
accountIndex: account?.id ?? 0,
|
||||||
|
defaultLabel: account?.label ?? '',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
walletAddresses.updateSubaddressList(accountIndex: account?.id ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void setExceptionHandler(void Function(FlutterErrorDetails) e) => onError = e;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> signMessage(String message, {String? address}) async {
|
||||||
|
final useAddress = address ?? "";
|
||||||
|
return wownero_wallet.signMessage(message, address: useAddress);
|
||||||
|
}
|
||||||
|
}
|
119
cw_wownero/lib/wownero_wallet_addresses.dart
Normal file
119
cw_wownero/lib/wownero_wallet_addresses.dart
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
import 'package:cw_core/account.dart';
|
||||||
|
import 'package:cw_core/address_info.dart';
|
||||||
|
import 'package:cw_core/subaddress.dart';
|
||||||
|
import 'package:cw_core/wallet_addresses.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_wownero/api/wallet.dart';
|
||||||
|
import 'package:cw_wownero/wownero_account_list.dart';
|
||||||
|
import 'package:cw_wownero/wownero_subaddress_list.dart';
|
||||||
|
import 'package:cw_wownero/wownero_transaction_history.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
|
||||||
|
part 'wownero_wallet_addresses.g.dart';
|
||||||
|
|
||||||
|
class WowneroWalletAddresses = WowneroWalletAddressesBase with _$WowneroWalletAddresses;
|
||||||
|
|
||||||
|
abstract class WowneroWalletAddressesBase extends WalletAddresses with Store {
|
||||||
|
WowneroWalletAddressesBase(
|
||||||
|
WalletInfo walletInfo, WowneroTransactionHistory wowneroTransactionHistory)
|
||||||
|
: accountList = WowneroAccountList(),
|
||||||
|
_wowneroTransactionHistory = wowneroTransactionHistory,
|
||||||
|
subaddressList = WowneroSubaddressList(),
|
||||||
|
address = '',
|
||||||
|
super(walletInfo);
|
||||||
|
|
||||||
|
final WowneroTransactionHistory _wowneroTransactionHistory;
|
||||||
|
@override
|
||||||
|
@observable
|
||||||
|
String address;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
Account? account;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
Subaddress? subaddress;
|
||||||
|
|
||||||
|
WowneroSubaddressList subaddressList;
|
||||||
|
|
||||||
|
WowneroAccountList accountList;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> init() async {
|
||||||
|
accountList.update();
|
||||||
|
account = accountList.accounts.first;
|
||||||
|
updateSubaddressList(accountIndex: account?.id ?? 0);
|
||||||
|
await updateAddressesInBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> updateAddressesInBox() async {
|
||||||
|
try {
|
||||||
|
final _subaddressList = WowneroSubaddressList();
|
||||||
|
|
||||||
|
addressesMap.clear();
|
||||||
|
addressInfos.clear();
|
||||||
|
|
||||||
|
accountList.accounts.forEach((account) {
|
||||||
|
_subaddressList.update(accountIndex: account.id);
|
||||||
|
_subaddressList.subaddresses.forEach((subaddress) {
|
||||||
|
addressesMap[subaddress.address] = subaddress.label;
|
||||||
|
addressInfos[account.id] ??= [];
|
||||||
|
addressInfos[account.id]?.add(AddressInfo(
|
||||||
|
address: subaddress.address, label: subaddress.label, accountIndex: account.id));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await saveAddressesInBox();
|
||||||
|
} catch (e) {
|
||||||
|
print(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validate() {
|
||||||
|
accountList.update();
|
||||||
|
final accountListLength = accountList.accounts.length;
|
||||||
|
|
||||||
|
if (accountListLength <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
subaddressList.update(accountIndex: accountList.accounts.first.id);
|
||||||
|
final subaddressListLength = subaddressList.subaddresses.length;
|
||||||
|
|
||||||
|
if (subaddressListLength <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateSubaddressList({required int accountIndex}) {
|
||||||
|
subaddressList.update(accountIndex: accountIndex);
|
||||||
|
subaddress = subaddressList.subaddresses.first;
|
||||||
|
address = subaddress!.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateUsedSubaddress() async {
|
||||||
|
final transactions = _wowneroTransactionHistory.transactions.values.toList();
|
||||||
|
|
||||||
|
transactions.forEach((element) {
|
||||||
|
final accountIndex = element.accountIndex;
|
||||||
|
final addressIndex = element.addressIndex;
|
||||||
|
usedAddresses.add(getAddress(accountIndex: accountIndex, addressIndex: addressIndex));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateUnusedSubaddress(
|
||||||
|
{required int accountIndex, required String defaultLabel}) async {
|
||||||
|
await subaddressList.updateWithAutoGenerate(
|
||||||
|
accountIndex: accountIndex,
|
||||||
|
defaultLabel: defaultLabel,
|
||||||
|
usedAddresses: usedAddresses.toList());
|
||||||
|
subaddress = subaddressList.subaddresses.last;
|
||||||
|
address = subaddress!.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool containsAddress(String address) =>
|
||||||
|
addressInfos[account?.id ?? 0]?.any((it) => it.address == address) ?? false;
|
||||||
|
}
|
354
cw_wownero/lib/wownero_wallet_service.dart
Normal file
354
cw_wownero/lib/wownero_wallet_service.dart
Normal file
|
@ -0,0 +1,354 @@
|
||||||
|
import 'dart:ffi';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'package:cw_core/monero_wallet_utils.dart';
|
||||||
|
import 'package:cw_core/pathForWallet.dart';
|
||||||
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
|
import 'package:cw_core/wallet_base.dart';
|
||||||
|
import 'package:cw_core/wallet_credentials.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/wallet_service.dart';
|
||||||
|
import 'package:cw_core/wallet_type.dart';
|
||||||
|
import 'package:cw_core/get_height_by_date.dart';
|
||||||
|
import 'package:cw_wownero/api/exceptions/wallet_opening_exception.dart';
|
||||||
|
import 'package:cw_wownero/api/wallet_manager.dart' as wownero_wallet_manager;
|
||||||
|
import 'package:cw_wownero/api/wallet_manager.dart';
|
||||||
|
import 'package:cw_wownero/wownero_wallet.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:polyseed/polyseed.dart';
|
||||||
|
import 'package:monero/wownero.dart' as wownero;
|
||||||
|
|
||||||
|
class WowneroNewWalletCredentials extends WalletCredentials {
|
||||||
|
WowneroNewWalletCredentials(
|
||||||
|
{required String name, required this.language, required this.isPolyseed, String? password})
|
||||||
|
: super(name: name, password: password);
|
||||||
|
|
||||||
|
final String language;
|
||||||
|
final bool isPolyseed;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WowneroRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||||
|
WowneroRestoreWalletFromSeedCredentials(
|
||||||
|
{required String name, required this.mnemonic, int height = 0, String? password})
|
||||||
|
: super(name: name, password: password, height: height);
|
||||||
|
|
||||||
|
final String mnemonic;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WowneroWalletLoadingException implements Exception {
|
||||||
|
@override
|
||||||
|
String toString() => 'Failure to load the wallet.';
|
||||||
|
}
|
||||||
|
|
||||||
|
class WowneroRestoreWalletFromKeysCredentials extends WalletCredentials {
|
||||||
|
WowneroRestoreWalletFromKeysCredentials(
|
||||||
|
{required String name,
|
||||||
|
required String password,
|
||||||
|
required this.language,
|
||||||
|
required this.address,
|
||||||
|
required this.viewKey,
|
||||||
|
required this.spendKey,
|
||||||
|
int height = 0})
|
||||||
|
: super(name: name, password: password, height: height);
|
||||||
|
|
||||||
|
final String language;
|
||||||
|
final String address;
|
||||||
|
final String viewKey;
|
||||||
|
final String spendKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WowneroWalletService extends WalletService<
|
||||||
|
WowneroNewWalletCredentials,
|
||||||
|
WowneroRestoreWalletFromSeedCredentials,
|
||||||
|
WowneroRestoreWalletFromKeysCredentials,
|
||||||
|
WowneroNewWalletCredentials> {
|
||||||
|
WowneroWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
||||||
|
|
||||||
|
final Box<WalletInfo> walletInfoSource;
|
||||||
|
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||||
|
|
||||||
|
static bool walletFilesExist(String path) =>
|
||||||
|
!File(path).existsSync() && !File('$path.keys').existsSync();
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletType getType() => WalletType.wownero;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<WowneroWallet> create(WowneroNewWalletCredentials credentials, {bool? isTestnet}) async {
|
||||||
|
try {
|
||||||
|
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||||
|
|
||||||
|
if (credentials.isPolyseed) {
|
||||||
|
final polyseed = Polyseed.create();
|
||||||
|
final lang = PolyseedLang.getByEnglishName(credentials.language);
|
||||||
|
|
||||||
|
final heightOverride =
|
||||||
|
getWowneroHeightByDate(date: DateTime.now().subtract(Duration(days: 2)));
|
||||||
|
|
||||||
|
return _restoreFromPolyseed(
|
||||||
|
path, credentials.password!, polyseed, credentials.walletInfo!, lang,
|
||||||
|
overrideHeight: heightOverride);
|
||||||
|
}
|
||||||
|
|
||||||
|
await wownero_wallet_manager.createWallet(
|
||||||
|
path: path, password: credentials.password!, language: credentials.language);
|
||||||
|
final wallet = WowneroWallet(
|
||||||
|
walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource);
|
||||||
|
await wallet.init();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
} catch (e) {
|
||||||
|
// TODO: Implement Exception for wallet list service.
|
||||||
|
print('WowneroWalletsManager Error: ${e.toString()}');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> isWalletExit(String name) async {
|
||||||
|
try {
|
||||||
|
final path = await pathForWallet(name: name, type: getType());
|
||||||
|
return wownero_wallet_manager.isWalletExist(path: path);
|
||||||
|
} catch (e) {
|
||||||
|
// TODO: Implement Exception for wallet list service.
|
||||||
|
print('WowneroWalletsManager Error: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<WowneroWallet> openWallet(String name, String password) async {
|
||||||
|
WowneroWallet? wallet;
|
||||||
|
try {
|
||||||
|
final path = await pathForWallet(name: name, type: getType());
|
||||||
|
|
||||||
|
if (walletFilesExist(path)) {
|
||||||
|
await repairOldAndroidWallet(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
await wownero_wallet_manager.openWalletAsync({'path': path, 'password': password});
|
||||||
|
final walletInfo = walletInfoSource.values
|
||||||
|
.firstWhere((info) => info.id == WalletBase.idFor(name, getType()));
|
||||||
|
wallet = WowneroWallet(walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
|
||||||
|
final isValid = wallet.walletAddresses.validate();
|
||||||
|
|
||||||
|
if (!isValid) {
|
||||||
|
await restoreOrResetWalletFiles(name);
|
||||||
|
wallet.close();
|
||||||
|
return openWallet(name, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
await wallet.init();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
} catch (e, s) {
|
||||||
|
// TODO: Implement Exception for wallet list service.
|
||||||
|
|
||||||
|
final bool isBadAlloc = e.toString().contains('bad_alloc') ||
|
||||||
|
(e is WalletOpeningException &&
|
||||||
|
(e.message == 'std::bad_alloc' || e.message.contains('bad_alloc')));
|
||||||
|
|
||||||
|
final bool doesNotCorrespond = e.toString().contains('does not correspond') ||
|
||||||
|
(e is WalletOpeningException && e.message.contains('does not correspond'));
|
||||||
|
|
||||||
|
final bool isMissingCacheFilesIOS = e.toString().contains('basic_string') ||
|
||||||
|
(e is WalletOpeningException && e.message.contains('basic_string'));
|
||||||
|
|
||||||
|
final bool isMissingCacheFilesAndroid = e.toString().contains('input_stream') ||
|
||||||
|
e.toString().contains('input stream error') ||
|
||||||
|
(e is WalletOpeningException &&
|
||||||
|
(e.message.contains('input_stream') || e.message.contains('input stream error')));
|
||||||
|
|
||||||
|
final bool invalidSignature = e.toString().contains('invalid signature') ||
|
||||||
|
(e is WalletOpeningException && e.message.contains('invalid signature'));
|
||||||
|
|
||||||
|
if (!isBadAlloc &&
|
||||||
|
!doesNotCorrespond &&
|
||||||
|
!isMissingCacheFilesIOS &&
|
||||||
|
!isMissingCacheFilesAndroid &&
|
||||||
|
!invalidSignature &&
|
||||||
|
wallet != null &&
|
||||||
|
wallet.onError != null) {
|
||||||
|
wallet.onError!(FlutterErrorDetails(exception: e, stack: s));
|
||||||
|
}
|
||||||
|
|
||||||
|
await restoreOrResetWalletFiles(name);
|
||||||
|
return openWallet(name, password);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> remove(String wallet) async {
|
||||||
|
final path = await pathForWalletDir(name: wallet, type: getType());
|
||||||
|
if (openedWalletsByPath["$path/$wallet"] != null) {
|
||||||
|
// NOTE: this is realistically only required on windows.
|
||||||
|
print("closing wallet");
|
||||||
|
final wmaddr = wmPtr.address;
|
||||||
|
final waddr = openedWalletsByPath["$path/$wallet"]!.address;
|
||||||
|
// await Isolate.run(() {
|
||||||
|
wownero.WalletManager_closeWallet(
|
||||||
|
Pointer.fromAddress(wmaddr), Pointer.fromAddress(waddr), false);
|
||||||
|
// });
|
||||||
|
openedWalletsByPath.remove("$path/$wallet");
|
||||||
|
print("wallet closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
final file = Directory(path);
|
||||||
|
final isExist = file.existsSync();
|
||||||
|
|
||||||
|
if (isExist) {
|
||||||
|
await file.delete(recursive: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
final walletInfo = walletInfoSource.values
|
||||||
|
.firstWhere((info) => info.id == WalletBase.idFor(wallet, getType()));
|
||||||
|
await walletInfoSource.delete(walletInfo.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> rename(String currentName, String password, String newName) async {
|
||||||
|
final currentWalletInfo = walletInfoSource.values
|
||||||
|
.firstWhere((info) => info.id == WalletBase.idFor(currentName, getType()));
|
||||||
|
final currentWallet =
|
||||||
|
WowneroWallet(walletInfo: currentWalletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
|
||||||
|
|
||||||
|
await currentWallet.renameWalletFiles(newName);
|
||||||
|
|
||||||
|
final newWalletInfo = currentWalletInfo;
|
||||||
|
newWalletInfo.id = WalletBase.idFor(newName, getType());
|
||||||
|
newWalletInfo.name = newName;
|
||||||
|
|
||||||
|
await walletInfoSource.put(currentWalletInfo.key, newWalletInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<WowneroWallet> restoreFromKeys(WowneroRestoreWalletFromKeysCredentials credentials,
|
||||||
|
{bool? isTestnet}) async {
|
||||||
|
try {
|
||||||
|
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||||
|
await wownero_wallet_manager.restoreFromKeys(
|
||||||
|
path: path,
|
||||||
|
password: credentials.password!,
|
||||||
|
language: credentials.language,
|
||||||
|
restoreHeight: credentials.height!,
|
||||||
|
address: credentials.address,
|
||||||
|
viewKey: credentials.viewKey,
|
||||||
|
spendKey: credentials.spendKey);
|
||||||
|
final wallet = WowneroWallet(
|
||||||
|
walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource);
|
||||||
|
await wallet.init();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
} catch (e) {
|
||||||
|
// TODO: Implement Exception for wallet list service.
|
||||||
|
print('WowneroWalletsManager Error: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<WowneroWallet> restoreFromHardwareWallet(WowneroNewWalletCredentials credentials) {
|
||||||
|
throw UnimplementedError(
|
||||||
|
"Restoring a Wownero wallet from a hardware wallet is not yet supported!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<WowneroWallet> restoreFromSeed(WowneroRestoreWalletFromSeedCredentials credentials,
|
||||||
|
{bool? isTestnet}) async {
|
||||||
|
// Restore from Polyseed
|
||||||
|
if (Polyseed.isValidSeed(credentials.mnemonic)) {
|
||||||
|
return restoreFromPolyseed(credentials);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||||
|
await wownero_wallet_manager.restoreFromSeed(
|
||||||
|
path: path,
|
||||||
|
password: credentials.password!,
|
||||||
|
seed: credentials.mnemonic,
|
||||||
|
restoreHeight: credentials.height!);
|
||||||
|
final wallet = WowneroWallet(
|
||||||
|
walletInfo: credentials.walletInfo!, unspentCoinsInfo: unspentCoinsInfoSource);
|
||||||
|
await wallet.init();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
} catch (e) {
|
||||||
|
// TODO: Implement Exception for wallet list service.
|
||||||
|
print('WowneroWalletsManager Error: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<WowneroWallet> restoreFromPolyseed(
|
||||||
|
WowneroRestoreWalletFromSeedCredentials credentials) async {
|
||||||
|
try {
|
||||||
|
final path = await pathForWallet(name: credentials.name, type: getType());
|
||||||
|
final polyseedCoin = PolyseedCoin.POLYSEED_WOWNERO;
|
||||||
|
final lang = PolyseedLang.getByPhrase(credentials.mnemonic);
|
||||||
|
final polyseed = Polyseed.decode(credentials.mnemonic, lang, polyseedCoin);
|
||||||
|
|
||||||
|
return _restoreFromPolyseed(
|
||||||
|
path, credentials.password!, polyseed, credentials.walletInfo!, lang);
|
||||||
|
} catch (e) {
|
||||||
|
// TODO: Implement Exception for wallet list service.
|
||||||
|
print('WowneroWalletsManager Error: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<WowneroWallet> _restoreFromPolyseed(
|
||||||
|
String path, String password, Polyseed polyseed, WalletInfo walletInfo, PolyseedLang lang,
|
||||||
|
{PolyseedCoin coin = PolyseedCoin.POLYSEED_WOWNERO, int? overrideHeight}) async {
|
||||||
|
final height = overrideHeight ??
|
||||||
|
getWowneroHeightByDate(date: DateTime.fromMillisecondsSinceEpoch(polyseed.birthday * 1000));
|
||||||
|
final spendKey = polyseed.generateKey(coin, 32).toHexString();
|
||||||
|
final seed = polyseed.encode(lang, coin);
|
||||||
|
|
||||||
|
walletInfo.isRecovery = true;
|
||||||
|
walletInfo.restoreHeight = height;
|
||||||
|
|
||||||
|
await wownero_wallet_manager.restoreFromSpendKey(
|
||||||
|
path: path,
|
||||||
|
password: password,
|
||||||
|
seed: seed,
|
||||||
|
language: lang.nameEnglish,
|
||||||
|
restoreHeight: height,
|
||||||
|
spendKey: spendKey);
|
||||||
|
|
||||||
|
final wallet = WowneroWallet(walletInfo: walletInfo, unspentCoinsInfo: unspentCoinsInfoSource);
|
||||||
|
await wallet.init();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> repairOldAndroidWallet(String name) async {
|
||||||
|
try {
|
||||||
|
if (!Platform.isAndroid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final oldAndroidWalletDirPath = await outdatedAndroidPathForWalletDir(name: name);
|
||||||
|
final dir = Directory(oldAndroidWalletDirPath);
|
||||||
|
|
||||||
|
if (!dir.existsSync()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final newWalletDirPath = await pathForWalletDir(name: name, type: getType());
|
||||||
|
|
||||||
|
dir.listSync().forEach((f) {
|
||||||
|
final file = File(f.path);
|
||||||
|
final name = f.path.split('/').last;
|
||||||
|
final newPath = newWalletDirPath + '/$name';
|
||||||
|
final newFile = File(newPath);
|
||||||
|
|
||||||
|
if (!newFile.existsSync()) {
|
||||||
|
newFile.createSync();
|
||||||
|
}
|
||||||
|
newFile.writeAsBytesSync(file.readAsBytesSync());
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
print(e.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
757
cw_wownero/pubspec.lock
Normal file
757
cw_wownero/pubspec.lock
Normal file
|
@ -0,0 +1,757 @@
|
||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "47.0.0"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.7.0"
|
||||||
|
args:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: args
|
||||||
|
sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
|
asn1lib:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: asn1lib
|
||||||
|
sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.11.0"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
build:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build
|
||||||
|
sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.1"
|
||||||
|
build_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_config
|
||||||
|
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
build_daemon:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_daemon
|
||||||
|
sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.0"
|
||||||
|
build_resolvers:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: build_resolvers
|
||||||
|
sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.10"
|
||||||
|
build_runner:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: build_runner
|
||||||
|
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.9"
|
||||||
|
build_runner_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: build_runner_core
|
||||||
|
sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.2.7"
|
||||||
|
built_collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: built_collection
|
||||||
|
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.1.1"
|
||||||
|
built_value:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: built_value
|
||||||
|
sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "8.4.3"
|
||||||
|
characters:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: characters
|
||||||
|
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
checked_yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: checked_yaml
|
||||||
|
sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
|
clock:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: clock
|
||||||
|
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
code_builder:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: code_builder
|
||||||
|
sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.4.0"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.18.0"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.1"
|
||||||
|
crypto:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: crypto
|
||||||
|
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
cw_core:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "../cw_core"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.0.1"
|
||||||
|
dart_style:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: dart_style
|
||||||
|
sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.4"
|
||||||
|
encrypt:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: encrypt
|
||||||
|
sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "5.0.1"
|
||||||
|
fake_async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fake_async
|
||||||
|
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.1"
|
||||||
|
ffi:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file
|
||||||
|
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.1.4"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_mobx:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_mobx
|
||||||
|
sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.6+5"
|
||||||
|
flutter_test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
frontend_server_client:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: frontend_server_client
|
||||||
|
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.0"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
graphs:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: graphs
|
||||||
|
sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
hashlib:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hashlib
|
||||||
|
sha256: "71bf102329ddb8e50c8a995ee4645ae7f1728bb65e575c17196b4d8262121a96"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.0"
|
||||||
|
hashlib_codecs:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hashlib_codecs
|
||||||
|
sha256: "49e2a471f74b15f1854263e58c2ac11f2b631b5b12c836f9708a35397d36d626"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
hive:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: hive
|
||||||
|
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.3"
|
||||||
|
hive_generator:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: hive_generator
|
||||||
|
sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
|
http:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.1"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.2"
|
||||||
|
intl:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: intl
|
||||||
|
sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.18.1"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
|
js:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: js
|
||||||
|
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.7"
|
||||||
|
json_annotation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: json_annotation
|
||||||
|
sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.8.0"
|
||||||
|
leak_tracker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker
|
||||||
|
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.0.0"
|
||||||
|
leak_tracker_flutter_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_flutter_testing
|
||||||
|
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
|
leak_tracker_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_testing
|
||||||
|
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.16+1"
|
||||||
|
material_color_utilities:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: material_color_utilities
|
||||||
|
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.0"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.11.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
|
mobx:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: mobx
|
||||||
|
sha256: f1862bd92c6a903fab67338f27e2f731117c3cb9ea37cee1a487f9e4e0de314a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3+1"
|
||||||
|
mobx_codegen:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: mobx_codegen
|
||||||
|
sha256: "86122e410d8ea24dda0c69adb5c2a6ccadd5ce02ad46e144764e0d0184a06181"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
monero:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "."
|
||||||
|
ref: "200ad221441c6481439c966288b6e8834769ed21"
|
||||||
|
resolved-ref: "200ad221441c6481439c966288b6e8834769ed21"
|
||||||
|
url: "https://git.mrcyjanek.net/mrcyjanek/monero.dart"
|
||||||
|
source: git
|
||||||
|
version: "0.0.0"
|
||||||
|
mutex:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: mutex
|
||||||
|
sha256: "8827da25de792088eb33e572115a5eb0d61d61a3c01acbc8bcbe76ed78f1a1f2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.0"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
path:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.0"
|
||||||
|
path_provider:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: path_provider
|
||||||
|
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
path_provider_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_android
|
||||||
|
sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
path_provider_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_foundation
|
||||||
|
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.1"
|
||||||
|
path_provider_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_linux
|
||||||
|
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
path_provider_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_platform_interface
|
||||||
|
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
path_provider_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_windows
|
||||||
|
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: platform
|
||||||
|
sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.0"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
pointycastle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pointycastle
|
||||||
|
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.7.3"
|
||||||
|
polyseed:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: polyseed
|
||||||
|
sha256: "9b48ec535b10863f78f6354ec983b4cc0c88ca69ff48fee469d0fd1954b01d4f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.0.2"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.1"
|
||||||
|
process:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: process
|
||||||
|
sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.2.4"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
pubspec_parse:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pubspec_parse
|
||||||
|
sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
|
sky_engine:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.99"
|
||||||
|
socks5_proxy:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: socks5_proxy
|
||||||
|
sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
|
source_gen:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_gen
|
||||||
|
sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.6"
|
||||||
|
source_helper:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_helper
|
||||||
|
sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.3"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.0"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.11.1"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
stream_transform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_transform
|
||||||
|
sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.6.1"
|
||||||
|
timing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: timing
|
||||||
|
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.1"
|
||||||
|
vector_math:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_math
|
||||||
|
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "13.0.0"
|
||||||
|
watcher:
|
||||||
|
dependency: "direct overridden"
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
|
win32:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: win32
|
||||||
|
sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.3"
|
||||||
|
xdg_directories:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xdg_directories
|
||||||
|
sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.0+3"
|
||||||
|
yaml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: yaml
|
||||||
|
sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.1"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.2.0-0 <4.0.0"
|
||||||
|
flutter: ">=3.7.0"
|
82
cw_wownero/pubspec.yaml
Normal file
82
cw_wownero/pubspec.yaml
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
name: cw_wownero
|
||||||
|
description: A new flutter plugin project.
|
||||||
|
version: 0.0.1
|
||||||
|
publish_to: none
|
||||||
|
author: Cake Wallet
|
||||||
|
homepage: https://cakewallet.com
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: ">=2.19.0 <3.0.0"
|
||||||
|
flutter: ">=1.20.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
ffi: ^2.0.1
|
||||||
|
http: ^1.1.0
|
||||||
|
path_provider: ^2.0.11
|
||||||
|
mobx: ^2.0.7+4
|
||||||
|
flutter_mobx: ^2.0.6+1
|
||||||
|
intl: ^0.18.0
|
||||||
|
encrypt: ^5.0.1
|
||||||
|
polyseed: ^0.0.2
|
||||||
|
cw_core:
|
||||||
|
path: ../cw_core
|
||||||
|
monero:
|
||||||
|
git:
|
||||||
|
url: https://git.mrcyjanek.net/mrcyjanek/monero.dart
|
||||||
|
ref: 200ad221441c6481439c966288b6e8834769ed21
|
||||||
|
mutex: ^3.1.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
build_runner: ^2.4.7
|
||||||
|
build_resolvers: ^2.0.9
|
||||||
|
mobx_codegen: ^2.0.7
|
||||||
|
hive_generator: ^1.1.3
|
||||||
|
|
||||||
|
dependency_overrides:
|
||||||
|
watcher: ^1.1.0
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
# This section identifies this Flutter project as a plugin project.
|
||||||
|
# The androidPackage and pluginClass identifiers should not ordinarily
|
||||||
|
# be modified. They are used by the tooling to maintain consistency when
|
||||||
|
# adding or updating assets for this project.
|
||||||
|
|
||||||
|
|
||||||
|
# To add assets to your plugin package, add an assets section, like this:
|
||||||
|
# assets:
|
||||||
|
# - images/a_dot_burr.jpeg
|
||||||
|
# - images/a_dot_ham.jpeg
|
||||||
|
#
|
||||||
|
# For details regarding assets in packages, see
|
||||||
|
# https://flutter.dev/assets-and-images/#from-packages
|
||||||
|
#
|
||||||
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
|
# https://flutter.dev/assets-and-images/#resolution-aware.
|
||||||
|
|
||||||
|
# To add custom fonts to your plugin package, add a fonts section here,
|
||||||
|
# in this "flutter" section. Each entry in this list should have a
|
||||||
|
# "family" key with the font family name, and a "fonts" key with a
|
||||||
|
# list giving the asset and other descriptors for the font. For
|
||||||
|
# example:
|
||||||
|
# fonts:
|
||||||
|
# - family: Schyler
|
||||||
|
# fonts:
|
||||||
|
# - asset: fonts/Schyler-Regular.ttf
|
||||||
|
# - asset: fonts/Schyler-Italic.ttf
|
||||||
|
# style: italic
|
||||||
|
# - family: Trajan Pro
|
||||||
|
# fonts:
|
||||||
|
# - asset: fonts/TrajanPro.ttf
|
||||||
|
# - asset: fonts/TrajanPro_Bold.ttf
|
||||||
|
# weight: 700
|
||||||
|
#
|
||||||
|
# For details regarding fonts in packages, see
|
||||||
|
# https://flutter.dev/custom-fonts/#from-packages
|
|
@ -7,7 +7,7 @@
|
||||||
**Core Folder/Files Setup**
|
**Core Folder/Files Setup**
|
||||||
- Idenitify your core component/package (major project component), which would power the integration e.g web3dart, solana, onchain etc
|
- Idenitify your core component/package (major project component), which would power the integration e.g web3dart, solana, onchain etc
|
||||||
- Add a new entry to `WalletType` class in `cw_core/wallet_type.dart`.
|
- Add a new entry to `WalletType` class in `cw_core/wallet_type.dart`.
|
||||||
- Fill out the necessary information int he various functions in the files, concerning the wallet name, the native currency type, symbol etc.
|
- Fill out the necessary information in the various functions in the files, concerning the wallet name, the native currency type, symbol etc.
|
||||||
- Go to `cw_core/lib/currency_for_wallet_type.dart`, in the `currencyForWalletType` function, add a case for `walletx`, returning the native cryptocurrency for `walletx`.
|
- Go to `cw_core/lib/currency_for_wallet_type.dart`, in the `currencyForWalletType` function, add a case for `walletx`, returning the native cryptocurrency for `walletx`.
|
||||||
- If the cryptocurrency for walletx is not available among the default cryptocurrencies, add a new cryptocurrency entry in `cw_core/lib/cryptocurrency.dart`.
|
- If the cryptocurrency for walletx is not available among the default cryptocurrencies, add a new cryptocurrency entry in `cw_core/lib/cryptocurrency.dart`.
|
||||||
- Add the newly created cryptocurrency name to the list named `all` in this file.
|
- Add the newly created cryptocurrency name to the list named `all` in this file.
|
||||||
|
@ -70,7 +70,7 @@ A `Proxy` class is used to communicate with the specific wallet package we have.
|
||||||
outputContent += '\tWalletType.walletx,\n’;
|
outputContent += '\tWalletType.walletx,\n’;
|
||||||
}
|
}
|
||||||
|
|
||||||
- Head over to `scripts/android/pubspec.sh` script, and modify the `CONFIG_ARGS` under `$CAKEWALLET`. Add `"—walletx”` to the end of the passed in params.
|
- Head over to `scripts/android/pubspec_gen.sh` script, and modify the `CONFIG_ARGS` under `$CAKEWALLET`. Add `"—walletx”` to the end of the passed in params.
|
||||||
- Repeat this in `scripts/ios/app_config.sh` and `scripts/macos/app_config.sh`
|
- Repeat this in `scripts/ios/app_config.sh` and `scripts/macos/app_config.sh`
|
||||||
- Open a terminal and cd into `scripts/android/`. Run the following commands to run setup configuration scripts(proxy class, add walletx to list of wallet types and add cw_walletx to pubspec).
|
- Open a terminal and cd into `scripts/android/`. Run the following commands to run setup configuration scripts(proxy class, add walletx to list of wallet types and add cw_walletx to pubspec).
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,7 @@ class WalletCreationService {
|
||||||
case WalletType.tron:
|
case WalletType.tron:
|
||||||
return true;
|
return true;
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
|
case WalletType.wownero:
|
||||||
case WalletType.none:
|
case WalletType.none:
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
|
|
|
@ -78,6 +78,7 @@ class ProvidersHelper {
|
||||||
];
|
];
|
||||||
case WalletType.none:
|
case WalletType.none:
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
|
case WalletType.wownero:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,6 +115,7 @@ class ProvidersHelper {
|
||||||
case WalletType.banano:
|
case WalletType.banano:
|
||||||
case WalletType.none:
|
case WalletType.none:
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
|
case WalletType.wownero:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
|
||||||
case WalletType.tron:
|
case WalletType.tron:
|
||||||
return true;
|
return true;
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
|
case WalletType.wownero:
|
||||||
case WalletType.none:
|
case WalletType.none:
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
|
|
|
@ -80,6 +80,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
||||||
return true;
|
return true;
|
||||||
case WalletType.none:
|
case WalletType.none:
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
|
case WalletType.wownero:
|
||||||
case WalletType.haven:
|
case WalletType.haven:
|
||||||
case WalletType.litecoin:
|
case WalletType.litecoin:
|
||||||
case WalletType.bitcoinCash:
|
case WalletType.bitcoinCash:
|
||||||
|
|
343
lib/wownero/cw_wownero.dart
Normal file
343
lib/wownero/cw_wownero.dart
Normal file
|
@ -0,0 +1,343 @@
|
||||||
|
part of 'wownero.dart';
|
||||||
|
|
||||||
|
class CWWowneroAccountList extends WowneroAccountList {
|
||||||
|
CWWowneroAccountList(this._wallet);
|
||||||
|
|
||||||
|
final Object _wallet;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@computed
|
||||||
|
ObservableList<Account> get accounts {
|
||||||
|
final wowneroWallet = _wallet as WowneroWallet;
|
||||||
|
final accounts = wowneroWallet.walletAddresses.accountList.accounts
|
||||||
|
.map((acc) => Account(id: acc.id, label: acc.label, balance: acc.balance))
|
||||||
|
.toList();
|
||||||
|
return ObservableList<Account>.of(accounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(Object wallet) {
|
||||||
|
final wowneroWallet = wallet as WowneroWallet;
|
||||||
|
wowneroWallet.walletAddresses.accountList.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void refresh(Object wallet) {
|
||||||
|
final wowneroWallet = wallet as WowneroWallet;
|
||||||
|
wowneroWallet.walletAddresses.accountList.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Account> getAll(Object wallet) {
|
||||||
|
final wowneroWallet = wallet as WowneroWallet;
|
||||||
|
return wowneroWallet.walletAddresses.accountList
|
||||||
|
.getAll()
|
||||||
|
.map((acc) => Account(id: acc.id, label: acc.label, balance: acc.balance))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> addAccount(Object wallet, {required String label}) async {
|
||||||
|
final wowneroWallet = wallet as WowneroWallet;
|
||||||
|
await wowneroWallet.walletAddresses.accountList.addAccount(label: label);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setLabelAccount(Object wallet,
|
||||||
|
{required int accountIndex, required String label}) async {
|
||||||
|
final wowneroWallet = wallet as WowneroWallet;
|
||||||
|
await wowneroWallet.walletAddresses.accountList
|
||||||
|
.setLabelAccount(accountIndex: accountIndex, label: label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CWWowneroSubaddressList extends WowneroSubaddressList {
|
||||||
|
CWWowneroSubaddressList(this._wallet);
|
||||||
|
|
||||||
|
final Object _wallet;
|
||||||
|
|
||||||
|
@override
|
||||||
|
@computed
|
||||||
|
ObservableList<Subaddress> get subaddresses {
|
||||||
|
final wowneroWallet = _wallet as WowneroWallet;
|
||||||
|
final subAddresses = wowneroWallet.walletAddresses.subaddressList.subaddresses
|
||||||
|
.map((sub) => Subaddress(id: sub.id, address: sub.address, label: sub.label))
|
||||||
|
.toList();
|
||||||
|
return ObservableList<Subaddress>.of(subAddresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void update(Object wallet, {required int accountIndex}) {
|
||||||
|
final wowneroWallet = wallet as WowneroWallet;
|
||||||
|
wowneroWallet.walletAddresses.subaddressList.update(accountIndex: accountIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void refresh(Object wallet, {required int accountIndex}) {
|
||||||
|
final wowneroWallet = wallet as WowneroWallet;
|
||||||
|
wowneroWallet.walletAddresses.subaddressList.refresh(accountIndex: accountIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Subaddress> getAll(Object wallet) {
|
||||||
|
final wowneroWallet = wallet as WowneroWallet;
|
||||||
|
return wowneroWallet.walletAddresses.subaddressList
|
||||||
|
.getAll()
|
||||||
|
.map((sub) => Subaddress(id: sub.id, label: sub.label, address: sub.address))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> addSubaddress(Object wallet,
|
||||||
|
{required int accountIndex, required String label}) async {
|
||||||
|
final wowneroWallet = wallet as WowneroWallet;
|
||||||
|
await wowneroWallet.walletAddresses.subaddressList
|
||||||
|
.addSubaddress(accountIndex: accountIndex, label: label);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setLabelSubaddress(Object wallet,
|
||||||
|
{required int accountIndex, required int addressIndex, required String label}) async {
|
||||||
|
final wowneroWallet = wallet as WowneroWallet;
|
||||||
|
await wowneroWallet.walletAddresses.subaddressList
|
||||||
|
.setLabelSubaddress(accountIndex: accountIndex, addressIndex: addressIndex, label: label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CWWowneroWalletDetails extends WowneroWalletDetails {
|
||||||
|
CWWowneroWalletDetails(this._wallet);
|
||||||
|
|
||||||
|
final Object _wallet;
|
||||||
|
|
||||||
|
@computed
|
||||||
|
@override
|
||||||
|
Account get account {
|
||||||
|
final wowneroWallet = _wallet as WowneroWallet;
|
||||||
|
final acc = wowneroWallet.walletAddresses.account;
|
||||||
|
return Account(id: acc!.id, label: acc.label, balance: acc.balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@computed
|
||||||
|
@override
|
||||||
|
WowneroBalance get balance {
|
||||||
|
throw Exception('Unimplemented');
|
||||||
|
// return WowneroBalance();
|
||||||
|
//return WowneroBalance(
|
||||||
|
// fullBalance: balance.fullBalance,
|
||||||
|
// unlockedBalance: balance.unlockedBalance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CWWownero extends Wownero {
|
||||||
|
@override
|
||||||
|
WowneroAccountList getAccountList(Object wallet) => CWWowneroAccountList(wallet);
|
||||||
|
|
||||||
|
@override
|
||||||
|
WowneroSubaddressList getSubaddressList(Object wallet) => CWWowneroSubaddressList(wallet);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TransactionHistoryBase getTransactionHistory(Object wallet) {
|
||||||
|
final wowneroWallet = wallet as WowneroWallet;
|
||||||
|
return wowneroWallet.transactionHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
WowneroWalletDetails getWowneroWalletDetails(Object wallet) => CWWowneroWalletDetails(wallet);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int getHeightByDate({required DateTime date}) => getWowneroHeightByDate(date: date);
|
||||||
|
|
||||||
|
@override
|
||||||
|
TransactionPriority getDefaultTransactionPriority() => MoneroTransactionPriority.automatic;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TransactionPriority getWowneroTransactionPrioritySlow() => MoneroTransactionPriority.slow;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TransactionPriority getWowneroTransactionPriorityAutomatic() =>
|
||||||
|
MoneroTransactionPriority.automatic;
|
||||||
|
|
||||||
|
@override
|
||||||
|
TransactionPriority deserializeWowneroTransactionPriority({required int raw}) =>
|
||||||
|
MoneroTransactionPriority.deserialize(raw: raw);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<TransactionPriority> getTransactionPriorities() => MoneroTransactionPriority.all;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> getWowneroWordList(String language) {
|
||||||
|
if (language.startsWith("POLYSEED_")) {
|
||||||
|
final lang = language.replaceAll("POLYSEED_", "");
|
||||||
|
return PolyseedLang.getByEnglishName(lang).words;
|
||||||
|
}
|
||||||
|
switch (language.toLowerCase()) {
|
||||||
|
case 'english':
|
||||||
|
return EnglishMnemonics.words;
|
||||||
|
case 'chinese (simplified)':
|
||||||
|
return ChineseSimplifiedMnemonics.words;
|
||||||
|
case 'dutch':
|
||||||
|
return DutchMnemonics.words;
|
||||||
|
case 'german':
|
||||||
|
return GermanMnemonics.words;
|
||||||
|
case 'japanese':
|
||||||
|
return JapaneseMnemonics.words;
|
||||||
|
case 'portuguese':
|
||||||
|
return PortugueseMnemonics.words;
|
||||||
|
case 'russian':
|
||||||
|
return RussianMnemonics.words;
|
||||||
|
case 'spanish':
|
||||||
|
return SpanishMnemonics.words;
|
||||||
|
case 'french':
|
||||||
|
return FrenchMnemonics.words;
|
||||||
|
case 'italian':
|
||||||
|
return ItalianMnemonics.words;
|
||||||
|
default:
|
||||||
|
return EnglishMnemonics.words;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletCredentials createWowneroRestoreWalletFromKeysCredentials(
|
||||||
|
{required String name,
|
||||||
|
required String spendKey,
|
||||||
|
required String viewKey,
|
||||||
|
required String address,
|
||||||
|
required String password,
|
||||||
|
required String language,
|
||||||
|
required int height}) =>
|
||||||
|
WowneroRestoreWalletFromKeysCredentials(
|
||||||
|
name: name,
|
||||||
|
spendKey: spendKey,
|
||||||
|
viewKey: viewKey,
|
||||||
|
address: address,
|
||||||
|
password: password,
|
||||||
|
language: language,
|
||||||
|
height: height);
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletCredentials createWowneroRestoreWalletFromSeedCredentials(
|
||||||
|
{required String name,
|
||||||
|
required String password,
|
||||||
|
required int height,
|
||||||
|
required String mnemonic}) =>
|
||||||
|
WowneroRestoreWalletFromSeedCredentials(
|
||||||
|
name: name, password: password, height: height, mnemonic: mnemonic);
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletCredentials createWowneroNewWalletCredentials({
|
||||||
|
required String name,
|
||||||
|
required String language,
|
||||||
|
required bool isPolyseed,
|
||||||
|
String? password}) =>
|
||||||
|
WowneroNewWalletCredentials(
|
||||||
|
name: name, password: password, language: language, isPolyseed: isPolyseed);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, String> getKeys(Object wallet) {
|
||||||
|
final wowneroWallet = wallet as WowneroWallet;
|
||||||
|
final keys = wowneroWallet.keys;
|
||||||
|
return <String, String>{
|
||||||
|
'privateSpendKey': keys.privateSpendKey,
|
||||||
|
'privateViewKey': keys.privateViewKey,
|
||||||
|
'publicSpendKey': keys.publicSpendKey,
|
||||||
|
'publicViewKey': keys.publicViewKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object createWowneroTransactionCreationCredentials(
|
||||||
|
{required List<Output> outputs, required TransactionPriority priority}) =>
|
||||||
|
WowneroTransactionCreationCredentials(
|
||||||
|
outputs: outputs
|
||||||
|
.map((out) => OutputInfo(
|
||||||
|
fiatAmount: out.fiatAmount,
|
||||||
|
cryptoAmount: out.cryptoAmount,
|
||||||
|
address: out.address,
|
||||||
|
note: out.note,
|
||||||
|
sendAll: out.sendAll,
|
||||||
|
extractedAddress: out.extractedAddress,
|
||||||
|
isParsedAddress: out.isParsedAddress,
|
||||||
|
formattedCryptoAmount: out.formattedCryptoAmount))
|
||||||
|
.toList(),
|
||||||
|
priority: priority as MoneroTransactionPriority);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object createWowneroTransactionCreationCredentialsRaw(
|
||||||
|
{required List<OutputInfo> outputs, required TransactionPriority priority}) =>
|
||||||
|
WowneroTransactionCreationCredentials(
|
||||||
|
outputs: outputs, priority: priority as MoneroTransactionPriority);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String formatterWowneroAmountToString({required int amount}) =>
|
||||||
|
wowneroAmountToString(amount: amount);
|
||||||
|
|
||||||
|
@override
|
||||||
|
double formatterWowneroAmountToDouble({required int amount}) =>
|
||||||
|
wowneroAmountToDouble(amount: amount);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int formatterWowneroParseAmount({required String amount}) => wowneroParseAmount(amount: amount);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Account getCurrentAccount(Object wallet) {
|
||||||
|
final wowneroWallet = wallet as WowneroWallet;
|
||||||
|
final acc = wowneroWallet.walletAddresses.account;
|
||||||
|
return Account(id: acc!.id, label: acc.label, balance: acc.balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void setCurrentAccount(Object wallet, int id, String label, String? balance) {
|
||||||
|
final wowneroWallet = wallet as WowneroWallet;
|
||||||
|
wowneroWallet.walletAddresses.account =
|
||||||
|
wownero_account.Account(id: id, label: label, balance: balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onStartup() => wownero_wallet_api.onStartup();
|
||||||
|
|
||||||
|
@override
|
||||||
|
int getTransactionInfoAccountId(TransactionInfo tx) {
|
||||||
|
final wowneroTransactionInfo = tx as WowneroTransactionInfo;
|
||||||
|
return wowneroTransactionInfo.accountIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
WalletService createWowneroWalletService(
|
||||||
|
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) =>
|
||||||
|
WowneroWalletService(walletInfoSource, unspentCoinSource);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getTransactionAddress(Object wallet, int accountIndex, int addressIndex) {
|
||||||
|
final wowneroWallet = wallet as WowneroWallet;
|
||||||
|
return wowneroWallet.getTransactionAddress(accountIndex, addressIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getSubaddressLabel(Object wallet, int accountIndex, int addressIndex) {
|
||||||
|
final wowneroWallet = wallet as WowneroWallet;
|
||||||
|
return wowneroWallet.getSubaddressLabel(accountIndex, addressIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, String> pendingTransactionInfo(Object transaction) {
|
||||||
|
final ptx = transaction as PendingWowneroTransaction;
|
||||||
|
return {'id': ptx.id, 'hex': ptx.hex, 'key': ptx.txKey};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Unspent> getUnspents(Object wallet) {
|
||||||
|
final wowneroWallet = wallet as WowneroWallet;
|
||||||
|
return wowneroWallet.unspentCoins;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> updateUnspents(Object wallet) async {
|
||||||
|
final wowneroWallet = wallet as WowneroWallet;
|
||||||
|
await wowneroWallet.updateUnspent();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> getCurrentHeight() async {
|
||||||
|
return wownero_wallet_api.getCurrentHeight();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +1,13 @@
|
||||||
cd cw_core; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
#cd cw_core; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
||||||
cd cw_evm; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
#cd cw_evm; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
||||||
cd cw_monero; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
#cd cw_monero; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
||||||
cd cw_bitcoin; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
#cd cw_bitcoin; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
||||||
cd cw_haven; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
#cd cw_haven; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
||||||
cd cw_nano; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
#cd cw_nano; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
||||||
cd cw_bitcoin_cash; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
#cd cw_bitcoin_cash; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
||||||
cd cw_solana; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
#cd cw_solana; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
||||||
cd cw_tron; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
#cd cw_tron; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
||||||
cd cw_polygon; flutter pub get; cd ..
|
cd cw_wownero; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
||||||
cd cw_ethereum; flutter pub get; cd ..
|
#cd cw_polygon; flutter pub get; cd ..
|
||||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
#cd cw_ethereum; flutter pub get; cd ..
|
||||||
|
#flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||||
|
|
|
@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in
|
||||||
CONFIG_ARGS="--monero"
|
CONFIG_ARGS="--monero"
|
||||||
;;
|
;;
|
||||||
$CAKEWALLET)
|
$CAKEWALLET)
|
||||||
CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron" #TODO: fix and add back --haven
|
CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero" #TODO: fix and add back --haven
|
||||||
;;
|
;;
|
||||||
$HAVEN)
|
$HAVEN)
|
||||||
CONFIG_ARGS="--haven"
|
CONFIG_ARGS="--haven"
|
||||||
|
|
|
@ -30,7 +30,7 @@ case $APP_IOS_TYPE in
|
||||||
CONFIG_ARGS="--monero"
|
CONFIG_ARGS="--monero"
|
||||||
;;
|
;;
|
||||||
$CAKEWALLET)
|
$CAKEWALLET)
|
||||||
CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron"
|
CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero"
|
||||||
if [ "$CW_WITH_HAVEN" = true ];then
|
if [ "$CW_WITH_HAVEN" = true ];then
|
||||||
CONFIG_ARGS="$CONFIG_ARGS --haven"
|
CONFIG_ARGS="$CONFIG_ARGS --haven"
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -33,7 +33,7 @@ case $APP_MACOS_TYPE in
|
||||||
$MONERO_COM)
|
$MONERO_COM)
|
||||||
CONFIG_ARGS="--monero";;
|
CONFIG_ARGS="--monero";;
|
||||||
$CAKEWALLET)
|
$CAKEWALLET)
|
||||||
CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron";; #--haven
|
CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero";; #--haven
|
||||||
esac
|
esac
|
||||||
|
|
||||||
cp -rf pubspec_description.yaml pubspec.yaml
|
cp -rf pubspec_description.yaml pubspec.yaml
|
||||||
|
|
|
@ -9,6 +9,7 @@ const nanoOutputPath = 'lib/nano/nano.dart';
|
||||||
const polygonOutputPath = 'lib/polygon/polygon.dart';
|
const polygonOutputPath = 'lib/polygon/polygon.dart';
|
||||||
const solanaOutputPath = 'lib/solana/solana.dart';
|
const solanaOutputPath = 'lib/solana/solana.dart';
|
||||||
const tronOutputPath = 'lib/tron/tron.dart';
|
const tronOutputPath = 'lib/tron/tron.dart';
|
||||||
|
const wowneroOutputPath = 'lib/wownero/wownero.dart';
|
||||||
const walletTypesPath = 'lib/wallet_types.g.dart';
|
const walletTypesPath = 'lib/wallet_types.g.dart';
|
||||||
const secureStoragePath = 'lib/core/secure_storage.dart';
|
const secureStoragePath = 'lib/core/secure_storage.dart';
|
||||||
const pubspecDefaultPath = 'pubspec_default.yaml';
|
const pubspecDefaultPath = 'pubspec_default.yaml';
|
||||||
|
@ -26,6 +27,7 @@ Future<void> main(List<String> args) async {
|
||||||
final hasPolygon = args.contains('${prefix}polygon');
|
final hasPolygon = args.contains('${prefix}polygon');
|
||||||
final hasSolana = args.contains('${prefix}solana');
|
final hasSolana = args.contains('${prefix}solana');
|
||||||
final hasTron = args.contains('${prefix}tron');
|
final hasTron = args.contains('${prefix}tron');
|
||||||
|
final hasWownero = args.contains('${prefix}wownero');
|
||||||
final excludeFlutterSecureStorage = args.contains('${prefix}excludeFlutterSecureStorage');
|
final excludeFlutterSecureStorage = args.contains('${prefix}excludeFlutterSecureStorage');
|
||||||
|
|
||||||
await generateBitcoin(hasBitcoin);
|
await generateBitcoin(hasBitcoin);
|
||||||
|
@ -37,6 +39,7 @@ Future<void> main(List<String> args) async {
|
||||||
await generatePolygon(hasPolygon);
|
await generatePolygon(hasPolygon);
|
||||||
await generateSolana(hasSolana);
|
await generateSolana(hasSolana);
|
||||||
await generateTron(hasTron);
|
await generateTron(hasTron);
|
||||||
|
await generateWownero(hasWownero);
|
||||||
// await generateBanano(hasEthereum);
|
// await generateBanano(hasEthereum);
|
||||||
|
|
||||||
await generatePubspec(
|
await generatePubspec(
|
||||||
|
@ -51,6 +54,7 @@ Future<void> main(List<String> args) async {
|
||||||
hasPolygon: hasPolygon,
|
hasPolygon: hasPolygon,
|
||||||
hasSolana: hasSolana,
|
hasSolana: hasSolana,
|
||||||
hasTron: hasTron,
|
hasTron: hasTron,
|
||||||
|
hasWownero: hasWownero,
|
||||||
);
|
);
|
||||||
await generateWalletTypes(
|
await generateWalletTypes(
|
||||||
hasMonero: hasMonero,
|
hasMonero: hasMonero,
|
||||||
|
@ -63,6 +67,7 @@ Future<void> main(List<String> args) async {
|
||||||
hasPolygon: hasPolygon,
|
hasPolygon: hasPolygon,
|
||||||
hasSolana: hasSolana,
|
hasSolana: hasSolana,
|
||||||
hasTron: hasTron,
|
hasTron: hasTron,
|
||||||
|
hasWownero: hasWownero,
|
||||||
);
|
);
|
||||||
await injectSecureStorage(!excludeFlutterSecureStorage);
|
await injectSecureStorage(!excludeFlutterSecureStorage);
|
||||||
}
|
}
|
||||||
|
@ -414,6 +419,187 @@ abstract class MoneroAccountList {
|
||||||
await outputFile.writeAsString(output);
|
await outputFile.writeAsString(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> generateWownero(bool hasImplementation) async {
|
||||||
|
final outputFile = File(wowneroOutputPath);
|
||||||
|
const wowneroCommonHeaders = """
|
||||||
|
import 'package:cw_core/unspent_transaction_output.dart';
|
||||||
|
import 'package:cw_core/unspent_coins_info.dart';
|
||||||
|
import 'package:mobx/mobx.dart';
|
||||||
|
import 'package:cw_core/wallet_credentials.dart';
|
||||||
|
import 'package:cw_core/wallet_info.dart';
|
||||||
|
import 'package:cw_core/transaction_priority.dart';
|
||||||
|
import 'package:cw_core/transaction_history.dart';
|
||||||
|
import 'package:cw_core/transaction_info.dart';
|
||||||
|
import 'package:cw_core/balance.dart';
|
||||||
|
import 'package:cw_core/output_info.dart';
|
||||||
|
import 'package:cake_wallet/view_model/send/output.dart';
|
||||||
|
import 'package:cw_core/wallet_service.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:polyseed/polyseed.dart';""";
|
||||||
|
const wowneroCWHeaders = """
|
||||||
|
import 'package:cw_core/get_height_by_date.dart';
|
||||||
|
import 'package:cw_core/wownero_amount_format.dart';
|
||||||
|
import 'package:cw_core/monero_transaction_priority.dart';
|
||||||
|
import 'package:cw_wownero/wownero_unspent.dart';
|
||||||
|
import 'package:cw_wownero/wownero_wallet_service.dart';
|
||||||
|
import 'package:cw_wownero/wownero_wallet.dart';
|
||||||
|
import 'package:cw_wownero/wownero_transaction_info.dart';
|
||||||
|
import 'package:cw_wownero/wownero_transaction_creation_credentials.dart';
|
||||||
|
import 'package:cw_core/account.dart' as wownero_account;
|
||||||
|
import 'package:cw_wownero/api/wallet.dart' as wownero_wallet_api;
|
||||||
|
import 'package:cw_wownero/mnemonics/english.dart';
|
||||||
|
import 'package:cw_wownero/mnemonics/chinese_simplified.dart';
|
||||||
|
import 'package:cw_wownero/mnemonics/dutch.dart';
|
||||||
|
import 'package:cw_wownero/mnemonics/german.dart';
|
||||||
|
import 'package:cw_wownero/mnemonics/japanese.dart';
|
||||||
|
import 'package:cw_wownero/mnemonics/russian.dart';
|
||||||
|
import 'package:cw_wownero/mnemonics/spanish.dart';
|
||||||
|
import 'package:cw_wownero/mnemonics/portuguese.dart';
|
||||||
|
import 'package:cw_wownero/mnemonics/french.dart';
|
||||||
|
import 'package:cw_wownero/mnemonics/italian.dart';
|
||||||
|
import 'package:cw_wownero/pending_wownero_transaction.dart';
|
||||||
|
""";
|
||||||
|
const wowneroCwPart = "part 'cw_wownero.dart';";
|
||||||
|
const wowneroContent = """
|
||||||
|
class Account {
|
||||||
|
Account({required this.id, required this.label, this.balance});
|
||||||
|
final int id;
|
||||||
|
final String label;
|
||||||
|
final String? balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Subaddress {
|
||||||
|
Subaddress({
|
||||||
|
required this.id,
|
||||||
|
required this.label,
|
||||||
|
required this.address});
|
||||||
|
final int id;
|
||||||
|
final String label;
|
||||||
|
final String address;
|
||||||
|
}
|
||||||
|
|
||||||
|
class WowneroBalance extends Balance {
|
||||||
|
WowneroBalance({required this.fullBalance, required this.unlockedBalance})
|
||||||
|
: formattedFullBalance = wownero!.formatterWowneroAmountToString(amount: fullBalance),
|
||||||
|
formattedUnlockedBalance =
|
||||||
|
wownero!.formatterWowneroAmountToString(amount: unlockedBalance),
|
||||||
|
super(unlockedBalance, fullBalance);
|
||||||
|
|
||||||
|
WowneroBalance.fromString(
|
||||||
|
{required this.formattedFullBalance,
|
||||||
|
required this.formattedUnlockedBalance})
|
||||||
|
: fullBalance = wownero!.formatterWowneroParseAmount(amount: formattedFullBalance),
|
||||||
|
unlockedBalance = wownero!.formatterWowneroParseAmount(amount: formattedUnlockedBalance),
|
||||||
|
super(wownero!.formatterWowneroParseAmount(amount: formattedUnlockedBalance),
|
||||||
|
wownero!.formatterWowneroParseAmount(amount: formattedFullBalance));
|
||||||
|
|
||||||
|
final int fullBalance;
|
||||||
|
final int unlockedBalance;
|
||||||
|
final String formattedFullBalance;
|
||||||
|
final String formattedUnlockedBalance;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get formattedAvailableBalance => formattedUnlockedBalance;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get formattedAdditionalBalance => formattedFullBalance;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class WowneroWalletDetails {
|
||||||
|
@observable
|
||||||
|
late Account account;
|
||||||
|
|
||||||
|
@observable
|
||||||
|
late WowneroBalance balance;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Wownero {
|
||||||
|
WowneroAccountList getAccountList(Object wallet);
|
||||||
|
|
||||||
|
WowneroSubaddressList getSubaddressList(Object wallet);
|
||||||
|
|
||||||
|
TransactionHistoryBase getTransactionHistory(Object wallet);
|
||||||
|
|
||||||
|
WowneroWalletDetails getWowneroWalletDetails(Object wallet);
|
||||||
|
|
||||||
|
String getTransactionAddress(Object wallet, int accountIndex, int addressIndex);
|
||||||
|
|
||||||
|
String getSubaddressLabel(Object wallet, int accountIndex, int addressIndex);
|
||||||
|
|
||||||
|
int getHeightByDate({required DateTime date});
|
||||||
|
TransactionPriority getDefaultTransactionPriority();
|
||||||
|
TransactionPriority getWowneroTransactionPrioritySlow();
|
||||||
|
TransactionPriority getWowneroTransactionPriorityAutomatic();
|
||||||
|
TransactionPriority deserializeWowneroTransactionPriority({required int raw});
|
||||||
|
List<TransactionPriority> getTransactionPriorities();
|
||||||
|
List<String> getWowneroWordList(String language);
|
||||||
|
|
||||||
|
List<Unspent> getUnspents(Object wallet);
|
||||||
|
Future<void> updateUnspents(Object wallet);
|
||||||
|
|
||||||
|
Future<int> getCurrentHeight();
|
||||||
|
|
||||||
|
WalletCredentials createWowneroRestoreWalletFromKeysCredentials({
|
||||||
|
required String name,
|
||||||
|
required String spendKey,
|
||||||
|
required String viewKey,
|
||||||
|
required String address,
|
||||||
|
required String password,
|
||||||
|
required String language,
|
||||||
|
required int height});
|
||||||
|
WalletCredentials createWowneroRestoreWalletFromSeedCredentials({required String name, required String password, required int height, required String mnemonic});
|
||||||
|
WalletCredentials createWowneroNewWalletCredentials({required String name, required String language, required bool isPolyseed, String password});
|
||||||
|
Map<String, String> getKeys(Object wallet);
|
||||||
|
Object createWowneroTransactionCreationCredentials({required List<Output> outputs, required TransactionPriority priority});
|
||||||
|
Object createWowneroTransactionCreationCredentialsRaw({required List<OutputInfo> outputs, required TransactionPriority priority});
|
||||||
|
String formatterWowneroAmountToString({required int amount});
|
||||||
|
double formatterWowneroAmountToDouble({required int amount});
|
||||||
|
int formatterWowneroParseAmount({required String amount});
|
||||||
|
Account getCurrentAccount(Object wallet);
|
||||||
|
void setCurrentAccount(Object wallet, int id, String label, String? balance);
|
||||||
|
void onStartup();
|
||||||
|
int getTransactionInfoAccountId(TransactionInfo tx);
|
||||||
|
WalletService createWowneroWalletService(Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource);
|
||||||
|
Map<String, String> pendingTransactionInfo(Object transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class WowneroSubaddressList {
|
||||||
|
ObservableList<Subaddress> get subaddresses;
|
||||||
|
void update(Object wallet, {required int accountIndex});
|
||||||
|
void refresh(Object wallet, {required int accountIndex});
|
||||||
|
List<Subaddress> getAll(Object wallet);
|
||||||
|
Future<void> addSubaddress(Object wallet, {required int accountIndex, required String label});
|
||||||
|
Future<void> setLabelSubaddress(Object wallet,
|
||||||
|
{required int accountIndex, required int addressIndex, required String label});
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class WowneroAccountList {
|
||||||
|
ObservableList<Account> get accounts;
|
||||||
|
void update(Object wallet);
|
||||||
|
void refresh(Object wallet);
|
||||||
|
List<Account> getAll(Object wallet);
|
||||||
|
Future<void> addAccount(Object wallet, {required String label});
|
||||||
|
Future<void> setLabelAccount(Object wallet, {required int accountIndex, required String label});
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
const wowneroEmptyDefinition = 'Wownero? wownero;\n';
|
||||||
|
const wowneroCWDefinition = 'Wownero? wownero = CWWownero();\n';
|
||||||
|
|
||||||
|
final output = '$wowneroCommonHeaders\n' +
|
||||||
|
(hasImplementation ? '$wowneroCWHeaders\n' : '\n') +
|
||||||
|
(hasImplementation ? '$wowneroCwPart\n\n' : '\n') +
|
||||||
|
(hasImplementation ? wowneroCWDefinition : wowneroEmptyDefinition) +
|
||||||
|
'\n' +
|
||||||
|
wowneroContent;
|
||||||
|
|
||||||
|
if (outputFile.existsSync()) {
|
||||||
|
await outputFile.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
await outputFile.writeAsString(output);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> generateHaven(bool hasImplementation) async {
|
Future<void> generateHaven(bool hasImplementation) async {
|
||||||
final outputFile = File(havenOutputPath);
|
final outputFile = File(havenOutputPath);
|
||||||
const havenCommonHeaders = """
|
const havenCommonHeaders = """
|
||||||
|
@ -1152,18 +1338,20 @@ abstract class Tron {
|
||||||
await outputFile.writeAsString(output);
|
await outputFile.writeAsString(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> generatePubspec(
|
Future<void> generatePubspec({
|
||||||
{required bool hasMonero,
|
required bool hasMonero,
|
||||||
required bool hasBitcoin,
|
required bool hasBitcoin,
|
||||||
required bool hasHaven,
|
required bool hasHaven,
|
||||||
required bool hasEthereum,
|
required bool hasEthereum,
|
||||||
required bool hasNano,
|
required bool hasNano,
|
||||||
required bool hasBanano,
|
required bool hasBanano,
|
||||||
required bool hasBitcoinCash,
|
required bool hasBitcoinCash,
|
||||||
required bool hasFlutterSecureStorage,
|
required bool hasFlutterSecureStorage,
|
||||||
required bool hasPolygon,
|
required bool hasPolygon,
|
||||||
required bool hasSolana,
|
required bool hasSolana,
|
||||||
required bool hasTron}) async {
|
required bool hasTron,
|
||||||
|
required bool hasWownero,
|
||||||
|
}) async {
|
||||||
const cwCore = """
|
const cwCore = """
|
||||||
cw_core:
|
cw_core:
|
||||||
path: ./cw_core
|
path: ./cw_core
|
||||||
|
@ -1224,13 +1412,16 @@ Future<void> generatePubspec(
|
||||||
cw_tron:
|
cw_tron:
|
||||||
path: ./cw_tron
|
path: ./cw_tron
|
||||||
""";
|
""";
|
||||||
|
const cwWownero = """
|
||||||
|
cw_wownero:
|
||||||
|
path: ./cw_wownero
|
||||||
|
""";
|
||||||
final inputFile = File(pubspecOutputPath);
|
final inputFile = File(pubspecOutputPath);
|
||||||
final inputText = await inputFile.readAsString();
|
final inputText = await inputFile.readAsString();
|
||||||
final inputLines = inputText.split('\n');
|
final inputLines = inputText.split('\n');
|
||||||
final dependenciesIndex = inputLines.indexWhere((line) => Platform.isWindows
|
final dependenciesIndex = inputLines.indexWhere((line) => Platform.isWindows
|
||||||
// On Windows it could contains `\r` (Carriage Return). It could be fixed in newer dart versions.
|
// On Windows it could contains `\r` (Carriage Return). It could be fixed in newer dart versions.
|
||||||
? line.toLowerCase() == 'dependencies:\r' ||
|
? line.toLowerCase() == 'dependencies:\r' || line.toLowerCase() == 'dependencies:'
|
||||||
line.toLowerCase() == 'dependencies:'
|
|
||||||
: line.toLowerCase() == 'dependencies:');
|
: line.toLowerCase() == 'dependencies:');
|
||||||
var output = cwCore;
|
var output = cwCore;
|
||||||
|
|
||||||
|
@ -1282,6 +1473,10 @@ Future<void> generatePubspec(
|
||||||
output += '\n$cwEVM';
|
output += '\n$cwEVM';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasWownero) {
|
||||||
|
output += '\n$cwWownero';
|
||||||
|
}
|
||||||
|
|
||||||
final outputLines = output.split('\n');
|
final outputLines = output.split('\n');
|
||||||
inputLines.insertAll(dependenciesIndex + 1, outputLines);
|
inputLines.insertAll(dependenciesIndex + 1, outputLines);
|
||||||
final outputContent = inputLines.join('\n');
|
final outputContent = inputLines.join('\n');
|
||||||
|
@ -1294,17 +1489,19 @@ Future<void> generatePubspec(
|
||||||
await outputFile.writeAsString(outputContent);
|
await outputFile.writeAsString(outputContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> generateWalletTypes(
|
Future<void> generateWalletTypes({
|
||||||
{required bool hasMonero,
|
required bool hasMonero,
|
||||||
required bool hasBitcoin,
|
required bool hasBitcoin,
|
||||||
required bool hasHaven,
|
required bool hasHaven,
|
||||||
required bool hasEthereum,
|
required bool hasEthereum,
|
||||||
required bool hasNano,
|
required bool hasNano,
|
||||||
required bool hasBanano,
|
required bool hasBanano,
|
||||||
required bool hasBitcoinCash,
|
required bool hasBitcoinCash,
|
||||||
required bool hasPolygon,
|
required bool hasPolygon,
|
||||||
required bool hasSolana,
|
required bool hasSolana,
|
||||||
required bool hasTron}) async {
|
required bool hasTron,
|
||||||
|
required bool hasWownero,
|
||||||
|
}) async {
|
||||||
final walletTypesFile = File(walletTypesPath);
|
final walletTypesFile = File(walletTypesPath);
|
||||||
|
|
||||||
if (walletTypesFile.existsSync()) {
|
if (walletTypesFile.existsSync()) {
|
||||||
|
@ -1355,6 +1552,10 @@ Future<void> generateWalletTypes(
|
||||||
outputContent += '\tWalletType.banano,\n';
|
outputContent += '\tWalletType.banano,\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasWownero) {
|
||||||
|
outputContent += '\tWalletType.wownero,\n';
|
||||||
|
}
|
||||||
|
|
||||||
if (hasHaven) {
|
if (hasHaven) {
|
||||||
outputContent += '\tWalletType.haven,\n';
|
outputContent += '\tWalletType.haven,\n';
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue