Skip to content

feat: register referral attribution from the launcher referrer argument - #9488

Open
braianj wants to merge 9 commits into
devfrom
feat/referral-referrer-arg
Open

feat: register referral attribution from the launcher referrer argument#9488
braianj wants to merge 9 commits into
devfrom
feat/referral-referrer-arg

Conversation

@braianj

@braianj braianj commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Consumes the --referrer launch argument the launcher passes and uses it to register the referral for new accounts.

  • New referrer app arg (AppArgsFlags.REFERRER) plus a validated ReferrerArg normalizer (0x + 40 hex, lowercased).
  • DeepLinkSignInUrl (extracted from DappDeepLinkAuthenticator so it is unit-testable) appends the referrer to the signature web app URL, so wallet users carry attribution into the web setup flow.
  • LobbyForNewAccountAuthState — which runs only for new accounts — registers the referral against social-api with a signed fetch, covering the email/OTP onboarding that never opens the browser. Time-boxed and best-effort: it never blocks or fails onboarding.

Please note before merging:

  • The registration is awaited before the user proceeds to the world, but the LoggedIn status transition happens earlier when the lobby is entered, so registration is not strictly ordered ahead of the LOGGED_IN analytics event. With an idempotent create on the backend and the multi-day finalization rule, the worst case is one login day not counted rather than a lost referral; closing the ordering gap fully would mean moving the status transition, which is left out of scope here.
  • Durable cross-session retry is not implemented: a failed registration retries only on re-entry to this state.
  • The added tests cover normalization and URL construction, not the ordering or retry behavior, and this branch has not been compiled or run locally — it needs a CI run to validate.

@braianj
braianj requested review from a team as code owners July 27, 2026 13:28
@github-actions
github-actions Bot requested a review from anicalbano July 27, 2026 13:28
@decentraland-bot decentraland-bot added the ext-contribution Identifies a contribution which was not initiated by a Unity Developer label Jul 27, 2026
@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

badge

Build failed! Check the logs to see what went wrong.
If the error repeats please consider the clean-build tag.

@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

badge

Warnings not reduced: 14075 => 14076 — remove at least one warning to merge.

Warnings/errors in files changed by this PR (41)
Assets/DCL/AuthenticationScreenFlow/AuthenticationScreenController.cs:156  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:124  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:143  CSharpWarnings::CS8602  Dereference of a possibly null reference
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:168  CSharpWarnings::CS8604  Possible null reference argument for parameter 'identityCache' in 'DCL.PerformanceAndDiagnostics.Analytics.AnalyticsContainer.CreateAsync'
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:41  CSharpWarnings::CS8618  Non-nullable field 'reportHandlingSettings' is uninitialized. Consider adding the 'required' modifier or declaring the field as nullable.
Assets/DCL/AuthenticationScreenFlow/States/LobbyForNewAccountAuthState.cs:58  CSharpWarnings::CS8618  Non-nullable fields 'newUserProfile', 'userEmail' must contain non-null values when exiting constructor. Consider adding the 'required' modifiers or declaring the fields as nullable.
Assets/DCL/AuthenticationScreenFlow/AuthenticationScreenController.cs:99  CSharpWarnings::CS8618  Non-nullable members 'DiscordButtonClicked', 'OTPVerified', 'OTPResend', 'ProfileFinalized', 'fsm', 'audio' must contain non-null values when exiting constructor. Consider adding the 'required' modifiers or declaring the members as nullable.
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:56  CSharpWarnings::CS8618  Non-nullable property 'Analytics' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:60  CSharpWarnings::CS8618  Non-nullable property 'AppArgs' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:278  CSharpWarnings::CS8618  Non-nullable property 'BuildData' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:57  CSharpWarnings::CS8618  Non-nullable property 'DebugSettings' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:45  CSharpWarnings::CS8618  Non-nullable property 'DecentralandUrlsSource' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:44  CSharpWarnings::CS8618  Non-nullable property 'DiagnosticsContainer' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:61  CSharpWarnings::CS8618  Non-nullable property 'LaunchMode' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:276  CSharpWarnings::CS8618  Non-nullable property 'ReportHandlingSettingsDevelopment' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:277  CSharpWarnings::CS8618  Non-nullable property 'ReportHandlingSettingsProduction' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:58  CSharpWarnings::CS8618  Non-nullable property 'VolumeBus' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:47  CSharpWarnings::CS8618  Non-nullable property 'Web3AccountFactory' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:46  CSharpWarnings::CS8618  Non-nullable property 'WebBrowser' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:66  CSharpWarnings::CS8618  Non-nullable property 'WebRequestsContainer' is uninitialized. Consider adding the 'required' modifier or declaring the property as nullable.
Assets/DCL/AuthenticationScreenFlow/AuthenticationScreenController.cs:94  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/AuthenticationScreenFlow/AuthenticationScreenController.cs:179  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/AuthenticationScreenFlow/AuthenticationScreenController.cs:180  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/AuthenticationScreenFlow/AuthenticationScreenController.cs:296  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:72  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/Infrastructure/Global/Dynamic/BootstrapContainer.cs:78  ConditionalAccessQualifierIsNonNullableAccordingToAPIContract  Conditional access qualifier expression is never null according to nullable reference types' annotations
Assets/DCL/AuthenticationScreenFlow/AuthenticationScreenController.cs:79  InconsistentNaming  Name 'CurrentRequestID' does not match rule 'members_should_be_pascal_case'. Suggested name is 'CurrentRequestId'.
Assets/DCL/AuthenticationScreenFlow/States/LobbyForNewAccountAuthState.cs:172  InconsistentNaming  Name 'OpenClickableURL' does not match rule 'members_should_be_pascal_case'. Suggested name is 'OpenClickableUrl'.
Assets/DCL/Web3/Authenticators/Implementations/Dapp/DappDeepLinkAuthenticator.cs:196  InconsistentNaming  Name 'address' does not match rule 'members_should_be_pascal_case'. Suggested name is 'Address'.
Assets/DCL/Web3/Authenticators/Implementations/Dapp/DappDeepLinkAuthenticator.cs:190  InconsistentNaming  Name 'authChain' does not match rule 'members_should_be_pascal_case'. Suggested name is 'AuthChain'.
Assets/DCL/Web3/Authenticators/Implementations/Dapp/DappDeepLinkAuthenticator.cs:189  InconsistentNaming  Name 'ephemeralIdentity' does not match rule 'members_should_be_pascal_case'. Suggested name is 'EphemeralIdentity'.
Assets/DCL/Web3/Authenticators/Implementations/Dapp/DappDeepLinkAuthenticator.cs:188  InconsistentNaming  Name 'expiration' does not match rule 'members_should_be_pascal_case'. Suggested name is 'Expiration'.
Assets/DCL/Web3/Authenticators/Implementations/Dapp/DappDeepLinkAuthenticator.cs:183  InconsistentNaming  Name 'identity' does not match rule 'members_should_be_pascal_case'. Suggested name is 'Identity'.
Assets/DCL/Web3/Authenticators/Implementations/Dapp/DappDeepLinkAuthenticator.cs:197  InconsistentNaming  Name 'privateKey' does not match rule 'members_should_be_pascal_case'. Suggested name is 'PrivateKey'.
Assets/DCL/Web3/Authenticators/Implementations/Dapp/DappDeepLinkAuthenticator.cs:198  InconsistentNaming  Name 'publicKey' does not match rule 'members_should_be_pascal_case'. Suggested name is 'PublicKey'.
Assets/DCL/Web3/Abstract/Web3Address.cs:56  RedundantCast  Type cast is redundant
Assets/DCL/AuthenticationScreenFlow/AuthenticationScreenController.cs:15  RedundantUsingDirective  Using directive is not required by the code and can be safely removed
Assets/DCL/AuthenticationScreenFlow/AuthenticationScreenController.cs:22  RedundantUsingDirective  Using directive is not required by the code and can be safely removed
Assets/DCL/AuthenticationScreenFlow/AuthenticationScreenController.cs:25  RedundantUsingDirective  Using directive is not required by the code and can be safely removed
Assets/DCL/AuthenticationScreenFlow/AuthenticationScreenController.cs:27  RedundantUsingDirective  Using directive is not required by the code and can be safely removed
Assets/DCL/AuthenticationScreenFlow/States/LobbyForNewAccountAuthState.cs:44  UnusedMember.Local  Constant 'REFERRAL_REGISTRATION_TIMEOUT_SECONDS' is never used

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

STEP 2 — Root-cause check

PASS. This PR adds a new feature (referral attribution from a launcher argument), not a bug fix. The diff correctly implements two referral consumption paths: URL construction for wallet sign-ups and API registration for new-account onboarding.

STEP 3 — Design & integration

PASS with notes.

Owner search — RegisterReferralAsync: The referral API call runs inside LobbyForNewAccountAuthState.PublishNewProfileAsync, which executes only when a new account completes onboarding. The lifecycle owner for new-account finalization is LobbyForNewAccountAuthState itself (it owns the profile publish, newsletter subscription, and transition to the world). Placing the referral registration here is correct — no other existing owner manages this lifecycle step. Files checked: AuthenticationScreenController.cs (state machine wiring), LobbyForExistingAccountAuthState (existing-account path — correctly excluded), InitAuthState, LoginSelectionAuthState.

Owner search — DeepLinkSignInUrl: Extracted from inline URL construction in DappDeepLinkAuthenticator.LoginAsync. The extraction is justified: it makes URL-building logic independently testable without instantiating the full authenticator. The split pays for itself in test coverage (CLAUDE.md §11 "Extracting when you should merge").

Owner search — ReferrerArg: Shared static utility consumed by both DeepLinkSignInUrl.Build and LobbyForNewAccountAuthState. Two independent consumers → extraction justified.

Referrer flow: The referrer enters via appArgs at two composition-root sites (BootstrapContainer.CreateWeb3Dependencies and Web3AuthenticationPlugin.InitializeAsync), feeding two separate dependency chains. This is noted as P2 below.

Teardown / consumption trace: No subscriptions, event hookups, connections, or persistent resources introduced. RegisterReferralAsync is awaited inline. No teardown needed.

STEP 4 — Member audit

  • DeepLinkSignInUrl.Build() — public static, 1 production caller (DappDeepLinkAuthenticator.LoginAsync), 4 test callers. Independently tested. Acceptable single-caller extraction.
  • ReferrerArg.Normalize() — public static, 2 production callers. Appropriate.
  • AppArgsFlags.REFERRER — public const, 3 usage sites. Appropriate.
  • DecentralandUrl.ReferralProgress — enum member, 2 usage sites. Appropriate.

No single-use predicates, absent-equals-false, or redundant-guard issues.

STEP 5 — Findings

See inline comments below for suggestion blocks.

# Sev Location Issue
1 P1 PR settings PR targets main instead of dev (repo default branch is dev)
2 P1 LobbyForNewAccountAuthState.cs:346–382 Combined referral timeout up to 10s blocks user with no feedback
3 P2 DappDeepLinkAuthenticator.cs:57 Raw referrer stored; inconsistent with LobbyForNewAccountAuthState which normalizes at construction

[P1] PR targets main instead of dev. This repo's default branch is dev (origin/HEAD → origin/dev). Per the branch model, feature PRs should target dev. Please retarget this PR before merge.

Security review: No security issues found. Input validation via ReferrerArg.Normalize (^0x[a-fA-F0-9]{40}$) is strict and correctly prevents injection in both URL and JSON interpolation paths. API requests use signed fetch. No secrets or sensitive data in the diff.

Git conventions (ADR-6): PASS. Title follows feat: ... format. Branch follows feat/... convention.

STEP 6 — Complexity

COMPLEX — Modifies authentication/onboarding flow, adds signed-fetch web requests during new-account finalization, touches DI wiring in BootstrapContainer and Web3AuthenticationPlugin, adds a new DecentralandUrl enum member, and spans 17 files across multiple assemblies.

STEP 7 — QA assessment

QA_REQUIRED: YES — Changes affect the runtime authentication/onboarding flow. New-account registration now includes a server round-trip (POST + PATCH) between profile creation and entering the world. Wallet sign-ups get a modified browser URL. Both paths need manual verification.

STEP 8 — Non-blocking warnings

None.

REVIEW_RESULT: FAIL ❌
COMPLEXITY: COMPLEX
COMPLEXITY_REASON: Modifies auth onboarding flow, adds signed-fetch web requests, touches DI wiring in BootstrapContainer/Web3AuthenticationPlugin, and spans 17 files across multiple assemblies.
QA_REQUIRED: YES


Reviewed by Jarvis 🤖 · Requested by braianj via Slack

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

Great work! Added small comments

Comment thread Explorer/Assets/DCL/Web3/Authenticators/Implementations/Dapp/DeepLinkSignInUrl.cs Outdated
Comment thread Explorer/Assets/DCL/Web3/ReferrerArg.cs Outdated
@braianj
braianj changed the base branch from main to dev July 27, 2026 16:19
@github-actions

Copy link
Copy Markdown
Contributor

Tests: 24459 passed, 0 failed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants