From 44402e64a354e5b5ba4a466729108836b61293e5 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Sat, 21 Mar 2026 18:32:13 +0100 Subject: [PATCH 1/8] chore(sdk): roll submodule for metadata race fix Points to GLEECBTC/komodo-defi-sdk-flutter#328 which adds mutex-protected atomic metadata updates to prevent concurrent coin activations from overwriting each other's state. --- sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk b/sdk index 482ca5feb5..fbce69f107 160000 --- a/sdk +++ b/sdk @@ -1 +1 @@ -Subproject commit 482ca5feb53960ded40788873f41d291bb317906 +Subproject commit fbce69f107ef5b7204d759f035af2c05da03ebae From 7dd5ae80ebc5c8a970f154349ed7a356d1983b24 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Sat, 21 Mar 2026 18:32:30 +0100 Subject: [PATCH 2/8] fix(coins): use atomic metadata updates for coin activation Batch-write all asset IDs to wallet metadata in a single call before launching parallel activations, then pass addToWalletMetadata: false to avoid N concurrent read-modify-write cycles that caused last-write-wins data loss. Migrate addActivatedCoins/removeActivatedCoins to use the new updateActiveUserKeyValue API for atomic single-key transforms. --- lib/bloc/coins_bloc/coins_bloc.dart | 14 +++++++- lib/bloc/coins_bloc/coins_repo.dart | 7 ++++ lib/model/kdf_auth_metadata_extension.dart | 39 ++++++++++------------ 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/lib/bloc/coins_bloc/coins_bloc.dart b/lib/bloc/coins_bloc/coins_bloc.dart index 48459fd251..bf40f8e741 100644 --- a/lib/bloc/coins_bloc/coins_bloc.dart +++ b/lib/bloc/coins_bloc/coins_bloc.dart @@ -549,8 +549,20 @@ class CoinsBloc extends Bloc { ); } + // Batch-write all asset IDs to wallet metadata in a single call before + // launching parallel activations. This avoids N concurrent read-modify-write + // cycles on the same metadata key which caused last-write-wins data loss. + await _coinsRepo.addAssetsToWalletMetadata( + coinsToActivate.map((asset) => asset.id), + ); + final enableFutures = coinsToActivate - .map((asset) => _coinsRepo.activateAssetsSync([asset])) + .map( + (asset) => _coinsRepo.activateAssetsSync( + [asset], + addToWalletMetadata: false, + ), + ) .toList(); // Ignore the return type here and let the broadcast handle the state updates as diff --git a/lib/bloc/coins_bloc/coins_repo.dart b/lib/bloc/coins_bloc/coins_repo.dart index 387cf0ac87..c28bd1930b 100644 --- a/lib/bloc/coins_bloc/coins_repo.dart +++ b/lib/bloc/coins_bloc/coins_repo.dart @@ -581,6 +581,13 @@ class CoinsRepo { } } + /// Adds the given assets (and their parent coins) to wallet metadata. + /// + /// This is exposed so callers can batch-write metadata before launching + /// parallel activations with `addToWalletMetadata: false`. + Future addAssetsToWalletMetadata(Iterable assets) => + _addAssetsToWalletMetdata(assets); + Future _addAssetsToWalletMetdata(Iterable assets) async { final parentIds = {}; for (final assetId in assets) { diff --git a/lib/model/kdf_auth_metadata_extension.dart b/lib/model/kdf_auth_metadata_extension.dart index 82df3ca1e3..e7832b11f5 100644 --- a/lib/model/kdf_auth_metadata_extension.dart +++ b/lib/model/kdf_auth_metadata_extension.dart @@ -97,40 +97,35 @@ extension KdfAuthMetadataExtension on KomodoDefiSdk { /// Adds new coin/asset IDs to the current user's activated coins list. /// - /// This method merges the provided [coins] with the existing activated coins, - /// ensuring no duplicates. The merged list is then stored in user metadata. + /// This method atomically merges the provided [coins] with the existing + /// activated coins, ensuring no duplicates and no lost writes under + /// concurrent calls. /// - /// If no user is currently signed in, the operation will complete but have no effect. + /// If no user is currently signed in, the operation will throw. /// /// [coins] - An iterable of coin/asset configuration IDs to add. Future addActivatedCoins(Iterable coins) async { - final existingCoins = - (await auth.currentUser)?.metadata.valueOrNull>( - 'activated_coins', - ) ?? - []; - - final mergedCoins = {...existingCoins, ...coins}.toList(); - await auth.setOrRemoveActiveUserKeyValue('activated_coins', mergedCoins); + await auth.updateActiveUserKeyValue('activated_coins', (current) { + final existing = (current as List?)?.cast() ?? []; + return {...existing, ...coins}.toList(); + }); } /// Removes specified coin/asset IDs from the current user's activated coins list. /// - /// This method removes all occurrences of the provided [coins] from the user's - /// activated coins list and updates the stored metadata. + /// This method atomically removes all occurrences of the provided [coins] + /// from the user's activated coins list, ensuring no lost writes under + /// concurrent calls. /// - /// If no user is currently signed in, the operation will complete but have no effect. + /// If no user is currently signed in, the operation will throw. /// /// [coins] - A list of coin/asset configuration IDs to remove. Future removeActivatedCoins(List coins) async { - final existingCoins = - (await auth.currentUser)?.metadata.valueOrNull>( - 'activated_coins', - ) ?? - []; - - existingCoins.removeWhere((coin) => coins.contains(coin)); - await auth.setOrRemoveActiveUserKeyValue('activated_coins', existingCoins); + await auth.updateActiveUserKeyValue('activated_coins', (current) { + final existing = (current as List?)?.cast() ?? []; + final updated = existing.where((c) => !coins.contains(c)).toList(); + return updated.isEmpty ? null : updated; + }); } /// Sets the seed backup confirmation status for the current user. From 69d03b9d6402070bf177567a865cb3ec41f905e4 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Sat, 21 Mar 2026 18:35:16 +0100 Subject: [PATCH 3/8] chore(sdk): roll submodule to dev (9532870) Includes fix(auth): add mutex-protected atomic metadata updates (#328). --- sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk b/sdk index fbce69f107..95328700e3 160000 --- a/sdk +++ b/sdk @@ -1 +1 @@ -Subproject commit fbce69f107ef5b7204d759f035af2c05da03ebae +Subproject commit 95328700e383ca7d47e1ea0a47b478c64e58eb41 From cbb37dc0852dbf3e1d06ddc0cfb65d5b9fa53cb5 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:08:25 +0100 Subject: [PATCH 4/8] chore(sdk): advance submodule to 317719d for WASM valueOrNull fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The merge from dev picked the fix/metadata-race side's SDK pointer (9532870, #328 only) instead of dev's pointer (317719d) which also includes #329 — the reified generics fix for _traverseJson that prevents valueOrNull> from silently returning null in WASM/minified release builds. --- sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk b/sdk index 95328700e3..317719d9eb 160000 --- a/sdk +++ b/sdk @@ -1 +1 @@ -Subproject commit 95328700e383ca7d47e1ea0a47b478c64e58eb41 +Subproject commit 317719d9ebe790d6f28e7a369ef857bd3d95b530 From 8a12d8ab18231666ea9725420f76227e274d0dff Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:16:24 +0100 Subject: [PATCH 5/8] chore(sdk): bump submodule for trezor test compilation fix Points to fix/trezor-test-missing-method (ea99ec6) which adds the missing updateActiveUserMetadataKey stub to _FakeAuthService in trezor_auth_service_test.dart. --- sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk b/sdk index 317719d9eb..ea99ec6d8b 160000 --- a/sdk +++ b/sdk @@ -1 +1 @@ -Subproject commit 317719d9ebe790d6f28e7a369ef857bd3d95b530 +Subproject commit ea99ec6d8b60dfdcd84ac4400c495c6a28ddf6d4 From 54a520dcdb31440f7ace31789a9927bd7b9f2046 Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:17:38 +0100 Subject: [PATCH 6/8] chore(sdk): roll submodule to dev (c70beed) Includes #328 (atomic metadata updates), #329 (reified generics for WASM), and #330 (trezor test compilation fix). --- sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk b/sdk index ea99ec6d8b..c70beedf2c 160000 --- a/sdk +++ b/sdk @@ -1 +1 @@ -Subproject commit ea99ec6d8b60dfdcd84ac4400c495c6a28ddf6d4 +Subproject commit c70beedf2c00dbc19d7e6b214a50b2f3bbc017e0 From 4dc173e2811c850f7e639f230e8b58732c29119e Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:21:01 +0100 Subject: [PATCH 7/8] ci: trigger CI rebuild From 5f81ef2d80da095fab6347e4e8ebf633247b00df Mon Sep 17 00:00:00 2001 From: CharlVS <77973576+CharlVS@users.noreply.github.com> Date: Mon, 23 Mar 2026 13:44:23 +0100 Subject: [PATCH 8/8] chore(sdk): bump submodule to dev after bundled coins commit merge Points sdk at 869dd00 (komodo-defi-sdk-flutter dev), including PR #331. --- sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk b/sdk index c70beedf2c..869dd00fee 160000 --- a/sdk +++ b/sdk @@ -1 +1 @@ -Subproject commit c70beedf2c00dbc19d7e6b214a50b2f3bbc017e0 +Subproject commit 869dd00fee6fb3c073041810a602c97ea786966e