From e71a1559eeb0395a92a5d4f0b4e719e9e72fa1d0 Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Fri, 19 Dec 2025 22:04:11 +0530 Subject: [PATCH 1/9] deprecate class DBAccountV4 --- lib/services/accounts/accounts.dart | 2 +- lib/services/accounts/native/android.dart | 6 +-- lib/services/accounts/native/apple.dart | 6 +-- lib/services/accounts/web.dart | 2 +- lib/services/db/backup/accounts.dart | 57 +---------------------- 5 files changed, 10 insertions(+), 63 deletions(-) diff --git a/lib/services/accounts/accounts.dart b/lib/services/accounts/accounts.dart index 9d0bf23f..e4fa2862 100644 --- a/lib/services/accounts/accounts.dart +++ b/lib/services/accounts/accounts.dart @@ -31,7 +31,7 @@ abstract class AccountsServiceInterface { // key = wb_$wallet_address, value = $name|$privateKey // get all accounts - Future> getAllAccounts(); + Future> getAllAccounts(); // set account Future setAccount(DBAccount account); diff --git a/lib/services/accounts/native/android.dart b/lib/services/accounts/native/android.dart index c48535ed..aff7a0d1 100644 --- a/lib/services/accounts/native/android.dart +++ b/lib/services/accounts/native/android.dart @@ -64,11 +64,11 @@ class AndroidAccountsService extends AccountsServiceInterface { } // write the account data in the accounts table - // TODO: use DBAccountV4, with getAccountFactoryAddressByAlias final account = DBAccount( alias: legacyBackup.alias, address: EthereumAddress.fromHex(legacyBackup.address), name: legacyBackup.name, + accountFactoryAddress: EthereumAddress.fromHex(getAccountFactoryAddressByAlias(legacyBackup.alias)), ); await _accountsDB.accounts.insert(account); @@ -189,8 +189,8 @@ class AndroidAccountsService extends AccountsServiceInterface { // get all wallet backups @override - Future> getAllAccounts() async { - final List accounts = await _accountsDB.accounts.all(); + Future> getAllAccounts() async { + final List accounts = await _accountsDB.accounts.all(); for (final account in accounts) { final privateKey = await _credentials.read(account.id); diff --git a/lib/services/accounts/native/apple.dart b/lib/services/accounts/native/apple.dart index b8ab257d..864c3cca 100644 --- a/lib/services/accounts/native/apple.dart +++ b/lib/services/accounts/native/apple.dart @@ -168,11 +168,11 @@ class AppleAccountsService extends AccountsServiceInterface { } // write the account data in the accounts table - // TODO: use DBAccountV4, with getAccountFactoryAddressByAlias final DBAccount account = DBAccount( alias: legacyBackup.alias, address: EthereumAddress.fromHex(legacyBackup.address), name: legacyBackup.name, + accountFactoryAddress: EthereumAddress.fromHex(getAccountFactoryAddressByAlias(legacyBackup.alias)), ); await _accountsDB.accounts.insert(account); @@ -302,8 +302,8 @@ class AppleAccountsService extends AccountsServiceInterface { // get all wallet backups @override - Future> getAllAccounts() async { - final List accounts = await _accountsDB.accounts.all(); + Future> getAllAccounts() async { + final List accounts = await _accountsDB.accounts.all(); for (final account in accounts) { final privateKey = await _credentials.read(account.id); diff --git a/lib/services/accounts/web.dart b/lib/services/accounts/web.dart index 00febc33..4b31f568 100644 --- a/lib/services/accounts/web.dart +++ b/lib/services/accounts/web.dart @@ -32,7 +32,7 @@ class WebAccountsService extends AccountsServiceInterface { // get all wallet backups @override - Future> getAllAccounts() async { + Future> getAllAccounts() async { return []; } diff --git a/lib/services/db/backup/accounts.dart b/lib/services/db/backup/accounts.dart index 448571ee..38768736 100644 --- a/lib/services/db/backup/accounts.dart +++ b/lib/services/db/backup/accounts.dart @@ -60,63 +60,10 @@ class DBAccount { } } -class DBAccountV4 extends DBAccount { - final EthereumAddress accountFactoryAddress; - - DBAccountV4({ - required super.alias, - required super.address, - required super.name, - super.username, - super.privateKey, - super.profile, - required this.accountFactoryAddress, - }) : super(); - - // Override toMap to include accountFactoryAddress and update the ID format - @override - Map toMap() { - final map = super.toMap(); - // Update the ID to the V4 format: address@accountFactoryAddress@alias - map['id'] = getAccountIdV4( - address: address, - alias: alias, - accountFactoryAddress: accountFactoryAddress, - ); - map['accountFactoryAddress'] = accountFactoryAddress.hexEip55; - return map; - } - - // fromMap factory for the V4 structure - factory DBAccountV4.fromMap(Map map) { - return DBAccountV4( - alias: map['alias'], - address: EthereumAddress.fromHex(map['address']), - name: map['name'], - username: map['username'], - accountFactoryAddress: - EthereumAddress.fromHex(map['accountFactoryAddress']), - privateKey: map['privateKey'] != null - ? EthPrivateKey.fromHex(map['privateKey']) - : null, - profile: map['profile'] != null - ? ProfileV1.fromJson(jsonDecode(map['profile'])) - : null, - ); - } -} - String getAccountID(EthereumAddress address, String alias) { return '${address.hexEip55}@$alias'; } -String getAccountIdV4({ - required EthereumAddress address, - required String alias, - required EthereumAddress accountFactoryAddress, -}) { - return '${address.hexEip55}@${accountFactoryAddress.hexEip55}@$alias'; -} class UserHandle { final String username; @@ -264,11 +211,11 @@ class AccountsTable extends DBTable { await db.delete(name); } - Future> all() async { + Future> all() async { final List> maps = await db.query(name); return List.generate(maps.length, (i) { - return DBAccountV4.fromMap(maps[i]); + return DBAccount.fromMap(maps[i]); }); } From 70120fe65ad963cceec039b64c06319c66b08383 Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Fri, 19 Dec 2025 22:08:22 +0530 Subject: [PATCH 2/9] todo comment --- lib/services/db/backup/db.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/db/backup/db.dart b/lib/services/db/backup/db.dart index ea620ab1..cdeaf97f 100644 --- a/lib/services/db/backup/db.dart +++ b/lib/services/db/backup/db.dart @@ -40,7 +40,7 @@ class AccountBackupDBService extends DBService { return; }, - version: 4, + version: 4, // TODO: figure out correct version number (Kevin, Others) ); final db = await databaseFactory.openDatabase( From e4c69728f5084818060a31bc0c0d94a5b3a58a8a Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Fri, 19 Dec 2025 22:09:10 +0530 Subject: [PATCH 3/9] attribute accountFactoryAddress in class DBAccount --- lib/services/db/backup/accounts.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/services/db/backup/accounts.dart b/lib/services/db/backup/accounts.dart index 38768736..56ee9746 100644 --- a/lib/services/db/backup/accounts.dart +++ b/lib/services/db/backup/accounts.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:citizenwallet/services/config/utils.dart'; import 'package:citizenwallet/services/db/db.dart'; import 'package:citizenwallet/services/wallet/contracts/profile.dart'; -import 'package:citizenwallet/services/wallet/wallet.dart'; import 'package:flutter/foundation.dart'; import 'package:sqflite/sqlite_api.dart'; import 'package:web3dart/crypto.dart'; @@ -13,6 +12,7 @@ class DBAccount { final String id; final String alias; final EthereumAddress address; + final EthereumAddress accountFactoryAddress; final String name; final UserHandle? userHandle; final String? username; @@ -22,6 +22,7 @@ class DBAccount { DBAccount({ required this.alias, required this.address, + required this.accountFactoryAddress, required this.name, this.username, this.privateKey, @@ -35,6 +36,7 @@ class DBAccount { 'id': id, 'alias': alias, 'address': address.hexEip55, + 'accountFactoryAddress': accountFactoryAddress.hexEip55, if (name.isNotEmpty) 'name': name, 'username': username, 'privateKey': @@ -48,6 +50,7 @@ class DBAccount { return DBAccount( alias: map['alias'], address: EthereumAddress.fromHex(map['address']), + accountFactoryAddress: EthereumAddress.fromHex(map['accountFactoryAddress']), name: map['name'], username: map['username'], privateKey: map['privateKey'] != null From d3d755c1cfb9ff1ef0c300111dd7d60cace18ade Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Fri, 19 Dec 2025 22:09:40 +0530 Subject: [PATCH 4/9] attribute _accountFactoryAddress in WalletService --- lib/services/wallet/wallet.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/services/wallet/wallet.dart b/lib/services/wallet/wallet.dart index afbe4beb..381269c2 100644 --- a/lib/services/wallet/wallet.dart +++ b/lib/services/wallet/wallet.dart @@ -78,6 +78,8 @@ class WalletService { late EthPrivateKey _credentials; // Represents a private key for an Ethereum account. late EthereumAddress _account; // Represents an Ethereum address. + late EthereumAddress _accountFactoryAddress; + late SigAuthService _sigAuth; late StackupEntryPoint @@ -100,6 +102,7 @@ class WalletService { EthPrivateKey get credentials => _credentials; EthereumAddress get address => _credentials.address; EthereumAddress get account => _account; + EthereumAddress get accountFactoryAddress => _accountFactoryAddress; /// retrieves the current balance of the address Future getBalance({String? addr, BigInt? tokenId}) async { @@ -154,13 +157,16 @@ class WalletService { Future init( EthereumAddress account, + EthereumAddress accountFactoryAddress, EthPrivateKey privateKey, NativeCurrency currency, - Config config, { + Config config, + { void Function(String)? onNotify, void Function(bool)? onFinished, }) async { _alias = config.community.alias; + _accountFactoryAddress = accountFactoryAddress; final token = config.getPrimaryToken(); final accountAbstractionConfig = From dd2842385ed1935728258a1ee31220664f99b9e3 Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Fri, 19 Dec 2025 22:11:59 +0530 Subject: [PATCH 5/9] DBAccount with account factory address --- lib/state/app/logic.dart | 5 +++++ lib/state/profile/logic.dart | 6 ++++++ lib/state/profiles/logic.dart | 1 + lib/state/wallet/logic.dart | 5 +++++ 4 files changed, 17 insertions(+) diff --git a/lib/state/app/logic.dart b/lib/state/app/logic.dart index b13b5d34..b8a090ba 100644 --- a/lib/state/app/logic.dart +++ b/lib/state/app/logic.dart @@ -189,6 +189,8 @@ class AppLogic { privateKey: credentials, name: token.name, alias: communityConfig.community.alias, + accountFactoryAddress: EthereumAddress.fromHex(communityConfig.community.primaryAccountFactory.address), + )); _theme.changeTheme(communityConfig.community.theme); @@ -302,6 +304,7 @@ class AppLogic { privateKey: credentials, name: name, alias: communityConfig.community.alias, + accountFactoryAddress: EthereumAddress.fromHex(communityConfig.community.primaryAccountFactory.address), ), ); @@ -366,6 +369,8 @@ class AppLogic { privateKey: credentials, name: '${token.symbol} Web Account', alias: communityConfig.community.alias, + accountFactoryAddress: EthereumAddress.fromHex(communityConfig.community.primaryAccountFactory.address), + ), ); diff --git a/lib/state/profile/logic.dart b/lib/state/profile/logic.dart index 6c53f64a..6bfc1290 100644 --- a/lib/state/profile/logic.dart +++ b/lib/state/profile/logic.dart @@ -150,6 +150,7 @@ class ProfileLogic { Future loadProfile({String? account, bool online = false}) async { final ethAccount = _wallet.account; final alias = _wallet.alias ?? ''; + final accountFactoryAddress = _wallet.accountFactoryAddress.hexEip55; final acc = account ?? ethAccount.hexEip55; resume(); @@ -210,6 +211,7 @@ class ProfileLogic { _accountBackupDBService.accounts.update(DBAccount( alias: alias, address: ethAccount, + accountFactoryAddress: EthereumAddress.fromHex(accountFactoryAddress), name: profile.name, username: profile.username, privateKey: null, @@ -325,6 +327,7 @@ class ProfileLogic { DBAccount( alias: _wallet.alias!, address: EthereumAddress.fromHex(newProfile.account), + accountFactoryAddress: _wallet.accountFactoryAddress, name: newProfile.name, username: newProfile.username, privateKey: null, @@ -407,6 +410,7 @@ class ProfileLogic { DBAccount( alias: _wallet.alias!, address: EthereumAddress.fromHex(newProfile.account), + accountFactoryAddress: _wallet.accountFactoryAddress, name: newProfile.name, username: newProfile.username, privateKey: null, @@ -471,6 +475,7 @@ class ProfileLogic { final address = _wallet.account.hexEip55; final alias = _wallet.alias ?? ''; + final accountFactoryAddress = _wallet.accountFactoryAddress.hexEip55; final account = await _accountBackupDBService.accounts .get(EthereumAddress.fromHex(address), alias); @@ -554,6 +559,7 @@ class ProfileLogic { DBAccount( alias: alias, address: EthereumAddress.fromHex(address), + accountFactoryAddress: EthereumAddress.fromHex(accountFactoryAddress), name: newProfile.name, username: newProfile.username, profile: newProfile, diff --git a/lib/state/profiles/logic.dart b/lib/state/profiles/logic.dart index febe6767..23e983de 100644 --- a/lib/state/profiles/logic.dart +++ b/lib/state/profiles/logic.dart @@ -232,6 +232,7 @@ class ProfilesLogic extends WidgetsBindingObserver { DBAccount( alias: account.alias, address: account.address, + accountFactoryAddress: account.accountFactoryAddress, name: updatedProfile.name, username: updatedProfile.username, profile: updatedProfile, diff --git a/lib/state/wallet/logic.dart b/lib/state/wallet/logic.dart index 2cc8dd78..382c5258 100644 --- a/lib/state/wallet/logic.dart +++ b/lib/state/wallet/logic.dart @@ -374,6 +374,7 @@ class WalletLogic extends WidgetsBindingObserver { await _wallet.init( dbWallet.address, + dbWallet.accountFactoryAddress, dbWallet.privateKey!, nativeCurrency, communityConfig, @@ -475,6 +476,8 @@ class WalletLogic extends WidgetsBindingObserver { privateKey: credentials, name: 'New ${token.symbol} Account', alias: communityConfig.community.alias, + accountFactoryAddress: EthereumAddress.fromHex( + communityConfig.community.primaryAccountFactory.address), )); _theme.changeTheme(communityConfig.community.theme); @@ -542,6 +545,7 @@ class WalletLogic extends WidgetsBindingObserver { privateKey: credentials, name: name, alias: communityConfig.community.alias, + accountFactoryAddress: EthereumAddress.fromHex(communityConfig.community.primaryAccountFactory.address), )); _theme.changeTheme(communityConfig.community.theme); @@ -571,6 +575,7 @@ class WalletLogic extends WidgetsBindingObserver { privateKey: dbWallet.privateKey, name: name, alias: dbWallet.alias, + accountFactoryAddress: dbWallet.accountFactoryAddress, )); loadDBWallets(); From ff418435a180a882f3004d607f3e622b015cd37a Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Fri, 19 Dec 2025 22:35:07 +0530 Subject: [PATCH 6/9] feat: migrate credentials to BackupWalletV5 format with accountFactoryAddress Migrates credential storage from address@alias format to address@accountFactoryAddress@alias format using BackupWalletV5. Updates all credential read/write operations in Android and Apple account services to use the new composite key structure. - Add migration version 5 for both platforms - Update getAllAccounts, setAccount, getAccount, deleteAccount methods - Migrate existing credentials automatically on app upgrade - Ensures accountFactoryAddress is part of credential identification --- lib/services/accounts/native/android.dart | 63 +++++++++++++++++++---- lib/services/accounts/native/apple.dart | 53 +++++++++++++++---- 2 files changed, 96 insertions(+), 20 deletions(-) diff --git a/lib/services/accounts/native/android.dart b/lib/services/accounts/native/android.dart index aff7a0d1..00e12873 100644 --- a/lib/services/accounts/native/android.dart +++ b/lib/services/accounts/native/android.dart @@ -68,7 +68,8 @@ class AndroidAccountsService extends AccountsServiceInterface { alias: legacyBackup.alias, address: EthereumAddress.fromHex(legacyBackup.address), name: legacyBackup.name, - accountFactoryAddress: EthereumAddress.fromHex(getAccountFactoryAddressByAlias(legacyBackup.alias)), + accountFactoryAddress: EthereumAddress.fromHex( + getAccountFactoryAddressByAlias(legacyBackup.alias)), ); await _accountsDB.accounts.insert(account); @@ -193,7 +194,14 @@ class AndroidAccountsService extends AccountsServiceInterface { final List accounts = await _accountsDB.accounts.all(); for (final account in accounts) { - final privateKey = await _credentials.read(account.id); + final backupKey = BackupWalletV5( + address: account.address.hexEip55, + alias: account.alias, + accountFactoryAddress: account.accountFactoryAddress.hexEip55, + privateKey: '', + ).key; + + final privateKey = await _credentials.read(backupKey); if (privateKey == null) { continue; } @@ -213,9 +221,16 @@ class AndroidAccountsService extends AccountsServiceInterface { return; } + final backup = BackupWalletV5( + address: account.address.hexEip55, + alias: account.alias, + accountFactoryAddress: account.accountFactoryAddress.hexEip55, + privateKey: bytesToHex(account.privateKey!.privateKey), + ); + await _credentials.write( - account.id, - bytesToHex(account.privateKey!.privateKey), + backup.key, + backup.value, ); } @@ -231,7 +246,14 @@ class AndroidAccountsService extends AccountsServiceInterface { return null; } - final privateKey = await _credentials.read(account.id); + final backupKey = BackupWalletV5( + address: account.address.hexEip55, + alias: account.alias, + accountFactoryAddress: account.accountFactoryAddress.hexEip55, + privateKey: '', + ).key; + + final privateKey = await _credentials.read(backupKey); if (privateKey == null) { return account; } @@ -250,16 +272,28 @@ class AndroidAccountsService extends AccountsServiceInterface { // delete wallet backup @override Future deleteAccount(String address, String alias) async { + // Get the account before deleting it + final account = + await _accountsDB.accounts.get(EthereumAddress.fromHex(address), alias); + + if (account == null) { + return; + } + await _accountsDB.accounts.delete( EthereumAddress.fromHex(address), alias, ); + final backupKey = BackupWalletV5( + address: account.address.hexEip55, + alias: account.alias, + accountFactoryAddress: account.accountFactoryAddress.hexEip55, + privateKey: '', + ).key; + await _credentials.delete( - getAccountID( - EthereumAddress.fromHex(address), - alias, - ), + backupKey, ); } @@ -357,9 +391,16 @@ class AndroidAccountsService extends AccountsServiceInterface { final allAccounts = await getAllAccounts(); // accounts with private keys for (final account in allAccounts) { + final backup = BackupWalletV5( + address: account.address.hexEip55, + alias: account.alias, + accountFactoryAddress: account.accountFactoryAddress.hexEip55, + privateKey: bytesToHex(account.privateKey!.privateKey), + ); + await _credentials.write( - account.id, - bytesToHex(account.privateKey!.privateKey), + backup.key, + backup.value, ); // null private key before updating in DB diff --git a/lib/services/accounts/native/apple.dart b/lib/services/accounts/native/apple.dart index 864c3cca..f7874c8b 100644 --- a/lib/services/accounts/native/apple.dart +++ b/lib/services/accounts/native/apple.dart @@ -172,7 +172,8 @@ class AppleAccountsService extends AccountsServiceInterface { alias: legacyBackup.alias, address: EthereumAddress.fromHex(legacyBackup.address), name: legacyBackup.name, - accountFactoryAddress: EthereumAddress.fromHex(getAccountFactoryAddressByAlias(legacyBackup.alias)), + accountFactoryAddress: EthereumAddress.fromHex( + getAccountFactoryAddressByAlias(legacyBackup.alias)), ); await _accountsDB.accounts.insert(account); @@ -306,7 +307,14 @@ class AppleAccountsService extends AccountsServiceInterface { final List accounts = await _accountsDB.accounts.all(); for (final account in accounts) { - final privateKey = await _credentials.read(account.id); + final backupKey = BackupWalletV5( + address: account.address.hexEip55, + alias: account.alias, + accountFactoryAddress: account.accountFactoryAddress.hexEip55, + privateKey: '', + ).key; + + final privateKey = await _credentials.read(backupKey); if (privateKey == null) { continue; } @@ -326,9 +334,16 @@ class AppleAccountsService extends AccountsServiceInterface { return; } + final backup = BackupWalletV5( + address: account.address.hexEip55, + alias: account.alias, + accountFactoryAddress: account.accountFactoryAddress.hexEip55, + privateKey: bytesToHex(account.privateKey!.privateKey), + ); + await _credentials.write( - account.id, - bytesToHex(account.privateKey!.privateKey), + backup.key, + backup.value, ); } @@ -344,7 +359,14 @@ class AppleAccountsService extends AccountsServiceInterface { return null; } - final privateKey = await _credentials.read(account.id); + final backupKey = BackupWalletV5( + address: account.address.hexEip55, + alias: account.alias, + accountFactoryAddress: account.accountFactoryAddress.hexEip55, + privateKey: '', + ).key; + + final privateKey = await _credentials.read(backupKey); if (privateKey == null) { return account; } @@ -363,16 +385,29 @@ class AppleAccountsService extends AccountsServiceInterface { // delete wallet backup @override Future deleteAccount(String address, String alias) async { + final account = await _accountsDB.accounts.get( + EthereumAddress.fromHex(address), + alias, + ); + + if (account == null) { + return; + } + await _accountsDB.accounts.delete( EthereumAddress.fromHex(address), alias, ); + final backupKey = BackupWalletV5( + address: account.address.hexEip55, + alias: account.alias, + accountFactoryAddress: account.accountFactoryAddress.hexEip55, + privateKey: '', + ).key; + await _credentials.delete( - getAccountID( - EthereumAddress.fromHex(address), - alias, - ), + backupKey, ); } From fb7395863069997e7613d47dd7620bd625401240 Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Fri, 19 Dec 2025 22:52:21 +0530 Subject: [PATCH 7/9] force use account factory address - get account abs config - get rpc url --- lib/services/config/config.dart | 18 ++++---- .../wallet/contracts/account_factory.dart | 5 ++- lib/services/wallet/wallet.dart | 11 ++--- test/services/config/config_v5_test.dart | 42 ++++++++++--------- 4 files changed, 40 insertions(+), 36 deletions(-) diff --git a/lib/services/config/config.dart b/lib/services/config/config.dart index 5c263d7e..46eed9a1 100644 --- a/lib/services/config/config.dart +++ b/lib/services/config/config.dart @@ -715,7 +715,6 @@ class Config { return primaryToken; } -// TODO: remove use of getPrimaryAccountAbstractionConfig ERC4337Config getPrimaryAccountAbstractionConfig() { final primaryAccountAbstraction = accounts[community.primaryAccountFactory.fullAddress]; @@ -727,14 +726,9 @@ class Config { return primaryAccountAbstraction; } - // TODO: force required accountFactoryAddress - // TODO: remove use of getPrimaryAccountAbstractionConfig - ERC4337Config getAccountAbstractionConfig({String? accountFactoryAddress}) { - // If no accountFactoryAddress is provided, return the primary config - if (accountFactoryAddress == null || accountFactoryAddress.isEmpty) { - return getPrimaryAccountAbstractionConfig(); - } - + ERC4337Config getAccountAbstractionConfig({ + required String accountFactoryAddress, + }) { // Build the full address key using chainId:accountFactoryAddress format final chainId = community.primaryToken.chainId; final fullAddress = '$chainId:$accountFactoryAddress'; @@ -765,8 +759,10 @@ class Config { return chain.node.url; } - // TODO: force required accountFactoryAddress - String getRpcUrl(String chainId, {String? accountFactoryAddress}) { + String getRpcUrl({ + required String chainId, + required String accountFactoryAddress, + }) { final chain = chains[chainId]; if (chain == null) { diff --git a/lib/services/wallet/contracts/account_factory.dart b/lib/services/wallet/contracts/account_factory.dart index cf27bffc..113b162d 100644 --- a/lib/services/wallet/contracts/account_factory.dart +++ b/lib/services/wallet/contracts/account_factory.dart @@ -14,7 +14,10 @@ Future accountFactoryServiceFromConfig(Config config, {String? customAccountFactory}) async { final primaryAccountFactory = config.community.primaryAccountFactory; - final url = config.getRpcUrl(primaryAccountFactory.chainId.toString()); + final url = config.getRpcUrl( + chainId: primaryAccountFactory.chainId.toString(), + accountFactoryAddress: primaryAccountFactory.address, + ); // final wsurl = // config.chains[primaryAccountFactory.chainId.toString()]!.node.wsUrl; print('url: $url'); diff --git a/lib/services/wallet/wallet.dart b/lib/services/wallet/wallet.dart index 381269c2..5e592284 100644 --- a/lib/services/wallet/wallet.dart +++ b/lib/services/wallet/wallet.dart @@ -160,8 +160,7 @@ class WalletService { EthereumAddress accountFactoryAddress, EthPrivateKey privateKey, NativeCurrency currency, - Config config, - { + Config config, { void Function(String)? onNotify, void Function(bool)? onFinished, }) async { @@ -169,14 +168,16 @@ class WalletService { _accountFactoryAddress = accountFactoryAddress; final token = config.getPrimaryToken(); - final accountAbstractionConfig = - config.getPrimaryAccountAbstractionConfig(); + + final accountAbstractionConfig = config.getAccountAbstractionConfig( + accountFactoryAddress: accountFactoryAddress.hexEip55); + final chain = config.chains[token.chainId.toString()]; _url = chain!.node.url; _wsurl = chain.node.wsUrl; - final rpcUrl = config.getRpcUrl(token.chainId.toString()); + final rpcUrl = config.getRpcUrl(chainId: token.chainId.toString(), accountFactoryAddress: accountFactoryAddress.hexEip55); _ethClient = Web3Client( rpcUrl, diff --git a/test/services/config/config_v5_test.dart b/test/services/config/config_v5_test.dart index 633d0194..5f1bf230 100644 --- a/test/services/config/config_v5_test.dart +++ b/test/services/config/config_v5_test.dart @@ -369,7 +369,9 @@ void main() { test('getPrimaryAccountAbstractionConfig returns valid config', () { for (final config in configs) { - final aaConfig = config.getPrimaryAccountAbstractionConfig(); + final aaConfig = config.getAccountAbstractionConfig( + accountFactoryAddress: + config.community.primaryAccountFactory.address); expect(aaConfig.entrypointAddress, isNotEmpty, reason: 'Entrypoint address should not be empty for ${config.community.alias}'); @@ -425,7 +427,9 @@ void main() { group('getAccountAbstractionConfig', () { test('returns primary config when no address provided', () { for (final config in configs) { - final aaConfig = config.getAccountAbstractionConfig(); + final aaConfig = config.getAccountAbstractionConfig( + accountFactoryAddress: + config.community.primaryAccountFactory.address); expect(aaConfig, isA(), reason: 'Should return ERC4337Config for ${config.community.alias}'); @@ -436,18 +440,14 @@ void main() { } }); - test('returns primary config when empty address provided', () { - for (final config in configs) { - final aaConfig = - config.getAccountAbstractionConfig(accountFactoryAddress: ''); - expect(aaConfig, isA(), - reason: - 'Should return ERC4337Config for ${config.community.alias}'); - expect(aaConfig.accountFactoryAddress, - config.community.primaryAccountFactory.address, - reason: - 'Should return primary account factory address when empty string provided for ${config.community.alias}'); - } + test('throws exception when empty address provided', () { + final config = configs.first; + expect( + () => config.getAccountAbstractionConfig(accountFactoryAddress: ''), + throwsException, + reason: + 'Should throw exception when empty account factory address is provided', + ); }); test( @@ -518,7 +518,8 @@ void main() { final expectedUrl = expectedUrls[primaryAccountFactory]; if (expectedUrl != null) { - final actualUrl = config.getRpcUrl(chainId); + final actualUrl = config.getRpcUrl( + chainId: chainId, accountFactoryAddress: primaryAccountFactory); expect(actualUrl, equals(expectedUrl), reason: 'RPC URL mismatch for $alias (primary account factory)'); } @@ -541,7 +542,7 @@ void main() { for (final accountFactory in expectedUrls.keys) { final expectedUrl = expectedUrls[accountFactory]!; final actualUrl = config.getRpcUrl( - chainId, + chainId: chainId, accountFactoryAddress: accountFactory, ); @@ -566,7 +567,7 @@ void main() { final urls = {}; for (final accountFactory in accountFactories) { final url = config.getRpcUrl( - chainId, + chainId: chainId, accountFactoryAddress: accountFactory, ); urls.add(url); @@ -585,7 +586,10 @@ void main() { final config = configs.first; expect( - () => config.getRpcUrl('99999'), + () => config.getRpcUrl( + chainId: '99999', + accountFactoryAddress: + config.community.primaryAccountFactory.address), throwsException, reason: 'Should throw exception for non-existent chain ID', ); @@ -597,7 +601,7 @@ void main() { expect( () => config.getRpcUrl( - chainId, + chainId: chainId, accountFactoryAddress: '0xNonExistentAddress', ), throwsException, From da67ff7c9f9e7daec5cf0d36ae4cd3ab82abdc6c Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Sun, 21 Dec 2025 22:29:22 +0530 Subject: [PATCH 8/9] temp comment out delete keys --- lib/services/accounts/native/android.dart | 3 ++- lib/services/accounts/native/apple.dart | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/services/accounts/native/android.dart b/lib/services/accounts/native/android.dart index 00e12873..81e610ed 100644 --- a/lib/services/accounts/native/android.dart +++ b/lib/services/accounts/native/android.dart @@ -156,7 +156,8 @@ class AndroidAccountsService extends AccountsServiceInterface { await _credentials.write(backup.key, backup.value); // Mark old key for deletion - toDelete.add(oldKey); + // TODO: delete the old key + // toDelete.add(oldKey); } catch (e) { // If we can't determine the account factory address, skip this key debugPrint('Error migrating key $oldKey: $e'); diff --git a/lib/services/accounts/native/apple.dart b/lib/services/accounts/native/apple.dart index f7874c8b..c56dd5d8 100644 --- a/lib/services/accounts/native/apple.dart +++ b/lib/services/accounts/native/apple.dart @@ -266,7 +266,8 @@ class AppleAccountsService extends AccountsServiceInterface { await _credentials.write(backup.key, backup.value); // Mark old key for deletion - toDelete.add(oldKey); + // TODO: delete the old key + // toDelete.add(oldKey); } catch (e) { // If we can't determine the account factory address, skip this key debugPrint('Error migrating key $oldKey: $e'); From ff51ba5a1a6054930f8648fe6189c152f71555b6 Mon Sep 17 00:00:00 2001 From: sajee_techi Date: Sun, 21 Dec 2025 23:47:52 +0530 Subject: [PATCH 9/9] service migration versions for new release - considering bad migration versions of branch https://github.com/citizenwallet/app/compare/release/2.0.30...release/2.0.26 --- lib/services/accounts/accounts.dart | 2 +- lib/services/accounts/native/android.dart | 9 +++++++++ lib/services/accounts/native/apple.dart | 11 +++++++++++ lib/services/db/backup/accounts.dart | 9 +++++++-- lib/services/db/backup/db.dart | 2 +- 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/lib/services/accounts/accounts.dart b/lib/services/accounts/accounts.dart index e4fa2862..a939c4d8 100644 --- a/lib/services/accounts/accounts.dart +++ b/lib/services/accounts/accounts.dart @@ -14,7 +14,7 @@ abstract class AccountsOptionsInterface {} /// /// This is used to store wallet backups and the implementation is platform specific. abstract class AccountsServiceInterface { - final int _version = 5; // TODO: figure out correct version number (Kevin, Others) + final int _version = 7; int get version => _version; diff --git a/lib/services/accounts/native/android.dart b/lib/services/accounts/native/android.dart index 81e610ed..56ae764d 100644 --- a/lib/services/accounts/native/android.dart +++ b/lib/services/accounts/native/android.dart @@ -95,6 +95,15 @@ class AndroidAccountsService extends AccountsServiceInterface { } }, 5: () async { + // bad migration, https://github.com/citizenwallet/app/blob/d4f72940e11f1812c34dfb47c0bffe7488a1c32e/lib/services/accounts/native/android.dart#L146 + }, + 6: () async { + // bad migration, https://github.com/citizenwallet/app/blob/d4f72940e11f1812c34dfb47c0bffe7488a1c32e/lib/services/accounts/native/android.dart#L251 + }, + 7: () async { + // distinguish migration starting from 4 (Others) + //distinguish migration starting from 6 (Kevin, Jonas) + // Read all credentials from secure storage final allValues = await _credentials.readAll(); diff --git a/lib/services/accounts/native/apple.dart b/lib/services/accounts/native/apple.dart index c56dd5d8..6a5f48b5 100644 --- a/lib/services/accounts/native/apple.dart +++ b/lib/services/accounts/native/apple.dart @@ -208,6 +208,17 @@ class AppleAccountsService extends AccountsServiceInterface { } }, 5: () async { + // bad migration, https://github.com/citizenwallet/app/blob/d4f72940e11f1812c34dfb47c0bffe7488a1c32e/lib/services/accounts/native/apple.dart#L154 + }, + 6: () async { + // bad migration, https://github.com/citizenwallet/app/blob/d4f72940e11f1812c34dfb47c0bffe7488a1c32e/lib/services/accounts/native/apple.dart#L264 + }, + 7: () async { + + // distinguish migration starting from 4 (Others) + //distinguish migration starting from 6 (Kevin, Jonas) + + // Read all credentials from Keychain final allValues = await _credentials.readAll(); diff --git a/lib/services/db/backup/accounts.dart b/lib/services/db/backup/accounts.dart index 56ee9746..0562feaf 100644 --- a/lib/services/db/backup/accounts.dart +++ b/lib/services/db/backup/accounts.dart @@ -50,7 +50,8 @@ class DBAccount { return DBAccount( alias: map['alias'], address: EthereumAddress.fromHex(map['address']), - accountFactoryAddress: EthereumAddress.fromHex(map['accountFactoryAddress']), + accountFactoryAddress: + EthereumAddress.fromHex(map['accountFactoryAddress']), name: map['name'], username: map['username'], privateKey: map['privateKey'] != null @@ -67,7 +68,6 @@ String getAccountID(EthereumAddress address, String alias) { return '${address.hexEip55}@$alias'; } - class UserHandle { final String username; final String communityAlias; @@ -121,6 +121,11 @@ class AccountsTable extends DBTable { 'ALTER TABLE $name ADD COLUMN username TEXT DEFAULT NULL', ], 4: [ + // bad migration,https://github.com/citizenwallet/app/blob/d4f72940e11f1812c34dfb47c0bffe7488a1c32e/lib/services/db/backup/accounts.dart#L123 + ], + 5: [ + // Kevin start from 4 + // Others start from 3 'ALTER TABLE $name ADD COLUMN accountFactoryAddress TEXT DEFAULT ""', 'PopulateAccountFactoryAddressMigration', ] diff --git a/lib/services/db/backup/db.dart b/lib/services/db/backup/db.dart index cdeaf97f..595fc9b3 100644 --- a/lib/services/db/backup/db.dart +++ b/lib/services/db/backup/db.dart @@ -40,7 +40,7 @@ class AccountBackupDBService extends DBService { return; }, - version: 4, // TODO: figure out correct version number (Kevin, Others) + version: 5, ); final db = await databaseFactory.openDatabase(