Merge branch 'flutter-upgrade' of https://github.com/cake-tech/cake_wallet into cake-phone

This commit is contained in:
OmarHatem 2022-10-21 18:07:22 +02:00
commit 57011073ec
23 changed files with 380 additions and 190 deletions

BIN
assets/images/dcr_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

BIN
assets/images/husd_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
assets/images/kmd_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

BIN
assets/images/mana_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
assets/images/mkr_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
assets/images/near_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

BIN
assets/images/oxt_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

BIN
assets/images/paxg_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

BIN
assets/images/pivx_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

BIN
assets/images/rune_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
assets/images/rvn_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

BIN
assets/images/scrt_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

BIN
assets/images/stx_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
assets/images/uni_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

View file

@ -51,6 +51,23 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> {
CryptoCurrency.zec, CryptoCurrency.zec,
CryptoCurrency.zen, CryptoCurrency.zen,
CryptoCurrency.xvg, CryptoCurrency.xvg,
CryptoCurrency.usdcpoly,
CryptoCurrency.dcr,
CryptoCurrency.husd,
CryptoCurrency.kmd,
CryptoCurrency.mana,
CryptoCurrency.maticpoly,
CryptoCurrency.matic,
CryptoCurrency.mkr,
CryptoCurrency.near,
CryptoCurrency.oxt,
CryptoCurrency.paxg,
CryptoCurrency.pivx,
CryptoCurrency.rune,
CryptoCurrency.rvn,
CryptoCurrency.scrt,
CryptoCurrency.uni,
CryptoCurrency.stx,
]; ];
static const xmr = CryptoCurrency(title: 'XMR', iconPath: 'assets/images/monero_icon.png', name: 'Monero', raw: 0); static const xmr = CryptoCurrency(title: 'XMR', iconPath: 'assets/images/monero_icon.png', name: 'Monero', raw: 0);
@ -102,6 +119,26 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> {
static const zen = CryptoCurrency(title: 'ZEN', iconPath: 'assets/images/zen_icon.png', raw: 44); static const zen = CryptoCurrency(title: 'ZEN', iconPath: 'assets/images/zen_icon.png', raw: 44);
static const xvg = CryptoCurrency(title: 'XVG', name: 'Verge', iconPath: 'assets/images/xvg_icon.png', raw: 45); static const xvg = CryptoCurrency(title: 'XVG', name: 'Verge', iconPath: 'assets/images/xvg_icon.png', raw: 45);
static const usdcpoly = CryptoCurrency(title: 'USDC', iconPath: 'assets/images/usdc_icon.png', tag: 'POLY', raw: 46);
static const dcr = CryptoCurrency(title: 'DCR', iconPath: 'assets/images/dcr_icon.png', raw: 47);
static const husd = CryptoCurrency(title: 'HUSD', iconPath: 'assets/images/husd_icon.png', tag: 'ETH', raw: 48);
static const kmd = CryptoCurrency(title: 'KMD', iconPath: 'assets/images/kmd_icon.png', raw: 49);
static const mana = CryptoCurrency(title: 'MANA', iconPath: 'assets/images/mana_icon.png', tag: 'ETH', raw: 50);
static const maticpoly = CryptoCurrency(title: 'MATIC', iconPath: 'assets/images/matic_icon.png', tag: 'POLY', raw: 51);
static const matic = CryptoCurrency(title: 'MATIC', iconPath: 'assets/images/matic_icon.png', tag: 'ETH', raw: 52);
static const mkr = CryptoCurrency(title: 'MKR', iconPath: 'assets/images/mkr_icon.png', tag: 'ETH', raw: 53);
static const near = CryptoCurrency(title: 'NEAR', iconPath: 'assets/images/near_icon.png', raw: 54);
static const oxt = CryptoCurrency(title: 'OXT', iconPath: 'assets/images/oxt_icon.png', tag: 'ETH', raw: 55);
static const paxg = CryptoCurrency(title: 'PAXG', iconPath: 'assets/images/paxg_icon.png', tag: 'ETH', raw: 56);
static const pivx = CryptoCurrency(title: 'PIVX', iconPath: 'assets/images/pivx_icon.png', raw: 57);
static const rune = CryptoCurrency(title: 'RUNE', iconPath: 'assets/images/rune_icon.png', raw: 58);
static const rvn = CryptoCurrency(title: 'RVN', iconPath: 'assets/images/rvn_icon.png', raw: 59);
static const scrt = CryptoCurrency(title: 'SCRT', iconPath: 'assets/images/scrt_icon.png', raw: 60);
static const uni = CryptoCurrency(title: 'UNI', iconPath: 'assets/images/uni_icon.png', tag: 'ETH', raw: 61);
static const stx = CryptoCurrency(title: 'STX', iconPath: 'assets/images/stx_icon.png', raw: 62);
static CryptoCurrency deserialize({required int raw}) { static CryptoCurrency deserialize({required int raw}) {
switch (raw) { switch (raw) {
case 0: case 0:
@ -196,6 +233,40 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> {
return CryptoCurrency.zen; return CryptoCurrency.zen;
case 45: case 45:
return CryptoCurrency.xvg; return CryptoCurrency.xvg;
case 46:
return CryptoCurrency.usdcpoly;
case 47:
return CryptoCurrency.dcr;
case 48:
return CryptoCurrency.husd;
case 49:
return CryptoCurrency.kmd;
case 50:
return CryptoCurrency.mana;
case 51:
return CryptoCurrency.maticpoly;
case 52:
return CryptoCurrency.matic;
case 53:
return CryptoCurrency.mkr;
case 54:
return CryptoCurrency.near;
case 55:
return CryptoCurrency.oxt;
case 56:
return CryptoCurrency.paxg;
case 57:
return CryptoCurrency.pivx;
case 58:
return CryptoCurrency.rune;
case 59:
return CryptoCurrency.rvn;
case 60:
return CryptoCurrency.scrt;
case 61:
return CryptoCurrency.uni;
case 62:
return CryptoCurrency.stx;
default: default:
throw Exception('Unexpected token: $raw for CryptoCurrency deserialize'); throw Exception('Unexpected token: $raw for CryptoCurrency deserialize');
} }
@ -295,6 +366,40 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> {
return CryptoCurrency.zen; return CryptoCurrency.zen;
case 'xvg': case 'xvg':
return CryptoCurrency.xvg; return CryptoCurrency.xvg;
case 'usdcpoly':
return CryptoCurrency.usdcpoly;
case 'dcr':
return CryptoCurrency.dcr;
case 'husd':
return CryptoCurrency.husd;
case 'kmd':
return CryptoCurrency.kmd;
case 'mana':
return CryptoCurrency.mana;
case 'maticpoly':
return CryptoCurrency.maticpoly;
case 'matic':
return CryptoCurrency.matic;
case 'mkr':
return CryptoCurrency.mkr;
case 'near':
return CryptoCurrency.near;
case 'oxt':
return CryptoCurrency.oxt;
case 'paxg':
return CryptoCurrency.paxg;
case 'pivx':
return CryptoCurrency.pivx;
case 'rune':
return CryptoCurrency.rune;
case 'rvn':
return CryptoCurrency.rvn;
case 'scrt':
return CryptoCurrency.scrt;
case 'uni':
return CryptoCurrency.uni;
case 'stx':
return CryptoCurrency.stx;
default: default:
throw Exception('Unexpected token: $raw for CryptoCurrency fromString'); throw Exception('Unexpected token: $raw for CryptoCurrency fromString');
} }

View file

@ -17,39 +17,25 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.ada: case CryptoCurrency.ada:
return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$' return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$'
'|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$'; '|^[0-9a-zA-Z]{105}\$|^addr1[0-9a-zA-Z]{98}\$';
case CryptoCurrency.ape:
return '0x[0-9a-zA-Z]';
case CryptoCurrency.avaxc:
return '0x[0-9a-zA-Z]';
case CryptoCurrency.bch:
return '[0-9a-zA-Z]';
case CryptoCurrency.bnb:
return '[0-9a-zA-Z]';
case CryptoCurrency.btc: case CryptoCurrency.btc:
return '^1[0-9a-zA-Z]{32}\$|^1[0-9a-zA-Z]{33}\$|^3[0-9a-zA-Z]{32}\$' return '^1[0-9a-zA-Z]{32}\$|^1[0-9a-zA-Z]{33}\$|^3[0-9a-zA-Z]{32}\$'
'|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{39}\$|^bc1[0-9a-zA-Z]{59}\$'; '|^3[0-9a-zA-Z]{33}\$|^bc1[0-9a-zA-Z]{39}\$|^bc1[0-9a-zA-Z]{59}\$';
case CryptoCurrency.dai:
return '[0-9a-zA-Z]';
case CryptoCurrency.dash:
return '[0-9a-zA-Z]';
case CryptoCurrency.eos:
return '[0-9a-zA-Z]';
case CryptoCurrency.eth:
return '0x[0-9a-zA-Z]';
case CryptoCurrency.ltc:
return '[0-9a-zA-Z]';
case CryptoCurrency.nano: case CryptoCurrency.nano:
return '[0-9a-zA-Z_]'; return '[0-9a-zA-Z_]';
case CryptoCurrency.trx:
return '[0-9a-zA-Z]';
case CryptoCurrency.usdc: case CryptoCurrency.usdc:
case CryptoCurrency.usdcpoly:
case CryptoCurrency.husd:
case CryptoCurrency.ape:
case CryptoCurrency.avaxc:
case CryptoCurrency.eth:
case CryptoCurrency.mana:
case CryptoCurrency.matic:
case CryptoCurrency.maticpoly:
case CryptoCurrency.mkr:
case CryptoCurrency.oxt:
case CryptoCurrency.paxg:
case CryptoCurrency.uni:
return '0x[0-9a-zA-Z]'; return '0x[0-9a-zA-Z]';
case CryptoCurrency.usdt:
return '[0-9a-zA-Z]';
case CryptoCurrency.usdterc20:
return '[0-9a-zA-Z]';
case CryptoCurrency.xlm:
return '[0-9a-zA-Z]';
case CryptoCurrency.xrp: case CryptoCurrency.xrp:
return '^[0-9a-zA-Z]{34}\$|^X[0-9a-zA-Z]{46}\$'; return '^[0-9a-zA-Z]{34}\$|^X[0-9a-zA-Z]{46}\$';
case CryptoCurrency.xhv: case CryptoCurrency.xhv:
@ -67,6 +53,16 @@ class AddressValidator extends TextValidator {
case CryptoCurrency.xnok: case CryptoCurrency.xnok:
case CryptoCurrency.xnzd: case CryptoCurrency.xnzd:
case CryptoCurrency.xusd: case CryptoCurrency.xusd:
case CryptoCurrency.usdt:
case CryptoCurrency.usdterc20:
case CryptoCurrency.xlm:
case CryptoCurrency.trx:
case CryptoCurrency.dai:
case CryptoCurrency.dash:
case CryptoCurrency.eos:
case CryptoCurrency.ltc:
case CryptoCurrency.bch:
case CryptoCurrency.bnb:
return '[0-9a-zA-Z]'; return '[0-9a-zA-Z]';
case CryptoCurrency.hbar: case CryptoCurrency.hbar:
return '[0-9a-zA-Z.]'; return '[0-9a-zA-Z.]';
@ -74,6 +70,22 @@ class AddressValidator extends TextValidator {
return '^zs[0-9a-zA-Z]{75}'; return '^zs[0-9a-zA-Z]{75}';
case CryptoCurrency.zec: case CryptoCurrency.zec:
return '^t1[0-9a-zA-Z]{33}\$|^t3[0-9a-zA-Z]{33}\$'; return '^t1[0-9a-zA-Z]{33}\$|^t3[0-9a-zA-Z]{33}\$';
case CryptoCurrency.dcr:
return 'D[ksecS]([0-9a-zA-Z])+';
case CryptoCurrency.rvn:
return '[Rr]([1-9a-km-zA-HJ-NP-Z]){33}';
case CryptoCurrency.near:
return '[0-9a-f]{64}';
case CryptoCurrency.rune:
return 'thor1[0-9a-z]{38}';
case CryptoCurrency.scrt:
return 'secret1[0-9a-z]{38}';
case CryptoCurrency.stx:
return 'S[MP][0-9a-zA-Z]+';
case CryptoCurrency.kmd:
return 'R[0-9a-zA-Z]{33}';
case CryptoCurrency.pivx:
return 'D([1-9a-km-zA-HJ-NP-Z]){33}';
default: default:
return '[0-9a-zA-Z]'; return '[0-9a-zA-Z]';
} }
@ -160,6 +172,30 @@ class AddressValidator extends TextValidator {
return null; return null;
case CryptoCurrency.zec: case CryptoCurrency.zec:
return null; return null;
case CryptoCurrency.kmd:
case CryptoCurrency.pivx:
case CryptoCurrency.rvn:
return [34];
case CryptoCurrency.dcr:
return [35];
case CryptoCurrency.stx:
return [40, 41, 42];
case CryptoCurrency.usdcpoly:
case CryptoCurrency.husd:
case CryptoCurrency.mana:
case CryptoCurrency.matic:
case CryptoCurrency.maticpoly:
case CryptoCurrency.mkr:
case CryptoCurrency.oxt:
case CryptoCurrency.paxg:
case CryptoCurrency.uni:
return [42];
case CryptoCurrency.rune:
return [43];
case CryptoCurrency.scrt:
return [45];
case CryptoCurrency.near:
return [64];
default: default:
return []; return [];
} }

View file

@ -16,16 +16,7 @@ import 'package:flutter/foundation.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
class SideShiftExchangeProvider extends ExchangeProvider { class SideShiftExchangeProvider extends ExchangeProvider {
SideShiftExchangeProvider() SideShiftExchangeProvider() : super(pairList: _supportedPairs());
: super(
pairList: CryptoCurrency.all
.where((i) => i != CryptoCurrency.xhv)
.map((i) => CryptoCurrency.all
.where((i) => i != CryptoCurrency.xhv)
.map((k) => ExchangePair(from: i, to: k, reverse: true))
.where((c) => c != null))
.expand((i) => i)
.toList());
static const affiliateId = secrets.sideShiftAffiliateId; static const affiliateId = secrets.sideShiftAffiliateId;
static const apiBaseUrl = 'https://sideshift.ai/api'; static const apiBaseUrl = 'https://sideshift.ai/api';
@ -34,6 +25,35 @@ class SideShiftExchangeProvider extends ExchangeProvider {
static const quotePath = '/v1/quotes'; static const quotePath = '/v1/quotes';
static const permissionPath = '/v1/permissions'; static const permissionPath = '/v1/permissions';
static const List<CryptoCurrency> _notSupported = [
CryptoCurrency.xhv,
CryptoCurrency.dcr,
CryptoCurrency.husd,
CryptoCurrency.kmd,
CryptoCurrency.mkr,
CryptoCurrency.near,
CryptoCurrency.oxt,
CryptoCurrency.paxg,
CryptoCurrency.pivx,
CryptoCurrency.rune,
CryptoCurrency.rvn,
CryptoCurrency.scrt,
CryptoCurrency.stx,
];
static List<ExchangePair> _supportedPairs() {
final supportedCurrencies = CryptoCurrency.all
.where((element) => !_notSupported.contains(element))
.toList();
return supportedCurrencies
.map((i) => supportedCurrencies
.map((k) => ExchangePair(from: i, to: k, reverse: true))
.where((c) => c != null))
.expand((i) => i)
.toList();
}
@override @override
ExchangeProviderDescription get description => ExchangeProviderDescription get description =>
ExchangeProviderDescription.sideShift; ExchangeProviderDescription.sideShift;
@ -270,6 +290,14 @@ class SideShiftExchangeProvider extends ExchangeProvider {
return currency.tag!.toLowerCase(); return currency.tag!.toLowerCase();
case CryptoCurrency.usdterc20: case CryptoCurrency.usdterc20:
return 'usdtErc20'; return 'usdtErc20';
case CryptoCurrency.usdttrc20:
return 'usdtTrc20';
case CryptoCurrency.usdcpoly:
return 'usdcpolygon';
case CryptoCurrency.usdcsol:
return 'usdcsol';
case CryptoCurrency.maticpoly:
return 'polygon';
default: default:
return currency.title.toLowerCase(); return currency.title.toLowerCase();
} }

View file

@ -17,8 +17,11 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
SimpleSwapExchangeProvider() SimpleSwapExchangeProvider()
: super( : super(
pairList: CryptoCurrency.all pairList: CryptoCurrency.all
.map((i) => .where((i) => i != CryptoCurrency.zaddr)
CryptoCurrency.all.map((k) => ExchangePair(from: i, to: k, reverse: true)).where((c) => c != null)) .map((i) => CryptoCurrency.all
.where((i) => i != CryptoCurrency.zaddr)
.map((k) => ExchangePair(from: i, to: k, reverse: true))
.where((c) => c != null))
.expand((i) => i) .expand((i) => i)
.toList()); .toList());
@ -56,7 +59,7 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
final uri = Uri.https(apiAuthority, getEstimatePath, params); final uri = Uri.https(apiAuthority, getEstimatePath, params);
final response = await get(uri); final response = await get(uri);
if (response.body == null) return 0.00; if (response.body == null || response.body == "null") return 0.00;
final data = json.decode(response.body) as String; final data = json.decode(response.body) as String;
return double.parse(data); return double.parse(data);
@ -151,8 +154,8 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
} }
final responseJSON = json.decode(response.body) as Map<String, dynamic>; final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final min = responseJSON['min'] != null ? double.tryParse(responseJSON['min'] as String) : null; final min = double.tryParse(responseJSON['min'] as String? ?? '');
final max = responseJSON['max'] != null ? double.parse(responseJSON['max'] as String) : null; final max = double.tryParse(responseJSON['max'] as String? ?? '');
return Limits(min: min, max: max); return Limits(min: min, max: max);
} }
@ -217,7 +220,13 @@ class SimpleSwapExchangeProvider extends ExchangeProvider {
case CryptoCurrency.bnb: case CryptoCurrency.bnb:
return currency.tag!.toLowerCase(); return currency.tag!.toLowerCase();
case CryptoCurrency.usdterc20: case CryptoCurrency.usdterc20:
return 'usdterc'; return 'usdterc20';
case CryptoCurrency.usdttrc20:
return 'usdttrc20';
case CryptoCurrency.usdcpoly:
return 'usdcpoly';
case CryptoCurrency.usdcsol:
return 'usdcspl';
default: default:
return currency.title.toLowerCase(); return currency.title.toLowerCase();
} }

View file

@ -179,6 +179,7 @@ class ExchangePage extends BasePage {
padding: EdgeInsets.fromLTRB(24, 100, 24, 32), padding: EdgeInsets.fromLTRB(24, 100, 24, 32),
child: Observer( child: Observer(
builder: (_) => ExchangeCard( builder: (_) => ExchangeCard(
onDispose: disposeBestRateSync,
hasAllAmount: exchangeViewModel.hasAllAmount, hasAllAmount: exchangeViewModel.hasAllAmount,
allAmount: exchangeViewModel.hasAllAmount allAmount: exchangeViewModel.hasAllAmount
? () => exchangeViewModel ? () => exchangeViewModel
@ -265,6 +266,7 @@ class ExchangePage extends BasePage {
EdgeInsets.only(top: 29, left: 24, right: 24), EdgeInsets.only(top: 29, left: 24, right: 24),
child: Observer( child: Observer(
builder: (_) => ExchangeCard( builder: (_) => ExchangeCard(
onDispose: disposeBestRateSync,
amountFocusNode: _receiveAmountFocus, amountFocusNode: _receiveAmountFocus,
addressFocusNode: _receiveAddressFocus, addressFocusNode: _receiveAddressFocus,
key: receiveKey, key: receiveKey,
@ -743,13 +745,13 @@ class ExchangePage extends BasePage {
if (_receiveAmountFocus.hasFocus) { if (_receiveAmountFocus.hasFocus) {
exchangeViewModel.isFixedRateMode = true; exchangeViewModel.isFixedRateMode = true;
} }
exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text); // exchangeViewModel.changeReceiveAmount(amount: receiveAmountController.text);
}); });
_depositAmountFocus.addListener(() { _depositAmountFocus.addListener(() {
exchangeViewModel.isFixedRateMode = false; exchangeViewModel.isFixedRateMode = false;
exchangeViewModel.changeDepositAmount( // exchangeViewModel.changeDepositAmount(
amount: depositAmountController.text); // amount: depositAmountController.text);
}); });
_isReactionsSet = true; _isReactionsSet = true;
@ -791,4 +793,6 @@ class ExchangePage extends BasePage {
final address = await extractAddressFromParsed(context, parsedAddress); final address = await extractAddressFromParsed(context, parsedAddress);
return address; return address;
} }
void disposeBestRateSync() => exchangeViewModel.bestRateSync?.cancel();
} }

View file

@ -37,7 +37,8 @@ class ExchangeCard extends StatefulWidget {
this.addressFocusNode, this.addressFocusNode,
this.allAmount, this.allAmount,
this.onPushPasteButton, this.onPushPasteButton,
this.onPushAddressBookButton}) this.onPushAddressBookButton,
this.onDispose})
: super(key: key); : super(key: key);
final List<CryptoCurrency> currencies; final List<CryptoCurrency> currencies;
@ -63,6 +64,7 @@ class ExchangeCard extends StatefulWidget {
final VoidCallback? allAmount; final VoidCallback? allAmount;
final void Function(BuildContext context)? onPushPasteButton; final void Function(BuildContext context)? onPushPasteButton;
final void Function(BuildContext context)? onPushAddressBookButton; final void Function(BuildContext context)? onPushAddressBookButton;
final Function()? onDispose;
@override @override
ExchangeCardState createState() => ExchangeCardState(); ExchangeCardState createState() => ExchangeCardState();
@ -106,6 +108,13 @@ class ExchangeCardState extends State<ExchangeCard> {
super.initState(); super.initState();
} }
@override
void dispose() {
widget.onDispose?.call();
super.dispose();
}
void changeLimits({String? min, String? max}) { void changeLimits({String? min, String? max}) {
setState(() { setState(() {
_min = min; _min = min;

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:collection'; import 'dart:collection';
import 'dart:convert'; import 'dart:convert';
@ -59,12 +60,12 @@ abstract class ExchangeViewModelBase with Store {
receiveCurrency = wallet.currency, receiveCurrency = wallet.currency,
depositCurrency = wallet.currency, depositCurrency = wallet.currency,
providerList = [ChangeNowExchangeProvider(), SideShiftExchangeProvider(), SimpleSwapExchangeProvider()], providerList = [ChangeNowExchangeProvider(), SideShiftExchangeProvider(), SimpleSwapExchangeProvider()],
selectedProviders = ObservableList<ExchangeProvider>(), selectedProviders = ObservableList<ExchangeProvider>() {
currentTradeAvailableProviders = SplayTreeMap<double, ExchangeProvider>() {
const excludeDepositCurrencies = [CryptoCurrency.btt, CryptoCurrency.nano]; const excludeDepositCurrencies = [CryptoCurrency.btt, CryptoCurrency.nano];
const excludeReceiveCurrencies = [CryptoCurrency.xlm, CryptoCurrency.xrp, const excludeReceiveCurrencies = [CryptoCurrency.xlm, CryptoCurrency.xrp,
CryptoCurrency.bnb, CryptoCurrency.btt, CryptoCurrency.nano]; CryptoCurrency.bnb, CryptoCurrency.btt, CryptoCurrency.nano];
_initialPairBasedOnWallet(); _initialPairBasedOnWallet();
final Map<String, dynamic> exchangeProvidersSelection = json final Map<String, dynamic> exchangeProvidersSelection = json
.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}") as Map<String, dynamic>; .decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}") as Map<String, dynamic>;
@ -76,6 +77,11 @@ abstract class ExchangeViewModelBase with Store {
: (exchangeProvidersSelection[element.title] as bool)) : (exchangeProvidersSelection[element.title] as bool))
.toList()); .toList());
_setAvailableProviders();
_calculateBestRate();
bestRateSync = Timer.periodic(Duration(seconds: 10), (timer) => _calculateBestRate());
isDepositAddressEnabled = !(depositCurrency == wallet.currency); isDepositAddressEnabled = !(depositCurrency == wallet.currency);
isReceiveAddressEnabled = !(receiveCurrency == wallet.currency); isReceiveAddressEnabled = !(receiveCurrency == wallet.currency);
depositAmount = ''; depositAmount = '';
@ -119,8 +125,15 @@ abstract class ExchangeViewModelBase with Store {
/// Maps in dart are not sorted by default /// Maps in dart are not sorted by default
/// SplayTreeMap is a map sorted by keys /// SplayTreeMap is a map sorted by keys
/// will use it to sort available providers /// will use it to sort available providers
/// depending on the amount they yield for the current trade /// based on the rate they yield for the current trade
SplayTreeMap<double, ExchangeProvider> currentTradeAvailableProviders; ///
///
/// initialize with descending comparator
/// since we want largest rate first
final SplayTreeMap<double, ExchangeProvider> _sortedAvailableProviders =
SplayTreeMap<double, ExchangeProvider>((double a, double b) => b.compareTo(a));
final List<ExchangeProvider> _tradeAvailableProviders = [];
@observable @observable
ObservableList<ExchangeProvider> selectedProviders; ObservableList<ExchangeProvider> selectedProviders;
@ -191,6 +204,10 @@ abstract class ExchangeViewModelBase with Store {
final SettingsStore _settingsStore; final SettingsStore _settingsStore;
double _bestRate = 0.0;
late Timer bestRateSync;
@action @action
void changeDepositCurrency({required CryptoCurrency currency}) { void changeDepositCurrency({required CryptoCurrency currency}) {
depositCurrency = currency; depositCurrency = currency;
@ -210,68 +227,11 @@ abstract class ExchangeViewModelBase with Store {
} }
@action @action
void changeReceiveAmount({required String amount}) { Future<void> changeReceiveAmount({required String amount}) async {
receiveAmount = amount; receiveAmount = amount;
isReverse = true; isReverse = true;
if (amount == null || amount.isEmpty) { if (amount.isEmpty) {
depositAmount = '';
receiveAmount = '';
return;
}
final _enteredAmount = double.parse(amount.replaceAll(',', '.')) ?? 0;
currentTradeAvailableProviders.clear();
for (var provider in selectedProviders) {
/// if this provider is not valid for the current pair, skip it
if (!providersForCurrentPair().contains(provider)) {
continue;
}
provider
.calculateAmount(
from: receiveCurrency,
to: depositCurrency,
amount: _enteredAmount,
isFixedRateMode: isFixedRateMode,
isReceiveAmount: true)
.then((amount) {
final from = isFixedRateMode
? receiveCurrency
: depositCurrency;
final to = isFixedRateMode
? depositCurrency
: receiveCurrency;
provider.fetchLimits(
from: from,
to: to,
isFixedRateMode: isFixedRateMode,
).then((limits) {
/// if the entered amount doesn't exceed the limits of this provider
if ((limits?.max ?? double.maxFinite) >= _enteredAmount
&& (limits?.min ?? 0) <= _enteredAmount) {
/// add this provider as its valid for this trade
/// will be sorted ascending already since
/// we seek the least deposit amount
currentTradeAvailableProviders[amount] = provider;
}
return amount;
}).then((amount) => depositAmount = _cryptoNumberFormat
.format(amount)
.toString()
.replaceAll(RegExp('\\,'), ''));
});
}
}
@action
void changeDepositAmount({required String amount}) {
depositAmount = amount;
isReverse = false;
if (amount == null || amount.isEmpty) {
depositAmount = ''; depositAmount = '';
receiveAmount = ''; receiveAmount = '';
return; return;
@ -279,61 +239,76 @@ abstract class ExchangeViewModelBase with Store {
final _enteredAmount = double.tryParse(amount.replaceAll(',', '.')) ?? 0; final _enteredAmount = double.tryParse(amount.replaceAll(',', '.')) ?? 0;
currentTradeAvailableProviders.clear(); if (_bestRate == 0) {
for (var provider in selectedProviders) { depositAmount = S.current.fetching;
/// if this provider is not valid for the current pair, skip it
if (!providersForCurrentPair().contains(provider)) { await _calculateBestRate();
continue;
} }
provider
.calculateAmount( depositAmount = _cryptoNumberFormat
.format(_enteredAmount / _bestRate)
.toString()
.replaceAll(RegExp('\\,'), '');
}
@action
Future<void> changeDepositAmount({required String amount}) async {
depositAmount = amount;
isReverse = false;
if (amount.isEmpty) {
depositAmount = '';
receiveAmount = '';
return;
}
final _enteredAmount = double.tryParse(amount.replaceAll(',', '.')) ?? 0;
/// in case the best rate was not calculated yet
if (_bestRate == 0) {
receiveAmount = S.current.fetching;
await _calculateBestRate();
}
receiveAmount = _cryptoNumberFormat
.format(_bestRate * _enteredAmount)
.toString()
.replaceAll(RegExp('\\,'), '');
}
Future<void> _calculateBestRate() async {
final result = await Future.wait<double>(
_tradeAvailableProviders
.map((element) => element.calculateAmount(
from: depositCurrency, from: depositCurrency,
to: receiveCurrency, to: receiveCurrency,
amount: _enteredAmount, amount: 1,
isFixedRateMode: isFixedRateMode, isFixedRateMode: isFixedRateMode,
isReceiveAmount: false) isReceiveAmount: false))
.then((amount) { );
final from = isFixedRateMode _sortedAvailableProviders.clear();
? receiveCurrency
: depositCurrency;
final to = isFixedRateMode
? depositCurrency
: receiveCurrency;
provider.fetchLimits( for (int i=0;i<result.length;i++) {
from: from, if (result[i] != 0) {
to: to,
isFixedRateMode: isFixedRateMode,
).then((limits) {
/// if the entered amount doesn't exceed the limits of this provider
if ((limits?.max ?? double.maxFinite) >= _enteredAmount
&& (limits?.min ?? 0) <= _enteredAmount) {
/// add this provider as its valid for this trade /// add this provider as its valid for this trade
/// subtract from maxFinite so the provider _sortedAvailableProviders[result[i]] = _tradeAvailableProviders[i];
/// with the largest amount would be sorted ascending
currentTradeAvailableProviders[double.maxFinite - amount] = provider;
} }
return amount; }
}).then((amount) => receiveAmount = if (_sortedAvailableProviders.isNotEmpty) {
receiveAmount = _cryptoNumberFormat _bestRate = _sortedAvailableProviders.keys.first;
.format(amount)
.toString()
.replaceAll(RegExp('\\,'), ''));
});
} }
} }
@action @action
Future loadLimits() async { Future<void> loadLimits() async {
if (selectedProviders.isEmpty) { if (selectedProviders.isEmpty) {
return; return;
} }
limitsState = LimitsIsLoading(); limitsState = LimitsIsLoading();
try {
final from = isFixedRateMode final from = isFixedRateMode
? receiveCurrency ? receiveCurrency
: depositCurrency; : depositCurrency;
@ -341,31 +316,38 @@ abstract class ExchangeViewModelBase with Store {
? depositCurrency ? depositCurrency
: receiveCurrency; : receiveCurrency;
limits = await selectedProviders.first.fetchLimits( double lowestMin = double.maxFinite;
double? highestMax = 0.0;
for (var provider in selectedProviders) {
/// if this provider is not valid for the current pair, skip it
if (!providersForCurrentPair().contains(provider)) {
continue;
}
try {
final tempLimits = await provider.fetchLimits(
from: from, from: from,
to: to, to: to,
isFixedRateMode: isFixedRateMode); isFixedRateMode: isFixedRateMode);
/// if the first provider limits is bounded then check with other providers if (tempLimits.min != null && tempLimits.min! < lowestMin) {
/// for the highest maximum limit lowestMin = tempLimits.min!;
if (limits.max != null) { }
for (int i = 1;i < selectedProviders.length;i++) { if (highestMax != null && (tempLimits.max ?? double.maxFinite) > highestMax) {
final Limits tempLimits = await selectedProviders[i].fetchLimits( highestMax = tempLimits.max;
from: from, }
to: to, } catch (e) {
isFixedRateMode: isFixedRateMode); continue;
}
}
/// set the limits with the maximum provider limit if (lowestMin < double.maxFinite) {
/// if there is a provider with null max then it's the maximum limit limits = Limits(min: lowestMin, max: highestMax);
if ((tempLimits.max ?? double.maxFinite) > limits.max!) {
limits = tempLimits;
}
}
}
limitsState = LimitsLoadedSuccessfully(limits: limits); limitsState = LimitsLoadedSuccessfully(limits: limits);
} catch (e) { } else {
limitsState = LimitsLoadedFailure(error: e.toString()); limitsState = LimitsLoadedFailure(error: 'Limits loading failed');
} }
} }
@ -374,7 +356,7 @@ abstract class ExchangeViewModelBase with Store {
TradeRequest? request; TradeRequest? request;
String amount = ''; String amount = '';
for (var provider in currentTradeAvailableProviders.values) { for (var provider in _sortedAvailableProviders.values) {
if (!(await provider.checkIsAvailable())) { if (!(await provider.checkIsAvailable())) {
continue; continue;
} }
@ -383,7 +365,7 @@ abstract class ExchangeViewModelBase with Store {
request = SideShiftRequest( request = SideShiftRequest(
depositMethod: depositCurrency, depositMethod: depositCurrency,
settleMethod: receiveCurrency, settleMethod: receiveCurrency,
depositAmount: depositAmount?.replaceAll(',', '.') ?? '', depositAmount: depositAmount.replaceAll(',', '.'),
settleAddress: receiveAddress, settleAddress: receiveAddress,
refundAddress: depositAddress, refundAddress: depositAddress,
); );
@ -394,7 +376,7 @@ abstract class ExchangeViewModelBase with Store {
request = SimpleSwapRequest( request = SimpleSwapRequest(
from: depositCurrency, from: depositCurrency,
to: receiveCurrency, to: receiveCurrency,
amount: depositAmount?.replaceAll(',', '.') ?? '', amount: depositAmount.replaceAll(',', '.'),
address: receiveAddress, address: receiveAddress,
refundAddress: depositAddress, refundAddress: depositAddress,
); );
@ -405,8 +387,8 @@ abstract class ExchangeViewModelBase with Store {
request = XMRTOTradeRequest( request = XMRTOTradeRequest(
from: depositCurrency, from: depositCurrency,
to: receiveCurrency, to: receiveCurrency,
amount: depositAmount?.replaceAll(',', '.') ?? '', amount: depositAmount.replaceAll(',', '.'),
receiveAmount: receiveAmount?.replaceAll(',', '.') ?? '', receiveAmount: receiveAmount.replaceAll(',', '.'),
address: receiveAddress, address: receiveAddress,
refundAddress: depositAddress, refundAddress: depositAddress,
isBTCRequest: isReceiveAmountEntered); isBTCRequest: isReceiveAmountEntered);
@ -417,8 +399,8 @@ abstract class ExchangeViewModelBase with Store {
request = ChangeNowRequest( request = ChangeNowRequest(
from: depositCurrency, from: depositCurrency,
to: receiveCurrency, to: receiveCurrency,
fromAmount: depositAmount?.replaceAll(',', '.') ?? '', fromAmount: depositAmount.replaceAll(',', '.'),
toAmount: receiveAmount?.replaceAll(',', '.') ?? '', toAmount: receiveAmount.replaceAll(',', '.'),
refundAddress: depositAddress, refundAddress: depositAddress,
address: receiveAddress, address: receiveAddress,
isReverse: isReverse); isReverse: isReverse);
@ -429,7 +411,7 @@ abstract class ExchangeViewModelBase with Store {
request = MorphTokenRequest( request = MorphTokenRequest(
from: depositCurrency, from: depositCurrency,
to: receiveCurrency, to: receiveCurrency,
amount: depositAmount?.replaceAll(',', '.') ?? '', amount: depositAmount.replaceAll(',', '.'),
refundAddress: depositAddress, refundAddress: depositAddress,
address: receiveAddress); address: receiveAddress);
amount = depositAmount; amount = depositAmount;
@ -437,7 +419,7 @@ abstract class ExchangeViewModelBase with Store {
amount = amount.replaceAll(',', '.'); amount = amount.replaceAll(',', '.');
if (limitsState is LimitsLoadedSuccessfully && amount != null) { if (limitsState is LimitsLoadedSuccessfully) {
if (double.parse(amount) < limits.min!) { if (double.parse(amount) < limits.min!) {
continue; continue;
} else if (limits.max != null && double.parse(amount) > limits.max!) { } else if (limits.max != null && double.parse(amount) > limits.max!) {
@ -527,7 +509,7 @@ abstract class ExchangeViewModelBase with Store {
final providers = providerList final providers = providerList
.where((provider) => provider.pairList .where((provider) => provider.pairList
.where((pair) => .where((pair) =>
pair.from == (from ?? depositCurrency) && pair.to == (to ?? receiveCurrency)) pair.from == from && pair.to == to)
.isNotEmpty) .isNotEmpty)
.toList(); .toList();
@ -537,6 +519,10 @@ abstract class ExchangeViewModelBase with Store {
void _onPairChange() { void _onPairChange() {
depositAmount = ''; depositAmount = '';
receiveAmount = ''; receiveAmount = '';
loadLimits();
_setAvailableProviders();
_bestRate = 0;
_calculateBestRate();
} }
void _initialPairBasedOnWallet() { void _initialPairBasedOnWallet() {
@ -579,11 +565,15 @@ abstract class ExchangeViewModelBase with Store {
@action @action
void addExchangeProvider(ExchangeProvider provider) { void addExchangeProvider(ExchangeProvider provider) {
selectedProviders.add(provider); selectedProviders.add(provider);
if (providersForCurrentPair().contains(provider)) {
_tradeAvailableProviders.add(provider);
}
} }
@action @action
void removeExchangeProvider(ExchangeProvider provider) { void removeExchangeProvider(ExchangeProvider provider) {
selectedProviders.remove(provider); selectedProviders.remove(provider);
_tradeAvailableProviders.remove(provider);
} }
@action @action
@ -593,13 +583,14 @@ abstract class ExchangeViewModelBase with Store {
isFixedRateMode = false; isFixedRateMode = false;
_defineIsReceiveAmountEditable(); _defineIsReceiveAmountEditable();
loadLimits(); loadLimits();
_bestRate = 0;
_calculateBestRate();
final Map<String, dynamic> exchangeProvidersSelection = json final Map<String, dynamic> exchangeProvidersSelection = json
.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}") as Map<String, dynamic>; .decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}") as Map<String, dynamic>;
exchangeProvidersSelection.updateAll((key, dynamic value) => false); for (var provider in providerList) {
for (var provider in selectedProviders) { exchangeProvidersSelection[provider.title] = selectedProviders.contains(provider);
exchangeProvidersSelection[provider.title] = true;
} }
sharedPreferences.setString( sharedPreferences.setString(
@ -612,4 +603,12 @@ abstract class ExchangeViewModelBase with Store {
final providersForPair = providersForCurrentPair(); final providersForPair = providersForCurrentPair();
return selectedProviders.any((element) => element.isAvailable && providersForPair.contains(element)); return selectedProviders.any((element) => element.isAvailable && providersForPair.contains(element));
} }
void _setAvailableProviders() {
_tradeAvailableProviders.clear();
_tradeAvailableProviders.addAll(
selectedProviders
.where((provider) => providersForCurrentPair().contains(provider)));
}
} }

View file

@ -64,7 +64,7 @@ abstract class RestoreFromBackupViewModelBase with Store {
} catch (e) { } catch (e) {
var msg = e.toString(); var msg = e.toString();
if (msg == 'Message authentication code (MAC) is invalid') { if (msg.toLowerCase().contains("message authentication code (mac)")) {
msg = 'Incorrect backup password'; msg = 'Incorrect backup password';
} }