diff --git a/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Plugins.meta b/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Plugins.meta new file mode 100644 index 000000000..b43dfb8c9 --- /dev/null +++ b/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 573bc83104efa7646bca1108f5feaa20 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Plugins/EthereumWindow.jslib b/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Plugins/EthereumWindow.jslib new file mode 100644 index 000000000..6be9dd2c2 --- /dev/null +++ b/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Plugins/EthereumWindow.jslib @@ -0,0 +1,36 @@ +mergeInto(LibraryManager.library, { + Request: async function (message, gameObjectName, callback, fallback) { + const parsedObjectName = UTF8ToString(gameObjectName); + const parsedCallback = UTF8ToString(callback); + const parsedFallback = UTF8ToString(fallback); + let request = JSON.parse(UTF8ToString(message)); + try + { + const response = await window.ethereum.request(request); + let rpcResponse = + { + jsonrpc: "2.0", + result: response, + id: request.id, + error: null + } + + var json = JSON.stringify(rpcResponse); + nethereumUnityInstance.SendMessage(parsedObjectName, parsedCallback, json); + } + catch(e) + { + let rpcResponse = + { + jsonrpc: "2.0", + id: request.id, + error: + { + message: e.message + } + } + var json = JSON.stringify(rpcResponse); + nethereumUnityInstance.SendMessage(parsedObjectName, parsedFallback, json); + } + } +}); \ No newline at end of file diff --git a/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Plugins/EthereumWindow.jslib.meta b/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Plugins/EthereumWindow.jslib.meta new file mode 100644 index 000000000..7b9a54aee --- /dev/null +++ b/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Plugins/EthereumWindow.jslib.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: 6f07ffb4f5724d94f9015b3a22dfa5b7 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 1 + 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.hyperplay/Runtime/Scripts/HyperPlayController.cs b/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Scripts/HyperPlayController.cs new file mode 100644 index 000000000..eda2a9a97 --- /dev/null +++ b/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Scripts/HyperPlayController.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using ChainSafe.Gaming.Web3; +using Nethereum.JsonRpc.Client.RpcMessages; +using Newtonsoft.Json; +using UnityEngine; + +namespace ChainSafe.Gaming.HyperPlay +{ + /// + /// A controller script for side-loaded browser games and making awaitable JsonRPC requests. + /// + public class HyperPlayController : MonoBehaviour + { + [DllImport("__Internal")] + public static extern string Request(string message, string gameObjectName, string callback, string fallback); + + private readonly Dictionary> _requestTcsMap = new Dictionary>(); + + /// + /// Make JsonRPC request to the HyperPlay side-loaded browser games on HyperPlay desktop client. + /// + /// JsonRPC method name. + /// JsonRPC request parameters. + /// Rpc Response. + public Task Request(string method, params object[] parameters) + { + string id = Guid.NewGuid().ToString(); + + var request = new RpcRequestMessage(id, method, parameters); + + string message = JsonConvert.SerializeObject(request); + + var requestTcs = new TaskCompletionSource(); + + _requestTcsMap.Add(id, requestTcs); + + Request(message, gameObject.name, nameof(Response), nameof(RequestError)); + + return requestTcs.Task; + } + + /// + /// JsonRPC Response callback. + /// + /// Response Result string. + /// Throws Exception if response has errors. + public void Response(string result) + { + var response = JsonConvert.DeserializeObject(result); + + if (_requestTcsMap.TryGetValue(response.Id.ToString(), out TaskCompletionSource requestTcs)) + { + if (!requestTcs.TrySetResult(response)) + { + requestTcs.SetException(new Web3Exception("Error setting result.")); + } + } + + else + { + throw new Web3Exception("Can't find Request Task."); + } + } + + /// + /// Request Error callback. + /// + /// Error message. + /// Throws exception if setting error fails. + public void RequestError(string error) + { + var response = JsonConvert.DeserializeObject(error); + + if (_requestTcsMap.TryGetValue(response.Id.ToString(), out TaskCompletionSource requestTcs)) + { + if (!requestTcs.TrySetException(new Web3Exception(response.Error.Message))) + { + requestTcs.SetException(new Web3Exception($"Error setting error: {response.Error.Message}")); + } + } + + else + { + throw new Web3Exception("Can't find request Task."); + } + } + } +} diff --git a/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Scripts/HyperPlayController.cs.meta b/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Scripts/HyperPlayController.cs.meta new file mode 100644 index 000000000..b7f2d1454 --- /dev/null +++ b/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Scripts/HyperPlayController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dd065f9f0732b2c44b758fa81040b54e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Scripts/HyperPlayWebGLProvider.cs b/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Scripts/HyperPlayWebGLProvider.cs new file mode 100644 index 000000000..054fc736e --- /dev/null +++ b/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Scripts/HyperPlayWebGLProvider.cs @@ -0,0 +1,90 @@ +using System.Threading.Tasks; +using ChainSafe.Gaming.Evm; +using ChainSafe.Gaming.LocalStorage; +using ChainSafe.Gaming.Web3; +using ChainSafe.Gaming.Web3.Environment; +using UnityEngine; + +namespace ChainSafe.Gaming.HyperPlay +{ + /// + /// Concrete implementation of for side-loaded browser games on HyperPlay desktop client. + /// + public class HyperPlayWebGLProvider : HyperPlayProvider + { + private readonly IHyperPlayConfig _config; + private readonly IHyperPlayData _data; + private readonly DataStorage _dataStorage; + private readonly HyperPlayController _hyperPlayController; + + /// + /// Initializes a new instance of the class. + /// + /// Injected . + /// Injected . + /// Injected . + /// HttpClient to make requests. + /// ChainConfig to fetch chain data. + /// Injected . + public HyperPlayWebGLProvider(IHyperPlayConfig config, IHyperPlayData data, DataStorage dataStorage, IHttpClient httpClient, IChainConfig chainConfig, ChainRegistryProvider chainRegistryProvider) : base(config, data, dataStorage, httpClient, chainConfig, chainRegistryProvider) + { + _config = config; + _data = data; + _dataStorage = dataStorage; + + // Initialize Unity controller. + _hyperPlayController = Object.FindObjectOfType(); + + if (_hyperPlayController == null) + { + GameObject controllerObj = new GameObject(nameof(HyperPlayController), typeof(HyperPlayController)); + + _hyperPlayController = controllerObj.GetComponent(); + } + + Object.DontDestroyOnLoad(_hyperPlayController.gameObject); + } + + /// + /// Connect to wallet from a side-loaded browser game via HyperPlay desktop client and return the account address. + /// + /// Signed-in account public address. + public override async Task Connect() + { + string[] accounts = await Perform("eth_requestAccounts"); + + string account = accounts[0]; + + // Saved account exists. + if (_data.RememberSession) + { + return account; + } + + if (_config.RememberSession) + { + _data.RememberSession = true; + + _data.SavedAccount = account; + + await _dataStorage.Save(_data); + } + + return account; + } + + /// + /// Make RPC request on HyperPlay desktop client. + /// + /// RPC request method name. + /// RPC request parameters. + /// RPC request response type. + /// RPC request Response. + public override async Task Perform(string method, params object[] parameters) + { + var response = await _hyperPlayController.Request(method, parameters); + + return response.Result.ToObject(); + } + } +} diff --git a/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Scripts/HyperPlayWebGLProvider.cs.meta b/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Scripts/HyperPlayWebGLProvider.cs.meta new file mode 100644 index 000000000..67877e92b --- /dev/null +++ b/Packages/io.chainsafe.web3-unity.hyperplay/Runtime/Scripts/HyperPlayWebGLProvider.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bf3150495260dda4db527f68c58bcafc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/ChainSafe.Gaming.HyperPlay/HyperPlayExtensions.cs b/src/ChainSafe.Gaming.HyperPlay/HyperPlayExtensions.cs index f1fde1cb0..4508ab6bc 100644 --- a/src/ChainSafe.Gaming.HyperPlay/HyperPlayExtensions.cs +++ b/src/ChainSafe.Gaming.HyperPlay/HyperPlayExtensions.cs @@ -1,9 +1,5 @@ -using ChainSafe.Gaming.Evm.Signers; using ChainSafe.Gaming.LocalStorage; using ChainSafe.Gaming.Web3.Build; -using ChainSafe.Gaming.Web3.Core; -using ChainSafe.Gaming.Web3.Core.Evm; -using ChainSafe.Gaming.Web3.Core.Logout; using ChainSafe.Gaming.Web3.Evm.Wallet; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -19,6 +15,19 @@ public static class HyperPlayExtensions /// Config for connecting via HyperPlay. /// The same service collection that was passed in. This enables fluent style. public static IWeb3ServiceCollection UseHyperPlay(this IWeb3ServiceCollection collection, IHyperPlayConfig config) + { + return collection.UseHyperPlay(config); + } + + /// + /// Binds implementation of as to Web3 as a service. + /// + /// Service collection to bind implementations to. + /// Config for connecting via HyperPlay. + /// Type of . + /// The same service collection that was passed in. This enables fluent style. + public static IWeb3ServiceCollection UseHyperPlay(this IWeb3ServiceCollection collection, IHyperPlayConfig config) + where T : HyperPlayProvider { collection.AssertServiceNotBound(); @@ -28,7 +37,7 @@ public static IWeb3ServiceCollection UseHyperPlay(this IWeb3ServiceCollection co collection.Replace(ServiceDescriptor.Singleton(typeof(IHyperPlayConfig), config)); - collection.UseWalletProvider(config); + collection.UseWalletProvider(config); return collection; } diff --git a/src/UnitySampleProject/Assets/Samples/web3.unity SDK HyperPlay/1.0.0/Web3.Unity HyperPlay Samples/Scripts/HyperPlayLoginProvider.cs b/src/UnitySampleProject/Assets/Samples/web3.unity SDK HyperPlay/1.0.0/Web3.Unity HyperPlay Samples/Scripts/HyperPlayLoginProvider.cs index f71ba2bad..93263321f 100644 --- a/src/UnitySampleProject/Assets/Samples/web3.unity SDK HyperPlay/1.0.0/Web3.Unity HyperPlay Samples/Scripts/HyperPlayLoginProvider.cs +++ b/src/UnitySampleProject/Assets/Samples/web3.unity SDK HyperPlay/1.0.0/Web3.Unity HyperPlay Samples/Scripts/HyperPlayLoginProvider.cs @@ -44,10 +44,16 @@ public Web3Builder ConfigureServices(Web3Builder web3Builder) { return web3Builder.Configure(services => { - services.UseHyperPlay(new HyperPlayConfig + var config = new HyperPlayConfig { RememberSession = rememberMeToggle.isOn || _storedSessionAvailable, - }).UseWalletSigner().UseWalletTransactionExecutor(); + }; +#if UNITY_WEBGL && !UNITY_EDITOR + services.UseHyperPlay(config); +#else + services.UseHyperPlay(config); +#endif + services.UseWalletSigner().UseWalletTransactionExecutor(); }); }