diff --git a/sample/Reown.AppKit.Unity/Packages/manifest.json b/sample/Reown.AppKit.Unity/Packages/manifest.json index 9eb200f..eea271a 100644 --- a/sample/Reown.AppKit.Unity/Packages/manifest.json +++ b/sample/Reown.AppKit.Unity/Packages/manifest.json @@ -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", diff --git a/sample/Reown.AppKit.Unity/Packages/packages-lock.json b/sample/Reown.AppKit.Unity/Packages/packages-lock.json index ccab06a..b69e311 100644 --- a/sample/Reown.AppKit.Unity/Packages/packages-lock.json +++ b/sample/Reown.AppKit.Unity/Packages/packages-lock.json @@ -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, @@ -367,4 +374,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/Reown.AppKit.Unity/Plugins/AppKit.jslib b/src/Reown.AppKit.Unity/Plugins/AppKit.jslib index e355939..caea98b 100644 --- a/src/Reown.AppKit.Unity/Plugins/AppKit.jslib +++ b/src/Reown.AppKit.Unity/Plugins/AppKit.jslib @@ -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/appkit-cdn@1.6.5/dist/appkit.js").then(AppKit => { + import("https://cdn.jsdelivr.net/npm/@reown/appkit-cdn@1.6.8/dist/appkit.js").then(AppKit => { const WagmiCore = AppKit['WagmiCore']; const WagmiAdapter = AppKit['WagmiAdapter']; const Chains = AppKit['networks']; diff --git a/src/Reown.AppKit.Unity/Runtime/Connectors/Connector.cs b/src/Reown.AppKit.Unity/Runtime/Connectors/Connector.cs index fd86f0e..8cbc9b9 100644 --- a/src/Reown.AppKit.Unity/Runtime/Connectors/Connector.cs +++ b/src/Reown.AppKit.Unity/Runtime/Connectors/Connector.cs @@ -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); } diff --git a/src/Reown.AppKit.Unity/Runtime/Connectors/WalletConnect/WalletConnectConnector.cs b/src/Reown.AppKit.Unity/Runtime/Connectors/WalletConnect/WalletConnectConnector.cs index 413f45e..3c77442 100644 --- a/src/Reown.AppKit.Unity/Runtime/Connectors/WalletConnect/WalletConnectConnector.cs +++ b/src/Reown.AppKit.Unity/Runtime/Connectors/WalletConnect/WalletConnectConnector.cs @@ -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; @@ -176,9 +177,25 @@ protected override async Task DisconnectAsyncCore() } } + private async Task WaitForSessionUpdateAsync(TimeSpan timeout) + { + var tcs = new TaskCompletionSource(); + var sessionUpdateHandler = new EventHandler((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, @@ -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) + || !@namespace.Chains.Contains(caip2ChainId)) + { + try + { + await AppKit.Evm.RpcRequestAsync("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(e.Message); + ReownLogger.LogError($"[MetaMask Error] {metaMaskError.Message}"); + } + catch (Exception ex) + { + Debug.LogException(ex); + } + + throw; + } + catch (Exception e) + { + Debug.LogException(e); + } + } } else { diff --git a/src/Reown.AppKit.Unity/Runtime/Controllers/ConnectorController.cs b/src/Reown.AppKit.Unity/Runtime/Controllers/ConnectorController.cs index e0d6d33..8331515 100644 --- a/src/Reown.AppKit.Unity/Runtime/Controllers/ConnectorController.cs +++ b/src/Reown.AppKit.Unity/Runtime/Controllers/ConnectorController.cs @@ -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 GetAccountAsyncCore() diff --git a/src/Reown.AppKit.Unity/Runtime/Controllers/NetworkController/NetworkControllerCore.cs b/src/Reown.AppKit.Unity/Runtime/Controllers/NetworkController/NetworkControllerCore.cs index f1a31f0..09eac03 100644 --- a/src/Reown.AppKit.Unity/Runtime/Controllers/NetworkController/NetworkControllerCore.cs +++ b/src/Reown.AppKit.Unity/Runtime/Controllers/NetworkController/NetworkControllerCore.cs @@ -3,6 +3,7 @@ using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; +using UnityEngine; namespace Reown.AppKit.Unity { @@ -19,20 +20,13 @@ protected override Task InitializeAsyncCore(IEnumerable 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 { diff --git a/src/Reown.AppKit.Unity/Runtime/Evm/EvmService.cs b/src/Reown.AppKit.Unity/Runtime/Evm/EvmService.cs index 9e21e61..2f7e657 100644 --- a/src/Reown.AppKit.Unity/Runtime/Evm/EvmService.cs +++ b/src/Reown.AppKit.Unity/Runtime/Evm/EvmService.cs @@ -160,6 +160,17 @@ public Task GetGasPriceAsync() { return GetGasPriceAsyncCore(); } + + + // -- RPC Request ---------------------------------------------- + + public Task RpcRequestAsync(string method, params object[] parameters) + { + if (string.IsNullOrWhiteSpace(method)) + throw new ArgumentNullException(nameof(method)); + + return RpcRequestAsyncCore(method, parameters); + } protected abstract Task InitializeAsyncCore(SignClientUnity signClient); @@ -176,5 +187,6 @@ public Task GetGasPriceAsync() protected abstract Task EstimateGasAsyncCore(string addressTo, BigInteger value, string data = null); protected abstract Task EstimateGasAsyncCore(string contractAddress, string contractAbi, string methodName, BigInteger value = default, params object[] arguments); protected abstract Task GetGasPriceAsyncCore(); + protected abstract Task RpcRequestAsyncCore(string method, params object[] parameters); } } \ No newline at end of file diff --git a/src/Reown.AppKit.Unity/Runtime/Evm/NethereumEvmService.cs b/src/Reown.AppKit.Unity/Runtime/Evm/NethereumEvmService.cs index 7d3fbc1..9cc4bba 100644 --- a/src/Reown.AppKit.Unity/Runtime/Evm/NethereumEvmService.cs +++ b/src/Reown.AppKit.Unity/Runtime/Evm/NethereumEvmService.cs @@ -304,5 +304,13 @@ protected override async Task GetGasPriceAsyncCore() var hexBigInt = await Web3.Eth.GasPrice.SendRequestAsync(); return hexBigInt.Value; } + + + // -- RPC Request ---------------------------------------------- + + protected override Task RpcRequestAsyncCore(string method, params object[] parameters) + { + return Web3.Client.SendRequestAsync(method, null, parameters); + } } } \ No newline at end of file diff --git a/src/Reown.AppKit.Unity/Runtime/Evm/WagmiEvmService.cs b/src/Reown.AppKit.Unity/Runtime/Evm/WagmiEvmService.cs index 95ccbe0..a7a4032 100644 --- a/src/Reown.AppKit.Unity/Runtime/Evm/WagmiEvmService.cs +++ b/src/Reown.AppKit.Unity/Runtime/Evm/WagmiEvmService.cs @@ -87,6 +87,11 @@ protected override async Task GetGasPriceAsyncCore() var result = await WagmiInterop.GetGasPriceAsync(); return BigInteger.Parse(result); } + + protected override Task RpcRequestAsyncCore(string method, params object[] parameters) + { + throw new System.NotImplementedException(); + } } #endif } \ No newline at end of file diff --git a/src/Reown.Sign.Nethereum/Runtime/Extensions.cs b/src/Reown.Sign.Nethereum/Runtime/Extensions.cs index 3059eb6..978942e 100644 --- a/src/Reown.Sign.Nethereum/Runtime/Extensions.cs +++ b/src/Reown.Sign.Nethereum/Runtime/Extensions.cs @@ -17,6 +17,7 @@ public static class Extensions /// Reown Sign client /// Ethereum chain to switch to /// Thrown when is null + [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) diff --git a/src/Reown.Sign.Nethereum/Runtime/ReownInterceptor.cs b/src/Reown.Sign.Nethereum/Runtime/ReownInterceptor.cs index d1a2586..f1efdfd 100644 --- a/src/Reown.Sign.Nethereum/Runtime/ReownInterceptor.cs +++ b/src/Reown.Sign.Nethereum/Runtime/ReownInterceptor.cs @@ -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 { @@ -69,7 +70,7 @@ public override async Task InterceptSendRequestAsync( 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(); @@ -96,39 +97,44 @@ public override async Task InterceptSendRequestAsync( 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"); } } } \ No newline at end of file diff --git a/src/Reown.Sign.Nethereum/Runtime/ReownSignService.cs b/src/Reown.Sign.Nethereum/Runtime/ReownSignService.cs index 12d61bd..dca81ba 100644 --- a/src/Reown.Sign.Nethereum/Runtime/ReownSignService.cs +++ b/src/Reown.Sign.Nethereum/Runtime/ReownSignService.cs @@ -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 { @@ -42,16 +43,23 @@ public Task WalletSwitchEthereumChainAsync(SwitchEthereumChainParameter return WalletSwitchEthereumChainAsyncCore(chainId); } - public Task WalletAddEthereumChainAsync(AddEthereumChainParameter chain) + public Task WalletAddEthereumChainAsync(EthereumChain chain) { return WalletAddEthereumChainAsyncCore(chain); } + public Task 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 SendTransactionAsyncCore(TransactionInput transaction); protected abstract Task PersonalSignAsyncCore(string message); protected abstract Task EthSignTypedDataV4AsyncCore(string data); protected abstract Task WalletSwitchEthereumChainAsyncCore(SwitchEthereumChainParameter chainId); - protected abstract Task WalletAddEthereumChainAsyncCore(AddEthereumChainParameter chain); + protected abstract Task WalletAddEthereumChainAsyncCore(EthereumChain chain); } } \ No newline at end of file diff --git a/src/Reown.Sign.Nethereum/Runtime/ReownSignServiceCore.cs b/src/Reown.Sign.Nethereum/Runtime/ReownSignServiceCore.cs index a2bc10c..c3274d3 100644 --- a/src/Reown.Sign.Nethereum/Runtime/ReownSignServiceCore.cs +++ b/src/Reown.Sign.Nethereum/Runtime/ReownSignServiceCore.cs @@ -75,11 +75,9 @@ protected override async Task WalletSwitchEthereumChainAsyncCore(SwitchE return await _signClient.Request(switchChainRequest); } - protected override async Task WalletAddEthereumChainAsyncCore(AddEthereumChainParameter chain) + protected override async Task 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(addEthereumChainRequest); } } diff --git a/src/Reown.Sign/Runtime/Internals/EngineTasks.cs b/src/Reown.Sign/Runtime/Internals/EngineTasks.cs index c8391d9..dabc726 100644 --- a/src/Reown.Sign/Runtime/Internals/EngineTasks.cs +++ b/src/Reown.Sign/Runtime/Internals/EngineTasks.cs @@ -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))