diff --git a/Assets/SequenceSDK/Sidekick.meta b/Assets/SequenceSDK/Sidekick.meta new file mode 100644 index 00000000..d6e4fccf --- /dev/null +++ b/Assets/SequenceSDK/Sidekick.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 001fe241a79c272428e10da40e12c5a4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/SequenceSDK/Sidekick/SideKickWalletTests.cs b/Assets/SequenceSDK/Sidekick/SideKickWalletTests.cs new file mode 100644 index 00000000..4ee218b4 --- /dev/null +++ b/Assets/SequenceSDK/Sidekick/SideKickWalletTests.cs @@ -0,0 +1,25 @@ +using System; +using System.Threading.Tasks; +using NUnit.Framework; +using Newtonsoft.Json.Linq; +using Sequence.Sidekick; +public class SequenceSidekickTests +{ + [Test] + public async Task TestGetWalletAddress() + { + try + { + var sidekick = new SequenceSidekickClient(); + string response = await sidekick.GetWalletAddress(); + Assert.IsNotNull(response); + var json = JObject.Parse(response); + Assert.IsTrue(json.ContainsKey("walletAddress")); + } + catch (Exception e) + { + Assert.Fail("Expected no exception, but got: " + e.Message); + } + } + +} diff --git a/Assets/SequenceSDK/Sidekick/SideKickWalletTests.cs.meta b/Assets/SequenceSDK/Sidekick/SideKickWalletTests.cs.meta new file mode 100644 index 00000000..bb679998 --- /dev/null +++ b/Assets/SequenceSDK/Sidekick/SideKickWalletTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 017246b8d45cfa14d91ee0abf92a13aa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/SequenceSDK/Sidekick/SidekickERC1155Tests.cs b/Assets/SequenceSDK/Sidekick/SidekickERC1155Tests.cs new file mode 100644 index 00000000..7bc670d7 --- /dev/null +++ b/Assets/SequenceSDK/Sidekick/SidekickERC1155Tests.cs @@ -0,0 +1,81 @@ +using System; +using System.Threading.Tasks; +using NUnit.Framework; +using UnityEngine; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Sequence; +using Sequence.Sidekick; +public class SidekickERC1155MintTest +{ + private SequenceSidekickClient sidekick; + private string chainId; + private string walletAddress; + + [SetUp] + public void Setup() + { + sidekick = new SequenceSidekickClient(Chain.TestnetArbitrumSepolia); + chainId = sidekick.Chain.GetChainId(); + } + + + [Test] + public async Task DeployERC1155() + { + try + { + walletAddress = await sidekick.GetWalletAddress(); + string recipientAddress = JObject.Parse(walletAddress)["address"]?.ToString(); + + var deployBody = new + { + defaultAdmin = recipientAddress, + minter = recipientAddress, + name = "MYTESTERC1155" + }; + + + string deployJson = JsonConvert.SerializeObject(deployBody); + + string deployResult = await sidekick.DeployERC1155(deployJson); + + Debug.Log("Deploy result: " + deployResult); + Assert.IsNotNull(deployResult, "Deploy result must not be null"); + } + catch (Exception e) + { + Assert.Fail("ERC1155 deploy flow failed: " + e.Message); + } + } + + + [Test] + public async Task MintERC1155() + { + try + { + walletAddress = await sidekick.GetWalletAddress(); + + string recipientAddress = JObject.Parse(walletAddress)["address"]?.ToString(); + + var mintBody = new + { + recipient = recipientAddress, + id = "0", + amount = "1", + data = "0x00" + }; + string mintJson = JsonConvert.SerializeObject(mintBody); + string mintResult = await sidekick.MintERC1155("0x63c12baa017b2bcb6855d83506500edcac423c3c", mintJson); + + Debug.Log("Mint result: " + mintResult); + + Assert.IsNotNull(mintResult, "Mint result must not be null"); + } + catch (Exception e) + { + Assert.Fail("ERC1155 flow failed: " + e.Message); + } + } +} diff --git a/Assets/SequenceSDK/Sidekick/SidekickERC1155Tests.cs.meta b/Assets/SequenceSDK/Sidekick/SidekickERC1155Tests.cs.meta new file mode 100644 index 00000000..94e8d9f4 --- /dev/null +++ b/Assets/SequenceSDK/Sidekick/SidekickERC1155Tests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 71b6d06c155472b4699f44b7dbb09b93 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/SequenceSDK/Sidekick/SidekickTests.asmdef b/Assets/SequenceSDK/Sidekick/SidekickTests.asmdef new file mode 100644 index 00000000..3350ea0f --- /dev/null +++ b/Assets/SequenceSDK/Sidekick/SidekickTests.asmdef @@ -0,0 +1,24 @@ +{ + "name": "SidekickTests", + "rootNamespace": "", + "references": [ + "GUID:f7fd4ba36aabd1d499450c174865e70b", + "GUID:0acc523941302664db1f4e527237feb3", + "GUID:27619889b8ba8c24980f49ee34dbb44a", + "GUID:c8f59a655c1ffd54cac5a528235b9f1c", + "GUID:13065393405843745b1313eb7159f82a" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/SequenceSDK/Sidekick/SidekickTests.asmdef.meta b/Assets/SequenceSDK/Sidekick/SidekickTests.asmdef.meta new file mode 100644 index 00000000..cadef0fb --- /dev/null +++ b/Assets/SequenceSDK/Sidekick/SidekickTests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a79d7c2264f7f5f46a4838d49aa6c43a +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Editor/Sidekick.meta b/Packages/Sequence-Unity/Editor/Sidekick.meta new file mode 100644 index 00000000..36e9a334 --- /dev/null +++ b/Packages/Sequence-Unity/Editor/Sidekick.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9470df17bc9a49f429274960a115a103 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Editor/Sidekick/SidekickDockerUtility.cs b/Packages/Sequence-Unity/Editor/Sidekick/SidekickDockerUtility.cs new file mode 100644 index 00000000..a36cef23 --- /dev/null +++ b/Packages/Sequence-Unity/Editor/Sidekick/SidekickDockerUtility.cs @@ -0,0 +1,201 @@ +using UnityEditor; +using UnityEngine; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Sequence.Sidekick.Config; +public static class SidekickDockerUtility +{ + private static SidekickConfig config; + + private static string SidekickPath + { + get + { + if (config == null) + { + config = Resources.Load("SidekickConfig"); + if (config == null) + { + UnityEngine.Debug.LogError("Could not load SidekickConfig from Resources."); + return string.Empty; + } + } + return config.SidekickPath; + } + } + + private static string dockerDesktopPath + { + get + { + if (config == null) + { + config = Resources.Load("SidekickConfig"); + if (config == null) + { + UnityEngine.Debug.LogError("Could not load SidekickConfig from Resources."); + return string.Empty; + } + } + return config.DockerDesktopPath; + } + } + + + [MenuItem("Sequence Sidekick/Start", false, 0)] + private static void StartSidekick() + { + if (IsSidekickRunning()) return; + + EnsureDockerDesktopRunning(); + + RunCommand("pnpm docker:restart", SidekickPath); + } + + [MenuItem("Sequence Sidekick/Start", true)] + private static bool ValidateStart() => !IsSidekickRunning(); + + [MenuItem("Sequence Sidekick/Stop", false, 1)] + private static void StopSidekick() + { + if (!IsSidekickRunning()) return; + + RunCommand("pnpm docker:stop", SidekickPath); + } + + [MenuItem("Sequence Sidekick/Stop", true)] + private static bool ValidateStop() => IsSidekickRunning(); + + private static void RunCommand(string command, string workingDirectory) + { + if (string.IsNullOrEmpty(workingDirectory) || !Directory.Exists(workingDirectory)) + { + UnityEngine.Debug.LogError($"Sidekick path not set or invalid: {workingDirectory}"); + return; + } + + ProcessStartInfo psi = new ProcessStartInfo + { + FileName = "cmd.exe", + Arguments = $"/c {command}", + WorkingDirectory = workingDirectory, + CreateNoWindow = true, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + Process process = new Process { StartInfo = psi }; + + process.OutputDataReceived += (sender, e) => + { + if (!string.IsNullOrEmpty(e.Data)) + UnityEngine.Debug.Log($"[Docker] {e.Data}"); + }; + + process.ErrorDataReceived += (sender, e) => + { + if (!string.IsNullOrEmpty(e.Data)) + UnityEngine.Debug.LogWarning($"[Docker] {e.Data}"); + }; + + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + } + private static void EnsureDockerDesktopRunning() + { + const int maxWaitTimeSeconds = 120; + const int pollIntervalMs = 2000; + + System.Threading.Tasks.Task.Run(() => + { + if (!Process.GetProcessesByName("Docker Desktop").Any()) + { + if (File.Exists(config.DockerDesktopPath)) + { + Process.Start(config.DockerDesktopPath); + } + else + { + UnityEngine.Debug.LogWarning("[Docker] Docker Desktop not found at Sidekick Config set path."); + return; + } + } + + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + while (stopwatch.Elapsed.TotalSeconds < maxWaitTimeSeconds) + { + if (IsDockerDaemonReady()) + { + UnityEngine.Debug.Log("[Docker] Docker is ready."); + return; + } + + System.Threading.Thread.Sleep(pollIntervalMs); + } + + UnityEngine.Debug.LogError("[Docker] Timed out waiting for Docker to become ready."); + }); + } + + private static bool IsDockerDaemonReady() + { + try + { + ProcessStartInfo psi = new ProcessStartInfo + { + FileName = "cmd.exe", + Arguments = "/c docker info", + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + UseShellExecute = false + }; + + using (Process process = Process.Start(psi)) + { + process.WaitForExit(3000); + + string output = process.StandardOutput.ReadToEnd(); + string error = process.StandardError.ReadToEnd(); + + return process.ExitCode == 0 && output.Contains("Server Version"); + } + } + catch + { + return false; + } + } + + private static bool IsSidekickRunning() + { + try + { + var psi = new ProcessStartInfo + { + FileName = "cmd.exe", + Arguments = "/c docker ps --format \"{{.Names}}\"", + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using (var process = Process.Start(psi)) + { + process.WaitForExit(); + var output = process.StandardOutput.ReadToEnd(); + + return output.Contains("sidekick"); + } + } + catch + { + return false; + } + } +} diff --git a/Packages/Sequence-Unity/Editor/Sidekick/SidekickDockerUtility.cs.meta b/Packages/Sequence-Unity/Editor/Sidekick/SidekickDockerUtility.cs.meta new file mode 100644 index 00000000..9fe7371d --- /dev/null +++ b/Packages/Sequence-Unity/Editor/Sidekick/SidekickDockerUtility.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a726ece5a906fca4393ef76dfabe8e00 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Editor/Sidekick/SidekickEditor.asmdef b/Packages/Sequence-Unity/Editor/Sidekick/SidekickEditor.asmdef new file mode 100644 index 00000000..6635784a --- /dev/null +++ b/Packages/Sequence-Unity/Editor/Sidekick/SidekickEditor.asmdef @@ -0,0 +1,23 @@ +{ + "name": "Sequence.SidekickEditor", + "rootNamespace": "", + "references": [ + "GUID:4e0a798abbda240658187632ae443a67", + "GUID:f7fd4ba36aabd1d499450c174865e70b", + "GUID:b4f9c0f8f363f439b9e337f79050f189", + "GUID:a35e3a53d4439435f8b36ed2c6158cd8", + "GUID:c8f59a655c1ffd54cac5a528235b9f1c", + "GUID:13065393405843745b1313eb7159f82a" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Editor/Sidekick/SidekickEditor.asmdef.meta b/Packages/Sequence-Unity/Editor/Sidekick/SidekickEditor.asmdef.meta new file mode 100644 index 00000000..ec49d705 --- /dev/null +++ b/Packages/Sequence-Unity/Editor/Sidekick/SidekickEditor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1115d98b227df534a9e9b3e5092485e9 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Editor/Sidekick/SidekickEditor.cs b/Packages/Sequence-Unity/Editor/Sidekick/SidekickEditor.cs new file mode 100644 index 00000000..3ad6be9c --- /dev/null +++ b/Packages/Sequence-Unity/Editor/Sidekick/SidekickEditor.cs @@ -0,0 +1,376 @@ +using UnityEditor; +using UnityEngine; +using System; +using Sequence.Sidekick; + + + + [CustomEditor(typeof(SidekickEditorController))] + public class SidekickEditor : UnityEditor.Editor + { + bool deployFoldout = true; + bool mintFoldout = true; + bool generateFoldout = true; + bool grantRoleFoldout = true; + + ContractType selectedDeployType; + ContractType selectedMintType; + ContractType selectedGenerateType; + + GUIStyle HeaderStyle => new GUIStyle(EditorStyles.label) + { + fontStyle = FontStyle.Bold, + fontSize = 16, + normal = { textColor = new Color(0.9f, 0.9f, 0.9f) } + }; + + GUIStyle FoldoutStyle => new GUIStyle(EditorStyles.foldout) + { + fontStyle = FontStyle.Bold, + fontSize = 14, + normal = { textColor = new Color(0.8f, 0.8f, 0.8f) }, + hover = { textColor = Color.white }, + active = { textColor = Color.white } + }; + + GUIStyle SectionBoxStyle + { + get + { + var style = new GUIStyle(GUI.skin.box); + style.normal.background = MakeTex(2, 2, new Color(0.15f, 0.15f, 0.15f, 0.7f)); + style.margin = new RectOffset(0, 0, 5, 10); + style.padding = new RectOffset(10, 10, 10, 10); + return style; + } + } + GUIStyle ButtonStyle + { + get + { + var style = new GUIStyle(GUI.skin.button) + { + fontStyle = FontStyle.Bold, + fontSize = 13 + }; + + style.normal.textColor = Color.white; + style.hover.textColor = Color.white; + + return style; + } + } + GUIStyle LabelBoldStyle => new GUIStyle(EditorStyles.label) + { + fontStyle = FontStyle.Bold, + fontSize = 12, + normal = { textColor = Color.white } + }; + + public override void OnInspectorGUI() + { + var controller = (SidekickEditorController)target; + + EditorGUILayout.Space(8); + EditorGUILayout.LabelField("Sidekick Wallet", HeaderStyle); + + GUILayout.Space(6); + if (GUILayout.Button("Get Wallet Address", ButtonStyle, GUILayout.Height(28))) + controller.GetWalletAddress(); + + //GUILayout.Space(6); + //if (GUILayout.Button("Get Contracts", ButtonStyle, GUILayout.Height(28))) + // controller.GetContracts(); + + EditorGUILayout.Space(14); + + #region Deploy + deployFoldout = EditorGUILayout.Foldout(deployFoldout, "Deploy", true, FoldoutStyle); + if (deployFoldout) + { + using (new EditorGUILayout.VerticalScope(SectionBoxStyle)) + { + selectedDeployType = (ContractType)EditorGUILayout.EnumPopup("Contract Type", selectedDeployType); + + EditorGUILayout.Space(); + + switch (selectedDeployType) + { + case ContractType.CurrencyToken: + controller.DeployERC20InitialOwner = EditorGUILayout.TextField("Initial Owner", controller.DeployERC20InitialOwner); + controller.DeployERC20Name = EditorGUILayout.TextField("Name", controller.DeployERC20Name); + controller.DeployERC20Symbol = EditorGUILayout.TextField("Symbol", controller.DeployERC20Symbol); + GUILayout.Space(8); + if (GUILayout.Button("Deploy Currency Token", ButtonStyle, GUILayout.Height(26))) + controller.DeployERC20(); + break; + + case ContractType.NFTContract: + controller.DeployERC721DefaultAdmin = EditorGUILayout.TextField("Default Admin", controller.DeployERC721DefaultAdmin); + controller.DeployERC721Minter = EditorGUILayout.TextField("Minter", controller.DeployERC721Minter); + controller.DeployERC721Name = EditorGUILayout.TextField("Name", controller.DeployERC721Name); + controller.DeployERC721Symbol = EditorGUILayout.TextField("Symbol", controller.DeployERC721Symbol); + GUILayout.Space(8); + if (GUILayout.Button("Deploy NFT Contract", ButtonStyle, GUILayout.Height(26))) + controller.DeployERC721(); + break; + + case ContractType.Web3GameItem: + controller.DeployDefaultAdmin = EditorGUILayout.TextField("Default Admin", controller.DeployDefaultAdmin); + controller.DeployMinter = EditorGUILayout.TextField("Minter", controller.DeployMinter); + controller.DeployName = EditorGUILayout.TextField("Name", controller.DeployName); + GUILayout.Space(8); + if (GUILayout.Button("Deploy Web 3 Game Item", ButtonStyle, GUILayout.Height(26))) + controller.DeployERC1155(); + break; + } + } + } + #endregion + + #region Grant Role + grantRoleFoldout = EditorGUILayout.Foldout(grantRoleFoldout, "Grant Role", true, FoldoutStyle); + if (grantRoleFoldout) + { + using (new EditorGUILayout.VerticalScope(SectionBoxStyle)) + { + controller.SelectedContractRole = (ContractRole)EditorGUILayout.EnumPopup("Role", controller.SelectedContractRole); + + switch (controller.SelectedContractRole) + { + case ContractRole.Minter: + controller.GrantRoleRole = "0xdfd8f10cabf255ee49ecf3db8b4502eea7eac26c270f6e5f0f28506f6f3fbb55"; + break; + default: + throw new ArgumentOutOfRangeException(nameof(controller.SelectedContractRole), "Unsupported role"); + } + + controller.GrantRoleContractAddress = EditorGUILayout.TextField("Contract Address", controller.GrantRoleContractAddress); + controller.GrantRoleAccount = EditorGUILayout.TextField("Account Address", controller.GrantRoleAccount); + + GUILayout.Space(8); + + if (GUILayout.Button("Grant Role", ButtonStyle, GUILayout.Height(26))) + controller.GrantERC1155Role(); + } + } + #endregion + + #region Mint + mintFoldout = EditorGUILayout.Foldout(mintFoldout, "Mint", true, FoldoutStyle); + if (mintFoldout) + { + using (new EditorGUILayout.VerticalScope(SectionBoxStyle)) + { + selectedMintType = (ContractType)EditorGUILayout.EnumPopup("Item Type", selectedMintType); + GUILayout.Space(6); + + switch (selectedMintType) + { + case ContractType.CurrencyToken: + controller.MintERC20ContractAddress = EditorGUILayout.TextField("Currency Address", controller.MintERC20ContractAddress); + controller.MintERC20RecipientAddress = EditorGUILayout.TextField("Recipient Address", controller.MintERC20RecipientAddress); + controller.MintERC20Amount = EditorGUILayout.TextField("Amount", controller.MintERC20Amount); + GUILayout.Space(8); + if (GUILayout.Button("Mint CurrencyToken", ButtonStyle, GUILayout.Height(26))) + controller.MintERC20(); + break; + + case ContractType.NFTContract: + controller.SafeMintERC721ContractAddress = EditorGUILayout.TextField("NFT Contract Address", controller.SafeMintERC721ContractAddress); + controller.IsBatchMintERC721 = EditorGUILayout.Toggle("Batch Mint", controller.IsBatchMintERC721); + + GUILayout.Space(8); + + if (controller.IsBatchMintERC721) + { + int batchSize = controller.SafeMintERC721BatchRecipients?.Length ?? 0; + batchSize = EditorGUILayout.IntField("Batch Size", batchSize); + batchSize = Mathf.Max(batchSize, 0); + GUILayout.Space(6); + + var recipients = controller.SafeMintERC721BatchRecipients ?? new string[0]; + var tokenIds = controller.SafeMintERC721BatchTokenIds ?? new string[0]; + + if (recipients.Length != batchSize) + Array.Resize(ref recipients, batchSize); + if (tokenIds.Length != batchSize) + Array.Resize(ref tokenIds, batchSize); + + for (int i = 0; i < batchSize; i++) + { + EditorGUILayout.LabelField($"Mint {i + 1}", LabelBoldStyle); + recipients[i] = EditorGUILayout.TextField("Recipient", recipients[i]); + tokenIds[i] = EditorGUILayout.TextField("Token ID", tokenIds[i]); + GUILayout.Space(6); + } + + controller.SafeMintERC721BatchRecipients = recipients; + controller.SafeMintERC721BatchTokenIds = tokenIds; + + if (GUILayout.Button("Mint NFT Contract Batch", ButtonStyle, GUILayout.Height(26))) + controller.SafeMintBatchERC721(); + } + else + { + controller.SafeMintERC721Recipient = EditorGUILayout.TextField("Recipient Address", controller.SafeMintERC721Recipient); + controller.SafeMintERC721TokenId = EditorGUILayout.TextField("Token ID", controller.SafeMintERC721TokenId); + GUILayout.Space(8); + if (GUILayout.Button("Mint NFT Contract", ButtonStyle, GUILayout.Height(26))) + controller.SafeMintERC721(); + } + break; + + case ContractType.Web3GameItem: + controller.MintERC1155ContractAddress = EditorGUILayout.TextField("Item Address", controller.MintERC1155ContractAddress); + controller.IsBatchMintERC1155 = EditorGUILayout.Toggle("Batch Mint", controller.IsBatchMintERC1155); + GUILayout.Space(8); + + if (controller.IsBatchMintERC1155) + { + int batchSize = controller.MintERC1155BatchRecipients?.Length ?? 0; + batchSize = EditorGUILayout.IntField("Batch Size", batchSize); + batchSize = Mathf.Max(batchSize, 0); + GUILayout.Space(6); + + var recipients = controller.MintERC1155BatchRecipients ?? new string[0]; + var ids = controller.MintERC1155BatchIds ?? new string[0]; + var amounts = controller.MintERC1155BatchAmounts ?? new string[0]; + var datas = controller.MintERC1155BatchDatas ?? new string[0]; + + if (recipients.Length != batchSize) Array.Resize(ref recipients, batchSize); + if (ids.Length != batchSize) Array.Resize(ref ids, batchSize); + if (amounts.Length != batchSize) Array.Resize(ref amounts, batchSize); + if (datas.Length != batchSize) Array.Resize(ref datas, batchSize); + + for (int i = 0; i < batchSize; i++) + { + EditorGUILayout.LabelField($"Mint {i + 1}", LabelBoldStyle); + recipients[i] = EditorGUILayout.TextField("Recipient", recipients[i]); + ids[i] = EditorGUILayout.TextField("ID", ids[i]); + amounts[i] = EditorGUILayout.TextField("Amount", amounts[i]); + datas[i] = EditorGUILayout.TextField("Data", datas[i]); + GUILayout.Space(6); + } + + controller.MintERC1155BatchRecipients = recipients; + controller.MintERC1155BatchIds = ids; + controller.MintERC1155BatchAmounts = amounts; + controller.MintERC1155BatchDatas = datas; + + if (GUILayout.Button("Mint Web 3 Game Item Batch", ButtonStyle, GUILayout.Height(26))) + controller.MintERC1155Batch(); + } + else + { + controller.MintERC1155RecipientAddress = EditorGUILayout.TextField("Recipient Address", controller.MintERC1155RecipientAddress); + controller.MintERC1155Id = EditorGUILayout.TextField("ID", controller.MintERC1155Id); + controller.MintERC1155Amount = EditorGUILayout.TextField("Amount", controller.MintERC1155Amount); + controller.MintERC1155Data = EditorGUILayout.TextField("Data", controller.MintERC1155Data); + GUILayout.Space(8); + if (GUILayout.Button("Mint Web 3 Game Item", ButtonStyle, GUILayout.Height(26))) + controller.MintERC1155(); + } + break; + } + } + } + #endregion + #region Generate SO + generateFoldout = EditorGUILayout.Foldout(generateFoldout, "Generate SO", true, FoldoutStyle); + if (generateFoldout) + { + using (new EditorGUILayout.VerticalScope(SectionBoxStyle)) + { + selectedGenerateType = (ContractType)EditorGUILayout.EnumPopup("Contract Type", selectedGenerateType); + GUILayout.Space(8); + + switch (selectedGenerateType) + { + case ContractType.CurrencyToken: + controller.soContractAddress = EditorGUILayout.TextField("Currency Address", controller.soContractAddress); + controller.soTokenDecimals = EditorGUILayout.IntField("Token Decimals", controller.soTokenDecimals); + GUILayout.Space(8); + + if (GUILayout.Button("Create Currency Token ScriptableObject", ButtonStyle, GUILayout.Height(26))) + { + var asset = CreateInstance(); + asset.contractAddress = controller.soContractAddress; + + string path = EditorUtility.SaveFilePanelInProject("Save ERC20 ScriptableObject", "NewERC20Scriptable", "asset", "Please enter a file name to save the asset to"); + if (!string.IsNullOrEmpty(path)) + { + AssetDatabase.CreateAsset(asset, path); + AssetDatabase.SaveAssets(); + EditorUtility.FocusProjectWindow(); + Selection.activeObject = asset; + } + } + break; + + case ContractType.NFTContract: + controller.soContractAddress = EditorGUILayout.TextField("Contract Address", controller.soContractAddress); + GUILayout.Space(8); + + if (GUILayout.Button("Create NFT Contract ScriptableObject", ButtonStyle, GUILayout.Height(26))) + { + var asset = CreateInstance(); + asset.contractAddress = controller.soContractAddress; + + string path = EditorUtility.SaveFilePanelInProject("Save ERC721 ScriptableObject", "NewERC721Scriptable", "asset", "Please enter a file name to save the asset to"); + if (!string.IsNullOrEmpty(path)) + { + AssetDatabase.CreateAsset(asset, path); + AssetDatabase.SaveAssets(); + EditorUtility.FocusProjectWindow(); + Selection.activeObject = asset; + } + } + + break; + + case ContractType.Web3GameItem: + controller.soContractAddress = EditorGUILayout.TextField("Item Address", controller.soContractAddress); + controller.soSpecifyBurnAddress = EditorGUILayout.Toggle("Specify Burn Address", controller.soSpecifyBurnAddress); + + if (controller.soSpecifyBurnAddress) + { + controller.soContractBurnAddress = EditorGUILayout.TextField("Burn Address", controller.soContractBurnAddress); + + } + GUILayout.Space(8); + + if (GUILayout.Button("Create Web 3 Game Item ScriptableObject", ButtonStyle, GUILayout.Height(26))) + { + var asset = CreateInstance(); + asset.contractAddress = controller.soContractAddress; + asset.specifyBurnAddress = controller.soSpecifyBurnAddress; + + string path = EditorUtility.SaveFilePanelInProject("Save ERC1155 ScriptableObject", "NewERC1155Scriptable", "asset", "Please enter a file name to save the asset to"); + if (!string.IsNullOrEmpty(path)) + { + AssetDatabase.CreateAsset(asset, path); + AssetDatabase.SaveAssets(); + EditorUtility.FocusProjectWindow(); + Selection.activeObject = asset; + } + } + break; + + } + } + } + #endregion + } + + private Texture2D MakeTex(int width, int height, Color col) + { + Color[] pix = new Color[width * height]; + for (int i = 0; i < pix.Length; i++) pix[i] = col; + Texture2D result = new Texture2D(width, height); + result.SetPixels(pix); + result.Apply(); + return result; + } + } + diff --git a/Packages/Sequence-Unity/Editor/Sidekick/SidekickEditor.cs.meta b/Packages/Sequence-Unity/Editor/Sidekick/SidekickEditor.cs.meta new file mode 100644 index 00000000..3984d6ce --- /dev/null +++ b/Packages/Sequence-Unity/Editor/Sidekick/SidekickEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: be52a77155b094f4eb30b94267580886 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/Samples~/Demo/Resources/Prefabs/Common/DatePicker.prefab.meta b/Packages/Sequence-Unity/Sequence/Samples~/Demo/Resources/Prefabs/Common/DatePicker.prefab.meta index 232b87f9..c3802ab8 100644 --- a/Packages/Sequence-Unity/Sequence/Samples~/Demo/Resources/Prefabs/Common/DatePicker.prefab.meta +++ b/Packages/Sequence-Unity/Sequence/Samples~/Demo/Resources/Prefabs/Common/DatePicker.prefab.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 8b2c48b893a79b8489b719918807a34d +guid: e968328049247dd4aa83e38d2fdd2ea4 PrefabImporter: externalObjects: {} userData: diff --git a/Packages/Sequence-Unity/Sequence/Samples~/Demo/Resources/Prefabs/Common/LoadingScreen.prefab.meta b/Packages/Sequence-Unity/Sequence/Samples~/Demo/Resources/Prefabs/Common/LoadingScreen.prefab.meta index b4f384aa..67a10345 100644 --- a/Packages/Sequence-Unity/Sequence/Samples~/Demo/Resources/Prefabs/Common/LoadingScreen.prefab.meta +++ b/Packages/Sequence-Unity/Sequence/Samples~/Demo/Resources/Prefabs/Common/LoadingScreen.prefab.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 21aa1af318f304faca1367525db9bc28 +guid: 5b387988d1bedd8489482f4663359b56 PrefabImporter: externalObjects: {} userData: diff --git a/Packages/Sequence-Unity/Sequence/Samples~/Demo/Resources/Prefabs/Common/QrCodeView.prefab.meta b/Packages/Sequence-Unity/Sequence/Samples~/Demo/Resources/Prefabs/Common/QrCodeView.prefab.meta index b5b8970a..3f28f9bd 100644 --- a/Packages/Sequence-Unity/Sequence/Samples~/Demo/Resources/Prefabs/Common/QrCodeView.prefab.meta +++ b/Packages/Sequence-Unity/Sequence/Samples~/Demo/Resources/Prefabs/Common/QrCodeView.prefab.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 7cf47fcef03d043179b4447c9980330f +guid: ab588d599d2e99543ace02fa3ba3fcde PrefabImporter: externalObjects: {} userData: diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/DeployedContractAddressExtractor.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/DeployedContractAddressExtractor.cs new file mode 100644 index 00000000..20da2c5f --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/DeployedContractAddressExtractor.cs @@ -0,0 +1,36 @@ +namespace Sequence.EmbeddedWallet +{ + public static class DeployedeContractAddressExtractor + { + public static string ExtractFirstContractAddressExceptOwn(TransactionReceipt receipt, string ownContractAddress) + { + ownContractAddress = ownContractAddress?.ToLower(); + + if (!string.IsNullOrEmpty(receipt.contractAddress)) + { + var contractAddrLower = receipt.contractAddress.ToLower(); + if (contractAddrLower != ownContractAddress) + { + return contractAddrLower; + } + } + + if (receipt.logs != null) + { + foreach (var log in receipt.logs) + { + if (!string.IsNullOrEmpty(log.address)) + { + var addrLower = log.address.ToLower(); + if (addrLower != ownContractAddress) + { + return addrLower; + } + } + } + } + + return null; + } + } +} diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/DeployedContractAddressExtractor.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/DeployedContractAddressExtractor.cs.meta new file mode 100644 index 00000000..10e0b143 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/DeployedContractAddressExtractor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 41194a16e50bdba48849569c409ac9dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects.meta new file mode 100644 index 00000000..67b88267 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 404ebe9c6cb21d846aa8ed133114c6d8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects/ERC1155Scriptable.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects/ERC1155Scriptable.cs new file mode 100644 index 00000000..fdc0e916 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects/ERC1155Scriptable.cs @@ -0,0 +1,27 @@ +using UnityEngine; +using Sequence.Contracts; + +[CreateAssetMenu(fileName = "ERC1155ContractData", menuName = "Sequence/ERC1155 Contract")] +public class ERC1155Scriptable : ScriptableObject +{ + public string contractAddress; + public bool specifyBurnAddress; + + public GameObject linkedGameObject; + + private ERC1155 _erc1155; + + + public ERC1155 GetContract() + { + if (_erc1155 == null) + { + if (specifyBurnAddress) + _erc1155 = new ERC1155(contractAddress, specifyBurnAddress); + else + _erc1155 = new ERC1155(contractAddress); + + } + return _erc1155; + } +} diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects/ERC1155Scriptable.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects/ERC1155Scriptable.cs.meta new file mode 100644 index 00000000..9aeaf163 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects/ERC1155Scriptable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f43439d1f99de164fb5b87ee45ff3e5c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects/ERC20Scriptable.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects/ERC20Scriptable.cs new file mode 100644 index 00000000..4f88b984 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects/ERC20Scriptable.cs @@ -0,0 +1,21 @@ +using UnityEngine; +using Sequence.Contracts; + +[CreateAssetMenu(fileName = "ERC20ContractData", menuName = "Sequence/ERC20 Contract")] +public class ERC20Scriptable : ScriptableObject +{ + public string contractAddress; + + public GameObject linkedGameObject; + + private ERC20 _erc20; + + public ERC20 GetContract() + { + if (_erc20 == null) + { + _erc20 = new ERC20(contractAddress); + } + return _erc20; + } +} diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects/ERC20Scriptable.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects/ERC20Scriptable.cs.meta new file mode 100644 index 00000000..a8f78637 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects/ERC20Scriptable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5cea2bf24d028f846ab53bacf860010b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects/ERC721Scriptable.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects/ERC721Scriptable.cs new file mode 100644 index 00000000..b14e33a2 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects/ERC721Scriptable.cs @@ -0,0 +1,21 @@ +using UnityEngine; +using Sequence.Contracts; + +[CreateAssetMenu(fileName = "ERC721ContractData", menuName = "Sequence/ERC721 Contract")] +public class ERC721Scriptable : ScriptableObject +{ + public string contractAddress; + + public GameObject linkedGameObject; + + private ERC721 _erc721; + + public ERC721 GetContract() + { + if (_erc721 == null) + { + _erc721 = new ERC721(contractAddress); + } + return _erc721; + } +} diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects/ERC721Scriptable.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects/ERC721Scriptable.cs.meta new file mode 100644 index 00000000..2b8423be --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Ethereum/Contract/ScriptableObjects/ERC721Scriptable.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fc72839ff10fc4d4fabd2d2d7bf77f5c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Pay/Transak/NftDataEncoder.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Pay/Transak/NftDataEncoder.cs index fdf6873d..241dba55 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Pay/Transak/NftDataEncoder.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Pay/Transak/NftDataEncoder.cs @@ -1,6 +1,7 @@ using System; using Newtonsoft.Json; using Sequence.Utils; +using UnityEngine; // Make sure this is included for logging namespace Sequence.Pay.Transak { @@ -8,9 +9,16 @@ public class NftDataEncoder { public static string Encode(TransakNftData item) { - string itemJson = JsonConvert.SerializeObject(new [] { item }); + string itemJson = JsonConvert.SerializeObject(new[] { item }); + Debug.Log("JSON Serialized:\n" + itemJson); + string itemJsonBase64 = itemJson.StringToBase64(); - return Uri.EscapeDataString(itemJsonBase64); + Debug.Log("Base64 Encoded (with padding):\n" + itemJsonBase64); + + string encoded = Uri.EscapeDataString(itemJsonBase64); + Debug.Log("URL Encoded:\n" + encoded); + + return encoded; } } -} \ No newline at end of file +} diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Pay/Transak/TransakNFTCheckout.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Pay/Transak/TransakNFTCheckout.cs index 3343ca11..96486562 100644 --- a/Packages/Sequence-Unity/Sequence/SequenceSDK/Pay/Transak/TransakNFTCheckout.cs +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Pay/Transak/TransakNFTCheckout.cs @@ -107,13 +107,15 @@ public async Task OpenNFTCheckoutLink(TransakNftData item, TransakContractId con Application.OpenURL(link); } - private async Task GetNFTCheckoutLink(TransakNftData item, string callData, Address contractAddress, TransakContractId contractId) + private async Task GetNFTCheckoutLink(TransakNftData item, string callData, Address contractAddress, TransakContractId contractId) { if (contractId.Chain != _chain) { throw new ArgumentException($"The provided {nameof(contractId)} is not for the same chain as the current instance of {nameof(TransakNFTCheckout)}, given: {contractId.Chain}, expected: {_chain}"); } + Debug.Log(callData); + string transakCallData = CallDataCompressor.Compress(callData); string baseUrl = "https://global.transak.com"; diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick.meta new file mode 100644 index 00000000..efc08b16 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e8ac46c277919214fa23a6ae009fcfa0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/Config.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/Config.meta new file mode 100644 index 00000000..8672ba68 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/Config.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2d6e26980a1bf5d408256223196fee33 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/Config/SidekickConfig.asmdef b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/Config/SidekickConfig.asmdef new file mode 100644 index 00000000..677a5a82 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/Config/SidekickConfig.asmdef @@ -0,0 +1,3 @@ +{ + "name": "SidekickConfig" +} diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/Config/SidekickConfig.asmdef.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/Config/SidekickConfig.asmdef.meta new file mode 100644 index 00000000..d81cecc2 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/Config/SidekickConfig.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 13065393405843745b1313eb7159f82a +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/Config/SidekickConfig.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/Config/SidekickConfig.cs new file mode 100644 index 00000000..7c8f5184 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/Config/SidekickConfig.cs @@ -0,0 +1,17 @@ +using UnityEngine; + +namespace Sequence.Sidekick.Config +{ + [CreateAssetMenu(fileName = "SidekickConfig", menuName = "Sequence/SidekickConfig")] + public class SidekickConfig : ScriptableObject + { + [Tooltip("Your secret key from your Sidekick project")] + public string SecretKey; + + [Tooltip("The local file path to the cloned Sidekick repository on your machine. For example: 'C:/Projects/sidekick'.")] + public string SidekickPath; + + [Tooltip("The local installation path to Docker Desktop. For example: 'C:/Program Files/Docker/Docker'.")] + public string DockerDesktopPath; + } +} diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/Config/SidekickConfig.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/Config/SidekickConfig.cs.meta new file mode 100644 index 00000000..0e347ce4 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/Config/SidekickConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cc489fae0c88420439d1433f41fd641c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/SequenceSidekick.asmdef b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/SequenceSidekick.asmdef new file mode 100644 index 00000000..debae739 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/SequenceSidekick.asmdef @@ -0,0 +1,18 @@ +{ + "name": "SequenceSidekick", + "rootNamespace": "", + "references": [ + "GUID:4e0a798abbda240658187632ae443a67", + "GUID:f7fd4ba36aabd1d499450c174865e70b", + "GUID:13065393405843745b1313eb7159f82a" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/SequenceSidekick.asmdef.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/SequenceSidekick.asmdef.meta new file mode 100644 index 00000000..671ff3d7 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/SequenceSidekick.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c8f59a655c1ffd54cac5a528235b9f1c +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/SequenceSidekickClient.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/SequenceSidekickClient.cs new file mode 100644 index 00000000..d8838d97 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/SequenceSidekickClient.cs @@ -0,0 +1,268 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using Sequence.Sidekick.Config; + +namespace Sequence.Sidekick +{ + public class SequenceSidekickClient + { + private static readonly System.Net.Http.HttpClient client = new(); + private string baseUrl = "http://localhost:3000"; + + public Chain Chain; + + private string ChainId => Chain.GetChainId(); + + public SequenceSidekickClient() { } + + public SequenceSidekickClient(Chain chain) + { + Chain = chain; + } + + #region HttpRequests + private async Task GetAsync(string endpoint, Dictionary headers = null) + { + using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, baseUrl + endpoint)) + { + if (headers != null) + { + foreach (var header in headers) + { + request.Headers.Add(header.Key, header.Value); + } + } + + HttpResponseMessage response = await client.SendAsync(request); + response.EnsureSuccessStatusCode(); + return await response.Content.ReadAsStringAsync(); + } + } + + private async Task PostAsync(string endpoint, string json) + { + string url = baseUrl + endpoint; + + using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url)) + { + request.Headers.Add("x-secret-key", Resources.Load("SidekickConfig").SecretKey); + + var content = new StringContent(json, Encoding.UTF8, "application/json"); + request.Content = content; + + try + { + HttpResponseMessage response = await client.SendAsync(request); + string responseContent = await response.Content.ReadAsStringAsync(); + response.EnsureSuccessStatusCode(); + return responseContent; + } + catch (HttpRequestException e) + { + throw new System.Exception(e.Message); + } + } + } + + #endregion + + #region Wallet + public async Task GetWalletAddress() + { + return await GetAsync("/sidekick/wallet-address"); + } + #endregion + + #region Contract + public async Task ReadContract(string address, string functionName, string json) + { + return await PostAsync($"/read/contract/{ChainId}/{address}/{functionName}", json); + } + + public async Task WriteContract(string address, string functionName, string json) + { + return await PostAsync($"/write/contract/{ChainId}/{address}/{functionName}", json); + } + + public async Task GetAllContracts() + { + return await GetAsync("/contract/getAll"); + } + + public async Task GetContract(string address) + { + return await GetAsync($"/contract/get/{address}"); + } + + public async Task IsContractDeployed(string address) + { + return await GetAsync($"/contract/isDeployed/{ChainId}/{address}"); + } + + public async Task DeployContract(string json) + { + return await PostAsync($"/deploy/contract/{ChainId}", json); + } + + public async Task ImportContractsFromBuilder(string projectId, string json) + { + return await PostAsync($"/contract/importAllFromBuilder/{projectId}", json); + } + + public async Task AddContract(string json) + { + return await PostAsync("/contract/add", json); + } + #endregion + + #region ERC20 + public async Task TransferERC20(string address, string json) + { + return await PostAsync($"/write/erc20/{ChainId}/{address}/transfer", json); + } + + public async Task ApproveERC20(string address, string json) + { + return await PostAsync($"/write/erc20/{ChainId}/{address}/approve", json); + } + + public async Task MintERC20(string address, string json) + { + return await PostAsync($"/write/erc20/{ChainId}/{address}/mint", json); + } + + public async Task TransferFromERC20(string address, string json) + { + return await PostAsync($"/write/erc20/{ChainId}/{address}/transferFrom", json); + } + + public async Task DeployERC20(string json) + { + return await PostAsync($"/deploy/erc20/{ChainId}", json); + } + #endregion + + #region ERC721 + public async Task SafeMintERC721(string address, string json) + { + return await PostAsync($"/write/erc721/{ChainId}/{address}/safeMint", json); + } + + public async Task SafeMintBatchERC721(string address, string json) + { + return await PostAsync($"/write/erc721/{ChainId}/{address}/safeMintBatch", json); + } + + public async Task BalanceOfERC721(string address) + { + return await GetAsync($"/read/erc721/{ChainId}/{address}/balanceOf"); + } + + public async Task DeployERC721(string json) + { + return await PostAsync($"/deploy/erc721/{ChainId}", json); + } + #endregion + + #region ERC1155 + public async Task MintERC1155(string address, string json) + { + return await PostAsync($"/write/erc1155/{ChainId}/{address}/mint", json); + } + + public async Task MintBatchERC1155(string address, string json) + { + return await PostAsync($"/write/erc1155/{ChainId}/{address}/mintBatch", json); + } + + public async Task GrantRoleERC1155(string address, string json) + { + return await PostAsync($"/write/erc1155/{ChainId}/{address}/grantRole", json); + } + + public async Task HasRoleERC1155(string address) + { + return await GetAsync($"/read/erc1155/{ChainId}/{address}/hasRole"); + } + + public async Task MinterRoleERC1155(string address) + { + return await GetAsync($"/read/erc1155/{ChainId}/{address}/minterRole"); + } + + public async Task BalanceOfERC1155(string address) + { + return await GetAsync($"/read/erc1155/{ChainId}/{address}/balanceOf"); + } + + public async Task DeployERC1155(string json) + { + return await PostAsync($"/deploy/erc1155/{ChainId}", json); + } + #endregion + + #region Transactions + public async Task GetAllTransactions() + { + return await GetAsync("/transactions"); + } + + public async Task GetTransaction(string txHash) + { + return await GetAsync($"/transactions/{txHash}"); + } + #endregion + + #region Webhooks + public async Task AddWebhook(string json) + { + return await PostAsync("/webhook/add", json); + } + + public async Task RemoveWebhook(string json) + { + return await PostAsync("/webhook/remove", json); + } + + public async Task RemoveAllWebhooks() + { + return await PostAsync("/webhook/removeAll", ""); + } + + public async Task GetAllWebhooks() + { + return await GetAsync("/webhook/getAll"); + } + #endregion + + #region Jobs + public async Task StartERC20RewardsJob(string address, string json) + { + return await PostAsync($"/jobs/erc20/rewards/{ChainId}/{address}/start", json); + } + + public async Task StopERC20RewardsJob(string address, string json) + { + return await PostAsync($"/jobs/erc20/rewards/{ChainId}/{address}/stop", json); + } + + public async Task GetAllJobs() + { + return await GetAsync("/jobs"); + } + + public async Task GetJob(string jobId) + { + return await GetAsync($"/jobs/{jobId}"); + } + + public async Task CleanJobs(string json) + { + return await PostAsync("/jobs/clean", json); + } + #endregion + } +} diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/SequenceSidekickClient.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/SequenceSidekickClient.cs.meta new file mode 100644 index 00000000..d004ce6f --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/SequenceSidekickClient.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f4a9e5d7422bad042bf9aac61897a164 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/SidekickEditorController.cs b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/SidekickEditorController.cs new file mode 100644 index 00000000..eb97cb3e --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/SidekickEditorController.cs @@ -0,0 +1,588 @@ +using UnityEditor; +using UnityEngine; +using System.Text; +using Sequence.EmbeddedWallet; +using Sequence.Provider; + +namespace Sequence.Sidekick +{ + public class SidekickEditorController : MonoBehaviour + { + private SequenceSidekickClient sidekick = new SequenceSidekickClient(Chain.TestnetArbitrumSepolia); + + #region Sidekick Wallet + + public void GetWalletAddress() + { + EditorApplication.delayCall += async () => + { + try + { + string result = await sidekick.GetWalletAddress(); + Debug.Log(result); + } + catch (System.Exception ex) + { + Debug.LogError("Failed to recover sidekick wallet address: " + ex.Message); + } + }; + } + + #endregion + + #region Sidekick Contracts + + public void GetContracts() + { + EditorApplication.delayCall += async () => + { + try + { + string result = await sidekick.GetAllContracts(); + Debug.Log(result); + } + catch (System.Exception ex) + { + Debug.LogError("Failed to recover builder contracts: " + ex.Message); + } + }; + } + + #endregion + + #region Deploy + + #region Deploy ERC20 + + public string DeployERC20InitialOwner { get; set; } + public string DeployERC20Name { get; set; } + public string DeployERC20Symbol { get; set; } + + public async void DeployERC20() + { + + var payload = new DeployERC20Payload + { + initialOwner = DeployERC20InitialOwner, + name = DeployERC20Name, + symbol = DeployERC20Symbol + }; + + string jsonString = JsonUtility.ToJson(payload); + + Debug.Log("Deploying ERC20: " + jsonString); + + try + { + string result = await sidekick.DeployERC20(jsonString); + + DeployResult deployResult = JsonUtility.FromJson(result); + + if (!string.IsNullOrEmpty(deployResult.result.txHash)) + { + + var receipt = await new SequenceEthClient(sidekick.Chain).WaitForTransactionReceipt(deployResult.result.txHash); + + if (receipt != null) + { + string deployedAddress = DeployedeContractAddressExtractor.ExtractFirstContractAddressExceptOwn(receipt, DeployERC20InitialOwner); + if (!string.IsNullOrEmpty(deployedAddress)) + { + Debug.Log($"Deployed contract address: {deployedAddress}"); + } + else + { + Debug.LogWarning("Could not extract deployed contract address from receipt."); + } + } + else + { + Debug.LogError("Failed to get transaction receipt."); + } + } + else + { + Debug.LogError($"Deployment error: {deployResult.result.error}"); + } + } + catch (System.Exception ex) + { + Debug.LogError("Deploy ERC20 failed: " + ex.Message); + } + } + + #endregion + + #region Deploy ERC721 + public string DeployERC721DefaultAdmin { get; set; } + public string DeployERC721Minter { get; set; } + public string DeployERC721Name { get; set; } + public string DeployERC721Symbol { get; set; } + + public async void DeployERC721() + { + var payload = new DeployERC721Payload + { + defaultAdmin = DeployERC721DefaultAdmin, + minter = DeployERC721Minter, + name = DeployERC721Name, + symbol = DeployERC721Symbol + }; + + string jsonString = JsonUtility.ToJson(payload); + + Debug.Log("Deploying ERC721: " + jsonString); + + try + { + string result = await sidekick.DeployERC721(jsonString); + + DeployResult deployResult = JsonUtility.FromJson(result); + + if (!string.IsNullOrEmpty(deployResult.result.txHash)) + { + + var receipt = await new SequenceEthClient(sidekick.Chain).WaitForTransactionReceipt(deployResult.result.txHash); + + if (receipt != null) + { + string deployedAddress = DeployedeContractAddressExtractor.ExtractFirstContractAddressExceptOwn(receipt, DeployERC721DefaultAdmin); + if (!string.IsNullOrEmpty(deployedAddress)) + { + Debug.Log($"Deployed contract address: {deployedAddress}"); + } + else + { + Debug.LogWarning("Could not extract deployed contract address from receipt."); + } + } + else + { + Debug.LogError("Failed to get transaction receipt."); + } + } + else + { + Debug.LogError($"Deployment error: {deployResult.result.error}"); + } + } + catch (System.Exception ex) + { + Debug.LogError("Deploy ERC721 failed: " + ex.Message); + } + } + + #endregion + + #region Deploy ERC1155 + + public string DeployDefaultAdmin { get; set; } + public string DeployMinter { get; set; } + public string DeployName { get; set; } + + public async void DeployERC1155() + { + var deployPayload = new DeployERC1155Payload + { + defaultAdmin = DeployDefaultAdmin, + minter = DeployMinter, + name = DeployName + }; + + string jsonString = JsonUtility.ToJson(deployPayload); + + Debug.Log("Deploying ERC1155: " + jsonString); + + try + { + string result = await sidekick.DeployERC1155(jsonString); + + DeployResult deployResult = JsonUtility.FromJson(result); + + if (!string.IsNullOrEmpty(deployResult.result.txHash)) + { + var receipt = await new SequenceEthClient(sidekick.Chain).WaitForTransactionReceipt(deployResult.result.txHash); + + if (receipt != null) + { + string deployedAddress = DeployedeContractAddressExtractor.ExtractFirstContractAddressExceptOwn(receipt, DeployDefaultAdmin); + if (!string.IsNullOrEmpty(deployedAddress)) + { + Debug.Log($"Deployed contract address: {deployedAddress}"); + } + else + { + Debug.LogWarning("Could not extract deployed contract address from receipt."); + } + } + else + { + Debug.LogError("Failed to get transaction receipt."); + } + } + else + { + Debug.LogError($"Deployment error: {deployResult.result.error}"); + } + } + catch (System.Exception ex) + { + Debug.LogError("Deploy ERC1155 failed: " + ex.Message); + } + } + + #endregion + + #endregion + + #region Role + + public ContractRole SelectedContractRole { get; set; } + public string GrantRoleContractAddress { get; set; } + public string GrantRoleRole { get; set; } + public string GrantRoleAccount { get; set; } + + public void GrantERC1155Role() + { + var grantRolePayload = new GrantRolePayload + { + role = GrantRoleRole, + account = GrantRoleAccount + }; + + string jsonString = JsonUtility.ToJson(grantRolePayload); + + Debug.Log("Granting role ERC1155: " + jsonString); + + EditorApplication.delayCall += async () => + { + try + { + string result = await sidekick.GrantRoleERC1155(GrantRoleContractAddress, jsonString); + Debug.Log("Grant role result: " + result); + } + catch (System.Exception ex) + { + Debug.LogError("Grant ERC1155 role failed: " + ex.Message); + } + }; + } + + #endregion + + #region Mint + + #region MintERC20 + + public string MintERC20ContractAddress { get; set; } + public string MintERC20RecipientAddress { get; set; } + public string MintERC20Amount { get; set; } + + public void MintERC20() + { + var mintJson = new MintERC20Payload + { + to = MintERC20RecipientAddress, + amount = MintERC20Amount + }; + string jsonString = JsonUtility.ToJson(mintJson); + + Debug.Log("Minting ERC20: " + jsonString); + + EditorApplication.delayCall += async () => + { + try + { + string result = await sidekick.MintERC20(MintERC20ContractAddress, jsonString); + Debug.Log("Mint result: " + result); + } + catch (System.Exception ex) + { + Debug.LogError("Mint ERC20 failed: " + ex.Message); + } + }; + } + + #endregion + + + #region Mint ERC721 + + public bool IsBatchMintERC721 { get; set; } = false; + public string SafeMintERC721ContractAddress { get; set; } + + public string SafeMintERC721Recipient { get; set; } + public string SafeMintERC721TokenId { get; set; } + + public async void SafeMintERC721() + { + var payload = new SafeMintERC721Payload + { + to = SafeMintERC721Recipient, + tokenId = SafeMintERC721TokenId + }; + string jsonString = JsonUtility.ToJson(payload); + + Debug.Log("Calling safeMint ERC721: " + jsonString); + + try + { + string result = await sidekick.SafeMintERC721(SafeMintERC721ContractAddress, jsonString); + Debug.Log("safeMint result: " + result); + } + catch (System.Exception ex) + { + Debug.LogError("safeMint ERC721 failed: " + ex.Message); + } + } + + public string[] SafeMintERC721BatchRecipients { get; set; } + public string[] SafeMintERC721BatchTokenIds { get; set; } + + public async void SafeMintBatchERC721() + { + var payload = new SafeMintBatchERC721Payload + { + recipients = SafeMintERC721BatchRecipients, + tokenIds = SafeMintERC721BatchTokenIds + }; + string jsonString = JsonUtility.ToJson(payload); + + Debug.Log("Calling safeMintBatch ERC721: " + jsonString); + + try + { + string result = await sidekick.SafeMintBatchERC721(SafeMintERC721ContractAddress, jsonString); + Debug.Log("safeMintBatch result: " + result); + } + catch (System.Exception ex) + { + Debug.LogError("safeMintBatch ERC721 failed: " + ex.Message); + } + } + + #endregion + + #region Mint ERC1155 + + public bool IsBatchMintERC1155 { get; set; } = false; + + public string MintERC1155ContractAddress { get; set; } + + public string MintERC1155RecipientAddress { get; set; } + public string MintERC1155Id { get; set; } + public string MintERC1155Amount { get; set; } + public string MintERC1155Data { get; set; } + public void MintERC1155() + { + var mintJson = new MintERC1155Payload + { + recipient = MintERC1155RecipientAddress, + id = MintERC1155Id, + amount = MintERC1155Amount, + data = ToHexString(MintERC1155Data) + }; + + string jsonString = JsonUtility.ToJson(mintJson); + + Debug.Log("Minting ERC1155: " + jsonString); + + EditorApplication.delayCall += async () => + { + try + { + string result = await sidekick.MintERC1155(MintERC1155ContractAddress, jsonString); + Debug.Log("Mint result: " + result); + } + catch (System.Exception ex) + { + Debug.LogError("Mint ERC1155 failed: " + ex.Message); + } + }; + } + public string[] MintERC1155BatchRecipients { get; set; } = new string[0]; + public string[] MintERC1155BatchIds { get; set; } = new string[0]; + public string[] MintERC1155BatchAmounts { get; set; } = new string[0]; + public string[] MintERC1155BatchDatas { get; set; } = new string[0]; + + public void MintERC1155Batch() + { + string[] hexDatas = new string[MintERC1155BatchDatas.Length]; + + for (int i = 0; i < MintERC1155BatchDatas.Length; i++) + { + hexDatas[i] = ToHexString(MintERC1155BatchDatas[i]); + + } + + var mintJson = new MintERC1155BatchPayload + { + recipients = MintERC1155BatchRecipients, + ids = MintERC1155BatchIds, + amounts = MintERC1155BatchAmounts, + datas = hexDatas + }; + + string jsonString = JsonUtility.ToJson(mintJson); + Debug.Log("Minting ERC1155 Batch: " + jsonString); + + EditorApplication.delayCall += async () => + { + try + { + string result = await sidekick.MintBatchERC1155(MintERC1155ContractAddress, jsonString); + Debug.Log("Mint batch result: " + result); + } + catch (System.Exception ex) + { + Debug.LogError("Mint ERC1155 batch failed: " + ex.Message); + } + }; + } + + #endregion + + + #endregion + + + #region Create SO + + public string soContractAddress { get; set; } + public string soContractBurnAddress { get; set; } + public int soTokenDecimals { get; set; } + public bool soSpecifyBurnAddress { get; set; } + + #endregion + + public string ToHexString(string input) + { + byte[] bytes = Encoding.UTF8.GetBytes(input); + StringBuilder hex = new StringBuilder("0x"); + + foreach (byte b in bytes) + { + hex.Append(b.ToString("x2")); + } + + return hex.ToString(); + } + } + +} + +#region Payloads + +#region Deploy + +[System.Serializable] +public class DeployERC1155Payload +{ + public string defaultAdmin; + public string minter; + public string name; +} + +[System.Serializable] +public class DeployERC20Payload +{ + public string initialOwner; + public string name; + public string symbol; +} + +[System.Serializable] +public class DeployERC721Payload +{ + public string defaultAdmin; + public string minter; + public string name; + public string symbol; +} + +#endregion + +#region Mint + +[System.Serializable] +public class MintERC20Payload +{ + public string to; + public string amount; +} + + +[System.Serializable] +public class SafeMintERC721Payload +{ + public string to; + public string tokenId; +} + +[System.Serializable] +public class SafeMintBatchERC721Payload +{ + public string[] recipients; + public string[] tokenIds; +} +[System.Serializable] +public class MintERC1155Payload +{ + public string recipient; + public string id; + public string amount; + public string data; +} + +[System.Serializable] +public class MintERC1155BatchPayload +{ + public string[] recipients; + public string[] ids; + public string[] amounts; + public string[] datas; +} + + +#endregion + +#region Role + +[System.Serializable] +public class GrantRolePayload +{ + public string role; + public string account; +} + +#endregion + +#endregion + + +[System.Serializable] +public class DeployResult +{ + public ResultData result; + + [System.Serializable] + public class ResultData + { + public string txHash; + public string txUrl; + public string error; + } +} + +public enum ContractType +{ + CurrencyToken, + NFTContract, + Web3GameItem, +} + +public enum ContractRole +{ + Minter +} + diff --git a/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/SidekickEditorController.cs.meta b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/SidekickEditorController.cs.meta new file mode 100644 index 00000000..39cbf572 --- /dev/null +++ b/Packages/Sequence-Unity/Sequence/SequenceSDK/Sidekick/SidekickEditorController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 40b315f114ec9004d907e97fe436eb55 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: