diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index 4945a031..36c007f5 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -13,6 +13,7 @@ using Thirdweb; using Thirdweb.AccountAbstraction; using Thirdweb.AI; +using Thirdweb.Bridge; using Thirdweb.Indexer; using Thirdweb.Pay; @@ -41,6 +42,93 @@ #endregion +#region Bridge + +// // Create a ThirdwebBridge instance +// var bridge = await ThirdwebBridge.Create(client); + +// // Buy - Get a quote for buying a specific amount of tokens +// var buyQuote = await bridge.Buy_Quote( +// originChainId: 1, +// originTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum +// destinationChainId: 324, +// destinationTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync +// buyAmountWei: BigInteger.Parse("0.1".ToWei()) +// ); +// Console.WriteLine($"Buy quote: {JsonConvert.SerializeObject(buyQuote, Formatting.Indented)}"); + +// // Buy - Get an executable set of transactions (alongside a quote) for buying a specific amount of tokens +// var preparedBuy = await bridge.Buy_Prepare( +// originChainId: 1, +// originTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum +// destinationChainId: 324, +// destinationTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync +// buyAmountWei: BigInteger.Parse("0.1".ToWei()), +// sender: await Utils.GetAddressFromENS(client, "vitalik.eth"), +// receiver: await myWallet.GetAddress() +// ); +// Console.WriteLine($"Prepared Buy contains {preparedBuy.Transactions.Count} transaction(s)!"); + +// // Sell - Get a quote for selling a specific amount of tokens +// var sellQuote = await bridge.Sell_Quote( +// originChainId: 324, +// originTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync +// destinationChainId: 1, +// destinationTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum +// sellAmountWei: BigInteger.Parse("0.1".ToWei()) +// ); +// Console.WriteLine($"Sell quote: {JsonConvert.SerializeObject(sellQuote, Formatting.Indented)}"); + +// // Sell - Get an executable set of transactions (alongside a quote) for selling a specific amount of tokens +// var preparedSell = await bridge.Sell_Prepare( +// originChainId: 324, +// originTokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync +// destinationChainId: 1, +// destinationTokenAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum +// sellAmountWei: BigInteger.Parse("0.1".ToWei()), +// sender: await Utils.GetAddressFromENS(client, "vitalik.eth"), +// receiver: await myWallet.GetAddress() +// ); +// Console.WriteLine($"Prepared Sell contains {preparedSell.Transactions.Count} transaction(s)!"); + +// // Transfer - Get an executable transaction for transferring a specific amount of tokens +// var preparedTransfer = await bridge.Transfer_Prepare( +// chainId: 137, +// tokenAddress: Constants.NATIVE_TOKEN_ADDRESS, // ETH on zkSync +// transferAmountWei: BigInteger.Parse("0.1".ToWei()), +// sender: await Utils.GetAddressFromENS(client, "vitalik.eth"), +// receiver: await myWallet.GetAddress() +// ); +// Console.WriteLine($"Prepared Transfer: {JsonConvert.SerializeObject(preparedTransfer, Formatting.Indented)}"); + +// // You may use our extensions to execute yourself... +// var myTx = await preparedTransfer.Transactions[0].ToThirdwebTransaction(myWallet); +// var myHash = await ThirdwebTransaction.Send(myTx); + +// // ...and poll for the status... +// var status = await bridge.Status(transactionHash: myHash, chainId: 1); +// var isComplete = status.StatusType == StatusType.COMPLETED; +// Console.WriteLine($"Status: {JsonConvert.SerializeObject(status, Formatting.Indented)}"); + +// // Or use our Execute extensions directly to handle everything for you! + +// // Execute a prepared Buy +// var buyResult = await bridge.Execute(myWallet, preparedBuy); +// var buyHashes = buyResult.Select(receipt => receipt.TransactionHash).ToList(); +// Console.WriteLine($"Buy hashes: {JsonConvert.SerializeObject(buyHashes, Formatting.Indented)}"); + +// // Execute a prepared Sell +// var sellResult = await bridge.Execute(myWallet, preparedSell); +// var sellHashes = sellResult.Select(receipt => receipt.TransactionHash).ToList(); +// Console.WriteLine($"Sell hashes: {JsonConvert.SerializeObject(sellHashes, Formatting.Indented)}"); + +// // Execute a prepared Transfer +// var transferResult = await bridge.Execute(myWallet, preparedTransfer); +// var transferHashes = transferResult.Select(receipt => receipt.TransactionHash).ToList(); +// Console.WriteLine($"Transfer hashes: {JsonConvert.SerializeObject(transferHashes, Formatting.Indented)}"); + +#endregion + #region Indexer // // Create a ThirdwebInsight instance diff --git a/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.Extensions.cs b/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.Extensions.cs new file mode 100644 index 00000000..04d1428e --- /dev/null +++ b/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.Extensions.cs @@ -0,0 +1,121 @@ +using System.Numerics; + +namespace Thirdweb.Bridge; + +public static class ThirdwebBridgeExtensions +{ + #region Execution + + /// + /// Executes buy transaction(s) and handles status polling. + /// + /// The Thirdweb bridge. + /// The executor wallet. + /// The buy data. + /// The cancellation token. + /// The transaction receipts as a list of . + public static async Task> Execute(this ThirdwebBridge bridge, IThirdwebWallet executor, BuyPrepareData preparedBuy, CancellationToken cancellationToken = default) + { + return await ExecuteInternal(bridge, executor, preparedBuy.Transactions, cancellationToken); + } + + /// + /// Executes sell transaction(s) and handles status polling. + /// + /// The Thirdweb bridge. + /// The executor wallet. + /// The prepared sell data. + /// The cancellation token. + /// The transaction receipts as a list of . + public static async Task> Execute( + this ThirdwebBridge bridge, + IThirdwebWallet executor, + SellPrepareData preparedSell, + CancellationToken cancellationToken = default + ) + { + return await ExecuteInternal(bridge, executor, preparedSell.Transactions, cancellationToken); + } + + /// + /// Executes a transfer transaction and handles status polling. + /// + /// The Thirdweb bridge. + /// The executor wallet. + /// The prepared transfer data. + /// The cancellation token. + /// The transaction receipts as a list of . + public static Task> Execute( + this ThirdwebBridge bridge, + IThirdwebWallet executor, + TransferPrepareData preparedTransfer, + CancellationToken cancellationToken = default + ) + { + return ExecuteInternal(bridge, executor, preparedTransfer.Transactions, cancellationToken); + } + + private static async Task> ExecuteInternal( + this ThirdwebBridge bridge, + IThirdwebWallet executor, + List transactions, + CancellationToken cancellationToken = default + ) + { + var receipts = new List(); + foreach (var tx in transactions) + { + var thirdwebTx = await tx.ToThirdwebTransaction(executor); + var hash = await ThirdwebTransaction.Send(thirdwebTx); + receipts.Add(await ThirdwebTransaction.WaitForTransactionReceipt(executor.Client, tx.ChainId, hash, cancellationToken)); + _ = await bridge.WaitForStatusCompletion(hash, tx.ChainId, cancellationToken); + } + return receipts; + } + + #endregion + + #region Helpers + + public static async Task ToThirdwebTransaction(this Transaction transaction, IThirdwebWallet executor) + { + return await ThirdwebTransaction.Create( + executor, + new ThirdwebTransactionInput( + chainId: transaction.ChainId, + to: transaction.To, + value: BigInteger.Parse(string.IsNullOrEmpty(transaction.Value) ? "0" : transaction.Value), + data: string.IsNullOrEmpty(transaction.Data) ? "0x" : transaction.Data + ) + ); + } + + public static async Task WaitForStatusCompletion(this ThirdwebBridge bridge, string hash, BigInteger chainId, CancellationToken cancellationToken = default) + { + if (string.IsNullOrEmpty(hash)) + { + throw new ArgumentNullException(nameof(hash)); + } + + if (chainId == 0) + { + throw new ArgumentNullException(nameof(chainId)); + } + + var status = await bridge.Status(hash, chainId); + while (status.StatusType is StatusType.PENDING or StatusType.NOT_FOUND) + { + await ThirdwebTask.Delay(500, cancellationToken); + status = await bridge.Status(hash, chainId); + } + + if (status.StatusType is StatusType.FAILED) + { + throw new Exception($"Transaction with hash {hash} failed."); + } + + return status; + } + + #endregion +} diff --git a/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.Types.cs b/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.Types.cs new file mode 100644 index 00000000..a5a7219c --- /dev/null +++ b/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.Types.cs @@ -0,0 +1,369 @@ +using System.Numerics; +using Newtonsoft.Json; + +namespace Thirdweb.Bridge; + +/// +/// 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("data")] + internal T Data { get; set; } +} + +#region Common Types + +/// +/// Represents the base intent object for different types of transactions. +/// +public class Intent +{ + /// + /// The chain ID where the transaction originates. + /// + [JsonProperty("originChainId")] + public BigInteger OriginChainId { get; set; } + + /// + /// The token address in the origin chain. + /// + [JsonProperty("originTokenAddress")] + public string OriginTokenAddress { get; set; } + + /// + /// The chain ID where the transaction is executed. + /// + [JsonProperty("destinationChainId")] + public BigInteger DestinationChainId { get; set; } + + /// + /// The token address in the destination chain. + /// + [JsonProperty("destinationTokenAddress")] + public string DestinationTokenAddress { get; set; } + + /// + /// The amount involved in the transaction (buy, sell, or transfer) in wei. + /// + public virtual string AmountWei { get; set; } +} + +/// +/// Represents the common fields for both Buy and Sell transactions. +/// +public class QuoteData + where TIntent : Intent +{ + /// + /// The amount (in wei) of the input token that must be paid to receive the desired amount. + /// + [JsonProperty("originAmount")] + public string OriginAmount { get; set; } + + /// + /// The amount (in wei) of the output token to be received by the receiver address. + /// + [JsonProperty("destinationAmount")] + public string DestinationAmount { get; set; } + + /// + /// The timestamp when the quote was generated. + /// + [JsonProperty("timestamp")] + public long Timestamp { get; set; } + + /// + /// The block number when the quote was generated. + /// + [JsonProperty("blockNumber")] + public string BlockNumber { get; set; } + + /// + /// The estimated execution time in milliseconds for filling the quote. + /// + [JsonProperty("estimatedExecutionTimeMs")] + public long EstimatedExecutionTimeMs { get; set; } + + /// + /// The intent object containing details about the transaction. + /// + [JsonProperty("intent")] + public TIntent Intent { get; set; } +} + +/// +/// Represents a transaction to be executed. +/// +public class Transaction +{ + /// + /// The chain ID where the transaction will take place. + /// + [JsonProperty("chainId")] + public BigInteger ChainId { get; set; } + + /// + /// The address to which the transaction is sent, or null if not applicable. + /// + [JsonProperty("to", NullValueHandling = NullValueHandling.Ignore)] + public string To { get; set; } + + /// + /// The value (amount) to be sent in the transaction. + /// + [JsonProperty("value")] + public string Value { get; set; } + + /// + /// The transaction data. + /// + [JsonProperty("data")] + public string Data { get; set; } + + /// + /// The type of the transaction (e.g., "eip1559"). + /// + [JsonProperty("type")] + public string Type { get; set; } +} + +#endregion + +#region Buy + +/// +/// Represents the data returned in the buy quote response. +/// +public class BuyQuoteData : QuoteData { } + +/// +/// Represents the data returned in the buy prepare response. +/// +public class BuyPrepareData : QuoteData +{ + /// + /// An array of transactions to be executed to fulfill this quote (in order). + /// + [JsonProperty("transactions")] + public List Transactions { get; set; } + + /// + /// The expiration timestamp for this prepared quote and its transactions (if applicable). + /// + [JsonProperty("expiration")] + public long? Expiration { get; set; } +} + +/// +/// Represents the intent object for a buy quote. +/// +public class BuyIntent : Intent +{ + /// + /// The desired output amount in wei for buying. + /// + [JsonProperty("buyAmountWei")] + public override string AmountWei { get; set; } +} + +#endregion + +#region Sell + +/// +/// Represents the data returned in the sell quote response. +/// +public class SellQuoteData : QuoteData { } + +/// +/// Represents the data returned in the sell prepare response. +/// +public class SellPrepareData : QuoteData +{ + /// + /// An array of transactions to be executed to fulfill this quote (in order). + /// + [JsonProperty("transactions")] + public List Transactions { get; set; } + + /// + /// The expiration timestamp for this prepared quote and its transactions (if applicable). + /// + [JsonProperty("expiration")] + public long? Expiration { get; set; } +} + +/// +/// Represents the intent object for a sell quote. +/// +public class SellIntent : Intent +{ + /// + /// The amount to sell in wei. + /// + [JsonProperty("sellAmountWei")] + public override string AmountWei { get; set; } +} + +#endregion + +#region Transfer + +/// +/// Represents the data returned in the transfer prepare response. +/// +public class TransferPrepareData +{ + [JsonProperty("originAmount")] + public string OriginAmount { get; set; } + + [JsonProperty("destinationAmount")] + public string DestinationAmount { get; set; } + + [JsonProperty("timestamp")] + public long Timestamp { get; set; } + + [JsonProperty("blockNumber")] + public string BlockNumber { get; set; } + + [JsonProperty("estimatedExecutionTimeMs")] + public long EstimatedExecutionTimeMs { get; set; } + + [JsonProperty("transactions")] + public List Transactions { get; set; } + + [JsonProperty("expiration")] + public long? Expiration { get; set; } + + [JsonProperty("intent")] + public TransferIntent Intent { get; set; } +} + +/// +/// Represents the intent object for the transfer prepare response. +/// +public class TransferIntent +{ + [JsonProperty("chainId")] + public int ChainId { get; set; } + + [JsonProperty("tokenAddress")] + public string TokenAddress { get; set; } + + [JsonProperty("transferAmountWei")] + public string TransferAmountWei { get; set; } + + [JsonProperty("sender")] + public string Sender { get; set; } + + [JsonProperty("receiver")] + public string Receiver { get; set; } +} + +#endregion + +#region Status + +/// +/// Represents the possible statuses for a transaction. +/// +public enum StatusType +{ + FAILED, + PENDING, + COMPLETED, + NOT_FOUND +} + +/// +/// Represents the data returned in the status response. +/// +public class StatusData +{ + /// + /// The status of the transaction (as StatusType enum). + /// + [JsonIgnore] + public StatusType StatusType => + this.Status switch + { + "FAILED" => StatusType.FAILED, + "PENDING" => StatusType.PENDING, + "COMPLETED" => StatusType.COMPLETED, + "NOT_FOUND" => StatusType.NOT_FOUND, + _ => throw new InvalidOperationException($"Unknown status: {this.Status}") + }; + + /// + /// The status of the transaction. + /// + [JsonProperty("status")] + public string Status { get; set; } + + /// + /// A list of transactions involved in this status. + /// + [JsonProperty("transactions")] + public List Transactions { get; set; } + + /// + /// The origin chain ID (for PENDING and COMPLETED statuses). + /// + [JsonProperty("originChainId", NullValueHandling = NullValueHandling.Ignore)] + public BigInteger? OriginChainId { get; set; } + + /// + /// The origin token address (for PENDING and COMPLETED statuses). + /// + [JsonProperty("originTokenAddress", NullValueHandling = NullValueHandling.Ignore)] + public string OriginTokenAddress { get; set; } + + /// + /// The destination chain ID (for PENDING and COMPLETED statuses). + /// + [JsonProperty("destinationChainId", NullValueHandling = NullValueHandling.Ignore)] + public BigInteger? DestinationChainId { get; set; } + + /// + /// The destination token address (for PENDING and COMPLETED statuses). + /// + [JsonProperty("destinationTokenAddress", NullValueHandling = NullValueHandling.Ignore)] + public string DestinationTokenAddress { get; set; } + + /// + /// The origin token amount in wei (for PENDING and COMPLETED statuses). + /// + [JsonProperty("originAmount", NullValueHandling = NullValueHandling.Ignore)] + public string OriginAmount { get; set; } + + /// + /// The destination token amount in wei (for COMPLETED status). + /// + [JsonProperty("destinationAmount", NullValueHandling = NullValueHandling.Ignore)] + public string DestinationAmount { get; set; } +} + +/// +/// Represents the transaction details for a specific status. +/// +public class TransactionStatus +{ + /// + /// The chain ID where the transaction took place. + /// + [JsonProperty("chainId")] + public BigInteger ChainId { get; set; } + + /// + /// The transaction hash of the transaction. + /// + [JsonProperty("transactionHash")] + public string TransactionHash { get; set; } +} + +#endregion diff --git a/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.cs b/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.cs new file mode 100644 index 00000000..095fd8fd --- /dev/null +++ b/Thirdweb/Thirdweb.Bridge/ThirdwebBridge.cs @@ -0,0 +1,380 @@ +using System.Numerics; +using Newtonsoft.Json; + +namespace Thirdweb.Bridge; + +public class ThirdwebBridge +{ + private readonly IThirdwebHttpClient _httpClient; + + internal ThirdwebBridge(ThirdwebClient client) + { + this._httpClient = client.HttpClient; + } + + /// + /// Create a new instance of the ThirdwebBridge class. + /// + /// The ThirdwebClient instance. + /// A new instance of . + public static Task Create(ThirdwebClient client) + { + return Task.FromResult(new ThirdwebBridge(client)); + } + + #region Buy + + /// + /// Get a quote for buying a specific amount of tokens on any chain. + /// + /// The chain ID of the origin chain. + /// The address of the token on the origin chain. + /// The chain ID of the destination chain. + /// The address of the token on the destination chain. + /// The amount of tokens to buy in wei. + /// A object representing the quote. + /// Thrown when one of the parameters is invalid. + public async Task Buy_Quote(BigInteger originChainId, string originTokenAddress, BigInteger destinationChainId, string destinationTokenAddress, BigInteger buyAmountWei) + { + if (originChainId <= 0) + { + throw new ArgumentException("originChainId cannot be less than or equal to 0", nameof(originChainId)); + } + + if (destinationChainId <= 0) + { + throw new ArgumentException("destinationChainId cannot be less than or equal to 0", nameof(destinationChainId)); + } + + if (!Utils.IsValidAddress(originTokenAddress)) + { + throw new ArgumentException("originTokenAddress is not a valid address", nameof(originTokenAddress)); + } + + if (buyAmountWei <= 0) + { + throw new ArgumentException("buyAmountWei cannot be less than or equal to 0", nameof(buyAmountWei)); + } + + var url = $"{Constants.BRIDGE_API_URL}/v1/buy/quote"; + var queryParams = new Dictionary + { + { "originChainId", originChainId.ToString() }, + { "originTokenAddress", originTokenAddress }, + { "destinationChainId", destinationChainId.ToString() }, + { "destinationTokenAddress", destinationTokenAddress }, + { "buyAmountWei", buyAmountWei.ToString() } + }; + url = AppendQueryParams(url, queryParams); + + var response = await this._httpClient.GetAsync(url).ConfigureAwait(false); + _ = response.EnsureSuccessStatusCode(); + var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var result = JsonConvert.DeserializeObject>(responseContent); + return result.Data; + } + + /// + /// Get the transactions required to buy a specific amount of tokens on any chain, alongside the quote. + /// + /// The chain ID of the origin chain. + /// The address of the token on the origin chain. + /// The chain ID of the destination chain. + /// The address of the token on the destination chain. + /// The amount of tokens to buy in wei. + /// The address of the sender. + /// The address of the receiver. + /// A object representing the prepare data. + /// Thrown when one of the parameters is invalid. + public async Task Buy_Prepare( + BigInteger originChainId, + string originTokenAddress, + BigInteger destinationChainId, + string destinationTokenAddress, + BigInteger buyAmountWei, + string sender, + string receiver + ) + { + if (originChainId <= 0) + { + throw new ArgumentException("originChainId cannot be less than or equal to 0", nameof(originChainId)); + } + + if (destinationChainId <= 0) + { + throw new ArgumentException("destinationChainId cannot be less than or equal to 0", nameof(destinationChainId)); + } + + if (!Utils.IsValidAddress(originTokenAddress)) + { + throw new ArgumentException("originTokenAddress is not a valid address", nameof(originTokenAddress)); + } + + if (buyAmountWei <= 0) + { + throw new ArgumentException("buyAmountWei cannot be less than or equal to 0", nameof(buyAmountWei)); + } + + if (!Utils.IsValidAddress(sender)) + { + throw new ArgumentException("sender is not a valid address", nameof(sender)); + } + + if (!Utils.IsValidAddress(receiver)) + { + throw new ArgumentException("receiver is not a valid address", nameof(receiver)); + } + + var url = $"{Constants.BRIDGE_API_URL}/v1/buy/prepare"; + var queryParams = new Dictionary + { + { "originChainId", originChainId.ToString() }, + { "originTokenAddress", originTokenAddress }, + { "destinationChainId", destinationChainId.ToString() }, + { "destinationTokenAddress", destinationTokenAddress }, + { "buyAmountWei", buyAmountWei.ToString() }, + { "sender", sender }, + { "receiver", receiver } + }; + url = AppendQueryParams(url, queryParams); + + var response = await this._httpClient.GetAsync(url).ConfigureAwait(false); + _ = response.EnsureSuccessStatusCode(); + var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var result = JsonConvert.DeserializeObject>(responseContent); + return result.Data; + } + + #endregion + + #region Sell + + /// + /// Get a quote for selling a specific amount of tokens on any chain. + /// + /// The chain ID of the origin chain. + /// The address of the token on the origin chain. + /// The chain ID of the destination chain. + /// The address of the token on the destination chain. + /// The amount of tokens to sell in wei. + /// A object representing the quote. + /// Thrown when one of the parameters is invalid. + public async Task Sell_Quote(BigInteger originChainId, string originTokenAddress, BigInteger destinationChainId, string destinationTokenAddress, BigInteger sellAmountWei) + { + if (originChainId <= 0) + { + throw new ArgumentException("originChainId cannot be less than or equal to 0", nameof(originChainId)); + } + + if (destinationChainId <= 0) + { + throw new ArgumentException("destinationChainId cannot be less than or equal to 0", nameof(destinationChainId)); + } + + if (!Utils.IsValidAddress(originTokenAddress)) + { + throw new ArgumentException("originTokenAddress is not a valid address", nameof(originTokenAddress)); + } + + if (sellAmountWei <= 0) + { + throw new ArgumentException("sellAmountWei cannot be less than or equal to 0", nameof(sellAmountWei)); + } + + var url = $"{Constants.BRIDGE_API_URL}/v1/sell/quote"; + var queryParams = new Dictionary + { + { "originChainId", originChainId.ToString() }, + { "originTokenAddress", originTokenAddress }, + { "destinationChainId", destinationChainId.ToString() }, + { "destinationTokenAddress", destinationTokenAddress }, + { "sellAmountWei", sellAmountWei.ToString() } + }; + url = AppendQueryParams(url, queryParams); + + var response = await this._httpClient.GetAsync(url).ConfigureAwait(false); + _ = response.EnsureSuccessStatusCode(); + var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var result = JsonConvert.DeserializeObject>(responseContent); + return result.Data; + } + + /// + /// Get the transactions required to sell a specific amount of tokens on any chain, alongside the quote. + /// + /// The chain ID of the origin chain. + /// The address of the token on the origin chain. + /// The chain ID of the destination chain. + /// The address of the token on the destination chain. + /// The amount of tokens to sell in wei. + /// The address of the sender. + /// The address of the receiver. + /// A object representing the prepare data. + /// Thrown when one of the parameters is invalid. + public async Task Sell_Prepare( + BigInteger originChainId, + string originTokenAddress, + BigInteger destinationChainId, + string destinationTokenAddress, + BigInteger sellAmountWei, + string sender, + string receiver + ) + { + if (originChainId <= 0) + { + throw new ArgumentException("originChainId cannot be less than or equal to 0", nameof(originChainId)); + } + + if (destinationChainId <= 0) + { + throw new ArgumentException("destinationChainId cannot be less than or equal to 0", nameof(destinationChainId)); + } + + if (!Utils.IsValidAddress(originTokenAddress)) + { + throw new ArgumentException("originTokenAddress is not a valid address", nameof(originTokenAddress)); + } + + if (sellAmountWei <= 0) + { + throw new ArgumentException("sellAmountWei cannot be less than or equal to 0", nameof(sellAmountWei)); + } + + if (!Utils.IsValidAddress(sender)) + { + throw new ArgumentException("sender is not a valid address", nameof(sender)); + } + + if (!Utils.IsValidAddress(receiver)) + { + throw new ArgumentException("receiver is not a valid address", nameof(receiver)); + } + + var url = $"{Constants.BRIDGE_API_URL}/v1/sell/prepare"; + var queryParams = new Dictionary + { + { "originChainId", originChainId.ToString() }, + { "originTokenAddress", originTokenAddress }, + { "destinationChainId", destinationChainId.ToString() }, + { "destinationTokenAddress", destinationTokenAddress }, + { "sellAmountWei", sellAmountWei.ToString() }, + { "sender", sender }, + { "receiver", receiver } + }; + url = AppendQueryParams(url, queryParams); + + var response = await this._httpClient.GetAsync(url).ConfigureAwait(false); + _ = response.EnsureSuccessStatusCode(); + var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var result = JsonConvert.DeserializeObject>(responseContent); + return result.Data; + } + + #endregion + + #region Transfer + + /// + /// Get the transactions required to transfer a specific amount of tokens on any chain. + /// + /// The chain ID of the token. + /// The address of the token. + /// The amount of tokens to transfer in wei. + /// The address of the sender. + /// The address of the receiver. + /// A object representing the prepare data. + /// Thrown when one of the parameters is invalid. + public async Task Transfer_Prepare(BigInteger chainId, string tokenAddress, BigInteger transferAmountWei, string sender, string receiver) + { + if (chainId <= 0) + { + throw new ArgumentException("chainId cannot be less than or equal to 0", nameof(chainId)); + } + + if (!Utils.IsValidAddress(tokenAddress)) + { + throw new ArgumentException("tokenAddress is not a valid address", nameof(tokenAddress)); + } + + if (transferAmountWei <= 0) + { + throw new ArgumentException("transferAmountWei cannot be less than or equal to 0", nameof(transferAmountWei)); + } + + if (!Utils.IsValidAddress(sender)) + { + throw new ArgumentException("sender is not a valid address", nameof(sender)); + } + + if (!Utils.IsValidAddress(receiver)) + { + throw new ArgumentException("receiver is not a valid address", nameof(receiver)); + } + + var url = $"{Constants.BRIDGE_API_URL}/v1/transfer/prepare"; + var queryParams = new Dictionary + { + { "chainId", chainId.ToString() }, + { "tokenAddress", tokenAddress }, + { "transferAmountWei", transferAmountWei.ToString() }, + { "sender", sender }, + { "receiver", receiver } + }; + url = AppendQueryParams(url, queryParams); + + var response = await this._httpClient.GetAsync(url).ConfigureAwait(false); + _ = response.EnsureSuccessStatusCode(); + var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var result = JsonConvert.DeserializeObject>(responseContent); + return result.Data; + } + + #endregion + + #region Status + + /// + /// Get the status of any bridge-initiated transaction. + /// + /// The hash of the transaction. + /// The chain ID of the transaction. + /// A object representing the status. + /// Thrown when one of the parameters is invalid. + public async Task Status(string transactionHash, BigInteger chainId) + { + if (string.IsNullOrWhiteSpace(transactionHash)) + { + throw new ArgumentException("transactionHash cannot be null or empty", nameof(transactionHash)); + } + + if (chainId <= 0) + { + throw new ArgumentException("chainId cannot be less than or equal to 0", nameof(chainId)); + } + + var url = $"{Constants.BRIDGE_API_URL}/v1/status"; + var queryParams = new Dictionary { { "transactionHash", transactionHash }, { "chainId", chainId.ToString() } }; + url = AppendQueryParams(url, queryParams); + + var response = await this._httpClient.GetAsync(url).ConfigureAwait(false); + _ = response.EnsureSuccessStatusCode(); + var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + var result = JsonConvert.DeserializeObject>(responseContent); + return result.Data; + } + + #endregion + + private static string AppendQueryParams(string url, Dictionary queryParams) + { + var query = new List(); + foreach (var param in queryParams) + { + query.Add($"{param.Key}={param.Value}"); + } + + return url + "?" + string.Join("&", query); + } +} diff --git a/Thirdweb/Thirdweb.Pay/ThirdwebPay.cs b/Thirdweb/Thirdweb.Pay/ThirdwebPay.cs index 7d39b97f..f493cc28 100644 --- a/Thirdweb/Thirdweb.Pay/ThirdwebPay.cs +++ b/Thirdweb/Thirdweb.Pay/ThirdwebPay.cs @@ -1,5 +1,6 @@ namespace Thirdweb.Pay; +[Obsolete("This class is deprecated, please use ThirdwebBridge instead.")] public partial class ThirdwebPay { private const string THIRDWEB_PAY_BASE_URL = "https://pay.thirdweb.com"; diff --git a/Thirdweb/Thirdweb.Utils/Constants.cs b/Thirdweb/Thirdweb.Utils/Constants.cs index 4d507be9..1fbbebe3 100644 --- a/Thirdweb/Thirdweb.Utils/Constants.cs +++ b/Thirdweb/Thirdweb.Utils/Constants.cs @@ -4,6 +4,13 @@ public static class Constants { public const string VERSION = "2.18.6"; + internal const string BRIDGE_API_URL = "https://bridge.thirdweb.com"; + internal const string NEBULA_API_URL = "https://nebula-api.thirdweb.com"; + internal const string INSIGHT_API_URL = "https://insight.thirdweb.com"; + internal const string SOCIAL_API_URL = "https://social.thirdweb.com"; + internal const string PIN_URI = "https://storage.thirdweb.com/ipfs/upload"; + internal const string FALLBACK_IPFS_GATEWAY = "https://ipfs.io/ipfs/"; + public const string IERC20_INTERFACE_ID = "0x36372b07"; public const string IERC721_INTERFACE_ID = "0x80ac58cd"; public const string IERC1155_INTERFACE_ID = "0xd9b67a26"; @@ -31,16 +38,10 @@ public static class Constants internal const string DUMMY_SIG = "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c"; internal const string DUMMY_PAYMASTER_AND_DATA_HEX = "0x0101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000001010101010100000000000000000000000000000000000000000000000000000000000000000101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"; - internal const string FALLBACK_IPFS_GATEWAY = "https://ipfs.io/ipfs/"; - internal const string PIN_URI = "https://storage.thirdweb.com/ipfs/upload"; 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 INSIGHT_API_URL = "https://insight.thirdweb.com"; - 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\"}]";