mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-03-12 09:32:33 +00:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into breez
This commit is contained in:
commit
a1ee4d88c1
65 changed files with 751 additions and 292 deletions
BIN
assets/images/thorchain.png
Normal file
BIN
assets/images/thorchain.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
|
@ -195,7 +195,8 @@ abstract class ElectrumWalletBase
|
|||
List<BitcoinOutput> outputs,
|
||||
int? feeRate,
|
||||
BitcoinTransactionPriority? priority,
|
||||
{int? inputsCount}) async {
|
||||
{int? inputsCount,
|
||||
String? memo}) async {
|
||||
final utxos = <UtxoWithAddress>[];
|
||||
List<ECPrivate> privateKeys = [];
|
||||
|
||||
|
@ -253,7 +254,11 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
final estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize(
|
||||
utxos: utxos, outputs: outputs, network: network);
|
||||
utxos: utxos,
|
||||
outputs: outputs,
|
||||
network: network,
|
||||
memo: memo,
|
||||
);
|
||||
|
||||
int fee = feeRate != null
|
||||
? feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize)
|
||||
|
@ -300,7 +305,13 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
}
|
||||
|
||||
return EstimatedTxResult(utxos: utxos, privateKeys: privateKeys, fee: fee, amount: amount);
|
||||
return EstimatedTxResult(
|
||||
utxos: utxos,
|
||||
privateKeys: privateKeys,
|
||||
fee: fee,
|
||||
amount: amount,
|
||||
memo: memo,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -348,13 +359,17 @@ abstract class ElectrumWalletBase
|
|||
outputs,
|
||||
transactionCredentials.feeRate,
|
||||
transactionCredentials.priority,
|
||||
memo: transactionCredentials.outputs.first.memo,
|
||||
);
|
||||
|
||||
final txb = BitcoinTransactionBuilder(
|
||||
utxos: estimatedTx.utxos,
|
||||
outputs: outputs,
|
||||
fee: BigInt.from(estimatedTx.fee),
|
||||
network: network);
|
||||
utxos: estimatedTx.utxos,
|
||||
outputs: outputs,
|
||||
fee: BigInt.from(estimatedTx.fee),
|
||||
network: network,
|
||||
memo: estimatedTx.memo,
|
||||
outputOrdering: BitcoinOrdering.none,
|
||||
);
|
||||
|
||||
final transaction = txb.buildTransaction((txDigest, utxo, publicKey, sighash) {
|
||||
final key = estimatedTx.privateKeys
|
||||
|
@ -888,13 +903,19 @@ class EstimateTxParams {
|
|||
}
|
||||
|
||||
class EstimatedTxResult {
|
||||
EstimatedTxResult(
|
||||
{required this.utxos, required this.privateKeys, required this.fee, required this.amount});
|
||||
EstimatedTxResult({
|
||||
required this.utxos,
|
||||
required this.privateKeys,
|
||||
required this.fee,
|
||||
required this.amount,
|
||||
this.memo,
|
||||
});
|
||||
|
||||
final List<UtxoWithAddress> utxos;
|
||||
final List<ECPrivate> privateKeys;
|
||||
final int fee;
|
||||
final int amount;
|
||||
final String? memo;
|
||||
}
|
||||
|
||||
BitcoinBaseAddress addressTypeFromStr(String address, BasedUtxoNetwork network) {
|
||||
|
|
|
@ -31,6 +31,9 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
@override
|
||||
String get feeFormatted => bitcoinAmountToString(amount: fee);
|
||||
|
||||
@override
|
||||
int? get outputCount => _tx.outputs.length;
|
||||
|
||||
final List<void Function(ElectrumTransactionInfo transaction)> _listeners;
|
||||
|
||||
@override
|
||||
|
|
|
@ -141,6 +141,8 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
|||
|
||||
var allInputsAmount = 0;
|
||||
|
||||
final String? opReturnMemo = outputs.first.memo;
|
||||
|
||||
if (unspentCoins.isEmpty) await updateUnspent();
|
||||
|
||||
for (final utx in unspentCoins) {
|
||||
|
@ -283,6 +285,8 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
|||
txb.addOutput(changeAddress, changeValue);
|
||||
}
|
||||
|
||||
if (opReturnMemo != null) txb.addOutputData(opReturnMemo);
|
||||
|
||||
for (var i = 0; i < inputs.length; i++) {
|
||||
final input = inputs[i];
|
||||
final keyPair = generateKeyPair(
|
||||
|
@ -291,7 +295,6 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
|||
txb.sign(i, keyPair, input.value);
|
||||
}
|
||||
|
||||
// Build the transaction
|
||||
final tx = txb.build();
|
||||
|
||||
return PendingBitcoinCashTransaction(tx, type,
|
||||
|
|
|
@ -7,7 +7,8 @@ class OutputInfo {
|
|||
this.formattedCryptoAmount,
|
||||
this.fiatAmount,
|
||||
this.note,
|
||||
this.extractedAddress,});
|
||||
this.extractedAddress,
|
||||
this.memo});
|
||||
|
||||
final String? fiatAmount;
|
||||
final String? cryptoAmount;
|
||||
|
@ -17,4 +18,5 @@ class OutputInfo {
|
|||
final bool sendAll;
|
||||
final bool isParsedAddress;
|
||||
final int? formattedCryptoAmount;
|
||||
final String? memo;
|
||||
}
|
|
@ -3,6 +3,7 @@ mixin PendingTransaction {
|
|||
String get amountFormatted;
|
||||
String get feeFormatted;
|
||||
String get hex;
|
||||
int? get outputCount => null;
|
||||
|
||||
Future<void> commit();
|
||||
}
|
|
@ -14,6 +14,7 @@ import 'package:flutter/services.dart';
|
|||
import 'package:http/http.dart';
|
||||
import 'package:erc20/erc20.dart';
|
||||
import 'package:web3dart/web3dart.dart';
|
||||
import 'package:hex/hex.dart' as hex;
|
||||
|
||||
abstract class EVMChainClient {
|
||||
final httpClient = Client();
|
||||
|
@ -85,6 +86,7 @@ abstract class EVMChainClient {
|
|||
required CryptoCurrency currency,
|
||||
required int exponent,
|
||||
String? contractAddress,
|
||||
String? data,
|
||||
}) async {
|
||||
assert(currency == CryptoCurrency.eth ||
|
||||
currency == CryptoCurrency.maticpoly ||
|
||||
|
@ -100,6 +102,7 @@ abstract class EVMChainClient {
|
|||
to: EthereumAddress.fromHex(toAddress),
|
||||
maxPriorityFeePerGas: EtherAmount.fromInt(EtherUnit.gwei, priority.tip),
|
||||
amount: isEVMCompatibleChain ? EtherAmount.inWei(BigInt.parse(amount)) : EtherAmount.zero(),
|
||||
data: data != null ? hexToBytes(data) : null,
|
||||
);
|
||||
|
||||
final signedTransaction =
|
||||
|
@ -140,12 +143,14 @@ abstract class EVMChainClient {
|
|||
required EthereumAddress to,
|
||||
required EtherAmount amount,
|
||||
EtherAmount? maxPriorityFeePerGas,
|
||||
Uint8List? data,
|
||||
}) {
|
||||
return Transaction(
|
||||
from: from,
|
||||
to: to,
|
||||
maxPriorityFeePerGas: maxPriorityFeePerGas,
|
||||
value: amount,
|
||||
data: data,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -222,6 +227,10 @@ abstract class EVMChainClient {
|
|||
}
|
||||
}
|
||||
|
||||
Uint8List hexToBytes(String hexString) {
|
||||
return Uint8List.fromList(hex.HEX.decode(hexString.startsWith('0x') ? hexString.substring(2) : hexString));
|
||||
}
|
||||
|
||||
void stop() {
|
||||
_client?.dispose();
|
||||
}
|
||||
|
|
|
@ -224,6 +224,13 @@ abstract class EVMChainWalletBase
|
|||
final outputs = _credentials.outputs;
|
||||
final hasMultiDestination = outputs.length > 1;
|
||||
|
||||
final String? opReturnMemo = outputs.first.memo;
|
||||
|
||||
String? hexOpReturnMemo;
|
||||
if (opReturnMemo != null) {
|
||||
hexOpReturnMemo = '0x${opReturnMemo.codeUnits.map((char) => char.toRadixString(16).padLeft(2, '0')).join()}';
|
||||
}
|
||||
|
||||
final CryptoCurrency transactionCurrency =
|
||||
balance.keys.firstWhere((element) => element.title == _credentials.currency.title);
|
||||
|
||||
|
@ -279,6 +286,7 @@ abstract class EVMChainWalletBase
|
|||
exponent: exponent,
|
||||
contractAddress:
|
||||
transactionCurrency is Erc20Token ? transactionCurrency.contractAddress : null,
|
||||
data: hexOpReturnMemo,
|
||||
);
|
||||
|
||||
return pendingEVMChainTransaction;
|
||||
|
|
|
@ -3,6 +3,7 @@ import 'dart:typed_data';
|
|||
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:web3dart/crypto.dart';
|
||||
import 'package:hex/hex.dart' as Hex;
|
||||
|
||||
class PendingEVMChainTransaction with PendingTransaction {
|
||||
final Function sendTransaction;
|
||||
|
@ -38,5 +39,12 @@ class PendingEVMChainTransaction with PendingTransaction {
|
|||
String get hex => bytesToHex(signedTransaction, include0x: true);
|
||||
|
||||
@override
|
||||
String get id => '';
|
||||
String get id {
|
||||
final String eip1559Hex = '0x02${hex.substring(2)}';
|
||||
final Uint8List bytes = Uint8List.fromList(Hex.HEX.decode(eip1559Hex.substring(2)));
|
||||
|
||||
var txid = keccak256(bytes);
|
||||
|
||||
return '0x${Hex.HEX.encode(txid)}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ class PolygonClient extends EVMChainClient {
|
|||
required EthereumAddress to,
|
||||
required EtherAmount amount,
|
||||
EtherAmount? maxPriorityFeePerGas,
|
||||
Uint8List? data,
|
||||
|
||||
}) {
|
||||
return Transaction(
|
||||
from: from,
|
||||
|
|
|
@ -85,7 +85,8 @@ class CWBitcoin extends Bitcoin {
|
|||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount))
|
||||
formattedCryptoAmount: out.formattedCryptoAmount,
|
||||
memo: out.memo))
|
||||
.toList(),
|
||||
priority: priority as BitcoinTransactionPriority,
|
||||
feeRate: feeRate);
|
||||
|
|
|
@ -22,20 +22,24 @@ import 'package:flutter/material.dart';
|
|||
import 'package:http/http.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class MoonPaySellProvider extends BuyProvider {
|
||||
MoonPaySellProvider({
|
||||
class MoonPayProvider extends BuyProvider {
|
||||
MoonPayProvider({
|
||||
required SettingsStore settingsStore,
|
||||
required WalletBase wallet,
|
||||
bool isTestEnvironment = false,
|
||||
}) : baseUrl = isTestEnvironment ? _baseTestUrl : _baseProductUrl,
|
||||
}) : baseSellUrl = isTestEnvironment ? _baseSellTestUrl : _baseSellProductUrl,
|
||||
baseBuyUrl = isTestEnvironment ? _baseBuyTestUrl : _baseBuyProductUrl,
|
||||
this._settingsStore = settingsStore,
|
||||
super(wallet: wallet, isTestEnvironment: isTestEnvironment);
|
||||
|
||||
final SettingsStore _settingsStore;
|
||||
|
||||
static const _baseTestUrl = 'sell-sandbox.moonpay.com';
|
||||
static const _baseProductUrl = 'sell.moonpay.com';
|
||||
static const _baseSellTestUrl = 'sell-sandbox.moonpay.com';
|
||||
static const _baseSellProductUrl = 'sell.moonpay.com';
|
||||
static const _baseBuyTestUrl = 'buy-staging.moonpay.com';
|
||||
static const _baseBuyProductUrl = 'buy.moonpay.com';
|
||||
static const _cIdBaseUrl = 'exchange-helper.cakewallet.com';
|
||||
static const _apiUrl = 'https://api.moonpay.com';
|
||||
|
||||
@override
|
||||
String get providerDescription =>
|
||||
|
@ -62,8 +66,14 @@ class MoonPaySellProvider extends BuyProvider {
|
|||
|
||||
static String get _apiKey => secrets.moonPayApiKey;
|
||||
|
||||
final String baseBuyUrl;
|
||||
final String baseSellUrl;
|
||||
|
||||
String get currencyCode => walletTypeToCryptoCurrency(wallet.type).title.toLowerCase();
|
||||
|
||||
String get trackUrl => baseBuyUrl + '/transaction_receipt?transactionId=';
|
||||
|
||||
static String get _exchangeHelperApiKey => secrets.exchangeHelperApiKey;
|
||||
final String baseUrl;
|
||||
|
||||
Future<String> getMoonpaySignature(String query) async {
|
||||
final uri = Uri.https(_cIdBaseUrl, "/api/moonpay");
|
||||
|
@ -85,147 +95,92 @@ class MoonPaySellProvider extends BuyProvider {
|
|||
}
|
||||
}
|
||||
|
||||
Future<Uri> requestMoonPayUrl({
|
||||
Future<Uri> requestSellMoonPayUrl({
|
||||
required CryptoCurrency currency,
|
||||
required String refundWalletAddress,
|
||||
required SettingsStore settingsStore,
|
||||
}) async {
|
||||
final customParams = {
|
||||
final params = {
|
||||
'theme': themeToMoonPayTheme(settingsStore.currentTheme),
|
||||
'language': settingsStore.languageCode,
|
||||
'colorCode': settingsStore.currentTheme.type == ThemeType.dark
|
||||
? '#${Palette.blueCraiola.value.toRadixString(16).substring(2, 8)}'
|
||||
: '#${Palette.moderateSlateBlue.value.toRadixString(16).substring(2, 8)}',
|
||||
'defaultCurrencyCode': _normalizeCurrency(currency),
|
||||
'refundWalletAddress': refundWalletAddress,
|
||||
};
|
||||
|
||||
final originalUri = Uri.https(
|
||||
baseUrl,
|
||||
'',
|
||||
<String, dynamic>{
|
||||
'apiKey': _apiKey,
|
||||
'defaultBaseCurrencyCode': _normalizeCurrency(currency),
|
||||
'refundWalletAddress': refundWalletAddress,
|
||||
}..addAll(customParams),
|
||||
);
|
||||
if (_apiKey.isNotEmpty) {
|
||||
params['apiKey'] = _apiKey;
|
||||
}
|
||||
|
||||
final signature = await getMoonpaySignature('?${originalUri.query}');
|
||||
final originalUri = Uri.https(
|
||||
baseSellUrl,
|
||||
'',
|
||||
params,
|
||||
);
|
||||
|
||||
if (isTestEnvironment) {
|
||||
return originalUri;
|
||||
}
|
||||
|
||||
final signature = await getMoonpaySignature('?${originalUri.query}');
|
||||
|
||||
final query = Map<String, dynamic>.from(originalUri.queryParameters);
|
||||
query['signature'] = signature;
|
||||
final signedUri = originalUri.replace(queryParameters: query);
|
||||
return signedUri;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
|
||||
try {
|
||||
final uri = await requestMoonPayUrl(
|
||||
currency: wallet.currency,
|
||||
refundWalletAddress: wallet.walletAddresses.address,
|
||||
settingsStore: _settingsStore,
|
||||
);
|
||||
|
||||
if (await canLaunchUrl(uri)) {
|
||||
if (DeviceInfo.instance.isMobile) {
|
||||
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['MoonPay', uri]);
|
||||
} else {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
} else {
|
||||
throw Exception('Could not launch URL');
|
||||
}
|
||||
} catch (e) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertWithOneAction(
|
||||
alertTitle: 'MoonPay',
|
||||
alertContent: 'The MoonPay service is currently unavailable: $e',
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _normalizeCurrency(CryptoCurrency currency) {
|
||||
if (currency == CryptoCurrency.maticpoly) {
|
||||
return "MATIC_POLYGON";
|
||||
}
|
||||
|
||||
return currency.toString().toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
class MoonPayBuyProvider extends BuyProvider {
|
||||
MoonPayBuyProvider({required WalletBase wallet, bool isTestEnvironment = false})
|
||||
: baseUrl = isTestEnvironment ? _baseTestUrl : _baseProductUrl,
|
||||
super(wallet: wallet, isTestEnvironment: isTestEnvironment);
|
||||
|
||||
static const _baseTestUrl = 'https://buy-staging.moonpay.com';
|
||||
static const _baseProductUrl = 'https://buy.moonpay.com';
|
||||
static const _apiUrl = 'https://api.moonpay.com';
|
||||
// BUY:
|
||||
static const _currenciesSuffix = '/v3/currencies';
|
||||
static const _quoteSuffix = '/buy_quote';
|
||||
static const _transactionsSuffix = '/v1/transactions';
|
||||
static const _ipAddressSuffix = '/v4/ip_address';
|
||||
static const _apiKey = secrets.moonPayApiKey;
|
||||
static const _secretKey = secrets.moonPaySecretKey;
|
||||
|
||||
@override
|
||||
String get title => 'MoonPay';
|
||||
Future<Uri> requestBuyMoonPayUrl({
|
||||
required CryptoCurrency currency,
|
||||
required SettingsStore settingsStore,
|
||||
required String walletAddress,
|
||||
String? amount,
|
||||
}) async {
|
||||
final params = {
|
||||
'theme': themeToMoonPayTheme(settingsStore.currentTheme),
|
||||
'language': settingsStore.languageCode,
|
||||
'colorCode': settingsStore.currentTheme.type == ThemeType.dark
|
||||
? '#${Palette.blueCraiola.value.toRadixString(16).substring(2, 8)}'
|
||||
: '#${Palette.moderateSlateBlue.value.toRadixString(16).substring(2, 8)}',
|
||||
'defaultCurrencyCode': _normalizeCurrency(currency),
|
||||
'baseCurrencyCode': _normalizeCurrency(currency),
|
||||
'baseCurrencyAmount': amount ?? '0',
|
||||
'currencyCode': currencyCode,
|
||||
'walletAddress': walletAddress,
|
||||
'lockAmount': 'true',
|
||||
'showAllCurrencies': 'false',
|
||||
'showWalletAddressForm': 'false',
|
||||
'enabledPaymentMethods':
|
||||
'credit_debit_card,apple_pay,google_pay,samsung_pay,sepa_bank_transfer,gbp_bank_transfer,gbp_open_banking_payment',
|
||||
};
|
||||
|
||||
@override
|
||||
String get providerDescription =>
|
||||
'MoonPay offers a fast and simple way to buy and sell cryptocurrencies';
|
||||
if (_apiKey.isNotEmpty) {
|
||||
params['apiKey'] = _apiKey;
|
||||
}
|
||||
|
||||
@override
|
||||
String get lightIcon => 'assets/images/moonpay_light.png';
|
||||
final originalUri = Uri.https(
|
||||
baseBuyUrl,
|
||||
'',
|
||||
params,
|
||||
);
|
||||
|
||||
@override
|
||||
String get darkIcon => 'assets/images/moonpay_dark.png';
|
||||
if (isTestEnvironment) {
|
||||
return originalUri;
|
||||
}
|
||||
|
||||
String get currencyCode => walletTypeToCryptoCurrency(wallet.type).title.toLowerCase();
|
||||
|
||||
String get trackUrl => baseUrl + '/transaction_receipt?transactionId=';
|
||||
|
||||
String baseUrl;
|
||||
|
||||
Future<String> requestUrl(String amount, String sourceCurrency) async {
|
||||
final enabledPaymentMethods = 'credit_debit_card%2Capple_pay%2Cgoogle_pay%2Csamsung_pay'
|
||||
'%2Csepa_bank_transfer%2Cgbp_bank_transfer%2Cgbp_open_banking_payment';
|
||||
|
||||
final suffix = '?apiKey=' +
|
||||
_apiKey +
|
||||
'¤cyCode=' +
|
||||
currencyCode +
|
||||
'&enabledPaymentMethods=' +
|
||||
enabledPaymentMethods +
|
||||
'&walletAddress=' +
|
||||
wallet.walletAddresses.address +
|
||||
'&baseCurrencyCode=' +
|
||||
sourceCurrency.toLowerCase() +
|
||||
'&baseCurrencyAmount=' +
|
||||
amount +
|
||||
'&lockAmount=true' +
|
||||
'&showAllCurrencies=false' +
|
||||
'&showWalletAddressForm=false';
|
||||
|
||||
final originalUrl = baseUrl + suffix;
|
||||
|
||||
final messageBytes = utf8.encode(suffix);
|
||||
final key = utf8.encode(_secretKey);
|
||||
final hmac = Hmac(sha256, key);
|
||||
final digest = hmac.convert(messageBytes);
|
||||
final signature = base64.encode(digest.bytes);
|
||||
final urlWithSignature = originalUrl + '&signature=${Uri.encodeComponent(signature)}';
|
||||
|
||||
return isTestEnvironment ? originalUrl : urlWithSignature;
|
||||
final signature = await getMoonpaySignature('?${originalUri.query}');
|
||||
final query = Map<String, dynamic>.from(originalUri.queryParameters);
|
||||
query['signature'] = signature;
|
||||
final signedUri = originalUri.replace(queryParameters: query);
|
||||
return signedUri;
|
||||
}
|
||||
|
||||
Future<BuyAmount> calculateAmount(String amount, String sourceCurrency) async {
|
||||
|
@ -300,6 +255,52 @@ class MoonPayBuyProvider extends BuyProvider {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> launchProvider(BuildContext context, bool? isBuyAction) =>
|
||||
throw UnimplementedError();
|
||||
Future<void> launchProvider(BuildContext context, bool? isBuyAction) async {
|
||||
// try {
|
||||
late final Uri uri;
|
||||
if (isBuyAction ?? true) {
|
||||
uri = await requestBuyMoonPayUrl(
|
||||
currency: wallet.currency,
|
||||
walletAddress: wallet.walletAddresses.address,
|
||||
settingsStore: _settingsStore,
|
||||
);
|
||||
} else {
|
||||
uri = await requestSellMoonPayUrl(
|
||||
currency: wallet.currency,
|
||||
refundWalletAddress: wallet.walletAddresses.address,
|
||||
settingsStore: _settingsStore,
|
||||
);
|
||||
}
|
||||
|
||||
if (await canLaunchUrl(uri)) {
|
||||
if (DeviceInfo.instance.isMobile) {
|
||||
Navigator.of(context).pushNamed(Routes.webViewPage, arguments: ['MoonPay', uri]);
|
||||
} else {
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
}
|
||||
} else {
|
||||
throw Exception('Could not launch URL');
|
||||
}
|
||||
// } catch (e) {
|
||||
// await showDialog<void>(
|
||||
// context: context,
|
||||
// builder: (BuildContext context) {
|
||||
// return AlertWithOneAction(
|
||||
// alertTitle: 'MoonPay',
|
||||
// alertContent: 'The MoonPay service is currently unavailable: $e',
|
||||
// buttonText: S.of(context).ok,
|
||||
// buttonAction: () => Navigator.of(context).pop(),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
||||
String _normalizeCurrency(CryptoCurrency currency) {
|
||||
if (currency == CryptoCurrency.maticpoly) {
|
||||
return "MATIC_POLYGON";
|
||||
}
|
||||
|
||||
return currency.toString().toLowerCase();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/core/secure_storage.dart';
|
||||
import 'package:cake_wallet/core/totp_request_details.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||
|
@ -64,7 +66,7 @@ class AuthService with Store {
|
|||
|
||||
Future<bool> authenticate(String pin) async {
|
||||
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
|
||||
final encodedPin = await secureStorage.read(key: key);
|
||||
final encodedPin = await readSecureStorage(secureStorage, key);
|
||||
final decodedPin = decodedPinCode(pin: encodedPin!);
|
||||
|
||||
return decodedPin == pin;
|
||||
|
@ -76,7 +78,8 @@ class AuthService with Store {
|
|||
}
|
||||
|
||||
Future<bool> requireAuth() async {
|
||||
final timestamp = int.tryParse(await secureStorage.read(key: SecureKey.lastAuthTimeMilliseconds) ?? '0');
|
||||
final timestamp =
|
||||
int.tryParse(await secureStorage.read(key: SecureKey.lastAuthTimeMilliseconds) ?? '0');
|
||||
final duration = _durationToRequireAuth(timestamp ?? 0);
|
||||
final requiredPinInterval = settingsStore.pinTimeOutDuration;
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:cake_wallet/core/secure_storage.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:cake_wallet/entities/secret_store_key.dart';
|
||||
import 'package:cake_wallet/entities/encrypt.dart';
|
||||
|
@ -10,7 +11,7 @@ class KeyService {
|
|||
Future<String> getWalletPassword({required String walletName}) async {
|
||||
final key = generateStoreKeyFor(
|
||||
key: SecretStoreKey.moneroWalletPassword, walletName: walletName);
|
||||
final encodedPassword = await _secureStorage.read(key: key);
|
||||
final encodedPassword = await readSecureStorage(_secureStorage, key);
|
||||
return decodeWalletPassword(password: encodedPassword!);
|
||||
}
|
||||
|
||||
|
|
27
lib/core/secure_storage.dart
Normal file
27
lib/core/secure_storage.dart
Normal file
|
@ -0,0 +1,27 @@
|
|||
import 'dart:async';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
// For now, we can create a utility function to handle this.
|
||||
//
|
||||
// However, we could look into abstracting the entire FlutterSecureStorage package
|
||||
// so the app doesn't depend on the package directly but an absraction.
|
||||
// It'll make these kind of modifications to read/write come from a single point.
|
||||
|
||||
Future<String?> readSecureStorage(FlutterSecureStorage secureStorage, String key) async {
|
||||
String? result;
|
||||
const maxWait = Duration(seconds: 3);
|
||||
const checkInterval = Duration(milliseconds: 200);
|
||||
|
||||
DateTime start = DateTime.now();
|
||||
|
||||
while (result == null && DateTime.now().difference(start) < maxWait) {
|
||||
result = await secureStorage.read(key: key);
|
||||
|
||||
if (result != null) {
|
||||
break;
|
||||
}
|
||||
|
||||
await Future.delayed(checkInterval);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
|
@ -208,6 +208,7 @@ import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
|
|||
import 'package:cake_wallet/view_model/wallet_restore_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
@ -817,8 +818,11 @@ Future<void> setup({
|
|||
getIt
|
||||
.registerFactory<DFXBuyProvider>(() => DFXBuyProvider(wallet: getIt.get<AppStore>().wallet!));
|
||||
|
||||
getIt.registerFactory<MoonPaySellProvider>(() => MoonPaySellProvider(
|
||||
settingsStore: getIt.get<AppStore>().settingsStore, wallet: getIt.get<AppStore>().wallet!));
|
||||
getIt.registerFactory<MoonPayProvider>(() => MoonPayProvider(
|
||||
settingsStore: getIt.get<AppStore>().settingsStore,
|
||||
wallet: getIt.get<AppStore>().wallet!,
|
||||
isTestEnvironment: kDebugMode,
|
||||
));
|
||||
|
||||
getIt.registerFactory<OnRamperBuyProvider>(() => OnRamperBuyProvider(
|
||||
getIt.get<AppStore>().settingsStore,
|
||||
|
|
|
@ -11,7 +11,7 @@ enum ProviderType {
|
|||
robinhood,
|
||||
dfx,
|
||||
onramper,
|
||||
moonpaySell,
|
||||
moonpay,
|
||||
}
|
||||
|
||||
extension ProviderTypeName on ProviderType {
|
||||
|
@ -25,7 +25,7 @@ extension ProviderTypeName on ProviderType {
|
|||
return 'DFX Connect';
|
||||
case ProviderType.onramper:
|
||||
return 'Onramper';
|
||||
case ProviderType.moonpaySell:
|
||||
case ProviderType.moonpay:
|
||||
return 'MoonPay';
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ extension ProviderTypeName on ProviderType {
|
|||
return 'dfx_connect_provider';
|
||||
case ProviderType.onramper:
|
||||
return 'onramper_provider';
|
||||
case ProviderType.moonpaySell:
|
||||
case ProviderType.moonpay:
|
||||
return 'moonpay_provider';
|
||||
}
|
||||
}
|
||||
|
@ -62,10 +62,11 @@ class ProvidersHelper {
|
|||
ProviderType.onramper,
|
||||
ProviderType.dfx,
|
||||
ProviderType.robinhood,
|
||||
ProviderType.moonpay,
|
||||
];
|
||||
case WalletType.litecoin:
|
||||
case WalletType.bitcoinCash:
|
||||
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood];
|
||||
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood, ProviderType.moonpay];
|
||||
case WalletType.solana:
|
||||
return [ProviderType.askEachTime, ProviderType.onramper, ProviderType.robinhood];
|
||||
case WalletType.none:
|
||||
|
@ -83,18 +84,18 @@ class ProvidersHelper {
|
|||
return [
|
||||
ProviderType.askEachTime,
|
||||
ProviderType.onramper,
|
||||
ProviderType.moonpaySell,
|
||||
ProviderType.moonpay,
|
||||
ProviderType.dfx,
|
||||
];
|
||||
case WalletType.litecoin:
|
||||
case WalletType.bitcoinCash:
|
||||
return [ProviderType.askEachTime, ProviderType.moonpaySell];
|
||||
return [ProviderType.askEachTime, ProviderType.moonpay];
|
||||
case WalletType.solana:
|
||||
return [
|
||||
ProviderType.askEachTime,
|
||||
ProviderType.onramper,
|
||||
ProviderType.robinhood,
|
||||
ProviderType.moonpaySell,
|
||||
ProviderType.moonpay,
|
||||
];
|
||||
case WalletType.monero:
|
||||
case WalletType.nano:
|
||||
|
@ -114,10 +115,10 @@ class ProvidersHelper {
|
|||
return getIt.get<DFXBuyProvider>();
|
||||
case ProviderType.onramper:
|
||||
return getIt.get<OnRamperBuyProvider>();
|
||||
case ProviderType.moonpay:
|
||||
return getIt.get<MoonPayProvider>();
|
||||
case ProviderType.askEachTime:
|
||||
return null;
|
||||
case ProviderType.moonpaySell:
|
||||
return getIt.get<MoonPaySellProvider>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,8 @@ class CWEthereum extends Ethereum {
|
|||
sendAll: out.sendAll,
|
||||
extractedAddress: out.extractedAddress,
|
||||
isParsedAddress: out.isParsedAddress,
|
||||
formattedCryptoAmount: out.formattedCryptoAmount))
|
||||
formattedCryptoAmount: out.formattedCryptoAmount,
|
||||
memo: out.memo))
|
||||
.toList(),
|
||||
priority: priority as EVMChainTransactionPriority,
|
||||
currency: currency,
|
||||
|
|
|
@ -22,6 +22,8 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
|
|||
ExchangeProviderDescription(title: 'Trocador', raw: 5, image: 'assets/images/trocador.png');
|
||||
static const exolix =
|
||||
ExchangeProviderDescription(title: 'Exolix', raw: 6, image: 'assets/images/exolix.png');
|
||||
static const thorChain =
|
||||
ExchangeProviderDescription(title: 'ThorChain' , raw: 8, image: 'assets/images/thorchain.png');
|
||||
|
||||
static const all = ExchangeProviderDescription(title: 'All trades', raw: 7, image: '');
|
||||
|
||||
|
@ -41,6 +43,8 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
|
|||
return trocador;
|
||||
case 6:
|
||||
return exolix;
|
||||
case 8:
|
||||
return thorChain;
|
||||
case 7:
|
||||
return all;
|
||||
default:
|
||||
|
|
248
lib/exchange/provider/thorchain_exchange.provider.dart
Normal file
248
lib/exchange/provider/thorchain_exchange.provider.dart
Normal file
|
@ -0,0 +1,248 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||
import 'package:cake_wallet/exchange/limits.dart';
|
||||
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/trade.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:hive/hive.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class ThorChainExchangeProvider extends ExchangeProvider {
|
||||
ThorChainExchangeProvider({required this.tradesStore})
|
||||
: super(pairList: supportedPairs(_notSupported));
|
||||
|
||||
static final List<CryptoCurrency> _notSupported = [
|
||||
...(CryptoCurrency.all
|
||||
.where((element) => ![
|
||||
CryptoCurrency.btc,
|
||||
CryptoCurrency.eth,
|
||||
CryptoCurrency.ltc,
|
||||
CryptoCurrency.bch,
|
||||
CryptoCurrency.aave,
|
||||
CryptoCurrency.dai,
|
||||
CryptoCurrency.gusd,
|
||||
CryptoCurrency.usdc,
|
||||
CryptoCurrency.usdterc20,
|
||||
CryptoCurrency.wbtc,
|
||||
].contains(element))
|
||||
.toList())
|
||||
];
|
||||
|
||||
static final isRefundAddressSupported = [CryptoCurrency.eth];
|
||||
|
||||
static const _baseURL = 'thornode.ninerealms.com';
|
||||
static const _quotePath = '/thorchain/quote/swap';
|
||||
static const _txInfoPath = '/thorchain/tx/status/';
|
||||
static const _affiliateName = 'cakewallet';
|
||||
static const _affiliateBps = '175';
|
||||
|
||||
final Box<Trade> tradesStore;
|
||||
|
||||
@override
|
||||
String get title => 'THORChain';
|
||||
|
||||
@override
|
||||
bool get isAvailable => true;
|
||||
|
||||
@override
|
||||
bool get isEnabled => true;
|
||||
|
||||
@override
|
||||
bool get supportsFixedRate => false;
|
||||
|
||||
@override
|
||||
ExchangeProviderDescription get description => ExchangeProviderDescription.thorChain;
|
||||
|
||||
@override
|
||||
Future<bool> checkIsAvailable() async => true;
|
||||
|
||||
@override
|
||||
Future<double> fetchRate(
|
||||
{required CryptoCurrency from,
|
||||
required CryptoCurrency to,
|
||||
required double amount,
|
||||
required bool isFixedRateMode,
|
||||
required bool isReceiveAmount}) async {
|
||||
try {
|
||||
if (amount == 0) return 0.0;
|
||||
|
||||
final params = {
|
||||
'from_asset': _normalizeCurrency(from),
|
||||
'to_asset': _normalizeCurrency(to),
|
||||
'amount': _doubleToThorChainString(amount),
|
||||
'affiliate': _affiliateName,
|
||||
'affiliate_bps': _affiliateBps
|
||||
};
|
||||
|
||||
final responseJSON = await _getSwapQuote(params);
|
||||
|
||||
final expectedAmountOut = responseJSON['expected_amount_out'] as String? ?? '0.0';
|
||||
|
||||
return _thorChainAmountToDouble(expectedAmountOut) / amount;
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Limits> fetchLimits(
|
||||
{required CryptoCurrency from,
|
||||
required CryptoCurrency to,
|
||||
required bool isFixedRateMode}) async {
|
||||
final params = {
|
||||
'from_asset': _normalizeCurrency(from),
|
||||
'to_asset': _normalizeCurrency(to),
|
||||
'amount': _doubleToThorChainString(1),
|
||||
'affiliate': _affiliateName,
|
||||
'affiliate_bps': _affiliateBps
|
||||
};
|
||||
|
||||
final responseJSON = await _getSwapQuote(params);
|
||||
final minAmountIn = responseJSON['recommended_min_amount_in'] as String? ?? '0.0';
|
||||
|
||||
return Limits(min: _thorChainAmountToDouble(minAmountIn));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Trade> createTrade({required TradeRequest request, required bool isFixedRateMode}) async {
|
||||
String formattedToAddress = request.toAddress.startsWith('bitcoincash:')
|
||||
? request.toAddress.replaceFirst('bitcoincash:', '')
|
||||
: request.toAddress;
|
||||
|
||||
final formattedFromAmount = double.parse(request.fromAmount);
|
||||
|
||||
final params = {
|
||||
'from_asset': _normalizeCurrency(request.fromCurrency),
|
||||
'to_asset': _normalizeCurrency(request.toCurrency),
|
||||
'amount': _doubleToThorChainString(formattedFromAmount),
|
||||
'destination': formattedToAddress,
|
||||
'affiliate': _affiliateName,
|
||||
'affiliate_bps': _affiliateBps,
|
||||
'refund_address':
|
||||
isRefundAddressSupported.contains(request.fromCurrency) ? request.refundAddress : '',
|
||||
};
|
||||
|
||||
final responseJSON = await _getSwapQuote(params);
|
||||
|
||||
final inputAddress = responseJSON['inbound_address'] as String?;
|
||||
final memo = responseJSON['memo'] as String?;
|
||||
|
||||
return Trade(
|
||||
id: '',
|
||||
from: request.fromCurrency,
|
||||
to: request.toCurrency,
|
||||
provider: description,
|
||||
inputAddress: inputAddress,
|
||||
createdAt: DateTime.now(),
|
||||
amount: request.fromAmount,
|
||||
state: TradeState.notFound,
|
||||
payoutAddress: request.toAddress,
|
||||
memo: memo);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Trade> findTradeById({required String id}) async {
|
||||
if (id.isEmpty) throw Exception('Trade id is empty');
|
||||
final formattedId = id.startsWith('0x') ? id.substring(2) : id;
|
||||
final uri = Uri.https(_baseURL, '$_txInfoPath$formattedId');
|
||||
final response = await http.get(uri);
|
||||
|
||||
if (response.statusCode == 404) {
|
||||
throw Exception('Trade not found for id: $formattedId');
|
||||
} else if (response.statusCode != 200) {
|
||||
throw Exception('Unexpected HTTP status: ${response.statusCode}');
|
||||
}
|
||||
|
||||
final responseJSON = json.decode(response.body);
|
||||
final Map<String, dynamic> stagesJson = responseJSON['stages'] as Map<String, dynamic>;
|
||||
|
||||
final inboundObservedStarted = stagesJson['inbound_observed']?['started'] as bool? ?? true;
|
||||
if (!inboundObservedStarted) {
|
||||
throw Exception('Trade has not started for id: $formattedId');
|
||||
}
|
||||
|
||||
final currentState = _updateStateBasedOnStages(stagesJson) ?? TradeState.notFound;
|
||||
|
||||
final tx = responseJSON['tx'];
|
||||
final String fromAddress = tx['from_address'] as String? ?? '';
|
||||
final String toAddress = tx['to_address'] as String? ?? '';
|
||||
final List<dynamic> coins = tx['coins'] as List<dynamic>;
|
||||
final String? memo = tx['memo'] as String?;
|
||||
|
||||
final parts = memo?.split(':') ?? [];
|
||||
|
||||
final String toChain = parts.length > 1 ? parts[1].split('.')[0] : '';
|
||||
final String toAsset = parts.length > 1 && parts[1].split('.').length > 1 ? parts[1].split('.')[1].split('-')[0] : '';
|
||||
|
||||
final formattedToChain = CryptoCurrency.fromString(toChain);
|
||||
final toAssetWithChain = CryptoCurrency.fromString(toAsset, walletCurrency:formattedToChain);
|
||||
|
||||
final plannedOutTxs = responseJSON['planned_out_txs'] as List<dynamic>?;
|
||||
final isRefund = plannedOutTxs?.any((tx) => tx['refund'] == true) ?? false;
|
||||
|
||||
return Trade(
|
||||
id: id,
|
||||
from: CryptoCurrency.fromString(tx['chain'] as String? ?? ''),
|
||||
to: toAssetWithChain,
|
||||
provider: description,
|
||||
inputAddress: fromAddress,
|
||||
payoutAddress: toAddress,
|
||||
amount: coins.first['amount'] as String? ?? '0.0',
|
||||
state: currentState,
|
||||
memo: memo,
|
||||
isRefund: isRefund,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> _getSwapQuote(Map<String, String> params) async {
|
||||
Uri uri = Uri.https(_baseURL, _quotePath, params);
|
||||
|
||||
final response = await http.get(uri);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('Unexpected HTTP status: ${response.statusCode}');
|
||||
}
|
||||
|
||||
if (response.body.contains('error')) {
|
||||
throw Exception('Unexpected response: ${response.body}');
|
||||
}
|
||||
|
||||
return json.decode(response.body) as Map<String, dynamic>;
|
||||
}
|
||||
|
||||
String _normalizeCurrency(CryptoCurrency currency) {
|
||||
final networkTitle = currency.tag == 'ETH' ? 'ETH' : currency.title;
|
||||
return '$networkTitle.${currency.title}';
|
||||
}
|
||||
|
||||
String _doubleToThorChainString(double amount) => (amount * 1e8).toInt().toString();
|
||||
|
||||
double _thorChainAmountToDouble(String amount) => double.parse(amount) / 1e8;
|
||||
|
||||
TradeState? _updateStateBasedOnStages(Map<String, dynamic> stages) {
|
||||
TradeState? currentState;
|
||||
|
||||
if (stages['inbound_observed']['completed'] as bool? ?? false) {
|
||||
currentState = TradeState.confirmation;
|
||||
}
|
||||
if (stages['inbound_confirmation_counted']['completed'] as bool? ?? false) {
|
||||
currentState = TradeState.confirmed;
|
||||
}
|
||||
if (stages['inbound_finalised']['completed'] as bool? ?? false) {
|
||||
currentState = TradeState.processing;
|
||||
}
|
||||
if (stages['swap_finalised']['completed'] as bool? ?? false) {
|
||||
currentState = TradeState.traded;
|
||||
}
|
||||
if (stages['outbound_signed']['completed'] as bool? ?? false) {
|
||||
currentState = TradeState.success;
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
}
|
|
@ -27,7 +27,10 @@ class Trade extends HiveObject {
|
|||
this.password,
|
||||
this.providerId,
|
||||
this.providerName,
|
||||
this.fromWalletAddress
|
||||
this.fromWalletAddress,
|
||||
this.memo,
|
||||
this.txId,
|
||||
this.isRefund,
|
||||
}) {
|
||||
if (provider != null) providerRaw = provider.raw;
|
||||
|
||||
|
@ -105,6 +108,15 @@ class Trade extends HiveObject {
|
|||
@HiveField(17)
|
||||
String? fromWalletAddress;
|
||||
|
||||
@HiveField(18)
|
||||
String? memo;
|
||||
|
||||
@HiveField(19)
|
||||
String? txId;
|
||||
|
||||
@HiveField(20)
|
||||
bool? isRefund;
|
||||
|
||||
static Trade fromMap(Map<String, Object?> map) {
|
||||
return Trade(
|
||||
id: map['id'] as String,
|
||||
|
@ -115,7 +127,10 @@ class Trade extends HiveObject {
|
|||
map['date'] != null ? DateTime.fromMillisecondsSinceEpoch(map['date'] as int) : null,
|
||||
amount: map['amount'] as String,
|
||||
walletId: map['wallet_id'] as String,
|
||||
fromWalletAddress: map['from_wallet_address'] as String?
|
||||
fromWalletAddress: map['from_wallet_address'] as String?,
|
||||
memo: map['memo'] as String?,
|
||||
txId: map['tx_id'] as String?,
|
||||
isRefund: map['isRefund'] as bool?
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -128,7 +143,10 @@ class Trade extends HiveObject {
|
|||
'date': createdAt != null ? createdAt!.millisecondsSinceEpoch : null,
|
||||
'amount': amount,
|
||||
'wallet_id': walletId,
|
||||
'from_wallet_address': fromWalletAddress
|
||||
'from_wallet_address': fromWalletAddress,
|
||||
'memo': memo,
|
||||
'tx_id': txId,
|
||||
'isRefund': isRefund
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,8 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
|
|||
static const success = TradeState(raw: 'success', title: 'Success');
|
||||
static TradeState deserialize({required String raw}) {
|
||||
switch (raw) {
|
||||
case 'NOT_FOUND':
|
||||
return notFound;
|
||||
case 'pending':
|
||||
return pending;
|
||||
case 'confirming':
|
||||
|
@ -98,6 +100,7 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
|
|||
case 'sending':
|
||||
return sending;
|
||||
case 'success':
|
||||
case 'done':
|
||||
return success;
|
||||
default:
|
||||
throw Exception('Unexpected token: $raw in TradeState deserialize');
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/option_tile.dart';
|
||||
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/themes/extensions/option_tile_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
|
@ -25,45 +26,46 @@ class BuySellOptionsPage extends BasePage {
|
|||
? dashboardViewModel.availableBuyProviders
|
||||
: dashboardViewModel.availableSellProviders;
|
||||
|
||||
return Container(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 330),
|
||||
child: Column(
|
||||
children: [
|
||||
...availableProviders.map((provider) {
|
||||
final icon = Image.asset(
|
||||
isLightMode ? provider.lightIcon : provider.darkIcon,
|
||||
height: 40,
|
||||
width: 40,
|
||||
);
|
||||
return ScrollableWithBottomSection(
|
||||
content: Container(
|
||||
child: Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 330),
|
||||
child: Column(
|
||||
children: [
|
||||
...availableProviders.map((provider) {
|
||||
final icon = Image.asset(
|
||||
isLightMode ? provider.lightIcon : provider.darkIcon,
|
||||
height: 40,
|
||||
width: 40,
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: OptionTile(
|
||||
image: icon,
|
||||
title: provider.toString(),
|
||||
description: provider.providerDescription,
|
||||
onPressed: () => provider.launchProvider(context, isBuyAction),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
Spacer(),
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
|
||||
child: Text(
|
||||
isBuyAction
|
||||
? S.of(context).select_buy_provider_notice
|
||||
: S.of(context).select_sell_provider_notice,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
child: OptionTile(
|
||||
image: icon,
|
||||
title: provider.toString(),
|
||||
description: provider.providerDescription,
|
||||
onPressed: () => provider.launchProvider(context, isBuyAction),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
bottomSection: Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
|
||||
child: Text(
|
||||
isBuyAction
|
||||
? S.of(context).select_buy_provider_notice
|
||||
: S.of(context).select_sell_provider_notice,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -60,7 +60,7 @@ class BuyWebViewPageBodyState extends State<BuyWebViewPageBody> {
|
|||
_saveOrder(keyword: 'completed', splitSymbol: '/');
|
||||
}
|
||||
|
||||
if (widget.buyViewModel.selectedProvider is MoonPayBuyProvider) {
|
||||
if (widget.buyViewModel.selectedProvider is MoonPayProvider) {
|
||||
_saveOrder(keyword: 'transactionId', splitSymbol: '=');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ class FilterTile extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 24.0),
|
||||
padding: EdgeInsets.symmetric(vertical: 6.0, horizontal: 24.0),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ class SyncIndicatorIcon extends StatelessWidget {
|
|||
static const String created = 'created';
|
||||
static const String fetching = 'fetching';
|
||||
static const String finished = 'finished';
|
||||
static const String success = 'success';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -45,6 +46,7 @@ class SyncIndicatorIcon extends StatelessWidget {
|
|||
indicatorColor = Colors.red;
|
||||
break;
|
||||
case finished:
|
||||
case success:
|
||||
indicatorColor = PaletteDark.brightGreen;
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -34,7 +34,9 @@ class TradeRow extends StatelessWidget {
|
|||
mainAxisSize: MainAxisSize.max,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
_getPoweredImage(provider)!,
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
child: Image.asset(provider.image, width: 36, height: 36)),
|
||||
SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
|
@ -69,38 +71,4 @@ class TradeRow extends StatelessWidget {
|
|||
),
|
||||
));
|
||||
}
|
||||
|
||||
Widget? _getPoweredImage(ExchangeProviderDescription provider) {
|
||||
Widget? image;
|
||||
|
||||
switch (provider) {
|
||||
case ExchangeProviderDescription.xmrto:
|
||||
image = Image.asset('assets/images/xmrto.png', height: 36, width: 36);
|
||||
break;
|
||||
case ExchangeProviderDescription.changeNow:
|
||||
image = Image.asset('assets/images/changenow.png', height: 36, width: 36);
|
||||
break;
|
||||
case ExchangeProviderDescription.morphToken:
|
||||
image = Image.asset('assets/images/morph.png', height: 36, width: 36);
|
||||
break;
|
||||
case ExchangeProviderDescription.sideShift:
|
||||
image = Image.asset('assets/images/sideshift.png', width: 36, height: 36);
|
||||
break;
|
||||
case ExchangeProviderDescription.simpleSwap:
|
||||
image = Image.asset('assets/images/simpleSwap.png', width: 36, height: 36);
|
||||
break;
|
||||
case ExchangeProviderDescription.trocador:
|
||||
image = ClipRRect(
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
child: Image.asset('assets/images/trocador.png', width: 36, height: 36));
|
||||
break;
|
||||
case ExchangeProviderDescription.exolix:
|
||||
image = Image.asset('assets/images/exolix.png', width: 36, height: 36);
|
||||
break;
|
||||
default:
|
||||
image = null;
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
|
||||
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
|
||||
import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
|
@ -60,7 +62,7 @@ class ExchangePage extends BasePage {
|
|||
final _receiveAmountFocus = FocusNode();
|
||||
final _receiveAddressFocus = FocusNode();
|
||||
final _receiveAmountDebounce = Debounce(Duration(milliseconds: 500));
|
||||
final _depositAmountDebounce = Debounce(Duration(milliseconds: 500));
|
||||
Debounce _depositAmountDebounce = Debounce(Duration(milliseconds: 500));
|
||||
var _isReactionsSet = false;
|
||||
|
||||
final arrowBottomPurple = Image.asset(
|
||||
|
@ -431,7 +433,9 @@ class ExchangePage extends BasePage {
|
|||
}
|
||||
if (state is TradeIsCreatedSuccessfully) {
|
||||
exchangeViewModel.reset();
|
||||
Navigator.of(context).pushNamed(Routes.exchangeConfirm);
|
||||
(exchangeViewModel.tradesStore.trade?.provider == ExchangeProviderDescription.thorChain)
|
||||
? Navigator.of(context).pushReplacementNamed(Routes.exchangeTrade)
|
||||
: Navigator.of(context).pushReplacementNamed(Routes.exchangeConfirm);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -470,6 +474,13 @@ class ExchangePage extends BasePage {
|
|||
if (depositAmountController.text != exchangeViewModel.depositAmount &&
|
||||
depositAmountController.text != S.of(context).all) {
|
||||
exchangeViewModel.isSendAllEnabled = false;
|
||||
final isThorChain = exchangeViewModel.selectedProviders
|
||||
.any((provider) => provider is ThorChainExchangeProvider);
|
||||
|
||||
_depositAmountDebounce = isThorChain
|
||||
? Debounce(Duration(milliseconds: 1000))
|
||||
: Debounce(Duration(milliseconds: 500));
|
||||
|
||||
_depositAmountDebounce.run(() {
|
||||
exchangeViewModel.changeDepositAmount(amount: depositAmountController.text);
|
||||
exchangeViewModel.isReceiveAmountEntered = false;
|
||||
|
|
|
@ -3,18 +3,20 @@ import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
|
|||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part'trade_filter_store.g.dart';
|
||||
part 'trade_filter_store.g.dart';
|
||||
|
||||
class TradeFilterStore = TradeFilterStoreBase with _$TradeFilterStore;
|
||||
|
||||
abstract class TradeFilterStoreBase with Store {
|
||||
TradeFilterStoreBase() : displayXMRTO = true,
|
||||
TradeFilterStoreBase()
|
||||
: displayXMRTO = true,
|
||||
displayChangeNow = true,
|
||||
displaySideShift = true,
|
||||
displayMorphToken = true,
|
||||
displaySimpleSwap = true,
|
||||
displayTrocador = true,
|
||||
displayExolix = true;
|
||||
displayExolix = true,
|
||||
displayThorChain = true;
|
||||
|
||||
@observable
|
||||
bool displayXMRTO;
|
||||
|
@ -37,8 +39,17 @@ abstract class TradeFilterStoreBase with Store {
|
|||
@observable
|
||||
bool displayExolix;
|
||||
|
||||
@observable
|
||||
bool displayThorChain;
|
||||
|
||||
@computed
|
||||
bool get displayAllTrades => displayChangeNow && displaySideShift && displaySimpleSwap && displayTrocador && displayExolix;
|
||||
bool get displayAllTrades =>
|
||||
displayChangeNow &&
|
||||
displaySideShift &&
|
||||
displaySimpleSwap &&
|
||||
displayTrocador &&
|
||||
displayExolix &&
|
||||
displayThorChain;
|
||||
|
||||
@action
|
||||
void toggleDisplayExchange(ExchangeProviderDescription provider) {
|
||||
|
@ -64,6 +75,9 @@ abstract class TradeFilterStoreBase with Store {
|
|||
case ExchangeProviderDescription.exolix:
|
||||
displayExolix = !displayExolix;
|
||||
break;
|
||||
case ExchangeProviderDescription.thorChain:
|
||||
displayThorChain = !displayThorChain;
|
||||
break;
|
||||
case ExchangeProviderDescription.all:
|
||||
if (displayAllTrades) {
|
||||
displayChangeNow = false;
|
||||
|
@ -73,6 +87,7 @@ abstract class TradeFilterStoreBase with Store {
|
|||
displaySimpleSwap = false;
|
||||
displayTrocador = false;
|
||||
displayExolix = false;
|
||||
displayThorChain = false;
|
||||
} else {
|
||||
displayChangeNow = true;
|
||||
displaySideShift = true;
|
||||
|
@ -81,6 +96,7 @@ abstract class TradeFilterStoreBase with Store {
|
|||
displaySimpleSwap = true;
|
||||
displayTrocador = true;
|
||||
displayExolix = true;
|
||||
displayThorChain = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -96,16 +112,13 @@ abstract class TradeFilterStoreBase with Store {
|
|||
? _trades
|
||||
.where((item) =>
|
||||
(displayXMRTO && item.trade.provider == ExchangeProviderDescription.xmrto) ||
|
||||
(displaySideShift &&
|
||||
item.trade.provider == ExchangeProviderDescription.sideShift) ||
|
||||
(displayChangeNow &&
|
||||
item.trade.provider == ExchangeProviderDescription.changeNow) ||
|
||||
(displayMorphToken &&
|
||||
item.trade.provider == ExchangeProviderDescription.morphToken) ||
|
||||
(displaySimpleSwap &&
|
||||
item.trade.provider == ExchangeProviderDescription.simpleSwap) ||
|
||||
(displaySideShift && item.trade.provider == ExchangeProviderDescription.sideShift) ||
|
||||
(displayChangeNow && item.trade.provider == ExchangeProviderDescription.changeNow) ||
|
||||
(displayMorphToken && item.trade.provider == ExchangeProviderDescription.morphToken) ||
|
||||
(displaySimpleSwap && item.trade.provider == ExchangeProviderDescription.simpleSwap) ||
|
||||
(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador) ||
|
||||
(displayExolix && item.trade.provider == ExchangeProviderDescription.exolix))
|
||||
(displayExolix && item.trade.provider == ExchangeProviderDescription.exolix) ||
|
||||
(displayThorChain && item.trade.provider == ExchangeProviderDescription.thorChain))
|
||||
.toList()
|
||||
: _trades;
|
||||
}
|
||||
|
|
|
@ -71,7 +71,7 @@ abstract class AnonpayDetailsViewModelBase with Store {
|
|||
]);
|
||||
|
||||
items.add(TrackTradeListItem(
|
||||
title: 'Track',
|
||||
title: S.current.track,
|
||||
value: invoiceDetail.clearnetStatusUrl,
|
||||
onTap: () => launchUrlString(invoiceDetail.clearnetStatusUrl)));
|
||||
}
|
||||
|
|
|
@ -93,18 +93,6 @@ abstract class BuyViewModelBase with Store {
|
|||
_providerList.add(WyreBuyProvider(wallet: wallet));
|
||||
}
|
||||
|
||||
var isMoonPayEnabled = false;
|
||||
try {
|
||||
isMoonPayEnabled = await MoonPayBuyProvider.onEnabled();
|
||||
} catch (e) {
|
||||
isMoonPayEnabled = false;
|
||||
print(e.toString());
|
||||
}
|
||||
|
||||
if (isMoonPayEnabled) {
|
||||
_providerList.add(MoonPayBuyProvider(wallet: wallet));
|
||||
}
|
||||
|
||||
items = _providerList.map((provider) =>
|
||||
BuyItem(provider: provider, buyAmountViewModel: buyAmountViewModel))
|
||||
.toList();
|
||||
|
|
|
@ -123,6 +123,11 @@ abstract class DashboardViewModelBase with Store {
|
|||
caption: ExchangeProviderDescription.exolix.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.exolix)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayThorChain,
|
||||
caption: ExchangeProviderDescription.thorChain.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.thorChain)),
|
||||
]
|
||||
},
|
||||
subname = '',
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
|||
import 'package:cake_wallet/exchange/provider/exolix_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/thorchain_exchange.provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/trade.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
@ -47,6 +48,9 @@ abstract class ExchangeTradeViewModelBase with Store {
|
|||
case ExchangeProviderDescription.exolix:
|
||||
_provider = ExolixExchangeProvider();
|
||||
break;
|
||||
case ExchangeProviderDescription.thorChain:
|
||||
_provider = ThorChainExchangeProvider(tradesStore: trades);
|
||||
break;
|
||||
}
|
||||
|
||||
_updateItems();
|
||||
|
@ -100,8 +104,13 @@ abstract class ExchangeTradeViewModelBase with Store {
|
|||
final output = sendViewModel.outputs.first;
|
||||
output.address = trade.inputAddress ?? '';
|
||||
output.setCryptoAmount(trade.amount);
|
||||
if (_provider is ThorChainExchangeProvider) output.memo = trade.memo;
|
||||
sendViewModel.selectedCryptoCurrency = trade.from;
|
||||
await sendViewModel.createTransaction();
|
||||
final pendingTransaction = await sendViewModel.createTransaction(provider: _provider);
|
||||
if (_provider is ThorChainExchangeProvider) {
|
||||
trade.id = pendingTransaction?.id ?? '';
|
||||
trades.add(trade);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -127,6 +136,8 @@ abstract class ExchangeTradeViewModelBase with Store {
|
|||
tradesStore.trade!.from.tag != null ? '${tradesStore.trade!.from.tag}' + ' ' : '';
|
||||
final tagTo = tradesStore.trade!.to.tag != null ? '${tradesStore.trade!.to.tag}' + ' ' : '';
|
||||
items.clear();
|
||||
|
||||
if(trade.provider != ExchangeProviderDescription.thorChain)
|
||||
items.add(ExchangeTradeItem(
|
||||
title: "${trade.provider.title} ${S.current.id}", data: '${trade.id}', isCopied: true));
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
|
||||
|
@ -9,6 +10,7 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
|||
import 'package:cake_wallet/entities/preferences_key.dart';
|
||||
import 'package:cake_wallet/entities/wallet_contact.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_template.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_trade_state.dart';
|
||||
import 'package:cake_wallet/exchange/limits.dart';
|
||||
|
@ -18,6 +20,7 @@ import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
|||
import 'package:cake_wallet/exchange/provider/exolix_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/thorchain_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_request.dart';
|
||||
|
@ -96,7 +99,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
|||
|
||||
/// if the provider is not in the user settings (user's first time or newly added provider)
|
||||
/// then use its default value decided by us
|
||||
selectedProviders = ObservableList.of(providersForCurrentPair()
|
||||
selectedProviders = ObservableList.of(providerList
|
||||
.where((element) => exchangeProvidersSelection[element.title] == null
|
||||
? element.isEnabled
|
||||
: (exchangeProvidersSelection[element.title] as bool))
|
||||
|
@ -148,6 +151,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
|||
SimpleSwapExchangeProvider(),
|
||||
TrocadorExchangeProvider(
|
||||
useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates),
|
||||
ThorChainExchangeProvider(tradesStore: trades),
|
||||
if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
|
||||
];
|
||||
|
||||
|
@ -500,8 +504,16 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
|||
await provider.createTrade(request: request, isFixedRateMode: isFixedRateMode);
|
||||
trade.walletId = wallet.id;
|
||||
trade.fromWalletAddress = wallet.walletAddresses.address;
|
||||
|
||||
if (!isCanCreateTrade(trade)) {
|
||||
tradeState = TradeIsCreatedFailure(
|
||||
title: S.current.trade_not_created,
|
||||
error: S.current.thorchain_taproot_address_not_supported);
|
||||
return;
|
||||
}
|
||||
|
||||
tradesStore.setTrade(trade);
|
||||
await trades.add(trade);
|
||||
if (trade.provider != ExchangeProviderDescription.thorChain) await trades.add(trade);
|
||||
tradeState = TradeIsCreatedSuccessfully(trade: trade);
|
||||
|
||||
/// return after the first successful trade
|
||||
|
@ -758,4 +770,17 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
|||
int get depositMaxDigits => depositCurrency.decimals;
|
||||
|
||||
int get receiveMaxDigits => receiveCurrency.decimals;
|
||||
|
||||
bool isCanCreateTrade(Trade trade) {
|
||||
if (trade.provider == ExchangeProviderDescription.thorChain) {
|
||||
final payoutAddress = trade.payoutAddress ?? '';
|
||||
final fromWalletAddress = trade.fromWalletAddress ?? '';
|
||||
final tapRootPattern = RegExp(P2trAddress.regex.pattern);
|
||||
|
||||
if (tapRootPattern.hasMatch(payoutAddress) || tapRootPattern.hasMatch(fromWalletAddress)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ abstract class OrderDetailsViewModelBase with Store {
|
|||
_provider = WyreBuyProvider(wallet: wallet);
|
||||
break;
|
||||
case BuyProviderDescription.moonPay:
|
||||
_provider = MoonPayBuyProvider(wallet: wallet);
|
||||
// _provider = MoonPayProvider(wallet: wallet);// TODO: CW-521
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -50,9 +50,9 @@ abstract class OrderDetailsViewModelBase with Store {
|
|||
@action
|
||||
Future<void> _updateOrder() async {
|
||||
try {
|
||||
if (_provider != null && (_provider is MoonPayBuyProvider || _provider is WyreBuyProvider)) {
|
||||
final updatedOrder = _provider is MoonPayBuyProvider
|
||||
? await (_provider as MoonPayBuyProvider).findOrderById(order.id)
|
||||
if (_provider != null && (_provider is MoonPayProvider || _provider is WyreBuyProvider)) {
|
||||
final updatedOrder = _provider is MoonPayProvider
|
||||
? await (_provider as MoonPayProvider).findOrderById(order.id)
|
||||
: await (_provider as WyreBuyProvider).findOrderById(order.id);
|
||||
updatedOrder.from = order.from;
|
||||
updatedOrder.to = order.to;
|
||||
|
@ -89,17 +89,17 @@ abstract class OrderDetailsViewModelBase with Store {
|
|||
value: order.provider.title)
|
||||
);
|
||||
|
||||
if (_provider != null && (_provider is MoonPayBuyProvider || _provider is WyreBuyProvider)) {
|
||||
if (_provider != null && (_provider is MoonPayProvider || _provider is WyreBuyProvider)) {
|
||||
|
||||
final trackUrl = _provider is MoonPayBuyProvider
|
||||
? (_provider as MoonPayBuyProvider).trackUrl
|
||||
final trackUrl = _provider is MoonPayProvider
|
||||
? (_provider as MoonPayProvider).trackUrl
|
||||
: (_provider as WyreBuyProvider).trackUrl;
|
||||
|
||||
if (trackUrl.isNotEmpty ?? false) {
|
||||
final buildURL = trackUrl + '${order.transferId}';
|
||||
items.add(
|
||||
TrackTradeListItem(
|
||||
title: 'Track',
|
||||
title: S.current.track,
|
||||
value: buildURL,
|
||||
onTap: () {
|
||||
try {
|
||||
|
|
|
@ -66,6 +66,8 @@ abstract class OutputBase with Store {
|
|||
@observable
|
||||
String extractedAddress;
|
||||
|
||||
String? memo;
|
||||
|
||||
@computed
|
||||
bool get isParsedAddress =>
|
||||
parsedAddress.parseFrom != ParseFrom.notParsed && parsedAddress.name.isNotEmpty;
|
||||
|
@ -175,6 +177,7 @@ abstract class OutputBase with Store {
|
|||
fiatAmount = '';
|
||||
address = '';
|
||||
note = '';
|
||||
memo = null;
|
||||
resetParsedAddress();
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ import 'package:cake_wallet/entities/contact.dart';
|
|||
import 'package:cake_wallet/entities/priority_for_wallet_type.dart';
|
||||
import 'package:cake_wallet/entities/transaction_description.dart';
|
||||
import 'package:cake_wallet/ethereum/ethereum.dart';
|
||||
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
|
||||
import 'package:cake_wallet/nano/nano.dart';
|
||||
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
|
||||
import 'package:cake_wallet/entities/contact_record.dart';
|
||||
|
@ -296,14 +298,20 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
}
|
||||
|
||||
@action
|
||||
Future<void> createTransaction() async {
|
||||
Future<PendingTransaction?> createTransaction({ExchangeProvider? provider}) async {
|
||||
try {
|
||||
state = IsExecutingState();
|
||||
pendingTransaction = await wallet.createTransaction(_credentials());
|
||||
if (provider is ThorChainExchangeProvider) {
|
||||
final outputCount = pendingTransaction?.outputCount ?? 0;
|
||||
if (outputCount > 10) throw Exception("ThorChain does not support more than 10 outputs");
|
||||
}
|
||||
state = ExecutedSuccessfullyState();
|
||||
return pendingTransaction;
|
||||
} catch (e) {
|
||||
print('Failed with ${e.toString()}');
|
||||
state = FailureState(e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
|||
import 'package:cake_wallet/exchange/provider/exolix_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/thorchain_exchange.provider.dart';
|
||||
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/trade.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
@ -52,6 +53,9 @@ abstract class TradeDetailsViewModelBase with Store {
|
|||
case ExchangeProviderDescription.exolix:
|
||||
_provider = ExolixExchangeProvider();
|
||||
break;
|
||||
case ExchangeProviderDescription.thorChain:
|
||||
_provider = ThorChainExchangeProvider(tradesStore: trades);
|
||||
break;
|
||||
}
|
||||
|
||||
_updateItems();
|
||||
|
@ -62,6 +66,24 @@ abstract class TradeDetailsViewModelBase with Store {
|
|||
}
|
||||
}
|
||||
|
||||
static String? getTrackUrl(ExchangeProviderDescription provider, Trade trade) {
|
||||
switch (provider) {
|
||||
case ExchangeProviderDescription.changeNow:
|
||||
return 'https://changenow.io/exchange/txs/${trade.id}';
|
||||
case ExchangeProviderDescription.sideShift:
|
||||
return 'https://sideshift.ai/orders/${trade.id}';
|
||||
case ExchangeProviderDescription.simpleSwap:
|
||||
return 'https://simpleswap.io/exchange?id=${trade.id}';
|
||||
case ExchangeProviderDescription.trocador:
|
||||
return 'https://trocador.app/en/checkout/${trade.id}';
|
||||
case ExchangeProviderDescription.exolix:
|
||||
return 'https://exolix.com/transaction/${trade.id}';
|
||||
case ExchangeProviderDescription.thorChain:
|
||||
return 'https://track.ninerealms.com/${trade.id}';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final Box<Trade> trades;
|
||||
|
||||
@observable
|
||||
|
@ -125,46 +147,26 @@ abstract class TradeDetailsViewModelBase with Store {
|
|||
items.add(StandartListItem(
|
||||
title: S.current.trade_details_provider, value: trade.provider.toString()));
|
||||
|
||||
if (trade.provider == ExchangeProviderDescription.changeNow) {
|
||||
final buildURL = 'https://changenow.io/exchange/txs/${trade.id.toString()}';
|
||||
final trackUrl = TradeDetailsViewModelBase.getTrackUrl(trade.provider, trade);
|
||||
if (trackUrl != null) {
|
||||
items.add(TrackTradeListItem(
|
||||
title: 'Track',
|
||||
value: buildURL,
|
||||
onTap: () {
|
||||
_launchUrl(buildURL);
|
||||
}));
|
||||
title: S.current.track, value: trackUrl, onTap: () => _launchUrl(trackUrl)));
|
||||
}
|
||||
|
||||
if (trade.provider == ExchangeProviderDescription.sideShift) {
|
||||
final buildURL = 'https://sideshift.ai/orders/${trade.id.toString()}';
|
||||
items.add(
|
||||
TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
|
||||
}
|
||||
|
||||
if (trade.provider == ExchangeProviderDescription.simpleSwap) {
|
||||
final buildURL = 'https://simpleswap.io/exchange?id=${trade.id.toString()}';
|
||||
items.add(
|
||||
TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
|
||||
if (trade.isRefund == true) {
|
||||
items.add(StandartListItem(
|
||||
title: 'Refund', value: trade.refundAddress ?? ''));
|
||||
}
|
||||
|
||||
if (trade.provider == ExchangeProviderDescription.trocador) {
|
||||
final buildURL = 'https://trocador.app/en/checkout/${trade.id.toString()}';
|
||||
items.add(
|
||||
TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
|
||||
|
||||
items.add(StandartListItem(
|
||||
title: '${trade.providerName} ${S.current.id.toUpperCase()}',
|
||||
value: trade.providerId ?? ''));
|
||||
|
||||
if (trade.password != null && trade.password!.isNotEmpty)
|
||||
if (trade.password != null && trade.password!.isNotEmpty) {
|
||||
items.add(StandartListItem(
|
||||
title: '${trade.providerName} ${S.current.password}', value: trade.password ?? ''));
|
||||
}
|
||||
|
||||
if (trade.provider == ExchangeProviderDescription.exolix) {
|
||||
final buildURL = 'https://exolix.com/transaction/${trade.id.toString()}';
|
||||
items.add(
|
||||
TrackTradeListItem(title: 'Track', value: buildURL, onTap: () => _launchUrl(buildURL)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -652,6 +652,7 @@
|
|||
"template_name": "اسم القالب",
|
||||
"third_intro_content": "يعيش Yats خارج Cake Wallet أيضًا. يمكن استبدال أي عنوان محفظة على وجه الأرض بـ Yat!",
|
||||
"third_intro_title": "يتماشي Yat بلطف مع الآخرين",
|
||||
"thorchain_taproot_address_not_supported": "لا يدعم مزود Thorchain عناوين Taproot. يرجى تغيير العنوان أو تحديد مزود مختلف.",
|
||||
"time": "${minutes}د ${seconds}س",
|
||||
"tip": "بقشيش:",
|
||||
"today": "اليوم",
|
||||
|
@ -669,6 +670,7 @@
|
|||
"totp_code": "كود TOTP",
|
||||
"totp_secret_code": "كود TOTP السري",
|
||||
"totp_verification_success": "تم التحقق بنجاح!",
|
||||
"track": " ﺭﺎﺴﻣ",
|
||||
"trade_details_copied": "تم نسخ ${title} إلى الحافظة",
|
||||
"trade_details_created_at": "أنشئت في",
|
||||
"trade_details_fetching": "جار الجلب",
|
||||
|
|
|
@ -652,6 +652,7 @@
|
|||
"template_name": "Име на шаблон",
|
||||
"third_intro_content": "Yats също живее извън Cake Wallet. Всеки адрес на портфейл може да бъде заменен с Yat!",
|
||||
"third_intro_title": "Yat добре се сработва с други",
|
||||
"thorchain_taproot_address_not_supported": "Доставчикът на Thorchain не поддържа адреси на TapRoot. Моля, променете адреса или изберете друг доставчик.",
|
||||
"time": "${minutes} мин ${seconds} сек",
|
||||
"tip": "Tip:",
|
||||
"today": "Днес",
|
||||
|
@ -669,6 +670,7 @@
|
|||
"totp_code": "TOTP код",
|
||||
"totp_secret_code": "TOTP таен код",
|
||||
"totp_verification_success": "Проверката е успешна!",
|
||||
"track": "Писта",
|
||||
"trade_details_copied": "${title} копирано",
|
||||
"trade_details_created_at": "Създадено",
|
||||
"trade_details_fetching": "Обработка",
|
||||
|
|
|
@ -652,6 +652,7 @@
|
|||
"template_name": "Název šablony",
|
||||
"third_intro_content": "Yat existuje i mimo Cake Wallet. Jakákoliv adresa peněženky na světě může být nahrazena Yatem!",
|
||||
"third_intro_title": "Yat dobře spolupracuje s ostatními",
|
||||
"thorchain_taproot_address_not_supported": "Poskytovatel Thorchain nepodporuje adresy Taproot. Změňte adresu nebo vyberte jiného poskytovatele.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Spropitné:",
|
||||
"today": "Dnes",
|
||||
|
@ -669,6 +670,7 @@
|
|||
"totp_code": "Kód TOTP",
|
||||
"totp_secret_code": "Tajný kód TOTP",
|
||||
"totp_verification_success": "Ověření proběhlo úspěšně!",
|
||||
"track": "Dráha",
|
||||
"trade_details_copied": "${title} zkopírováno do schránky",
|
||||
"trade_details_created_at": "Vytvořeno v",
|
||||
"trade_details_fetching": "Získávám",
|
||||
|
|
|
@ -653,6 +653,7 @@
|
|||
"template_name": "Vorlagenname",
|
||||
"third_intro_content": "Yats leben auch außerhalb von Cake Wallet. Jede Wallet-Adresse auf der Welt kann durch ein Yat ersetzt werden!",
|
||||
"third_intro_title": "Yat spielt gut mit anderen",
|
||||
"thorchain_taproot_address_not_supported": "Der Thorchain -Anbieter unterstützt keine Taproot -Adressen. Bitte ändern Sie die Adresse oder wählen Sie einen anderen Anbieter aus.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Hinweis:",
|
||||
"today": "Heute",
|
||||
|
@ -670,6 +671,7 @@
|
|||
"totp_code": "TOTP-Code",
|
||||
"totp_secret_code": "TOTP-Geheimcode",
|
||||
"totp_verification_success": "Verifizierung erfolgreich!",
|
||||
"track": "Schiene",
|
||||
"trade_details_copied": "${title} in die Zwischenablage kopiert",
|
||||
"trade_details_created_at": "Erzeugt am",
|
||||
"trade_details_fetching": "Wird ermittelt",
|
||||
|
|
|
@ -652,6 +652,7 @@
|
|||
"template_name": "Template Name",
|
||||
"third_intro_content": "Yats live outside of Cake Wallet, too. Any wallet address on earth can be replaced with a Yat!",
|
||||
"third_intro_title": "Yat plays nicely with others",
|
||||
"thorchain_taproot_address_not_supported": "The ThorChain provider does not support Taproot addresses. Please change the address or select a different provider.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Tip:",
|
||||
"today": "Today",
|
||||
|
@ -669,6 +670,7 @@
|
|||
"totp_code": "TOTP Code",
|
||||
"totp_secret_code": "TOTP Secret Code",
|
||||
"totp_verification_success": "Verification Successful!",
|
||||
"track": "Track",
|
||||
"trade_details_copied": "${title} copied to Clipboard",
|
||||
"trade_details_created_at": "Created at",
|
||||
"trade_details_fetching": "Fetching",
|
||||
|
|
|
@ -653,6 +653,7 @@
|
|||
"template_name": "Nombre de la plantilla",
|
||||
"third_intro_content": "Los Yats también viven fuera de Cake Wallet. Cualquier dirección de billetera en la tierra se puede reemplazar con un Yat!",
|
||||
"third_intro_title": "Yat juega muy bien con otras",
|
||||
"thorchain_taproot_address_not_supported": "El proveedor de Thorchain no admite las direcciones de Taproot. Cambie la dirección o seleccione un proveedor diferente.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Consejo:",
|
||||
"today": "Hoy",
|
||||
|
@ -670,6 +671,7 @@
|
|||
"totp_code": "Código TOTP",
|
||||
"totp_secret_code": "Código secreto TOTP",
|
||||
"totp_verification_success": "¡Verificación exitosa!",
|
||||
"track": "Pista",
|
||||
"trade_details_copied": "${title} Copiado al portapapeles",
|
||||
"trade_details_created_at": "Creado en",
|
||||
"trade_details_fetching": "Cargando",
|
||||
|
|
|
@ -652,6 +652,7 @@
|
|||
"template_name": "Nom du modèle",
|
||||
"third_intro_content": "Les Yats existent aussi en dehors de Cake Wallet. Toute adresse sur terre peut être remplacée par un Yat !",
|
||||
"third_intro_title": "Yat est universel",
|
||||
"thorchain_taproot_address_not_supported": "Le fournisseur de Thorchain ne prend pas en charge les adresses de tapoot. Veuillez modifier l'adresse ou sélectionner un autre fournisseur.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Pourboire :",
|
||||
"today": "Aujourd'hui",
|
||||
|
@ -669,6 +670,7 @@
|
|||
"totp_code": "Code TOTP",
|
||||
"totp_secret_code": "Secret TOTP",
|
||||
"totp_verification_success": "Vérification réussie !",
|
||||
"track": "Piste",
|
||||
"trade_details_copied": "${title} copié vers le presse-papier",
|
||||
"trade_details_created_at": "Créé le",
|
||||
"trade_details_fetching": "Récupération",
|
||||
|
|
|
@ -654,6 +654,7 @@
|
|||
"template_name": "Sunan Samfura",
|
||||
"third_intro_content": "Yats suna zaune a wajen Kek Wallet, kuma. Ana iya maye gurbin kowane adireshin walat a duniya da Yat!",
|
||||
"third_intro_title": "Yat yana wasa da kyau tare da wasu",
|
||||
"thorchain_taproot_address_not_supported": "Mai ba da tallafi na ThorChain baya goyan bayan adreshin taproot. Da fatan za a canza adireshin ko zaɓi mai bayarwa daban.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Tukwici:",
|
||||
"today": "Yau",
|
||||
|
@ -671,6 +672,7 @@
|
|||
"totp_code": "Lambar totp",
|
||||
"totp_secret_code": "Lambar sirri",
|
||||
"totp_verification_success": "Tabbatar cin nasara!",
|
||||
"track": "Waƙa",
|
||||
"trade_details_copied": "${title} an kwafa zuwa cikin kwafin",
|
||||
"trade_details_created_at": "An ƙirƙira a",
|
||||
"trade_details_fetching": "Daukewa",
|
||||
|
|
|
@ -654,6 +654,7 @@
|
|||
"template_name": "टेम्पलेट नाम",
|
||||
"third_intro_content": "Yats Cake Wallet के बाहर भी रहता है। धरती पर किसी भी वॉलेट पते को Yat से बदला जा सकता है!",
|
||||
"third_intro_title": "Yat दूसरों के साथ अच्छा खेलता है",
|
||||
"thorchain_taproot_address_not_supported": "थोरचेन प्रदाता टैपरोट पते का समर्थन नहीं करता है। कृपया पता बदलें या एक अलग प्रदाता का चयन करें।",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "टिप:",
|
||||
"today": "आज",
|
||||
|
@ -671,6 +672,7 @@
|
|||
"totp_code": "टीओटीपी कोड",
|
||||
"totp_secret_code": "टीओटीपी गुप्त कोड",
|
||||
"totp_verification_success": "सत्यापन सफल!",
|
||||
"track": "रास्ता",
|
||||
"trade_details_copied": "${title} क्लिपबोर्ड पर नकल",
|
||||
"trade_details_created_at": "पर बनाया गया",
|
||||
"trade_details_fetching": "ला रहा है",
|
||||
|
|
|
@ -652,6 +652,7 @@
|
|||
"template_name": "Naziv predloška",
|
||||
"third_intro_content": "Yats žive i izvan Cake Wallet -a. Bilo koja adresa novčanika na svijetu može se zamijeniti Yat!",
|
||||
"third_intro_title": "Yat se lijepo igra s drugima",
|
||||
"thorchain_taproot_address_not_supported": "Thorchain pružatelj ne podržava Taproot adrese. Promijenite adresu ili odaberite drugog davatelja usluga.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Savjet:",
|
||||
"today": "Danas",
|
||||
|
@ -669,6 +670,7 @@
|
|||
"totp_code": "TOTP kod",
|
||||
"totp_secret_code": "TOTP tajni kod",
|
||||
"totp_verification_success": "Provjera uspješna!",
|
||||
"track": "Staza",
|
||||
"trade_details_copied": "${title} kopiran u međuspremnik",
|
||||
"trade_details_created_at": "Stvoreno u",
|
||||
"trade_details_fetching": "Dohvaćanje",
|
||||
|
|
|
@ -655,6 +655,7 @@
|
|||
"template_name": "Nama Templat",
|
||||
"third_intro_content": "Yats hidup di luar Cake Wallet juga. Setiap alamat dompet di dunia dapat diganti dengan Yat!",
|
||||
"third_intro_title": "Yat bermain baik dengan yang lain",
|
||||
"thorchain_taproot_address_not_supported": "Penyedia Thorchain tidak mendukung alamat Taproot. Harap ubah alamatnya atau pilih penyedia yang berbeda.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Tip:",
|
||||
"today": "Hari ini",
|
||||
|
@ -672,6 +673,7 @@
|
|||
"totp_code": "Kode TOTP",
|
||||
"totp_secret_code": "Kode Rahasia TOTP",
|
||||
"totp_verification_success": "Verifikasi Berhasil!",
|
||||
"track": "Melacak",
|
||||
"trade_details_copied": "${title} disalin ke Clipboard",
|
||||
"trade_details_created_at": "Dibuat pada",
|
||||
"trade_details_fetching": "Mengambil",
|
||||
|
|
|
@ -654,6 +654,7 @@
|
|||
"template_name": "Nome modello",
|
||||
"third_intro_content": "Yat può funzionare anche fuori da Cake Wallet. Qualsiasi indirizzo di portafoglio sulla terra può essere sostituito con uno Yat!",
|
||||
"third_intro_title": "Yat gioca bene con gli altri",
|
||||
"thorchain_taproot_address_not_supported": "Il provider di Thorchain non supporta gli indirizzi di TapRoot. Si prega di modificare l'indirizzo o selezionare un fornitore diverso.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Suggerimento:",
|
||||
"today": "Oggi",
|
||||
|
@ -671,6 +672,7 @@
|
|||
"totp_code": "Codice TOTP",
|
||||
"totp_secret_code": "TOTP codice segreto",
|
||||
"totp_verification_success": "Verifica riuscita!",
|
||||
"track": "Traccia",
|
||||
"trade_details_copied": "${title} copiati negli Appunti",
|
||||
"trade_details_created_at": "Creato alle",
|
||||
"trade_details_fetching": "Recupero",
|
||||
|
|
|
@ -652,6 +652,7 @@
|
|||
"template_name": "テンプレート名",
|
||||
"third_intro_content": "YatsはCakeWalletの外にも住んでいます。 地球上のどのウォレットアドレスもYatに置き換えることができます!",
|
||||
"third_intro_title": "Yatは他の人とうまく遊ぶ",
|
||||
"thorchain_taproot_address_not_supported": "Thorchainプロバイダーは、TapRootアドレスをサポートしていません。アドレスを変更するか、別のプロバイダーを選択してください。",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "ヒント: ",
|
||||
"today": "今日",
|
||||
|
@ -669,6 +670,7 @@
|
|||
"totp_code": "TOTP コード",
|
||||
"totp_secret_code": "TOTPシークレットコード",
|
||||
"totp_verification_success": "検証成功!",
|
||||
"track": "追跡",
|
||||
"trade_details_copied": "${title} クリップボードにコピーしました",
|
||||
"trade_details_created_at": "で作成",
|
||||
"trade_details_fetching": "フェッチング",
|
||||
|
|
|
@ -653,6 +653,7 @@
|
|||
"template_name": "템플릿 이름",
|
||||
"third_intro_content": "Yats는 Cake Wallet 밖에서도 살고 있습니다. 지구상의 모든 지갑 주소는 Yat!",
|
||||
"third_intro_title": "Yat는 다른 사람들과 잘 놉니다.",
|
||||
"thorchain_taproot_address_not_supported": "Thorchain 제공 업체는 Taproot 주소를 지원하지 않습니다. 주소를 변경하거나 다른 공급자를 선택하십시오.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "팁:",
|
||||
"today": "오늘",
|
||||
|
@ -670,6 +671,7 @@
|
|||
"totp_code": "TOTP 코드",
|
||||
"totp_secret_code": "TOTP 비밀 코드",
|
||||
"totp_verification_success": "확인 성공!",
|
||||
"track": "길",
|
||||
"trade_details_copied": "${title} 클립 보드에 복사",
|
||||
"trade_details_created_at": "에 작성",
|
||||
"trade_details_fetching": "가져 오는 중",
|
||||
|
|
|
@ -652,6 +652,7 @@
|
|||
"template_name": "နမူနာပုံစံ",
|
||||
"third_intro_content": "Yats သည် Cake Wallet အပြင်ဘက်တွင် နေထိုင်ပါသည်။ ကမ္ဘာပေါ်ရှိ မည်သည့်ပိုက်ဆံအိတ်လိပ်စာကို Yat ဖြင့် အစားထိုးနိုင်ပါသည်။",
|
||||
"third_intro_title": "Yat သည် အခြားသူများနှင့် ကောင်းစွာကစားသည်။",
|
||||
"thorchain_taproot_address_not_supported": "Thorchain Provider သည် Taproot လိပ်စာများကိုမထောက်ခံပါ။ ကျေးဇူးပြု. လိပ်စာကိုပြောင်းပါသို့မဟုတ်အခြားပံ့ပိုးပေးသူကိုရွေးချယ်ပါ။",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "အကြံပြုချက်-",
|
||||
"today": "ဒီနေ့",
|
||||
|
@ -669,6 +670,7 @@
|
|||
"totp_code": "TOTP ကုဒ်",
|
||||
"totp_secret_code": "TOTP လျှို့ဝှက်ကုဒ်",
|
||||
"totp_verification_success": "အတည်ပြုခြင်း အောင်မြင်ပါသည်။",
|
||||
"track": "တစ်ပုဒ်",
|
||||
"trade_details_copied": "${title} ကို Clipboard သို့ ကူးယူထားသည်။",
|
||||
"trade_details_created_at": "တွင်ဖန်တီးခဲ့သည်။",
|
||||
"trade_details_fetching": "ခေါ်ယူခြင်း။",
|
||||
|
|
|
@ -652,6 +652,7 @@
|
|||
"template_name": "Sjabloonnaam",
|
||||
"third_intro_content": "Yats wonen ook buiten Cake Wallet. Elk portemonnee-adres op aarde kan worden vervangen door een Yat!",
|
||||
"third_intro_title": "Yat speelt leuk met anderen",
|
||||
"thorchain_taproot_address_not_supported": "De Thorchain -provider ondersteunt geen Taprooot -adressen. Wijzig het adres of selecteer een andere provider.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Tip:",
|
||||
"today": "Vandaag",
|
||||
|
@ -669,6 +670,7 @@
|
|||
"totp_code": "TOTP-code",
|
||||
"totp_secret_code": "TOTP-geheime code",
|
||||
"totp_verification_success": "Verificatie geslaagd!",
|
||||
"track": "Spoor",
|
||||
"trade_details_copied": "${title} gekopieerd naar het klembord",
|
||||
"trade_details_created_at": "Gemaakt bij",
|
||||
"trade_details_fetching": "Ophalen",
|
||||
|
|
|
@ -652,6 +652,7 @@
|
|||
"template_name": "Nazwa szablonu",
|
||||
"third_intro_content": "Yats mieszkają również poza Cake Wallet. Każdy adres portfela na ziemi można zastąpić Yat!",
|
||||
"third_intro_title": "Yat ładnie bawi się z innymi",
|
||||
"thorchain_taproot_address_not_supported": "Dostawca Thorchain nie obsługuje adresów TAPROOT. Zmień adres lub wybierz innego dostawcę.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "wskazówka:",
|
||||
"today": "Dzisiaj",
|
||||
|
@ -669,6 +670,7 @@
|
|||
"totp_code": "Kod TOTP",
|
||||
"totp_secret_code": "Tajny kod TOTP",
|
||||
"totp_verification_success": "Weryfikacja powiodła się!",
|
||||
"track": "Ścieżka",
|
||||
"trade_details_copied": "${title} skopiowane do schowka",
|
||||
"trade_details_created_at": "Utworzono ",
|
||||
"trade_details_fetching": "Pobieranie",
|
||||
|
|
|
@ -654,6 +654,7 @@
|
|||
"template_name": "Nome do modelo",
|
||||
"third_intro_content": "Yats também mora fora da Cake Wallet. Qualquer endereço de carteira na Terra pode ser substituído por um Yat!",
|
||||
"third_intro_title": "Yat joga bem com os outros",
|
||||
"thorchain_taproot_address_not_supported": "O provedor de Thorchain não suporta endereços de raiz de Tap. Altere o endereço ou selecione um provedor diferente.",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "Dica:",
|
||||
"today": "Hoje",
|
||||
|
@ -671,6 +672,7 @@
|
|||
"totp_code": "Código TOTP",
|
||||
"totp_secret_code": "Código Secreto TOTP",
|
||||
"totp_verification_success": "Verificação bem-sucedida!",
|
||||
"track": "Acompanhar",
|
||||
"trade_details_copied": "${title} copiados para a área de transferência",
|
||||
"trade_details_created_at": "Criada em",
|
||||
"trade_details_fetching": "Buscando",
|
||||
|
|
|
@ -653,6 +653,7 @@
|
|||
"template_name": "Имя Шаблона",
|
||||
"third_intro_content": "Yat находятся за пределами Cake Wallet. Любой адрес кошелька на земле можно заменить на Yat!",
|
||||
"third_intro_title": "Yat хорошо взаимодействует с другими",
|
||||
"thorchain_taproot_address_not_supported": "Поставщик Thorchain не поддерживает адреса taproot. Пожалуйста, измените адрес или выберите другого поставщика.",
|
||||
"time": "${minutes}мин ${seconds}сек",
|
||||
"tip": "Совет:",
|
||||
"today": "Сегодня",
|
||||
|
@ -670,6 +671,7 @@
|
|||
"totp_code": "TOTP-код",
|
||||
"totp_secret_code": "Секретный код ТОТП",
|
||||
"totp_verification_success": "Проверка прошла успешно!",
|
||||
"track": "Отслеживать",
|
||||
"trade_details_copied": "${title} скопировано в буфер обмена",
|
||||
"trade_details_created_at": "Создано",
|
||||
"trade_details_fetching": "Получение",
|
||||
|
|
|
@ -652,6 +652,7 @@
|
|||
"template_name": "ชื่อแม่แบบ",
|
||||
"third_intro_content": "Yat อาศัยอยู่นอก Cake Wallet ด้วย ที่อยู่กระเป๋าใดๆ ทั่วโลกสามารถแทนด้วย Yat ได้อีกด้วย!",
|
||||
"third_intro_title": "Yat ปฏิบัติตนอย่างดีกับผู้อื่น",
|
||||
"thorchain_taproot_address_not_supported": "ผู้ให้บริการ Thorchain ไม่รองรับที่อยู่ taproot โปรดเปลี่ยนที่อยู่หรือเลือกผู้ให้บริการอื่น",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "เพิ่มค่าตอบแทน:",
|
||||
"today": "วันนี้",
|
||||
|
@ -669,6 +670,7 @@
|
|||
"totp_code": "รหัสทีโอพี",
|
||||
"totp_secret_code": "รหัสลับ TOTP",
|
||||
"totp_verification_success": "การยืนยันสำเร็จ!",
|
||||
"track": "ติดตาม",
|
||||
"trade_details_copied": "${title} คัดลอกไปยัง Clipboard",
|
||||
"trade_details_created_at": "สร้างเมื่อ",
|
||||
"trade_details_fetching": "กำลังเรียกข้อมูล",
|
||||
|
|
|
@ -652,6 +652,7 @@
|
|||
"template_name": "Pangalan ng Template",
|
||||
"third_intro_content": "Ang mga yats ay nakatira sa labas ng cake wallet, din. Ang anumang address ng pitaka sa mundo ay maaaring mapalitan ng isang yat!",
|
||||
"third_intro_title": "Si Yat ay mahusay na gumaganap sa iba",
|
||||
"thorchain_taproot_address_not_supported": "Ang Tagabigay ng Thorchain ay hindi sumusuporta sa mga address ng taproot. Mangyaring baguhin ang address o pumili ng ibang provider.",
|
||||
"time": "${minutes} m ${seconds} s",
|
||||
"tip": "Tip:",
|
||||
"today": "Ngayon",
|
||||
|
@ -669,6 +670,7 @@
|
|||
"totp_code": "TOTP code",
|
||||
"totp_secret_code": "TOTP Secret Code",
|
||||
"totp_verification_success": "Matagumpay ang pagpapatunay!",
|
||||
"track": "Subaybayan",
|
||||
"trade_details_copied": "${title} kinopya sa clipboard",
|
||||
"trade_details_created_at": "Nilikha sa",
|
||||
"trade_details_fetching": "Pagkuha",
|
||||
|
|
|
@ -652,6 +652,7 @@
|
|||
"template_name": "şablon adı",
|
||||
"third_intro_content": "Yat'lar Cake Wallet'ın dışında da çalışabilir. Dünya üzerindeki herhangi bir cüzdan adresi Yat ile değiştirilebilir!",
|
||||
"third_intro_title": "Yat diğerleriyle iyi çalışır",
|
||||
"thorchain_taproot_address_not_supported": "Thorchain sağlayıcısı Taproot adreslerini desteklemiyor. Lütfen adresi değiştirin veya farklı bir sağlayıcı seçin.",
|
||||
"time": "${minutes}d ${seconds}s",
|
||||
"tip": "Bahşiş:",
|
||||
"today": "Bugün",
|
||||
|
@ -669,6 +670,7 @@
|
|||
"totp_code": "TOTP Kodu",
|
||||
"totp_secret_code": "TOTP Gizli Kodu",
|
||||
"totp_verification_success": "Doğrulama Başarılı!",
|
||||
"track": "İzlemek",
|
||||
"trade_details_copied": "${title} panoya kopyalandı",
|
||||
"trade_details_created_at": "'da oluşturuldu",
|
||||
"trade_details_fetching": "Getiriliyor",
|
||||
|
|
|
@ -653,6 +653,7 @@
|
|||
"template_name": "Назва шаблону",
|
||||
"third_intro_content": "Yat знаходиться за межами Cake Wallet. Будь-яку адресу гаманця на землі можна замінити на Yat!",
|
||||
"third_intro_title": "Yat добре взаємодіє з іншими",
|
||||
"thorchain_taproot_address_not_supported": "Постачальник Thorchain не підтримує адреси Taproot. Будь ласка, змініть адресу або виберіть іншого постачальника.",
|
||||
"time": "${minutes}хв ${seconds}сек",
|
||||
"tip": "Порада:",
|
||||
"today": "Сьогодні",
|
||||
|
@ -670,6 +671,7 @@
|
|||
"totp_code": "Код TOTP",
|
||||
"totp_secret_code": "Секретний код TOTP",
|
||||
"totp_verification_success": "Перевірка успішна!",
|
||||
"track": "Відслідковувати",
|
||||
"trade_details_copied": "${title} скопійовано в буфер обміну",
|
||||
"trade_details_created_at": "Створено",
|
||||
"trade_details_fetching": "Отримання",
|
||||
|
|
|
@ -654,6 +654,7 @@
|
|||
"template_name": "ٹیمپلیٹ کا نام",
|
||||
"third_intro_content": "Yats بھی Cake والیٹ سے باہر رہتے ہیں۔ زمین پر کسی بھی بٹوے کے پتے کو Yat سے تبدیل کیا جا سکتا ہے!",
|
||||
"third_intro_title": "Yat دوسروں کے ساتھ اچھی طرح کھیلتا ہے۔",
|
||||
"thorchain_taproot_address_not_supported": "تھورچین فراہم کنندہ ٹیپروٹ پتے کی حمایت نہیں کرتا ہے۔ براہ کرم پتہ تبدیل کریں یا ایک مختلف فراہم کنندہ کو منتخب کریں۔",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "ٹپ:",
|
||||
"today": "آج",
|
||||
|
@ -671,6 +672,7 @@
|
|||
"totp_code": "TOTP کوڈ",
|
||||
"totp_secret_code": "TOTP خفیہ کوڈ",
|
||||
"totp_verification_success": "توثیق کامیاب!",
|
||||
"track": " ﮏﯾﺮﭨ",
|
||||
"trade_details_copied": "${title} کو کلپ بورڈ پر کاپی کیا گیا۔",
|
||||
"trade_details_created_at": "پر تخلیق کیا گیا۔",
|
||||
"trade_details_fetching": "لا رہا ہے۔",
|
||||
|
|
|
@ -653,6 +653,7 @@
|
|||
"template_name": "Orukọ Awoṣe",
|
||||
"third_intro_content": "A sì lè lo Yats níta Cake Wallet. A lè rọ́pò Àdírẹ́sì kankan àpamọ́wọ́ fún Yat!",
|
||||
"third_intro_title": "Àlàáfíà ni Yat àti àwọn ìmíìn jọ wà",
|
||||
"thorchain_taproot_address_not_supported": "Olupese Trockchain ko ṣe atilẹyin awọn adirẹsi Taproot. Jọwọ yi adirẹsi pada tabi yan olupese ti o yatọ.",
|
||||
"time": "${minutes}ìṣj ${seconds}ìṣs",
|
||||
"tip": "Owó àfikún:",
|
||||
"today": "Lénìí",
|
||||
|
@ -670,6 +671,7 @@
|
|||
"totp_code": "Koodu TOTP",
|
||||
"totp_secret_code": "Koodu iye TOTP",
|
||||
"totp_verification_success": "Ìbẹrẹ dọkita!",
|
||||
"track": "Orin",
|
||||
"trade_details_copied": "Ti ṣeda ${title} sí àtẹ àkọsílẹ̀",
|
||||
"trade_details_created_at": "Ṣíṣe ní",
|
||||
"trade_details_fetching": "Ń mú wá",
|
||||
|
|
|
@ -652,6 +652,7 @@
|
|||
"template_name": "模板名称",
|
||||
"third_intro_content": "Yats 也住在 Cake Wallet 之外。 地球上任何一個錢包地址都可以用一個Yat來代替!",
|
||||
"third_intro_title": "Yat 和別人玩得很好",
|
||||
"thorchain_taproot_address_not_supported": "Thorchain提供商不支持Taproot地址。请更改地址或选择其他提供商。",
|
||||
"time": "${minutes}m ${seconds}s",
|
||||
"tip": "提示:",
|
||||
"today": "今天",
|
||||
|
@ -669,6 +670,7 @@
|
|||
"totp_code": "TOTP代码",
|
||||
"totp_secret_code": "TOTP密码",
|
||||
"totp_verification_success": "验证成功!",
|
||||
"track": "追踪",
|
||||
"trade_details_copied": "${title} 复制到剪贴板",
|
||||
"trade_details_created_at": "创建于",
|
||||
"trade_details_fetching": "正在获取",
|
||||
|
|
|
@ -1114,7 +1114,7 @@ Future<void> generatePubspec(
|
|||
final inputFile = File(pubspecOutputPath);
|
||||
final inputText = await inputFile.readAsString();
|
||||
final inputLines = inputText.split('\n');
|
||||
final dependenciesIndex = inputLines.indexWhere((line) => line.toLowerCase() == 'dependencies:');
|
||||
final dependenciesIndex = inputLines.indexWhere((line) => line.toLowerCase().contains('dependencies:'));
|
||||
var output = cwCore;
|
||||
|
||||
if (hasMonero) {
|
||||
|
|
Loading…
Reference in a new issue