From 7a406def755b79b1cf9fd807ac343303dc8350fe Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Fri, 17 Jan 2025 00:14:29 +0700 Subject: [PATCH 1/7] Nebula AI .NET Integration (Beta) TOOL-3098 --- Thirdweb.Console/Program.cs | 10 + Thirdweb/Thirdweb.AI/ChatClient.cs | 32 ++ Thirdweb/Thirdweb.AI/ExecutionClient.cs | 32 ++ Thirdweb/Thirdweb.AI/FeedbackClient.cs | 28 ++ Thirdweb/Thirdweb.AI/SessionManager.cs | 64 ++++ Thirdweb/Thirdweb.AI/ThirdwebNebula.Types.cs | 340 +++++++++++++++++++ Thirdweb/Thirdweb.AI/ThirdwebNebula.cs | 193 +++++++++++ Thirdweb/Thirdweb.Utils/Constants.cs | 3 + 8 files changed, 702 insertions(+) create mode 100644 Thirdweb/Thirdweb.AI/ChatClient.cs create mode 100644 Thirdweb/Thirdweb.AI/ExecutionClient.cs create mode 100644 Thirdweb/Thirdweb.AI/FeedbackClient.cs create mode 100644 Thirdweb/Thirdweb.AI/SessionManager.cs create mode 100644 Thirdweb/Thirdweb.AI/ThirdwebNebula.Types.cs create mode 100644 Thirdweb/Thirdweb.AI/ThirdwebNebula.cs diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index 73a83e6..4913f39 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Thirdweb; +using Thirdweb.AI; using Thirdweb.Pay; DotEnv.Load(); @@ -35,6 +36,15 @@ #endregion +#region AI + +var nebula = await ThirdwebNebula.Create(client); +var myWallet = await PrivateKeyWallet.Generate(client); +var response = await nebula.Chat(prompt: "How much ETH is in my wallet?", wallet: myWallet, context: new NebulaContext(chainIds: new List { 421614 })); +Console.WriteLine($"Response: {response.Message}"); + +#endregion + #region Get Social Profiles // var socialProfiles = await Utils.GetSocialProfiles(client, "joenrv.eth"); diff --git a/Thirdweb/Thirdweb.AI/ChatClient.cs b/Thirdweb/Thirdweb.AI/ChatClient.cs new file mode 100644 index 0000000..73c19ee --- /dev/null +++ b/Thirdweb/Thirdweb.AI/ChatClient.cs @@ -0,0 +1,32 @@ +using System.Text; +using Newtonsoft.Json; + +namespace Thirdweb.AI; + +internal class ChatClient +{ + private readonly IThirdwebHttpClient _httpClient; + + public ChatClient(IThirdwebHttpClient httpClient) + { + this._httpClient = httpClient; + } + + public async Task SendMessageAsync(ChatParamsSingleMessage message) + { + var content = new StringContent(JsonConvert.SerializeObject(message), Encoding.UTF8, "application/json"); + var response = await this._httpClient.PostAsync($"{Constants.NEBULA_API_URL}/chat", content); + _ = response.EnsureSuccessStatusCode(); + var responseContent = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(responseContent); + } + + public async Task SendMessagesAsync(ChatParamsMultiMessages messages) + { + var content = new StringContent(JsonConvert.SerializeObject(messages), Encoding.UTF8, "application/json"); + var response = await this._httpClient.PostAsync($"{Constants.NEBULA_API_URL}/chat", content); + _ = response.EnsureSuccessStatusCode(); + var responseContent = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(responseContent); + } +} diff --git a/Thirdweb/Thirdweb.AI/ExecutionClient.cs b/Thirdweb/Thirdweb.AI/ExecutionClient.cs new file mode 100644 index 0000000..831789f --- /dev/null +++ b/Thirdweb/Thirdweb.AI/ExecutionClient.cs @@ -0,0 +1,32 @@ +using System.Text; +using Newtonsoft.Json; + +namespace Thirdweb.AI; + +internal class ExecutionClient +{ + private readonly IThirdwebHttpClient _httpClient; + + public ExecutionClient(IThirdwebHttpClient httpClient) + { + this._httpClient = httpClient; + } + + public async Task ExecuteAsync(ChatParamsSingleMessage command) + { + var content = new StringContent(JsonConvert.SerializeObject(command), Encoding.UTF8, "application/json"); + var response = await this._httpClient.PostAsync($"{Constants.NEBULA_API_URL}/execute", content); + _ = response.EnsureSuccessStatusCode(); + var responseContent = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(responseContent); + } + + public async Task ExecuteBatchAsync(ChatParamsMultiMessages commands) + { + var content = new StringContent(JsonConvert.SerializeObject(commands), Encoding.UTF8, "application/json"); + var response = await this._httpClient.PostAsync($"{Constants.NEBULA_API_URL}/execute", content); + _ = response.EnsureSuccessStatusCode(); + var responseContent = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(responseContent); + } +} diff --git a/Thirdweb/Thirdweb.AI/FeedbackClient.cs b/Thirdweb/Thirdweb.AI/FeedbackClient.cs new file mode 100644 index 0000000..2a9e4cc --- /dev/null +++ b/Thirdweb/Thirdweb.AI/FeedbackClient.cs @@ -0,0 +1,28 @@ +using System.Text; +using Newtonsoft.Json; + +namespace Thirdweb.AI; + +internal class FeedbackClient +{ + private readonly IThirdwebHttpClient _httpClient; + + public FeedbackClient(IThirdwebHttpClient httpClient) + { + this._httpClient = httpClient; + } + + /// + /// Submits feedback for a specific session and request. + /// + /// The feedback parameters to submit. + /// The submitted feedback details. + public async Task SubmitFeedbackAsync(FeedbackParams feedback) + { + var content = new StringContent(JsonConvert.SerializeObject(feedback), Encoding.UTF8, "application/json"); + var response = await this._httpClient.PostAsync($"{Constants.NEBULA_API_URL}/feedback", content); + _ = response.EnsureSuccessStatusCode(); + var responseContent = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject>(responseContent).Result; + } +} diff --git a/Thirdweb/Thirdweb.AI/SessionManager.cs b/Thirdweb/Thirdweb.AI/SessionManager.cs new file mode 100644 index 0000000..b608bb6 --- /dev/null +++ b/Thirdweb/Thirdweb.AI/SessionManager.cs @@ -0,0 +1,64 @@ +using System.Text; +using Newtonsoft.Json; + +namespace Thirdweb.AI; + +internal class SessionManager +{ + private readonly IThirdwebHttpClient _httpClient; + + public SessionManager(IThirdwebHttpClient httpClient) + { + this._httpClient = httpClient; + } + + public async Task> ListSessionsAsync() + { + var response = await this._httpClient.GetAsync($"{Constants.NEBULA_API_URL}/session/list"); + _ = response.EnsureSuccessStatusCode(); + var content = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject>>(content).Result; + } + + public async Task GetSessionAsync(string sessionId) + { + var response = await this._httpClient.GetAsync($"{Constants.NEBULA_API_URL}/session/{sessionId}"); + _ = response.EnsureSuccessStatusCode(); + var content = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject>(content).Result; + } + + public async Task CreateSessionAsync(CreateSessionParams parameters) + { + var content = new StringContent(JsonConvert.SerializeObject(parameters), Encoding.UTF8, "application/json"); + var response = await this._httpClient.PostAsync($"{Constants.NEBULA_API_URL}/session", content); + _ = response.EnsureSuccessStatusCode(); + var responseContent = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject>(responseContent).Result; + } + + public async Task UpdateSessionAsync(string sessionId, UpdateSessionParams parameters) + { + var content = new StringContent(JsonConvert.SerializeObject(parameters), Encoding.UTF8, "application/json"); + var response = await this._httpClient.PutAsync($"{Constants.NEBULA_API_URL}/session/{sessionId}", content); + _ = response.EnsureSuccessStatusCode(); + var responseContent = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject>(responseContent).Result; + } + + public async Task DeleteSessionAsync(string sessionId) + { + var response = await this._httpClient.DeleteAsync($"{Constants.NEBULA_API_URL}/session/{sessionId}"); + _ = response.EnsureSuccessStatusCode(); + var content = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject>(content).Result; + } + + public async Task ClearSessionAsync(string sessionId) + { + var response = await this._httpClient.PostAsync($"{Constants.NEBULA_API_URL}/session/{sessionId}/clear", null); + _ = response.EnsureSuccessStatusCode(); + var content = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject>(content).Result; + } +} diff --git a/Thirdweb/Thirdweb.AI/ThirdwebNebula.Types.cs b/Thirdweb/Thirdweb.AI/ThirdwebNebula.Types.cs new file mode 100644 index 0000000..fea7937 --- /dev/null +++ b/Thirdweb/Thirdweb.AI/ThirdwebNebula.Types.cs @@ -0,0 +1,340 @@ +using Newtonsoft.Json; + +namespace Thirdweb.AI; + +/// +/// Represents the response model wrapping the result of an API call. +/// +/// The type of the result. +internal class ResponseModel +{ + /// + /// The result returned by the API. + /// + [JsonProperty("result")] + internal T Result { get; set; } +} + +/// +/// Represents an action performed by an agent in a session. +/// +internal class AgentAction +{ + [JsonProperty("session_id")] + internal string SessionId { get; set; } + + [JsonProperty("request_id")] + internal string RequestId { get; set; } + + [JsonProperty("type")] + internal string Type { get; set; } + + [JsonProperty("source")] + internal string Source { get; set; } + + [JsonProperty("data")] + internal string Data { get; set; } +} + +/// +/// Represents a single chat message. +/// +internal class ChatMessage +{ + [JsonProperty("role")] + internal string Role { get; set; } + + [JsonProperty("content")] + internal string Content { get; set; } +} + +/// +/// Represents parameters for sending multiple chat messages in a single request. +/// +internal class ChatParamsMultiMessages +{ + [JsonProperty("stream")] + internal bool? Stream { get; set; } = false; + + [JsonProperty("session_id")] + internal string SessionId { get; set; } + + [JsonProperty("config")] + internal ExecuteConfig Config { get; set; } + + [JsonProperty("execute_config")] + internal ExecuteConfig ExecuteConfig { get; set; } + + [JsonProperty("context_filter")] + internal ContextFilter ContextFilter { get; set; } + + [JsonProperty("model_name")] + internal string ModelName { get; set; } + + [JsonProperty("messages")] + internal List Messages { get; set; } +} + +/// +/// Represents parameters for sending a single chat message. +/// +internal class ChatParamsSingleMessage +{ + [JsonProperty("stream")] + internal bool? Stream { get; set; } = false; + + [JsonProperty("session_id")] + internal string SessionId { get; set; } + + [JsonProperty("config")] + internal ExecuteConfig Config { get; set; } + + [JsonProperty("execute_config")] + internal ExecuteConfig ExecuteConfig { get; set; } + + [JsonProperty("context_filter")] + internal ContextFilter ContextFilter { get; set; } + + [JsonProperty("model_name")] + internal string ModelName { get; set; } + + [JsonProperty("message")] + internal string Message { get; set; } +} + +/// +/// Represents the response from a chat interaction. +/// +public class ChatResponse +{ + [JsonProperty("message")] + internal string Message { get; set; } + + [JsonProperty("actions")] + internal List Actions { get; set; } + + [JsonProperty("session_id")] + internal string SessionId { get; set; } + + [JsonProperty("request_id")] + internal string RequestId { get; set; } +} + +/// +/// Represents filters for narrowing down context in which operations are performed. +/// +internal class ContextFilter +{ + [JsonProperty("chain_ids")] + internal List ChainIds { get; set; } + + [JsonProperty("contract_addresses")] + internal List ContractAddresses { get; set; } + + [JsonProperty("wallet_addresses")] + internal List WalletAddresses { get; set; } +} + +/// +/// Represents parameters for creating a new session. +/// +internal class CreateSessionParams +{ + [JsonProperty("model_name")] + internal string ModelName { get; set; } = "t0-001"; + + [JsonProperty("title")] + internal string Title { get; set; } + + [JsonProperty("is_public")] + internal bool? IsPublic { get; set; } + + [JsonProperty("execute_config")] + internal ExecuteConfig ExecuteConfig { get; set; } + + [JsonProperty("context_filter")] + internal ContextFilter ContextFilter { get; set; } +} + +/// +/// Represents execution configuration options. +/// +internal class ExecuteConfig +{ + [JsonProperty("mode")] + internal string Mode { get; set; } = "client"; + + [JsonProperty("signer_wallet_address")] + internal string SignerWalletAddress { get; set; } + + [JsonProperty("engine_url")] + internal string EngineUrl { get; set; } + + [JsonProperty("engine_authorization_token")] + internal string EngineAuthorizationToken { get; set; } + + [JsonProperty("engine_backend_wallet_address")] + internal string EngineBackendWalletAddress { get; set; } + + [JsonProperty("smart_account_address")] + internal string SmartAccountAddress { get; set; } + + [JsonProperty("smart_account_factory_address")] + internal string SmartAccountFactoryAddress { get; set; } + + [JsonProperty("smart_account_session_key")] + internal string SmartAccountSessionKey { get; set; } +} + +/// +/// Represents a feedback submission. +/// +internal class Feedback +{ + [JsonProperty("id")] + internal string Id { get; set; } + + [JsonProperty("account_id")] + internal string AccountId { get; set; } + + [JsonProperty("session_id")] + internal string SessionId { get; set; } + + [JsonProperty("request_id")] + internal string RequestId { get; set; } + + [JsonProperty("feedback_rating")] + internal int? FeedbackRating { get; set; } + + [JsonProperty("feedback_response")] + internal string FeedbackResponse { get; set; } + + [JsonProperty("comment")] + internal string Comment { get; set; } + + [JsonProperty("created_at")] + internal DateTime? CreatedAt { get; set; } + + [JsonProperty("updated_at")] + internal DateTime? UpdatedAt { get; set; } +} + +/// +/// Parameters for submitting feedback. +/// +internal class FeedbackParams +{ + [JsonProperty("session_id")] + internal string SessionId { get; set; } + + [JsonProperty("request_id")] + internal string RequestId { get; set; } + + [JsonProperty("feedback_rating")] + internal int? FeedbackRating { get; set; } + + [JsonProperty("feedback_response")] + internal string FeedbackResponse { get; set; } + + [JsonProperty("comment")] + internal string Comment { get; set; } +} + +/// +/// Represents session details. +/// +internal class Session +{ + [JsonProperty("id")] + internal string Id { get; set; } + + [JsonProperty("account_id")] + internal string AccountId { get; set; } + + [JsonProperty("model_name")] + internal string ModelName { get; set; } + + [JsonProperty("is_public")] + internal bool? IsPublic { get; set; } + + [JsonProperty("execute_config")] + internal ExecuteConfig ExecuteConfig { get; set; } + + [JsonProperty("title")] + internal string Title { get; set; } + + [JsonProperty("memory")] + internal List Memory { get; set; } + + [JsonProperty("history")] + internal List History { get; set; } + + [JsonProperty("action")] + internal List Action { get; set; } + + [JsonProperty("context_filter")] + internal ContextFilter ContextFilter { get; set; } + + [JsonProperty("archive_at")] + internal DateTime? ArchiveAt { get; set; } + + [JsonProperty("deleted_at")] + internal DateTime? DeletedAt { get; set; } + + [JsonProperty("created_at")] + internal DateTime? CreatedAt { get; set; } + + [JsonProperty("updated_at")] + internal DateTime? UpdatedAt { get; set; } +} + +/// +/// Represents parameters for updating a session. +/// +internal class UpdateSessionParams +{ + [JsonProperty("title")] + internal string Title { get; set; } + + [JsonProperty("model_name")] + internal string ModelName { get; set; } + + [JsonProperty("is_public")] + internal bool? IsPublic { get; set; } + + [JsonProperty("execute_config")] + internal ExecuteConfig ExecuteConfig { get; set; } + + [JsonProperty("context_filter")] + internal ContextFilter ContextFilter { get; set; } +} + +/// +/// Represents the response for deleting a session. +/// +internal class SessionDeleteResponse +{ + [JsonProperty("id")] + internal string Id { get; set; } + + [JsonProperty("deleted_at")] + internal DateTime DeletedAt { get; set; } +} + +/// +/// Represents a session in a session list. +/// +internal class SessionList +{ + [JsonProperty("id")] + internal string Id { get; set; } + + [JsonProperty("title")] + internal string Title { get; set; } + + [JsonProperty("created_at")] + internal DateTime CreatedAt { get; set; } + + [JsonProperty("updated_at")] + internal DateTime UpdatedAt { get; set; } +} diff --git a/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs b/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs new file mode 100644 index 0000000..9d3fe0c --- /dev/null +++ b/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs @@ -0,0 +1,193 @@ +using System.Numerics; +using Newtonsoft.Json; + +namespace Thirdweb.AI; + +public class NebulaChatResult +{ + public string Message { get; set; } + public List Transactions { get; set; } +} + +public class NebulaExecuteResult +{ + public ThirdwebTransactionReceipt TransactionReceipt { get; set; } +} + +public class NebulaContext +{ + public List ChainIds { get; set; } + public List ContractAddresses { get; set; } + public List WalletAddresses { get; set; } + + /// + /// Represents filters for narrowing down context in which operations are performed. + /// + /// The chain IDs to filter by. + /// The contract addresses to filter by. + /// The wallet addresses to filter by. + public NebulaContext(List chainIds = null, List contractAddresses = null, List walletAddresses = null) + { + this.ChainIds = chainIds; + this.ContractAddresses = contractAddresses; + this.WalletAddresses = walletAddresses; + } +} + +public class ThirdwebNebula +{ + public string SessionId { get; private set; } + + internal SessionManager Sessions { get; } + internal ChatClient ChatClient { get; } + internal ExecutionClient ExecuteClient { get; } + internal FeedbackClient FeedbackClient { get; } + + internal ThirdwebNebula(ThirdwebClient client) + { + var httpClient = client.HttpClient; + this.Sessions = new SessionManager(httpClient); + this.ChatClient = new ChatClient(httpClient); + this.ExecuteClient = new ExecutionClient(httpClient); + this.FeedbackClient = new FeedbackClient(httpClient); + } + + public static async Task Create(ThirdwebClient client, string model = Constants.NEBULA_DEFAULT_MODEL) + { + var nebula = new ThirdwebNebula(client); + var session = await nebula.Sessions.CreateSessionAsync( + new CreateSessionParams() + { + ModelName = model, + Title = $"Thirdweb .NET SDK (v{Constants.VERSION}) | Nebula {model} Session | Client ID: {client.ClientId}", + IsPublic = false + } + ); + nebula.SessionId = session.Id; + return nebula; + } + + public async Task Chat(string prompt, IThirdwebWallet wallet = null, NebulaContext context = null) + { + if (string.IsNullOrWhiteSpace(prompt)) + { + throw new ArgumentException("Prompt cannot be null or empty.", nameof(prompt)); + } + + var contextFiler = await PrepareContextFilter(wallet, context); + + var result = await this.ChatClient.SendMessageAsync( + new ChatParamsSingleMessage() + { + SessionId = this.SessionId, + Message = prompt, + ContextFilter = contextFiler, + Config = wallet == null ? null : new ExecuteConfig() { Mode = "client", SignerWalletAddress = await wallet.GetAddress() } + } + ); + + var transactions = await PrepareTransactions(wallet, result.Actions); + + return new NebulaChatResult() { Message = result.Message, Transactions = transactions == null || transactions.Count == 0 ? null : transactions }; + } + + public async Task Execute(string prompt, IThirdwebWallet wallet = null, NebulaContext context = null) + { + if (string.IsNullOrWhiteSpace(prompt)) + { + throw new ArgumentException("Prompt cannot be null or empty.", nameof(prompt)); + } + + var contextFiler = await PrepareContextFilter(wallet, context); + + var result = await this.ExecuteClient.ExecuteAsync( + new ChatParamsSingleMessage() + { + SessionId = this.SessionId, + Message = prompt, + ContextFilter = contextFiler, + ExecuteConfig = wallet == null ? null : new ExecuteConfig() { Mode = "client", SignerWalletAddress = await wallet.GetAddress() } + } + ); + + Console.WriteLine(JsonConvert.SerializeObject(result)); + // TODO + throw new NotImplementedException(); + } + + private static async Task PrepareContextFilter(IThirdwebWallet wallet, NebulaContext context) + { + context ??= new NebulaContext(); + + if (wallet != null) + { + var walletAddress = await wallet.GetAddress(); + + // Add the wallet address to the context + if (context.WalletAddresses == null || context.WalletAddresses.Count == 0) + { + context.WalletAddresses = new List() { walletAddress }; + } + else if (!context.WalletAddresses.Contains(walletAddress)) + { + context.WalletAddresses.Add(walletAddress); + } + + // If it's a smart wallet, add the contract address and chain ID to the context + if (wallet is SmartWallet smartWallet) + { + if (context.ContractAddresses == null || context.ContractAddresses.Count == 0) + { + context.ContractAddresses = new List() { walletAddress }; + } + else if (!context.ContractAddresses.Contains(walletAddress)) + { + context.ContractAddresses.Add(walletAddress); + } + + if (context.ChainIds == null || context.ChainIds.Count == 0) + { + context.ChainIds = new List() { smartWallet.ActiveChainId }; + } + else if (!context.ChainIds.Contains(smartWallet.ActiveChainId)) + { + context.ChainIds.Add(smartWallet.ActiveChainId); + } + } + } + + return new ContextFilter() + { + ChainIds = context?.ChainIds?.Select(id => id.ToString()).ToList(), + ContractAddresses = context?.ContractAddresses, + WalletAddresses = context?.WalletAddresses + }; + } + + private static async Task> PrepareTransactions(IThirdwebWallet wallet, List actions) + { + if (wallet != null && actions != null && actions.Count > 0) + { + var transactionTasks = actions + .Select(action => + { + if (action.Type == "transaction") + { + var txInput = JsonConvert.DeserializeObject(action.Data.ToString()); + return ThirdwebTransaction.Create(wallet, txInput); + } + else + { + return null; + } + }) + .ToList(); + + return (await Task.WhenAll(transactionTasks)).Where(tx => tx != null).ToList(); + } + else + { + return null; + } + } +} diff --git a/Thirdweb/Thirdweb.Utils/Constants.cs b/Thirdweb/Thirdweb.Utils/Constants.cs index 5cc4fb1..b3325ee 100644 --- a/Thirdweb/Thirdweb.Utils/Constants.cs +++ b/Thirdweb/Thirdweb.Utils/Constants.cs @@ -36,6 +36,9 @@ public static class Constants internal const string ENS_REGISTRY_ADDRESS = "0xce01f8eee7E479C928F8919abD53E553a36CeF67"; internal const string SOCIAL_API_URL = "https://social.thirdweb.com"; + internal const string NEBULA_API_URL = "https://nebula-api.thirdweb.com"; + internal const string NEBULA_DEFAULT_MODEL = "t0-001"; + internal const string ENTRYPOINT_V06_ABI = /*lang=json,strict*/ "[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"preOpGas\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"paid\",\"type\":\"uint256\"},{\"internalType\":\"uint48\",\"name\":\"validAfter\",\"type\":\"uint48\"},{\"internalType\":\"uint48\",\"name\":\"validUntil\",\"type\":\"uint48\"},{\"internalType\":\"bool\",\"name\":\"targetSuccess\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"targetResult\",\"type\":\"bytes\"}],\"name\":\"ExecutionResult\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"opIndex\",\"type\":\"uint256\"},{\"internalType\":\"string\",\"name\":\"reason\",\"type\":\"string\"}],\"name\":\"FailedOp\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"}],\"name\":\"SenderAddressResult\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"aggregator\",\"type\":\"address\"}],\"name\":\"SignatureValidationFailed\",\"type\":\"error\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"preOpGas\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"prefund\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"sigFailed\",\"type\":\"bool\"},{\"internalType\":\"uint48\",\"name\":\"validAfter\",\"type\":\"uint48\"},{\"internalType\":\"uint48\",\"name\":\"validUntil\",\"type\":\"uint48\"},{\"internalType\":\"bytes\",\"name\":\"paymasterContext\",\"type\":\"bytes\"}],\"internalType\":\"struct IEntryPoint.ReturnInfo\",\"name\":\"returnInfo\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"stake\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"unstakeDelaySec\",\"type\":\"uint256\"}],\"internalType\":\"struct IStakeManager.StakeInfo\",\"name\":\"senderInfo\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"stake\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"unstakeDelaySec\",\"type\":\"uint256\"}],\"internalType\":\"struct IStakeManager.StakeInfo\",\"name\":\"factoryInfo\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"stake\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"unstakeDelaySec\",\"type\":\"uint256\"}],\"internalType\":\"struct IStakeManager.StakeInfo\",\"name\":\"paymasterInfo\",\"type\":\"tuple\"}],\"name\":\"ValidationResult\",\"type\":\"error\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"preOpGas\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"prefund\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"sigFailed\",\"type\":\"bool\"},{\"internalType\":\"uint48\",\"name\":\"validAfter\",\"type\":\"uint48\"},{\"internalType\":\"uint48\",\"name\":\"validUntil\",\"type\":\"uint48\"},{\"internalType\":\"bytes\",\"name\":\"paymasterContext\",\"type\":\"bytes\"}],\"internalType\":\"struct IEntryPoint.ReturnInfo\",\"name\":\"returnInfo\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"stake\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"unstakeDelaySec\",\"type\":\"uint256\"}],\"internalType\":\"struct IStakeManager.StakeInfo\",\"name\":\"senderInfo\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"stake\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"unstakeDelaySec\",\"type\":\"uint256\"}],\"internalType\":\"struct IStakeManager.StakeInfo\",\"name\":\"factoryInfo\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"stake\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"unstakeDelaySec\",\"type\":\"uint256\"}],\"internalType\":\"struct IStakeManager.StakeInfo\",\"name\":\"paymasterInfo\",\"type\":\"tuple\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"aggregator\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"stake\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"unstakeDelaySec\",\"type\":\"uint256\"}],\"internalType\":\"struct IStakeManager.StakeInfo\",\"name\":\"stakeInfo\",\"type\":\"tuple\"}],\"internalType\":\"struct IEntryPoint.AggregatorStakeInfo\",\"name\":\"aggregatorInfo\",\"type\":\"tuple\"}],\"name\":\"ValidationResultWithAggregation\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"userOpHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"factory\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"paymaster\",\"type\":\"address\"}],\"name\":\"AccountDeployed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[],\"name\":\"BeforeExecution\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"totalDeposit\",\"type\":\"uint256\"}],\"name\":\"Deposited\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"aggregator\",\"type\":\"address\"}],\"name\":\"SignatureAggregatorChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"totalStaked\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"unstakeDelaySec\",\"type\":\"uint256\"}],\"name\":\"StakeLocked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"withdrawTime\",\"type\":\"uint256\"}],\"name\":\"StakeUnlocked\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"withdrawAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"StakeWithdrawn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"userOpHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"paymaster\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bool\",\"name\":\"success\",\"type\":\"bool\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"actualGasCost\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"actualGasUsed\",\"type\":\"uint256\"}],\"name\":\"UserOperationEvent\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"userOpHash\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"revertReason\",\"type\":\"bytes\"}],\"name\":\"UserOperationRevertReason\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"withdrawAddress\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"Withdrawn\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"SIG_VALIDATION_FAILED\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"initCode\",\"type\":\"bytes\"},{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"paymasterAndData\",\"type\":\"bytes\"}],\"name\":\"_validateSenderAndPaymaster\",\"outputs\":[],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"unstakeDelaySec\",\"type\":\"uint32\"}],\"name\":\"addStake\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"depositTo\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"deposits\",\"outputs\":[{\"internalType\":\"uint112\",\"name\":\"deposit\",\"type\":\"uint112\"},{\"internalType\":\"bool\",\"name\":\"staked\",\"type\":\"bool\"},{\"internalType\":\"uint112\",\"name\":\"stake\",\"type\":\"uint112\"},{\"internalType\":\"uint32\",\"name\":\"unstakeDelaySec\",\"type\":\"uint32\"},{\"internalType\":\"uint48\",\"name\":\"withdrawTime\",\"type\":\"uint48\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"getDepositInfo\",\"outputs\":[{\"components\":[{\"internalType\":\"uint112\",\"name\":\"deposit\",\"type\":\"uint112\"},{\"internalType\":\"bool\",\"name\":\"staked\",\"type\":\"bool\"},{\"internalType\":\"uint112\",\"name\":\"stake\",\"type\":\"uint112\"},{\"internalType\":\"uint32\",\"name\":\"unstakeDelaySec\",\"type\":\"uint32\"},{\"internalType\":\"uint48\",\"name\":\"withdrawTime\",\"type\":\"uint48\"}],\"internalType\":\"struct IStakeManager.DepositInfo\",\"name\":\"info\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint192\",\"name\":\"key\",\"type\":\"uint192\"}],\"name\":\"getNonce\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"initCode\",\"type\":\"bytes\"}],\"name\":\"getSenderAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"initCode\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"callGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"verificationGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"preVerificationGas\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxFeePerGas\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxPriorityFeePerGas\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"paymasterAndData\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"internalType\":\"struct UserOperation\",\"name\":\"userOp\",\"type\":\"tuple\"}],\"name\":\"getUserOpHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"initCode\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"callGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"verificationGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"preVerificationGas\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxFeePerGas\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxPriorityFeePerGas\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"paymasterAndData\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"internalType\":\"struct UserOperation[]\",\"name\":\"userOps\",\"type\":\"tuple[]\"},{\"internalType\":\"contract IAggregator\",\"name\":\"aggregator\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"internalType\":\"struct IEntryPoint.UserOpsPerAggregator[]\",\"name\":\"opsPerAggregator\",\"type\":\"tuple[]\"},{\"internalType\":\"address payable\",\"name\":\"beneficiary\",\"type\":\"address\"}],\"name\":\"handleAggregatedOps\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"initCode\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"callGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"verificationGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"preVerificationGas\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxFeePerGas\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxPriorityFeePerGas\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"paymasterAndData\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"internalType\":\"struct UserOperation[]\",\"name\":\"ops\",\"type\":\"tuple[]\"},{\"internalType\":\"address payable\",\"name\":\"beneficiary\",\"type\":\"address\"}],\"name\":\"handleOps\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint192\",\"name\":\"key\",\"type\":\"uint192\"}],\"name\":\"incrementNonce\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"},{\"components\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"callGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"verificationGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"preVerificationGas\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"paymaster\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"maxFeePerGas\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxPriorityFeePerGas\",\"type\":\"uint256\"}],\"internalType\":\"struct EntryPoint.MemoryUserOp\",\"name\":\"mUserOp\",\"type\":\"tuple\"},{\"internalType\":\"bytes32\",\"name\":\"userOpHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"prefund\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"contextOffset\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"preOpGas\",\"type\":\"uint256\"}],\"internalType\":\"struct EntryPoint.UserOpInfo\",\"name\":\"opInfo\",\"type\":\"tuple\"},{\"internalType\":\"bytes\",\"name\":\"context\",\"type\":\"bytes\"}],\"name\":\"innerHandleOp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"actualGasCost\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint192\",\"name\":\"\",\"type\":\"uint192\"}],\"name\":\"nonceSequenceNumber\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"initCode\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"callGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"verificationGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"preVerificationGas\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxFeePerGas\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxPriorityFeePerGas\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"paymasterAndData\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"internalType\":\"struct UserOperation\",\"name\":\"op\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"targetCallData\",\"type\":\"bytes\"}],\"name\":\"simulateHandleOp\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"nonce\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"initCode\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"callData\",\"type\":\"bytes\"},{\"internalType\":\"uint256\",\"name\":\"callGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"verificationGasLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"preVerificationGas\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxFeePerGas\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"maxPriorityFeePerGas\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"paymasterAndData\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"internalType\":\"struct UserOperation\",\"name\":\"userOp\",\"type\":\"tuple\"}],\"name\":\"simulateValidation\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unlockStake\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"withdrawAddress\",\"type\":\"address\"}],\"name\":\"withdrawStake\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"withdrawAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"withdrawAmount\",\"type\":\"uint256\"}],\"name\":\"withdrawTo\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]"; From 3e199ac3531f514777a537f7fab10601c8f0c267 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Fri, 17 Jan 2025 19:54:13 +0700 Subject: [PATCH 2/7] multi chat and execute --- Thirdweb.Console/Program.cs | 22 ++++++- Thirdweb/Thirdweb.AI/ThirdwebNebula.Types.cs | 4 +- Thirdweb/Thirdweb.AI/ThirdwebNebula.cs | 62 ++++++++++++++++---- 3 files changed, 70 insertions(+), 18 deletions(-) diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index 4913f39..94cccff 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -38,10 +38,26 @@ #region AI +var myChain = 11155111; +var myWallet = await SmartWallet.Create(personalWallet: await PrivateKeyWallet.Generate(client), chainId: myChain, gasless: true); + var nebula = await ThirdwebNebula.Create(client); -var myWallet = await PrivateKeyWallet.Generate(client); -var response = await nebula.Chat(prompt: "How much ETH is in my wallet?", wallet: myWallet, context: new NebulaContext(chainIds: new List { 421614 })); -Console.WriteLine($"Response: {response.Message}"); + +// var response = await nebula.Chat(message: "What is my wallet address?", wallet: myWallet); +// Console.WriteLine($"Single Response: {response.Message}"); + +// var responses = await nebula.Chat( +// messages: new List { "What's the symbol of this contract?", "How much ETH does it have?" }, +// context: new NebulaContext(contractAddresses: new List { "0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8" }, chainIds: new List { myChain }) +// ); +// Console.WriteLine($"Multiple Responses: {responses.Message}"); + +// var receipt = await nebula.Execute( +// "Send a transaction with 0x data on sepolia to vitalik.eth using my wallet.", +// wallet: myWallet, +// context: new NebulaContext(chainIds: new List { myChain }) +// ); +// Console.WriteLine($"Receipt: {JsonConvert.SerializeObject(receipt, Formatting.Indented)}"); #endregion diff --git a/Thirdweb/Thirdweb.AI/ThirdwebNebula.Types.cs b/Thirdweb/Thirdweb.AI/ThirdwebNebula.Types.cs index fea7937..719a705 100644 --- a/Thirdweb/Thirdweb.AI/ThirdwebNebula.Types.cs +++ b/Thirdweb/Thirdweb.AI/ThirdwebNebula.Types.cs @@ -42,7 +42,7 @@ internal class AgentAction internal class ChatMessage { [JsonProperty("role")] - internal string Role { get; set; } + internal string Role { get; set; } = "user"; [JsonProperty("content")] internal string Content { get; set; } @@ -141,7 +141,7 @@ internal class ContextFilter internal class CreateSessionParams { [JsonProperty("model_name")] - internal string ModelName { get; set; } = "t0-001"; + internal string ModelName { get; set; } = Constants.NEBULA_DEFAULT_MODEL; [JsonProperty("title")] internal string Title { get; set; } diff --git a/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs b/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs index 9d3fe0c..b633d3d 100644 --- a/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs +++ b/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs @@ -11,7 +11,8 @@ public class NebulaChatResult public class NebulaExecuteResult { - public ThirdwebTransactionReceipt TransactionReceipt { get; set; } + public string Message { get; set; } + public List TransactionReceipts { get; set; } } public class NebulaContext @@ -67,11 +68,11 @@ public static async Task Create(ThirdwebClient client, string mo return nebula; } - public async Task Chat(string prompt, IThirdwebWallet wallet = null, NebulaContext context = null) + public async Task Chat(string message, IThirdwebWallet wallet = null, NebulaContext context = null) { - if (string.IsNullOrWhiteSpace(prompt)) + if (string.IsNullOrWhiteSpace(message)) { - throw new ArgumentException("Prompt cannot be null or empty.", nameof(prompt)); + throw new ArgumentException("Message cannot be null or empty.", nameof(message)); } var contextFiler = await PrepareContextFilter(wallet, context); @@ -80,7 +81,7 @@ public async Task Chat(string prompt, IThirdwebWallet wallet = new ChatParamsSingleMessage() { SessionId = this.SessionId, - Message = prompt, + Message = message, ContextFilter = contextFiler, Config = wallet == null ? null : new ExecuteConfig() { Mode = "client", SignerWalletAddress = await wallet.GetAddress() } } @@ -91,28 +92,63 @@ public async Task Chat(string prompt, IThirdwebWallet wallet = return new NebulaChatResult() { Message = result.Message, Transactions = transactions == null || transactions.Count == 0 ? null : transactions }; } - public async Task Execute(string prompt, IThirdwebWallet wallet = null, NebulaContext context = null) + public async Task Chat(List messages, IThirdwebWallet wallet = null, NebulaContext context = null) { - if (string.IsNullOrWhiteSpace(prompt)) + if (messages == null || messages.Count == 0 || messages.Any(string.IsNullOrWhiteSpace)) { - throw new ArgumentException("Prompt cannot be null or empty.", nameof(prompt)); + throw new ArgumentException("Messages cannot be null or empty.", nameof(messages)); } var contextFiler = await PrepareContextFilter(wallet, context); + var result = await this.ChatClient.SendMessagesAsync( + new ChatParamsMultiMessages() + { + SessionId = this.SessionId, + Messages = messages.Select(prompt => new ChatMessage() { Role = "user", Content = prompt }).ToList(), + ContextFilter = contextFiler, + Config = wallet == null ? null : new ExecuteConfig() { Mode = "client", SignerWalletAddress = await wallet.GetAddress() } + } + ); + + var transactions = await PrepareTransactions(wallet, result.Actions); + + return new NebulaChatResult() { Message = result.Message, Transactions = transactions == null || transactions.Count == 0 ? null : transactions }; + } + + public async Task Execute(string message, IThirdwebWallet wallet, NebulaContext context = null) + { + if (string.IsNullOrWhiteSpace(message)) + { + throw new ArgumentException("Message cannot be null or empty.", nameof(message)); + } + + if (wallet == null) + { + throw new ArgumentException("Wallet cannot be null.", nameof(wallet)); + } + + var contextFiler = await PrepareContextFilter(wallet, context); var result = await this.ExecuteClient.ExecuteAsync( new ChatParamsSingleMessage() { SessionId = this.SessionId, - Message = prompt, + Message = message, ContextFilter = contextFiler, - ExecuteConfig = wallet == null ? null : new ExecuteConfig() { Mode = "client", SignerWalletAddress = await wallet.GetAddress() } + ExecuteConfig = new ExecuteConfig() { Mode = "client", SignerWalletAddress = await wallet.GetAddress() } } ); - Console.WriteLine(JsonConvert.SerializeObject(result)); - // TODO - throw new NotImplementedException(); + var transactions = await PrepareTransactions(wallet, result.Actions); + if (transactions == null || transactions.Count == 0) + { + return new NebulaExecuteResult() { Message = result.Message }; + } + else + { + var receipts = await Task.WhenAll(transactions.Select(ThirdwebTransaction.SendAndWaitForTransactionReceipt)); + return new NebulaExecuteResult() { Message = result.Message, TransactionReceipts = receipts.ToList() }; + } } private static async Task PrepareContextFilter(IThirdwebWallet wallet, NebulaContext context) From a6e6d471b4a8975324122e03fb8aa49a04de185b Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 23 Jan 2025 19:29:00 +0700 Subject: [PATCH 3/7] use execute_config for chat --- Thirdweb/Thirdweb.AI/ThirdwebNebula.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs b/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs index b633d3d..af6314c 100644 --- a/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs +++ b/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs @@ -83,7 +83,7 @@ public async Task Chat(string message, IThirdwebWallet wallet SessionId = this.SessionId, Message = message, ContextFilter = contextFiler, - Config = wallet == null ? null : new ExecuteConfig() { Mode = "client", SignerWalletAddress = await wallet.GetAddress() } + ExecuteConfig = wallet == null ? null : new ExecuteConfig() { Mode = "client", SignerWalletAddress = await wallet.GetAddress() } } ); @@ -107,7 +107,7 @@ public async Task Chat(List messages, IThirdwebWallet SessionId = this.SessionId, Messages = messages.Select(prompt => new ChatMessage() { Role = "user", Content = prompt }).ToList(), ContextFilter = contextFiler, - Config = wallet == null ? null : new ExecuteConfig() { Mode = "client", SignerWalletAddress = await wallet.GetAddress() } + ExecuteConfig = wallet == null ? null : new ExecuteConfig() { Mode = "client", SignerWalletAddress = await wallet.GetAddress() } } ); From 2ae308f67721d48e4f7908204be4b45611f6f1f0 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Fri, 24 Jan 2025 18:48:10 +0700 Subject: [PATCH 4/7] Fix /chat --- Thirdweb.Console/Program.cs | 42 +++++++++++++++++--------- Thirdweb/Thirdweb.AI/ThirdwebNebula.cs | 24 +++++++++++++-- 2 files changed, 49 insertions(+), 17 deletions(-) diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index 94cccff..5f52a90 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -38,25 +38,39 @@ #region AI +// Prepare some context var myChain = 11155111; var myWallet = await SmartWallet.Create(personalWallet: await PrivateKeyWallet.Generate(client), chainId: myChain, gasless: true); +var myContractAddress = "0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8"; // DropERC1155 +// Create a Nebula session var nebula = await ThirdwebNebula.Create(client); -// var response = await nebula.Chat(message: "What is my wallet address?", wallet: myWallet); -// Console.WriteLine($"Single Response: {response.Message}"); - -// var responses = await nebula.Chat( -// messages: new List { "What's the symbol of this contract?", "How much ETH does it have?" }, -// context: new NebulaContext(contractAddresses: new List { "0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8" }, chainIds: new List { myChain }) -// ); -// Console.WriteLine($"Multiple Responses: {responses.Message}"); - -// var receipt = await nebula.Execute( -// "Send a transaction with 0x data on sepolia to vitalik.eth using my wallet.", -// wallet: myWallet, -// context: new NebulaContext(chainIds: new List { myChain }) -// ); +// Chat, passing wallet context +var response1 = await nebula.Chat(message: "What is my wallet address?", wallet: myWallet); +Console.WriteLine($"Response 1: {response1.Message}"); + +// Chat, passing contract context +var response2 = await nebula.Chat( + message: "What's the total supply of token id 0 for this contract?", + context: new NebulaContext(contractAddresses: new List { myContractAddress }, chainIds: new List { myChain }) +); +Console.WriteLine($"Response 2: {response2.Message}"); + +// Chat, passing multiple messages and context +var response3 = await nebula.Chat( + messages: new List + { + new($"Tell me the name of this contract: {myContractAddress}", NebulaChatRole.User), + new("The name of the contract is CatDrop", NebulaChatRole.Assistant), + new("What's the symbol of this contract?", NebulaChatRole.User), + }, + context: new NebulaContext(contractAddresses: new List { myContractAddress }, chainIds: new List { myChain }) +); +Console.WriteLine($"Response 3: {response3.Message}"); + +// // Execute, this directly sends transactions +// var receipt = await nebula.Execute("Send a valueless transaction to my wallet!", wallet: myWallet, context: new NebulaContext(chainIds: new List { myChain })); // Console.WriteLine($"Receipt: {JsonConvert.SerializeObject(receipt, Formatting.Indented)}"); #endregion diff --git a/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs b/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs index af6314c..685ebfd 100644 --- a/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs +++ b/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs @@ -3,6 +3,24 @@ namespace Thirdweb.AI; +public enum NebulaChatRole +{ + User, + Assistant +} + +public class NebulaChatMessage +{ + public NebulaChatRole Role { get; set; } = NebulaChatRole.User; + public string Message { get; set; } + + public NebulaChatMessage(string message, NebulaChatRole role = NebulaChatRole.User) + { + this.Message = message; + this.Role = role; + } +} + public class NebulaChatResult { public string Message { get; set; } @@ -92,9 +110,9 @@ public async Task Chat(string message, IThirdwebWallet wallet return new NebulaChatResult() { Message = result.Message, Transactions = transactions == null || transactions.Count == 0 ? null : transactions }; } - public async Task Chat(List messages, IThirdwebWallet wallet = null, NebulaContext context = null) + public async Task Chat(List messages, IThirdwebWallet wallet = null, NebulaContext context = null) { - if (messages == null || messages.Count == 0 || messages.Any(string.IsNullOrWhiteSpace)) + if (messages == null || messages.Count == 0 || messages.Any(m => string.IsNullOrWhiteSpace(m.Message))) { throw new ArgumentException("Messages cannot be null or empty.", nameof(messages)); } @@ -105,7 +123,7 @@ public async Task Chat(List messages, IThirdwebWallet new ChatParamsMultiMessages() { SessionId = this.SessionId, - Messages = messages.Select(prompt => new ChatMessage() { Role = "user", Content = prompt }).ToList(), + Messages = messages.Select(prompt => new ChatMessage() { Content = prompt.Message, Role = prompt.Role.ToString().ToLower() }).ToList(), ContextFilter = contextFiler, ExecuteConfig = wallet == null ? null : new ExecuteConfig() { Mode = "client", SignerWalletAddress = await wallet.GetAddress() } } From c7cef729af4862761be4d8860528e0684eefd390 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Sat, 25 Jan 2025 00:57:37 +0700 Subject: [PATCH 5/7] Add explicit session id & implicit Execute (Batch) --- Thirdweb.Console/Program.cs | 28 +++++++++-- Thirdweb/Thirdweb.AI/ThirdwebNebula.cs | 66 +++++++++++++++++++++----- 2 files changed, 80 insertions(+), 14 deletions(-) diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index ddb9b17..7cd65ed 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -69,9 +69,31 @@ ); Console.WriteLine($"Response 3: {response3.Message}"); -// // Execute, this directly sends transactions -// var receipt = await nebula.Execute("Send a valueless transaction to my wallet!", wallet: myWallet, context: new NebulaContext(chainIds: new List { myChain })); -// Console.WriteLine($"Receipt: {JsonConvert.SerializeObject(receipt, Formatting.Indented)}"); +// Execute, this directly sends transactions +var executionResult = await nebula.Execute("Send 0 ETH to vitalik.eth", wallet: myWallet, context: new NebulaContext(chainIds: new List { myChain })); +if (executionResult.TransactionReceipts != null && executionResult.TransactionReceipts.Count > 0) +{ + Console.WriteLine($"Receipt: {executionResult.TransactionReceipts[0]}"); +} +else +{ + Console.WriteLine($"Message: {executionResult.Message}"); +} + +// Batch execute +var batchExecutionResult = await nebula.Execute( + new List { new("Send 0 ETH to vitalik.eth", NebulaChatRole.User), new("Are you sure?", NebulaChatRole.Assistant), new("Yes", NebulaChatRole.User) }, + wallet: myWallet, + context: new NebulaContext(chainIds: new List { myChain }) +); +if (batchExecutionResult.TransactionReceipts != null && batchExecutionResult.TransactionReceipts.Count > 0) +{ + Console.WriteLine($"Receipts: {JsonConvert.SerializeObject(batchExecutionResult.TransactionReceipts, Formatting.Indented)}"); +} +else +{ + Console.WriteLine($"Message: {batchExecutionResult.Message}"); +} #endregion diff --git a/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs b/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs index 685ebfd..c792d22 100644 --- a/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs +++ b/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs @@ -71,19 +71,28 @@ internal ThirdwebNebula(ThirdwebClient client) this.FeedbackClient = new FeedbackClient(httpClient); } - public static async Task Create(ThirdwebClient client, string model = Constants.NEBULA_DEFAULT_MODEL) + public static async Task Create(ThirdwebClient client, string model = Constants.NEBULA_DEFAULT_MODEL, string sessionId = null) { var nebula = new ThirdwebNebula(client); - var session = await nebula.Sessions.CreateSessionAsync( - new CreateSessionParams() - { - ModelName = model, - Title = $"Thirdweb .NET SDK (v{Constants.VERSION}) | Nebula {model} Session | Client ID: {client.ClientId}", - IsPublic = false - } - ); - nebula.SessionId = session.Id; - return nebula; + + if (!string.IsNullOrWhiteSpace(sessionId)) + { + nebula.SessionId = sessionId; + return nebula; + } + else + { + var session = await nebula.Sessions.CreateSessionAsync( + new CreateSessionParams() + { + ModelName = model, + Title = $"Thirdweb .NET SDK (v{Constants.VERSION}) | Nebula {model} Session | Client ID: {client.ClientId}", + IsPublic = false + } + ); + nebula.SessionId = session.Id; + return nebula; + } } public async Task Chat(string message, IThirdwebWallet wallet = null, NebulaContext context = null) @@ -169,6 +178,41 @@ public async Task Execute(string message, IThirdwebWallet w } } + public async Task Execute(List messages, IThirdwebWallet wallet, NebulaContext context = null) + { + if (messages == null || messages.Count == 0 || messages.Any(m => string.IsNullOrWhiteSpace(m.Message))) + { + throw new ArgumentException("Messages cannot be null or empty.", nameof(messages)); + } + + if (wallet == null) + { + throw new ArgumentException("Wallet cannot be null.", nameof(wallet)); + } + + var contextFiler = await PrepareContextFilter(wallet, context); + var result = await this.ExecuteClient.ExecuteBatchAsync( + new ChatParamsMultiMessages() + { + SessionId = this.SessionId, + Messages = messages.Select(prompt => new ChatMessage() { Content = prompt.Message, Role = prompt.Role.ToString().ToLower() }).ToList(), + ContextFilter = contextFiler, + ExecuteConfig = new ExecuteConfig() { Mode = "client", SignerWalletAddress = await wallet.GetAddress() } + } + ); + + var transactions = await PrepareTransactions(wallet, result.Actions); + if (transactions == null || transactions.Count == 0) + { + return new NebulaExecuteResult() { Message = result.Message }; + } + else + { + var receipts = await Task.WhenAll(transactions.Select(ThirdwebTransaction.SendAndWaitForTransactionReceipt)); + return new NebulaExecuteResult() { Message = result.Message, TransactionReceipts = receipts.ToList() }; + } + } + private static async Task PrepareContextFilter(IThirdwebWallet wallet, NebulaContext context) { context ??= new NebulaContext(); From 6d3bcdb56c77c1a379eb74fe74cd2a6f312c355e Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Sat, 25 Jan 2025 01:17:52 +0700 Subject: [PATCH 6/7] swap session id and model & add tests --- .../Thirdweb.AI/Thirdweb.AI.Tests.cs | 122 ++++++++++++++++++ Thirdweb/Thirdweb.AI/ThirdwebNebula.cs | 2 +- 2 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 Thirdweb.Tests/Thirdweb.AI/Thirdweb.AI.Tests.cs diff --git a/Thirdweb.Tests/Thirdweb.AI/Thirdweb.AI.Tests.cs b/Thirdweb.Tests/Thirdweb.AI/Thirdweb.AI.Tests.cs new file mode 100644 index 0000000..ae097a8 --- /dev/null +++ b/Thirdweb.Tests/Thirdweb.AI/Thirdweb.AI.Tests.cs @@ -0,0 +1,122 @@ +using System.Numerics; +using Thirdweb.AI; + +namespace Thirdweb.Tests.AI; + +public class NebulaTests : BaseTests +{ + private const string NEBULA_TEST_CONTRACT = "0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8"; + private const int NEBULA_TEST_CHAIN = 11155111; + + public NebulaTests(ITestOutputHelper output) + : base(output) { } + + [Fact(Timeout = 120000)] + public async Task Create_CreatesSession() + { + var nebula = await ThirdwebNebula.Create(this.Client); + Assert.NotNull(nebula); + Assert.NotNull(nebula.SessionId); + } + + [Fact(Timeout = 120000)] + public async Task Create_ResumesSession() + { + var nebula = await ThirdwebNebula.Create(this.Client); + var sessionId = nebula.SessionId; + Assert.NotNull(nebula); + Assert.NotNull(nebula.SessionId); + + nebula = await ThirdwebNebula.Create(this.Client, sessionId); + Assert.NotNull(nebula); + Assert.Equal(sessionId, nebula.SessionId); + } + + [Fact(Timeout = 120000)] + public async Task Chat_Single_ReturnsResponse() + { + var nebula = await ThirdwebNebula.Create(this.Client); + var response = await nebula.Chat( + message: "What's the symbol of this contract?", + context: new NebulaContext(contractAddresses: new List { NEBULA_TEST_CONTRACT }, chainIds: new List { NEBULA_TEST_CHAIN }) + ); + Assert.NotNull(response); + Assert.NotNull(response.Message); + Assert.Contains("CAT", response.Message); + } + + [Fact(Timeout = 120000)] + public async Task Chat_Single_NoContext_ReturnsResponse() + { + var nebula = await ThirdwebNebula.Create(this.Client); + var response = await nebula.Chat(message: $"What's the symbol of this contract: {NEBULA_TEST_CONTRACT} (Sepolia)?"); + Assert.NotNull(response); + Assert.NotNull(response.Message); + Assert.Contains("CAT", response.Message); + } + + [Fact(Timeout = 120000)] + public async Task Chat_Multiple_ReturnsResponse() + { + var nebula = await ThirdwebNebula.Create(this.Client); + var response = await nebula.Chat( + messages: new List + { + new("What's the symbol of this contract?", NebulaChatRole.User), + new("The symbol is CAT", NebulaChatRole.Assistant), + new("What's the name of this contract?", NebulaChatRole.User), + }, + context: new NebulaContext(contractAddresses: new List { NEBULA_TEST_CONTRACT }, chainIds: new List { NEBULA_TEST_CHAIN }) + ); + Assert.NotNull(response); + Assert.NotNull(response.Message); + Assert.Contains("CatDrop", response.Message); + } + + [Fact(Timeout = 120000)] + public async Task Chat_UnderstandsWalletContext() + { + var wallet = await PrivateKeyWallet.Generate(this.Client); + var expectedAddress = await wallet.GetAddress(); + var nebula = await ThirdwebNebula.Create(this.Client); + var response = await nebula.Chat(message: "What is my wallet address?", wallet: wallet); + Assert.NotNull(response); + Assert.NotNull(response.Message); + Assert.Contains(expectedAddress, response.Message); + } + + // [Fact(Timeout = 120000)] + // public async Task Execute_ReturnsMessageAndReceipt() + // { + // var signer = await PrivateKeyWallet.Generate(this.Client); + // var wallet = await SmartWallet.Create(signer, NEBULA_TEST_CHAIN); + // var nebula = await ThirdwebNebula.Create(this.Client); + // var response = await nebula.Execute("Send 0 ETH to vitalik.eth", wallet: wallet); + // Assert.NotNull(response); + // Assert.NotNull(response.Message); + // Assert.NotNull(response.TransactionReceipts); + // Assert.NotEmpty(response.TransactionReceipts); + // Assert.NotNull(response.TransactionReceipts[0].TransactionHash); + // Assert.True(response.TransactionReceipts[0].TransactionHash.Length == 66); + // } + + // [Fact(Timeout = 120000)] + // public async Task Execute_ReturnsMessageAndReceipts() + // { + // var signer = await PrivateKeyWallet.Generate(this.Client); + // var wallet = await SmartWallet.Create(signer, NEBULA_TEST_CHAIN); + // var nebula = await ThirdwebNebula.Create(this.Client); + // var response = await nebula.Execute( + // new List { new("Send 0 ETH to vitalik.eth and satoshi.eth", NebulaChatRole.User), new("Are you sure?", NebulaChatRole.Assistant), new("Yes", NebulaChatRole.User) }, + // wallet: wallet + // ); + // Assert.NotNull(response); + // Assert.NotNull(response.Message); + // Assert.NotNull(response.TransactionReceipts); + // Assert.NotEmpty(response.TransactionReceipts); + // Assert.NotNull(response.TransactionReceipts[0].TransactionHash); + // Assert.True(response.TransactionReceipts[0].TransactionHash.Length == 66); + // Assert.NotNull(response.TransactionReceipts[1].TransactionHash); + // Assert.True(response.TransactionReceipts[1].TransactionHash.Length == 66); + // } +} diff --git a/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs b/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs index c792d22..ed010c4 100644 --- a/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs +++ b/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs @@ -71,7 +71,7 @@ internal ThirdwebNebula(ThirdwebClient client) this.FeedbackClient = new FeedbackClient(httpClient); } - public static async Task Create(ThirdwebClient client, string model = Constants.NEBULA_DEFAULT_MODEL, string sessionId = null) + public static async Task Create(ThirdwebClient client, string sessionId = null, string model = Constants.NEBULA_DEFAULT_MODEL) { var nebula = new ThirdwebNebula(client); From fd202173f6271aad965d1df5ce4e1046e5ce9969 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Sat, 25 Jan 2025 04:07:58 +0700 Subject: [PATCH 7/7] Working Execute & Multi-Execute! --- Thirdweb.Console/Program.cs | 12 ++- .../Thirdweb.AI/Thirdweb.AI.Tests.cs | 82 +++++++++++-------- Thirdweb/Thirdweb.AI/ThirdwebNebula.cs | 27 +++--- .../ThirdwebTransactionInput.cs | 2 + 4 files changed, 76 insertions(+), 47 deletions(-) diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index 7cd65ed..67f7921 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -42,6 +42,7 @@ var myChain = 11155111; var myWallet = await SmartWallet.Create(personalWallet: await PrivateKeyWallet.Generate(client), chainId: myChain, gasless: true); var myContractAddress = "0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8"; // DropERC1155 +var usdcAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"; // Create a Nebula session var nebula = await ThirdwebNebula.Create(client); @@ -70,7 +71,7 @@ Console.WriteLine($"Response 3: {response3.Message}"); // Execute, this directly sends transactions -var executionResult = await nebula.Execute("Send 0 ETH to vitalik.eth", wallet: myWallet, context: new NebulaContext(chainIds: new List { myChain })); +var executionResult = await nebula.Execute("Approve 1 USDC to vitalik.eth", wallet: myWallet, context: new NebulaContext(contractAddresses: new List() { usdcAddress })); if (executionResult.TransactionReceipts != null && executionResult.TransactionReceipts.Count > 0) { Console.WriteLine($"Receipt: {executionResult.TransactionReceipts[0]}"); @@ -82,9 +83,14 @@ // Batch execute var batchExecutionResult = await nebula.Execute( - new List { new("Send 0 ETH to vitalik.eth", NebulaChatRole.User), new("Are you sure?", NebulaChatRole.Assistant), new("Yes", NebulaChatRole.User) }, + new List + { + new("What's the address of vitalik.eth", NebulaChatRole.User), + new("The address of vitalik.eth is 0xd8dA6BF26964aF8E437eEa5e3616511D7G3a3298", NebulaChatRole.Assistant), + new("Approve 1 USDC to them", NebulaChatRole.User), + }, wallet: myWallet, - context: new NebulaContext(chainIds: new List { myChain }) + context: new NebulaContext(contractAddresses: new List() { usdcAddress }) ); if (batchExecutionResult.TransactionReceipts != null && batchExecutionResult.TransactionReceipts.Count > 0) { diff --git a/Thirdweb.Tests/Thirdweb.AI/Thirdweb.AI.Tests.cs b/Thirdweb.Tests/Thirdweb.AI/Thirdweb.AI.Tests.cs index ae097a8..a91afd2 100644 --- a/Thirdweb.Tests/Thirdweb.AI/Thirdweb.AI.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.AI/Thirdweb.AI.Tests.cs @@ -6,6 +6,7 @@ namespace Thirdweb.Tests.AI; public class NebulaTests : BaseTests { private const string NEBULA_TEST_CONTRACT = "0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8"; + private const string NEBULA_TEST_USDC_ADDRESS = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"; private const int NEBULA_TEST_CHAIN = 11155111; public NebulaTests(ITestOutputHelper output) @@ -70,7 +71,7 @@ public async Task Chat_Multiple_ReturnsResponse() ); Assert.NotNull(response); Assert.NotNull(response.Message); - Assert.Contains("CatDrop", response.Message); + Assert.Contains("CatDrop", response.Message, StringComparison.OrdinalIgnoreCase); } [Fact(Timeout = 120000)] @@ -85,38 +86,51 @@ public async Task Chat_UnderstandsWalletContext() Assert.Contains(expectedAddress, response.Message); } - // [Fact(Timeout = 120000)] - // public async Task Execute_ReturnsMessageAndReceipt() - // { - // var signer = await PrivateKeyWallet.Generate(this.Client); - // var wallet = await SmartWallet.Create(signer, NEBULA_TEST_CHAIN); - // var nebula = await ThirdwebNebula.Create(this.Client); - // var response = await nebula.Execute("Send 0 ETH to vitalik.eth", wallet: wallet); - // Assert.NotNull(response); - // Assert.NotNull(response.Message); - // Assert.NotNull(response.TransactionReceipts); - // Assert.NotEmpty(response.TransactionReceipts); - // Assert.NotNull(response.TransactionReceipts[0].TransactionHash); - // Assert.True(response.TransactionReceipts[0].TransactionHash.Length == 66); - // } + [Fact(Timeout = 120000)] + public async Task Execute_ReturnsMessageAndReceipt() + { + var signer = await PrivateKeyWallet.Generate(this.Client); + var wallet = await SmartWallet.Create(signer, NEBULA_TEST_CHAIN); + var nebula = await ThirdwebNebula.Create(this.Client); + var response = await nebula.Execute( + new List + { + new("What's the address of vitalik.eth", NebulaChatRole.User), + new("The address of vitalik.eth is 0xd8dA6BF26964aF8E437eEa5e3616511D7G3a3298", NebulaChatRole.Assistant), + new("Approve 1 USDC to them", NebulaChatRole.User), + }, + wallet: wallet, + context: new NebulaContext(contractAddresses: new List() { NEBULA_TEST_USDC_ADDRESS }) + ); + Assert.NotNull(response); + Assert.NotNull(response.Message); + Assert.NotNull(response.TransactionReceipts); + Assert.NotEmpty(response.TransactionReceipts); + Assert.NotNull(response.TransactionReceipts[0].TransactionHash); + Assert.True(response.TransactionReceipts[0].TransactionHash.Length == 66); + } - // [Fact(Timeout = 120000)] - // public async Task Execute_ReturnsMessageAndReceipts() - // { - // var signer = await PrivateKeyWallet.Generate(this.Client); - // var wallet = await SmartWallet.Create(signer, NEBULA_TEST_CHAIN); - // var nebula = await ThirdwebNebula.Create(this.Client); - // var response = await nebula.Execute( - // new List { new("Send 0 ETH to vitalik.eth and satoshi.eth", NebulaChatRole.User), new("Are you sure?", NebulaChatRole.Assistant), new("Yes", NebulaChatRole.User) }, - // wallet: wallet - // ); - // Assert.NotNull(response); - // Assert.NotNull(response.Message); - // Assert.NotNull(response.TransactionReceipts); - // Assert.NotEmpty(response.TransactionReceipts); - // Assert.NotNull(response.TransactionReceipts[0].TransactionHash); - // Assert.True(response.TransactionReceipts[0].TransactionHash.Length == 66); - // Assert.NotNull(response.TransactionReceipts[1].TransactionHash); - // Assert.True(response.TransactionReceipts[1].TransactionHash.Length == 66); - // } + [Fact(Timeout = 120000)] + public async Task Execute_ReturnsMessageAndReceipts() + { + var signer = await PrivateKeyWallet.Generate(this.Client); + var wallet = await SmartWallet.Create(signer, NEBULA_TEST_CHAIN); + var nebula = await ThirdwebNebula.Create(this.Client); + var response = await nebula.Execute( + new List + { + new("What's the address of vitalik.eth", NebulaChatRole.User), + new("The address of vitalik.eth is 0xd8dA6BF26964aF8E437eEa5e3616511D7G3a3298", NebulaChatRole.Assistant), + new("Approve 1 USDC to them", NebulaChatRole.User), + }, + wallet: wallet, + context: new NebulaContext(contractAddresses: new List() { NEBULA_TEST_USDC_ADDRESS }) + ); + Assert.NotNull(response); + Assert.NotNull(response.Message); + Assert.NotNull(response.TransactionReceipts); + Assert.NotEmpty(response.TransactionReceipts); + Assert.NotNull(response.TransactionReceipts[0].TransactionHash); + Assert.True(response.TransactionReceipts[0].TransactionHash.Length == 66); + } } diff --git a/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs b/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs index ed010c4..2c9c99c 100644 --- a/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs +++ b/Thirdweb/Thirdweb.AI/ThirdwebNebula.cs @@ -234,14 +234,14 @@ private static async Task PrepareContextFilter(IThirdwebWallet wa // If it's a smart wallet, add the contract address and chain ID to the context if (wallet is SmartWallet smartWallet) { - if (context.ContractAddresses == null || context.ContractAddresses.Count == 0) - { - context.ContractAddresses = new List() { walletAddress }; - } - else if (!context.ContractAddresses.Contains(walletAddress)) - { - context.ContractAddresses.Add(walletAddress); - } + // if (context.ContractAddresses == null || context.ContractAddresses.Count == 0) + // { + // context.ContractAddresses = new List() { walletAddress }; + // } + // else if (!context.ContractAddresses.Contains(walletAddress)) + // { + // context.ContractAddresses.Add(walletAddress); + // } if (context.ChainIds == null || context.ChainIds.Count == 0) { @@ -269,9 +269,9 @@ private static async Task> PrepareTransactions(IThirdw var transactionTasks = actions .Select(action => { - if (action.Type == "transaction") + if (action.Type == "sign_transaction") { - var txInput = JsonConvert.DeserializeObject(action.Data.ToString()); + var txInput = JsonConvert.DeserializeObject(action.Data); return ThirdwebTransaction.Create(wallet, txInput); } else @@ -281,6 +281,13 @@ private static async Task> PrepareTransactions(IThirdw }) .ToList(); + if (transactionTasks == null || transactionTasks.Count == 0) + { + return null; + } + + _ = transactionTasks.RemoveAll(task => task == null); + return (await Task.WhenAll(transactionTasks)).Where(tx => tx != null).ToList(); } else diff --git a/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs b/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs index f3ed9cd..7ef2571 100644 --- a/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs +++ b/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs @@ -10,6 +10,8 @@ namespace Thirdweb; /// public class ThirdwebTransactionInput { + internal ThirdwebTransactionInput() { } + public ThirdwebTransactionInput(BigInteger chainId) { this.ChainId = chainId > 0 ? new HexBigInteger(chainId) : throw new ArgumentException("Invalid Chain ID");