Merge branch 'staging' into masternode-operator-reward
What changed, and why it matters
This commit is a routine feature merge that adds support for a new Ethereum token called rsFIRO, updates some app configuration scripts, refreshes a privacy-related Git dependency, and fills in missing API-key placeholders for exchange integrations. There is no direct evidence in the diff of a security vulnerability, malicious code, or a fix for a disclosed security issue. The changes are mostly product/configuration plumbing.
Treat as a normal feature merge. Verify the new mobile_app_privacy dependency commit does not introduce unexpected behavior. Confirm the rsFIRO contract address and decimals match the official token specification before release. No security patch or incident response is indicated by the diff.
Security signals we found
Database migration inserts a hardcoded token contract if the app config includes it and the contract is not already present
External Git dependency mobile_app_privacy changed to a new commit; content of new commit not supplied
New exchange API key placeholders added (Trocador, LetsExchange, CypherGoat) in test/prebuild scripts
No security-relevant commit message or changelog text present
Evidence from the diff
The merge brings in: (1) a new default ERC-20 token rsFIRO (address 0x2744ea5ac9b11cb5e3cd63d3a88e858336aeddc2, decimals 8) with per-app SVG assets; (2) migration logic bumping the database version from 16 to 17 and conditionally inserting rsFIRO into the user’s token catalog; (3) app-config scripts now declare per-flavor default Ethereum token lists; (4) updates to the mobile_app_privacy Git dependency ref from c393e8d… to 0f07a82…; (5) addition of Trocador, LetsExchange, and CypherGoat API-key placeholders in CI/prebuild scripts. No cryptographic, authorization, or input-validation flaws are visible in the diff.
Changed components
lib/utilities/default_eth_tokens.dartlib/db/db_version_migration.dartlib/app_config.dartscripts/app_config/configure_*.shscripts/ensure_test_app_config.shpubspec.lock / scripts/app_config/templates/pubspec.template.yaml.github/workflows/test.yamlscripts/prebuild.shscripts/prebuild.ps1Inspect captured patch +500 / −305
### .github/workflows/test.yaml
@@ -60,6 +60,12 @@ jobs:
const kShopInBitPartnerSecret = "";
const kCakePayApiToken = "";
const kExolixApiKey = "";
+ const kLetsExchangeId = "";
+ const kLetsExchangeToken = "";
+ const kCypherGoatApiKey = "";
+ const kCypherGoatAffiliate = "";
+ const kTrocadorApiKey = "";
+ const kTrocadorRefCode = "";
EOF
fi
### .gitignore
@@ -106,9 +106,6 @@ pubspec.yaml
scripts/linux/build/libsecret/subprojects/gi-docgen/.meson-subproject-wrap-hash.txt
-crypto_plugins/cs_monero/built_outputs
-crypto_plugins/cs_monero/build
-crypto_plugins/*.diff
/devtools_options.yaml
# generated interfaces
### .gitmodules
[binary or diff unavailable]
### asset_sources/svg/campfire/rsfiro.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 254.12 254.12" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="56.64" x2="197.49" y1="43.13" y2="210.99"><stop offset="0" stop-color="#dd704f"/><stop offset="1" stop-color="#8f314f"/><stop offset="1" stop-color="#ff9a03"/></linearGradient><circle cx="127.06" cy="127.06" fill="#fff" r="127.06"/><path d="m28.53 174.99c-7.06-14.48-11.03-30.74-11.03-47.93 0-60.51 49.05-109.56 109.56-109.56 31.32 0 59.55 13.16 79.52 34.24h-51.46c-2.54 0-4.87 1.41-6.06 3.65l-30.58 57.97h-39.35c-3.77 0-6.85 3.08-6.85 6.85v13.69c0 3.77 3.08 6.85 6.85 6.85h24.9l-16.13 30.58c-1.19 2.25-3.52 3.65-6.06 3.65h-53.31zm143.75-95.86c-2.54 0-4.87 1.41-6.06 3.65l-16.13 30.58h24.9c3.77 0 6.85 3.08 6.85 6.85v13.69c0 3.77-3.08 6.85-6.85 6.85h-39.35l-30.58 57.97c-1.19 2.25-3.52 3.65-6.06 3.65h-51.46c19.97 21.07 48.2 34.24 79.52 34.24 60.51 0 109.56-49.05 109.56-109.56 0-17.19-3.97-33.45-11.03-47.93h-53.31z" fill="url(#a)"/><g fill="#fff"><path d="m45.95 47.64h11.48l.41 8.22h.82c1.09-3.36 2.82-5.69 5.18-7s5.52-1.96 9.48-1.96v11.3c-3.89 0-6.92.53-9.07 1.59s-3.69 2.77-4.61 5.13c-.92 2.37-1.38 5.63-1.38 9.8v20.54h-12.3v-47.61z"/><path d="m89.3 95.62c-3.28-.25-6.15-.56-8.61-.93v-8.87c5.06.56 10.04.84 14.96.84 2.6 0 4.53-.15 5.79-.47 1.26-.31 2.08-.81 2.46-1.49.37-.68.56-1.74.56-3.17s-.12-2.55-.36-3.17-.67-1.06-1.28-1.31-1.74-.47-3.38-.65l-10.56-1.31c-3.76-.43-6.3-1.71-7.64-3.83-1.33-2.12-2-5.48-2-10.08 0-3.86.77-6.82 2.31-8.87s3.67-3.47 6.41-4.25c2.73-.78 6.18-1.17 10.35-1.17 2.6 0 5.35.14 8.25.42s5.28.64 7.12 1.07v8.78c-4.58-.62-9.19-.93-13.84-.93-2.32 0-4.08.11-5.28.33s-2.05.69-2.56 1.4c-.51.72-.77 1.79-.77 3.22s.08 2.35.26 2.94c.17.59.55 1.01 1.13 1.26s1.59.47 3.02.65l10.66 1.21c2.32.31 4.18.84 5.59 1.59 1.4.75 2.49 2.07 3.28 3.97.78 1.9 1.18 4.65 1.18 8.26 0 4.05-.7 7.16-2.1 9.34s-3.38 3.66-5.94 4.43-5.89 1.17-9.99 1.17c-2.73 0-5.74-.13-9.02-.37z"/></g></svg>
\ No newline at end of file
### asset_sources/svg/stack_duo/rsfiro.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 254.12 254.12" xmlns="http://www.w3.org/2000/svg"/>
\ No newline at end of file
### asset_sources/svg/stack_wallet/rsfiro.svg
@@ -0,0 +1 @@
+<svg viewBox="0 0 254.12 254.12" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="56.64" x2="197.49" y1="43.13" y2="210.99"><stop offset="0" stop-color="#dd704f"/><stop offset="1" stop-color="#8f314f"/><stop offset="1" stop-color="#ff9a03"/></linearGradient><circle cx="127.06" cy="127.06" fill="#fff" r="127.06"/><path d="m28.53 174.99c-7.06-14.48-11.03-30.74-11.03-47.93 0-60.51 49.05-109.56 109.56-109.56 31.32 0 59.55 13.16 79.52 34.24h-51.46c-2.54 0-4.87 1.41-6.06 3.65l-30.58 57.97h-39.35c-3.77 0-6.85 3.08-6.85 6.85v13.69c0 3.77 3.08 6.85 6.85 6.85h24.9l-16.13 30.58c-1.19 2.25-3.52 3.65-6.06 3.65h-53.31zm143.75-95.86c-2.54 0-4.87 1.41-6.06 3.65l-16.13 30.58h24.9c3.77 0 6.85 3.08 6.85 6.85v13.69c0 3.77-3.08 6.85-6.85 6.85h-39.35l-30.58 57.97c-1.19 2.25-3.52 3.65-6.06 3.65h-51.46c19.97 21.07 48.2 34.24 79.52 34.24 60.51 0 109.56-49.05 109.56-109.56 0-17.19-3.97-33.45-11.03-47.93h-53.31z" fill="url(#a)"/><g fill="#fff"><path d="m45.95 47.64h11.48l.41 8.22h.82c1.09-3.36 2.82-5.69 5.18-7s5.52-1.96 9.48-1.96v11.3c-3.89 0-6.92.53-9.07 1.59s-3.69 2.77-4.61 5.13c-.92 2.37-1.38 5.63-1.38 9.8v20.54h-12.3v-47.61z"/><path d="m89.3 95.62c-3.28-.25-6.15-.56-8.61-.93v-8.87c5.06.56 10.04.84 14.96.84 2.6 0 4.53-.15 5.79-.47 1.26-.31 2.08-.81 2.46-1.49.37-.68.56-1.74.56-3.17s-.12-2.55-.36-3.17-.67-1.06-1.28-1.31-1.74-.47-3.38-.65l-10.56-1.31c-3.76-.43-6.3-1.71-7.64-3.83-1.33-2.12-2-5.48-2-10.08 0-3.86.77-6.82 2.31-8.87s3.67-3.47 6.41-4.25c2.73-.78 6.18-1.17 10.35-1.17 2.6 0 5.35.14 8.25.42s5.28.64 7.12 1.07v8.78c-4.58-.62-9.19-.93-13.84-.93-2.32 0-4.08.11-5.28.33s-2.05.69-2.56 1.4c-.51.72-.77 1.79-.77 3.22s.08 2.35.26 2.94c.17.59.55 1.01 1.13 1.26s1.59.47 3.02.65l10.66 1.21c2.32.31 4.18.84 5.59 1.59 1.4.75 2.49 2.07 3.28 3.97.78 1.9 1.18 4.65 1.18 8.26 0 4.05-.7 7.16-2.1 9.34s-3.38 3.66-5.94 4.43-5.89 1.17-9.99 1.17c-2.73 0-5.74-.13-9.02-.37z"/></g></svg>
\ No newline at end of file
### 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/db/db_version_migration.dart
@@ -24,6 +24,7 @@ import '../services/mixins/wallet_db.dart';
import '../services/wallets_service.dart';
import '../utilities/amount/amount.dart';
import '../utilities/constants.dart';
+import '../utilities/default_eth_tokens.dart';
import '../utilities/flutter_secure_storage_interface.dart';
import '../utilities/logger.dart';
import '../utilities/prefs.dart';
@@ -379,9 +380,8 @@ class DbVersionMigrator with WalletDB {
final mwcMigrationWalletsService = WalletsService();
final mwcMigrationWalletNames =
await mwcMigrationWalletsService.walletNames;
- final mwcIdentifier = Mimblewimblecoin(
- CryptoCurrencyNetwork.main,
- ).identifier;
+ final mwcIdentifier = Mimblewimblecoin(CryptoCurrencyNetwork.main)
+ .identifier;
for (final walletId in mwcMigrationWalletNames.keys) {
if (mwcMigrationWalletNames[walletId]!.coinIdentifier ==
mwcIdentifier) {
@@ -399,6 +399,30 @@ class DbVersionMigrator with WalletDB {
// try to continue migrating
return await migrate(16, secureStore: secureStore);
+ case 16:
+ if (AppConfig.defaultEthTokens.contains(DefaultTokens.rsFiro)) {
+ final db = MainDB.instance;
+ await db.initMainDB();
+ if (await db.getEthContracts().isNotEmpty() &&
+ await db
+ .getEthContracts()
+ .filter()
+ .addressEqualTo(
+ DefaultTokens.rsFiro.address,
+ caseSensitive: false,
+ )
+ .isEmpty()) {
+ await db.putEthContract(DefaultTokens.rsFiro);
+ }
+ }
+
+ await DB.instance.put<dynamic>(
+ boxName: DB.boxNameDBInfo,
+ key: "hive_data_version",
+ value: 17,
+ );
+ return await migrate(17, secureStore: secureStore);
+
default:
// finally return
return;
### 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';
@@ -100,11 +100,10 @@ class _EditWalletTokensViewState extends ConsumerState<EditWalletTokensView> {
}
Future<void> onNextPressed() async {
- final selectedTokens =
- tokenEntities
- .where((e) => e.selected)
- .map((e) => e.token.address)
- .toList();
+ final selectedTokens = tokenEntities
+ .where((e) => e.selected)
+ .map((e) => e.token.address)
+ .toList();
final wallet = ref.read(pWallets).getWallet(widget.walletId);
@@ -119,13 +118,11 @@ class _EditWalletTokensViewState extends ConsumerState<EditWalletTokensView> {
Navigator.of(context).pop(42);
} else {
if (isDesktop) {
- Navigator.of(
- context,
- ).popUntil(ModalRoute.withName(DesktopHomeView.routeName));
+ Navigator.of(context)
+ .popUntil(ModalRoute.withName(DesktopHomeView.routeName));
} else {
- await Navigator.of(
- context,
- ).pushNamedAndRemoveUntil(HomeView.routeName, (route) => false);
+ await Navigator.of(context)
+ .pushNamedAndRemoveUntil(HomeView.routeName, (route) => false);
}
if (mounted) {
unawaited(
@@ -160,9 +157,8 @@ class _EditWalletTokensViewState extends ConsumerState<EditWalletTokensView> {
),
);
} else {
- final result = await Navigator.of(
- context,
- ).pushNamed(AddCustomTokenView.routeName);
+ final result = await Navigator.of(context)
+ .pushNamed(AddCustomTokenView.routeName);
contract = result as EthContract?;
}
@@ -177,7 +173,9 @@ class _EditWalletTokensViewState extends ConsumerState<EditWalletTokensView> {
tokenEntities.add(
AddTokenListElementData(contract!)..selected = true,
);
- tokenEntities.sort((a, b) => a.token.name.compareTo(b.token.name));
+ tokenEntities.sort(
+ (a, b) => a.token.name.compareTo(b.token.name),
+ );
}
});
}
@@ -199,9 +197,7 @@ class _EditWalletTokensViewState extends ConsumerState<EditWalletTokensView> {
),
);
} else {
- final result = await Navigator.of(
- context,
- ).pushNamed(
+ final result = await Navigator.of(context).pushNamed(
AddCustomSolanaTokenView.routeName,
arguments: widget.walletId,
);
@@ -228,9 +224,7 @@ class _EditWalletTokensViewState extends ConsumerState<EditWalletTokensView> {
if (tokenEntities
.where((e) => e.token.address == token!.address)
.isEmpty) {
- tokenEntities.add(
- AddTokenListElementData(token!)..selected = true,
- );
+ tokenEntities.add(AddTokenListElementData(token!)..selected = true);
tokenEntities.sort((a, b) => a.token.name.compareTo(b.token.name));
}
});
@@ -268,7 +262,7 @@ class _EditWalletTokensViewState extends ConsumerState<EditWalletTokensView> {
.findAllSync();
if (contracts.isEmpty) {
- contracts.addAll(DefaultTokens.list);
+ contracts.addAll(AppConfig.defaultEthTokens);
MainDB.instance
.putEthContracts(contracts)
.then(
@@ -448,9 +442,8 @@ class _EditWalletTokensViewState extends ConsumerState<EditWalletTokensView> {
_searchTerm = value;
});
},
- style: STextStyles.desktopTextMedium(
- context,
- ).copyWith(height: 2),
+ style: STextStyles.desktopTextMedium(context)
+ .copyWith(height: 2),
decoration:
standardInputDecoration(
"Search",
@@ -516,9 +509,9 @@ class _EditWalletTokensViewState extends ConsumerState<EditWalletTokensView> {
} else {
return Background(
child: Scaffold(
- backgroundColor: Theme.of(
- context,
- ).extension<StackColors>()!.background,
+ backgroundColor: Theme.of(context)
+ .extension<StackColors>()!
+ .background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
@@ -533,14 +526,14 @@ class _EditWalletTokensViewState extends ConsumerState<EditWalletTokensView> {
child: AppBarIconButton(
size: 36,
shadows: const [],
- color: Theme.of(
- context,
- ).extension<StackColors>()!.background,
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .background,
icon: SvgPicture.asset(
Assets.svg.circlePlusFilled,
- color: Theme.of(
- context,
- ).extension<StackColors>()!.topNavIconPrimary,
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .topNavIconPrimary,
width: 20,
height: 20,
),
### lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart
@@ -20,6 +20,7 @@ import '../../../../services/exchange/change_now/change_now_exchange.dart';
import '../../../../services/exchange/exchange_data_loading_service.dart';
import '../../../../themes/stack_colors.dart';
import '../../../../utilities/assets.dart';
+import '../../../../utilities/default_eth_tokens.dart';
import '../../../../utilities/text_styles.dart';
import '../../../../utilities/util.dart';
import '../../../../widgets/app_icon.dart';
@@ -54,18 +55,14 @@ class _AddTokenListElementState extends ConsumerState<AddTokenListElement> {
super.initState();
ExchangeDataLoadingService.instance.isar.then((isar) async {
- final currency =
- await isar.currencies
- .where()
- .exchangeNameEqualTo(ChangeNowExchange.exchangeName)
- .filter()
- .tokenContractEqualTo(
- widget.data.token.address,
- caseSensitive: false,
- )
- .and()
- .imageIsNotEmpty()
- .findFirst();
+ final currency = await isar.currencies
+ .where()
+ .exchangeNameEqualTo(ChangeNowExchange.exchangeName)
+ .filter()
+ .tokenContractEqualTo(widget.data.token.address, caseSensitive: false)
+ .and()
+ .imageIsNotEmpty()
+ .findFirst();
if (mounted) {
WidgetsBinding.instance.addPostFrameCallback((_) {
@@ -86,50 +83,54 @@ class _AddTokenListElementState extends ConsumerState<AddTokenListElement> {
return RoundedWhiteContainer(
padding: EdgeInsets.all(isDesktop ? 16 : 12),
- borderColor:
- isDesktop
- ? Theme.of(context).extension<StackColors>()!.backgroundAppBar
- : null,
+ borderColor: isDesktop
+ ? Theme.of(context).extension<StackColors>()!.backgroundAppBar
+ : null,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
- currency != null
- ? SvgPicture.network(
- currency!.image,
- width: iconSize,
- height: iconSize,
- placeholderBuilder:
- (_) => AppIcon(width: iconSize, height: iconSize),
- )
- : AppIcon(width: iconSize, height: iconSize),
+ if (widget.data.token is EthContract &&
+ widget.data.token.address.toLowerCase() ==
+ DefaultTokens.rsFiro.address)
+ SvgPicture.asset(
+ Assets.svg.rsFiro,
+ width: iconSize,
+ height: iconSize,
+ )
+ else
+ currency != null
+ ? SvgPicture.network(
+ currency!.image,
+ width: iconSize,
+ height: iconSize,
+ placeholderBuilder: (_) =>
+ AppIcon(width: iconSize, height: iconSize),
+ )
+ : AppIcon(width: iconSize, height: iconSize),
const SizedBox(width: 12),
ConditionalParent(
condition: isDesktop,
- builder:
- (child) => Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- child,
- const SizedBox(height: 2),
- Text(
- widget.data.token.symbol,
- style: STextStyles.desktopTextExtraExtraSmall(
- context,
- ),
- overflow: TextOverflow.ellipsis,
- ),
- ],
+ builder: (child) => Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ child,
+ const SizedBox(height: 2),
+ Text(
+ widget.data.token.symbol,
+ style: STextStyles.desktopTextExtraExtraSmall(context),
+ overflow: TextOverflow.ellipsis,
),
+ ],
+ ),
child: Text(
isDesktop
? mainLabel
: "$mainLabel (${widget.data.token.symbol})",
- style:
- isDesktop
- ? STextStyles.desktopTextSmall(context)
- : STextStyles.w600_14(context),
+ style: isDesktop
+ ? STextStyles.desktopTextSmall(context)
+ : STextStyles.w600_14(context),
overflow: TextOverflow.ellipsis,
),
),
@@ -138,21 +139,20 @@ class _AddTokenListElementState extends ConsumerState<AddTokenListElement> {
const SizedBox(width: 4),
isDesktop
? Checkbox(
- value: widget.data.selected,
- onChanged:
- (newValue) =>
- setState(() => widget.data.selected = newValue!),
- )
+ value: widget.data.selected,
+ onChanged: (newValue) =>
+ setState(() => widget.data.selected = newValue!),
+ )
: SizedBox(
- height: 20,
- width: 40,
- child: DraggableSwitchButton(
- isOn: widget.data.selected,
- onValueChanged: (newValue) {
- widget.data.selected = newValue;
- },
+ height: 20,
+ width: 40,
+ child: DraggableSwitchButton(
+ isOn: widget.data.selected,
+ onValueChanged: (newValue) {
+ widget.data.selected = newValue;
+ },
+ ),
),
- ),
],
),
);
### 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';
@@ -103,17 +102,15 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
if (isDesktop) {
contract = await showDialog(
context: context,
- builder:
- (context) => const DesktopDialog(
- maxWidth: 580,
- maxHeight: 500,
- child: AddCustomTokenView(),
- ),
+ builder: (context) => const DesktopDialog(
+ maxWidth: 580,
+ maxHeight: 500,
+ child: AddCustomTokenView(),
+ ),
);
} else {
- contract = await Navigator.of(
- context,
- ).pushNamed(AddCustomTokenView.routeName);
+ contract = await Navigator.of(context)
+ .pushNamed(AddCustomTokenView.routeName);
}
if (contract != null) {
@@ -137,17 +134,15 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
if (isDesktop) {
token = await showDialog(
context: context,
- builder:
- (context) => const DesktopDialog(
- maxWidth: 580,
- maxHeight: 500,
- child: AddCustomSolanaTokenView(),
- ),
+ builder: (context) => const DesktopDialog(
+ maxWidth: 580,
+ maxHeight: 500,
+ child: AddCustomSolanaTokenView(),
+ ),
);
} else {
- token = await Navigator.of(
- context,
- ).pushNamed(AddCustomSolanaTokenView.routeName);
+ token = await Navigator.of(context)
+ .pushNamed(AddCustomSolanaTokenView.routeName);
}
if (token != null) {
@@ -176,11 +171,13 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
}
if (AppConfig.coins.whereType<Ethereum>().isNotEmpty) {
- final contracts =
- MainDB.instance.getEthContracts().sortByName().findAllSync();
+ final contracts = MainDB.instance
+ .getEthContracts()
+ .sortByName()
+ .findAllSync();
if (contracts.isEmpty) {
- contracts.addAll(DefaultTokens.list);
+ contracts.addAll(AppConfig.defaultEthTokens);
MainDB.instance
.putEthContracts(contracts)
.then(
@@ -274,60 +271,60 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
_searchTerm = value;
});
},
- style: STextStyles.desktopTextMedium(
- context,
- ).copyWith(height: 2),
- decoration: standardInputDecoration(
- "Search",
- _searchFocusNode,
- context,
- ).copyWith(
- contentPadding: const EdgeInsets.symmetric(
- vertical: 10,
- ),
- prefixIcon: Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: 16,
- // vertical: 20,
- ),
- child: SvgPicture.asset(
- Assets.svg.search,
- width: 24,
- height: 24,
- color:
- Theme.of(context)
+ style: STextStyles.desktopTextMedium(context)
+ .copyWith(height: 2),
+ decoration:
+ standardInputDecoration(
+ "Search",
+ _searchFocusNode,
+ context,
+ ).copyWith(
+ contentPadding: const EdgeInsets.symmetric(
+ vertical: 10,
+ ),
+ prefixIcon: Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 16,
+ // vertical: 20,
+ ),
+ child: SvgPicture.asset(
+ Assets.svg.search,
+ width: 24,
+ height: 24,
+ color: Theme.of(context)
.extension<StackColors>()!
.textFieldDefaultSearchIconLeft,
- ),
- ),
- suffixIcon:
- _searchFieldController.text.isNotEmpty
+ ),
+ ),
+ suffixIcon:
+ _searchFieldController.text.isNotEmpty
? Padding(
- padding: const EdgeInsets.only(
- right: 10,
- ),
- child: UnconstrainedBox(
- child: Row(
- children: [
- TextFieldIconButton(
- child: const XIcon(
- width: 24,
- height: 24,
+ padding: const EdgeInsets.only(
+ right: 10,
+ ),
+ child: UnconstrainedBox(
+ child: Row(
+ children: [
+ TextFieldIconButton(
+ child: const XIcon(
+ width: 24,
+ height: 24,
+ ),
+ onTap: () async {
+ setState(() {
+ _searchFieldController
+ .text =
+ "";
+ _searchTerm = "";
+ });
+ },
),
- onTap: () async {
- setState(() {
- _searchFieldController
- .text = "";
- _searchTerm = "";
- });
- },
- ),
- ],
+ ],
+ ),
),
- ),
- )
+ )
: null,
- ),
+ ),
),
),
),
@@ -365,7 +362,10 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
if (solTokenEntities.isNotEmpty)
ExpandingSubListItem(
title: "Solana tokens",
- entities: filter(_searchTerm, solTokenEntities),
+ entities: filter(
+ _searchTerm,
+ solTokenEntities,
+ ),
initialState: ExpandableState.expanded,
animationDurationMultiplier: 0.5,
trailing: AddCustomTokenSelector(
@@ -395,8 +395,9 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
} else {
return Background(
child: Scaffold(
- backgroundColor:
- Theme.of(context).extension<StackColors>()!.background,
+ backgroundColor: Theme.of(context)
+ .extension<StackColors>()!
+ .background,
appBar: AppBar(
leading: AppBarBackButton(
onPressed: () {
@@ -419,58 +420,61 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
Constants.size.circularBorderRadius,
),
child: Semantics(
- label:
- "Search Text Field. Inputs Text To Search In Wallets.",
+ label: "Search Text Field. Inputs Text To Search In Wallets.",
excludeSemantics: true,
child: TextField(
autofocus: isDesktop,
autocorrect: !isDesktop,
enableSuggestions: !isDesktop,
controller: _searchFieldController,
focusNode: _searchFocusNode,
- onChanged:
- (value) => setState(() => _searchTerm = value),
+ onChanged: (value) =>
+ setState(() => _searchTerm = value),
style: STextStyles.field(context),
- decoration: standardInputDecoration(
- "Search",
- _searchFocusNode,
- context,
- desktopMed: isDesktop,
- ).copyWith(
- prefixIcon: Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: 10,
- vertical: 16,
- ),
- child: SvgPicture.asset(
- Assets.svg.search,
- width: 16,
- height: 16,
- ),
- ),
- suffixIcon:
- _searchFieldController.text.isNotEmpty
+ decoration:
+ standardInputDecoration(
+ "Search",
+ _searchFocusNode,
+ context,
+ desktopMed: isDesktop,
+ ).copyWith(
+ prefixIcon: Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 10,
+ vertical: 16,
+ ),
+ child: SvgPicture.asset(
+ Assets.svg.search,
+ width: 16,
+ height: 16,
+ ),
+ ),
+ suffixIcon:
+ _searchFieldController.text.isNotEmpty
? Padding(
- padding: const EdgeInsets.only(right: 0),
- child: UnconstrainedBox(
- child: Row(
- children: [
- TextFieldIconButton(
- child: const XIcon(),
- onTap: () async {
- setState(() {
- _searchFieldController.text =
- "";
- _searchTerm = "";
- });
- },
- ),
- ],
+ padding: const EdgeInsets.only(
+ right: 0,
+ ),
+ child: UnconstrainedBox(
+ child: Row(
+ children: [
+ TextFieldIconButton(
+ child: const XIcon(),
+ onTap: () async {
+ setState(() {
+ _searchFieldController
+ .text =
+ "";
+ _searchTerm = "";
+ });
+ },
+ ),
+ ],
+ ),
),
- ),
- )
+ )
: null,
- ),
+ ),
),
),
),
### lib/pages/add_wallet_views/add_wallet_view/sub_widgets/coin_select_item.dart
@@ -26,6 +26,7 @@ import '../../../../themes/coin_icon_provider.dart';
import '../../../../themes/stack_colors.dart';
import '../../../../utilities/assets.dart';
import '../../../../utilities/constants.dart';
+import '../../../../utilities/default_eth_tokens.dart';
import '../../../../utilities/text_styles.dart';
import '../../../../utilities/util.dart';
import '../../../../widgets/app_icon.dart';
@@ -137,40 +138,46 @@ class _CoinSelectItemState extends ConsumerState<CoinSelectItem> {
constraints: BoxConstraints(minHeight: isDesktop ? 70 : 0),
child: Row(
children: [
- tokenImageUri != null
- ? tokenImageUri!.toLowerCase().endsWith(".svg")
- ? SvgPicture.network(
- tokenImageUri!,
- width: 26,
- height: 26,
- placeholderBuilder: (_) =>
- const AppIcon(width: 26, height: 26),
- )
- : Image.network(
- tokenImageUri!,
- width: 26,
- height: 26,
- errorBuilder: (_, _, _) => SvgPicture.file(
- File(
- ref.watch(
- coinIconProvider(
- widget.entity.cryptoCurrency,
+ if (widget.entity is EthTokenEntity &&
+ (widget.entity as EthTokenEntity).token.address
+ .toLowerCase() ==
+ DefaultTokens.rsFiro.address)
+ SvgPicture.asset(Assets.svg.rsFiro, width: 26, height: 26)
+ else
+ tokenImageUri != null
+ ? tokenImageUri!.toLowerCase().endsWith(".svg")
+ ? SvgPicture.network(
+ tokenImageUri!,
+ width: 26,
+ height: 26,
+ placeholderBuilder: (_) =>
+ const AppIcon(width: 26, height: 26),
+ )
+ : Image.network(
+ tokenImageUri!,
+ width: 26,
+ height: 26,
+ errorBuilder: (_, _, _) => SvgPicture.file(
+ File(
+ ref.watch(
+ coinIconProvider(
+ widget.entity.cryptoCurrency,
+ ),
),
),
+ width: 26,
+ height: 26,
),
- width: 26,
- height: 26,
- ),
- )
- : SvgPicture.file(
- File(
- ref.watch(
- coinIconProvider(widget.entity.cryptoCurrency),
+ )
+ : SvgPicture.file(
+ File(
+ ref.watch(
+ coinIconProvider(widget.entity.cryptoCurrency),
+ ),
),
+ width: 26,
+ height: 26,
),
- width: 26,
- height: 26,
- ),
SizedBox(width: isDesktop ? 12 : 10),
Text(
"${widget.entity.name} (${widget.entity.ticker})",
@@ -187,9 +194,9 @@ class _CoinSelectItemState extends ConsumerState<CoinSelectItem> {
height: 24,
child: SvgPicture.asset(
Assets.svg.check,
- color: Theme.of(
- context,
- ).extension<StackColors>()!.accentColorDark,
+ color: Theme.of(context)
+ .extension<StackColors>()!
+ .accentColorDark,
),
),
),
### 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/services/exchange/trocador/trocador_api.dart
@@ -318,8 +318,7 @@ abstract class TrocadorAPI {
String error = map["error"] as String? ?? json.toString();
if (error ==
"trade could not be generated, some unknown error happened") {
- error =
- "This trade couldn't be completed. Please select another provider.";
+ error = "This trade couldn't be completed. Please select another provider.";
}
Logging.instance.e(
### lib/utilities/assets.dart
@@ -117,6 +117,8 @@ class _SVG {
final coinControl = const _COIN_CONTROL();
+ String get rsFiro => "assets/svg/rsfiro.svg";
+
String get peers => "assets/svg/peers.svg";
String get fusing => "assets/svg/fusing.svg";
String get upFromLine => "assets/svg/up-from-line.svg";
### lib/utilities/constants.dart
@@ -40,7 +40,7 @@ abstract class Constants {
// Enable Logger.print statements
static const bool disableLogger = false;
- static const int currentDataVersion = 16;
+ static const int currentDataVersion = 17;
static const int rescanV1 = 1;
### 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,
+ );
}
### lib/widgets/icon_widgets/eth_token_icon.dart
@@ -19,6 +19,8 @@ import '../../models/isar/exchange_cache/currency.dart';
import '../../services/exchange/change_now/change_now_exchange.dart';
import '../../services/exchange/exchange_data_loading_service.dart';
import '../../themes/coin_icon_provider.dart';
+import '../../utilities/assets.dart';
+import '../../utilities/default_eth_tokens.dart';
import '../../wallets/crypto_currency/crypto_currency.dart';
import '../loading_indicator.dart';
@@ -38,10 +40,13 @@ class EthTokenIcon extends ConsumerStatefulWidget {
class _EthTokenIconState extends ConsumerState<EthTokenIcon> {
String? imageUrl;
+ bool get _isRsFiro =>
+ widget.contractAddress.toLowerCase() == DefaultTokens.rsFiro.address;
@override
void initState() {
super.initState();
+ if (_isRsFiro) return;
ExchangeDataLoadingService.instance.isar.then((isar) async {
final currency = await isar.currencies
@@ -67,6 +72,14 @@ class _EthTokenIconState extends ConsumerState<EthTokenIcon> {
@override
Widget build(BuildContext context) {
+ if (_isRsFiro) {
+ return SvgPicture.asset(
+ Assets.svg.rsFiro,
+ width: widget.size,
+ height: widget.size,
+ );
+ }
+
if (imageUrl == null || imageUrl!.isEmpty) {
return SvgPicture.file(
File(ref.watch(coinIconProvider(Ethereum(.main)))),
### pubspec.lock
@@ -1631,8 +1631,8 @@ packages:
dependency: "direct main"
description:
path: "."
- ref: c393e8d1be93cce013f53fe16ec44080b2fdefdd
- resolved-ref: c393e8d1be93cce013f53fe16ec44080b2fdefdd
+ ref: "0f07a82569854c1830a66ae86daa9308b382617a"
+ resolved-ref: "0f07a82569854c1830a66ae86daa9308b382617a"
url: "https://github.com/cypherstack/mobile_app_privacy"
source: git
version: "0.0.4"
### 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/app_config/templates/pubspec.template.yaml
@@ -272,7 +272,7 @@ dependencies:
mobile_app_privacy:
git:
url: https://github.com/cypherstack/mobile_app_privacy
- ref: c393e8d1be93cce013f53fe16ec44080b2fdefdd
+ ref: 0f07a82569854c1830a66ae86daa9308b382617a
# required for web3dart to use EthereumAddress class...
wallet: 0.0.18
### 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",
### scripts/prebuild.ps1
@@ -2,9 +2,27 @@
$KEYS = "..\lib\external_api_keys.dart"
if (-not (Test-Path $KEYS)) {
Write-Host "prebuild.ps1: creating template lib/external_api_keys.dart file"
- "const kChangeNowApiKey = '';" + "`nconst kSimpleSwapApiKey = '';" + "`nconst kNanswapApiKey = '';" + "`nconst kNanoSwapRpcApiKey = '';" + "`nconst kWizSwapApiKey = '';" + "`nconst kShopInBitAccessKey = '';" + "`nconst kShopInBitPartnerSecret = '';" + "`nconst kCakePayApiToken = '';" + "`nconst kExolixApiKey = '';" + "`nconst kLetsExchangeId = '';" + "`nconst kLetsExchangeToken = '';" + "`nconst kCypherGoatApiKey = '';" + "`nconst kCypherGoatAffiliate = '';" | Out-File $KEYS -Encoding UTF8
+
+ @'
+const String kChangeNowApiKey = "";
+const String kSimpleSwapApiKey = "";
+const String kNanswapApiKey = "";
+const String kNanoSwapRpcApiKey = "";
+const String kWizSwapApiKey = "";
+const kShopInBitAccessKey = "";
+const kShopInBitPartnerSecret = "";
+const kCakePayApiToken = "";
+const kExolixApiKey = "";
+const kLetsExchangeId = "";
+const kLetsExchangeToken = "";
+const kCypherGoatApiKey = "";
+const kCypherGoatAffiliate = "";
+const kTrocadorApiKey = "";
+const kTrocadorRefCode = "";
+'@ | Out-File $KEYS -Encoding UTF8
}
+
# Create template wallet test parameter files if they don't already exist
$coins = @("bitcoin", "bitcoincash", "dogecoin", "namecoin", "firo", "particl") # TODO add monero and wownero when those tests are updated to use the .gitignored test wallet setup: when doing that, make sure to update the test vectors for a new, private development seed
### scripts/prebuild.sh
@@ -4,7 +4,23 @@
KEYS=../lib/external_api_keys.dart
if ! test -f "$KEYS"; then
echo 'prebuild.sh: creating template lib/external_api_keys.dart file'
- printf 'const kChangeNowApiKey = "";\nconst kSimpleSwapApiKey = "";\nconst kNanswapApiKey = "";\nconst kNanoSwapRpcApiKey = "";\nconst kWizSwapApiKey = "";\nconst kShopInBitAccessKey = "";\nconst kShopInBitPartnerSecret = "";\nconst kCakePayApiToken = "";\nconst kExolixApiKey = "";\nconst kLetsExchangeId = "";\nconst kLetsExchangeToken = "";\nconst kCypherGoatApiKey = "";\nconst kCypherGoatAffiliate = "";\n' > $KEYS
+ cat << 'EOF' > "$KEYS"
+const kChangeNowApiKey = "";
+const kSimpleSwapApiKey = "";
+const kNanswapApiKey = "";
+const kNanoSwapRpcApiKey = "";
+const kWizSwapApiKey = "";
+const kShopInBitAccessKey = "";
+const kShopInBitPartnerSecret = "";
+const kCakePayApiToken = "";
+const kExolixApiKey = "";
+const kLetsExchangeId = "";
+const kLetsExchangeToken = "";
+const kCypherGoatApiKey = "";
+const kCypherGoatAffiliate = "";
+const kTrocadorApiKey = "";
+const kTrocadorRefCode = "";
+EOF
fi
# Create template wallet test parameter files if they don't already exist
### test/db_version_migration_test.dart
@@ -1,14 +1,61 @@
-// import 'package:flutter_test/flutter_test.dart';
-// import 'package:hive_test/hive_test.dart';
+import 'dart:io';
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:isar_community/isar.dart';
+import 'package:stackwallet/db/db_version_migration.dart';
+import 'package:stackwallet/db/hive/db.dart';
+import 'package:stackwallet/db/isar/main_db.dart';
+import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
+import 'package:stackwallet/utilities/constants.dart';
+import 'package:stackwallet/utilities/default_eth_tokens.dart';
+import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart';
+
+import 'hive/hive_ce_test_utils.dart';
void main() {
- // setUp(() async {
- // await setUpTestHive();
- // });
- //
- // // no migration to test yet
- //
- // tearDown(() async {
- // await tearDownTestHive();
- // });
+ test('v17 adds rsFIRO once and preserves the token catalog', () async {
+ await setUpHiveCeTest();
+ addTearDown(tearDownHiveCeTest);
+ final info = await DB.instance.hive.openBox<dynamic>(DB.boxNameDBInfo);
+ final directory = await Directory.systemTemp.createTemp(
+ 'rsfiro_migration_',
+ );
+ addTearDown(() => directory.delete(recursive: true));
+ await Isar.initializeIsarCore(download: true);
+ final isar = await Isar.open(
+ [EthContractSchema],
+ directory: directory.path,
+ inspector: false,
+ );
+ addTearDown(() => isar.close(deleteFromDisk: true));
+ final db = MainDB.instance;
+ await db.initMainDB(mock: isar);
+ Future<void> migrate(int version) =>
+ DbVersionMigrator().migrate(version, secureStore: FakeSecureStorage());
+
+ await migrate(16);
+ expect(Constants.currentDataVersion, 17);
+ expect(info.get('hive_data_version'), 17);
+ expect(await db.getEthContracts().isEmpty(), isTrue);
+
+ final custom = DefaultTokens.usdc.copyWith(name: 'Custom token');
+ await db.putEthContract(custom);
+ await migrate(16);
+ expect(await db.getEthContracts().count(), 2);
+ expect((await db.getEthContract(custom.address))!.name, 'Custom token');
+
+ final existing = (await db.getEthContract(DefaultTokens.rsFiro.address))!
+ .copyWith(
+ address: DefaultTokens.rsFiro.address.toUpperCase(),
+ abi: 'keep',
+ );
+ await db.putEthContract(existing);
+ await migrate(16);
+ expect(await db.getEthContracts().count(), 2);
+ expect((await db.getEthContract(existing.address))!.abi, 'keep');
+
+ await isar.writeTxn(() => isar.ethContracts.delete(existing.id));
+ await migrate(info.get('hive_data_version') as int);
+ expect(await db.getEthContracts().count(), 1);
+ });
}
### test/widgets/eth_token_icon_test.dart
@@ -0,0 +1,32 @@
+import 'package:flutter/widgets.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:stackwallet/utilities/assets.dart';
+import 'package:stackwallet/utilities/default_eth_tokens.dart';
+import 'package:stackwallet/widgets/icon_widgets/eth_token_icon.dart';
+
+void main() {
+ testWidgets('rsFIRO uses the per-app SVG by contract address', (
+ tester,
+ ) async {
+ final token = DefaultTokens.rsFiro;
+ expect(token.address, '0x2744ea5ac9b11cb5e3cd63d3a88e858336aeddc2');
+ expect(token.decimals, 8);
+ await tester.pumpWidget(
+ ProviderScope(
+ child: Center(
+ child: EthTokenIcon(
+ contractAddress: token.address.toUpperCase(),
+ size: 26,
+ ),
+ ),
+ ),
+ );
+ await tester.pumpAndSettle();
+ final svg = tester.widget<SvgPicture>(find.byType(SvgPicture));
+ expect((svg.bytesLoader as SvgAssetLoader).assetName, Assets.svg.rsFiro);
+ expect(tester.getSize(find.byType(SvgPicture)), const Size.square(26));
+ expect(tester.takeException(), isNull);
+ });
+}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.