mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-27 06:18:50 +01:00
Add tbb to solana default tokens (#3461)
* fix android CI * deat: add The Bitcoin Bull to solana default tokens list * add verification status for solana tokens
This commit is contained in:
@@ -635,6 +635,9 @@ Future<void> defaultSettingsMigration(
|
||||
enabled: false,
|
||||
);
|
||||
break;
|
||||
case 70:
|
||||
await _addTbbTokenToExistingSolanaWallets();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -1325,3 +1328,40 @@ Future<void> _addXaut0TokenToExistingSolanaWallets() async {
|
||||
printV('Error in XAUT0 migration: $e');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _addTbbTokenToExistingSolanaWallets() async {
|
||||
try {
|
||||
final tbbToken = SPLToken(
|
||||
name: "The Bitcoin Bull",
|
||||
symbol: "TBB",
|
||||
mintAddress: "42cXQvAAr7hcPBPWAS4ocVtDyeJ4Fa6gRR2uG4gppump",
|
||||
decimal: 6,
|
||||
mint: "tbb",
|
||||
enabled: false,
|
||||
iconPath: "assets/images/tbb_icon.png",
|
||||
);
|
||||
|
||||
final allWallets = await WalletInfo.getAll();
|
||||
|
||||
final solanaWallets = allWallets.where((wallet) => wallet.type == WalletType.solana).toList();
|
||||
|
||||
for (final walletInfo in solanaWallets) {
|
||||
final sanitizedName = walletInfo.name.replaceAll(" ", "_");
|
||||
final boxName = "${sanitizedName}_${SPLToken.boxName}";
|
||||
|
||||
Box<SPLToken> tokenBox;
|
||||
if (CakeHive.isBoxOpen(boxName)) {
|
||||
tokenBox = CakeHive.box<SPLToken>(boxName);
|
||||
} else {
|
||||
tokenBox = await CakeHive.openBox<SPLToken>(boxName);
|
||||
}
|
||||
|
||||
final tbbAddress = tbbToken.mintAddress;
|
||||
if (!tokenBox.containsKey(tbbAddress)) {
|
||||
await tokenBox.put(tbbAddress, tbbToken);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
printV("Error in TBB migration: $e");
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -316,7 +316,7 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
|
||||
payjoinSessionSource: payjoinSessionSource,
|
||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||
havenSeedStore: havenSeedStore,
|
||||
initialMigrationVersion: 69,
|
||||
initialMigrationVersion: 70,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -116,6 +116,12 @@ class CWSolana extends Solana {
|
||||
return await solanaWallet.getSPLToken(mintAddress);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool?> isTokenVerifiedOnJupiter(WalletBase wallet, String mintAddress) {
|
||||
final solanaWallet = wallet as SolanaWallet;
|
||||
return solanaWallet.isTokenVerifiedOnJupiter(mintAddress);
|
||||
}
|
||||
|
||||
@override
|
||||
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction) {
|
||||
if (transaction.amount.currency.symbol == CryptoCurrency.sol.symbol) {
|
||||
|
||||
@@ -40,13 +40,11 @@ class EditTokenPage extends BasePage {
|
||||
: S.current.add_token;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return EditTokenPageBody(
|
||||
Widget body(BuildContext context) => EditTokenPageBody(
|
||||
homeSettingsViewModel: homeSettingsViewModel,
|
||||
token: token,
|
||||
initialContractAddress: initialContractAddress,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EditTokenPageBody extends StatefulWidget {
|
||||
@@ -83,6 +81,11 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||
bool _disclaimerChecked = false;
|
||||
bool isEditingToken = false;
|
||||
bool _isTokenVerified = false;
|
||||
bool _isJupiterVerified = false;
|
||||
String? _jupiterVerifiedAddress;
|
||||
|
||||
bool get _isJupiterVerifiedForCurrentAddress =>
|
||||
_isJupiterVerified && _jupiterVerifiedAddress == _contractAddressController.text;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -129,14 +132,28 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||
final isVerified = widget.homeSettingsViewModel.checkIfTokenIsWhitelisted(contractAddress);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
setState(() {
|
||||
_isTokenVerified = isVerified;
|
||||
});
|
||||
|
||||
_checkIfTokenIsVerifiedOnJupiter(contractAddress);
|
||||
}
|
||||
|
||||
Future<void> _checkIfTokenIsVerifiedOnJupiter(String contractAddress) async {
|
||||
final isVerified =
|
||||
await widget.homeSettingsViewModel.checkIfTokenIsVerifiedOnJupiter(contractAddress);
|
||||
|
||||
if (!mounted || _contractAddressController.text != contractAddress) return;
|
||||
|
||||
setState(() {
|
||||
_isJupiterVerified = isVerified ?? false;
|
||||
_jupiterVerifiedAddress = contractAddress;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
Widget build(BuildContext context) => GestureDetector(
|
||||
onTap: () => FocusScope.of(context).unfocus(),
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
@@ -174,8 +191,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||
SizedBox(height: 20),
|
||||
],
|
||||
Observer(
|
||||
builder: (context) {
|
||||
return Row(
|
||||
builder: (context) => Row(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: LoadingPrimaryButton(
|
||||
@@ -210,14 +226,12 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||
if (isTokenAlreadyAdded) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertWithOneAction(
|
||||
builder: (dialogContext) => AlertWithOneAction(
|
||||
alertTitle: S.current.warning,
|
||||
alertContent: S.of(context).token_already_exists,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(dialogContext).pop(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -225,13 +239,15 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||
final isWhitelisted = await widget.homeSettingsViewModel
|
||||
.checkIfTokenIsWhitelisted(_contractAddressController.text);
|
||||
|
||||
final hasPotentialError = !isWhitelisted &&
|
||||
final isTrusted = isWhitelisted || _isJupiterVerifiedForCurrentAddress;
|
||||
|
||||
final hasPotentialError = !isTrusted &&
|
||||
await widget.homeSettingsViewModel
|
||||
.checkIfERC20TokenContractAddressIsAPotentialScamAddress(
|
||||
_contractAddressController.text,
|
||||
);
|
||||
|
||||
bool isPotentialScam = hasPotentialError && !isWhitelisted;
|
||||
bool isPotentialScam = hasPotentialError && !isTrusted;
|
||||
|
||||
// Normalize to catch homoglyph spoofing attacks
|
||||
final tokenSymbol = normalizeHomoglyphs(
|
||||
@@ -243,7 +259,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||
// (e.g. fake ETH on Ethereum, fake SOL on Solana)
|
||||
final nativeSymbol =
|
||||
widget.homeSettingsViewModel.nativeToken.title.toUpperCase();
|
||||
if (tokenSymbol == nativeSymbol && !isWhitelisted) {
|
||||
if (tokenSymbol == nativeSymbol && !isTrusted) {
|
||||
isPotentialScam = true;
|
||||
}
|
||||
|
||||
@@ -252,7 +268,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||
if (widget.homeSettingsViewModel.checkIfTokenSymbolMatchesDefaultToken(
|
||||
tokenSymbol,
|
||||
) &&
|
||||
!isWhitelisted) {
|
||||
!isTrusted) {
|
||||
isPotentialScam = true;
|
||||
}
|
||||
|
||||
@@ -277,23 +293,20 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||
} catch (e) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertWithOneAction(
|
||||
builder: (dialogContext) => AlertWithOneAction(
|
||||
alertTitle: S.current.warning,
|
||||
alertContent: e.toString(),
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(dialogContext).pop(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (hasPotentialError && !isWhitelisted) {
|
||||
if (hasPotentialError && !isTrusted) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertWithTwoActions(
|
||||
builder: (dialogContext) => AlertWithTwoActions(
|
||||
alertTitle: S.current.warning,
|
||||
alertContent: S.current.contract_warning,
|
||||
rightButtonText: S.of(context).continue_text,
|
||||
@@ -303,8 +316,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||
await actionCall();
|
||||
},
|
||||
actionLeftButton: () => Navigator.of(dialogContext).pop(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
@@ -312,14 +324,12 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||
} catch (e) {
|
||||
showPopUp<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertWithOneAction(
|
||||
builder: (dialogContext) => AlertWithOneAction(
|
||||
alertTitle: "Unable to add token",
|
||||
alertContent: "$e",
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () => Navigator.of(context).pop(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
if (mounted) {
|
||||
@@ -334,14 +344,12 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _getTokenInfo() async {
|
||||
if (_contractAddressController.text.isNotEmpty) {
|
||||
@@ -377,8 +385,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _tokenForm() {
|
||||
return Form(
|
||||
Widget _tokenForm() => Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -402,7 +409,7 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||
size: 75,
|
||||
),
|
||||
),
|
||||
if (_isTokenVerified)
|
||||
if (_isTokenVerified || _isJupiterVerifiedForCurrentAddress)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
@@ -425,6 +432,16 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_isJupiterVerifiedForCurrentAddress) ...[
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
S.of(context).token_verified_on_jupiter,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
SizedBox(height: 25),
|
||||
AddressTextField(
|
||||
controller: _contractAddressController,
|
||||
@@ -517,5 +534,4 @@ class _EditTokenPageBodyState extends State<EditTokenPageBody> {
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,6 +408,14 @@ abstract class HomeSettingsViewModelBase with Store {
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<bool?> checkIfTokenIsVerifiedOnJupiter(String mintAddress) async {
|
||||
if (_balanceViewModel.wallet.type != WalletType.solana) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return solana!.isTokenVerifiedOnJupiter(_balanceViewModel.wallet, mintAddress);
|
||||
}
|
||||
|
||||
CryptoCurrency get nativeToken => _balanceViewModel.wallet.currency;
|
||||
|
||||
void _updateFiatPrices(CryptoCurrency token) async {
|
||||
|
||||
Reference in New Issue
Block a user