2024-11-09 19:00:56 +00:00
|
|
|
import 'dart:convert';
|
|
|
|
import 'dart:developer';
|
|
|
|
|
2023-04-14 04:39:08 +00:00
|
|
|
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
2023-12-28 19:20:59 +00:00
|
|
|
import 'package:cake_wallet/buy/buy_provider.dart';
|
2024-11-09 19:00:56 +00:00
|
|
|
import 'package:cake_wallet/buy/buy_quote.dart';
|
|
|
|
import 'package:cake_wallet/buy/payment_method.dart';
|
|
|
|
import 'package:cake_wallet/entities/fiat_currency.dart';
|
2023-08-22 18:49:37 +00:00
|
|
|
import 'package:cake_wallet/generated/i18n.dart';
|
2023-04-14 04:39:08 +00:00
|
|
|
import 'package:cake_wallet/store/settings_store.dart';
|
2023-08-22 18:49:37 +00:00
|
|
|
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
2023-05-10 13:58:31 +00:00
|
|
|
import 'package:cw_core/crypto_currency.dart';
|
2024-11-09 19:00:56 +00:00
|
|
|
import 'package:cw_core/currency.dart';
|
2023-04-14 04:39:08 +00:00
|
|
|
import 'package:cw_core/wallet_base.dart';
|
2023-08-22 18:49:37 +00:00
|
|
|
import 'package:flutter/material.dart';
|
2024-11-09 19:00:56 +00:00
|
|
|
import 'package:http/http.dart' as http;
|
2023-09-14 19:14:49 +00:00
|
|
|
import 'package:url_launcher/url_launcher.dart';
|
2023-04-14 04:39:08 +00:00
|
|
|
|
2023-12-28 19:20:59 +00:00
|
|
|
class OnRamperBuyProvider extends BuyProvider {
|
|
|
|
OnRamperBuyProvider(this._settingsStore,
|
|
|
|
{required WalletBase wallet, bool isTestEnvironment = false})
|
2024-05-05 01:44:50 +00:00
|
|
|
: super(wallet: wallet, isTestEnvironment: isTestEnvironment, ledgerVM: null);
|
2023-12-28 19:20:59 +00:00
|
|
|
|
|
|
|
static const _baseUrl = 'buy.onramper.com';
|
2024-11-09 19:00:56 +00:00
|
|
|
static const _baseApiUrl = 'api.onramper.com';
|
|
|
|
static const quotes = '/quotes';
|
|
|
|
static const paymentTypes = '/payment-types';
|
|
|
|
static const supported = '/supported';
|
2023-04-14 04:39:08 +00:00
|
|
|
|
|
|
|
final SettingsStore _settingsStore;
|
|
|
|
|
2024-11-09 19:00:56 +00:00
|
|
|
String get _apiKey => secrets.onramperApiKey;
|
|
|
|
|
2023-12-28 19:20:59 +00:00
|
|
|
@override
|
|
|
|
String get title => 'Onramper';
|
|
|
|
|
|
|
|
@override
|
2024-01-01 13:05:37 +00:00
|
|
|
String get providerDescription => S.current.onramper_option_description;
|
2023-12-28 19:20:59 +00:00
|
|
|
|
|
|
|
@override
|
|
|
|
String get lightIcon => 'assets/images/onramper_light.png';
|
|
|
|
|
|
|
|
@override
|
|
|
|
String get darkIcon => 'assets/images/onramper_dark.png';
|
2023-04-14 04:39:08 +00:00
|
|
|
|
2024-11-09 19:00:56 +00:00
|
|
|
@override
|
|
|
|
bool get isAggregator => true;
|
2023-05-10 13:58:31 +00:00
|
|
|
|
2024-11-09 19:00:56 +00:00
|
|
|
Future<List<PaymentMethod>> getAvailablePaymentTypes(
|
|
|
|
String fiatCurrency, String cryptoCurrency, bool isBuyAction) async {
|
|
|
|
final params = {
|
|
|
|
'fiatCurrency': fiatCurrency,
|
|
|
|
'type': isBuyAction ? 'buy' : 'sell',
|
|
|
|
'isRecurringPayment': 'false'
|
|
|
|
};
|
|
|
|
|
|
|
|
final url = Uri.https(_baseApiUrl, '$supported$paymentTypes/$fiatCurrency', params);
|
|
|
|
|
|
|
|
try {
|
|
|
|
final response =
|
|
|
|
await http.get(url, headers: {'Authorization': _apiKey, 'accept': 'application/json'});
|
|
|
|
|
|
|
|
if (response.statusCode == 200) {
|
|
|
|
final Map<String, dynamic> data = jsonDecode(response.body) as Map<String, dynamic>;
|
|
|
|
final List<dynamic> message = data['message'] as List<dynamic>;
|
|
|
|
return message
|
|
|
|
.map((item) => PaymentMethod.fromOnramperJson(item as Map<String, dynamic>))
|
|
|
|
.toList();
|
|
|
|
} else {
|
|
|
|
print('Failed to fetch available payment types');
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
print('Failed to fetch available payment types: $e');
|
|
|
|
return [];
|
2023-05-10 13:58:31 +00:00
|
|
|
}
|
|
|
|
}
|
2023-04-14 04:39:08 +00:00
|
|
|
|
2024-11-09 19:00:56 +00:00
|
|
|
Future<Map<String, dynamic>> getOnrampMetadata() async {
|
|
|
|
final url = Uri.https(_baseApiUrl, '$supported/onramps/all');
|
|
|
|
|
|
|
|
try {
|
|
|
|
final response =
|
|
|
|
await http.get(url, headers: {'Authorization': _apiKey, 'accept': 'application/json'});
|
|
|
|
|
|
|
|
if (response.statusCode == 200) {
|
|
|
|
final Map<String, dynamic> data = jsonDecode(response.body) as Map<String, dynamic>;
|
|
|
|
|
|
|
|
final List<dynamic> onramps = data['message'] as List<dynamic>;
|
|
|
|
|
|
|
|
final Map<String, dynamic> result = {
|
|
|
|
for (var onramp in onramps)
|
|
|
|
(onramp['id'] as String): {
|
|
|
|
'displayName': onramp['displayName'] as String,
|
|
|
|
'svg': onramp['icons']['svg'] as String
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
return result;
|
|
|
|
} else {
|
|
|
|
print('Failed to fetch onramp metadata');
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
print('Error occurred: $e');
|
|
|
|
return {};
|
|
|
|
}
|
2023-08-22 18:49:37 +00:00
|
|
|
}
|
|
|
|
|
2024-11-09 19:00:56 +00:00
|
|
|
@override
|
|
|
|
Future<List<Quote>?> fetchQuote(
|
|
|
|
{required CryptoCurrency cryptoCurrency,
|
|
|
|
required FiatCurrency fiatCurrency,
|
|
|
|
required double amount,
|
|
|
|
required bool isBuyAction,
|
|
|
|
required String walletAddress,
|
|
|
|
PaymentType? paymentType,
|
|
|
|
String? countryCode}) async {
|
|
|
|
String? paymentMethod;
|
|
|
|
|
|
|
|
if (paymentType != null && paymentType != PaymentType.all) {
|
|
|
|
paymentMethod = normalizePaymentMethod(paymentType);
|
|
|
|
if (paymentMethod == null) paymentMethod = paymentType.name;
|
|
|
|
}
|
|
|
|
|
|
|
|
final actionType = isBuyAction ? 'buy' : 'sell';
|
|
|
|
|
|
|
|
final normalizedCryptoCurrency = _getNormalizeCryptoCurrency(cryptoCurrency);
|
|
|
|
|
|
|
|
final params = {
|
|
|
|
'amount': amount.toString(),
|
|
|
|
if (paymentMethod != null) 'paymentMethod': paymentMethod,
|
|
|
|
'clientName': 'CakeWallet',
|
|
|
|
'type': actionType,
|
|
|
|
'walletAddress': walletAddress,
|
|
|
|
'isRecurringPayment': 'false',
|
|
|
|
'input': 'source',
|
|
|
|
};
|
|
|
|
|
|
|
|
log('Onramper: Fetching $actionType quote: ${isBuyAction ? normalizedCryptoCurrency : fiatCurrency.name} -> ${isBuyAction ? fiatCurrency.name : normalizedCryptoCurrency}, amount: $amount, paymentMethod: $paymentMethod');
|
|
|
|
|
|
|
|
final sourceCurrency = isBuyAction ? fiatCurrency.name : normalizedCryptoCurrency;
|
|
|
|
final destinationCurrency = isBuyAction ? normalizedCryptoCurrency : fiatCurrency.name;
|
|
|
|
|
|
|
|
final url = Uri.https(_baseApiUrl, '$quotes/${sourceCurrency}/${destinationCurrency}', params);
|
|
|
|
final headers = {'Authorization': _apiKey, 'accept': 'application/json'};
|
|
|
|
|
|
|
|
try {
|
|
|
|
final response = await http.get(url, headers: headers);
|
|
|
|
|
|
|
|
if (response.statusCode == 200) {
|
|
|
|
final data = jsonDecode(response.body) as List<dynamic>;
|
|
|
|
if (data.isEmpty) return null;
|
|
|
|
|
|
|
|
List<Quote> validQuotes = [];
|
|
|
|
|
|
|
|
final onrampMetadata = await getOnrampMetadata();
|
|
|
|
|
|
|
|
for (var item in data) {
|
|
|
|
|
|
|
|
if (item['errors'] != null) continue;
|
|
|
|
|
|
|
|
final paymentMethod = (item as Map<String, dynamic>)['paymentMethod'] as String;
|
|
|
|
|
|
|
|
final rampId = item['ramp'] as String?;
|
|
|
|
final rampMetaData = onrampMetadata[rampId] as Map<String, dynamic>?;
|
|
|
|
|
|
|
|
if (rampMetaData == null) continue;
|
|
|
|
|
|
|
|
final quote = Quote.fromOnramperJson(
|
|
|
|
item, isBuyAction, onrampMetadata, _getPaymentTypeByString(paymentMethod));
|
|
|
|
quote.setFiatCurrency = fiatCurrency;
|
|
|
|
quote.setCryptoCurrency = cryptoCurrency;
|
|
|
|
validQuotes.add(quote);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (validQuotes.isEmpty) return null;
|
|
|
|
|
|
|
|
return validQuotes;
|
|
|
|
} else {
|
|
|
|
print('Onramper: Failed to fetch rate');
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
} catch (e) {
|
|
|
|
print('Onramper: Failed to fetch rate $e');
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void>? launchProvider(
|
|
|
|
{required BuildContext context,
|
|
|
|
required Quote quote,
|
|
|
|
required double amount,
|
|
|
|
required bool isBuyAction,
|
|
|
|
required String cryptoCurrencyAddress,
|
|
|
|
String? countryCode}) async {
|
|
|
|
final actionType = isBuyAction ? 'buy' : 'sell';
|
|
|
|
final prefix = actionType == 'sell' ? actionType + '_' : '';
|
|
|
|
|
|
|
|
final primaryColor = getColorStr(Theme.of(context).primaryColor);
|
|
|
|
final secondaryColor = getColorStr(Theme.of(context).colorScheme.background);
|
|
|
|
final primaryTextColor = getColorStr(Theme.of(context).extension<CakeTextTheme>()!.titleColor);
|
|
|
|
final secondaryTextColor =
|
|
|
|
getColorStr(Theme.of(context).extension<CakeTextTheme>()!.secondaryTextColor);
|
|
|
|
final containerColor = getColorStr(Theme.of(context).colorScheme.background);
|
|
|
|
var cardColor = getColorStr(Theme.of(context).cardColor);
|
2023-08-22 18:49:37 +00:00
|
|
|
|
|
|
|
if (_settingsStore.currentTheme.title == S.current.high_contrast_theme) {
|
|
|
|
cardColor = getColorStr(Colors.white);
|
2023-04-14 04:39:08 +00:00
|
|
|
}
|
|
|
|
|
2024-11-09 19:00:56 +00:00
|
|
|
final defaultCrypto = _getNormalizeCryptoCurrency(quote.cryptoCurrency);
|
|
|
|
|
|
|
|
final paymentMethod = normalizePaymentMethod(quote.paymentType);
|
2023-04-14 04:39:08 +00:00
|
|
|
|
2024-11-09 19:00:56 +00:00
|
|
|
final uri = Uri.https(_baseUrl, '', {
|
2023-04-14 04:39:08 +00:00
|
|
|
'apiKey': _apiKey,
|
2024-11-09 19:00:56 +00:00
|
|
|
'mode': actionType,
|
|
|
|
'${prefix}defaultFiat': quote.fiatCurrency.name,
|
2024-11-09 21:44:08 +00:00
|
|
|
'${prefix}defaultCrypto': defaultCrypto,
|
2024-11-09 19:00:56 +00:00
|
|
|
'${prefix}defaultAmount': amount.toString(),
|
|
|
|
if (paymentMethod != null) '${prefix}defaultPaymentMethod': paymentMethod,
|
|
|
|
'onlyOnramps': quote.rampId,
|
|
|
|
'networkWallets': '$defaultCrypto:$cryptoCurrencyAddress',
|
|
|
|
'walletAddress': cryptoCurrencyAddress,
|
2023-04-14 04:39:08 +00:00
|
|
|
'supportSwap': "false",
|
|
|
|
'primaryColor': primaryColor,
|
|
|
|
'secondaryColor': secondaryColor,
|
2024-11-09 19:00:56 +00:00
|
|
|
'containerColor': containerColor,
|
2023-04-14 04:39:08 +00:00
|
|
|
'primaryTextColor': primaryTextColor,
|
|
|
|
'secondaryTextColor': secondaryTextColor,
|
2024-01-08 14:02:12 +00:00
|
|
|
'cardColor': cardColor,
|
2023-04-14 04:39:08 +00:00
|
|
|
});
|
2023-09-14 19:14:49 +00:00
|
|
|
|
2024-11-09 19:00:56 +00:00
|
|
|
if (await canLaunchUrl(uri)) {
|
|
|
|
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
2023-09-14 19:14:49 +00:00
|
|
|
} else {
|
2024-11-09 19:00:56 +00:00
|
|
|
throw Exception('Could not launch URL');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
List<CryptoCurrency> mainCurrency = [
|
|
|
|
CryptoCurrency.btc,
|
|
|
|
CryptoCurrency.eth,
|
|
|
|
CryptoCurrency.sol,
|
|
|
|
];
|
|
|
|
|
|
|
|
String _tagToNetwork(String tag) {
|
|
|
|
switch (tag) {
|
|
|
|
case 'OMNI':
|
|
|
|
return tag;
|
|
|
|
case 'POL':
|
|
|
|
return 'POLYGON';
|
|
|
|
default:
|
|
|
|
return CryptoCurrency.fromString(tag).fullName ?? tag;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
String _getNormalizeCryptoCurrency(Currency currency) {
|
|
|
|
if (currency is CryptoCurrency) {
|
|
|
|
if (!mainCurrency.contains(currency)) {
|
|
|
|
final network = currency.tag == null ? currency.fullName : _tagToNetwork(currency.tag!);
|
|
|
|
return '${currency.title}_${network?.replaceAll(' ', '')}'.toUpperCase();
|
|
|
|
}
|
|
|
|
return currency.title.toUpperCase();
|
2023-09-14 19:14:49 +00:00
|
|
|
}
|
2024-11-09 19:00:56 +00:00
|
|
|
return currency.name.toUpperCase();
|
2023-09-14 19:14:49 +00:00
|
|
|
}
|
2024-11-09 19:00:56 +00:00
|
|
|
|
|
|
|
String? normalizePaymentMethod(PaymentType paymentType) {
|
|
|
|
switch (paymentType) {
|
|
|
|
case PaymentType.bankTransfer:
|
|
|
|
return 'banktransfer';
|
|
|
|
case PaymentType.creditCard:
|
|
|
|
return 'creditcard';
|
|
|
|
case PaymentType.debitCard:
|
|
|
|
return 'debitcard';
|
|
|
|
case PaymentType.applePay:
|
|
|
|
return 'applepay';
|
|
|
|
case PaymentType.googlePay:
|
|
|
|
return 'googlepay';
|
|
|
|
case PaymentType.revolutPay:
|
|
|
|
return 'revolutpay';
|
|
|
|
case PaymentType.neteller:
|
|
|
|
return 'neteller';
|
|
|
|
case PaymentType.skrill:
|
|
|
|
return 'skrill';
|
|
|
|
case PaymentType.sepa:
|
|
|
|
return 'sepabanktransfer';
|
|
|
|
case PaymentType.sepaInstant:
|
|
|
|
return 'sepainstant';
|
|
|
|
case PaymentType.ach:
|
|
|
|
return 'ach';
|
|
|
|
case PaymentType.achInstant:
|
|
|
|
return 'iach';
|
|
|
|
case PaymentType.Khipu:
|
|
|
|
return 'khipu';
|
|
|
|
case PaymentType.palomaBanktTansfer:
|
|
|
|
return 'palomabanktransfer';
|
|
|
|
case PaymentType.ovo:
|
|
|
|
return 'ovo';
|
|
|
|
case PaymentType.zaloPay:
|
|
|
|
return 'zalopay';
|
|
|
|
case PaymentType.zaloBankTransfer:
|
|
|
|
return 'zalobanktransfer';
|
|
|
|
case PaymentType.gcash:
|
|
|
|
return 'gcash';
|
|
|
|
case PaymentType.imps:
|
|
|
|
return 'imps';
|
|
|
|
case PaymentType.dana:
|
|
|
|
return 'dana';
|
|
|
|
case PaymentType.ideal:
|
|
|
|
return 'ideal';
|
|
|
|
default:
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
PaymentType _getPaymentTypeByString(String paymentMethod) {
|
|
|
|
switch (paymentMethod.toLowerCase()) {
|
|
|
|
case 'banktransfer':
|
|
|
|
return PaymentType.bankTransfer;
|
|
|
|
case 'creditcard':
|
|
|
|
return PaymentType.creditCard;
|
|
|
|
case 'debitcard':
|
|
|
|
return PaymentType.debitCard;
|
|
|
|
case 'applepay':
|
|
|
|
return PaymentType.applePay;
|
|
|
|
case 'googlepay':
|
|
|
|
return PaymentType.googlePay;
|
|
|
|
case 'revolutpay':
|
|
|
|
return PaymentType.revolutPay;
|
|
|
|
case 'neteller':
|
|
|
|
return PaymentType.neteller;
|
|
|
|
case 'skrill':
|
|
|
|
return PaymentType.skrill;
|
|
|
|
case 'sepabanktransfer':
|
|
|
|
return PaymentType.sepa;
|
|
|
|
case 'sepainstant':
|
|
|
|
return PaymentType.sepaInstant;
|
|
|
|
case 'ach':
|
|
|
|
return PaymentType.ach;
|
|
|
|
case 'iach':
|
|
|
|
return PaymentType.achInstant;
|
|
|
|
case 'khipu':
|
|
|
|
return PaymentType.Khipu;
|
|
|
|
case 'palomabanktransfer':
|
|
|
|
return PaymentType.palomaBanktTansfer;
|
|
|
|
case 'ovo':
|
|
|
|
return PaymentType.ovo;
|
|
|
|
case 'zalopay':
|
|
|
|
return PaymentType.zaloPay;
|
|
|
|
case 'zalobanktransfer':
|
|
|
|
return PaymentType.zaloBankTransfer;
|
|
|
|
case 'gcash':
|
|
|
|
return PaymentType.gcash;
|
|
|
|
case 'imps':
|
|
|
|
return PaymentType.imps;
|
|
|
|
case 'dana':
|
|
|
|
return PaymentType.dana;
|
|
|
|
case 'ideal':
|
|
|
|
return PaymentType.ideal;
|
|
|
|
default:
|
|
|
|
return PaymentType.all;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
String getColorStr(Color color) => color.value.toRadixString(16).replaceAll(RegExp(r'^ff'), "");
|
2023-04-14 04:39:08 +00:00
|
|
|
}
|