Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: chain switching #52

Merged
merged 3 commits into from
Feb 12, 2025
Merged
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
1 change: 1 addition & 0 deletions sample/Reown.AppKit.Unity/Packages/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"com.skibitsky.scene-reference": "1.1.1",
"com.unity.ide.rider": "3.0.34",
"com.unity.ide.visualstudio": "2.0.22",
"com.unity.mobile.android-logcat": "1.4.4",
"com.unity.nuget.newtonsoft-json": "3.2.1",
"com.unity.test-framework": "1.4.5",
"com.unity.ugui": "2.0.0",
Expand Down
9 changes: 8 additions & 1 deletion sample/Reown.AppKit.Unity/Packages/packages-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,13 @@
},
"url": "https://packages.unity.com"
},
"com.unity.mobile.android-logcat": {
"version": "1.4.4",
"depth": 0,
"source": "registry",
"dependencies": {},
"url": "https://packages.unity.com"
},
"com.unity.nuget.newtonsoft-json": {
"version": "3.2.1",
"depth": 0,
Expand Down Expand Up @@ -367,4 +374,4 @@
}
}
}
}
}
2 changes: 1 addition & 1 deletion src/Reown.AppKit.Unity/Plugins/AppKit.jslib
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ mergeInto(LibraryManager.library, {
const enableAnalytics = parameters.enableAnalytics;

// Load the scripts and initialize the configuration
import("https://cdn.jsdelivr.net/npm/@reown/[email protected].5/dist/appkit.js").then(AppKit => {
import("https://cdn.jsdelivr.net/npm/@reown/[email protected].8/dist/appkit.js").then(AppKit => {
const WagmiCore = AppKit['WagmiCore'];
const WagmiAdapter = AppKit['WagmiAdapter'];
const Chains = AppKit['networks'];
Expand Down
3 changes: 0 additions & 3 deletions src/Reown.AppKit.Unity/Runtime/Connectors/Connector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,6 @@ public async Task DisconnectAsync()

public async Task ChangeActiveChainAsync(Chain chain)
{
if (!IsAccountConnected)
throw new Exception("No account connected"); // TODO: use custom ex type

await ChangeActiveChainAsyncCore(chain);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Reown.Core.Common.Logging;
using Reown.Core.Common.Model.Errors;
using Reown.Sign.Models;
using Reown.Sign.Models.Engine;
using Reown.Sign.Models.Engine.Methods;
using Reown.Sign.Nethereum;
using Reown.Sign.Nethereum.Model;
using Reown.Sign.Unity;
using UnityEngine;
Expand Down Expand Up @@ -176,9 +177,25 @@ protected override async Task DisconnectAsyncCore()
}
}

private async Task WaitForSessionUpdateAsync(TimeSpan timeout)
{
var tcs = new TaskCompletionSource<bool>();
var sessionUpdateHandler = new EventHandler<Session>((s, session) => { tcs.TrySetResult(true); });

_signClient.SessionUpdatedUnity += sessionUpdateHandler;
try
{
await Task.WhenAny(tcs.Task, Task.Delay(timeout));
}
finally
{
_signClient.SessionUpdatedUnity -= sessionUpdateHandler;
}
}

protected override async Task ChangeActiveChainAsyncCore(Chain chain)
{
if (ActiveSessionSupportsMethod("wallet_switchEthereumChain") && !ActiveSessionIncludesChain(chain.ChainId))
if (ActiveSessionSupportsMethod("wallet_addEthereumChain") && !ActiveSessionIncludesChain(chain.ChainId))
{
var ethereumChain = new EthereumChain(
chain.ChainReference,
Expand All @@ -193,7 +210,41 @@ protected override async Task ChangeActiveChainAsyncCore(Chain chain)
chain.BlockExplorer.url
}
);
await _signClient.SwitchEthereumChainAsync(ethereumChain);

var caip2ChainId = $"eip155:{ethereumChain.chainIdDecimal}";
if (!_signClient.AddressProvider.DefaultSession.Namespaces.TryGetValue("eip155", out var @namespace)
|| [email protected](caip2ChainId))
{
try
{
await AppKit.Evm.RpcRequestAsync<string>("wallet_addEthereumChain", ethereumChain);

await _signClient.AddressProvider.SetDefaultChainIdAsync(chain.ChainId);

await WaitForSessionUpdateAsync(TimeSpan.FromSeconds(5));

OnChainChanged(new ChainChangedEventArgs(chain.ChainId));
OnAccountChanged(new AccountChangedEventArgs(GetCurrentAccount()));
}
catch (ReownNetworkException e)
{
try
{
var metaMaskError = JsonConvert.DeserializeObject<MetaMaskError>(e.Message);
ReownLogger.LogError($"[MetaMask Error] {metaMaskError.Message}");
}
catch (Exception ex)
{
Debug.LogException(ex);
}

throw;
}
catch (Exception e)
{
Debug.LogException(e);
}
}
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,13 @@ protected override async Task DisconnectAsyncCore()

protected override Task ChangeActiveChainAsyncCore(Chain chain)
{
return ActiveConnector.ChangeActiveChainAsync(chain);
// On WebGL ActiveConnector is always set to WebGlConnector.
// On Native platforms ActiveConnector is set to the last used connector after session resumption
// or to the connector that was used to connect the account. So, on Native ActiveConnector is null when
// no account is connected. In this case, we don't change the chain in the connector.
return ActiveConnector == null
? Task.CompletedTask
: ActiveConnector.ChangeActiveChainAsync(chain);
}

protected override Task<Account> GetAccountAsyncCore()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;

namespace Reown.AppKit.Unity
{
Expand All @@ -19,20 +20,13 @@ protected override Task InitializeAsyncCore(IEnumerable<Chain> supportedChains)

protected override async Task ChangeActiveChainAsyncCore(Chain chain)
{
if (AppKit.ConnectorController.IsAccountConnected)
{
// Request connector to change active chain.
// If connector approves the change, it will trigger the ChainChanged event.
await AppKit.ConnectorController.ChangeActiveChainAsync(chain);
// Request connector to change active chain.
// If connector approves the change, it will trigger the ChainChanged event.
await AppKit.ConnectorController.ChangeActiveChainAsync(chain);

var previousChain = ActiveChain;
ActiveChain = chain;
OnChainChanged(new ChainChangedEventArgs(previousChain, chain));
}
else
{
ActiveChain = chain;
}
var previousChain = ActiveChain;
ActiveChain = chain;
OnChainChanged(new ChainChangedEventArgs(previousChain, chain));

AppKit.EventsController.SendEvent(new Event
{
Expand Down
12 changes: 12 additions & 0 deletions src/Reown.AppKit.Unity/Runtime/Evm/EvmService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,17 @@ public Task<BigInteger> GetGasPriceAsync()
{
return GetGasPriceAsyncCore();
}


// -- RPC Request ----------------------------------------------

public Task<T> RpcRequestAsync<T>(string method, params object[] parameters)
{
if (string.IsNullOrWhiteSpace(method))
throw new ArgumentNullException(nameof(method));

return RpcRequestAsyncCore<T>(method, parameters);
}


protected abstract Task InitializeAsyncCore(SignClientUnity signClient);
Expand All @@ -176,5 +187,6 @@ public Task<BigInteger> GetGasPriceAsync()
protected abstract Task<BigInteger> EstimateGasAsyncCore(string addressTo, BigInteger value, string data = null);
protected abstract Task<BigInteger> EstimateGasAsyncCore(string contractAddress, string contractAbi, string methodName, BigInteger value = default, params object[] arguments);
protected abstract Task<BigInteger> GetGasPriceAsyncCore();
protected abstract Task<T> RpcRequestAsyncCore<T>(string method, params object[] parameters);
}
}
8 changes: 8 additions & 0 deletions src/Reown.AppKit.Unity/Runtime/Evm/NethereumEvmService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,5 +304,13 @@ protected override async Task<BigInteger> GetGasPriceAsyncCore()
var hexBigInt = await Web3.Eth.GasPrice.SendRequestAsync();
return hexBigInt.Value;
}


// -- RPC Request ----------------------------------------------

protected override Task<T> RpcRequestAsyncCore<T>(string method, params object[] parameters)
{
return Web3.Client.SendRequestAsync<T>(method, null, parameters);
}
}
}
5 changes: 5 additions & 0 deletions src/Reown.AppKit.Unity/Runtime/Evm/WagmiEvmService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ protected override async Task<BigInteger> GetGasPriceAsyncCore()
var result = await WagmiInterop.GetGasPriceAsync();
return BigInteger.Parse(result);
}

protected override Task<T> RpcRequestAsyncCore<T>(string method, params object[] parameters)
{
throw new System.NotImplementedException();
}
}
#endif
}
1 change: 1 addition & 0 deletions src/Reown.Sign.Nethereum/Runtime/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public static class Extensions
/// <param name="signClient">Reown Sign client</param>
/// <param name="ethereumChain">Ethereum chain to switch to</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="ethereumChain"/> is null</exception>
[Obsolete("This extension will be removed in the future versions. Reown AppKit will handle chain switching in MetaMask internally.")]
public static async Task SwitchEthereumChainAsync(this ISignClient signClient, EthereumChain ethereumChain)
{
if (ethereumChain == null)
Expand Down
52 changes: 29 additions & 23 deletions src/Reown.Sign.Nethereum/Runtime/ReownInterceptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Nethereum.RPC;
using Nethereum.RPC.Eth.DTOs;
using Nethereum.RPC.HostWallet;
using Reown.Sign.Nethereum.Model;

namespace Reown.Sign.Nethereum
{
Expand Down Expand Up @@ -69,7 +70,7 @@ public override async Task<object> InterceptSendRequestAsync<T>(

if (request.Method == ApiMethods.wallet_addEthereumChain.ToString())
{
return await _reownSignService.WalletAddEthereumChainAsync((AddEthereumChainParameter)request.RawParameters[0]);
return await _reownSignService.WalletAddEthereumChainAsync((EthereumChain)request.RawParameters[0]);
}

throw new NotImplementedException();
Expand All @@ -96,39 +97,44 @@ public override async Task<object> InterceptSendRequestAsync<T>(
if (!_reownSignService.IsWalletConnected)
throw new InvalidOperationException("[ReownInterceptor] Wallet is not connected");

if (_reownSignService.IsMethodSupported(method))
if (!_reownSignService.IsMethodSupported(method))
return await base
.InterceptSendRequestAsync(interceptedSendRequestAsync, method, route, paramList)
.ConfigureAwait(false);

if (method == ApiMethods.eth_sendTransaction.ToString())
{
if (method == ApiMethods.eth_sendTransaction.ToString())
{
return await _reownSignService.SendTransactionAsync((TransactionInput)paramList[0]);
}
return await _reownSignService.SendTransactionAsync((TransactionInput)paramList[0]);
}

if (method == ApiMethods.personal_sign.ToString())
{
return await _reownSignService.PersonalSignAsync((string)paramList[0]);
}
if (method == ApiMethods.personal_sign.ToString())
{
return await _reownSignService.PersonalSignAsync((string)paramList[0]);
}

if (method == ApiMethods.eth_signTypedData_v4.ToString())
{
return await _reownSignService.EthSignTypedDataV4Async((string)paramList[0]);
}
if (method == ApiMethods.eth_signTypedData_v4.ToString())
{
return await _reownSignService.EthSignTypedDataV4Async((string)paramList[0]);
}

if (method == ApiMethods.wallet_switchEthereumChain.ToString())
{
return await _reownSignService.WalletSwitchEthereumChainAsync((SwitchEthereumChainParameter)paramList[0]);
}

if (method == ApiMethods.wallet_switchEthereumChain.ToString())
if (method == ApiMethods.wallet_addEthereumChain.ToString())
{
try
{
return await _reownSignService.WalletSwitchEthereumChainAsync((SwitchEthereumChainParameter)paramList[0]);
return await _reownSignService.WalletAddEthereumChainAsync((EthereumChain)paramList[0]);
}

if (method == ApiMethods.wallet_addEthereumChain.ToString())
catch (InvalidCastException)
{
return await _reownSignService.WalletAddEthereumChainAsync((AddEthereumChainParameter)paramList[0]);
}

throw new NotImplementedException();
}

return await base
.InterceptSendRequestAsync(interceptedSendRequestAsync, method, route, paramList)
.ConfigureAwait(false);
throw new NotImplementedException($"Method {method} is not implemented");
}
}
}
12 changes: 10 additions & 2 deletions src/Reown.Sign.Nethereum/Runtime/ReownSignService.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Threading.Tasks;
using Nethereum.RPC.Eth.DTOs;
using Nethereum.RPC.HostWallet;
using Reown.Sign.Nethereum.Model;

namespace Reown.Sign.Nethereum
{
Expand Down Expand Up @@ -42,16 +43,23 @@ public Task<object> WalletSwitchEthereumChainAsync(SwitchEthereumChainParameter
return WalletSwitchEthereumChainAsyncCore(chainId);
}

public Task<object> WalletAddEthereumChainAsync(AddEthereumChainParameter chain)
public Task<object> WalletAddEthereumChainAsync(EthereumChain chain)
{
return WalletAddEthereumChainAsyncCore(chain);
}

public Task<object> WalletAddEthereumChainAsync(AddEthereumChainParameter chain)
{
var nativeCurrency = new Currency(chain.NativeCurrency.Name, chain.NativeCurrency.Symbol, (int)chain.NativeCurrency.Decimals);
var ethereumChain = new EthereumChain(chain.ChainId.HexValue, chain.ChainName, nativeCurrency, chain.RpcUrls.ToArray(), chain.BlockExplorerUrls.ToArray());
return WalletAddEthereumChainAsyncCore(ethereumChain);
}

protected abstract bool IsMethodSupportedCore(string method);
protected abstract Task<object> SendTransactionAsyncCore(TransactionInput transaction);
protected abstract Task<object> PersonalSignAsyncCore(string message);
protected abstract Task<object> EthSignTypedDataV4AsyncCore(string data);
protected abstract Task<object> WalletSwitchEthereumChainAsyncCore(SwitchEthereumChainParameter chainId);
protected abstract Task<object> WalletAddEthereumChainAsyncCore(AddEthereumChainParameter chain);
protected abstract Task<object> WalletAddEthereumChainAsyncCore(EthereumChain chain);
}
}
6 changes: 2 additions & 4 deletions src/Reown.Sign.Nethereum/Runtime/ReownSignServiceCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,9 @@ protected override async Task<object> WalletSwitchEthereumChainAsyncCore(SwitchE
return await _signClient.Request<WalletSwitchEthereumChain, string>(switchChainRequest);
}

protected override async Task<object> WalletAddEthereumChainAsyncCore(AddEthereumChainParameter chain)
protected override async Task<object> WalletAddEthereumChainAsyncCore(EthereumChain chain)
{
var nativeCurrency = new Currency(chain.NativeCurrency.Name, chain.NativeCurrency.Symbol, (int)chain.NativeCurrency.Decimals);
var ethereumChain = new EthereumChain(chain.ChainId.HexValue, chain.ChainName, nativeCurrency, chain.RpcUrls.ToArray(), chain.BlockExplorerUrls.ToArray());
var addEthereumChainRequest = new WalletAddEthereumChain(ethereumChain);
var addEthereumChainRequest = new WalletAddEthereumChain(chain);
return await _signClient.Request<WalletAddEthereumChain, string>(addEthereumChainRequest);
}
}
Expand Down
4 changes: 1 addition & 3 deletions src/Reown.Sign/Runtime/Internals/EngineTasks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,7 @@ Task IEnginePrivate.Cleanup()

if (sessionTopics.Count == 0 && proposalIds.Count == 0)
return Task.CompletedTask;

ReownLogger.Log($"Clearing {sessionTopics.Count} expired sessions and {proposalIds.Count} expired proposals");


return Task.WhenAll(
sessionTopics.Select(t => PrivateThis.DeleteSession(t)).Concat(
proposalIds.Select(id => PrivateThis.DeleteProposal(id))
Expand Down
Loading