diff --git a/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs b/Thirdweb/Thirdweb.Transactions/ThirdwebTransaction.cs index cd3abb9..3b555a4 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 d6ef3c9..41e638a 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 164d4b1..daf26b4 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 8a2231e..726e504 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 26d90a4..1589a6b 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 68548c8..613a5d1 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 }