diff --git a/Thirdweb.Tests/Thirdweb.Contracts/Thirdweb.Contracts.Tests.cs b/Thirdweb.Tests/Thirdweb.Contracts/Thirdweb.Contracts.Tests.cs index 01aee88b..2e88ce8b 100644 --- a/Thirdweb.Tests/Thirdweb.Contracts/Thirdweb.Contracts.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.Contracts/Thirdweb.Contracts.Tests.cs @@ -94,6 +94,14 @@ public async Task ReadTest_PartialSig() Assert.Equal("Kitty DropERC20", result); } + [Fact(Timeout = 120000)] + public async Task PrepareTest_NoSig() + { + var contract = await this.GetContract(); + var exception = await Assert.ThrowsAsync(async () => await ThirdwebContract.Prepare(null, contract, "sup", 0)); + Assert.Contains("Method signature not found in contract ABI.", exception.Message); + } + private sealed class AllowlistProof { public List Proof { get; set; } = new List(); @@ -263,7 +271,7 @@ private async Task GetAccount() private async Task GetContract() { var client = this.Client; - var contract = await ThirdwebContract.Create(client: client, address: "0xEBB8a39D865465F289fa349A67B3391d8f910da9", chain: 421614); + var contract = await ThirdwebContract.Create(client: client, address: "0xEBB8a39D865465F289fa349A67B3391d8f910da9", chain: 421614); // DropERC20 return contract; } } diff --git a/Thirdweb.Tests/Thirdweb.Extensions/Thirdweb.Extensions.Tests.cs b/Thirdweb.Tests/Thirdweb.Extensions/Thirdweb.Extensions.Tests.cs index 55ab7e84..724d6c36 100644 --- a/Thirdweb.Tests/Thirdweb.Extensions/Thirdweb.Extensions.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.Extensions/Thirdweb.Extensions.Tests.cs @@ -105,6 +105,18 @@ public async Task NullChecks() _ = await Assert.ThrowsAsync(() => ThirdwebExtensions.Transfer(wallet, 0, validAddress, 0)); _ = await Assert.ThrowsAsync(() => ThirdwebExtensions.Transfer(wallet, this._chainId, null, 0)); _ = await Assert.ThrowsAsync(() => ThirdwebExtensions.Transfer(wallet, this._chainId, validAddress, -1)); + + // GetTransactionCountRaw + _ = await Assert.ThrowsAsync(() => ThirdwebExtensions.GetTransactionCountRaw(null, this._chainId, validAddress)); + _ = await Assert.ThrowsAsync(() => ThirdwebExtensions.GetTransactionCountRaw(client, 0, validAddress)); + _ = await Assert.ThrowsAsync(() => ThirdwebExtensions.GetTransactionCountRaw(client, this._chainId, null)); + + // GetTransactionCount + _ = await Assert.ThrowsAsync(() => ThirdwebExtensions.GetTransactionCount(null)); + _ = await Assert.ThrowsAsync(() => ThirdwebExtensions.GetTransactionCount(null, "latest")); + + // ERC721_TokenByIndex + _ = await Assert.ThrowsAsync(() => ThirdwebExtensions.ERC721_TokenByIndex(null, 0)); } [Fact(Timeout = 120000)] @@ -269,6 +281,37 @@ public async Task Transfer() Assert.True(receipt.TransactionHash.Length == 66); } + [Fact(Timeout = 120000)] + public async Task Contract_Read() + { + var contract = await this.GetTokenERC20Contract(); + var result = await contract.Read("name"); + Assert.NotNull(result); + Assert.NotEmpty(result); + } + + [Fact(Timeout = 120000)] + public async Task Contract_Write() + { + var contract = await this.GetTokenERC20Contract(); + var wallet = await this.GetSmartWallet(); + var receipt = await contract.Write(wallet, "approve", 0, contract.Address, BigInteger.Zero); + Assert.NotNull(receipt); + Assert.True(receipt.TransactionHash.Length == 66); + } + + [Fact(Timeout = 120000)] + public async Task Contract_Prepare() + { + var contract = await this.GetTokenERC20Contract(); + var wallet = await this.GetSmartWallet(); + var transaction = await contract.Prepare(wallet, "approve", 0, contract.Address, BigInteger.Zero); + Assert.NotNull(transaction); + Assert.NotNull(transaction.Input.Data); + Assert.NotNull(transaction.Input.To); + Assert.NotNull(transaction.Input.Value); + } + #endregion #region ERC20 @@ -394,6 +437,30 @@ public async Task ERC20_Approve() #endregion + #region ERC721A + + [Fact(Timeout = 120000)] + public async Task ERC721A_TokensOfOwner() + { + var contract = await this.GetERC721AContract(); + var ownerAddress = "0x10a798EC43A776c39BA19978EDb6e4a7706326FA"; + var tokens = await contract.ERC721A_TokensOfOwner(ownerAddress); + Assert.NotNull(tokens); + Assert.NotEmpty(tokens); + } + + [Fact(Timeout = 120000)] + public async Task ERC721A_TokensOfOwnerIn() + { + var contract = await this.GetERC721AContract(); + var ownerAddress = "0x10a798EC43A776c39BA19978EDb6e4a7706326FA"; + var tokens = await contract.ERC721A_TokensOfOwnerIn(ownerAddress, 0, 1); + Assert.NotNull(tokens); + Assert.NotEmpty(tokens); + } + + #endregion + #region ERC721 [Fact(Timeout = 120000)] diff --git a/Thirdweb.Tests/Thirdweb.Transactions/Thirdweb.Transactions.Tests.cs b/Thirdweb.Tests/Thirdweb.Transactions/Thirdweb.Transactions.Tests.cs index 855085fe..83895fe7 100644 --- a/Thirdweb.Tests/Thirdweb.Transactions/Thirdweb.Transactions.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.Transactions/Thirdweb.Transactions.Tests.cs @@ -444,4 +444,16 @@ public async Task WaitForTransactionReceipt_CancellationToken() var aaReceipt2 = await ThirdwebTransaction.WaitForTransactionReceipt(client, chainId, aaTxHash, CancellationToken.None); Assert.NotNull(aaReceipt2); } + + [Fact(Timeout = 120000)] + public async Task WaitForTransactionReceipt_ToStringReturnsJson() + { + var client = this.Client; + var chainId = 421614; + var normalTxHash = "0x5a0b6cdb01ecfb25b368d3de1ac844414980ee3c330ec8c1435117b75027b5d7"; + + var normalReceipt = await ThirdwebTransaction.WaitForTransactionReceipt(client, chainId, normalTxHash); + Assert.NotNull(normalReceipt); + Assert.StartsWith("{", normalReceipt.ToString()); + } } diff --git a/Thirdweb.Tests/Thirdweb.Utils/Thirdweb.Utils.Tests.cs b/Thirdweb.Tests/Thirdweb.Utils/Thirdweb.Utils.Tests.cs index eff0e27e..f61a9613 100644 --- a/Thirdweb.Tests/Thirdweb.Utils/Thirdweb.Utils.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.Utils/Thirdweb.Utils.Tests.cs @@ -669,4 +669,98 @@ public async Task GetSocialProfiles_ThrowsException_InvalidAuth() Assert.Contains("Failed to fetch social profiles", exception.Message); } + + [Fact(Timeout = 120000)] + public async Task IsEip155Enforced_ReturnsFalse_WhenChainIs1() + { + var chainId = new BigInteger(1); + var isEip155Enforced = await Utils.IsEip155Enforced(this.Client, chainId); + + Assert.False(isEip155Enforced); + } + + [Fact(Timeout = 120000)] + public async Task IsEip155Enforced_ReturnsTrue_WhenChainIs842() + { + var chainId = new BigInteger(842); + var isEip155Enforced = await Utils.IsEip155Enforced(this.Client, chainId); + + Assert.True(isEip155Enforced); + } + + [Fact(Timeout = 120000)] + public void ReconstructHttpClient_WithHeaders() + { + var newClient = Utils.ReconstructHttpClient(this.Client.HttpClient, this.Client.HttpClient.Headers); + var newHeaders = newClient.Headers; + + Assert.NotNull(newHeaders); + Assert.Equal(this.Client.HttpClient.Headers, newHeaders); + } + + [Fact(Timeout = 120000)] + public void ReconstructHttpClient_WithoutHeaders() + { + var newClient = Utils.ReconstructHttpClient(this.Client.HttpClient); + var newHeaders = newClient.Headers; + + Assert.NotNull(newHeaders); + Assert.Empty(newHeaders); + } + + [Fact(Timeout = 120000)] + public async Task FetchGasPrice_Success() + { + var gasPrice = await Utils.FetchGasPrice(this.Client, 1); + Assert.True(gasPrice > 0); + } + + [Fact(Timeout = 120000)] + public async Task FetchGasFees_1() + { + var (maxFee, maxPrio) = await Utils.FetchGasFees(this.Client, 1); + Assert.True(maxFee > 0); + Assert.True(maxPrio > 0); + Assert.NotEqual(maxFee, maxPrio); + } + + [Fact(Timeout = 120000)] + public async Task FetchGasFees_137() + { + var (maxFee, maxPrio) = await Utils.FetchGasFees(this.Client, 137); + Assert.True(maxFee > 0); + Assert.True(maxPrio > 0); + Assert.True(maxFee > maxPrio); + } + + [Fact(Timeout = 120000)] + public async Task FetchGasFees_80002() + { + var (maxFee, maxPrio) = await Utils.FetchGasFees(this.Client, 80002); + Assert.True(maxFee > 0); + Assert.True(maxPrio > 0); + Assert.True(maxFee > maxPrio); + } + + [Fact(Timeout = 120000)] + public async Task FetchGasFees_Celo() + { + var chainId = new BigInteger(42220); + var (maxFee, maxPrio) = await Utils.FetchGasFees(this.Client, chainId); + Assert.True(maxFee > 0); + Assert.True(maxPrio > 0); + Assert.Equal(maxFee, maxPrio); + + chainId = new BigInteger(44787); + (maxFee, maxPrio) = await Utils.FetchGasFees(this.Client, chainId); + Assert.True(maxFee > 0); + Assert.True(maxPrio > 0); + Assert.Equal(maxFee, maxPrio); + + chainId = new BigInteger(62320); + (maxFee, maxPrio) = await Utils.FetchGasFees(this.Client, chainId); + Assert.True(maxFee > 0); + Assert.True(maxPrio > 0); + Assert.Equal(maxFee, maxPrio); + } } diff --git a/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.PrivateKeyWallet.Tests.cs b/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.PrivateKeyWallet.Tests.cs index 64a0cf99..3183341b 100644 --- a/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.PrivateKeyWallet.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.PrivateKeyWallet.Tests.cs @@ -21,13 +21,52 @@ public async Task Initialization_Success() } [Fact(Timeout = 120000)] - public async void Initialization_NullPrivateKey() + public async void Create_NullClient() + { + _ = await Assert.ThrowsAsync(() => PrivateKeyWallet.Create(null, "0x1234567890abcdef")); + } + + [Fact(Timeout = 120000)] + public async void Create_NullPrivateKey() { var client = this.Client; var ex = await Assert.ThrowsAsync(async () => await PrivateKeyWallet.Create(client, null)); Assert.Equal("Private key cannot be null or empty. (Parameter 'privateKeyHex')", ex.Message); } + [Fact(Timeout = 120000)] + public async void Create_EmptyPrivateKey() + { + var client = this.Client; + var ex = await Assert.ThrowsAsync(async () => await PrivateKeyWallet.Create(client, string.Empty)); + Assert.Equal("Private key cannot be null or empty. (Parameter 'privateKeyHex')", ex.Message); + } + + [Fact(Timeout = 120000)] + public async void Generate_NullClient() + { + _ = await Assert.ThrowsAsync(() => PrivateKeyWallet.Generate(null)); + } + + [Fact(Timeout = 120000)] + public async void LoadOrGenerate_NullClient() + { + _ = await Assert.ThrowsAsync(() => PrivateKeyWallet.LoadOrGenerate(null)); + } + + [Fact(Timeout = 120000)] + public async void SaveAndDelete_CheckPath() + { + var wallet = await PrivateKeyWallet.Generate(this.Client); + await wallet.Save(); + + var path = PrivateKeyWallet.GetSavePath(); + Assert.True(File.Exists(path)); + + PrivateKeyWallet.Delete(); + Assert.False(File.Exists(path)); + } + [Fact(Timeout = 120000)] public async Task Connect() { diff --git a/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs b/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs index 1e6a786f..419350cd 100644 --- a/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs +++ b/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs @@ -215,9 +215,7 @@ public static async Task EstimateTotalCosts(ThirdwebTransaction tran /// The estimated gas price. public static async Task EstimateGasPrice(ThirdwebTransaction transaction, bool withBump = true) { - var rpc = ThirdwebRPC.GetRpcInstance(transaction._wallet.Client, transaction.Input.ChainId.Value); - var hex = new HexBigInteger(await rpc.SendRequestAsync("eth_gasPrice").ConfigureAwait(false)); - return withBump ? hex.Value * 10 / 9 : hex.Value; + return await Utils.FetchGasPrice(transaction._wallet.Client, transaction.Input.ChainId.Value, withBump).ConfigureAwait(false); } /// @@ -238,38 +236,9 @@ public static async Task EstimateGasPrice(ThirdwebTransaction transa var maxPriorityFee = fees["max_priority_fee_per_gas"].ToObject().Value; return withBump ? (maxFee * 10 / 5, maxPriorityFee * 10 / 5) : (maxFee, maxPriorityFee); } - - var gasPrice = await EstimateGasPrice(transaction, withBump).ConfigureAwait(false); - - // Polygon Mainnet & Amoy - if (chainId == (BigInteger)137 || chainId == (BigInteger)80002) - { - return (gasPrice * 3 / 2, gasPrice * 4 / 3); - } - - // Celo Mainnet, Alfajores & Baklava - if (chainId == (BigInteger)42220 || chainId == (BigInteger)44787 || chainId == (BigInteger)62320) - { - return (gasPrice, gasPrice); - } - - try - { - var block = await rpc.SendRequestAsync("eth_getBlockByNumber", "latest", true).ConfigureAwait(false); - var baseBlockFee = block["baseFeePerGas"]?.ToObject(); - var maxFeePerGas = baseBlockFee.Value * 2; - var maxPriorityFeePerGas = ((await rpc.SendRequestAsync("eth_maxPriorityFeePerGas").ConfigureAwait(false))?.Value) ?? maxFeePerGas / 2; - - if (maxPriorityFeePerGas > maxFeePerGas) - { - maxPriorityFeePerGas = maxFeePerGas / 2; - } - - return (maxFeePerGas + (maxPriorityFeePerGas * 10 / 9), maxPriorityFeePerGas * 10 / 9); - } - catch + else { - return (gasPrice, gasPrice); + return await Utils.FetchGasFees(transaction._wallet.Client, chainId, withBump).ConfigureAwait(false); } } diff --git a/Thirdweb/Thirdweb.Utils/Utils.Types.cs b/Thirdweb/Thirdweb.Utils/Utils.Types.cs index 5541300c..0360b97c 100644 --- a/Thirdweb/Thirdweb.Utils/Utils.Types.cs +++ b/Thirdweb/Thirdweb.Utils/Utils.Types.cs @@ -45,12 +45,6 @@ public class ThirdwebChainData [JsonProperty("icon")] public ThirdwebChainIcon Icon { get; set; } - [JsonProperty("faucets")] - public List Faucets { get; set; } - - [JsonProperty("slip44")] - public int? Slip44 { get; set; } - [JsonProperty("ens")] public ThirdwebChainEns Ens { get; set; } @@ -114,12 +108,6 @@ public class ThirdwebChainExplorer public ThirdwebChainIcon Icon { get; set; } } -public class ThirdwebChainBridge -{ - [JsonProperty("url")] - public string Url { get; set; } -} - [JsonObject(ItemNullValueHandling = NullValueHandling.Ignore)] public class FarcasterProfile { diff --git a/Thirdweb/Thirdweb.Utils/Utils.cs b/Thirdweb/Thirdweb.Utils/Utils.cs index 83b9c14a..5d528d20 100644 --- a/Thirdweb/Thirdweb.Utils/Utils.cs +++ b/Thirdweb/Thirdweb.Utils/Utils.cs @@ -10,6 +10,7 @@ using Nethereum.ABI.Model; using Nethereum.Contracts; using Nethereum.Hex.HexConvertors.Extensions; +using Nethereum.Hex.HexTypes; using Nethereum.Signer; using Nethereum.Util; using Newtonsoft.Json; @@ -25,6 +26,7 @@ public static partial class Utils private static readonly Dictionary _eip155EnforcedCache = new(); private static readonly Dictionary _chainDataCache = new(); private static readonly Dictionary _ensCache = new(); + private static readonly List _errorSubstringsComposite = new() { new string[] { "account", "not found!" }, new[] { "wrong", "chainid" } }; /// /// Computes the client ID from the given secret key. @@ -129,6 +131,16 @@ public static byte[] HexToBytes(this string hex) return hex.HexToByteArray(); } + /// + /// Converts the given hex string to a big integer. + /// + /// The hex string to convert. + /// The big integer. + public static BigInteger HexToBigInt(this string hex) + { + return new HexBigInteger(hex).Value; + } + /// /// Converts the given string to a hex string. /// @@ -294,7 +306,7 @@ public static string GenerateSIWE(LoginPayloadData loginPayloadData) /// True if it is a zkSync chain ID, otherwise false. public static async Task IsZkSync(ThirdwebClient client, BigInteger chainId) { - if (chainId.Equals(324) || chainId.Equals(300) || chainId.Equals(302) || chainId.Equals(11124) || chainId.Equals(4654) || chainId.Equals(333271)) + if (chainId.Equals(324) || chainId.Equals(300) || chainId.Equals(302) || chainId.Equals(11124) || chainId.Equals(4654) || chainId.Equals(333271) || chainId.Equals(37111)) { return true; } @@ -604,9 +616,6 @@ private static List GetJProperties(string mainTypeName, MemberValue[] return list; } - private static readonly string[] _composite1 = new[] { "account", "not found!" }; - private static readonly string[] _composite2 = new[] { "wrong", "chainid" }; - public static async Task IsEip155Enforced(ThirdwebClient client, BigInteger chainId) { if (_eip155EnforcedCache.TryGetValue(chainId, out var value)) @@ -651,9 +660,7 @@ public static async Task IsEip155Enforced(ThirdwebClient client, BigIntege else { // Check if all substrings in any of the composite substrings are present - var errorSubstringsComposite = new List { _composite1, _composite2 }; - - result = errorSubstringsComposite.Any(arr => arr.All(substring => errorMsg.Contains(substring))); + result = _errorSubstringsComposite.Any(arr => arr.All(substring => errorMsg.Contains(substring))); } } @@ -847,6 +854,53 @@ public static async Task IsDeployed(ThirdwebClient client, BigInteger chai return code != "0x"; } + public static async Task FetchGasPrice(ThirdwebClient client, BigInteger chainId, bool withBump = true) + { + var rpc = ThirdwebRPC.GetRpcInstance(client, chainId); + var hex = await rpc.SendRequestAsync("eth_gasPrice").ConfigureAwait(false); + var gasPrice = hex.HexToBigInt(); + return withBump ? gasPrice * 10 / 9 : gasPrice; + } + + public static async Task<(BigInteger maxFeePerGas, BigInteger maxPriorityFeePerGas)> FetchGasFees(ThirdwebClient client, BigInteger chainId, bool withBump = true) + { + var rpc = ThirdwebRPC.GetRpcInstance(client, chainId); + + // Polygon Mainnet & Amoy + if (chainId == (BigInteger)137 || chainId == (BigInteger)80002) + { + var gasPrice = await FetchGasPrice(client, chainId, withBump).ConfigureAwait(false); + return (gasPrice * 3 / 2, gasPrice * 4 / 3); + } + + // Celo Mainnet, Alfajores & Baklava + if (chainId == (BigInteger)42220 || chainId == (BigInteger)44787 || chainId == (BigInteger)62320) + { + var gasPrice = await FetchGasPrice(client, chainId, withBump).ConfigureAwait(false); + return (gasPrice, gasPrice); + } + + try + { + var block = await rpc.SendRequestAsync("eth_getBlockByNumber", "latest", true).ConfigureAwait(false); + var baseBlockFee = block["baseFeePerGas"]?.ToObject(); + var maxFeePerGas = baseBlockFee.Value * 2; + var maxPriorityFeePerGas = ((await rpc.SendRequestAsync("eth_maxPriorityFeePerGas").ConfigureAwait(false))?.Value) ?? maxFeePerGas / 2; + + if (maxPriorityFeePerGas > maxFeePerGas) + { + maxPriorityFeePerGas = maxFeePerGas / 2; + } + + return (maxFeePerGas + (maxPriorityFeePerGas * 10 / 9), maxPriorityFeePerGas * 10 / 9); + } + catch + { + var gasPrice = await FetchGasPrice(client, chainId, withBump).ConfigureAwait(false); + return (gasPrice, gasPrice); + } + } + public static IThirdwebHttpClient ReconstructHttpClient(IThirdwebHttpClient httpClient, Dictionary defaultHeaders = null) { var reconstructedHttpClient = httpClient.GetType().GetConstructor(Type.EmptyTypes).Invoke(null) as IThirdwebHttpClient; diff --git a/Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs b/Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs index f91a7036..3c5879bd 100644 --- a/Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs @@ -130,6 +130,19 @@ Task RecoverAddressFromTypedDataV4(T data, TypedData Task Disconnect(); + /// + /// Links a new account (auth method) to the current wallet. The current wallet must be connected and the wallet being linked must not be fully connected ie created. + /// + /// The wallet to link. + /// The OTP code if the wallet to link is an email or phone wallet. + /// Set to true if linking OAuth on mobile. + /// The action to open the browser if linking OAuth. + /// The redirect scheme if linking OAuth on mobile. + /// The browser to use if linking OAuth. + /// The chain ID if linking an external wallet (SIWE). + /// The JWT token if linking custom JWT auth. + /// The login payload if linking custom AuthEndpoint auth. + /// A list of objects. Task> LinkAccount( IThirdwebWallet walletToLink, string otp = null, @@ -142,6 +155,10 @@ Task> LinkAccount( string payload = null ); + /// + /// Returns a list of linked accounts to the current wallet. + /// + /// A list of objects. Task> GetLinkedAccounts(); } diff --git a/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs b/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs index d1e95321..f5696b68 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs @@ -104,6 +104,22 @@ BigInteger erc20PaymasterStorageSlot this._erc20PaymasterStorageSlot = erc20PaymasterStorageSlot; } + #region Creation + + /// + /// Creates a new instance of . + /// + /// The smart wallet's signer to use. + /// The chain ID. + /// Whether to sponsor gas for transactions. + /// Override the default factory address. + /// Override the canonical account address that would be found deterministically based on the signer. + /// Override the default entry point address. We provide Constants for different versions. + /// Override the default thirdweb bundler URL. + /// Override the default thirdweb paymaster URL. + /// Use an ERC20 paymaster and sponsor gas with ERC20s. If set, factoryAddress and accountAddressOverride are ignored. + /// A new instance of . + /// Thrown if the personal account is not connected. public static async Task Create( IThirdwebWallet personalWallet, BigInteger chainId, @@ -177,6 +193,19 @@ public static async Task Create( ); } + #endregion + + #region Wallet Specific + + /// + /// Returns the signer that was used to connect to this SmartWallet. + /// + /// The signer. + public Task GetPersonalWallet() + { + return Task.FromResult(this._personalAccount); + } + /// /// Attempts to set the active network to the specified chain ID. Requires related contracts to be deterministically deployed on the chain. /// @@ -206,6 +235,10 @@ public async Task SwitchNetwork(BigInteger chainId) } } + /// + /// Checks if the smart account is deployed on the current chain. A smart account is typically deployed when a personal message is signed or a transaction is sent. + /// + /// True if deployed, otherwise false. public async Task IsDeployed() { if (await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false)) @@ -217,68 +250,266 @@ public async Task IsDeployed() return code != "0x"; } - public async Task SendTransaction(ThirdwebTransactionInput transactionInput) + /// + /// Forces the smart account to deploy on the current chain. This is typically not necessary as the account will deploy automatically when needed. + /// + public async Task ForceDeploy() { - if (transactionInput == null) + if (await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false)) { - throw new InvalidOperationException("SmartAccount.SendTransaction: Transaction input is required."); + return; } - await this.SwitchNetwork(transactionInput.ChainId.Value).ConfigureAwait(false); + if (await this.IsDeployed().ConfigureAwait(false)) + { + return; + } - var transaction = await ThirdwebTransaction - .Create(await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false) ? this._personalAccount : this, transactionInput) - .ConfigureAwait(false); - transaction = await ThirdwebTransaction.Prepare(transaction).ConfigureAwait(false); - transactionInput = transaction.Input; + if (this.IsDeploying) + { + throw new InvalidOperationException("SmartAccount.ForceDeploy: Account is already deploying."); + } - if (await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false)) + var input = new ThirdwebTransactionInput(this._chainId) { - if (this._gasless) + Data = "0x", + To = this._accountContract.Address, + Value = new HexBigInteger(0) + }; + var txHash = await this.SendTransaction(input).ConfigureAwait(false); + _ = await ThirdwebTransaction.WaitForTransactionReceipt(this.Client, this._chainId, txHash).ConfigureAwait(false); + } + + /// + /// Verifies if a signature is valid for a message using EIP-1271. + /// + /// The message to verify. + /// The signature to verify. + /// True if the signature is valid, otherwise false. + public async Task IsValidSignature(string message, string signature) + { + try + { + var magicValue = await ThirdwebContract.Read(this._accountContract, "isValidSignature", message.StringToHex(), signature.HexToBytes()).ConfigureAwait(false); + return magicValue.BytesToHex() == new byte[] { 0x16, 0x26, 0xba, 0x7e }.BytesToHex(); + } + catch + { + try { - (var paymaster, var paymasterInput) = await this.ZkPaymasterData(transactionInput).ConfigureAwait(false); - transaction = transaction.SetZkSyncOptions(new ZkSyncOptions(paymaster: paymaster, paymasterInput: paymasterInput)); - var zkTx = await ThirdwebTransaction.ConvertToZkSyncTransaction(transaction).ConfigureAwait(false); - var zkTxSigned = await EIP712.GenerateSignature_ZkSyncTransaction("zkSync", "2", transaction.Input.ChainId.Value, zkTx, this).ConfigureAwait(false); - // Match bundler ZkTransactionInput type without recreating - var hash = await this.ZkBroadcastTransaction( - new - { - nonce = zkTx.Nonce.ToString(), - from = zkTx.From, - to = zkTx.To, - gas = zkTx.GasLimit.ToString(), - gasPrice = string.Empty, - value = zkTx.Value.ToString(), - data = Utils.BytesToHex(zkTx.Data), - maxFeePerGas = zkTx.MaxFeePerGas.ToString(), - maxPriorityFeePerGas = zkTx.MaxPriorityFeePerGas.ToString(), - chainId = this._chainId.ToString(), - signedTransaction = zkTxSigned, - paymaster - } - ) + var magicValue = await ThirdwebContract + .Read(this._accountContract, "isValidSignature", Encoding.UTF8.GetBytes(message).HashPrefixedMessage(), signature.HexToBytes()) .ConfigureAwait(false); - return hash; + return magicValue.BytesToHex() == new byte[] { 0x16, 0x26, 0xba, 0x7e }.BytesToHex(); } - else + catch { - return await ThirdwebTransaction.Send(transaction).ConfigureAwait(false); + return false; } } - else + } + + /// + /// Gets all admins for the smart account. + /// + /// A list of admin addresses. + public async Task> GetAllAdmins() + { + if (await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false)) { - var signedOp = await this.SignUserOp(transactionInput).ConfigureAwait(false); - return await this.SendUserOp(signedOp).ConfigureAwait(false); + throw new InvalidOperationException("Account Permissions are not supported in ZkSync"); } + + var result = await ThirdwebContract.Read>(this._accountContract, "getAllAdmins").ConfigureAwait(false); + return result ?? new List(); } - public async Task ExecuteTransaction(ThirdwebTransactionInput transactionInput) + /// + /// Gets all active signers for the smart account. + /// + /// A list of . + public async Task> GetAllActiveSigners() { - var txHash = await this.SendTransaction(transactionInput).ConfigureAwait(false); + if (await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false)) + { + throw new InvalidOperationException("Account Permissions are not supported in ZkSync"); + } + + var result = await ThirdwebContract.Read>(this._accountContract, "getAllActiveSigners").ConfigureAwait(false); + return result ?? new List(); + } + + /// + /// Creates a new session key for a signer to use with the smart account. + /// + /// The address of the signer to create a session key for. + /// The list of approved targets for the signer. Use a list of a single Constants.ADDRESS_ZERO to enable all contracts. + /// The maximum amount of native tokens the signer can send in a single transaction. + /// The timestamp when the permission starts. Can be set to zero. + /// The timestamp when the permission ends. Make use of our Utils to get UNIX timestamps. + /// The timestamp when the request validity starts. Can be set to zero. + /// The timestamp when the request validity ends. Make use of our Utils to get UNIX timestamps. + public async Task CreateSessionKey( + string signerAddress, + List approvedTargets, + string nativeTokenLimitPerTransactionInWei, + string permissionStartTimestamp, + string permissionEndTimestamp, + string reqValidityStartTimestamp, + string reqValidityEndTimestamp + ) + { + if (await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false)) + { + throw new InvalidOperationException("Account Permissions are not supported in ZkSync"); + } + + var request = new SignerPermissionRequest() + { + Signer = signerAddress, + IsAdmin = 0, + ApprovedTargets = approvedTargets, + NativeTokenLimitPerTransaction = BigInteger.Parse(nativeTokenLimitPerTransactionInWei), + PermissionStartTimestamp = BigInteger.Parse(permissionStartTimestamp), + PermissionEndTimestamp = BigInteger.Parse(permissionEndTimestamp), + ReqValidityStartTimestamp = BigInteger.Parse(reqValidityStartTimestamp), + ReqValidityEndTimestamp = BigInteger.Parse(reqValidityEndTimestamp), + Uid = Guid.NewGuid().ToByteArray() + }; + + var signature = await EIP712.GenerateSignature_SmartAccount("Account", "1", this._chainId, await this.GetAddress().ConfigureAwait(false), request, this._personalAccount).ConfigureAwait(false); + // Do it this way to avoid triggering an extra sig from estimation + var data = new Contract(null, this._accountContract.Abi, this._accountContract.Address).GetFunction("setPermissionsForSigner").GetData(request, signature.HexToBytes()); + var txInput = new ThirdwebTransactionInput(this._chainId) + { + To = this._accountContract.Address, + Value = new HexBigInteger(0), + Data = data + }; + var txHash = await this.SendTransaction(txInput).ConfigureAwait(false); + return await ThirdwebTransaction.WaitForTransactionReceipt(this.Client, this._chainId, txHash).ConfigureAwait(false); + } + + /// + /// Revokes a session key from a signer. + /// + /// The address of the signer to revoke. + /// The transaction receipt. + public async Task RevokeSessionKey(string signerAddress) + { + return await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false) + ? throw new InvalidOperationException("Account Permissions are not supported in ZkSync") + : await this.CreateSessionKey(signerAddress, new List(), "0", "0", "0", "0", Utils.GetUnixTimeStampIn10Years().ToString()).ConfigureAwait(false); + } + + /// + /// Adds a new admin to the smart account. + /// + /// The address of the admin to add. + /// The transaction receipt. + public async Task AddAdmin(string admin) + { + if (await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false)) + { + throw new InvalidOperationException("Account Permissions are not supported in ZkSync"); + } + + var request = new SignerPermissionRequest() + { + Signer = admin, + IsAdmin = 1, + ApprovedTargets = new List(), + NativeTokenLimitPerTransaction = 0, + PermissionStartTimestamp = Utils.GetUnixTimeStampNow() - 3600, + PermissionEndTimestamp = Utils.GetUnixTimeStampIn10Years(), + ReqValidityStartTimestamp = Utils.GetUnixTimeStampNow() - 3600, + ReqValidityEndTimestamp = Utils.GetUnixTimeStampIn10Years(), + Uid = Guid.NewGuid().ToByteArray() + }; + + var signature = await EIP712.GenerateSignature_SmartAccount("Account", "1", this._chainId, await this.GetAddress(), request, this._personalAccount).ConfigureAwait(false); + var data = new Contract(null, this._accountContract.Abi, this._accountContract.Address).GetFunction("setPermissionsForSigner").GetData(request, signature.HexToBytes()); + var txInput = new ThirdwebTransactionInput(this._chainId) + { + To = this._accountContract.Address, + Value = new HexBigInteger(0), + Data = data + }; + var txHash = await this.SendTransaction(txInput).ConfigureAwait(false); + return await ThirdwebTransaction.WaitForTransactionReceipt(this.Client, this._chainId, txHash).ConfigureAwait(false); + } + + /// + /// Removes an existing admin from the smart account. + /// + /// The address of the admin to remove. + /// The transaction receipt. + public async Task RemoveAdmin(string admin) + { + if (await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false)) + { + throw new InvalidOperationException("Account Permissions are not supported in ZkSync"); + } + + var request = new SignerPermissionRequest() + { + Signer = admin, + IsAdmin = 2, + ApprovedTargets = new List(), + NativeTokenLimitPerTransaction = 0, + PermissionStartTimestamp = Utils.GetUnixTimeStampNow() - 3600, + PermissionEndTimestamp = Utils.GetUnixTimeStampIn10Years(), + ReqValidityStartTimestamp = Utils.GetUnixTimeStampNow() - 3600, + ReqValidityEndTimestamp = Utils.GetUnixTimeStampIn10Years(), + Uid = Guid.NewGuid().ToByteArray() + }; + + var signature = await EIP712.GenerateSignature_SmartAccount("Account", "1", this._chainId, await this.GetAddress().ConfigureAwait(false), request, this._personalAccount).ConfigureAwait(false); + var data = new Contract(null, this._accountContract.Abi, this._accountContract.Address).GetFunction("setPermissionsForSigner").GetData(request, signature.HexToBytes()); + var txInput = new ThirdwebTransactionInput(this._chainId) + { + To = this._accountContract.Address, + Value = new HexBigInteger(0), + Data = data + }; + var txHash = await this.SendTransaction(txInput).ConfigureAwait(false); return await ThirdwebTransaction.WaitForTransactionReceipt(this.Client, this._chainId, txHash).ConfigureAwait(false); } + /// + /// Estimates the gas cost for a user operation. More accurate than ThirdwebTransaction estimation, but slower. + /// + /// The transaction to estimate. + /// The estimated gas cost. + public async Task EstimateUserOperationGas(ThirdwebTransactionInput transaction) + { + await this.SwitchNetwork(transaction.ChainId.Value).ConfigureAwait(false); + + if (await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false)) + { + throw new Exception("User Operations are not supported in ZkSync"); + } + + var signedOp = await this.SignUserOp(transaction, null, simulation: true).ConfigureAwait(false); + if (signedOp is UserOperationV6) + { + var castSignedOp = signedOp as UserOperationV6; + var cost = castSignedOp.CallGasLimit + castSignedOp.VerificationGasLimit + castSignedOp.PreVerificationGas; + return cost; + } + else if (signedOp is UserOperationV7) + { + var castSignedOp = signedOp as UserOperationV7; + var cost = + castSignedOp.CallGasLimit + castSignedOp.VerificationGasLimit + castSignedOp.PreVerificationGas + castSignedOp.PaymasterVerificationGasLimit + castSignedOp.PaymasterPostOpGasLimit; + return cost; + } + else + { + throw new Exception("Invalid signed operation type"); + } + } + private async Task<(byte[] initCode, string factory, string factoryData)> GetInitCode() { if (await this.IsDeployed().ConfigureAwait(false)) @@ -668,36 +899,70 @@ private static UserOperationHexifiedV7 EncodeUserOperation(UserOperationV7 userO }; } - public async Task ForceDeploy() + #endregion + + #region IThirdwebWallet + + public async Task SendTransaction(ThirdwebTransactionInput transactionInput) { - if (await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false)) + if (transactionInput == null) { - return; + throw new InvalidOperationException("SmartAccount.SendTransaction: Transaction input is required."); } - if (await this.IsDeployed().ConfigureAwait(false)) - { - return; - } + await this.SwitchNetwork(transactionInput.ChainId.Value).ConfigureAwait(false); - if (this.IsDeploying) + var transaction = await ThirdwebTransaction + .Create(await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false) ? this._personalAccount : this, transactionInput) + .ConfigureAwait(false); + transaction = await ThirdwebTransaction.Prepare(transaction).ConfigureAwait(false); + transactionInput = transaction.Input; + + if (await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false)) { - throw new InvalidOperationException("SmartAccount.ForceDeploy: Account is already deploying."); + if (this._gasless) + { + (var paymaster, var paymasterInput) = await this.ZkPaymasterData(transactionInput).ConfigureAwait(false); + transaction = transaction.SetZkSyncOptions(new ZkSyncOptions(paymaster: paymaster, paymasterInput: paymasterInput)); + var zkTx = await ThirdwebTransaction.ConvertToZkSyncTransaction(transaction).ConfigureAwait(false); + var zkTxSigned = await EIP712.GenerateSignature_ZkSyncTransaction("zkSync", "2", transaction.Input.ChainId.Value, zkTx, this).ConfigureAwait(false); + // Match bundler ZkTransactionInput type without recreating + var hash = await this.ZkBroadcastTransaction( + new + { + nonce = zkTx.Nonce.ToString(), + from = zkTx.From, + to = zkTx.To, + gas = zkTx.GasLimit.ToString(), + gasPrice = string.Empty, + value = zkTx.Value.ToString(), + data = Utils.BytesToHex(zkTx.Data), + maxFeePerGas = zkTx.MaxFeePerGas.ToString(), + maxPriorityFeePerGas = zkTx.MaxPriorityFeePerGas.ToString(), + chainId = this._chainId.ToString(), + signedTransaction = zkTxSigned, + paymaster + } + ) + .ConfigureAwait(false); + return hash; + } + else + { + return await ThirdwebTransaction.Send(transaction).ConfigureAwait(false); + } } - - var input = new ThirdwebTransactionInput(this._chainId) + else { - Data = "0x", - To = this._accountContract.Address, - Value = new HexBigInteger(0) - }; - var txHash = await this.SendTransaction(input).ConfigureAwait(false); - _ = await ThirdwebTransaction.WaitForTransactionReceipt(this.Client, this._chainId, txHash).ConfigureAwait(false); + var signedOp = await this.SignUserOp(transactionInput).ConfigureAwait(false); + return await this.SendUserOp(signedOp).ConfigureAwait(false); + } } - public Task GetPersonalWallet() + public async Task ExecuteTransaction(ThirdwebTransactionInput transactionInput) { - return Task.FromResult(this._personalAccount); + var txHash = await this.SendTransaction(transactionInput).ConfigureAwait(false); + return await ThirdwebTransaction.WaitForTransactionReceipt(this.Client, this._chainId, txHash).ConfigureAwait(false); } public async Task GetAddress() @@ -727,6 +992,11 @@ public Task PersonalSign(byte[] rawMessage) throw new NotImplementedException(); } + /// + /// Signs a message with the personal account. If the smart account is deployed, the message will be wrapped 712 and signed by the smart account and verified with 1271. If the smart account is not deployed, it will deploy it first. + /// + /// The message to sign. + /// The signature. public async Task PersonalSign(string message) { if (await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false)) @@ -779,171 +1049,16 @@ public async Task RecoverAddressFromPersonalSign(string message, string : await this.GetAddress().ConfigureAwait(false); } - public async Task IsValidSignature(string message, string signature) - { - try - { - var magicValue = await ThirdwebContract.Read(this._accountContract, "isValidSignature", message.StringToHex(), signature.HexToBytes()).ConfigureAwait(false); - return magicValue.BytesToHex() == new byte[] { 0x16, 0x26, 0xba, 0x7e }.BytesToHex(); - } - catch - { - try - { - var magicValue = await ThirdwebContract - .Read(this._accountContract, "isValidSignature", Encoding.UTF8.GetBytes(message).HashPrefixedMessage(), signature.HexToBytes()) - .ConfigureAwait(false); - return magicValue.BytesToHex() == new byte[] { 0x16, 0x26, 0xba, 0x7e }.BytesToHex(); - } - catch - { - return false; - } - } - } - - public async Task> GetAllAdmins() - { - if (await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false)) - { - throw new InvalidOperationException("Account Permissions are not supported in ZkSync"); - } - - var result = await ThirdwebContract.Read>(this._accountContract, "getAllAdmins").ConfigureAwait(false); - return result ?? new List(); - } - - public async Task> GetAllActiveSigners() - { - if (await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false)) - { - throw new InvalidOperationException("Account Permissions are not supported in ZkSync"); - } - - var result = await ThirdwebContract.Read>(this._accountContract, "getAllActiveSigners").ConfigureAwait(false); - return result ?? new List(); - } - - public async Task CreateSessionKey( - string signerAddress, - List approvedTargets, - string nativeTokenLimitPerTransactionInWei, - string permissionStartTimestamp, - string permissionEndTimestamp, - string reqValidityStartTimestamp, - string reqValidityEndTimestamp - ) - { - if (await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false)) - { - throw new InvalidOperationException("Account Permissions are not supported in ZkSync"); - } - - var request = new SignerPermissionRequest() - { - Signer = signerAddress, - IsAdmin = 0, - ApprovedTargets = approvedTargets, - NativeTokenLimitPerTransaction = BigInteger.Parse(nativeTokenLimitPerTransactionInWei), - PermissionStartTimestamp = BigInteger.Parse(permissionStartTimestamp), - PermissionEndTimestamp = BigInteger.Parse(permissionEndTimestamp), - ReqValidityStartTimestamp = BigInteger.Parse(reqValidityStartTimestamp), - ReqValidityEndTimestamp = BigInteger.Parse(reqValidityEndTimestamp), - Uid = Guid.NewGuid().ToByteArray() - }; - - var signature = await EIP712.GenerateSignature_SmartAccount("Account", "1", this._chainId, await this.GetAddress().ConfigureAwait(false), request, this._personalAccount).ConfigureAwait(false); - // Do it this way to avoid triggering an extra sig from estimation - var data = new Contract(null, this._accountContract.Abi, this._accountContract.Address).GetFunction("setPermissionsForSigner").GetData(request, signature.HexToBytes()); - var txInput = new ThirdwebTransactionInput(this._chainId) - { - To = this._accountContract.Address, - Value = new HexBigInteger(0), - Data = data - }; - var txHash = await this.SendTransaction(txInput).ConfigureAwait(false); - return await ThirdwebTransaction.WaitForTransactionReceipt(this.Client, this._chainId, txHash).ConfigureAwait(false); - } - - public async Task RevokeSessionKey(string signerAddress) - { - return await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false) - ? throw new InvalidOperationException("Account Permissions are not supported in ZkSync") - : await this.CreateSessionKey(signerAddress, new List(), "0", "0", "0", "0", Utils.GetUnixTimeStampIn10Years().ToString()).ConfigureAwait(false); - } - - public async Task AddAdmin(string admin) - { - if (await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false)) - { - throw new InvalidOperationException("Account Permissions are not supported in ZkSync"); - } - - var request = new SignerPermissionRequest() - { - Signer = admin, - IsAdmin = 1, - ApprovedTargets = new List(), - NativeTokenLimitPerTransaction = 0, - PermissionStartTimestamp = Utils.GetUnixTimeStampNow() - 3600, - PermissionEndTimestamp = Utils.GetUnixTimeStampIn10Years(), - ReqValidityStartTimestamp = Utils.GetUnixTimeStampNow() - 3600, - ReqValidityEndTimestamp = Utils.GetUnixTimeStampIn10Years(), - Uid = Guid.NewGuid().ToByteArray() - }; - - var signature = await EIP712.GenerateSignature_SmartAccount("Account", "1", this._chainId, await this.GetAddress(), request, this._personalAccount).ConfigureAwait(false); - var data = new Contract(null, this._accountContract.Abi, this._accountContract.Address).GetFunction("setPermissionsForSigner").GetData(request, signature.HexToBytes()); - var txInput = new ThirdwebTransactionInput(this._chainId) - { - To = this._accountContract.Address, - Value = new HexBigInteger(0), - Data = data - }; - var txHash = await this.SendTransaction(txInput).ConfigureAwait(false); - return await ThirdwebTransaction.WaitForTransactionReceipt(this.Client, this._chainId, txHash).ConfigureAwait(false); - } - - public async Task RemoveAdmin(string admin) - { - if (await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false)) - { - throw new InvalidOperationException("Account Permissions are not supported in ZkSync"); - } - - var request = new SignerPermissionRequest() - { - Signer = admin, - IsAdmin = 2, - ApprovedTargets = new List(), - NativeTokenLimitPerTransaction = 0, - PermissionStartTimestamp = Utils.GetUnixTimeStampNow() - 3600, - PermissionEndTimestamp = Utils.GetUnixTimeStampIn10Years(), - ReqValidityStartTimestamp = Utils.GetUnixTimeStampNow() - 3600, - ReqValidityEndTimestamp = Utils.GetUnixTimeStampIn10Years(), - Uid = Guid.NewGuid().ToByteArray() - }; - - var signature = await EIP712.GenerateSignature_SmartAccount("Account", "1", this._chainId, await this.GetAddress().ConfigureAwait(false), request, this._personalAccount).ConfigureAwait(false); - var data = new Contract(null, this._accountContract.Abi, this._accountContract.Address).GetFunction("setPermissionsForSigner").GetData(request, signature.HexToBytes()); - var txInput = new ThirdwebTransactionInput(this._chainId) - { - To = this._accountContract.Address, - Value = new HexBigInteger(0), - Data = data - }; - var txHash = await this.SendTransaction(txInput).ConfigureAwait(false); - return await ThirdwebTransaction.WaitForTransactionReceipt(this.Client, this._chainId, txHash).ConfigureAwait(false); - } - public Task SignTypedDataV4(string json) { + // TODO: Implement wrapped version return this._personalAccount.SignTypedDataV4(json); } public Task SignTypedDataV4(T data, TypedData typedData) where TDomain : IDomain { + // TODO: Implement wrapped version return this._personalAccount.SignTypedDataV4(data, typedData); } @@ -953,35 +1068,6 @@ public Task RecoverAddressFromTypedDataV4(T data, TypedData< return this._personalAccount.RecoverAddressFromTypedDataV4(data, typedData, signature); } - public async Task EstimateUserOperationGas(ThirdwebTransactionInput transaction) - { - await this.SwitchNetwork(transaction.ChainId.Value).ConfigureAwait(false); - - if (await Utils.IsZkSync(this.Client, this._chainId).ConfigureAwait(false)) - { - throw new Exception("User Operations are not supported in ZkSync"); - } - - var signedOp = await this.SignUserOp(transaction, null, simulation: true).ConfigureAwait(false); - if (signedOp is UserOperationV6) - { - var castSignedOp = signedOp as UserOperationV6; - var cost = castSignedOp.CallGasLimit + castSignedOp.VerificationGasLimit + castSignedOp.PreVerificationGas; - return cost; - } - else if (signedOp is UserOperationV7) - { - var castSignedOp = signedOp as UserOperationV7; - var cost = - castSignedOp.CallGasLimit + castSignedOp.VerificationGasLimit + castSignedOp.PreVerificationGas + castSignedOp.PaymasterVerificationGasLimit + castSignedOp.PaymasterPostOpGasLimit; - return cost; - } - else - { - throw new Exception("Invalid signed operation type"); - } - } - public async Task SignTransaction(ThirdwebTransactionInput transaction) { await this.SwitchNetwork(transaction.ChainId.Value).ConfigureAwait(false); @@ -1066,4 +1152,6 @@ public async Task> GetLinkedAccounts() return await personalWallet.GetLinkedAccounts().ConfigureAwait(false); } } + + #endregion }