What changed, and why it matters
This commit is a routine work-in-progress UI feature for buying and selling cryptocurrency inside the Cake Wallet app. It adds a new screen where users can pick preset fiat amounts (like $50, $100, $500) or enter a custom amount. There is nothing in the changes that fixes a security bug, opens a vulnerability, or handles sensitive data in a risky way. It is normal product development code.
No security action required. Review as normal feature code if desired.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff converts NewBuySellAmountPage from StatelessWidget to StatefulWidget, adds a BuySellDefaultAmountSelector grid of preset fiat amounts per currency, wires selection to BuySellViewModel.changeFiatAmount, adds a ‘custom’ option, and adds localized strings. It also adds useSafeArea:true to a modal bottom sheet and stubs an abstract method in BuyProvider with => null. No security-relevant logic (cryptography, authentication, network trust, input validation, secrets handling) is introduced or altered.
Changed components
lib/new-ui/pages/buy_sell/buy_sell_amount_page.dartlib/new-ui/widgets/buy_sell/buy_sell_selector_modal.dartlib/view_model/buy/buy_sell_view_model.dartlib/buy/buy_provider.dartres/values/strings_en.arbInspect captured patch +209 / −18
diff --git a/lib/buy/buy_provider.dart b/lib/buy/buy_provider.dart
index 5053a7ef..d2a4bc96 100644
--- a/lib/buy/buy_provider.dart
+++ b/lib/buy/buy_provider.dart
@@ -43,7 +43,7 @@ abstract class BuyProvider {
required double amount,
required bool isBuyAction,
required String cryptoCurrencyAddress,
- String? countryCode});
+ String? countryCode}) => null;
Future<String> requestUrl(String amount, String sourceCurrency) => throw UnimplementedError();
diff --git a/lib/new-ui/pages/buy_sell/buy_sell_amount_page.dart b/lib/new-ui/pages/buy_sell/buy_sell_amount_page.dart
index b29a747d..3b36c583 100644
--- a/lib/new-ui/pages/buy_sell/buy_sell_amount_page.dart
+++ b/lib/new-ui/pages/buy_sell/buy_sell_amount_page.dart
@@ -1,15 +1,25 @@
+import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart';
import 'package:cake_wallet/new-ui/widgets/receive_page/receive_top_bar.dart';
+import 'package:cake_wallet/themes/core/theme_extension.dart';
import 'package:cake_wallet/view_model/buy/buy_sell_view_model.dart';
+import 'package:cw_core/amount/money.dart';
import 'package:flutter/material.dart';
-class NewBuySellAmountPage extends StatelessWidget {
+class NewBuySellAmountPage extends StatefulWidget {
const NewBuySellAmountPage({super.key, required this.mode, required this.buySellViewModel});
final BuySellPageMode mode;
final BuySellViewModel buySellViewModel;
+ @override
+ State<NewBuySellAmountPage> createState() => _NewBuySellAmountPageState();
+}
+
+class _NewBuySellAmountPageState extends State<NewBuySellAmountPage> {
+ bool _customAmountMode = false;
+
@override
Widget build(BuildContext context) {
return Container(
@@ -25,26 +35,152 @@ class NewBuySellAmountPage extends StatelessWidget {
),
),
child: SafeArea(
- child: Column(children: [
- ModalTopBar(title: _pageTitle, leadingIcon: Icon(Icons.close), onLeadingPressed: Navigator.of(context).pop,),
- Expanded(child: GridView.builder(itemCount: 6, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
- crossAxisCount: 2,
- crossAxisSpacing: 10,
- mainAxisSpacing: 10,
- mainAxisExtent: 150), itemBuilder: (context, index){
-
-
- }))
- ],),
+ child: Column(
+ children: [
+ ModalTopBar(
+ title: _pageTitle,
+ leadingIcon: Icon(Icons.close),
+ onLeadingPressed: Navigator.of(context).pop,
+ ),
+ Expanded(
+ child: BuySellDefaultAmountSelector(
+ defaultAmounts: widget.buySellViewModel.defaultAmounts,
+ currency: widget.buySellViewModel.fiatCurrency,
+ mode: widget.mode,
+ onSelected: (amount) {
+ if (amount == null) {
+ setState(() {
+ _customAmountMode = true;
+ });
+ } else {
+ widget.buySellViewModel.changeFiatAmount(amount: amount);
+ }
+ },
+ ))
+ ],
+ ),
),
);
}
- String get _pageTitle =>
- mode == BuySellPageMode.buy ? S.current.buy : S.current.sell +
- ((buySellViewModel.cryptoCurrencies.length == 1)
- ? " ${buySellViewModel.cryptoCurrencies.first.fullName}"
+ String get _pageTitle => widget.mode == BuySellPageMode.buy
+ ? S.current.buy
+ : S.current.sell +
+ ((widget.buySellViewModel.cryptoCurrencies.length == 1)
+ ? " ${widget.buySellViewModel.cryptoCurrencies.first.fullName}"
: "");
}
+class BuySellDefaultAmountSelector extends StatelessWidget {
+ const BuySellDefaultAmountSelector(
+ {super.key,
+ required this.defaultAmounts,
+ required this.currency,
+ required this.mode,
+ required this.onSelected});
+
+ final List<String> defaultAmounts;
+ final FiatCurrency currency;
+ final BuySellPageMode mode;
+ final Function(String?) onSelected;
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ spacing: 24,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Text(
+ mode == BuySellPageMode.sell
+ ? S.of(context).choose_amount_to_sell
+ : S.of(context).choose_amount_to_buy,
+ style: TextStyle(color: Theme.of(context).colorScheme.onSurfaceVariant),
+ ),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 18.0),
+ child: GridView.builder(
+ shrinkWrap: true,
+ // +1 for "custom" option
+ itemCount: defaultAmounts.length + 1,
+ gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
+ crossAxisCount: 2, crossAxisSpacing: 8, mainAxisSpacing: 16, mainAxisExtent: 105),
+ itemBuilder: (context, index) {
+ final String? item = index == defaultAmounts.length ? null : defaultAmounts[index];
+
+ return BuySellAmountPill(
+ amount: item == null ? null : Money.parse(item, currency),
+ onTap: () => onSelected(item),
+ );
+ }),
+ ),
+ ],
+ );
+ }
+}
+
+class BuySellAmountPill extends StatelessWidget {
+ const BuySellAmountPill({super.key, this.amount, required this.onTap});
+
+ final Money? amount;
+ final VoidCallback onTap;
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(9999999999),
+ border: Border.all(
+ width: 1,
+ color: Theme.of(context).colorScheme.surfaceContainerHigh,
+ ),
+ gradient: LinearGradient(
+ colors: [
+ context.customColors.cardGradientColorPrimary,
+ context.customColors.cardGradientColorSecondary
+ ],
+ begin: Alignment.topCenter,
+ end: Alignment.bottomCenter,
+ ),
+ ),
+ child: Material(
+ color: Colors.transparent,
+ borderRadius: BorderRadius.circular(9999999999),
+ child: InkWell(
+ borderRadius: BorderRadius.circular(9999999999),
+ onTap: onTap,
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Row(
+ spacing: 4,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ if (amount != null)
+ Text(
+ amount!.toStringWithPrecision(fractionalDigits: 0),
+ style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
+ ),
+ Text(
+ amount?.currency.symbol ?? S.of(context).custom,
+ style: TextStyle(
+ fontSize: 18,
+ fontWeight: FontWeight.w600,
+ color: amount == null
+ ? Theme.of(context).colorScheme.primary
+ : Theme.of(context).colorScheme.onSurfaceVariant),
+ )
+ ],
+ ),
+ if (amount == null)
+ Text(
+ S.of(context).enter_amount,
+ style: TextStyle(
+ fontSize: 12, color: Theme.of(context).colorScheme.onSurfaceVariant),
+ )
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart b/lib/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart
index 85b82566..7e6efd00 100644
--- a/lib/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart
+++ b/lib/new-ui/widgets/buy_sell/buy_sell_selector_modal.dart
@@ -46,7 +46,7 @@ class BuySellSelectorModal extends StatelessWidget {
void openBuySellPage(BuildContext context, BuySellPageMode mode) {
Navigator.of(context).pop();
- showModalBottomSheet(isScrollControlled: true, context: context, builder: (modalContext)=>ModalNavigator(rootPage: getIt.get<NewBuySellAmountPage>(param1: mode), parentContext: context,));
+ showModalBottomSheet(useSafeArea:true, isScrollControlled: true, context: context, builder: (modalContext)=>ModalNavigator(rootPage: getIt.get<NewBuySellAmountPage>(param1: mode), parentContext: context,));
}
}
diff --git a/lib/view_model/buy/buy_sell_view_model.dart b/lib/view_model/buy/buy_sell_view_model.dart
index a299aeba..4d181d7f 100644
--- a/lib/view_model/buy/buy_sell_view_model.dart
+++ b/lib/view_model/buy/buy_sell_view_model.dart
@@ -149,6 +149,59 @@ abstract class BuySellViewModelBase extends WalletChangeListenerViewModel with S
@observable
bool skipIsReadyToTradeReaction = false;
+
+
+ // based on usd values, should have roughly equal worth (was done with ai though so it's subject to correction)
+ static final Map<FiatCurrency, List<String>> _defaultAmountsMap = {
+ FiatCurrency.amd: ["20000", "40000", "200000", "400000", "1000000"],
+ FiatCurrency.aud: ["100", "200", "1000", "2000", "5000"],
+ FiatCurrency.bgn: ["100", "200", "1000", "2000", "5000"],
+ FiatCurrency.brl: ["250", "500", "2500", "5000", "12500"],
+ FiatCurrency.cad: ["50", "100", "500", "1000", "2500"],
+ FiatCurrency.chf: ["50", "100", "500", "1000", "2500"],
+ FiatCurrency.clp: ["50000", "100000", "500000", "1000000", "2500000"],
+ FiatCurrency.cop: ["200000", "400000", "2000000", "4000000", "10000000"],
+ FiatCurrency.czk: ["1000", "2000", "10000", "20000", "50000"],
+ FiatCurrency.dkk: ["400", "800", "4000", "8000", "20000"],
+ FiatCurrency.egp: ["2500", "5000", "25000", "50000", "125000"],
+ FiatCurrency.eur: ["50", "100", "500", "1000", "2500"],
+ FiatCurrency.gbp: ["50", "100", "500", "1000", "2500"],
+ FiatCurrency.gtq: ["400", "800", "4000", "8000", "20000"],
+ FiatCurrency.hkd: ["400", "800", "4000", "8000", "20000"],
+ FiatCurrency.hrk: ["400", "800", "4000", "8000", "20000"],
+ FiatCurrency.huf: ["20000", "40000", "200000", "400000", "1000000"],
+ FiatCurrency.idr: ["800000", "1600000", "8000000", "16000000", "40000000"],
+ FiatCurrency.ils: ["200", "400", "2000", "4000", "10000"],
+ FiatCurrency.inr: ["5000", "10000", "50000", "100000", "250000"],
+ FiatCurrency.isk: ["7000", "14000", "70000", "140000", "350000"],
+ FiatCurrency.jpy: ["10000", "20000", "100000", "200000", "500000"],
+ FiatCurrency.krw: ["50000", "100000", "500000", "1000000", "2500000"],
+ FiatCurrency.mad: ["500", "1000", "5000", "10000", "25000"],
+ FiatCurrency.mxn: ["1000", "2000", "10000", "20000", "50000"],
+ FiatCurrency.myr: ["250", "500", "2500", "5000", "12500"],
+ FiatCurrency.ngn: ["50000", "100000", "500000", "1000000", "2500000"],
+ FiatCurrency.nok: ["500", "1000", "5000", "10000", "25000"],
+ FiatCurrency.nzd: ["100", "200", "1000", "2000", "5000"],
+ FiatCurrency.php: ["3000", "6000", "30000", "60000", "150000"],
+ FiatCurrency.pkr: ["15000", "30000", "150000", "300000", "750000"],
+ FiatCurrency.pln: ["200", "400", "2000", "4000", "10000"],
+ FiatCurrency.ron: ["250", "500", "2500", "5000", "12500"],
+ FiatCurrency.sek: ["500", "1000", "5000", "10000", "25000"],
+ FiatCurrency.sgd: ["50", "100", "500", "1000", "2500"],
+ FiatCurrency.thb: ["2000", "4000", "20000", "40000", "100000"],
+ FiatCurrency.tur: ["1500", "3000", "15000", "30000", "75000"],
+ FiatCurrency.twd: ["1500", "3000", "15000", "30000", "75000"],
+ FiatCurrency.usd: ["50", "100", "500", "1000", "2500"],
+ FiatCurrency.vnd: ["1000000", "2000000", "10000000", "20000000", "50000000"],
+ FiatCurrency.zar: ["1000", "2000", "10000", "20000", "50000"],
+ FiatCurrency.kes: ["5000", "10000", "50000", "100000", "250000"],
+ };
+
+ // the fallback is just the usd values.
+ // not great but this fallback shouldn't be triggered anyway
+ List<String> get defaultAmounts =>
+ _defaultAmountsMap[fiatCurrency] ?? ["50", "100", "500", "1000", "2500"];
+
@computed
bool get isReadyToTrade {
final hasSelectedQuote = selectedQuote != null;
diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb
index 8cf1c783..207ee288 100644
--- a/res/values/strings_en.arb
+++ b/res/values/strings_en.arb
@@ -199,6 +199,8 @@
"choose_a_provider": "Choose a provider",
"choose_account": "Choose account",
"choose_address": "\n\nPlease choose the address:",
+ "choose_amount_to_buy": "Choose amount to buy",
+ "choose_amount_to_sell": "Choose amount to sell",
"choose_card_value": "Choose a card value",
"choose_derivation": "Choose Wallet Derivation",
"choose_from_available_options": "Choose from the available options:",
Why this scored 18/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.