diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index 8af4723..689e7fb 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -28,10 +28,10 @@ var inAppWallet = await InAppWallet.Create(client: client, authprovider: AuthProvider.Google); // or email: null, phoneNumber: "+1234567890" // Reset InAppWallet (optional step for testing login flow) -if (await inAppWallet.IsConnected()) -{ - await inAppWallet.Disconnect(); -} +// if (await inAppWallet.IsConnected()) +// { +// await inAppWallet.Disconnect(); +// } // Relog if InAppWallet not logged in if (!await inAppWallet.IsConnected()) @@ -64,10 +64,39 @@ // } } +// Prepare a transaction directly, or with Contract.Prepare +var tx = await ThirdwebTransaction.Create( + client: client, + wallet: privateKeyWallet, + txInput: new ThirdwebTransactionInput() + { + From = await privateKeyWallet.GetAddress(), + To = await privateKeyWallet.GetAddress(), + Value = new HexBigInteger(BigInteger.Zero), + }, + chainId: 300 +); + +// Set zkSync options +tx.SetZkSyncOptions( + new ZkSyncOptions( + // Paymaster contract address + paymaster: "0xbA226d47Cbb2731CBAA67C916c57d68484AA269F", + // IPaymasterFlow interface encoded data + paymasterInput: "0x8c5a344500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" + ) +); + +// Send as usual, it's now gasless! +var txHash = await ThirdwebTransaction.Send(transaction: tx); +Console.WriteLine($"Transaction hash: {txHash}"); + + + // Create smart wallet with InAppWallet signer -var smartWallet = await SmartWallet.Create(client: client, personalWallet: inAppWallet, factoryAddress: "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", gasless: true, chainId: 421614); -var res = await smartWallet.Authenticate("http://localhost:8000", 421614); -Console.WriteLine($"Smart wallet auth result: {res}"); +// var smartWallet = await SmartWallet.Create(client: client, personalWallet: inAppWallet, factoryAddress: "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", gasless: true, chainId: 421614); +// var res = await smartWallet.Authenticate("http://localhost:8000", 421614); +// Console.WriteLine($"Smart wallet auth result: {res}"); // // Grant a session key to pk wallet (advanced use case) // _ = await smartWallet.CreateSessionKey( @@ -124,7 +153,7 @@ // Console.WriteLine($"Transaction receipt: {JsonConvert.SerializeObject(receipt)}"); // // Transaction Builder - raw transfer -// var rawTx = new TransactionInput +// var rawTx = new ThirdwebTransactionInput // { // From = await smartWallet.GetAddress(), // To = await smartWallet.GetAddress(), diff --git a/Thirdweb.Tests/Thirdweb.Contracts.Tests.cs b/Thirdweb.Tests/Thirdweb.Contracts.Tests.cs index 83f9b64..dd2f7eb 100644 --- a/Thirdweb.Tests/Thirdweb.Contracts.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.Contracts.Tests.cs @@ -116,10 +116,17 @@ public async Task WriteTest_PrivateKeyAccount() var pricePerToken = BigInteger.Zero; var allowlistProof = new object[] { new byte[] { }, BigInteger.Zero, BigInteger.Zero, Constants.ADDRESS_ZERO }; var data = new byte[] { }; - var exception = await Assert.ThrowsAsync( - async () => await ThirdwebContract.Write(privateKeyAccount, contract, "claim", 0, receiver, quantity, currency, pricePerToken, allowlistProof, data) - ); - Assert.Contains("insufficient funds", exception.Message); + try + { + var res = await ThirdwebContract.Write(privateKeyAccount, contract, "claim", 0, receiver, quantity, currency, pricePerToken, allowlistProof, data); + Assert.NotNull(res); + Assert.NotNull(res.TransactionHash); + Assert.Equal(66, res.TransactionHash.Length); + } + catch (Exception ex) + { + Assert.Contains("insufficient funds", ex.Message); + } } private async Task GetAccount() diff --git a/Thirdweb.Tests/Thirdweb.PrivateKeyWallet.Tests.cs b/Thirdweb.Tests/Thirdweb.PrivateKeyWallet.Tests.cs index 383e16c..9596da1 100644 --- a/Thirdweb.Tests/Thirdweb.PrivateKeyWallet.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.PrivateKeyWallet.Tests.cs @@ -59,10 +59,27 @@ public async Task EthSign_Success() public async Task EthSign_NullMessage() { var account = await GetAccount(); - var ex = await Assert.ThrowsAsync(() => account.EthSign(null)); + var ex = await Assert.ThrowsAsync(() => account.EthSign(null as string)); Assert.Equal("Message to sign cannot be null. (Parameter 'message')", ex.Message); } + [Fact] + public async Task EthSignRaw_Success() + { + var account = await GetAccount(); + var message = "Hello, World!"; + var signature = await account.EthSign(System.Text.Encoding.UTF8.GetBytes(message)); + Assert.True(signature.Length == 132); + } + + [Fact] + public async Task EthSignRaw_NullMessage() + { + var account = await GetAccount(); + var ex = await Assert.ThrowsAsync(() => account.EthSign(null as byte[])); + Assert.Equal("Message to sign cannot be null. (Parameter 'rawMessage')", ex.Message); + } + [Fact] public async Task PersonalSign_Success() { @@ -155,7 +172,7 @@ public async Task SignTypedDataV4_Typed_NullData() public async Task SignTransaction_Success() { var account = await GetAccount(); - var transaction = new TransactionInput + var transaction = new ThirdwebTransactionInput { From = await account.GetAddress(), To = Constants.ADDRESS_ZERO, @@ -173,7 +190,7 @@ public async Task SignTransaction_Success() public async Task SignTransaction_NoFrom_Success() { var account = await GetAccount(); - var transaction = new TransactionInput + var transaction = new ThirdwebTransactionInput { To = Constants.ADDRESS_ZERO, // Value = new HexBigInteger(0), @@ -198,7 +215,7 @@ public async Task SignTransaction_NullTransaction() public async Task SignTransaction_NoNonce() { var account = await GetAccount(); - var transaction = new TransactionInput + var transaction = new ThirdwebTransactionInput { From = await account.GetAddress(), To = Constants.ADDRESS_ZERO, @@ -214,7 +231,7 @@ public async Task SignTransaction_NoNonce() public async Task SignTransaction_WrongFrom() { var account = await GetAccount(); - var transaction = new TransactionInput + var transaction = new ThirdwebTransactionInput { From = Constants.ADDRESS_ZERO, To = Constants.ADDRESS_ZERO, @@ -231,7 +248,7 @@ public async Task SignTransaction_WrongFrom() public async Task SignTransaction_NoGasPrice() { var account = await GetAccount(); - var transaction = new TransactionInput + var transaction = new ThirdwebTransactionInput { From = await account.GetAddress(), To = Constants.ADDRESS_ZERO, @@ -241,14 +258,14 @@ public async Task SignTransaction_NoGasPrice() Nonce = new HexBigInteger(99999999999) }; var ex = await Assert.ThrowsAsync(() => account.SignTransaction(transaction, 421614)); - Assert.Equal("Transaction gas price must be set for legacy transactions", ex.Message); + Assert.Equal("Transaction MaxPriorityFeePerGas and MaxFeePerGas must be set for EIP-1559 transactions", ex.Message); } [Fact] public async Task SignTransaction_1559_Success() { var account = await GetAccount(); - var transaction = new TransactionInput + var transaction = new ThirdwebTransactionInput { From = await account.GetAddress(), To = Constants.ADDRESS_ZERO, @@ -256,7 +273,6 @@ public async Task SignTransaction_1559_Success() Gas = new HexBigInteger(21000), Data = "0x", Nonce = new HexBigInteger(99999999999), - Type = new HexBigInteger(2), MaxFeePerGas = new HexBigInteger(10000000000), MaxPriorityFeePerGas = new HexBigInteger(10000000000) }; @@ -268,7 +284,7 @@ public async Task SignTransaction_1559_Success() public async Task SignTransaction_1559_NoMaxFeePerGas() { var account = await GetAccount(); - var transaction = new TransactionInput + var transaction = new ThirdwebTransactionInput { From = await account.GetAddress(), To = Constants.ADDRESS_ZERO, @@ -276,7 +292,6 @@ public async Task SignTransaction_1559_NoMaxFeePerGas() Gas = new HexBigInteger(21000), Data = "0x", Nonce = new HexBigInteger(99999999999), - Type = new HexBigInteger(2), MaxPriorityFeePerGas = new HexBigInteger(10000000000) }; var ex = await Assert.ThrowsAsync(() => account.SignTransaction(transaction, 421614)); @@ -287,7 +302,7 @@ public async Task SignTransaction_1559_NoMaxFeePerGas() public async Task SignTransaction_1559_NoMaxPriorityFeePerGas() { var account = await GetAccount(); - var transaction = new TransactionInput + var transaction = new ThirdwebTransactionInput { From = await account.GetAddress(), To = Constants.ADDRESS_ZERO, @@ -295,7 +310,6 @@ public async Task SignTransaction_1559_NoMaxPriorityFeePerGas() Gas = new HexBigInteger(21000), Data = "0x", Nonce = new HexBigInteger(99999999999), - Type = new HexBigInteger(2), MaxFeePerGas = new HexBigInteger(10000000000) }; var ex = await Assert.ThrowsAsync(() => account.SignTransaction(transaction, 421614)); diff --git a/Thirdweb.Tests/Thirdweb.SmartWallet.Tests.cs b/Thirdweb.Tests/Thirdweb.SmartWallet.Tests.cs index 2efb62e..f2c62e8 100644 --- a/Thirdweb.Tests/Thirdweb.SmartWallet.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.SmartWallet.Tests.cs @@ -74,7 +74,7 @@ public async Task SendTransaction_Success() { var account = await GetSmartAccount(); var tx = await account.SendTransaction( - new TransactionInput() + new ThirdwebTransactionInput() { From = await account.GetAddress(), To = await account.GetAddress(), @@ -91,7 +91,7 @@ public async Task SendTransaction_ClientBundleId_Success() var privateKeyAccount = await PrivateKeyWallet.Create(client, _testPrivateKey); var smartAccount = await SmartWallet.Create(client, personalWallet: privateKeyAccount, factoryAddress: "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", gasless: true, chainId: 421614); var tx = await smartAccount.SendTransaction( - new TransactionInput() + new ThirdwebTransactionInput() { From = await smartAccount.GetAddress(), To = await smartAccount.GetAddress(), diff --git a/Thirdweb.Tests/Thirdweb.Transactions.Tests.cs b/Thirdweb.Tests/Thirdweb.Transactions.Tests.cs index e5620e6..8edb1c2 100644 --- a/Thirdweb.Tests/Thirdweb.Transactions.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.Transactions.Tests.cs @@ -13,9 +13,9 @@ private async Task CreateSampleTransaction() { var client = ThirdwebClient.Create(secretKey: _secretKey); var wallet = await PrivateKeyWallet.Create(client, _testPrivateKey); - var chainId = new BigInteger(1); + var chainId = new BigInteger(421614); - var transaction = await ThirdwebTransaction.Create(client, wallet, new TransactionInput(), chainId); + var transaction = await ThirdwebTransaction.Create(client, wallet, new ThirdwebTransactionInput(), chainId); return transaction; } @@ -24,8 +24,8 @@ public async Task Create_ValidatesInputParameters() { var client = ThirdwebClient.Create(secretKey: _secretKey); var wallet = await PrivateKeyWallet.Create(client, _testPrivateKey); - var txInput = new TransactionInput() { From = await wallet.GetAddress() }; - var chainId = new BigInteger(1); + var txInput = new ThirdwebTransactionInput() { From = await wallet.GetAddress() }; + var chainId = new BigInteger(421614); var transaction = await ThirdwebTransaction.Create(client, wallet, txInput, chainId); Assert.NotNull(transaction); } @@ -35,7 +35,7 @@ public async Task Create_ThrowsOnInvalidAddress() { var client = ThirdwebClient.Create(secretKey: _secretKey); var wallet = await PrivateKeyWallet.Create(client, _testPrivateKey); - var txInput = new TransactionInput() { From = "0x123" }; + var txInput = new ThirdwebTransactionInput() { From = "0x123" }; var ex = await Assert.ThrowsAsync(() => ThirdwebTransaction.Create(client, wallet, txInput, BigInteger.Zero)); Assert.Contains("Transaction sender (from) must match wallet address", ex.Message); } @@ -45,7 +45,7 @@ public async Task Create_ThrowsOnInvalidChainId() { var client = ThirdwebClient.Create(secretKey: _secretKey); var wallet = await PrivateKeyWallet.Create(client, _testPrivateKey); - var txInput = new TransactionInput(); + var txInput = new ThirdwebTransactionInput(); _ = await Assert.ThrowsAsync(() => ThirdwebTransaction.Create(client, wallet, txInput, BigInteger.Zero)); } @@ -75,7 +75,7 @@ public async Task SetValue_SetsValue() } [Fact] - public async Task SetValue_SetsData() + public async Task SetData_SetsData() { var transaction = await CreateSampleTransaction(); var data = "0x123456"; @@ -84,7 +84,7 @@ public async Task SetValue_SetsData() } [Fact] - public async Task SetValue_SetsGasPrice() + public async Task SetGasPrice_SetsGasPrice() { var transaction = await CreateSampleTransaction(); var gas = new BigInteger(1000); @@ -92,6 +92,37 @@ public async Task SetValue_SetsGasPrice() Assert.Equal(gas.ToHexBigInteger(), transaction.Input.GasPrice); } + [Fact] + public async Task SetMaxFeePerGas_SetsMaxFeePerGas() + { + var transaction = await CreateSampleTransaction(); + var gas = new BigInteger(1000); + _ = transaction.SetMaxFeePerGas(gas); + Assert.Equal(gas.ToHexBigInteger(), transaction.Input.MaxFeePerGas); + } + + [Fact] + public async Task SetMaxPriorityFeePerGas_SetsMaxPriorityFeePerGas() + { + var transaction = await CreateSampleTransaction(); + var gas = new BigInteger(1000); + _ = transaction.SetMaxPriorityFeePerGas(gas); + Assert.Equal(gas.ToHexBigInteger(), transaction.Input.MaxPriorityFeePerGas); + } + + [Fact] + public async Task SetAllGasParams_ThrowsInvalid() + { + var transaction = await CreateSampleTransaction(); + var gas = new BigInteger(1000); + _ = transaction.SetTo(Constants.ADDRESS_ZERO); + _ = transaction.SetGasPrice(gas); + _ = transaction.SetMaxFeePerGas(gas); + _ = transaction.SetMaxPriorityFeePerGas(gas); + var ex = await Assert.ThrowsAsync(() => ThirdwebTransaction.Send(transaction)); + Assert.Contains("Transaction GasPrice and MaxFeePerGas/MaxPriorityFeePerGas cannot be set at the same time", ex.Message); + } + [Fact] public async Task Sign_SmartWallet_SignsTransaction() { @@ -101,7 +132,7 @@ public async Task Sign_SmartWallet_SignsTransaction() var transaction = await ThirdwebTransaction.Create( client, smartAccount, - new TransactionInput() + new ThirdwebTransactionInput() { To = Constants.ADDRESS_ZERO, Value = new HexBigInteger(0), @@ -132,6 +163,44 @@ public async Task Send_CorrectlyHandlesNonce() Assert.Equal("123", transaction.Input.Nonce.Value.ToString()); } + [Fact] + public async Task Send_ZkSync_TransfersGaslessly() + { + var transaction = await CreateSampleTransaction(); + _ = transaction.SetChainId(300); + _ = transaction.SetTo("0xbA226d47Cbb2731CBAA67C916c57d68484AA269F"); + _ = transaction.SetValue(BigInteger.Zero); + _ = transaction.SetZkSyncOptions( + new ZkSyncOptions( + paymaster: "0xbA226d47Cbb2731CBAA67C916c57d68484AA269F", + paymasterInput: "0x8c5a344500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000", + gasPerPubdataByteLimit: 50000, + factoryDeps: new List() + ) + ); + var receipt = await ThirdwebTransaction.SendAndWaitForTransactionReceipt(transaction); + Assert.NotNull(receipt); + Assert.StartsWith("0x", receipt.TransactionHash); + } + + [Fact] + public async Task Send_ZkSync_NoGasPerPubFactoryDepsTransfersGaslessly() + { + var transaction = await CreateSampleTransaction(); + _ = transaction.SetChainId(300); + _ = transaction.SetTo("0xbA226d47Cbb2731CBAA67C916c57d68484AA269F"); + _ = transaction.SetValue(BigInteger.Zero); + _ = transaction.SetZkSyncOptions( + new ZkSyncOptions( + paymaster: "0xbA226d47Cbb2731CBAA67C916c57d68484AA269F", + paymasterInput: "0x8c5a344500000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000000" + ) + ); + var receipt = await ThirdwebTransaction.SendAndWaitForTransactionReceipt(transaction); + Assert.NotNull(receipt); + Assert.StartsWith("0x", receipt.TransactionHash); + } + [Fact] public async Task EstimateTotalCosts_CalculatesCostsCorrectly() { @@ -199,13 +268,13 @@ public async Task EstimateGasCosts_SmartWalletHigherThanPrivateKeyWallet() var privateKeyAccount = await PrivateKeyWallet.Create(client, _testPrivateKey); var smartAccount = await SmartWallet.Create(client, personalWallet: privateKeyAccount, factoryAddress: "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", gasless: true, chainId: 421614); - var transaction = await ThirdwebTransaction.Create(client, smartAccount, new TransactionInput(), 421614); + var transaction = await ThirdwebTransaction.Create(client, smartAccount, new ThirdwebTransactionInput(), 421614); _ = transaction.SetTo(Constants.ADDRESS_ZERO); _ = transaction.SetValue(new BigInteger(1000)); var smartCosts = await ThirdwebTransaction.EstimateGasCosts(transaction); - transaction = await ThirdwebTransaction.Create(client, privateKeyAccount, new TransactionInput(), 421614); + transaction = await ThirdwebTransaction.Create(client, privateKeyAccount, new ThirdwebTransactionInput(), 421614); _ = transaction.SetTo(Constants.ADDRESS_ZERO); _ = transaction.SetValue(new BigInteger(1000)); @@ -230,6 +299,19 @@ public async Task EstimateTotalCosts_HigherThanGasCostsByValue() Assert.True(costs[0].wei - costs[1].wei == transaction.Input.Value.Value); } + [Fact] + public async Task EstimateGasFees_ReturnsCorrectly() + { + var transaction = await CreateSampleTransaction(); + _ = transaction.SetValue(new BigInteger(1000)); + _ = transaction.SetTo(Constants.ADDRESS_ZERO); + + (var maxFee, var maxPrio) = await ThirdwebTransaction.EstimateGasFees(transaction); + + Assert.NotEqual(BigInteger.Zero, maxFee); + Assert.NotEqual(BigInteger.Zero, maxPrio); + } + [Fact] public async Task EstimateGasPrice_BumpsCorrectly() { @@ -257,7 +339,7 @@ public async Task Simulate_ReturnsData() var client = ThirdwebClient.Create(secretKey: _secretKey); var privateKeyAccount = await PrivateKeyWallet.Create(client, _testPrivateKey); var smartAccount = await SmartWallet.Create(client, personalWallet: privateKeyAccount, factoryAddress: "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", gasless: true, chainId: 421614); - var transaction = await ThirdwebTransaction.Create(client, smartAccount, new TransactionInput(), 421614); + var transaction = await ThirdwebTransaction.Create(client, smartAccount, new ThirdwebTransactionInput(), 421614); _ = transaction.SetValue(new BigInteger(0)); _ = transaction.SetGasLimit(250000); diff --git a/Thirdweb.Tests/Thirdweb.Utils.Tests.cs b/Thirdweb.Tests/Thirdweb.Utils.Tests.cs index d850bf6..b2ac60d 100644 --- a/Thirdweb.Tests/Thirdweb.Utils.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.Utils.Tests.cs @@ -31,6 +31,17 @@ public void HashPrefixedMessage() Assert.Equal("0xc8ee0d506e864589b799a645ddb88b08f5d39e8049f9f702b3b61fa15e55fc73", hashStr); } + [Fact] + public void HashMessage() + { + var messageStr = "Hello, World!"; + var message = System.Text.Encoding.UTF8.GetBytes(messageStr); + var hashStr = Utils.HashMessage(messageStr); + var hash = Utils.HashMessage(message); + Assert.Equal(hashStr, Utils.BytesToHex(hash)); + Assert.Equal("0xacaf3289d7b601cbd114fb36c4d29c85bbfd5e133f14cb355c3fd8d99367964f", hashStr); + } + [Fact] public void BytesToHex() { diff --git a/Thirdweb.Tests/Thirdweb.Wallets.Tests.cs b/Thirdweb.Tests/Thirdweb.Wallets.Tests.cs index 5aec1aa..048749f 100644 --- a/Thirdweb.Tests/Thirdweb.Wallets.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.Wallets.Tests.cs @@ -23,6 +23,15 @@ public async Task GetAddress() Assert.Equal(await wallet.GetAddress(), await wallet.GetAddress()); } + [Fact] + public async Task EthSignRaw() + { + var wallet = await GetAccount(); + var message = "Hello, world!"; + var signature = await wallet.EthSign(System.Text.Encoding.UTF8.GetBytes(message)); + Assert.NotNull(signature); + } + [Fact] public async Task EthSign() { @@ -104,7 +113,7 @@ await wallet.GetAddress(), public async Task SignTransaction() { var wallet = await GetAccount(); - var transaction = new TransactionInput + var transaction = new ThirdwebTransactionInput { To = await wallet.GetAddress(), Data = "0x", diff --git a/Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs b/Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs index 70472d1..cb45ef6 100644 --- a/Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs +++ b/Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs @@ -68,7 +68,7 @@ public static async Task Prepare(IThirdwebWallet wallet, Th var service = new Nethereum.Contracts.Contract(null, contract.Abi, contract.Address); var function = service.GetFunction(method); var data = function.GetData(parameters); - var transaction = new TransactionInput + var transaction = new ThirdwebTransactionInput { From = await wallet.GetAddress(), To = contract.Address, diff --git a/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs b/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs index 8979dac..023ff3a 100644 --- a/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs +++ b/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs @@ -5,6 +5,7 @@ using Nethereum.Contracts; using Nethereum.ABI.FunctionEncoding; using Nethereum.Hex.HexConvertors.Extensions; +using Newtonsoft.Json.Linq; namespace Thirdweb { @@ -16,12 +17,12 @@ public struct TotalCosts public class ThirdwebTransaction { - public TransactionInput Input { get; private set; } + public ThirdwebTransactionInput Input { get; private set; } private readonly ThirdwebClient _client; private readonly IThirdwebWallet _wallet; - private ThirdwebTransaction(ThirdwebClient client, IThirdwebWallet wallet, TransactionInput txInput, BigInteger chainId) + private ThirdwebTransaction(ThirdwebClient client, IThirdwebWallet wallet, ThirdwebTransactionInput txInput, BigInteger chainId) { Input = txInput; _client = client; @@ -29,20 +30,33 @@ private ThirdwebTransaction(ThirdwebClient client, IThirdwebWallet wallet, Trans Input.ChainId = chainId.ToHexBigInteger(); } - public static async Task Create(ThirdwebClient client, IThirdwebWallet wallet, TransactionInput txInput, BigInteger chainId) + public static async Task Create(ThirdwebClient client, IThirdwebWallet wallet, ThirdwebTransactionInput txInput, BigInteger chainId) { var address = await wallet.GetAddress(); txInput.From ??= address; txInput.Data ??= "0x"; - return address != txInput.From - ? throw new ArgumentException("Transaction sender (from) must match wallet address") - : client == null - ? throw new ArgumentNullException(nameof(client)) - : wallet == null - ? throw new ArgumentNullException(nameof(wallet)) - : chainId == 0 - ? throw new ArgumentException("Invalid Chain ID") - : new ThirdwebTransaction(client, wallet, txInput, chainId); + + if (address != txInput.From) + { + throw new ArgumentException("Transaction sender (from) must match wallet address"); + } + + if (client == null) + { + throw new ArgumentNullException(nameof(client)); + } + + if (wallet == null) + { + throw new ArgumentNullException(nameof(wallet)); + } + + if (chainId == 0) + { + throw new ArgumentException("Invalid Chain ID"); + } + + return new ThirdwebTransaction(client, wallet, txInput, chainId); } public override string ToString() @@ -86,6 +100,30 @@ public ThirdwebTransaction SetNonce(BigInteger nonce) return this; } + public ThirdwebTransaction SetMaxFeePerGas(BigInteger maxFeePerGas) + { + Input.MaxFeePerGas = maxFeePerGas.ToHexBigInteger(); + return this; + } + + public ThirdwebTransaction SetMaxPriorityFeePerGas(BigInteger maxPriorityFeePerGas) + { + Input.MaxPriorityFeePerGas = maxPriorityFeePerGas.ToHexBigInteger(); + return this; + } + + public ThirdwebTransaction SetChainId(BigInteger chainId) + { + Input.ChainId = chainId.ToHexBigInteger(); + return this; + } + + public ThirdwebTransaction SetZkSyncOptions(ZkSyncOptions zkSyncOptions) + { + Input.ZkSync = zkSyncOptions; + return this; + } + public static async Task EstimateGasCosts(ThirdwebTransaction transaction) { var gasPrice = transaction.Input.GasPrice?.Value ?? await EstimateGasPrice(transaction); @@ -108,6 +146,53 @@ public static async Task EstimateGasPrice(ThirdwebTransaction transa return withBump ? hex.Value * 10 / 9 : hex.Value; } + public static async Task<(BigInteger, BigInteger)> EstimateGasFees(ThirdwebTransaction transaction, bool withBump = true) + { + var rpc = ThirdwebRPC.GetRpcInstance(transaction._client, transaction.Input.ChainId.Value); + var chainId = transaction.Input.ChainId.Value; + + if (IsZkSyncTransaction(transaction)) + { + var fees = await rpc.SendRequestAsync("zks_estimateFee", transaction.Input, "latest"); + var maxFee = fees["max_fee_per_gas"].ToObject().Value; + var maxPriorityFee = fees["max_priority_fee_per_gas"].ToObject().Value; + return (maxFee, maxPriorityFee == 0 ? maxFee : maxPriorityFee); + } + + var gasPrice = await EstimateGasPrice(transaction, withBump); + + try + { + var block = await rpc.SendRequestAsync("eth_getBlockByNumber", "latest", true); + var maxPriorityFeePerGas = await rpc.SendRequestAsync("eth_maxPriorityFeePerGas"); + var baseBlockFee = block["result"]?["baseFeePerGas"]?.ToObject() ?? new BigInteger(100); + + if (chainId == 42220 || chainId == 44787 || chainId == 62320) + { + return (gasPrice, gasPrice); + } + + if (!maxPriorityFeePerGas.HasValue) + { + maxPriorityFeePerGas = new BigInteger(2); + } + + var preferredMaxPriorityFeePerGas = maxPriorityFeePerGas.Value * 10 / 9; + var maxFeePerGas = (baseBlockFee * 2) + preferredMaxPriorityFeePerGas; + + if (withBump) + { + maxFeePerGas *= 10 / 9; + } + + return (maxFeePerGas, preferredMaxPriorityFeePerGas); + } + catch + { + return (gasPrice, gasPrice); + } + } + public static async Task Simulate(ThirdwebTransaction transaction) { var rpc = ThirdwebRPC.GetRpcInstance(transaction._client, transaction.Input.ChainId.Value); @@ -125,7 +210,9 @@ public static async Task EstimateGasLimit(ThirdwebTransaction transa else { var rpc = ThirdwebRPC.GetRpcInstance(transaction._client, transaction.Input.ChainId.Value); - var hex = await rpc.SendRequestAsync("eth_estimateGas", transaction.Input, "latest"); + var hex = IsZkSyncTransaction(transaction) + ? (await rpc.SendRequestAsync("zks_estimateFee", transaction.Input, "latest"))["gas_limit"].ToString() + : await rpc.SendRequestAsync("eth_estimateGas", transaction.Input, "latest"); return new HexBigInteger(hex).Value; } } @@ -142,31 +229,69 @@ public static async Task Send(ThirdwebTransaction transaction) throw new ArgumentException("To address must be provided"); } + if (transaction.Input.GasPrice != null && (transaction.Input.MaxFeePerGas != null || transaction.Input.MaxPriorityFeePerGas != null)) + { + throw new InvalidOperationException("Transaction GasPrice and MaxFeePerGas/MaxPriorityFeePerGas cannot be set at the same time"); + } + transaction.Input.From ??= await transaction._wallet.GetAddress(); transaction.Input.Value ??= new HexBigInteger(0); transaction.Input.Data ??= "0x"; - transaction.Input.MaxFeePerGas = null; - transaction.Input.MaxPriorityFeePerGas = null; transaction.Input.Gas ??= new HexBigInteger(await EstimateGasLimit(transaction)); - transaction.Input.GasPrice ??= new HexBigInteger(await EstimateGasPrice(transaction)); + if (transaction.Input.GasPrice == null) + { + var (maxFeePerGas, maxPriorityFeePerGas) = await EstimateGasFees(transaction); + transaction.Input.MaxFeePerGas ??= maxFeePerGas.ToHexBigInteger(); + transaction.Input.MaxPriorityFeePerGas ??= maxPriorityFeePerGas.ToHexBigInteger(); + } + else + { + transaction.Input.MaxFeePerGas = null; + transaction.Input.MaxPriorityFeePerGas = null; + } var rpc = ThirdwebRPC.GetRpcInstance(transaction._client, transaction.Input.ChainId.Value); string hash; - switch (transaction._wallet.AccountType) - { - case ThirdwebAccountType.PrivateKeyAccount: - transaction.Input.Nonce ??= new HexBigInteger(await rpc.SendRequestAsync("eth_getTransactionCount", await transaction._wallet.GetAddress(), "latest")); - var signedTx = await Sign(transaction); - hash = await rpc.SendRequestAsync("eth_sendRawTransaction", signedTx); - break; - case ThirdwebAccountType.SmartAccount: - var smartAccount = transaction._wallet as SmartWallet; - hash = await smartAccount.SendTransaction(transaction.Input); - break; - default: - throw new NotImplementedException("Account type not supported"); - } - Console.WriteLine($"Transaction hash: {hash}"); + if (IsZkSyncTransaction(transaction)) + { + var zkTx = new AccountAbstraction.ZkSyncAATransaction + { + TxType = 0x71, + From = new HexBigInteger(transaction.Input.From).Value, + To = new HexBigInteger(transaction.Input.To).Value, + GasLimit = transaction.Input.Gas.Value, + GasPerPubdataByteLimit = 50000, + MaxFeePerGas = transaction.Input.MaxFeePerGas?.Value ?? transaction.Input.GasPrice.Value, + MaxPriorityFeePerGas = transaction.Input.MaxPriorityFeePerGas?.Value ?? transaction.Input.GasPrice.Value, + Paymaster = transaction.Input.ZkSync.Value.Paymaster, + Nonce = transaction.Input.Nonce ?? new HexBigInteger(await rpc.SendRequestAsync("eth_getTransactionCount", transaction.Input.From, "latest")), + Value = transaction.Input.Value.Value, + Data = transaction.Input.Data.HexToByteArray(), + FactoryDeps = transaction.Input.ZkSync.Value.FactoryDeps, + PaymasterInput = transaction.Input.ZkSync.Value.PaymasterInput + }; + Console.WriteLine("zkTx: " + JsonConvert.SerializeObject(zkTx)); + var zkTxSigned = await EIP712.GenerateSignature_ZkSyncTransaction("zkSync", "2", transaction.Input.ChainId.Value, zkTx, transaction._wallet); + hash = await rpc.SendRequestAsync("eth_sendRawTransaction", zkTxSigned); + } + else + { + switch (transaction._wallet.AccountType) + { + case ThirdwebAccountType.PrivateKeyAccount: + transaction.Input.Nonce ??= new HexBigInteger(await rpc.SendRequestAsync("eth_getTransactionCount", await transaction._wallet.GetAddress(), "latest")); + var signedTx = await Sign(transaction); + hash = await rpc.SendRequestAsync("eth_sendRawTransaction", signedTx); + break; + case ThirdwebAccountType.SmartAccount: + + var smartAccount = transaction._wallet as SmartWallet; + hash = await smartAccount.SendTransaction(transaction.Input); + break; + default: + throw new NotImplementedException("Account type not supported"); + } + } return hash; } @@ -218,5 +343,13 @@ public static async Task WaitForTransactionReceipt(ThirdwebC return receipt; } + + private static bool IsZkSyncTransaction(ThirdwebTransaction transaction) + { + return (transaction.Input.ChainId.Value.Equals(324) || transaction.Input.ChainId.Value.Equals(300)) + && transaction.Input.ZkSync.HasValue + && transaction.Input.ZkSync.Value.Paymaster != 0 + && transaction.Input.ZkSync.Value.PaymasterInput != null; + } } } diff --git a/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs b/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs new file mode 100644 index 0000000..6b15368 --- /dev/null +++ b/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs @@ -0,0 +1,84 @@ +using System.Numerics; +using Nethereum.Hex.HexConvertors.Extensions; +using Nethereum.Hex.HexTypes; +using Newtonsoft.Json; + +namespace Thirdweb +{ + public class ThirdwebTransactionInput + { + public ThirdwebTransactionInput() { } + + [JsonProperty(PropertyName = "nonce")] + public HexBigInteger Nonce { get; set; } + + private string _from; + private string _to; + private string _data; + + [JsonProperty(PropertyName = "from")] + public string From + { + get => _from.EnsureHexPrefix(); + set => _from = value; + } + + [JsonProperty(PropertyName = "to")] + public string To + { + get => _to.EnsureHexPrefix(); + set => _to = value; + } + + [JsonProperty(PropertyName = "gas")] + public HexBigInteger Gas { get; set; } + + [JsonProperty(PropertyName = "gasPrice")] + public HexBigInteger GasPrice { get; set; } + + [JsonProperty(PropertyName = "value")] + public HexBigInteger Value { get; set; } + + [JsonProperty(PropertyName = "data")] + public string Data + { + get => _data.EnsureHexPrefix(); + set => _data = value; + } + + [JsonProperty(PropertyName = "maxFeePerGas")] + public HexBigInteger MaxFeePerGas { get; set; } + + [JsonProperty(PropertyName = "maxPriorityFeePerGas")] + public HexBigInteger MaxPriorityFeePerGas { get; set; } + + [JsonProperty(PropertyName = "chainId")] + public HexBigInteger ChainId { get; set; } + + [JsonProperty(PropertyName = "zkSyncOptions", NullValueHandling = NullValueHandling.Ignore)] + public ZkSyncOptions? ZkSync { get; set; } + } + + public struct ZkSyncOptions + { + [JsonProperty(PropertyName = "gasPerPubdataByteLimit")] + public BigInteger GasPerPubdataByteLimit { get; set; } + + [JsonProperty(PropertyName = "factoryDeps")] + public List FactoryDeps { get; set; } + + [JsonProperty(PropertyName = "paymaster")] + public BigInteger Paymaster { get; set; } + + [JsonProperty(PropertyName = "paymasterInput")] + public byte[] PaymasterInput { get; set; } + + public ZkSyncOptions(string paymaster, string paymasterInput, BigInteger? gasPerPubdataByteLimit = null, List factoryDeps = null) + { + Paymaster = new HexBigInteger(paymaster).Value; + PaymasterInput = paymasterInput.HexToByteArray(); + GasPerPubdataByteLimit = gasPerPubdataByteLimit ?? new BigInteger(50000); + FactoryDeps = factoryDeps ?? new List(); + } + } +} diff --git a/Thirdweb/Thirdweb.Utils/Utils.cs b/Thirdweb/Thirdweb.Utils/Utils.cs index 1b96423..f32bcbf 100644 --- a/Thirdweb/Thirdweb.Utils/Utils.cs +++ b/Thirdweb/Thirdweb.Utils/Utils.cs @@ -4,6 +4,7 @@ using System.Text; using Nethereum.Hex.HexConvertors.Extensions; using Nethereum.Signer; +using Nethereum.Util; namespace Thirdweb { @@ -40,6 +41,16 @@ public static string HashPrefixedMessage(this string message) return signer.HashPrefixedMessage(Encoding.UTF8.GetBytes(message)).ToHex(true); } + public static byte[] HashMessage(this byte[] messageBytes) + { + return Sha3Keccack.Current.CalculateHash(messageBytes); + } + + public static string HashMessage(this string message) + { + return Sha3Keccack.Current.CalculateHash(Encoding.UTF8.GetBytes(message)).ToHex(true); + } + public static string BytesToHex(byte[] bytes) { return bytes.ToHex(true); diff --git a/Thirdweb/Thirdweb.Wallets/EIP712.cs b/Thirdweb/Thirdweb.Wallets/EIP712.cs index f845b3b..b0e2539 100644 --- a/Thirdweb/Thirdweb.Wallets/EIP712.cs +++ b/Thirdweb/Thirdweb.Wallets/EIP712.cs @@ -1,5 +1,15 @@ using System.Numerics; using Nethereum.ABI.EIP712; +using Nethereum.ABI.FunctionEncoding.Attributes; +using Nethereum.Hex.HexConvertors.Extensions; +using Nethereum.Hex.HexTypes; +using Nethereum.Model; +using Nethereum.RLP; +using Nethereum.Signer; +using Nethereum.Signer.Crypto; +using Nethereum.Signer.EIP712; +using Nethereum.Util; +using Newtonsoft.Json; namespace Thirdweb { @@ -32,6 +42,20 @@ IThirdwebWallet signer return await signer.SignTypedDataV4(accountMessage, typedData); } + public static async Task GenerateSignature_ZkSyncTransaction( + string domainName, + string version, + BigInteger chainId, + AccountAbstraction.ZkSyncAATransaction transaction, + IThirdwebWallet signer + ) + { + var typedData = GetTypedDefinition_ZkSyncTransaction(domainName, version, chainId); + var signatureHex = await signer.SignTypedDataV4(transaction, typedData); + var signatureRaw = EthECDSASignatureFactory.ExtractECDSASignature(signatureHex); + return SerializeEip712(transaction, signatureRaw, chainId); + } + public static TypedData GetTypedDefinition_SmartAccount(string domainName, string version, BigInteger chainId, string verifyingContract) { return new TypedData @@ -63,5 +87,55 @@ public static TypedData GetTypedDefinition_SmartAccount_AccountMessage(s PrimaryType = nameof(AccountAbstraction.AccountMessage), }; } + + public static TypedData GetTypedDefinition_ZkSyncTransaction(string domainName, string version, BigInteger chainId) + { + return new TypedData + { + Domain = new DomainWithNameVersionAndChainId + { + Name = domainName, + Version = version, + ChainId = chainId, + }, + Types = MemberDescriptionFactory.GetTypesMemberDescription(typeof(DomainWithNameVersionAndChainId), typeof(AccountAbstraction.ZkSyncAATransaction)), + PrimaryType = "Transaction", + }; + } + + private static string SerializeEip712(AccountAbstraction.ZkSyncAATransaction transaction, EthECDSASignature signature, BigInteger chainId) + { + if (chainId == 0) + { + throw new ArgumentException("Chain ID must be provided for EIP712 transactions!"); + } + + var fields = new List + { + transaction.Nonce.ToByteArray(isUnsigned: true, isBigEndian: true), + transaction.MaxPriorityFeePerGas.ToByteArray(isUnsigned: true, isBigEndian: true), + transaction.MaxFeePerGas.ToByteArray(isUnsigned: true, isBigEndian: true), + transaction.GasLimit.ToByteArray(isUnsigned: true, isBigEndian: true), + transaction.To.ToByteArray(isUnsigned: true, isBigEndian: true), + transaction.Value == 0 ? new byte[0] : transaction.Value.ToByteArray(isUnsigned: true, isBigEndian: true), + transaction.Data == null ? new byte[0] : transaction.Data, + }; + + fields.Add(signature.IsVSignedForYParity() ? new byte[] { 0x1b } : new byte[] { 0x1c }); + fields.Add(signature.R); + fields.Add(signature.S); + + fields.Add(chainId.ToByteArray(isUnsigned: true, isBigEndian: true)); + fields.Add(transaction.From.ToByteArray(isUnsigned: true, isBigEndian: true)); + + // Add meta + fields.Add(transaction.GasPerPubdataByteLimit.ToByteArray(isUnsigned: true, isBigEndian: true)); + fields.Add(new byte[] { }); // TODO: FactoryDeps + fields.Add(signature.CreateStringSignature().HexToByteArray()); + // add array of rlp encoded paymaster/paymasterinput + fields.Add(RLP.EncodeElement(transaction.Paymaster.ToByteArray(isUnsigned: true, isBigEndian: true)).Concat(RLP.EncodeElement(transaction.PaymasterInput)).ToArray()); + + return "0x71" + RLP.EncodeDataItemsAsElementOrListAndCombineAsList(fields.ToArray(), new int[] { 13, 15 }).ToHex(); + } } } diff --git a/Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs b/Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs index 2832c5d..03f7dee 100644 --- a/Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs @@ -9,6 +9,7 @@ public interface IThirdwebWallet { public ThirdwebAccountType AccountType { get; } public Task GetAddress(); + public Task EthSign(byte[] rawMessage); public Task EthSign(string message); public Task PersonalSign(byte[] rawMessage); public Task PersonalSign(string message); @@ -16,7 +17,7 @@ public interface IThirdwebWallet public Task SignTypedDataV4(T data, TypedData typedData) where TDomain : IDomain; public Task IsConnected(); - public Task SignTransaction(TransactionInput transaction, BigInteger chainId); + public Task SignTransaction(ThirdwebTransactionInput transaction, BigInteger chainId); public Task Authenticate(string domain, BigInteger chainId, string authPayloadPath = "/auth/payload", string authLoginPath = "/auth/login", HttpClient httpClient = null); } diff --git a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs index bc89740..3fd85fe 100644 --- a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs @@ -37,6 +37,18 @@ public virtual Task GetAddress() return Task.FromResult(_ecKey.GetPublicAddress()); } + public virtual Task EthSign(byte[] rawMessage) + { + if (rawMessage == null) + { + throw new ArgumentNullException(nameof(rawMessage), "Message to sign cannot be null."); + } + + var signer = new MessageSigner(); + var signature = signer.Sign(rawMessage, _ecKey); + return Task.FromResult(signature); + } + public virtual Task EthSign(string message) { if (message == null) @@ -98,7 +110,7 @@ public virtual Task SignTypedDataV4(T data, TypedData SignTransaction(TransactionInput transaction, BigInteger chainId) + public virtual async Task SignTransaction(ThirdwebTransactionInput transaction, BigInteger chainId) { if (transaction == null) { @@ -120,7 +132,14 @@ public virtual async Task SignTransaction(TransactionInput transaction, var value = transaction.Value ?? new HexBigInteger(0); string signedTransaction; - if (transaction.Type != null && transaction.Type.Value == TransactionType.EIP1559.AsByte()) + + if (transaction.GasPrice != null) + { + var gasPrice = transaction.GasPrice; + var legacySigner = new LegacyTransactionSigner(); + signedTransaction = legacySigner.SignTransaction(_ecKey.GetPrivateKey(), chainId, transaction.To, value.Value, nonce, gasPrice.Value, gasLimit.Value, transaction.Data); + } + else { if (transaction.MaxPriorityFeePerGas == null || transaction.MaxFeePerGas == null) { @@ -128,32 +147,12 @@ public virtual async Task SignTransaction(TransactionInput transaction, } var maxPriorityFeePerGas = transaction.MaxPriorityFeePerGas.Value; var maxFeePerGas = transaction.MaxFeePerGas.Value; - var transaction1559 = new Transaction1559( - chainId, - nonce, - maxPriorityFeePerGas, - maxFeePerGas, - gasLimit, - transaction.To, - value, - transaction.Data, - transaction.AccessList.ToSignerAccessListItemArray() - ); + var transaction1559 = new Transaction1559(chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, transaction.To, value, transaction.Data, null); var signer = new Transaction1559Signer(); signer.SignTransaction(_ecKey, transaction1559); signedTransaction = transaction1559.GetRLPEncoded().ToHex(); } - else - { - if (transaction.GasPrice == null) - { - throw new InvalidOperationException("Transaction gas price must be set for legacy transactions"); - } - var gasPrice = transaction.GasPrice; - var legacySigner = new LegacyTransactionSigner(); - signedTransaction = legacySigner.SignTransaction(_ecKey.GetPrivateKey(), chainId, transaction.To, value.Value, nonce, gasPrice.Value, gasLimit.Value, transaction.Data); - } return "0x" + signedTransaction; } diff --git a/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs b/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs index ec77b8e..b045fa8 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs @@ -98,7 +98,7 @@ public async Task IsDeployed() return code != "0x"; } - public async Task SendTransaction(TransactionInput transaction) + public async Task SendTransaction(ThirdwebTransactionInput transaction) { if (transaction == null) { @@ -120,7 +120,7 @@ private async Task GetInitCode() return data.HexToByteArray(); } - private async Task SignUserOp(TransactionInput transactionInput, int? requestId = null) + private async Task SignUserOp(ThirdwebTransactionInput transactionInput, int? requestId = null) { requestId ??= 1; @@ -246,7 +246,12 @@ private UserOperationHexified EncodeUserOperation(UserOperation userOperation) public async Task ForceDeploy() { - var input = new TransactionInput("0x", _accountContract.Address, new HexBigInteger(0)); + var input = new ThirdwebTransactionInput() + { + Data = "0x", + To = _accountContract.Address, + Value = new HexBigInteger(0) + }; var txHash = await SendTransaction(input); _ = await ThirdwebTransaction.WaitForTransactionReceipt(_client, _chainId, txHash); } @@ -261,6 +266,11 @@ public Task GetAddress() return Task.FromResult(_accountContract.Address); } + public Task EthSign(byte[] rawMessage) + { + return _personalAccount.EthSign(rawMessage); + } + public Task EthSign(string message) { return _personalAccount.EthSign(message); @@ -344,7 +354,7 @@ string reqValidityEndTimestamp var signature = await EIP712.GenerateSignature_SmartAccount("Account", "1", _chainId, await GetAddress(), request, _personalAccount); var data = new Contract(null, _accountContract.Abi, _accountContract.Address).GetFunction("setPermissionsForSigner").GetData(request, signature.HexToByteArray()); - var txInput = new TransactionInput() + var txInput = new ThirdwebTransactionInput() { From = await GetAddress(), To = _accountContract.Address, @@ -372,7 +382,7 @@ public async Task AddAdmin(string admin) var signature = await EIP712.GenerateSignature_SmartAccount("Account", "1", _chainId, await GetAddress(), request, _personalAccount); var data = new Contract(null, _accountContract.Abi, _accountContract.Address).GetFunction("setPermissionsForSigner").GetData(request, signature.HexToByteArray()); - var txInput = new TransactionInput() + var txInput = new ThirdwebTransactionInput() { From = await GetAddress(), To = _accountContract.Address, @@ -400,7 +410,7 @@ public async Task RemoveAdmin(string admin) var signature = await EIP712.GenerateSignature_SmartAccount("Account", "1", _chainId, await GetAddress(), request, _personalAccount); var data = new Contract(null, _accountContract.Abi, _accountContract.Address).GetFunction("setPermissionsForSigner").GetData(request, signature.HexToByteArray()); - var txInput = new TransactionInput() + var txInput = new ThirdwebTransactionInput() { From = await GetAddress(), To = _accountContract.Address, @@ -422,14 +432,14 @@ public Task SignTypedDataV4(T data, TypedData typed return _personalAccount.SignTypedDataV4(data, typedData); } - public async Task EstimateUserOperationGas(TransactionInput transaction, BigInteger chainId) + public async Task EstimateUserOperationGas(ThirdwebTransactionInput transaction, BigInteger chainId) { var signedOp = await SignUserOp(transaction); var cost = signedOp.CallGasLimit + signedOp.VerificationGasLimit + signedOp.PreVerificationGas; return cost; } - public async Task SignTransaction(TransactionInput transaction, BigInteger chainId) + public async Task SignTransaction(ThirdwebTransactionInput transaction, BigInteger chainId) { return JsonConvert.SerializeObject(EncodeUserOperation(await SignUserOp(transaction))); } diff --git a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs index 3e9bf72..6b1591f 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs @@ -170,7 +170,50 @@ public class SignerPermissionRequest public class AccountMessage { - [Nethereum.ABI.FunctionEncoding.Attributes.Parameter("bytes", "message", 1)] + [Parameter("bytes", "message", 1)] public virtual byte[] Message { get; set; } } + + [Struct("Transaction")] + public class ZkSyncAATransaction + { + [Parameter("uint256", "txType", 1)] + public virtual BigInteger TxType { get; set; } + + [Parameter("uint256", "from", 2)] + public virtual BigInteger From { get; set; } + + [Parameter("uint256", "to", 3)] + public virtual BigInteger To { get; set; } + + [Parameter("uint256", "gasLimit", 4)] + public virtual BigInteger GasLimit { get; set; } + + [Parameter("uint256", "gasPerPubdataByteLimit", 5)] + public virtual BigInteger GasPerPubdataByteLimit { get; set; } + + [Parameter("uint256", "maxFeePerGas", 6)] + public virtual BigInteger MaxFeePerGas { get; set; } + + [Parameter("uint256", "maxPriorityFeePerGas", 7)] + public virtual BigInteger MaxPriorityFeePerGas { get; set; } + + [Parameter("uint256", "paymaster", 8)] + public virtual BigInteger Paymaster { get; set; } + + [Parameter("uint256", "nonce", 9)] + public virtual BigInteger Nonce { get; set; } + + [Parameter("uint256", "value", 10)] + public virtual BigInteger Value { get; set; } + + [Parameter("bytes", "data", 11)] + public virtual byte[] Data { get; set; } + + [Parameter("bytes32[]", "factoryDeps", 12)] + public virtual List FactoryDeps { get; set; } + + [Parameter("bytes", "paymasterInput", 13)] + public virtual byte[] PaymasterInput { get; set; } + } }