From b5356375f1185121a4798a0efbf3e91696a9b59f Mon Sep 17 00:00:00 2001 From: robGG1997 Date: Thu, 14 Sep 2023 11:30:11 +0300 Subject: [PATCH 01/15] Revert "automated duplicating imported samples modifications (#595)" This reverts commit a5558941827a91df51e2dd200648dcd13a1231c8. --- .github/workflows/duplicate_sample.yaml | 28 ------------------- duplicate_package_sample.sh | 14 ---------- .../Samples~/Web3.Unity/Prefabs.meta | 8 ------ .../Samples~/Web3.Unity/Scenes.meta | 8 ------ .../Samples~/Web3.Unity/Scripts.meta | 8 ------ .../Samples~/Web3.Unity/Sprites.meta | 8 ------ 6 files changed, 74 deletions(-) delete mode 100644 .github/workflows/duplicate_sample.yaml delete mode 100644 duplicate_package_sample.sh delete mode 100644 src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Prefabs.meta delete mode 100644 src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Scenes.meta delete mode 100644 src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Scripts.meta delete mode 100644 src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Sprites.meta diff --git a/.github/workflows/duplicate_sample.yaml b/.github/workflows/duplicate_sample.yaml deleted file mode 100644 index 793149e56..000000000 --- a/.github/workflows/duplicate_sample.yaml +++ /dev/null @@ -1,28 +0,0 @@ -name: Duplicate-Package-Sample - -on: - push: - branches: - - main - -jobs: - duplicate: - name: duplicate package sample - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@v2 - - - name: Run duplicate script file - run: bash ${GITHUB_WORKSPACE}/duplicate_package_sample.sh - - - name: Commit and Push - run: | - git config --global user.email $git_email - git config --global user.name "${{ github.actor }}" - git add ./src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/. -f - git diff-index --cached --quiet HEAD || git commit -m "Auto-duplicate Package Samples" - git push - env: - git_email: "${{ github.actor }}@users.noreply.github.com" \ No newline at end of file diff --git a/duplicate_package_sample.sh b/duplicate_package_sample.sh deleted file mode 100644 index f3a677742..000000000 --- a/duplicate_package_sample.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -# Specify the source directory and the destination directory -SOURCE_DIRECTORY="src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0-pre001/Web3.Unity Samples/" -DESTINATION_DIRECTORY="src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/" - -# clear destination directory first -rm -r "$DESTINATION_DIRECTORY" - -# Copy source to the destination -cp -r "$SOURCE_DIRECTORY" "$DESTINATION_DIRECTORY" - -#add all modified files -git add "src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/." -f \ No newline at end of file diff --git a/src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Prefabs.meta b/src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Prefabs.meta deleted file mode 100644 index 5b35daff8..000000000 --- a/src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Prefabs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 45a04d2621549e3458049c6c447c3941 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Scenes.meta b/src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Scenes.meta deleted file mode 100644 index 5488a62ed..000000000 --- a/src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Scenes.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: f39f8dda9616349489a7803d7e0aff30 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Scripts.meta b/src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Scripts.meta deleted file mode 100644 index 9a1e8b8d4..000000000 --- a/src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Scripts.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: eaef630f284e6e04a88b5939e0eb1cc5 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Sprites.meta b/src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Sprites.meta deleted file mode 100644 index 7175ee89d..000000000 --- a/src/UnityPackages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Sprites.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 0c06517aee7843642a091a67c72b1379 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: From 882ff14853124ddf37b851f45024bbf4ebc90d12 Mon Sep 17 00:00:00 2001 From: robGG1997 Date: Thu, 9 Nov 2023 10:50:28 +0300 Subject: [PATCH 02/15] setup for IOS build based WC's feedback --- src/UnitySampleProject/Assets/Editor.meta | 8 ++++++ .../Assets/Editor/PreBuild.cs | 28 +++++++++++++++++++ .../Assets/Editor/PreBuild.cs.meta | 11 ++++++++ .../Scripts/Scenes/ExistingWalletLogin.cs | 23 +++++++++++++++ src/UnitySampleProject/Assets/link.xml | 9 ++++++ 5 files changed, 79 insertions(+) create mode 100644 src/UnitySampleProject/Assets/Editor.meta create mode 100644 src/UnitySampleProject/Assets/Editor/PreBuild.cs create mode 100644 src/UnitySampleProject/Assets/Editor/PreBuild.cs.meta diff --git a/src/UnitySampleProject/Assets/Editor.meta b/src/UnitySampleProject/Assets/Editor.meta new file mode 100644 index 000000000..143e467af --- /dev/null +++ b/src/UnitySampleProject/Assets/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 896dc5f43fe9f474e99214d59fdbb5cb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/UnitySampleProject/Assets/Editor/PreBuild.cs b/src/UnitySampleProject/Assets/Editor/PreBuild.cs new file mode 100644 index 000000000..579b9a12a --- /dev/null +++ b/src/UnitySampleProject/Assets/Editor/PreBuild.cs @@ -0,0 +1,28 @@ + +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEngine; + +[InitializeOnLoad] +public static class PreBuild +{ + static PreBuild() + { + if (SessionState.GetBool(nameof(PreBuild), false)) + { + return; + } + + Debug.Log("Running Pre Build Operations..."); + +#if UNITY_IOS + PlayerSettings.stripEngineCode = false; + PlayerSettings.SetManagedStrippingLevel(BuildTargetGroup.iOS, ManagedStrippingLevel.Disabled); + + Debug.Log("Code managed stripping Level Disabled for IOS"); +#endif + + SessionState.SetBool(nameof(PreBuild), true); + } +} \ No newline at end of file diff --git a/src/UnitySampleProject/Assets/Editor/PreBuild.cs.meta b/src/UnitySampleProject/Assets/Editor/PreBuild.cs.meta new file mode 100644 index 000000000..0523d02ec --- /dev/null +++ b/src/UnitySampleProject/Assets/Editor/PreBuild.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c25a4f1f3c6d184799c85aca0d9984d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs index 0c9ed5b43..deac71696 100644 --- a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs +++ b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs @@ -14,11 +14,16 @@ using UnityEngine; using UnityEngine.Assertions; using UnityEngine.Networking; +using UnityEngine.Scripting; using UnityEngine.Serialization; using UnityEngine.UI; using WalletConnectSharp.Core; +using WalletConnectSharp.Core.Controllers; +using WalletConnectSharp.Events; +using WalletConnectSharp.Events.Model; using WalletConnectSharp.Sign.Models; using WalletConnectSharp.Sign.Models.Engine; +using WalletConnectSharp.Sign.Models.Engine.Methods; /// /// Login using an existing wallet using Wallet Connect. @@ -266,4 +271,22 @@ private void SessionApproved(SessionStruct session) Debug.Log($"{session.Topic} Approved"); } + +#if !UNITY_MONO + [Preserve] + void SetupAOT() + { + // Reference all required models + // This is required so AOT code is generated for these generic functions + var historyFactory = new JsonRpcHistoryFactory(null); + Debug.Log(historyFactory.JsonRpcHistoryOfType().GetType().FullName); + Debug.Log(historyFactory.JsonRpcHistoryOfType().GetType().FullName); + Debug.Log(historyFactory.JsonRpcHistoryOfType().GetType().FullName); + Debug.Log(historyFactory.JsonRpcHistoryOfType().GetType().FullName); + Debug.Log(historyFactory.JsonRpcHistoryOfType().GetType().FullName); + Debug.Log(historyFactory.JsonRpcHistoryOfType().GetType().FullName); + EventManager>.InstanceOf(null).PropagateEvent(null, null); + throw new InvalidOperationException("This method is only for AOT code generation."); + } +#endif } \ No newline at end of file diff --git a/src/UnitySampleProject/Assets/link.xml b/src/UnitySampleProject/Assets/link.xml index 6bdac4ded..269e4a760 100644 --- a/src/UnitySampleProject/Assets/link.xml +++ b/src/UnitySampleProject/Assets/link.xml @@ -1,4 +1,13 @@ + + + + + + + + + \ No newline at end of file From 1f58ff3caf8adcb54f4ba0dfee4ce1442eb54bc8 Mon Sep 17 00:00:00 2001 From: robGG1997 Date: Mon, 13 Nov 2023 16:57:52 +0300 Subject: [PATCH 03/15] added custom websocket implementation --- .../WalletConnectConfig.cs | 3 + .../WalletConnectCustomProvider.cs | 1 + .../Scenes/SampleLogin.unity | 45 +++ .../Web3.Unity Samples/Scripts/Samples.asmdef | 1 + .../Scripts/Scenes/ExistingWalletLogin.cs | 3 + .../Scripts/WalletConnectWebSocket.cs | 330 ++++++++++++++++++ .../Scripts/WalletConnectWebSocket.cs.meta | 11 + .../Scripts/WalletConnectWebSocketBuilder.cs | 30 ++ .../WalletConnectWebSocketBuilder.cs.meta | 11 + src/UnitySampleProject/Packages/manifest.json | 3 +- 10 files changed, 437 insertions(+), 1 deletion(-) create mode 100644 src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocket.cs create mode 100644 src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocket.cs.meta create mode 100644 src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocketBuilder.cs create mode 100644 src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocketBuilder.cs.meta diff --git a/src/ChainSafe.Gaming.WalletConnect/WalletConnectConfig.cs b/src/ChainSafe.Gaming.WalletConnect/WalletConnectConfig.cs index 1993aceb3..1c08f593a 100644 --- a/src/ChainSafe.Gaming.WalletConnect/WalletConnectConfig.cs +++ b/src/ChainSafe.Gaming.WalletConnect/WalletConnectConfig.cs @@ -3,6 +3,7 @@ using ChainSafe.Gaming.WalletConnect.Models; using Newtonsoft.Json; using WalletConnectSharp.Core; +using WalletConnectSharp.Network.Interfaces; using WalletConnectSharp.Sign.Models; using WalletConnectSharp.Sign.Models.Engine; @@ -65,6 +66,8 @@ public class WalletConnectConfig /// public string BaseContext { get; set; } + public IConnectionBuilder ConnectionBuilder { get; set; } + /// /// Chain of wallet to connect to, eg - Goerli. /// Used for specifying required namespaces for connecting to wallet. diff --git a/src/ChainSafe.Gaming.WalletConnect/WalletConnectCustomProvider.cs b/src/ChainSafe.Gaming.WalletConnect/WalletConnectCustomProvider.cs index 3a3b2646d..9f988acfe 100644 --- a/src/ChainSafe.Gaming.WalletConnect/WalletConnectCustomProvider.cs +++ b/src/ChainSafe.Gaming.WalletConnect/WalletConnectCustomProvider.cs @@ -98,6 +98,7 @@ private async Task Initialize() ProjectId = config.ProjectId, Storage = BuildStorage(), BaseContext = config.BaseContext, + ConnectionBuilder = config.ConnectionBuilder, }); await Core.Start(); diff --git a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scenes/SampleLogin.unity b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scenes/SampleLogin.unity index 0f766adb4..33e386d1c 100644 --- a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scenes/SampleLogin.unity +++ b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scenes/SampleLogin.unity @@ -2681,6 +2681,7 @@ MonoBehaviour: redirectToWalletToggle: {fileID: 981405684} loginButton: {fileID: 787012150} rememberMeToggle: {fileID: 724612376} + builder: {fileID: 1810619523} projectId: f4bff60eb260841f46b1c77588cd8acb projectName: Web3.Unity baseContext: unity-game @@ -3418,6 +3419,50 @@ MonoBehaviour: m_PersistentCalls: m_Calls: [] m_IsOn: 1 +--- !u!1 &1810619522 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1810619524} + - component: {fileID: 1810619523} + m_Layer: 0 + m_Name: WebSocketBuilder + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1810619523 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1810619522} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6dc71ee83d1afff469d88de475a57994, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!4 &1810619524 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1810619522} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1880270952 GameObject: m_ObjectHideFlags: 0 diff --git a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Samples.asmdef b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Samples.asmdef index 975c32f4a..c1c74fe40 100644 --- a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Samples.asmdef +++ b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Samples.asmdef @@ -4,6 +4,7 @@ "references": [ "GUID:5426c6b788696eb4c88f4198b59839eb", "GUID:a37273c0f1cc99640b9cce89c64b02d0", + "GUID:04376767bc1f3b428aefa3d20743e819", "GUID:6055be8ebefd69e48b49212b09b47b2f" ], "includePlatforms": [], diff --git a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs index deac71696..61dac3981 100644 --- a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs +++ b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs @@ -37,6 +37,8 @@ public class ExistingWalletLogin : Login [SerializeField] public Button loginButton; [SerializeField] private Toggle rememberMeToggle; + + [SerializeField] private WalletConnectWebSocketBuilder builder; [Header("Wallet Connect")] [SerializeField] private string projectId; @@ -185,6 +187,7 @@ private void BuildWalletConnectConfig() ProjectId = projectId, ProjectName = projectName, BaseContext = baseContext, + ConnectionBuilder = builder, Chain = chain, Metadata = metadata, // try and get saved value diff --git a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocket.cs b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocket.cs new file mode 100644 index 000000000..5a04f33b9 --- /dev/null +++ b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocket.cs @@ -0,0 +1,330 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using NativeWebSocket; +using Newtonsoft.Json; +using UnityEngine; +using WalletConnectSharp.Common; +using WalletConnectSharp.Common.Logging; +using WalletConnectSharp.Common.Utils; +using WalletConnectSharp.Events; +using WalletConnectSharp.Events.Model; +using WalletConnectSharp.Network; +using WalletConnectSharp.Network.Models; +using WalletConnectSharp.Network.Websocket; + +public class WalletConnectWebSocket : MonoBehaviour, IJsonRpcConnection, IModule +{ + WebSocket _socket; + + private EventDelegator _delegator; + + [SerializeField] private string url; + private bool _registering; + private Guid _context; + private TaskCompletionSource beginConnect; + private bool begunConnect; + + /// + /// The Open timeout + /// + public TimeSpan OpenTimeout = TimeSpan.FromSeconds(60); + + /// + /// The Url to connect to + /// + public string Url + { + get { return url; } + set => url = value; + } + + public bool IsPaused { get; private set; } + + /// + /// The name of this websocket connection module + /// + public string Name + { + get { return "websocket-connection"; } + } + + /// + /// The context string of this Websocket module + /// + public string Context + { + get { return _context.ToString(); } + } + + /// + /// The EventDelegator this Websocket connection module is using + /// + public EventDelegator Events + { + get { return _delegator; } + } + + /// + /// Whether this websocket connection is connected + /// + public bool Connected + { + get + { + bool connected = _socket != null && _socket.State == WebSocketState.Open; + WCLogger.Log("Websocket Connected State: " + connected); + return connected; + } + } + + /// + /// Whether this websocket connection is currently connecting + /// + public bool Connecting + { + get { return _registering; } + } + + public async void Dispose() + { + if (Connected) + { + WCLogger.Log("Socket is being disposed, cleanup"); + await Close(); + } + } + + public async Task Open() + { + await Register(this.url); + } + + public Task Open(T options) + { + if (typeof(string).IsAssignableFrom(typeof(T))) + { + return Register(options as string); + } + + return Open(); + } + + public async Task Close() + { + if (_socket == null) throw new IOException("Connection already closed"); + + await _socket.Close(); + + WCLogger.Log("Closing websocket due to Close() being called"); + OnClose(WebSocketCloseCode.Normal); + } + + public async Task SendRequest(IJsonRpcRequest requestPayload, object context) + { + if (_socket == null) _socket = await Register(this.url); + + try + { + Debug.Log($"[WCWebSocket-{_context}] Sending request {JsonConvert.SerializeObject(requestPayload)}"); + + await _socket.SendText(JsonConvert.SerializeObject(requestPayload)); + } + catch (Exception e) + { + Debug.LogError(e); + + OnError(requestPayload, e); + } + } + + public async Task SendResult(IJsonRpcResult responsePayload, object context) + { + if (_socket == null) _socket = await Register(this.url); + + try + { + await _socket.SendText(JsonConvert.SerializeObject(responsePayload)); + } + catch (Exception e) + { + OnError(responsePayload, e); + } + } + + public async Task SendError(IJsonRpcError errorPayload, object context) + { + if (_socket == null) _socket = await Register(this.url); + + try + { + await _socket.SendText(JsonConvert.SerializeObject(errorPayload)); + } + catch (Exception e) + { + OnError(errorPayload, e); + } + } + + private void Awake() + { + _context = Guid.NewGuid(); + _delegator = new EventDelegator(this); + } + + private async Task Register(string _url) + { + if (!Validation.IsWsUrl(_url)) + { + throw new ArgumentException("Provided URL is not compatible with WebSocket connection: " + _url); + } + + if (_registering) + { + TaskCompletionSource registeringTask = + new TaskCompletionSource(TaskCreationOptions.None); + + Events.ListenForOnce(WebsocketConnectionEvents.RegisterError, + delegate(object sender, GenericEvent @event) + { + registeringTask.SetException(@event.EventData); + }); + + Events.ListenForOnce(WebsocketConnectionEvents.Open, + delegate(object sender, GenericEvent @event) + { + registeringTask.SetResult(@event.EventData); + }); + + await registeringTask.Task; + + return registeringTask.Task.Result; + } + + this.url = _url; + this._registering = true; + + try + { + _socket = new WebSocket(this.url); + //_socket = new WebsocketClient(new Uri(_url)); + + await StartWebsocket(_socket).WithTimeout(OpenTimeout, "Unavailable WS RPC url at " + this.url); + OnOpen(_socket); + return _socket; + } + catch (Exception e) + { + Events.Trigger(WebsocketConnectionEvents.RegisterError, e); + + WCLogger.Log($"Calling close due to exception {e}"); + OnClose(WebSocketCloseCode.ServerError); + + throw; + } + } + + private Task StartWebsocket(WebSocket socket) + { + this._socket = socket; + begunConnect = false; + beginConnect = new TaskCompletionSource(); + + return beginConnect.Task; + } + + private void OnOpen(WebSocket socket) + { + socket.OnMessage += OnPayload; + socket.OnClose += OnDisconnect; + + this._registering = false; + Events.Trigger(WebsocketConnectionEvents.Open, _socket); + } + + private void OnDisconnect(WebSocketCloseCode obj) + { + if (obj != WebSocketCloseCode.Normal) Events.Trigger(WebsocketConnectionEvents.Error, obj); + + WCLogger.Log("Socket closing due to Disconnect event from socket"); + OnClose(obj); + } + + private void OnClose(WebSocketCloseCode obj) + { + if (this._socket == null) return; + + //_socket.Dispose(); + this._socket = null; + this._registering = false; + Events.Trigger(WebsocketConnectionEvents.Close, obj); + } + + private void OnPayload(byte[] data) + { + string json = System.Text.Encoding.UTF8.GetString(data); + + Debug.Log($"[WCWebSocket-{_context}] Got payload {json}"); + + if (string.IsNullOrWhiteSpace(json)) return; + + Debug.Log($"[WCWebsocket-{_context}] Triggering payload event with JSON {json}"); + + Events.Trigger(WebsocketConnectionEvents.Payload, json); + } + + void Update() + { +#if !UNITY_WEBGL || UNITY_EDITOR + if (_socket != null) _socket.DispatchMessageQueue(); +#endif + } + + private async void LateUpdate() + { + if (begunConnect || beginConnect == null || beginConnect.Task.IsCompleted) return; + if (_socket == null) return; + + begunConnect = true; + this._socket.OnOpen += SocketOnOnOpen; + try + { + await this._socket.Connect(); + } + catch (Exception e) + { + this.beginConnect.TrySetException(e); + } + } + + private void SocketOnOnOpen() + { + beginConnect.TrySetResult(true); + } + + private string addressNotFoundError = "getaddrinfo ENOTFOUND"; + private string connectionRefusedError = "connect ECONNREFUSED"; + + private void OnError(IJsonRpcPayload ogPayload, Exception e) + { + var exception = e.Message.Contains(addressNotFoundError) || e.Message.Contains(connectionRefusedError) + ? new IOException("Unavailable WS RPC url at " + this.url) + : e; + + var message = exception.Message; + var payload = new JsonRpcResponse(ogPayload.Id, + new Error() { Code = exception.HResult, Data = null, Message = message }, default(T)); + + //Trigger the payload event, converting the new JsonRpcResponse object to JSON string + Events.Trigger(WebsocketConnectionEvents.Payload, JsonConvert.SerializeObject(payload)); + + Debug.LogError(e); + } + + private void OnApplicationPause(bool pauseStatus) + { + IsPaused = pauseStatus; + } +} \ No newline at end of file diff --git a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocket.cs.meta b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocket.cs.meta new file mode 100644 index 000000000..bbbb0ff6d --- /dev/null +++ b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocket.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9d74bd135ec0b7948af61a6966dfcb40 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocketBuilder.cs b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocketBuilder.cs new file mode 100644 index 000000000..8ecb35951 --- /dev/null +++ b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocketBuilder.cs @@ -0,0 +1,30 @@ +using System.Collections; +using System.Collections.Generic; +using System.Threading.Tasks; +using ChainSafe.Gaming.Evm.Unity; +using UnityEngine; +using WalletConnectSharp.Network; +using WalletConnectSharp.Network.Interfaces; + +public class WalletConnectWebSocketBuilder : MonoBehaviour, IConnectionBuilder +{ + public Task CreateConnection(string url) + { + TaskCompletionSource taskCompletionSource = + new TaskCompletionSource(); + + Dispatcher.Instance().Enqueue(() => + { + DontDestroyOnLoad(gameObject); + + Debug.Log("Building websocket with URL " + url); + var websocket = gameObject.AddComponent(); + websocket.Url = url; + + taskCompletionSource.TrySetResult(websocket); + }); + + + return taskCompletionSource.Task; + } +} diff --git a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocketBuilder.cs.meta b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocketBuilder.cs.meta new file mode 100644 index 000000000..f250cf2cb --- /dev/null +++ b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocketBuilder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6dc71ee83d1afff469d88de475a57994 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/UnitySampleProject/Packages/manifest.json b/src/UnitySampleProject/Packages/manifest.json index 38ca12ddc..eb28483d4 100644 --- a/src/UnitySampleProject/Packages/manifest.json +++ b/src/UnitySampleProject/Packages/manifest.json @@ -1,5 +1,6 @@ { "dependencies": { + "com.endel.nativewebsocket": "https://github.com/endel/NativeWebSocket.git#upm", "com.unity.collab-proxy": "2.0.7", "com.unity.feature.2d": "1.0.0", "com.unity.ide.rider": "3.0.25", @@ -12,8 +13,8 @@ "com.unity.ugui": "1.0.0", "com.unity.visualscripting": "1.8.0", "io.chainsafe.web3-unity": "file:../../../Packages/io.chainsafe.web3-unity", - "io.chainsafe.web3-unity.web3auth": "file:../../../Packages/io.chainsafe.web3-unity.web3auth", "io.chainsafe.web3-unity.lootboxes": "file:../../../Packages/io.chainsafe.web3-unity.lootboxes", + "io.chainsafe.web3-unity.web3auth": "file:../../../Packages/io.chainsafe.web3-unity.web3auth", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", "com.unity.modules.animation": "1.0.0", From 9d735b300aa9e7ede50d30fd128b094d4500b0dc Mon Sep 17 00:00:00 2001 From: robGG1997 Date: Tue, 14 Nov 2023 14:24:06 +0300 Subject: [PATCH 04/15] refactor and cleanup --- .../Runtime/Scripts/WalletConnect.meta | 8 + .../WalletConnect/WalletConnectWebSocket.cs | 357 ++++++++ .../WalletConnectWebSocket.cs.meta | 0 .../WalletConnectWebSocketBuilder.cs | 42 + .../WalletConnectWebSocketBuilder.cs.meta | 0 .../Scripts/WalletConnect/WebSocket.meta | 8 + .../WalletConnect/WebSocket/WebSocket.cs | 848 ++++++++++++++++++ .../WalletConnect/WebSocket/WebSocket.cs.meta | 11 + .../WalletConnect/WebSocket/WebSocket.jslib | 333 +++++++ .../WebSocket/WebSocket.jslib.meta | 32 + .../WebSocket/endel.nativewebsocket.asmdef | 3 + .../endel.nativewebsocket.asmdef.meta | 7 + .../Scenes/SampleLogin.unity | 45 - .../Web3.Unity Samples/Scripts/Samples.asmdef | 1 - .../Scripts/Scenes/ExistingWalletLogin.cs | 22 +- .../Scripts/WalletConnectWebSocket.cs | 330 ------- .../Scripts/WalletConnectWebSocketBuilder.cs | 30 - src/UnitySampleProject/Packages/manifest.json | 3 +- 18 files changed, 1669 insertions(+), 411 deletions(-) create mode 100644 Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect.meta create mode 100644 Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WalletConnectWebSocket.cs rename {src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts => Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect}/WalletConnectWebSocket.cs.meta (100%) create mode 100644 Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WalletConnectWebSocketBuilder.cs rename {src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts => Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect}/WalletConnectWebSocketBuilder.cs.meta (100%) create mode 100644 Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket.meta create mode 100644 Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.cs create mode 100644 Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.cs.meta create mode 100644 Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.jslib create mode 100644 Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.jslib.meta create mode 100644 Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/endel.nativewebsocket.asmdef create mode 100644 Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/endel.nativewebsocket.asmdef.meta delete mode 100644 src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocket.cs delete mode 100644 src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocketBuilder.cs diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect.meta b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect.meta new file mode 100644 index 000000000..a2de9aa6e --- /dev/null +++ b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 336f7d2de35c9f74191d05fa2158e27b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WalletConnectWebSocket.cs b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WalletConnectWebSocket.cs new file mode 100644 index 000000000..c29c83657 --- /dev/null +++ b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WalletConnectWebSocket.cs @@ -0,0 +1,357 @@ +#if !UNITY_2022_1_OR_NEWER +using System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using NativeWebSocket; +using Newtonsoft.Json; +using UnityEngine; +using WalletConnectSharp.Common; +using WalletConnectSharp.Common.Logging; +using WalletConnectSharp.Common.Utils; +using WalletConnectSharp.Events; +using WalletConnectSharp.Events.Model; +using WalletConnectSharp.Network; +using WalletConnectSharp.Network.Models; +using WalletConnectSharp.Network.Websocket; + +namespace ChainSafe.Gaming.WalletConnect +{ + /// + /// Custom web socket implementation for Wallet Connect, based on NativeWebSocket https://github.com/endel/NativeWebSocket. + /// + public class WalletConnectWebSocket : MonoBehaviour, IJsonRpcConnection, IModule + { + private const string AddressNotFoundError = "getaddrinfo ENOTFOUND"; + private const string ConnectionRefusedError = "connect ECONNREFUSED"; + + private WebSocket webSocket; + + private EventDelegator delegator; + + private string url; + + private bool registering; + + private Guid contextId; + + private TaskCompletionSource beginConnection; + + private bool connectionStarted; + + private TimeSpan connectionTimeout = TimeSpan.FromSeconds(60); + + /// + /// The Url to connect to + /// + public string Url + { + get => url; + set => url = value; + } + + public bool IsPaused { get; private set; } + + /// + /// The name of this websocket connection module + /// + public string Name => "websocket-connection"; + + /// + /// The context string of this Websocket module + /// + public string Context => contextId.ToString(); + + /// + /// The EventDelegator this Websocket connection module is using + /// + public EventDelegator Events => delegator; + + /// + /// Whether this websocket connection is connected + /// + public bool Connected + { + get + { + bool connected = webSocket != null && webSocket.State == WebSocketState.Open; + WCLogger.Log("Websocket Connected State: " + connected); + return connected; + } + } + + /// + /// Whether this websocket connection is currently connecting + /// + public bool Connecting => registering; + + public async void Dispose() + { + if (Connected) + { + WCLogger.Log("Socket is being disposed, cleanup"); + await Close(); + } + } + + /// + /// Open Socket. + /// + public async Task Open() + { + await Register(this.url); + } + + /// + /// Open Socket. + /// + /// Socket options. + /// Options type. + /// + public Task Open(T options) + { + if (typeof(string).IsAssignableFrom(typeof(T))) + { + return Register(options as string); + } + + return Open(); + } + + /// + /// Close socket. + /// + /// Throws if socket is already closed. + public async Task Close() + { + if (webSocket == null) throw new IOException("Connection already closed"); + + await webSocket.Close(); + + WCLogger.Log("Closing websocket due to Close() being called"); + OnClose(WebSocketCloseCode.Normal); + } + + /// + /// Send request using socket. + /// + /// Payload to be sent. + /// Socket context. + /// Payload Type. + public async Task SendRequest(IJsonRpcRequest requestPayload, object context) + { + if (webSocket == null) webSocket = await Register(this.url); + + try + { + Debug.Log($"[WCWebSocket-{contextId}] Sending request {JsonConvert.SerializeObject(requestPayload)}"); + + await webSocket.SendText(JsonConvert.SerializeObject(requestPayload)); + } + catch (Exception e) + { + Debug.LogError(e); + + OnError(requestPayload, e); + } + } + + /// + /// Send result using socket. + /// + /// Payload to be sent. + /// Socket context. + /// Payload Type. + public async Task SendResult(IJsonRpcResult responsePayload, object context) + { + if (webSocket == null) webSocket = await Register(this.url); + + try + { + await webSocket.SendText(JsonConvert.SerializeObject(responsePayload)); + } + catch (Exception e) + { + OnError(responsePayload, e); + } + } + + /// + /// Send error using socket. + /// + /// Payload to be sent. + /// Socket context. + public async Task SendError(IJsonRpcError errorPayload, object context) + { + if (webSocket == null) webSocket = await Register(this.url); + + try + { + await webSocket.SendText(JsonConvert.SerializeObject(errorPayload)); + } + catch (Exception e) + { + OnError(errorPayload, e); + } + } + + private void Awake() + { + contextId = Guid.NewGuid(); + delegator = new EventDelegator(this); + } + + private async Task Register(string newUrl) + { + if (!Validation.IsWsUrl(newUrl)) + { + throw new ArgumentException("Provided URL is not compatible with WebSocket connection: " + newUrl); + } + + if (registering) + { + TaskCompletionSource registeringTask = + new TaskCompletionSource(TaskCreationOptions.None); + + Events.ListenForOnce(WebsocketConnectionEvents.RegisterError, + delegate(object sender, GenericEvent @event) + { + registeringTask.SetException(@event.EventData); + }); + + Events.ListenForOnce(WebsocketConnectionEvents.Open, + delegate(object sender, GenericEvent @event) + { + registeringTask.SetResult(@event.EventData); + }); + + await registeringTask.Task; + + return registeringTask.Task.Result; + } + + this.url = newUrl; + this.registering = true; + + try + { + webSocket = new WebSocket(this.url); + //_socket = new WebsocketClient(new Uri(_url)); + + await StartWebsocket(webSocket).WithTimeout(connectionTimeout, "Unavailable WS RPC url at " + this.url); + OnOpen(webSocket); + return webSocket; + } + catch (Exception e) + { + Events.Trigger(WebsocketConnectionEvents.RegisterError, e); + + WCLogger.Log($"Calling close due to exception {e}"); + OnClose(WebSocketCloseCode.ServerError); + + throw; + } + } + + private Task StartWebsocket(WebSocket socket) + { + this.webSocket = socket; + connectionStarted = false; + beginConnection = new TaskCompletionSource(); + + return beginConnection.Task; + } + + private void OnOpen(WebSocket socket) + { + socket.OnMessage += OnPayload; + socket.OnClose += OnDisconnect; + + this.registering = false; + Events.Trigger(WebsocketConnectionEvents.Open, this.webSocket); + } + + private void OnDisconnect(WebSocketCloseCode code) + { + if (code != WebSocketCloseCode.Normal) Events.Trigger(WebsocketConnectionEvents.Error, code); + + WCLogger.Log("Socket closing due to Disconnect event from socket"); + OnClose(code); + } + + private void OnClose(WebSocketCloseCode code) + { + if (this.webSocket == null) return; + + //_socket.Dispose(); + this.webSocket = null; + this.registering = false; + Events.Trigger(WebsocketConnectionEvents.Close, code); + } + + private void OnPayload(byte[] data) + { + string json = Encoding.UTF8.GetString(data); + + Debug.Log($"[WCWebSocket-{contextId}] Got payload {json}"); + + if (string.IsNullOrWhiteSpace(json)) return; + + Debug.Log($"[WCWebsocket-{contextId}] Triggering payload event with JSON {json}"); + + Events.Trigger(WebsocketConnectionEvents.Payload, json); + } + + void Update() + { +#if !UNITY_WEBGL || UNITY_EDITOR + if (webSocket != null) webSocket.DispatchMessageQueue(); +#endif + } + + private async void LateUpdate() + { + if (connectionStarted || beginConnection == null || beginConnection.Task.IsCompleted) return; + if (webSocket == null) return; + + connectionStarted = true; + this.webSocket.OnOpen += SocketOnOnOpen; + try + { + await this.webSocket.Connect(); + } + catch (Exception e) + { + this.beginConnection.TrySetException(e); + } + } + + private void SocketOnOnOpen() + { + beginConnection.TrySetResult(true); + } + + private void OnError(IJsonRpcPayload payload, Exception e) + { + var exception = e.Message.Contains(AddressNotFoundError) || e.Message.Contains(ConnectionRefusedError) + ? new IOException("Unavailable WS RPC url at " + this.url) + : e; + + string message = exception.Message; + + var response = new JsonRpcResponse(payload.Id, + new Error() { Code = exception.HResult, Data = null, Message = message }, default(T)); + + //Trigger the payload event, converting the new JsonRpcResponse object to JSON string + Events.Trigger(WebsocketConnectionEvents.Payload, JsonConvert.SerializeObject(response)); + + Debug.LogError(e); + } + + private void OnApplicationPause(bool isPaused) + { + IsPaused = isPaused; + } + } +} +#endif \ No newline at end of file diff --git a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocket.cs.meta b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WalletConnectWebSocket.cs.meta similarity index 100% rename from src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocket.cs.meta rename to Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WalletConnectWebSocket.cs.meta diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WalletConnectWebSocketBuilder.cs b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WalletConnectWebSocketBuilder.cs new file mode 100644 index 000000000..e0b1f1f41 --- /dev/null +++ b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WalletConnectWebSocketBuilder.cs @@ -0,0 +1,42 @@ +#if !UNITY_2022_1_OR_NEWER +using System.Threading.Tasks; +using ChainSafe.Gaming.Evm.Unity; +using UnityEngine; +using WalletConnectSharp.Network; +using WalletConnectSharp.Network.Interfaces; + +namespace ChainSafe.Gaming.WalletConnect +{ + /// + /// This is a custom connection builder for Wallet Connect. + /// We need this because of Unity's IL2CPP build code stripping and reachability issue. + /// For version 2022 and above this issue has been fixed by Unity as stated here https://blog.unity.com/engine-platform/il2cpp-full-generic-sharing-in-unity-2022-1-beta so this custom implementation isn't needed. + /// + public class WalletConnectWebSocketBuilder : MonoBehaviour, IConnectionBuilder + { + /// + /// Create WebSocket connection for Wallet Connect. + /// + /// + /// Created connection. + public Task CreateConnection(string url) + { + TaskCompletionSource taskCompletionSource = + new TaskCompletionSource(); + + Dispatcher.Instance().Enqueue(() => + { + Debug.Log("Building websocket with URL " + url); + var websocket = gameObject.AddComponent(); + websocket.Url = url; + + taskCompletionSource.TrySetResult(websocket); + }); + + + return taskCompletionSource.Task; + } + } +} + +#endif \ No newline at end of file diff --git a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocketBuilder.cs.meta b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WalletConnectWebSocketBuilder.cs.meta similarity index 100% rename from src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocketBuilder.cs.meta rename to Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WalletConnectWebSocketBuilder.cs.meta diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket.meta b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket.meta new file mode 100644 index 000000000..f6b669be0 --- /dev/null +++ b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3430bfcc4c3b3944b91c5be04ddd9dd2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.cs b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.cs new file mode 100644 index 000000000..503cf4363 --- /dev/null +++ b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.cs @@ -0,0 +1,848 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.WebSockets; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +using AOT; +using System.Runtime.InteropServices; +using UnityEngine; +using System.Collections; + +public class MainThreadUtil : MonoBehaviour +{ + public static MainThreadUtil Instance { get; private set; } + public static SynchronizationContext synchronizationContext { get; private set; } + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + public static void Setup() + { + Instance = new GameObject("MainThreadUtil") + .AddComponent(); + synchronizationContext = SynchronizationContext.Current; + } + + public static void Run(IEnumerator waitForUpdate) + { + synchronizationContext.Post(_ => Instance.StartCoroutine( + waitForUpdate), null); + } + + void Awake() + { + gameObject.hideFlags = HideFlags.HideAndDontSave; + DontDestroyOnLoad(gameObject); + } +} + +public class WaitForUpdate : CustomYieldInstruction +{ + public override bool keepWaiting + { + get { return false; } + } + + public MainThreadAwaiter GetAwaiter() + { + var awaiter = new MainThreadAwaiter(); + MainThreadUtil.Run(CoroutineWrapper(this, awaiter)); + return awaiter; + } + + public class MainThreadAwaiter : INotifyCompletion + { + Action continuation; + + public bool IsCompleted { get; set; } + + public void GetResult() { } + + public void Complete() + { + IsCompleted = true; + continuation?.Invoke(); + } + + void INotifyCompletion.OnCompleted(Action continuation) + { + this.continuation = continuation; + } + } + + public static IEnumerator CoroutineWrapper(IEnumerator theWorker, MainThreadAwaiter awaiter) + { + yield return theWorker; + awaiter.Complete(); + } +} + +namespace NativeWebSocket +{ + public delegate void WebSocketOpenEventHandler(); + public delegate void WebSocketMessageEventHandler(byte[] data); + public delegate void WebSocketErrorEventHandler(string errorMsg); + public delegate void WebSocketCloseEventHandler(WebSocketCloseCode closeCode); + + public enum WebSocketCloseCode + { + /* Do NOT use NotSet - it's only purpose is to indicate that the close code cannot be parsed. */ + NotSet = 0, + Normal = 1000, + Away = 1001, + ProtocolError = 1002, + UnsupportedData = 1003, + Undefined = 1004, + NoStatus = 1005, + Abnormal = 1006, + InvalidData = 1007, + PolicyViolation = 1008, + TooBig = 1009, + MandatoryExtension = 1010, + ServerError = 1011, + TlsHandshakeFailure = 1015 + } + + public enum WebSocketState + { + Connecting, + Open, + Closing, + Closed + } + + public interface IWebSocket + { + event WebSocketOpenEventHandler OnOpen; + event WebSocketMessageEventHandler OnMessage; + event WebSocketErrorEventHandler OnError; + event WebSocketCloseEventHandler OnClose; + + WebSocketState State { get; } + } + + public static class WebSocketHelpers + { + public static WebSocketCloseCode ParseCloseCodeEnum(int closeCode) + { + + if (WebSocketCloseCode.IsDefined(typeof(WebSocketCloseCode), closeCode)) + { + return (WebSocketCloseCode)closeCode; + } + else + { + return WebSocketCloseCode.Undefined; + } + + } + + public static WebSocketException GetErrorMessageFromCode(int errorCode, Exception inner) + { + switch (errorCode) + { + case -1: + return new WebSocketUnexpectedException("WebSocket instance not found.", inner); + case -2: + return new WebSocketInvalidStateException("WebSocket is already connected or in connecting state.", inner); + case -3: + return new WebSocketInvalidStateException("WebSocket is not connected.", inner); + case -4: + return new WebSocketInvalidStateException("WebSocket is already closing.", inner); + case -5: + return new WebSocketInvalidStateException("WebSocket is already closed.", inner); + case -6: + return new WebSocketInvalidStateException("WebSocket is not in open state.", inner); + case -7: + return new WebSocketInvalidArgumentException("Cannot close WebSocket. An invalid code was specified or reason is too long.", inner); + default: + return new WebSocketUnexpectedException("Unknown error.", inner); + } + } + } + + public class WebSocketException : Exception + { + public WebSocketException() { } + public WebSocketException(string message) : base(message) { } + public WebSocketException(string message, Exception inner) : base(message, inner) { } + } + + public class WebSocketUnexpectedException : WebSocketException + { + public WebSocketUnexpectedException() { } + public WebSocketUnexpectedException(string message) : base(message) { } + public WebSocketUnexpectedException(string message, Exception inner) : base(message, inner) { } + } + + public class WebSocketInvalidArgumentException : WebSocketException + { + public WebSocketInvalidArgumentException() { } + public WebSocketInvalidArgumentException(string message) : base(message) { } + public WebSocketInvalidArgumentException(string message, Exception inner) : base(message, inner) { } + } + + public class WebSocketInvalidStateException : WebSocketException + { + public WebSocketInvalidStateException() { } + public WebSocketInvalidStateException(string message) : base(message) { } + public WebSocketInvalidStateException(string message, Exception inner) : base(message, inner) { } + } + + public class WaitForBackgroundThread + { + public ConfiguredTaskAwaitable.ConfiguredTaskAwaiter GetAwaiter() + { + return Task.Run(() => { }).ConfigureAwait(false).GetAwaiter(); + } + } + +#if UNITY_WEBGL && !UNITY_EDITOR + + /// + /// WebSocket class bound to JSLIB. + /// + public class WebSocket : IWebSocket { + + /* WebSocket JSLIB functions */ + [DllImport ("__Internal")] + public static extern int WebSocketConnect (int instanceId); + + [DllImport ("__Internal")] + public static extern int WebSocketClose (int instanceId, int code, string reason); + + [DllImport ("__Internal")] + public static extern int WebSocketSend (int instanceId, byte[] dataPtr, int dataLength); + + [DllImport ("__Internal")] + public static extern int WebSocketSendText (int instanceId, string message); + + [DllImport ("__Internal")] + public static extern int WebSocketGetState (int instanceId); + + protected int instanceId; + + public event WebSocketOpenEventHandler OnOpen; + public event WebSocketMessageEventHandler OnMessage; + public event WebSocketErrorEventHandler OnError; + public event WebSocketCloseEventHandler OnClose; + + public WebSocket (string url, Dictionary headers = null) { + if (!WebSocketFactory.isInitialized) { + WebSocketFactory.Initialize (); + } + + int instanceId = WebSocketFactory.WebSocketAllocate (url); + WebSocketFactory.instances.Add (instanceId, this); + + this.instanceId = instanceId; + } + + public WebSocket (string url, string subprotocol, Dictionary headers = null) { + if (!WebSocketFactory.isInitialized) { + WebSocketFactory.Initialize (); + } + + int instanceId = WebSocketFactory.WebSocketAllocate (url); + WebSocketFactory.instances.Add (instanceId, this); + + WebSocketFactory.WebSocketAddSubProtocol(instanceId, subprotocol); + + this.instanceId = instanceId; + } + + public WebSocket (string url, List subprotocols, Dictionary headers = null) { + if (!WebSocketFactory.isInitialized) { + WebSocketFactory.Initialize (); + } + + int instanceId = WebSocketFactory.WebSocketAllocate (url); + WebSocketFactory.instances.Add (instanceId, this); + + foreach (string subprotocol in subprotocols) { + WebSocketFactory.WebSocketAddSubProtocol(instanceId, subprotocol); + } + + this.instanceId = instanceId; + } + + ~WebSocket () { + WebSocketFactory.HandleInstanceDestroy (this.instanceId); + } + + public int GetInstanceId () { + return this.instanceId; + } + + public Task Connect () { + int ret = WebSocketConnect (this.instanceId); + + if (ret < 0) + throw WebSocketHelpers.GetErrorMessageFromCode (ret, null); + + return Task.CompletedTask; + } + + public void CancelConnection () { + if (State == WebSocketState.Open) + Close (WebSocketCloseCode.Abnormal); + } + + public Task Close (WebSocketCloseCode code = WebSocketCloseCode.Normal, string reason = null) { + int ret = WebSocketClose (this.instanceId, (int) code, reason); + + if (ret < 0) + throw WebSocketHelpers.GetErrorMessageFromCode (ret, null); + + return Task.CompletedTask; + } + + public Task Send (byte[] data) { + int ret = WebSocketSend (this.instanceId, data, data.Length); + + if (ret < 0) + throw WebSocketHelpers.GetErrorMessageFromCode (ret, null); + + return Task.CompletedTask; + } + + public Task SendText (string message) { + int ret = WebSocketSendText (this.instanceId, message); + + if (ret < 0) + throw WebSocketHelpers.GetErrorMessageFromCode (ret, null); + + return Task.CompletedTask; + } + + public WebSocketState State { + get { + int state = WebSocketGetState (this.instanceId); + + if (state < 0) + throw WebSocketHelpers.GetErrorMessageFromCode (state, null); + + switch (state) { + case 0: + return WebSocketState.Connecting; + + case 1: + return WebSocketState.Open; + + case 2: + return WebSocketState.Closing; + + case 3: + return WebSocketState.Closed; + + default: + return WebSocketState.Closed; + } + } + } + + public void DelegateOnOpenEvent () { + this.OnOpen?.Invoke (); + } + + public void DelegateOnMessageEvent (byte[] data) { + this.OnMessage?.Invoke (data); + } + + public void DelegateOnErrorEvent (string errorMsg) { + this.OnError?.Invoke (errorMsg); + } + + public void DelegateOnCloseEvent (int closeCode) { + this.OnClose?.Invoke (WebSocketHelpers.ParseCloseCodeEnum (closeCode)); + } + + } + +#else + + public class WebSocket : IWebSocket + { + public event WebSocketOpenEventHandler OnOpen; + public event WebSocketMessageEventHandler OnMessage; + public event WebSocketErrorEventHandler OnError; + public event WebSocketCloseEventHandler OnClose; + + private Uri uri; + private Dictionary headers; + private List subprotocols; + private ClientWebSocket m_Socket = new ClientWebSocket(); + + private CancellationTokenSource m_TokenSource; + private CancellationToken m_CancellationToken; + + private readonly object OutgoingMessageLock = new object(); + private readonly object IncomingMessageLock = new object(); + + private bool isSending = false; + private List> sendBytesQueue = new List>(); + private List> sendTextQueue = new List>(); + + public WebSocket(string url, Dictionary headers = null) + { + uri = new Uri(url); + + if (headers == null) + { + this.headers = new Dictionary(); + } + else + { + this.headers = headers; + } + + subprotocols = new List(); + + string protocol = uri.Scheme; + if (!protocol.Equals("ws") && !protocol.Equals("wss")) + throw new ArgumentException("Unsupported protocol: " + protocol); + } + + public WebSocket(string url, string subprotocol, Dictionary headers = null) + { + uri = new Uri(url); + + if (headers == null) + { + this.headers = new Dictionary(); + } + else + { + this.headers = headers; + } + + subprotocols = new List {subprotocol}; + + string protocol = uri.Scheme; + if (!protocol.Equals("ws") && !protocol.Equals("wss")) + throw new ArgumentException("Unsupported protocol: " + protocol); + } + + public WebSocket(string url, List subprotocols, Dictionary headers = null) + { + uri = new Uri(url); + + if (headers == null) + { + this.headers = new Dictionary(); + } + else + { + this.headers = headers; + } + + this.subprotocols = subprotocols; + + string protocol = uri.Scheme; + if (!protocol.Equals("ws") && !protocol.Equals("wss")) + throw new ArgumentException("Unsupported protocol: " + protocol); + } + + public void CancelConnection() + { + m_TokenSource?.Cancel(); + } + + public async Task Connect() + { + try + { + m_TokenSource = new CancellationTokenSource(); + m_CancellationToken = m_TokenSource.Token; + + m_Socket = new ClientWebSocket(); + + foreach (var header in headers) + { + m_Socket.Options.SetRequestHeader(header.Key, header.Value); + } + + foreach (string subprotocol in subprotocols) { + m_Socket.Options.AddSubProtocol(subprotocol); + } + + await m_Socket.ConnectAsync(uri, m_CancellationToken); + OnOpen?.Invoke(); + + await Receive(); + } + catch (Exception ex) + { + OnError?.Invoke(ex.Message); + OnClose?.Invoke(WebSocketCloseCode.Abnormal); + } + finally + { + if (m_Socket != null) + { + m_TokenSource.Cancel(); + m_Socket.Dispose(); + } + } + } + + public WebSocketState State + { + get + { + switch (m_Socket.State) + { + case System.Net.WebSockets.WebSocketState.Connecting: + return WebSocketState.Connecting; + + case System.Net.WebSockets.WebSocketState.Open: + return WebSocketState.Open; + + case System.Net.WebSockets.WebSocketState.CloseSent: + case System.Net.WebSockets.WebSocketState.CloseReceived: + return WebSocketState.Closing; + + case System.Net.WebSockets.WebSocketState.Closed: + return WebSocketState.Closed; + + default: + return WebSocketState.Closed; + } + } + } + + public Task Send(byte[] bytes) + { + // return m_Socket.SendAsync(buffer, WebSocketMessageType.Binary, true, CancellationToken.None); + return SendMessage(sendBytesQueue, WebSocketMessageType.Binary, new ArraySegment(bytes)); + } + + public Task SendText(string message) + { + var encoded = Encoding.UTF8.GetBytes(message); + + // m_Socket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None); + return SendMessage(sendTextQueue, WebSocketMessageType.Text, new ArraySegment(encoded, 0, encoded.Length)); + } + + private async Task SendMessage(List> queue, WebSocketMessageType messageType, ArraySegment buffer) + { + // Return control to the calling method immediately. + // await Task.Yield (); + + // Make sure we have data. + if (buffer.Count == 0) + { + return; + } + + // The state of the connection is contained in the context Items dictionary. + bool sending; + + lock (OutgoingMessageLock) + { + sending = isSending; + + // If not, we are now. + if (!isSending) + { + isSending = true; + } + } + + if (!sending) + { + // Lock with a timeout, just in case. + if (!Monitor.TryEnter(m_Socket, 1000)) + { + // If we couldn't obtain exclusive access to the socket in one second, something is wrong. + await m_Socket.CloseAsync(WebSocketCloseStatus.InternalServerError, string.Empty, m_CancellationToken); + return; + } + + try + { + // Send the message synchronously. + var t = m_Socket.SendAsync(buffer, messageType, true, m_CancellationToken); + t.Wait(m_CancellationToken); + } + finally + { + Monitor.Exit(m_Socket); + } + + // Note that we've finished sending. + lock (OutgoingMessageLock) + { + isSending = false; + } + + // Handle any queued messages. + await HandleQueue(queue, messageType); + } + else + { + // Add the message to the queue. + lock (OutgoingMessageLock) + { + queue.Add(buffer); + } + } + } + + private async Task HandleQueue(List> queue, WebSocketMessageType messageType) + { + var buffer = new ArraySegment(); + lock (OutgoingMessageLock) + { + // Check for an item in the queue. + if (queue.Count > 0) + { + // Pull it off the top. + buffer = queue[0]; + queue.RemoveAt(0); + } + } + + // Send that message. + if (buffer.Count > 0) + { + await SendMessage(queue, messageType, buffer); + } + } + + private List m_MessageList = new List(); + + // simple dispatcher for queued messages. + public void DispatchMessageQueue() + { + if (m_MessageList.Count == 0) + { + return; + } + + List messageListCopy; + + lock (IncomingMessageLock) + { + messageListCopy = new List(m_MessageList); + m_MessageList.Clear(); + } + + var len = messageListCopy.Count; + for (int i = 0; i < len; i++) + { + OnMessage?.Invoke(messageListCopy[i]); + } + } + + public async Task Receive() + { + WebSocketCloseCode closeCode = WebSocketCloseCode.Abnormal; + await new WaitForBackgroundThread(); + + ArraySegment buffer = new ArraySegment(new byte[8192]); + try + { + while (m_Socket.State == System.Net.WebSockets.WebSocketState.Open) + { + WebSocketReceiveResult result = null; + + using (var ms = new MemoryStream()) + { + do + { + result = await m_Socket.ReceiveAsync(buffer, m_CancellationToken); + ms.Write(buffer.Array, buffer.Offset, result.Count); + } + while (!result.EndOfMessage); + + ms.Seek(0, SeekOrigin.Begin); + + if (result.MessageType == WebSocketMessageType.Text) + { + lock (IncomingMessageLock) + { + m_MessageList.Add(ms.ToArray()); + } + + //using (var reader = new StreamReader(ms, Encoding.UTF8)) + //{ + // string message = reader.ReadToEnd(); + // OnMessage?.Invoke(this, new MessageEventArgs(message)); + //} + } + else if (result.MessageType == WebSocketMessageType.Binary) + { + lock (IncomingMessageLock) + { + m_MessageList.Add(ms.ToArray()); + } + } + else if (result.MessageType == WebSocketMessageType.Close) + { + await Close(); + closeCode = WebSocketHelpers.ParseCloseCodeEnum((int)result.CloseStatus); + break; + } + } + } + } + catch (Exception) + { + m_TokenSource.Cancel(); + } + finally + { + await new WaitForUpdate(); + OnClose?.Invoke(closeCode); + } + } + + public async Task Close() + { + if (State == WebSocketState.Open) + { + await m_Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, m_CancellationToken); + } + } + } +#endif + + /// + /// Factory + /// + + /// + /// Class providing static access methods to work with JSLIB WebSocket or WebSocketSharp interface + /// + public static class WebSocketFactory + { + +#if UNITY_WEBGL && !UNITY_EDITOR + /* Map of websocket instances */ + public static Dictionary instances = new Dictionary (); + + /* Delegates */ + public delegate void OnOpenCallback (int instanceId); + public delegate void OnMessageCallback (int instanceId, System.IntPtr msgPtr, int msgSize); + public delegate void OnErrorCallback (int instanceId, System.IntPtr errorPtr); + public delegate void OnCloseCallback (int instanceId, int closeCode); + + /* WebSocket JSLIB callback setters and other functions */ + [DllImport ("__Internal")] + public static extern int WebSocketAllocate (string url); + + [DllImport ("__Internal")] + public static extern int WebSocketAddSubProtocol (int instanceId, string subprotocol); + + [DllImport ("__Internal")] + public static extern void WebSocketFree (int instanceId); + + [DllImport ("__Internal")] + public static extern void WebSocketSetOnOpen (OnOpenCallback callback); + + [DllImport ("__Internal")] + public static extern void WebSocketSetOnMessage (OnMessageCallback callback); + + [DllImport ("__Internal")] + public static extern void WebSocketSetOnError (OnErrorCallback callback); + + [DllImport ("__Internal")] + public static extern void WebSocketSetOnClose (OnCloseCallback callback); + + /* If callbacks was initialized and set */ + public static bool isInitialized = false; + + /* + * Initialize WebSocket callbacks to JSLIB + */ + public static void Initialize () { + + WebSocketSetOnOpen (DelegateOnOpenEvent); + WebSocketSetOnMessage (DelegateOnMessageEvent); + WebSocketSetOnError (DelegateOnErrorEvent); + WebSocketSetOnClose (DelegateOnCloseEvent); + + isInitialized = true; + + } + + /// + /// Called when instance is destroyed (by destructor) + /// Method removes instance from map and free it in JSLIB implementation + /// + /// Instance identifier. + public static void HandleInstanceDestroy (int instanceId) { + + instances.Remove (instanceId); + WebSocketFree (instanceId); + + } + + [MonoPInvokeCallback (typeof (OnOpenCallback))] + public static void DelegateOnOpenEvent (int instanceId) { + + WebSocket instanceRef; + + if (instances.TryGetValue (instanceId, out instanceRef)) { + instanceRef.DelegateOnOpenEvent (); + } + + } + + [MonoPInvokeCallback (typeof (OnMessageCallback))] + public static void DelegateOnMessageEvent (int instanceId, System.IntPtr msgPtr, int msgSize) { + + WebSocket instanceRef; + + if (instances.TryGetValue (instanceId, out instanceRef)) { + byte[] msg = new byte[msgSize]; + Marshal.Copy (msgPtr, msg, 0, msgSize); + + instanceRef.DelegateOnMessageEvent (msg); + } + + } + + [MonoPInvokeCallback (typeof (OnErrorCallback))] + public static void DelegateOnErrorEvent (int instanceId, System.IntPtr errorPtr) { + + WebSocket instanceRef; + + if (instances.TryGetValue (instanceId, out instanceRef)) { + + string errorMsg = Marshal.PtrToStringAuto (errorPtr); + instanceRef.DelegateOnErrorEvent (errorMsg); + + } + + } + + [MonoPInvokeCallback (typeof (OnCloseCallback))] + public static void DelegateOnCloseEvent (int instanceId, int closeCode) { + + WebSocket instanceRef; + + if (instances.TryGetValue (instanceId, out instanceRef)) { + instanceRef.DelegateOnCloseEvent (closeCode); + } + + } +#endif + + /// + /// Create WebSocket client instance + /// + /// The WebSocket instance. + /// WebSocket valid URL. + public static WebSocket CreateInstance(string url) + { + return new WebSocket(url); + } + + } + +} diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.cs.meta b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.cs.meta new file mode 100644 index 000000000..708e6f1c3 --- /dev/null +++ b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: df0e653fbf9005342904aa0f14d46088 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.jslib b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.jslib new file mode 100644 index 000000000..052f2132d --- /dev/null +++ b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.jslib @@ -0,0 +1,333 @@ + +var LibraryWebSocket = { + $webSocketState: { + /* + * Map of instances + * + * Instance structure: + * { + * url: string, + * ws: WebSocket + * } + */ + instances: {}, + + /* Last instance ID */ + lastId: 0, + + /* Event listeners */ + onOpen: null, + onMesssage: null, + onError: null, + onClose: null, + + /* Debug mode */ + debug: false + }, + + /** + * Set onOpen callback + * + * @param callback Reference to C# static function + */ + WebSocketSetOnOpen: function(callback) { + + webSocketState.onOpen = callback; + + }, + + /** + * Set onMessage callback + * + * @param callback Reference to C# static function + */ + WebSocketSetOnMessage: function(callback) { + + webSocketState.onMessage = callback; + + }, + + /** + * Set onError callback + * + * @param callback Reference to C# static function + */ + WebSocketSetOnError: function(callback) { + + webSocketState.onError = callback; + + }, + + /** + * Set onClose callback + * + * @param callback Reference to C# static function + */ + WebSocketSetOnClose: function(callback) { + + webSocketState.onClose = callback; + + }, + + /** + * Allocate new WebSocket instance struct + * + * @param url Server URL + */ + WebSocketAllocate: function(url) { + + var urlStr = UTF8ToString(url); + var id = webSocketState.lastId++; + + webSocketState.instances[id] = { + subprotocols: [], + url: urlStr, + ws: null + }; + + return id; + + }, + + /** + * Add subprotocol to instance + * + * @param instanceId Instance ID + * @param subprotocol Subprotocol name to add to instance + */ + WebSocketAddSubProtocol: function(instanceId, subprotocol) { + + var subprotocolStr = UTF8ToString(subprotocol); + webSocketState.instances[instanceId].subprotocols.push(subprotocolStr); + + }, + + /** + * Remove reference to WebSocket instance + * + * If socket is not closed function will close it but onClose event will not be emitted because + * this function should be invoked by C# WebSocket destructor. + * + * @param instanceId Instance ID + */ + WebSocketFree: function(instanceId) { + + var instance = webSocketState.instances[instanceId]; + + if (!instance) return 0; + + // Close if not closed + if (instance.ws && instance.ws.readyState < 2) + instance.ws.close(); + + // Remove reference + delete webSocketState.instances[instanceId]; + + return 0; + + }, + + /** + * Connect WebSocket to the server + * + * @param instanceId Instance ID + */ + WebSocketConnect: function(instanceId) { + + var instance = webSocketState.instances[instanceId]; + if (!instance) return -1; + + if (instance.ws !== null) + return -2; + + instance.ws = new WebSocket(instance.url, instance.subprotocols); + + instance.ws.binaryType = 'arraybuffer'; + + instance.ws.onopen = function() { + + if (webSocketState.debug) + console.log("[JSLIB WebSocket] Connected."); + + if (webSocketState.onOpen) + Module.dynCall_vi(webSocketState.onOpen, instanceId); + + }; + + instance.ws.onmessage = function(ev) { + + if (webSocketState.debug) + console.log("[JSLIB WebSocket] Received message:", ev.data); + + if (webSocketState.onMessage === null) + return; + + if (ev.data instanceof ArrayBuffer) { + + var dataBuffer = new Uint8Array(ev.data); + + var buffer = _malloc(dataBuffer.length); + HEAPU8.set(dataBuffer, buffer); + + try { + Module.dynCall_viii(webSocketState.onMessage, instanceId, buffer, dataBuffer.length); + } finally { + _free(buffer); + } + + } else { + var dataBuffer = (new TextEncoder()).encode(ev.data); + + var buffer = _malloc(dataBuffer.length); + HEAPU8.set(dataBuffer, buffer); + + try { + Module.dynCall_viii(webSocketState.onMessage, instanceId, buffer, dataBuffer.length); + } finally { + _free(buffer); + } + + } + + }; + + instance.ws.onerror = function(ev) { + + if (webSocketState.debug) + console.log("[JSLIB WebSocket] Error occured."); + + if (webSocketState.onError) { + + var msg = "WebSocket error."; + var length = lengthBytesUTF8(msg) + 1; + var buffer = _malloc(length); + stringToUTF8(msg, buffer, length); + + try { + Module.dynCall_vii(webSocketState.onError, instanceId, buffer); + } finally { + _free(buffer); + } + + } + + }; + + instance.ws.onclose = function(ev) { + + if (webSocketState.debug) + console.log("[JSLIB WebSocket] Closed."); + + if (webSocketState.onClose) + Module.dynCall_vii(webSocketState.onClose, instanceId, ev.code); + + delete instance.ws; + + }; + + return 0; + + }, + + /** + * Close WebSocket connection + * + * @param instanceId Instance ID + * @param code Close status code + * @param reasonPtr Pointer to reason string + */ + WebSocketClose: function(instanceId, code, reasonPtr) { + + var instance = webSocketState.instances[instanceId]; + if (!instance) return -1; + + if (!instance.ws) + return -3; + + if (instance.ws.readyState === 2) + return -4; + + if (instance.ws.readyState === 3) + return -5; + + var reason = ( reasonPtr ? UTF8ToString(reasonPtr) : undefined ); + + try { + instance.ws.close(code, reason); + } catch(err) { + return -7; + } + + return 0; + + }, + + /** + * Send message over WebSocket + * + * @param instanceId Instance ID + * @param bufferPtr Pointer to the message buffer + * @param length Length of the message in the buffer + */ + WebSocketSend: function(instanceId, bufferPtr, length) { + + var instance = webSocketState.instances[instanceId]; + if (!instance) return -1; + + if (!instance.ws) + return -3; + + if (instance.ws.readyState !== 1) + return -6; + + instance.ws.send(HEAPU8.buffer.slice(bufferPtr, bufferPtr + length)); + + return 0; + + }, + + /** + * Send text message over WebSocket + * + * @param instanceId Instance ID + * @param bufferPtr Pointer to the message buffer + * @param length Length of the message in the buffer + */ + WebSocketSendText: function(instanceId, message) { + + var instance = webSocketState.instances[instanceId]; + if (!instance) return -1; + + if (!instance.ws) + return -3; + + if (instance.ws.readyState !== 1) + return -6; + + instance.ws.send(UTF8ToString(message)); + + return 0; + + }, + + /** + * Return WebSocket readyState + * + * @param instanceId Instance ID + */ + WebSocketGetState: function(instanceId) { + + var instance = webSocketState.instances[instanceId]; + if (!instance) return -1; + + if (instance.ws) + return instance.ws.readyState; + else + return 3; + + } + +}; + +autoAddDeps(LibraryWebSocket, '$webSocketState'); +mergeInto(LibraryManager.library, LibraryWebSocket); diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.jslib.meta b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.jslib.meta new file mode 100644 index 000000000..a4f0052dd --- /dev/null +++ b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.jslib.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: 3b767cc116f846f4b813fe00142b8736 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 0 + settings: + DefaultValueInitialized: true + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/endel.nativewebsocket.asmdef b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/endel.nativewebsocket.asmdef new file mode 100644 index 000000000..871f704e5 --- /dev/null +++ b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/endel.nativewebsocket.asmdef @@ -0,0 +1,3 @@ +{ + "name": "endel.nativewebsocket" +} diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/endel.nativewebsocket.asmdef.meta b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/endel.nativewebsocket.asmdef.meta new file mode 100644 index 000000000..01d2d89bc --- /dev/null +++ b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/endel.nativewebsocket.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 04376767bc1f3b428aefa3d20743e819 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scenes/SampleLogin.unity b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scenes/SampleLogin.unity index 33e386d1c..0f766adb4 100644 --- a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scenes/SampleLogin.unity +++ b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scenes/SampleLogin.unity @@ -2681,7 +2681,6 @@ MonoBehaviour: redirectToWalletToggle: {fileID: 981405684} loginButton: {fileID: 787012150} rememberMeToggle: {fileID: 724612376} - builder: {fileID: 1810619523} projectId: f4bff60eb260841f46b1c77588cd8acb projectName: Web3.Unity baseContext: unity-game @@ -3419,50 +3418,6 @@ MonoBehaviour: m_PersistentCalls: m_Calls: [] m_IsOn: 1 ---- !u!1 &1810619522 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 1810619524} - - component: {fileID: 1810619523} - m_Layer: 0 - m_Name: WebSocketBuilder - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!114 &1810619523 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1810619522} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 6dc71ee83d1afff469d88de475a57994, type: 3} - m_Name: - m_EditorClassIdentifier: ---- !u!4 &1810619524 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1810619522} - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_RootOrder: 4 - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1880270952 GameObject: m_ObjectHideFlags: 0 diff --git a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Samples.asmdef b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Samples.asmdef index c1c74fe40..975c32f4a 100644 --- a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Samples.asmdef +++ b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Samples.asmdef @@ -4,7 +4,6 @@ "references": [ "GUID:5426c6b788696eb4c88f4198b59839eb", "GUID:a37273c0f1cc99640b9cce89c64b02d0", - "GUID:04376767bc1f3b428aefa3d20743e819", "GUID:6055be8ebefd69e48b49212b09b47b2f" ], "includePlatforms": [], diff --git a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs index 61dac3981..918c1b39c 100644 --- a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs +++ b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs @@ -15,7 +15,6 @@ using UnityEngine.Assertions; using UnityEngine.Networking; using UnityEngine.Scripting; -using UnityEngine.Serialization; using UnityEngine.UI; using WalletConnectSharp.Core; using WalletConnectSharp.Core.Controllers; @@ -37,8 +36,11 @@ public class ExistingWalletLogin : Login [SerializeField] public Button loginButton; [SerializeField] private Toggle rememberMeToggle; - - [SerializeField] private WalletConnectWebSocketBuilder builder; + +#if !UNITY_2022_1_OR_NEWER + // Use a custom connection builder due to an issue fixed in version 2022 and above https://blog.unity.com/engine-platform/il2cpp-full-generic-sharing-in-unity-2022-1-beta. + private WalletConnectWebSocketBuilder builder; +#endif [Header("Wallet Connect")] [SerializeField] private string projectId; @@ -78,6 +80,17 @@ protected override IEnumerator Initialize() Assert.IsNotNull(loginButton); Assert.IsNotNull(rememberMeToggle); +#if !UNITY_2022_1_OR_NEWER + // Initialize custom web socket. + GameObject webSocketBuilderObj = + new GameObject(nameof(WalletConnectWebSocketBuilder), typeof(WalletConnectWebSocketBuilder)); + + builder = webSocketBuilderObj.GetComponent(); + + // keep web socket during scene unload + DontDestroyOnLoad(gameObject); +#endif + #if UNITY_ANDROID if (!Application.isEditor) @@ -187,7 +200,10 @@ private void BuildWalletConnectConfig() ProjectId = projectId, ProjectName = projectName, BaseContext = baseContext, +#if !UNITY_2022_1_OR_NEWER + // Assign custom connection builder/web socket. ConnectionBuilder = builder, +#endif Chain = chain, Metadata = metadata, // try and get saved value diff --git a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocket.cs b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocket.cs deleted file mode 100644 index 5a04f33b9..000000000 --- a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocket.cs +++ /dev/null @@ -1,330 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using NativeWebSocket; -using Newtonsoft.Json; -using UnityEngine; -using WalletConnectSharp.Common; -using WalletConnectSharp.Common.Logging; -using WalletConnectSharp.Common.Utils; -using WalletConnectSharp.Events; -using WalletConnectSharp.Events.Model; -using WalletConnectSharp.Network; -using WalletConnectSharp.Network.Models; -using WalletConnectSharp.Network.Websocket; - -public class WalletConnectWebSocket : MonoBehaviour, IJsonRpcConnection, IModule -{ - WebSocket _socket; - - private EventDelegator _delegator; - - [SerializeField] private string url; - private bool _registering; - private Guid _context; - private TaskCompletionSource beginConnect; - private bool begunConnect; - - /// - /// The Open timeout - /// - public TimeSpan OpenTimeout = TimeSpan.FromSeconds(60); - - /// - /// The Url to connect to - /// - public string Url - { - get { return url; } - set => url = value; - } - - public bool IsPaused { get; private set; } - - /// - /// The name of this websocket connection module - /// - public string Name - { - get { return "websocket-connection"; } - } - - /// - /// The context string of this Websocket module - /// - public string Context - { - get { return _context.ToString(); } - } - - /// - /// The EventDelegator this Websocket connection module is using - /// - public EventDelegator Events - { - get { return _delegator; } - } - - /// - /// Whether this websocket connection is connected - /// - public bool Connected - { - get - { - bool connected = _socket != null && _socket.State == WebSocketState.Open; - WCLogger.Log("Websocket Connected State: " + connected); - return connected; - } - } - - /// - /// Whether this websocket connection is currently connecting - /// - public bool Connecting - { - get { return _registering; } - } - - public async void Dispose() - { - if (Connected) - { - WCLogger.Log("Socket is being disposed, cleanup"); - await Close(); - } - } - - public async Task Open() - { - await Register(this.url); - } - - public Task Open(T options) - { - if (typeof(string).IsAssignableFrom(typeof(T))) - { - return Register(options as string); - } - - return Open(); - } - - public async Task Close() - { - if (_socket == null) throw new IOException("Connection already closed"); - - await _socket.Close(); - - WCLogger.Log("Closing websocket due to Close() being called"); - OnClose(WebSocketCloseCode.Normal); - } - - public async Task SendRequest(IJsonRpcRequest requestPayload, object context) - { - if (_socket == null) _socket = await Register(this.url); - - try - { - Debug.Log($"[WCWebSocket-{_context}] Sending request {JsonConvert.SerializeObject(requestPayload)}"); - - await _socket.SendText(JsonConvert.SerializeObject(requestPayload)); - } - catch (Exception e) - { - Debug.LogError(e); - - OnError(requestPayload, e); - } - } - - public async Task SendResult(IJsonRpcResult responsePayload, object context) - { - if (_socket == null) _socket = await Register(this.url); - - try - { - await _socket.SendText(JsonConvert.SerializeObject(responsePayload)); - } - catch (Exception e) - { - OnError(responsePayload, e); - } - } - - public async Task SendError(IJsonRpcError errorPayload, object context) - { - if (_socket == null) _socket = await Register(this.url); - - try - { - await _socket.SendText(JsonConvert.SerializeObject(errorPayload)); - } - catch (Exception e) - { - OnError(errorPayload, e); - } - } - - private void Awake() - { - _context = Guid.NewGuid(); - _delegator = new EventDelegator(this); - } - - private async Task Register(string _url) - { - if (!Validation.IsWsUrl(_url)) - { - throw new ArgumentException("Provided URL is not compatible with WebSocket connection: " + _url); - } - - if (_registering) - { - TaskCompletionSource registeringTask = - new TaskCompletionSource(TaskCreationOptions.None); - - Events.ListenForOnce(WebsocketConnectionEvents.RegisterError, - delegate(object sender, GenericEvent @event) - { - registeringTask.SetException(@event.EventData); - }); - - Events.ListenForOnce(WebsocketConnectionEvents.Open, - delegate(object sender, GenericEvent @event) - { - registeringTask.SetResult(@event.EventData); - }); - - await registeringTask.Task; - - return registeringTask.Task.Result; - } - - this.url = _url; - this._registering = true; - - try - { - _socket = new WebSocket(this.url); - //_socket = new WebsocketClient(new Uri(_url)); - - await StartWebsocket(_socket).WithTimeout(OpenTimeout, "Unavailable WS RPC url at " + this.url); - OnOpen(_socket); - return _socket; - } - catch (Exception e) - { - Events.Trigger(WebsocketConnectionEvents.RegisterError, e); - - WCLogger.Log($"Calling close due to exception {e}"); - OnClose(WebSocketCloseCode.ServerError); - - throw; - } - } - - private Task StartWebsocket(WebSocket socket) - { - this._socket = socket; - begunConnect = false; - beginConnect = new TaskCompletionSource(); - - return beginConnect.Task; - } - - private void OnOpen(WebSocket socket) - { - socket.OnMessage += OnPayload; - socket.OnClose += OnDisconnect; - - this._registering = false; - Events.Trigger(WebsocketConnectionEvents.Open, _socket); - } - - private void OnDisconnect(WebSocketCloseCode obj) - { - if (obj != WebSocketCloseCode.Normal) Events.Trigger(WebsocketConnectionEvents.Error, obj); - - WCLogger.Log("Socket closing due to Disconnect event from socket"); - OnClose(obj); - } - - private void OnClose(WebSocketCloseCode obj) - { - if (this._socket == null) return; - - //_socket.Dispose(); - this._socket = null; - this._registering = false; - Events.Trigger(WebsocketConnectionEvents.Close, obj); - } - - private void OnPayload(byte[] data) - { - string json = System.Text.Encoding.UTF8.GetString(data); - - Debug.Log($"[WCWebSocket-{_context}] Got payload {json}"); - - if (string.IsNullOrWhiteSpace(json)) return; - - Debug.Log($"[WCWebsocket-{_context}] Triggering payload event with JSON {json}"); - - Events.Trigger(WebsocketConnectionEvents.Payload, json); - } - - void Update() - { -#if !UNITY_WEBGL || UNITY_EDITOR - if (_socket != null) _socket.DispatchMessageQueue(); -#endif - } - - private async void LateUpdate() - { - if (begunConnect || beginConnect == null || beginConnect.Task.IsCompleted) return; - if (_socket == null) return; - - begunConnect = true; - this._socket.OnOpen += SocketOnOnOpen; - try - { - await this._socket.Connect(); - } - catch (Exception e) - { - this.beginConnect.TrySetException(e); - } - } - - private void SocketOnOnOpen() - { - beginConnect.TrySetResult(true); - } - - private string addressNotFoundError = "getaddrinfo ENOTFOUND"; - private string connectionRefusedError = "connect ECONNREFUSED"; - - private void OnError(IJsonRpcPayload ogPayload, Exception e) - { - var exception = e.Message.Contains(addressNotFoundError) || e.Message.Contains(connectionRefusedError) - ? new IOException("Unavailable WS RPC url at " + this.url) - : e; - - var message = exception.Message; - var payload = new JsonRpcResponse(ogPayload.Id, - new Error() { Code = exception.HResult, Data = null, Message = message }, default(T)); - - //Trigger the payload event, converting the new JsonRpcResponse object to JSON string - Events.Trigger(WebsocketConnectionEvents.Payload, JsonConvert.SerializeObject(payload)); - - Debug.LogError(e); - } - - private void OnApplicationPause(bool pauseStatus) - { - IsPaused = pauseStatus; - } -} \ No newline at end of file diff --git a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocketBuilder.cs b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocketBuilder.cs deleted file mode 100644 index 8ecb35951..000000000 --- a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/WalletConnectWebSocketBuilder.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Threading.Tasks; -using ChainSafe.Gaming.Evm.Unity; -using UnityEngine; -using WalletConnectSharp.Network; -using WalletConnectSharp.Network.Interfaces; - -public class WalletConnectWebSocketBuilder : MonoBehaviour, IConnectionBuilder -{ - public Task CreateConnection(string url) - { - TaskCompletionSource taskCompletionSource = - new TaskCompletionSource(); - - Dispatcher.Instance().Enqueue(() => - { - DontDestroyOnLoad(gameObject); - - Debug.Log("Building websocket with URL " + url); - var websocket = gameObject.AddComponent(); - websocket.Url = url; - - taskCompletionSource.TrySetResult(websocket); - }); - - - return taskCompletionSource.Task; - } -} diff --git a/src/UnitySampleProject/Packages/manifest.json b/src/UnitySampleProject/Packages/manifest.json index eb28483d4..38ca12ddc 100644 --- a/src/UnitySampleProject/Packages/manifest.json +++ b/src/UnitySampleProject/Packages/manifest.json @@ -1,6 +1,5 @@ { "dependencies": { - "com.endel.nativewebsocket": "https://github.com/endel/NativeWebSocket.git#upm", "com.unity.collab-proxy": "2.0.7", "com.unity.feature.2d": "1.0.0", "com.unity.ide.rider": "3.0.25", @@ -13,8 +12,8 @@ "com.unity.ugui": "1.0.0", "com.unity.visualscripting": "1.8.0", "io.chainsafe.web3-unity": "file:../../../Packages/io.chainsafe.web3-unity", - "io.chainsafe.web3-unity.lootboxes": "file:../../../Packages/io.chainsafe.web3-unity.lootboxes", "io.chainsafe.web3-unity.web3auth": "file:../../../Packages/io.chainsafe.web3-unity.web3auth", + "io.chainsafe.web3-unity.lootboxes": "file:../../../Packages/io.chainsafe.web3-unity.lootboxes", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", "com.unity.modules.animation": "1.0.0", From 4fa9363b26681cf99e5ec98d666a3029e6f2bbe6 Mon Sep 17 00:00:00 2001 From: robGG1997 Date: Tue, 14 Nov 2023 14:27:07 +0300 Subject: [PATCH 05/15] moved web socket as plugin in packages --- .../{Scripts/WalletConnect => Plugins}/WebSocket.meta | 0 .../WalletConnect => Plugins}/WebSocket/WebSocket.cs | 0 .../WalletConnect => Plugins}/WebSocket/WebSocket.cs.meta | 0 .../WalletConnect => Plugins}/WebSocket/WebSocket.jslib | 0 .../WebSocket/WebSocket.jslib.meta | 0 .../WalletConnect/WebSocket/endel.nativewebsocket.asmdef | 3 --- .../WebSocket/endel.nativewebsocket.asmdef.meta | 7 ------- 7 files changed, 10 deletions(-) rename Packages/io.chainsafe.web3-unity/Runtime/{Scripts/WalletConnect => Plugins}/WebSocket.meta (100%) rename Packages/io.chainsafe.web3-unity/Runtime/{Scripts/WalletConnect => Plugins}/WebSocket/WebSocket.cs (100%) rename Packages/io.chainsafe.web3-unity/Runtime/{Scripts/WalletConnect => Plugins}/WebSocket/WebSocket.cs.meta (100%) rename Packages/io.chainsafe.web3-unity/Runtime/{Scripts/WalletConnect => Plugins}/WebSocket/WebSocket.jslib (100%) rename Packages/io.chainsafe.web3-unity/Runtime/{Scripts/WalletConnect => Plugins}/WebSocket/WebSocket.jslib.meta (100%) delete mode 100644 Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/endel.nativewebsocket.asmdef delete mode 100644 Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/endel.nativewebsocket.asmdef.meta diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket.meta b/Packages/io.chainsafe.web3-unity/Runtime/Plugins/WebSocket.meta similarity index 100% rename from Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket.meta rename to Packages/io.chainsafe.web3-unity/Runtime/Plugins/WebSocket.meta diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.cs b/Packages/io.chainsafe.web3-unity/Runtime/Plugins/WebSocket/WebSocket.cs similarity index 100% rename from Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.cs rename to Packages/io.chainsafe.web3-unity/Runtime/Plugins/WebSocket/WebSocket.cs diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.cs.meta b/Packages/io.chainsafe.web3-unity/Runtime/Plugins/WebSocket/WebSocket.cs.meta similarity index 100% rename from Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.cs.meta rename to Packages/io.chainsafe.web3-unity/Runtime/Plugins/WebSocket/WebSocket.cs.meta diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.jslib b/Packages/io.chainsafe.web3-unity/Runtime/Plugins/WebSocket/WebSocket.jslib similarity index 100% rename from Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.jslib rename to Packages/io.chainsafe.web3-unity/Runtime/Plugins/WebSocket/WebSocket.jslib diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.jslib.meta b/Packages/io.chainsafe.web3-unity/Runtime/Plugins/WebSocket/WebSocket.jslib.meta similarity index 100% rename from Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/WebSocket.jslib.meta rename to Packages/io.chainsafe.web3-unity/Runtime/Plugins/WebSocket/WebSocket.jslib.meta diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/endel.nativewebsocket.asmdef b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/endel.nativewebsocket.asmdef deleted file mode 100644 index 871f704e5..000000000 --- a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/endel.nativewebsocket.asmdef +++ /dev/null @@ -1,3 +0,0 @@ -{ - "name": "endel.nativewebsocket" -} diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/endel.nativewebsocket.asmdef.meta b/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/endel.nativewebsocket.asmdef.meta deleted file mode 100644 index 01d2d89bc..000000000 --- a/Packages/io.chainsafe.web3-unity/Runtime/Scripts/WalletConnect/WebSocket/endel.nativewebsocket.asmdef.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: 04376767bc1f3b428aefa3d20743e819 -AssemblyDefinitionImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: From ce31e78fe53ac760b2731fe216c3a1d82ba0b8cb Mon Sep 17 00:00:00 2001 From: robGG1997 Date: Tue, 14 Nov 2023 14:50:26 +0300 Subject: [PATCH 06/15] fixed ws destroy error --- .../Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs index 918c1b39c..8a555c044 100644 --- a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs +++ b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs @@ -88,7 +88,7 @@ protected override IEnumerator Initialize() builder = webSocketBuilderObj.GetComponent(); // keep web socket during scene unload - DontDestroyOnLoad(gameObject); + DontDestroyOnLoad(webSocketBuilderObj); #endif #if UNITY_ANDROID From 26e535a253ad43fb463250c1be4da08e86e2f59f Mon Sep 17 00:00:00 2001 From: robGG1997 Date: Tue, 14 Nov 2023 15:00:31 +0300 Subject: [PATCH 07/15] reuse connection builder after a logout --- .../Scripts/Scenes/ExistingWalletLogin.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs index 8a555c044..08dcf0b0c 100644 --- a/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs +++ b/src/UnitySampleProject/Assets/Samples/web3.unity SDK/2.5.0/Web3.Unity Samples/Scripts/Scenes/ExistingWalletLogin.cs @@ -39,7 +39,7 @@ public class ExistingWalletLogin : Login #if !UNITY_2022_1_OR_NEWER // Use a custom connection builder due to an issue fixed in version 2022 and above https://blog.unity.com/engine-platform/il2cpp-full-generic-sharing-in-unity-2022-1-beta. - private WalletConnectWebSocketBuilder builder; + private WalletConnectWebSocketBuilder connectionBuilder; #endif [Header("Wallet Connect")] [SerializeField] @@ -81,14 +81,20 @@ protected override IEnumerator Initialize() Assert.IsNotNull(rememberMeToggle); #if !UNITY_2022_1_OR_NEWER - // Initialize custom web socket. - GameObject webSocketBuilderObj = - new GameObject(nameof(WalletConnectWebSocketBuilder), typeof(WalletConnectWebSocketBuilder)); - builder = webSocketBuilderObj.GetComponent(); + connectionBuilder = FindObjectOfType(); - // keep web socket during scene unload - DontDestroyOnLoad(webSocketBuilderObj); + // Initialize custom web socket if it's not already. + if (connectionBuilder == null) + { + GameObject webSocketBuilderObj = + new GameObject(nameof(WalletConnectWebSocketBuilder), typeof(WalletConnectWebSocketBuilder)); + + connectionBuilder = webSocketBuilderObj.GetComponent(); + + // keep web socket during scene unload + DontDestroyOnLoad(webSocketBuilderObj); + } #endif #if UNITY_ANDROID @@ -202,7 +208,7 @@ private void BuildWalletConnectConfig() BaseContext = baseContext, #if !UNITY_2022_1_OR_NEWER // Assign custom connection builder/web socket. - ConnectionBuilder = builder, + ConnectionBuilder = connectionBuilder, #endif Chain = chain, Metadata = metadata, From 53516b09a83c0d9ec99c0fb7eade6385f6b33a03 Mon Sep 17 00:00:00 2001 From: robGG1997 Date: Tue, 14 Nov 2023 15:04:19 +0300 Subject: [PATCH 08/15] ignore generated data files in sample project --- src/UnitySampleProject/.gitignore | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/UnitySampleProject/.gitignore b/src/UnitySampleProject/.gitignore index 727c26cbe..fde00b818 100644 --- a/src/UnitySampleProject/.gitignore +++ b/src/UnitySampleProject/.gitignore @@ -80,6 +80,13 @@ crashlytics-build.properties /Assets/WebGLTemplates /Assets/WebGLTemplates.meta +# Ignore generated data files +/Assets/PayerData.json +/Assets/PayerData.json.meta + +/Assets/walletconnect.json +/Assets/walletconnect.json.meta + # Ignore the project config since it can always be rebuilt /Assets/Resources.meta /Assets/Resources/ProjectConfigData.asset From 3ad5cdb740e0da5ab4909e22f9b191612e85e8ce Mon Sep 17 00:00:00 2001 From: robGG1997 Date: Tue, 14 Nov 2023 15:08:03 +0300 Subject: [PATCH 09/15] removed some unwanted changes and ignored files --- src/UnitySampleProject/Assets/PlayerData.json | 1 - src/UnitySampleProject/Assets/PlayerData.json.meta | 7 ------- src/UnitySampleProject/Assets/Plugins.meta | 8 -------- 3 files changed, 16 deletions(-) delete mode 100644 src/UnitySampleProject/Assets/PlayerData.json delete mode 100644 src/UnitySampleProject/Assets/PlayerData.json.meta delete mode 100644 src/UnitySampleProject/Assets/Plugins.meta diff --git a/src/UnitySampleProject/Assets/PlayerData.json b/src/UnitySampleProject/Assets/PlayerData.json deleted file mode 100644 index 3aa8c0f3f..000000000 --- a/src/UnitySampleProject/Assets/PlayerData.json +++ /dev/null @@ -1 +0,0 @@ -{"WalletConnectConfig":null} diff --git a/src/UnitySampleProject/Assets/PlayerData.json.meta b/src/UnitySampleProject/Assets/PlayerData.json.meta deleted file mode 100644 index 7e6a8d520..000000000 --- a/src/UnitySampleProject/Assets/PlayerData.json.meta +++ /dev/null @@ -1,7 +0,0 @@ -fileFormatVersion: 2 -guid: f3c322c946d55d34eaadaf3a50312c5c -TextScriptImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/src/UnitySampleProject/Assets/Plugins.meta b/src/UnitySampleProject/Assets/Plugins.meta deleted file mode 100644 index a64f6fd98..000000000 --- a/src/UnitySampleProject/Assets/Plugins.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 9265622591a5741fca8ccae4087d191a -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: From 5a39a1c62b22841df3fe66d45347b47f2e2d4f14 Mon Sep 17 00:00:00 2001 From: robGG1997 Date: Tue, 14 Nov 2023 15:20:25 +0300 Subject: [PATCH 10/15] removed editor folder from assets and added docs for connection builder in WalletConnectConfig.cs --- .../WalletConnectConfig.cs | 4 +++ src/UnitySampleProject/Assets/Editor.meta | 8 ------ .../Assets/Editor/PreBuild.cs | 28 ------------------- .../Assets/Editor/PreBuild.cs.meta | 11 -------- 4 files changed, 4 insertions(+), 47 deletions(-) delete mode 100644 src/UnitySampleProject/Assets/Editor.meta delete mode 100644 src/UnitySampleProject/Assets/Editor/PreBuild.cs delete mode 100644 src/UnitySampleProject/Assets/Editor/PreBuild.cs.meta diff --git a/src/ChainSafe.Gaming.WalletConnect/WalletConnectConfig.cs b/src/ChainSafe.Gaming.WalletConnect/WalletConnectConfig.cs index 1c08f593a..f36b1e067 100644 --- a/src/ChainSafe.Gaming.WalletConnect/WalletConnectConfig.cs +++ b/src/ChainSafe.Gaming.WalletConnect/WalletConnectConfig.cs @@ -66,6 +66,10 @@ public class WalletConnectConfig /// public string BaseContext { get; set; } + /// + /// Custom Connection Builder. + /// This can be useful if the default web socket implementation is unreachable. + /// public IConnectionBuilder ConnectionBuilder { get; set; } /// diff --git a/src/UnitySampleProject/Assets/Editor.meta b/src/UnitySampleProject/Assets/Editor.meta deleted file mode 100644 index 143e467af..000000000 --- a/src/UnitySampleProject/Assets/Editor.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 896dc5f43fe9f474e99214d59fdbb5cb -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/src/UnitySampleProject/Assets/Editor/PreBuild.cs b/src/UnitySampleProject/Assets/Editor/PreBuild.cs deleted file mode 100644 index 579b9a12a..000000000 --- a/src/UnitySampleProject/Assets/Editor/PreBuild.cs +++ /dev/null @@ -1,28 +0,0 @@ - -using UnityEditor; -using UnityEditor.Build; -using UnityEditor.Build.Reporting; -using UnityEngine; - -[InitializeOnLoad] -public static class PreBuild -{ - static PreBuild() - { - if (SessionState.GetBool(nameof(PreBuild), false)) - { - return; - } - - Debug.Log("Running Pre Build Operations..."); - -#if UNITY_IOS - PlayerSettings.stripEngineCode = false; - PlayerSettings.SetManagedStrippingLevel(BuildTargetGroup.iOS, ManagedStrippingLevel.Disabled); - - Debug.Log("Code managed stripping Level Disabled for IOS"); -#endif - - SessionState.SetBool(nameof(PreBuild), true); - } -} \ No newline at end of file diff --git a/src/UnitySampleProject/Assets/Editor/PreBuild.cs.meta b/src/UnitySampleProject/Assets/Editor/PreBuild.cs.meta deleted file mode 100644 index 0523d02ec..000000000 --- a/src/UnitySampleProject/Assets/Editor/PreBuild.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 3c25a4f1f3c6d184799c85aca0d9984d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: From 467787bd2217f6ae31fc95b389db8091f2337c4d Mon Sep 17 00:00:00 2001 From: robGG1997 Date: Tue, 14 Nov 2023 12:21:01 +0000 Subject: [PATCH 11/15] Auto-duplicate Packages Samples --- .../Scripts/Scenes/ExistingWalletLogin.cs | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/Packages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Scripts/Scenes/ExistingWalletLogin.cs b/Packages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Scripts/Scenes/ExistingWalletLogin.cs index 0c9ed5b43..08dcf0b0c 100644 --- a/Packages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Scripts/Scenes/ExistingWalletLogin.cs +++ b/Packages/io.chainsafe.web3-unity/Samples~/Web3.Unity/Scripts/Scenes/ExistingWalletLogin.cs @@ -14,11 +14,15 @@ using UnityEngine; using UnityEngine.Assertions; using UnityEngine.Networking; -using UnityEngine.Serialization; +using UnityEngine.Scripting; using UnityEngine.UI; using WalletConnectSharp.Core; +using WalletConnectSharp.Core.Controllers; +using WalletConnectSharp.Events; +using WalletConnectSharp.Events.Model; using WalletConnectSharp.Sign.Models; using WalletConnectSharp.Sign.Models.Engine; +using WalletConnectSharp.Sign.Models.Engine.Methods; /// /// Login using an existing wallet using Wallet Connect. @@ -33,6 +37,11 @@ public class ExistingWalletLogin : Login [SerializeField] private Toggle rememberMeToggle; +#if !UNITY_2022_1_OR_NEWER + // Use a custom connection builder due to an issue fixed in version 2022 and above https://blog.unity.com/engine-platform/il2cpp-full-generic-sharing-in-unity-2022-1-beta. + private WalletConnectWebSocketBuilder connectionBuilder; +#endif + [Header("Wallet Connect")] [SerializeField] private string projectId; @@ -71,6 +80,23 @@ protected override IEnumerator Initialize() Assert.IsNotNull(loginButton); Assert.IsNotNull(rememberMeToggle); +#if !UNITY_2022_1_OR_NEWER + + connectionBuilder = FindObjectOfType(); + + // Initialize custom web socket if it's not already. + if (connectionBuilder == null) + { + GameObject webSocketBuilderObj = + new GameObject(nameof(WalletConnectWebSocketBuilder), typeof(WalletConnectWebSocketBuilder)); + + connectionBuilder = webSocketBuilderObj.GetComponent(); + + // keep web socket during scene unload + DontDestroyOnLoad(webSocketBuilderObj); + } +#endif + #if UNITY_ANDROID if (!Application.isEditor) @@ -180,6 +206,10 @@ private void BuildWalletConnectConfig() ProjectId = projectId, ProjectName = projectName, BaseContext = baseContext, +#if !UNITY_2022_1_OR_NEWER + // Assign custom connection builder/web socket. + ConnectionBuilder = connectionBuilder, +#endif Chain = chain, Metadata = metadata, // try and get saved value @@ -266,4 +296,22 @@ private void SessionApproved(SessionStruct session) Debug.Log($"{session.Topic} Approved"); } + +#if !UNITY_MONO + [Preserve] + void SetupAOT() + { + // Reference all required models + // This is required so AOT code is generated for these generic functions + var historyFactory = new JsonRpcHistoryFactory(null); + Debug.Log(historyFactory.JsonRpcHistoryOfType().GetType().FullName); + Debug.Log(historyFactory.JsonRpcHistoryOfType().GetType().FullName); + Debug.Log(historyFactory.JsonRpcHistoryOfType().GetType().FullName); + Debug.Log(historyFactory.JsonRpcHistoryOfType().GetType().FullName); + Debug.Log(historyFactory.JsonRpcHistoryOfType().GetType().FullName); + Debug.Log(historyFactory.JsonRpcHistoryOfType().GetType().FullName); + EventManager>.InstanceOf(null).PropagateEvent(null, null); + throw new InvalidOperationException("This method is only for AOT code generation."); + } +#endif } \ No newline at end of file From c7ccf011431853acbf793c58a5cd3bd97d20643c Mon Sep 17 00:00:00 2001 From: robGG1997 Date: Tue, 14 Nov 2023 12:21:34 +0000 Subject: [PATCH 12/15] Published Solution Dependencies to Package Libraries as DLLs --- .../Libraries/ChainSafe.Gaming.Unity.dll | Bin 15360 -> 15360 bytes .../ChainSafe.Gaming.WalletConnect.dll | Bin 35840 -> 36352 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Libraries/ChainSafe.Gaming.Unity.dll b/Packages/io.chainsafe.web3-unity/Runtime/Libraries/ChainSafe.Gaming.Unity.dll index c38ccea8f917d93a975e14b0ad8b180ccd30a2fc..75ac5a8ea5f1aae85915fa4803b9e49fd5af68d6 100644 GIT binary patch delta 104 zcmZpuXsDRb$?PqAW@FC{C4p&-Pp^E=336)Od&qYUbNuwpCCVoJEGzB*O_{vLC_rG5 y!3o_|#S^Y;Pb$1njA45}na8+A0V-IU3l#*Zt|-q){JXN*#k_dg@68*G12_Pl7B6c6 delta 104 zcmZpuXsDRb$((R*>&BiNN&?&sC07>8-E``5PU&EeIJs(biLwblOOeCl_Q`9E0tD0r xA5^T{so_3dR3#+j^Jam`JjN{wP{HLeL6B-I*Dpbed3!!1R&{PGAwth@MrP3I7X9{`t4vDo^XmpV+8N+ps1vpniVg`)l z&{<+vW#&lf$jp{9nwcqOyQNO$S^dp2XB!XX$DxAn7-^n4Nwd^*%+SSBU1cti(vevr zWi+!$%63bg!rbMoEH+nq`VB0Ds%n%B^bHkm<^m z!cggEz}r8C8QGt0@Tw5v9F>~RgY^B{b^LL{wqU|#24GAuo$ zTP1|8;$d-7D~5$Xs%Qhc&<4g{Io>Sw-Dke(8=i;$F(a)0N1D2Sd}$6{2DR#n*5APc zXA8#8dkt2zrc#%g^Zg0!GBe{pA+-jzyohHl$|3m*BfcJ#BlSaI6|?Jv?J(c=*ZQtt zVWo07Q&*UkdBaP&u}|q%V^3I&%PuBL2&`t=+S^F)uzK%?4Cz1s6Qi5k6rY zLdqeI_@w14c@rDOokEr323W~rI5i3(#ExoJg;lE1>Jv5C*1xFHI?F*9Ij*TUn*t>x z(a_3_5J(j!xFzT&QY}2l$o55Co`nogGVc#uSk-Nvv!K$W(JY1Sz&oVG(bDajtuh<( zPi&AiiVaDx8K=@KQPJ8GFiG{A>elseDRly*-__ZC80K46n|t!hjIkh5ZJha+{P=kz z(44#TMvSnLYUlpzRcxR)!?je%$ehS!JC?LFY^W6W%j*5lJ26kvtcVz@oy((9%qUGo z?RK!V3YOZkfV@(K+Y%h6O)_r|Rz@btCPsYu7#I4Y|zbEkPYQmaw3`hp%c#&KdF#_K+CR)ankTCh)fD{PNqmI4VwyA=Gb&TQgdQp3&1!-*MImI4_p z=6v2~MrCAXPsCF)oM)5@LlchlFI+%*&xkGs}F0EVGR}9UNNU;ZFq1T+_ zPK`FFMvCmw_?I(mwf29TVOA2XKw1XDx}6mngZ&tTK3O{9MeuD~Q7Mui%6Idl?}Nyo z>}UiL%9rI=c0$XI?0hg3j1Daip_Yhap?(nO#Msvi$50ljx@9 zCR7dOAJu;bhCBAAN|3}zC={wGw(5mKswH7wTTrTvG4C!Ios`te#0-T~ooI2OK$RV} z)?$0DsYUx2W4a+ZM6)my*r}-<~{!P*NI7*CA)>P7XA}f!bp8zF0qPxVbl0n6(39CmpG~P!VyI4tG*q5n-i=RQeRC z5G6Jir%xaADFV<7$KHh;$(~UYwf2sdyU})e1=5qkR)tK|0Bhn_*>Vq*HTkU`x4nt( zzZdP@=?Iprv+Xq0%-xF`?za1Y1MMO9LY%q3kHBETFGRpoLC@^yqYSd(fj$E40D5L8 z1Z@uj`Z(3{5O~>={b7~0w&W9KOXf&1Y>ix4r%DW4T-GJrRcTgW*tqC3Y~0Eowzh}+ zIMBx)svnNxcM zaHv!Q0US6>rCFM%+POFy#hxsiO4d3=J5o=;N~#*&94L-Bd8&vl$-=j0P_H<(9Zjn0 zkstoxa7%}frN82ft&9)!yig{hw?6aAMg1_Z@Q}m2dJ@gOOH}$Pl$cm18ewAH#+hz= z8nVnTfJ$4)l`<;<_T(r><(iPqV0r5P{X5Xoo#HKU3?5_MI}>&Uy{3t`#3zc2#&Dl1 zq-VOgY<~tA))n6}s`sztj?=7&=&GH|K4bi(nOBm^ z%RyQDWE$0MzmMwAfo|Kwwfdn}O|`uqUVxO0a!sE)qe}R4ZEnf1shMb^+;Cf zmmvtbRQeTGAk^xX-+_v_!|qh}RZv65%h+3?xii0q$PUe|_{vay;9QkvX~eDCxjY)h zm|2e$ZUj58q3|Cl$7urtFNO-N>`RJ5hP73eeow1CV=gQm(7h=>V{R-h$)14`M?^DF z`1Hb2-qxqO7d-}I3PyMv21jDE(^ZSkTmHcCI=g|Tv=8MFYe}*-EXlLDur6L$l9dQ* z|DRw+rCEU`$;Htq#<4Uj2MTTmLvOIvHyP3ZUM%gFXx4k5noLx}lkydo)`NdO^NpZ(Ytn_tEUU_VYI zdLA%mnzf0N>`e5HT+Tt^(`#m_^lVUWxsO}A%S4*mAf;hv@ z)TuiHA zvW|UzY!*7t3NbE5L%KCtpDYm+5a{|Ubk*jp$N*nBG_mEI3tr4Inx z-Ue`ZW-eJUT_*f@;$agU&p|HR4*|o72RoR=!%~ii%bs|=8E%BMyk@?iEUOg@((+qQ z_lk`a=Bl?!!%of{+)a0poDQ(99Wv|6O0o-(EckmK3PiEOoyKP4Y$@mYaIDQ>)!Wmd zHSEE|?;RMdboWtaO3`M24jouy*`lF^tZ<&LGJdr(e%zVzWX|Wpy1i#HUx#qKm>4X^ zWaXkyxZDO-WO=&{>8g8s7jt3<+xvU)$~qwQic|0RjeYwsdtzS*qmI-c`Po&4IXB!* zPbwmWjy-)5bjZjBD17?K4n)<2@12Ng66&b*5-3_efML0Xj9CMvvZR>{p>n6Ogq0#Z80-Rl1K89rA5NBmUwJY@rYmuFv>VNsJ z62sG@{Ofm>h^9yRH&N=9t-rwizXj>+(H!n=mbH3BsQN8!8-Q3ZaYW_`yY213-7*ZR z`Cck{_xpA#zm>^Pc~sJz!T&jv=9&HKvI$P5Gxc}4)$$L3TytT8x1FM%evAzAiXoGJ zaLpag)Amf2W+~rsxjY)}-~CvU;WAR6!3=LP9^PWaVKET$q&|m6j_!dzawGMHS<$~N z0ONf0;JzHGFU=|atHu>o;xNC8Xf?{4%0Qp7ymiU4MQwd#;G@$$dFzUFst7QCAUHDb&cX<7FJf&v&ll9`r9f+ePD=3UJf`0gyhtRCLXXl$ z7wZ(%_JwsiCp5a)OP@w6qh6XDsEq0~Fu`&+`U;RQR-G*h(_qIOzfK=%mpWqfu3GPl zQ8uv=Cf@aP)x0F*Gr@gfFS+t7qa{?Atn-)PM7A|qLc3#IlQCMJTo4J2%1qFPznBhi_d`j4mzOSR5m>biGffZ84VI ziK;qXs&PI(EMO<52z?#0qSL-8*SRLS-d94a3u-+jl#czew1jStG(>dzE3_pvwXh*l zLTjUYAm8er3H()ZW@#n0#h%Oe(uWA9DobA#+^*^L7!qDW$40g$1Jo~XS#f~s1Iy68 z39og!Bf#B%EHU|<59K3`$VVl;5qu3!uY&7y=&1r8Cgod>B$uL93B6vBuXMUTcr)-A z-_5F$Eo-QdY8A@9U8VgD&SbBP}il&sYThpVh1y!^z za0EH%C_5Ieyozy+(W%Le#bNR}rlRBfq~lvk_k{y=IJT^~lK$y)#FMnf`%Yn)+WZEF z_{!3$aP*;4ZuLgYDC$SQi%$oBQ5kzA9;Uy=3(Is`1k=aT+}Jxs$I@W!9dtQAc0*wa zU0AZm+*=vR-W)v+Yg*&~%7b)c{&9&$&`x}V)CpZ9VJ=Yz9*P|$%oWo((XEYH+<4J# z6J4B65#6Ff*2QU}=-wAy8BG@5Bhs#nP805?Z0P>tMk=RimQuI@G*h^r3pa@7lcyp` zOQOOmurhcPw2-9=LW5xZ7zItrMM5@HpnuXxbp$F7M-oUU1h6S8D4v3HMLR zK3*7r(j-B#hA5^fCLEZjis0Jqavxdpohr?ic~(LeGwyO~w9UN%nj)7I^fSgL(0AyJ?-|ztJB=HF&x<5u zaI1^;mDuxM4sUYCGd#zacG~2K5f_0Xllmt|jsnSZ(&rx<&IN71=Pqund9-e!gw zS0WUhh8AmBGN1W9z*A#KY>7sXX&lNgDdN)Tx7ur<4RyfXTk`|Z+}5h|J$cqvaO02r zH0$l)I`X-68sc!Fd7U`p9YcG4PFg}$YyW3)m?2hJB zbnr;zOW@01*S9Kh9WpqMZ#QPS3-WOV`Jyc_G`dmC2mX@&B>5=f=Mg^7x#lFk(s@Sm z=Q-Ca$$qGXPo1y}61(s;n^q2n)*21YV+{vgWpihObJ8xGyUjV;aM71G_n33Ed1dvW z{BwK_%Aiv!&>ZcTnNL;^&ZdJ6%1euF+4^7*f1bM7<~~R^C?Bn`xi6AI^3e}%E*Tqd z_~~k!8xflT&a}C7q|wNuEjG6%G6mdiHdo|0(+JQ#Ha9Tqm<9GBo4p}%E#=dbHg|7g zzL8JQ+1%9pYq7_@WOEDi=NmzK-R7Q2EHpy&w$1%Mu>{@UP*Lh{bI}EI=&DqoX+dvHfD_?`k}Db8)qeNG)ifmaGQ+tlAYi-Syhb{ z{+}Ch+ALg$@k9T;;BK*XyCV-83A)SHy%M<^+)i8fqI%Rw(&M)7ZM7QQZksD~UQ1a% zZgKQ~)NXb>ua%KsxDL89e;1X}G+W2hvW!fd+axs~u{oZkXvaG)bgL9|CWlTN@f#+U#FqK{}3J?BN=nBj|8X!+xHT6iBgaYmGgg1~r;u!gUyLdV+Y$ zmD#%c91Us=4YGBQIf68XhTFQ+u=r1)(Y9`eFNh4(r?QsO=g@F0&9bYm4l~zmSKSm2 z;$eA#t@{~twbW|s?t!kB(zb3qbmQm>TQ?cHadeHPlbJuBuCr^tjhf@>rWD>Vb4(wi z>Ug@{R!)dEs1xa4n>#fcq!a03o4cy8LDkVyHn*`bNOiQw=6v1;RZlP5T#+|O^|a6C zO7k1kNp#TW2IdFx#Qz8!`d@3@s~Xes9d$DO*sl3f(O}O+x>2|eV_(t5Y9ifg>&mfvOrpD)%Tk9i9NWhvdeBy08yV?o zq$li#H%6{jjr1#9cc<6H5#&W%_n>!^I+gxl>v()8(*aw@<2#u?uyvz+V?C$QUu|8z zuMXUo!e!SQbE9KDKOn-}0<2$SakLJcN4T}dBSoiprclV%JzF#xT-?&(wdZutRC>hH zQDJDNXFC1E=H3qGInSWsc;CZ&!au;xpc8EFv*JAG*)&bKwOQJbIL9-K9=4VLr51W- z)AKerIDCO;4t--)r2&x^&s@s#y^VM|X*7?nv^c|o2{n(d6|TdGVLr{H8*JUV(49k_ zwyq7jb7;GzE6N^Uc&TSTJz%qJw22<;;WC~kdZmZc=v>;@!(Hk*mk#xC8ZDrYdbrK{ z0^G5C9X+2(JiFQL^^R`tIqmDLceSzDRz9bF?!CgZq=)<1yTP;6=B$Q%y=60K=)KzD zORvR!?lnCZ^>Ey-jlzApUrur1uAnWn)w7)D_vo@3T|$d&1rKRJ0b=fSn+bk<{ zw6MNcnFFceOc@L+oGF=Z%>Vz4+5gq(VZ~j39AUf<-76gVdDLHy{ZFp+bvi0H#^2(K zs>FhZt3Wr6@qgLNh8{r&FfH_}ewJ9s&AJr;z=!n203#8KhT<1oyCiLtCw^`WB9r zwtbygS<03<9}4#^rY#B6O7e|C%u2#*FhUA9D6V9UH>09g;G=_BtI?PwW8a$AoM8M{ zHQh2{>oH^+-7VG_HTt~_L^tC3_g&HVB~H9Ab7G&+=?#1@0rx(>m*A%G8D21K$D6h7 zRElpL-vqu%e9Q1H$F~As5AnVl0=iJ2=?b8ij-lhTc!G}s!_R8QM#1TVvjm$3*8!un z8K}`A!3yp8!cv+p8`E^zkTyYLX@mGRj&unOQZaBa4Hn5U>I&Z!8lztK&Zc8jEeWxmR?;Rh+C_D^I}V^>xNHod(tMK|Rh9Dw>~0f7TPcJNZlTTETmE~2PbYT*$A@;| zP2yYry+D@K(D=|n8X?!k5pty+A-B>Iaw{DnH_tZ7|#lQWt)dwGzQB zDr?g=;VHTTI4ZhUE7r<hA@!)m-8;(F<9{=a#v|XP~_*;$Nlqs?C8bfR>!kKXZvsI&wrp-y*?O2V@o^o76+}SlWFlo}G z^jL5ua6#xT$91B;NwhbKwu{b={g>k-HQ3>FZl`4l!+BV($mhAT!IRB%)~g@qM}RfK zBIj+?zqrzQkKO&z#3o0*`eUNbSwrs^Gy;buH>n!pr@zN>#5dom;TG58+^liL9&M>N z=PY*QqPwszxc+8s7;>;#7N@4I@Zae4Xs^QQb!vI6(>d6Iyk_y{dxdW$@NbB3u%pv^ zI}~3hnI0Xx3k{Ahybt(vau*%L4S+ZJpL8DM;Ak}MNa7jigA(x*67drf@ot%_yJcvW z%JA(benGTbUJC7&3Er$e@D6Zg>($ed<6R;3XJ3PBDSr1o#r0b|`r5J!T^{Y7*m_sJ zRqmyl-UTeIpCci5zU!zmc~^--z#zs;c-oSizk3 zE=hHlq`FH|eMsZ$)eepC!l%h(Jf!gr>yXAbt3%qyiGbU$)#65UNXz=X0rw%TG@p_0 zL5H;CAX(uUTF}p3pz%bD%dC!LKAU(A%;S2R%!AWp9)z*G=iC9DoZ8%Bob_{VT;lQf zpP_hH#26tLu#^J8{xk$wO{0Lv(JJ8BEZu@)0^JXsOiuvM5XBr|y%=g1T!7>CEqP0* z4MuY!=@9G`+$MOdbh%6DF2RF>hXmghEotnWCRh&4;&*|<^w-$m!5Vi^Cz5)>Mv*iL z-7L67Bsrlw1Um({3GNc?5`0!;rw<>mNIuMK63GHcj^r(XBpk?zf0vQ2Q8V3*)Q!9#-Y3X(&d5X?HH zC>QK6IM}g;CI{+-o*>u=$r*trp%(})fn-h~C-fD9>mXSW=oI=^!R?UzM_`xG&kDW( z$t8h=J~`PVhoHx~h3?5O7rH{Qzu;iVCkE?;t`}^CWO}ek=w`tskbE4> z3Ed&M4w5f|okDLD+zyEm>Js_|XLbt}gboSw5fl|6&Bd;GTwAC*R3Y?W(8q`BgsvBC zgrp(VBy_Xj5=hPn<%I4KTnEYgP^Zw_1a}E`2_6(YQx=yf3Buzr+ z1Up5N?G$F0;6Xvk7ZZYYf=z-s!A`+lg5@FUCdBpY1e-+CBy`S_6qwL<3LX?JkFumm zu(ME#gM#HTQ3#p^6H?hvoSNRKU!*^-zpguQPAB~J=uBXjb0)3@{AWLYBOfI{PStr7 z$B$t8Q$7uXv>LVk=C~CXidG-vLZPn>GW~UuadhnGz;%&(fP)=7fIAWo0~hBr?n^x8 z!QS}u_|src39@K;oSQx8XY5G60KCEfGVr~~YruEao4`^h*F50iIxk6=E2R5h3;mNY z%YPv_Rq$iMDN&YRSI8LmGET^6JUJrG>P7UAqPHO$A>HqY{2BBf?_r?T!O6bQK`)Im z?kV~jI801m7QWS7(eSK$0PZ6i4Kyd5Jj8<|1ahS4SaZe675E#_Su~84x$WdKH%@)1 zHP760@(-|ZP6oZyEInmbHbXN&uL3Ik&GKx}R|6HTr&*w{0Txq(RslRk>rW*#33Lga z3c8d|16_(&&HXVx(?G}RbkGSp6Lf;k0-dCppp!HkbQ#SBT}J1CE~h5Y}Drg~? z3Oe8X$0h3^- z2ver_Gt*s(o1#^(w{?H~UhccHIey|-=4`OH;(`cQ?B$vJ%G@zAa{hE&aKBso5!`^k zEAvbDJfpim&F)ROuoC`Px!%tO?tZvc!TQae>!hwpl=(X~m-y0&V&T(@ekyS}Es zyC!QR^vPPi?xZIDYdR0*ttju&uck)V9_=^!tD15B_lReRln$TT|xD)`~G3^m0uL=eE1bmF6|v(Qrpd3y;vH3|K|na67_h z@_#;3;f`YKpZP%RAg$Savvs)kqUmcZ_5Ml=U~)u5xnG&X+bXqRnP<19$_E?K@GKPw z=eGLF2}Xyg2t_o1P}7pkjx=v>8#CoKr&kLb?jY_k;oNJ6*AX_tmxO}`f!z@5_<@JJ zoenxK4$KO7;NNtYTMOlK7~@dxkjpD{Ip}p}^HnxG}Mk>oaLGdILgUDopA0A z53J|jfcYY6zuI7jq4-2htd>geOQrY2mzXy$PfUgTiE!?p_-@dnq0w4|{79LE#d0TU zDz3xTjwNv`m-}7NTtQ>UWd!nFBzU43B7@ghjaTQ$5w3-R zJIp=g9j=rXOj0nZx$(=&+(V&-S@tjy=pLI#-Ef7+#i^qMqg+eC1_<{+` zLJB2q*)C)!pB&+iHKC4mVksU#{qVQ|M0S=IBYokH4Uz6@e{l8QkHM>la&}W>evjAk(pcvX51nkvRsG1J0-b}(9)RO!y}B*<)M|%FwR|= zau;zH?OEZ}p+uUb3M;}G6?>N0g@M(D8PA-#@v?%&*3`$4AFmm^ zf&{-+@OCece>9xirp0}zdoO}wIy78UTA)W@`Y*4_KB)!ZDvx|1U`H3QB8a(l#3!n7 zZm$djkAl~pJGs39J3)JSNpgPn_U31AXt|x(P5rTkfm?eg@f%7BBiYJlshzJ8Viy43=#Eloh#<^6evO*92WZOo~9V6TtBzh z2$y632sZ=*(M3{4e6oZ11ohp#^^Md5ecG*-%zOW}m|Ip%%gX%6)LVgX246o<$PR`z4n5Qi$vk`w7JS6;2BdY63^%M13eU5N zP%}@f7d^H(*U>}mWyK+3Ws_txhhd@|BDXb4pO`Hx%M;UiM+r~I1{SUnvWCeSLe4O^ zt&CdUVziNBaa>gM7(>lG1Yv)8ML2`)95G-!<@Lci%B``M?;6;HpOByp#6Lkx>`h)g z8gj4kKR<6`cyRc!e@V6jd6=IpZ!1z#2Rcy>7v9>M!&Ab{Y)ORz!zM_j5e z5gr??@G4yENKwFUgSRX!e@!5-iD)1z9Pp&Z!T+uK1!U=g> zUjF#g#{0fl^s`^=|N2uixca(dZQ+q+wh*=}ulm{f(*pah-Ia*F@uB}V%O+zb)8(5g jVyfu2oqt(fu9@XmAKV#O`;|kD9WHNImGkFgQk8turP*egzgG3~X2#P>ZFyIqKBLq~Kj585L z@P+jFD55`!qE~!XM5aH17p=ngY^-`abQd@T{<}*J`zu_K&rp+V%Fk+Q8zkV1a)o z;!)(IY2(nbjYzY{tvq(i6!i-&d$LMvMBX9ws;b7*ek)w40Za9%+=wus8ZTk3T~?smwHK6PeSc?R3}{W{byT${NB( z5Qw7b2*hv#vCq*htzo#=AwXs8D98%> z;!&7#Mw#PDBPi)T+kadZ9(yqh#b_J{j)en3IAD?mi^0sfVoqhwlQxl=CvB(0wnC11 zrYxj0SToG3MAI>l@#j>!gat@&us*q{QlraVP6O*43E*7@j-4`6!)A(Im6;{2C(|lz zA~QqUPDgCzIpfW=FE*aYM^UlwC@3zSa=SJ#V%UYEt}^qb^<*xRHj%kN+D=Doq3Ad( z7uwec1`a`ycx#SPX>L93n51AIh1m(#+zgq$J5V!d3T8lgEvV?qXJJ}(6luDG`*U$J zV&?@1S#y+yrKc}?og=oU0<{oEkd+7ZC^KTu3=T;;3j(659uXt99h{$XM5qfNEyA?$ zk1Dzexz&aw-#Esu4Lz1OqNfuh_MXs4yCgii^lA(l)oLpj{R$p<+t0_~H#VW0vXeN%X}wH#VO#Dl#j!e3-IW&)FX6uK$o7W3vcXH)fU>UShASOW66PVMJmN@5dhX9VsYTo= zR2Fx@N*2S`F$f`fSgR?lQH9Q!&|q8tLSs>@hc5Tr*koTGDLEQz#hD#JkY-qN7(Iy9 z?#zvKIk(f1;gjs=BbU_lSm!LL^s(q>jfF;iiKo5CHCJR$%RjzZ)@TyCbz@a}85Er@ z0V_bSt6g*hT(U-k`+b>9BNN{CYwWl32N=-uLaot0oL_c67Kz@TM!&dW8dfKdKTaiw z1T(CqVn*gTZaZaTGL)4-Vefkd`;$tuAa1BmZVxB0Eov&^_JbuK7}@J;Vn z1>8bDYOQ*ZG2M?{xGh$g?6cIsJ|Mv0GRv}WiVes*F@-Y6vuP)$kg^Inrqete6AR)Y z)yeJQ1ael|7gNYu15@#!KNhr(z=%!^erp);_QAc3cAlb!TX-os&Zpd9ODIeFka|y5 z?UnvCPlt64G;1&J6=NPx>PI-1)36kD^xHGge|K~|yl8-rF7Kz=&TAnSwl3_A*XhvD zLOb@WV5RF((h(qG=x1SvuFb6FQLJJ1FU;~Yoh(H%SkOILiYTj?vlLex77OAL)yeJQ z1oGW4OA+fS2xFR9b=ypFLGz&Usl)(y!HP#73L(t z38Wn_Id@W=IoO9e=$EC@uYvAZ52bj1EZ@(szQ4zZW_jJk@@4rA7~Os+J0Fcj6T>QE z&=Phs7Hvnjplr7m7gdHkEqV`;xhley<>wBFcZS}67gS^Uhjp}i$K4>T5+pGmi^b}S z9lcmgwU^rW6qIVm+CM26m6Fs;Va7~r1A4q$pavXv)?()vJC+z!jM77Lh*~if*yE`a zEvk=>uun}C8Z12qrEM=rlo;dSB^LU4`{qQ-&kKm>VN0UEnFY$oG;`y;$d$56d4gYo zC6#7DSaovKZ)I~YQ_0HqTQ&CJWaY?zYv|nz{;d^zX>w>vI?B*7uk|1Y8s|V=w*6dk zpmv0PAX%7o13Cd?d#s0`Sc7sK?xhgI#_27l(l{1yl-O9jA-&-P1fUm=S*H=no>7-@ z_Kx<4(RXnL(qq+1CSXUeD?=b?+aH0jE`L$L?{1>|?m~Zm+QgFecAN~2+@sL&cRU6- z#GT?G*qI;oV;IbMTo~3U#y9s9Mw#&h7#&*x`uWiQBxu=z!$_`dz{kr5%yFr#k(tP? zbBM$&p|G$F;hjpe0JFqRzggmL?ksg|{Z|eQabnK=xStT2)H6@@WAp@yIs3M7I+rll_ckFv@Uz`N5`nz?zZlbgc{Y{AYRBHnwf zpTLM!4X3j27RS9@8DdMa@STF`V_Tchr-sHMLHvWmEWLp=y^9B17rAvb0mJpHBOeV! z9pR;hI(ioUQAgX+ppIs!>j z0bKoa04K=9`Y(WYyvibMG_}qvSfyVFBj!`-U$Ou@Pi^~dU~#|cx3a$iR)3sKz7v{1 z^CnnsXnw_4gxYuKsWfxre$~nC;RNQ)nSSmBJHJK~ezyXr3(UM2DsZwdDT*1+)>!)A zq}onn3Ecxg%Ybj&y+nuaE%7akK_^jyy($3#rV6nA5CBsMo)jTpRr4};&Z z8(2$kp{;ioC0oPJc_uf`MGI>ZC(YV@e}Ea4W&zeDH-{6L$I`4EAh;V0z0FqlGDrjP zYUy-L^J)>(fz0$i(^xG-+4L=9ngv)e-1MsivFvd+W#>?p7i>N#}S zd&??QGMEGq^Yz<*UAA3RJ{bG&*z(>N${F_AFI&Q*1 zdHyu&+Z!-_hZ9}|AM>?ay#?Ymz1FWd+~Z_>V_Pcb&78%pbN#~Q9c#Y!-3wKkxmfs} z-1gSTNkQkf=9K~&Vx52?>qCoX8FaaG%nM_;(77taxEPJ;{obQ4+ipx1^%TiW`|MOn z&2Qls0(%Dy0z1bENu_@WTBY{^cDxI~;n^Ef1yiNqzZVai;CSBSwsSwAiFoj?k$9NP z@o?K4PcXxskd|NDz5xRo#e($wj?;Z?!$PeFYc#w|c!Rs^A(GPo-uL_MwgDyCc}N!g zJr50{Sn5w>g^A~S%O-2oV^^7j*FO3-It4g)xI*{-37tZ*(@nZHJv zKORg4GUs#S+}5+G*Lsc@g~4nTD>wbZ%;!B zZ+faX_H$v>V|_%FbL*!4Fv%>G8;B0cbsXIAv?nrE&x9>~1?aW??hWBr5xV8HlWdN4&{>^Q_ zL~uu#;zY!;mGW3vD+fWU0vWmRraZVZB-w$E`jCE(w$JIuz@8IqM?{VW&MNJH1ZPOS zx2mAlXMM_2B(S&tpS~|drSuB_^ZP>h*em?A2=%H(kK?}Ij;!@cKI`k2bw)%m`n?@D z0AjtABC>$#ceew7J6^-?`=VbedH4HnD!-G-FL+kcoWcJ&llJ0)P1!O|rPul#Znggf zK(4ia1%hp-sCOJAvjBR^`CNl*E#A_#?@m)`=JK7E+rx=LJ+CF1F2jN&cr6vj1H8qE z!(t#7u)c&uj_Q$qe8c*iJ#Nr|hZ)*x9RU1{#f#=& zc3}r1rKS^!o(zC@<{9+GWFlVegHAfLr5UaFG@>c?o9D{TBaEAQ6YxE-k`F30Gw+jp zogR*yR-n^+c?|CoxJ}@x!mkqi80~TNjcQv>oyNo-FACD`xF->$hKMJj)7@pv|1hi- zMX1^{DjU}6pmv4Fqz}|ZA(Q@IW)$nRBg{(ImNUF3y44KQ`{?lb z(43 zlJet;R1N(z{BcnYr6M0=U>jcRv^>JY?~<58-XCBJ6!I~UejGg&PX7(A&!F`MJVEaT zry{+j=#``=3-Xmt9npILBcXfLKr%3Jl3pk-4(RlU;8t@Oy_2je7)IG+OV$(*qpJ$4 z3X-&{GG`f0gQ9HEfsXGJsU1{t`dbgNlI zYmy%q4W!?O(xoLdFPJNg(3J24fd4G{8M0Mh%Dt{kK2VsVlgoAi-j*C*hKb9Ttx4&0 zAxs}hUngc29ZA*NEDZUN#4&|Q`k?rDY#T(YxNcFv8g--XOgxOW&6HLVr7QCvE^7hq z#S^6rWG(pEH!XT)dg!;;rtr6-U)QYqoOKVAC<<9mJ%>`u(^@2;?K^_b- z7`bYJY`sv=2{nrD7HSAG+D)S=yFp0a1G;G(-7D1JKz1V&8-@Cf$WEpQggQq$o=y)6 zb%jtf=pmu-c0|9k=@FquifkS|CRB@17trGf;w*2cU!$GgoL{rww2HIe8q2`#_OAf9 z$mjKHbdRwL_yan_oyHA-8DkCLE5ey2`U~}C*!LbWf9S8IOOtm2-jKh+AEW)oMrfX; zKM44S|53n0-p!D%OKf#@G&)?$eI3r_W?~xzqms`7vZ3j@JM87vM{7xYTXnfM-hQik zK=uWg`G=ojt?@I!!x&tnQT}CEc}JTJX9O5NP^4k;{3;v({3LP6xCFRqp-s8i-&!?_|b3KC{0Kdnf{=@tc zPQ0oG`$^f_BS9yOFJ#1?zS+KLzQ6#t#lHf41i|nyJxZ+erNr^w&dmQB{E`B%!BGYb zjqcREfZUgz$m4eseqQ#yS@yZk#mGM|`zEFep@p9&A>S$6fj8Qe3J|o?X!Z^@JhaNC zrh)R(4wu^Ktv7u1l}kP2t+%(-4$U7EYE}kKs6==4KgB*+Tb-R5ZB{|L$mLxWjpA2Q z3tj4*RI>`vQkQB^MJYtrxYW$}QAU`qcd2>tQJ`#>D)Jm>0@>`62dfBB$mrpjL^tww;DnA3%TP~%SwHh&c&!tMs z&IR>}OU0vW$fVC)YCyEbFzJv>{i-zEtm32%bTaZuX_Vp=bg7%e&8mR#u90lr6OQ65 zTd7dXX{7fuBSFJl>Spg^BT3^N&7wU?jS6X6FSX1lq=iD=ZoFQ;!YHAugj#FtD_@-j znR8T)>ESg-DXkK!%Qz=|8>qFetUG>}QAX=r*`x8rpzd{Luc>aMoE~yz@2Sf`J?T<~ z-Zhk>&xHC|yT|j2mZEPR8J(BEgR&`V!r_kSV~tBMMax}kt*AceQe24WC{!11i|?Qc zYU2Hsy6EZBW>rBOUFyu>14acAPIYXPE4G5h3)O|(J_6ZULfuY3FMGnM%u<(-E0MPC z#vuBk+mSsTLhrZ~J2QlS?@~W2{ahbHML00>5F3Io8AIqimwH~kZdB1zLfP2gCuPH&G7JlG-_#pP)?$TQLRhy@WW`7OMMdmyD^NG3w1lG z!uN53{((?z#fwk--L7UWR?`UjkxSi}jM9;`y_ah79z_STy&Z#rk#yLhjE#Y2bu4}% z!LD@~PX(fQi{%To(s<0%tVU6xD|^NhrBPJj%Br!z8{`uX_d7Bar3M<|%3eml(bVEn zb4{kET6|cB>SbmW$(rQ~(-4lKc|vs=t05ahm$2{O|} z&%4x@$V?Nx;!F7>EtRwvK_m-?}a(h2kjP>6q}5e+u0@$?s0 zSQ3oVc>2bnB&KHaR&$cIA%J}taB5ier*9L8zTDH3#*9OKi?8Dol$0u>f& zCH+3y5SUC4Ix_kq+7y^VAGp-B(LC?zRKZs%>;i9qnnpD)b!l;)cRDo*wUT`069O~o z0ax}5yC->m;2he~PdJxe>80ib=F;w7 zN~3wSx0kv_pGRN!QW~AlKI8q&Zr|YPpUG>sb5Crr~sm*S3{G^(*r)=3jxie(vU9NQ<$&?J{)+0``Z zxIWp{bhb;eEKAMf`ea$&05zN&qfv!(BjZ*1|Nr3US;d3xiZi?c11lWpdA={jx9flJ zN`H56lDpt0Tt$^w&~U8p!ygH9WF;V53T{sDS=q# z5ja&~tH8NgX;uR!XdR$Ndj%G2dy=KpB3n|6Y(=ZVaimc?BzQpa05QO7ssz-iTD65H z(+Krsa2D{{`ID(ZRlSJgpLLMdAD9o@I#Uy;O&`T8#@V1&k+FYBBBG;2qjJl}v2Vy4A&jhqP@7;8{R+ zcN+qDMcb?%EQMwILHX-iv$)<2*S`RMTRi&}8otOji|-BU#;C3u)K$@tnj~>glDH>H z+@BMlXg?=D$u8Bd#i?njwigGo26cPcM8Gv=Q`9!~%Ys>WP4j7Jr{V+4*~F)xUFx&Y z)oPczBC;IN;p5;FgqY7bXVZAQdvqdukX|<*R0ruDa}A=sz3eg7rEW}~ryZm_@J??~ z%cIX=pyhCKw;CLL9{3-T5!T$T!kFc^1ZuddG0u=r5PWPa6AnX-&cp0~sYYm570%KY z$d`*&wX#_AEXc~7E~QhF&7Qg1)wn#>(S_cVf!|%O(NrnB`7X>B&(h{=v&&|C=8K`l z^tES!r&Xk_+J6=<_8g+dz*0{qhP=Vks-~pYdbZJ6Ok=CY@DROO_ONFK#(K_kBk@=_ zQugk08y~Unh%N(civ7-WtBCIs@m<8yZS-a0pPmD%+T-4Ye?|FPPlK9M-sG*LpBA(L{zv&*RY&}q*F3)oo#Q=3?_|r` zy=yg&Skqd9Id9A}JFx@nfIFZ zJf9)HN>3)Z5&Vu6J{2J=o1lMeY6rf0vu3qtN%(1RwTHtvL<`Hed!Iz8+2=gZO03UH ztS?GYz9^HjKxXbm;txG9%Ez4-rIuUOi@{P~gSsm|%ok9vgvR(55T9~hb>n_AH4mR@ za>;9a4W84>R{2^yQ;^*Sw25x^{aSR!c~tOz*u=-9toEX;5kTLoebp z%>mzs;?^NLI*_k_D$*}R`i1bgIn}(<)hEH;`(NF zZ|NK?RnGS|$#Hg@Bzc=8d9TJ-s7)H*cPB|1?$!7jwO8Zo(_Zboa-Tn}73VYlNxG>^E_^ zx)L7)FQNAX!|=_AVVr&sSW2G(4x;=ZZpTyvcoek(j-eHR?-%@lKvFWizzTtb)WdXB z-XOJw9>{AF&UkR1${P>P&-2=Zb1pdV*o11fCZopEw~f z;9F0{u?oQ}1y+Mo6>AcFoWK@vj*hhnJ~u1Pe6YsFa)NgW+#s-9;0}R11@0GkK%l1c zz?A}<^fYY>juX5M_&341f_Dkr0M5ywZozi|Umw~jc!fWkroV)m{A{Sr9|GLqPg8T4 zj5MtcR{*{fZZbqFFlVq11=3WVR}o-*KW>xY1Q}KcY!cWeFeh+_!2JR#B$@)7LQ?;2 zLTnJYL*R>{pRj4cDJ&fX4h}QFN$@s-Ie{Al?hv?NAmvFfflUH8>o-(EJ}3VrDP%Gab{s6U~H^*NCzA^YFAlt{iW?<-T5G@IY z#~1Ab42$V$<_-2o%`f2ZaR=b$Qf>d}#QK23zoe=LHO2nu#7aN^Bb^cWS5SEq2KZ;= z8>mKe?4u@}>c14foYUws`??9O+3V;`;Hv-?t)>~kuLmrqCan_i1Z@zNP&4onngG0% zCIT;|lYy7fB;aNEMmz`?n<>D{>2%;Jng%>Y(}545nZO6oEZ`M%Ht-6X1H6*jfLGEy z`;wFDv%9=+c)#(U?px>kwePpS5`Cb4jlNnB`G@+C@OyK7FYEuqzr9uccoY0{kP||E z^!=RX_uy{l==Jp;g}eLrc};PY)AxsX3pe!dD-N@#PK;-VWAN`w4&t7Vzo#2+Kuyit&`#znoY<`p@EnMNT`M zHpy6{-1a#75SLg~l}j&o>Fdzi$Fy8F^>Obh+S}f5DBpLjcCvOIp4D2DZ;f_^Z@u=E zuUmWF_YEENoubw1r)Z;eFU`=up|jE6iS|ByB{lh8(tfJ%)((MJ;NPu{+VW*fiLvF# z(|6}>Iic+fZOhs7K8;4V&Uxmi3#$&DdTqmB_r1D!*7Nqvj>?ABdWEK$x%+(;N(&nP zgxQs+nIm;+BTiFh_&w${{x3wz?1G5@x1Z=3s?D~4+c8|*Wrr4*26t)^OnM@g+i4G9 zT!lZ^oUz!d7-A&MGgZXQtqWBUx>c(<#kFu$!*6XsjjD0KZ+x|ncHmy zJ*HtUG@}NATpjCb3GlF|0xuH>&NRF5|EWH|7R%)@cd^_)Ur_K0;Mdw!ONLF@YXr4K ztP`VnD>N1HRFDB3Gq)!I>$yEJUnKok80;_v2gSq+Q92+>2h4@`olDA3gZt%X?hu~U zdLlMTi&GdWldzcY#lMXy)8T5@`DIQn_xd2mZ{3hRw=WOXrTO4qmuBl%49|{$+(5?7 z+}=0^;KFT^BX0$ggKljQ=S4N8I7NsdMI-(AW2Y$cxT!o+j^@!wEVtRrZ8clbd7Eao znz`+<*@DMrn`awlBN#kjZgX(BQd%@c(Uj)LUoqx3$Ii>Lhvku;x!LN6D?Be=9RnB@ zS~+Y0!RVK&*lfkMQC3s{hnz`s=I&BZ3Nsg*9jZV&jus~#h4+jFFx%`{ocykXp`dax zjPDn<*lf3PbYRHUiOr6k2Vc4tdUSItBTL{yBoe_~8XiUsu2in8MyITnN?AD$n~m(1 z*@*m>M&VWfAC`VS-J@6GrLxsJ>4Ad zGfrO3uA4YnpR;q$66#v(_FKTbn?mH|Bx82n=Hw*fW+D>`;tz*q)-tgLNq>MR7L&>| zDr`*ec2wHsoIG^pV({t^B0HkO0uz(r7KhsySX3x@#8%uftvd&Z1iRpRI+%qhslcXi=lCzwbv%K{Z14VlM!8BkHL(O80-PJ37p}EGjV3Ci^OJg_3i3mX9BJ~_Zsu} z2`M`$Tlo_#R0zw*b9c~QmafRw@pfa@VRtj93OW@FSyN0m3f;)tg|nVf2*|$pE}~Y3 zvDrL3X4qU}F7*}$=hlqg2>GCv477m6aDjr#{Mg`G8;V>7P3W1 zAHY;l_(!+MTfpkcnAhNhfS2on1UUfO8gj+rFw6FW9p7?lCgVTy^CkETQti|!d`9WU z^DV%)%lUxf{P5`vAL;QIcSLvJi)V#>c*Vfz9g9bQK+Lp+wuRW^hs4cM@ K|E#SY?*A`$KI8`g From 0b1b90a9ca53b337fb511847add5be1540b607ae Mon Sep 17 00:00:00 2001 From: robGG1997 Date: Tue, 14 Nov 2023 16:05:49 +0300 Subject: [PATCH 13/15] transfer erc 1155 test now passing --- Packages/io.chainsafe.web3-unity/Tests/Runtime/EvmTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Packages/io.chainsafe.web3-unity/Tests/Runtime/EvmTests.cs b/Packages/io.chainsafe.web3-unity/Tests/Runtime/EvmTests.cs index 55b954dc8..8764b25f0 100644 --- a/Packages/io.chainsafe.web3-unity/Tests/Runtime/EvmTests.cs +++ b/Packages/io.chainsafe.web3-unity/Tests/Runtime/EvmTests.cs @@ -333,7 +333,7 @@ public IEnumerator TestTransferErc721() [UnityTest] public IEnumerator TestTransferErc1155() { - config.TestResponse = "0x5de8fb6c522d4ba85f09961f03bbdc1ee8d9c283ddd1dea0f3ccdfa27cc189af"; + config.TestResponse = "0x390b47d378e9a6de830e2cc6d624de0920efc44d7b40fb61f75d983545c987fc"; var transferErc1155 = Erc1155.TransferErc1155(web3, Contracts.Erc1155, Transfer1155Id, Transfer1155Amount, SendToAddress); yield return new WaitUntil(() => transferErc1155.IsCompleted); if (transferErc1155.Exception != null) throw transferErc1155.Exception; From 8a332d6750a07722634aa570f895a2dbee3925e9 Mon Sep 17 00:00:00 2001 From: robGG1997 Date: Thu, 16 Nov 2023 08:03:01 +0000 Subject: [PATCH 14/15] Published Solution Dependencies to Package Libraries as DLLs --- .../Libraries/ChainSafe.Gaming.Unity.dll | Bin 15360 -> 15360 bytes .../ChainSafe.Gaming.WalletConnect.dll | Bin 36352 -> 36352 bytes .../Libraries/WalletConnectSharp.Events.dll | Bin 17920 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100755 Packages/io.chainsafe.web3-unity/Runtime/Libraries/WalletConnectSharp.Events.dll diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Libraries/ChainSafe.Gaming.Unity.dll b/Packages/io.chainsafe.web3-unity/Runtime/Libraries/ChainSafe.Gaming.Unity.dll index 75ac5a8ea5f1aae85915fa4803b9e49fd5af68d6..eeba981497716c1427453b3cfa381d470de706c7 100644 GIT binary patch delta 105 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 delta 6004 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 diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Libraries/WalletConnectSharp.Events.dll b/Packages/io.chainsafe.web3-unity/Runtime/Libraries/WalletConnectSharp.Events.dll deleted file mode 100755 index 5eb3f817b8b1d24105051de59002a7922e2c5fa8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17920 zcmeHvdw5*cb?-WlIrG$@8NF;I%f}CyL6T+p0oY)SWIeFJ#>SEjCJ=c%b1aQLnj_DQ zYzrYrF5t%H5h##AfCLOl!y_%^MQ-YRfe(k27FwE1(j*tSq)W_+z5)BITz;;3tDA6o==3 zFHGO^zr5hP%Ep%$3=F4o=4jR)%Em{`L_CwR^X4wg%#LNuRL1Py+;5K9Nvo}*BCyyM zy`_g}qoUFG?u`AQ(At}{&OuO=FpQS^zZSyWxve84x9RcOmw2lK!? zE(AcC^wrfHm^>!5YRl%b31FhzPGk(c>+qI69T3x&wbC{iSy!6E%d)!hmOULr>&sBd z*0Wvlux=`O=+!o&ch4nKg!=n~Ms=c!DA0_wn$zuStOlv7O)^>4CxuE>)vi$|#G{>v zHl`8P2UMeNmC?E^R?Cbc#Sm!MfzVnPni++age@sn%&e5T&Mluy_5f43_Fks#}7m zYB_H=K*X`8I;WGtsM560%X|z9e4KaqV)Iy0SOg@!c_C~e84%#y4nZ<2)LbJtV;DmN z>QQ~WPR}BRbs}nx%{U1U)z#d5&O#5pVB98Y)FaJhyU>xC$$}u&P&Io2=L5(ef+Rgq z8#JOyY$1q;Fl!*gk0N^!@Yaj!Z$rZ{K-Pi~bWpPwgMO@OPIgiVa~F{dEtr2PQWzlO=nw-rV7CASw4mWNyml+{dJJ5Fd<7dHt{nLN>MonL z?q0MDYe#YjNdeY#IZ{KNT)~MpxssFW$yJ;{_w$hE&Ib_6OkIP<1oMZyKCe#{HYT{@ zXs^q2|*xd<9Zl|b$yfEb3lrFpli>9@kA>tA6I zLp%+5Vwi|VbA7u(8&MZ763JaE_V-M}C-mBoI>BC6QCp?^qq_KsI>F9nOmMkp;1U@edrr1l60C)-5@N z>8_tLAk)aLM?upRmmGqC@?yv}a`1E%b4d(-)PnwRU~&)0G4!G35CTAXB{XJmXHkhu zV(3DP+p9jV17o{5Po0*Mwu16JY2kU)5-jh%*S-RE-v~zZR>SXjySK_yTVMA2*L_D{rE{T5p z@yEXAPIxoiiZCd&6b{jgH;8=U`5kZsPpSq}6H){`g?+ILbl5lIIA2+ea!hT13eDQr z0)$q72i2l#>^dM7PO0B!53JH_t30c3Luo{hc)nO=%wlxIHM%$AEi*cXz%|N7M3I%@LU$4Qh}@-GR?do zbH6vD-|%PnDOW1%xH`cCH9HTuH0omN1Xs4l01D;%k)O5ie!8H;={vX6i*_(DpWIF> z{NHKy0HiK)5nb9VH!pN|)?YuZxTw&EN?0#>zxc^*oI%$*vm|HDzeCp<>J{~CYAeY- z_mkz?2hr&>$*E~{!J!+4?F*7a2RG5T*{K2 zYwL3NGLc1cth^71c$Z|*wkpRi?0_(4UqqoC9$J?<2#yp{ z*O+_aF+ig(hVHj4d7};+r4rd&7eg(O2|mH`h@XWEsjd-dp-+G6bfIkP^*AK9BA_lr zPn<&g36OB`E7UCFw@ z1g1s(`D`jPgm8^>J`_3*-&?e;pDGtP_XUeC-PYHQG86b;_)xY!ZSP`cJYZF>nt#mi zM?LyiWhL z*GUbEd-?(fOwI6s!1wg`{Z({xfa|q;qF#eOZ4CPidd9=BOY%RJd|u$40-J?CSMuN1 zne%USmU(Zm0r@XMra`9!_Icj-2k2k4CqVymjZ60^+f;*oF7R5NDO-IEHz*8$;$vxV z3Vay!1YHlieTJU*vxd(L&)N|4)QJR-%C-KdXtGG;w2I87lD|rL&i69U8h?W?K&P}P z(Z_%A|98Wn`-JCK;s3GdcD_)a(%uLEDEbwk5&s9^{A<)TXtT(<4xC|nH~2&7g;C}q zf$d>oIOo@q^a!RPjy&z}3-(}4yr5LltqNm)3^QX*f>qEw!TwUPN?J^ZA*DLVoYmAu z4?}hyW!U-BO2IC0u~ou3s{Sg#oac#@N#!MP59D7UoQYs9Mzs!WfwrKMu7sXFz}Aw6 z7A}X}9;(M(3S<8$SOawl_OW0l%DK!GY$07L*!hAjqCT9$f1$1h{3~@WEvG;B{z!{Z zDlqO@KqJU2^o_ujMJhp= zaJRmkZWcr;vJ&hwXVE;9t<=fbS?j@EDZz{;j}k z6@z{e7zUhF(|`lsnUq2QPh9>j+*LZ_>j^yx-L7`AxV{Lj?R2m?Kl4XWc2KYv=aLfL8a1znwMz_o#sswhc zU`NoN0B)3Yi&(rfpPCuLOb?eRdR~q&7kYG3ZTK$uHJ-sHF(?SFN#^HpN zLA`-uSmTKf(@{05&!bksj`#w;8f8A+?P68F^A(eR=U@~J9@iIAM>E$uLUWwFO{dJ)6jYSO^sZoseHR2%`eORETgLrsq7U6N?q3tBEiCh}^lu5R5{&!zzvvvg z*C~UZm+3KjK(Kd|{~DZ&E!o>H#`bKbjjb%j>1`_|1^YZT`%I&i?kZx<#xi=u#g1yt z#tQnTi~TpF8Mj5xyV!nzv%xC{KQDM1)t7YM`|jXRETcSRxt)pJS4*OO?$~xL2*gIHbU(zq3ha67v$PN~P=Y@*B!A_%tz9wak%nth2 zG|uCK-H4T{7n~Sy?ar#x0AJt@Ki? zjdgM6$_8o|>_!@+&nX+|vt?zM(H(Bt{mKo-Wpuo(>~cEcmfeN+E{8mY`a-KNf{AG z&%YPTpD90`Q=@IreFq}y4#ZLZ+kq8$2fQ(Wl%s0|(1U2j`2~OhtY#d))(Gqr*eCEx zfjb1o1*Qb<6F3f7gE+-Sz8}=vs+a*AmCRByk)aSI*PtBhOKETzS-(ujHkbJjVQ5%nJxJO%{{Ss}Rqur-GrY!^IKB7tYCHv-^H`jaUCasM z72<^KZcfNf=OyX&3Gh_XOQPFLVu2IHdmkR7UunN~OIgmVBJDNV4ZSA2q1R;1dmHpu z=r!V<(W@f!Rgw0pNPAT@c@?XiLMMs$N+(61lcLW_S&e=x5mX%kJL$UVt=BrJ#~dg?WB0iN3ybhBtG+)a=Z4NUQN6<{FWZmqWVW-pOfN~ zI=tv9NZ@|0QRao-(AOwCd~fTW3I#sUmn%QgKG2_5j`@F!yuUmGIBca1ijslE?bPcHy2qJIEvp??Nk zNlo51?5KMIyQm-VavBHhrwdT`Q?wp1L7M>g3d~X3*C~>_XabUV(k*~f0v{6i2;D`; zea}k%P2u^az_&ybQn;2P&=lAq@UXx~1U@hDErFWK{EGy33hWXX7x=8e=LEhfa7t(X zhXg(=@J%O=-Q#zSHwBWxc~fAAz&8!9MP7y-l%w0}Ir;^4D%UIDP~zHgEvB#3|4@Hg z|FQmedWGi_SXe<6*0AUG;LnRF?2`x_0wk=nN3nKU`t5+{=yw5Dgi3I;{|lhh`5pqi zDEN4>bhXO0?BF*IN4knN(1X`2r!5~K-(UODGDmt9r=lS8b$}|JO%=%31FAG1_e%HW;{8`jz_C`XjpGnd^ypR(X0o{T_XaSMS2T z1I$5u+G9%Lu{?Q=^V7MWz>WcyV&Fa*k8>iefThED&%#@YH-a|`d+?)^7ju%+52e13 z8n6=MUWEQ8z-b6LpH4%9tH^P=iuw*;t((qs(?&O)@1`cr6?qF>e36Scx%i-)#@#gG zrWVo$`YG1QKT;a#5#=bYR&S%6dM7=h-a|iAzknC3s(L@(I^{?Dr@?E2w^80@p3TZK z&tl^D8k~@%EBJ22x;2A?E7}L!Y3)EZH8f;p2gXOOb-N10b&0{j?o@6x9Ut#X$8$M{ zbtYBn&m=Bi#Zot>a(OG$YiBoS5+b*p)|p9mTRAJ6il_^qQ&twdZa0?C)(sF()9r%6_MNlQR|~zbCo?u;W#hZj7Pxx$qoED) zOfqd{H^oOm=@ZhHti3;#w6Zf;7~MC99_7co>=D+LIX1*|!~IsiSQ(j1tqi)J5LsM_ zo9T@w@^*F{=xO7DUhEzm?9AAi@ezA0$K%w#a|QK|WfDlF_NI6yjv)lvol4|Wb|wx{ zD~i@xdAiW{!Ih2#l&xDsYd6`+v9z_02DZ`wLEr9pK2Aeceo*8KmYarIthLaS%nD0W z|2T$xq^-+Nr!CPU*XDMWwvHyY#K+TiJlU5dsfx5SnWV0GI=w5N*t_1^ZD*~XgH~cJ zZzU-)JK5#pri-j=^X#~(!NJ~?l}^%HIIA_XD?MIDD)wRB-oe53XbsnrF83Aj5$klS zRB-1-?)2g=JHy{KI<25{bkxcu30AQNQX|&oDIk|x`At@S*iKT$%IEU%rg%0<(`E;C zWi3?c%W!eR;wtPlUHD!X^=0Sw%CRiWOS(LS)eg+w*A4sjVH3W4~7xRKM-PW!#4oT~?_5tZ=f1b&m z?#Mz|oSm*Sj|=8q@E6u>G=*7OkdQ1DIkMPVGihb=OGSk?22%O*8UyhxY|@MG;8_Rk z?A{W8LDdbZB*IdO-g6MqFNc{}A{9jULeuS5cJ{))4CcW|T;_24j9hTQ-Kn857;DxR z5L*-3)adlWZfkdZEDdKI*=1!*S~;c@L0heK{GjA=rSgK-2ztq}ME>-GE_-wwE6DJy zMF@kL@lqYvNhLhyQ@c_eD@&zt*zNK3m__>qZnZ}2{Z?tJOkXrGGo0zNc{lvIe{5HR zmnk5dckeDlqJd#dtRyBoU4?jJ+T>zl-{T-(1@o=m4@qV^$d%jB1gV+q=dP-TzM<_vKx z>&HM6;#;>p5>I8MR@YdTmt&XGDG{IS(LszwJ`4TfnnkNxyWJ&$F1N5c78VG~##1?K zbEfBD!V-6-&g>9YU=HsE${D+l=yxEGNMi5Kw_TP)V@{Ca8AR~bQ7fAt7sMUfjj7B& za#o8y*idYVXUi6g0;MZ$fzI)-u*!>Jibx-JG?<=6d{7p%BAP?|x3a{MZ@_lrB3;$R zP6o2$(N63K+JjXj>#T-7BdI(i#dU#t)3y!V9_q>7w|zdz2o zHr_=5O0vn$q!@Mk-<^sNW$au&mB>j42kd-2y(yJWV+i1U$=oM+QXi`dr?hh1bQv~d z+7tqDzm?seVs6)~3zIcRgDH33=G-uksl|ROg56+~0_GUj*%k>t&8EEWNDU`5Am)MZ zmSC~$j#XB82?q0D> zij#%^OPh&!o*Z9-9g17w&Aav>Kw$&M(}h>ad@6%X&%sn)#>@@kb0Bg7(E_Sojcg zWw+(3jE(3x$AH2{nDc!Z%iR{U?_jFP%*27Ja{{M%6E|i#{8==DZw{wv9F&XkPEb2d z;;zr6JZPgRFVL5RZsMIJT5uug`|)PLwUn^6ganI*ATv)kWl5!=mV97b2ZFf;ot`1cr(BcAnhqC z)eXvYmALk>)G{H3mCu4B4*A?(NvSxbnJ7Idnv~l9B-vEchgvzbz_k;oy_t67YXw`- z2DgHbUzx{TQpEfSTCALh{3WL z$F=UbuEXcIGnFb(W>+ePnmqFC5fiimIeetmG*gC&?((>odkxLy!AXP4gOwByV}HvN zombM+#4RijHV@+gKxgo0tp(Tp<@WsN{%ZV4--DI4k1sxT=l4i6NzqM3@p%9uHJlB5 zOjQX3g3xEwL>DVngv3PRtBLy5DymX6ql)k<0k2XZd26CmUjYw3UvP6?Q)OmyOB6`- zL6lb{0HE!#TBZ0&3rDBE9-h~rd({TjUlWb1QEOh*!qrbW95zT*8Y)#!P4qfeO+iHu zDdBlxofHLCl;~h04HaIsCR#1jMhb^&qI1+nDAtPW1zZ|2%KZrY zOc1LT^rXb?H&H%p0|5R)dAQi6Fgg>4>iE7v)QU_!ceXbi?S^q+DL`M;!r#7z z=vYH^pMfEQWZaU$er!I9PW{j@Xr=6+(AWi~h%v6rsoNH3J>0B{NZ`yM=HU4Q)1aQ>@N>dM%p_QwYdJZg z@$6$I!w!vg3prhJ=OijiPaL#j8W!Mb4T1QB-PN$CQo$F;G2B%gULcAC!Un{cKxFD+ z%z(($gP6p~BZGekITaX}ii(iwRebV9rd~&bJSCmTL@c)fqv84*o1Oc!mW87oDh2|# zMsyspsZ|LZa3q8puy#1CD1azT(w8QxK7M)W>sR{PzrOjMZ+!Jn-&Yg^Y=~z8ByP0R zjp)>Uy1_~s(N&cwm^NXnnJ{z4MjWo9u2BnhQ+O?OR6=@m8xKr4nmVgpaf6l<1byv_ zriL3y^_jXFZa_f6g|s5!P`rg}aWL1S?a_AB$B;SMLQreO6*PWz0>2N1IACJ7BI-;X ztu)K#DaV%zo=Qa(iy^K7wMv}$eg(`6plC4)`(l_DtcE}4`j10m{{d4pgpcj;gB5%i z2?>5Vz*p@9YVB1x$zEZXPGvYp<03`@pDKLkD>S$5ik<;n%gA+Bt9x3$c>jvF<&Y4r zF5Z;5cgS*)Wa4uL9FVj2SSs0S4yA_Td&Uk}>0GO6CHGo;al@{V7ncwgUlZX=U}P5W z7R*@UTyxr9+g#DUVkLHJ#}%6TPNz9)XY;1L+sqGJCZ7rVv6yBC%QKy9CZ+?`&l= z@w9nBZW(f>oh=r}T_W>AK#{c@cLf-Bg*-4>+j1wn9ABzzmtmMYO&RCH!aY~;?>{QG zTDSt_!`=9{YYvSy?0@6m&ZyS7btwyP4=Mw>t(4S~!Uov~=ztO7fiR1?M(`uLm;H zwsS5m=)mVw=cGHccHsnD;4HLKx=`TWm!$H!cNBm8T#Zke3**4&@C boznPn-Tw=oEzbWLv6g?-^PkWEcNX|RK!~pP From c1ac3fde5f596acefc3992e41e7ece2f249fc7e5 Mon Sep 17 00:00:00 2001 From: robGG1997 Date: Thu, 16 Nov 2023 09:46:18 +0000 Subject: [PATCH 15/15] Published Solution Dependencies to Package Libraries as DLLs --- .../Libraries/WalletConnectSharp.Events.dll | Bin 0 -> 17920 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100755 Packages/io.chainsafe.web3-unity/Runtime/Libraries/WalletConnectSharp.Events.dll diff --git a/Packages/io.chainsafe.web3-unity/Runtime/Libraries/WalletConnectSharp.Events.dll b/Packages/io.chainsafe.web3-unity/Runtime/Libraries/WalletConnectSharp.Events.dll new file mode 100755 index 0000000000000000000000000000000000000000..5eb3f817b8b1d24105051de59002a7922e2c5fa8 GIT binary patch literal 17920 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 boznPn-Tw=oEzbWLv6g?-^PkWEcNX|RK!~pP literal 0 HcmV?d00001