From 8537bf3c47c20c44424a709c38f53e201cdf66f0 Mon Sep 17 00:00:00 2001 From: Firekeeper <0xFirekeeper@gmail.com> Date: Sat, 7 Dec 2024 06:19:59 +0700 Subject: [PATCH] ERC-6492 Predeploy Signature Verification (#105) --- .../Thirdweb.SmartWallet.Tests.cs | 7 ++ .../Thirdweb.Contracts/ThirdwebContract.cs | 58 ++++------ .../Thirdweb.Extensions/ThirdwebExtensions.cs | 20 ++++ Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs | 10 +- Thirdweb/Thirdweb.Utils/Constants.cs | 6 ++ Thirdweb/Thirdweb.Utils/Utils.cs | 18 +++- .../SmartWallet/SmartWallet.cs | 101 +++++++++++------- .../Thirdweb.AccountAbstraction/AATypes.cs | 33 ++++++ 8 files changed, 174 insertions(+), 79 deletions(-) diff --git a/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.SmartWallet.Tests.cs b/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.SmartWallet.Tests.cs index 7b88aa8..a87daca 100644 --- a/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.SmartWallet.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.Wallets/Thirdweb.SmartWallet.Tests.cs @@ -144,8 +144,15 @@ public async Task GetAddress_WithOverride() public async Task PersonalSign() // This is the only different signing mechanism for smart wallets, also tests isValidSignature { var account = await this.GetSmartAccount(); + + // ERC-6942 Verification var sig = await account.PersonalSign("Hello, world!"); Assert.NotNull(sig); + + // Raw EIP-1271 Verification + await account.ForceDeploy(); + var sig2 = await account.PersonalSign("Hello, world!"); + Assert.NotNull(sig2); } [Fact(Timeout = 120000)] diff --git a/Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs b/Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs index dfcff81..54ff023 100644 --- a/Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs +++ b/Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs @@ -102,28 +102,12 @@ public static async Task FetchAbi(ThirdwebClient client, string address, public static async Task Read(ThirdwebContract contract, string method, params object[] parameters) { var rpc = ThirdwebRPC.GetRpcInstance(contract.Client, contract.Chain); - 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."); - } - } - - var data = function.GetData(parameters); + (var data, var function) = EncodeFunctionCall(contract, method, parameters); var resultData = await rpc.SendRequestAsync("eth_call", new { to = contract.Address, data }, "latest").ConfigureAwait(false); if ((typeof(T).IsGenericType && typeof(T).GetGenericTypeDefinition() == typeof(List<>)) || typeof(T).IsArray) { + var contractRaw = new Contract(null, contract.Abi, contract.Address); var functionAbi = contractRaw.ContractBuilder.ContractABI.FindFunctionABIFromInputData(data); var decoder = new FunctionCallDecoder(); var outputList = new FunctionCallDecoder().DecodeDefaultData(resultData.HexToBytes(), functionAbi.OutputParameters); @@ -168,23 +152,7 @@ public static async Task Read(ThirdwebContract contract, string method, pa /// A prepared transaction. public static async Task Prepare(IThirdwebWallet wallet, ThirdwebContract contract, string method, BigInteger weiValue, params object[] parameters) { - 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."); - } - } - - var data = function.GetData(parameters); + var data = contract.CreateCallData(method, parameters); var transaction = new ThirdwebTransactionInput(chainId: contract.Chain) { To = contract.Address, @@ -210,6 +178,26 @@ public static async Task Write(IThirdwebWallet walle return await ThirdwebTransaction.SendAndWaitForTransactionReceipt(thirdwebTx).ConfigureAwait(false); } + internal static (string callData, Function function) EncodeFunctionCall(ThirdwebContract contract, string method, params object[] parameters) + { + 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); + } + /// /// Gets a function matching the specified signature from the contract. /// diff --git a/Thirdweb/Thirdweb.Extensions/ThirdwebExtensions.cs b/Thirdweb/Thirdweb.Extensions/ThirdwebExtensions.cs index 311b134..54c000d 100644 --- a/Thirdweb/Thirdweb.Extensions/ThirdwebExtensions.cs +++ b/Thirdweb/Thirdweb.Extensions/ThirdwebExtensions.cs @@ -9,6 +9,13 @@ public static class ThirdwebExtensions { #region Common + /// + /// Returns whether the contract supports the specified interface. + /// + /// The contract instance. + /// The interface ID to check. + /// A task that represents the asynchronous operation. The task result contains a boolean indicating whether the contract supports the interface. + /// public static async Task SupportsInterface(this ThirdwebContract contract, string interfaceId) { if (contract == null) @@ -19,6 +26,19 @@ public static async Task SupportsInterface(this ThirdwebContract contract, return await ThirdwebContract.Read(contract, "supportsInterface", interfaceId.HexToBytes()); } + /// + /// Encodes the function call for the specified method and parameters. + /// + /// The contract instance. + /// The method to call. + /// The parameters for the method. + /// The generated calldata. + public static string CreateCallData(this ThirdwebContract contract, string method, params object[] parameters) + { + (var data, _) = ThirdwebContract.EncodeFunctionCall(contract, method, parameters); + return data; + } + /// /// Reads data from the contract using the specified method. /// diff --git a/Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs b/Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs index ff3bf13..729eddd 100644 --- a/Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs +++ b/Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs @@ -77,7 +77,7 @@ public async Task SendRequestAsync(string method, params o { lock (this._cacheLock) { - var cacheKey = GetCacheKey(method, parameters); + var cacheKey = GetCacheKey(this._rpcUrl.ToString(), method, parameters); if (this._cache.TryGetValue(cacheKey, out var cachedItem) && (DateTime.Now - cachedItem.Timestamp) < this._cacheDuration) { if (cachedItem.Response is TResponse cachedResponse) @@ -121,7 +121,7 @@ public async Task SendRequestAsync(string method, params o { lock (this._cacheLock) { - var cacheKey = GetCacheKey(method, parameters); + var cacheKey = GetCacheKey(this._rpcUrl.ToString(), method, parameters); this._cache[cacheKey] = (response, DateTime.Now); } return response; @@ -133,7 +133,7 @@ public async Task SendRequestAsync(string method, params o var deserializedResponse = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(result)); lock (this._cacheLock) { - var cacheKey = GetCacheKey(method, parameters); + var cacheKey = GetCacheKey(this._rpcUrl.ToString(), method, parameters); this._cache[cacheKey] = (deserializedResponse, DateTime.Now); } return deserializedResponse; @@ -238,10 +238,12 @@ private async Task SendBatchAsync(List batch) } } - private static string GetCacheKey(string method, params object[] parameters) + private static string GetCacheKey(string rpcUrl, string method, params object[] parameters) { var keyBuilder = new StringBuilder(); + _ = keyBuilder.Append(rpcUrl); + _ = keyBuilder.Append(method); foreach (var param in parameters) diff --git a/Thirdweb/Thirdweb.Utils/Constants.cs b/Thirdweb/Thirdweb.Utils/Constants.cs index 2ebb91a..eaf15b8 100644 --- a/Thirdweb/Thirdweb.Utils/Constants.cs +++ b/Thirdweb/Thirdweb.Utils/Constants.cs @@ -16,6 +16,12 @@ public static class Constants public const string DEFAULT_FACTORY_ADDRESS_V06 = "0x85e23b94e7F5E9cC1fF78BCe78cfb15B81f0DF00"; public const string DEFAULT_FACTORY_ADDRESS_V07 = "0x4bE0ddfebcA9A5A4a617dee4DeCe99E7c862dceb"; + public const string EIP_1271_MAGIC_VALUE = "0x1626ba7e00000000000000000000000000000000000000000000000000000000"; + public const string ERC_6492_MAGIC_VALUE = "0x6492649264926492649264926492649264926492649264926492649264926492"; + public const string MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11"; + public const string MULTICALL3_ABI = + /*lang=json,strict*/ + "[{\"type\":\"function\",\"stateMutability\":\"payable\",\"outputs\":[{\"type\":\"uint256\",\"name\":\"blockNumber\",\"internalType\":\"uint256\"},{\"type\":\"bytes[]\",\"name\":\"returnData\",\"internalType\":\"bytes[]\"}],\"name\":\"aggregate\",\"inputs\":[{\"type\":\"tuple[]\",\"name\":\"calls\",\"internalType\":\"struct Multicall3.Call[]\",\"components\":[{\"type\":\"address\",\"name\":\"target\",\"internalType\":\"address\"},{\"type\":\"bytes\",\"name\":\"callData\",\"internalType\":\"bytes\"}]}]},{\"type\":\"function\",\"stateMutability\":\"payable\",\"outputs\":[{\"type\":\"tuple[]\",\"name\":\"returnData\",\"internalType\":\"struct Multicall3.Result[]\",\"components\":[{\"type\":\"bool\",\"name\":\"success\",\"internalType\":\"bool\"},{\"type\":\"bytes\",\"name\":\"returnData\",\"internalType\":\"bytes\"}]}],\"name\":\"aggregate3\",\"inputs\":[{\"type\":\"tuple[]\",\"name\":\"calls\",\"internalType\":\"struct Multicall3.Call3[]\",\"components\":[{\"type\":\"address\",\"name\":\"target\",\"internalType\":\"address\"},{\"type\":\"bool\",\"name\":\"allowFailure\",\"internalType\":\"bool\"},{\"type\":\"bytes\",\"name\":\"callData\",\"internalType\":\"bytes\"}]}]},{\"type\":\"function\",\"stateMutability\":\"payable\",\"outputs\":[{\"type\":\"tuple[]\",\"name\":\"returnData\",\"internalType\":\"struct Multicall3.Result[]\",\"components\":[{\"type\":\"bool\",\"name\":\"success\",\"internalType\":\"bool\"},{\"type\":\"bytes\",\"name\":\"returnData\",\"internalType\":\"bytes\"}]}],\"name\":\"aggregate3Value\",\"inputs\":[{\"type\":\"tuple[]\",\"name\":\"calls\",\"internalType\":\"struct Multicall3.Call3Value[]\",\"components\":[{\"type\":\"address\",\"name\":\"target\",\"internalType\":\"address\"},{\"type\":\"bool\",\"name\":\"allowFailure\",\"internalType\":\"bool\"},{\"type\":\"uint256\",\"name\":\"value\",\"internalType\":\"uint256\"},{\"type\":\"bytes\",\"name\":\"callData\",\"internalType\":\"bytes\"}]}]},{\"type\":\"function\",\"stateMutability\":\"payable\",\"outputs\":[{\"type\":\"uint256\",\"name\":\"blockNumber\",\"internalType\":\"uint256\"},{\"type\":\"bytes32\",\"name\":\"blockHash\",\"internalType\":\"bytes32\"},{\"type\":\"tuple[]\",\"name\":\"returnData\",\"internalType\":\"struct Multicall3.Result[]\",\"components\":[{\"type\":\"bool\",\"name\":\"success\",\"internalType\":\"bool\"},{\"type\":\"bytes\",\"name\":\"returnData\",\"internalType\":\"bytes\"}]}],\"name\":\"blockAndAggregate\",\"inputs\":[{\"type\":\"tuple[]\",\"name\":\"calls\",\"internalType\":\"struct Multicall3.Call[]\",\"components\":[{\"type\":\"address\",\"name\":\"target\",\"internalType\":\"address\"},{\"type\":\"bytes\",\"name\":\"callData\",\"internalType\":\"bytes\"}]}]},{\"type\":\"function\",\"stateMutability\":\"view\",\"outputs\":[{\"type\":\"uint256\",\"name\":\"basefee\",\"internalType\":\"uint256\"}],\"name\":\"getBasefee\",\"inputs\":[]},{\"type\":\"function\",\"stateMutability\":\"view\",\"outputs\":[{\"type\":\"bytes32\",\"name\":\"blockHash\",\"internalType\":\"bytes32\"}],\"name\":\"getBlockHash\",\"inputs\":[{\"type\":\"uint256\",\"name\":\"blockNumber\",\"internalType\":\"uint256\"}]},{\"type\":\"function\",\"stateMutability\":\"view\",\"outputs\":[{\"type\":\"uint256\",\"name\":\"blockNumber\",\"internalType\":\"uint256\"}],\"name\":\"getBlockNumber\",\"inputs\":[]},{\"type\":\"function\",\"stateMutability\":\"view\",\"outputs\":[{\"type\":\"uint256\",\"name\":\"chainid\",\"internalType\":\"uint256\"}],\"name\":\"getChainId\",\"inputs\":[]},{\"type\":\"function\",\"stateMutability\":\"view\",\"outputs\":[{\"type\":\"address\",\"name\":\"coinbase\",\"internalType\":\"address\"}],\"name\":\"getCurrentBlockCoinbase\",\"inputs\":[]},{\"type\":\"function\",\"stateMutability\":\"view\",\"outputs\":[{\"type\":\"uint256\",\"name\":\"difficulty\",\"internalType\":\"uint256\"}],\"name\":\"getCurrentBlockDifficulty\",\"inputs\":[]},{\"type\":\"function\",\"stateMutability\":\"view\",\"outputs\":[{\"type\":\"uint256\",\"name\":\"gaslimit\",\"internalType\":\"uint256\"}],\"name\":\"getCurrentBlockGasLimit\",\"inputs\":[]},{\"type\":\"function\",\"stateMutability\":\"view\",\"outputs\":[{\"type\":\"uint256\",\"name\":\"timestamp\",\"internalType\":\"uint256\"}],\"name\":\"getCurrentBlockTimestamp\",\"inputs\":[]},{\"type\":\"function\",\"stateMutability\":\"view\",\"outputs\":[{\"type\":\"uint256\",\"name\":\"balance\",\"internalType\":\"uint256\"}],\"name\":\"getEthBalance\",\"inputs\":[{\"type\":\"address\",\"name\":\"addr\",\"internalType\":\"address\"}]},{\"type\":\"function\",\"stateMutability\":\"view\",\"outputs\":[{\"type\":\"bytes32\",\"name\":\"blockHash\",\"internalType\":\"bytes32\"}],\"name\":\"getLastBlockHash\",\"inputs\":[]},{\"type\":\"function\",\"stateMutability\":\"payable\",\"outputs\":[{\"type\":\"tuple[]\",\"name\":\"returnData\",\"internalType\":\"struct Multicall3.Result[]\",\"components\":[{\"type\":\"bool\",\"name\":\"success\",\"internalType\":\"bool\"},{\"type\":\"bytes\",\"name\":\"returnData\",\"internalType\":\"bytes\"}]}],\"name\":\"tryAggregate\",\"inputs\":[{\"type\":\"bool\",\"name\":\"requireSuccess\",\"internalType\":\"bool\"},{\"type\":\"tuple[]\",\"name\":\"calls\",\"internalType\":\"struct Multicall3.Call[]\",\"components\":[{\"type\":\"address\",\"name\":\"target\",\"internalType\":\"address\"},{\"type\":\"bytes\",\"name\":\"callData\",\"internalType\":\"bytes\"}]}]},{\"type\":\"function\",\"stateMutability\":\"payable\",\"outputs\":[{\"type\":\"uint256\",\"name\":\"blockNumber\",\"internalType\":\"uint256\"},{\"type\":\"bytes32\",\"name\":\"blockHash\",\"internalType\":\"bytes32\"},{\"type\":\"tuple[]\",\"name\":\"returnData\",\"internalType\":\"struct Multicall3.Result[]\",\"components\":[{\"type\":\"bool\",\"name\":\"success\",\"internalType\":\"bool\"},{\"type\":\"bytes\",\"name\":\"returnData\",\"internalType\":\"bytes\"}]}],\"name\":\"tryBlockAndAggregate\",\"inputs\":[{\"type\":\"bool\",\"name\":\"requireSuccess\",\"internalType\":\"bool\"},{\"type\":\"tuple[]\",\"name\":\"calls\",\"internalType\":\"struct Multicall3.Call[]\",\"components\":[{\"type\":\"address\",\"name\":\"target\",\"internalType\":\"address\"},{\"type\":\"bytes\",\"name\":\"callData\",\"internalType\":\"bytes\"}]}]}]"; public const string REDIRECT_HTML = "

Authentication Complete!

You may close this tab now and return to the game

"; diff --git a/Thirdweb/Thirdweb.Utils/Utils.cs b/Thirdweb/Thirdweb.Utils/Utils.cs index 7a7bd86..9ecb99a 100644 --- a/Thirdweb/Thirdweb.Utils/Utils.cs +++ b/Thirdweb/Thirdweb.Utils/Utils.cs @@ -4,6 +4,7 @@ using System.Text; using System.Text.RegularExpressions; using ADRaffy.ENSNormalize; +using Nethereum.ABI; using Nethereum.ABI.EIP712; using Nethereum.ABI.FunctionEncoding; using Nethereum.ABI.FunctionEncoding.Attributes; @@ -87,8 +88,7 @@ public static byte[] HashPrefixedMessage(this byte[] messageBytes) /// The hashed message. public static string HashPrefixedMessage(this string message) { - var signer = new EthereumMessageSigner(); - return signer.HashPrefixedMessage(Encoding.UTF8.GetBytes(message)).ToHex(true); + return HashPrefixedMessage(Encoding.UTF8.GetBytes(message)).BytesToHex(); } /// @@ -1020,4 +1020,18 @@ static void StringifyLargeNumbers(JToken token) return jObject.ToString(); } + + /// + /// Serializes a signature for use with ERC-6492. The signature must be generated by a signer for an ERC-4337 Account Factory account with counterfactual deployment addresses. + /// + /// The ERC-4337 Account Factory address + /// Account deployment calldata (if not deployed) for counterfactual verification + /// The original signature + /// The serialized signature hex string. + public static string SerializeErc6492Signature(string address, byte[] data, byte[] signature) + { + var encoder = new ABIEncode(); + 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); + } } diff --git a/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs b/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs index 0208604..6f5d941 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartWallet/SmartWallet.cs @@ -321,19 +321,66 @@ public async Task ForceDeploy() } /// - /// Verifies if a signature is valid for a message using EIP-1271. + /// Verifies if a signature is valid for a message using EIP-1271 or ERC-6492. /// /// The message to verify. /// The signature to verify. /// True if the signature is valid, otherwise false. public async Task IsValidSignature(string message, string signature) { - try + var isCounterFactual = signature.EndsWith(Constants.ERC_6492_MAGIC_VALUE[2..]); + + // ERC-6492 + if (isCounterFactual) { - var magicValue = await ThirdwebContract.Read(this._accountContract, "isValidSignature", message.StringToHex(), signature.HexToBytes()).ConfigureAwait(false); - return magicValue.BytesToHex() == new byte[] { 0x16, 0x26, 0xba, 0x7e }.BytesToHex(); + var erc6492Sig = new ABIEncode().DecodeEncodedComplexType(signature.HexToBytes().Take(signature.Length - 32).ToArray()); + var multicall3 = await ThirdwebContract.Create(this.Client, Constants.MULTICALL3_ADDRESS, this._chainId).ConfigureAwait(false); + List result; + try + { + result = await multicall3 + .Read>( + method: "aggregate3", + parameters: new object[] + { + new List + { + new() + { + Target = erc6492Sig.Create2Factory, + AllowFailure = true, + CallData = erc6492Sig.FactoryCalldata + }, + new() + { + Target = this._accountContract.Address, + AllowFailure = true, + CallData = this._accountContract.CreateCallData("isValidSignature", message.HashPrefixedMessage().HexToBytes(), erc6492Sig.SigToValidate).HexToBytes() + } + } + } + ) + .ConfigureAwait(false); + + var success = result[1].Success; + var returnData = result[1].ReturnData.BytesToHex(); + if (!success) + { + var revertMsg = new Nethereum.ABI.FunctionEncoding.FunctionCallDecoder().DecodeFunctionErrorMessage(returnData); + throw new Exception($"SmartAccount.IsValidSignature: Call to account contract failed: {revertMsg}"); + } + else + { + return returnData == Constants.EIP_1271_MAGIC_VALUE; + } + } + catch + { + return false; + } } - catch + // EIP-1271 + else { try { @@ -1030,7 +1077,7 @@ public Task PersonalSign(byte[] rawMessage) } /// - /// Signs a message with the personal account. If the smart account is deployed, the message will be wrapped 712 and signed by the smart account and verified with 1271. If the smart account is not deployed, it will deploy it first. + /// Signs a message with the personal account. The message will be verified using EIPs 1271 and 6492 if applicable. /// /// The message to sign. /// The signature. @@ -1041,42 +1088,20 @@ public async Task PersonalSign(string message) return await this._personalAccount.PersonalSign(message).ConfigureAwait(false); } - if (!await this.IsDeployed()) - { - while (this.IsDeploying) - { - await ThirdwebTask.Delay(100).ConfigureAwait(false); - } - await this.ForceDeploy().ConfigureAwait(false); - } - - if (await this.IsDeployed().ConfigureAwait(false)) - { - var originalMsgHash = Encoding.UTF8.GetBytes(message).HashPrefixedMessage(); - bool factorySupports712; - try - { - _ = await ThirdwebContract.Read(this._accountContract, "getMessageHash", originalMsgHash).ConfigureAwait(false); - factorySupports712 = true; - } - catch - { - factorySupports712 = false; - } + var originalMsgHash = Encoding.UTF8.GetBytes(message).HashPrefixedMessage(); - var sig = factorySupports712 - ? await EIP712 - .GenerateSignature_SmartAccount_AccountMessage("Account", "1", this._chainId, await this.GetAddress().ConfigureAwait(false), originalMsgHash, this._personalAccount) - .ConfigureAwait(false) - : await this._personalAccount.PersonalSign(message).ConfigureAwait(false); + var sig = await EIP712 + .GenerateSignature_SmartAccount_AccountMessage("Account", "1", this._chainId, await this.GetAddress().ConfigureAwait(false), originalMsgHash, this._personalAccount) + .ConfigureAwait(false); - var isValid = await this.IsValidSignature(message, sig); - return isValid ? sig : throw new Exception("Invalid signature."); - } - else + if (!await this.IsDeployed().ConfigureAwait(false)) { - throw new Exception("Smart account could not be deployed, unable to sign message."); + (_, var factory, var factoryData) = await this.GetInitCode(); + sig = Utils.SerializeErc6492Signature(address: factory, data: factoryData.HexToBytes(), signature: sig.HexToBytes()); } + + var isValid = await this.IsValidSignature(message, sig); + return isValid ? sig : throw new Exception("Invalid signature."); } public async Task RecoverAddressFromPersonalSign(string message, string signature) diff --git a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs index f608dda..5505461 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs @@ -452,3 +452,36 @@ public class SignerPermissions [Parameter("uint128", "endTimestamp", 5)] public virtual BigInteger EndTimestamp { get; set; } } + +public class Multicall3_Call3 +{ + [Parameter("address", "target", 1)] + public virtual string Target { get; set; } + + [Parameter("bool", "allowFailure", 2)] + public virtual bool AllowFailure { get; set; } + + [Parameter("bytes", "callData", 3)] + public virtual byte[] CallData { get; set; } +} + +public class Multicall3_Result +{ + [Parameter("bool", "success", 1)] + public virtual bool Success { get; set; } + + [Parameter("bytes", "returnData", 2)] + public virtual byte[] ReturnData { get; set; } +} + +public class Erc6492Signature +{ + [Parameter("address", "create2Factory", 1)] + public string Create2Factory { get; set; } + + [Parameter("bytes", "factoryCalldata", 2)] + public byte[] FactoryCalldata { get; set; } + + [Parameter("bytes", "callData", 3)] + public byte[] SigToValidate { get; set; } +}