This commit is contained in:
Matthew Fosse 2024-09-06 08:34:07 -07:00
commit fbffda74dd
32 changed files with 585 additions and 163 deletions

View file

@ -195,6 +195,8 @@ jobs:
echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart
- name: Rename app - name: Rename app
run: | run: |

View file

@ -180,6 +180,8 @@ jobs:
echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart
- name: Rename app - name: Rename app
run: | run: |

BIN
assets/images/stealthex.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View file

@ -17,6 +17,3 @@
- -
uri: node.community.rino.io:18081 uri: node.community.rino.io:18081
is_default: false is_default: false
-
uri: node.moneroworld.com:18089
is_default: false

View file

@ -138,11 +138,17 @@ PendingTransactionDescription createTransactionMultDestSync(
int accountIndex = 0, int accountIndex = 0,
List<String> preferredInputs = const []}) { List<String> preferredInputs = const []}) {
final dstAddrs = outputs.map((e) => e.address).toList();
final amounts = outputs.map((e) => monero.Wallet_amountFromString(e.amount)).toList();
// print("multDest: dstAddrs: $dstAddrs");
// print("multDest: amounts: $amounts");
final txptr = monero.Wallet_createTransactionMultDest( final txptr = monero.Wallet_createTransactionMultDest(
wptr!, wptr!,
dstAddr: outputs.map((e) => e.address).toList(), dstAddr: dstAddrs,
isSweepAll: false, isSweepAll: false,
amounts: outputs.map((e) => monero.Wallet_amountFromString(e.amount)).toList(), amounts: amounts,
mixinCount: 0, mixinCount: 0,
pendingTransactionPriority: priorityRaw, pendingTransactionPriority: priorityRaw,
subaddr_account: accountIndex, subaddr_account: accountIndex,
@ -307,7 +313,34 @@ class Transaction {
confirmations = monero.TransactionInfo_confirmations(txInfo), confirmations = monero.TransactionInfo_confirmations(txInfo),
fee = monero.TransactionInfo_fee(txInfo), fee = monero.TransactionInfo_fee(txInfo),
description = monero.TransactionInfo_description(txInfo), description = monero.TransactionInfo_description(txInfo),
key = monero.Wallet_getTxKey(wptr!, txid: monero.TransactionInfo_hash(txInfo)); key = getTxKey(txInfo);
static String getTxKey(monero.TransactionInfo txInfo) {
final txKey = monero.Wallet_getTxKey(wptr!, txid: monero.TransactionInfo_hash(txInfo));
final status = monero.Wallet_status(wptr!);
if (status != 0) {
return monero.Wallet_errorString(wptr!);
}
return breakTxKey(txKey);
}
static String breakTxKey(String input) {
final x = 64;
StringBuffer buffer = StringBuffer();
for (int i = 0; i < input.length; i += x) {
int endIndex = i + x;
if (endIndex > input.length) {
endIndex = input.length;
}
buffer.write(input.substring(i, endIndex));
if (endIndex != input.length) {
buffer.write('\n\n');
}
}
return buffer.toString().trim();
}
Transaction.dummy({ Transaction.dummy({
required this.displayLabel, required this.displayLabel,

View file

@ -463,8 +463,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "impls/monero.dart" path: "impls/monero.dart"
ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b ref: 3cb38bee9385faf46b03fd73aab85f3ac4115bf7
resolved-ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b resolved-ref: 3cb38bee9385faf46b03fd73aab85f3ac4115bf7
url: "https://github.com/mrcyjanek/monero_c" url: "https://github.com/mrcyjanek/monero_c"
source: git source: git
version: "0.0.0" version: "0.0.0"

View file

@ -25,7 +25,7 @@ dependencies:
monero: monero:
git: git:
url: https://github.com/mrcyjanek/monero_c url: https://github.com/mrcyjanek/monero_c
ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b # monero_c hash ref: 3cb38bee9385faf46b03fd73aab85f3ac4115bf7 # monero_c hash
path: impls/monero.dart path: impls/monero.dart
mutex: ^3.1.0 mutex: ^3.1.0

View file

@ -466,21 +466,25 @@ class NanoClient {
blocks = blocks as Map<String, dynamic>; blocks = blocks as Map<String, dynamic>;
// confirm all receivable blocks: try {
for (final blockHash in blocks.keys) { // confirm all receivable blocks:
final block = blocks[blockHash]; for (final blockHash in blocks.keys) {
final String amountRaw = block["amount"] as String; final block = blocks[blockHash];
await receiveBlock( final String amountRaw = block["amount"] as String;
blockHash: blockHash, await receiveBlock(
amountRaw: amountRaw, blockHash: blockHash,
privateKey: privateKey, amountRaw: amountRaw,
destinationAddress: destinationAddress, privateKey: privateKey,
); destinationAddress: destinationAddress,
// a bit of a hack: );
await Future<void>.delayed(const Duration(seconds: 2)); // a bit of a hack:
await Future<void>.delayed(const Duration(seconds: 2));
}
return blocks.keys.length;
} catch (_) {
// we failed to confirm all receivable blocks for w/e reason (PoW / node outage / etc)
return 0;
} }
return blocks.keys.length;
} }
void stop() {} void stop() {}

View file

@ -14,8 +14,11 @@ import 'package:bip39/bip39.dart' as bip39;
import 'package:nanodart/nanodart.dart'; import 'package:nanodart/nanodart.dart';
import 'package:nanoutil/nanoutil.dart'; import 'package:nanoutil/nanoutil.dart';
class NanoWalletService extends WalletService<NanoNewWalletCredentials, class NanoWalletService extends WalletService<
NanoRestoreWalletFromSeedCredentials, NanoRestoreWalletFromKeysCredentials, NanoNewWalletCredentials> { NanoNewWalletCredentials,
NanoRestoreWalletFromSeedCredentials,
NanoRestoreWalletFromKeysCredentials,
NanoNewWalletCredentials> {
NanoWalletService(this.walletInfoSource, this.isDirect); NanoWalletService(this.walletInfoSource, this.isDirect);
final Box<WalletInfo> walletInfoSource; final Box<WalletInfo> walletInfoSource;
@ -33,8 +36,12 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
String seedKey = NanoSeeds.generateSeed(); String seedKey = NanoSeeds.generateSeed();
String mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey); String mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey);
// ensure default if not present: // should never happen but just in case:
credentials.walletInfo!.derivationInfo ??= DerivationInfo(derivationType: DerivationType.nano); if (credentials.walletInfo!.derivationInfo == null) {
credentials.walletInfo!.derivationInfo = DerivationInfo(derivationType: DerivationType.nano);
} else if (credentials.walletInfo!.derivationInfo!.derivationType == null) {
credentials.walletInfo!.derivationInfo!.derivationType = DerivationType.nano;
}
final wallet = NanoWallet( final wallet = NanoWallet(
walletInfo: credentials.walletInfo!, walletInfo: credentials.walletInfo!,
@ -86,7 +93,8 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
} }
@override @override
Future<NanoWallet> restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials, {bool? isTestnet}) async { Future<NanoWallet> restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials,
{bool? isTestnet}) async {
if (credentials.seedKey.contains(' ')) { if (credentials.seedKey.contains(' ')) {
throw Exception("Invalid key!"); throw Exception("Invalid key!");
} else { } else {
@ -106,6 +114,13 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
} }
} }
// should never happen but just in case:
if (credentials.walletInfo!.derivationInfo == null) {
credentials.walletInfo!.derivationInfo = DerivationInfo(derivationType: DerivationType.nano);
} else if (credentials.walletInfo!.derivationInfo!.derivationType == null) {
credentials.walletInfo!.derivationInfo!.derivationType = DerivationType.nano;
}
final wallet = await NanoWallet( final wallet = await NanoWallet(
password: credentials.password!, password: credentials.password!,
mnemonic: mnemonic ?? credentials.seedKey, mnemonic: mnemonic ?? credentials.seedKey,
@ -119,11 +134,13 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
@override @override
Future<NanoWallet> restoreFromHardwareWallet(NanoNewWalletCredentials credentials) { Future<NanoWallet> restoreFromHardwareWallet(NanoNewWalletCredentials credentials) {
throw UnimplementedError("Restoring a Nano wallet from a hardware wallet is not yet supported!"); throw UnimplementedError(
"Restoring a Nano wallet from a hardware wallet is not yet supported!");
} }
@override @override
Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async { Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials,
{bool? isTestnet}) async {
if (credentials.mnemonic.contains(' ')) { if (credentials.mnemonic.contains(' ')) {
if (!bip39.validateMnemonic(credentials.mnemonic)) { if (!bip39.validateMnemonic(credentials.mnemonic)) {
throw nm.NanoMnemonicIsIncorrectException(); throw nm.NanoMnemonicIsIncorrectException();

View file

@ -463,8 +463,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "impls/monero.dart" path: "impls/monero.dart"
ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b ref: 3cb38bee9385faf46b03fd73aab85f3ac4115bf7
resolved-ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b resolved-ref: 3cb38bee9385faf46b03fd73aab85f3ac4115bf7
url: "https://github.com/mrcyjanek/monero_c" url: "https://github.com/mrcyjanek/monero_c"
source: git source: git
version: "0.0.0" version: "0.0.0"

View file

@ -25,7 +25,7 @@ dependencies:
monero: monero:
git: git:
url: https://github.com/mrcyjanek/monero_c url: https://github.com/mrcyjanek/monero_c
ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b # monero_c hash ref: 3cb38bee9385faf46b03fd73aab85f3ac4115bf7 # monero_c hash
path: impls/monero.dart path: impls/monero.dart
mutex: ^3.1.0 mutex: ^3.1.0

View file

@ -40,6 +40,7 @@ const solanaDefaultNodeUri = 'rpc.ankr.com';
const tronDefaultNodeUri = 'trx.nownodes.io'; const tronDefaultNodeUri = 'trx.nownodes.io';
const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002'; const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002';
const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568'; const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568';
const moneroWorldNodeUri = '.moneroworld.com';
Future<void> defaultSettingsMigration( Future<void> defaultSettingsMigration(
{required int version, {required int version,
@ -245,6 +246,9 @@ Future<void> defaultSettingsMigration(
_fixNodesUseSSLFlag(nodes); _fixNodesUseSSLFlag(nodes);
await changeDefaultNanoNode(nodes, sharedPreferences); await changeDefaultNanoNode(nodes, sharedPreferences);
break; break;
case 40:
await removeMoneroWorld(sharedPreferences: sharedPreferences, nodes: nodes);
break;
default: default:
break; break;
} }
@ -488,15 +492,7 @@ Node? getBitcoinCashDefaultElectrumServer({required Box<Node> nodes}) {
Node getMoneroDefaultNode({required Box<Node> nodes}) { Node getMoneroDefaultNode({required Box<Node> nodes}) {
final timeZone = DateTime.now().timeZoneOffset.inHours; final timeZone = DateTime.now().timeZoneOffset.inHours;
var nodeUri = ''; var nodeUri = newCakeWalletMoneroUri;
if (timeZone >= 1) {
// Eurasia
nodeUri = 'xmr-node-eu.cakewallet.com:18081';
} else if (timeZone <= -4) {
// America
nodeUri = 'xmr-node-usa-east.cakewallet.com:18081';
}
try { try {
return nodes.values.firstWhere((Node node) => node.uriRaw == nodeUri); return nodes.values.firstWhere((Node node) => node.uriRaw == nodeUri);
@ -1260,3 +1256,22 @@ Future<void> replaceTronDefaultNode({
// If it's not, we switch user to the new default node: NowNodes // If it's not, we switch user to the new default node: NowNodes
await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes); await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
} }
Future<void> removeMoneroWorld(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
const cakeWalletMoneroNodeUriPattern = '.moneroworld.com';
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentMoneroNode = nodes.values.firstWhere((node) => node.key == currentMoneroNodeId);
final needToReplaceCurrentMoneroNode = currentMoneroNode.uri.toString().contains(cakeWalletMoneroNodeUriPattern);
nodes.values.forEach((node) async {
if (node.type == WalletType.monero &&
node.uri.toString().contains(cakeWalletMoneroNodeUriPattern)) {
await node.delete();
}
});
if (needToReplaceCurrentMoneroNode) {
await changeMoneroCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
}
}

View file

@ -27,6 +27,8 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
ExchangeProviderDescription(title: 'ThorChain', raw: 8, image: 'assets/images/thorchain.png'); ExchangeProviderDescription(title: 'ThorChain', raw: 8, image: 'assets/images/thorchain.png');
static const quantex = static const quantex =
ExchangeProviderDescription(title: 'Quantex', raw: 9, image: 'assets/images/quantex.png'); ExchangeProviderDescription(title: 'Quantex', raw: 9, image: 'assets/images/quantex.png');
static const stealthEx =
ExchangeProviderDescription(title: 'StealthEx', raw: 10, image: 'assets/images/stealthex.png');
static ExchangeProviderDescription deserialize({required int raw}) { static ExchangeProviderDescription deserialize({required int raw}) {
switch (raw) { switch (raw) {
@ -50,6 +52,8 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
return thorChain; return thorChain;
case 9: case 9:
return quantex; return quantex;
case 10:
return stealthEx;
default: default:
throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize'); throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize');
} }

View file

@ -0,0 +1,299 @@
import 'dart:convert';
import 'dart:developer';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_not_created_exception.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:http/http.dart' as http;
class StealthExExchangeProvider extends ExchangeProvider {
StealthExExchangeProvider() : super(pairList: supportedPairs(_notSupported));
static const List<CryptoCurrency> _notSupported = [];
static final apiKey = secrets.stealthExBearerToken;
static final _additionalFeePercent = double.tryParse(secrets.stealthExAdditionalFeePercent);
static const _baseUrl = 'https://api.stealthex.io';
static const _rangePath = '/v4/rates/range';
static const _amountPath = '/v4/rates/estimated-amount';
static const _exchangesPath = '/v4/exchanges';
@override
String get title => 'StealthEX';
@override
bool get isAvailable => true;
@override
bool get isEnabled => true;
@override
bool get supportsFixedRate => true;
@override
ExchangeProviderDescription get description => ExchangeProviderDescription.stealthEx;
@override
Future<bool> checkIsAvailable() async => true;
@override
Future<Limits> fetchLimits(
{required CryptoCurrency from,
required CryptoCurrency to,
required bool isFixedRateMode}) async {
final curFrom = isFixedRateMode ? to : from;
final curTo = isFixedRateMode ? from : to;
final headers = {'Authorization': apiKey, 'Content-Type': 'application/json'};
final body = {
'route': {
'from': {'symbol': _getName(curFrom), 'network': _getNetwork(curFrom)},
'to': {'symbol': _getName(curTo), 'network': _getNetwork(curTo)}
},
'estimation': isFixedRateMode ? 'reversed' : 'direct',
'rate': isFixedRateMode ? 'fixed' : 'floating',
'additional_fee_percent': _additionalFeePercent,
};
try {
final response = await http.post(Uri.parse(_baseUrl + _rangePath),
headers: headers, body: json.encode(body));
if (response.statusCode != 200) {
throw Exception('StealthEx fetch limits failed: ${response.body}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final min = responseJSON['min_amount'] as double?;
final max = responseJSON['max_amount'] as double?;
return Limits(min: min, max: max);
} catch (e) {
log(e.toString());
throw Exception('StealthEx failed to fetch limits');
}
}
@override
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount}) async {
final response = await getEstimatedExchangeAmount(
from: from, to: to, amount: amount, isFixedRateMode: isFixedRateMode);
final estimatedAmount = response['estimated_amount'] as double? ?? 0.0;
return estimatedAmount > 0.0
? isFixedRateMode
? amount / estimatedAmount
: estimatedAmount / amount
: 0.0;
}
@override
Future<Trade> createTrade(
{required TradeRequest request,
required bool isFixedRateMode,
required bool isSendAll}) async {
String? rateId;
String? validUntil;
try {
if (isFixedRateMode) {
final response = await getEstimatedExchangeAmount(
from: request.fromCurrency,
to: request.toCurrency,
amount: double.parse(request.toAmount),
isFixedRateMode: isFixedRateMode);
rateId = response['rate_id'] as String?;
validUntil = response['valid_until'] as String?;
if (rateId == null) throw TradeNotCreatedException(description);
}
final headers = {'Authorization': apiKey, 'Content-Type': 'application/json'};
final body = {
'route': {
'from': {
'symbol': _getName(request.fromCurrency),
'network': _getNetwork(request.fromCurrency)
},
'to': {'symbol': _getName(request.toCurrency), 'network': _getNetwork(request.toCurrency)}
},
'estimation': isFixedRateMode ? 'reversed' : 'direct',
'rate': isFixedRateMode ? 'fixed' : 'floating',
if (isFixedRateMode) 'rate_id': rateId,
'amount':
isFixedRateMode ? double.parse(request.toAmount) : double.parse(request.fromAmount),
'address': request.toAddress,
'refund_address': request.refundAddress,
'additional_fee_percent': _additionalFeePercent,
};
final response = await http.post(Uri.parse(_baseUrl + _exchangesPath),
headers: headers, body: json.encode(body));
if (response.statusCode != 201) {
throw Exception('StealthEx create trade failed: ${response.body}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final deposit = responseJSON['deposit'] as Map<String, dynamic>;
final withdrawal = responseJSON['withdrawal'] as Map<String, dynamic>;
final id = responseJSON['id'] as String;
final from = deposit['symbol'] as String;
final to = withdrawal['symbol'] as String;
final payoutAddress = withdrawal['address'] as String;
final depositAddress = deposit['address'] as String;
final refundAddress = responseJSON['refund_address'] as String;
final depositAmount = toDouble(deposit['amount']);
final receiveAmount = toDouble(withdrawal['amount']);
final status = responseJSON['status'] as String;
final createdAtString = responseJSON['created_at'] as String;
final createdAt = DateTime.parse(createdAtString);
final expiredAt = validUntil != null
? DateTime.parse(validUntil)
: DateTime.now().add(Duration(minutes: 5));
CryptoCurrency fromCurrency;
if (request.fromCurrency.tag != null && request.fromCurrency.title.toLowerCase() == from) {
fromCurrency = request.fromCurrency;
} else {
fromCurrency = CryptoCurrency.fromString(from);
}
CryptoCurrency toCurrency;
if (request.toCurrency.tag != null && request.toCurrency.title.toLowerCase() == to) {
toCurrency = request.toCurrency;
} else {
toCurrency = CryptoCurrency.fromString(to);
}
return Trade(
id: id,
from: fromCurrency,
to: toCurrency,
provider: description,
inputAddress: depositAddress,
payoutAddress: payoutAddress,
refundAddress: refundAddress,
amount: depositAmount.toString(),
receiveAmount: receiveAmount.toString(),
state: TradeState.deserialize(raw: status),
createdAt: createdAt,
expiredAt: expiredAt,
);
} catch (e) {
log(e.toString());
throw TradeNotCreatedException(description);
}
}
@override
Future<Trade> findTradeById({required String id}) async {
final headers = {'Authorization': apiKey, 'Content-Type': 'application/json'};
final uri = Uri.parse('$_baseUrl$_exchangesPath/$id');
final response = await http.get(uri, headers: headers);
if (response.statusCode != 200) {
throw Exception('StealthEx fetch trade failed: ${response.body}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final deposit = responseJSON['deposit'] as Map<String, dynamic>;
final withdrawal = responseJSON['withdrawal'] as Map<String, dynamic>;
final respId = responseJSON['id'] as String;
final from = deposit['symbol'] as String;
final to = withdrawal['symbol'] as String;
final payoutAddress = withdrawal['address'] as String;
final depositAddress = deposit['address'] as String;
final refundAddress = responseJSON['refund_address'] as String;
final depositAmount = toDouble(deposit['amount']);
final receiveAmount = toDouble(withdrawal['amount']);
final status = responseJSON['status'] as String;
final createdAtString = responseJSON['created_at'] as String;
final createdAt = DateTime.parse(createdAtString);
return Trade(
id: respId,
from: CryptoCurrency.fromString(from),
to: CryptoCurrency.fromString(to),
provider: description,
inputAddress: depositAddress,
payoutAddress: payoutAddress,
refundAddress: refundAddress,
amount: depositAmount.toString(),
receiveAmount: receiveAmount.toString(),
state: TradeState.deserialize(raw: status),
createdAt: createdAt,
isRefund: status == 'refunded',
);
}
Future<Map<String, dynamic>> getEstimatedExchangeAmount(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode}) async {
final headers = {'Authorization': apiKey, 'Content-Type': 'application/json'};
final body = {
'route': {
'from': {'symbol': _getName(from), 'network': _getNetwork(from)},
'to': {'symbol': _getName(to), 'network': _getNetwork(to)}
},
'estimation': isFixedRateMode ? 'reversed' : 'direct',
'rate': isFixedRateMode ? 'fixed' : 'floating',
'amount': amount,
'additional_fee_percent': _additionalFeePercent,
};
try {
final response = await http.post(Uri.parse(_baseUrl + _amountPath),
headers: headers, body: json.encode(body));
if (response.statusCode != 200) return {};
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final rate = responseJSON['rate'] as Map<String, dynamic>?;
return {
'estimated_amount': responseJSON['estimated_amount'] as double?,
if (rate != null) 'valid_until': rate['valid_until'] as String?,
if (rate != null) 'rate_id': rate['id'] as String?
};
} catch (e) {
log(e.toString());
return {};
}
}
double toDouble(dynamic value) {
if (value is int) {
return value.toDouble();
} else if (value is double) {
return value;
} else {
return 0.0;
}
}
String _getName(CryptoCurrency currency) {
if (currency == CryptoCurrency.usdcEPoly) return 'usdce';
return currency.title.toLowerCase();
}
String _getNetwork(CryptoCurrency currency) {
if (currency.tag == null) return 'mainnet';
if (currency == CryptoCurrency.maticpoly) return 'mainnet';
if (currency.tag == 'POLY') return 'matic';
return currency.tag!.toLowerCase();
}
}

View file

@ -40,7 +40,6 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
static const exchanging = TradeState(raw: 'exchanging', title: 'Exchanging'); static const exchanging = TradeState(raw: 'exchanging', title: 'Exchanging');
static const sending = TradeState(raw: 'sending', title: 'Sending'); static const sending = TradeState(raw: 'sending', title: 'Sending');
static const success = TradeState(raw: 'success', title: 'Success'); static const success = TradeState(raw: 'success', title: 'Success');
static TradeState deserialize({required String raw}) { static TradeState deserialize({required String raw}) {
switch (raw) { switch (raw) {
@ -119,6 +118,7 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
case 'refunded': case 'refunded':
return refunded; return refunded;
case 'confirmation': case 'confirmation':
case 'verifying':
return confirmation; return confirmation;
case 'confirmed': case 'confirmed':
return confirmed; return confirmed;

View file

@ -49,11 +49,14 @@ final rootKey = GlobalKey<RootState>();
final RouteObserver<PageRoute<dynamic>> routeObserver = RouteObserver<PageRoute<dynamic>>(); final RouteObserver<PageRoute<dynamic>> routeObserver = RouteObserver<PageRoute<dynamic>>();
Future<void> main() async { Future<void> main() async {
await runAppWithZone();
}
Future<void> runAppWithZone() async {
bool isAppRunning = false; bool isAppRunning = false;
await runZonedGuarded(() async { await runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
FlutterError.onError = ExceptionHandler.onError; FlutterError.onError = ExceptionHandler.onError;
/// A callback that is invoked when an unhandled error occurs in the root /// A callback that is invoked when an unhandled error occurs in the root
@ -63,42 +66,14 @@ Future<void> main() async {
return true; return true;
}; };
await initializeAppAtRoot();
await setDefaultMinimumWindowSize();
await CakeHive.close();
await initializeAppConfigs();
runApp(App()); runApp(App());
isAppRunning = true; isAppRunning = true;
}, (error, stackTrace) async { }, (error, stackTrace) async {
if (!isAppRunning) { if (!isAppRunning) {
runApp( runApp(
MaterialApp( TopLevelErrorWidget(error: error, stackTrace: stackTrace),
debugShowCheckedModeBanner: false,
scrollBehavior: AppScrollBehavior(),
home: Scaffold(
body: SingleChildScrollView(
child: Container(
margin: EdgeInsets.only(top: 50, left: 20, right: 20, bottom: 20),
child: Column(
children: [
Text(
'Error:\n${error.toString()}',
style: TextStyle(fontSize: 22),
),
Text(
'Stack trace:\n${stackTrace.toString()}',
style: TextStyle(fontSize: 16),
),
],
),
),
),
),
),
); );
} }
@ -106,6 +81,12 @@ Future<void> main() async {
}); });
} }
Future<void> initializeAppAtRoot({bool reInitializing = false}) async {
if (!reInitializing) await setDefaultMinimumWindowSize();
await CakeHive.close();
await initializeAppConfigs();
}
Future<void> initializeAppConfigs() async { Future<void> initializeAppConfigs() async {
setRootDirFromEnv(); setRootDirFromEnv();
final appDir = await getAppDir(); final appDir = await getAppDir();
@ -210,7 +191,7 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions, transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage, secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo, anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 39, initialMigrationVersion: 40,
); );
} }
@ -342,3 +323,41 @@ class _HomeState extends State<_Home> {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
} }
class TopLevelErrorWidget extends StatelessWidget {
const TopLevelErrorWidget({
required this.error,
required this.stackTrace,
super.key,
});
final Object error;
final StackTrace stackTrace;
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
scrollBehavior: AppScrollBehavior(),
home: Scaffold(
body: SingleChildScrollView(
child: Container(
margin: EdgeInsets.only(top: 50, left: 20, right: 20, bottom: 20),
child: Column(
children: [
Text(
'Error:\n${error.toString()}',
style: TextStyle(fontSize: 22),
),
Text(
'Stack trace:\n${stackTrace.toString()}',
style: TextStyle(fontSize: 16),
),
],
),
),
),
),
);
}
}

View file

@ -40,13 +40,11 @@ class NanoChangeRepPage extends BasePage {
(node) => node.account == currentRepAccount, (node) => node.account == currentRepAccount,
orElse: () => N2Node( orElse: () => N2Node(
account: currentRepAccount, account: currentRepAccount,
alias: currentRepAccount,
score: 0, score: 0,
uptime: "???", uptime: "???",
weight: 0, weight: 0,
), ),
); );
return currentNode; return currentNode;
} }
@ -57,9 +55,7 @@ class NanoChangeRepPage extends BasePage {
child: FutureBuilder( child: FutureBuilder(
future: nano!.getN2Reps(_wallet), future: nano!.getN2Reps(_wallet),
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.data == null) { final reps = snapshot.data ?? [];
return SizedBox();
}
return Container( return Container(
padding: EdgeInsets.only(left: 24, right: 24), padding: EdgeInsets.only(left: 24, right: 24),
@ -101,29 +97,35 @@ class NanoChangeRepPage extends BasePage {
), ),
_buildSingleRepresentative( _buildSingleRepresentative(
context, context,
getCurrentRepNode(snapshot.data as List<N2Node>), getCurrentRepNode(reps),
isList: false, isList: false,
divider: false,
), ),
Divider(height: 20), if (reps.isNotEmpty) ...[
Container( Divider(height: 20),
margin: EdgeInsets.only(top: 12), Container(
child: Text( margin: EdgeInsets.only(top: 12),
S.current.nano_pick_new_rep, child: Text(
style: TextStyle( S.current.nano_pick_new_rep,
fontSize: 16, style: TextStyle(
fontWeight: FontWeight.w700, fontSize: 16,
fontWeight: FontWeight.w700,
),
), ),
), ),
), Divider(height: 20),
],
], ],
), ),
], ],
), ),
contentPadding: EdgeInsets.only(bottom: 24), contentPadding: EdgeInsets.only(bottom: 24),
content: Container( content: Container(
child: Column( child: reps.isNotEmpty
children: _getRepresentativeWidgets(context, snapshot.data as List<N2Node>), ? Column(
), children: _getRepresentativeWidgets(context, reps),
)
: SizedBox(),
), ),
bottomSectionPadding: EdgeInsets.only(bottom: 24), bottomSectionPadding: EdgeInsets.only(bottom: 24),
bottomSection: Observer( bottomSection: Observer(
@ -207,19 +209,22 @@ class NanoChangeRepPage extends BasePage {
final List<Widget> ret = []; final List<Widget> ret = [];
for (final N2Node node in list) { for (final N2Node node in list) {
if (node.alias != null && node.alias!.trim().isNotEmpty) { if (node.alias != null && node.alias!.trim().isNotEmpty) {
ret.add(_buildSingleRepresentative(context, node)); bool divider = node != list.first;
ret.add(_buildSingleRepresentative(context, node, divider: divider, isList: true));
} }
} }
return ret; return ret;
} }
Widget _buildSingleRepresentative(BuildContext context, N2Node rep, {bool isList = true}) { Widget _buildSingleRepresentative(
BuildContext context,
N2Node rep, {
bool isList = true,
bool divider = false,
}) {
return Column( return Column(
children: <Widget>[ children: <Widget>[
if (isList) if (divider) Divider(height: 2),
Divider(
height: 2,
),
TextButton( TextButton(
style: TextButton.styleFrom( style: TextButton.styleFrom(
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@ -244,11 +249,11 @@ class NanoChangeRepPage extends BasePage {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text( Text(
_sanitizeAlias(rep.alias), rep.alias ?? rep.account!,
style: TextStyle( style: TextStyle(
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor, color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
fontSize: 18, fontSize: rep.alias == null ? 14 : 18,
), ),
), ),
Container( Container(
@ -337,11 +342,4 @@ class NanoChangeRepPage extends BasePage {
], ],
); );
} }
String _sanitizeAlias(String? alias) {
if (alias != null) {
return alias.replaceAll(RegExp(r'[^a-zA-Z_.!?_;:-]'), '');
}
return '';
}
} }

View file

@ -406,24 +406,16 @@ class WalletRestorePage extends BasePage {
) as DerivationInfo?; ) as DerivationInfo?;
} else if (derivationsWithHistory == 1) { } else if (derivationsWithHistory == 1) {
dInfo = derivations[derivationWithHistoryIndex]; dInfo = derivations[derivationWithHistoryIndex];
} } else if (derivations.length == 1) {
// get the default derivation for this wallet type:
if (dInfo == null) {
// we only return 1 derivation if we're pretty sure we know which one to use: // we only return 1 derivation if we're pretty sure we know which one to use:
if (derivations.length == 1) { dInfo = derivations.first;
dInfo = derivations.first; } else {
} else { // if we have multiple possible derivations, and none (or multiple) have histories
// if we have multiple possible derivations, and none have histories // we just default to the most common one:
// we just default to the most common one: dInfo = walletRestoreViewModel.getCommonRestoreDerivation();
dInfo = walletRestoreViewModel.getCommonRestoreDerivation();
}
} }
this.derivationInfo = dInfo; this.derivationInfo = dInfo;
if (this.derivationInfo == null) {
this.derivationInfo = walletRestoreViewModel.getDefaultDerivation();
}
await walletRestoreViewModel.create(options: _credentials()); await walletRestoreViewModel.create(options: _credentials());
seedSettingsViewModel.setPassphrase(null); seedSettingsViewModel.setPassphrase(null);

View file

@ -273,6 +273,7 @@ class SendPage extends BasePage {
? template.cryptoCurrency ? template.cryptoCurrency
: template.fiatCurrency, : template.fiatCurrency,
onTap: () async { onTap: () async {
sendViewModel.state = IsExecutingState();
if (template.additionalRecipients?.isNotEmpty ?? false) { if (template.additionalRecipients?.isNotEmpty ?? false) {
sendViewModel.clearOutputs(); sendViewModel.clearOutputs();
@ -301,6 +302,7 @@ class SendPage extends BasePage {
template: template, template: template,
); );
} }
sendViewModel.state = InitialExecutionState();
}, },
onRemove: () { onRemove: () {
showPopUp<void>( showPopUp<void>(
@ -368,6 +370,7 @@ class SendPage extends BasePage {
builder: (_) { builder: (_) {
return LoadingPrimaryButton( return LoadingPrimaryButton(
onPressed: () async { onPressed: () async {
if (sendViewModel.state is IsExecutingState) return;
if (_formKey.currentState != null && !_formKey.currentState!.validate()) { if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
if (sendViewModel.outputs.length > 1) { if (sendViewModel.outputs.length > 1) {
showErrorValidationAlert(context); showErrorValidationAlert(context);

View file

@ -113,10 +113,6 @@ class CheckBoxPickerState extends State<CheckBoxPicker> {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
if (item.isDisabled) {
return;
}
bool newValue = !item.value; bool newValue = !item.value;
item.value = newValue; item.value = newValue;
widget.onChanged(index, newValue); widget.onChanged(index, newValue);
@ -134,7 +130,7 @@ class CheckBoxPickerState extends State<CheckBoxPicker> {
borderColor: Theme.of(context).dividerColor, borderColor: Theme.of(context).dividerColor,
iconColor: Colors.white, iconColor: Colors.white,
onChanged: (bool? value) { onChanged: (bool? value) {
if (value == null || item.isDisabled) { if (value == null) {
return; return;
} }

View file

@ -16,7 +16,8 @@ abstract class TradeFilterStoreBase with Store {
displaySimpleSwap = true, displaySimpleSwap = true,
displayTrocador = true, displayTrocador = true,
displayExolix = true, displayExolix = true,
displayThorChain = true; displayThorChain = true,
displayStealthEx = true;
@observable @observable
bool displayXMRTO; bool displayXMRTO;
@ -42,6 +43,9 @@ abstract class TradeFilterStoreBase with Store {
@observable @observable
bool displayThorChain; bool displayThorChain;
@observable
bool displayStealthEx;
@computed @computed
bool get displayAllTrades => bool get displayAllTrades =>
displayChangeNow && displayChangeNow &&
@ -49,7 +53,8 @@ abstract class TradeFilterStoreBase with Store {
displaySimpleSwap && displaySimpleSwap &&
displayTrocador && displayTrocador &&
displayExolix && displayExolix &&
displayThorChain; displayThorChain &&
displayStealthEx;
@action @action
void toggleDisplayExchange(ExchangeProviderDescription provider) { void toggleDisplayExchange(ExchangeProviderDescription provider) {
@ -78,6 +83,9 @@ abstract class TradeFilterStoreBase with Store {
case ExchangeProviderDescription.thorChain: case ExchangeProviderDescription.thorChain:
displayThorChain = !displayThorChain; displayThorChain = !displayThorChain;
break; break;
case ExchangeProviderDescription.stealthEx:
displayStealthEx = !displayStealthEx;
break;
case ExchangeProviderDescription.all: case ExchangeProviderDescription.all:
if (displayAllTrades) { if (displayAllTrades) {
displayChangeNow = false; displayChangeNow = false;
@ -88,6 +96,7 @@ abstract class TradeFilterStoreBase with Store {
displayTrocador = false; displayTrocador = false;
displayExolix = false; displayExolix = false;
displayThorChain = false; displayThorChain = false;
displayStealthEx = false;
} else { } else {
displayChangeNow = true; displayChangeNow = true;
displaySideShift = true; displaySideShift = true;
@ -97,6 +106,7 @@ abstract class TradeFilterStoreBase with Store {
displayTrocador = true; displayTrocador = true;
displayExolix = true; displayExolix = true;
displayThorChain = true; displayThorChain = true;
displayStealthEx = true;
} }
break; break;
} }
@ -112,13 +122,19 @@ abstract class TradeFilterStoreBase with Store {
? _trades ? _trades
.where((item) => .where((item) =>
(displayXMRTO && item.trade.provider == ExchangeProviderDescription.xmrto) || (displayXMRTO && item.trade.provider == ExchangeProviderDescription.xmrto) ||
(displaySideShift && item.trade.provider == ExchangeProviderDescription.sideShift) || (displaySideShift &&
(displayChangeNow && item.trade.provider == ExchangeProviderDescription.changeNow) || item.trade.provider == ExchangeProviderDescription.sideShift) ||
(displayMorphToken && item.trade.provider == ExchangeProviderDescription.morphToken) || (displayChangeNow &&
(displaySimpleSwap && item.trade.provider == ExchangeProviderDescription.simpleSwap) || item.trade.provider == ExchangeProviderDescription.changeNow) ||
(displayMorphToken &&
item.trade.provider == ExchangeProviderDescription.morphToken) ||
(displaySimpleSwap &&
item.trade.provider == ExchangeProviderDescription.simpleSwap) ||
(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador) || (displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador) ||
(displayExolix && item.trade.provider == ExchangeProviderDescription.exolix) || (displayExolix && item.trade.provider == ExchangeProviderDescription.exolix) ||
(displayThorChain && item.trade.provider == ExchangeProviderDescription.thorChain)) (displayThorChain &&
item.trade.provider == ExchangeProviderDescription.thorChain) ||
(displayStealthEx && item.trade.provider == ExchangeProviderDescription.stealthEx))
.toList() .toList()
: _trades; : _trades;
} }

View file

@ -1,12 +1,14 @@
import 'dart:convert'; import 'dart:convert';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/preferences_key.dart'; import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/provider_types.dart'; import 'package:cake_wallet/entities/provider_types.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/service_status.dart'; import 'package:cake_wallet/entities/service_status.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart'; import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
@ -45,11 +47,9 @@ import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:eth_sig_util/util/utils.dart'; import 'package:eth_sig_util/util/utils.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
part 'dashboard_view_model.g.dart'; part 'dashboard_view_model.g.dart';
@ -129,6 +129,11 @@ abstract class DashboardViewModelBase with Store {
caption: ExchangeProviderDescription.thorChain.title, caption: ExchangeProviderDescription.thorChain.title,
onChanged: () => onChanged: () =>
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.thorChain)), tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.thorChain)),
FilterItem(
value: () => tradeFilterStore.displayStealthEx,
caption: ExchangeProviderDescription.stealthEx.title,
onChanged: () =>
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.stealthEx)),
] ]
}, },
subname = '', subname = '',

View file

@ -70,12 +70,10 @@ class TransactionListItem extends ActionListItem with Keyable {
} }
String get formattedStatus { String get formattedStatus {
if (transaction.direction == TransactionDirection.incoming) { if (balanceViewModel.wallet.type == WalletType.monero ||
if (balanceViewModel.wallet.type == WalletType.monero || balanceViewModel.wallet.type == WalletType.wownero ||
balanceViewModel.wallet.type == WalletType.wownero || balanceViewModel.wallet.type == WalletType.haven) {
balanceViewModel.wallet.type == WalletType.haven) { return formattedPendingStatus;
return formattedPendingStatus;
}
} }
return transaction.isPending ? S.current.pending : ''; return transaction.isPending ? S.current.pending : '';
} }

View file

@ -7,6 +7,7 @@ import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/quantex_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/quantex_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart'; import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/exchange/trade.dart';
@ -52,6 +53,8 @@ abstract class ExchangeTradeViewModelBase with Store {
case ExchangeProviderDescription.quantex: case ExchangeProviderDescription.quantex:
_provider = QuantexExchangeProvider(); _provider = QuantexExchangeProvider();
break; break;
case ExchangeProviderDescription.stealthEx:
_provider = StealthExExchangeProvider();
case ExchangeProviderDescription.thorChain: case ExchangeProviderDescription.thorChain:
_provider = ThorChainExchangeProvider(tradesStore: trades); _provider = ThorChainExchangeProvider(tradesStore: trades);
break; break;

View file

@ -4,6 +4,7 @@ import 'dart:convert';
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cake_wallet/core/create_trade_result.dart'; import 'package:cake_wallet/core/create_trade_result.dart';
import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/sync_status.dart'; import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/transaction_priority.dart';
@ -160,15 +161,16 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
final SharedPreferences sharedPreferences; final SharedPreferences sharedPreferences;
List<ExchangeProvider> get _allProviders => [ List<ExchangeProvider> get _allProviders => [
ChangeNowExchangeProvider(settingsStore: _settingsStore), ChangeNowExchangeProvider(settingsStore: _settingsStore),
SideShiftExchangeProvider(), SideShiftExchangeProvider(),
SimpleSwapExchangeProvider(), SimpleSwapExchangeProvider(),
ThorChainExchangeProvider(tradesStore: trades), ThorChainExchangeProvider(tradesStore: trades),
if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(), if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
QuantexExchangeProvider(), QuantexExchangeProvider(),
TrocadorExchangeProvider( StealthExExchangeProvider(),
useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates), TrocadorExchangeProvider(
]; useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates),
];
@observable @observable
ExchangeProvider? provider; ExchangeProvider? provider;

View file

@ -62,7 +62,7 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
derivationInfo = options["derivationInfo"] as DerivationInfo?; derivationInfo = options["derivationInfo"] as DerivationInfo?;
passphrase = options["passphrase"] as String?; passphrase = options["passphrase"] as String?;
} }
derivationInfo ??= getDefaultDerivation(); derivationInfo ??= getDefaultCreateDerivation();
switch (restoreWallet.restoreMode) { switch (restoreWallet.restoreMode) {
case WalletRestoreMode.keys: case WalletRestoreMode.keys:

View file

@ -46,7 +46,7 @@ abstract class RestoreFromBackupViewModelBase with Store {
final data = await file.readAsBytes(); final data = await file.readAsBytes();
await backupService.importBackup(data, password); await backupService.importBackup(data, password);
await main(); await initializeAppAtRoot(reInitializing: true);
final store = getIt.get<AppStore>(); final store = getIt.get<AppStore>();
ReactionDisposer? reaction; ReactionDisposer? reaction;

View file

@ -7,6 +7,7 @@ import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/quantex_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/quantex_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart'; import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart'; import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/exchange/trade.dart';
@ -60,6 +61,9 @@ abstract class TradeDetailsViewModelBase with Store {
case ExchangeProviderDescription.quantex: case ExchangeProviderDescription.quantex:
_provider = QuantexExchangeProvider(); _provider = QuantexExchangeProvider();
break; break;
case ExchangeProviderDescription.stealthEx:
_provider = StealthExExchangeProvider();
break;
} }
_updateItems(); _updateItems();
@ -86,6 +90,8 @@ abstract class TradeDetailsViewModelBase with Store {
return 'https://track.ninerealms.com/${trade.id}'; return 'https://track.ninerealms.com/${trade.id}';
case ExchangeProviderDescription.quantex: case ExchangeProviderDescription.quantex:
return 'https://myquantex.com/send/${trade.id}'; return 'https://myquantex.com/send/${trade.id}';
case ExchangeProviderDescription.stealthEx:
return 'https://stealthex.io/exchange/?id=${trade.id}';
} }
return null; return null;
} }

View file

@ -97,7 +97,7 @@ abstract class WalletCreationVMBase with Store {
dirPath: dirPath, dirPath: dirPath,
address: '', address: '',
showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven, showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven,
derivationInfo: credentials.derivationInfo ?? getDefaultDerivation(), derivationInfo: credentials.derivationInfo ?? getDefaultCreateDerivation(),
hardwareWalletType: credentials.hardwareWalletType, hardwareWalletType: credentials.hardwareWalletType,
); );
@ -116,7 +116,7 @@ abstract class WalletCreationVMBase with Store {
} }
} }
DerivationInfo? getDefaultDerivation() { DerivationInfo? getDefaultCreateDerivation() {
final useBip39 = seedSettingsViewModel.bitcoinSeedType.type == DerivationType.bip39; final useBip39 = seedSettingsViewModel.bitcoinSeedType.type == DerivationType.bip39;
switch (type) { switch (type) {
case WalletType.nano: case WalletType.nano:
@ -147,10 +147,14 @@ abstract class WalletCreationVMBase with Store {
} }
DerivationInfo? getCommonRestoreDerivation() { DerivationInfo? getCommonRestoreDerivation() {
final useElectrum = seedSettingsViewModel.bitcoinSeedType.type == DerivationType.electrum;
switch (this.type) { switch (this.type) {
case WalletType.nano: case WalletType.nano:
return DerivationInfo(derivationType: DerivationType.nano); return DerivationInfo(derivationType: DerivationType.nano);
case WalletType.bitcoin: case WalletType.bitcoin:
if (useElectrum) {
return bitcoin!.getElectrumDerivations()[DerivationType.electrum]!.first;
}
return DerivationInfo( return DerivationInfo(
derivationType: DerivationType.bip39, derivationType: DerivationType.bip39,
derivationPath: "m/84'/0'/0'/0", derivationPath: "m/84'/0'/0'/0",
@ -158,6 +162,9 @@ abstract class WalletCreationVMBase with Store {
scriptType: "p2wpkh", scriptType: "p2wpkh",
); );
case WalletType.litecoin: case WalletType.litecoin:
if (useElectrum) {
return bitcoin!.getElectrumDerivations()[DerivationType.electrum]!.first;
}
return DerivationInfo( return DerivationInfo(
derivationType: DerivationType.bip39, derivationType: DerivationType.bip39,
derivationPath: "m/84'/2'/0'/0", derivationPath: "m/84'/2'/0'/0",

View file

@ -42,7 +42,8 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
type == WalletType.tron, type == WalletType.tron,
isButtonEnabled = false, isButtonEnabled = false,
mode = WalletRestoreMode.seed, mode = WalletRestoreMode.seed,
super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel, type: type, isRecovery: true) { super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel,
type: type, isRecovery: true) {
switch (type) { switch (type) {
case WalletType.monero: case WalletType.monero:
availableModes = WalletRestoreMode.values; availableModes = WalletRestoreMode.values;
@ -194,10 +195,11 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
case WalletType.nano: case WalletType.nano:
return nano!.createNanoRestoreWalletFromKeysCredentials( return nano!.createNanoRestoreWalletFromKeysCredentials(
name: name, name: name,
password: password, password: password,
seedKey: options['private_key'] as String, seedKey: options['private_key'] as String,
derivationType: options["derivationType"] as DerivationType); derivationType: derivationInfo!.derivationType!,
);
case WalletType.polygon: case WalletType.polygon:
return polygon!.createPolygonRestoreWalletFromPrivateKey( return polygon!.createPolygonRestoreWalletFromPrivateKey(
name: name, name: name,

View file

@ -8,7 +8,7 @@ if [[ ! -d "monero_c" ]];
then then
git clone https://github.com/mrcyjanek/monero_c --branch rewrite-wip git clone https://github.com/mrcyjanek/monero_c --branch rewrite-wip
cd monero_c cd monero_c
git checkout 5de323b1ba7387cf73973042f06383d4dbe619f5 git checkout 3cb38bee9385faf46b03fd73aab85f3ac4115bf7
git reset --hard git reset --hard
git submodule update --init --force --recursive git submodule update --init --force --recursive
./apply_patches.sh monero ./apply_patches.sh monero

View file

@ -43,6 +43,8 @@ class SecretKey {
SecretKey('cakePayApiKey', () => ''), SecretKey('cakePayApiKey', () => ''),
SecretKey('CSRFToken', () => ''), SecretKey('CSRFToken', () => ''),
SecretKey('authorization', () => ''), SecretKey('authorization', () => ''),
SecretKey('stealthExBearerToken', () => ''),
SecretKey('stealthExAdditionalFeePercent', () => ''),
]; ];
static final evmChainsSecrets = [ static final evmChainsSecrets = [