feat: integrate `getOnramperSignature` method and add support for retrieving Onramper signature (#2553)
What changed, and why it matters
This commit adds a new step when a user buys cryptocurrency through the Onramper service. Before opening the Onramper checkout page, the app now sends the user's chosen crypto address to a Cake Wallet server (exchange-helper.cakewallet.com) to request a signature. That signature is then included in the checkout URL. The change also normalizes the blockchain network name to lowercase. There is no direct evidence in the commit of a vulnerability, but it introduces a new network call that handles a real user address and an API key, which is worth reviewing for privacy and trust assumptions.
Treat this as a routine feature commit that warrants a focused security review rather than an incident response. Verify that exchange-helper.cakewallet.com uses TLS with certificate pinning, that the API key is scoped and rotated, that the returned signature is validated before use, and that the server does not log or retain user addresses. Confirm whether Onramper requires this signature for fraud/integrity purposes and document the trust boundary. No immediate patch is indicated by the diff alone.
Security signals we found
New outbound network request to a Cake-controlled helper API carrying a user crypto address and an API key
Third-party/fiat-ramp integration now depends on a server-side signature whose generation logic is not visible in the repository
API key retrieved from app secrets and sent in a request header
No visible signature validation or certificate pinning for the helper endpoint
Lowercase normalization of networkWallets parameter, likely to match Onramper's expected format
Evidence from the diff
The diff adds getOnramperSignature() in lib/buy/onramper/onramper_buy_provider.dart. It POSTs a JSON body {‘query’: ‘networkWallets=
Changed components
lib/buy/onramper/onramper_buy_provider.dartOnramper fiat-to-crypto purchase flowCake Wallet exchange helper service (exchange-helper.cakewallet.com)Inspect captured patch +30 / −2
diff --git a/lib/buy/onramper/onramper_buy_provider.dart b/lib/buy/onramper/onramper_buy_provider.dart
index 1d1d0292..615d510b 100644
--- a/lib/buy/onramper/onramper_buy_provider.dart
+++ b/lib/buy/onramper/onramper_buy_provider.dart
@@ -29,6 +29,7 @@ class OnRamperBuyProvider extends BuyProvider {
static const _baseUrl = 'buy.onramper.com';
static const _baseApiUrl = 'api.onramper.com';
+ static const _cIdBaseUrl = 'exchange-helper.cakewallet.com';
static const quotes = '/quotes';
static const paymentTypes = '/payment-types';
static const supported = '/supported';
@@ -44,6 +45,8 @@ class OnRamperBuyProvider extends BuyProvider {
String get _apiKey => secrets.onramperApiKey;
+ String get _exchangeHelperApiKey => secrets.exchangeHelperApiKey;
+
@override
String get title => 'Onramper';
@@ -59,6 +62,24 @@ class OnRamperBuyProvider extends BuyProvider {
@override
bool get isAggregator => true;
+ Future<String> getOnramperSignature(String query) async {
+ final uri = Uri.https(_cIdBaseUrl, "/api/onramper");
+
+ final response = await ProxyWrapper().post(
+ clearnetUri: uri,
+ headers: {'Content-Type': 'application/json', 'x-api-key': _exchangeHelperApiKey},
+ body: json.encode({'query': query}),
+ );
+
+
+ if (response.statusCode == 200) {
+ return (jsonDecode(response.body) as Map<String, dynamic>)['signature'] as String;
+ } else {
+ throw Exception(
+ 'Provider currently unavailable. Status: ${response.statusCode} ${response.body}');
+ }
+ }
+
Future<String?> getRecommendedPaymentType(bool isBuyAction) async {
final params = {'type': isBuyAction ? 'buy' : 'sell'};
@@ -261,6 +282,11 @@ class OnRamperBuyProvider extends BuyProvider {
final paymentMethod = quote.paymentType == PaymentType.unknown ? quote.customPaymentMethodType : normalizePaymentMethod(quote.paymentType);
+ final networkWallets =
+ '${_tagToNetwork(quote.cryptoCurrency.tag ?? quote.cryptoCurrency.title).toLowerCase()}:$cryptoCurrencyAddress';
+
+ final signature = await getOnramperSignature("networkWallets=$networkWallets");
+
final uri = Uri.https(_baseUrl, '', {
'apiKey': _apiKey,
'txnType': actionType,
@@ -270,7 +296,7 @@ class OnRamperBuyProvider extends BuyProvider {
'skipTransactionScreen': "true",
if (paymentMethod != null) 'txnPaymentMethod': paymentMethod,
'txnOnramp': quote.rampId,
- 'networkWallets': '${_tagToNetwork(quote.cryptoCurrency.tag ?? quote.cryptoCurrency.title)}:$cryptoCurrencyAddress',
+ 'networkWallets': networkWallets,
'supportSwap': "false",
'primaryColor': primaryColor,
'secondaryColor': secondaryColor,
@@ -278,6 +304,7 @@ class OnRamperBuyProvider extends BuyProvider {
'primaryTextColor': primaryTextColor,
'secondaryTextColor': secondaryTextColor,
'cardColor': cardColor,
+ 'signature': signature,
});
if (await canLaunchUrl(uri)) {
@@ -422,5 +449,6 @@ class OnRamperBuyProvider extends BuyProvider {
}
}
- String getColorStr(Color color) => color.value.toRadixString(16).replaceAll(RegExp(r'^ff'), "");
+ String getColorStr(Color color) =>
+ color.toARGB32().toRadixString(16).replaceAll(RegExp(r'^ff'), "");
}
Why this scored 28/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.