dev
dart 466 lines 12.8 KB
Raw
1 import 'dart:io';
2
3 import 'package:cw_core/db/sqlite_debug.dart';
4 import 'package:cw_core/root_dir.dart';
5 import 'package:cw_core/utils/print_verbose.dart';
6 import 'package:flutter/foundation.dart';
7 import 'package:sqflite_common_ffi/sqflite_ffi.dart';
8 import 'package:path/path.dart' as p;
9
10 Database? db;
11
12 Future<void> _addColumnIfNotExists(
13 Database db, {
14 required String table,
15 required String column,
16 required String definition,
17 }) async {
18 final result = await db.rawQuery("PRAGMA table_info($table)");
19 final columnExists = result.any((row) => row['name'] == column);
20
21 if (!columnExists) {
22 await db.execute(
23 'ALTER TABLE $table ADD COLUMN $column $definition;',
24 );
25 }
26 }
27
28 Future<File> sqliteDebugMarkerFile() async {
29 final appDir = await getAppDir();
30 final dbDebugMarker = p.join(appDir.path, ".sqlite_db_debug");
31 return File(dbDebugMarker);
32 }
33
34 Future<void> initDb({String? pathOverride}) async {
35 if (!kDebugMode && !kProfileMode) {
36 await _initDb(pathOverride: pathOverride);
37 return;
38 }
39 final dbDebugMarker = await sqliteDebugMarkerFile();
40 try {
41 if (dbDebugMarker.existsSync()) {
42 throw Exception("Debug marker is present");
43 }
44 await _initDb(pathOverride: pathOverride);
45 } catch (e, s) {
46 await handleSqliteError(e, s);
47 }
48 }
49
50 Future<void> _initDb({String? pathOverride}) async {
51 if (Platform.isLinux || Platform.isWindows) {
52 databaseFactory = databaseFactoryFfi;
53 }
54
55 // getAppDir is predictable on all platforms and ensures the db gets included in backups.
56 final dbFileOld = File("${await getDatabasesPath()}/cake.db");
57 final dbFile = File("${(await getAppDir()).path}/cake.db");
58
59 if (Platform.isAndroid && dbFileOld.existsSync() && dbFileOld.path != dbFile.path) {
60 final copied = dbFileOld.copySync(dbFile.path);
61 if (copied.existsSync()) {
62 dbFileOld.deleteSync();
63 }
64 }
65 await db?.close();
66 db = await openDatabase(dbFile.path, version: 10,
67 onUpgrade: (Database db, int oldVersion, int newVersion) async {
68 printV("migrating: $oldVersion, $newVersion");
69 if (oldVersion <= 1) {
70 await db.execute('''
71 DELETE FROM WalletInfo
72 WHERE walletInfoId NOT IN (
73 SELECT MIN(walletInfoId)
74 FROM WalletInfo
75 GROUP BY id
76 );
77
78 CREATE UNIQUE INDEX IF NOT EXISTS idx_walletinfo_id_unique
79 ON WalletInfo (id);
80 ''');
81 }
82 if (oldVersion <= 2) {
83 await db.execute('''
84 CREATE TABLE IF NOT EXISTS BalanceCardStyleSettings (
85 walletInfoId INTEGER,
86 accountIndex INTEGER DEFAULT -1,
87 gradientIndex INTEGER DEFAULT -1,
88 useSpecialDesign BOOLEAN DEFAULT FALSE,
89 backgroundImagePath TEXT DEFAULT "",
90 PRIMARY KEY (walletInfoId, accountIndex),
91 FOREIGN KEY (walletInfoId) REFERENCES WalletInfo(walletInfoId)
92 );
93 ''');
94 await _addColumnIfNotExists(
95 db,
96 table: 'WalletInfo',
97 column: 'receiveInfoboxDismissed',
98 definition: 'BOOLEAN DEFAULT FALSE',
99 );
100
101 await _addColumnIfNotExists(
102 db,
103 table: 'BalanceCardStyleSettings',
104 column: 'cardOrder',
105 definition: 'INTEGER DEFAULT 0',
106 );
107 }
108 if (oldVersion <= 3) {
109 await _addColumnIfNotExists(db,
110 table: "WalletInfo", column: "showCombinedBalance", definition: "BOOLEAN DEFAULT TRUE");
111 // null - primary token (eth, sol etc)
112 // not null - address of fav token
113 // if address doesn't correspond to a valid token, fallback to primary token
114 await _addColumnIfNotExists(db,
115 table: "WalletInfo", column: "favoriteTokenAddress", definition: "TEXT DEFAULT NULL");
116 }
117
118 if (oldVersion <= 4) {
119 await _createBridgeTransferTable(db);
120 }
121
122 if (oldVersion <= 5) {
123 await _createTradeTable(db);
124 }
125 if (oldVersion <= 6) {
126 await _addColumnIfNotExists(
127 db,
128 table: 'Trade',
129 column: 'toAddressExtraId',
130 definition: 'TEXT',
131 );
132 }
133 if (oldVersion <= 7) {
134 await _createNodeTable(db);
135 }
136 if (oldVersion <= 8) {
137 await _addColumnIfNotExists(
138 db,
139 table: 'BalanceCardStyleSettings',
140 column: 'iconStyleIndex',
141 definition: 'INTEGER DEFAULT 0',
142 );
143 await _addColumnIfNotExists(
144 db,
145 table: 'BalanceCardStyleSettings',
146 column: 'isGradientOnly',
147 definition: 'BOOLEAN DEFAULT FALSE',
148 );
149 }
150 if (oldVersion <= 9) {
151 await _createErc20TokenTable(db);
152 await _createSplTokenTable(db);
153 await _createTronTokenTable(db);
154 }
155 }, onCreate: (Database db, int version) async {
156 await db.execute('''
157 CREATE TABLE WalletInfo (
158 walletInfoId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
159 id TEXT NOT NULL,
160 name TEXT NOT NULL,
161 "type" INTEGER NOT NULL,
162 isRecovery INTEGER DEFAULT (0) NOT NULL,
163 walletInfoDerivationInfoId INTEGER NOT NULL,
164 restoreHeight INTEGER DEFAULT (0) NOT NULL,
165 "timestamp" INTEGER DEFAULT (0) NOT NULL,
166 dirPath TEXT NOT NULL,
167 "path" TEXT NOT NULL,
168 address TEXT NOT NULL,
169 yatEid TEXT,
170 yatLastUsedAddressRaw TEXT,
171 showIntroCakePayCard INTEGER DEFAULT (1),
172 addressPageType TEXT,
173 network TEXT,
174 hardwareWalletType INTEGER,
175 parentAddress TEXT,
176 hashedWalletIdentifier TEXT,
177 isNonSeedWallet INTEGER DEFAULT (0) NOT NULL,
178 sortOrder INTEGER DEFAULT (0) NOT NULL,
179 receiveInfoboxDismissed BOOLEAN DEFAULT FALSE,
180 showCombinedBalance BOOLEAN DEFAULT TRUE,
181 favoriteTokenAddress TEXT DEFAULT NULL
182 );
183 ''');
184
185 await db.execute('''
186 CREATE TABLE WalletInfoDerivationInfo (
187 walletInfoDerivationInfoId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
188 address TEXT NOT NULL,
189 balance TEXT NOT NULL,
190 transactionsCount INTEGER DEFAULT (0) NOT NULL,
191 derivationType INTEGER NOT NULL,
192 derivationPath TEXT,
193 scriptType TEXT,
194 description TEXT
195 );
196 ''');
197
198 await db.execute('''
199 CREATE TABLE WalletInfoAddress (
200 walletInfoAddressId INTEGER PRIMARY KEY AUTOINCREMENT,
201 walletInfoId INTEGER,
202 "type" INTEGER NOT NULL,
203 address TEXT NOT NULL,
204 CONSTRAINT WalletInfoAddress_WalletInfo_FK FOREIGN KEY (walletInfoId) REFERENCES WalletInfo(walletInfoId)
205 );
206 ''');
207
208 await db.execute('''
209 CREATE TABLE WalletInfoAddressInfo (
210 walletInfoAddressInfoId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
211 walletInfoId INTEGER NOT NULL,
212 mapKey INTEGER NOT NULL,
213 mapValueAccountIndex INTEGER NOT NULL,
214 mapValueAddress TEXT NOT NULL,
215 mapValueLabel TEXT NOT NULL,
216 CONSTRAINT WalletInfoAddressInfo_WalletInfo_FK FOREIGN KEY (walletInfoId) REFERENCES WalletInfo(walletInfoId)
217 );
218 ''');
219
220 await db.execute('''
221 CREATE TABLE "WalletInfoAddressMap" (
222 walletInfoAddressMapId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
223 walletInfoId INTEGER NOT NULL,
224 addressKey TEXT NOT NULL,
225 addressValue TEXT NOT NULL,
226 CONSTRAINT WalletInfoAddress_WalletInfo_FK FOREIGN KEY (walletInfoId) REFERENCES WalletInfo(walletInfoId)
227 );
228 ''');
229 await db.execute('''
230 CREATE UNIQUE INDEX IF NOT EXISTS idx_walletinfo_id_unique
231 ON WalletInfo (id);
232 ''');
233 await db.execute('''
234 CREATE TABLE BalanceCardStyleSettings (
235 walletInfoId INTEGER,
236 accountIndex INTEGER DEFAULT -1,
237 gradientIndex INTEGER DEFAULT -1,
238 useSpecialDesign BOOLEAN DEFAULT FALSE,
239 backgroundImagePath TEXT DEFAULT "",
240 iconStyleIndex INTEGER DEFAULT 0,
241 isGradientOnly BOOLEAN DEFAULT FALSE,
242 cardOrder INTEGER DEFAULT 0,
243 PRIMARY KEY (walletInfoId, accountIndex),
244 FOREIGN KEY (walletInfoId) REFERENCES WalletInfo(walletInfoId)
245 );
246 ''');
247 await _createBridgeTransferTable(db);
248 await _createNodeTable(db);
249 await _createTradeTable(db);
250 await _createErc20TokenTable(db);
251 await _createSplTokenTable(db);
252 await _createTronTokenTable(db);
253 });
254 }
255
256 Future<void> _createTradeTable(Database db) async {
257 await db.execute('''
258 CREATE TABLE IF NOT EXISTS Trade (
259 tradeId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
260 id TEXT NOT NULL,
261 providerRaw INTEGER NOT NULL DEFAULT 0,
262 fromTitle TEXT,
263 fromName TEXT,
264 fromTag TEXT,
265 fromFullName TEXT,
266 fromDecimals INTEGER,
267 fromRaw INTEGER,
268 fromIconPath TEXT,
269 fromFlatIconPath TEXT,
270 fromChainIconPath TEXT,
271 toTitle TEXT,
272 toName TEXT,
273 toTag TEXT,
274 toFullName TEXT,
275 toDecimals INTEGER,
276 toRaw INTEGER,
277 toIconPath TEXT,
278 toFlatIconPath TEXT,
279 toChainIconPath TEXT,
280 stateRaw TEXT NOT NULL DEFAULT '',
281 createdAt INTEGER,
282 expiredAt INTEGER,
283 amount TEXT NOT NULL DEFAULT '',
284 receiveAmount TEXT,
285 inputAddress TEXT,
286 extraId TEXT,
287 outputTransaction TEXT,
288 refundAddress TEXT,
289 walletId TEXT,
290 payoutAddress TEXT,
291 toAddressExtraId TEXT,
292 password TEXT,
293 providerId TEXT,
294 providerName TEXT,
295 fromWalletAddress TEXT,
296 memo TEXT,
297 txId TEXT,
298 isRefund INTEGER DEFAULT 0,
299 isSendAll INTEGER DEFAULT 0,
300 router TEXT,
301 needToRegisterInSwapXyz INTEGER DEFAULT 0,
302 sourceTokenAddress TEXT,
303 sourceTokenDecimals INTEGER,
304 routerData TEXT,
305 routerValue TEXT,
306 routerChainId INTEGER,
307 sourceTokenAmountRaw TEXT,
308 requiresTokenApproval INTEGER DEFAULT 0,
309 chainId INTEGER,
310 fee REAL
311 );
312 ''');
313 await db.execute('''
314 CREATE UNIQUE INDEX IF NOT EXISTS idx_trade_id_unique
315 ON Trade (id);
316 ''');
317 }
318
319 Future<Map<String, dynamic>> dumpDb() async {
320 try {
321 return await _dumpDb();
322 } catch (e) {
323 return {
324 "error": e.toString(),
325 "stackTrace": StackTrace.current.toString(),
326 };
327 }
328 }
329
330 Future<List<String>> _getTableNames(Database db) async {
331 final tableNames = await db.rawQuery('SELECT name FROM sqlite_master WHERE type = "table"');
332 return tableNames.map((e) => (e["name"]).toString()).toList();
333 }
334
335 Future<Map<String, dynamic>> _dumpDb() async {
336 final tableNames = await _getTableNames(db!);
337 final ret = <String, dynamic>{};
338 for (final tableName in tableNames) {
339 ret[tableName] = await db!.query(tableName);
340 }
341 return ret;
342 }
343
344 Future<Map<String, dynamic>> dumpCustomDb(String path) async {
345 final db = await openDatabase(path);
346 final tableNames = await _getTableNames(db);
347 final ret = <String, dynamic>{};
348 for (final tableName in tableNames) {
349 ret[tableName] = await db.query(tableName);
350 }
351 return ret;
352 }
353
354 Future<void> _createBridgeTransferTable(Database db) async {
355 await db.execute('''
356 CREATE TABLE IF NOT EXISTS BridgeTransfer (
357 id TEXT NOT NULL PRIMARY KEY,
358 wallet_id TEXT NOT NULL,
359 source_chain_id INTEGER NOT NULL,
360 destination_chain_id INTEGER NOT NULL,
361 token_symbol TEXT NOT NULL,
362 token_contract TEXT NOT NULL,
363 amount TEXT NOT NULL,
364 recipient_address TEXT NOT NULL,
365 source_tx_hash TEXT NOT NULL,
366 status TEXT NOT NULL,
367 created_at INTEGER NOT NULL,
368 updated_at INTEGER,
369 confirmed_at INTEGER,
370 amount_raw TEXT,
371 error_message TEXT,
372 status_message TEXT
373 );
374 ''');
375 await db.execute('''
376 CREATE INDEX IF NOT EXISTS idx_bridgetransfer_wallet_id
377 ON BridgeTransfer(wallet_id);
378 ''');
379 }
380
381 Future<void> _createErc20TokenTable(Database db) async {
382 await db.execute("""
383 CREATE TABLE IF NOT EXISTS Erc20Token (
384 Erc20TokenId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
385 walletName TEXT NOT NULL,
386 chainId INTEGER NOT NULL,
387 name TEXT NOT NULL DEFAULT '',
388 symbol TEXT NOT NULL DEFAULT '',
389 contractAddress TEXT NOT NULL,
390 decimal INTEGER NOT NULL DEFAULT 0,
391 enabled INTEGER NOT NULL DEFAULT 1,
392 iconPath TEXT,
393 tag TEXT,
394 isPotentialScam INTEGER NOT NULL DEFAULT 0
395 );
396 """);
397 await db.execute("""
398 CREATE UNIQUE INDEX IF NOT EXISTS idx_erc20token_wallet_chain_contract
399 ON Erc20Token (walletName, chainId, contractAddress);
400 """);
401 }
402
403 Future<void> _createSplTokenTable(Database db) async {
404 await db.execute("""
405 CREATE TABLE IF NOT EXISTS SPLToken (
406 SPLTokenId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
407 walletName TEXT NOT NULL,
408 name TEXT NOT NULL DEFAULT '',
409 symbol TEXT NOT NULL DEFAULT '',
410 mintAddress TEXT NOT NULL,
411 decimal INTEGER NOT NULL DEFAULT 0,
412 mint TEXT NOT NULL DEFAULT '',
413 enabled INTEGER NOT NULL DEFAULT 1,
414 iconPath TEXT,
415 tag TEXT,
416 isPotentialScam INTEGER NOT NULL DEFAULT 0
417 );
418 """);
419 await db.execute("""
420 CREATE UNIQUE INDEX IF NOT EXISTS idx_spltoken_wallet_mint
421 ON SPLToken (walletName, mintAddress);
422 """);
423 }
424
425 Future<void> _createTronTokenTable(Database db) async {
426 await db.execute("""
427 CREATE TABLE IF NOT EXISTS TronToken (
428 TronTokenId INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
429 walletName TEXT NOT NULL,
430 name TEXT NOT NULL DEFAULT '',
431 symbol TEXT NOT NULL DEFAULT '',
432 contractAddress TEXT NOT NULL,
433 decimal INTEGER NOT NULL DEFAULT 0,
434 enabled INTEGER NOT NULL DEFAULT 1,
435 iconPath TEXT,
436 tag TEXT,
437 isPotentialScam INTEGER NOT NULL DEFAULT 0
438 );
439 """);
440 await db.execute("""
441 CREATE UNIQUE INDEX IF NOT EXISTS idx_trontoken_wallet_contract
442 ON TronToken (walletName, contractAddress);
443 """);
444 }
445
446 Future<void> _createNodeTable(Database db) async {
447 db.execute("""
448 CREATE TABLE Node (
449 NodeId INTEGER PRIMARY KEY,
450 uri TEXT NOT NULL,
451 path TEXT,
452 login TEXT,
453 label TEXT,
454 password TEXT,
455 isPow INTEGER NOT NULL,
456 useSSL INTEGER,
457 typeRaw INTEGER NOT NULL,
458 trusted INTEGER NOT NULL,
459 socksProxyAddress TEXT,
460 isEnabledForAutoSwitching BOOLEAN DEFAULT FALSE,
461 isOfficial BOOLEAN DEFAULT FALSE,
462 isBuiltin BOOLEAN DEFAULT FALSE,
463 isDefault BOOLEAN DEFAULT FALSE
464 );
465 """);
466 }