Skip to content

feat(mwa-v2): complete MWA v2 rewrite with docs#7

Open
Zurcusa wants to merge 29 commits into
mainfrom
pr/6-mwa-docs
Open

feat(mwa-v2): complete MWA v2 rewrite with docs#7
Zurcusa wants to merge 29 commits into
mainfrom
pr/6-mwa-docs

Conversation

@Zurcusa
Copy link
Copy Markdown
Owner

@Zurcusa Zurcusa commented May 7, 2026

This is a combined PR consolidating the full MWA v2 stack for upstream review. Individual PRs with granular diffs are linked below.

Status Type ⚠️ Core Change Issues
Ready Feature Yes Closes #273, closes #189, closes #272, partial for #250

Overview

Complete rewrite of the Mobile Wallet Adapter (MWA) implementation from v1 to v2. 120 files changed, ~4200 additions across types, wire protocol, client, adapter, cache, tests, and documentation.

Individual PRs (for granular review)

PR Scope Description
#3 Types 21 new C# files: v2 domain types, interfaces, result hierarchies, exceptions
#4 Wire + Client + Cache Wire protocol v2, client rewrite with CAIP-2 chains, PlayerPrefsAuthorizationCache, response parser with size caps, 9 test suites
#5 Adapter Full SolanaMobileWalletAdapter rewrite: session lifecycle, SIWS, concurrency gate, silent reconnect, 5 test suites
#6 Documentation Quick-start, method reference, cache guide, migration guide, Backpack investigation

What Changed

Core Types (PR #3)

  • AccountInfo, AuthorizationRecord, SignInPayload, SignInResult, SendOptions
  • IAuthorizationCache interface (#272)
  • Discriminated-union result types: DeauthorizeResult, ReconnectResult, SignAndSendTxResult
  • Typed exceptions: JsonRpcException, OperationInFlightException, TransportException

Wire Protocol + Client (PR #4)

  • v2 wire format with CAIP-2 chain identifiers and backward-compat cluster field
  • AuthorizeAsync with SIWS, DeauthorizeAsync, SignAndSendTransactionsAsync, CloneAuthorizationAsync
  • AuthorizationResponseParser with permissive parsing and size caps for untrusted wallet data
  • PlayerPrefsAuthorizationCache with scoped keys, schema versioning, corrupt-JSON recovery

Adapter (PR #5)

  • New API: Disconnect(), Reconnect() (4-state result), Deauthorize() (tri-state), GetCapabilities(), LoginWithSignIn(SignInPayload), SignAndSendTransactions(Transaction[], SendOptions)
  • Silent-first login: checks cache → silent authorize → fresh authorize fallback
  • Concurrency gate: SemaphoreSlim(1,1) prevents overlapping wallet calls
  • Legacy migration: auto-migrates "pk"/"authToken" and PR#269 keys to new cache

Documentation (PR #6)

  • mwa-quick-start.md — install, configure, first connection
  • mwa-method-reference.md — all 13 public methods with signatures and result variants
  • mwa-cache-guide.md — IAuthorizationCache interface, default/custom implementations
  • mwa-migration-v1-to-v2.md — v1 → v2 migration guide
  • sign-and-send-investigation.md — Backpack failure analysis

Known Issue: Backpack sign_and_send_transactions

⚠️ sign_and_send_transactions fails with Backpack wallet. The transaction is successfully created, signed, and submitted on-chain — the issue is only in receiving the response back in the dApp. Backpack's WebSocket server crashes during the RPC submission phase, corrupting the local association before the response can be delivered. The dApp's pending request hangs indefinitely. This is a Backpack-specific issue — Phantom and Solflare handle the same flow correctly, surviving 8–12 seconds of Android activity STOP without any WebSocket errors.

sign_transactions works with all wallets including Backpack (no RPC submission involved). Full investigation: docs/sign-and-send-investigation.md

Pre-existing LocalAssociationScenario Issues

These issues exist in the upstream LocalAssociationScenario.cs and are outside the scope of this rewrite:

  1. Missing return in ExecuteNextAction (line 134-135): Falls through to _actions.Dequeue() on an empty queue after CloseAssociation().
  2. Unbounded OnClose reconnect loop (line 46-50): No delay, no retry limit, no close-awareness. Thread pool storm when the wallet server is gone.
  3. Wrong timeout unit (line 33): TimeSpan.FromSeconds(9000) = 2.5-hour timeout instead of 9 seconds.
  4. Null-unsafe scenarioResult access: scenarioResult.WasSuccessful throws if scenario returns null.

Breaking Changes

  • IAdapterOperations.Authorize() and Reauthorize() removed — use AuthorizeAsync()
  • AuthorizationResult.PublicKey and AccountLabel removed — use AuthorizationHelpers.PrimaryAccountPublicKeyBytes() and PrimaryAccount().Label

Demo App

https://github.com/Zurcusa/unity-solana-mwa-example

Testing Matrix

Tested on Pixel 7 emulator and Solana Seeker (real device).

Wallets Tested

Wallet Networks
Seed Vault (Solana Phone) DevNet, MainNet
Phantom DevNet, MainNet
Solflare DevNet, MainNet
Backpack MainNet
Jupiter MainNet

Functionality Coverage

Feature Emulator (Pixel 7) Solana Seeker
Login Passed Passed
Disconnect Passed Passed
Reconnect Passed Passed
Deauthorize Passed Passed
Sign Transaction Passed Passed
Sign All Transactions Passed Passed
Sign Message Passed Passed
Sign and Send ⚠️ Backpack fails (see Known Issue) ⚠️ Backpack fails (see Known Issue)
Get Capabilities Passed Passed
Sign-In With Solana (SIWS) Passed Passed

- CapabilitiesResult JSON + Response envelope
- Deauthorize/GetCapabilities request shape via mock sender
- IAdapterOperations reflection contract + [Preserve]
- PlayerPrefs migration via MigrateLegacyPrefKeys reflection
Refs magicblock-labs#269
Stable GUIDs for Unity import and cross-machine consistency.
- Assert Deauthorize/GetCapabilities payloads via JObject so JsonProperty
  drift can't hide broken wire keys
- Snapshot/restore MWA PlayerPrefs keys so EditMode runs don't wipe dev state
@Zurcusa Zurcusa force-pushed the pr/6-mwa-docs branch 2 times, most recently from 7c20faf to c92dac5 Compare May 7, 2026 11:24
…a-keepConnectionAlive-tests

Feat(test)/mwa keep connection alive tests
@Zurcusa Zurcusa force-pushed the pr/6-mwa-docs branch 3 times, most recently from 67e50a7 to 8863367 Compare May 11, 2026 12:54
Zurcusa added 20 commits May 11, 2026 16:44
- Guard null/empty address in PrimaryAccountPublicKeyBytes
- Private constructors on DeauthorizeResult and ReconnectResult
- Null-result check in JsonRpc20Client.ReceiverRaw
- Cache auth token refresh in sign_and_send authorize action
- LogoutSuppressed + OnWalletDisconnected on auth revocation paths
- Fix base64 → base58 typo in method reference
- Guard null/empty address in PrimaryAccountPublicKeyBytes
- Private constructors on DeauthorizeResult and ReconnectResult
- Null-result check in JsonRpc20Client.ReceiverRaw
- Cache auth token refresh in sign_and_send authorize action
- LogoutSuppressed + OnWalletDisconnected on auth revocation paths
- Fix base64 → base58 typo in method reference
- Return base58-encoded signatures instead of base64 (Solana RPC compat)
- Remove duplicate sendOptions wire field (MWA v2 spec uses "options" only)
- Gate Debug.Log wire messages behind UNITY_EDITOR || MWA_VERBOSE_WIRE
- Remove unused LegacyPkKey constant from PlayerPrefsAuthorizationCache
- Make _warnedThisSession static for once-per-session semantics
- Add SIWS fallback length guard for signed payloads < 64 bytes
- Enable BouncyCastle for Editor platform (fixes EditMode test loading)
- Add namespace to JsonRpcErrorCodes
- Fix SchemaVersion default to match ExpectedSchemaVersion (2)
- Fix DeauthorizeTests missing _gate reflection init (4 test failures)
- Fix ConcurrencyTests assertion for GetUninitializedObject compatibility
- Fix ReconnectTests platform guard for non-Android EditMode runs
- Remove invalid LegacyPk_DeletedOnConstruction test
Zurcusa added 3 commits May 11, 2026 16:44
- DeauthorizeRequestWireTest: use RpcMethodNames.Deauthorize in assertion
  instead of hardcoded string
- ReconnectTests: promote _gate lookup to static field with OneTimeSetUp
  guard so reflection drift fails fast with a clear message
LoginWithSignInInternal SIWS fallback was the last AuthorizeAsync call
site that discarded the return value, losing token rotations.
- Null-safe constructor: default solanaWalletOptions to prevent NPE
- Validate payload elements are non-null before sending to wallet
- Treat missing auth_token in clone_authorization as protocol error
- Use LoadValidCachedRecordAsync in ReloadAuthTokenFromCacheIfNeeded
  to enforce schema/chain validation on cached records
- Guard null authorization in ReconnectInternal success path
- Remove unused using in ConcurrencyTests
Semaphore gate:
- Wrap _Login, _SignAllTransactions, SignMessage, GetCapabilities in
  TryAcquireGate/ReleaseGate to prevent concurrent state corruption

Bug fixes:
- Preserve WaitForCommitmentToSendNextTransaction when backfilling MinContextSlot
- Set WalletBase.Account on LoginWithSignIn success
- Default null mwaOptions in SolanaWalletAdapter so cache injection works

Upstream PR magicblock-labs#283 test compat:
- Update IAdapterOperationsContractTests for v2 AuthorizeAsync API
- Rewrite SolanaMobileWalletAdapterPrefsTests for v2 cache-based migration
- Fix MobileWalletAdapterClientLifecycleTests Authorize → AuthorizeAsync
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants