dev
dart 213 lines 6.08 KB
Raw
1 import "package:cw_core/cake_hive.dart";
2 import "package:cw_core/db/sqlite.dart";
3 import "package:cw_core/erc20_token.dart" as erc20_new;
4 import "package:cw_core/hive_type_ids.dart";
5 import "package:cw_core/utils/print_verbose.dart";
6 import "package:cw_core/wallet_info.dart";
7 import "package:cw_core/wallet_type.dart";
8 import "package:hive/hive.dart";
9 import "package:sqflite/sqflite.dart";
10
11 part "erc20_token_legacy.part.dart";
12
13 Future<void> performErc20TokenHiveMigration() async {
14 try {
15 if (!CakeHive.isAdapterRegistered(Erc20Token.typeId)) {
16 CakeHive.registerAdapter(Erc20TokenAdapter());
17 }
18
19 final wallets = await WalletInfo.getAll();
20 await Erc20Token.migrateAllToSqlite(wallets);
21 } catch (e) {
22 printV("Error performing Erc20Token Hive migration: $e, continuing anyway");
23 }
24 }
25
26 // @HiveType(typeId: Erc20Token.typeId)
27 class Erc20Token extends HiveObject {
28 Erc20Token({
29 required this.name,
30 required this.symbol,
31 required this.contractAddress,
32 required this.decimal,
33 bool enabled = true,
34 this.iconPath,
35 this.tag,
36 this.isPotentialScam = false,
37 }) : _enabled = enabled;
38 // @HiveField(0)
39 final String name;
40 // @HiveField(1)
41 final String symbol;
42 // @HiveField(2)
43 final String contractAddress;
44 // @HiveField(3)
45 final int decimal;
46 // @HiveField(4, defaultValue: true)
47 bool _enabled;
48 // @HiveField(5)
49 String? iconPath;
50 // @HiveField(6)
51 final String? tag;
52 // @HiveField(7, defaultValue: false)
53 bool isPotentialScam;
54
55 bool get enabled => _enabled;
56
57 set enabled(bool value) => _enabled = value;
58
59 static const typeId = ERC20_TOKEN_TYPE_ID;
60 static const boxName = "Erc20Tokens";
61 static const ethereumBoxName = "EthereumErc20Tokens";
62 static const polygonBoxName = "PolygonErc20Tokens";
63 static const baseBoxName = "BaseErc20Tokens";
64 static const arbitrumBoxName = "ArbitrumErc20Tokens";
65 static const bscBoxName = "BscErc20Tokens";
66
67 static const chainIdToBoxSuffix = {
68 1: ethereumBoxName,
69 137: polygonBoxName,
70 8453: baseBoxName,
71 42161: arbitrumBoxName,
72 56: bscBoxName,
73 };
74
75 static const evmWalletTypes = [
76 WalletType.ethereum,
77 WalletType.polygon,
78 WalletType.base,
79 WalletType.arbitrum,
80 WalletType.bsc,
81 ];
82
83 static Future<void> migrateAllToSqlite(List<WalletInfo> wallets) async {
84 await _migrateLegacyGlobalBox(wallets);
85
86 final sanitizedToRawNames = <String, Set<String>>{};
87 for (final wallet in wallets) {
88 if (!evmWalletTypes.contains(wallet.type)) {
89 continue;
90 }
91
92 sanitizedToRawNames
93 .putIfAbsent(wallet.name.replaceAll(" ", "_"), () => <String>{})
94 .add(wallet.name);
95 }
96
97 for (final entry in sanitizedToRawNames.entries) {
98 for (final chainEntry in chainIdToBoxSuffix.entries) {
99 final tokenBoxName = "${entry.key}_${chainEntry.value}";
100 try {
101 if (!await CakeHive.boxExists(tokenBoxName)) {
102 continue;
103 }
104
105 final box = await CakeHive.openBox<Erc20Token>(tokenBoxName);
106
107 for (final group in _mergeByLowercaseContract(box)) {
108 for (final rawName in entry.value) {
109 await group.token.migrateToSqlite(walletName: rawName, chainId: chainEntry.key);
110 }
111 await box.deleteAll(group.sourceKeys);
112 }
113
114 await box.deleteFromDisk();
115 } catch (e) {
116 printV("Error migrating erc20 token box $tokenBoxName: $e, continuing anyway");
117 }
118 }
119 }
120 }
121
122 static Future<void> _migrateLegacyGlobalBox(List<WalletInfo> wallets) async {
123 try {
124 if (!await CakeHive.boxExists(boxName)) {
125 return;
126 }
127
128 if (!wallets.any((wallet) => wallet.type == WalletType.ethereum)) {
129 return;
130 }
131
132 final box = await CakeHive.openBox<Erc20Token>(boxName);
133 final ethereumWallets =
134 wallets.where((wallet) => wallet.type == WalletType.ethereum).toList();
135
136 for (final group in _mergeByLowercaseContract(box)) {
137 for (final wallet in ethereumWallets) {
138 await group.token.migrateToSqlite(walletName: wallet.name, chainId: 1);
139 }
140 await box.deleteAll(group.sourceKeys);
141 }
142
143 await box.deleteFromDisk();
144 } catch (e) {
145 printV("Error migrating legacy global erc20 token box: $e, continuing anyway");
146 }
147 }
148
149 static List<_MergedTokenGroup> _mergeByLowercaseContract(Box<Erc20Token> box) {
150 final groups = <String, _MergedTokenGroup>{};
151
152 for (final key in box.keys) {
153 final token = box.get(key);
154 if (token == null) {
155 continue;
156 }
157
158 final lowerKey = token.contractAddress.toLowerCase();
159 final existing = groups[lowerKey];
160
161 if (existing == null) {
162 groups[lowerKey] = _MergedTokenGroup(token: token, sourceKeys: [key]);
163 continue;
164 }
165
166 groups[lowerKey] = _MergedTokenGroup(
167 token: Erc20Token(
168 name: token.name,
169 symbol: token.symbol,
170 contractAddress: lowerKey,
171 decimal: token.decimal,
172 enabled: token.enabled || existing.token.enabled,
173 iconPath:
174 (token.iconPath?.isNotEmpty ?? false) ? token.iconPath : existing.token.iconPath,
175 tag: token.tag ?? existing.token.tag,
176 isPotentialScam: token.isPotentialScam || existing.token.isPotentialScam,
177 ),
178 sourceKeys: [...existing.sourceKeys, key],
179 );
180 }
181
182 return groups.values.toList();
183 }
184
185 Future<void> migrateToSqlite({required String walletName, required int chainId}) async {
186 final row = erc20_new.Erc20Token(
187 name: name,
188 symbol: symbol,
189 contractAddress: contractAddress,
190 decimal: decimal,
191 enabled: _enabled,
192 iconPath: iconPath,
193 tag: tag,
194 isPotentialScam: isPotentialScam,
195 walletName: walletName,
196 chainId: chainId,
197 ).toMap();
198 row[erc20_new.Erc20Token.selfIdColumn] = null;
199
200 await db!.insert(
201 erc20_new.Erc20Token.tableName,
202 row,
203 conflictAlgorithm: ConflictAlgorithm.replace,
204 );
205 }
206 }
207
208 class _MergedTokenGroup {
209 _MergedTokenGroup({required this.token, required this.sourceKeys});
210
211 final Erc20Token token;
212 final List<dynamic> sourceKeys;
213 }