Skip to content

improvement: Added customClient option in SignAndSendTransaction & Added detection of denial of transaction#268

Open
memxor wants to merge 2 commits intomagicblock-labs:mainfrom
memxor:main
Open

improvement: Added customClient option in SignAndSendTransaction & Added detection of denial of transaction#268
memxor wants to merge 2 commits intomagicblock-labs:mainfrom
memxor:main

Conversation

@memxor
Copy link
Copy Markdown

@memxor memxor commented Mar 27, 2026

| Status | Type | Core Change |
| Ready | Feature | No |

Commit 1 — customClient on SignAndSendTransaction (e.g. d07eab0)

  • WalletBase.SignAndSendTransaction now accepts an optional IRpcClient customClient. When provided, submission uses that client; otherwise behavior is unchanged (ActiveRpcClient).
  • Rationale: Lets callers (including Ephemeral Rollup / custom RPC scenarios) submit signed transactions to a specific endpoint instead of only the wallet’s active client.

Commit 2 — Detect transaction decline (e.g. 0727202)

This commit builds on the UserApprovedTransaction / TriggerUserApprovedTransaction(bool) by signaling declines and failures, not only successful approvals.

Behavior

  • When a transaction or message signing flow ends with an error, empty payload, or other deeplink failure (e.g. user dismisses wallet, error string returned, missing data), adapters call TriggerUserApprovedTransaction(false) so listeners can treat it as declined or failed in the same frame as success paths use true.
  • Aligns Phantom deep link, WebGL, and Solana Mobile flows so integrations can distinguish “user approved” vs “user declined / error” without guessing from logs alone.

Files involved in commit 2

  • Runtime/codebase/DeepLinkWallets/PhantomDeepLink.cs — decline/error paths for transaction and message signing callbacks.
  • Runtime/codebase/SolanaWalletAdapterWebGL/SolanaWalletAdapterWebGL.cs — matching behavior where WebGL reports cancel/error.
  • Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs — transaction path (and any message-signing parity if included).
  • Runtime/codebase/SolanaWalletAdapter.cs — shared event/API if touched.

Why it matters

  • UI/state: Games and apps can clear loading state, show “cancelled” messaging, or retry when the wallet returns an error or the user backs out.
  • Consistency: Reduces platform drift (one stack firing events on failure, another silent).

Deploy / integration notes

  • Subscribers to UserApprovedTransaction should expect false not only for “declined” but for any failed or incomplete signing callback where the adapter triggers it (treat as non-success).
  • No change to wire formats or RPC; this is adapter signaling (plus commit 1’s optional customClient for send).

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 27, 2026

Walkthrough

Added a UserApprovedTransaction event and triggers across wallet adapters; extended WalletBase.SignAndSendTransaction to accept an optional IRpcClient for submitting transactions. Signing logic otherwise unchanged.

Changes

Cohort / File(s) Summary
Wallet RPC override
Runtime/codebase/WalletBase.cs
Added IRpcClient customClient = null parameter to SignAndSendTransaction and use (customClient ?? ActiveRpcClient).SendTransactionAsync(...) for submission.
User approval event and triggers
Runtime/codebase/SolanaWalletAdapter.cs, Runtime/codebase/SolanaWalletAdapterWebGL/SolanaWalletAdapterWebGL.cs, Runtime/codebase/DeepLinkWallets/PhantomDeepLink.cs, Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs
Introduced public static event EventHandler<bool> UserApprovedTransaction and TriggerUserApprovedTransaction(bool). Added calls to trigger approval (true/false) in WebGL, DeepLink (Phantom) callbacks, and SolanaMobile signing flows immediately after signing results are obtained. Also added trigger calls on failure paths where previously only errors were logged.
Whitespace/formatting
Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs
Minor formatting/whitespace adjustments (no behavioral changes).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title covers two distinct features: adding a customClient option and detecting transaction denial. Both changes are present in the changeset, but the title uses ampersand conjunction without clearly prioritizing the main change.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering the problem, solution, rationale, and deployment notes effectively.

✏️ 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

🤖 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/DeepLinkWallets/PhantomDeepLink.cs`:
- Around line 305-311: On the error branch inside PhantomDeepLink (where it
currently calls SolanaWalletAdapter.TriggerUserApprovedTransaction(false) and
returns), also complete the pending TaskCompletionSource so awaiters don't hang:
call _signedTransactionTaskCompletionSource.TrySetException(new
Exception("Transaction signing cancelled")) and then
_signedTransactionTaskCompletionSource.TrySetResult(null) (matching the WebGL
behavior) before returning; keep the existing
TriggerUserApprovedTransaction(false) call intact.
- Around line 328-334: The error path in PhantomDeepLink.cs never completes the
TaskCompletionSource used by SignMessage/ParseSuccessfullySignedTransaction,
causing awaiters to hang; modify the error branch (where it currently calls
SolanaWalletAdapter.TriggerUserApprovedTransaction(false) and logs the error) to
complete _signedMessageTaskCompletionSource (use TrySetException(new
Exception(errorMessage)) or TrySetResult(null)/false as appropriate for the
expected result type) so callers are released, and use TrySet... methods to
avoid race InvalidOperationExceptions; update the same pattern for
ParseSuccessfullySignedTransaction if present.

In `@Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs`:
- Around line 128-129: The SignMessage method is missing the cross-platform
event trigger; after the call to StartAndExecute completes in SignMessage (same
spot where StartAndExecute is used in the transaction flow), call
SolanaWalletAdapter.TriggerUserApprovedTransaction(result.WasSuccessful) to
mirror the behavior in the transaction path. Add this trigger immediately after
StartAndExecute returns (before the subsequent if (!result.WasSuccessful)
branch) so message signing fires the same UserApprovedTransaction event as the
transaction flow.
🪄 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: 6b2c1303-dc89-4b60-920a-4da8bce9415f

📥 Commits

Reviewing files that changed from the base of the PR and between d07eab0 and 0727202.

📒 Files selected for processing (4)
  • Runtime/codebase/DeepLinkWallets/PhantomDeepLink.cs
  • Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs
  • Runtime/codebase/SolanaWalletAdapter.cs
  • Runtime/codebase/SolanaWalletAdapterWebGL/SolanaWalletAdapterWebGL.cs

Comment on lines 305 to +311
if (!string.IsNullOrEmpty(errorMessage) || string.IsNullOrEmpty(data))
{
SolanaWalletAdapter.TriggerUserApprovedTransaction(false);
Debug.LogError($"Deeplink error: Error: {errorMessage} + Data: {data}");
return;
}
else SolanaWalletAdapter.TriggerUserApprovedTransaction(true);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: TaskCompletionSource never completed on error path - callers will hang indefinitely.

The new trigger code is placed in an error path that returns early without completing _signedTransactionTaskCompletionSource. Any code awaiting _SignTransaction will hang forever when the user declines.

Compare with the WebGL implementation which properly handles this:

_signedTransactionTaskCompletionSource.TrySetException(new Exception("Transaction signing cancelled"));
_signedTransactionTaskCompletionSource.TrySetResult(null);
Proposed fix
         if (!string.IsNullOrEmpty(errorMessage) || string.IsNullOrEmpty(data))
         {
             SolanaWalletAdapter.TriggerUserApprovedTransaction(false);
+            _signedTransactionTaskCompletionSource?.TrySetException(new Exception($"Deeplink error: {errorMessage}"));
+            _signedTransactionTaskCompletionSource?.TrySetResult(null);
             Debug.LogError($"Deeplink error: Error: {errorMessage} + Data: {data}");
             return;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Runtime/codebase/DeepLinkWallets/PhantomDeepLink.cs` around lines 305 - 311,
On the error branch inside PhantomDeepLink (where it currently calls
SolanaWalletAdapter.TriggerUserApprovedTransaction(false) and returns), also
complete the pending TaskCompletionSource so awaiters don't hang: call
_signedTransactionTaskCompletionSource.TrySetException(new
Exception("Transaction signing cancelled")) and then
_signedTransactionTaskCompletionSource.TrySetResult(null) (matching the WebGL
behavior) before returning; keep the existing
TriggerUserApprovedTransaction(false) call intact.

Comment on lines 328 to +334
if (!string.IsNullOrEmpty(errorMessage) || string.IsNullOrEmpty(data))
{
SolanaWalletAdapter.TriggerUserApprovedTransaction(false);
Debug.LogError($"Deeplink error: Error: {errorMessage} + Data: {data}");
return;
}
else SolanaWalletAdapter.TriggerUserApprovedTransaction(true);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: TaskCompletionSource never completed on error path - callers will hang indefinitely.

Same issue as ParseSuccessfullySignedTransaction - the _signedMessageTaskCompletionSource is never completed when there's an error, causing callers awaiting SignMessage to hang forever.

Proposed fix
         if (!string.IsNullOrEmpty(errorMessage) || string.IsNullOrEmpty(data))
         {
             SolanaWalletAdapter.TriggerUserApprovedTransaction(false);
+            _signedMessageTaskCompletionSource?.TrySetException(new Exception($"Deeplink error: {errorMessage}"));
+            _signedMessageTaskCompletionSource?.TrySetResult(null);
             Debug.LogError($"Deeplink error: Error: {errorMessage} + Data: {data}");
             return;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Runtime/codebase/DeepLinkWallets/PhantomDeepLink.cs` around lines 328 - 334,
The error path in PhantomDeepLink.cs never completes the TaskCompletionSource
used by SignMessage/ParseSuccessfullySignedTransaction, causing awaiters to
hang; modify the error branch (where it currently calls
SolanaWalletAdapter.TriggerUserApprovedTransaction(false) and logs the error) to
complete _signedMessageTaskCompletionSource (use TrySetException(new
Exception(errorMessage)) or TrySetResult(null)/false as appropriate for the
expected result type) so callers are released, and use TrySet... methods to
avoid race InvalidOperationExceptions; update the same pattern for
ParseSuccessfullySignedTransaction if present.

Comment on lines +128 to +129

SolanaWalletAdapter.TriggerUserApprovedTransaction(result.WasSuccessful);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

LGTM - but missing trigger in SignMessage method.

The trigger placement here is correct, firing after StartAndExecute completes with the appropriate success status.

However, the SignMessage method (lines 147-189) in this file does not trigger UserApprovedTransaction, whereas the WebGL and PhantomDeepLink implementations do trigger the event for message signing. This creates inconsistent cross-platform behavior.

Proposed fix to add trigger in SignMessage
             }
         );
+        SolanaWalletAdapter.TriggerUserApprovedTransaction(result.WasSuccessful);
         if (!result.WasSuccessful)
         {
             Debug.LogError(result.Error.Message);

Add this after line 181, before the if (!result.WasSuccessful) check.

🤖 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
128 - 129, The SignMessage method is missing the cross-platform event trigger;
after the call to StartAndExecute completes in SignMessage (same spot where
StartAndExecute is used in the transaction flow), call
SolanaWalletAdapter.TriggerUserApprovedTransaction(result.WasSuccessful) to
mirror the behavior in the transaction path. Add this trigger immediately after
StartAndExecute returns (before the subsequent if (!result.WasSuccessful)
branch) so message signing fires the same UserApprovedTransaction event as the
transaction flow.

@lopeselio
Copy link
Copy Markdown
Contributor

@Kuldotha can you please check this?

@memxor memxor changed the title improvement: Added customClient option in SignAndSendTransaction improvement: Added customClient option in SignAndSendTransaction & Added detection of denial of transaction Mar 31, 2026
@JoshhSandhu
Copy link
Copy Markdown
Contributor

Hey @memxor @lopeselio,

i noticed we are both doing open heart surgery on SolanaMobileWalletAdapter.cs right now, my pr #269 is an active fix for issue #267, which heavily rewrites the MWA auth/signing flow to handle session caching and silent reconnects

cause we are modifying the exact same try/catch blocks in _SignAllTransactions and SignTransaction, these two PRs are going to cause a pretty nasty merge conflict for the maintainers

since #269 contains the heavier structural changes, would you be open to me just pulling your TriggerUserApprovedTransaction event logic directly into my pr? it perfectly complements the ux fixes im adding, and it saves @Kuldotha from having to untangle a rebase conflict.

happy to add you as a coauthor on the commit for the event triggers so you get the contribution credit lmk what you think!

@memxor
Copy link
Copy Markdown
Author

memxor commented Apr 8, 2026

Hey @JoshhSandhu sorry we have gone through your code, and we have seen that you've major changes in there. We have done very minor changes in this PR, so we have decided not to sync.

And as it goes for the session caching, we have already fixed that in a previous PR of ours. Thanks for understanding.

bool skipPreflight = false,
Commitment commitment = Commitment.Confirmed)
Commitment commitment = Commitment.Confirmed,
IRpcClient customClient = null)
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.

This is not the place to pass a custom rpc. Like I mentioned in TG, this is unnecessary as well if you create an additional wallet for the ER connection.

@Kuldotha
Copy link
Copy Markdown
Contributor

Kuldotha commented Apr 10, 2026

@memxor We disagree with commit 1 and don't think this should be in the SDK. Just make a separate wallet for the ER.

About commit 2, isn't the issue that we just return the error and never set a result to the TCS? Your change looks like it works around this issue, and wouldn't be necessary if the TCS would be handled properly. Or am I missing something?

If so, this also doesn't fix the issue for people who aren't specifically using the event but are waiting for the TCS, they will still be left hanging.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants