Skip to content

feat(urls): configurable base domain via --base-domain - #9728

Closed
eordano wants to merge 13 commits into
devfrom
gateway
Closed

feat(urls): configurable base domain via --base-domain#9728
eordano wants to merge 13 commits into
devfrom
gateway

Conversation

@eordano

@eordano eordano commented Aug 13, 2026

Copy link
Copy Markdown
Member

Target a non-decentraland.* deployment (e.g. interconnected.online) by resolving every backend host through DecentralandUrlsSource.ResolveDomain, which swaps the whole decentraland.{ENV} host token for a custom base domain supplied by the --base-domain app arg (AppArgsFlags.BASE_DOMAIN). The default path is byte-identical, swapping only the {ENV} TLD.

Consumers that follow the resolved domain: teleport validation (ChatEnvironmentValidator), realm trust (DeepLinkAllowlist), the main-realm comms fallback (RealmController), the realm-name server list (RealmNamesMap), the pre-login feature-flag whitelist URL (GetFeatureFlagsUrl), the smart-wearable content fallback (SmartWearableCache), and the local scene adapter (LocalGateKeeperSceneAdapter). Gateway routing already follows it: the transform runs on the {ENV} template and ResolveDomain rewrites the whole token, gateway host included. The default "Empty place" no longer hardcodes a peer.decentraland.org thumbnail and falls back to the built-in placeholder.

Characterization tests pin every DecentralandUrl across org/zone/custom domains and gateway routing (gatekeeper family included); unit tests cover custom-domain teleport validation and deep-link realm trust.

QA: Full automated testing should not raise any errors or changes -- this is internal only. Test --base-domain interconnected.online to see if it works on a different catalyst setup

Target a non-decentraland.* deployment (e.g. interconnected.online) by
resolving every backend host through DecentralandUrlsSource.ResolveDomain,
which swaps the whole decentraland.{ENV} host token for a custom base domain
supplied by the --base-domain app arg (AppArgsFlags.BASE_DOMAIN). The default
path is byte-identical, swapping only the {ENV} TLD.

Consumers that follow the resolved domain: teleport validation
(ChatEnvironmentValidator), realm trust (DeepLinkAllowlist), the main-realm
comms fallback (RealmController), the realm-name server list (RealmNamesMap),
the pre-login feature-flag whitelist URL (GetFeatureFlagsUrl), the
smart-wearable content fallback (SmartWearableCache), and the local scene
adapter (LocalGateKeeperSceneAdapter). Gateway routing already follows it: the
transform runs on the {ENV} template and ResolveDomain rewrites the whole
token, gateway host included. The default "Empty place" no longer hardcodes a
peer.decentraland.org thumbnail and falls back to the built-in placeholder.

Characterization tests pin every DecentralandUrl across org/zone/custom
domains and gateway routing (gatekeeper family included); unit tests cover
custom-domain teleport validation and deep-link realm trust.
@eordano
eordano requested review from a team as code owners August 13, 2026 12:16
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

🚦 CI Status

Build

New build in progress, come back later!

Lint

Warnings count reduced: 13156 => 13093

Warnings/errors in files changed by this PR (102)
Assets/DCL/Backpack/SmartWearableAuthorizationPopupController.cs:67  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/Backpack/SmartWearableAuthorizationPopupController.cs:83  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/TeleportPrompt/TeleportPromptController.cs:42  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/TeleportPrompt/TeleportPromptController.cs:71  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/TeleportPrompt/TeleportPromptController.cs:92  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/TeleportPrompt/TeleportPromptController.cs:92  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/TeleportPrompt/TeleportPromptController.cs:114  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/TeleportPrompt/TeleportPromptController.cs:114  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/TeleportPrompt/TeleportPromptController.cs:123  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/TeleportPrompt/TeleportPromptController.cs:130  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/Backpack/SmartWearableAuthorizationPopupController.cs:67  CSharpWarnings::CS8604  Possible null reference argument for parameter 'thumbnail' in 'Runtime.Wearables.SmartWearableAuthorizationPopupView.Setup'
Assets/DCL/Navmap/PlaceElementView.cs:46  CSharpWarnings::CS8618  Non-nullable event 'OnMouseHover' is uninitialized. Consider adding the 'required' modifier or declaring the event as nullable.
Assets/DCL/Navmap/PlaceElementView.cs:31  CSharpWarnings::CS8618  Non-nullable field 'arrowImage' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/Navmap/PlaceElementView.cs:22  CSharpWarnings::CS8618  Non-nullable field 'placeCreator' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/Navmap/PlaceElementView.cs:16  CSharpWarnings::CS8618  Non-nullable field 'placeImage' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/Navmap/PlaceElementView.cs:19  CSharpWarnings::CS8618  Non-nullable field 'placeName' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/Navmap/PlaceElementView.cs:13  CSharpWarnings::CS8618  Non-nullable field 'playerCounterContainer' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/Navmap/PlaceElementView.cs:25  CSharpWarnings::CS8618  Non-nullable field 'playersCount' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/Navmap/PlaceElementView.cs:34  CSharpWarnings::CS8618  Non-nullable field 'resultAnimator' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/Navmap/PlaceElementView.cs:28  CSharpWarnings::CS8618  Non-nullable field 'resultButton' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/TeleportPrompt/TeleportPromptController.cs:26  CSharpWarnings::CS8618  Non-nullable fields 'resultCallback', 'cts' must contain non-null values when exiting constructor. Consider adding the 'required' modifiers or declaring the fields as nullable.
Assets/DCL/LOD/Systems/LODContainer.cs:51  CSharpWarnings::CS8618  Non-nullable members 'roadAssetsPrefabList', 'RoadCoordinates' must contain non-null values when exiting constructor. Consider adding the 'required' modifiers or declaring the members as nullable.
Assets/DCL/LOD/Systems/LODContainer.cs:144  CSharpWarnings::CS8618  Non-nullable property 'LODSettingAsset' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Navmap/PlaceElementView.cs:37  CSharpWarnings::CS8618  Non-nullable property 'LiveContainer' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/LOD/Systems/LODContainer.cs:141  CSharpWarnings::CS8618  Non-nullable property 'RoadData' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/PlacesAPIService/PlacesAPIResponse.cs:144  ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract  Expression is always false according to nullable reference types' annotations
Assets/DCL/Infrastructure/Global/StaticContainer.cs:280  ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract  Expression is always true according to nullable reference types' annotations
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:80  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:86  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/Navmap/PlaceElementView.cs:62  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/Navmap/PlaceElementView.cs:69  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/Roads/Systems/RoadPlugin.cs:54  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/TeleportPrompt/TeleportPromptController.cs:83  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/TeleportPrompt/TeleportPromptController.cs:86  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/PlacesAPIService/PlacesAPIResponse.cs:212  InconsistentNaming  Name 'IPlacesAPIResponse' does not match rule 'interfaces_should_be_pascal_case_with_i_prefix'. Suggested name is 'IPlacesApiResponse'.
Assets/DCL/Infrastructure/Global/StaticContainer.cs:141  InconsistentNaming  Name 'ISSDescriptorDiskCache' does not match rule 'members_should_be_pascal_case'. Suggested name is 'IssDescriptorDiskCache'.
Assets/DCL/Infrastructure/Global/Tests/EditMode/RealmLaunchSettingsShould.cs:208  InconsistentNaming  Name 'IgnoreMacOSRealmInvalidation' does not match rule 'members_should_be_pascal_case'. Suggested name is 'IgnoreMacOsRealmInvalidation'.
Assets/DCL/ApplicationGuards/ApplicationMinimumSpecsGuard/Logic/Utils/SystemSpecUtils.cs:141  InconsistentNaming  Name 'IsMacOSVersionAcceptable' does not match rule 'members_should_be_pascal_case'. Suggested name is 'IsMacOsVersionAcceptable'.
Assets/DCL/PlacesAPIService/PlacesAPIResponse.cs:221  InconsistentNaming  Name 'PlacesAPIGetParcelResponse' does not match rule 'members_should_be_pascal_case'. Suggested name is 'PlacesApiGetParcelResponse'.
Assets/DCL/PlacesAPIService/PlacesAPIResponse.cs:192  InconsistentNaming  Name 'PlacesAPIResponse' does not match rule 'members_should_be_pascal_case'. Suggested name is 'PlacesApiResponse'.
Assets/DCL/SmartWearables/SmartWearableCache.cs:188  InconsistentNaming  Name 'RequiresWeb3API' does not match rule 'members_should_be_pascal_case'. Suggested name is 'RequiresWeb3Api'.
Assets/DCL/SmartWearables/SmartWearableCache.cs:87  InconsistentNaming  Name 'RequiresWeb3APIAsync' does not match rule 'members_should_be_pascal_case'. Suggested name is 'RequiresWeb3ApiAsync'.
Assets/DCL/NetworkDefinitions/Browser/DecentralandUrlsSource.cs:171  InconsistentNaming  Name 'ResolvedBaseDomain' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'resolvedBaseDomain'.
Assets/DCL/ApplicationGuards/ApplicationMinimumSpecsGuard/Logic/Utils/SystemSpecUtils.cs:211  InconsistentNaming  Name 'actualGBFloat' does not match rule 'non_public_members_should_be_camel_case'. Suggested name is 'actualGbFloat'.
Assets/DCL/ApplicationGuards/ApplicationMinimumSpecsGuard/Logic/Utils/SystemSpecUtils.cs:237  InconsistentNaming  Name 'always_accepted_cpus' does not match rule 'members_should_be_pascal_case'. Suggested name is 'AlwaysAcceptedCpus'.
Assets/DCL/ApplicationGuards/ApplicationMinimumSpecsGuard/Logic/Utils/SystemSpecUtils.cs:253  InconsistentNaming  Name 'apple_silicon_supported_regex' does not match rule 'members_should_be_pascal_case'. Suggested name is 'AppleSiliconSupportedRegex'.
Assets/DCL/ApplicationGuards/ApplicationMinimumSpecsGuard/Logic/Utils/SystemSpecUtils.cs:249  InconsistentNaming  Name 'arc_gpu_supported_version_regex' does not match rule 'members_should_be_pascal_case'. Suggested name is 'ArcGPUSupportedVersionRegex'.
Assets/DCL/Navmap/PlaceElementView.cs:31  InconsistentNaming  Name 'arrowImage' does not match rule 'members_should_be_pascal_case'. Suggested name is 'ArrowImage'.
Assets/DCL/Infrastructure/Global/Dynamic/RealmUrl/Names/RealmNamesMap.cs:73  InconsistentNaming  Name 'baseUrl' does not match rule 'members_should_be_pascal_case'. Suggested name is 'BaseUrl'.
Assets/DCL/Infrastructure/Global/Dynamic/RealmUrl/Names/RealmNamesMap.cs:81  InconsistentNaming  Name 'configurations' does not match rule 'members_should_be_pascal_case'. Suggested name is 'Configurations'.

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

Tests

All Unity tests passed ✅

TESTS SUITE Result Passed Failed Skipped
EditMode ✅ Passed 25264 0 13
PlayMode ✅ Passed 236 0 37

@github-actions
github-actions Bot requested review from NickKhalow and dalkia August 13, 2026 12:16
@decentraland-bot decentraland-bot added the ext-contribution Identifies a contribution which was not initiated by a Unity Developer label Aug 13, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Slack notification sent to #explorer-ext-contributions for external review.
To re-send, delete this comment and re-add the ext-contribution label.

@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.

PR Review: feat(urls): configurable base domain via --base-domain

STEP 2 — Root-cause check: PASS ✅

The PR introduces a --base-domain CLI argument so the client can target a non-decentraland.* deployment (e.g. interconnected.online). The approach is sound: DecentralandUrlsSource.ResolveDomain() centralizes domain substitution. The default path replaces only {ENV} (byte-identical to before, today-mixture safe); a custom base domain replaces the whole decentraland.{ENV} token, moving every backend host. This is a feature addition, not a symptom fix.

STEP 3 — Design & integration: PASS ✅

No new long-lived units — all changes extend existing classes. The design introduces:

  • A customBaseDomain field on DecentralandUrlsSource (immutable after construction)
  • A static customBaseDomain on DeepLinkAllowlist (follows existing whitelistedWorlds pattern, set once during initialization, reset in test teardown)
  • New constructor parameters on SmartWearableCache, RealmNamesMap, ChatEnvironmentValidator

Owner search results:

  • DecentralandUrlsSource already owns all URL resolution — ResolveDomain() and ResolvedBaseDomain belong here.
  • DeepLinkAllowlist already owns host trust checks — extending IsDecentralandHost with the custom domain is the right place.
  • ChatEnvironmentValidator already owns teleport validation — parameterizing its domain suffix rather than hardcoding it is correct.
  • GatewayUrlsSource derives gateway routing from the base class — using ResolvedBaseDomain (protected) is legitimate inheritance, not leaky abstraction.

Teardown/consumption trace:

  • No new subscriptions, event hookups, connections, or disposable resources introduced. All changes are constructor-time wiring or one-time static initialization.

STEP 4 — Member audit: PASS ✅

Member Consumers Verdict
ResolveDomain() (private) Probe(), Url(), ResolveOptimizedAssetsUrl() — central substitution point Correctly encapsulated
ResolvedBaseDomain (protected) GatewayUrlsSource ctor (3 refs: resolvedNonClientHosts, gatewayPrefix, domainSuffix) Legitimate protected accessor for inheritance
SetCustomBaseDomain() (public static) MainSceneLoader.InitializeFlowAsync(), test teardown Follows existing SetWhitelistedWorlds pattern
DOMAIN_TOKEN (protected const) ResolveDomain(), GatewayUrlsSource ctor, GetFeatureFlagsUrl() Correctly scoped

No single-use intermediaries, no absent-≠-false predicates, no redundant guards.

STEP 5 — Line-level review

Security review: No security issues found.

  • BASE_DOMAIN is a CLI arg, NOT in the deep link PERMITTED_KEYS or WHITELISTED_REALM_PERMITTED_KEYS — an attacker cannot inject it via deep link ✅
  • IsDecentralandHost boundary checks are correct: host.Length > customBaseDomain.Length + host[..] == '.' + EndsWith — rejects both bare domain and suffix-spoof attacks ✅
  • ChatEnvironmentValidator.HostHasSuffix rejects userinfo-based host confusion (@ check before :) ✅
  • No secrets, credentials, or PII exposed ✅
  • Gateway routing works correctly: IsGatewayTransformable runs on unresolved templates (which always contain .decentraland.), then ResolveDomain substitutes the custom domain afterward ✅

Findings: Two P2 issues (see line comments below).

STEP 6 — Complexity: COMPLEX

20 files changed (+444 −37). Touches URL infrastructure (DecentralandUrlsSource, GatewayUrlsSource), gateway routing, deep link trust model (DeepLinkAllowlist), teleport validation, realm name resolution, and smart wearable content fallback across multiple assemblies.

STEP 7 — QA assessment: YES

Runtime code changes affecting URL routing, teleport validation, and realm trust. Although the default path is byte-identical (backward compatible when --base-domain is not supplied), the URL resolution and gateway routing paths have changed.

STEP 8 — Non-blocking warnings

None. Main scene not modified.

STEP 9 — Verdict

REVIEW_RESULT: PASS ✅
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Touches URL infrastructure, gateway routing, deep link trust model, and teleport validation across 7+ assemblies
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by unknown via Slack

Comment thread docs/base-domain-generalization.md Outdated
@eordano eordano added the force-build Used to trigger a build on draft PR label Aug 13, 2026
@decentraland-bot

This comment has been minimized.

@decentraland-bot

This comment has been minimized.

Rename catalyrst -> catalyst in the base-domain doc and drop the
ReSharper CheckNamespace suppression from the new characterization test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@decentraland-bot

This comment has been minimized.

@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 1 — Context & Scope

PR: feat(urls): configurable base domain via --base-domain
Target: dev
Files: 20 changed (+442 −37)
Subsystems: NetworkDefinitions (DecentralandUrlsSource, GatewayUrlsSource), Infrastructure (DeepLinkAllowlist, ChatContainer, RealmController, MainSceneLoader, StaticContainer, BootstrapContainer), Chat (ChatEnvironmentValidator), PlacesAPIService, SmartWearables.

Loaded: CLAUDE.md, docs/README.md, review-instructions.md. Reviewed full source of DecentralandUrlsSource.cs, GatewayUrlsSource.cs, IDecentralandUrlsSource.cs, DeepLinkAllowlist.cs, RealmController.cs, ChatContainer.cs, SmartWearableCache.cs, ThumbnailLoader.cs.

STEP 2 — Root-cause check

PASS. This PR adds a feature (configurable base domain via --base-domain), not a bug fix. The approach centralizes domain resolution through ResolveDomain() — a new private method that substitutes either {ENV} (default, byte-identical to prior behavior) or the full decentraland.{ENV} token (custom domain). The design correctly addresses the need to target non-decentraland.* deployments without modifying the DecentralandEnvironment enum.

STEP 3 — Design & integration

PASS. No new long-lived units introduced. The changes parameterize existing classes:

  • DecentralandUrlsSource: gains customBaseDomain field, DOMAIN_TOKEN constant, ResolveDomain() method, and ResolvedBaseDomain property. All contained within the existing class hierarchy.
  • GatewayUrlsSource: derives gateway prefix/suffix/non-client hosts from ResolvedBaseDomain instead of direct ENV replacement. Gateway transform runs on raw templates before ResolveDomain() — these compose correctly (confirmed by characterization tests).
  • DeepLinkAllowlist.SetCustomBaseDomain(): follows the established SetWhitelistedWorlds() pattern (static mutable state, same class). Domain-trust check mirrors the existing ALL_DOMAINS loop with identical dot-boundary validation. Suffix-spoofing correctly rejected (test at line 800 confirms interconnected.online.attacker.comfalse).
  • Consumer updates (ChatEnvironmentValidator, RealmNamesMap, SmartWearableCache, RealmController) receive the resolved domain via dependency injection or the URL source — no lifecycle duplication, no new reconciliation loops.

Owner search: No new lifecycle-owning units to audit.

Teardown/consumption trace: No new subscriptions, event hookups, or connections. RealmData.RealmType.OnUpdate += ResetRealmDependentUrls is pre-existing and unchanged.

STEP 4 — Member audit

Member Consumers Assessment
ResolvedBaseDomain (protected) ResolveDomain(), GatewayUrlsSource ctor (3 reads) Multi-use, justified. Two external call sites re-derive this value — see inline finding.
ResolveDomain() (private) Probe(), Url(), ResolveOptimizedAssetsUrl() 4 call sites, cleanly encapsulates branching logic.
DOMAIN_TOKEN (const) ResolveDomain(), GetFeatureFlagsUrl(), GatewayUrlsSource ctor 3 call sites across 2 files, justified.
SetCustomBaseDomain() MainSceneLoader.InitializeFlowAsync, test teardown 2 call sites, follows SetWhitelistedWorlds pattern.
realmDomainSuffix (ChatEnvironmentValidator) ValidateTeleport() Zone/Org cases Replaces previously hardcoded domain constants. Legitimate parameterization.

Note: GetFeatureFlagsUrl (line 255) duplicates the custom-domain-or-env-fallback branching from ResolveDomain() as a static method. This is necessary since it runs before the instance exists (InitializeDeepLinkWorldWhitelistAsync), but the parallel logic could drift — worth extracting a shared static helper if this method gains more callers.

STEP 5 — Line-level review

See inline comments for each finding with suggestion blocks.

Summary of findings:

# Sev File Issue
1 P2 ChatContainer.cs:106-108 Fragile .Replace("https://", "") re-derives ResolvedBaseDomain
2 P2 RealmController.cs:375 Same .Replace pattern (second occurrence)
3 P2 DeepLinkAllowlist.cs:172-173 No format validation on custom domain — single-label domains trust entire TLDs
4 P2 DecentralandUrlsSource.cs:68 Redundant null-forgiving ! operator

Security review: No secrets committed. --base-domain is correctly excluded from PERMITTED_KEYS (not injectable via deep links). Domain-trust expansion in IsDecentralandHost uses proper dot-boundary validation matching the existing ALL_DOMAINS pattern. Gateway routing correctly composes with domain resolution. No auth bypass vectors identified.

Characterization tests: The 246-line DecentralandUrlsSourceCharacterizationShould.cs is excellent — it pins every domain-bearing URL for both Org and Zone environments, verifies env-independent URLs stay unchanged, tests gateway routing round-trips, and covers custom-domain retargeting. This is exactly the safety net needed for this change.

STEP 6 — Complexity assessment

COMPLEX. Touches URL resolution, domain trust (DeepLinkAllowlist), gateway routing, realm validation, and chat teleport validation across 20 files spanning NetworkDefinitions, Infrastructure, Chat, PlacesAPIService, and SmartWearables assemblies.

STEP 7 — QA assessment

QA_REQUIRED: YES. Changes runtime URL resolution affecting realm connections, teleportation validation, gateway routing, smart wearable content loading, and realm name resolution. However, the PR is designed to be byte-identical on the default path (no custom domain), so the risk to existing behavior is low.

STEP 8 — Non-blocking warnings

None. Main.unity is not modified.

STEP 9 — Verdict

REVIEW_RESULT: PASS ✅
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Touches URL resolution, domain trust, gateway routing, and realm validation across 20 files in NetworkDefinitions, Infrastructure, Chat, and SmartWearables assemblies
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by eordano via GitHub

Comment on lines +106 to +108
string realmDomainSuffix = bootstrapContainer.DecentralandUrlsSource
.Url(DCL.Multiplayer.Connections.DecentralandUrls.DecentralandUrl.Host)
.Replace("https://", string.Empty);

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.

[P2] Re-derives ResolvedBaseDomain via fragile scheme stripping (CLAUDE.md: "Don't re-derive what already exists").

DecentralandUrlsSource.ResolvedBaseDomain already computes this exact value, but is protected and not on IDecentralandUrlsSource. Both this site and RealmController.ResolveHostname (line 375) independently strip the scheme with .Replace("https://", ""), which silently breaks if the scheme ever changes.

Ideal fix: expose BaseDomain on IDecentralandUrlsSource (backed by ResolvedBaseDomain). Immediate improvement: use Uri.Host for safer parsing:

Suggested change
string realmDomainSuffix = bootstrapContainer.DecentralandUrlsSource
.Url(DCL.Multiplayer.Connections.DecentralandUrls.DecentralandUrl.Host)
.Replace("https://", string.Empty);
string realmDomainSuffix = new Uri(bootstrapContainer.DecentralandUrlsSource
.Url(DCL.Multiplayer.Connections.DecentralandUrls.DecentralandUrl.Host)).Host;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I agree with the comment from Jarvis:

[P2] Re-derives ResolvedBaseDomain via fragile scheme stripping (CLAUDE.md: "Don't re-derive what already exists").

DecentralandUrlsSource.ResolvedBaseDomain already computes this exact value, but is protected and not on IDecentralandUrlsSource. Both this site and RealmController.ResolveHostname (line 375) independently strip the scheme with .Replace("https://", ""), which silently breaks if the scheme ever changes.

Ideal fix: expose BaseDomain on IDecentralandUrlsSource (backed by ResolvedBaseDomain). Immediate improvement: use Uri.Host for safer parsing:

// TODO: take in consideration the web3-network. If its sepolia then it should be .zone
? "realm-provider." + IDecentralandUrlsSource.ORG_DOMAIN
// Consider it as the "main" realm which shares the comms with many catalysts.
? "realm-provider." + decentralandUrlsSource.Url(DecentralandUrl.Host).Replace("https://", string.Empty)

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.

[P2] Same .Replace("https://", "") pattern — second occurrence of the fragile domain extraction. Same BaseDomain interface property would eliminate both.

Suggested change
? "realm-provider." + decentralandUrlsSource.Url(DecentralandUrl.Host).Replace("https://", string.Empty)
? "realm-provider." + new Uri(decentralandUrlsSource.Url(DecentralandUrl.Host)).Host

Comment on lines +172 to +173
public static void SetCustomBaseDomain(string? domain) =>
customBaseDomain = string.IsNullOrWhiteSpace(domain) ? null : domain.Trim();

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.

[P2] Defense-in-depth: validate domain shape. A single-label domain (e.g. --base-domain com) would cause IsDecentralandHost to trust every .com host. While --base-domain is CLI-only (not in PERMITTED_KEYS, so deep links cannot inject it), basic format validation prevents accidental misconfiguration and hardens the public API surface of SetCustomBaseDomain().

Suggested change
public static void SetCustomBaseDomain(string? domain) =>
customBaseDomain = string.IsNullOrWhiteSpace(domain) ? null : domain.Trim();
public static void SetCustomBaseDomain(string? domain)
{
if (string.IsNullOrWhiteSpace(domain))
{
customBaseDomain = null;
return;
}
string trimmed = domain.Trim();
// Require at least two labels and reject URL control characters (defense-in-depth).
if (!trimmed.Contains('.') || trimmed.IndexOfAny(new[] { '/', '?', '#', '@', ':' }) >= 0)
throw new ArgumentException($"Invalid base domain: '{trimmed}'");
customBaseDomain = trimmed;
}

string? customBaseDomain = null)
{
decentralandDomain = environment.ToString()!.ToLower();
this.customBaseDomain = string.IsNullOrWhiteSpace(customBaseDomain) ? null : customBaseDomain!.Trim();

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.

[P2] Redundant null-forgiving !IsNullOrWhiteSpace returning false guarantees non-null on the else branch; the ! is unnecessary noise.

Suggested change
this.customBaseDomain = string.IsNullOrWhiteSpace(customBaseDomain) ? null : customBaseDomain!.Trim();
this.customBaseDomain = string.IsNullOrWhiteSpace(customBaseDomain) ? null : customBaseDomain.Trim();

eordano and others added 3 commits August 14, 2026 10:21
Gateway-eligible URLs resolved before feature flags load are no longer
cached in raw form for the process lifetime; they stay uncacheable until
the use-gateway flag is known. The main-realm comms fallback pins
realm-provider.decentraland.org for every decentraland.* environment and
follows only a custom base domain. Characterization tests pin the
pre-flags -> post-flags resolution sequence.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SystemSpecUtils honors TryGetJsonPayload's result and absent individual
fields instead of dereferencing null members; a failing specs evaluation
reports the exception and never blocks startup, so
--skip-minimum-specs-screen stays effective.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A payload defining only one platform's requirements no longer rejects the
other platform: when every field of a dimension (cpu/gpu/os) is absent the
check passes, while an explicitly-present empty list still rejects.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@decentraland-bot

This comment has been minimized.

eordano and others added 2 commits August 14, 2026 14:49
Clears the 50 InspectCode findings in files this PR touches (CS8618
uninitialized non-nullables via null! / nullable annotations per file
idiom, CS8602/03/04 via real guards or invariant-backed forgiveness),
bringing the warning count back under the ratchet baseline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…missing

The tolerance fixes land without a regression test for the missing-payload
path; this pins every SystemSpecUtils check against an empty flags
configuration, live-hit on a --base-domain deployment whose flags backend
served no alfa-minimum-requirements payload (boot died on splash).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@decentraland-bot

This comment has been minimized.

eordano and others added 4 commits August 14, 2026 16:22
…domain check

Pre-flags gateway-eligible URLs get a dedicated FlagsPending cache state:
cached provisionally, then dropped and re-resolved once feature flags
load. This closes the torn-read window that could permanently cache an
un-gatewayed URL when flags landed mid-resolution, and bounds
flags-never-load sessions to one resolution per URL instead of one per
call. RealmController classifies environment domains by exact membership
in ALL_DOMAINS instead of a spoofable name prefix, and SmartWearableCache
returns the tracked item instead of null! on the cancelled path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
StaticContainer.GPUInstancingService is nullable for real: the disabled
path (no render feature / instancing off) now skips registering the
GPU-instancing feature set (plugin, RoadsPresence, debug system) instead
of carrying a null! that consumers dereference; intentional disable and
misconfiguration are logged distinctly. RealmController's main-realm
hostname derivation becomes an internal static seam with tests pinning
org, the zone->org re-pin, custom domains, and the env-domain spoof shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Unity-bundled NUnit predates Assert.Multiple (CS0117), which broke
compilation of DCL.EditMode.Tests and with it the Lint and EditMode jobs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@decentraland-bot

This comment has been minimized.

@eordano

eordano commented Aug 15, 2026

Copy link
Copy Markdown
Member Author

Two client patches, rebased onto 6b430f4d6 (current gateway head) and verified to apply cleanly. They are intended to land as two separate commits.

I could not push them myself — see "Why this is a comment" at the bottom.


Commit 1 — fix(landscape): skip TerrainLoaded when the terrain has no model

Regression from 8cfe5b60d (#8994).

TerrainGenerator.cs:147 early-returns without assigning TerrainModel when the occupied-parcel set is empty — which is exactly what a realm resolving to WorldManifest.Empty produces. Landscape.cs:80 then raises TerrainLoaded unconditionally, and RenderGroundSystem.cs:200 dereferences terrain.TerrainModel!, throwing an NRE.

The reason this is nastier than a stray exception: LoadLandscapeStartupOperation runs inside a SequentialLoadingOperation. The throw abandons the rest of the chain, so PlayerTeleporting never runs and no scene is ever requested — while the client still reports Completed. The observable symptom is a healthy-looking boot into a permanently empty world, with no error surfaced to the user.

The guard belongs at the publisher, not in each subscriber: GrassIndirectRenderer.cs:172-174 has the identical unguarded dereference and would throw next. ITerrain.cs:16 already declares TerrainModel as nullable, so this is just honouring a contract that was already there.

Still present at 6b430f4d6 — the recent RenderGroundSystem change (92d457503) is an allocation-reuse refactor and does not add a null guard.

diff --git a/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/Landscapes/Landscape.cs b/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/Landscapes/Landscape.cs
index 04c8e488b..a62aee054 100644
--- a/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/Landscapes/Landscape.cs
+++ b/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/Landscapes/Landscape.cs
@@ -77,6 +77,17 @@ namespace Global.Dynamic.Landscapes
                 }
             }
 
+            // TerrainGenerator early-returns without assigning TerrainModel when the parcel set is empty,
+            // which is what a realm resolving to WorldManifest.Empty produces - every deployment whose
+            // realm name is not one of the hardcoded DCL ones lands here. Subscribers dereference
+            // TerrainModel unguarded (RenderGroundSystem, GrassIndirectRenderer), so notifying throws and
+            // aborts the sequential startup chain before the player is ever teleported.
+            if (CurrentTerrain.TerrainModel == null)
+            {
+                ReportHub.Log(ReportCategory.LANDSCAPE, "Terrain has no model (empty parcel set); skipping TerrainLoaded");
+                return EnumResult<LandscapeError>.SuccessResult();
+            }
+
             TerrainLoaded?.Invoke(CurrentTerrain);
             return EnumResult<LandscapeError>.SuccessResult();
         }

Commit 2 — feat(urls): configurable genesis world manifest via --world-manifest-url

WorldManifestProvider resolves the genesis manifest — the terrain occupancy map — from two hardcoded S3 addresses (places-dcf8abb.s3.amazonaws.com, places-e22845c.s3.us-east-1.amazonaws.com) that no domain substitution reaches. A --base-domain deployment therefore has no way to supply its own. This follows the precedent of the existing --gatekeeper-url and --optimized-assets-url overrides.

Please don't take only half of this. The realm-name gate is widened in the same change:

MAIN_REALM_NAMES.Contains(realmName) || genesisManifestOverride != null

Without that second clause, a deployment that supplies its own manifest is still refused genesis terrain for not being named one of the twelve DCL realms — and would have to impersonate one of them to get it. The override standing in for the name check is the whole point.

diff --git a/Explorer/Assets/DCL/Infrastructure/Global/AppArgs/AppArgsFlags.cs b/Explorer/Assets/DCL/Infrastructure/Global/AppArgs/AppArgsFlags.cs
index 5905fa463..3e896c637 100644
--- a/Explorer/Assets/DCL/Infrastructure/Global/AppArgs/AppArgsFlags.cs
+++ b/Explorer/Assets/DCL/Infrastructure/Global/AppArgs/AppArgsFlags.cs
@@ -145,6 +145,14 @@ namespace Global.AppArgs
 
         public const string OPTIMIZED_ASSETS_URL = "optimized-assets-url";
 
+        /// <summary>
+        ///     Overrides the genesis world manifest — the terrain occupancy map. The default is a hardcoded
+        ///     S3 bucket that no domain substitution reaches, so a "--base-domain" deployment has no way to
+        ///     supply its own. Passing it also opts the realm into genesis terrain regardless of its name,
+        ///     which otherwise requires the deployment to impersonate one of the twelve DCL realm names.
+        /// </summary>
+        public const string WORLD_MANIFEST_URL = "world-manifest-url";
+
         public const string NO_LIVEKIT_MODE = "no-livekit-mode";
 
         public const string NATIVE_SHUTDOWN_STOPWATCH = "native-shutdown-stopwatch";
diff --git a/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/WorldManifestProvider.cs b/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/WorldManifestProvider.cs
index 77a28e084..1b3647f35 100644
--- a/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/WorldManifestProvider.cs
+++ b/Explorer/Assets/DCL/Infrastructure/Global/Dynamic/WorldManifestProvider.cs
@@ -28,16 +28,26 @@ namespace Global.Dynamic
 
         private WorldManifest? cachedMainManifest;
 
-        public WorldManifestProvider(IWebRequestController webRequestController)
+        /// <summary>
+        ///     "--world-manifest-url". The genesis manifest above lives at a hardcoded S3 address that no
+        ///     domain substitution reaches, so a custom base domain cannot otherwise supply its own terrain
+        ///     occupancy map. When set it also stands in for the realm-name check: a deployment naming its
+        ///     realm anything other than the twelve DCL ones still gets genesis terrain, instead of having
+        ///     to impersonate one of them.
+        /// </summary>
+        private readonly string? genesisManifestOverride;
+
+        public WorldManifestProvider(IWebRequestController webRequestController, string? genesisManifestOverride = null)
         {
             this.webRequestController = webRequestController;
+            this.genesisManifestOverride = string.IsNullOrWhiteSpace(genesisManifestOverride) ? null : genesisManifestOverride!.Trim();
         }
 
         public async UniTask<WorldManifest> FetchWorldManifestAsync(URLDomain assetBundleRegistry, string realmName, DecentralandEnvironment environment, CancellationToken ct)
         {
             try
             {
-                if(MAIN_REALM_NAMES.Contains(realmName))
+                if(MAIN_REALM_NAMES.Contains(realmName) || genesisManifestOverride != null)
                     return await FetchGenesisManifestAsync(environment, ct);
 
                 if(realmName.EndsWith(dclWorldName))
@@ -87,7 +97,9 @@ namespace Global.Dynamic
                 if (cachedMainManifest.HasValue)
                     return cachedMainManifest.Value;
 
-                URLAddress manifestURL = environment == DecentralandEnvironment.Zone ? ZONE_MANIFEST_URL : ORG_MANIFEST_URL;
+                URLAddress manifestURL = genesisManifestOverride != null
+                    ? URLAddress.FromString(genesisManifestOverride)
+                    : environment == DecentralandEnvironment.Zone ? ZONE_MANIFEST_URL : ORG_MANIFEST_URL;
 
                 string? result = await webRequestController
                                       .GetAsync(new CommonArguments(manifestURL), ct,
diff --git a/Explorer/Assets/DCL/Infrastructure/Global/StaticContainer.cs b/Explorer/Assets/DCL/Infrastructure/Global/StaticContainer.cs
index 06b9294de..aa80720a8 100644
--- a/Explorer/Assets/DCL/Infrastructure/Global/StaticContainer.cs
+++ b/Explorer/Assets/DCL/Infrastructure/Global/StaticContainer.cs
@@ -353,7 +353,8 @@ namespace Global
                 promisesAnalyticsPlugin
             };
 
-            container.WorldManifestProvider = new WorldManifestProvider(container.WebRequestsContainer.WebRequestController);
+            appArgs.TryGetValue(AppArgsFlags.WORLD_MANIFEST_URL, out string? worldManifestUrl);
+            container.WorldManifestProvider = new WorldManifestProvider(container.WebRequestsContainer.WebRequestController, worldManifestUrl);
 
             return (container, true);
         }

Applying

Both hunks were applied to a clean worktree at 6b430f4d6 and git apply --check passes. The StaticContainer.cs hunk above is already rebased over 92d457503 (the GPUInstancingService? nullability change), so its pre-image is 06b9294de, not the 41ed4e7b8 an older copy of this patch would reference.

Symbols were checked against the tree at 6b430f4d6:

  • ReportCategory.LANDSCAPE exists (ReportCategory.cs:180), and ReportHub.Log(ReportData, object) accepts it — same call shape 92d457503 itself introduced for ReportCategory.GPU_INSTANCING.
  • URLAddress.FromString exists (URLAddress.cs:24).
  • IAppArgs.TryGetValue(string, out string?) matches (ApplicationParametersParser.cs:66).
  • new WorldManifestProvider(...) has exactly one call site, the one patched; the new parameter is defaulted, so nothing else breaks.

Not compiled in Unity — no Editor available on the machine that produced this.

Why this is a comment rather than two commits

gateway carries a required_signatures ruleset (ruleset_id 479294 — it is the only rule on the branch), and a plain local commit from this machine has no verified signature, so a push is rejected with GH013. The mechanism that produced the rest of this branch's commits — GitHub signing them server-side with its web-flow key — was not available to the automation session that prepared these patches. The patches themselves are ready and rebased; someone with a signing setup can apply them as-is.

The skip note justified keeping the `= null!` initializer sweep by calling it the
sanctioned `[field: SerializeField]` inspector idiom with a linter carve-out.
Neither holds: CLAUDE.md § Anti-Patterns sanctions `null!` only for deserialized
JSON DTO fields, and the custom-rules linter has no null!/default! rule at all.
The decision to keep the sweep is still right, but for a different reason - the
`{ get; private set; } = null!` form is what composition-root containers already
use (654 occurrences at the PR base) and `Explorer/Assets/csc.rsp` already
`-nowarn:8618`, so the initializers change no compiler outcome and dropping them
would only move the two files against the prevailing pattern. The note now cites
that evidence instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018c638dR1vPysCMbYt2qQg5

Also includes (squashed): fix(gateway): apply --base-domain before deep links, restore empty-place thumbnails
Review fixes for PR #9728.

DeepLinkAllowlist.SetCustomBaseDomain was called ~40 lines after
InitializeDeepLinks(), which fires exactly once and synchronously walks
ProcessDeepLinkParameters -> IsRealmWhitelisted -> IsDecentralandHost - the very
check that consults customBaseDomain. On a cold launch carrying both
--base-domain=X and a decentraland:// link into a world hosted on X, that check
ran with customBaseDomain still null, so the link's whitelisted-realm-gated dev
params (local-scene, hub, skip-auth-screen, mcp) were denied for the exact launch
the flag exists to support. The arg is now read once, right after the deferring
parser is built, and handed to both the allowlist and the whitelist fetch; the
duplicate read inside InitializeDeepLinkWorldWhitelistAsync is gone.

PlacesAPIResponse's "Empty place" lost its hardcoded peer.decentraland.org
thumbnail, and ImageController only paints a placeholder when the caller supplies
defaultSprite. PlaceInfoPanelController, PlaceElementView (search results) and
TeleportPromptController passed none, so any parcel without a registered place
rendered a transparent image instead of a thumbnail. Each now passes the
placeholder its own prefab already ships (DefaultImagePlace.png) - the teleport
prompt reuses viewInstance.defaultImage, the other two capture the sprite the
prefab authored on the ImageView before the first request replaces it.

Null-forgiving suppressions are replaced by restructuring rather than annotation:
- FixedScenePointers.SceneResults is a non-nullable field; only `= default` made
  it look nullable, so the struct is read back from the world after the wait.
- CreateBootstrapperAsync reached back into the half-built container for
  IdentityCache; it now takes the identity cache the caller already holds.
- The Sentry scope configurator binds SentryReportHandler with a pattern, since
  DiagnosticsContainer.Sentry is genuinely nullable.
- UrlData.Url is nullable by design, so Url()/Probe() bind it with `is { }` and
  fall back to the NOT_CONFIGURED sentinel, now a named constant shared with
  UrlData.ToString().
- SmartWearableCache passes the already-narrowed AvatarAttachmentDTO into
  IsSmart/GetContentUrl instead of re-dereferencing IWearable.DTO.
- The smart-wearable authorization popup reports the missing scene metadata
  instead of dereferencing it blind.

LocalGateKeeperSceneAdapter was the only SUPPORTED_URLS member with no gateway
coverage. It is added to the default-domain routing and round-trip cases; because
RawUrl resolves its host eagerly it carries no decentraland.{ENV} token for
IsGatewayTransformable to match under a custom base domain, so that case is
pinned by its own test instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018c638dR1vPysCMbYt2qQg5
@decentraland-bot

Copy link
Copy Markdown
Contributor

PR #9728, run #32035435390

Builds: Windows change, Windows baseline, macOS change, macOS baseline

How to read this table
  • Each build is measured 3 times. The values are the median, and (min–max) is the lowest and highest of those runs — a wide range means the metric is noisy and small differences are not trustworthy.
  • Δ is Change minus Baseline (a negative Δ means Change is faster).
  • 🟢 faster / 🔴 slower — a real difference: larger than both 3% and the run-to-run range.
  • ⚪ within noise — the difference is smaller than how much the build varies between its own runs, so it cannot be told apart from random variation. Treat it as no change.
  • Exceptions per run — the average number of exceptions in a run's log; more than the baseline is flagged 🔴 even when frame times look fine. The Exception breakdown under each table groups them by the explorer's report category and exception type (as totals across the runs).
  • A run that logged unusually many exceptions (at least 10 and 5× the median of its build's runs — e.g. a service was down during it) is excluded from all numbers and called out under the table.

⚠️ Apple M1 failed to produce results — see the run for details.

⚠️ Intel Core i5 failed to produce results — see the run for details.

@mikhail-dcl mikhail-dcl left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The current implementation with domain overriding created low-quality injections in several places, while the real purpose is to replace the whole environment.

Thus, I propose to revise the whole implementation by introducing a separate value in DecentralandEnvironment enum, which in conjunction with the app arg, will provide the first-class source for decentraland urls, instead of clumsily sitting on top of the org / zone / today. In turn it will ensure by design that the default env will not leak anywhere by accident.

Comment on lines +106 to +108
string realmDomainSuffix = bootstrapContainer.DecentralandUrlsSource
.Url(DCL.Multiplayer.Connections.DecentralandUrls.DecentralandUrl.Host)
.Replace("https://", string.Empty);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I agree with the comment from Jarvis:

[P2] Re-derives ResolvedBaseDomain via fragile scheme stripping (CLAUDE.md: "Don't re-derive what already exists").

DecentralandUrlsSource.ResolvedBaseDomain already computes this exact value, but is protected and not on IDecentralandUrlsSource. Both this site and RealmController.ResolveHostname (line 375) independently strip the scheme with .Replace("https://", ""), which silently breaks if the scheme ever changes.

Ideal fix: expose BaseDomain on IDecentralandUrlsSource (backed by ResolvedBaseDomain). Immediate improvement: use Uri.Host for safer parsing:

"🔴 Error. You cannot change realms in the Today environment. Please restart DCL with the desired environment");
case DecentralandEnvironment.Zone:
return HostHasSuffix(realmToTeleportTo, IDecentralandUrlsSource.ZONE_DOMAIN)
return HostHasSuffix(realmToTeleportTo, realmDomainSuffix)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This class reveals the real design defect of this PR (look, two paths are now identical because of the changes).

The whole description is provided in the parent review comment

@mikhail-dcl

Copy link
Copy Markdown
Collaborator

This PR will be closed in favor "chore/e2e-infra" according to the renewed e2e strategy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ext-contribution Identifies a contribution which was not initiated by a Unity Developer force-build Used to trigger a build on draft PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants