From c63c348c380ddbfb6b4b95c942fe268983d1fee4 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 19 Dec 2024 03:43:57 +0700 Subject: [PATCH 1/8] EIP-7702 Integration Experimenting on Odyssey for now --- .../ThirdwebTransaction.cs | 28 ++++++++++++- .../ThirdwebTransactionInput.cs | 40 ++++++++++++++++++- Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs | 13 +++++- .../EcosystemWallet/EcosystemWallet.cs | 5 +++ .../PrivateKeyWallet/PrivateKeyWallet.cs | 12 ++++++ .../SmartWallet/SmartWallet.cs | 5 +++ 6 files changed, 99 insertions(+), 4 deletions(-) diff --git a/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs b/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs index cd3abb9e..3b555a41 100644 --- a/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs +++ b/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs @@ -357,7 +357,33 @@ public static async Task Send(ThirdwebTransaction transaction) var rpc = ThirdwebRPC.GetRpcInstance(transaction._wallet.Client, transaction.Input.ChainId.Value); string hash; - if (await Utils.IsZkSync(transaction._wallet.Client, transaction.Input.ChainId.Value).ConfigureAwait(false) && transaction.Input.ZkSync.HasValue) + + if (transaction.Input.AuthorizationList != null) + { + var authorization = transaction.Input.AuthorizationList[0]; + hash = await rpc.SendRequestAsync( + "wallet_sendTransaction", + new + { + authorizationList = new[] + { + new + { + address = authorization.Address, + chainId = authorization.ChainId.HexToBigInt(), + nonce = authorization.Nonce.HexToBigInt(), + r = authorization.R, + s = authorization.S, + yParity = authorization.YParity == "0x00" ? 0 : 1 + } + }, + data = transaction.Input.Data, + to = transaction.Input.To, + } + ) + .ConfigureAwait(false); + } + else if (await Utils.IsZkSync(transaction._wallet.Client, transaction.Input.ChainId.Value).ConfigureAwait(false) && transaction.Input.ZkSync.HasValue) { var zkTx = await ConvertToZkSyncTransaction(transaction).ConfigureAwait(false); var zkTxSigned = await EIP712.GenerateSignature_ZkSyncTransaction("zkSync", "2", transaction.Input.ChainId.Value, zkTx, transaction._wallet).ConfigureAwait(false); diff --git a/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs b/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs index d6ef3c91..41e638af 100644 --- a/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs +++ b/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs @@ -26,7 +26,8 @@ public ThirdwebTransactionInput( string data = null, BigInteger? maxFeePerGas = null, BigInteger? maxPriorityFeePerGas = null, - ZkSyncOptions? zkSync = null + ZkSyncOptions? zkSync = null, + EIP7702Authorization? authorization = null ) { this.ChainId = chainId > 0 ? new HexBigInteger(chainId) : throw new ArgumentException("Invalid Chain ID"); @@ -40,6 +41,7 @@ public ThirdwebTransactionInput( this.MaxFeePerGas = maxFeePerGas == null ? null : new HexBigInteger(maxFeePerGas.Value); this.MaxPriorityFeePerGas = maxPriorityFeePerGas == null ? null : new HexBigInteger(maxPriorityFeePerGas.Value); this.ZkSync = zkSync; + this.AuthorizationList = authorization == null ? null : new List { authorization.Value }; } /// @@ -123,6 +125,11 @@ public string Data /// [JsonProperty(PropertyName = "zkSyncOptions", NullValueHandling = NullValueHandling.Ignore)] public ZkSyncOptions? ZkSync { get; set; } + +#nullable enable + [JsonProperty(PropertyName = "authorizationList", NullValueHandling = NullValueHandling.Ignore)] + public List? AuthorizationList { get; set; } +#nullable disable } /// @@ -179,3 +186,34 @@ public ZkSyncOptions(string paymaster = null, string paymasterInput = null, BigI } } } + +public struct EIP7702Authorization +{ + [JsonProperty(PropertyName = "chainId")] + public string ChainId { get; set; } + + [JsonProperty(PropertyName = "address")] + public string Address { get; set; } + + [JsonProperty(PropertyName = "nonce")] + public string Nonce { get; set; } + + [JsonProperty(PropertyName = "yParity")] + public string YParity { get; set; } + + [JsonProperty(PropertyName = "r")] + public string R { get; set; } + + [JsonProperty(PropertyName = "s")] + public string S { get; set; } + + public EIP7702Authorization(BigInteger chainId, string address, BigInteger nonce, byte[] yParity, byte[] r, byte[] s) + { + this.ChainId = new HexBigInteger(chainId).HexValue; + this.Address = address.EnsureHexPrefix(); + this.Nonce = new HexBigInteger(nonce).HexValue; + this.YParity = yParity.BytesToHex(); + this.R = r.BytesToHex(); + this.S = s.BytesToHex(); + } +} diff --git a/Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs b/Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs index 164d4b1d..daf26b4d 100644 --- a/Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs @@ -1,4 +1,5 @@ -using Nethereum.ABI.EIP712; +using System.Numerics; +using Nethereum.ABI.EIP712; using Newtonsoft.Json; namespace Thirdweb; @@ -150,7 +151,7 @@ Task> LinkAccount( Action browserOpenAction = null, string mobileRedirectScheme = "thirdweb://", IThirdwebBrowser browser = null, - System.Numerics.BigInteger? chainId = null, + BigInteger? chainId = null, string jwt = null, string payload = null ); @@ -166,6 +167,14 @@ Task> LinkAccount( /// /// A list of objects. Task> GetLinkedAccounts(); + + /// + /// Signs an EIP-7702 authorization to invoke contract functions to an externally owned account. + /// + /// The chain ID of the contract. + /// The address of the contract. + /// The signed authorization as an that can be used with . + Task SignAuthorization(BigInteger chainId, string contractAddress); } /// diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs index 8a2231e4..726e5044 100644 --- a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs @@ -931,5 +931,10 @@ public virtual Task RecoverAddressFromTypedDataV4(T data, Ty return Task.FromResult(address); } + public Task SignAuthorization(BigInteger chainId, string contractAddress) + { + throw new NotImplementedException(); + } + #endregion } diff --git a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs index 26d90a48..1589a6b5 100644 --- a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs @@ -4,6 +4,7 @@ using Nethereum.Hex.HexConvertors.Extensions; using Nethereum.Hex.HexTypes; using Nethereum.Model; +using Nethereum.RLP; using Nethereum.Signer; using Nethereum.Signer.EIP712; @@ -385,5 +386,16 @@ public Task> UnlinkAccount(LinkedAccount accountToUnlink) throw new InvalidOperationException("UnlinkAccount is not supported for private key wallets."); } + public async Task SignAuthorization(BigInteger chainId, string contractAddress) + { + var nonce = await this.GetTransactionCount(chainId); + var authorizationHash = Utils.HashMessage( + Utils.HexConcat("0x05", RLP.EncodeList(new HexBigInteger(chainId).HexValue.HexToBytes(), contractAddress.HexToBytes(), new HexBigInteger(nonce).HexValue.HexToBytes()).BytesToHex()[2..]) + ); + var authorizationSignature = await this.PersonalSign(authorizationHash); + var ecdsa = EthECDSASignatureFactory.ExtractECDSASignature(authorizationSignature); + return new EIP7702Authorization(chainId, contractAddress, nonce, ecdsa.V, ecdsa.R, ecdsa.S); + } + #endregion } diff --git a/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs b/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs index 68548c8f..613a5d1f 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs @@ -1225,5 +1225,10 @@ public async Task> GetLinkedAccounts() } } + public Task SignAuthorization(BigInteger chainId, string contractAddress) + { + return this._personalAccount.SignAuthorization(chainId, contractAddress); + } + #endregion } From 37ea6f712ee9ceb8b25e57d1b40f17a408db5c76 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Fri, 20 Dec 2024 21:54:06 +0700 Subject: [PATCH 2/8] progress push --- Thirdweb.Console/Program.cs | 102 ++++++++++++--- .../ThirdwebTransaction.cs | 53 ++++---- .../ThirdwebTransactionInput.cs | 6 +- Thirdweb/Thirdweb.Utils/Utils.cs | 22 ++++ Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs | 3 +- .../EcosystemWallet/EcosystemWallet.cs | 2 +- .../PrivateKeyWallet/PrivateKeyWallet.cs | 121 +++++++++++++++--- .../SmartWallet/SmartWallet.cs | 4 +- 8 files changed, 237 insertions(+), 76 deletions(-) diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index e8d52ab8..17a17ce7 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -18,20 +18,94 @@ // Do not use private keys client side, use InAppWallet/SmartWallet instead var privateKey = Environment.GetEnvironmentVariable("PRIVATE_KEY"); -// Fetch timeout options are optional, default is 120000ms -var client = ThirdwebClient.Create(secretKey: secretKey, fetchTimeoutOptions: new TimeoutOptions(storage: 120000, rpc: 120000, other: 120000)); - -// Create a private key wallet -var privateKeyWallet = await PrivateKeyWallet.Generate(client: client); - -// var walletAddress = await privateKeyWallet.GetAddress(); -// Console.WriteLine($"PK Wallet address: {walletAddress}"); +// Initialize client +var client = ThirdwebClient.Create(secretKey: secretKey); + +// Chain and contract addresses +var chainWith7702 = 7078815900; +var erc20ContractAddress = "0x852e8247A55C49dc3b7e6f8788347813e562F597"; // Mekong Token +var delegationContractAddress = "0x7B9E7AFd452666302352D82161B083dF792f7Cf4"; // BatchCallDelegation + +// Initialize contracts normally +var erc20Contract = await ThirdwebContract.Create(client: client, address: erc20ContractAddress, chain: chainWith7702); +var delegationContract = await ThirdwebContract.Create( + client: client, + address: delegationContractAddress, + chain: chainWith7702, + abi: /*lang=json,strict*/ + "[{\"anonymous\": false,\"inputs\": [{\"indexed\": true,\"internalType\": \"address\",\"name\": \"to\",\"type\": \"address\"},{\"indexed\": false,\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"},{\"indexed\": false,\"internalType\": \"bytes\",\"name\": \"data\",\"type\": \"bytes\"}],\"name\": \"Executed\",\"type\": \"event\"},{\"inputs\": [{\"components\": [{\"internalType\": \"bytes\",\"name\": \"data\",\"type\": \"bytes\"},{\"internalType\": \"address\",\"name\": \"to\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"}],\"internalType\": \"struct BatchCallDelegation.Call[]\",\"name\": \"calls\",\"type\": \"tuple[]\"}],\"name\": \"execute\",\"outputs\": [],\"stateMutability\": \"payable\",\"type\": \"function\"}]" +); + +// Initialize a 7702 EOA +var eoaWallet = await PrivateKeyWallet.Generate(client); +var eoaWalletAddress = await eoaWallet.GetAddress(); +Console.WriteLine($"EOA address: {eoaWalletAddress}"); + +// Temporary - fund eoa wallet +var fundingWallet = await PrivateKeyWallet.Create(client, privateKey); +await ThirdwebTransaction.SendAndWaitForTransactionReceipt( + await ThirdwebTransaction.Create(fundingWallet, new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, value: BigInteger.Parse("0.1".ToWei()))) +); + +// Sign the authorization to make it point to the delegation contract +var authorization = await eoaWallet.SignAuthorization(chainId: chainWith7702, contractAddress: delegationContractAddress, willSelfExecute: true); +Console.WriteLine($"Authorization: {JsonConvert.SerializeObject(authorization, Formatting.Indented)}"); + +// Execute the delegation +var tx = await ThirdwebTransaction.Create(eoaWallet, new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, authorization: authorization)); +var hash = (await ThirdwebTransaction.SendAndWaitForTransactionReceipt(tx)).TransactionHash; +Console.WriteLine($"Transaction hash: {hash}"); + +// Initialize another wallet, the "executor" that will hit the eoa's execute function +var executorWallet = await InAppWallet.Create(client: client, authProvider: AuthProvider.Google); +if (!await executorWallet.IsConnected()) +{ + _ = await executorWallet.LoginWithOauth( + isMobile: false, + browserOpenAction: (url) => + { + var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true }; + _ = Process.Start(psi); + } + ); +} +var executorWalletAddress = await executorWallet.GetAddress(); +Console.WriteLine($"Executor address: {executorWalletAddress}"); + +// Log erc20 balance of executor before the claim +var executorBalanceBefore = await erc20Contract.ERC20_BalanceOf(executorWalletAddress); +Console.WriteLine($"Executor balance before: {executorBalanceBefore}"); + +// Prepare the claim call +var claimCallData = erc20Contract.CreateCallData( + "claim", + new object[] + { + executorWalletAddress, // receiver + 100, // quantity + Constants.NATIVE_TOKEN_ADDRESS, // currency + 0, // pricePerToken + new object[] { Array.Empty(), BigInteger.Zero, BigInteger.Zero, Constants.ADDRESS_ZERO }, // allowlistProof + Array.Empty() // data + } +); + +// Embed the claim call in the execute call +var executeCallData = delegationContract.CreateCallData("execute", new object[] { new object[] { claimCallData, eoaWalletAddress, BigInteger.Zero } }); + +// Execute from the executor wallet targeting the eoa which is pointing to the delegation contract +var tx2 = await ThirdwebTransaction.Create(executorWallet, new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, data: executeCallData)); +var hash2 = (await ThirdwebTransaction.SendAndWaitForTransactionReceipt(tx2)).TransactionHash; +Console.WriteLine($"Transaction hash: {hash2}"); + +// Log erc20 balance of executor after the claim +var executorBalanceAfter = await erc20Contract.ERC20_BalanceOf(executorWalletAddress); +Console.WriteLine($"Executor balance after: {executorBalanceAfter}"); #region Contract Interaction // var contract = await ThirdwebContract.Create(client: client, address: "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d", chain: 1); -// var nfts = await contract.ERC721_GetAllNFTs(); -// Console.WriteLine($"NFTs: {JsonConvert.SerializeObject(nfts, Formatting.Indented)}"); +// var result = await contract. #endregion @@ -44,13 +118,7 @@ #region AA 0.6 -// var smartWallet06 = await SmartWallet.Create( -// personalWallet: privateKeyWallet, -// chainId: 421614, -// gasless: true, -// factoryAddress: "0xa8deE7854fb1eA8c13b713585C81d91ea86dAD84", -// entryPoint: Constants.ENTRYPOINT_ADDRESS_V06 -// ); +// var smartWallet06 = await SmartWallet.Create(personalWallet: privateKeyWallet, chainId: 421614); // var receipt06 = await smartWallet06.ExecuteTransaction(new ThirdwebTransactionInput(chainId: 421614, to: await smartWallet06.GetAddress(), value: 0, data: "0x")); diff --git a/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs b/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs index 3b555a41..869fc762 100644 --- a/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs +++ b/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs @@ -262,16 +262,32 @@ public static async Task EstimateGasLimit(ThirdwebTransaction transa { var rpc = ThirdwebRPC.GetRpcInstance(transaction._wallet.Client, transaction.Input.ChainId.Value); - if (await Utils.IsZkSync(transaction._wallet.Client, transaction.Input.ChainId.Value).ConfigureAwait(false)) + // Remove when https://github.com/ethereum/execution-apis/issues/561 is in + var txInput = new ThirdwebTransactionInput( + chainId: transaction.Input.ChainId, + from: transaction.Input.From, + to: transaction.Input.To, + nonce: transaction.Input.Nonce, + value: transaction.Input.Value, + data: transaction.Input.Data, + zkSync: transaction.Input.ZkSync + ); + + var extraGas = transaction.Input.AuthorizationList == null ? 0 : 100000; + BigInteger finalGas; + + if (await Utils.IsZkSync(transaction._wallet.Client, txInput.ChainId.Value).ConfigureAwait(false)) { - var hex = (await rpc.SendRequestAsync("zks_estimateFee", transaction.Input).ConfigureAwait(false))["gas_limit"].ToString(); - return new HexBigInteger(hex).Value * 10 / 5; + var hex = (await rpc.SendRequestAsync("zks_estimateFee", txInput).ConfigureAwait(false))["gas_limit"].ToString(); + finalGas = hex.HexToBigInt() * 2; } else { - var hex = await rpc.SendRequestAsync("eth_estimateGas", transaction.Input).ConfigureAwait(false); - return new HexBigInteger(hex).Value * 10 / 7; + var hex = await rpc.SendRequestAsync("eth_estimateGas", txInput).ConfigureAwait(false); + finalGas = hex.HexToBigInt() * 2; } + + return finalGas + extraGas; } /// @@ -358,32 +374,7 @@ public static async Task Send(ThirdwebTransaction transaction) var rpc = ThirdwebRPC.GetRpcInstance(transaction._wallet.Client, transaction.Input.ChainId.Value); string hash; - if (transaction.Input.AuthorizationList != null) - { - var authorization = transaction.Input.AuthorizationList[0]; - hash = await rpc.SendRequestAsync( - "wallet_sendTransaction", - new - { - authorizationList = new[] - { - new - { - address = authorization.Address, - chainId = authorization.ChainId.HexToBigInt(), - nonce = authorization.Nonce.HexToBigInt(), - r = authorization.R, - s = authorization.S, - yParity = authorization.YParity == "0x00" ? 0 : 1 - } - }, - data = transaction.Input.Data, - to = transaction.Input.To, - } - ) - .ConfigureAwait(false); - } - else if (await Utils.IsZkSync(transaction._wallet.Client, transaction.Input.ChainId.Value).ConfigureAwait(false) && transaction.Input.ZkSync.HasValue) + if (await Utils.IsZkSync(transaction._wallet.Client, transaction.Input.ChainId.Value).ConfigureAwait(false) && transaction.Input.ZkSync.HasValue) { var zkTx = await ConvertToZkSyncTransaction(transaction).ConfigureAwait(false); var zkTxSigned = await EIP712.GenerateSignature_ZkSyncTransaction("zkSync", "2", transaction.Input.ChainId.Value, zkTx, transaction._wallet).ConfigureAwait(false); diff --git a/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs b/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs index 41e638af..7ae3fd59 100644 --- a/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs +++ b/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs @@ -207,12 +207,12 @@ public struct EIP7702Authorization [JsonProperty(PropertyName = "s")] public string S { get; set; } - public EIP7702Authorization(BigInteger chainId, string address, BigInteger nonce, byte[] yParity, byte[] r, byte[] s) + public EIP7702Authorization(BigInteger chainId, string address, BigInteger nonce, byte[] v, byte[] r, byte[] s) { this.ChainId = new HexBigInteger(chainId).HexValue; - this.Address = address.EnsureHexPrefix(); + this.Address = address; this.Nonce = new HexBigInteger(nonce).HexValue; - this.YParity = yParity.BytesToHex(); + this.YParity = v.BytesToHex(); this.R = r.BytesToHex(); this.S = s.BytesToHex(); } diff --git a/Thirdweb/Thirdweb.Utils/Utils.cs b/Thirdweb/Thirdweb.Utils/Utils.cs index 9ecb99ae..2b974755 100644 --- a/Thirdweb/Thirdweb.Utils/Utils.cs +++ b/Thirdweb/Thirdweb.Utils/Utils.cs @@ -1034,4 +1034,26 @@ public static string SerializeErc6492Signature(string address, byte[] data, byte var encodedParams = encoder.GetABIEncoded(new ABIValue("address", address), new ABIValue("bytes", data), new ABIValue("bytes", signature)); return HexConcat(encodedParams.BytesToHex(), Constants.ERC_6492_MAGIC_VALUE); } + + /// + /// Removes leading zeroes from the given byte array. + /// + public static byte[] TrimZeroes(this byte[] bytes) + { + var trimmed = new List(); + var previousByteWasZero = true; + + for (var i = 0; i < bytes.Length; i++) + { + if (previousByteWasZero && bytes[i] == 0) + { + continue; + } + + previousByteWasZero = false; + trimmed.Add(bytes[i]); + } + + return trimmed.ToArray(); + } } diff --git a/Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs b/Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs index daf26b4d..54bd1554 100644 --- a/Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs @@ -173,8 +173,9 @@ Task> LinkAccount( /// /// The chain ID of the contract. /// The address of the contract. + /// Set to true if the wallet will also be the executor of the transaction, otherwise false. /// The signed authorization as an that can be used with . - Task SignAuthorization(BigInteger chainId, string contractAddress); + Task SignAuthorization(BigInteger chainId, string contractAddress, bool willSelfExecute); } /// diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs index 726e5044..376cf37b 100644 --- a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs @@ -931,7 +931,7 @@ public virtual Task RecoverAddressFromTypedDataV4(T data, Ty return Task.FromResult(address); } - public Task SignAuthorization(BigInteger chainId, string contractAddress) + public Task SignAuthorization(BigInteger chainId, string contractAddress, bool willSelfExecute) { throw new NotImplementedException(); } diff --git a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs index 1589a6b5..9d9e05da 100644 --- a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs @@ -3,7 +3,6 @@ using Nethereum.ABI.EIP712; using Nethereum.Hex.HexConvertors.Extensions; using Nethereum.Hex.HexTypes; -using Nethereum.Model; using Nethereum.RLP; using Nethereum.Signer; using Nethereum.Signer.EIP712; @@ -300,10 +299,10 @@ public virtual Task SignTransaction(ThirdwebTransactionInput transaction throw new ArgumentNullException(nameof(transaction)); } - var nonce = transaction.Nonce ?? throw new ArgumentNullException(nameof(transaction), "Transaction nonce has not been set"); - - var gasLimit = transaction.Gas; - var value = transaction.Value ?? new HexBigInteger(0); + if (transaction.Nonce == null) + { + throw new ArgumentNullException(nameof(transaction), "Transaction nonce has not been set"); + } string signedTransaction; @@ -315,10 +314,10 @@ public virtual Task SignTransaction(ThirdwebTransactionInput transaction this.EcKey.GetPrivateKey(), transaction.ChainId.Value, transaction.To, - value.Value, - nonce, + transaction.Value.Value, + transaction.Nonce.Value, gasPrice.Value, - gasLimit.Value, + transaction.Gas.Value, transaction.Data ); } @@ -328,13 +327,82 @@ public virtual Task SignTransaction(ThirdwebTransactionInput transaction { throw new InvalidOperationException("Transaction MaxPriorityFeePerGas and MaxFeePerGas must be set for EIP-1559 transactions"); } - var maxPriorityFeePerGas = transaction.MaxPriorityFeePerGas.Value; - var maxFeePerGas = transaction.MaxFeePerGas.Value; - var transaction1559 = new Transaction1559(transaction.ChainId.Value, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, transaction.To, value, transaction.Data, null); - var signer = new Transaction1559Signer(); - _ = signer.SignTransaction(this.EcKey, transaction1559); - signedTransaction = transaction1559.GetRLPEncoded().ToHex(); + var encodedData = new List + { + RLP.EncodeElement(transaction.ChainId.Value.ToBytesForRLPEncoding()), + RLP.EncodeElement(transaction.Nonce.Value.ToBytesForRLPEncoding()), + RLP.EncodeElement(transaction.MaxPriorityFeePerGas.Value.ToBytesForRLPEncoding()), + RLP.EncodeElement(transaction.MaxFeePerGas.Value.ToBytesForRLPEncoding()), + RLP.EncodeElement(transaction.Gas.Value.ToBytesForRLPEncoding()), + RLP.EncodeElement(transaction.To.HexToBytes()), + RLP.EncodeElement(transaction.Value.Value.ToBytesForRLPEncoding()), + RLP.EncodeElement(transaction.Data.HexToBytes()), + new byte[] { 0xc0 }, // AccessList, empty so short list bytes + }; + + if (transaction.AuthorizationList != null) + { + var encodedAuthorizationList = new List(); + foreach (var authorizationList in transaction.AuthorizationList) + { + var encodedItem = new List() + { + RLP.EncodeElement(authorizationList.ChainId.HexToBytes()), + RLP.EncodeElement(authorizationList.Address.HexToBytes()), + RLP.EncodeElement(authorizationList.Nonce.HexToBytes()), + RLP.EncodeElement(authorizationList.YParity.HexToBytes()), + RLP.EncodeElement(authorizationList.R.HexToBytes().TrimZeroes()), + RLP.EncodeElement(authorizationList.S.HexToBytes().TrimZeroes()) + }; + encodedAuthorizationList.Add(RLP.EncodeList(encodedItem.ToArray())); + } + encodedData.Add(RLP.EncodeList(encodedAuthorizationList.ToArray())); + } + + var encodedBytes = RLP.EncodeList(encodedData.ToArray()); + var returnBytes = new byte[encodedBytes.Length + 1]; + Array.Copy(encodedBytes, 0, returnBytes, 1, encodedBytes.Length); + returnBytes[0] = transaction.AuthorizationList != null ? (byte)0x04 : (byte)0x02; + + var rawHash = Utils.HashMessage(returnBytes); + var rawSignature = this.EcKey.SignAndCalculateYParityV(rawHash); + + byte[] v; + byte[] r; + byte[] s; + + if (rawSignature != null && rawSignature.V != null) + { + if (rawSignature.V.Length == 0 || rawSignature.V[0] == 0) + { + v = Array.Empty(); + } + else + { + v = rawSignature.V; + } + v = RLP.EncodeElement(v); + r = RLP.EncodeElement(rawSignature.R.TrimZeroes()); + s = RLP.EncodeElement(rawSignature.S.TrimZeroes()); + } + else + { + v = RLP.EncodeElement(Array.Empty()); + r = RLP.EncodeElement(Array.Empty()); + s = RLP.EncodeElement(Array.Empty()); + } + + encodedData.Add(v); + encodedData.Add(r); + encodedData.Add(s); + + encodedBytes = RLP.EncodeList(encodedData.ToArray()); + returnBytes = new byte[encodedBytes.Length + 1]; + Array.Copy(encodedBytes, 0, returnBytes, 1, encodedBytes.Length); + returnBytes[0] = transaction.AuthorizationList != null ? (byte)0x04 : (byte)0x02; + + signedTransaction = returnBytes.ToHex(); } return Task.FromResult("0x" + signedTransaction); @@ -386,15 +454,26 @@ public Task> UnlinkAccount(LinkedAccount accountToUnlink) throw new InvalidOperationException("UnlinkAccount is not supported for private key wallets."); } - public async Task SignAuthorization(BigInteger chainId, string contractAddress) + public async Task SignAuthorization(BigInteger chainId, string contractAddress, bool willSelfExecute) { var nonce = await this.GetTransactionCount(chainId); - var authorizationHash = Utils.HashMessage( - Utils.HexConcat("0x05", RLP.EncodeList(new HexBigInteger(chainId).HexValue.HexToBytes(), contractAddress.HexToBytes(), new HexBigInteger(nonce).HexValue.HexToBytes()).BytesToHex()[2..]) - ); - var authorizationSignature = await this.PersonalSign(authorizationHash); - var ecdsa = EthECDSASignatureFactory.ExtractECDSASignature(authorizationSignature); - return new EIP7702Authorization(chainId, contractAddress, nonce, ecdsa.V, ecdsa.R, ecdsa.S); + if (willSelfExecute) + { + nonce++; + } + var encodedData = new List + { + RLP.EncodeElement(new HexBigInteger(chainId).Value.ToBytesForRLPEncoding()), + RLP.EncodeElement(contractAddress.HexToBytes()), + RLP.EncodeElement(new HexBigInteger(nonce).Value.ToBytesForRLPEncoding()) + }; + var encodedBytes = RLP.EncodeList(encodedData.ToArray()); + var returnElements = new byte[encodedBytes.Length + 1]; + Array.Copy(encodedBytes.ToArray(), 0, returnElements, 1, encodedBytes.Length); + returnElements[0] = 0x05; + var authorizationHash = Utils.HashMessage(returnElements); + var authorizationSignature = this.EcKey.SignAndCalculateYParityV(authorizationHash); + return new EIP7702Authorization(chainId, contractAddress, nonce, authorizationSignature.V, authorizationSignature.R, authorizationSignature.S); } #endregion diff --git a/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs b/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs index 613a5d1f..37215a4d 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs @@ -1225,9 +1225,9 @@ public async Task> GetLinkedAccounts() } } - public Task SignAuthorization(BigInteger chainId, string contractAddress) + public Task SignAuthorization(BigInteger chainId, string contractAddress, bool willSelfExecute) { - return this._personalAccount.SignAuthorization(chainId, contractAddress); + return this._personalAccount.SignAuthorization(chainId, contractAddress, willSelfExecute); } #endregion From 7db813b93994fca39f606b99fb8c7fbe10f92429 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Fri, 20 Dec 2024 22:04:17 +0700 Subject: [PATCH 3/8] fix encoding --- Thirdweb.Console/Program.cs | 9 ++++++--- .../PrivateKeyWallet/PrivateKeyWallet.cs | 8 ++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index 17a17ce7..583a9b23 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -43,9 +43,12 @@ // Temporary - fund eoa wallet var fundingWallet = await PrivateKeyWallet.Create(client, privateKey); -await ThirdwebTransaction.SendAndWaitForTransactionReceipt( - await ThirdwebTransaction.Create(fundingWallet, new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, value: BigInteger.Parse("0.1".ToWei()))) -); +var fundingHash = ( + await ThirdwebTransaction.SendAndWaitForTransactionReceipt( + await ThirdwebTransaction.Create(fundingWallet, new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, value: BigInteger.Parse("0.1".ToWei()))) + ) +).TransactionHash; +Console.WriteLine($"Funding hash: {fundingHash}"); // Sign the authorization to make it point to the delegation contract var authorization = await eoaWallet.SignAuthorization(chainId: chainWith7702, contractAddress: delegationContractAddress, willSelfExecute: true); diff --git a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs index 9d9e05da..ff07d382 100644 --- a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs @@ -348,12 +348,12 @@ public virtual Task SignTransaction(ThirdwebTransactionInput transaction { var encodedItem = new List() { - RLP.EncodeElement(authorizationList.ChainId.HexToBytes()), + RLP.EncodeElement(authorizationList.ChainId.HexToBigInt().ToBytesForRLPEncoding()), RLP.EncodeElement(authorizationList.Address.HexToBytes()), - RLP.EncodeElement(authorizationList.Nonce.HexToBytes()), + RLP.EncodeElement(authorizationList.Nonce.HexToBigInt().ToBytesForRLPEncoding()), RLP.EncodeElement(authorizationList.YParity.HexToBytes()), - RLP.EncodeElement(authorizationList.R.HexToBytes().TrimZeroes()), - RLP.EncodeElement(authorizationList.S.HexToBytes().TrimZeroes()) + RLP.EncodeElement(authorizationList.R.HexToBytes()), + RLP.EncodeElement(authorizationList.S.HexToBytes()) }; encodedAuthorizationList.Add(RLP.EncodeList(encodedItem.ToArray())); } From 0196f4704fd30a0c58f5fbc0a42dc4a6833879e0 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Mon, 23 Dec 2024 22:46:52 +0700 Subject: [PATCH 4/8] add decoder --- Thirdweb.Console/Program.cs | 41 ++++----- .../ThirdwebTransaction.cs | 35 +++----- Thirdweb/Thirdweb.Utils/Utils.cs | 90 +++++++++++++++++++ .../PrivateKeyWallet/PrivateKeyWallet.cs | 18 ++-- 4 files changed, 126 insertions(+), 58 deletions(-) diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index 583a9b23..77a877c4 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -41,40 +41,29 @@ var eoaWalletAddress = await eoaWallet.GetAddress(); Console.WriteLine($"EOA address: {eoaWalletAddress}"); -// Temporary - fund eoa wallet -var fundingWallet = await PrivateKeyWallet.Create(client, privateKey); -var fundingHash = ( - await ThirdwebTransaction.SendAndWaitForTransactionReceipt( - await ThirdwebTransaction.Create(fundingWallet, new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, value: BigInteger.Parse("0.1".ToWei()))) - ) -).TransactionHash; -Console.WriteLine($"Funding hash: {fundingHash}"); +// // Temporary - fund eoa wallet +// var fundingWallet = await PrivateKeyWallet.Create(client, privateKey); +// var fundingHash = ( +// await ThirdwebTransaction.SendAndWaitForTransactionReceipt( +// await ThirdwebTransaction.Create(fundingWallet, new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, value: BigInteger.Parse("0.01".ToWei()))) +// ) +// ).TransactionHash; +// Console.WriteLine($"Funding hash: {fundingHash}"); // Sign the authorization to make it point to the delegation contract -var authorization = await eoaWallet.SignAuthorization(chainId: chainWith7702, contractAddress: delegationContractAddress, willSelfExecute: true); +var authorization = await eoaWallet.SignAuthorization(chainId: chainWith7702, contractAddress: delegationContractAddress, willSelfExecute: false); Console.WriteLine($"Authorization: {JsonConvert.SerializeObject(authorization, Formatting.Indented)}"); -// Execute the delegation -var tx = await ThirdwebTransaction.Create(eoaWallet, new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, authorization: authorization)); -var hash = (await ThirdwebTransaction.SendAndWaitForTransactionReceipt(tx)).TransactionHash; -Console.WriteLine($"Transaction hash: {hash}"); - // Initialize another wallet, the "executor" that will hit the eoa's execute function -var executorWallet = await InAppWallet.Create(client: client, authProvider: AuthProvider.Google); -if (!await executorWallet.IsConnected()) -{ - _ = await executorWallet.LoginWithOauth( - isMobile: false, - browserOpenAction: (url) => - { - var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true }; - _ = Process.Start(psi); - } - ); -} +var executorWallet = await PrivateKeyWallet.Create(client, privateKey); var executorWalletAddress = await executorWallet.GetAddress(); Console.WriteLine($"Executor address: {executorWalletAddress}"); +// Execute the delegation +var tx = await ThirdwebTransaction.Create(executorWallet, new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, authorization: authorization)); +var hash = (await ThirdwebTransaction.SendAndWaitForTransactionReceipt(tx)).TransactionHash; +Console.WriteLine($"Transaction hash: {hash}"); + // Log erc20 balance of executor before the claim var executorBalanceBefore = await erc20Contract.ERC20_BalanceOf(executorWalletAddress); Console.WriteLine($"Executor balance before: {executorBalanceBefore}"); diff --git a/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs b/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs index 869fc762..944436f3 100644 --- a/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs +++ b/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs @@ -261,33 +261,24 @@ public static async Task Simulate(ThirdwebTransaction transaction) public static async Task EstimateGasLimit(ThirdwebTransaction transaction) { var rpc = ThirdwebRPC.GetRpcInstance(transaction._wallet.Client, transaction.Input.ChainId.Value); - - // Remove when https://github.com/ethereum/execution-apis/issues/561 is in - var txInput = new ThirdwebTransactionInput( - chainId: transaction.Input.ChainId, - from: transaction.Input.From, - to: transaction.Input.To, - nonce: transaction.Input.Nonce, - value: transaction.Input.Value, - data: transaction.Input.Data, - zkSync: transaction.Input.ZkSync - ); - - var extraGas = transaction.Input.AuthorizationList == null ? 0 : 100000; - BigInteger finalGas; - - if (await Utils.IsZkSync(transaction._wallet.Client, txInput.ChainId.Value).ConfigureAwait(false)) + var isZkSync = await Utils.IsZkSync(transaction._wallet.Client, transaction.Input.ChainId.Value).ConfigureAwait(false); + BigInteger divider = isZkSync + ? 7 + : transaction.Input.AuthorizationList == null + ? 5 + : 3; + BigInteger baseGas; + if (isZkSync) { - var hex = (await rpc.SendRequestAsync("zks_estimateFee", txInput).ConfigureAwait(false))["gas_limit"].ToString(); - finalGas = hex.HexToBigInt() * 2; + var hex = (await rpc.SendRequestAsync("zks_estimateFee", transaction.Input).ConfigureAwait(false))["gas_limit"].ToString(); + baseGas = hex.HexToBigInt(); } else { - var hex = await rpc.SendRequestAsync("eth_estimateGas", txInput).ConfigureAwait(false); - finalGas = hex.HexToBigInt() * 2; + var hex = await rpc.SendRequestAsync("eth_estimateGas", transaction.Input).ConfigureAwait(false); + baseGas = hex.HexToBigInt(); } - - return finalGas + extraGas; + return baseGas * 10 / divider; } /// diff --git a/Thirdweb/Thirdweb.Utils/Utils.cs b/Thirdweb/Thirdweb.Utils/Utils.cs index 2b974755..72d9630b 100644 --- a/Thirdweb/Thirdweb.Utils/Utils.cs +++ b/Thirdweb/Thirdweb.Utils/Utils.cs @@ -12,6 +12,8 @@ using Nethereum.Contracts; using Nethereum.Hex.HexConvertors.Extensions; using Nethereum.Hex.HexTypes; +using Nethereum.Model; +using Nethereum.RLP; using Nethereum.Signer; using Nethereum.Util; using Newtonsoft.Json; @@ -1056,4 +1058,92 @@ public static byte[] TrimZeroes(this byte[] bytes) return trimmed.ToArray(); } + + /// + /// Decodes the given RLP-encoded transaction data. + /// + /// The RLP-encoded signed transaction data. + /// The decoded transaction input and signature. + public static (ThirdwebTransactionInput transactionInput, string signature) DecodeTransaction(string signedRlpData) + { + return DecodeTransaction(signedRlpData.HexToBytes()); + } + + /// + /// Decodes the given RLP-encoded transaction data. + /// + /// The RLP-encoded signed transaction data. + /// The decoded transaction input and signature. + public static (ThirdwebTransactionInput transactionInput, string signature) DecodeTransaction(byte[] signedRlpData) + { + var maybeType = signedRlpData[0]; + if (maybeType is 0x04 or 0x02) + { + signedRlpData = signedRlpData.Skip(1).ToArray(); + } + + var decodedList = RLP.Decode(signedRlpData); + var decodedElements = (RLPCollection)decodedList; + var chainId = decodedElements[0].RLPData.ToBigIntegerFromRLPDecoded(); + var nonce = decodedElements[1].RLPData.ToBigIntegerFromRLPDecoded(); + var maxPriorityFeePerGas = decodedElements[2].RLPData.ToBigIntegerFromRLPDecoded(); + var maxFeePerGas = decodedElements[3].RLPData.ToBigIntegerFromRLPDecoded(); + var gasLimit = decodedElements[4].RLPData.ToBigIntegerFromRLPDecoded(); + var receiverAddress = decodedElements[5].RLPData?.BytesToHex(); + var amount = decodedElements[6].RLPData.ToBigIntegerFromRLPDecoded(); + var data = decodedElements[7].RLPData?.BytesToHex(); + // 8th decoded element is access list + var authorizations = DecodeAutorizationList(decodedElements[9]?.RLPData); + + var signature = RLPSignedDataDecoder.DecodeSignature(decodedElements, 10); + return ( + new ThirdwebTransactionInput( + chainId: chainId, + to: receiverAddress, + nonce: nonce, + gas: gasLimit, + value: amount, + data: data, + maxFeePerGas: maxFeePerGas, + maxPriorityFeePerGas: maxPriorityFeePerGas + ) + { + AuthorizationList = authorizations + }, + signature.CreateStringSignature() + ); + } + + /// + /// Decodes the given RLP-encoded authorization list. + /// + public static List DecodeAutorizationList(byte[] authorizationListEncoded) + { + if (authorizationListEncoded == null || authorizationListEncoded.Length == 0 || authorizationListEncoded[0] == RLP.OFFSET_SHORT_LIST) + { + return null; + } + + var decodedList = (RLPCollection)RLP.Decode(authorizationListEncoded); + + var authorizationLists = new List(); + foreach (var rlpElement in decodedList) + { + var decodedItem = (RLPCollection)rlpElement; + var authorizationListItem = new EIP7702Authorization + { + ChainId = new HexBigInteger(decodedItem[0].RLPData.ToBigIntegerFromRLPDecoded()).HexValue, + Address = decodedItem[1].RLPData.BytesToHex(), + Nonce = new HexBigInteger(decodedItem[2].RLPData.ToBigIntegerFromRLPDecoded()).HexValue + }; + var signature = RLPSignedDataDecoder.DecodeSignature(decodedItem, 3); + authorizationListItem.YParity = signature.V.BytesToHex(); + authorizationListItem.R = signature.R.BytesToHex(); + authorizationListItem.S = signature.S.BytesToHex(); + + authorizationLists.Add(authorizationListItem); + } + + return authorizationLists; + } } diff --git a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs index ff07d382..62fcc154 100644 --- a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs @@ -2,7 +2,6 @@ using System.Text; using Nethereum.ABI.EIP712; using Nethereum.Hex.HexConvertors.Extensions; -using Nethereum.Hex.HexTypes; using Nethereum.RLP; using Nethereum.Signer; using Nethereum.Signer.EIP712; @@ -351,9 +350,9 @@ public virtual Task SignTransaction(ThirdwebTransactionInput transaction RLP.EncodeElement(authorizationList.ChainId.HexToBigInt().ToBytesForRLPEncoding()), RLP.EncodeElement(authorizationList.Address.HexToBytes()), RLP.EncodeElement(authorizationList.Nonce.HexToBigInt().ToBytesForRLPEncoding()), - RLP.EncodeElement(authorizationList.YParity.HexToBytes()), - RLP.EncodeElement(authorizationList.R.HexToBytes()), - RLP.EncodeElement(authorizationList.S.HexToBytes()) + RLP.EncodeElement(authorizationList.YParity.HexToBytes()[0] == 0 ? Array.Empty() : authorizationList.YParity.HexToBytes()), + RLP.EncodeElement(authorizationList.R.HexToBytes().TrimZeroes()), + RLP.EncodeElement(authorizationList.S.HexToBytes().TrimZeroes()) }; encodedAuthorizationList.Add(RLP.EncodeList(encodedItem.ToArray())); } @@ -402,7 +401,11 @@ public virtual Task SignTransaction(ThirdwebTransactionInput transaction Array.Copy(encodedBytes, 0, returnBytes, 1, encodedBytes.Length); returnBytes[0] = transaction.AuthorizationList != null ? (byte)0x04 : (byte)0x02; + // (var tx, var sig) = Utils.DecodeTransaction(returnBytes); + signedTransaction = returnBytes.ToHex(); + + // (var tx, var sig) = Utils.DecodeTransaction("0x" + signedTransaction); } return Task.FromResult("0x" + signedTransaction); @@ -461,12 +464,7 @@ public async Task SignAuthorization(BigInteger chainId, st { nonce++; } - var encodedData = new List - { - RLP.EncodeElement(new HexBigInteger(chainId).Value.ToBytesForRLPEncoding()), - RLP.EncodeElement(contractAddress.HexToBytes()), - RLP.EncodeElement(new HexBigInteger(nonce).Value.ToBytesForRLPEncoding()) - }; + var encodedData = new List { RLP.EncodeElement(chainId.ToBytesForRLPEncoding()), RLP.EncodeElement(contractAddress.HexToBytes()), RLP.EncodeElement(nonce.ToBytesForRLPEncoding()) }; var encodedBytes = RLP.EncodeList(encodedData.ToArray()); var returnElements = new byte[encodedBytes.Length + 1]; Array.Copy(encodedBytes.ToArray(), 0, returnElements, 1, encodedBytes.Length); From 234e7e94f87d6c5faae161b1db62bb168b87a9c9 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Tue, 24 Dec 2024 03:36:12 +0700 Subject: [PATCH 5/8] Full working example --- Makefile | 4 + Thirdweb.Console/Program.Types.cs | 16 ++ Thirdweb.Console/Program.cs | 180 ++++++++++-------- .../Thirdweb.Contracts/ThirdwebContract.cs | 30 +-- .../ThirdwebTransactionInput.cs | 4 +- Thirdweb/Thirdweb.Utils/Utils.cs | 18 +- .../PrivateKeyWallet/PrivateKeyWallet.cs | 49 +++-- 7 files changed, 177 insertions(+), 124 deletions(-) create mode 100644 Makefile create mode 100644 Thirdweb.Console/Program.Types.cs diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..7c185962 --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +.PHONY: run + +run: + dotnet run --project Thirdweb.Console diff --git a/Thirdweb.Console/Program.Types.cs b/Thirdweb.Console/Program.Types.cs new file mode 100644 index 00000000..701c22ca --- /dev/null +++ b/Thirdweb.Console/Program.Types.cs @@ -0,0 +1,16 @@ +using System.Numerics; +using Nethereum.ABI.FunctionEncoding.Attributes; + +namespace Thirdweb.Console; + +public class Call +{ + [Parameter("bytes", "data", 1)] + public required byte[] Data { get; set; } + + [Parameter("address", "to", 2)] + public required string To { get; set; } + + [Parameter("uint256", "value", 3)] + public required BigInteger Value { get; set; } +} diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index 77a877c4..ac5b8565 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -18,86 +18,20 @@ // Do not use private keys client side, use InAppWallet/SmartWallet instead var privateKey = Environment.GetEnvironmentVariable("PRIVATE_KEY"); -// Initialize client -var client = ThirdwebClient.Create(secretKey: secretKey); - -// Chain and contract addresses -var chainWith7702 = 7078815900; -var erc20ContractAddress = "0x852e8247A55C49dc3b7e6f8788347813e562F597"; // Mekong Token -var delegationContractAddress = "0x7B9E7AFd452666302352D82161B083dF792f7Cf4"; // BatchCallDelegation - -// Initialize contracts normally -var erc20Contract = await ThirdwebContract.Create(client: client, address: erc20ContractAddress, chain: chainWith7702); -var delegationContract = await ThirdwebContract.Create( - client: client, - address: delegationContractAddress, - chain: chainWith7702, - abi: /*lang=json,strict*/ - "[{\"anonymous\": false,\"inputs\": [{\"indexed\": true,\"internalType\": \"address\",\"name\": \"to\",\"type\": \"address\"},{\"indexed\": false,\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"},{\"indexed\": false,\"internalType\": \"bytes\",\"name\": \"data\",\"type\": \"bytes\"}],\"name\": \"Executed\",\"type\": \"event\"},{\"inputs\": [{\"components\": [{\"internalType\": \"bytes\",\"name\": \"data\",\"type\": \"bytes\"},{\"internalType\": \"address\",\"name\": \"to\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"}],\"internalType\": \"struct BatchCallDelegation.Call[]\",\"name\": \"calls\",\"type\": \"tuple[]\"}],\"name\": \"execute\",\"outputs\": [],\"stateMutability\": \"payable\",\"type\": \"function\"}]" -); - -// Initialize a 7702 EOA -var eoaWallet = await PrivateKeyWallet.Generate(client); -var eoaWalletAddress = await eoaWallet.GetAddress(); -Console.WriteLine($"EOA address: {eoaWalletAddress}"); - -// // Temporary - fund eoa wallet -// var fundingWallet = await PrivateKeyWallet.Create(client, privateKey); -// var fundingHash = ( -// await ThirdwebTransaction.SendAndWaitForTransactionReceipt( -// await ThirdwebTransaction.Create(fundingWallet, new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, value: BigInteger.Parse("0.01".ToWei()))) -// ) -// ).TransactionHash; -// Console.WriteLine($"Funding hash: {fundingHash}"); - -// Sign the authorization to make it point to the delegation contract -var authorization = await eoaWallet.SignAuthorization(chainId: chainWith7702, contractAddress: delegationContractAddress, willSelfExecute: false); -Console.WriteLine($"Authorization: {JsonConvert.SerializeObject(authorization, Formatting.Indented)}"); - -// Initialize another wallet, the "executor" that will hit the eoa's execute function -var executorWallet = await PrivateKeyWallet.Create(client, privateKey); -var executorWalletAddress = await executorWallet.GetAddress(); -Console.WriteLine($"Executor address: {executorWalletAddress}"); - -// Execute the delegation -var tx = await ThirdwebTransaction.Create(executorWallet, new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, authorization: authorization)); -var hash = (await ThirdwebTransaction.SendAndWaitForTransactionReceipt(tx)).TransactionHash; -Console.WriteLine($"Transaction hash: {hash}"); - -// Log erc20 balance of executor before the claim -var executorBalanceBefore = await erc20Contract.ERC20_BalanceOf(executorWalletAddress); -Console.WriteLine($"Executor balance before: {executorBalanceBefore}"); - -// Prepare the claim call -var claimCallData = erc20Contract.CreateCallData( - "claim", - new object[] - { - executorWalletAddress, // receiver - 100, // quantity - Constants.NATIVE_TOKEN_ADDRESS, // currency - 0, // pricePerToken - new object[] { Array.Empty(), BigInteger.Zero, BigInteger.Zero, Constants.ADDRESS_ZERO }, // allowlistProof - Array.Empty() // data - } -); - -// Embed the claim call in the execute call -var executeCallData = delegationContract.CreateCallData("execute", new object[] { new object[] { claimCallData, eoaWalletAddress, BigInteger.Zero } }); - -// Execute from the executor wallet targeting the eoa which is pointing to the delegation contract -var tx2 = await ThirdwebTransaction.Create(executorWallet, new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, data: executeCallData)); -var hash2 = (await ThirdwebTransaction.SendAndWaitForTransactionReceipt(tx2)).TransactionHash; -Console.WriteLine($"Transaction hash: {hash2}"); - -// Log erc20 balance of executor after the claim -var executorBalanceAfter = await erc20Contract.ERC20_BalanceOf(executorWalletAddress); -Console.WriteLine($"Executor balance after: {executorBalanceAfter}"); +// Fetch timeout options are optional, default is 120000ms +var client = ThirdwebClient.Create(secretKey: secretKey, fetchTimeoutOptions: new TimeoutOptions(storage: 120000, rpc: 120000, other: 120000)); + +// Create a private key wallet +var privateKeyWallet = await PrivateKeyWallet.Generate(client: client); + +// var walletAddress = await privateKeyWallet.GetAddress(); +// Console.WriteLine($"PK Wallet address: {walletAddress}"); #region Contract Interaction // var contract = await ThirdwebContract.Create(client: client, address: "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d", chain: 1); -// var result = await contract. +// var nfts = await contract.ERC721_GetAllNFTs(); +// Console.WriteLine($"NFTs: {JsonConvert.SerializeObject(nfts, Formatting.Indented)}"); #endregion @@ -110,7 +44,13 @@ #region AA 0.6 -// var smartWallet06 = await SmartWallet.Create(personalWallet: privateKeyWallet, chainId: 421614); +// var smartWallet06 = await SmartWallet.Create( +// personalWallet: privateKeyWallet, +// chainId: 421614, +// gasless: true, +// factoryAddress: "0xa8deE7854fb1eA8c13b713585C81d91ea86dAD84", +// entryPoint: Constants.ENTRYPOINT_ADDRESS_V06 +// ); // var receipt06 = await smartWallet06.ExecuteTransaction(new ThirdwebTransactionInput(chainId: 421614, to: await smartWallet06.GetAddress(), value: 0, data: "0x")); @@ -151,6 +91,92 @@ #endregion +#region EIP-7702 + +// // Chain and contract addresses +// var chainWith7702 = 911867; +// var erc20ContractAddress = "0xAA462a5BE0fc5214507FDB4fB2474a7d5c69065b"; // Fake ERC20 +// var delegationContractAddress = "0x654F42b74885EE6803F403f077bc0409f1066c58"; // BatchCallDelegation + +// // Initialize contracts normally +// var erc20Contract = await ThirdwebContract.Create(client: client, address: erc20ContractAddress, chain: chainWith7702); +// var delegationContract = await ThirdwebContract.Create(client: client, address: delegationContractAddress, chain: chainWith7702); + +// // Initialize a (to-be) 7702 EOA +// var eoaWallet = await PrivateKeyWallet.Generate(client); +// var eoaWalletAddress = await eoaWallet.GetAddress(); +// Console.WriteLine($"EOA address: {eoaWalletAddress}"); + +// // Initialize another wallet, the "executor" that will hit the eoa's (to-be) execute function +// var executorWallet = await PrivateKeyWallet.Generate(client); +// var executorWalletAddress = await executorWallet.GetAddress(); +// Console.WriteLine($"Executor address: {executorWalletAddress}"); + +// // Fund the executor wallet +// var fundingWallet = await PrivateKeyWallet.Create(client, privateKey); +// var fundingHash = (await fundingWallet.Transfer(chainWith7702, executorWalletAddress, BigInteger.Parse("0.001".ToWei()))).TransactionHash; +// Console.WriteLine($"Funded Executor Wallet: {fundingHash}"); + +// // Sign the authorization to make it point to the delegation contract +// var authorization = await eoaWallet.SignAuthorization(chainId: chainWith7702, contractAddress: delegationContractAddress, willSelfExecute: false); +// Console.WriteLine($"Authorization: {JsonConvert.SerializeObject(authorization, Formatting.Indented)}"); + +// // Execute the delegation +// var tx = await ThirdwebTransaction.Create(executorWallet, new ThirdwebTransactionInput(chainId: chainWith7702, to: executorWalletAddress, authorization: authorization)); +// var hash = (await ThirdwebTransaction.SendAndWaitForTransactionReceipt(tx)).TransactionHash; +// Console.WriteLine($"Authorization execution transaction hash: {hash}"); + +// // Prove that code has been deployed to the eoa +// var rpc = ThirdwebRPC.GetRpcInstance(client, chainWith7702); +// var code = await rpc.SendRequestAsync("eth_getCode", eoaWalletAddress, "latest"); +// Console.WriteLine($"EOA code: {code}"); + +// // Log erc20 balance of executor before the claim +// var executorBalanceBefore = await erc20Contract.ERC20_BalanceOf(executorWalletAddress); +// Console.WriteLine($"Executor balance before: {executorBalanceBefore}"); + +// // Prepare the claim call +// var claimCallData = erc20Contract.CreateCallData( +// "claim", +// new object[] +// { +// executorWalletAddress, // receiver +// 100, // quantity +// Constants.NATIVE_TOKEN_ADDRESS, // currency +// 0, // pricePerToken +// new object[] { Array.Empty(), BigInteger.Zero, BigInteger.Zero, Constants.ADDRESS_ZERO }, // allowlistProof +// Array.Empty() // data +// } +// ); + +// // Embed the claim call in the execute call +// var executeCallData = delegationContract.CreateCallData( +// method: "execute", +// parameters: new object[] +// { +// new List +// { +// new() +// { +// Data = claimCallData.HexToBytes(), +// To = erc20ContractAddress, +// Value = BigInteger.Zero +// } +// } +// } +// ); + +// // Execute from the executor wallet targeting the eoa which is pointing to the delegation contract +// var tx2 = await ThirdwebTransaction.Create(executorWallet, new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, data: executeCallData)); +// var hash2 = (await ThirdwebTransaction.SendAndWaitForTransactionReceipt(tx2)).TransactionHash; +// Console.WriteLine($"Token claim transaction hash: {hash2}"); + +// // Log erc20 balance of executor after the claim +// var executorBalanceAfter = await erc20Contract.ERC20_BalanceOf(executorWalletAddress); +// Console.WriteLine($"Executor balance after: {executorBalanceAfter}"); + +#endregion + #region Smart Ecosystem Wallet // var eco = await EcosystemWallet.Create(client: client, ecosystemId: "ecosystem.the-bonfire", authProvider: AuthProvider.Twitch); diff --git a/Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs b/Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs index 54ff0233..97b011a5 100644 --- a/Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs +++ b/Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs @@ -182,19 +182,6 @@ internal static (string callData, Function function) EncodeFunctionCall(Thirdweb { var contractRaw = new Contract(null, contract.Abi, contract.Address); var function = GetFunctionMatchSignature(contractRaw, method, parameters); - if (function == null) - { - if (method.Contains('(')) - { - var canonicalSignature = ExtractCanonicalSignature(method); - var selector = Nethereum.Util.Sha3Keccack.Current.CalculateHash(canonicalSignature)[..8]; - function = contractRaw.GetFunctionBySignature(selector); - } - else - { - throw new ArgumentException("Method signature not found in contract ABI."); - } - } return (function.GetData(parameters), function); } @@ -207,6 +194,11 @@ internal static (string callData, Function function) EncodeFunctionCall(Thirdweb /// The matching function, or null if no match is found. private static Function GetFunctionMatchSignature(Contract contract, string functionName, params object[] args) { + if (functionName.StartsWith("0x")) + { + return contract.GetFunctionBySignature(functionName); + } + var abi = contract.ContractBuilder.ContractABI; var functions = abi.Functions; var paramsCount = args?.Length ?? 0; @@ -218,7 +210,17 @@ private static Function GetFunctionMatchSignature(Contract contract, string func return contract.GetFunctionBySignature(sha); } } - return null; + + if (functionName.Contains('(')) + { + var canonicalSignature = ExtractCanonicalSignature(functionName); + var selector = Utils.HashMessage(canonicalSignature)[..8]; + return contract.GetFunctionBySignature(selector); + } + else + { + throw new ArgumentException("Method signature not found in contract ABI."); + } } /// diff --git a/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs b/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs index 7ae3fd59..f3ed9cdc 100644 --- a/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs +++ b/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs @@ -207,12 +207,12 @@ public struct EIP7702Authorization [JsonProperty(PropertyName = "s")] public string S { get; set; } - public EIP7702Authorization(BigInteger chainId, string address, BigInteger nonce, byte[] v, byte[] r, byte[] s) + public EIP7702Authorization(BigInteger chainId, string address, BigInteger nonce, byte[] yParity, byte[] r, byte[] s) { this.ChainId = new HexBigInteger(chainId).HexValue; this.Address = address; this.Nonce = new HexBigInteger(nonce).HexValue; - this.YParity = v.BytesToHex(); + this.YParity = yParity.BytesToHex(); this.R = r.BytesToHex(); this.S = s.BytesToHex(); } diff --git a/Thirdweb/Thirdweb.Utils/Utils.cs b/Thirdweb/Thirdweb.Utils/Utils.cs index 72d9630b..d1d8932d 100644 --- a/Thirdweb/Thirdweb.Utils/Utils.cs +++ b/Thirdweb/Thirdweb.Utils/Utils.cs @@ -1076,8 +1076,8 @@ public static (ThirdwebTransactionInput transactionInput, string signature) Deco /// The decoded transaction input and signature. public static (ThirdwebTransactionInput transactionInput, string signature) DecodeTransaction(byte[] signedRlpData) { - var maybeType = signedRlpData[0]; - if (maybeType is 0x04 or 0x02) + var txType = signedRlpData[0]; + if (txType is 0x04 or 0x02) { signedRlpData = signedRlpData.Skip(1).ToArray(); } @@ -1093,9 +1093,9 @@ public static (ThirdwebTransactionInput transactionInput, string signature) Deco var amount = decodedElements[6].RLPData.ToBigIntegerFromRLPDecoded(); var data = decodedElements[7].RLPData?.BytesToHex(); // 8th decoded element is access list - var authorizations = DecodeAutorizationList(decodedElements[9]?.RLPData); + var authorizations = txType == 0x04 ? DecodeAutorizationList(decodedElements[9]?.RLPData) : null; - var signature = RLPSignedDataDecoder.DecodeSignature(decodedElements, 10); + var signature = RLPSignedDataDecoder.DecodeSignature(decodedElements, txType == 0x04 ? 10 : 9); return ( new ThirdwebTransactionInput( chainId: chainId, @@ -1146,4 +1146,14 @@ public static List DecodeAutorizationList(byte[] authoriza return authorizationLists; } + + internal static byte[] ToByteArrayForRLPEncoding(this BigInteger value) + { + if (value == 0) + { + return Array.Empty(); + } + + return value.ToBytesForRLPEncoding(); + } } diff --git a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs index 62fcc154..9ffab046 100644 --- a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs @@ -329,14 +329,14 @@ public virtual Task SignTransaction(ThirdwebTransactionInput transaction var encodedData = new List { - RLP.EncodeElement(transaction.ChainId.Value.ToBytesForRLPEncoding()), - RLP.EncodeElement(transaction.Nonce.Value.ToBytesForRLPEncoding()), - RLP.EncodeElement(transaction.MaxPriorityFeePerGas.Value.ToBytesForRLPEncoding()), - RLP.EncodeElement(transaction.MaxFeePerGas.Value.ToBytesForRLPEncoding()), - RLP.EncodeElement(transaction.Gas.Value.ToBytesForRLPEncoding()), + RLP.EncodeElement(transaction.ChainId.Value.ToByteArrayForRLPEncoding()), + RLP.EncodeElement(transaction.Nonce.Value.ToByteArrayForRLPEncoding()), + RLP.EncodeElement(transaction.MaxPriorityFeePerGas.Value.ToByteArrayForRLPEncoding()), + RLP.EncodeElement(transaction.MaxFeePerGas.Value.ToByteArrayForRLPEncoding()), + RLP.EncodeElement(transaction.Gas.Value.ToByteArrayForRLPEncoding()), RLP.EncodeElement(transaction.To.HexToBytes()), - RLP.EncodeElement(transaction.Value.Value.ToBytesForRLPEncoding()), - RLP.EncodeElement(transaction.Data.HexToBytes()), + RLP.EncodeElement(transaction.Value.Value.ToByteArrayForRLPEncoding()), + RLP.EncodeElement(transaction.Data == null ? Array.Empty() : transaction.Data.HexToBytes()), new byte[] { 0xc0 }, // AccessList, empty so short list bytes }; @@ -347,10 +347,10 @@ public virtual Task SignTransaction(ThirdwebTransactionInput transaction { var encodedItem = new List() { - RLP.EncodeElement(authorizationList.ChainId.HexToBigInt().ToBytesForRLPEncoding()), + RLP.EncodeElement(authorizationList.ChainId.HexToBigInt().ToByteArrayForRLPEncoding()), RLP.EncodeElement(authorizationList.Address.HexToBytes()), - RLP.EncodeElement(authorizationList.Nonce.HexToBigInt().ToBytesForRLPEncoding()), - RLP.EncodeElement(authorizationList.YParity.HexToBytes()[0] == 0 ? Array.Empty() : authorizationList.YParity.HexToBytes()), + RLP.EncodeElement(authorizationList.Nonce.HexToBigInt().ToByteArrayForRLPEncoding()), + RLP.EncodeElement(authorizationList.YParity == "0x00" ? Array.Empty() : authorizationList.YParity.HexToBytes()), RLP.EncodeElement(authorizationList.R.HexToBytes().TrimZeroes()), RLP.EncodeElement(authorizationList.S.HexToBytes().TrimZeroes()) }; @@ -370,27 +370,17 @@ public virtual Task SignTransaction(ThirdwebTransactionInput transaction byte[] v; byte[] r; byte[] s; - - if (rawSignature != null && rawSignature.V != null) + if (rawSignature.V.Length == 0 || rawSignature.V[0] == 0) { - if (rawSignature.V.Length == 0 || rawSignature.V[0] == 0) - { - v = Array.Empty(); - } - else - { - v = rawSignature.V; - } - v = RLP.EncodeElement(v); - r = RLP.EncodeElement(rawSignature.R.TrimZeroes()); - s = RLP.EncodeElement(rawSignature.S.TrimZeroes()); + v = Array.Empty(); } else { - v = RLP.EncodeElement(Array.Empty()); - r = RLP.EncodeElement(Array.Empty()); - s = RLP.EncodeElement(Array.Empty()); + v = rawSignature.V; } + v = RLP.EncodeElement(v); + r = RLP.EncodeElement(rawSignature.R.TrimZeroes()); + s = RLP.EncodeElement(rawSignature.S.TrimZeroes()); encodedData.Add(v); encodedData.Add(r); @@ -464,7 +454,12 @@ public async Task SignAuthorization(BigInteger chainId, st { nonce++; } - var encodedData = new List { RLP.EncodeElement(chainId.ToBytesForRLPEncoding()), RLP.EncodeElement(contractAddress.HexToBytes()), RLP.EncodeElement(nonce.ToBytesForRLPEncoding()) }; + var encodedData = new List + { + RLP.EncodeElement(chainId.ToByteArrayForRLPEncoding()), + RLP.EncodeElement(contractAddress.HexToBytes()), + RLP.EncodeElement(nonce.ToByteArrayForRLPEncoding()) + }; var encodedBytes = RLP.EncodeList(encodedData.ToArray()); var returnElements = new byte[encodedBytes.Length + 1]; Array.Copy(encodedBytes.ToArray(), 0, returnElements, 1, encodedBytes.Length); From f1d10db6d8d7e2cecef81a1d823021479f2c4bfb Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Wed, 25 Dec 2024 00:26:24 +0700 Subject: [PATCH 6/8] fix tests --- .../Thirdweb.PrivateKeyWallet.Tests.cs | 30 +++++++------------ .../PrivateKeyWallet/PrivateKeyWallet.cs | 3 +- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.PrivateKeyWallet.Tests.cs b/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.PrivateKeyWallet.Tests.cs index 3183341b..41c0a62f 100644 --- a/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.PrivateKeyWallet.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.PrivateKeyWallet.Tests.cs @@ -209,16 +209,16 @@ public async Task SignTypedDataV4_Typed_NullData() public async Task SignTransaction_Success() { var account = await this.GetAccount(); - var transaction = new ThirdwebTransactionInput(421614) - { - From = await account.GetAddress(), - To = Constants.ADDRESS_ZERO, - // Value = new HexBigInteger(0), - Gas = new HexBigInteger(21000), - // Data = "0x", - Nonce = new HexBigInteger(99999999999), - GasPrice = new HexBigInteger(10000000000), - }; + var transaction = new ThirdwebTransactionInput( + chainId: 421614, + from: await account.GetAddress(), + to: Constants.ADDRESS_ZERO, + value: 0, + gas: 21000, + data: "0x", + nonce: 99999999999, + gasPrice: 10000000000 + ); var signature = await account.SignTransaction(transaction); Assert.NotNull(signature); } @@ -227,15 +227,7 @@ public async Task SignTransaction_Success() public async Task SignTransaction_NoFrom_Success() { var account = await this.GetAccount(); - var transaction = new ThirdwebTransactionInput(421614) - { - To = Constants.ADDRESS_ZERO, - // Value = new HexBigInteger(0), - Gas = new HexBigInteger(21000), - Data = "0x", - Nonce = new HexBigInteger(99999999999), - GasPrice = new HexBigInteger(10000000000), - }; + var transaction = new ThirdwebTransactionInput(chainId: 421614, to: Constants.ADDRESS_ZERO, value: 0, gas: 21000, data: "0x", nonce: 99999999999, gasPrice: 10000000000); var signature = await account.SignTransaction(transaction); Assert.NotNull(signature); } diff --git a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs index 9ffab046..3a3f5084 100644 --- a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs @@ -307,7 +307,6 @@ public virtual Task SignTransaction(ThirdwebTransactionInput transaction if (transaction.GasPrice != null) { - var gasPrice = transaction.GasPrice; var legacySigner = new LegacyTransactionSigner(); signedTransaction = legacySigner.SignTransaction( this.EcKey.GetPrivateKey(), @@ -315,7 +314,7 @@ public virtual Task SignTransaction(ThirdwebTransactionInput transaction transaction.To, transaction.Value.Value, transaction.Nonce.Value, - gasPrice.Value, + transaction.GasPrice.Value, transaction.Gas.Value, transaction.Data ); From 9abe28d5632facdee701e6546e37cccfe59b31a7 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 26 Dec 2024 00:12:23 +0700 Subject: [PATCH 7/8] cov --- .../Thirdweb.Contracts.Tests.cs | 8 ++++ .../Thirdweb.Utils/Thirdweb.Utils.Tests.cs | 28 +++++++++++++ .../Thirdweb.PrivateKeyWallet.Tests.cs | 42 +++++++++++++++++++ .../Thirdweb.SmartWallet.Tests.cs | 12 ++++++ .../ThirdwebTransaction.cs | 4 +- Thirdweb/Thirdweb.Utils/Utils.cs | 41 ++++++++++++++++-- .../PrivateKeyWallet/PrivateKeyWallet.cs | 5 ++- 7 files changed, 133 insertions(+), 7 deletions(-) diff --git a/Thirdweb.Tests/Thirdweb.Contracts/Thirdweb.Contracts.Tests.cs b/Thirdweb.Tests/Thirdweb.Contracts/Thirdweb.Contracts.Tests.cs index b2c9293a..e8a8314d 100644 --- a/Thirdweb.Tests/Thirdweb.Contracts/Thirdweb.Contracts.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.Contracts/Thirdweb.Contracts.Tests.cs @@ -78,6 +78,14 @@ public async Task ReadTest_Tuple() Assert.Equal(0, result.ReturnValue2); } + [Fact(Timeout = 120000)] + public async Task ReadTest_4Bytes() + { + var contract = await this.GetContract(); + var result = await ThirdwebContract.Read(contract, "0x06fdde03"); + Assert.Equal("Kitty DropERC20", result); + } + [Fact(Timeout = 120000)] public async Task ReadTest_FullSig() { diff --git a/Thirdweb.Tests/Thirdweb.Utils/Thirdweb.Utils.Tests.cs b/Thirdweb.Tests/Thirdweb.Utils/Thirdweb.Utils.Tests.cs index 35afbb31..f1802b95 100644 --- a/Thirdweb.Tests/Thirdweb.Utils/Thirdweb.Utils.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.Utils/Thirdweb.Utils.Tests.cs @@ -866,4 +866,32 @@ public void PreprocessTypedDataJson_NestedLargeNumbers() Assert.Equal(expectedJObject, processedJObject); } + + [Fact] + public void DecodeTransaction_1559WithAuthList() + { + var signedTxStr = + "0x04f8ca830de9fb8082011882031083025bee94ff5d95e5aa1b5af3f106079518228a92818737728080c0f85ef85c830de9fb94654f42b74885ee6803f403f077bc0409f1066c588080a0a5caed9b0c46657a452250a3279f45937940c87c45854aead6a902d99bc638f39faa58026c6b018d36b8935a42f2bcf68097c712c9f09ca014c70887678e08a980a027ecc69e66eb9e28cbe6edab10fc827fcb6d2a34cdcb89d8b6aabc6e35608692a0750d306b04a50a35de57bd6aca11f207a8dd404f9d92502ce6e3817e52f79a1c"; + (var txInput, var signature) = Utils.DecodeTransaction(signedTxStr); + Assert.Equal("0xfF5D95e5aA1B5Af3F106079518228A9281873772", txInput.To); + Assert.Equal("0x", txInput.Data); + Assert.Equal(0, txInput.Value.Value); + Assert.NotNull(txInput.AuthorizationList); + _ = Assert.Single(txInput.AuthorizationList); + Assert.Equal("0x654F42b74885EE6803F403f077bc0409f1066c58", txInput.AuthorizationList[0].Address); + Assert.Equal("0xde9fb", txInput.AuthorizationList[0].ChainId); + Assert.Equal("0x0", txInput.AuthorizationList[0].Nonce); + + (txInput, var signature2) = Utils.DecodeTransaction(signedTxStr.HexToBytes()); + Assert.Equal("0xfF5D95e5aA1B5Af3F106079518228A9281873772", txInput.To); + Assert.Equal("0x", txInput.Data); + Assert.Equal(0, txInput.Value.Value); + Assert.NotNull(txInput.AuthorizationList); + _ = Assert.Single(txInput.AuthorizationList); + Assert.Equal("0x654F42b74885EE6803F403f077bc0409f1066c58", txInput.AuthorizationList[0].Address); + Assert.Equal("0xde9fb", txInput.AuthorizationList[0].ChainId); + Assert.Equal("0x0", txInput.AuthorizationList[0].Nonce); + + Assert.Equal(signature, signature2); + } } diff --git a/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.PrivateKeyWallet.Tests.cs b/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.PrivateKeyWallet.Tests.cs index 41c0a62f..145c6c7a 100644 --- a/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.PrivateKeyWallet.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.PrivateKeyWallet.Tests.cs @@ -223,6 +223,26 @@ public async Task SignTransaction_Success() Assert.NotNull(signature); } + [Fact(Timeout = 120000)] + public async Task SignTransaction_WithAuthorizationList_Success() + { + var account = await this.GetAccount(); + var authorization = await account.SignAuthorization(421614, Constants.ADDRESS_ZERO, false); + var transaction = new ThirdwebTransactionInput( + chainId: 421614, + from: await account.GetAddress(), + to: Constants.ADDRESS_ZERO, + value: 0, + gas: 21000, + data: "0x", + nonce: 99999999999, + gasPrice: 10000000000, + authorization: authorization + ); + var signature = await account.SignTransaction(transaction); + Assert.NotNull(signature); + } + [Fact(Timeout = 120000)] public async Task SignTransaction_NoFrom_Success() { @@ -461,4 +481,26 @@ public async Task Export_ReturnsPrivateKey() Assert.NotNull(privateKey); Assert.Equal(privateKey, await wallet.Export()); } + + [Fact(Timeout = 120000)] + public async Task SignAuthorization_SelfExecution() + { + var wallet = await PrivateKeyWallet.Generate(this.Client); + var chainId = 911867; + var targetAddress = "0x654F42b74885EE6803F403f077bc0409f1066c58"; + + var currentNonce = await wallet.GetTransactionCount(chainId); + + var authorization = await wallet.SignAuthorization(chainId: chainId, contractAddress: targetAddress, willSelfExecute: false); + + Assert.Equal(chainId.NumberToHex(), authorization.ChainId); + Assert.Equal(targetAddress, authorization.Address); + Assert.True(authorization.Nonce.HexToNumber() == currentNonce); + + authorization = await wallet.SignAuthorization(chainId: chainId, contractAddress: targetAddress, willSelfExecute: true); + + Assert.Equal(chainId.NumberToHex(), authorization.ChainId); + Assert.Equal(targetAddress, authorization.Address); + Assert.True(authorization.Nonce.HexToNumber() == currentNonce + 1); + } } diff --git a/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.SmartWallet.Tests.cs b/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.SmartWallet.Tests.cs index c5ace0ec..23c3f3a7 100644 --- a/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.SmartWallet.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.SmartWallet.Tests.cs @@ -364,6 +364,18 @@ public async Task SwitchNetwork_NonZkToZk_Success() Assert.NotEqual(addy1, addy2); } + [Fact(Timeout = 120000)] + public async Task SignAuthorization_WithPrivateKeyWallet_Success() + { + var smartWallet = await SmartWallet.Create(personalWallet: await PrivateKeyWallet.Generate(this.Client), chainId: 421614); + var smartWalletSigner = await smartWallet.GetPersonalWallet(); + var signature1 = await smartWallet.SignAuthorization(chainId: 421614, contractAddress: Constants.ADDRESS_ZERO, willSelfExecute: true); + var signature2 = await smartWalletSigner.SignAuthorization(chainId: 421614, contractAddress: Constants.ADDRESS_ZERO, willSelfExecute: true); + Assert.Equal(signature1.ChainId, signature2.ChainId); + Assert.Equal(signature1.Address, signature2.Address); + Assert.Equal(signature1.Nonce, signature2.Nonce); + } + // [Fact(Timeout = 120000)] // public async Task MultiChainTransaction_Success() // { diff --git a/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs b/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs index 944436f3..7a08e461 100644 --- a/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs +++ b/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs @@ -271,12 +271,12 @@ public static async Task EstimateGasLimit(ThirdwebTransaction transa if (isZkSync) { var hex = (await rpc.SendRequestAsync("zks_estimateFee", transaction.Input).ConfigureAwait(false))["gas_limit"].ToString(); - baseGas = hex.HexToBigInt(); + baseGas = hex.HexToNumber(); } else { var hex = await rpc.SendRequestAsync("eth_estimateGas", transaction.Input).ConfigureAwait(false); - baseGas = hex.HexToBigInt(); + baseGas = hex.HexToNumber(); } return baseGas * 10 / divider; } diff --git a/Thirdweb/Thirdweb.Utils/Utils.cs b/Thirdweb/Thirdweb.Utils/Utils.cs index d1d8932d..362825d0 100644 --- a/Thirdweb/Thirdweb.Utils/Utils.cs +++ b/Thirdweb/Thirdweb.Utils/Utils.cs @@ -138,11 +138,46 @@ public static byte[] HexToBytes(this string hex) /// /// The hex string to convert. /// The big integer. + [Obsolete("Use HexToNumber instead.")] public static BigInteger HexToBigInt(this string hex) { return new HexBigInteger(hex).Value; } + /// + /// Converts the given hex string to a big integer. + /// + /// The hex string to convert. + /// The big integer. + public static BigInteger HexToNumber(this string hex) + { + return new HexBigInteger(hex).Value; + } + + /// + /// Converts the given big integer to a hex string. + /// + public static string NumberToHex(this BigInteger number) + { + return new HexBigInteger(number).HexValue; + } + + /// + /// Converts the given integer to a hex string. + /// + public static string NumberToHex(this int number) + { + return NumberToHex(number); + } + + /// + /// Converts the given long to a hex string. + /// + public static string NumberToHex(this long number) + { + return NumberToHex(number); + } + /// /// Converts the given string to a hex string. /// @@ -885,7 +920,7 @@ public static async Task FetchGasPrice(ThirdwebClient client, BigInt { var rpc = ThirdwebRPC.GetRpcInstance(client, chainId); var hex = await rpc.SendRequestAsync("eth_gasPrice").ConfigureAwait(false); - var gasPrice = hex.HexToBigInt(); + var gasPrice = hex.HexToNumber(); return withBump ? gasPrice * 10 / 9 : gasPrice; } @@ -1099,7 +1134,7 @@ public static (ThirdwebTransactionInput transactionInput, string signature) Deco return ( new ThirdwebTransactionInput( chainId: chainId, - to: receiverAddress, + to: receiverAddress.ToChecksumAddress(), nonce: nonce, gas: gasLimit, value: amount, @@ -1133,7 +1168,7 @@ public static List DecodeAutorizationList(byte[] authoriza var authorizationListItem = new EIP7702Authorization { ChainId = new HexBigInteger(decodedItem[0].RLPData.ToBigIntegerFromRLPDecoded()).HexValue, - Address = decodedItem[1].RLPData.BytesToHex(), + Address = decodedItem[1].RLPData.BytesToHex().ToChecksumAddress(), Nonce = new HexBigInteger(decodedItem[2].RLPData.ToBigIntegerFromRLPDecoded()).HexValue }; var signature = RLPSignedDataDecoder.DecodeSignature(decodedItem, 3); diff --git a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs index 3a3f5084..481b92b0 100644 --- a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs @@ -346,9 +346,9 @@ public virtual Task SignTransaction(ThirdwebTransactionInput transaction { var encodedItem = new List() { - RLP.EncodeElement(authorizationList.ChainId.HexToBigInt().ToByteArrayForRLPEncoding()), + RLP.EncodeElement(authorizationList.ChainId.HexToNumber().ToByteArrayForRLPEncoding()), RLP.EncodeElement(authorizationList.Address.HexToBytes()), - RLP.EncodeElement(authorizationList.Nonce.HexToBigInt().ToByteArrayForRLPEncoding()), + RLP.EncodeElement(authorizationList.Nonce.HexToNumber().ToByteArrayForRLPEncoding()), RLP.EncodeElement(authorizationList.YParity == "0x00" ? Array.Empty() : authorizationList.YParity.HexToBytes()), RLP.EncodeElement(authorizationList.R.HexToBytes().TrimZeroes()), RLP.EncodeElement(authorizationList.S.HexToBytes().TrimZeroes()) @@ -393,6 +393,7 @@ public virtual Task SignTransaction(ThirdwebTransactionInput transaction // (var tx, var sig) = Utils.DecodeTransaction(returnBytes); signedTransaction = returnBytes.ToHex(); + Console.WriteLine(signedTransaction); // (var tx, var sig) = Utils.DecodeTransaction("0x" + signedTransaction); } From 425c70dceaa6ee894ba478c6a0bf860543ce2087 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 26 Dec 2024 00:22:16 +0700 Subject: [PATCH 8/8] fix recursion --- Thirdweb/Thirdweb.Utils/Utils.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Thirdweb/Thirdweb.Utils/Utils.cs b/Thirdweb/Thirdweb.Utils/Utils.cs index 362825d0..5a09f076 100644 --- a/Thirdweb/Thirdweb.Utils/Utils.cs +++ b/Thirdweb/Thirdweb.Utils/Utils.cs @@ -167,7 +167,7 @@ public static string NumberToHex(this BigInteger number) /// public static string NumberToHex(this int number) { - return NumberToHex(number); + return NumberToHex(new BigInteger(number)); } /// @@ -175,7 +175,7 @@ public static string NumberToHex(this int number) /// public static string NumberToHex(this long number) { - return NumberToHex(number); + return NumberToHex(new BigInteger(number)); } ///