feat: Add token to recieve address QR and link to pay anything flow on Solana and Tron, also fix token not showing on evm wallets QRs when scanned from dashboard (#3320)
David Adegoke committed
Jun 10, 2026 at 18:31 UTC
99f96b0a78965c2f1a19ef36ef1d140c91927cec
5 files changed
+64
-31
cw_core/lib/payment_uris.dart
+22
-4
@@ -176,14 +176,23 @@ class PolygonURI extends PaymentURI {
176
}
177
178
class SolanaURI extends PaymentURI {
179
- SolanaURI({required super.amount, required super.address});
179
+ SolanaURI({required super.amount, required super.address, this.contractAddress});
180
+
181
+ final String? contractAddress;
182
183
@override
184
String toString() {
185
var base = 'solana:$address';
186
+ final params = <String>[];
187
188
if (amount.isNotEmpty) {
186
- base += '?amount=${amount.replaceAll(',', '.')}';
189
+ params.add('amount=${amount.replaceAll(',', '.')}');
190
+ }
191
+ if (contractAddress != null && contractAddress!.isNotEmpty) {
192
+ params.add('spl-token=$contractAddress');
193
+ }
194
+ if (params.isNotEmpty) {
195
+ base += '?${params.join('&')}';
196
}
197
198
return base;
@@ -191,14 +200,23 @@ class SolanaURI extends PaymentURI {
200
}
201
202
class TronURI extends PaymentURI {
194
- TronURI({required super.amount, required super.address});
203
+ TronURI({required super.amount, required super.address, this.contractAddress});
204
+
205
+ final String? contractAddress;
206
207
@override
208
String toString() {
209
var base = 'tron:$address';
210
+ final params = <String>[];
211
212
if (amount.isNotEmpty) {
201
- base += '?amount=${amount.replaceAll(',', '.')}';
213
+ params.add('amount=${amount.replaceAll(',', '.')}');
214
+ }
215
+ if (contractAddress != null && contractAddress!.isNotEmpty) {
216
+ params.add('token=$contractAddress');
217
+ }
218
+ if (params.isNotEmpty) {
219
+ base += '?${params.join('&')}';
220
}
221
222
return base;
lib/new-ui/pages/send_page.dart
+8
@@ -228,6 +228,14 @@ class _NewSendPageState extends State<NewSendPage> {
228
_addressControllers[0].text = widget.initialPaymentRequest!.address;
229
_amountControllers[0].text = widget.initialPaymentRequest!.amount;
230
_memoControllers[0].text = widget.initialPaymentRequest!.note;
231
+ final contractAddress = widget.initialPaymentRequest!.contractAddress;
232
+ if (contractAddress != null && contractAddress.isNotEmpty) {
233
+ WidgetsBinding.instance.addPostFrameCallback((_) {
234
+ if (mounted) {
235
+ widget.sendViewModel.fetchTokenForContractAddress(contractAddress);
236
+ }
237
+ });
238
+ }
239
}
240
241
/// if the current wallet doesn't match the one in the qr code
lib/utils/payment_request.dart
+10
@@ -60,6 +60,16 @@ class PaymentRequest {
60
address = paymentUri.address;
61
amount = paymentUri.amount;
62
contractAddress = paymentUri.contractAddress;
63
+ } else if (scheme == "tron") {
64
+ final token = uri.queryParameters['token'];
65
+ if (token != null && token.isNotEmpty) {
66
+ contractAddress = token;
67
+ }
68
+ } else if (scheme == "solana") {
69
+ final splToken = uri.queryParameters['spl-token'];
70
+ if (splToken != null && splToken.isNotEmpty) {
71
+ contractAddress = splToken;
72
+ }
73
}
74
}
75
lib/utils/token_utilities.dart
+8
-27
@@ -144,7 +144,7 @@ class TokenUtilities {
144
return result;
145
}
146
147
- /// Finds a token by address across wallets depending on [walletType]
147
+ /// Finds a token by address for the given [walletType].
148
/// - EVM chains: match by contractAddress
149
/// - Solana: match by mintAddress
150
/// - Tron: match by contractAddress
@@ -152,34 +152,15 @@ class TokenUtilities {
152
required WalletType walletType,
153
required String address,
154
}) async {
155
- if(address.isEmpty) return null;
155
+ if (address.isEmpty) return null;
156
final lower = address.toLowerCase();
157
- switch (walletType) {
158
- case WalletType.ethereum:
159
- case WalletType.polygon:
160
- case WalletType.base:
161
- case WalletType.arbitrum:
162
- case WalletType.bsc:
163
- final tokens = await loadAllUniqueEvmTokens();
164
- for (final t in tokens) {
165
- if (t.contractAddress.toLowerCase() == lower) return t;
166
- }
167
- return null;
168
- case WalletType.solana:
169
- final solTokens = await loadAllUniqueSolTokens();
170
- for (final t in solTokens) {
171
- if (t.mintAddress.toLowerCase() == lower) return t;
172
- }
173
- return null;
174
- case WalletType.tron:
175
- final tronTokens = await loadAllUniqueTronTokens();
176
- for (final t in tronTokens) {
177
- if (t.contractAddress.toLowerCase() == lower) return t;
178
- }
179
- return null;
180
- default:
181
- return null;
157
+ final tokens = await getAvailableTokensForNetwork(walletType);
158
+ for (final t in tokens) {
159
+ if (t is Erc20Token && t.contractAddress.toLowerCase() == lower) return t;
160
+ if (t is SPLToken && t.mintAddress.toLowerCase() == lower) return t;
161
+ if (t is TronToken && t.contractAddress.toLowerCase() == lower) return t;
162
}
163
+ return null;
164
}
165
166
static Future<Box<Erc20Token>> _openEvmTokensBoxFor(WalletInfo walletInfo) async {
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+16
@@ -34,6 +34,8 @@ import 'package:cw_core/currency.dart';
34
import 'package:cw_core/currency_for_wallet_type.dart';
35
import 'package:cw_core/erc20_token.dart';
36
import 'package:cw_core/payment_uris.dart';
37
+import 'package:cw_core/spl_token.dart';
38
+import 'package:cw_core/tron_token.dart';
39
import 'package:cw_core/wallet_type.dart';
40
import 'package:mobx/mobx.dart';
41
@@ -202,6 +204,20 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
204
amount: _amount,
205
contractAddress: (tokenCurrency as Erc20Token).contractAddress);
206
}
207
+ if (tokenCurrency is TronToken && wallet.type == WalletType.tron) {
208
+ return TronURI(
209
+ amount: _amount,
210
+ address: wallet.walletAddresses.address,
211
+ contractAddress: (tokenCurrency as TronToken).contractAddress,
212
+ );
213
+ }
214
+ if (tokenCurrency is SPLToken && wallet.type == WalletType.solana) {
215
+ return SolanaURI(
216
+ amount: _amount,
217
+ address: wallet.walletAddresses.address,
218
+ contractAddress: (tokenCurrency as SPLToken).mintAddress,
219
+ );
220
+ }
221
if (isLightning && _lnPaymentRequest != null) return _lnPaymentRequest!;
222
return wallet.walletAddresses.getPaymentUri(_amount);
223
}