Skip to content

feat(mwa): add injectable auth token cache abstraction#284

Open
JoshhSandhu wants to merge 3 commits into
magicblock-labs:mainfrom
JoshhSandhu:feat/mwa-auth-cache-abstraction
Open

feat(mwa): add injectable auth token cache abstraction#284
JoshhSandhu wants to merge 3 commits into
magicblock-labs:mainfrom
JoshhSandhu:feat/mwa-auth-cache-abstraction

Conversation

@JoshhSandhu
Copy link
Copy Markdown
Contributor

@JoshhSandhu JoshhSandhu commented Apr 28, 2026

⚠️ NOTE: Foundation PR for Issues #271 and #272 (duplicate trackers for the
same IMwaAuthCache ask). Default behavior is unchanged from PR #269:
existing cached sessions continue to work with zero migration. The new
value is the seam: developers can now plug in Android Keystore /
EncryptedSharedPreferences for at-rest encryption without forking the SDK.
No public method signatures change. Adds optional ctor parameter only.

Status Type ⚠️ Core Change Issues
Ready Feature No #271, #272

Problem

PR #269 fixed the auth-token lifecycle but left token persistence hardcoded
to PlayerPrefs. PlayerPrefs is plaintext app storage on Android: a local
device compromise, an adb backup, or a rooted-device dump can extract the
bearer token and replay the session against the wallet.

Issues #271 and #272 (duplicate trackers, same ask) request the SDK make
this layer pluggable so developers shipping production games can inject
platform-secure storage (Android Keystore, EncryptedSharedPreferences, iOS
Keychain) without touching SDK internals.

There is no abstraction today. SolanaMobileWalletAdapter writes the token
directly to a hardcoded PlayerPrefs key, and every read/write/delete site
in _Login, _SignAllTransactions, SignMessage, Logout, and
DisconnectWallet does the same thing inline.

Solution

Introduce a narrow IMwaAuthCache interface (Get / Set / Clear,
auth-token only) plus a default PlayerPrefsAuthCache implementation that
preserves the existing storage key solana_sdk.mwa.auth_token. Both
adapters accept the cache as an optional constructor argument that defaults
to the PlayerPrefs implementation.

Why a 3-method interface, token only:

Why keep solana_sdk.mwa.auth_token as the default key:

  • Users who connected on main after PR Fix/mwa keep connection alive auth token restore #269 keep their session on
    upgrade; no migration code, no silent logout.
  • The key literal exists in exactly one production location now
    (PlayerPrefsAuthCache.DefaultKey); SolanaMobileWalletAdapter.PrefKeyAuthToken
    aliases it as a compile-time constant chain.

Backward compatibility:

  • Logout() stays override void, the WalletBase override contract is
    unchanged. The cache Clear() call is awaited synchronously inside it.
  • Both adapter constructors append the new IMwaAuthCache authCache = null
    parameter at the end with default null. Every existing call site
    (including Web3.LoginWalletAdapter()) compiles unchanged.
  • MigrateLegacyPrefKeys() from PR Fix/mwa keep connection alive auth token restore #269 is left untouched: pre-Fix/mwa keep connection alive auth token restore #269 installs
    upgrading directly to this PR still get migrated from the legacy pk /
    authToken keys.

Optional per-identity scoping:
new PlayerPrefsAuthCache("phantom") writes to
solana_sdk.mwa.auth_token.phantom. Useful for apps that connect to
multiple wallet identities. Default (no scope) is backward compatible.

Before & After Screenshots

BEFORE: Token persistence hardcoded to PlayerPrefs

AFTER: Adapter routes through IMwaAuthCache, default impl preserves PR #269 behavior, custom impls plug in via constructor:

image
IMWA_authCashe.3.mp4
  • cold launch -> MWA prompt -> approve -> play -> close
  • reopen -> silent reconnect, no prompt
  • in-game Logout -> close -> reopen -> fresh prompt (cache cleared)]

Custom cache injection example (lives in PR body, not in SDK):

public class FileAuthCache : IMwaAuthCache
{
    private readonly string _path =
        Path.Combine(Application.persistentDataPath, "mwa_auth.bin");

    public Task<string> Get()
    {
        if (!File.Exists(_path)) return Task.FromResult<string>(null);
        var v = File.ReadAllText(_path);
        return Task.FromResult(string.IsNullOrEmpty(v) ? null : v);
    }

    public Task Set(string authToken)
    {
        if (string.IsNullOrEmpty(authToken)) return Task.CompletedTask;
        File.WriteAllText(_path, authToken);
        return Task.CompletedTask;
    }

    public Task Clear()
    {
        if (File.Exists(_path)) File.Delete(_path);
        return Task.CompletedTask;
    }
}

// Inject:
var adapter = new SolanaWalletAdapter(
    options, rpcCluster, customRpc, webSocketsRpc,
    autoConnectOnStartup: false,
    authCache: new FileAuthCache());

Other changes (e.g. bug fixes, small refactors)

  • Single source of truth for the auth-token key. The literal
    "solana_sdk.mwa.auth_token" now lives only in
    PlayerPrefsAuthCache.DefaultKey. SolanaMobileWalletAdapter.PrefKeyAuthToken
    is now private const string PrefKeyAuthToken = PlayerPrefsAuthCache.DefaultKey;,
    preserving the named constant pattern requested on PR Fix/mwa keep connection alive auth token restore #269 while removing
    the duplicated literal.
  • Auth-token reads/writes in the adapter are now async-aware (await _authCache.Get() / await _authCache.Set(...) / await _authCache.Clear())
    inside the existing async methods. Logout() is the only sync entry
    point; it uses .GetAwaiter().GetResult() on Clear() to keep the
    WalletBase override contract.
  • [Preserve] attribute on PlayerPrefsAuthCache so IL2CPP code stripping
    does not remove it in AOT builds.
  • Coexists cleanly with open PR Feat(test)/mwa keep connection alive tests #283 (test coverage): no changes to
    IAdapterOperations (still 6 methods), MigrateLegacyPrefKeys body /
    signature / access untouched, PrefKeyPublicKey and PrefKeyAuthToken
    constants still declared. All four Feat(test)/mwa keep connection alive tests #283 reflection tests stay green.

Deploy Notes

Runtime change. No new external dependencies. No Unity Editor / build
pipeline changes. No new package references in EditMode.asmdef.

New scripts:

  • Runtime/codebase/SolanaMobileStack/MwaAuthCache/IMwaAuthCache.cs : 3-method interface (Get / Set / Clear) for auth-token persistence.
  • Runtime/codebase/SolanaMobileStack/MwaAuthCache/PlayerPrefsAuthCache.cs : default IMwaAuthCache implementation backed by PlayerPrefs. Optional scope arg on the second constructor for per-identity bucketing.
  • Tests/EditMode/MwaAuthCache/PlayerPrefsAuthCacheTests.cs : ~14 EditMode tests covering round-trip, no-op on empty/null input, idempotent clear, scope isolation, backward-compat with the PR Fix/mwa keep connection alive auth token restore #269 key, and reflection guards on both adapter constructors.

Modified scripts:

  • Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs : routes every auth-token read/write through _authCache, accepts optional IMwaAuthCache ctor argument, retains MigrateLegacyPrefKeys and both PrefKey* constants for PR Feat(test)/mwa keep connection alive tests #283 compatibility.
  • Runtime/codebase/SolanaWalletAdapter.cs : forwards the optional cache to the Android SolanaMobileWalletAdapter. WebGL and iOS branches ignore the parameter (those platforms do not use MWA bearer tokens).

New dependencies:

  • None.

Tested on Android (Solana Seeker via Mobile Wallet Adapter) in CrossyRoad: https://github.com/JoshhSandhu/CrossyRoad

Closes #271
Closes #272

Summary by CodeRabbit

  • New Features
    • Configurable auth-token persistence with a default local-storage implementation and optional scoped token buckets.
    • Wallet adapters now accept an optional auth-cache to enable custom caching strategies; auth tokens are loaded, persisted, and cleared via the cache, preserving public-key storage and legacy key compatibility.
  • Tests
    • Added tests covering storage behavior, scoped/backward-compatible keys, cache-injection wiring, and clear/round-trip scenarios.

Issue magicblock-labs#271 needs pluggable auth token persistence so games can use
Keystore/EncryptedSharedPreferences instead of hardcoded PlayerPrefs.

Default cache keeps key `solana_sdk.mwa.auth_token`, preserving PR magicblock-labs#269
sessions with no migration while enabling custom secure backends.

Refs magicblock-labs#271
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 53d5353d-e99f-4e62-8d63-a5a8f0119065

📥 Commits

Reviewing files that changed from the base of the PR and between e874dfe and d431425.

📒 Files selected for processing (1)
  • Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs

Walkthrough

Adds an IMwaAuthCache abstraction with a default PlayerPrefsAuthCache, injects it into wallet adapters for auth-token persistence, updates adapter logic to use the cache, and adds EditMode tests and Unity .meta files for the new assets.

Changes

Cohort / File(s) Summary
Auth Cache Interface & Impl
Runtime/codebase/SolanaMobileStack/MwaAuthCache/IMwaAuthCache.cs, Runtime/codebase/SolanaMobileStack/MwaAuthCache/PlayerPrefsAuthCache.cs
Adds IMwaAuthCache (async Get(), Set(string), Clear()) and a PlayerPrefsAuthCache default implementation with scoped keys and backward-compatible default key.
Adapter Integration
Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs, Runtime/codebase/SolanaWalletAdapter.cs
Adapters accept optional IMwaAuthCache in constructors; SolanaMobileWalletAdapter now reads/writes/clears auth tokens via the cache and clears it on failures/logout; SolanaWalletAdapter forwards the cache to Android MWA.
Tests
Tests/EditMode/MwaAuthCache/PlayerPrefsAuthCacheTests.cs
Adds NUnit EditMode tests covering Get/Set/Clear semantics, scoped-key isolation, null/empty handling, backward-compatibility with legacy key, and constructor injection signatures.
Unity Metadata
Runtime/codebase/SolanaMobileStack/MwaAuthCache/*.meta, Tests/EditMode/MwaAuthCache/*.meta
Adds Unity .meta files (GUIDs and importer defaults) for the new source and test files.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Kuldotha
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.12% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: introducing an injectable auth token cache abstraction (IMwaAuthCache interface and PlayerPrefsAuthCache implementation).
Description check ✅ Passed The description comprehensively addresses the template with all key sections filled: Problem, Solution, Before & After, Deploy Notes, and properly structured status table with linked issues.
Linked Issues check ✅ Passed The PR fully implements the core requirements from both #271 and #272: IMwaAuthCache interface with Get/Set/Clear methods [#271, #272], PlayerPrefsAuthCache default implementation [#271, #272], integration into SolanaMobileWalletAdapter and SolanaWalletAdapter constructors [#271, #272], and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are within scope: new IMwaAuthCache interface and PlayerPrefsAuthCache implementation, adapter constructor updates for injection, comprehensive tests, and aligned with #271/#272 objectives. No unrelated refactoring or out-of-scope modifications present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs (1)

130-138: ⚠️ Potential issue | 🟠 Major

Reset in-memory token when cached credentials are invalidated.

At Line 130-133 and Line 136-138, persisted credentials are cleared but _authToken is not. A stale in-memory token can survive this branch and drive later calls down Reauthorize(...) incorrectly.

Suggested fix
                     // Reauthorize failed or returned empty token - clear cached credentials
+                    _authToken = null;
                     PlayerPrefs.DeleteKey(PrefKeyPublicKey);
                     PlayerPrefs.Save();
                     await _authCache.Clear();
                 }
                 else if (!pk.IsNullOrEmpty())
                 {
+                    _authToken = null;
                     PlayerPrefs.DeleteKey(PrefKeyPublicKey);
                     PlayerPrefs.Save();
                 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs` around lines
130 - 138, When clearing persisted credentials in SolanaMobileWalletAdapter (the
branches that call PlayerPrefs.DeleteKey(PrefKeyPublicKey); PlayerPrefs.Save();
and await _authCache.Clear()), also reset the in-memory token by setting
_authToken = null (or equivalent cleared state) so a stale in-memory token
cannot survive and trigger Reauthorize(...); update both the branch that awaits
_authCache.Clear() and the else branch that only deletes PlayerPrefs to clear
_authToken alongside existing cleanup calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Runtime/codebase/SolanaMobileStack/MwaAuthCache/IMwaAuthCache.cs.meta`:
- Around line 3-11: The .cs.meta file for IMwaAuthCache includes an unnecessary
MonoImporter block; edit IMwaAuthCache.cs.meta to remove the entire MonoImporter
section and leave only the minimal metadata fields (fileFormatVersion and guid)
to match the repository convention for C# .cs.meta files (i.e., ensure only
fileFormatVersion and guid keys remain, removing externalObjects,
serializedVersion, defaultReferences, executionOrder, icon, userData,
assetBundleName, and assetBundleVariant entries).

In
`@Runtime/codebase/SolanaMobileStack/MwaAuthCache/PlayerPrefsAuthCache.cs.meta`:
- Around line 3-11: The .cs.meta contains an unnecessary MonoImporter block;
replace it with the repo's minimal C# meta format by removing the MonoImporter
section and leaving only fileFormatVersion and guid entries (ensure the existing
guid value is preserved and include the correct fileFormatVersion key), i.e.,
trim everything referencing
MonoImporter/externalObjects/serializedVersion/defaultReferences/executionOrder/icon/userData/assetBundleName/assetBundleVariant
and keep only the minimal metadata keys fileFormatVersion and guid.

In `@Tests/EditMode/MwaAuthCache/PlayerPrefsAuthCacheTests.cs.meta`:
- Around line 3-11: Replace the verbose MonoImporter block in the meta file with
the minimal convention: remove the entire MonoImporter section (externalObjects,
serializedVersion, defaultReferences, executionOrder, icon, userData,
assetBundleName, assetBundleVariant) and leave only fileFormatVersion and guid
entries (preserve the existing guid value and set fileFormatVersion to the repo
standard). Ensure no other keys remain so the .cs.meta matches the minimal C#
meta format used elsewhere.

---

Outside diff comments:
In `@Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs`:
- Around line 130-138: When clearing persisted credentials in
SolanaMobileWalletAdapter (the branches that call
PlayerPrefs.DeleteKey(PrefKeyPublicKey); PlayerPrefs.Save(); and await
_authCache.Clear()), also reset the in-memory token by setting _authToken = null
(or equivalent cleared state) so a stale in-memory token cannot survive and
trigger Reauthorize(...); update both the branch that awaits _authCache.Clear()
and the else branch that only deletes PlayerPrefs to clear _authToken alongside
existing cleanup calls.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ee2c5f80-edbe-4f8c-bbf8-47b9515bfed9

📥 Commits

Reviewing files that changed from the base of the PR and between 74a2179 and 3d3fb3a.

📒 Files selected for processing (10)
  • Runtime/codebase/SolanaMobileStack/MwaAuthCache.meta
  • Runtime/codebase/SolanaMobileStack/MwaAuthCache/IMwaAuthCache.cs
  • Runtime/codebase/SolanaMobileStack/MwaAuthCache/IMwaAuthCache.cs.meta
  • Runtime/codebase/SolanaMobileStack/MwaAuthCache/PlayerPrefsAuthCache.cs
  • Runtime/codebase/SolanaMobileStack/MwaAuthCache/PlayerPrefsAuthCache.cs.meta
  • Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs
  • Runtime/codebase/SolanaWalletAdapter.cs
  • Tests/EditMode/MwaAuthCache.meta
  • Tests/EditMode/MwaAuthCache/PlayerPrefsAuthCacheTests.cs
  • Tests/EditMode/MwaAuthCache/PlayerPrefsAuthCacheTests.cs.meta

Comment thread Runtime/codebase/SolanaMobileStack/MwaAuthCache/IMwaAuthCache.cs.meta Outdated
Comment thread Runtime/codebase/SolanaMobileStack/MwaAuthCache/PlayerPrefsAuthCache.cs.meta Outdated
Comment thread Tests/EditMode/MwaAuthCache/PlayerPrefsAuthCacheTests.cs.meta Outdated
Stale _authToken could survive prefs/cache wipe and push the next
login down Reauthorize() with a known-bad value.

Trim new C# .cs.meta to repo minimal format (fileFormatVersion + guid).

Refs magicblock-labs#271
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs`:
- Around line 264-271: Logout() currently calls
_authCache.Clear().GetAwaiter().GetResult() synchronously which can throw and
abort the logout flow (preventing DisconnectWallet() from reaching
OnWalletDisconnected); wrap the synchronous cache clear call in a try/catch that
catches Exception, logs the failure (including exception details) and continues
so Logout() completes; apply the same guarded pattern to the other synchronous
cache clear usage at the location referenced (lines ~305-306) to ensure
DisconnectWallet()/OnWalletDisconnected always runs even if
IMwaAuthCache.Clear() fails.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 9cb9c8f7-eed2-420d-8aad-ec77b4a95f2f

📥 Commits

Reviewing files that changed from the base of the PR and between 3d3fb3a and e874dfe.

📒 Files selected for processing (4)
  • Runtime/codebase/SolanaMobileStack/MwaAuthCache/IMwaAuthCache.cs.meta
  • Runtime/codebase/SolanaMobileStack/MwaAuthCache/PlayerPrefsAuthCache.cs.meta
  • Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs
  • Tests/EditMode/MwaAuthCache/PlayerPrefsAuthCacheTests.cs.meta

Comment thread Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs
Custom cache backends can throw on clear. Log and continue so
DisconnectWallet still invokes OnWalletDisconnected.

Refs magicblock-labs#271
@JoshhSandhu
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 28, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@JoshhSandhu
Copy link
Copy Markdown
Contributor Author

@Kuldotha Lmk if you want me to walk you through this one when you have a chance to review. CodeRabbit checks are all green!

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

Labels

None yet

Projects

None yet

1 participant