Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Runtime/codebase/DeepLinkWallets/PhantomDeepLink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,10 @@ private void ParseSuccessfullySignedAllTransactions(string url)
result.TryGetValue("errorCode", out var errorCode);
_signedAllTransactionsTaskCompletionSource?.TrySetResult(null);
Debug.LogError($"Deeplink error: Error: {errorMessage} + Data: {data}");
SolanaWalletAdapter.TriggerUserApprovedTransaction(false);
return;
}
else SolanaWalletAdapter.TriggerUserApprovedTransaction(true);

data = data.Replace("#", "");
var k = MontgomeryCurve25519.KeyExchange(_phantomEncryptionPubKey, PhantomConnectionAccountPrivateKey);
Expand All @@ -302,9 +304,11 @@ private void ParseSuccessfullySignedTransaction(string url)
result.TryGetValue("errorMessage", out var errorMessage);
if (!string.IsNullOrEmpty(errorMessage) || string.IsNullOrEmpty(data))
{
SolanaWalletAdapter.TriggerUserApprovedTransaction(false);
Debug.LogError($"Deeplink error: Error: {errorMessage} + Data: {data}");
return;
}
else SolanaWalletAdapter.TriggerUserApprovedTransaction(true);
Comment on lines 305 to +311

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.

data = data.Replace("#", "");
var k = MontgomeryCurve25519.KeyExchange(_phantomEncryptionPubKey, PhantomConnectionAccountPrivateKey);
var unencryptedMessage = XSalsa20Poly1305.TryDecrypt(Encoders.Base58.DecodeData(data), k, Encoders.Base58.DecodeData(nonce));
Expand All @@ -323,9 +327,11 @@ private void ParseSuccessfullySignedMessage(string url)
result.TryGetValue("errorMessage", out var errorMessage);
if (!string.IsNullOrEmpty(errorMessage) || string.IsNullOrEmpty(data))
{
SolanaWalletAdapter.TriggerUserApprovedTransaction(false);
Debug.LogError($"Deeplink error: Error: {errorMessage} + Data: {data}");
return;
}
else SolanaWalletAdapter.TriggerUserApprovedTransaction(true);
Comment on lines 328 to +334

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.

data = data.Replace("#", "");
var k = MontgomeryCurve25519.KeyExchange(_phantomEncryptionPubKey, PhantomConnectionAccountPrivateKey);
var unencryptedMessage = XSalsa20Poly1305.TryDecrypt(Encoders.Base58.DecodeData(data), k, Encoders.Base58.DecodeData(nonce));
Expand Down
20 changes: 11 additions & 9 deletions Runtime/codebase/SolanaMobileStack/SolanaMobileWalletAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace Solana.Unity.SDK
{

[Serializable]
public class SolanaMobileWalletAdapterOptions
{
Expand All @@ -20,13 +20,13 @@ public class SolanaMobileWalletAdapterOptions
public string name = "Solana.Unity-SDK";
public bool keepConnectionAlive = true;
}


[Obsolete("Use SolanaWalletAdapter class instead, which is the cross platform wrapper.")]
public class SolanaMobileWalletAdapter : WalletBase
{
private readonly SolanaMobileWalletAdapterOptions _walletOptions;

private Transaction _currentTransaction;

private TaskCompletionSource<Account> _loginTaskCompletionSource;
Expand All @@ -36,9 +36,9 @@ public class SolanaMobileWalletAdapter : WalletBase

public SolanaMobileWalletAdapter(
SolanaMobileWalletAdapterOptions solanaWalletOptions,
RpcCluster rpcCluster = RpcCluster.DevNet,
string customRpcUri = null,
string customStreamingRpcUri = null,
RpcCluster rpcCluster = RpcCluster.DevNet,
string customRpcUri = null,
string customStreamingRpcUri = null,
bool autoConnectOnStartup = false) : base(rpcCluster, customRpcUri, customStreamingRpcUri, autoConnectOnStartup
)
{
Expand Down Expand Up @@ -116,7 +116,7 @@ protected override async Task<Transaction[]> _SignAllTransactions(Transaction[]
authorization = await client.Reauthorize(
new Uri(_walletOptions.identityUri),
new Uri(_walletOptions.iconUri, UriKind.Relative),
_walletOptions.name, _authToken);
_walletOptions.name, _authToken);
}
},
async client =>
Expand All @@ -125,6 +125,8 @@ protected override async Task<Transaction[]> _SignAllTransactions(Transaction[]
}
}
);

SolanaWalletAdapter.TriggerUserApprovedTransaction(result.WasSuccessful);
Comment on lines +128 to +129

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.

if (!result.WasSuccessful)
{
Debug.LogError(result.Error.Message);
Expand Down Expand Up @@ -165,7 +167,7 @@ public override async Task<byte[]> SignMessage(byte[] message)
authorization = await client.Reauthorize(
new Uri(_walletOptions.identityUri),
new Uri(_walletOptions.iconUri, UriKind.Relative),
_walletOptions.name, _authToken);
_walletOptions.name, _authToken);
}
},
async client =>
Expand Down
9 changes: 8 additions & 1 deletion Runtime/codebase/SolanaWalletAdapter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,18 @@ public class SolanaWalletAdapterOptions
public SolanaWalletAdapterWebGLOptions solanaWalletAdapterWebGLOptions;
public PhantomWalletOptions phantomWalletOptions;
}

public class SolanaWalletAdapter: WalletBase
{
private readonly WalletBase _internalWallet;

public static event EventHandler<bool> UserApprovedTransaction;

internal static void TriggerUserApprovedTransaction(bool approvedTransaction)
{
UserApprovedTransaction?.Invoke(null, approvedTransaction);
}

public SolanaWalletAdapter(SolanaWalletAdapterOptions options, RpcCluster rpcCluster = RpcCluster.DevNet, string customRpcUri = null, string customStreamingRpcUri = null, bool autoConnectOnStartup = false) : base(rpcCluster, customRpcUri, customStreamingRpcUri, autoConnectOnStartup)
{
#if UNITY_ANDROID
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using AOT;
using AOT;
using Solana.Unity.Rpc.Models;
using Solana.Unity.Wallet;
using System;
using System.Threading.Tasks;
using UnityEngine;

// ReSharper disable once CheckNamespace
Expand Down Expand Up @@ -211,6 +210,7 @@ private static void OnWalletConnected(string walletPubKey)
[MonoPInvokeCallback(typeof(Action<string>))]
public static void OnTransactionSigned(string transaction)
{
SolanaWalletAdapter.TriggerUserApprovedTransaction(transaction != null);
if (transaction == null)
{
_signedTransactionTaskCompletionSource.TrySetException(new Exception("Transaction signing cancelled"));
Expand All @@ -228,6 +228,7 @@ public static void OnTransactionSigned(string transaction)
[MonoPInvokeCallback(typeof(Action<string>))]
public static void OnAllTransactionsSigned(string transactions)
{
SolanaWalletAdapter.TriggerUserApprovedTransaction(transactions != null);
if (transactions == null)
{
_signedAllTransactionsTaskCompletionSource.TrySetException(new Exception("Transactions signing cancelled"));
Expand All @@ -252,6 +253,7 @@ public static void OnAllTransactionsSigned(string transactions)
[MonoPInvokeCallback(typeof(Action<string>))]
public static void OnMessageSigned(string signature)
{
SolanaWalletAdapter.TriggerUserApprovedTransaction(signature != null);
if (signature == null)
{
_signedMessageTaskCompletionSource.TrySetException(new Exception("Message signing cancelled"));
Expand Down
5 changes: 3 additions & 2 deletions Runtime/codebase/WalletBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,11 @@ public virtual async Task<RequestResult<string>> SignAndSendTransaction
(
Transaction transaction,
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.

{
var signedTransaction = await SignTransaction(transaction);
return await ActiveRpcClient.SendTransactionAsync(
return await (customClient ?? ActiveRpcClient).SendTransactionAsync(
Convert.ToBase64String(signedTransaction.Serialize()),
skipPreflight: skipPreflight, preFlightCommitment: commitment);
}
Expand Down