use database contract data and contract management updates

This commit is contained in:
julian 2023-03-02 18:40:12 -06:00
parent a5d8fdde79
commit babbd75da3
23 changed files with 504 additions and 256 deletions

View file

@ -358,4 +358,20 @@ class MainDB {
throw MainDBException("failed addNewTransactionData", e);
}
}
// ========== Ethereum =======================================================
// eth contracts
QueryBuilder<EthContract, EthContract, QWhere> getEthContracts() =>
isar.ethContracts.where();
Future<void> putEthContract(EthContract contract) => isar.writeTxn(() async {
await isar.ethContracts.put(contract);
});
Future<void> putEthContracts(List<EthContract> contracts) =>
isar.writeTxn(() async {
await isar.ethContracts.putAll(contracts);
});
}

View file

@ -1,11 +1,11 @@
import 'package:stackwallet/models/add_wallet_list_entity/add_wallet_list_entity.dart';
import 'package:stackwallet/models/ethereum/eth_token.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
class EthTokenEntity extends AddWalletListEntity {
EthTokenEntity(this.token);
final EthContractInfo token;
final EthContract token;
@override
Coin get coin => Coin.ethereum;
@ -17,5 +17,5 @@ class EthTokenEntity extends AddWalletListEntity {
String get ticker => token.symbol;
@override
List<Object?> get props => [coin, name, ticker, token.contractAddress];
List<Object?> get props => [coin, name, ticker, token.address];
}

View file

@ -1,10 +0,0 @@
import 'package:stackwallet/models/ethereum/eth_token.dart';
class Erc20ContractInfo extends EthContractInfo {
const Erc20ContractInfo({
required super.contractAddress,
required super.name,
required super.symbol,
required super.decimals,
});
}

View file

@ -1,10 +0,0 @@
import 'package:stackwallet/models/ethereum/eth_token.dart';
class Erc721ContractInfo extends EthContractInfo {
const Erc721ContractInfo({
required super.contractAddress,
required super.name,
required super.symbol,
required super.decimals,
});
}

View file

@ -1,62 +0,0 @@
import 'dart:convert';
import 'package:equatable/equatable.dart';
import 'package:stackwallet/models/ethereum/erc20_token.dart';
import 'package:stackwallet/models/ethereum/erc721_token.dart';
abstract class EthContractInfo extends Equatable {
const EthContractInfo({
required this.contractAddress,
required this.name,
required this.symbol,
required this.decimals,
});
final String contractAddress;
final String name;
final String symbol;
final int decimals;
static EthContractInfo? fromMap(Map<String, dynamic> map) {
switch (map["runtimeType"]) {
case "Erc20ContractInfo":
return Erc20ContractInfo(
contractAddress: map["contractAddress"] as String,
name: map["name"] as String,
symbol: map["symbol"] as String,
decimals: map["decimals"] as int,
);
case "Erc721ContractInfo":
return Erc721ContractInfo(
contractAddress: map["contractAddress"] as String,
name: map["name"] as String,
symbol: map["symbol"] as String,
decimals: map["decimals"] as int,
);
default:
return null;
}
}
static EthContractInfo? fromJson(String json) => fromMap(
Map<String, dynamic>.from(
jsonDecode(json) as Map,
),
);
Map<String, dynamic> toMap() => {
"runtimeType": "$runtimeType",
"contractAddress": contractAddress,
"name": name,
"symbol": symbol,
"decimals": decimals,
};
String toJson() => jsonEncode(toMap());
@override
String toString() => toMap().toString();
@override
List<Object?> get props => [contractAddress];
}

View file

@ -11,6 +11,7 @@ class EthContract extends Contract {
required this.symbol,
required this.decimals,
required this.type,
required this.walletIds,
this.abi,
this.otherData,
});
@ -28,10 +29,34 @@ class EthContract extends Contract {
late final String? abi;
late final List<String> walletIds;
@enumerated
late final EthContractType type;
late final String? otherData;
EthContract copyWith({
Id? id,
String? address,
String? name,
String? symbol,
int? decimals,
EthContractType? type,
List<String>? walletIds,
String? abi,
String? otherData,
}) =>
EthContract(
address: address ?? this.address,
name: name ?? this.name,
symbol: symbol ?? this.symbol,
decimals: decimals ?? this.decimals,
type: type ?? this.type,
walletIds: walletIds ?? this.walletIds,
abi: abi ?? this.abi,
otherData: otherData ?? this.otherData,
)..id = id ?? this.id;
}
// Used in Isar db and stored there as int indexes so adding/removing values

View file

@ -52,6 +52,11 @@ const EthContractSchema = CollectionSchema(
name: r'type',
type: IsarType.byte,
enumMap: _EthContracttypeEnumValueMap,
),
r'walletIds': PropertySchema(
id: 7,
name: r'walletIds',
type: IsarType.stringList,
)
},
estimateSize: _ethContractEstimateSize,
@ -103,6 +108,13 @@ int _ethContractEstimateSize(
}
}
bytesCount += 3 + object.symbol.length * 3;
bytesCount += 3 + object.walletIds.length * 3;
{
for (var i = 0; i < object.walletIds.length; i++) {
final value = object.walletIds[i];
bytesCount += value.length * 3;
}
}
return bytesCount;
}
@ -119,6 +131,7 @@ void _ethContractSerialize(
writer.writeString(offsets[4], object.otherData);
writer.writeString(offsets[5], object.symbol);
writer.writeByte(offsets[6], object.type.index);
writer.writeStringList(offsets[7], object.walletIds);
}
EthContract _ethContractDeserialize(
@ -136,6 +149,7 @@ EthContract _ethContractDeserialize(
symbol: reader.readString(offsets[5]),
type: _EthContracttypeValueEnumMap[reader.readByteOrNull(offsets[6])] ??
EthContractType.erc20,
walletIds: reader.readStringList(offsets[7]) ?? [],
);
object.id = id;
return object;
@ -163,6 +177,8 @@ P _ethContractDeserializeProp<P>(
case 6:
return (_EthContracttypeValueEnumMap[reader.readByteOrNull(offset)] ??
EthContractType.erc20) as P;
case 7:
return (reader.readStringList(offset) ?? []) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
@ -1230,6 +1246,231 @@ extension EthContractQueryFilter
));
});
}
QueryBuilder<EthContract, EthContract, QAfterFilterCondition>
walletIdsElementEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'walletIds',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EthContract, EthContract, QAfterFilterCondition>
walletIdsElementGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'walletIds',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EthContract, EthContract, QAfterFilterCondition>
walletIdsElementLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'walletIds',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EthContract, EthContract, QAfterFilterCondition>
walletIdsElementBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'walletIds',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EthContract, EthContract, QAfterFilterCondition>
walletIdsElementStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'walletIds',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EthContract, EthContract, QAfterFilterCondition>
walletIdsElementEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'walletIds',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EthContract, EthContract, QAfterFilterCondition>
walletIdsElementContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'walletIds',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EthContract, EthContract, QAfterFilterCondition>
walletIdsElementMatches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'walletIds',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<EthContract, EthContract, QAfterFilterCondition>
walletIdsElementIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'walletIds',
value: '',
));
});
}
QueryBuilder<EthContract, EthContract, QAfterFilterCondition>
walletIdsElementIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'walletIds',
value: '',
));
});
}
QueryBuilder<EthContract, EthContract, QAfterFilterCondition>
walletIdsLengthEqualTo(int length) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'walletIds',
length,
true,
length,
true,
);
});
}
QueryBuilder<EthContract, EthContract, QAfterFilterCondition>
walletIdsIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'walletIds',
0,
true,
0,
true,
);
});
}
QueryBuilder<EthContract, EthContract, QAfterFilterCondition>
walletIdsIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'walletIds',
0,
false,
999999,
true,
);
});
}
QueryBuilder<EthContract, EthContract, QAfterFilterCondition>
walletIdsLengthLessThan(
int length, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'walletIds',
0,
true,
length,
include,
);
});
}
QueryBuilder<EthContract, EthContract, QAfterFilterCondition>
walletIdsLengthGreaterThan(
int length, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'walletIds',
length,
include,
999999,
true,
);
});
}
QueryBuilder<EthContract, EthContract, QAfterFilterCondition>
walletIdsLengthBetween(
int lower,
int upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.listLength(
r'walletIds',
lower,
includeLower,
upper,
includeUpper,
);
});
}
}
extension EthContractQueryObject
@ -1472,6 +1713,12 @@ extension EthContractQueryWhereDistinct
return query.addDistinctBy(r'type');
});
}
QueryBuilder<EthContract, EthContract, QDistinct> distinctByWalletIds() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'walletIds');
});
}
}
extension EthContractQueryProperty
@ -1523,4 +1770,11 @@ extension EthContractQueryProperty
return query.addPropertyName(r'type');
});
}
QueryBuilder<EthContract, List<String>, QQueryOperations>
walletIdsProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'walletIds');
});
}
}

View file

@ -3,7 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/ethereum/eth_token.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
import 'package:stackwallet/services/ethereum/ethereum_api.dart';
import 'package:stackwallet/utilities/show_loading.dart';
import 'package:stackwallet/utilities/text_styles.dart';
@ -39,7 +39,7 @@ class _AddCustomTokenViewState extends ConsumerState<AddCustomTokenView> {
bool enableSubFields = false;
bool addTokenButtonEnabled = false;
EthContractInfo? currentToken;
EthContract? currentToken;
@override
Widget build(BuildContext context) {
@ -96,6 +96,7 @@ class _AddCustomTokenViewState extends ConsumerState<AddCustomTokenView> {
nameController.text = currentToken!.name;
symbolController.text = currentToken!.symbol;
decimalsController.text = currentToken!.decimals.toString();
currentToken!.walletIds.add(widget.walletId);
} else {
nameController.text = "";
symbolController.text = "";

View file

@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/ethereum/eth_token.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/db/isar/main_db.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
import 'package:stackwallet/pages/add_wallet_views/add_token_view/add_custom_token_view.dart';
import 'package:stackwallet/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list.dart';
import 'package:stackwallet/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart';
@ -60,7 +62,7 @@ class _AddTokenViewState extends ConsumerState<AddTokenView> {
(e) =>
e.token.name.toLowerCase().contains(lowercaseTerm) ||
e.token.symbol.toLowerCase().contains(lowercaseTerm) ||
e.token.contractAddress.toLowerCase().contains(lowercaseTerm),
e.token.address.toLowerCase().contains(lowercaseTerm),
);
}
@ -69,16 +71,16 @@ class _AddTokenViewState extends ConsumerState<AddTokenView> {
Future<void> onNextPressed() async {
final selectedTokens =
tokenEntities.where((e) => e.selected).map((e) => e.token).toSet();
tokenEntities.where((e) => e.selected).map((e) => e.token).toList();
final ethWallet = ref
.read(walletsChangeNotifierProvider)
.getManager(widget.walletId)
.wallet as EthereumWallet;
await ethWallet.addTokenContract(selectedTokens);
await ethWallet.addTokenContracts(selectedTokens);
if (mounted) {
Navigator.of(context).pop();
Navigator.of(context).pop(42);
}
}
@ -87,15 +89,18 @@ class _AddTokenViewState extends ConsumerState<AddTokenView> {
AddCustomTokenView.routeName,
arguments: widget.walletId,
);
if (token is EthContractInfo) {
setState(() {
if (tokenEntities
.where((e) => e.token.contractAddress == token.contractAddress)
.isEmpty) {
tokenEntities.add(AddTokenListElementData(token)..selected = true);
tokenEntities.sort((a, b) => a.token.name.compareTo(b.token.name));
}
});
if (token is EthContract) {
await MainDB.instance.putEthContract(token);
if (mounted) {
setState(() {
if (tokenEntities
.where((e) => e.token.address == token.address)
.isEmpty) {
tokenEntities.add(AddTokenListElementData(token)..selected = true);
tokenEntities.sort((a, b) => a.token.name.compareTo(b.token.name));
}
});
}
}
}
@ -104,9 +109,15 @@ class _AddTokenViewState extends ConsumerState<AddTokenView> {
_searchFieldController = TextEditingController();
_searchFocusNode = FocusNode();
tokenEntities
.addAll(DefaultTokens.list.map((e) => AddTokenListElementData(e)));
tokenEntities.sort((a, b) => a.token.name.compareTo(b.token.name));
final contracts =
MainDB.instance.getEthContracts().sortByName().findAllSync();
if (contracts.isEmpty) {
contracts.addAll(DefaultTokens.list);
MainDB.instance.putEthContracts(contracts);
}
tokenEntities.addAll(contracts.map((e) => AddTokenListElementData(e)));
super.initState();
}

View file

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/models/ethereum/eth_token.dart';
import 'package:stackwallet/models/isar/exchange_cache/currency.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart';
import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart';
import 'package:stackwallet/utilities/assets.dart';
@ -13,7 +13,7 @@ import 'package:stackwallet/widgets/rounded_white_container.dart';
class AddTokenListElementData {
AddTokenListElementData(this.token);
final EthContractInfo token;
final EthContract token;
bool selected = false;
}
@ -34,7 +34,7 @@ class _AddTokenListElementState extends State<AddTokenListElement> {
.exchangeNameEqualTo(ChangeNowExchange.exchangeName)
.filter()
.tokenContractEqualTo(
widget.data.token.contractAddress,
widget.data.token.address,
caseSensitive: false,
)
.and()

View file

@ -3,9 +3,12 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/db/isar/main_db.dart';
import 'package:stackwallet/models/add_wallet_list_entity/add_wallet_list_entity.dart';
import 'package:stackwallet/models/add_wallet_list_entity/sub_classes/coin_entity.dart';
import 'package:stackwallet/models/add_wallet_list_entity/sub_classes/eth_token_entity.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/sub_widgets/add_wallet_text.dart';
import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/sub_widgets/expanding_sub_list_item.dart';
import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/sub_widgets/next_button.dart';
@ -67,7 +70,7 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
e.name.toLowerCase().contains(lowercaseTerm) ||
e.coin.name.toLowerCase().contains(lowercaseTerm) ||
(e is EthTokenEntity &&
e.token.contractAddress.toLowerCase().contains(lowercaseTerm)),
e.token.address.toLowerCase().contains(lowercaseTerm)),
);
}
@ -92,8 +95,15 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
coinEntities.addAll(_coinsTestnet.map((e) => CoinEntity(e)));
}
tokenEntities.addAll(DefaultTokens.list.map((e) => EthTokenEntity(e)));
tokenEntities.sort((a, b) => a.name.compareTo(b.name));
final contracts =
MainDB.instance.getEthContracts().sortByName().findAllSync();
if (contracts.isEmpty) {
contracts.addAll(DefaultTokens.list);
MainDB.instance.putEthContracts(contracts);
}
tokenEntities.addAll(contracts.map((e) => EthTokenEntity(e)));
super.initState();
}

View file

@ -2,7 +2,6 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/ethereum/eth_token.dart';
import 'package:stackwallet/notifications/show_flush_bar.dart';
import 'package:stackwallet/providers/global/debug_service_provider.dart';
import 'package:stackwallet/providers/providers.dart';
@ -13,8 +12,6 @@ import 'package:stackwallet/utilities/theme/stack_colors.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import '../../../models/ethereum/erc20_token.dart';
class HiddenSettings extends StatelessWidget {
const HiddenSettings({Key? key}) : super(key: key);
@ -192,20 +189,20 @@ class HiddenSettings extends StatelessWidget {
Consumer(builder: (_, ref, __) {
return GestureDetector(
onTap: () async {
final erc20 = Erc20ContractInfo(
contractAddress: 'some con',
name: "loonamsn",
symbol: "DD",
decimals: 19,
);
final json = erc20.toJson();
print(json);
final ee = EthContractInfo.fromJson(json);
print(ee);
// final erc20 = Erc20ContractInfo(
// contractAddress: 'some con',
// name: "loonamsn",
// symbol: "DD",
// decimals: 19,
// );
//
// final json = erc20.toJson();
//
// print(json);
//
// final ee = EthContractInfo.fromJson(json);
//
// print(ee);
},
child: RoundedWhiteContainer(
child: Text(

View file

@ -3,11 +3,12 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/ethereum/eth_token.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/db/isar/main_db.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
import 'package:stackwallet/pages/add_wallet_views/add_token_view/add_token_view.dart';
import 'package:stackwallet/pages/token_view/sub_widgets/my_tokens_list.dart';
import 'package:stackwallet/providers/global/wallets_provider.dart';
import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/text_styles.dart';
@ -38,6 +39,34 @@ class _TokenDetailsViewState extends ConsumerState<MyTokensView> {
late final String walletAddress;
late final TextEditingController _searchController;
final searchFieldFocusNode = FocusNode();
String _searchString = "";
List<EthContract> _filter(String searchTerm) {
if (searchTerm.isNotEmpty) {
final term = searchTerm.toLowerCase();
return MainDB.instance
.getEthContracts()
.filter()
.walletIdsElementEqualTo(widget.walletId)
.and()
.group(
(q) => q
.nameContains(term, caseSensitive: false)
.or()
.symbolContains(term, caseSensitive: false)
.or()
.addressContains(term, caseSensitive: false),
)
.findAllSync();
// return tokens.toList();
}
//implement search/filter
return MainDB.instance
.getEthContracts()
.filter()
.walletIdsElementEqualTo(widget.walletId)
.findAllSync();
}
@override
void initState() {
@ -53,26 +82,10 @@ class _TokenDetailsViewState extends ConsumerState<MyTokensView> {
super.dispose();
}
String _searchString = "";
List<EthContractInfo> _filter(
Set<EthContractInfo> tokens, String searchTerm) {
if (searchTerm.isEmpty) {
return tokens.toList();
}
//implement search/filter
return tokens.toList();
}
@override
Widget build(BuildContext context) {
final isDesktop = Util.isDesktop;
final tokens = (ref.watch(walletsChangeNotifierProvider
.select((value) => value.getManager(widget.walletId).wallet))
as EthereumWallet)
.contracts;
return MasterScaffold(
background: Theme.of(context).extension<StackColors>()!.background,
isDesktop: isDesktop,
@ -160,11 +173,15 @@ class _TokenDetailsViewState extends ConsumerState<MyTokensView> {
width: 20,
height: 20,
),
onPressed: () {
Navigator.of(context).pushNamed(
onPressed: () async {
final result = await Navigator.of(context).pushNamed(
AddTokenView.routeName,
arguments: widget.walletId,
);
if (mounted && result == 42) {
setState(() {});
}
},
),
),
@ -273,7 +290,7 @@ class _TokenDetailsViewState extends ConsumerState<MyTokensView> {
Expanded(
child: MyTokensList(
walletId: widget.walletId,
tokens: _filter(tokens, _searchString),
tokens: _filter(_searchString),
),
),
],

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/svg.dart';
import 'package:stackwallet/models/ethereum/eth_token.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
import 'package:stackwallet/pages/token_view/token_view.dart';
import 'package:stackwallet/providers/global/secure_store_provider.dart';
import 'package:stackwallet/providers/providers.dart';
@ -24,7 +24,7 @@ class MyTokenSelectItem extends ConsumerStatefulWidget {
}) : super(key: key);
final String walletId;
final EthContractInfo token;
final EthContract token;
@override
ConsumerState<MyTokenSelectItem> createState() => _MyTokenSelectItemState();
@ -133,7 +133,7 @@ class _MyTokenSelectItemState extends ConsumerState<MyTokenSelectItem> {
Text("${ref.watch(
priceAnd24hChangeNotifierProvider.select(
(value) => value
.getTokenPrice(widget.token.contractAddress)
.getTokenPrice(widget.token.address)
.item1
.toStringAsFixed(2),
),

View file

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:stackwallet/models/ethereum/eth_token.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
import 'package:stackwallet/pages/token_view/sub_widgets/my_token_select_item.dart';
class MyTokensList extends StatelessWidget {
@ -11,7 +11,7 @@ class MyTokensList extends StatelessWidget {
}) : super(key: key);
final String walletId;
final List<EthContractInfo> tokens;
final List<EthContract> tokens;
@override
Widget build(BuildContext context) {
@ -22,7 +22,7 @@ class MyTokensList extends StatelessWidget {
itemBuilder: (ctx, index) {
final token = tokens[index];
return Padding(
key: Key(token.contractAddress),
key: Key(token.address),
padding: const EdgeInsets.all(4),
child: MyTokenSelectItem(
walletId: walletId,

View file

@ -78,7 +78,7 @@ class _TokenViewState extends ConsumerState<TokenView> {
SvgPicture.asset(
Assets.svg.iconForToken(
contractAddress: ref.watch(tokenServiceProvider
.select((value) => value!.token.contractAddress))),
.select((value) => value!.token.address))),
width: 24,
height: 24,
),
@ -118,7 +118,7 @@ class _TokenViewState extends ConsumerState<TokenView> {
walletId: widget.walletId,
initialSyncStatus: initialSyncStatus,
tokenContractAddress: ref.watch(tokenServiceProvider
.select((value) => value!.token.contractAddress)),
.select((value) => value!.token.address)),
),
),
),

View file

@ -7,7 +7,6 @@ import 'package:http/http.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/db/isar/main_db.dart';
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/ethereum/eth_token.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart';
@ -18,7 +17,6 @@ import 'package:stackwallet/services/event_bus/events/global/refresh_percent_cha
import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart';
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
import 'package:stackwallet/services/mixins/eth_extras_wallet_cache.dart';
import 'package:stackwallet/services/mixins/wallet_cache.dart';
import 'package:stackwallet/services/mixins/wallet_db.dart';
import 'package:stackwallet/services/node_service.dart';
@ -39,8 +37,7 @@ import 'package:web3dart/web3dart.dart' as web3;
const int MINIMUM_CONFIRMATIONS = 3;
class EthereumWallet extends CoinServiceAPI
with WalletCache, WalletDB, EthExtrasWalletCache {
class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB {
EthereumWallet({
required String walletId,
required String walletName,
@ -55,7 +52,6 @@ class EthereumWallet extends CoinServiceAPI
_coin = coin;
_secureStore = secureStore;
initCache(walletId, coin);
initEthExtrasCache(walletId);
initWalletDB(mockableOverride: mockableOverride);
}
@ -66,29 +62,47 @@ class EthereumWallet extends CoinServiceAPI
Timer? timer;
Timer? _networkAliveTimer;
Set<EthContractInfo> get contracts => getCachedTokenContracts();
Future<void> addTokenContracts(List<EthContract> contracts) async {
List<EthContract> updatedContracts = [];
for (final contract in contracts) {
final updatedWalletIds = contract.walletIds.toList();
if (!updatedWalletIds.contains(walletId)) {
updatedWalletIds.add(walletId);
updatedContracts.add(contract.copyWith(walletIds: updatedWalletIds));
} else {
updatedContracts.add(contract);
}
}
await db.putEthContracts(updatedContracts);
Future<void> addTokenContract(Set<EthContractInfo> contractInfo) =>
updateCachedTokenContracts(contracts..addAll(contractInfo)).then(
(value) => GlobalEventBus.instance.fire(
UpdatedInBackgroundEvent(
"$contractInfo updated/added for: $walletId $walletName",
walletId,
),
),
);
GlobalEventBus.instance.fire(
UpdatedInBackgroundEvent(
"$contracts updated/added for: $walletId $walletName",
walletId,
),
);
}
Future<void> removeTokenContract(String contractAddress) =>
updateCachedTokenContracts(contracts
..removeWhere((e) => e.contractAddress == contractAddress))
.then(
(value) => GlobalEventBus.instance.fire(
UpdatedInBackgroundEvent(
"$contractAddress removed for: $walletId $walletName",
walletId,
),
),
);
Future<void> removeTokenContract(String contractAddress) async {
final contract =
await db.getEthContracts().addressEqualTo(contractAddress).findFirst();
if (contract == null) {
return; // todo some error?
}
final updatedWalletIds = contract.walletIds.toList();
updatedWalletIds.removeWhere((e) => e == contractAddress);
await db.putEthContract(contract.copyWith(walletIds: updatedWalletIds));
GlobalEventBus.instance.fire(
UpdatedInBackgroundEvent(
"$contractAddress removed for: $walletId $walletName",
walletId,
),
);
}
@override
String get walletId => _walletId;

View file

@ -1,4 +1,4 @@
import 'package:stackwallet/models/ethereum/eth_token.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
import 'package:stackwallet/models/token_balance.dart';
import 'package:stackwallet/services/ethereum/ethereum_api.dart';
import 'package:stackwallet/services/mixins/eth_token_cache.dart';
@ -6,7 +6,7 @@ import 'package:stackwallet/utilities/logger.dart';
class CachedEthTokenBalance with EthTokenCache {
final String walletId;
final EthContractInfo token;
final EthContract token;
CachedEthTokenBalance(this.walletId, this.token) {
initCache(walletId, token);
@ -15,13 +15,13 @@ class CachedEthTokenBalance with EthTokenCache {
Future<void> fetchAndUpdateCachedBalance(String address) async {
final response = await EthereumAPI.getWalletTokenBalance(
address: address,
contractAddress: token.contractAddress,
contractAddress: token.address,
);
if (response.value != null) {
await updateCachedBalance(
TokenBalance(
contractAddress: token.contractAddress,
contractAddress: token.address,
decimalPlaces: token.decimals,
total: response.value!,
spendable: response.value!,

View file

@ -5,9 +5,7 @@ import 'package:decimal/decimal.dart';
import 'package:http/http.dart';
import 'package:stackwallet/dto/ethereum/eth_token_tx_dto.dart';
import 'package:stackwallet/dto/ethereum/eth_tx_dto.dart';
import 'package:stackwallet/models/ethereum/erc20_token.dart';
import 'package:stackwallet/models/ethereum/erc721_token.dart';
import 'package:stackwallet/models/ethereum/eth_token.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/utilities/default_nodes.dart';
import 'package:stackwallet/utilities/eth_commons.dart';
@ -64,7 +62,10 @@ abstract class EthereumAPI {
final List<EthTxDTO> txns = [];
for (final map in list!) {
txns.add(EthTxDTO.fromMap(Map<String, dynamic>.from(map as Map)));
final txn = EthTxDTO.fromMap(Map<String, dynamic>.from(map as Map));
if (txn.hasToken == 0) {
txns.add(txn);
}
}
return EthereumResponse(
txns,
@ -311,7 +312,7 @@ abstract class EthereumAPI {
slow: feesSlow.toInt());
}
static Future<EthereumResponse<EthContractInfo>> getTokenByContractAddress(
static Future<EthereumResponse<EthContract>> getTokenByContractAddress(
String contractAddress) async {
try {
final response = await get(Uri.parse(
@ -322,20 +323,24 @@ abstract class EthereumAPI {
final json = jsonDecode(response.body);
if (json["message"] == "OK") {
final map = Map<String, dynamic>.from(json["result"] as Map);
EthContractInfo? token;
EthContract? token;
if (map["type"] == "ERC-20") {
token = Erc20ContractInfo(
contractAddress: map["contractAddress"] as String,
token = EthContract(
address: map["contractAddress"] as String,
decimals: int.parse(map["decimals"] as String),
name: map["name"] as String,
symbol: map["symbol"] as String,
type: EthContractType.erc20,
walletIds: [],
);
} else if (map["type"] == "ERC-721") {
token = Erc721ContractInfo(
contractAddress: map["contractAddress"] as String,
token = EthContract(
address: map["contractAddress"] as String,
decimals: int.parse(map["decimals"] as String),
name: map["name"] as String,
symbol: map["symbol"] as String,
type: EthContractType.erc721,
walletIds: [],
);
} else {
throw EthApiException(

View file

@ -5,9 +5,7 @@ import 'package:ethereum_addresses/ethereum_addresses.dart';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart';
import 'package:isar/isar.dart';
import 'package:stackwallet/models/ethereum/eth_token.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/address.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
import 'package:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/models/token_balance.dart';
@ -30,7 +28,7 @@ import 'package:tuple/tuple.dart';
import 'package:web3dart/web3dart.dart' as web3dart;
class EthereumTokenService extends ChangeNotifier with EthTokenCache {
final EthContractInfo token;
final EthContract token;
final EthereumWallet ethWallet;
final TransactionNotificationTracker tracker;
final SecureStorageInterface _secureStore;
@ -51,7 +49,7 @@ class EthereumTokenService extends ChangeNotifier with EthTokenCache {
required SecureStorageInterface secureStore,
required this.tracker,
}) : _secureStore = secureStore {
_contractAddress = web3dart.EthereumAddress.fromHex(token.contractAddress);
_contractAddress = web3dart.EthereumAddress.fromHex(token.address);
initCache(ethWallet.walletId, token);
}
@ -220,7 +218,7 @@ class EthereumTokenService extends ChangeNotifier with EthTokenCache {
GlobalEventBus.instance.fire(
WalletSyncStatusChangedEvent(
WalletSyncStatus.syncing,
ethWallet.walletId + token.contractAddress,
ethWallet.walletId + token.address,
coin,
),
);
@ -237,7 +235,7 @@ class EthereumTokenService extends ChangeNotifier with EthTokenCache {
GlobalEventBus.instance.fire(
WalletSyncStatusChangedEvent(
WalletSyncStatus.synced,
ethWallet.walletId + token.contractAddress,
ethWallet.walletId + token.address,
coin,
),
);
@ -256,7 +254,7 @@ class EthereumTokenService extends ChangeNotifier with EthTokenCache {
String _balance = balanceRequest.first.toString();
final newBalance = TokenBalance(
contractAddress: token.contractAddress,
contractAddress: token.address,
total: int.parse(_balance),
spendable: int.parse(_balance),
blockedTotal: 0,
@ -270,7 +268,7 @@ class EthereumTokenService extends ChangeNotifier with EthTokenCache {
Future<List<Transaction>> get transactions => ethWallet.db
.getTransactions(ethWallet.walletId)
.filter()
.otherDataEqualTo(token.contractAddress)
.otherDataEqualTo(token.address)
.sortByTimestampDesc()
.findAll();
@ -279,7 +277,7 @@ class EthereumTokenService extends ChangeNotifier with EthTokenCache {
final response = await EthereumAPI.getTokenTransactions(
address: addressString,
contractAddress: token.contractAddress,
contractAddress: token.address,
);
if (response.value == null) {

View file

@ -1,29 +0,0 @@
import 'package:stackwallet/db/hive/db.dart';
import 'package:stackwallet/models/ethereum/eth_token.dart';
mixin EthExtrasWalletCache {
late final String _walletId;
void initEthExtrasCache(String walletId) {
_walletId = walletId;
}
// cached list of user added token contracts
Set<EthContractInfo> getCachedTokenContracts() {
final list = DB.instance.get<dynamic>(
boxName: _walletId,
key: "ethTokenContracts",
) as List<String>? ??
[];
return list.map((e) => EthContractInfo.fromJson(e)!).toSet();
}
Future<void> updateCachedTokenContracts(
Set<EthContractInfo> contracts) async {
await DB.instance.put<dynamic>(
boxName: _walletId,
key: "ethTokenContracts",
value: contracts.map((e) => e.toJson()).toList(),
);
}
}

View file

@ -1,5 +1,5 @@
import 'package:stackwallet/db/hive/db.dart';
import 'package:stackwallet/models/ethereum/eth_token.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
import 'package:stackwallet/models/token_balance.dart';
abstract class _Keys {
@ -10,9 +10,9 @@ abstract class _Keys {
mixin EthTokenCache {
late final String _walletId;
late final EthContractInfo _token;
late final EthContract _token;
void initCache(String walletId, EthContractInfo token) {
void initCache(String walletId, EthContract token) {
_walletId = walletId;
_token = token;
}
@ -21,11 +21,11 @@ mixin EthTokenCache {
TokenBalance getCachedBalance() {
final jsonString = DB.instance.get<dynamic>(
boxName: _walletId,
key: _Keys.tokenBalance(_token.contractAddress),
key: _Keys.tokenBalance(_token.address),
) as String?;
if (jsonString == null) {
return TokenBalance(
contractAddress: _token.contractAddress,
contractAddress: _token.address,
decimalPlaces: _token.decimals,
total: 0,
spendable: 0,
@ -41,7 +41,7 @@ mixin EthTokenCache {
Future<void> updateCachedBalance(TokenBalance balance) async {
await DB.instance.put<dynamic>(
boxName: _walletId,
key: _Keys.tokenBalance(_token.contractAddress),
key: _Keys.tokenBalance(_token.address),
value: balance.toJsonIgnoreCoin(),
);
}

View file

@ -1,43 +1,54 @@
import 'package:stackwallet/models/ethereum/erc20_token.dart';
import 'package:stackwallet/models/ethereum/eth_token.dart';
import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart';
abstract class DefaultTokens {
static const List<EthContractInfo> list = [
Erc20ContractInfo(
contractAddress: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
static List<EthContract> list = [
EthContract(
address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
name: "USD Coin",
symbol: "USDC",
decimals: 6,
type: EthContractType.erc20,
walletIds: [],
),
Erc20ContractInfo(
contractAddress: "0xdac17f958d2ee523a2206206994597c13d831ec7",
EthContract(
address: "0xdac17f958d2ee523a2206206994597c13d831ec7",
name: "Tether",
symbol: "USDT",
decimals: 6,
type: EthContractType.erc20,
walletIds: [],
),
Erc20ContractInfo(
contractAddress: "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce",
EthContract(
address: "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce",
name: "Shiba Inu",
symbol: "SHIB",
decimals: 18,
type: EthContractType.erc20,
walletIds: [],
),
Erc20ContractInfo(
contractAddress: "0xB8c77482e45F1F44dE1745F52C74426C631bDD52",
EthContract(
address: "0xB8c77482e45F1F44dE1745F52C74426C631bDD52",
name: "BNB Token",
symbol: "BNB",
decimals: 18,
type: EthContractType.erc20,
walletIds: [],
),
Erc20ContractInfo(
contractAddress: "0x4Fabb145d64652a948d72533023f6E7A623C7C53",
EthContract(
address: "0x4Fabb145d64652a948d72533023f6E7A623C7C53",
name: "BUSD",
symbol: "BUSD",
decimals: 18,
type: EthContractType.erc20,
walletIds: [],
),
Erc20ContractInfo(
contractAddress: "0x514910771af9ca656af840dff83e8264ecf986ca",
EthContract(
address: "0x514910771af9ca656af840dff83e8264ecf986ca",
name: "Chainlink",
symbol: "LINK",
decimals: 18,
type: EthContractType.erc20,
walletIds: [],
),
];
}