diff --git a/Assets/Thirdweb/Editor/ThirdwebManagerEditor.cs b/Assets/Thirdweb/Editor/ThirdwebManagerEditor.cs index b3fe1b0c..a7cacd7c 100644 --- a/Assets/Thirdweb/Editor/ThirdwebManagerEditor.cs +++ b/Assets/Thirdweb/Editor/ThirdwebManagerEditor.cs @@ -5,119 +5,59 @@ namespace Thirdweb.Editor { - [CustomEditor(typeof(ThirdwebManager))] - public class ThirdwebManagerEditor : UnityEditor.Editor + public abstract class ThirdwebManagerBaseEditor : UnityEditor.Editor + where T : MonoBehaviour { - private SerializedProperty clientIdProp; - private SerializedProperty bundleIdProp; - private SerializedProperty initializeOnAwakeProp; - private SerializedProperty showDebugLogsProp; - private SerializedProperty optOutUsageAnalyticsProp; - private SerializedProperty supportedChainsProp; - private SerializedProperty redirectPageHtmlOverrideProp; - - private int selectedTab = 0; - private readonly string[] tabTitles = { "Client", "Preferences", "Misc", "Debug" }; + protected SerializedProperty initializeOnAwakeProp; + protected SerializedProperty showDebugLogsProp; + protected SerializedProperty autoConnectLastWalletProp; + protected SerializedProperty supportedChainsProp; + protected SerializedProperty includedWalletIdsProp; + protected SerializedProperty redirectPageHtmlOverrideProp; + protected SerializedProperty rpcOverridesProp; - private GUIStyle headerStyle; - private GUIStyle buttonStyle; + protected int selectedTab; + protected GUIStyle buttonStyle; + protected Texture2D bannerImage; - private Texture2D bannerImage; + protected virtual string[] TabTitles => new string[] { "Client/Server", "Preferences", "Misc", "Debug" }; - private void OnEnable() + protected virtual void OnEnable() { - clientIdProp = FindProperty("ClientId"); - bundleIdProp = FindProperty("BundleId"); - initializeOnAwakeProp = FindProperty("InitializeOnAwake"); - showDebugLogsProp = FindProperty("ShowDebugLogs"); - optOutUsageAnalyticsProp = FindProperty("OptOutUsageAnalytics"); - supportedChainsProp = FindProperty("SupportedChains"); - redirectPageHtmlOverrideProp = FindProperty("RedirectPageHtmlOverride"); + initializeOnAwakeProp = FindProp("InitializeOnAwake"); + showDebugLogsProp = FindProp("ShowDebugLogs"); + autoConnectLastWalletProp = FindProp("AutoConnectLastWallet"); + supportedChainsProp = FindProp("SupportedChains"); + includedWalletIdsProp = FindProp("IncludedWalletIds"); + redirectPageHtmlOverrideProp = FindProp("RedirectPageHtmlOverride"); + rpcOverridesProp = FindProp("RpcOverrides"); bannerImage = Resources.Load("EditorBanner"); } - private void InitializeStyles() - { - buttonStyle = new GUIStyle(GUI.skin.button) - { - fontStyle = FontStyle.Bold, - alignment = TextAnchor.MiddleLeft, - padding = new RectOffset(10, 10, 10, 10) - }; - } - - private SerializedProperty FindProperty(string propertyName) - { - var targetType = target.GetType(); - var property = targetType.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); - if (property == null) - return null; - - var backingFieldName = $"<{propertyName}>k__BackingField"; - return serializedObject.FindProperty(backingFieldName); - } - public override void OnInspectorGUI() { serializedObject.Update(); - if (headerStyle == null || buttonStyle == null) + if (buttonStyle == null) { InitializeStyles(); } - // Draw Banner and Title DrawBannerAndTitle(); - - // Draw Tab Bar DrawTabs(); - - // Draw Selected Tab Content GUILayout.Space(10); DrawSelectedTabContent(); serializedObject.ApplyModifiedProperties(); } - private void DrawBannerAndTitle() - { - GUILayout.BeginVertical(); - GUILayout.Space(10); - - GUILayout.BeginHorizontal(); - - if (bannerImage != null) - { - GUILayout.Label(bannerImage, GUILayout.Width(64), GUILayout.Height(64)); - } - - GUILayout.Space(10); - - GUILayout.BeginVertical(); - GUILayout.Space(10); - GUILayout.Label("Thirdweb Configuration", EditorStyles.boldLabel); - GUILayout.Label("Configure your settings and preferences.\nYou can access ThirdwebManager.Instance from anywhere.", EditorStyles.miniLabel); - GUILayout.EndVertical(); - - GUILayout.EndHorizontal(); - - GUILayout.Space(10); - - GUILayout.EndVertical(); - } - - private void DrawTabs() - { - selectedTab = GUILayout.Toolbar(selectedTab, tabTitles, GUILayout.Height(25)); - } - - private void DrawSelectedTabContent() + protected virtual void DrawSelectedTabContent() { switch (selectedTab) { case 0: - DrawClientTab(); + DrawClientOrServerTab(); break; case 1: DrawPreferencesTab(); @@ -134,62 +74,54 @@ private void DrawSelectedTabContent() } } - private void DrawClientTab() - { - EditorGUILayout.HelpBox("Configure your client settings here.", MessageType.Info); - DrawProperty(clientIdProp, "Client ID"); - DrawProperty(bundleIdProp, "Bundle ID"); - DrawButton( - "Create API Key", - () => - { - Application.OpenURL("https://thirdweb.com/create-api-key"); - } - ); - } + protected abstract void DrawClientOrServerTab(); - private void DrawPreferencesTab() + protected virtual void DrawPreferencesTab() { EditorGUILayout.HelpBox("Set your preferences and initialization options here.", MessageType.Info); DrawProperty(initializeOnAwakeProp, "Initialize On Awake"); DrawProperty(showDebugLogsProp, "Show Debug Logs"); - DrawProperty(optOutUsageAnalyticsProp, "Opt-Out of Usage Analytics"); + DrawProperty(autoConnectLastWalletProp, "Auto-Connect Last Wallet"); } - private void DrawMiscTab() + protected virtual void DrawMiscTab() { EditorGUILayout.HelpBox("Configure other settings here.", MessageType.Info); - - // Wallet Connect Settings - GUILayout.Label("Wallet Connect Settings", EditorStyles.boldLabel); - DrawProperty(supportedChainsProp, "Supported Chains"); - + DrawProperty(rpcOverridesProp, "RPC Overrides"); GUILayout.Space(10); - - // Desktop OAuth Settings - GUILayout.Label("Desktop OAuth Settings", EditorStyles.boldLabel); - EditorGUILayout.LabelField("Redirect Page HTML Override", EditorStyles.boldLabel); + EditorGUILayout.LabelField("OAuth Redirect Page HTML Override", EditorStyles.boldLabel); redirectPageHtmlOverrideProp.stringValue = EditorGUILayout.TextArea(redirectPageHtmlOverrideProp.stringValue, GUILayout.MinHeight(75)); + GUILayout.Space(10); + DrawProperty(supportedChainsProp, "WalletConnect Supported Chains"); + DrawProperty(includedWalletIdsProp, "WalletConnect Included Wallet IDs"); } - private void DrawDebugTab() + protected virtual void DrawDebugTab() { EditorGUILayout.HelpBox("Debug your settings here.", MessageType.Info); - DrawButton( "Log Active Wallet Info", () => { if (Application.isPlaying) { - var wallet = ((ThirdwebManager)target).GetActiveWallet(); - if (wallet != null) + var mgr = target as T; + var method = mgr.GetType().GetMethod("GetActiveWallet", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (method != null) { - Debug.Log($"Active Wallet ({wallet.GetType().Name}) Address: {wallet.GetAddress().Result}"); + var wallet = method.Invoke(mgr, null) as IThirdwebWallet; + if (wallet != null) + { + Debug.Log($"Active Wallet ({wallet.GetType().Name}) Address: {wallet.GetAddress().Result}"); + } + else + { + Debug.LogWarning("No active wallet found."); + } } else { - Debug.LogWarning("No active wallet found."); + Debug.LogWarning("GetActiveWallet() not found."); } } else @@ -198,7 +130,6 @@ private void DrawDebugTab() } } ); - DrawButton( "Open Documentation", () => @@ -208,7 +139,42 @@ private void DrawDebugTab() ); } - private void DrawProperty(SerializedProperty property, string label) + protected void DrawBannerAndTitle() + { + GUILayout.BeginVertical(); + GUILayout.Space(10); + GUILayout.BeginHorizontal(); + if (bannerImage != null) + { + GUILayout.Label(bannerImage, GUILayout.Width(64), GUILayout.Height(64)); + } + GUILayout.Space(10); + GUILayout.BeginVertical(); + GUILayout.Space(10); + GUILayout.Label("Thirdweb Configuration", EditorStyles.boldLabel); + GUILayout.Label("Configure your settings and preferences.\nYou can access ThirdwebManager.Instance from anywhere.", EditorStyles.miniLabel); + GUILayout.EndVertical(); + GUILayout.EndHorizontal(); + GUILayout.Space(10); + GUILayout.EndVertical(); + } + + protected void DrawTabs() + { + selectedTab = GUILayout.Toolbar(selectedTab, TabTitles, GUILayout.Height(25)); + } + + protected void InitializeStyles() + { + buttonStyle = new GUIStyle(GUI.skin.button) + { + fontStyle = FontStyle.Bold, + alignment = TextAnchor.MiddleLeft, + padding = new RectOffset(10, 10, 10, 10) + }; + } + + protected void DrawProperty(SerializedProperty property, string label) { if (property != null) { @@ -220,15 +186,81 @@ private void DrawProperty(SerializedProperty property, string label) } } - private void DrawButton(string label, System.Action action) + protected void DrawButton(string label, System.Action action) { GUILayout.FlexibleSpace(); - // center label if (GUILayout.Button(label, buttonStyle, GUILayout.Height(35), GUILayout.ExpandWidth(true))) { action.Invoke(); } GUILayout.FlexibleSpace(); } + + protected SerializedProperty FindProp(string propName) + { + var targetType = target.GetType(); + var property = targetType.GetProperty(propName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + if (property == null) + return null; + var backingFieldName = $"<{propName}>k__BackingField"; + return serializedObject.FindProperty(backingFieldName); + } + } + + [CustomEditor(typeof(ThirdwebManager))] + public class ThirdwebManagerEditor : ThirdwebManagerBaseEditor + { + SerializedProperty clientIdProp; + SerializedProperty bundleIdProp; + + protected override void OnEnable() + { + base.OnEnable(); + clientIdProp = FindProp("ClientId"); + bundleIdProp = FindProp("BundleId"); + } + + protected override string[] TabTitles => new string[] { "Client", "Preferences", "Misc", "Debug" }; + + protected override void DrawClientOrServerTab() + { + EditorGUILayout.HelpBox("Configure your client settings here.", MessageType.Info); + DrawProperty(clientIdProp, "Client ID"); + DrawProperty(bundleIdProp, "Bundle ID"); + DrawButton( + "Create API Key", + () => + { + Application.OpenURL("https://thirdweb.com/create-api-key"); + } + ); + } + } + + [CustomEditor(typeof(ThirdwebManagerServer))] + public class ThirdwebManagerServerEditor : ThirdwebManagerBaseEditor + { + SerializedProperty secretKeyProp; + + protected override void OnEnable() + { + base.OnEnable(); + secretKeyProp = FindProp("SecretKey"); + } + + protected override string[] TabTitles => new string[] { "Client", "Preferences", "Misc", "Debug" }; + + protected override void DrawClientOrServerTab() + { + EditorGUILayout.HelpBox("Configure your client settings here.", MessageType.Info); + DrawProperty(secretKeyProp, "Secret Key"); + DrawButton( + "Create API Key", + () => + { + Application.OpenURL("https://thirdweb.com/create-api-key"); + } + ); + } } } diff --git a/Assets/Thirdweb/Examples/Scripts/PlaygroundManager.cs b/Assets/Thirdweb/Examples/Scripts/PlaygroundManager.cs index 9cdac9af..e55bb617 100644 --- a/Assets/Thirdweb/Examples/Scripts/PlaygroundManager.cs +++ b/Assets/Thirdweb/Examples/Scripts/PlaygroundManager.cs @@ -125,6 +125,7 @@ private async void ConnectWallet(WalletOptions options) currentPanel.Action1Button.onClick.AddListener(async () => { var address = await wallet.GetAddress(); + address.CopyToClipboard(); Log(currentPanel.LogText, $"Address: {address}"); }); diff --git a/Assets/Thirdweb/Plugins/ThreadingPatcher/Editor/WebGLThreadingPatcher.cs b/Assets/Thirdweb/Plugins/ThreadingPatcher/Editor/WebGLThreadingPatcher.cs index 03745144..d6f6dbee 100644 --- a/Assets/Thirdweb/Plugins/ThreadingPatcher/Editor/WebGLThreadingPatcher.cs +++ b/Assets/Thirdweb/Plugins/ThreadingPatcher/Editor/WebGLThreadingPatcher.cs @@ -18,7 +18,11 @@ public void OnPostBuildPlayerScriptDLLs(BuildReport report) if (report.summary.platform != BuildTarget.WebGL) return; +#if UNITY_2022_1_OR_NEWER var mscorLibDll = report.GetFiles().FirstOrDefault(f => f.path.EndsWith("mscorlib.dll")).path; +#else + var mscorLibDll = report.files.FirstOrDefault(f => f.path.EndsWith("mscorlib.dll")).path; +#endif if (mscorLibDll == null) { Debug.LogError("Can't find mscorlib.dll in build dll files"); diff --git a/Assets/Thirdweb/Plugins/ThreadingPatcher/Plugins/SystemThreadingTimer.jslib b/Assets/Thirdweb/Plugins/ThreadingPatcher/Plugins/SystemThreadingTimer.jslib index 5c75db8a..af011ec8 100644 --- a/Assets/Thirdweb/Plugins/ThreadingPatcher/Plugins/SystemThreadingTimer.jslib +++ b/Assets/Thirdweb/Plugins/ThreadingPatcher/Plugins/SystemThreadingTimer.jslib @@ -15,7 +15,7 @@ var SystemThreadingTimerLib = { setTimeout(function() { if (id === vars.currentCallbackId) - Runtime.dynCall('v', vars.callback); + {{{ makeDynCall("v", "vars.callback") }}}(); }, interval); } diff --git a/Assets/Thirdweb/Plugins/WalletConnectUnity/com.walletconnect.core/Runtime/External/NativeWebSocket/WebSocket.jslib b/Assets/Thirdweb/Plugins/WalletConnectUnity/com.walletconnect.core/Runtime/External/NativeWebSocket/WebSocket.jslib index 052f2132..4fbe4587 100644 --- a/Assets/Thirdweb/Plugins/WalletConnectUnity/com.walletconnect.core/Runtime/External/NativeWebSocket/WebSocket.jslib +++ b/Assets/Thirdweb/Plugins/WalletConnectUnity/com.walletconnect.core/Runtime/External/NativeWebSocket/WebSocket.jslib @@ -150,7 +150,7 @@ var LibraryWebSocket = { console.log("[JSLIB WebSocket] Connected."); if (webSocketState.onOpen) - Module.dynCall_vi(webSocketState.onOpen, instanceId); + {{{ makeDynCall("vi", "webSocketState.onOpen") }}}(instanceId); }; @@ -170,7 +170,7 @@ var LibraryWebSocket = { HEAPU8.set(dataBuffer, buffer); try { - Module.dynCall_viii(webSocketState.onMessage, instanceId, buffer, dataBuffer.length); + {{{ makeDynCall("viii", "webSocketState.onMessage") }}}(instanceId, buffer, dataBuffer.length); } finally { _free(buffer); } @@ -182,7 +182,7 @@ var LibraryWebSocket = { HEAPU8.set(dataBuffer, buffer); try { - Module.dynCall_viii(webSocketState.onMessage, instanceId, buffer, dataBuffer.length); + {{{ makeDynCall("viii", "webSocketState.onMessage") }}}(instanceId, buffer, dataBuffer.length); } finally { _free(buffer); } @@ -204,7 +204,7 @@ var LibraryWebSocket = { stringToUTF8(msg, buffer, length); try { - Module.dynCall_vii(webSocketState.onError, instanceId, buffer); + {{{ makeDynCall("vii", "webSocketState.onError") }}}(instanceId, buffer); } finally { _free(buffer); } @@ -219,7 +219,7 @@ var LibraryWebSocket = { console.log("[JSLIB WebSocket] Closed."); if (webSocketState.onClose) - Module.dynCall_vii(webSocketState.onClose, instanceId, ev.code); + {{{ makeDynCall("vii", "webSocketState.onClose") }}}(instanceId, ev.code); delete instance.ws; diff --git a/Assets/Thirdweb/Plugins/WebGLInputCopy/WebGLInput/Mobile/WebGLInputMobile.jslib b/Assets/Thirdweb/Plugins/WebGLInputCopy/WebGLInput/Mobile/WebGLInputMobile.jslib index c3fc6192..8fdfde3d 100644 --- a/Assets/Thirdweb/Plugins/WebGLInputCopy/WebGLInput/Mobile/WebGLInputMobile.jslib +++ b/Assets/Thirdweb/Plugins/WebGLInputCopy/WebGLInput/Mobile/WebGLInputMobile.jslib @@ -6,7 +6,7 @@ var WebGLInputMobile = { document.body.addEventListener("touchend", function () { document.body.removeEventListener("touchend", arguments.callee); - Runtime.dynCall("vi", touchend, [id]); + {{{ makeDynCall("vi", "touchend") }}}(id); }); return id; @@ -14,7 +14,7 @@ var WebGLInputMobile = { WebGLInputMobileOnFocusOut: function (id, focusout) { document.body.addEventListener("focusout", function () { document.body.removeEventListener("focusout", arguments.callee); - Runtime.dynCall("vi", focusout, [id]); + {{{ makeDynCall("vi", "focusout") }}}(id); }); }, } diff --git a/Assets/Thirdweb/Plugins/WebGLInputCopy/WebGLInput/WebGLInput.jslib b/Assets/Thirdweb/Plugins/WebGLInputCopy/WebGLInput/WebGLInput.jslib index a17092d5..62055cbd 100644 --- a/Assets/Thirdweb/Plugins/WebGLInputCopy/WebGLInput/WebGLInput.jslib +++ b/Assets/Thirdweb/Plugins/WebGLInputCopy/WebGLInput/WebGLInput.jslib @@ -1,19 +1,6 @@ var WebGLInput = { $instances: [], WebGLInputInit : function() { - // use WebAssembly.Table : makeDynCall - // when enable. dynCall is undefined - if(typeof dynCall === "undefined") - { - // make Runtime.dynCall to undefined - Runtime = { dynCall : undefined } - } - else - { - // Remove the `Runtime` object from "v1.37.27: 12/24/2017" - // if Runtime not defined. create and add functon!! - if(typeof Runtime === "undefined") Runtime = { dynCall : dynCall } - } }, WebGLInputCreate: function (canvasId, x, y, width, height, fontsize, text, placeholder, isMultiLine, isPassword, isHidden, isMobile) { @@ -112,7 +99,7 @@ var WebGLInput = { input.setSelectionRange(start + 1, start + 1); input.oninput(); // call oninput to exe ValueChange function!! } else { - (!!Runtime.dynCall) ? Runtime.dynCall("vii", cb, [id, e.shiftKey ? -1 : 1]) : {{{ makeDynCall("vii", "cb") }}}(id, e.shiftKey ? -1 : 1); + {{{ makeDynCall("vii", "cb") }}}(id, e.shiftKey ? -1 : 1); } } }); @@ -124,13 +111,13 @@ var WebGLInput = { WebGLInputOnFocus: function (id, cb) { var input = instances[id]; input.onfocus = function () { - (!!Runtime.dynCall) ? Runtime.dynCall("vi", cb, [id]) : {{{ makeDynCall("vi", "cb") }}}(id); + {{{ makeDynCall("vi", "cb") }}}(id); }; }, WebGLInputOnBlur: function (id, cb) { var input = instances[id]; input.onblur = function () { - (!!Runtime.dynCall) ? Runtime.dynCall("vi", cb, [id]) : {{{ makeDynCall("vi", "cb") }}}(id); + {{{ makeDynCall("vi", "cb") }}}(id); }; }, WebGLInputIsFocus: function (id) { @@ -143,7 +130,7 @@ var WebGLInput = { var bufferSize = lengthBytesUTF8(returnStr) + 1; var buffer = _malloc(bufferSize); stringToUTF8(returnStr, buffer, bufferSize); - (!!Runtime.dynCall) ? Runtime.dynCall("vii", cb, [id, buffer]) : {{{ makeDynCall("vii", "cb") }}}(id, buffer); + {{{ makeDynCall("vii", "cb") }}}(id, buffer); }; }, WebGLInputOnEditEnd:function(id, cb){ @@ -153,7 +140,7 @@ var WebGLInput = { var bufferSize = lengthBytesUTF8(returnStr) + 1; var buffer = _malloc(bufferSize); stringToUTF8(returnStr, buffer, bufferSize); - (!!Runtime.dynCall) ? Runtime.dynCall("vii", cb, [id, buffer]) : {{{ makeDynCall("vii", "cb") }}}(id, buffer); + {{{ makeDynCall("vii", "cb") }}}(id, buffer); }; }, WebGLInputOnKeyboardEvent:function(id, cb){ @@ -167,7 +154,7 @@ var WebGLInput = { var shift = e.shiftKey ? 1 : 0; var ctrl = e.ctrlKey ? 1 : 0; var alt = e.altKey ? 1 : 0; - (!!Runtime.dynCall) ? Runtime.dynCall("viiiiiii", cb, [id, mode, key, code, shift, ctrl, alt]) : {{{ makeDynCall("viiiiiii", "cb") }}}(id, mode, key, code, shift, ctrl, alt); + {{{ makeDynCall("viiiiiii", "cb") }}}(id, mode, key, code, shift, ctrl, alt); } } input.addEventListener('keydown', function(e) { func(1, e); }); diff --git a/Assets/Thirdweb/Plugins/WebGLInputCopy/WebGLWindow/WebGLWindow.jslib b/Assets/Thirdweb/Plugins/WebGLInputCopy/WebGLWindow/WebGLWindow.jslib index 42bec935..a81860f2 100644 --- a/Assets/Thirdweb/Plugins/WebGLInputCopy/WebGLWindow/WebGLWindow.jslib +++ b/Assets/Thirdweb/Plugins/WebGLInputCopy/WebGLWindow/WebGLWindow.jslib @@ -1,18 +1,9 @@ +#if parseInt(EMSCRIPTEN_VERSION.split('.')[0]) < 2 || (parseInt(EMSCRIPTEN_VERSION.split('.')[0]) == 2 && parseInt(EMSCRIPTEN_VERSION.split('.')[1]) < 0) || (parseInt(EMSCRIPTEN_VERSION.split('.')[0]) == 2 && parseInt(EMSCRIPTEN_VERSION.split('.')[1]) == 0 && parseInt(EMSCRIPTEN_VERSION.split('.')[2]) < 3) +#error "ThirdWeb plugin requires building with Emscripten 2.0.3 and Unity 2021.2 or newer. Please update" +#endif + var WebGLWindow = { WebGLWindowInit : function() { - // use WebAssembly.Table : makeDynCall - // when enable. dynCall is undefined - if(typeof dynCall === "undefined") - { - // make Runtime.dynCall to undefined - Runtime = { dynCall : undefined } - } - else - { - // Remove the `Runtime` object from "v1.37.27: 12/24/2017" - // if Runtime not defined. create and add functon!! - if(typeof Runtime === "undefined") Runtime = { dynCall : dynCall } - } }, WebGLWindowGetCanvasName: function() { var elements = document.getElementsByTagName('canvas'); @@ -33,17 +24,17 @@ var WebGLWindow = { }, WebGLWindowOnFocus: function (cb) { window.addEventListener('focus', function () { - (!!Runtime.dynCall) ? Runtime.dynCall("v", cb, []) : {{{ makeDynCall("v", "cb") }}}(); + {{{ makeDynCall("v", "cb") }}}(); }); }, WebGLWindowOnBlur: function (cb) { window.addEventListener('blur', function () { - (!!Runtime.dynCall) ? Runtime.dynCall("v", cb, []) : {{{ makeDynCall("v", "cb") }}}(); + {{{ makeDynCall("v", "cb") }}}(); }); }, WebGLWindowOnResize: function(cb) { window.addEventListener('resize', function () { - (!!Runtime.dynCall) ? Runtime.dynCall("v", cb, []) : {{{ makeDynCall("v", "cb") }}}(); + {{{ makeDynCall("v", "cb") }}}(); }); }, WebGLWindowInjectFullscreen : function () { diff --git a/Assets/Thirdweb/Runtime/NET/Thirdweb.dll b/Assets/Thirdweb/Runtime/NET/Thirdweb.dll index 9ab540ac..973ca628 100644 Binary files a/Assets/Thirdweb/Runtime/NET/Thirdweb.dll and b/Assets/Thirdweb/Runtime/NET/Thirdweb.dll differ diff --git a/Assets/Thirdweb/Runtime/Unity/Browser/CrossPlatformUnityBrowser.cs b/Assets/Thirdweb/Runtime/Unity/Browser/CrossPlatformUnityBrowser.cs index 58dafa8d..b87c4b16 100644 --- a/Assets/Thirdweb/Runtime/Unity/Browser/CrossPlatformUnityBrowser.cs +++ b/Assets/Thirdweb/Runtime/Unity/Browser/CrossPlatformUnityBrowser.cs @@ -16,12 +16,23 @@ public CrossPlatformUnityBrowser(string htmlOverride = null) htmlOverride = null; } - var go = new GameObject("WebGLInAppWalletBrowser"); - #if UNITY_EDITOR _unityBrowser = new InAppWalletBrowser(htmlOverride); #elif UNITY_WEBGL - _unityBrowser = go.AddComponent(); +#if UNITY_6000_0_OR_NEWER + var existingBrowser = UnityEngine.Object.FindAnyObjectByType(); +#else + var existingBrowser = GameObject.FindObjectOfType(); +#endif + if (existingBrowser != null) + { + _unityBrowser = existingBrowser; + } + else + { + var go = new GameObject("WebGLInAppWalletBrowser"); + _unityBrowser = go.AddComponent(); + } #elif UNITY_ANDROID _unityBrowser = new AndroidBrowser(); #elif UNITY_IOS diff --git a/Assets/Thirdweb/Runtime/Unity/Prefabs/ThirdwebManager.prefab b/Assets/Thirdweb/Runtime/Unity/Prefabs/ThirdwebManager.prefab index c1809009..8ea4a188 100644 --- a/Assets/Thirdweb/Runtime/Unity/Prefabs/ThirdwebManager.prefab +++ b/Assets/Thirdweb/Runtime/Unity/Prefabs/ThirdwebManager.prefab @@ -52,7 +52,9 @@ MonoBehaviour: k__BackingField: 1 k__BackingField: 0 k__BackingField: ee6e060000000000 + k__BackingField: [] k__BackingField: + k__BackingField: [] --- !u!1001 &5352000285921552497 PrefabInstance: m_ObjectHideFlags: 0 diff --git a/Assets/Thirdweb/Runtime/Unity/Prefabs/ThirdwebManagerServer.prefab b/Assets/Thirdweb/Runtime/Unity/Prefabs/ThirdwebManagerServer.prefab new file mode 100644 index 00000000..88cb514f --- /dev/null +++ b/Assets/Thirdweb/Runtime/Unity/Prefabs/ThirdwebManagerServer.prefab @@ -0,0 +1,55 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &2985666425045432145 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7977017408921700919} + - component: {fileID: 5964706357837968276} + m_Layer: 0 + m_Name: ThirdwebManagerServer + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &7977017408921700919 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2985666425045432145} + serializedVersion: 2 + 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_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &5964706357837968276 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2985666425045432145} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a197f9da6b1311840bb25b207982e287, type: 3} + m_Name: + m_EditorClassIdentifier: + k__BackingField: 1 + k__BackingField: 1 + k__BackingField: 0 + k__BackingField: 0 + k__BackingField: ee6e060000000000 + k__BackingField: [] + k__BackingField: + k__BackingField: [] + k__BackingField: diff --git a/Assets/Thirdweb/Runtime/Unity/Prefabs/ThirdwebManagerServer.prefab.meta b/Assets/Thirdweb/Runtime/Unity/Prefabs/ThirdwebManagerServer.prefab.meta new file mode 100644 index 00000000..8d442704 --- /dev/null +++ b/Assets/Thirdweb/Runtime/Unity/Prefabs/ThirdwebManagerServer.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: afb575e501b7e2342b3080838f0545cb +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Thirdweb/Runtime/Unity/ThirdwebManager.cs b/Assets/Thirdweb/Runtime/Unity/ThirdwebManager.cs index a5ecf29a..b0230950 100644 --- a/Assets/Thirdweb/Runtime/Unity/ThirdwebManager.cs +++ b/Assets/Thirdweb/Runtime/Unity/ThirdwebManager.cs @@ -1,136 +1,10 @@ using UnityEngine; -using System.Collections.Generic; -using System.Numerics; -using System.Threading.Tasks; using System.Linq; -using System; -using System.IO; +using System.Numerics; namespace Thirdweb.Unity { - public enum WalletProvider - { - PrivateKeyWallet, - InAppWallet, - WalletConnectWallet, - MetaMaskWallet, - EcosystemWallet - } - - public class InAppWalletOptions : EcosystemWalletOptions - { - public InAppWalletOptions( - string email = null, - string phoneNumber = null, - AuthProvider authprovider = AuthProvider.Default, - string jwtOrPayload = null, - string legacyEncryptionKey = null, - string storageDirectoryPath = null, - IThirdwebWallet siweSigner = null - ) - : base( - email: email, - phoneNumber: phoneNumber, - authprovider: authprovider, - jwtOrPayload: jwtOrPayload, - storageDirectoryPath: storageDirectoryPath, - siweSigner: siweSigner, - legacyEncryptionKey: legacyEncryptionKey - ) { } - } - - public class EcosystemWalletOptions - { - public string EcosystemId; - public string EcosystemPartnerId; - public string Email; - public string PhoneNumber; - public AuthProvider AuthProvider; - public string JwtOrPayload; - public string StorageDirectoryPath; - public IThirdwebWallet SiweSigner; - public string LegacyEncryptionKey; - - public EcosystemWalletOptions( - string ecosystemId = null, - string ecosystemPartnerId = null, - string email = null, - string phoneNumber = null, - AuthProvider authprovider = AuthProvider.Default, - string jwtOrPayload = null, - string storageDirectoryPath = null, - IThirdwebWallet siweSigner = null, - string legacyEncryptionKey = null - ) - { - EcosystemId = ecosystemId; - EcosystemPartnerId = ecosystemPartnerId; - Email = email; - PhoneNumber = phoneNumber; - AuthProvider = authprovider; - JwtOrPayload = jwtOrPayload; - StorageDirectoryPath = storageDirectoryPath ?? Path.Combine(Application.persistentDataPath, "Thirdweb", "EcosystemWallet"); - SiweSigner = siweSigner; - LegacyEncryptionKey = legacyEncryptionKey; - } - } - - public class SmartWalletOptions - { - public bool SponsorGas; - public string FactoryAddress; - public string AccountAddressOverride; - public string EntryPoint; - public string BundlerUrl; - public string PaymasterUrl; - public TokenPaymaster TokenPaymaster; - - public SmartWalletOptions( - bool sponsorGas, - string factoryAddress = null, - string accountAddressOverride = null, - string entryPoint = null, - string bundlerUrl = null, - string paymasterUrl = null, - TokenPaymaster tokenPaymaster = TokenPaymaster.NONE - ) - { - SponsorGas = sponsorGas; - FactoryAddress = factoryAddress; - AccountAddressOverride = accountAddressOverride; - EntryPoint = entryPoint; - BundlerUrl = bundlerUrl; - PaymasterUrl = paymasterUrl; - TokenPaymaster = tokenPaymaster; - } - } - - public class WalletOptions - { - public WalletProvider Provider; - public BigInteger ChainId; - public InAppWalletOptions InAppWalletOptions; - public EcosystemWalletOptions EcosystemWalletOptions; - public SmartWalletOptions SmartWalletOptions; - - public WalletOptions( - WalletProvider provider, - BigInteger chainId, - InAppWalletOptions inAppWalletOptions = null, - EcosystemWalletOptions ecosystemWalletOptions = null, - SmartWalletOptions smartWalletOptions = null - ) - { - Provider = provider; - ChainId = chainId; - InAppWalletOptions = inAppWalletOptions ?? new InAppWalletOptions(); - SmartWalletOptions = smartWalletOptions; - EcosystemWalletOptions = ecosystemWalletOptions; - } - } - - [HelpURL("http://portal.thirdweb.com/unity/v5/thirdwebmanager")] - public class ThirdwebManager : MonoBehaviour + public class ThirdwebManager : ThirdwebManagerBase { [field: SerializeField] private string ClientId { get; set; } @@ -138,380 +12,40 @@ public class ThirdwebManager : MonoBehaviour [field: SerializeField] private string BundleId { get; set; } - [field: SerializeField] - private bool InitializeOnAwake { get; set; } = true; - - [field: SerializeField] - private bool ShowDebugLogs { get; set; } = true; - - [field: SerializeField] - private bool OptOutUsageAnalytics { get; set; } = false; - - [field: SerializeField] - private ulong[] SupportedChains { get; set; } = new ulong[] { 421614 }; - - [field: SerializeField] - private string RedirectPageHtmlOverride { get; set; } = null; - - public ThirdwebClient Client { get; private set; } - - public IThirdwebWallet ActiveWallet { get; private set; } - - public static ThirdwebManager Instance { get; private set; } - - public static readonly string THIRDWEB_UNITY_SDK_VERSION = "5.13.0"; - - private bool _initialized; - - private Dictionary _walletMapping; - - private void Awake() + public new static ThirdwebManager Instance { - if (Instance == null) - { - Instance = this; - DontDestroyOnLoad(gameObject); - } - else - { - Destroy(gameObject); - return; - } - - ThirdwebDebug.IsEnabled = ShowDebugLogs; - - if (InitializeOnAwake) - { - Initialize(); - } + get => ThirdwebManagerBase.Instance as ThirdwebManager; } - public void Initialize() + protected override ThirdwebClient CreateClient() { - if (string.IsNullOrEmpty(ClientId)) + if (string.IsNullOrWhiteSpace(ClientId)) { - ThirdwebDebug.LogError("ClientId and must be set in order to initialize ThirdwebManager. Get your API key from https://thirdweb.com/create-api-key"); - return; + ThirdwebDebug.LogError("ClientId must be set in order to initialize ThirdwebManager. " + "Get your API key from https://thirdweb.com/create-api-key"); + return null; } - if (string.IsNullOrEmpty(BundleId)) + if (string.IsNullOrWhiteSpace(BundleId)) { BundleId = null; } - BundleId ??= Application.identifier ?? $"com.{Application.companyName}.{Application.productName}"; + BundleId ??= string.IsNullOrWhiteSpace(Application.identifier) ? $"com.{Application.companyName}.{Application.productName}" : Application.identifier; - Client = ThirdwebClient.Create( + return ThirdwebClient.Create( clientId: ClientId, bundleId: BundleId, httpClient: new CrossPlatformUnityHttpClient(), sdkName: Application.platform == RuntimePlatform.WebGLPlayer ? "UnitySDK_WebGL" : "UnitySDK", sdkOs: Application.platform.ToString(), sdkPlatform: "unity", - sdkVersion: THIRDWEB_UNITY_SDK_VERSION + sdkVersion: THIRDWEB_UNITY_SDK_VERSION, + rpcOverrides: (RpcOverrides == null || RpcOverrides.Count == 0) + ? null + : RpcOverrides.ToDictionary(rpcOverride => new BigInteger(rpcOverride.ChainId), rpcOverride => rpcOverride.RpcUrl) ); - - ThirdwebDebug.Log("ThirdwebManager initialized."); - - _walletMapping = new Dictionary(); - - _initialized = true; - } - - public async Task GetContract(string address, BigInteger chainId, string abi = null) - { - if (!_initialized) - { - throw new InvalidOperationException("ThirdwebManager is not initialized."); - } - - return await ThirdwebContract.Create(Client, address, chainId, abi); - } - - public IThirdwebWallet GetActiveWallet() - { - return ActiveWallet; - } - - public void SetActiveWallet(IThirdwebWallet wallet) - { - ActiveWallet = wallet; } - public IThirdwebWallet GetWallet(string address) - { - if (_walletMapping.TryGetValue(address, out var wallet)) - { - return wallet; - } - - throw new KeyNotFoundException($"Wallet with address {address} not found."); - } - - public async Task AddWallet(IThirdwebWallet wallet) - { - var address = await wallet.GetAddress(); - _walletMapping.TryAdd(address, wallet); - return wallet; - } - - public void RemoveWallet(string address) - { - if (_walletMapping.ContainsKey(address)) - { - _walletMapping.Remove(address, out var wallet); - } - } - - public async Task ConnectWallet(WalletOptions walletOptions) - { - if (!_initialized) - { - throw new InvalidOperationException("ThirdwebManager is not initialized."); - } - - if (walletOptions == null) - { - throw new ArgumentNullException(nameof(walletOptions)); - } - - if (walletOptions.ChainId <= 0) - { - throw new ArgumentException("ChainId must be greater than 0."); - } - - IThirdwebWallet wallet = null; - - switch (walletOptions.Provider) - { - case WalletProvider.PrivateKeyWallet: - wallet = await PrivateKeyWallet.Generate(client: Client); - break; - case WalletProvider.InAppWallet: - wallet = await InAppWallet.Create( - client: Client, - email: walletOptions.InAppWalletOptions.Email, - phoneNumber: walletOptions.InAppWalletOptions.PhoneNumber, - authProvider: walletOptions.InAppWalletOptions.AuthProvider, - storageDirectoryPath: walletOptions.InAppWalletOptions.StorageDirectoryPath, - siweSigner: walletOptions.InAppWalletOptions.SiweSigner, - legacyEncryptionKey: walletOptions.InAppWalletOptions.LegacyEncryptionKey - ); - break; - case WalletProvider.EcosystemWallet: - if (walletOptions.EcosystemWalletOptions == null) - { - throw new ArgumentException("EcosystemWalletOptions must be provided for EcosystemWallet provider."); - } - if (string.IsNullOrEmpty(walletOptions.EcosystemWalletOptions.EcosystemId)) - { - throw new ArgumentException("EcosystemId must be provided for EcosystemWallet provider."); - } - wallet = await EcosystemWallet.Create( - client: Client, - ecosystemId: walletOptions.EcosystemWalletOptions.EcosystemId, - ecosystemPartnerId: walletOptions.EcosystemWalletOptions.EcosystemPartnerId, - email: walletOptions.EcosystemWalletOptions.Email, - phoneNumber: walletOptions.EcosystemWalletOptions.PhoneNumber, - authProvider: walletOptions.EcosystemWalletOptions.AuthProvider, - storageDirectoryPath: walletOptions.EcosystemWalletOptions.StorageDirectoryPath, - siweSigner: walletOptions.EcosystemWalletOptions.SiweSigner, - legacyEncryptionKey: walletOptions.EcosystemWalletOptions.LegacyEncryptionKey - ); - break; - case WalletProvider.WalletConnectWallet: - var supportedChains = SupportedChains.Select(chain => new BigInteger(chain)).ToArray(); - wallet = await WalletConnectWallet.Create(client: Client, initialChainId: walletOptions.ChainId, supportedChains: supportedChains); - break; - case WalletProvider.MetaMaskWallet: - wallet = await MetaMaskWallet.Create(client: Client, activeChainId: walletOptions.ChainId); - break; - } - - if (walletOptions.Provider == WalletProvider.InAppWallet && !await wallet.IsConnected()) - { - ThirdwebDebug.Log("Session does not exist or is expired, proceeding with InAppWallet authentication."); - - var inAppWallet = wallet as InAppWallet; - - if (walletOptions.InAppWalletOptions.AuthProvider == AuthProvider.Default) - { - await inAppWallet.SendOTP(); - _ = await InAppWalletModal.LoginWithOtp(inAppWallet); - } - else if (walletOptions.InAppWalletOptions.AuthProvider == AuthProvider.Siwe) - { - _ = await inAppWallet.LoginWithSiwe(walletOptions.ChainId); - } - else if (walletOptions.InAppWalletOptions.AuthProvider == AuthProvider.JWT) - { - _ = await inAppWallet.LoginWithJWT(walletOptions.InAppWalletOptions.JwtOrPayload); - } - else if (walletOptions.InAppWalletOptions.AuthProvider == AuthProvider.AuthEndpoint) - { - _ = await inAppWallet.LoginWithAuthEndpoint(walletOptions.InAppWalletOptions.JwtOrPayload); - } - else if (walletOptions.InAppWalletOptions.AuthProvider == AuthProvider.Guest) - { - _ = await inAppWallet.LoginWithGuest(); - } - else - { - _ = await inAppWallet.LoginWithOauth( - isMobile: Application.isMobilePlatform, - browserOpenAction: (url) => Application.OpenURL(url), - mobileRedirectScheme: BundleId + "://", - browser: new CrossPlatformUnityBrowser(RedirectPageHtmlOverride) - ); - } - } - - if (walletOptions.Provider == WalletProvider.EcosystemWallet && !await wallet.IsConnected()) - { - ThirdwebDebug.Log("Session does not exist or is expired, proceeding with EcosystemWallet authentication."); - - var ecosystemWallet = wallet as EcosystemWallet; - - if (walletOptions.EcosystemWalletOptions.AuthProvider == AuthProvider.Default) - { - await ecosystemWallet.SendOTP(); - _ = await EcosystemWalletModal.LoginWithOtp(ecosystemWallet); - } - else if (walletOptions.EcosystemWalletOptions.AuthProvider == AuthProvider.Siwe) - { - _ = await ecosystemWallet.LoginWithSiwe(walletOptions.ChainId); - } - else if (walletOptions.EcosystemWalletOptions.AuthProvider == AuthProvider.JWT) - { - _ = await ecosystemWallet.LoginWithJWT(walletOptions.EcosystemWalletOptions.JwtOrPayload); - } - else if (walletOptions.EcosystemWalletOptions.AuthProvider == AuthProvider.AuthEndpoint) - { - _ = await ecosystemWallet.LoginWithAuthEndpoint(walletOptions.EcosystemWalletOptions.JwtOrPayload); - } - else if (walletOptions.EcosystemWalletOptions.AuthProvider == AuthProvider.Guest) - { - _ = await ecosystemWallet.LoginWithGuest(); - } - else - { - _ = await ecosystemWallet.LoginWithOauth( - isMobile: Application.isMobilePlatform, - browserOpenAction: (url) => Application.OpenURL(url), - mobileRedirectScheme: BundleId + "://", - browser: new CrossPlatformUnityBrowser(RedirectPageHtmlOverride) - ); - } - } - - var address = await wallet.GetAddress(); - ThirdwebDebug.Log($"Wallet address: {address}"); - - var isSmartWallet = walletOptions.SmartWalletOptions != null; - - if (!OptOutUsageAnalytics) - { - TrackUsage("connectWallet", "connect", isSmartWallet ? "smartWallet" : walletOptions.Provider.ToString()[..1].ToLower() + walletOptions.Provider.ToString()[1..], address); - } - - if (isSmartWallet) - { - ThirdwebDebug.Log("Upgrading to SmartWallet."); - return await UpgradeToSmartWallet(wallet, walletOptions.ChainId, walletOptions.SmartWalletOptions); - } - else - { - await AddWallet(wallet); - SetActiveWallet(wallet); - return wallet; - } - } - - public async Task UpgradeToSmartWallet(IThirdwebWallet personalWallet, BigInteger chainId, SmartWalletOptions smartWalletOptions) - { - if (!_initialized) - { - throw new InvalidOperationException("ThirdwebManager is not initialized."); - } - - if (personalWallet.AccountType == ThirdwebAccountType.SmartAccount) - { - ThirdwebDebug.LogWarning("Wallet is already a SmartWallet."); - return personalWallet as SmartWallet; - } - - if (smartWalletOptions == null) - { - throw new ArgumentNullException(nameof(smartWalletOptions)); - } - - if (chainId <= 0) - { - throw new ArgumentException("ChainId must be greater than 0."); - } - - var wallet = await SmartWallet.Create( - personalWallet: personalWallet, - chainId: chainId, - gasless: smartWalletOptions.SponsorGas, - factoryAddress: smartWalletOptions.FactoryAddress, - accountAddressOverride: smartWalletOptions.AccountAddressOverride, - entryPoint: smartWalletOptions.EntryPoint, - bundlerUrl: smartWalletOptions.BundlerUrl, - paymasterUrl: smartWalletOptions.PaymasterUrl, - tokenPaymaster: smartWalletOptions.TokenPaymaster - ); - - await AddWallet(wallet); - SetActiveWallet(wallet); - - return wallet; - } - - public async Task> LinkAccount(IThirdwebWallet mainWallet, IThirdwebWallet walletToLink, string otp = null, BigInteger? chainId = null, string jwtOrPayload = null) - { - return await mainWallet.LinkAccount( - walletToLink: walletToLink, - otp: otp, - isMobile: Application.isMobilePlatform, - browserOpenAction: (url) => Application.OpenURL(url), - mobileRedirectScheme: BundleId + "://", - browser: new CrossPlatformUnityBrowser(RedirectPageHtmlOverride), - chainId: chainId, - jwt: jwtOrPayload, - payload: jwtOrPayload - ); - } - - private async void TrackUsage(string source, string action, string walletType, string walletAddress) - { - if (string.IsNullOrEmpty(source) || string.IsNullOrEmpty(action) || string.IsNullOrEmpty(walletType) || string.IsNullOrEmpty(walletAddress)) - { - ThirdwebDebug.LogWarning("Invalid usage analytics parameters."); - return; - } - - try - { - var content = new System.Net.Http.StringContent( - Newtonsoft.Json.JsonConvert.SerializeObject( - new - { - source, - action, - walletAddress, - walletType, - } - ), - System.Text.Encoding.UTF8, - "application/json" - ); - _ = await Client.HttpClient.PostAsync("https://c.thirdweb.com/event", content); - } - catch - { - ThirdwebDebug.LogWarning($"Failed to report usage analytics."); - } - } + protected override string MobileRedirectScheme => BundleId + "://"; } } diff --git a/Assets/Thirdweb/Runtime/Unity/ThirdwebManagerBase.cs b/Assets/Thirdweb/Runtime/Unity/ThirdwebManagerBase.cs new file mode 100644 index 00000000..5cb1214d --- /dev/null +++ b/Assets/Thirdweb/Runtime/Unity/ThirdwebManagerBase.cs @@ -0,0 +1,639 @@ +using UnityEngine; +using System.Collections.Generic; +using System.Numerics; +using System.Threading.Tasks; +using System.Linq; +using System; +using System.IO; +using Newtonsoft.Json; + +namespace Thirdweb.Unity +{ + [Serializable] + public enum WalletProvider + { + PrivateKeyWallet, + InAppWallet, + WalletConnectWallet, + MetaMaskWallet, + EcosystemWallet + } + + [Serializable] + public class InAppWalletOptions : EcosystemWalletOptions + { + public InAppWalletOptions( + string email = null, + string phoneNumber = null, + AuthProvider authprovider = AuthProvider.Default, + string jwtOrPayload = null, + string storageDirectoryPath = null, + IThirdwebWallet siweSigner = null, + string legacyEncryptionKey = null, + string walletSecret = null, + List forceSiweExternalWalletIds = null, + ExecutionMode executionMode = ExecutionMode.EOA + ) + : base( + email: email, + phoneNumber: phoneNumber, + authprovider: authprovider, + jwtOrPayload: jwtOrPayload, + storageDirectoryPath: storageDirectoryPath, + siweSigner: siweSigner, + legacyEncryptionKey: legacyEncryptionKey, + walletSecret: walletSecret, + forceSiweExternalWalletIds: forceSiweExternalWalletIds, + executionMode: executionMode + ) { } + } + + [Serializable] + public class EcosystemWalletOptions + { + [JsonProperty("ecosystemId")] + public string EcosystemId; + + [JsonProperty("ecosystemPartnerId")] + public string EcosystemPartnerId; + + [JsonProperty("email")] + public string Email; + + [JsonProperty("phoneNumber")] + public string PhoneNumber; + + [JsonProperty("authProvider")] + public AuthProvider AuthProvider; + + [JsonProperty("jwtOrPayload")] + public string JwtOrPayload; + + [JsonProperty("storageDirectoryPath")] + public string StorageDirectoryPath; + + [JsonProperty("siweSigner")] + public IThirdwebWallet SiweSigner; + + [JsonProperty("legacyEncryptionKey")] + public string LegacyEncryptionKey; + + [JsonProperty("walletSecret")] + public string WalletSecret; + + [JsonProperty("forceSiweExternalWalletIds")] + public List ForceSiweExternalWalletIds; + + [JsonProperty("executionMode")] + public ExecutionMode ExecutionMode = ExecutionMode.EOA; + + public EcosystemWalletOptions( + string ecosystemId = null, + string ecosystemPartnerId = null, + string email = null, + string phoneNumber = null, + AuthProvider authprovider = AuthProvider.Default, + string jwtOrPayload = null, + string storageDirectoryPath = null, + IThirdwebWallet siweSigner = null, + string legacyEncryptionKey = null, + string walletSecret = null, + List forceSiweExternalWalletIds = null, + ExecutionMode executionMode = ExecutionMode.EOA + ) + { + EcosystemId = ecosystemId; + EcosystemPartnerId = ecosystemPartnerId; + Email = email; + PhoneNumber = phoneNumber; + AuthProvider = authprovider; + JwtOrPayload = jwtOrPayload; + StorageDirectoryPath = storageDirectoryPath ?? Path.Combine(Application.persistentDataPath, "Thirdweb", "EcosystemWallet"); + SiweSigner = siweSigner; + LegacyEncryptionKey = legacyEncryptionKey; + WalletSecret = walletSecret; + ForceSiweExternalWalletIds = forceSiweExternalWalletIds; + ExecutionMode = executionMode; + } + } + + [Serializable] + public class SmartWalletOptions + { + [JsonProperty("sponsorGas")] + public bool SponsorGas; + + [JsonProperty("factoryAddress")] + public string FactoryAddress; + + [JsonProperty("accountAddressOverride")] + public string AccountAddressOverride; + + [JsonProperty("entryPoint")] + public string EntryPoint; + + [JsonProperty("bundlerUrl")] + public string BundlerUrl; + + [JsonProperty("paymasterUrl")] + public string PaymasterUrl; + + [JsonProperty("tokenPaymaster")] + public TokenPaymaster TokenPaymaster; + + public SmartWalletOptions( + bool sponsorGas, + string factoryAddress = null, + string accountAddressOverride = null, + string entryPoint = null, + string bundlerUrl = null, + string paymasterUrl = null, + TokenPaymaster tokenPaymaster = TokenPaymaster.NONE + ) + { + SponsorGas = sponsorGas; + FactoryAddress = factoryAddress; + AccountAddressOverride = accountAddressOverride; + EntryPoint = entryPoint; + BundlerUrl = bundlerUrl; + PaymasterUrl = paymasterUrl; + TokenPaymaster = tokenPaymaster; + } + } + + [Serializable] + public class WalletOptions + { + [JsonProperty("provider")] + public WalletProvider Provider; + + [JsonProperty("chainId")] + public BigInteger ChainId; + + [JsonProperty("inAppWalletOptions")] + public InAppWalletOptions InAppWalletOptions; + + [JsonProperty("ecosystemWalletOptions", NullValueHandling = NullValueHandling.Ignore)] + public EcosystemWalletOptions EcosystemWalletOptions; + + [JsonProperty("smartWalletOptions", NullValueHandling = NullValueHandling.Ignore)] + public SmartWalletOptions SmartWalletOptions; + + public WalletOptions( + WalletProvider provider, + BigInteger chainId, + InAppWalletOptions inAppWalletOptions = null, + EcosystemWalletOptions ecosystemWalletOptions = null, + SmartWalletOptions smartWalletOptions = null + ) + { + Provider = provider; + ChainId = chainId; + InAppWalletOptions = inAppWalletOptions ?? new InAppWalletOptions(); + SmartWalletOptions = smartWalletOptions; + EcosystemWalletOptions = ecosystemWalletOptions; + } + } + + [Serializable] + public struct RpcOverride + { + public ulong ChainId; + public string RpcUrl; + } + + [HelpURL("http://portal.thirdweb.com/unity/v5/thirdwebmanager")] + public abstract class ThirdwebManagerBase : MonoBehaviour + { + [field: SerializeField] + protected bool InitializeOnAwake { get; set; } = true; + + [field: SerializeField] + protected bool ShowDebugLogs { get; set; } = true; + + [field: SerializeField] + protected bool AutoConnectLastWallet { get; set; } = false; + + [field: SerializeField] + protected ulong[] SupportedChains { get; set; } = new ulong[] { 421614 }; + + [field: SerializeField] + protected string[] IncludedWalletIds { get; set; } = null; + + [field: SerializeField] + protected string RedirectPageHtmlOverride { get; set; } = null; + + [field: SerializeField] + protected List RpcOverrides { get; set; } = null; + + public ThirdwebClient Client { get; protected set; } + public IThirdwebWallet ActiveWallet { get; protected set; } + public bool Initialized { get; protected set; } + + public static ThirdwebManagerBase Instance { get; protected set; } + + public static readonly string THIRDWEB_UNITY_SDK_VERSION = "5.22.0"; + + protected const string THIRDWEB_AUTO_CONNECT_OPTIONS_KEY = "ThirdwebAutoConnectOptions"; + + protected Dictionary _walletMapping; + + protected abstract ThirdwebClient CreateClient(); + + protected abstract string MobileRedirectScheme { get; } + + // ------------------------------------------------------ + // Lifecycle Methods + // ------------------------------------------------------ + + protected virtual void Awake() + { + if (Instance == null) + { + Instance = this; + DontDestroyOnLoad(gameObject); + } + else + { + Destroy(gameObject); + return; + } + + ThirdwebDebug.IsEnabled = ShowDebugLogs; + + if (InitializeOnAwake) + { + Initialize(); + } + } + + public virtual async void Initialize() + { + Client = CreateClient(); + if (Client == null) + { + ThirdwebDebug.LogError("Failed to initialize ThirdwebManager."); + return; + } + + ThirdwebDebug.Log("ThirdwebManager initialized."); + + _walletMapping = new Dictionary(); + + if (AutoConnectLastWallet && GetAutoConnectOptions(out var lastWalletOptions)) + { + ThirdwebDebug.Log("Auto-connecting to last wallet."); + try + { + _ = await ConnectWallet(lastWalletOptions); + ThirdwebDebug.Log("Auto-connected to last wallet."); + } + catch (Exception e) + { + ThirdwebDebug.LogError("Failed to auto-connect to last wallet: " + e.Message); + } + } + + Initialized = true; + } + + // ------------------------------------------------------ + // Contract Methods + // ------------------------------------------------------ + + public virtual async Task GetContract(string address, BigInteger chainId, string abi = null) + { + if (!Initialized) + { + throw new InvalidOperationException("ThirdwebManager is not initialized."); + } + + return await ThirdwebContract.Create(Client, address, chainId, abi); + } + + // ------------------------------------------------------ + // Active Wallet Methods + // ------------------------------------------------------ + + public virtual IThirdwebWallet GetActiveWallet() + { + return ActiveWallet; + } + + public virtual void SetActiveWallet(IThirdwebWallet wallet) + { + ActiveWallet = wallet; + } + + public virtual IThirdwebWallet GetWallet(string address) + { + if (_walletMapping.TryGetValue(address, out var wallet)) + { + return wallet; + } + + throw new KeyNotFoundException($"Wallet with address {address} not found."); + } + + public virtual async Task AddWallet(IThirdwebWallet wallet) + { + var address = await wallet.GetAddress(); + _walletMapping.TryAdd(address, wallet); + return wallet; + } + + public virtual void RemoveWallet(string address) + { + if (_walletMapping.ContainsKey(address)) + { + _walletMapping.Remove(address, out var _); + } + } + + // ------------------------------------------------------ + // Connection Methods + // ------------------------------------------------------ + + public virtual async Task ConnectWallet(WalletOptions walletOptions) + { + if (walletOptions == null) + { + throw new ArgumentNullException(nameof(walletOptions)); + } + + if (walletOptions.ChainId <= 0) + { + throw new ArgumentException("ChainId must be greater than 0."); + } + + IThirdwebWallet wallet = null; + + switch (walletOptions.Provider) + { + case WalletProvider.PrivateKeyWallet: + wallet = await PrivateKeyWallet.Generate(client: Client); + break; + + case WalletProvider.InAppWallet: + wallet = await InAppWallet.Create( + client: Client, + email: walletOptions.InAppWalletOptions.Email, + phoneNumber: walletOptions.InAppWalletOptions.PhoneNumber, + authProvider: walletOptions.InAppWalletOptions.AuthProvider, + storageDirectoryPath: walletOptions.InAppWalletOptions.StorageDirectoryPath, + siweSigner: walletOptions.InAppWalletOptions.SiweSigner, + legacyEncryptionKey: walletOptions.InAppWalletOptions.LegacyEncryptionKey, + walletSecret: walletOptions.InAppWalletOptions.WalletSecret, + executionMode: walletOptions.InAppWalletOptions.ExecutionMode + ); + break; + + case WalletProvider.EcosystemWallet: + if (walletOptions.EcosystemWalletOptions == null) + { + throw new ArgumentException("EcosystemWalletOptions must be provided for EcosystemWallet provider."); + } + if (string.IsNullOrEmpty(walletOptions.EcosystemWalletOptions.EcosystemId)) + { + throw new ArgumentException("EcosystemId must be provided for EcosystemWallet provider."); + } + wallet = await EcosystemWallet.Create( + client: Client, + ecosystemId: walletOptions.EcosystemWalletOptions.EcosystemId, + ecosystemPartnerId: walletOptions.EcosystemWalletOptions.EcosystemPartnerId, + email: walletOptions.EcosystemWalletOptions.Email, + phoneNumber: walletOptions.EcosystemWalletOptions.PhoneNumber, + authProvider: walletOptions.EcosystemWalletOptions.AuthProvider, + storageDirectoryPath: walletOptions.EcosystemWalletOptions.StorageDirectoryPath, + siweSigner: walletOptions.EcosystemWalletOptions.SiweSigner, + legacyEncryptionKey: walletOptions.EcosystemWalletOptions.LegacyEncryptionKey, + walletSecret: walletOptions.EcosystemWalletOptions.WalletSecret, + executionMode: walletOptions.EcosystemWalletOptions.ExecutionMode + ); + break; + + case WalletProvider.WalletConnectWallet: + var supportedChains = SupportedChains.Select(chain => new BigInteger(chain)).ToArray(); + var includedWalletIds = IncludedWalletIds == null || IncludedWalletIds.Length == 0 ? null : IncludedWalletIds; + wallet = await WalletConnectWallet.Create(client: Client, initialChainId: walletOptions.ChainId, supportedChains: supportedChains, includedWalletIds: includedWalletIds); + break; + + case WalletProvider.MetaMaskWallet: + wallet = await MetaMaskWallet.Create(client: Client, activeChainId: walletOptions.ChainId); + break; + } + + // InAppWallet auth flow + if (walletOptions.Provider == WalletProvider.InAppWallet && !await wallet.IsConnected()) + { + ThirdwebDebug.Log("Session does not exist or is expired, proceeding with InAppWallet authentication."); + + var inAppWallet = wallet as InAppWallet; + switch (walletOptions.InAppWalletOptions.AuthProvider) + { + case AuthProvider.Default: + await inAppWallet.SendOTP(); + _ = await InAppWalletModal.LoginWithOtp(inAppWallet); + break; + case AuthProvider.Siwe: + _ = await inAppWallet.LoginWithSiwe(walletOptions.ChainId); + break; + case AuthProvider.JWT: + _ = await inAppWallet.LoginWithJWT(walletOptions.InAppWalletOptions.JwtOrPayload); + break; + case AuthProvider.AuthEndpoint: + _ = await inAppWallet.LoginWithAuthEndpoint(walletOptions.InAppWalletOptions.JwtOrPayload); + break; + case AuthProvider.Guest: + _ = await inAppWallet.LoginWithGuest(SystemInfo.deviceUniqueIdentifier); + break; + case AuthProvider.Backend: + _ = await inAppWallet.LoginWithBackend(); + break; + case AuthProvider.SiweExternal: + _ = await inAppWallet.LoginWithSiweExternal( + isMobile: Application.isMobilePlatform, + browserOpenAction: (url) => Application.OpenURL(url), + forceWalletIds: walletOptions.InAppWalletOptions.ForceSiweExternalWalletIds == null || walletOptions.InAppWalletOptions.ForceSiweExternalWalletIds.Count == 0 + ? null + : walletOptions.InAppWalletOptions.ForceSiweExternalWalletIds, + mobileRedirectScheme: MobileRedirectScheme, + browser: new CrossPlatformUnityBrowser(RedirectPageHtmlOverride) + ); + break; + default: + _ = await inAppWallet.LoginWithOauth( + isMobile: Application.isMobilePlatform, + browserOpenAction: (url) => Application.OpenURL(url), + mobileRedirectScheme: MobileRedirectScheme, + browser: new CrossPlatformUnityBrowser(RedirectPageHtmlOverride) + ); + break; + } + } + + // EcosystemWallet auth flow + if (walletOptions.Provider == WalletProvider.EcosystemWallet && !await wallet.IsConnected()) + { + ThirdwebDebug.Log("Session does not exist or is expired, proceeding with EcosystemWallet authentication."); + + var ecosystemWallet = wallet as EcosystemWallet; + switch (walletOptions.EcosystemWalletOptions.AuthProvider) + { + case AuthProvider.Default: + await ecosystemWallet.SendOTP(); + _ = await EcosystemWalletModal.LoginWithOtp(ecosystemWallet); + break; + case AuthProvider.Siwe: + _ = await ecosystemWallet.LoginWithSiwe(walletOptions.ChainId); + break; + case AuthProvider.JWT: + _ = await ecosystemWallet.LoginWithJWT(walletOptions.EcosystemWalletOptions.JwtOrPayload); + break; + case AuthProvider.AuthEndpoint: + _ = await ecosystemWallet.LoginWithAuthEndpoint(walletOptions.EcosystemWalletOptions.JwtOrPayload); + break; + case AuthProvider.Guest: + _ = await ecosystemWallet.LoginWithGuest(SystemInfo.deviceUniqueIdentifier); + break; + case AuthProvider.Backend: + _ = await ecosystemWallet.LoginWithBackend(); + break; + case AuthProvider.SiweExternal: + _ = await ecosystemWallet.LoginWithSiweExternal( + isMobile: Application.isMobilePlatform, + browserOpenAction: (url) => Application.OpenURL(url), + forceWalletIds: walletOptions.EcosystemWalletOptions.ForceSiweExternalWalletIds == null || walletOptions.EcosystemWalletOptions.ForceSiweExternalWalletIds.Count == 0 + ? null + : walletOptions.EcosystemWalletOptions.ForceSiweExternalWalletIds, + mobileRedirectScheme: MobileRedirectScheme, + browser: new CrossPlatformUnityBrowser(RedirectPageHtmlOverride) + ); + break; + default: + _ = await ecosystemWallet.LoginWithOauth( + isMobile: Application.isMobilePlatform, + browserOpenAction: (url) => Application.OpenURL(url), + mobileRedirectScheme: MobileRedirectScheme, + browser: new CrossPlatformUnityBrowser(RedirectPageHtmlOverride) + ); + break; + } + } + + var address = await wallet.GetAddress(); + var isSmartWallet = walletOptions.SmartWalletOptions != null; + + SetAutoConnectOptions(walletOptions); + + // If SmartWallet, do upgrade + if (isSmartWallet) + { + ThirdwebDebug.Log("Upgrading to SmartWallet."); + return await UpgradeToSmartWallet(wallet, walletOptions.ChainId, walletOptions.SmartWalletOptions); + } + else + { + await AddWallet(wallet); + SetActiveWallet(wallet); + return wallet; + } + } + + public virtual async Task UpgradeToSmartWallet(IThirdwebWallet personalWallet, BigInteger chainId, SmartWalletOptions smartWalletOptions) + { + if (personalWallet.AccountType == ThirdwebAccountType.SmartAccount) + { + ThirdwebDebug.LogWarning("Wallet is already a SmartWallet."); + return personalWallet as SmartWallet; + } + + if (smartWalletOptions == null) + { + throw new ArgumentNullException(nameof(smartWalletOptions)); + } + + if (chainId <= 0) + { + throw new ArgumentException("ChainId must be greater than 0."); + } + + var wallet = await SmartWallet.Create( + personalWallet: personalWallet, + chainId: chainId, + gasless: smartWalletOptions.SponsorGas, + factoryAddress: smartWalletOptions.FactoryAddress, + accountAddressOverride: smartWalletOptions.AccountAddressOverride, + entryPoint: smartWalletOptions.EntryPoint, + bundlerUrl: smartWalletOptions.BundlerUrl, + paymasterUrl: smartWalletOptions.PaymasterUrl, + tokenPaymaster: smartWalletOptions.TokenPaymaster + ); + + await AddWallet(wallet); + SetActiveWallet(wallet); + + // Persist "smartWalletOptions" to auto-connect + if (AutoConnectLastWallet && GetAutoConnectOptions(out var lastWalletOptions)) + { + lastWalletOptions.SmartWalletOptions = smartWalletOptions; + SetAutoConnectOptions(lastWalletOptions); + } + + return wallet; + } + + public virtual async Task> LinkAccount(IThirdwebWallet mainWallet, IThirdwebWallet walletToLink, string otp = null, BigInteger? chainId = null, string jwtOrPayload = null) + { + return await mainWallet.LinkAccount( + walletToLink: walletToLink, + otp: otp, + isMobile: Application.isMobilePlatform, + browserOpenAction: (url) => Application.OpenURL(url), + mobileRedirectScheme: MobileRedirectScheme, + browser: new CrossPlatformUnityBrowser(RedirectPageHtmlOverride), + chainId: chainId, + jwt: jwtOrPayload, + payload: jwtOrPayload + ); + } + + protected virtual bool GetAutoConnectOptions(out WalletOptions lastWalletOptions) + { + var connectOptionsStr = PlayerPrefs.GetString(THIRDWEB_AUTO_CONNECT_OPTIONS_KEY, null); + if (!string.IsNullOrEmpty(connectOptionsStr)) + { + try + { + lastWalletOptions = JsonConvert.DeserializeObject(connectOptionsStr); + return true; + } + catch + { + ThirdwebDebug.LogWarning("Failed to load last wallet options."); + PlayerPrefs.DeleteKey(THIRDWEB_AUTO_CONNECT_OPTIONS_KEY); + lastWalletOptions = null; + return false; + } + } + lastWalletOptions = null; + return false; + } + + protected virtual void SetAutoConnectOptions(WalletOptions walletOptions) + { + if (AutoConnectLastWallet && walletOptions.Provider != WalletProvider.WalletConnectWallet) + { + try + { + PlayerPrefs.SetString(THIRDWEB_AUTO_CONNECT_OPTIONS_KEY, JsonConvert.SerializeObject(walletOptions)); + } + catch + { + ThirdwebDebug.LogWarning("Failed to save last wallet options."); + PlayerPrefs.DeleteKey(THIRDWEB_AUTO_CONNECT_OPTIONS_KEY); + } + } + } + } +} diff --git a/Assets/Thirdweb/Runtime/Unity/ThirdwebManagerBase.cs.meta b/Assets/Thirdweb/Runtime/Unity/ThirdwebManagerBase.cs.meta new file mode 100644 index 00000000..44a30b21 --- /dev/null +++ b/Assets/Thirdweb/Runtime/Unity/ThirdwebManagerBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3bd1bb5e5d7733646a69989260142701 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Thirdweb/Runtime/Unity/ThirdwebManagerServer.cs b/Assets/Thirdweb/Runtime/Unity/ThirdwebManagerServer.cs new file mode 100644 index 00000000..b0ffb66e --- /dev/null +++ b/Assets/Thirdweb/Runtime/Unity/ThirdwebManagerServer.cs @@ -0,0 +1,40 @@ +using UnityEngine; +using System.Linq; +using System.Numerics; + +namespace Thirdweb.Unity +{ + public class ThirdwebManagerServer : ThirdwebManagerBase + { + [field: SerializeField] + private string SecretKey { get; set; } + + public new static ThirdwebManagerServer Instance + { + get => ThirdwebManagerBase.Instance as ThirdwebManagerServer; + } + + protected override ThirdwebClient CreateClient() + { + if (string.IsNullOrEmpty(SecretKey)) + { + ThirdwebDebug.LogError("SecretKey must be set in order to initialize ThirdwebManagerServer."); + return null; + } + + return ThirdwebClient.Create( + secretKey: SecretKey, + httpClient: new CrossPlatformUnityHttpClient(), + sdkName: Application.platform == RuntimePlatform.WebGLPlayer ? "UnitySDK_WebGL" : "UnitySDK", + sdkOs: Application.platform.ToString(), + sdkPlatform: "unity", + sdkVersion: THIRDWEB_UNITY_SDK_VERSION, + rpcOverrides: (RpcOverrides == null || RpcOverrides.Count == 0) + ? null + : RpcOverrides.ToDictionary(rpcOverride => new BigInteger(rpcOverride.ChainId), rpcOverride => rpcOverride.RpcUrl) + ); + } + + protected override string MobileRedirectScheme => "tw-server://"; + } +} diff --git a/Assets/Thirdweb/Runtime/Unity/ThirdwebManagerServer.cs.meta b/Assets/Thirdweb/Runtime/Unity/ThirdwebManagerServer.cs.meta new file mode 100644 index 00000000..24146539 --- /dev/null +++ b/Assets/Thirdweb/Runtime/Unity/ThirdwebManagerServer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a197f9da6b1311840bb25b207982e287 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Thirdweb/Runtime/Unity/ThirdwebUnityExtensions.cs b/Assets/Thirdweb/Runtime/Unity/ThirdwebUnityExtensions.cs index 7cea6b10..cee6a71c 100644 --- a/Assets/Thirdweb/Runtime/Unity/ThirdwebUnityExtensions.cs +++ b/Assets/Thirdweb/Runtime/Unity/ThirdwebUnityExtensions.cs @@ -1,13 +1,44 @@ +using System; using System.Numerics; using System.Threading.Tasks; using UnityEngine; using ZXing; using ZXing.QrCode; +#if UNITY_WEBGL +using System.Runtime.InteropServices; +#endif + namespace Thirdweb.Unity { public static class ThirdwebUnityExtensions { +#if UNITY_WEBGL + [DllImport("__Internal")] + private static extern string ThirdwebCopyBuffer(string text); +#endif + + public static void CopyToClipboard(this string text) + { + try + { + if (Application.platform == RuntimePlatform.WebGLPlayer) + { +#if UNITY_WEBGL + ThirdwebCopyBuffer(text); +#endif + } + else + { + GUIUtility.systemCopyBuffer = text; + } + } + catch (Exception e) + { + ThirdwebDebug.LogWarning($"Failed to copy to clipboard: {e}"); + } + } + public static async Task GetNFTSprite(this NFT nft, ThirdwebClient client) { var bytes = await nft.GetNFTImageBytes(client); @@ -25,7 +56,7 @@ public static async Task GetNFTSprite(this NFT nft, ThirdwebClient clien public static async Task UpgradeToSmartWallet(this IThirdwebWallet personalWallet, BigInteger chainId, SmartWalletOptions smartWalletOptions) { - return await ThirdwebManager.Instance.UpgradeToSmartWallet(personalWallet, chainId, smartWalletOptions); + return await ThirdwebManagerBase.Instance.UpgradeToSmartWallet(personalWallet, chainId, smartWalletOptions); } public static Texture2D ToQRTexture(this string textForEncoding, Color? fgColor = null, Color? bgColor = null, int width = 512, int height = 512) diff --git a/Assets/Thirdweb/Runtime/Unity/Wallets/Core/MetaMaskWallet.cs b/Assets/Thirdweb/Runtime/Unity/Wallets/Core/MetaMaskWallet.cs index e10e13ce..be425730 100644 --- a/Assets/Thirdweb/Runtime/Unity/Wallets/Core/MetaMaskWallet.cs +++ b/Assets/Thirdweb/Runtime/Unity/Wallets/Core/MetaMaskWallet.cs @@ -16,6 +16,7 @@ namespace Thirdweb.Unity public class MetaMaskWallet : IThirdwebWallet { public ThirdwebClient Client => _client; + public string WalletId => "metamask"; public ThirdwebAccountType AccountType => ThirdwebAccountType.ExternalAccount; private static ThirdwebClient _client; @@ -38,20 +39,18 @@ public static async Task Create(ThirdwebClient client, BigIntege { ThirdwebDebug.Log("MetaMask already initialized."); await mmWallet.SwitchNetwork(activeChainId); - return mmWallet; } else { if (metaMaskInstance.IsMetaMaskAvailable()) { ThirdwebDebug.Log("MetaMask is available. Enabling Ethereum..."); - var isEnabled = await metaMaskInstance.EnableEthereumAsync(); + var isEnabled = await metaMaskInstance.EnableEthereumAsync(activeChainId); ThirdwebDebug.Log($"Ethereum enabled: {isEnabled}"); if (isEnabled && !string.IsNullOrEmpty(metaMaskInstance.GetAddress())) { ThirdwebDebug.Log("MetaMask initialized successfully."); await mmWallet.SwitchNetwork(activeChainId); - return mmWallet; } else { @@ -63,6 +62,8 @@ public static async Task Create(ThirdwebClient client, BigIntege throw new Exception("MetaMask is not available."); } } + Utils.TrackConnection(mmWallet); + return mmWallet; } #region IThirdwebWallet @@ -116,19 +117,32 @@ public async Task SendTransaction(ThirdwebTransactionInput transaction) Method = "eth_sendTransaction", Params = new object[] { - new TransactionInput() - { - Nonce = transaction.Nonce, - From = await GetAddress(), - To = transaction.To, - Gas = transaction.Gas, - GasPrice = transaction.GasPrice, - Value = transaction.Value, - Data = transaction.Data, - MaxFeePerGas = transaction.MaxFeePerGas, - MaxPriorityFeePerGas = transaction.MaxPriorityFeePerGas, - ChainId = new HexBigInteger(WebGLMetaMask.Instance.GetActiveChainId()), - } + transaction.GasPrice == null + ? new + { + nonce = transaction.Nonce.HexValue, + from = await GetAddress(), + to = transaction.To, + gas = transaction.Gas.HexValue, + value = transaction.Value?.HexValue ?? "0x0", + data = transaction.Data, + maxFeePerGas = transaction.MaxFeePerGas.HexValue, + maxPriorityFeePerGas = transaction.MaxPriorityFeePerGas?.HexValue ?? "0x0", + chainId = WebGLMetaMask.Instance.GetActiveChainId().NumberToHex(), + type = "0x2" + } + : new + { + nonce = transaction.Nonce.HexValue, + from = await GetAddress(), + to = transaction.To, + gas = transaction.Gas.HexValue, + value = transaction.Value?.HexValue ?? "0x0", + data = transaction.Data, + gasPrice = transaction.GasPrice.HexValue, + chainId = WebGLMetaMask.Instance.GetActiveChainId().NumberToHex(), + type = "0x0" + } } }; return await WebGLMetaMask.Instance.RequestAsync(rpcRequest); @@ -191,10 +205,18 @@ public Task RecoverAddressFromTypedDataV4(T data, TypedData< throw new NotImplementedException(); } - public Task Disconnect() + public async Task Disconnect() { - ThirdwebDebug.Log("Disconnecting has no effect on this wallet."); - return Task.CompletedTask; + try + { + _ = await WebGLMetaMask.Instance.RequestAsync( + new RpcRequest { Method = "wallet_revokePermissions", Params = new object[] { new Dictionary { { "eth_accounts", new object() } } } } + ); + } + catch + { + // no-op + } } public Task> LinkAccount( @@ -206,7 +228,9 @@ public Task> LinkAccount( IThirdwebBrowser browser = null, BigInteger? chainId = null, string jwt = null, - string payload = null + string payload = null, + string defaultSessionIdOverride = null, + List forceWalletIds = null ) { throw new InvalidOperationException("LinkAccount is not supported by external wallets."); diff --git a/Assets/Thirdweb/Runtime/Unity/Wallets/Core/WalletConnectWallet.cs b/Assets/Thirdweb/Runtime/Unity/Wallets/Core/WalletConnectWallet.cs index 9e70d1af..44c096b9 100644 --- a/Assets/Thirdweb/Runtime/Unity/Wallets/Core/WalletConnectWallet.cs +++ b/Assets/Thirdweb/Runtime/Unity/Wallets/Core/WalletConnectWallet.cs @@ -18,7 +18,7 @@ namespace Thirdweb.Unity public class WalletConnectWallet : IThirdwebWallet { public ThirdwebClient Client => _client; - + public string WalletId => "walletconnect"; public ThirdwebAccountType AccountType => ThirdwebAccountType.ExternalAccount; protected ThirdwebClient _client; @@ -35,7 +35,7 @@ protected WalletConnectWallet(ThirdwebClient client) _client = client; } - public async static Task Create(ThirdwebClient client, BigInteger initialChainId, BigInteger[] supportedChains) + public async static Task Create(ThirdwebClient client, BigInteger initialChainId, BigInteger[] supportedChains, string[] includedWalletIds) { var eip155ChainsSupported = new string[] { }; if (supportedChains != null) @@ -44,6 +44,7 @@ public async static Task Create(ThirdwebClient client, BigI _exception = null; _isConnected = false; _supportedChains = eip155ChainsSupported; + _includedWalletIds = includedWalletIds; if (WalletConnect.Instance != null && WalletConnect.Instance.IsConnected) { @@ -91,7 +92,9 @@ public async static Task Create(ThirdwebClient client, BigI _walletConnectService = new WalletConnectServiceCore(WalletConnect.Instance.SignClient); } - return new WalletConnectWallet(client); + var wcw = new WalletConnectWallet(client); + Utils.TrackConnection(wcw); + return wcw; } public async Task SwitchNetwork(BigInteger chainId) @@ -270,7 +273,9 @@ public Task> LinkAccount( IThirdwebBrowser browser = null, BigInteger? chainId = null, string jwt = null, - string payload = null + string payload = null, + string defaultSessionIdOverride = null, + List forceWalletIds = null ) { throw new InvalidOperationException("LinkAccount is not supported by external wallets."); diff --git a/Assets/Thirdweb/Runtime/Unity/Wallets/Core/WebGLMetaMask.cs b/Assets/Thirdweb/Runtime/Unity/Wallets/Core/WebGLMetaMask.cs index 63652dde..30bf892f 100644 --- a/Assets/Thirdweb/Runtime/Unity/Wallets/Core/WebGLMetaMask.cs +++ b/Assets/Thirdweb/Runtime/Unity/Wallets/Core/WebGLMetaMask.cs @@ -67,8 +67,9 @@ private void Awake() } } - public async Task EnableEthereumAsync() + public async Task EnableEthereumAsync(BigInteger chainId) { + _activeChainId = chainId; _enableEthereumTaskCompletionSource = new TaskCompletionSource(); #if UNITY_WEBGL && !UNITY_EDITOR diff --git a/Assets/Thirdweb/Runtime/Unity/WebGL/WebGLCopyBuffer.jslib b/Assets/Thirdweb/Runtime/Unity/WebGL/WebGLCopyBuffer.jslib new file mode 100644 index 00000000..c88c0a1b --- /dev/null +++ b/Assets/Thirdweb/Runtime/Unity/WebGL/WebGLCopyBuffer.jslib @@ -0,0 +1,31 @@ +mergeInto(LibraryManager.library, { + ThirdwebCopyBuffer: function (textPtr) { + var text = UTF8ToString(textPtr); + + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard + .writeText(text) + .then(function () { + console.log("Copied to clipboard:", text); + }) + .catch(function (err) { + console.warn("Failed to copy text with navigator.clipboard:", err); + fallbackCopyText(text); + }); + } else { + fallbackCopyText(text); + } + + function fallbackCopyText(textToCopy) { + var input = document.createElement("textarea"); + input.value = textToCopy; + input.style.position = "absolute"; + input.style.left = "-9999px"; + document.body.appendChild(input); + input.select(); + document.execCommand("copy"); + document.body.removeChild(input); + console.log("Copied to clipboard using fallback:", textToCopy); + } + }, +}); diff --git a/Assets/Thirdweb/Runtime/Unity/WebGL/WebGLCopyBuffer.jslib.meta b/Assets/Thirdweb/Runtime/Unity/WebGL/WebGLCopyBuffer.jslib.meta new file mode 100644 index 00000000..0fc1c902 --- /dev/null +++ b/Assets/Thirdweb/Runtime/Unity/WebGL/WebGLCopyBuffer.jslib.meta @@ -0,0 +1,32 @@ +fileFormatVersion: 2 +guid: 19e03b44cf3b7044a8f39760c8340904 +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/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index e174d928..34f68287 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -141,7 +141,7 @@ PlayerSettings: loadStoreDebugModeEnabled: 0 visionOSBundleVersion: 1.0 tvOSBundleVersion: 1.0 - bundleVersion: 5.13.0 + bundleVersion: 5.22.0 preloadedAssets: [] metroInputSource: 0 wsaTransparentSwapchain: 0 diff --git a/README.md b/README.md index f3a2ea6b..9ebc34d2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,17 @@ ![Thirdweb Unity SDK](https://github.com/thirdweb-dev/unity-sdk/assets/43042585/0eb16b66-317b-462b-9eb1-9425c0929c96) -[Unity Documentation](https://portal.thirdweb.com/unity/v5) -[.NET Documentation](https://portal.thirdweb.com/dotnet) +

+ All-In-One Cross-Platform Blockchain Unity SDK for Browser, Standalone, Mobile and Server Targets. +

+ +

+ + Unity Documentation + + + .NET Documentation + +

# Technical Demo @@ -9,18 +19,34 @@ Experience our multichain game demo leveraging In-App Wallets and Account Abstra ![image](https://github.com/thirdweb-dev/unity-sdk/assets/43042585/171198b2-83e7-4c8a-951b-79126dd47abb) +# Features + +This SDK provides a Unity-first integration of all [thirdweb](https://thirdweb.com) functionality, including but not limited to: + +- Support for all target platforms, Unity 2022 & Unity 6. +- First party support for [In-App Wallets](https://portal.thirdweb.com/connect/wallet/overview) (Guest, Email, Phone, Socials, Custom Auth+). +- First party support for [Account Abstraction](https://portal.thirdweb.com/connect/account-abstraction/overview) (Both EIP-4337 & zkSync Native AA). +- Instant connection to any chain with RPC Edge integration. +- Integrated IPFS upload/download. +- Create blockchain-powered agents with Thirdweb [Nebula](https://thirdweb.com/nebula). +- Easy to extend or wrap. +- High level contract extensions for interacting with common standards and thirdweb contract standards. +- Automatic ABI resolution. +- Build on top of thirdweb's [.NET SDK](https://portal.thirdweb.com/dotnet) - unity package updates are typically updates to a single DLL/a file or two. +- Get started in 5 minutes with a simple [ThirdwebManager](https://portal.thirdweb.com/unity/v5/thirdwebmanager) prefab. + # Supported Platforms & Wallets -**Build games for WebGL, Desktop, and Mobile using 1000+ supported chains, with various login options!** +**Build games for Web, Standalone, and Mobile using 2000+ supported chains, with various login options!** -| Wallet Provider | WebGL | Desktop | Mobile | -| ----------------------------------------- | :---: | :-----: | :----: | -| **In-App Wallet** (Email, Phone, Socials, Custom) | ✔️ | ✔️ | ✔️ | -| **Ecosystem Wallet** (IAW w/ partner permissions) | ✔️ | ✔️ | ✔️ | -| **Private Key Wallet** (Guest Mode) | ✔️ | ✔️ | ✔️ | -| **Wallet Connect Wallet** (400+ Wallets) | ✔️ | ✔️ | ✔️ | -| **MetaMask Wallet** (Browser Extension) | ✔️ | — | — | -| **Smart Wallet** (Account Abstraction) | ✔️ | ✔️ | ✔️ | +| Wallet Provider | Web | Standalone | Mobile | +| ------------------------------------------------------------ | :---: | :-----: | :----: | +| **In-App Wallet** (Guest, Email, Phone, Socials, Backend, Custom) | ✔️ | ✔️ | ✔️ | +| **Ecosystem Wallet** (IAW w/ partner permissions) | ✔️ | ✔️ | ✔️ | +| **Private Key Wallet** (Ephemereal, good for testing) | ✔️ | ✔️ | ✔️ | +| **Wallet Connect Wallet** (400+ Wallets) | ✔️ | ✔️ | ✔️ | +| **MetaMask Wallet** (Browser Extension) | ✔️ | — | — | +| **Smart Wallet** (Account Abstraction: 4337, ZkSync Native, 7702) | ✔️ | ✔️ | ✔️ | ✔️ Supported   ❌ Not Supported   — Not Applicable @@ -30,63 +56,24 @@ Experience our multichain game demo leveraging In-App Wallets and Account Abstra 2. **Explore:** Try out `Scene_Playground` to explore functionality and get onboarded. 3. **Learn:** Explore the [Unity v5 SDK Docs](https://portal.thirdweb.com/unity/v5) and the [.NET SDK Docs](https://portal.thirdweb.com/dotnet) to find a full API reference. -**Notes:** +## Miscellaneous -- Tested on Unity 2021.3+, 2022.3+, Unity 6 Preview. We recommend using 2022 LTS. -- Newtonsoft and EDM4U are included utilities; deselect if already installed to avoid conflicts. +- Recommended Unity Editor Version: 2022.3+ (LTS) +- Newtonsoft.Json and EDM4U are included utilities; deselect when importing if already installed to avoid conflicts. - If using .NET Framework and encountering `HttpUtility` errors, create `csc.rsp` with `-r:System.Web.dll` under `Assets`. -- Use version control and test removing duplicate DLLs if conflicts arise. +- Use version control and test removing duplicate DLLs if conflicts arise. Our SDK generally works with most versions of the few dependencies we do include. - To use your own WalletConnect Project ID, edit `Assets/Thirdweb/Plugins/WalletConnectUnity/Resources/WalletConnectProjectConfig.asset`. -# Build Instructions - -## General - -- **Build Settings:** Use `Smaller (faster) Builds` / `Shorter Build Time`. -- **Player Settings:** Use IL2CPP over Mono when available. -- **Stripping Level:** Set `Managed Stripping Level` to `Minimal` (`Player Settings` > `Other Settings` > `Optimization`). (Generally not a hard requirement unless using WalletConnect as a wallet provider option.) -- **Strip Engine Code:** Make sure this is turned off. - -## WebGL - -- **WebGL Template:** None enforced, feel free to customize! -- **Compression Format:** Set to `Disabled` (`Player Settings` > `Publishing Settings`) for final builds. -- **Testing WebGL Social Login Locally:** Host the build or run it locally with `Cross-Origin-Opener-Policy` set to `same-origin-allow-popups`. - -Example setup for testing In-App or Ecosystem Wallet Social Login locally (no longer required with Unity 6 Web, can use Build & Run): - -```javascript -// YourWebGLOutputFolder/server.js -const express = require("express"); -const app = express(); -const port = 8000; - -app.use((req, res, next) => { - res.header("Cross-Origin-Opener-Policy", "same-origin-allow-popups"); - next(); -}); - -app.use(express.static(".")); -app.listen(port, () => - console.log(`Server running on http://localhost:${port}`) -); - -// run it with `node server.js` -``` - -No action needed for hosted builds. - -## Mobile - -- **EDM4U:** Comes with the package, resolves dependencies at runtime. Use `Force Resolve` from `Assets` > `External Dependency Manager` > `Android Resolver`. -- **Redirect Schemes:** Set custom schemes matching your bundle ID in `Plugins/AndroidManifest.xml` or equivalent to ensure OAuth redirects. +## Additional Resources -# Migration from v4 +- [Documentation](https://portal.thirdweb.com/unity/v5) +- [Templates](https://thirdweb.com/templates) +- [Website](https://thirdweb.com) -See https://portal.thirdweb.com/unity/v5/migration-guide +## Support -# Need Help? +For help or feedback, please [visit our support site](https://thirdweb.com/support) -For any questions or support, visit our [Support Portal](https://thirdweb.com/support). +## Security -Thank you for trying out the Thirdweb Unity SDK! +If you believe you have found a security vulnerability in any of our packages, we kindly ask you not to open a public issue; and to disclose this to us by emailing `security@thirdweb.com`.