Skip to content

Commit 1e0120c

Browse files
authored
Merge pull request #948 from SatoshiPortal/develop
5.2.0
2 parents b93e052 + 1c63f2c commit 1e0120c

47 files changed

Lines changed: 1134 additions & 876 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CONTRIBUTING.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,22 @@ TODO: Describe the branch structure, forking to a personal repository, pull requ
66

77
## Generated files
88

9-
This project uses flutter freezed. Freezed files are not checked into git and must be generated locally using:
9+
A `makefile` is available to assist you during the setup of the project and generate all needed files using the command `make setup`.
1010

11-
```
12-
flutter pub run build_runner watch --delete-conflicting-outputs
13-
```
11+
12+
## Guidelines
13+
14+
- Keep it simple.
15+
16+
- Write code that's easy to read and maintain think of your teammate who will read it next.
17+
18+
- Suffix your files (`_datasource.dart` `_repository.dart` `_model.dart` `_usecase.dart` `_entity.dart` `_widget.dart` `_page.dart` )
19+
20+
- Avoid creating unnecessary folders. Since most files have descriptive suffixes, only create a folder if you're placing multiple related files inside.
21+
22+
- Avoid abstract classes unless there's a clear current need. Don’t preemptively abstract for hypothetical future cases, start concrete, and refactor when it becomes necessary.
23+
24+
- Favor end-to-end and integration testing at the use case and widget level rather than relying on mocks. Since this is an app project, our priority is to catch real-world issues across layers. Mocking should be used only when necessary
1425

1526
## Adding a new feature
1627

ios/Runner.xcodeproj/project.pbxproj

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -488,19 +488,19 @@
488488
buildSettings = {
489489
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
490490
CLANG_ENABLE_MODULES = YES;
491-
CURRENT_PROJECT_VERSION = 54;
491+
CURRENT_PROJECT_VERSION = 57;
492492
DEVELOPMENT_TEAM = BX99T32YGS;
493493
ENABLE_BITCODE = NO;
494-
FLUTTER_BUILD_NAME = 5.1.0;
495-
FLUTTER_BUILD_NUMBER = 54;
494+
FLUTTER_BUILD_NAME = 5.1.1;
495+
FLUTTER_BUILD_NUMBER = 57;
496496
INFOPLIST_FILE = Runner/Info.plist;
497497
INFOPLIST_KEY_CFBundleDisplayName = BULL;
498498
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance";
499499
LD_RUNPATH_SEARCH_PATHS = (
500500
"$(inherited)",
501501
"@executable_path/Frameworks",
502502
);
503-
MARKETING_VERSION = 5.1.0;
503+
MARKETING_VERSION = 5.1.1;
504504
PRODUCT_BUNDLE_IDENTIFIER = com.bullbitcoin.app;
505505
PRODUCT_NAME = "$(TARGET_NAME)";
506506
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -678,19 +678,19 @@
678678
buildSettings = {
679679
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
680680
CLANG_ENABLE_MODULES = YES;
681-
CURRENT_PROJECT_VERSION = 54;
681+
CURRENT_PROJECT_VERSION = 57;
682682
DEVELOPMENT_TEAM = BX99T32YGS;
683683
ENABLE_BITCODE = NO;
684-
FLUTTER_BUILD_NAME = 5.1.0;
685-
FLUTTER_BUILD_NUMBER = 54;
684+
FLUTTER_BUILD_NAME = 5.1.1;
685+
FLUTTER_BUILD_NUMBER = 57;
686686
INFOPLIST_FILE = Runner/Info.plist;
687687
INFOPLIST_KEY_CFBundleDisplayName = BULL;
688688
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance";
689689
LD_RUNPATH_SEARCH_PATHS = (
690690
"$(inherited)",
691691
"@executable_path/Frameworks",
692692
);
693-
MARKETING_VERSION = 5.1.0;
693+
MARKETING_VERSION = 5.1.1;
694694
PRODUCT_BUNDLE_IDENTIFIER = com.bullbitcoin.app;
695695
PRODUCT_NAME = "$(TARGET_NAME)";
696696
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -706,19 +706,19 @@
706706
buildSettings = {
707707
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
708708
CLANG_ENABLE_MODULES = YES;
709-
CURRENT_PROJECT_VERSION = 54;
709+
CURRENT_PROJECT_VERSION = 57;
710710
DEVELOPMENT_TEAM = BX99T32YGS;
711711
ENABLE_BITCODE = NO;
712-
FLUTTER_BUILD_NAME = 5.1.0;
713-
FLUTTER_BUILD_NUMBER = 54;
712+
FLUTTER_BUILD_NAME = 5.1.1;
713+
FLUTTER_BUILD_NUMBER = 57;
714714
INFOPLIST_FILE = Runner/Info.plist;
715715
INFOPLIST_KEY_CFBundleDisplayName = BULL;
716716
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.finance";
717717
LD_RUNPATH_SEARCH_PATHS = (
718718
"$(inherited)",
719719
"@executable_path/Frameworks",
720720
);
721-
MARKETING_VERSION = 5.1.0;
721+
MARKETING_VERSION = 5.1.1;
722722
PRODUCT_BUNDLE_IDENTIFIER = com.bullbitcoin.app;
723723
PRODUCT_NAME = "$(TARGET_NAME)";
724724
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";

lib/core/storage/migrations/004_legacy/migration01to02.dart

Lines changed: 81 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -34,64 +34,71 @@ Future<void> doMigration0_1to0_2() async {
3434

3535
OldSeed? liquidMainnetSeed;
3636
bool isDefault = true;
37-
log.info('0.1.*: Found ${walletIds.length} wallets');
37+
log.info('0.1.*: Starting wallet migration');
3838

3939
for (final walletId in walletIds) {
40-
final jsn = hiveDatasource.getValue(walletId as String);
41-
if (jsn == null) throw 'Abort';
42-
43-
Map<String, dynamic> walletObj = jsonDecode(jsn) as Map<String, dynamic>;
44-
45-
final mnemonicFingerprint = walletObj['mnemonicFingerprint'] as String;
46-
final seed = await oldSeedRepository.fetch(
47-
fingerprint: mnemonicFingerprint,
48-
);
49-
final res = await updateWalletObj(walletObj, seed, isDefault);
50-
liquidMainnetSeed ??= res.liquidMainnetSeed;
51-
walletObj = res.walletObj;
52-
if (liquidMainnetSeed != null) {
53-
isDefault = false;
40+
try {
41+
final jsn = hiveDatasource.getValue(walletId as String);
42+
if (jsn == null) {
43+
log.warning('0.1.*: Wallet data not found for ID: $walletId');
44+
continue;
45+
}
46+
47+
Map<String, dynamic> walletObj =
48+
jsonDecode(jsn) as Map<String, dynamic>;
49+
log.info(
50+
'0.1.*: Processing wallet ${walletObj['id']}, type: ${walletObj['type']}, network: ${walletObj['network']}',
51+
);
52+
53+
final mnemonicFingerprint = walletObj['mnemonicFingerprint'] as String;
54+
OldSeed? seed;
55+
try {
56+
seed = await oldSeedRepository.fetch(
57+
fingerprint: mnemonicFingerprint,
58+
);
59+
} catch (e) {
60+
log.severe(
61+
'0.1.*: Failed to fetch seed for wallet ${walletObj['id']}',
62+
);
63+
}
64+
65+
final res = await updateWalletObj(walletObj, seed, isDefault);
66+
liquidMainnetSeed ??= res.liquidMainnetSeed;
67+
walletObj = res.walletObj;
68+
if (liquidMainnetSeed != null) {
69+
isDefault = false;
70+
}
71+
72+
log.info(
73+
'0.1.*: Updated wallet ${walletObj['id']} to type: ${walletObj['type']}, mainWallet: ${walletObj['mainWallet']}',
74+
);
75+
76+
walletObj = await addIsLiquidFalse(walletObj);
77+
78+
final w = OldWallet.fromJson(walletObj);
79+
wallets.add(w);
80+
} catch (e) {
81+
log.severe('0.1.*: Error processing wallet $walletId: $e');
82+
continue;
5483
}
55-
56-
walletObj = await addIsLiquidFalse(walletObj);
57-
58-
final w = OldWallet.fromJson(walletObj);
59-
wallets.add(w);
60-
// this loop only adds bitcoin wallets
6184
}
6285

6386
if (liquidMainnetSeed == null) {
64-
throw 'Could not create liquid mainnet wallet. Abort.';
87+
throw 'Could not create liquid mainnet wallet. No valid mainnet seed found.';
6588
}
66-
final liqWallet = await createLiquidWallet(liquidMainnetSeed);
6789

90+
log.info('0.1.*: Creating liquid wallet from mainnet seed');
91+
final liqWallet = await createLiquidWallet(liquidMainnetSeed);
6892
wallets.addAll([liqWallet]);
69-
70-
// final mainWalletIdx = wallets.indexWhere(
71-
// (w) => !w.isTestnet() && w.isSecure(),
72-
// );
73-
74-
// final liqMainnetIdx = wallets.indexWhere(
75-
// (w) => !w.isTestnet() && w.isInstant(),
76-
// );
77-
78-
// if (mainWalletIdx != -1 && liqMainnetIdx != -1) {
79-
// if (wallets.length > 2) {
80-
// final tempMain = wallets[mainWalletIdx];
81-
// final tempLiq = wallets[liqMainnetIdx];
82-
// wallets.removeAt(mainWalletIdx);
83-
// wallets.removeAt(liqMainnetIdx - 1);
84-
// wallets.insert(0, tempLiq);
85-
// wallets.insert(1, tempMain);
86-
// }
87-
// }
93+
log.info('0.1.*: Liquid wallet created successfully');
8894

8995
final walletObjs = wallets.map((w) => w.toJson()).toList();
9096
final List<String> ids = [];
9197
for (final w in walletObjs) {
9298
final id = w['id'] as String;
9399
ids.add(id);
94100
final _ = await hiveDatasource.saveValue(key: id, value: jsonEncode(w));
101+
log.info('0.1.*: Saved migrated wallet $id');
95102
}
96103

97104
final idsJsn = jsonEncode({
@@ -105,19 +112,22 @@ Future<void> doMigration0_1to0_2() async {
105112
final walletIdsRawPost = hiveDatasource.getValue(
106113
OldStorageKeys.wallets.name,
107114
);
108-
if (walletIdsRawPost == null) throw 'No Wallets found';
115+
if (walletIdsRawPost == null) throw 'No Wallets found after migration';
109116

110117
final walletIdsPost =
111118
jsonDecode(walletIdsRawPost)['wallets'] as List<dynamic>;
112-
if (walletIdsPost.isEmpty) throw 'No Wallets found';
113-
log.info('0.1.*: Updated Wallets-> ${walletIdsPost.length} wallets');
119+
if (walletIdsPost.isEmpty) throw 'No Wallets found after migration';
120+
log.info(
121+
'0.1.*: Migration completed. Total wallets: ${walletIdsPost.length}',
122+
);
114123

115124
await secureStorageDatasource.store(
116125
key: OldStorageKeys.version.name,
117126
value: '0.2.0',
118-
); // gets overwritten by the exact 0.2.* version later}
119-
} catch (e) {
120-
log.severe('Legacy Migration Failed: $e');
127+
); // gets overwritten by the exact 0.2.* version later
128+
} catch (e, stack) {
129+
log.severe('Legacy Migration Failed', error: e, trace: stack);
130+
rethrow;
121131
}
122132
}
123133

@@ -128,6 +138,12 @@ updateWalletObj(
128138
bool isDefault,
129139
) async {
130140
OldSeed? liquidMainnetSeed;
141+
final originalType = walletObj['type'];
142+
final network = walletObj['network'];
143+
144+
log.info(
145+
'0.1.*: updateWalletObj - Processing wallet ${walletObj['id']}: type=$originalType, network=$network, isDefault=$isDefault',
146+
);
131147

132148
// TODO: Test this assumption
133149
// Assuming first wallet is to be changed to secure and further wallets to words
@@ -139,23 +155,35 @@ updateWalletObj(
139155
walletObj['type'] = 'main';
140156
walletObj['name'] = 'Secure Bitcoin';
141157
walletObj['mainWallet'] = true;
142-
143158
liquidMainnetSeed = seed;
159+
log.info(
160+
'0.1.*: Converted mainnet wallet to main type: ${walletObj['id']}',
161+
);
144162
} else {
145163
walletObj['type'] = 'words';
146164
walletObj['mainWallet'] = false;
165+
log.info(
166+
'0.1.*: Converted mainnet wallet to words type: ${walletObj['id']}',
167+
);
147168
}
148169
} else {
149170
walletObj['type'] = 'words';
150171
walletObj['mainWallet'] = false;
172+
log.info(
173+
'0.1.*: Converted non-mainnet wallet to words type: ${walletObj['id']}',
174+
);
151175
}
152-
153-
if (walletObj['type'] == 'xpub' || walletObj['type'] == 'coldcard') {
154-
walletObj['mainWallet'] = false;
155-
}
176+
} else if (walletObj['type'] == 'xpub' || walletObj['type'] == 'coldcard') {
177+
walletObj['mainWallet'] = false;
178+
log.info('0.1.*: Updated watch-only wallet: ${walletObj['id']}');
156179
}
180+
157181
walletObj.addAll({'baseWalletType': 'Bitcoin'});
158182

183+
log.info(
184+
'0.1.*: Wallet ${walletObj['id']} final state - type: ${walletObj['type']}, mainWallet: ${walletObj['mainWallet']}, baseWalletType: ${walletObj['baseWalletType']}',
185+
);
186+
159187
final ({OldSeed? liquidMainnetSeed, Map<String, dynamic> walletObj}) res = (
160188
liquidMainnetSeed: liquidMainnetSeed,
161189
walletObj: walletObj,

lib/core/utils/logger.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class Logger {
2525

2626
if (record.stackTrace != null) content.add(record.stackTrace.toString());
2727

28-
final tsvLine = content.join('\t');
28+
final tsvLine = _sanitizeContent(content).join('\t');
2929

3030
// We don't want to keep the info session in memory, they should be written to file
3131
if (record.level != dep.Level.INFO) session.add(tsvLine);
@@ -64,6 +64,16 @@ class Logger {
6464
await File(path).writeAsString('$message\n', mode: FileMode.append);
6565
}
6666

67+
List<String> _sanitizeContent(List<String> content) {
68+
final colors = RegExp(r'\x1B\[[0-9;]*[a-zA-Z]'); // ascii colors
69+
final tabNewLine = RegExp(r'[\t\n]'); // no tabs or newlines
70+
final sanitizedContent =
71+
content
72+
.map((e) => e.replaceAll(tabNewLine, ' ').replaceAll(colors, ''))
73+
.toList();
74+
return sanitizedContent;
75+
}
76+
6777
void info(Object? message, {Object? error, StackTrace? trace}) {
6878
logger.info(message, error, trace);
6979
}

lib/core/wallet/data/datasources/wallet/impl/lwk_wallet_datasource.dart

Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -75,43 +75,35 @@ class LwkWalletDatasource implements WalletDatasource {
7575
required WalletModel wallet,
7676
required ElectrumServerModel electrumServer,
7777
}) {
78-
try {
79-
// TODO: if needed, add these debugPrint to a filterable logger.debug
80-
// TODO: to avoid spamming the terminal with recurring prints
81-
// debugPrint('Sync requested for wallet: ${wallet.id}');
82-
return _activeSyncs.putIfAbsent(wallet.id, () async {
83-
try {
84-
// debugPrint('New sync started for wallet: ${wallet.id}');
85-
_walletSyncStartedController.add(wallet.id);
86-
syncExecutions.update(wallet.id, (v) => v + 1, ifAbsent: () => 1);
87-
final lwkWallet = await _createPublicWallet(wallet);
88-
await lwkWallet.sync_(
89-
electrumUrl: electrumServer.url,
90-
validateDomain: electrumServer.validateDomain,
91-
);
92-
// debugPrint('Sync completed for wallet: ${wallet.id}');
93-
} catch (e) {
94-
if (e is lwk.LwkError) {
95-
throw e.msg;
96-
} else {
97-
rethrow;
98-
}
99-
} finally {
100-
_walletSyncFinishedController.add(wallet.id);
101-
// Remove the sync so future syncs can be triggered
102-
// Do not await this, as it is not necessary and can cause deadlocks
103-
// since it returns the Future from the map.
104-
// ignore: unawaited_futures
105-
_activeSyncs.remove(wallet.id);
78+
// TODO: if needed, add these debugPrint to a filterable logger.debug
79+
// TODO: to avoid spamming the terminal with recurring prints
80+
//debugPrint('[Sync] Sync requested for wallet: ${wallet.id}');
81+
return _activeSyncs.putIfAbsent(wallet.id, () async {
82+
try {
83+
//debugPrint('[Sync] New sync started for wallet: ${wallet.id}');
84+
_walletSyncStartedController.add(wallet.id);
85+
syncExecutions.update(wallet.id, (v) => v + 1, ifAbsent: () => 1);
86+
final lwkWallet = await _createPublicWallet(wallet);
87+
await lwkWallet.sync_(
88+
electrumUrl: electrumServer.url,
89+
validateDomain: electrumServer.validateDomain,
90+
);
91+
//debugPrint('[Sync] Sync completed for wallet: ${wallet.id}');
92+
} catch (e) {
93+
if (e is lwk.LwkError) {
94+
throw e.msg;
95+
} else {
96+
rethrow;
10697
}
107-
});
108-
} catch (e) {
109-
if (e is lwk.LwkError) {
110-
throw e.msg;
111-
} else {
112-
rethrow;
98+
} finally {
99+
_walletSyncFinishedController.add(wallet.id);
100+
// Remove the sync so future syncs can be triggered
101+
// Do not await this, as it is not necessary and can cause deadlocks
102+
// since it returns the Future from the map.
103+
// ignore: unawaited_futures
104+
_activeSyncs.remove(wallet.id);
113105
}
114-
}
106+
});
115107
}
116108

117109
@override

0 commit comments

Comments
 (0)