feat: generate per-app Ethereum token defaults
What changed, and why it matters
This commit reorganizes how Stack Wallet and its related apps choose which Ethereum tokens appear by default. It does not fix a security bug and does not introduce an obvious vulnerability. The main change is moving the default token list from a single shared file into per-app configuration scripts, and adding a new rsFIRO token for the Campfire app. The token contract addresses shown in the diff match well-known public Ethereum addresses, so there is no direct evidence of malicious token substitution.
No immediate security action is required. As a defensive measure, maintainers should verify that the rsFIRO contract address 0x2744ea5ac9b11cb5e3cd63d3a88e858336aeddc2 matches the official Firo/Ethereum bridge token published by the Firo project, and consider adding automated checks so future per-app token defaults cannot be silently replaced with attacker-controlled contracts.
Security signals we found
Refactor of default token list source
Addition of new rsFIRO token contract address 0x2744ea5ac9b11cb5e3cd63d3a88e858336aeddc2
Per-app configuration now controls which tokens are pre-populated
No change to contract address validation or token metadata verification
Evidence from the diff
The patch refactors DefaultTokens from a single List
Changed components
lib/utilities/default_eth_tokens.dartlib/app_config.dartlib/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dartlib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dartlib/pages/shopinbit/shopinbit_payment_shared.dartscripts/app_config/configure_campfire.shscripts/app_config/configure_stack_duo.shscripts/app_config/configure_stack_wallet.shscripts/ensure_test_app_config.shInspect captured patch +80 / −47
### lib/app_config.dart
@@ -1,6 +1,8 @@
// ignore: unused_import
import 'dart:io';
+import 'models/isar/models/ethereum/eth_contract.dart';
+import 'utilities/default_eth_tokens.dart';
import 'wallets/crypto_currency/crypto_currency.dart';
import 'wallets/crypto_currency/intermediate/frost_currency.dart';
@@ -28,6 +30,8 @@ abstract class AppConfig {
static List<CryptoCurrency> get coins => _supportedCoins;
+ static List<EthContract> get defaultEthTokens => _defaultEthTokens;
+
static ({String from, String fromFuzzyNet, String to, String toFuzzyNet})
get swapDefaults => _swapDefaults;
### lib/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart
@@ -15,6 +15,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:isar_community/isar.dart';
+import '../../../app_config.dart';
import '../../../db/isar/main_db.dart';
import '../../../models/isar/models/ethereum/eth_contract.dart';
import '../../../models/isar/models/solana/sol_contract.dart';
@@ -25,7 +26,6 @@ import '../../../providers/global/wallets_provider.dart';
import '../../../themes/stack_colors.dart';
import '../../../utilities/assets.dart';
import '../../../utilities/constants.dart';
-import '../../../utilities/default_eth_tokens.dart';
import '../../../utilities/default_sol_tokens.dart';
import '../../../utilities/text_styles.dart';
import '../../../utilities/util.dart';
@@ -268,7 +268,7 @@ class _EditWalletTokensViewState extends ConsumerState<EditWalletTokensView> {
.findAllSync();
if (contracts.isEmpty) {
- contracts.addAll(DefaultTokens.list);
+ contracts.addAll(AppConfig.defaultEthTokens);
MainDB.instance
.putEthContracts(contracts)
.then(
### lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart
@@ -28,7 +28,6 @@ import '../../../providers/providers.dart';
import '../../../themes/stack_colors.dart';
import '../../../utilities/assets.dart';
import '../../../utilities/constants.dart';
-import '../../../utilities/default_eth_tokens.dart';
import '../../../utilities/default_sol_tokens.dart';
import '../../../utilities/text_styles.dart';
import '../../../utilities/util.dart';
@@ -180,7 +179,7 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
MainDB.instance.getEthContracts().sortByName().findAllSync();
if (contracts.isEmpty) {
- contracts.addAll(DefaultTokens.list);
+ contracts.addAll(AppConfig.defaultEthTokens);
MainDB.instance
.putEthContracts(contracts)
.then(
### lib/pages/shopinbit/shopinbit_payment_shared.dart
@@ -20,9 +20,7 @@ import '../../widgets/background.dart';
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
import 'shopinbit_send_from_view.dart';
-final String kShopInBitUsdtContractAddress = DefaultTokens.list
- .firstWhere((t) => t.symbol == "USDT")
- .address;
+final String kShopInBitUsdtContractAddress = DefaultTokens.usdt.address;
// Address + amount pulled out of one of the API's payment_links entries.
class ShopInBitPaymentTarget {
### lib/utilities/default_eth_tokens.dart
@@ -11,41 +11,46 @@
import '../models/isar/models/ethereum/eth_contract.dart';
abstract class DefaultTokens {
- static List<EthContract> list = [
- EthContract(
- address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
- name: "USD Coin",
- symbol: "USDC",
- decimals: 6,
- type: EthContractType.erc20,
- ),
- EthContract(
- address: "0xdac17f958d2ee523a2206206994597c13d831ec7",
- name: "Tether",
- symbol: "USDT",
- decimals: 6,
- type: EthContractType.erc20,
- ),
- EthContract(
- address: "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce",
- name: "Shiba Inu",
- symbol: "SHIB",
- decimals: 18,
- type: EthContractType.erc20,
- ),
- EthContract(
- address: "0x514910771af9ca656af840dff83e8264ecf986ca",
- name: "Chainlink",
- symbol: "LINK",
- decimals: 18,
- type: EthContractType.erc20,
- ),
- EthContract(
- address: "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",
- name: "Uniswap",
- symbol: "UNI",
- decimals: 18,
- type: EthContractType.erc20,
- ),
- ];
+ static final usdc = EthContract(
+ address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
+ name: "USD Coin",
+ symbol: "USDC",
+ decimals: 6,
+ type: EthContractType.erc20,
+ );
+ static final usdt = EthContract(
+ address: "0xdac17f958d2ee523a2206206994597c13d831ec7",
+ name: "Tether",
+ symbol: "USDT",
+ decimals: 6,
+ type: EthContractType.erc20,
+ );
+ static final shib = EthContract(
+ address: "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce",
+ name: "Shiba Inu",
+ symbol: "SHIB",
+ decimals: 18,
+ type: EthContractType.erc20,
+ );
+ static final link = EthContract(
+ address: "0x514910771af9ca656af840dff83e8264ecf986ca",
+ name: "Chainlink",
+ symbol: "LINK",
+ decimals: 18,
+ type: EthContractType.erc20,
+ );
+ static final uni = EthContract(
+ address: "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984",
+ name: "Uniswap",
+ symbol: "UNI",
+ decimals: 18,
+ type: EthContractType.erc20,
+ );
+ static final rsFiro = EthContract(
+ address: "0x2744ea5ac9b11cb5e3cd63d3a88e858336aeddc2",
+ name: "rsFIRO",
+ symbol: "rsFIRO",
+ decimals: 8,
+ type: EthContractType.erc20,
+ );
}
### scripts/app_config/configure_campfire.sh
@@ -75,8 +75,15 @@ const ({String light, String dark})? _appIconAsset = (
final List<CryptoCurrency> _supportedCoins = List.unmodifiable([
Firo(CryptoCurrencyNetwork.main),
+ Ethereum(CryptoCurrencyNetwork.main),
]);
+final List<EthContract> _defaultEthTokens = [
+ DefaultTokens.rsFiro,
+ DefaultTokens.usdc,
+ DefaultTokens.usdt,
+];
+
final ({String from, String fromFuzzyNet, String to, String toFuzzyNet})
_swapDefaults = (
from: "BTC",
@@ -85,4 +92,4 @@ _swapDefaults = (
toFuzzyNet: "firo",
);
-EOF
\ No newline at end of file
+EOF
### scripts/app_config/configure_stack_duo.sh
@@ -80,6 +80,8 @@ final List<CryptoCurrency> _supportedCoins = List.unmodifiable([
BitcoinFrost(CryptoCurrencyNetwork.test4),
]);
+const List<EthContract> _defaultEthTokens = [];
+
final ({String from, String fromFuzzyNet, String to, String toFuzzyNet})
_swapDefaults = (
from: "BTC",
@@ -88,4 +90,4 @@ _swapDefaults = (
toFuzzyNet: "xmr",
);
-EOF
\ No newline at end of file
+EOF
### scripts/app_config/configure_stack_wallet.sh
@@ -135,6 +135,15 @@ final List<CryptoCurrency> _supportedCoins = List.unmodifiable([
Xelis(CryptoCurrencyNetwork.test),
]);
+final List<EthContract> _defaultEthTokens = [
+ DefaultTokens.usdc,
+ DefaultTokens.usdt,
+ DefaultTokens.shib,
+ DefaultTokens.link,
+ DefaultTokens.uni,
+ DefaultTokens.rsFiro,
+];
+
final ({String from, String fromFuzzyNet, String to, String toFuzzyNet})
_swapDefaults = (
from: "BTC",
@@ -143,4 +152,4 @@ _swapDefaults = (
toFuzzyNet: "xmr",
);
-EOF
\ No newline at end of file
+EOF
### scripts/ensure_test_app_config.sh
@@ -80,6 +80,15 @@ final List<CryptoCurrency> _supportedCoins = List.unmodifiable([
Xelis(CryptoCurrencyNetwork.test),
]);
+final List<EthContract> _defaultEthTokens = [
+ DefaultTokens.usdc,
+ DefaultTokens.usdt,
+ DefaultTokens.shib,
+ DefaultTokens.link,
+ DefaultTokens.uni,
+ DefaultTokens.rsFiro,
+];
+
final ({String from, String fromFuzzyNet, String to, String toFuzzyNet})
_swapDefaults = (
from: "BTC",Why this scored 19/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.