From 50c0b67845884659fab85603568b84f144b939c2 Mon Sep 17 00:00:00 2001 From: Nicolas Lorusso Date: Mon, 24 Feb 2025 15:48:15 -0300 Subject: [PATCH 1/7] fix concurrency on ethereum calls --- .../Authenticators/DappWeb3Authenticator.cs | 52 ++++++++++++++++--- .../Modules/Ethereums/EthereumApiWrapper.cs | 8 +-- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs index 497eaa9fc9..c216abfb4b 100644 --- a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs +++ b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs @@ -2,8 +2,6 @@ using DCL.Browser; using DCL.Multiplayer.Connections.DecentralandUrls; using DCL.Web3.Abstract; -using DCL.Web3.Accounts; -using DCL.Web3.Accounts.Factory; using DCL.Web3.Chains; using DCL.Web3.Identities; using Newtonsoft.Json; @@ -28,6 +26,8 @@ public partial class DappWeb3Authenticator : IWeb3VerifiedAuthenticator, IVerifi private readonly IWeb3IdentityCache identityCache; private readonly IWeb3AccountFactory web3AccountFactory; private readonly HashSet whitelistMethods; + // Allow only one web3 operation at a time + private readonly SemaphoreSlim mutex = new(1, 1); private SocketIO? webSocket; private UniTaskCompletionSource? signatureOutcomeTask; @@ -61,8 +61,14 @@ public async UniTask SendAsync(EthApiRequest request, CancellationToken ct if (!whitelistMethods.Contains(request.method)) throw new Web3Exception($"The method is not allowed: {request.method}"); + await mutex.WaitAsync(ct); + + SynchronizationContext originalSyncContext = SynchronizationContext.Current; + try { + await UniTask.SwitchToMainThread(ct); + await ConnectToServerAsync(); SignatureIdResponse authenticationResponse = await RequestEthMethodAsync(new AuthorizedEthApiRequest @@ -83,13 +89,24 @@ public async UniTask SendAsync(EthApiRequest request, CancellationToken ct MethodResponse response = await RequestWalletConfirmationAsync>(authenticationResponse.requestId, signatureExpiration, ct); + await DisconnectFromServerAsync(); + // Strip out the requestId & sender fields. We assume that will not be needed by the client return response.result; } - finally + catch (Exception) { await DisconnectFromServerAsync(); - await UniTask.SwitchToMainThread(ct); + throw; + } + finally + { + if (originalSyncContext != null) + await UniTask.SwitchToSynchronizationContext(originalSyncContext, ct); + else + await UniTask.SwitchToMainThread(ct); + + mutex.Release(); } } @@ -103,8 +120,14 @@ public async UniTask SendAsync(EthApiRequest request, CancellationToken ct /// public async UniTask LoginAsync(CancellationToken ct) { + await mutex.WaitAsync(ct); + + SynchronizationContext originalSyncContext = SynchronizationContext.Current; + try { + await UniTask.SwitchToMainThread(ct); + await ConnectToServerAsync(); var ephemeralAccount = web3AccountFactory.CreateRandomAccount(); @@ -131,6 +154,8 @@ public async UniTask LoginAsync(CancellationToken ct) LoginResponse response = await RequestWalletConfirmationAsync(authenticationResponse.requestId, signatureExpiration, ct); + await DisconnectFromServerAsync(); + if (string.IsNullOrEmpty(response.sender)) throw new Web3Exception($"Cannot solve the signer's address from the signature. Request id: {authenticationResponse.requestId}"); @@ -143,10 +168,19 @@ public async UniTask LoginAsync(CancellationToken ct) return new DecentralandIdentity(new Web3Address(response.sender), ephemeralAccount, sessionExpiration, authChain); } - finally + catch (Exception) { await DisconnectFromServerAsync(); - await UniTask.SwitchToMainThread(ct); + throw; + } + finally + { + if (originalSyncContext != null) + await UniTask.SwitchToSynchronizationContext(originalSyncContext, ct); + else + await UniTask.SwitchToMainThread(ct); + + mutex.Release(); } } @@ -213,8 +247,10 @@ private string CreateEphemeralMessage(IWeb3Account ephemeralAccount, DateTime ex private async UniTask ConnectToServerAsync() { - SocketIO webSocket = InitializeWebSocket(); - await webSocket.ConnectAsync().AsUniTask().Timeout(TimeSpan.FromSeconds(TIMEOUT_SECONDS)); + await InitializeWebSocket() + .ConnectAsync() + .AsUniTask() + .Timeout(TimeSpan.FromSeconds(TIMEOUT_SECONDS)); } private void ProcessSignatureOutcomeMessage(SocketIOResponse response) diff --git a/Explorer/Assets/Scripts/SceneRuntime/Apis/Modules/Ethereums/EthereumApiWrapper.cs b/Explorer/Assets/Scripts/SceneRuntime/Apis/Modules/Ethereums/EthereumApiWrapper.cs index 7a97bed956..203758d4d5 100644 --- a/Explorer/Assets/Scripts/SceneRuntime/Apis/Modules/Ethereums/EthereumApiWrapper.cs +++ b/Explorer/Assets/Scripts/SceneRuntime/Apis/Modules/Ethereums/EthereumApiWrapper.cs @@ -17,8 +17,7 @@ public class EthereumApiWrapper : IJsApiWrapper private readonly IEthereumApi ethereumApi; private readonly ISceneExceptionsHandler sceneExceptionsHandler; private readonly IWeb3IdentityCache web3IdentityCache; - - private CancellationTokenSource sendCancellationToken; + private readonly CancellationTokenSource sendCancellationToken; private CancellationTokenSource signMessageCancellationToken; public EthereumApiWrapper(IEthereumApi ethereumApi, ISceneExceptionsHandler sceneExceptionsHandler, IWeb3IdentityCache web3IdentityCache) : this( @@ -129,10 +128,7 @@ async UniTask SendAndFormatAsync(double id, string } } - // TODO: support cancellations by id (?) - sendCancellationToken = sendCancellationToken.SafeRestart(); - - return SendAndFormatAsync(id, method, JsonConvert.DeserializeObject(jsonParams), sendCancellationToken.Token) + return SendAndFormatAsync(id, method, JsonConvert.DeserializeObject(jsonParams) ?? Array.Empty(), sendCancellationToken.Token) .ToDisconnectedPromise(); } } From 03f7403b920e1d7da5b1fcd988dd570ce3fe8fde Mon Sep 17 00:00:00 2001 From: Nicolas Lorusso Date: Tue, 25 Feb 2025 15:11:24 -0300 Subject: [PATCH 2/7] implement readonly eth methods that requires no wallet signature --- .../DecentralandUrls/DecentralandUrl.cs | 3 +- .../DecentralandUrlsSource.cs | 18 +- .../IDecentralandUrlsSource.cs | 1 + ...uthenticator.DappSignatureResponse.cs.meta | 11 - .../DappWeb3Authenticator.Default.cs | 92 +++++ .../DappWeb3Authenticator.Default.cs.meta | 3 + ...ppWeb3Authenticator.LoginAuthApiRequest.cs | 14 + ...3Authenticator.LoginAuthApiRequest.cs.meta | 3 + ...Web3Authenticator.LoginAuthApiResponse.cs} | 10 +- ...Authenticator.LoginAuthApiResponse.cs.meta | 3 + .../DappWeb3Authenticator.MethodResponse.cs | 15 + ...ppWeb3Authenticator.MethodResponse.cs.meta | 3 + .../Authenticators/DappWeb3Authenticator.cs | 364 +++++++++++------- Explorer/Assets/DCL/Web3/EthApiRequest.cs | 1 + Explorer/Assets/DCL/Web3/EthApiResponse.cs | 9 + .../Assets/DCL/Web3/EthApiResponse.cs.meta | 3 + Explorer/Assets/DCL/Web3/IEthereumApi.cs | 2 +- Explorer/Assets/DCL/Web3/Web3.asmdef | 3 +- .../Global/Dynamic/BootstrapContainer.cs | 26 +- .../Dynamic/DynamicSceneLoaderSettings.asset | 15 +- .../Dynamic/DynamicSceneLoaderSettings.cs | 2 +- .../Modules/Ethereums/EthereumApiWrapper.cs | 14 +- 22 files changed, 423 insertions(+), 192 deletions(-) delete mode 100644 Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.DappSignatureResponse.cs.meta create mode 100644 Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.Default.cs create mode 100644 Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.Default.cs.meta create mode 100644 Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.LoginAuthApiRequest.cs create mode 100644 Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.LoginAuthApiRequest.cs.meta rename Explorer/Assets/DCL/Web3/Authenticators/{DappWeb3Authenticator.DappSignatureResponse.cs => DappWeb3Authenticator.LoginAuthApiResponse.cs} (53%) create mode 100644 Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.LoginAuthApiResponse.cs.meta create mode 100644 Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.MethodResponse.cs create mode 100644 Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.MethodResponse.cs.meta create mode 100644 Explorer/Assets/DCL/Web3/EthApiResponse.cs create mode 100644 Explorer/Assets/DCL/Web3/EthApiResponse.cs.meta diff --git a/Explorer/Assets/DCL/Browser/DecentralandUrls/DecentralandUrl.cs b/Explorer/Assets/DCL/Browser/DecentralandUrls/DecentralandUrl.cs index 288d71d624..f1243bbd57 100644 --- a/Explorer/Assets/DCL/Browser/DecentralandUrls/DecentralandUrl.cs +++ b/Explorer/Assets/DCL/Browser/DecentralandUrls/DecentralandUrl.cs @@ -24,7 +24,8 @@ public enum DecentralandUrl ApiEvents, ApiAuth, - AuthSignature, + AuthSignatureWebApp, + ApiRpc, GateKeeperSceneAdapter, LocalGateKeeperSceneAdapter, diff --git a/Explorer/Assets/DCL/Browser/DecentralandUrls/DecentralandUrlsSource.cs b/Explorer/Assets/DCL/Browser/DecentralandUrls/DecentralandUrlsSource.cs index 26a6e19628..2f1089bcd5 100644 --- a/Explorer/Assets/DCL/Browser/DecentralandUrls/DecentralandUrlsSource.cs +++ b/Explorer/Assets/DCL/Browser/DecentralandUrls/DecentralandUrlsSource.cs @@ -17,22 +17,23 @@ public class DecentralandUrlsSource : IDecentralandUrlsSource private readonly Dictionary cache = new (); - private readonly string environmentDomainLowerCase; private readonly ILaunchMode launchMode; - public string DecentralandDomain => environmentDomainLowerCase; + public string DecentralandDomain { get; } + public DecentralandEnvironment Environment { get; } public DecentralandUrlsSource(DecentralandEnvironment environment, ILaunchMode launchMode) { - environmentDomainLowerCase = environment.ToString()!.ToLower(); + Environment = environment; + DecentralandDomain = environment.ToString()!.ToLower(); this.launchMode = launchMode; switch (environment) { case DecentralandEnvironment.Org: case DecentralandEnvironment.Zone: - ASSET_BUNDLE_URL = string.Format(ASSET_BUNDLE_URL_TEMPLATE, environmentDomainLowerCase); - GENESIS_URL = string.Format(GENESIS_URL_TEMPLATE, environmentDomainLowerCase); + ASSET_BUNDLE_URL = string.Format(ASSET_BUNDLE_URL_TEMPLATE, DecentralandDomain); + GENESIS_URL = string.Format(GENESIS_URL_TEMPLATE, DecentralandDomain); break; case DecentralandEnvironment.Today: @@ -40,7 +41,7 @@ public DecentralandUrlsSource(DecentralandEnvironment environment, ILaunchMode l //We want to fetch pointers from org, but asset bundles from today //Thats because how peer-testing.decentraland.org works. //Its a catalyst that replicates the org environment and eth network, but doesnt propagate back to the production catalysts - environmentDomainLowerCase = DecentralandEnvironment.Org.ToString()!.ToLower(); + DecentralandDomain = DecentralandEnvironment.Org.ToString()!.ToLower(); ASSET_BUNDLE_URL = "https://ab-cdn.decentraland.today"; //On staging, we hardcode the catalyst because its the only valid one with a valid comms configuration @@ -53,7 +54,7 @@ public string Url(DecentralandUrl decentralandUrl) { if (cache.TryGetValue(decentralandUrl, out string? url) == false) { - url = RawUrl(decentralandUrl).Replace(ENV, environmentDomainLowerCase); + url = RawUrl(decentralandUrl).Replace(ENV, DecentralandDomain); cache[decentralandUrl] = url; } @@ -76,7 +77,8 @@ private static string RawUrl(DecentralandUrl decentralandUrl) => DecentralandUrl.TermsOfUse => $"https://decentraland.{ENV}/terms", DecentralandUrl.ApiPlaces => $"https://places.decentraland.{ENV}/api/places", DecentralandUrl.ApiAuth => $"https://auth-api.decentraland.{ENV}", - DecentralandUrl.AuthSignature => $"https://decentraland.{ENV}/auth/requests", + DecentralandUrl.ApiRpc => $"wss://rpc.decentraland.{ENV}", + DecentralandUrl.AuthSignatureWebApp => $"https://decentraland.{ENV}/auth/requests", DecentralandUrl.BuilderApiDtos => $"https://builder-api.decentraland.{ENV}/v1/collections/[COL-ID]/items", DecentralandUrl.BuilderApiContent => $"https://builder-api.decentraland.{ENV}/v1/storage/contents/", DecentralandUrl.POI => $"https://dcl-lists.decentraland.{ENV}/pois", diff --git a/Explorer/Assets/DCL/Browser/DecentralandUrls/IDecentralandUrlsSource.cs b/Explorer/Assets/DCL/Browser/DecentralandUrls/IDecentralandUrlsSource.cs index 1e12200c7f..194a00dddb 100644 --- a/Explorer/Assets/DCL/Browser/DecentralandUrls/IDecentralandUrlsSource.cs +++ b/Explorer/Assets/DCL/Browser/DecentralandUrls/IDecentralandUrlsSource.cs @@ -7,6 +7,7 @@ public interface IDecentralandUrlsSource const string LAUNCHER_DOWNLOAD_URL = "https://github.com/decentraland/launcher/releases/download"; string DecentralandDomain { get; } + DecentralandEnvironment Environment { get; } string Url(DecentralandUrl decentralandUrl); string GetHostnameForFeatureFlag(); diff --git a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.DappSignatureResponse.cs.meta b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.DappSignatureResponse.cs.meta deleted file mode 100644 index c73a61c21d..0000000000 --- a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.DappSignatureResponse.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 4300333a810f480a99d6beba832a8a80 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.Default.cs b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.Default.cs new file mode 100644 index 0000000000..0fe7aef147 --- /dev/null +++ b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.Default.cs @@ -0,0 +1,92 @@ +using CommunicationData.URLHelpers; +using Cysharp.Threading.Tasks; +using DCL.Browser; +using DCL.Multiplayer.Connections.DecentralandUrls; +using DCL.Web3.Abstract; +using DCL.Web3.Identities; +using System.Collections.Generic; +using System.Threading; + +namespace DCL.Web3.Authenticators +{ + public partial class DappWeb3Authenticator + { + public class Default : IWeb3VerifiedAuthenticator, IVerifiedEthereumApi + { + private readonly IWeb3VerifiedAuthenticator originAuth; + private readonly IVerifiedEthereumApi originApi; + + public Default(IWeb3IdentityCache identityCache, IDecentralandUrlsSource decentralandUrlsSource, IWeb3AccountFactory web3AccountFactory) + { + URLAddress authApiUrl = URLAddress.FromString(decentralandUrlsSource.Url(DecentralandUrl.ApiAuth)); + URLAddress signatureUrl = URLAddress.FromString(decentralandUrlsSource.Url(DecentralandUrl.AuthSignatureWebApp)); + + URLBuilder urlBuilder = new URLBuilder(); + + const string NETWORK = "sepolia"; + + URLAddress rpcServerUrl = urlBuilder.AppendDomain(URLDomain.FromString(decentralandUrlsSource.Url(DecentralandUrl.ApiRpc))) + .AppendPath(new URLPath(NETWORK)) + .Build(); + + var origin = new DappWeb3Authenticator( + new UnityAppWebBrowser(decentralandUrlsSource), + authApiUrl, + signatureUrl, + rpcServerUrl, + identityCache, + web3AccountFactory, + new HashSet( + new[] + { + "eth_getBalance", + "eth_call", + "eth_blockNumber", + "eth_signTypedData_v4", + } + ), + new HashSet + { + "eth_getTransactionReceipt", + "eth_estimateGas", + "eth_call", + "eth_getBalance", + "eth_getStorageAt", + "eth_blockNumber", + "eth_gasPrice", + "eth_protocolVersion", + "net_version", + "web3_sha3", + "web3_clientVersion", + "eth_getTransactionCount", + "eth_getBlockByNumber", + "eth_getCode", + } + ); + + originApi = origin; + originAuth = origin; + } + + public void Dispose() + { + originAuth.Dispose(); // Disposes both + } + + public UniTask SendAsync(EthApiRequest request, CancellationToken ct) => + originApi.SendAsync(request, ct); + + public void AddVerificationListener(IVerifiedEthereumApi.VerificationDelegate callback) => + originApi.AddVerificationListener(callback); + + public UniTask LoginAsync(CancellationToken ct) => + originAuth.LoginAsync(ct); + + public UniTask LogoutAsync(CancellationToken cancellationToken) => + originAuth.LogoutAsync(cancellationToken); + + public void SetVerificationListener(IWeb3VerifiedAuthenticator.VerificationDelegate? callback) => + originAuth.SetVerificationListener(callback); + } + } +} diff --git a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.Default.cs.meta b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.Default.cs.meta new file mode 100644 index 0000000000..beb3b6a916 --- /dev/null +++ b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.Default.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1a04442f11934161bb177ec194107c71 +timeCreated: 1740495000 \ No newline at end of file diff --git a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.LoginAuthApiRequest.cs b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.LoginAuthApiRequest.cs new file mode 100644 index 0000000000..2dfde7a57f --- /dev/null +++ b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.LoginAuthApiRequest.cs @@ -0,0 +1,14 @@ +using System; + +namespace DCL.Web3.Authenticators +{ + public partial class DappWeb3Authenticator + { + [Serializable] + public struct LoginAuthApiRequest + { + public string method; + public object[] @params; + } + } +} diff --git a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.LoginAuthApiRequest.cs.meta b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.LoginAuthApiRequest.cs.meta new file mode 100644 index 0000000000..01b70de0e7 --- /dev/null +++ b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.LoginAuthApiRequest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8967138547114248a983f7108af2138d +timeCreated: 1740505587 \ No newline at end of file diff --git a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.DappSignatureResponse.cs b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.LoginAuthApiResponse.cs similarity index 53% rename from Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.DappSignatureResponse.cs rename to Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.LoginAuthApiResponse.cs index dfe6a23e87..dfbc361d06 100644 --- a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.DappSignatureResponse.cs +++ b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.LoginAuthApiResponse.cs @@ -5,19 +5,11 @@ namespace DCL.Web3.Authenticators public partial class DappWeb3Authenticator { [Serializable] - private struct LoginResponse + private struct LoginAuthApiResponse { public string requestId; public string result; public string sender; } - - [Serializable] - private struct MethodResponse - { - public string requestId; - public T result; - public string sender; - } } } diff --git a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.LoginAuthApiResponse.cs.meta b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.LoginAuthApiResponse.cs.meta new file mode 100644 index 0000000000..a213ecad68 --- /dev/null +++ b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.LoginAuthApiResponse.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 15fd70ff6f084529985b35e7604ce451 +timeCreated: 1740505579 \ No newline at end of file diff --git a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.MethodResponse.cs b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.MethodResponse.cs new file mode 100644 index 0000000000..446681d494 --- /dev/null +++ b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.MethodResponse.cs @@ -0,0 +1,15 @@ +using System; + +namespace DCL.Web3.Authenticators +{ + public partial class DappWeb3Authenticator + { + [Serializable] + private struct MethodResponse + { + public string requestId; + public object result; + public string sender; + } + } +} diff --git a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.MethodResponse.cs.meta b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.MethodResponse.cs.meta new file mode 100644 index 0000000000..f77ae11571 --- /dev/null +++ b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.MethodResponse.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 14054ebbde2f4fa98f664f0e25bcfed6 +timeCreated: 1740505594 \ No newline at end of file diff --git a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs index c216abfb4b..741890cd59 100644 --- a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs +++ b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs @@ -1,6 +1,6 @@ +using CommunicationData.URLHelpers; using Cysharp.Threading.Tasks; using DCL.Browser; -using DCL.Multiplayer.Connections.DecentralandUrls; using DCL.Web3.Abstract; using DCL.Web3.Chains; using DCL.Web3.Identities; @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Net.WebSockets; using System.Threading; namespace DCL.Web3.Authenticators @@ -19,95 +20,79 @@ namespace DCL.Web3.Authenticators public partial class DappWeb3Authenticator : IWeb3VerifiedAuthenticator, IVerifiedEthereumApi { private const int TIMEOUT_SECONDS = 30; + private const int RPC_BUFFER_SIZE = 50000; private readonly IWebBrowser webBrowser; - private readonly string serverUrl; - private readonly string signatureUrl; + private readonly URLAddress authApiUrl; + private readonly URLAddress signatureWebAppUrl; + private readonly URLAddress rpcServerUrl; private readonly IWeb3IdentityCache identityCache; private readonly IWeb3AccountFactory web3AccountFactory; private readonly HashSet whitelistMethods; + private readonly HashSet readOnlyMethods; + // Allow only one web3 operation at a time - private readonly SemaphoreSlim mutex = new(1, 1); + private readonly SemaphoreSlim mutex = new (1, 1); + private readonly byte[] rpcByteBuffer = new byte[RPC_BUFFER_SIZE]; - private SocketIO? webSocket; + private int authApiPendingOperations; + private int rpcPendingOperations; + private SocketIO? authApiWebSocket; + private ClientWebSocket? rpcWebSocket; private UniTaskCompletionSource? signatureOutcomeTask; private IWeb3VerifiedAuthenticator.VerificationDelegate? loginVerificationCallback; private IVerifiedEthereumApi.VerificationDelegate? signatureVerificationCallback; public DappWeb3Authenticator(IWebBrowser webBrowser, - string serverUrl, - string signatureUrl, + URLAddress authApiUrl, + URLAddress signatureWebAppUrl, + URLAddress rpcServerUrl, IWeb3IdentityCache identityCache, IWeb3AccountFactory web3AccountFactory, - HashSet whitelistMethods - ) + HashSet whitelistMethods, + HashSet readOnlyMethods) { this.webBrowser = webBrowser; - this.serverUrl = serverUrl; - this.signatureUrl = signatureUrl; + this.authApiUrl = authApiUrl; + this.signatureWebAppUrl = signatureWebAppUrl; + this.rpcServerUrl = rpcServerUrl; this.identityCache = identityCache; this.web3AccountFactory = web3AccountFactory; this.whitelistMethods = whitelistMethods; + this.readOnlyMethods = readOnlyMethods; } public void Dispose() { - try { webSocket?.Dispose(); } + try { authApiWebSocket?.Dispose(); } catch (ObjectDisposedException) { } } - public async UniTask SendAsync(EthApiRequest request, CancellationToken ct) + public async UniTask SendAsync(EthApiRequest request, CancellationToken ct) { if (!whitelistMethods.Contains(request.method)) throw new Web3Exception($"The method is not allowed: {request.method}"); - await mutex.WaitAsync(ct); - - SynchronizationContext originalSyncContext = SynchronizationContext.Current; - - try + if (string.Equals(request.method, "eth_accounts") + || string.Equals(request.method, "eth_requestAccounts")) { - await UniTask.SwitchToMainThread(ct); + string[] accounts = Array.Empty(); - await ConnectToServerAsync(); + if (identityCache.Identity != null) + accounts = new string[] { identityCache.EnsuredIdentity().Address }; - SignatureIdResponse authenticationResponse = await RequestEthMethodAsync(new AuthorizedEthApiRequest + return new EthApiResponse { - method = request.method, - @params = request.@params, - authChain = identityCache.Identity!.AuthChain.ToArray(), - }, ct); - - DateTime signatureExpiration = DateTime.UtcNow.AddMinutes(5); - - if (!string.IsNullOrEmpty(authenticationResponse.expiration)) - signatureExpiration = DateTime.Parse(authenticationResponse.expiration, null, DateTimeStyles.RoundtripKind); - - await UniTask.SwitchToMainThread(ct); - - signatureVerificationCallback?.Invoke(authenticationResponse.code, signatureExpiration); - - MethodResponse response = await RequestWalletConfirmationAsync>(authenticationResponse.requestId, signatureExpiration, ct); - - await DisconnectFromServerAsync(); - - // Strip out the requestId & sender fields. We assume that will not be needed by the client - return response.result; + id = request.id, + jsonrpc = "2.0", + result = accounts, + }; } - catch (Exception) - { - await DisconnectFromServerAsync(); - throw; - } - finally - { - if (originalSyncContext != null) - await UniTask.SwitchToSynchronizationContext(originalSyncContext, ct); - else - await UniTask.SwitchToMainThread(ct); - mutex.Release(); - } + if (IsReadOnly(request)) + return await SendWithoutConfirmationAsync(request, ct); + + return await SendWithConfirmationAsync(request, ct); } /// @@ -128,7 +113,7 @@ public async UniTask LoginAsync(CancellationToken ct) { await UniTask.SwitchToMainThread(ct); - await ConnectToServerAsync(); + await ConnectToAuthApiAsync(); var ephemeralAccount = web3AccountFactory.CreateRandomAccount(); @@ -136,7 +121,7 @@ public async UniTask LoginAsync(CancellationToken ct) DateTime sessionExpiration = DateTime.UtcNow.AddDays(7); string ephemeralMessage = CreateEphemeralMessage(ephemeralAccount, sessionExpiration); - SignatureIdResponse authenticationResponse = await RequestEthMethodAsync(new EthApiRequest + SignatureIdResponse authenticationResponse = await RequestEthMethodWithSignatureAsync(new LoginAuthApiRequest { method = "dcl_personal_sign", @params = new object[] { ephemeralMessage }, @@ -151,10 +136,10 @@ public async UniTask LoginAsync(CancellationToken ct) loginVerificationCallback?.Invoke(authenticationResponse.code, signatureExpiration, authenticationResponse.requestId); - LoginResponse response = await RequestWalletConfirmationAsync(authenticationResponse.requestId, + LoginAuthApiResponse response = await RequestWalletConfirmationAsync(authenticationResponse.requestId, signatureExpiration, ct); - await DisconnectFromServerAsync(); + await DisconnectFromAuthApiAsync(); if (string.IsNullOrEmpty(response.sender)) throw new Web3Exception($"Cannot solve the signer's address from the signature. Request id: {authenticationResponse.requestId}"); @@ -170,7 +155,7 @@ public async UniTask LoginAsync(CancellationToken ct) } catch (Exception) { - await DisconnectFromServerAsync(); + await DisconnectFromAuthApiAsync(); throw; } finally @@ -184,10 +169,8 @@ public async UniTask LoginAsync(CancellationToken ct) } } - public async UniTask LogoutAsync(CancellationToken cancellationToken) - { - await DisconnectFromServerAsync(); - } + public async UniTask LogoutAsync(CancellationToken cancellationToken) => + await DisconnectFromAuthApiAsync(); public void SetVerificationListener(IWeb3VerifiedAuthenticator.VerificationDelegate? callback) => loginVerificationCallback = callback; @@ -195,31 +178,153 @@ public void SetVerificationListener(IWeb3VerifiedAuthenticator.VerificationDeleg public void AddVerificationListener(IVerifiedEthereumApi.VerificationDelegate callback) => signatureVerificationCallback = callback; - private SocketIO InitializeWebSocket() + private async UniTask DisconnectFromAuthApiAsync() { - if (webSocket != null) return webSocket; + if (authApiWebSocket is { Connected: true }) + await authApiWebSocket.DisconnectAsync(); + } - var uri = new Uri(serverUrl); + private async UniTask SendWithoutConfirmationAsync(EthApiRequest request, CancellationToken ct) + { + SynchronizationContext originalSyncContext = SynchronizationContext.Current; - webSocket = new SocketIO(uri, new SocketIOOptions + try { - Transport = TransportProtocol.WebSocket, - }); + rpcPendingOperations++; + + await mutex.WaitAsync(ct); + + await UniTask.SwitchToMainThread(ct); + + await ConnectToRpcAsync(ct); - webSocket.JsonSerializer = new NewtonsoftJsonSerializer(new JsonSerializerSettings()); + var response = await RequestEthMethodWithoutSignature(request, ct) + .Timeout(TimeSpan.FromSeconds(TIMEOUT_SECONDS)); - webSocket.On("outcome", ProcessSignatureOutcomeMessage); + if (rpcPendingOperations <= 1) + await DisconnectFromRpcAsync(ct); - return webSocket; + return response; + } + catch (Exception) + { + await DisconnectFromRpcAsync(ct); + throw; + } + finally + { + if (originalSyncContext != null) + await UniTask.SwitchToSynchronizationContext(originalSyncContext, ct); + else + await UniTask.SwitchToMainThread(ct); + + mutex.Release(); + rpcPendingOperations--; + } } - private async UniTask DisconnectFromServerAsync() + private async UniTask DisconnectFromRpcAsync(CancellationToken ct) { - if (webSocket is { Connected: true }) - await webSocket.DisconnectAsync(); + if (rpcWebSocket == null) return; + + await rpcWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", ct); + rpcWebSocket.Abort(); + rpcWebSocket.Dispose(); + rpcWebSocket = null; + } + + private async UniTask ConnectToRpcAsync(CancellationToken ct) + { + if (rpcWebSocket?.State == WebSocketState.Open) return; + + rpcWebSocket = new ClientWebSocket(); + await rpcWebSocket.ConnectAsync(new Uri(rpcServerUrl), ct); + } + + private async UniTask RequestEthMethodWithoutSignature(EthApiRequest request, CancellationToken ct) + { + string reqJson = JsonConvert.SerializeObject(request); + byte[] bytes = System.Text.Encoding.UTF8.GetBytes(reqJson); + await rpcWebSocket!.SendAsync(bytes, WebSocketMessageType.Text, true, ct); + + while (!ct.IsCancellationRequested && rpcWebSocket.State == WebSocketState.Open) + { + WebSocketReceiveResult result = await rpcWebSocket.ReceiveAsync(rpcByteBuffer, ct); + + if (result.MessageType is WebSocketMessageType.Text or WebSocketMessageType.Binary) + { + string resJson = System.Text.Encoding.UTF8.GetString(rpcByteBuffer, 0, result.Count); + EthApiResponse response = JsonConvert.DeserializeObject(resJson); + + if (response.id == request.id) + return response; + } + } + + throw new Web3Exception("Unexpected data received from rpc"); + } + + private async UniTask SendWithConfirmationAsync(EthApiRequest request, CancellationToken ct) + { + SynchronizationContext originalSyncContext = SynchronizationContext.Current; + + try + { + authApiPendingOperations++; + + await mutex.WaitAsync(ct); + + await UniTask.SwitchToMainThread(ct); + + await ConnectToAuthApiAsync(); + + SignatureIdResponse authenticationResponse = await RequestEthMethodWithSignatureAsync(new AuthorizedEthApiRequest + { + method = request.method, + @params = request.@params, + authChain = identityCache.Identity!.AuthChain.ToArray(), + }, ct); + + DateTime signatureExpiration = DateTime.UtcNow.AddMinutes(5); + + if (!string.IsNullOrEmpty(authenticationResponse.expiration)) + signatureExpiration = DateTime.Parse(authenticationResponse.expiration, null, DateTimeStyles.RoundtripKind); + + await UniTask.SwitchToMainThread(ct); + + signatureVerificationCallback?.Invoke(authenticationResponse.code, signatureExpiration); + + MethodResponse response = await RequestWalletConfirmationAsync(authenticationResponse.requestId, signatureExpiration, ct); + + if (authApiPendingOperations <= 1) + await DisconnectFromAuthApiAsync(); + + // Strip out the requestId & sender fields. We assume that will not be needed by the client + return new EthApiResponse + { + id = request.id, + result = response.result, + jsonrpc = "2.0", + }; + } + catch (Exception) + { + await DisconnectFromAuthApiAsync(); + throw; + } + finally + { + if (originalSyncContext != null) + await UniTask.SwitchToSynchronizationContext(originalSyncContext, ct); + else + await UniTask.SwitchToMainThread(ct); + + mutex.Release(); + authApiPendingOperations--; + } } - private AuthChain CreateAuthChain(LoginResponse response, string ephemeralMessage) + private AuthChain CreateAuthChain(LoginAuthApiResponse response, string ephemeralMessage) { var authChain = AuthChain.Create(); @@ -245,26 +350,33 @@ private AuthChain CreateAuthChain(LoginResponse response, string ephemeralMessag private string CreateEphemeralMessage(IWeb3Account ephemeralAccount, DateTime expiration) => $"Decentraland Login\nEphemeral address: {ephemeralAccount.Address.OriginalFormat}\nExpiration: {expiration:yyyy-MM-ddTHH:mm:ss.fffZ}"; - private async UniTask ConnectToServerAsync() - { - await InitializeWebSocket() - .ConnectAsync() - .AsUniTask() - .Timeout(TimeSpan.FromSeconds(TIMEOUT_SECONDS)); - } + private void ProcessSignatureOutcomeMessage(SocketIOResponse response) => + signatureOutcomeTask?.TrySetResult(response); - private void ProcessSignatureOutcomeMessage(SocketIOResponse response) + private async UniTask RequestWalletConfirmationAsync(string requestId, DateTime expiration, CancellationToken ct) { - signatureOutcomeTask?.TrySetResult(response); + webBrowser.OpenUrl($"{signatureWebAppUrl}/{requestId}"); + + signatureOutcomeTask?.TrySetCanceled(ct); + signatureOutcomeTask = new UniTaskCompletionSource(); + + TimeSpan duration = expiration - DateTime.UtcNow; + + try + { + SocketIOResponse response = await signatureOutcomeTask.Task.Timeout(duration).AttachExternalCancellation(ct); + return response.GetValue(); + } + catch (TimeoutException) { throw new SignatureExpiredException(expiration); } } - private async UniTask RequestEthMethodAsync( + private async UniTask RequestEthMethodWithSignatureAsync( object request, CancellationToken ct) { UniTaskCompletionSource task = new (); - await webSocket!.EmitAsync("request", ct, + await authApiWebSocket!.EmitAsync("request", ct, r => { SignatureIdResponse signatureIdResponse = r.GetValue(); @@ -281,73 +393,37 @@ private async UniTask RequestEthMethodAsync( .AttachExternalCancellation(ct); } - private async UniTask RequestWalletConfirmationAsync(string requestId, DateTime expiration, CancellationToken ct) + private async UniTask ConnectToAuthApiAsync() { - webBrowser.OpenUrl($"{signatureUrl}/{requestId}"); - - signatureOutcomeTask?.TrySetCanceled(ct); - signatureOutcomeTask = new UniTaskCompletionSource(); - - TimeSpan duration = expiration - DateTime.UtcNow; - - try + if (authApiWebSocket == null) { - SocketIOResponse response = await signatureOutcomeTask.Task.Timeout(duration).AttachExternalCancellation(ct); - return response.GetValue(); - } - catch (TimeoutException) { throw new SignatureExpiredException(expiration); } - } + var uri = new Uri(authApiUrl); - public class Default : IWeb3VerifiedAuthenticator, IVerifiedEthereumApi - { - private readonly IWeb3VerifiedAuthenticator originAuth; - private readonly IVerifiedEthereumApi originApi; + authApiWebSocket = new SocketIO(uri, new SocketIOOptions + { + Transport = TransportProtocol.WebSocket, + }); - public Default(IWeb3IdentityCache identityCache, IDecentralandUrlsSource decentralandUrlsSource, IWeb3AccountFactory web3AccountFactory) - { - string serverUrl = decentralandUrlsSource.Url(DecentralandUrl.ApiAuth); - string signatureUrl = decentralandUrlsSource.Url(DecentralandUrl.AuthSignature); - - var origin = new DappWeb3Authenticator( - new UnityAppWebBrowser(decentralandUrlsSource), - serverUrl, - signatureUrl, - identityCache, - web3AccountFactory, - new HashSet( - new[] - { - "eth_getBalance", - "eth_call", - "eth_blockNumber", - "eth_signTypedData_v4", - } - ) - ); - - originApi = origin; - originAuth = origin; - } + authApiWebSocket.JsonSerializer = new NewtonsoftJsonSerializer(new JsonSerializerSettings()); - public void Dispose() - { - originAuth.Dispose(); // Disposes both + authApiWebSocket.On("outcome", ProcessSignatureOutcomeMessage); } - public UniTask SendAsync(EthApiRequest request, CancellationToken ct) => - originApi.SendAsync(request, ct); - - public void AddVerificationListener(IVerifiedEthereumApi.VerificationDelegate callback) => - originApi.AddVerificationListener(callback); + if (authApiWebSocket.Connected) return; - public UniTask LoginAsync(CancellationToken ct) => - originAuth.LoginAsync(ct); + await authApiWebSocket + .ConnectAsync() + .AsUniTask() + .Timeout(TimeSpan.FromSeconds(TIMEOUT_SECONDS)); + } - public UniTask LogoutAsync(CancellationToken cancellationToken) => - originAuth.LogoutAsync(cancellationToken); + private bool IsReadOnly(EthApiRequest request) + { + foreach (string method in readOnlyMethods) + if (string.Equals(method, request.method, StringComparison.OrdinalIgnoreCase)) + return true; - public void SetVerificationListener(IWeb3VerifiedAuthenticator.VerificationDelegate? callback) => - originAuth.SetVerificationListener(callback); + return false; } } } diff --git a/Explorer/Assets/DCL/Web3/EthApiRequest.cs b/Explorer/Assets/DCL/Web3/EthApiRequest.cs index bdf0c3c90e..84a6d1dec5 100644 --- a/Explorer/Assets/DCL/Web3/EthApiRequest.cs +++ b/Explorer/Assets/DCL/Web3/EthApiRequest.cs @@ -5,6 +5,7 @@ namespace DCL.Web3 [Serializable] public struct EthApiRequest { + public long id; public string method; public object[] @params; } diff --git a/Explorer/Assets/DCL/Web3/EthApiResponse.cs b/Explorer/Assets/DCL/Web3/EthApiResponse.cs new file mode 100644 index 0000000000..9987377a8a --- /dev/null +++ b/Explorer/Assets/DCL/Web3/EthApiResponse.cs @@ -0,0 +1,9 @@ +namespace DCL.Web3 +{ + public struct EthApiResponse + { + public long id; + public string jsonrpc; + public object result; + } +} diff --git a/Explorer/Assets/DCL/Web3/EthApiResponse.cs.meta b/Explorer/Assets/DCL/Web3/EthApiResponse.cs.meta new file mode 100644 index 0000000000..ee4adab796 --- /dev/null +++ b/Explorer/Assets/DCL/Web3/EthApiResponse.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f4638c8b7a064622a9be25837283eb60 +timeCreated: 1740497937 \ No newline at end of file diff --git a/Explorer/Assets/DCL/Web3/IEthereumApi.cs b/Explorer/Assets/DCL/Web3/IEthereumApi.cs index 838285989a..a8784f060a 100644 --- a/Explorer/Assets/DCL/Web3/IEthereumApi.cs +++ b/Explorer/Assets/DCL/Web3/IEthereumApi.cs @@ -6,6 +6,6 @@ namespace DCL.Web3 { public interface IEthereumApi : IDisposable { - UniTask SendAsync(EthApiRequest request, CancellationToken ct); + UniTask SendAsync(EthApiRequest request, CancellationToken ct); } } diff --git a/Explorer/Assets/DCL/Web3/Web3.asmdef b/Explorer/Assets/DCL/Web3/Web3.asmdef index 9124f01d06..fff3027649 100644 --- a/Explorer/Assets/DCL/Web3/Web3.asmdef +++ b/Explorer/Assets/DCL/Web3/Web3.asmdef @@ -13,7 +13,8 @@ "GUID:9428ba407ade34e4ebcf01cca4669d4b", "GUID:41931db0c299475fa4e0dcb60b48d656", "GUID:75edf6fa50ff464395ca31529ea14d25", - "GUID:3205adaaf7e904f1a9425faef1a6b323" + "GUID:3205adaaf7e904f1a9425faef1a6b323", + "GUID:8322ea9340a544c59ddc56d4793eac74" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/Explorer/Assets/Scripts/Global/Dynamic/BootstrapContainer.cs b/Explorer/Assets/Scripts/Global/Dynamic/BootstrapContainer.cs index 772dfe32d3..eb6e0479ff 100644 --- a/Explorer/Assets/Scripts/Global/Dynamic/BootstrapContainer.cs +++ b/Explorer/Assets/Scripts/Global/Dynamic/BootstrapContainer.cs @@ -1,4 +1,5 @@ using Arch.Core; +using CommunicationData.URLHelpers; using Cysharp.Threading.Tasks; using DCL.AssetsProvision; using DCL.Browser; @@ -208,26 +209,33 @@ private static IAnalyticsService CreateSegmentAnalyticsOrFallbackToDebug(Analyti return new DebugAnalyticsService(); } - private static ( - IVerifiedEthereumApi web3VerifiedAuthenticator, - IWeb3VerifiedAuthenticator web3Authenticator - ) + private static (IVerifiedEthereumApi web3VerifiedAuthenticator, IWeb3VerifiedAuthenticator web3Authenticator) CreateWeb3Dependencies( DynamicSceneLoaderSettings sceneLoaderSettings, IWeb3AccountFactory web3AccountFactory, IWeb3IdentityCache identityCache, IWebBrowser webBrowser, BootstrapContainer container, - IDecentralandUrlsSource decentralandUrlsSource - ) + IDecentralandUrlsSource decentralandUrlsSource) { + URLBuilder urlBuilder = new URLBuilder(); + urlBuilder.AppendDomain(URLDomain.FromString(decentralandUrlsSource.Url(DecentralandUrl.ApiRpc))); + + // TODO: this is a temporary thing until we solve the network in a better way + // if (decentralandUrlsSource.Environment == DecentralandEnvironment.Org) + // urlBuilder.AppendPath(new URLPath("mainnet")); + // else + urlBuilder.AppendPath(new URLPath("sepolia")); + var dappWeb3Authenticator = new DappWeb3Authenticator( webBrowser, - decentralandUrlsSource.Url(DecentralandUrl.ApiAuth), - decentralandUrlsSource.Url(DecentralandUrl.AuthSignature), + URLAddress.FromString(decentralandUrlsSource.Url(DecentralandUrl.ApiAuth)), + URLAddress.FromString(decentralandUrlsSource.Url(DecentralandUrl.AuthSignatureWebApp)), + urlBuilder.Build(), identityCache, web3AccountFactory, - new HashSet(sceneLoaderSettings.Web3WhitelistMethods) + new HashSet(sceneLoaderSettings.Web3WhitelistMethods), + new HashSet(sceneLoaderSettings.Web3ReadOnlyMethods) ); IWeb3VerifiedAuthenticator coreWeb3Authenticator = new ProxyVerifiedWeb3Authenticator(dappWeb3Authenticator, identityCache); diff --git a/Explorer/Assets/Scripts/Global/Dynamic/DynamicSceneLoaderSettings.asset b/Explorer/Assets/Scripts/Global/Dynamic/DynamicSceneLoaderSettings.asset index 485ead5911..50176d0745 100644 --- a/Explorer/Assets/Scripts/Global/Dynamic/DynamicSceneLoaderSettings.asset +++ b/Explorer/Assets/Scripts/Global/Dynamic/DynamicSceneLoaderSettings.asset @@ -12,7 +12,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 51b5f8f4ed664a47a7204b6a65d2d742, type: 3} m_Name: DynamicSceneLoaderSettings m_EditorClassIdentifier: - k__BackingField: 0 k__BackingField: - https://sdk-team-cdn.decentraland.org/ipfs/goerli-plaza-main - https://sdk-team-cdn.decentraland.org/ipfs/goerli-plaza-main-latest @@ -43,3 +42,17 @@ MonoBehaviour: - eth_signTypedData_v4 - eth_getCode - personal_sign + k__BackingField: + - eth_getTransactionReceipt + - eth_estimateGas + - eth_call + - eth_getStorageAt + - eth_blockNumber + - eth_gasPrice + - eth_protocolVersion + - net_version + - web3_sha3 + - web3_clientVersion + - eth_getTransactionCount + - eth_getBlockByNumber + - eth_getCode diff --git a/Explorer/Assets/Scripts/Global/Dynamic/DynamicSceneLoaderSettings.cs b/Explorer/Assets/Scripts/Global/Dynamic/DynamicSceneLoaderSettings.cs index e5dddb71a5..b08a1f375b 100644 --- a/Explorer/Assets/Scripts/Global/Dynamic/DynamicSceneLoaderSettings.cs +++ b/Explorer/Assets/Scripts/Global/Dynamic/DynamicSceneLoaderSettings.cs @@ -11,6 +11,6 @@ public class DynamicSceneLoaderSettings : ScriptableObject { [field: SerializeField] public List Realms { get; private set; } [field: SerializeField] public List Web3WhitelistMethods { get; private set; } - + [field: SerializeField] public List Web3ReadOnlyMethods { get; private set; } } } diff --git a/Explorer/Assets/Scripts/SceneRuntime/Apis/Modules/Ethereums/EthereumApiWrapper.cs b/Explorer/Assets/Scripts/SceneRuntime/Apis/Modules/Ethereums/EthereumApiWrapper.cs index 203758d4d5..20f37f60ed 100644 --- a/Explorer/Assets/Scripts/SceneRuntime/Apis/Modules/Ethereums/EthereumApiWrapper.cs +++ b/Explorer/Assets/Scripts/SceneRuntime/Apis/Modules/Ethereums/EthereumApiWrapper.cs @@ -62,8 +62,9 @@ async UniTask RequestPersonalSignatureAsync(CancellationTok try { - string signature = await ethereumApi.SendAsync(new EthApiRequest + var response = await ethereumApi.SendAsync(new EthApiRequest { + id = Guid.NewGuid().GetHashCode(), method = "personal_sign", @params = new object[] { @@ -72,7 +73,7 @@ async UniTask RequestPersonalSignatureAsync(CancellationTok }, }, ct); - return new SignMessageResponse(hex, message, signature); + return new SignMessageResponse(hex, message, (string)response.result); } catch (Exception e) { @@ -96,8 +97,9 @@ async UniTask SendAndFormatAsync(double id, string { try { - object result = await ethereumApi.SendAsync(new EthApiRequest + var result = await ethereumApi.SendAsync(new EthApiRequest { + id = (long)id, method = method, @params = @params, }, ct); @@ -106,9 +108,9 @@ async UniTask SendAndFormatAsync(double id, string { jsonAnyResponse = JsonConvert.SerializeObject(new SendEthereumMessageResponse.Payload { - id = (long)id, - jsonrpc = "2.0", - result = result, + id = result.id, + jsonrpc = result.jsonrpc, + result = result.result, }), }; } From 42fd4788b85e45a68ef64e48acb9beb00cd1e303 Mon Sep 17 00:00:00 2001 From: Nicolas Lorusso Date: Wed, 26 Feb 2025 12:31:30 -0300 Subject: [PATCH 3/7] fix js processing, handling of specific methods & refactor of network --- .../DappWeb3Authenticator.Default.cs | 12 +--- .../Authenticators/DappWeb3Authenticator.cs | 63 ++++++++++++++++--- .../Global/Dynamic/BootstrapContainer.cs | 14 +---- .../Modules/Ethereums/EthereumApiWrapper.cs | 25 +++----- .../Ethereums/SendEthereumMessageResponse.cs | 8 --- .../Js/Modules/EthereumController.js | 3 +- 6 files changed, 73 insertions(+), 52 deletions(-) diff --git a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.Default.cs b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.Default.cs index 0fe7aef147..488d8b10ca 100644 --- a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.Default.cs +++ b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.Default.cs @@ -20,14 +20,7 @@ public Default(IWeb3IdentityCache identityCache, IDecentralandUrlsSource decentr { URLAddress authApiUrl = URLAddress.FromString(decentralandUrlsSource.Url(DecentralandUrl.ApiAuth)); URLAddress signatureUrl = URLAddress.FromString(decentralandUrlsSource.Url(DecentralandUrl.AuthSignatureWebApp)); - - URLBuilder urlBuilder = new URLBuilder(); - - const string NETWORK = "sepolia"; - - URLAddress rpcServerUrl = urlBuilder.AppendDomain(URLDomain.FromString(decentralandUrlsSource.Url(DecentralandUrl.ApiRpc))) - .AppendPath(new URLPath(NETWORK)) - .Build(); + URLDomain rpcServerUrl = URLDomain.FromString(decentralandUrlsSource.Url(DecentralandUrl.ApiRpc)); var origin = new DappWeb3Authenticator( new UnityAppWebBrowser(decentralandUrlsSource), @@ -61,7 +54,8 @@ public Default(IWeb3IdentityCache identityCache, IDecentralandUrlsSource decentr "eth_getTransactionCount", "eth_getBlockByNumber", "eth_getCode", - } + }, + decentralandUrlsSource.Environment ); originApi = origin; diff --git a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs index 741890cd59..ef80cb5422 100644 --- a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs +++ b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs @@ -1,6 +1,7 @@ using CommunicationData.URLHelpers; using Cysharp.Threading.Tasks; using DCL.Browser; +using DCL.Multiplayer.Connections.DecentralandUrls; using DCL.Web3.Abstract; using DCL.Web3.Chains; using DCL.Web3.Identities; @@ -21,19 +22,26 @@ public partial class DappWeb3Authenticator : IWeb3VerifiedAuthenticator, IVerifi { private const int TIMEOUT_SECONDS = 30; private const int RPC_BUFFER_SIZE = 50000; + private const string NETWORK_MAINNET = "mainnet"; + private const string NETWORK_SEPOLIA = "sepolia"; + private const string MAINNET_CHAIN_ID = "0x1"; + private const string SEPOLIA_CHAIN_ID = "0xaa36a7"; + private const string MAINNET_NET_VERSION = "1"; + private const string SEPOLIA_NET_VERSION = "11155111"; private readonly IWebBrowser webBrowser; private readonly URLAddress authApiUrl; private readonly URLAddress signatureWebAppUrl; - private readonly URLAddress rpcServerUrl; + private readonly URLDomain rpcServerUrl; private readonly IWeb3IdentityCache identityCache; private readonly IWeb3AccountFactory web3AccountFactory; private readonly HashSet whitelistMethods; private readonly HashSet readOnlyMethods; - + private readonly DecentralandEnvironment environment; // Allow only one web3 operation at a time private readonly SemaphoreSlim mutex = new (1, 1); private readonly byte[] rpcByteBuffer = new byte[RPC_BUFFER_SIZE]; + private readonly URLBuilder urlBuilder = new (); private int authApiPendingOperations; private int rpcPendingOperations; @@ -46,11 +54,12 @@ public partial class DappWeb3Authenticator : IWeb3VerifiedAuthenticator, IVerifi public DappWeb3Authenticator(IWebBrowser webBrowser, URLAddress authApiUrl, URLAddress signatureWebAppUrl, - URLAddress rpcServerUrl, + URLDomain rpcServerUrl, IWeb3IdentityCache identityCache, IWeb3AccountFactory web3AccountFactory, HashSet whitelistMethods, - HashSet readOnlyMethods) + HashSet readOnlyMethods, + DecentralandEnvironment environment) { this.webBrowser = webBrowser; this.authApiUrl = authApiUrl; @@ -60,6 +69,7 @@ public DappWeb3Authenticator(IWebBrowser webBrowser, this.web3AccountFactory = web3AccountFactory; this.whitelistMethods = whitelistMethods; this.readOnlyMethods = readOnlyMethods; + this.environment = environment; } public void Dispose() @@ -89,6 +99,32 @@ public async UniTask SendAsync(EthApiRequest request, Cancellati }; } + if (string.Equals(request.method, "eth_chainId")) + { + // TODO: this is a temporary thing until we solve the network in a better way + string chainId = environment == DecentralandEnvironment.Org ? MAINNET_CHAIN_ID : SEPOLIA_CHAIN_ID; + + return new EthApiResponse + { + id = request.id, + jsonrpc = "2.0", + result = chainId, + }; + } + + if (string.Equals(request.method, "net_version")) + { + // TODO: this is a temporary thing until we solve the network in a better way + string chainId = environment == DecentralandEnvironment.Org ? MAINNET_NET_VERSION : SEPOLIA_NET_VERSION; + + return new EthApiResponse + { + id = request.id, + jsonrpc = "2.0", + result = chainId, + }; + } + if (IsReadOnly(request)) return await SendWithoutConfirmationAsync(request, ct); @@ -196,7 +232,9 @@ private async UniTask SendWithoutConfirmationAsync(EthApiRequest await UniTask.SwitchToMainThread(ct); - await ConnectToRpcAsync(ct); + // TODO: this is a temporary thing until we solve the network in a better way (probably it should be parametrized) + string network = environment == DecentralandEnvironment.Org ? NETWORK_MAINNET : NETWORK_SEPOLIA; + await ConnectToRpcAsync(network, ct); var response = await RequestEthMethodWithoutSignature(request, ct) .Timeout(TimeSpan.FromSeconds(TIMEOUT_SECONDS)); @@ -233,12 +271,16 @@ private async UniTask DisconnectFromRpcAsync(CancellationToken ct) rpcWebSocket = null; } - private async UniTask ConnectToRpcAsync(CancellationToken ct) + private async UniTask ConnectToRpcAsync(string network, CancellationToken ct) { if (rpcWebSocket?.State == WebSocketState.Open) return; + urlBuilder.Clear(); + urlBuilder.AppendDomain(rpcServerUrl); + urlBuilder.AppendPath(new URLPath(network)); + rpcWebSocket = new ClientWebSocket(); - await rpcWebSocket.ConnectAsync(new Uri(rpcServerUrl), ct); + await rpcWebSocket.ConnectAsync(new Uri(urlBuilder.Build()), ct); } private async UniTask RequestEthMethodWithoutSignature(EthApiRequest request, CancellationToken ct) @@ -247,7 +289,7 @@ private async UniTask RequestEthMethodWithoutSignature(EthApiReq byte[] bytes = System.Text.Encoding.UTF8.GetBytes(reqJson); await rpcWebSocket!.SendAsync(bytes, WebSocketMessageType.Text, true, ct); - while (!ct.IsCancellationRequested && rpcWebSocket.State == WebSocketState.Open) + while (!ct.IsCancellationRequested && rpcWebSocket?.State == WebSocketState.Open) { WebSocketReceiveResult result = await rpcWebSocket.ReceiveAsync(rpcByteBuffer, ct); @@ -259,6 +301,11 @@ private async UniTask RequestEthMethodWithoutSignature(EthApiReq if (response.id == request.id) return response; } + else if (result.MessageType == WebSocketMessageType.Close) + { + await DisconnectFromRpcAsync(ct); + break; + } } throw new Web3Exception("Unexpected data received from rpc"); diff --git a/Explorer/Assets/Scripts/Global/Dynamic/BootstrapContainer.cs b/Explorer/Assets/Scripts/Global/Dynamic/BootstrapContainer.cs index eb6e0479ff..9d9e6c5e45 100644 --- a/Explorer/Assets/Scripts/Global/Dynamic/BootstrapContainer.cs +++ b/Explorer/Assets/Scripts/Global/Dynamic/BootstrapContainer.cs @@ -218,24 +218,16 @@ private static (IVerifiedEthereumApi web3VerifiedAuthenticator, IWeb3VerifiedAut BootstrapContainer container, IDecentralandUrlsSource decentralandUrlsSource) { - URLBuilder urlBuilder = new URLBuilder(); - urlBuilder.AppendDomain(URLDomain.FromString(decentralandUrlsSource.Url(DecentralandUrl.ApiRpc))); - - // TODO: this is a temporary thing until we solve the network in a better way - // if (decentralandUrlsSource.Environment == DecentralandEnvironment.Org) - // urlBuilder.AppendPath(new URLPath("mainnet")); - // else - urlBuilder.AppendPath(new URLPath("sepolia")); - var dappWeb3Authenticator = new DappWeb3Authenticator( webBrowser, URLAddress.FromString(decentralandUrlsSource.Url(DecentralandUrl.ApiAuth)), URLAddress.FromString(decentralandUrlsSource.Url(DecentralandUrl.AuthSignatureWebApp)), - urlBuilder.Build(), + URLDomain.FromString(decentralandUrlsSource.Url(DecentralandUrl.ApiRpc)), identityCache, web3AccountFactory, new HashSet(sceneLoaderSettings.Web3WhitelistMethods), - new HashSet(sceneLoaderSettings.Web3ReadOnlyMethods) + new HashSet(sceneLoaderSettings.Web3ReadOnlyMethods), + decentralandUrlsSource.Environment ); IWeb3VerifiedAuthenticator coreWeb3Authenticator = new ProxyVerifiedWeb3Authenticator(dappWeb3Authenticator, identityCache); diff --git a/Explorer/Assets/Scripts/SceneRuntime/Apis/Modules/Ethereums/EthereumApiWrapper.cs b/Explorer/Assets/Scripts/SceneRuntime/Apis/Modules/Ethereums/EthereumApiWrapper.cs index 20f37f60ed..744b5c305d 100644 --- a/Explorer/Assets/Scripts/SceneRuntime/Apis/Modules/Ethereums/EthereumApiWrapper.cs +++ b/Explorer/Assets/Scripts/SceneRuntime/Apis/Modules/Ethereums/EthereumApiWrapper.cs @@ -54,6 +54,11 @@ public object TryPay(decimal amount, string currency, string toAddress) [PublicAPI("Used by StreamingAssets/Js/Modules/EthereumController.js")] public object SignMessage(string message) { + signMessageCancellationToken = signMessageCancellationToken.SafeRestart(); + + return RequestPersonalSignatureAsync(signMessageCancellationToken.Token) + .ToDisconnectedPromise(); + async UniTask RequestPersonalSignatureAsync(CancellationToken ct) { await UniTask.SwitchToMainThread(); @@ -83,16 +88,14 @@ async UniTask RequestPersonalSignatureAsync(CancellationTok return new SignMessageResponse(hex, message, string.Empty); } } - - signMessageCancellationToken = signMessageCancellationToken.SafeRestart(); - - return RequestPersonalSignatureAsync(signMessageCancellationToken.Token) - .ToDisconnectedPromise(); } [PublicAPI("Used by StreamingAssets/Js/Modules/EthereumController.js")] public object SendAsync(double id, string method, string jsonParams) { + return SendAndFormatAsync(id, method, JsonConvert.DeserializeObject(jsonParams) ?? Array.Empty(), sendCancellationToken.Token) + .ToDisconnectedPromise(); + async UniTask SendAndFormatAsync(double id, string method, object[] @params, CancellationToken ct) { try @@ -106,12 +109,7 @@ async UniTask SendAndFormatAsync(double id, string return new SendEthereumMessageResponse { - jsonAnyResponse = JsonConvert.SerializeObject(new SendEthereumMessageResponse.Payload - { - id = result.id, - jsonrpc = result.jsonrpc, - result = result.result, - }), + jsonAnyResponse = JsonConvert.SerializeObject(result), }; } catch (Exception e) @@ -120,7 +118,7 @@ async UniTask SendAndFormatAsync(double id, string return new SendEthereumMessageResponse { - jsonAnyResponse = JsonConvert.SerializeObject(new SendEthereumMessageResponse.Payload + jsonAnyResponse = JsonConvert.SerializeObject(new EthApiResponse { id = (long)id, jsonrpc = "2.0", @@ -129,9 +127,6 @@ async UniTask SendAndFormatAsync(double id, string }; } } - - return SendAndFormatAsync(id, method, JsonConvert.DeserializeObject(jsonParams) ?? Array.Empty(), sendCancellationToken.Token) - .ToDisconnectedPromise(); } } } diff --git a/Explorer/Assets/Scripts/SceneRuntime/Apis/Modules/Ethereums/SendEthereumMessageResponse.cs b/Explorer/Assets/Scripts/SceneRuntime/Apis/Modules/Ethereums/SendEthereumMessageResponse.cs index e3ec1c36fd..70008b9b1b 100644 --- a/Explorer/Assets/Scripts/SceneRuntime/Apis/Modules/Ethereums/SendEthereumMessageResponse.cs +++ b/Explorer/Assets/Scripts/SceneRuntime/Apis/Modules/Ethereums/SendEthereumMessageResponse.cs @@ -6,13 +6,5 @@ namespace SceneRuntime.Apis.Modules.Ethereums public struct SendEthereumMessageResponse { public string jsonAnyResponse; - - [Serializable] - public struct Payload - { - public long id; - public string jsonrpc; - public object result; - } } } diff --git a/Explorer/Assets/StreamingAssets/Js/Modules/EthereumController.js b/Explorer/Assets/StreamingAssets/Js/Modules/EthereumController.js index e6511e86e7..36f68b14da 100644 --- a/Explorer/Assets/StreamingAssets/Js/Modules/EthereumController.js +++ b/Explorer/Assets/StreamingAssets/Js/Modules/EthereumController.js @@ -13,7 +13,8 @@ module.exports.signMessage = async function (message) { } module.exports.sendAsync = async function (message) { - const result = await UnityEthereumApi.SendAsync(message.id, message.method, message.jsonParams) + const response = await UnityEthereumApi.SendAsync(message.id, message.method, message.jsonParams); + const result = JSON.parse(response.jsonAnyResponse); return result; } From 287529dd14f0c113da4deeec053dcbb56be0079f Mon Sep 17 00:00:00 2001 From: Nicolas Lorusso Date: Wed, 26 Feb 2025 13:02:45 -0300 Subject: [PATCH 4/7] fix js --- .../Assets/StreamingAssets/Js/Modules/EthereumController.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Explorer/Assets/StreamingAssets/Js/Modules/EthereumController.js b/Explorer/Assets/StreamingAssets/Js/Modules/EthereumController.js index 36f68b14da..ccd39021bc 100644 --- a/Explorer/Assets/StreamingAssets/Js/Modules/EthereumController.js +++ b/Explorer/Assets/StreamingAssets/Js/Modules/EthereumController.js @@ -13,9 +13,7 @@ module.exports.signMessage = async function (message) { } module.exports.sendAsync = async function (message) { - const response = await UnityEthereumApi.SendAsync(message.id, message.method, message.jsonParams); - const result = JSON.parse(response.jsonAnyResponse); - return result; + return await UnityEthereumApi.SendAsync(message.id, message.method, message.jsonParams); } module.exports.getUserAccount = async function (message) { From adb2bdbd986a11f37f4b655af110452de85ba303 Mon Sep 17 00:00:00 2001 From: Nicolas Lorusso Date: Wed, 26 Feb 2025 15:35:58 -0300 Subject: [PATCH 5/7] set the hasConnectedWeb3 to true during auth screen --- .../AuthenticationScreenFlow/AuthenticationScreenController.cs | 2 ++ Explorer/Assets/DCL/Profiles/Components/Profile.cs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Explorer/Assets/DCL/AuthenticationScreenFlow/AuthenticationScreenController.cs b/Explorer/Assets/DCL/AuthenticationScreenFlow/AuthenticationScreenController.cs index 5d1bc3812a..e5bb481a0a 100644 --- a/Explorer/Assets/DCL/AuthenticationScreenFlow/AuthenticationScreenController.cs +++ b/Explorer/Assets/DCL/AuthenticationScreenFlow/AuthenticationScreenController.cs @@ -321,6 +321,8 @@ private async UniTask FetchProfileAsync(CancellationToken ct) // When the profile was already in cache, for example your previous account after logout, we need to ensure that all systems related to the profile will update profile.IsDirty = true; + // Catalysts don't manipulate this field, so at this point we assume that the user is connected to web3 + profile.HasConnectedWeb3 = true; profileNameLabel!.Value = profile.Name; characterPreviewController?.Initialize(profile.Avatar); } diff --git a/Explorer/Assets/DCL/Profiles/Components/Profile.cs b/Explorer/Assets/DCL/Profiles/Components/Profile.cs index 5b4c4ac9c7..920e4d6117 100644 --- a/Explorer/Assets/DCL/Profiles/Components/Profile.cs +++ b/Explorer/Assets/DCL/Profiles/Components/Profile.cs @@ -62,7 +62,7 @@ internal set } } - public bool HasConnectedWeb3 { get; internal set; } + public bool HasConnectedWeb3 { get; set; } public string? Description { get; set; } public int TutorialStep { get; set; } public string? Email { get; internal set; } From 17e786f52426973c5f74d9afe0b210ed8c8c773b Mon Sep 17 00:00:00 2001 From: Nicolas Lorusso Date: Wed, 26 Feb 2025 17:47:55 -0300 Subject: [PATCH 6/7] fix tests --- .../Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs index ef80cb5422..1713b02ade 100644 --- a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs +++ b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs @@ -236,7 +236,7 @@ private async UniTask SendWithoutConfirmationAsync(EthApiRequest string network = environment == DecentralandEnvironment.Org ? NETWORK_MAINNET : NETWORK_SEPOLIA; await ConnectToRpcAsync(network, ct); - var response = await RequestEthMethodWithoutSignature(request, ct) + var response = await RequestEthMethodWithoutSignatureAsync(request, ct) .Timeout(TimeSpan.FromSeconds(TIMEOUT_SECONDS)); if (rpcPendingOperations <= 1) @@ -283,7 +283,7 @@ private async UniTask ConnectToRpcAsync(string network, CancellationToken ct) await rpcWebSocket.ConnectAsync(new Uri(urlBuilder.Build()), ct); } - private async UniTask RequestEthMethodWithoutSignature(EthApiRequest request, CancellationToken ct) + private async UniTask RequestEthMethodWithoutSignatureAsync(EthApiRequest request, CancellationToken ct) { string reqJson = JsonConvert.SerializeObject(request); byte[] bytes = System.Text.Encoding.UTF8.GetBytes(reqJson); From 8750909095cde93128a36f627305eca4b06c8371 Mon Sep 17 00:00:00 2001 From: Nicolas Lorusso Date: Thu, 27 Feb 2025 14:52:41 -0300 Subject: [PATCH 7/7] resolve network to methods --- .../Authenticators/DappWeb3Authenticator.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs index 1713b02ade..667b86c0ce 100644 --- a/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs +++ b/Explorer/Assets/DCL/Web3/Authenticators/DappWeb3Authenticator.cs @@ -101,8 +101,7 @@ public async UniTask SendAsync(EthApiRequest request, Cancellati if (string.Equals(request.method, "eth_chainId")) { - // TODO: this is a temporary thing until we solve the network in a better way - string chainId = environment == DecentralandEnvironment.Org ? MAINNET_CHAIN_ID : SEPOLIA_CHAIN_ID; + string chainId = GetChainId(); return new EthApiResponse { @@ -114,14 +113,13 @@ public async UniTask SendAsync(EthApiRequest request, Cancellati if (string.Equals(request.method, "net_version")) { - // TODO: this is a temporary thing until we solve the network in a better way - string chainId = environment == DecentralandEnvironment.Org ? MAINNET_NET_VERSION : SEPOLIA_NET_VERSION; + string netVersion = GetNetVersion(); return new EthApiResponse { id = request.id, jsonrpc = "2.0", - result = chainId, + result = netVersion, }; } @@ -232,9 +230,7 @@ private async UniTask SendWithoutConfirmationAsync(EthApiRequest await UniTask.SwitchToMainThread(ct); - // TODO: this is a temporary thing until we solve the network in a better way (probably it should be parametrized) - string network = environment == DecentralandEnvironment.Org ? NETWORK_MAINNET : NETWORK_SEPOLIA; - await ConnectToRpcAsync(network, ct); + await ConnectToRpcAsync(GetNetworkId(), ct); var response = await RequestEthMethodWithoutSignatureAsync(request, ct) .Timeout(TimeSpan.FromSeconds(TIMEOUT_SECONDS)); @@ -472,5 +468,17 @@ private bool IsReadOnly(EthApiRequest request) return false; } + + private string GetNetVersion() => + // TODO: this is a temporary thing until we solve the network in a better way + environment == DecentralandEnvironment.Org ? MAINNET_NET_VERSION : SEPOLIA_NET_VERSION; + + private string GetChainId() => + // TODO: this is a temporary thing until we solve the network in a better way + environment == DecentralandEnvironment.Org ? MAINNET_CHAIN_ID : SEPOLIA_CHAIN_ID; + + private string GetNetworkId() => + // TODO: this is a temporary thing until we solve the network in a better way (probably it should be parametrized) + environment == DecentralandEnvironment.Org ? NETWORK_MAINNET : NETWORK_SEPOLIA; } }