Skip to content

chore: new creditsflow refinements - #9535

Merged
davidejensen merged 11 commits into
devfrom
chore/new-credits-flow-refinements
Aug 3, 2026
Merged

chore: new creditsflow refinements#9535
davidejensen merged 11 commits into
devfrom
chore/new-credits-flow-refinements

Conversation

@davidejensen

@davidejensen davidejensen commented Jul 31, 2026

Copy link
Copy Markdown
Member

Pull Request Description

What does this PR change?

Fix #9534
This PR applies some visual fixes and improvements for the credits flow and fixes the sometimes failing lsiting fetch by ensuring a correct display of items price.

Test Instructions

Prerequisites

Test Steps

  1. Go to https://decentraland.zone/shop/overview
  2. Buy an item like https://decentraland.zone/shop/item/0x03b1940d80394614a5ba60abbf73fa749068bdad/19 (some items might not work in world but it's okay as we are in test environment)
  3. If you don't have enough credits you can buy them with card number 4242 4242 4242 4242 (the other card details can be random)
  4. Launch the client in .zone environment
  5. Equip the latest acquired wearable
  6. close the explore panel
  7. Open your own passport
  8. In the list of equipped wearables press on buy for the wearable you just bought and equipped
  9. Verify you can buy it from the client as well

Additional Testing Notes

  • Note any edge cases to verify
  • Mention specific areas that need careful testing
  • List known limitations or potential issues

Quality Checklist

  • Changes have been tested locally
  • Documentation has been updated (if required)
  • Performance impact has been considered
  • For SDK features: Test scene is included

Code Review Reference

Please review our Branch & PR Standards before submitting. It explains the automated review flow, QA/DEV approval requirements, and what each label does — especially useful for first-time contributors.

@davidejensen davidejensen self-assigned this Jul 31, 2026
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

badge

Warnings count reduced: 13974 => 13949

Warnings/errors in files changed by this PR (82)
Assets/DCL/MarketplaceCredits/Purchase/Chain/ManaUsdRateReader.cs:244  ArrangeMissingParentheses  Non-obvious precedence
Assets/DCL/FeatureFlags/FeatureFlagsStrings.cs:181  CSharpWarnings::CS0618  CS0618: Constant 'DCL.FeatureFlags.FeatureFlagsStrings.GPUI_ENABLED' is obsolete: 'GPU Instancer Pro terrain is no longer optional so the flag is not needed'
Assets/DCL/Passport/PassportController.cs:1061  CSharpWarnings::CS0618  CS0618: Method 'DCL.UI.ProfileElements.ProfilePictureView.Setup(ProfileRepositoryWrapper, in CompactInfo)' is obsolete: 'Use Bind instead.'
Assets/DCL/Web3/Authenticators/Implementations/ThirdWeb/ThirdWebEthereumApi.cs:343  CSharpWarnings::CS8604  Possible null reference argument for parameter 'hexValue' in 'DCL.Web3.Web3Utils.ParseHexToBigInteger'
Assets/DCL/Infrastructure/MVC/Tests/MVCManagerShould.cs:15  CSharpWarnings::CS8618  Non-nullable field 'mvcManager' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/Infrastructure/MVC/Tests/MVCManagerShould.cs:16  CSharpWarnings::CS8618  Non-nullable field 'popupCloserView' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/Infrastructure/MVC/Tests/MVCManagerShould.cs:14  CSharpWarnings::CS8618  Non-nullable field 'windowsStackManager' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/Passport/PassportController.cs:171  CSharpWarnings::CS8618  Non-nullable fields 'contextMenu', 'contextMenuSeparator', 'contextMenuJumpInButton', 'contextMenuBlockUserButton', 'contextMenuGiftButton', 'invitationButtonHandler' must contain non-null values when exiting constructor. Consider adding the 'required' modifiers or declaring the fields as nullable.
Assets/DCL/Passport/Modules/EquippedItemsPassportModuleController.cs:53  CSharpWarnings::CS8618  Non-nullable fields 'currentProfile', 'getEquippedItemsCts' must contain non-null values when exiting constructor. Consider adding the 'required' modifiers or declaring the fields as nullable.
Assets/DCL/ExplorePanel/ExplorePanelController.cs:75  CSharpWarnings::CS8618  Non-nullable fields 'tabsBySections', 'exploreSections', 'sectionSelectorController', 'setupExploreSectionsCts', 'checkForLiveEventsCts' must contain non-null values when exiting constructor. Consider adding the 'required' modifiers or declaring the fields as nullable.
Assets/DCL/PluginSystem/Global/ExplorePanelPlugin.cs:786  CSharpWarnings::CS8618  Non-nullable property 'EventInfoPrefab' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/PluginSystem/Global/ExplorePanelPlugin.cs:784  CSharpWarnings::CS8618  Non-nullable property 'PlaceCategoriesSO' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/PluginSystem/Global/ExplorePanelPlugin.cs:785  CSharpWarnings::CS8618  Non-nullable property 'PlaceDetailPanelPrefab' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/PluginSystem/Global/ExplorePanelPlugin.cs:787  CSharpWarnings::CS8618  Non-nullable property 'QualityPresets' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/PluginSystem/Global/ExplorePanelPlugin.cs:772  CSharpWarnings::CS8618  Non-nullable property 'pointAtMarkerVisibilitySettings' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Web3/Authenticators/Implementations/ThirdWeb/ThirdWebEthereumApi.cs:86  ConditionIsAlwaysTrueOrFalse  Expression is always false
Assets/DCL/Infrastructure/MVC/Manager/MVCManager.cs:155  ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract  Expression is always true according to nullable reference types' annotations
Assets/DCL/ExplorePanel/ExplorePanelController.cs:219  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/ExplorePanel/ExplorePanelController.cs:219  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/ExplorePanel/ExplorePanelController.cs:219  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/Web3/Authenticators/Implementations/ThirdWeb/ThirdWebEthereumApi.cs:271  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/Web3/Authenticators/Implementations/ThirdWeb/ThirdWebEthereumApi.cs:272  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/Passport/PassportController.cs:65  InconsistentNaming  Name 'BUTTON' does not match rule 'Enum member'. Suggested name is 'Button'.
Assets/DCL/PluginSystem/Global/CreditPurchasePlugin.cs:104  InconsistentNaming  Name 'CreditPurchasePopupPrefab' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'creditPurchasePopupPrefab'.
Assets/DCL/PluginSystem/Global/CreditPurchasePlugin.cs:105  InconsistentNaming  Name 'CreditsTopUpPopupPrefab' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'creditsTopUpPopupPrefab'.
Assets/DCL/PluginSystem/Global/ExplorePanelPlugin.cs:786  InconsistentNaming  Name 'EventInfoPrefab' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'eventInfoPrefab'.
Assets/DCL/Passport/PassportController.cs:66  InconsistentNaming  Name 'NOTIFICATION' does not match rule 'Enum member'. Suggested name is 'Notification'.
Assets/DCL/PluginSystem/Global/ExplorePanelPlugin.cs:785  InconsistentNaming  Name 'PlaceDetailPanelPrefab' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'placeDetailPanelPrefab'.
Assets/DCL/PluginSystem/Global/ExplorePanelPlugin.cs:787  InconsistentNaming  Name 'QualityPresets' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'qualityPresets'.
Assets/DCL/PerformanceAndDiagnostics/Analytics/AnalyticsEvents.cs:32  InconsistentNaming  Name 'UI' does not match rule 'members_should_be_pascal_case'. Suggested name is 'Ui'.
Assets/DCL/PluginSystem/Global/ExplorePanelPlugin.cs:137  InconsistentNaming  Name 'assetBundleURL' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'assetBundleUrl'.
Assets/DCL/Infrastructure/Global/Dynamic/DynamicWorldContainer.cs:428  InconsistentNaming  Name 'badgesAPIClient' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'badgesApiClient'.
Assets/DCL/Web3/EthApiResponse.cs:22  InconsistentNaming  Name 'code' does not match rule 'members_should_be_pascal_case'. Suggested name is 'Code'.
Assets/DCL/MarketplaceCredits/Purchase/CreditsPurchaseService.cs:23  InconsistentNaming  Name 'creditsAPIClient' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'creditsApiClient'.
Assets/DCL/MarketplaceCredits/Purchase/Tests/CreditsPurchaseServiceShould.cs:41  InconsistentNaming  Name 'creditsAPIClient' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'creditsApiClient'.
Assets/DCL/UI/Profiles/Credits/CreditsPanelController.cs:15  InconsistentNaming  Name 'creditsAPIClient' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'creditsApiClient'.
Assets/DCL/MarketplaceCredits/Purchase/CreditsPurchaseService.cs:35  InconsistentNaming  Name 'creditsAPIClient' does not match rule 'parameters_should_be_camel_case'. Suggested name is 'creditsApiClient'.
Assets/DCL/UI/Profiles/Credits/CreditsPanelController.cs:24  InconsistentNaming  Name 'creditsAPIClient' does not match rule 'parameters_should_be_camel_case'. Suggested name is 'creditsApiClient'.
Assets/DCL/Web3/EthApiResponse.cs:16  InconsistentNaming  Name 'error' does not match rule 'members_should_be_pascal_case'. Suggested name is 'Error'.
Assets/DCL/Web3/EthApiResponse.cs:10  InconsistentNaming  Name 'id' does not match rule 'members_should_be_pascal_case'. Suggested name is 'Id'.
Assets/DCL/Web3/EthApiResponse.cs:11  InconsistentNaming  Name 'jsonrpc' does not match rule 'members_should_be_pascal_case'. Suggested name is 'Jsonrpc'.
Assets/DCL/Infrastructure/Global/Dynamic/DynamicWorldContainer.cs:429  InconsistentNaming  Name 'marketplaceCreditsAPIClient' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'marketplaceCreditsApiClient'.
Assets/DCL/PluginSystem/Global/CreditPurchasePlugin.cs:27  InconsistentNaming  Name 'marketplaceCreditsAPIClient' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'marketplaceCreditsApiClient'.
Assets/DCL/PluginSystem/Global/ExplorePanelPlugin.cs:161  InconsistentNaming  Name 'marketplaceCreditsAPIClient' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'marketplaceCreditsApiClient'.
Assets/DCL/PluginSystem/Global/CreditPurchasePlugin.cs:40  InconsistentNaming  Name 'marketplaceCreditsAPIClient' does not match rule 'parameters_should_be_camel_case'. Suggested name is 'marketplaceCreditsApiClient'.
Assets/DCL/PluginSystem/Global/ExplorePanelPlugin.cs:265  InconsistentNaming  Name 'marketplaceCreditsAPIClient' does not match rule 'parameters_should_be_camel_case'. Suggested name is 'marketplaceCreditsApiClient'.
Assets/DCL/Infrastructure/Global/Dynamic/DynamicWorldContainer.cs:431  InconsistentNaming  Name 'marketplaceShopAPIClient' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'marketplaceShopApiClient'.
Assets/DCL/Web3/EthApiResponse.cs:23  InconsistentNaming  Name 'message' does not match rule 'members_should_be_pascal_case'. Suggested name is 'Message'.
Assets/DCL/Infrastructure/Global/Dynamic/DynamicWorldContainer.cs:201  InconsistentNaming  Name 'nftInfoAPIClient' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'nftInfoApiClient'.
Assets/DCL/PluginSystem/Global/ExplorePanelPlugin.cs:107  InconsistentNaming  Name 'placesAPIService' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'placesApiService'.

…and 32 more (see the csharp-lint-reports artifact).

@davidejensen
davidejensen marked this pull request as ready for review July 31, 2026 16:08
@davidejensen
davidejensen requested review from a team as code owners July 31, 2026 16:08
@decentraland-bot
decentraland-bot self-requested a review July 31, 2026 16:08

@decentraland-bot decentraland-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

STEP 2 — Root-cause check

PASS. The PR fixes intermittent quote failures for USD-pegged items by removing their unnecessary dependency on the MANA/USD oracle at quote time. The oracle rate was never needed for the credit price of USD-pegged items — the price is exact from the on-chain amount. The rate is only needed at purchase time to determine how much MANA the trade draws. This addresses the root cause (misplaced oracle dependency), not a symptom.

STEP 3 — Design & integration

PASS. ManaUsdRateReader is a service class injected by the DI container, not an ECS system — its persistent caches (aggregator addresses, decimals, rates, in-flight requests) are appropriate here.

Owner search: The rate reader is created and held by the credits purchase plugin via the DI container. No existing system or facade managed rate caching before. The caching belongs here.

Deferred rate resolution: USD-pegged items now resolve the MANA draw at purchase time instead of quote time. This is architecturally sound — the USD price is exact, and the MANA draw benefits from a fresh rate. The PrefetchAsync cache warm-up is a good optimization for reducing latency at the confirm click.

Teardown / consumption trace:

  • GetCreditsButton2.onClick.AddListener (line 47) → matched by RemoveListener in Dispose() (line 62) ✓
  • PrefetchAsync().Forget() — safe: PrefetchAsync catches all exceptions internally before returning ✓
  • FetchIntoAsync.Forget() — safe: the method handles all exceptions via try/catch and forwards them through the UniTaskCompletionSource
  • CancellationTokenSource(FETCH_TIMEOUT) — disposed via using
  • inFlightByMarketplace entry — removed in finally block of FetchIntoAsync

STEP 4 — Member audit

  • PrefetchAsync (ManaUsdRateReader.cs:121): 1 consumer (CreditsPurchaseService.QuoteInternalAsync). Distinct from ReadAsync in semantics — it swallows exceptions and logs warnings, providing a safe fire-and-forget entry point. Justified as a separate public method.
  • GetCreditsButton2 (CreditsPanelView.cs:16): 1 consumer (CreditsPanelController). Non-descriptive name — see P2 finding below.

STEP 5 — Line-level findings

See inline comments. Two P2 findings, no P0 or P1.

Verified non-issues:

  • ManaUsdRateReader cache race: Both the cache write (rateByMarketplace[key] = ...) and in-flight removal (inFlightByMarketplace.Remove(...)) happen under lock(gate). The cache is populated before TrySetResult, and the in-flight entry is removed in finally. A concurrent reader arriving between cache-write and in-flight-removal gets a cache hit (correct). No race.
  • Double ResolvingListing state: Intentional — the first fires from QuoteAsync (harmless, UI ignores it when not in Purchasing state), the second from PurchaseInternalAsync when resolving the rate for USD-pegged trades (!quote.IsLiveRatePrice). Test correctly captures this.
  • received[0] access at purchase time: trade.received[0] is validated at quote time (Length == 0 check), and quote.Trade is an immutable struct. Safe.
  • EthApiResponse.id null deserialization: [JsonProperty(NullValueHandling.Ignore)] on long id correctly defaults JSON null to 0 without throwing JsonSerializationException. Verified by new unit test.
  • DappWeb3EthereumApi response.id == 0 fallback: The mutex guarantees one in-flight request per socket, so cross-request confusion is not possible. The id == 0 check correctly matches null-id error frames from servers that echo "id": null for unparseable requests.

STEP 6 — Complexity

COMPLEX — touches 7+ meaningful files across TTL-cached rate reading with thread-safe request coalescing, async flow restructuring (deferred rate resolution), and RPC error handling across two Web3 API implementations (DappWeb3, ThirdWeb). 312 lines of new test coverage.

STEP 7 — QA assessment

QA_REQUIRED: YES — changes affect the runtime credits purchase flow (quote pricing, purchase-time rate resolution), purchase modal UI (spinner state, failure display, copy changes), credits panel UI (new button), and RPC communication error handling. All user-facing.

STEP 8 — Non-blocking warnings

None. Main scene not modified.

Security review

No security issues found. The lock-based synchronization is correct. CancellationToken propagation is improved across ThirdWeb RPC calls. JSON-RPC error handling is appropriate. The ThirdWebMetaTxService callback hardcodes CancellationToken.None but this is a pre-existing limitation of the callback type (Func<EthApiRequest, int, UniTask<EthApiResponse>>) and does not affect the credits purchase flow (which uses CreditsManagerMetaTxRelayer with proper CT threading).


REVIEW_RESULT: PASS ✅
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Multi-file changes spanning TTL-cached rate reading with thread-safe request coalescing, deferred rate resolution in the purchase flow, and RPC error handling across DappWeb3 and ThirdWeb implementations.
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by decentraland-bot via GitHub

Comment thread Explorer/Assets/DCL/UI/Profiles/Credits/CreditsPanelController.cs
Comment thread Explorer/Assets/DCL/UI/Profiles/Credits/CreditsPanelView.cs
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Jarvis reviewed this PR and found no blocking issues, but assessed it as complex — human DEV review is still required before merging.

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

badge

All Unity tests passed ✅

TESTS SUITE Result Passed Failed Skipped
EditMode ✅ Passed 24441 0 13
PlayMode ✅ Passed 236 0 5

@lorenzo-ranciaffi lorenzo-ranciaffi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't like the IReshowController approach, I think this could be tackled in a different manner (e.g. working with a stack of open UIs to walk backward) but that's a major change.
Since this needs to be merged asap, let's create a ticket to refactor this so we can tackle it later

@davidejensen
davidejensen enabled auto-merge (squash) August 3, 2026 13:12
@davidejensen
davidejensen disabled auto-merge August 3, 2026 15:23

@Ludmilafantaniella Ludmilafantaniella left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved ✅

Verified on both Windows and Mac:

  • Equip/unequip visual bug after closing Explore panel — fixed
  • "Go to backpack" button navigation — fixed
  • Price-unavailable modal text overflow — fixed
  • Packs not reappearing after buying from another tab — fixed

Known limitation (not blocking): if you start a credits purchase, close it with the X, and then try to buy more, it continues the previous transaction; if the browser tab is closed you can get stuck. Confirmed as a separate corner case to be handled in a follow-up PR.

9535-evi.mp4

✅Smoke test performed:

  • ✔️ Log In/Log Out
  • ✔️ Backpack and wearables in world
  • ✔️ Emotes in world and in backpack
  • ✔️ Teleport with map/coordinates/Jump In
  • ✔️ Chat and multiplayer
  • ✔️ Profile card
  • ✔️ Camera
  • ✔️ Skybox

@davidejensen
davidejensen enabled auto-merge (squash) August 3, 2026 15:45
@davidejensen
davidejensen merged commit b4f2525 into dev Aug 3, 2026
22 of 25 checks passed
@davidejensen
davidejensen deleted the chore/new-credits-flow-refinements branch August 3, 2026 17:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refinement of client new credits flow

4 participants