From eced8afdbd99462c7069241e771fce42953f7ffd Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Sat, 15 Mar 2025 03:57:06 +0700 Subject: [PATCH 01/15] [Beta] ThirdwebWallet - An All Encompassing Wallet Test Only Attempt to create an ultimate 7702 account, with the best dx possible. Eventually will have an implied executor, improving creation DX. Closes TOOL-3195 --- Thirdweb.Console/Program.cs | 126 ++------ Thirdweb/Thirdweb.Utils/Constants.cs | 2 + Thirdweb/Thirdweb.Utils/Utils.cs | 72 ++++- Thirdweb/Thirdweb.Wallets/EIP712.cs | 13 +- Thirdweb/Thirdweb.Wallets/EIP712Encoder.cs | 279 ++++++++++++++++++ .../EngineWallet/EngineWallet.cs | 4 +- .../PrivateKeyWallet/PrivateKeyWallet.cs | 12 +- .../Thirdweb.AccountAbstraction/AATypes.cs | 136 +++++++-- Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs | 202 +++++++++++++ 9 files changed, 701 insertions(+), 145 deletions(-) create mode 100644 Thirdweb/Thirdweb.Wallets/EIP712Encoder.cs create mode 100644 Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index 38a14c2..990dfe1 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -25,10 +25,10 @@ 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)); +var client = ThirdwebClient.Create(secretKey: secretKey, rpcOverrides: new Dictionary { { 11155111, "https://eth-sepolia.public.blastapi.io" } }); -// Create a private key wallet -var privateKeyWallet = await PrivateKeyWallet.Generate(client: client); +// Create a private key wallet +var privateKeyWallet = await PrivateKeyWallet.Generate(client); // var walletAddress = await privateKeyWallet.GetAddress(); // Console.WriteLine($"PK Wallet address: {walletAddress}"); @@ -229,113 +229,33 @@ #region EIP-7702 -// // -------------------------------------------------------------------------- -// // Configuration -// // -------------------------------------------------------------------------- +// // The session key signer +// var executorWallet = await PrivateKeyWallet.Create(client, privateKey); // needs to be funded, for now -// var chainWith7702 = 911867; -// var delegationContractAddress = "0xb012446cba783d0f7723daf96cf4c49005022307"; // MinimalAccount - -// // Required environment variables -// var backendWalletAddress = Environment.GetEnvironmentVariable("ENGINE_BACKEND_WALLET_ADDRESS") ?? throw new Exception("ENGINE_BACKEND_WALLET_ADDRESS is required"); -// var engineUrl = Environment.GetEnvironmentVariable("ENGINE_URL") ?? throw new Exception("ENGINE_URL is required"); -// var engineAccessToken = Environment.GetEnvironmentVariable("ENGINE_ACCESS_TOKEN") ?? throw new Exception("ENGINE_ACCESS_TOKEN is required"); - -// // -------------------------------------------------------------------------- -// // Initialize Engine Wallet -// // -------------------------------------------------------------------------- - -// var engineWallet = await EngineWallet.Create(client, engineUrl, engineAccessToken, backendWalletAddress, 15); - -// // -------------------------------------------------------------------------- -// // Delegation Contract Implementation -// // -------------------------------------------------------------------------- - -// var delegationContract = await ThirdwebContract.Create(client, delegationContractAddress, chainWith7702); - -// // Initialize a (to-be) 7702 EOA -// var eoaWallet = await PrivateKeyWallet.Generate(client); -// var eoaWalletAddress = await eoaWallet.GetAddress(); -// Console.WriteLine($"EOA address: {eoaWalletAddress}"); - -// // Sign the authorization to point to the delegation contract -// var authorization = await eoaWallet.SignAuthorization(chainWith7702, delegationContractAddress, willSelfExecute: false); -// Console.WriteLine($"Authorization: {JsonConvert.SerializeObject(authorization, Formatting.Indented)}"); - -// // Sign message for session key -// var sessionKeyParams = new SessionKeyParams_7702() +// // Session key permissions +// var sessionKeyParams = new SessionSpec() // { -// Signer = backendWalletAddress, -// NativeTokenLimitPerTransaction = 0, -// StartTimestamp = 0, -// EndTimestamp = Utils.GetUnixTimeStampNow() + (3600 * 24), -// ApprovedTargets = new List { Constants.ADDRESS_ZERO }, -// Uid = Guid.NewGuid().ToByteArray() -// }; -// var sessionKeySig = await EIP712.GenerateSignature_SmartAccount_7702("MinimalAccount", "1", chainWith7702, eoaWalletAddress, sessionKeyParams, eoaWallet); - -// // Create call data for the session key -// var sessionKeyCallData = delegationContract.CreateCallData("createSessionKeyWithSig", sessionKeyParams, sessionKeySig.HexToBytes()); - -// // Execute the delegation & session key creation in one go, from the backend! -// var delegationReceipt = await engineWallet.ExecuteTransaction(new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, data: sessionKeyCallData, authorization: authorization)); -// Console.WriteLine($"Delegation Execution Receipt: {JsonConvert.SerializeObject(delegationReceipt, Formatting.Indented)}"); - -// // Verify contract code deployed to the EOA -// var rpc = ThirdwebRPC.GetRpcInstance(client, chainWith7702); -// var code = await rpc.SendRequestAsync("eth_getCode", eoaWalletAddress, "latest"); -// Console.WriteLine($"EOA code: {code}"); - -// // The EOA is now a contract -// var eoaContract = await ThirdwebContract.Create(client, eoaWalletAddress, chainWith7702, delegationContract.Abi); - -// // -------------------------------------------------------------------------- -// // Mint Tokens (DropERC20) to the EOA Using the backend session key -// // -------------------------------------------------------------------------- - -// var erc20ContractAddress = "0xAA462a5BE0fc5214507FDB4fB2474a7d5c69065b"; // DropERC20 -// var erc20Contract = await ThirdwebContract.Create(client, erc20ContractAddress, chainWith7702); - -// // Log ERC20 balance before mint -// var eoaBalanceBefore = await erc20Contract.ERC20_BalanceOf(eoaWalletAddress); -// Console.WriteLine($"EOA balance before: {eoaBalanceBefore}"); - -// // Create execution call data (calling 'claim' on the DropERC20) -// var executeCallData = eoaContract.CreateCallData( -// "execute", -// new object[] +// Signer = await executorWallet.GetAddress(), +// ExpiresAt = Utils.GetUnixTimeStampNow() + (3600 * 24), +// CallPolicies = new List() { }, +// TransferPolicies = new List() // { -// new List +// new() // { -// new() -// { -// Data = erc20Contract -// .CreateCallData( -// "claim", -// new object[] -// { -// eoaWalletAddress, // 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 -// } -// ) -// .HexToBytes(), -// To = erc20ContractAddress, -// Value = BigInteger.Zero -// } +// Target = await Utils.GetAddressFromENS(client, "vitalik.eth"), +// MaxValuePerUse = BigInteger.Zero, +// ValueLimit = new() // } -// } -// ); +// }, +// Uid = Guid.NewGuid().ToByteArray() +// }; -// var executeReceipt = await engineWallet.ExecuteTransaction(new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, data: executeCallData)); -// Console.WriteLine($"Execute receipt: {JsonConvert.SerializeObject(executeReceipt, Formatting.Indented)}"); +// // This wallet explicitly uses 7702 delegation to the thirdweb MinimalAccount and creates a session key from which every tx will be executed +// var thirdwebWallet = await ThirdwebWallet.Create(client, 11155111, privateKeyWallet, executorWallet, sessionKeyParams); -// // Log ERC20 balance after mint -// var eoaBalanceAfter = await erc20Contract.ERC20_BalanceOf(eoaWalletAddress); -// Console.WriteLine($"EOA balance after: {eoaBalanceAfter}"); +// // Simple transfer, will use the session key automatically +// var receipt = await thirdwebWallet.Transfer(11155111, await Utils.GetAddressFromENS(client, "vitalik.eth"), 0); +// Console.WriteLine($"Receipt: {receipt}"); #endregion diff --git a/Thirdweb/Thirdweb.Utils/Constants.cs b/Thirdweb/Thirdweb.Utils/Constants.cs index a7317c6..c273a5e 100644 --- a/Thirdweb/Thirdweb.Utils/Constants.cs +++ b/Thirdweb/Thirdweb.Utils/Constants.cs @@ -12,6 +12,8 @@ public static class Constants public const string NATIVE_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; public const double DECIMALS_18 = 1000000000000000000; + public const string MINIMAL_ACCOUNT_7702 = "0xFabf2ca2377Bbc199EaE439b71fcD6a5925127e2"; + public const string ENTRYPOINT_ADDRESS_V06 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"; public const string ENTRYPOINT_ADDRESS_V07 = "0x0000000071727De22E5E9d8BAf0edAc6f37da032"; diff --git a/Thirdweb/Thirdweb.Utils/Utils.cs b/Thirdweb/Thirdweb.Utils/Utils.cs index de8fc98..bfbba2b 100644 --- a/Thirdweb/Thirdweb.Utils/Utils.cs +++ b/Thirdweb/Thirdweb.Utils/Utils.cs @@ -540,7 +540,7 @@ public static string ToJsonExternalWalletFriendly(TypedData + /// Waits for the transaction receipt. + /// + /// The Thirdweb client. + /// The chain ID. + /// The transaction hash. + /// The cancellation token. + /// The transaction receipt. + public static async Task WaitForTransactionReceipt(ThirdwebClient client, BigInteger chainId, string txHash, CancellationToken cancellationToken = default) + { + using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); + cts.CancelAfter(client.FetchTimeoutOptions.GetTimeout(TimeoutType.Other)); + + var rpc = ThirdwebRPC.GetRpcInstance(client, chainId); + ThirdwebTransactionReceipt receipt = null; + + try + { + do + { + receipt = await rpc.SendRequestAsync("eth_getTransactionReceipt", txHash).ConfigureAwait(false); + if (receipt == null) + { + await ThirdwebTask.Delay(100, cancellationToken).ConfigureAwait(false); + } + } while (receipt == null && !cts.Token.IsCancellationRequested); + + if (receipt == null) + { + throw new Exception($"Transaction {txHash} not found within the timeout period."); + } + + if (receipt.Status != null && receipt.Status.Value == 0) + { + throw new Exception($"Transaction {txHash} execution reverted."); + } + + var userOpEvent = receipt.DecodeAllEvents(); + if (userOpEvent != null && userOpEvent.Count > 0 && !userOpEvent[0].Event.Success) + { + var revertReasonEvent = receipt.DecodeAllEvents(); + var postOpRevertReasonEvent = receipt.DecodeAllEvents(); + if (revertReasonEvent != null && revertReasonEvent.Count > 0) + { + var revertReason = revertReasonEvent[0].Event.RevertReason; + var revertReasonString = new FunctionCallDecoder().DecodeFunctionErrorMessage(revertReason.ToHex(true)); + throw new Exception($"Transaction {txHash} execution silently reverted: {revertReasonString}"); + } + else if (postOpRevertReasonEvent != null && postOpRevertReasonEvent.Count > 0) + { + var revertReason = postOpRevertReasonEvent[0].Event.RevertReason; + var revertReasonString = new FunctionCallDecoder().DecodeFunctionErrorMessage(revertReason.ToHex(true)); + throw new Exception($"Transaction {txHash} execution silently reverted: {revertReasonString}"); + } + else + { + throw new Exception($"Transaction {txHash} execution silently reverted with no reason string"); + } + } + } + catch (OperationCanceledException) + { + throw new Exception($"Transaction receipt polling for hash {txHash} was cancelled."); + } + + return receipt; + } } diff --git a/Thirdweb/Thirdweb.Wallets/EIP712.cs b/Thirdweb/Thirdweb.Wallets/EIP712.cs index da749f1..8588ecc 100644 --- a/Thirdweb/Thirdweb.Wallets/EIP712.cs +++ b/Thirdweb/Thirdweb.Wallets/EIP712.cs @@ -51,7 +51,7 @@ public static async Task GenerateSignature_SmartAccount_7702( string version, BigInteger chainId, string verifyingContract, - AccountAbstraction.SessionKeyParams_7702 sessionKeyParams, + AccountAbstraction.SessionSpec sessionKeyParams, IThirdwebWallet signer ) { @@ -268,8 +268,15 @@ public static TypedData GetTypedDefinition_SmartAccount_7702(string doma ChainId = chainId, VerifyingContract = verifyingContract, }, - Types = MemberDescriptionFactory.GetTypesMemberDescription(typeof(Domain), typeof(AccountAbstraction.SessionKeyParams_7702)), - PrimaryType = "SessionKeyParams", + Types = MemberDescriptionFactory.GetTypesMemberDescription( + typeof(Domain), + typeof(AccountAbstraction.SessionSpec), + typeof(AccountAbstraction.CallSpec), + typeof(AccountAbstraction.Constraint), + typeof(AccountAbstraction.TransferSpec), + typeof(AccountAbstraction.UsageLimit) + ), + PrimaryType = "SessionSpec", }; } diff --git a/Thirdweb/Thirdweb.Wallets/EIP712Encoder.cs b/Thirdweb/Thirdweb.Wallets/EIP712Encoder.cs new file mode 100644 index 0000000..c9520d5 --- /dev/null +++ b/Thirdweb/Thirdweb.Wallets/EIP712Encoder.cs @@ -0,0 +1,279 @@ +using System.Text; +using Nethereum.Hex.HexConvertors.Extensions; +using Nethereum.ABI; +using Nethereum.ABI.FunctionEncoding; +using Nethereum.Util; +using System.Collections; +using System.Numerics; +using Nethereum.ABI.EIP712; + +namespace Thirdweb; + +public class EIP712Encoder +{ + public static EIP712Encoder Current { get; } = new EIP712Encoder(); + + private readonly ABIEncode _abiEncode = new(); + private readonly ParametersEncoder _parametersEncoder = new(); + + public byte[] EncodeTypedData(T message, TypedData typedData) + { + typedData.Message = MemberValueFactory.CreateFromMessage(message); + typedData.EnsureDomainRawValuesAreInitialised(); + return this.EncodeTypedDataRaw(typedData); + } + + public byte[] EncodeTypedData(T data, TDomain domain, string primaryTypeName) + { + var typedData = this.GenerateTypedData(data, domain, primaryTypeName); + + return this.EncodeTypedData(typedData); + } + + public byte[] EncodeTypedData(string json) + { + var typedDataRaw = TypedDataRawJsonConversion.DeserialiseJsonToRawTypedData(json); + return this.EncodeTypedDataRaw(typedDataRaw); + } + + public byte[] EncodeTypedData(string json, string messageKeySelector = "message") + { + var typedDataRaw = TypedDataRawJsonConversion.DeserialiseJsonToRawTypedData(json, messageKeySelector); + return this.EncodeTypedDataRaw(typedDataRaw); + } + + public byte[] EncodeAndHashTypedData(T message, TypedData typedData) + { + var encodedData = this.EncodeTypedData(message, typedData); + return Sha3Keccack.Current.CalculateHash(encodedData); + } + + public byte[] EncodeAndHashTypedData(TypedData typedData) + { + var encodedData = this.EncodeTypedData(typedData); + return Sha3Keccack.Current.CalculateHash(encodedData); + } + + public byte[] EncodeTypedData(TypedData typedData) + { + typedData.EnsureDomainRawValuesAreInitialised(); + return this.EncodeTypedDataRaw(typedData); + } + + public byte[] EncodeTypedDataRaw(TypedDataRaw typedData) + { + using var memoryStream = new MemoryStream(); + using var writer = new BinaryWriter(memoryStream); + writer.Write("1901".HexToByteArray()); + writer.Write(this.HashStruct(typedData.Types, "EIP712Domain", typedData.DomainRawValues)); + writer.Write(this.HashStruct(typedData.Types, typedData.PrimaryType, typedData.Message)); + + writer.Flush(); + var result = memoryStream.ToArray(); + return result; + } + + public byte[] HashDomainSeparator(TypedData typedData) + { + typedData.EnsureDomainRawValuesAreInitialised(); + using var memoryStream = new MemoryStream(); + using var writer = new BinaryWriter(memoryStream); + writer.Write(this.HashStruct(typedData.Types, "EIP712Domain", typedData.DomainRawValues)); + writer.Flush(); + var result = memoryStream.ToArray(); + return result; + } + + public byte[] HashStruct(T message, string primaryType, params Type[] types) + { + var memberDescriptions = MemberDescriptionFactory.GetTypesMemberDescription(types); + var memberValue = MemberValueFactory.CreateFromMessage(message); + return this.HashStruct(memberDescriptions, primaryType, memberValue); + } + + public string GetEncodedType(string primaryType, params Type[] types) + { + var memberDescriptions = MemberDescriptionFactory.GetTypesMemberDescription(types); + return EncodeType(memberDescriptions, primaryType); + } + + public string GetEncodedTypeDomainSeparator(TypedData typedData) + { + typedData.EnsureDomainRawValuesAreInitialised(); + return EncodeType(typedData.Types, "EIP712Domain"); + } + + private byte[] HashStruct(IDictionary types, string primaryType, IEnumerable message) + { + using var memoryStream = new MemoryStream(); + using var writer = new BinaryWriter(memoryStream); + var encodedType = EncodeType(types, primaryType); + var typeHash = Sha3Keccack.Current.CalculateHash(Encoding.UTF8.GetBytes(encodedType)); + writer.Write(typeHash); + + this.EncodeData(writer, types, message); + + writer.Flush(); + return Sha3Keccack.Current.CalculateHash(memoryStream.ToArray()); + } + + private static string EncodeType(IDictionary types, string typeName) + { + var encodedTypes = EncodeTypes(types, typeName); + var encodedPrimaryType = encodedTypes.Single(x => x.Key == typeName); + var encodedReferenceTypes = encodedTypes.Where(x => x.Key != typeName).OrderBy(x => x.Key).Select(x => x.Value); + var fullyEncodedType = encodedPrimaryType.Value + string.Join(string.Empty, encodedReferenceTypes.ToArray()); + + return fullyEncodedType; + } + + private static List> EncodeTypes(IDictionary types, string currentTypeName, HashSet visited = null) + { + visited ??= new HashSet(); + + if (visited.Contains(currentTypeName)) + { + return new List>(); + } + + _ = visited.Add(currentTypeName); + + var currentTypeMembers = types[currentTypeName]; + var currentTypeMembersEncoded = currentTypeMembers.Select(x => x.Type + " " + x.Name); + var result = new List> { new(currentTypeName, currentTypeName + "(" + string.Join(",", currentTypeMembersEncoded.ToArray()) + ")") }; + + foreach (var member in currentTypeMembers) + { + var referencedType = ConvertToElementType(member.Type); + if (Utils.IsReferenceType(referencedType) && !visited.Contains(referencedType)) + { + result.AddRange(EncodeTypes(types, referencedType, visited)); + } + } + + return result; + } + + private static string ConvertToElementType(string type) + { + if (type.Contains('[')) + { + return type[..type.IndexOf('[')]; + } + return type; + } + + private void EncodeData(BinaryWriter writer, IDictionary types, IEnumerable memberValues) + { + foreach (var memberValue in memberValues) + { + switch (memberValue.TypeName) + { + case var refType when Utils.IsReferenceType(refType): + { + writer.Write(this.HashStruct(types, memberValue.TypeName, (IEnumerable)memberValue.Value)); + break; + } + case "string": + { + var value = Encoding.UTF8.GetBytes((string)memberValue.Value); + var abiValueEncoded = Sha3Keccack.Current.CalculateHash(value); + writer.Write(abiValueEncoded); + break; + } + case "bytes": + { + byte[] value; + if (memberValue.Value is string v) + { + value = v.HexToByteArray(); + } + else + { + value = (byte[])memberValue.Value; + } + var abiValueEncoded = Sha3Keccack.Current.CalculateHash(value); + writer.Write(abiValueEncoded); + break; + } + default: + { + if (memberValue.TypeName.Contains('[')) + { + var items = (IList)memberValue.Value; + var itemsMemberValues = new List(); + foreach (var item in items) + { + itemsMemberValues.Add(new MemberValue() { TypeName = memberValue.TypeName[..memberValue.TypeName.LastIndexOf('[')], Value = item }); + } + using (var memoryStream = new MemoryStream()) + using (var writerItem = new BinaryWriter(memoryStream)) + { + this.EncodeData(writerItem, types, itemsMemberValues); + writerItem.Flush(); + writer.Write(Sha3Keccack.Current.CalculateHash(memoryStream.ToArray())); + } + } + else if (memberValue.TypeName.StartsWith("int") || memberValue.TypeName.StartsWith("uint")) + { + object value; + if (memberValue.Value is string) + { + BigInteger parsedOutput; + if (BigInteger.TryParse((string)memberValue.Value, out parsedOutput)) + { + value = parsedOutput; + } + else + { + value = memberValue.Value; + } + } + else + { + value = memberValue.Value; + } + var abiValue = new ABIValue(memberValue.TypeName, value); + var abiValueEncoded = this._abiEncode.GetABIEncoded(abiValue); + writer.Write(abiValueEncoded); + } + else + { + var abiValue = new ABIValue(memberValue.TypeName, memberValue.Value); + var abiValueEncoded = this._abiEncode.GetABIEncoded(abiValue); + writer.Write(abiValueEncoded); + } + break; + } + } + } + } + + public TypedData GenerateTypedData(T data, TDomain domain, string primaryTypeName) + { + var parameters = this._parametersEncoder.GetParameterAttributeValues(typeof(T), data).OrderBy(x => x.ParameterAttribute.Order); + + var typeMembers = new List(); + var typeValues = new List(); + foreach (var parameterAttributeValue in parameters) + { + typeMembers.Add(new MemberDescription { Type = parameterAttributeValue.ParameterAttribute.Type, Name = parameterAttributeValue.ParameterAttribute.Name }); + + typeValues.Add(new MemberValue { TypeName = parameterAttributeValue.ParameterAttribute.Type, Value = parameterAttributeValue.Value }); + } + + var result = new TypedData + { + PrimaryType = primaryTypeName, + Types = new Dictionary + { + [primaryTypeName] = typeMembers.ToArray(), + ["EIP712Domain"] = MemberDescriptionFactory.GetTypesMemberDescription(typeof(TDomain))["EIP712Domain"] + }, + Message = typeValues.ToArray(), + Domain = domain + }; + + return result; + } +} diff --git a/Thirdweb/Thirdweb.Wallets/EngineWallet/EngineWallet.cs b/Thirdweb/Thirdweb.Wallets/EngineWallet/EngineWallet.cs index a0c4eef..876fcd7 100644 --- a/Thirdweb/Thirdweb.Wallets/EngineWallet/EngineWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/EngineWallet/EngineWallet.cs @@ -80,7 +80,9 @@ public static EngineWallet Create(ThirdwebClient client, string engineUrl, strin engineClient.AddHeader(header.Key, header.Value); } } - return new EngineWallet(client, engineClient, engineUrl, walletAddress, timeoutSeconds); + var wallet = new EngineWallet(client, engineClient, engineUrl, walletAddress, timeoutSeconds); + Utils.TrackConnection(wallet); + return wallet; } #endregion diff --git a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs index ce3ae54..536cf48 100644 --- a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs @@ -257,9 +257,9 @@ public virtual Task SignTypedDataV4(string json) throw new ArgumentNullException(nameof(json), "Json to sign cannot be null."); } - var signer = new Eip712TypedDataSigner(); - var signature = signer.SignTypedDataV4(json, this.EcKey); - return Task.FromResult(signature); + var encodedData = EIP712Encoder.Current.EncodeTypedData(json); + var signature = this.EcKey.SignAndCalculateV(Utils.HashMessage(encodedData)); + return Task.FromResult(EthECDSASignature.CreateStringSignature(signature)); } public virtual Task SignTypedDataV4(T data, TypedData typedData) @@ -270,9 +270,9 @@ public virtual Task SignTypedDataV4(T data, TypedData RecoverAddressFromTypedDataV4(T data, TypedData typedData, string signature) diff --git a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs index 4da64e6..36d7cee 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs @@ -486,58 +486,134 @@ public class Erc6492Signature public byte[] SigToValidate { get; set; } } -[Struct("SessionKeyParams")] -public class SessionKeyParams_7702 +#region 7702 + +[Struct("SessionSpec")] +public class SessionSpec { [Parameter("address", "signer", 1)] [JsonProperty("signer")] - public string Signer { get; set; } - - [Parameter("uint256", "nativeTokenLimitPerTransaction", 2)] - [JsonProperty("nativeTokenLimitPerTransaction")] - public BigInteger NativeTokenLimitPerTransaction { get; set; } + public virtual string Signer { get; set; } - [Parameter("uint256", "startTimestamp", 3)] - [JsonProperty("startTimestamp")] - public BigInteger StartTimestamp { get; set; } + [Parameter("uint256", "expiresAt", 2)] + [JsonProperty("expiresAt")] + public virtual BigInteger ExpiresAt { get; set; } - [Parameter("uint256", "endTimestamp", 4)] - [JsonProperty("endTimestamp")] - public BigInteger EndTimestamp { get; set; } + [Parameter("tuple[]", "callPolicies", 3, structTypeName: "CallSpec[]")] + [JsonProperty("callPolicies")] + public virtual List CallPolicies { get; set; } - [Parameter("address[]", "approvedTargets", 5)] - [JsonProperty("approvedTargets")] - public List ApprovedTargets { get; set; } + [Parameter("tuple[]", "transferPolicies", 4, structTypeName: "TransferSpec[]")] + [JsonProperty("transferPolicies")] + public virtual List TransferPolicies { get; set; } - [Parameter("bytes32", "uid", 6)] + [Parameter("bytes32", "uid", 5)] [JsonProperty("uid")] - public byte[] Uid { get; set; } + public virtual byte[] Uid { get; set; } +} + +[Struct("CallSpec")] +public class CallSpec +{ + [Parameter("address", "target", 1)] + [JsonProperty("target")] + public virtual string Target { get; set; } + + [Parameter("bytes4", "selector", 2)] + [JsonProperty("selector")] + public virtual byte[] Selector { get; set; } + + [Parameter("uint256", "maxValuePerUse", 3)] + [JsonProperty("maxValuePerUse")] + public virtual BigInteger MaxValuePerUse { get; set; } + + [Parameter("tuple", "valueLimit", 4, structTypeName: "UsageLimit")] + [JsonProperty("valueLimit")] + public virtual UsageLimit ValueLimit { get; set; } + + [Parameter("tuple[]", "constraints", 5, structTypeName: "Constraint[]")] + [JsonProperty("constraints")] + public virtual List Constraints { get; set; } +} + +[Struct("TransferSpec")] +public class TransferSpec +{ + [Parameter("address", "target", 1)] + [JsonProperty("target")] + public virtual string Target { get; set; } + + [Parameter("uint256", "maxValuePerUse", 2)] + [JsonProperty("maxValuePerUse")] + public virtual BigInteger MaxValuePerUse { get; set; } + + [Parameter("tuple", "valueLimit", 3, structTypeName: "UsageLimit")] + [JsonProperty("valueLimit")] + public virtual UsageLimit ValueLimit { get; set; } +} + +[Struct("UsageLimit")] +public class UsageLimit +{ + [Parameter("uint8", "limitType", 1)] + [JsonProperty("limitType")] + public virtual byte LimitType { get; set; } + + [Parameter("uint256", "limit", 2)] + [JsonProperty("limit")] + public virtual BigInteger Limit { get; set; } + + [Parameter("uint256", "period", 3)] + [JsonProperty("period")] + public virtual BigInteger Period { get; set; } +} + +[Struct("Constraint")] +public class Constraint +{ + [Parameter("uint8", "condition", 1)] + [JsonProperty("condition")] + public virtual byte Condition { get; set; } + + [Parameter("uint64", "index", 2)] + [JsonProperty("index")] + public virtual ulong Index { get; set; } + + [Parameter("bytes32", "refValue", 3)] + [JsonProperty("refValue")] + public virtual byte[] RefValue { get; set; } + + [Parameter("tuple", "limit", 4, structTypeName: "UsageLimit")] + [JsonProperty("limit")] + public virtual UsageLimit Limit { get; set; } } [Struct("Call")] public class Call { - [Parameter("bytes", "data", 1)] - [JsonProperty("data")] - public byte[] Data { get; set; } - - [Parameter("address", "to", 2)] - [JsonProperty("to")] - public string To { get; set; } + [Parameter("address", "target", 1)] + [JsonProperty("target")] + public virtual string Target { get; set; } - [Parameter("uint256", "value", 3)] + [Parameter("uint256", "value", 2)] [JsonProperty("value")] - public BigInteger Value { get; set; } + public virtual BigInteger Value { get; set; } + + [Parameter("bytes", "data", 3)] + [JsonProperty("data")] + public virtual byte[] Data { get; set; } } [Struct("WrappedCalls")] public class WrappedCalls { - [Parameter("tuple[]", "calls", 1, "Call[]")] + [Parameter("tuple[]", "calls", 1)] [JsonProperty("calls")] - public List Calls { get; set; } + public virtual List Calls { get; set; } [Parameter("bytes32", "uid", 2)] [JsonProperty("uid")] - public byte[] Uid { get; set; } + public virtual byte[] Uid { get; set; } } + +#endregion diff --git a/Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs b/Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs new file mode 100644 index 0000000..880691c --- /dev/null +++ b/Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs @@ -0,0 +1,202 @@ +using System.Numerics; +using Nethereum.ABI.EIP712; +using Thirdweb.AccountAbstraction; + +namespace Thirdweb; + +/// +/// Represents a 7702 delegated wallet with granular session key permissions and automatic session key execution. +/// +public class ThirdwebWallet : IThirdwebWallet +{ + public string WalletId => "thirdweb"; + + public ThirdwebClient Client { get; } + public ThirdwebAccountType AccountType => ThirdwebAccountType.ExternalAccount; + + internal IThirdwebWallet UserWallet { get; } + internal IThirdwebWallet ExecutorWallet { get; } + internal ThirdwebContract UserContract { get; } + + internal ThirdwebWallet(ThirdwebClient client, IThirdwebWallet userWallet, IThirdwebWallet executorWallet, ThirdwebContract userContract) + { + this.Client = client; + this.UserWallet = userWallet; + this.ExecutorWallet = executorWallet; + this.UserContract = userContract; + } + + public static async Task Create(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, IThirdwebWallet executorWallet, SessionSpec sessionKeyParams) + { + var userWalletAddress = await userWallet.GetAddress(); + var executorWalletAddress = await executorWallet.GetAddress(); + if (sessionKeyParams != null && sessionKeyParams.Signer != executorWalletAddress) + { + throw new Exception("Session key signer must be the executor wallet"); + } + var delegationContract = await ThirdwebContract.Create(client, Constants.MINIMAL_ACCOUNT_7702, chainId); + + var rpc = ThirdwebRPC.GetRpcInstance(client, chainId); + var code = await rpc.SendRequestAsync("eth_getCode", userWalletAddress, "latest"); + var needsDelegation = code.ToLower() != $"0xef0100{Constants.MINIMAL_ACCOUNT_7702[2..]}".ToLower(); + + // Sign authorization if needed + EIP7702Authorization? authorization = needsDelegation ? await userWallet.SignAuthorization(chainId, Constants.MINIMAL_ACCOUNT_7702, willSelfExecute: false) : null; + + // TODO: We don't always need to create a session key when creating this wallet, handle with a flag or null check + + // Sign message for session key + var sessionKeySig = await EIP712.GenerateSignature_SmartAccount_7702("MinimalAccount", "1", chainId, userWalletAddress, sessionKeyParams, userWallet); + + // Create call data for the session + var sessionKeyCallData = delegationContract.CreateCallData("createSessionWithSig", sessionKeyParams, sessionKeySig.HexToBytes()); + + // Execute the delegation & session creation in one go + var delegationTx = await ThirdwebTransaction.Create( + executorWallet, + new ThirdwebTransactionInput(chainId: chainId, to: userWalletAddress, data: sessionKeyCallData, authorization: authorization) + ); + var delegationReceipt = await ThirdwebTransaction.SendAndWaitForTransactionReceipt(delegationTx); + Console.WriteLine($"Delegation receipt: {delegationReceipt}"); + + var newCode = await rpc.SendRequestAsync("eth_getCode", userWalletAddress, "latest"); + if (newCode.ToLower() != $"0xef0100{Constants.MINIMAL_ACCOUNT_7702[2..]}".ToLower()) + { + throw new Exception("Delegation failed"); + } + + var userContract = await ThirdwebContract.Create(client, userWalletAddress, chainId, delegationContract.Abi); + var wallet = new ThirdwebWallet(client, userWallet, executorWallet, userContract); + Utils.TrackConnection(wallet); + return wallet; + } + + #region IThirdwebWallet + + public Task GetAddress() + { + return this.UserWallet.GetAddress(); + } + + public Task EthSign(byte[] rawMessage) + { + return this.UserWallet.EthSign(rawMessage); + } + + public Task EthSign(string message) + { + return this.UserWallet.EthSign(message); + } + + public Task RecoverAddressFromEthSign(string message, string signature) + { + return this.UserWallet.RecoverAddressFromEthSign(message, signature); + } + + public Task PersonalSign(byte[] rawMessage) + { + return this.UserWallet.PersonalSign(rawMessage); + } + + public Task PersonalSign(string message) + { + return this.UserWallet.PersonalSign(message); + } + + public Task RecoverAddressFromPersonalSign(string message, string signature) + { + return this.UserWallet.RecoverAddressFromPersonalSign(message, signature); + } + + public Task SignTypedDataV4(string json) + { + return this.UserWallet.SignTypedDataV4(json); + } + + public Task SignTypedDataV4(T data, TypedData typedData) + where TDomain : IDomain + { + return this.UserWallet.SignTypedDataV4(data, typedData); + } + + public Task RecoverAddressFromTypedDataV4(T data, TypedData typedData, string signature) + where TDomain : IDomain + { + return this.UserWallet.RecoverAddressFromTypedDataV4(data, typedData, signature); + } + + public Task IsConnected() + { + return this.UserWallet.IsConnected(); + } + + public Task SignTransaction(ThirdwebTransactionInput transaction) + { + return this.UserWallet.SignTransaction(transaction); + } + + public async Task SendTransaction(ThirdwebTransactionInput transaction) + { + var calls = new List + { + new() + { + Target = transaction.To, + Value = transaction.Value?.Value ?? BigInteger.Zero, + Data = transaction.Data.HexToBytes() + } + }; + var tx = await this.UserContract.Prepare(this.ExecutorWallet, "execute", calls[0].Value, calls); + return await ThirdwebTransaction.Send(tx); + } + + public async Task ExecuteTransaction(ThirdwebTransactionInput transaction) + { + var hash = await this.SendTransaction(transaction); + return await Utils.WaitForTransactionReceipt(this.Client, transaction.ChainId, hash); + } + + public Task Disconnect() + { + return this.UserWallet.Disconnect(); + } + + public Task> LinkAccount( + IThirdwebWallet walletToLink, + string otp = null, + bool? isMobile = null, + Action browserOpenAction = null, + string mobileRedirectScheme = "thirdweb://", + IThirdwebBrowser browser = null, + BigInteger? chainId = null, + string jwt = null, + string payload = null, + string defaultSessionIdOverride = null, + List forceWalletIds = null + ) + { + return this.UserWallet.LinkAccount(walletToLink, otp, isMobile, browserOpenAction, mobileRedirectScheme, browser, chainId, jwt, payload, defaultSessionIdOverride, forceWalletIds); + } + + public Task> UnlinkAccount(LinkedAccount accountToUnlink) + { + return this.UserWallet.UnlinkAccount(accountToUnlink); + } + + public Task> GetLinkedAccounts() + { + return this.UserWallet.GetLinkedAccounts(); + } + + public Task SignAuthorization(BigInteger chainId, string contractAddress, bool willSelfExecute) + { + return this.UserWallet.SignAuthorization(chainId, contractAddress, willSelfExecute); + } + + public Task SwitchNetwork(BigInteger chainId) + { + return this.UserWallet.SwitchNetwork(chainId); + } + + #endregion +} From 31c011954f29eeefd9428b31b4e641c48716809c Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Sat, 15 Mar 2025 03:59:40 +0700 Subject: [PATCH 02/15] cleanup --- Thirdweb/Thirdweb.Utils/Utils.cs | 5 +++++ Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs | 9 ++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Thirdweb/Thirdweb.Utils/Utils.cs b/Thirdweb/Thirdweb.Utils/Utils.cs index bfbba2b..a0125b0 100644 --- a/Thirdweb/Thirdweb.Utils/Utils.cs +++ b/Thirdweb/Thirdweb.Utils/Utils.cs @@ -1323,4 +1323,9 @@ public static async Task WaitForTransactionReceipt(T return receipt; } + + public static bool IsDelegatedAccount(string accountCode) + { + return !accountCode.Equals($"0xef0100{Constants.MINIMAL_ACCOUNT_7702[2..]}", StringComparison.OrdinalIgnoreCase); + } } diff --git a/Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs b/Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs index 880691c..e16546c 100644 --- a/Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs @@ -38,7 +38,7 @@ public static async Task Create(ThirdwebClient client, BigIntege var rpc = ThirdwebRPC.GetRpcInstance(client, chainId); var code = await rpc.SendRequestAsync("eth_getCode", userWalletAddress, "latest"); - var needsDelegation = code.ToLower() != $"0xef0100{Constants.MINIMAL_ACCOUNT_7702[2..]}".ToLower(); + var needsDelegation = !Utils.IsDelegatedAccount(code); // Sign authorization if needed EIP7702Authorization? authorization = needsDelegation ? await userWallet.SignAuthorization(chainId, Constants.MINIMAL_ACCOUNT_7702, willSelfExecute: false) : null; @@ -56,13 +56,12 @@ public static async Task Create(ThirdwebClient client, BigIntege executorWallet, new ThirdwebTransactionInput(chainId: chainId, to: userWalletAddress, data: sessionKeyCallData, authorization: authorization) ); - var delegationReceipt = await ThirdwebTransaction.SendAndWaitForTransactionReceipt(delegationTx); - Console.WriteLine($"Delegation receipt: {delegationReceipt}"); + _ = await ThirdwebTransaction.SendAndWaitForTransactionReceipt(delegationTx); var newCode = await rpc.SendRequestAsync("eth_getCode", userWalletAddress, "latest"); - if (newCode.ToLower() != $"0xef0100{Constants.MINIMAL_ACCOUNT_7702[2..]}".ToLower()) + if (!Utils.IsDelegatedAccount(newCode)) { - throw new Exception("Delegation failed"); + throw new Exception("Delegation failed, code was not set."); } var userContract = await ThirdwebContract.Create(client, userWalletAddress, chainId, delegationContract.Abi); From 5b490b64fe091b546133b7f5e7ad701025a93ff6 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Wed, 7 May 2025 02:42:36 +0700 Subject: [PATCH 03/15] latest implementation, unmanaged execution, managed wip --- Thirdweb.Console/Program.cs | 46 +++---- Thirdweb/Thirdweb.Utils/Constants.cs | 31 ++--- Thirdweb/Thirdweb.Utils/Utils.cs | 6 +- .../Thirdweb.AccountAbstraction/AATypes.cs | 12 +- Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs | 116 ++++++++++-------- 5 files changed, 120 insertions(+), 91 deletions(-) diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index 20e7801..838b592 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -26,7 +26,7 @@ var privateKey = Environment.GetEnvironmentVariable("PRIVATE_KEY"); // Fetch timeout options are optional, default is 120000ms -var client = ThirdwebClient.Create(secretKey: secretKey, rpcOverrides: new Dictionary { { 11155111, "https://eth-sepolia.public.blastapi.io" } }); +var client = ThirdwebClient.Create(secretKey: secretKey); // Create a private key wallet var privateKeyWallet = await PrivateKeyWallet.Generate(client); @@ -326,33 +326,35 @@ #region EIP-7702 -// // The session key signer -// var executorWallet = await PrivateKeyWallet.Create(client, privateKey); // needs to be funded, for now +// var chain = 11155111; // sepolia -// // Session key permissions -// var sessionKeyParams = new SessionSpec() +// // Connect to EOA +// var userWallet = await InAppWallet.Create(client, authProvider: AuthProvider.Github); +// if (!await userWallet.IsConnected()) // { -// Signer = await executorWallet.GetAddress(), -// ExpiresAt = Utils.GetUnixTimeStampNow() + (3600 * 24), -// CallPolicies = new List() { }, -// TransferPolicies = new List() -// { -// new() +// _ = await userWallet.LoginWithOauth( +// isMobile: false, +// browserOpenAction: (url) => // { -// Target = await Utils.GetAddressFromENS(client, "vitalik.eth"), -// MaxValuePerUse = BigInteger.Zero, -// ValueLimit = new() +// var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true }; +// _ = Process.Start(psi); // } -// }, -// Uid = Guid.NewGuid().ToByteArray() -// }; +// ); +// } +// Console.WriteLine($"User Wallet address: {await userWallet.GetAddress()}"); -// // This wallet explicitly uses 7702 delegation to the thirdweb MinimalAccount and creates a session key from which every tx will be executed -// var thirdwebWallet = await ThirdwebWallet.Create(client, 11155111, privateKeyWallet, executorWallet, sessionKeyParams); +// // Upgrade EOA - This wallet explicitly uses EIP-7702 delegation to the thirdweb MinimalAccount (will delegate upon first tx) +// var thirdwebWallet = await ThirdwebWallet.Create(client, chain, userWallet, managedExecution: false); +// var thirdwebWalletAddress = await thirdwebWallet.GetAddress(); +// Console.WriteLine($"Thirdweb Wallet address: {thirdwebWalletAddress}"); // same as userWallet address, unlike when using EIP-4337 -// // Simple transfer, will use the session key automatically -// var receipt = await thirdwebWallet.Transfer(11155111, await Utils.GetAddressFromENS(client, "vitalik.eth"), 0); -// Console.WriteLine($"Receipt: {receipt}"); +// // Transact, will upgrade EOA +// var receipt = await thirdwebWallet.Transfer(chainId: chain, toAddress: await Utils.GetAddressFromENS(client, "vitalik.eth"), weiAmount: 0); +// Console.WriteLine($"Transfer Receipt: {receipt.TransactionHash}"); + +// // Double check that it was upgraded +// var isDelegated = await Utils.IsDelegatedAccount(client, chain, thirdwebWalletAddress); +// Console.WriteLine($"Is delegated: {isDelegated}"); #endregion diff --git a/Thirdweb/Thirdweb.Utils/Constants.cs b/Thirdweb/Thirdweb.Utils/Constants.cs index ec398f0..4fe5513 100644 --- a/Thirdweb/Thirdweb.Utils/Constants.cs +++ b/Thirdweb/Thirdweb.Utils/Constants.cs @@ -5,21 +5,21 @@ public static class Constants public const string VERSION = "2.20.1"; internal const string BRIDGE_API_URL = "https://bridge.thirdweb.com"; - internal const string NEBULA_API_URL = "https://nebula-api.thirdweb.com"; internal const string INSIGHT_API_URL = "https://insight.thirdweb.com"; internal const string SOCIAL_API_URL = "https://social.thirdweb.com"; internal const string PIN_URI = "https://storage.thirdweb.com/ipfs/upload"; internal const string FALLBACK_IPFS_GATEWAY = "https://ipfs.io/ipfs/"; - - public const string IERC20_INTERFACE_ID = "0x36372b07"; - public const string IERC721_INTERFACE_ID = "0x80ac58cd"; - public const string IERC1155_INTERFACE_ID = "0xd9b67a26"; + internal const string NEBULA_API_URL = "https://nebula-api.thirdweb.com"; + internal const string NEBULA_DEFAULT_MODEL = "t0-003"; + internal const int DEFAULT_FETCH_TIMEOUT = 120000; public const string ADDRESS_ZERO = "0x0000000000000000000000000000000000000000"; public const string NATIVE_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; public const double DECIMALS_18 = 1000000000000000000; - public const string MINIMAL_ACCOUNT_7702 = "0xFabf2ca2377Bbc199EaE439b71fcD6a5925127e2"; + public const string IERC20_INTERFACE_ID = "0x36372b07"; + public const string IERC721_INTERFACE_ID = "0x80ac58cd"; + public const string IERC1155_INTERFACE_ID = "0xd9b67a26"; public const string ENTRYPOINT_ADDRESS_V06 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"; public const string ENTRYPOINT_ADDRESS_V07 = "0x0000000071727De22E5E9d8BAf0edAc6f37da032"; @@ -29,20 +29,23 @@ public static class Constants 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

"; - internal const int DEFAULT_FETCH_TIMEOUT = 120000; internal const string DUMMY_SIG = "0xfffffffffffffffffffffffffffffff0000000000000000000000000000000007aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1c"; internal const string DUMMY_PAYMASTER_AND_DATA_HEX = "0x0101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000001010101010100000000000000000000000000000000000000000000000000000000000000000101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"; internal const string ENS_REGISTRY_ADDRESS = "0xce01f8eee7E479C928F8919abD53E553a36CeF67"; - internal const string NEBULA_DEFAULT_MODEL = "t0-003"; + public const string MINIMAL_ACCOUNT_7702 = "0x8001eA4C0f8595488A6a0cF7211F900948892092"; + public const string MINIMAL_ACCOUNT_7702_ABI = + /*lang=json,strict*/ + "[{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"allowanceUsage\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint64\",\"name\": \"period\",\"type\": \"uint64\"}],\"name\": \"AllowanceExceeded\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"}],\"name\": \"CallPolicyViolated\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"CallReverted\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"param\",\"type\": \"bytes32\"},{\"internalType\": \"bytes32\",\"name\": \"refValue\",\"type\": \"bytes32\"},{\"internalType\": \"uint8\",\"name\": \"condition\",\"type\": \"uint8\"}],\"name\": \"ConditionFailed\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"actualLength\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"expectedLength\",\"type\": \"uint256\"}],\"name\": \"InvalidDataLength\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"msgSender\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"thisAddress\",\"type\": \"address\"}],\"name\": \"InvalidSignature\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"lifetimeUsage\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"}],\"name\": \"LifetimeUsageExceeded\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"}],\"name\": \"MaxValueExceeded\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"NoCallsToExecute\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"SessionExpired\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"SessionExpiresTooSoon\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"SessionZeroSigner\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"}],\"name\": \"TransferPolicyViolated\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"UIDAlreadyProcessed\",\"type\": \"error\"},{\"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\"},{\"anonymous\": false,\"inputs\": [{\"indexed\": true,\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"},{\"internalType\": \"bool\",\"name\": \"isWildcard\",\"type\": \"bool\"},{\"internalType\": \"uint256\",\"name\": \"expiresAt\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"},{\"components\": [{\"internalType\": \"enum SessionLib.Condition\",\"name\": \"condition\",\"type\": \"uint8\"},{\"internalType\": \"uint64\",\"name\": \"index\",\"type\": \"uint64\"},{\"internalType\": \"bytes32\",\"name\": \"refValue\",\"type\": \"bytes32\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"limit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.Constraint[]\",\"name\": \"constraints\",\"type\": \"tuple[]\"}],\"internalType\": \"struct SessionLib.CallSpec[]\",\"name\": \"callPolicies\",\"type\": \"tuple[]\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.TransferSpec[]\",\"name\": \"transferPolicies\",\"type\": \"tuple[]\"},{\"internalType\": \"bytes32\",\"name\": \"uid\",\"type\": \"bytes32\"}],\"indexed\": false,\"internalType\": \"struct SessionLib.SessionSpec\",\"name\": \"sessionSpec\",\"type\": \"tuple\"}],\"name\": \"SessionCreated\",\"type\": \"event\"},{\"inputs\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"},{\"internalType\": \"bool\",\"name\": \"isWildcard\",\"type\": \"bool\"},{\"internalType\": \"uint256\",\"name\": \"expiresAt\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"},{\"components\": [{\"internalType\": \"enum SessionLib.Condition\",\"name\": \"condition\",\"type\": \"uint8\"},{\"internalType\": \"uint64\",\"name\": \"index\",\"type\": \"uint64\"},{\"internalType\": \"bytes32\",\"name\": \"refValue\",\"type\": \"bytes32\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"limit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.Constraint[]\",\"name\": \"constraints\",\"type\": \"tuple[]\"}],\"internalType\": \"struct SessionLib.CallSpec[]\",\"name\": \"callPolicies\",\"type\": \"tuple[]\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.TransferSpec[]\",\"name\": \"transferPolicies\",\"type\": \"tuple[]\"},{\"internalType\": \"bytes32\",\"name\": \"uid\",\"type\": \"bytes32\"}],\"internalType\": \"struct SessionLib.SessionSpec\",\"name\": \"sessionSpec\",\"type\": \"tuple\"},{\"internalType\": \"bytes\",\"name\": \"signature\",\"type\": \"bytes\"}],\"name\": \"createSessionWithSig\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"eip712Domain\",\"outputs\": [{\"internalType\": \"bytes1\",\"name\": \"fields\",\"type\": \"bytes1\"},{\"internalType\": \"string\",\"name\": \"name\",\"type\": \"string\"},{\"internalType\": \"string\",\"name\": \"version\",\"type\": \"string\"},{\"internalType\": \"uint256\",\"name\": \"chainId\",\"type\": \"uint256\"},{\"internalType\": \"address\",\"name\": \"verifyingContract\",\"type\": \"address\"},{\"internalType\": \"bytes32\",\"name\": \"salt\",\"type\": \"bytes32\"},{\"internalType\": \"uint256[]\",\"name\": \"extensions\",\"type\": \"uint256[]\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"},{\"internalType\": \"bytes\",\"name\": \"data\",\"type\": \"bytes\"}],\"internalType\": \"struct Call[]\",\"name\": \"calls\",\"type\": \"tuple[]\"}],\"name\": \"execute\",\"outputs\": [],\"stateMutability\": \"payable\",\"type\": \"function\"},{\"inputs\": [{\"components\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"},{\"internalType\": \"bytes\",\"name\": \"data\",\"type\": \"bytes\"}],\"internalType\": \"struct Call[]\",\"name\": \"calls\",\"type\": \"tuple[]\"},{\"internalType\": \"bytes32\",\"name\": \"uid\",\"type\": \"bytes32\"}],\"internalType\": \"struct WrappedCalls\",\"name\": \"wrappedCalls\",\"type\": \"tuple\"},{\"internalType\": \"bytes\",\"name\": \"signature\",\"type\": \"bytes\"}],\"name\": \"executeWithSig\",\"outputs\": [],\"stateMutability\": \"payable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"getCallPoliciesForSigner\",\"outputs\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"},{\"components\": [{\"internalType\": \"enum SessionLib.Condition\",\"name\": \"condition\",\"type\": \"uint8\"},{\"internalType\": \"uint64\",\"name\": \"index\",\"type\": \"uint64\"},{\"internalType\": \"bytes32\",\"name\": \"refValue\",\"type\": \"bytes32\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"limit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.Constraint[]\",\"name\": \"constraints\",\"type\": \"tuple[]\"}],\"internalType\": \"struct SessionLib.CallSpec[]\",\"name\": \"\",\"type\": \"tuple[]\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"getSessionExpirationForSigner\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"getSessionStateForSigner\",\"outputs\": [{\"components\": [{\"components\": [{\"internalType\": \"uint256\",\"name\": \"remaining\",\"type\": \"uint256\"},{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"index\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.LimitState[]\",\"name\": \"transferValue\",\"type\": \"tuple[]\"},{\"components\": [{\"internalType\": \"uint256\",\"name\": \"remaining\",\"type\": \"uint256\"},{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"index\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.LimitState[]\",\"name\": \"callValue\",\"type\": \"tuple[]\"},{\"components\": [{\"internalType\": \"uint256\",\"name\": \"remaining\",\"type\": \"uint256\"},{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"index\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.LimitState[]\",\"name\": \"callParams\",\"type\": \"tuple[]\"}],\"internalType\": \"struct SessionLib.SessionState\",\"name\": \"\",\"type\": \"tuple\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"getTransferPoliciesForSigner\",\"outputs\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.TransferSpec[]\",\"name\": \"\",\"type\": \"tuple[]\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"isWildcardSigner\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"uint256[]\",\"name\": \"\",\"type\": \"uint256[]\"},{\"internalType\": \"uint256[]\",\"name\": \"\",\"type\": \"uint256[]\"},{\"internalType\": \"bytes\",\"name\": \"\",\"type\": \"bytes\"}],\"name\": \"onERC1155BatchReceived\",\"outputs\": [{\"internalType\": \"bytes4\",\"name\": \"\",\"type\": \"bytes4\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"},{\"internalType\": \"bytes\",\"name\": \"\",\"type\": \"bytes\"}],\"name\": \"onERC1155Received\",\"outputs\": [{\"internalType\": \"bytes4\",\"name\": \"\",\"type\": \"bytes4\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"},{\"internalType\": \"bytes\",\"name\": \"\",\"type\": \"bytes\"}],\"name\": \"onERC721Received\",\"outputs\": [{\"internalType\": \"bytes4\",\"name\": \"\",\"type\": \"bytes4\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes4\",\"name\": \"interfaceId\",\"type\": \"bytes4\"}],\"name\": \"supportsInterface\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"view\",\"type\": \"function\"}]"; + + 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

"; internal const string ENTRYPOINT_V06_ABI = /*lang=json,strict*/ diff --git a/Thirdweb/Thirdweb.Utils/Utils.cs b/Thirdweb/Thirdweb.Utils/Utils.cs index ba0357c..1456d74 100644 --- a/Thirdweb/Thirdweb.Utils/Utils.cs +++ b/Thirdweb/Thirdweb.Utils/Utils.cs @@ -1328,8 +1328,10 @@ public static async Task WaitForTransactionReceipt(T return receipt; } - public static bool IsDelegatedAccount(string accountCode) + public static async Task IsDelegatedAccount(ThirdwebClient client, BigInteger chainId, string address) { - return !accountCode.Equals($"0xef0100{Constants.MINIMAL_ACCOUNT_7702[2..]}", StringComparison.OrdinalIgnoreCase); + var rpc = ThirdwebRPC.GetRpcInstance(client, chainId); + var code = await rpc.SendRequestAsync("eth_getCode", address, "latest"); + return !code.Equals($"0xef0100{Constants.MINIMAL_ACCOUNT_7702[2..]}", StringComparison.OrdinalIgnoreCase); } } diff --git a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs index 36d7cee..f06850a 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs @@ -495,19 +495,23 @@ public class SessionSpec [JsonProperty("signer")] public virtual string Signer { get; set; } - [Parameter("uint256", "expiresAt", 2)] + [Parameter("bool", "isWildcard", 2)] + [JsonProperty("isWildcard")] + public virtual bool IsWildcard { get; set; } + + [Parameter("uint256", "expiresAt", 3)] [JsonProperty("expiresAt")] public virtual BigInteger ExpiresAt { get; set; } - [Parameter("tuple[]", "callPolicies", 3, structTypeName: "CallSpec[]")] + [Parameter("tuple[]", "callPolicies", 4, structTypeName: "CallSpec[]")] [JsonProperty("callPolicies")] public virtual List CallPolicies { get; set; } - [Parameter("tuple[]", "transferPolicies", 4, structTypeName: "TransferSpec[]")] + [Parameter("tuple[]", "transferPolicies", 5, structTypeName: "TransferSpec[]")] [JsonProperty("transferPolicies")] public virtual List TransferPolicies { get; set; } - [Parameter("bytes32", "uid", 5)] + [Parameter("bytes32", "uid", 6)] [JsonProperty("uid")] public virtual byte[] Uid { get; set; } } diff --git a/Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs b/Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs index e16546c..a9c4a4f 100644 --- a/Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs @@ -15,61 +15,46 @@ public class ThirdwebWallet : IThirdwebWallet public ThirdwebAccountType AccountType => ThirdwebAccountType.ExternalAccount; internal IThirdwebWallet UserWallet { get; } - internal IThirdwebWallet ExecutorWallet { get; } internal ThirdwebContract UserContract { get; } + internal BigInteger ChainId { get; } + internal bool ManagedExecution { get; } - internal ThirdwebWallet(ThirdwebClient client, IThirdwebWallet userWallet, IThirdwebWallet executorWallet, ThirdwebContract userContract) + private EIP7702Authorization? Authorization { get; set; } + + internal ThirdwebWallet(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, ThirdwebContract userContract, EIP7702Authorization? authorization, bool managedExecution) { this.Client = client; + this.ChainId = chainId; this.UserWallet = userWallet; - this.ExecutorWallet = executorWallet; this.UserContract = userContract; + this.Authorization = authorization; + this.ManagedExecution = managedExecution; } - public static async Task Create(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, IThirdwebWallet executorWallet, SessionSpec sessionKeyParams) + public static async Task Create(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, bool managedExecution) { var userWalletAddress = await userWallet.GetAddress(); - var executorWalletAddress = await executorWallet.GetAddress(); - if (sessionKeyParams != null && sessionKeyParams.Signer != executorWalletAddress) - { - throw new Exception("Session key signer must be the executor wallet"); - } - var delegationContract = await ThirdwebContract.Create(client, Constants.MINIMAL_ACCOUNT_7702, chainId); - - var rpc = ThirdwebRPC.GetRpcInstance(client, chainId); - var code = await rpc.SendRequestAsync("eth_getCode", userWalletAddress, "latest"); - var needsDelegation = !Utils.IsDelegatedAccount(code); - - // Sign authorization if needed - EIP7702Authorization? authorization = needsDelegation ? await userWallet.SignAuthorization(chainId, Constants.MINIMAL_ACCOUNT_7702, willSelfExecute: false) : null; - - // TODO: We don't always need to create a session key when creating this wallet, handle with a flag or null check - - // Sign message for session key - var sessionKeySig = await EIP712.GenerateSignature_SmartAccount_7702("MinimalAccount", "1", chainId, userWalletAddress, sessionKeyParams, userWallet); - - // Create call data for the session - var sessionKeyCallData = delegationContract.CreateCallData("createSessionWithSig", sessionKeyParams, sessionKeySig.HexToBytes()); - - // Execute the delegation & session creation in one go - var delegationTx = await ThirdwebTransaction.Create( - executorWallet, - new ThirdwebTransactionInput(chainId: chainId, to: userWalletAddress, data: sessionKeyCallData, authorization: authorization) - ); - _ = await ThirdwebTransaction.SendAndWaitForTransactionReceipt(delegationTx); - - var newCode = await rpc.SendRequestAsync("eth_getCode", userWalletAddress, "latest"); - if (!Utils.IsDelegatedAccount(newCode)) - { - throw new Exception("Delegation failed, code was not set."); - } - - var userContract = await ThirdwebContract.Create(client, userWalletAddress, chainId, delegationContract.Abi); - var wallet = new ThirdwebWallet(client, userWallet, executorWallet, userContract); + var userContract = await ThirdwebContract.Create(client, userWalletAddress, chainId, Constants.MINIMAL_ACCOUNT_7702_ABI); + var needsDelegation = !await Utils.IsDelegatedAccount(client, chainId, userWalletAddress); + EIP7702Authorization? authorization = needsDelegation ? await userWallet.SignAuthorization(chainId, Constants.MINIMAL_ACCOUNT_7702, willSelfExecute: !managedExecution) : null; + var wallet = new ThirdwebWallet(client, chainId, userWallet, userContract, authorization, managedExecution); Utils.TrackConnection(wallet); return wallet; } + #region Wallet Specific + + public async Task CreateSessionKey(SessionSpec sessionKeyParams) + { + var userWalletAddress = await this.UserWallet.GetAddress(); + var sessionKeySig = await EIP712.GenerateSignature_SmartAccount_7702("MinimalAccount", "1", this.ChainId, userWalletAddress, sessionKeyParams, this.UserWallet); + var sessionKeyCallData = this.UserContract.CreateCallData("createSessionWithSig", sessionKeyParams, sessionKeySig.HexToBytes()); + var sessionKeyTx = await ThirdwebTransaction.Create(this, new ThirdwebTransactionInput(chainId: this.ChainId, to: userWalletAddress, data: sessionKeyCallData)); + return await ThirdwebTransaction.SendAndWaitForTransactionReceipt(sessionKeyTx); + } + + #endregion + #region IThirdwebWallet public Task GetAddress() @@ -136,17 +121,50 @@ public Task SignTransaction(ThirdwebTransactionInput transaction) public async Task SendTransaction(ThirdwebTransactionInput transaction) { - var calls = new List + // TODO: managed execution - executeWithSig + if (this.ManagedExecution) + { + throw new NotImplementedException("Managed execution is not yet implemented."); + + // 1. Create payload with eoa address, wrapped calls, signature and optional authorizationList + // 2. Send to https://{chainId}.bundler.thirdweb.com as RpcRequest w/ method tw_execute + // 3. Retrieve tx hash or queue id from response + // 4. Return tx hash + } + else { - new() + var calls = new List { - Target = transaction.To, - Value = transaction.Value?.Value ?? BigInteger.Zero, - Data = transaction.Data.HexToBytes() + new() + { + Target = transaction.To, + Value = transaction.Value?.Value ?? BigInteger.Zero, + Data = transaction.Data.HexToBytes() + } + }; + + BigInteger totalValue = 0; + foreach (var call in calls) + { + totalValue += call.Value; } - }; - var tx = await this.UserContract.Prepare(this.ExecutorWallet, "execute", calls[0].Value, calls); - return await ThirdwebTransaction.Send(tx); + + var tx = await this.UserContract.Prepare(wallet: this.UserWallet, method: "execute", weiValue: totalValue, parameters: new object[] { calls }); + + if (this.Authorization != null) + { + if (!await Utils.IsDelegatedAccount(this.Client, this.ChainId, await this.UserWallet.GetAddress())) + { + tx.Input.AuthorizationList = new List() { this.Authorization.Value }; + } + else + { + this.Authorization = null; + } + } + + return await ThirdwebTransaction.Send(tx); + } } public async Task ExecuteTransaction(ThirdwebTransactionInput transaction) From 2d7d927419deeb88a6e39e4d0b2509b69efa9db6 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Wed, 7 May 2025 04:32:03 +0700 Subject: [PATCH 04/15] update delegate util --- Thirdweb/Thirdweb.Utils/Utils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Thirdweb/Thirdweb.Utils/Utils.cs b/Thirdweb/Thirdweb.Utils/Utils.cs index 1456d74..efd3315 100644 --- a/Thirdweb/Thirdweb.Utils/Utils.cs +++ b/Thirdweb/Thirdweb.Utils/Utils.cs @@ -1332,6 +1332,6 @@ public static async Task IsDelegatedAccount(ThirdwebClient client, BigInte { var rpc = ThirdwebRPC.GetRpcInstance(client, chainId); var code = await rpc.SendRequestAsync("eth_getCode", address, "latest"); - return !code.Equals($"0xef0100{Constants.MINIMAL_ACCOUNT_7702[2..]}", StringComparison.OrdinalIgnoreCase); + return code.Equals($"0xef0100{Constants.MINIMAL_ACCOUNT_7702[2..]}", StringComparison.OrdinalIgnoreCase); } } From 77f8944170035e34c7a4db3f9f5fc1eeaa562c98 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Wed, 7 May 2025 05:01:14 +0700 Subject: [PATCH 05/15] eip7702 --- Thirdweb.Console/Program.cs | 2 +- Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs | 31 +++++++++++++++------ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index 838b592..ac1bf9d 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -344,7 +344,7 @@ // Console.WriteLine($"User Wallet address: {await userWallet.GetAddress()}"); // // Upgrade EOA - This wallet explicitly uses EIP-7702 delegation to the thirdweb MinimalAccount (will delegate upon first tx) -// var thirdwebWallet = await ThirdwebWallet.Create(client, chain, userWallet, managedExecution: false); +// var thirdwebWallet = await ThirdwebWallet.Create(client, chain, userWallet, ExecutionMode.EIP7702); // var thirdwebWalletAddress = await thirdwebWallet.GetAddress(); // Console.WriteLine($"Thirdweb Wallet address: {thirdwebWalletAddress}"); // same as userWallet address, unlike when using EIP-4337 diff --git a/Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs b/Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs index a9c4a4f..e006f59 100644 --- a/Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs @@ -4,6 +4,13 @@ namespace Thirdweb; +public enum ExecutionMode +{ + EOA, + EIP7702, + EIP7702Sponsored +} + /// /// Represents a 7702 delegated wallet with granular session key permissions and automatic session key execution. /// @@ -17,27 +24,29 @@ public class ThirdwebWallet : IThirdwebWallet internal IThirdwebWallet UserWallet { get; } internal ThirdwebContract UserContract { get; } internal BigInteger ChainId { get; } - internal bool ManagedExecution { get; } + internal ExecutionMode ExecutionMode { get; } private EIP7702Authorization? Authorization { get; set; } - internal ThirdwebWallet(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, ThirdwebContract userContract, EIP7702Authorization? authorization, bool managedExecution) + internal ThirdwebWallet(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, ThirdwebContract userContract, EIP7702Authorization? authorization, ExecutionMode executionMode) { this.Client = client; this.ChainId = chainId; this.UserWallet = userWallet; this.UserContract = userContract; this.Authorization = authorization; - this.ManagedExecution = managedExecution; + this.ExecutionMode = executionMode; } - public static async Task Create(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, bool managedExecution) + public static async Task Create(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, ExecutionMode executionMode) { var userWalletAddress = await userWallet.GetAddress(); var userContract = await ThirdwebContract.Create(client, userWalletAddress, chainId, Constants.MINIMAL_ACCOUNT_7702_ABI); var needsDelegation = !await Utils.IsDelegatedAccount(client, chainId, userWalletAddress); - EIP7702Authorization? authorization = needsDelegation ? await userWallet.SignAuthorization(chainId, Constants.MINIMAL_ACCOUNT_7702, willSelfExecute: !managedExecution) : null; - var wallet = new ThirdwebWallet(client, chainId, userWallet, userContract, authorization, managedExecution); + EIP7702Authorization? authorization = needsDelegation + ? await userWallet.SignAuthorization(chainId, Constants.MINIMAL_ACCOUNT_7702, willSelfExecute: executionMode != ExecutionMode.EIP7702Sponsored) + : null; + var wallet = new ThirdwebWallet(client, chainId, userWallet, userContract, authorization, executionMode); Utils.TrackConnection(wallet); return wallet; } @@ -122,16 +131,16 @@ public Task SignTransaction(ThirdwebTransactionInput transaction) public async Task SendTransaction(ThirdwebTransactionInput transaction) { // TODO: managed execution - executeWithSig - if (this.ManagedExecution) + if (this.ExecutionMode == ExecutionMode.EIP7702Sponsored) { - throw new NotImplementedException("Managed execution is not yet implemented."); + throw new NotImplementedException("EIP7702 Sponsored Execution mode is not yet implemented."); // 1. Create payload with eoa address, wrapped calls, signature and optional authorizationList // 2. Send to https://{chainId}.bundler.thirdweb.com as RpcRequest w/ method tw_execute // 3. Retrieve tx hash or queue id from response // 4. Return tx hash } - else + else if (this.ExecutionMode == ExecutionMode.EIP7702) { var calls = new List { @@ -165,6 +174,10 @@ public async Task SendTransaction(ThirdwebTransactionInput transaction) return await ThirdwebTransaction.Send(tx); } + else + { + return await this.UserWallet.SendTransaction(transaction); + } } public async Task ExecuteTransaction(ThirdwebTransactionInput transaction) From d24392c3e1cab4a0774b665b1f1220b56b4a2d44 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Wed, 7 May 2025 17:50:47 +0700 Subject: [PATCH 06/15] ThirdwebWallet -> SmarterWallet + fix yParity geth --- Thirdweb.Console/Program.cs | 57 +++++++------ Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs | 2 + .../ThirdwebTransactionInput.cs | 2 +- .../PrivateKeyWallet/PrivateKeyWallet.cs | 2 +- .../{ThirdwebWallet.cs => SmarterWallet.cs} | 82 +++++++++---------- 5 files changed, 72 insertions(+), 73 deletions(-) rename Thirdweb/Thirdweb.Wallets/{ThirdwebWallet.cs => SmarterWallet.cs} (73%) diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index ac1bf9d..945fc26 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -326,35 +326,34 @@ #region EIP-7702 -// var chain = 11155111; // sepolia - -// // Connect to EOA -// var userWallet = await InAppWallet.Create(client, authProvider: AuthProvider.Github); -// if (!await userWallet.IsConnected()) -// { -// _ = await userWallet.LoginWithOauth( -// isMobile: false, -// browserOpenAction: (url) => -// { -// var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true }; -// _ = Process.Start(psi); -// } -// ); -// } -// Console.WriteLine($"User Wallet address: {await userWallet.GetAddress()}"); - -// // Upgrade EOA - This wallet explicitly uses EIP-7702 delegation to the thirdweb MinimalAccount (will delegate upon first tx) -// var thirdwebWallet = await ThirdwebWallet.Create(client, chain, userWallet, ExecutionMode.EIP7702); -// var thirdwebWalletAddress = await thirdwebWallet.GetAddress(); -// Console.WriteLine($"Thirdweb Wallet address: {thirdwebWalletAddress}"); // same as userWallet address, unlike when using EIP-4337 - -// // Transact, will upgrade EOA -// var receipt = await thirdwebWallet.Transfer(chainId: chain, toAddress: await Utils.GetAddressFromENS(client, "vitalik.eth"), weiAmount: 0); -// Console.WriteLine($"Transfer Receipt: {receipt.TransactionHash}"); - -// // Double check that it was upgraded -// var isDelegated = await Utils.IsDelegatedAccount(client, chain, thirdwebWalletAddress); -// Console.WriteLine($"Is delegated: {isDelegated}"); +var chain = 11155111; // sepolia + +// Connect to EOA +var userWallet = await InAppWallet.Create(client, email: "firekeeper+7702testing04@thirdweb.com"); +if (!await userWallet.IsConnected()) +{ + await userWallet.SendOTP(); + Console.WriteLine("Enter OTP:"); + var otp = Console.ReadLine(); + _ = await userWallet.LoginWithOtp(otp: otp); +} +Console.WriteLine($"User Wallet address: {await userWallet.GetAddress()}"); + +Console.WriteLine("Send it some gas if testing with ExecutionMode.EOA"); +Console.ReadLine(); + +// Upgrade EOA - This wallet explicitly uses EIP-7702 delegation to the thirdweb MinimalAccount (will delegate upon first tx) +var smarterWallet = await SmarterWallet.Create(client, chain, userWallet, ExecutionMode.EOA); +var smarterWalletAddress = await smarterWallet.GetAddress(); +Console.WriteLine($"Thirdweb Wallet address: {smarterWalletAddress}"); // same as userWallet address, unlike when using EIP-4337 + +// Transact, will upgrade EOA +var receipt = await smarterWallet.Transfer(chainId: chain, toAddress: await Utils.GetAddressFromENS(client, "vitalik.eth"), weiAmount: 0); +Console.WriteLine($"Transfer Receipt: {receipt.TransactionHash}"); + +// Double check that it was upgraded +var isDelegated = await Utils.IsDelegatedAccount(client, chain, smarterWalletAddress); +Console.WriteLine($"Is delegated: {isDelegated}"); #endregion diff --git a/Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs b/Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs index 88884a7..93454a1 100644 --- a/Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs +++ b/Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs @@ -168,6 +168,7 @@ private ThirdwebRPC(ThirdwebClient client, BigInteger chainId) private async Task SendBatchAsync(List batch) { var batchJson = JsonConvert.SerializeObject(batch); + Console.WriteLine($"Sending batch request: {batchJson}"); var content = new StringContent(batchJson, Encoding.UTF8, "application/json"); try @@ -182,6 +183,7 @@ private async Task SendBatchAsync(List batch) } var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + Console.WriteLine(responseContent); if (responseContent.Equals("Unauthorized", StringComparison.OrdinalIgnoreCase)) { throw new HttpRequestException("Unauthorized"); diff --git a/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs b/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs index 7ef2571..03ec1d3 100644 --- a/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs +++ b/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs @@ -214,7 +214,7 @@ public EIP7702Authorization(BigInteger chainId, string address, BigInteger nonce this.ChainId = new HexBigInteger(chainId).HexValue; this.Address = address; this.Nonce = new HexBigInteger(nonce).HexValue; - this.YParity = yParity.BytesToHex(); + this.YParity = yParity.BytesToHex() == "0x00" ? "0x0" : "0x1"; this.R = r.BytesToHex(); this.S = s.BytesToHex(); } diff --git a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs index 536cf48..1529d49 100644 --- a/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs @@ -356,7 +356,7 @@ public virtual Task SignTransaction(ThirdwebTransactionInput transaction RLP.EncodeElement(authorizationList.ChainId.HexToNumber().ToByteArrayForRLPEncoding()), RLP.EncodeElement(authorizationList.Address.HexToBytes()), RLP.EncodeElement(authorizationList.Nonce.HexToNumber().ToByteArrayForRLPEncoding()), - RLP.EncodeElement(authorizationList.YParity == "0x00" ? Array.Empty() : authorizationList.YParity.HexToBytes()), + RLP.EncodeElement(authorizationList.YParity is "0x00" or "0x0" or "0x" ? Array.Empty() : authorizationList.YParity.HexToBytes()), RLP.EncodeElement(authorizationList.R.HexToBytes().TrimZeroes()), RLP.EncodeElement(authorizationList.S.HexToBytes().TrimZeroes()) }; diff --git a/Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs b/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs similarity index 73% rename from Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs rename to Thirdweb/Thirdweb.Wallets/SmarterWallet.cs index e006f59..6303828 100644 --- a/Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs @@ -8,15 +8,14 @@ public enum ExecutionMode { EOA, EIP7702, - EIP7702Sponsored } /// /// Represents a 7702 delegated wallet with granular session key permissions and automatic session key execution. /// -public class ThirdwebWallet : IThirdwebWallet +public class SmarterWallet : IThirdwebWallet { - public string WalletId => "thirdweb"; + public string WalletId => "smarter"; public ThirdwebClient Client { get; } public ThirdwebAccountType AccountType => ThirdwebAccountType.ExternalAccount; @@ -28,7 +27,7 @@ public class ThirdwebWallet : IThirdwebWallet private EIP7702Authorization? Authorization { get; set; } - internal ThirdwebWallet(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, ThirdwebContract userContract, EIP7702Authorization? authorization, ExecutionMode executionMode) + internal SmarterWallet(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, ThirdwebContract userContract, EIP7702Authorization? authorization, ExecutionMode executionMode) { this.Client = client; this.ChainId = chainId; @@ -38,15 +37,15 @@ internal ThirdwebWallet(ThirdwebClient client, BigInteger chainId, IThirdwebWall this.ExecutionMode = executionMode; } - public static async Task Create(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, ExecutionMode executionMode) + public static async Task Create(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, ExecutionMode executionMode) { var userWalletAddress = await userWallet.GetAddress(); var userContract = await ThirdwebContract.Create(client, userWalletAddress, chainId, Constants.MINIMAL_ACCOUNT_7702_ABI); var needsDelegation = !await Utils.IsDelegatedAccount(client, chainId, userWalletAddress); EIP7702Authorization? authorization = needsDelegation - ? await userWallet.SignAuthorization(chainId, Constants.MINIMAL_ACCOUNT_7702, willSelfExecute: executionMode != ExecutionMode.EIP7702Sponsored) + ? await userWallet.SignAuthorization(chainId, Constants.MINIMAL_ACCOUNT_7702, willSelfExecute: executionMode != ExecutionMode.EIP7702) : null; - var wallet = new ThirdwebWallet(client, chainId, userWallet, userContract, authorization, executionMode); + var wallet = new SmarterWallet(client, chainId, userWallet, userContract, authorization, executionMode); Utils.TrackConnection(wallet); return wallet; } @@ -130,54 +129,53 @@ public Task SignTransaction(ThirdwebTransactionInput transaction) public async Task SendTransaction(ThirdwebTransactionInput transaction) { - // TODO: managed execution - executeWithSig - if (this.ExecutionMode == ExecutionMode.EIP7702Sponsored) + ThirdwebTransaction finalTx; + switch (this.ExecutionMode) { - throw new NotImplementedException("EIP7702 Sponsored Execution mode is not yet implemented."); - + case ExecutionMode.EIP7702: + throw new NotImplementedException("EIP7702 Sponsored Execution mode is not yet implemented."); // 1. Create payload with eoa address, wrapped calls, signature and optional authorizationList // 2. Send to https://{chainId}.bundler.thirdweb.com as RpcRequest w/ method tw_execute // 3. Retrieve tx hash or queue id from response // 4. Return tx hash - } - else if (this.ExecutionMode == ExecutionMode.EIP7702) - { - var calls = new List - { - new() + case ExecutionMode.EOA: + // Direct Call struct + var calls = new List { - Target = transaction.To, - Value = transaction.Value?.Value ?? BigInteger.Zero, - Data = transaction.Data.HexToBytes() + new() + { + Target = transaction.To, + Value = transaction.Value?.Value ?? BigInteger.Zero, + Data = transaction.Data.HexToBytes() + } + }; + // Add up values of all calls + BigInteger totalValue = 0; + foreach (var call in calls) + { + totalValue += call.Value; } - }; + // Prepare a tx using the user wallet as the executor + finalTx = await this.UserContract.Prepare(wallet: this.UserWallet, method: "execute", weiValue: totalValue, parameters: new object[] { calls }); + break; + default: + throw new NotImplementedException($"Execution mode {this.ExecutionMode} is not supported."); + } - BigInteger totalValue = 0; - foreach (var call in calls) + // Append authorization if not delegated yet + if (this.Authorization != null) + { + if (!await Utils.IsDelegatedAccount(this.Client, this.ChainId, await this.UserWallet.GetAddress())) { - totalValue += call.Value; + finalTx.Input.AuthorizationList = new List() { this.Authorization.Value }; } - - var tx = await this.UserContract.Prepare(wallet: this.UserWallet, method: "execute", weiValue: totalValue, parameters: new object[] { calls }); - - if (this.Authorization != null) + else { - if (!await Utils.IsDelegatedAccount(this.Client, this.ChainId, await this.UserWallet.GetAddress())) - { - tx.Input.AuthorizationList = new List() { this.Authorization.Value }; - } - else - { - this.Authorization = null; - } + this.Authorization = null; } - - return await ThirdwebTransaction.Send(tx); - } - else - { - return await this.UserWallet.SendTransaction(transaction); } + // Send the transaction and return the + return await ThirdwebTransaction.Send(finalTx); } public async Task ExecuteTransaction(ThirdwebTransactionInput transaction) From 85df3033b5d198d685a81927aeb47ff01615bfcb Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Wed, 7 May 2025 19:24:19 +0700 Subject: [PATCH 07/15] 7702 sponsored execution progress push --- .../Thirdweb.AccountAbstraction/AATypes.cs | 23 ++++- .../BundlerClient.cs | 35 +++++++ Thirdweb/Thirdweb.Wallets/SmarterWallet.cs | 94 ++++++++++++------- 3 files changed, 115 insertions(+), 37 deletions(-) diff --git a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs index f06850a..cf0e1ff 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs @@ -240,6 +240,12 @@ public class EthGetUserOperationReceiptResponse public ThirdwebTransactionReceipt Receipt { get; set; } } +public class TwExecuteResponse +{ + [JsonProperty("queueId")] + public string QueueId { get; set; } +} + public class EntryPointWrapper { [JsonProperty("entryPoint")] @@ -606,18 +612,33 @@ public class Call [Parameter("bytes", "data", 3)] [JsonProperty("data")] public virtual byte[] Data { get; set; } + + public object EncodeForHttp() + { + return new + { + target = this.Target, + value = this.Value, + data = this.Data != null ? this.Data.BytesToHex() : "0x" + }; + } } [Struct("WrappedCalls")] public class WrappedCalls { - [Parameter("tuple[]", "calls", 1)] + [Parameter("tuple[]", "calls", 1, structTypeName: "Call[]")] [JsonProperty("calls")] public virtual List Calls { get; set; } [Parameter("bytes32", "uid", 2)] [JsonProperty("uid")] public virtual byte[] Uid { get; set; } + + public object EncodeForHttp() + { + return new { calls = this.Calls != null ? this.Calls.Select(c => c.EncodeForHttp()).ToList() : new List(), uid = this.Uid.BytesToHex() }; + } } #endregion diff --git a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/BundlerClient.cs b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/BundlerClient.cs index 620d925..000069d 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/BundlerClient.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/BundlerClient.cs @@ -5,6 +5,41 @@ namespace Thirdweb.AccountAbstraction; public static class BundlerClient { + // EIP 7702 requests + public static async Task TwExecute( + ThirdwebClient client, + string url, + object requestId, + string eoaAddress, + WrappedCalls wrappedCalls, + string signature, + EIP7702Authorization? authorization + ) + { + var response = await BundlerRequest( + client, + url, + requestId, + "tw_execute", + eoaAddress, + wrappedCalls.EncodeForHttp(), + signature, + authorization == null + ? null + : new + { + chainId = authorization?.ChainId.HexToNumber(), + address = authorization?.Address, + nonce = authorization?.Nonce.HexToNumber(), + yParity = authorization?.YParity.HexToNumber(), + r = authorization?.R, + s = authorization?.S + } + ) + .ConfigureAwait(false); + return JsonConvert.DeserializeObject(response.Result.ToString()); + } + // Bundler requests public static async Task EthGetUserOperationReceipt(ThirdwebClient client, string bundlerUrl, object requestId, string userOpHash) diff --git a/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs b/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs index 6303828..1fb02c2 100644 --- a/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs @@ -1,5 +1,6 @@ using System.Numerics; using Nethereum.ABI.EIP712; +using Nethereum.Util; using Thirdweb.AccountAbstraction; namespace Thirdweb; @@ -42,9 +43,7 @@ public static async Task Create(ThirdwebClient client, BigInteger var userWalletAddress = await userWallet.GetAddress(); var userContract = await ThirdwebContract.Create(client, userWalletAddress, chainId, Constants.MINIMAL_ACCOUNT_7702_ABI); var needsDelegation = !await Utils.IsDelegatedAccount(client, chainId, userWalletAddress); - EIP7702Authorization? authorization = needsDelegation - ? await userWallet.SignAuthorization(chainId, Constants.MINIMAL_ACCOUNT_7702, willSelfExecute: executionMode != ExecutionMode.EIP7702) - : null; + EIP7702Authorization? authorization = needsDelegation ? await userWallet.SignAuthorization(chainId, Constants.MINIMAL_ACCOUNT_7702, willSelfExecute: executionMode == ExecutionMode.EOA) : null; var wallet = new SmarterWallet(client, chainId, userWallet, userContract, authorization, executionMode); Utils.TrackConnection(wallet); return wallet; @@ -129,26 +128,59 @@ public Task SignTransaction(ThirdwebTransactionInput transaction) public async Task SendTransaction(ThirdwebTransactionInput transaction) { - ThirdwebTransaction finalTx; + var userWalletAddress = await this.UserWallet.GetAddress(); + + if (this.Authorization != null && await Utils.IsDelegatedAccount(this.Client, this.ChainId, userWalletAddress)) + { + this.Authorization = null; + } + + var calls = new List + { + new() + { + Target = transaction.To, + Value = transaction.Value?.Value ?? BigInteger.Zero, + Data = transaction.Data.HexToBytes() + } + }; + switch (this.ExecutionMode) { case ExecutionMode.EIP7702: - throw new NotImplementedException("EIP7702 Sponsored Execution mode is not yet implemented."); - // 1. Create payload with eoa address, wrapped calls, signature and optional authorizationList - // 2. Send to https://{chainId}.bundler.thirdweb.com as RpcRequest w/ method tw_execute - // 3. Retrieve tx hash or queue id from response - // 4. Return tx hash + var wrappedCalls = new WrappedCalls() { Calls = calls, Uid = Guid.NewGuid().ToByteArray().PadTo32Bytes() }; + var signature = await EIP712.GenerateSignature_SmartAccount_7702_WrappedCalls("MinimalAccount", "1", this.ChainId, userWalletAddress, wrappedCalls, this.UserWallet); + var response = await BundlerClient.TwExecute( + client: this.Client, + // url: $"{this.ChainId}.bundler.thirdweb.com", + url: "http://localhost:8787?chain=11155111", + requestId: 7702, + eoaAddress: userWalletAddress, + wrappedCalls: wrappedCalls, + signature: signature, + authorization: this.Authorization != null && !await Utils.IsDelegatedAccount(this.Client, this.ChainId, userWalletAddress) ? this.Authorization : null + ); + throw new NotImplementedException($"EIP-7702 transaction execution is not done, here's the queue id: {response.QueueId}"); + // string txHash = null; + // var ct = new CancellationTokenSource(this.Client.FetchTimeoutOptions.GetTimeout(TimeoutType.Other)); + // try + // { + // while (txHash == null) + // { + // ct.Token.ThrowIfCancellationRequested(); + + // var txReceipt = await BundlerClient.TwGetTransactionReceipt(client: this.Client, url: $"{this.ChainId}.bundler.thirdweb.com", requestId: 7702, queueId).ConfigureAwait(false); + + // txHash = txReceipt?.Receipt?.TransactionHash; + // await ThirdwebTask.Delay(100, ct.Token).ConfigureAwait(false); + // } + // } + // catch (OperationCanceledException) + // { + // throw new Exception($"EIP-7702 sponsored transaction timed out with queue id: {queueId}"); + // } + // break; case ExecutionMode.EOA: - // Direct Call struct - var calls = new List - { - new() - { - Target = transaction.To, - Value = transaction.Value?.Value ?? BigInteger.Zero, - Data = transaction.Data.HexToBytes() - } - }; // Add up values of all calls BigInteger totalValue = 0; foreach (var call in calls) @@ -156,26 +188,16 @@ public async Task SendTransaction(ThirdwebTransactionInput transaction) totalValue += call.Value; } // Prepare a tx using the user wallet as the executor - finalTx = await this.UserContract.Prepare(wallet: this.UserWallet, method: "execute", weiValue: totalValue, parameters: new object[] { calls }); - break; + var finalTx = await this.UserContract.Prepare(wallet: this.UserWallet, method: "execute", weiValue: totalValue, parameters: new object[] { calls }); + finalTx.Input.AuthorizationList = this.Authorization != null ? new List() { this.Authorization.Value } : null; + + // Append authorization if not delegated yet + + // Send the transaction and return the + return await ThirdwebTransaction.Send(finalTx); default: throw new NotImplementedException($"Execution mode {this.ExecutionMode} is not supported."); } - - // Append authorization if not delegated yet - if (this.Authorization != null) - { - if (!await Utils.IsDelegatedAccount(this.Client, this.ChainId, await this.UserWallet.GetAddress())) - { - finalTx.Input.AuthorizationList = new List() { this.Authorization.Value }; - } - else - { - this.Authorization = null; - } - } - // Send the transaction and return the - return await ThirdwebTransaction.Send(finalTx); } public async Task ExecuteTransaction(ThirdwebTransactionInput transaction) From 934b7ef730e72e3bfa23cc3999b7e8c6fffc755b Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Wed, 7 May 2025 19:47:43 +0700 Subject: [PATCH 08/15] tw_getTransactionHash --- .../Thirdweb.AccountAbstraction/AATypes.cs | 6 +++ .../BundlerClient.cs | 6 +++ Thirdweb/Thirdweb.Wallets/SmarterWallet.cs | 48 +++++++++++-------- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs index cf0e1ff..2885553 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs @@ -246,6 +246,12 @@ public class TwExecuteResponse public string QueueId { get; set; } } +public class TwGetTransactionHashResponse +{ + [JsonProperty("transactionHash")] + public string TransactionHash { get; set; } +} + public class EntryPointWrapper { [JsonProperty("entryPoint")] diff --git a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/BundlerClient.cs b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/BundlerClient.cs index 000069d..cb2fe58 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/BundlerClient.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/BundlerClient.cs @@ -40,6 +40,12 @@ public static async Task TwExecute( return JsonConvert.DeserializeObject(response.Result.ToString()); } + public static async Task TwGetTransactionHash(ThirdwebClient client, string url, int requestId, string queueId) + { + var response = await BundlerRequest(client, url, requestId, "tw_getTransactionHash", queueId).ConfigureAwait(false); + return JsonConvert.DeserializeObject(response.Result.ToString()); + } + // Bundler requests public static async Task EthGetUserOperationReceipt(ThirdwebClient client, string bundlerUrl, object requestId, string userOpHash) diff --git a/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs b/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs index 1fb02c2..e567b1f 100644 --- a/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs @@ -160,26 +160,34 @@ public async Task SendTransaction(ThirdwebTransactionInput transaction) signature: signature, authorization: this.Authorization != null && !await Utils.IsDelegatedAccount(this.Client, this.ChainId, userWalletAddress) ? this.Authorization : null ); - throw new NotImplementedException($"EIP-7702 transaction execution is not done, here's the queue id: {response.QueueId}"); - // string txHash = null; - // var ct = new CancellationTokenSource(this.Client.FetchTimeoutOptions.GetTimeout(TimeoutType.Other)); - // try - // { - // while (txHash == null) - // { - // ct.Token.ThrowIfCancellationRequested(); - - // var txReceipt = await BundlerClient.TwGetTransactionReceipt(client: this.Client, url: $"{this.ChainId}.bundler.thirdweb.com", requestId: 7702, queueId).ConfigureAwait(false); - - // txHash = txReceipt?.Receipt?.TransactionHash; - // await ThirdwebTask.Delay(100, ct.Token).ConfigureAwait(false); - // } - // } - // catch (OperationCanceledException) - // { - // throw new Exception($"EIP-7702 sponsored transaction timed out with queue id: {queueId}"); - // } - // break; + var queueId = response?.QueueId; + string txHash = null; + var ct = new CancellationTokenSource(this.Client.FetchTimeoutOptions.GetTimeout(TimeoutType.Other)); + try + { + while (txHash == null) + { + ct.Token.ThrowIfCancellationRequested(); + + var hashResponse = await BundlerClient + .TwGetTransactionHash( + client: this.Client, + // url: $"{this.ChainId}.bundler.thirdweb.com", + url: "http://localhost:8787?chain=11155111", + requestId: 7702, + queueId + ) + .ConfigureAwait(false); + + txHash = hashResponse?.TransactionHash; + await ThirdwebTask.Delay(100, ct.Token).ConfigureAwait(false); + } + return txHash; + } + catch (OperationCanceledException) + { + throw new Exception($"EIP-7702 sponsored transaction timed out with queue id: {queueId}"); + } case ExecutionMode.EOA: // Add up values of all calls BigInteger totalValue = 0; From 2c6384b9204267b37657bfbd8167d1121eb93cfe Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 8 May 2025 00:00:40 +0700 Subject: [PATCH 09/15] update minimalaccount implementation --- Thirdweb/Thirdweb.Utils/Constants.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Thirdweb/Thirdweb.Utils/Constants.cs b/Thirdweb/Thirdweb.Utils/Constants.cs index 4fe5513..4c0b609 100644 --- a/Thirdweb/Thirdweb.Utils/Constants.cs +++ b/Thirdweb/Thirdweb.Utils/Constants.cs @@ -35,10 +35,10 @@ public static class Constants "0x0101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000001010101010100000000000000000000000000000000000000000000000000000000000000000101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"; internal const string ENS_REGISTRY_ADDRESS = "0xce01f8eee7E479C928F8919abD53E553a36CeF67"; - public const string MINIMAL_ACCOUNT_7702 = "0x8001eA4C0f8595488A6a0cF7211F900948892092"; + public const string MINIMAL_ACCOUNT_7702 = "MINIMAL_ACCOUNT_7702_IMPLEMENTATION_ADDRESS"; public const string MINIMAL_ACCOUNT_7702_ABI = /*lang=json,strict*/ - "[{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"allowanceUsage\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint64\",\"name\": \"period\",\"type\": \"uint64\"}],\"name\": \"AllowanceExceeded\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"}],\"name\": \"CallPolicyViolated\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"CallReverted\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"param\",\"type\": \"bytes32\"},{\"internalType\": \"bytes32\",\"name\": \"refValue\",\"type\": \"bytes32\"},{\"internalType\": \"uint8\",\"name\": \"condition\",\"type\": \"uint8\"}],\"name\": \"ConditionFailed\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"actualLength\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"expectedLength\",\"type\": \"uint256\"}],\"name\": \"InvalidDataLength\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"msgSender\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"thisAddress\",\"type\": \"address\"}],\"name\": \"InvalidSignature\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"lifetimeUsage\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"}],\"name\": \"LifetimeUsageExceeded\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"}],\"name\": \"MaxValueExceeded\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"NoCallsToExecute\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"SessionExpired\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"SessionExpiresTooSoon\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"SessionZeroSigner\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"}],\"name\": \"TransferPolicyViolated\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"UIDAlreadyProcessed\",\"type\": \"error\"},{\"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\"},{\"anonymous\": false,\"inputs\": [{\"indexed\": true,\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"},{\"internalType\": \"bool\",\"name\": \"isWildcard\",\"type\": \"bool\"},{\"internalType\": \"uint256\",\"name\": \"expiresAt\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"},{\"components\": [{\"internalType\": \"enum SessionLib.Condition\",\"name\": \"condition\",\"type\": \"uint8\"},{\"internalType\": \"uint64\",\"name\": \"index\",\"type\": \"uint64\"},{\"internalType\": \"bytes32\",\"name\": \"refValue\",\"type\": \"bytes32\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"limit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.Constraint[]\",\"name\": \"constraints\",\"type\": \"tuple[]\"}],\"internalType\": \"struct SessionLib.CallSpec[]\",\"name\": \"callPolicies\",\"type\": \"tuple[]\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.TransferSpec[]\",\"name\": \"transferPolicies\",\"type\": \"tuple[]\"},{\"internalType\": \"bytes32\",\"name\": \"uid\",\"type\": \"bytes32\"}],\"indexed\": false,\"internalType\": \"struct SessionLib.SessionSpec\",\"name\": \"sessionSpec\",\"type\": \"tuple\"}],\"name\": \"SessionCreated\",\"type\": \"event\"},{\"inputs\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"},{\"internalType\": \"bool\",\"name\": \"isWildcard\",\"type\": \"bool\"},{\"internalType\": \"uint256\",\"name\": \"expiresAt\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"},{\"components\": [{\"internalType\": \"enum SessionLib.Condition\",\"name\": \"condition\",\"type\": \"uint8\"},{\"internalType\": \"uint64\",\"name\": \"index\",\"type\": \"uint64\"},{\"internalType\": \"bytes32\",\"name\": \"refValue\",\"type\": \"bytes32\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"limit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.Constraint[]\",\"name\": \"constraints\",\"type\": \"tuple[]\"}],\"internalType\": \"struct SessionLib.CallSpec[]\",\"name\": \"callPolicies\",\"type\": \"tuple[]\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.TransferSpec[]\",\"name\": \"transferPolicies\",\"type\": \"tuple[]\"},{\"internalType\": \"bytes32\",\"name\": \"uid\",\"type\": \"bytes32\"}],\"internalType\": \"struct SessionLib.SessionSpec\",\"name\": \"sessionSpec\",\"type\": \"tuple\"},{\"internalType\": \"bytes\",\"name\": \"signature\",\"type\": \"bytes\"}],\"name\": \"createSessionWithSig\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"eip712Domain\",\"outputs\": [{\"internalType\": \"bytes1\",\"name\": \"fields\",\"type\": \"bytes1\"},{\"internalType\": \"string\",\"name\": \"name\",\"type\": \"string\"},{\"internalType\": \"string\",\"name\": \"version\",\"type\": \"string\"},{\"internalType\": \"uint256\",\"name\": \"chainId\",\"type\": \"uint256\"},{\"internalType\": \"address\",\"name\": \"verifyingContract\",\"type\": \"address\"},{\"internalType\": \"bytes32\",\"name\": \"salt\",\"type\": \"bytes32\"},{\"internalType\": \"uint256[]\",\"name\": \"extensions\",\"type\": \"uint256[]\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"},{\"internalType\": \"bytes\",\"name\": \"data\",\"type\": \"bytes\"}],\"internalType\": \"struct Call[]\",\"name\": \"calls\",\"type\": \"tuple[]\"}],\"name\": \"execute\",\"outputs\": [],\"stateMutability\": \"payable\",\"type\": \"function\"},{\"inputs\": [{\"components\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"},{\"internalType\": \"bytes\",\"name\": \"data\",\"type\": \"bytes\"}],\"internalType\": \"struct Call[]\",\"name\": \"calls\",\"type\": \"tuple[]\"},{\"internalType\": \"bytes32\",\"name\": \"uid\",\"type\": \"bytes32\"}],\"internalType\": \"struct WrappedCalls\",\"name\": \"wrappedCalls\",\"type\": \"tuple\"},{\"internalType\": \"bytes\",\"name\": \"signature\",\"type\": \"bytes\"}],\"name\": \"executeWithSig\",\"outputs\": [],\"stateMutability\": \"payable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"getCallPoliciesForSigner\",\"outputs\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"},{\"components\": [{\"internalType\": \"enum SessionLib.Condition\",\"name\": \"condition\",\"type\": \"uint8\"},{\"internalType\": \"uint64\",\"name\": \"index\",\"type\": \"uint64\"},{\"internalType\": \"bytes32\",\"name\": \"refValue\",\"type\": \"bytes32\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"limit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.Constraint[]\",\"name\": \"constraints\",\"type\": \"tuple[]\"}],\"internalType\": \"struct SessionLib.CallSpec[]\",\"name\": \"\",\"type\": \"tuple[]\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"getSessionExpirationForSigner\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"getSessionStateForSigner\",\"outputs\": [{\"components\": [{\"components\": [{\"internalType\": \"uint256\",\"name\": \"remaining\",\"type\": \"uint256\"},{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"index\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.LimitState[]\",\"name\": \"transferValue\",\"type\": \"tuple[]\"},{\"components\": [{\"internalType\": \"uint256\",\"name\": \"remaining\",\"type\": \"uint256\"},{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"index\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.LimitState[]\",\"name\": \"callValue\",\"type\": \"tuple[]\"},{\"components\": [{\"internalType\": \"uint256\",\"name\": \"remaining\",\"type\": \"uint256\"},{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"index\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.LimitState[]\",\"name\": \"callParams\",\"type\": \"tuple[]\"}],\"internalType\": \"struct SessionLib.SessionState\",\"name\": \"\",\"type\": \"tuple\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"getTransferPoliciesForSigner\",\"outputs\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.TransferSpec[]\",\"name\": \"\",\"type\": \"tuple[]\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"isWildcardSigner\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"uint256[]\",\"name\": \"\",\"type\": \"uint256[]\"},{\"internalType\": \"uint256[]\",\"name\": \"\",\"type\": \"uint256[]\"},{\"internalType\": \"bytes\",\"name\": \"\",\"type\": \"bytes\"}],\"name\": \"onERC1155BatchReceived\",\"outputs\": [{\"internalType\": \"bytes4\",\"name\": \"\",\"type\": \"bytes4\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"},{\"internalType\": \"bytes\",\"name\": \"\",\"type\": \"bytes\"}],\"name\": \"onERC1155Received\",\"outputs\": [{\"internalType\": \"bytes4\",\"name\": \"\",\"type\": \"bytes4\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"},{\"internalType\": \"bytes\",\"name\": \"\",\"type\": \"bytes\"}],\"name\": \"onERC721Received\",\"outputs\": [{\"internalType\": \"bytes4\",\"name\": \"\",\"type\": \"bytes4\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes4\",\"name\": \"interfaceId\",\"type\": \"bytes4\"}],\"name\": \"supportsInterface\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"view\",\"type\": \"function\"}]"; + "[{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"allowanceUsage\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint64\",\"name\": \"period\",\"type\": \"uint64\"}],\"name\": \"AllowanceExceeded\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"}],\"name\": \"CallPolicyViolated\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"CallReverted\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"param\",\"type\": \"bytes32\"},{\"internalType\": \"bytes32\",\"name\": \"refValue\",\"type\": \"bytes32\"},{\"internalType\": \"uint8\",\"name\": \"condition\",\"type\": \"uint8\"}],\"name\": \"ConditionFailed\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"FnSelectorNotRecognized\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"actualLength\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"expectedLength\",\"type\": \"uint256\"}],\"name\": \"InvalidDataLength\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"msgSender\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"thisAddress\",\"type\": \"address\"}],\"name\": \"InvalidSignature\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"lifetimeUsage\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"}],\"name\": \"LifetimeUsageExceeded\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"}],\"name\": \"MaxValueExceeded\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"NoCallsToExecute\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"SessionExpired\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"SessionExpiresTooSoon\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"SessionZeroSigner\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"}],\"name\": \"TransferPolicyViolated\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"UIDAlreadyProcessed\",\"type\": \"error\"},{\"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\"},{\"anonymous\": false,\"inputs\": [{\"indexed\": true,\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"},{\"internalType\": \"bool\",\"name\": \"isWildcard\",\"type\": \"bool\"},{\"internalType\": \"uint256\",\"name\": \"expiresAt\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"},{\"components\": [{\"internalType\": \"enum SessionLib.Condition\",\"name\": \"condition\",\"type\": \"uint8\"},{\"internalType\": \"uint64\",\"name\": \"index\",\"type\": \"uint64\"},{\"internalType\": \"bytes32\",\"name\": \"refValue\",\"type\": \"bytes32\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"limit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.Constraint[]\",\"name\": \"constraints\",\"type\": \"tuple[]\"}],\"internalType\": \"struct SessionLib.CallSpec[]\",\"name\": \"callPolicies\",\"type\": \"tuple[]\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.TransferSpec[]\",\"name\": \"transferPolicies\",\"type\": \"tuple[]\"},{\"internalType\": \"bytes32\",\"name\": \"uid\",\"type\": \"bytes32\"}],\"indexed\": false,\"internalType\": \"struct SessionLib.SessionSpec\",\"name\": \"sessionSpec\",\"type\": \"tuple\"}],\"name\": \"SessionCreated\",\"type\": \"event\"},{\"stateMutability\": \"payable\",\"type\": \"fallback\"},{\"inputs\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"},{\"internalType\": \"bool\",\"name\": \"isWildcard\",\"type\": \"bool\"},{\"internalType\": \"uint256\",\"name\": \"expiresAt\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"},{\"components\": [{\"internalType\": \"enum SessionLib.Condition\",\"name\": \"condition\",\"type\": \"uint8\"},{\"internalType\": \"uint64\",\"name\": \"index\",\"type\": \"uint64\"},{\"internalType\": \"bytes32\",\"name\": \"refValue\",\"type\": \"bytes32\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"limit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.Constraint[]\",\"name\": \"constraints\",\"type\": \"tuple[]\"}],\"internalType\": \"struct SessionLib.CallSpec[]\",\"name\": \"callPolicies\",\"type\": \"tuple[]\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.TransferSpec[]\",\"name\": \"transferPolicies\",\"type\": \"tuple[]\"},{\"internalType\": \"bytes32\",\"name\": \"uid\",\"type\": \"bytes32\"}],\"internalType\": \"struct SessionLib.SessionSpec\",\"name\": \"sessionSpec\",\"type\": \"tuple\"},{\"internalType\": \"bytes\",\"name\": \"signature\",\"type\": \"bytes\"}],\"name\": \"createSessionWithSig\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"eip712Domain\",\"outputs\": [{\"internalType\": \"bytes1\",\"name\": \"fields\",\"type\": \"bytes1\"},{\"internalType\": \"string\",\"name\": \"name\",\"type\": \"string\"},{\"internalType\": \"string\",\"name\": \"version\",\"type\": \"string\"},{\"internalType\": \"uint256\",\"name\": \"chainId\",\"type\": \"uint256\"},{\"internalType\": \"address\",\"name\": \"verifyingContract\",\"type\": \"address\"},{\"internalType\": \"bytes32\",\"name\": \"salt\",\"type\": \"bytes32\"},{\"internalType\": \"uint256[]\",\"name\": \"extensions\",\"type\": \"uint256[]\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"},{\"internalType\": \"bytes\",\"name\": \"data\",\"type\": \"bytes\"}],\"internalType\": \"struct Call[]\",\"name\": \"calls\",\"type\": \"tuple[]\"}],\"name\": \"execute\",\"outputs\": [],\"stateMutability\": \"payable\",\"type\": \"function\"},{\"inputs\": [{\"components\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"},{\"internalType\": \"bytes\",\"name\": \"data\",\"type\": \"bytes\"}],\"internalType\": \"struct Call[]\",\"name\": \"calls\",\"type\": \"tuple[]\"},{\"internalType\": \"bytes32\",\"name\": \"uid\",\"type\": \"bytes32\"}],\"internalType\": \"struct WrappedCalls\",\"name\": \"wrappedCalls\",\"type\": \"tuple\"},{\"internalType\": \"bytes\",\"name\": \"signature\",\"type\": \"bytes\"}],\"name\": \"executeWithSig\",\"outputs\": [],\"stateMutability\": \"payable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"getCallPoliciesForSigner\",\"outputs\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"},{\"components\": [{\"internalType\": \"enum SessionLib.Condition\",\"name\": \"condition\",\"type\": \"uint8\"},{\"internalType\": \"uint64\",\"name\": \"index\",\"type\": \"uint64\"},{\"internalType\": \"bytes32\",\"name\": \"refValue\",\"type\": \"bytes32\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"limit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.Constraint[]\",\"name\": \"constraints\",\"type\": \"tuple[]\"}],\"internalType\": \"struct SessionLib.CallSpec[]\",\"name\": \"\",\"type\": \"tuple[]\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"getSessionExpirationForSigner\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"getSessionStateForSigner\",\"outputs\": [{\"components\": [{\"components\": [{\"internalType\": \"uint256\",\"name\": \"remaining\",\"type\": \"uint256\"},{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"index\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.LimitState[]\",\"name\": \"transferValue\",\"type\": \"tuple[]\"},{\"components\": [{\"internalType\": \"uint256\",\"name\": \"remaining\",\"type\": \"uint256\"},{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"index\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.LimitState[]\",\"name\": \"callValue\",\"type\": \"tuple[]\"},{\"components\": [{\"internalType\": \"uint256\",\"name\": \"remaining\",\"type\": \"uint256\"},{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"index\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.LimitState[]\",\"name\": \"callParams\",\"type\": \"tuple[]\"}],\"internalType\": \"struct SessionLib.SessionState\",\"name\": \"\",\"type\": \"tuple\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"getTransferPoliciesForSigner\",\"outputs\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.TransferSpec[]\",\"name\": \"\",\"type\": \"tuple[]\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"isWildcardSigner\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"uint256[]\",\"name\": \"\",\"type\": \"uint256[]\"},{\"internalType\": \"uint256[]\",\"name\": \"\",\"type\": \"uint256[]\"},{\"internalType\": \"bytes\",\"name\": \"\",\"type\": \"bytes\"}],\"name\": \"onERC1155BatchReceived\",\"outputs\": [{\"internalType\": \"bytes4\",\"name\": \"\",\"type\": \"bytes4\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"},{\"internalType\": \"bytes\",\"name\": \"\",\"type\": \"bytes\"}],\"name\": \"onERC1155Received\",\"outputs\": [{\"internalType\": \"bytes4\",\"name\": \"\",\"type\": \"bytes4\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"},{\"internalType\": \"bytes\",\"name\": \"\",\"type\": \"bytes\"}],\"name\": \"onERC721Received\",\"outputs\": [{\"internalType\": \"bytes4\",\"name\": \"\",\"type\": \"bytes4\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes4\",\"name\": \"interfaceId\",\"type\": \"bytes4\"}],\"name\": \"supportsInterface\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"stateMutability\": \"payable\",\"type\": \"receive\"}]"; public const string MULTICALL3_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11"; public const string MULTICALL3_ABI = From 356c5821f6c42091b9cfd0aa04d12d6ce8298b6f Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 8 May 2025 00:53:49 +0700 Subject: [PATCH 10/15] . --- Thirdweb/Thirdweb.Utils/Constants.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Thirdweb/Thirdweb.Utils/Constants.cs b/Thirdweb/Thirdweb.Utils/Constants.cs index 4c0b609..68f400e 100644 --- a/Thirdweb/Thirdweb.Utils/Constants.cs +++ b/Thirdweb/Thirdweb.Utils/Constants.cs @@ -35,7 +35,7 @@ public static class Constants "0x0101010101010101010101010101010101010101000000000000000000000000000000000000000000000000000001010101010100000000000000000000000000000000000000000000000000000000000000000101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101"; internal const string ENS_REGISTRY_ADDRESS = "0xce01f8eee7E479C928F8919abD53E553a36CeF67"; - public const string MINIMAL_ACCOUNT_7702 = "MINIMAL_ACCOUNT_7702_IMPLEMENTATION_ADDRESS"; + public const string MINIMAL_ACCOUNT_7702 = "0xbaC7e770af15d130Cd72838ff386f14FBF3e9a3D"; public const string MINIMAL_ACCOUNT_7702_ABI = /*lang=json,strict*/ "[{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"allowanceUsage\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint64\",\"name\": \"period\",\"type\": \"uint64\"}],\"name\": \"AllowanceExceeded\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"}],\"name\": \"CallPolicyViolated\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"CallReverted\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"bytes32\",\"name\": \"param\",\"type\": \"bytes32\"},{\"internalType\": \"bytes32\",\"name\": \"refValue\",\"type\": \"bytes32\"},{\"internalType\": \"uint8\",\"name\": \"condition\",\"type\": \"uint8\"}],\"name\": \"ConditionFailed\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"FnSelectorNotRecognized\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"actualLength\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"expectedLength\",\"type\": \"uint256\"}],\"name\": \"InvalidDataLength\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"msgSender\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"thisAddress\",\"type\": \"address\"}],\"name\": \"InvalidSignature\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"lifetimeUsage\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"}],\"name\": \"LifetimeUsageExceeded\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"}],\"name\": \"MaxValueExceeded\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"NoCallsToExecute\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"SessionExpired\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"SessionExpiresTooSoon\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"SessionZeroSigner\",\"type\": \"error\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"}],\"name\": \"TransferPolicyViolated\",\"type\": \"error\"},{\"inputs\": [],\"name\": \"UIDAlreadyProcessed\",\"type\": \"error\"},{\"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\"},{\"anonymous\": false,\"inputs\": [{\"indexed\": true,\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"},{\"internalType\": \"bool\",\"name\": \"isWildcard\",\"type\": \"bool\"},{\"internalType\": \"uint256\",\"name\": \"expiresAt\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"},{\"components\": [{\"internalType\": \"enum SessionLib.Condition\",\"name\": \"condition\",\"type\": \"uint8\"},{\"internalType\": \"uint64\",\"name\": \"index\",\"type\": \"uint64\"},{\"internalType\": \"bytes32\",\"name\": \"refValue\",\"type\": \"bytes32\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"limit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.Constraint[]\",\"name\": \"constraints\",\"type\": \"tuple[]\"}],\"internalType\": \"struct SessionLib.CallSpec[]\",\"name\": \"callPolicies\",\"type\": \"tuple[]\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.TransferSpec[]\",\"name\": \"transferPolicies\",\"type\": \"tuple[]\"},{\"internalType\": \"bytes32\",\"name\": \"uid\",\"type\": \"bytes32\"}],\"indexed\": false,\"internalType\": \"struct SessionLib.SessionSpec\",\"name\": \"sessionSpec\",\"type\": \"tuple\"}],\"name\": \"SessionCreated\",\"type\": \"event\"},{\"stateMutability\": \"payable\",\"type\": \"fallback\"},{\"inputs\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"},{\"internalType\": \"bool\",\"name\": \"isWildcard\",\"type\": \"bool\"},{\"internalType\": \"uint256\",\"name\": \"expiresAt\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"},{\"components\": [{\"internalType\": \"enum SessionLib.Condition\",\"name\": \"condition\",\"type\": \"uint8\"},{\"internalType\": \"uint64\",\"name\": \"index\",\"type\": \"uint64\"},{\"internalType\": \"bytes32\",\"name\": \"refValue\",\"type\": \"bytes32\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"limit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.Constraint[]\",\"name\": \"constraints\",\"type\": \"tuple[]\"}],\"internalType\": \"struct SessionLib.CallSpec[]\",\"name\": \"callPolicies\",\"type\": \"tuple[]\"},{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.TransferSpec[]\",\"name\": \"transferPolicies\",\"type\": \"tuple[]\"},{\"internalType\": \"bytes32\",\"name\": \"uid\",\"type\": \"bytes32\"}],\"internalType\": \"struct SessionLib.SessionSpec\",\"name\": \"sessionSpec\",\"type\": \"tuple\"},{\"internalType\": \"bytes\",\"name\": \"signature\",\"type\": \"bytes\"}],\"name\": \"createSessionWithSig\",\"outputs\": [],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [],\"name\": \"eip712Domain\",\"outputs\": [{\"internalType\": \"bytes1\",\"name\": \"fields\",\"type\": \"bytes1\"},{\"internalType\": \"string\",\"name\": \"name\",\"type\": \"string\"},{\"internalType\": \"string\",\"name\": \"version\",\"type\": \"string\"},{\"internalType\": \"uint256\",\"name\": \"chainId\",\"type\": \"uint256\"},{\"internalType\": \"address\",\"name\": \"verifyingContract\",\"type\": \"address\"},{\"internalType\": \"bytes32\",\"name\": \"salt\",\"type\": \"bytes32\"},{\"internalType\": \"uint256[]\",\"name\": \"extensions\",\"type\": \"uint256[]\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"},{\"internalType\": \"bytes\",\"name\": \"data\",\"type\": \"bytes\"}],\"internalType\": \"struct Call[]\",\"name\": \"calls\",\"type\": \"tuple[]\"}],\"name\": \"execute\",\"outputs\": [],\"stateMutability\": \"payable\",\"type\": \"function\"},{\"inputs\": [{\"components\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"value\",\"type\": \"uint256\"},{\"internalType\": \"bytes\",\"name\": \"data\",\"type\": \"bytes\"}],\"internalType\": \"struct Call[]\",\"name\": \"calls\",\"type\": \"tuple[]\"},{\"internalType\": \"bytes32\",\"name\": \"uid\",\"type\": \"bytes32\"}],\"internalType\": \"struct WrappedCalls\",\"name\": \"wrappedCalls\",\"type\": \"tuple\"},{\"internalType\": \"bytes\",\"name\": \"signature\",\"type\": \"bytes\"}],\"name\": \"executeWithSig\",\"outputs\": [],\"stateMutability\": \"payable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"getCallPoliciesForSigner\",\"outputs\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"},{\"components\": [{\"internalType\": \"enum SessionLib.Condition\",\"name\": \"condition\",\"type\": \"uint8\"},{\"internalType\": \"uint64\",\"name\": \"index\",\"type\": \"uint64\"},{\"internalType\": \"bytes32\",\"name\": \"refValue\",\"type\": \"bytes32\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"limit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.Constraint[]\",\"name\": \"constraints\",\"type\": \"tuple[]\"}],\"internalType\": \"struct SessionLib.CallSpec[]\",\"name\": \"\",\"type\": \"tuple[]\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"getSessionExpirationForSigner\",\"outputs\": [{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"getSessionStateForSigner\",\"outputs\": [{\"components\": [{\"components\": [{\"internalType\": \"uint256\",\"name\": \"remaining\",\"type\": \"uint256\"},{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"index\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.LimitState[]\",\"name\": \"transferValue\",\"type\": \"tuple[]\"},{\"components\": [{\"internalType\": \"uint256\",\"name\": \"remaining\",\"type\": \"uint256\"},{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"index\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.LimitState[]\",\"name\": \"callValue\",\"type\": \"tuple[]\"},{\"components\": [{\"internalType\": \"uint256\",\"name\": \"remaining\",\"type\": \"uint256\"},{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"bytes4\",\"name\": \"selector\",\"type\": \"bytes4\"},{\"internalType\": \"uint256\",\"name\": \"index\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.LimitState[]\",\"name\": \"callParams\",\"type\": \"tuple[]\"}],\"internalType\": \"struct SessionLib.SessionState\",\"name\": \"\",\"type\": \"tuple\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"getTransferPoliciesForSigner\",\"outputs\": [{\"components\": [{\"internalType\": \"address\",\"name\": \"target\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"maxValuePerUse\",\"type\": \"uint256\"},{\"components\": [{\"internalType\": \"enum SessionLib.LimitType\",\"name\": \"limitType\",\"type\": \"uint8\"},{\"internalType\": \"uint256\",\"name\": \"limit\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"period\",\"type\": \"uint256\"}],\"internalType\": \"struct SessionLib.UsageLimit\",\"name\": \"valueLimit\",\"type\": \"tuple\"}],\"internalType\": \"struct SessionLib.TransferSpec[]\",\"name\": \"\",\"type\": \"tuple[]\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"signer\",\"type\": \"address\"}],\"name\": \"isWildcardSigner\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"uint256[]\",\"name\": \"\",\"type\": \"uint256[]\"},{\"internalType\": \"uint256[]\",\"name\": \"\",\"type\": \"uint256[]\"},{\"internalType\": \"bytes\",\"name\": \"\",\"type\": \"bytes\"}],\"name\": \"onERC1155BatchReceived\",\"outputs\": [{\"internalType\": \"bytes4\",\"name\": \"\",\"type\": \"bytes4\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"},{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"},{\"internalType\": \"bytes\",\"name\": \"\",\"type\": \"bytes\"}],\"name\": \"onERC1155Received\",\"outputs\": [{\"internalType\": \"bytes4\",\"name\": \"\",\"type\": \"bytes4\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"address\",\"name\": \"\",\"type\": \"address\"},{\"internalType\": \"uint256\",\"name\": \"\",\"type\": \"uint256\"},{\"internalType\": \"bytes\",\"name\": \"\",\"type\": \"bytes\"}],\"name\": \"onERC721Received\",\"outputs\": [{\"internalType\": \"bytes4\",\"name\": \"\",\"type\": \"bytes4\"}],\"stateMutability\": \"nonpayable\",\"type\": \"function\"},{\"inputs\": [{\"internalType\": \"bytes4\",\"name\": \"interfaceId\",\"type\": \"bytes4\"}],\"name\": \"supportsInterface\",\"outputs\": [{\"internalType\": \"bool\",\"name\": \"\",\"type\": \"bool\"}],\"stateMutability\": \"view\",\"type\": \"function\"},{\"stateMutability\": \"payable\",\"type\": \"receive\"}]"; From 5bb61cde91950be9e8c70e5d74750445176f40a5 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 8 May 2025 04:15:34 +0700 Subject: [PATCH 11/15] ExecutionMode -> sponsorGas --- Thirdweb/Thirdweb.Wallets/SmarterWallet.cs | 125 +++++++++------------ 1 file changed, 56 insertions(+), 69 deletions(-) diff --git a/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs b/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs index e567b1f..8d3f52f 100644 --- a/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs @@ -5,12 +5,6 @@ namespace Thirdweb; -public enum ExecutionMode -{ - EOA, - EIP7702, -} - /// /// Represents a 7702 delegated wallet with granular session key permissions and automatic session key execution. /// @@ -24,27 +18,27 @@ public class SmarterWallet : IThirdwebWallet internal IThirdwebWallet UserWallet { get; } internal ThirdwebContract UserContract { get; } internal BigInteger ChainId { get; } - internal ExecutionMode ExecutionMode { get; } + internal bool SponsorGas { get; } private EIP7702Authorization? Authorization { get; set; } - internal SmarterWallet(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, ThirdwebContract userContract, EIP7702Authorization? authorization, ExecutionMode executionMode) + internal SmarterWallet(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, ThirdwebContract userContract, EIP7702Authorization? authorization, bool sponsorGas) { this.Client = client; this.ChainId = chainId; this.UserWallet = userWallet; this.UserContract = userContract; this.Authorization = authorization; - this.ExecutionMode = executionMode; + this.SponsorGas = sponsorGas; } - public static async Task Create(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, ExecutionMode executionMode) + public static async Task Create(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, bool sponsorGas = true) { var userWalletAddress = await userWallet.GetAddress(); var userContract = await ThirdwebContract.Create(client, userWalletAddress, chainId, Constants.MINIMAL_ACCOUNT_7702_ABI); var needsDelegation = !await Utils.IsDelegatedAccount(client, chainId, userWalletAddress); - EIP7702Authorization? authorization = needsDelegation ? await userWallet.SignAuthorization(chainId, Constants.MINIMAL_ACCOUNT_7702, willSelfExecute: executionMode == ExecutionMode.EOA) : null; - var wallet = new SmarterWallet(client, chainId, userWallet, userContract, authorization, executionMode); + EIP7702Authorization? authorization = needsDelegation ? await userWallet.SignAuthorization(chainId, Constants.MINIMAL_ACCOUNT_7702, willSelfExecute: !sponsorGas) : null; + var wallet = new SmarterWallet(client, chainId, userWallet, userContract, authorization, sponsorGas); Utils.TrackConnection(wallet); return wallet; } @@ -145,66 +139,59 @@ public async Task SendTransaction(ThirdwebTransactionInput transaction) } }; - switch (this.ExecutionMode) + if (this.SponsorGas) { - case ExecutionMode.EIP7702: - var wrappedCalls = new WrappedCalls() { Calls = calls, Uid = Guid.NewGuid().ToByteArray().PadTo32Bytes() }; - var signature = await EIP712.GenerateSignature_SmartAccount_7702_WrappedCalls("MinimalAccount", "1", this.ChainId, userWalletAddress, wrappedCalls, this.UserWallet); - var response = await BundlerClient.TwExecute( - client: this.Client, - // url: $"{this.ChainId}.bundler.thirdweb.com", - url: "http://localhost:8787?chain=11155111", - requestId: 7702, - eoaAddress: userWalletAddress, - wrappedCalls: wrappedCalls, - signature: signature, - authorization: this.Authorization != null && !await Utils.IsDelegatedAccount(this.Client, this.ChainId, userWalletAddress) ? this.Authorization : null - ); - var queueId = response?.QueueId; - string txHash = null; - var ct = new CancellationTokenSource(this.Client.FetchTimeoutOptions.GetTimeout(TimeoutType.Other)); - try - { - while (txHash == null) - { - ct.Token.ThrowIfCancellationRequested(); - - var hashResponse = await BundlerClient - .TwGetTransactionHash( - client: this.Client, - // url: $"{this.ChainId}.bundler.thirdweb.com", - url: "http://localhost:8787?chain=11155111", - requestId: 7702, - queueId - ) - .ConfigureAwait(false); - - txHash = hashResponse?.TransactionHash; - await ThirdwebTask.Delay(100, ct.Token).ConfigureAwait(false); - } - return txHash; - } - catch (OperationCanceledException) - { - throw new Exception($"EIP-7702 sponsored transaction timed out with queue id: {queueId}"); - } - case ExecutionMode.EOA: - // Add up values of all calls - BigInteger totalValue = 0; - foreach (var call in calls) + var wrappedCalls = new WrappedCalls() { Calls = calls, Uid = Guid.NewGuid().ToByteArray().PadTo32Bytes() }; + var signature = await EIP712.GenerateSignature_SmartAccount_7702_WrappedCalls("MinimalAccount", "1", this.ChainId, userWalletAddress, wrappedCalls, this.UserWallet); + var response = await BundlerClient.TwExecute( + client: this.Client, + // url: $"{this.ChainId}.bundler.thirdweb.com", + url: "http://localhost:8787?chain=11155111", + requestId: 7702, + eoaAddress: userWalletAddress, + wrappedCalls: wrappedCalls, + signature: signature, + authorization: this.Authorization != null && !await Utils.IsDelegatedAccount(this.Client, this.ChainId, userWalletAddress) ? this.Authorization : null + ); + var queueId = response?.QueueId; + string txHash = null; + var ct = new CancellationTokenSource(this.Client.FetchTimeoutOptions.GetTimeout(TimeoutType.Other)); + try + { + while (txHash == null) { - totalValue += call.Value; + ct.Token.ThrowIfCancellationRequested(); + + var hashResponse = await BundlerClient + .TwGetTransactionHash( + client: this.Client, + // url: $"{this.ChainId}.bundler.thirdweb.com", + url: "http://localhost:8787?chain=11155111", + requestId: 7702, + queueId + ) + .ConfigureAwait(false); + + txHash = hashResponse?.TransactionHash; + await ThirdwebTask.Delay(100, ct.Token).ConfigureAwait(false); } - // Prepare a tx using the user wallet as the executor - var finalTx = await this.UserContract.Prepare(wallet: this.UserWallet, method: "execute", weiValue: totalValue, parameters: new object[] { calls }); - finalTx.Input.AuthorizationList = this.Authorization != null ? new List() { this.Authorization.Value } : null; - - // Append authorization if not delegated yet - - // Send the transaction and return the - return await ThirdwebTransaction.Send(finalTx); - default: - throw new NotImplementedException($"Execution mode {this.ExecutionMode} is not supported."); + return txHash; + } + catch (OperationCanceledException) + { + throw new Exception($"EIP-7702 sponsored transaction timed out with queue id: {queueId}"); + } + } + else + { + BigInteger totalValue = 0; + foreach (var call in calls) + { + totalValue += call.Value; + } + var finalTx = await this.UserContract.Prepare(wallet: this.UserWallet, method: "execute", weiValue: totalValue, parameters: new object[] { calls }); + finalTx.Input.AuthorizationList = this.Authorization != null ? new List() { this.Authorization.Value } : null; + return await ThirdwebTransaction.Send(finalTx); } } From a728961a14b6fc1cf3ebdaefe2f19d4412b298dd Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 8 May 2025 05:25:12 +0700 Subject: [PATCH 12/15] working loop --- Thirdweb.Console/Program.cs | 23 +++++++++++++++---- Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs | 2 -- .../BundlerClient.cs | 6 ++--- Thirdweb/Thirdweb.Wallets/SmarterWallet.cs | 14 +++-------- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index 945fc26..637079d 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -8,6 +8,7 @@ using Nethereum.ABI; using Nethereum.Hex.HexConvertors.Extensions; using Nethereum.Hex.HexTypes; +using Nethereum.Util; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Thirdweb; @@ -329,7 +330,7 @@ var chain = 11155111; // sepolia // Connect to EOA -var userWallet = await InAppWallet.Create(client, email: "firekeeper+7702testing04@thirdweb.com"); +var userWallet = await InAppWallet.Create(client, email: "firekeeper+7702testing07@thirdweb.com"); if (!await userWallet.IsConnected()) { await userWallet.SendOTP(); @@ -339,11 +340,11 @@ } Console.WriteLine($"User Wallet address: {await userWallet.GetAddress()}"); -Console.WriteLine("Send it some gas if testing with ExecutionMode.EOA"); -Console.ReadLine(); +// Console.WriteLine("Send it some gas if testing with ExecutionMode.EOA"); +// Console.ReadLine(); // Upgrade EOA - This wallet explicitly uses EIP-7702 delegation to the thirdweb MinimalAccount (will delegate upon first tx) -var smarterWallet = await SmarterWallet.Create(client, chain, userWallet, ExecutionMode.EOA); +var smarterWallet = await SmarterWallet.Create(client: client, chainId: chain, userWallet: userWallet, sponsorGas: true); var smarterWalletAddress = await smarterWallet.GetAddress(); Console.WriteLine($"Thirdweb Wallet address: {smarterWalletAddress}"); // same as userWallet address, unlike when using EIP-4337 @@ -355,6 +356,20 @@ var isDelegated = await Utils.IsDelegatedAccount(client, chain, smarterWalletAddress); Console.WriteLine($"Is delegated: {isDelegated}"); +// Create a session key +var sessionKeyReceipt = await smarterWallet.CreateSessionKey( + new SessionSpec() + { + Signer = await Utils.GetAddressFromENS(client, "0xfirekeeper.eth"), + IsWildcard = true, + ExpiresAt = Utils.GetUnixTimeStampNow() + 86400, // 1 day + CallPolicies = new List(), + TransferPolicies = new List(), + Uid = Guid.NewGuid().ToByteArray().PadTo32Bytes() + } +); +Console.WriteLine($"Session key receipt: {sessionKeyReceipt.TransactionHash}"); + #endregion #region Smart Ecosystem Wallet diff --git a/Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs b/Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs index 93454a1..88884a7 100644 --- a/Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs +++ b/Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs @@ -168,7 +168,6 @@ private ThirdwebRPC(ThirdwebClient client, BigInteger chainId) private async Task SendBatchAsync(List batch) { var batchJson = JsonConvert.SerializeObject(batch); - Console.WriteLine($"Sending batch request: {batchJson}"); var content = new StringContent(batchJson, Encoding.UTF8, "application/json"); try @@ -183,7 +182,6 @@ private async Task SendBatchAsync(List batch) } var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - Console.WriteLine(responseContent); if (responseContent.Equals("Unauthorized", StringComparison.OrdinalIgnoreCase)) { throw new HttpRequestException("Unauthorized"); diff --git a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/BundlerClient.cs b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/BundlerClient.cs index cb2fe58..eb58d9f 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/BundlerClient.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/BundlerClient.cs @@ -30,10 +30,10 @@ public static async Task TwExecute( { chainId = authorization?.ChainId.HexToNumber(), address = authorization?.Address, - nonce = authorization?.Nonce.HexToNumber(), + nonce = authorization?.Nonce.HexToNumber().ToString(), yParity = authorization?.YParity.HexToNumber(), - r = authorization?.R, - s = authorization?.S + r = authorization?.R.HexToNumber().ToString(), + s = authorization?.S.HexToNumber().ToString() } ) .ConfigureAwait(false); diff --git a/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs b/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs index 8d3f52f..8e0cb45 100644 --- a/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs @@ -50,8 +50,7 @@ public async Task CreateSessionKey(SessionSpec sessi var userWalletAddress = await this.UserWallet.GetAddress(); var sessionKeySig = await EIP712.GenerateSignature_SmartAccount_7702("MinimalAccount", "1", this.ChainId, userWalletAddress, sessionKeyParams, this.UserWallet); var sessionKeyCallData = this.UserContract.CreateCallData("createSessionWithSig", sessionKeyParams, sessionKeySig.HexToBytes()); - var sessionKeyTx = await ThirdwebTransaction.Create(this, new ThirdwebTransactionInput(chainId: this.ChainId, to: userWalletAddress, data: sessionKeyCallData)); - return await ThirdwebTransaction.SendAndWaitForTransactionReceipt(sessionKeyTx); + return await this.ExecuteTransaction(new ThirdwebTransactionInput(chainId: this.ChainId, to: userWalletAddress, value: 0, data: sessionKeyCallData)); } #endregion @@ -145,8 +144,7 @@ public async Task SendTransaction(ThirdwebTransactionInput transaction) var signature = await EIP712.GenerateSignature_SmartAccount_7702_WrappedCalls("MinimalAccount", "1", this.ChainId, userWalletAddress, wrappedCalls, this.UserWallet); var response = await BundlerClient.TwExecute( client: this.Client, - // url: $"{this.ChainId}.bundler.thirdweb.com", - url: "http://localhost:8787?chain=11155111", + url: $"https://{this.ChainId}.bundler.thirdweb.com", requestId: 7702, eoaAddress: userWalletAddress, wrappedCalls: wrappedCalls, @@ -163,13 +161,7 @@ public async Task SendTransaction(ThirdwebTransactionInput transaction) ct.Token.ThrowIfCancellationRequested(); var hashResponse = await BundlerClient - .TwGetTransactionHash( - client: this.Client, - // url: $"{this.ChainId}.bundler.thirdweb.com", - url: "http://localhost:8787?chain=11155111", - requestId: 7702, - queueId - ) + .TwGetTransactionHash(client: this.Client, url: $"https://{this.ChainId}.bundler.thirdweb.com", requestId: 7702, queueId) .ConfigureAwait(false); txHash = hashResponse?.TransactionHash; From 3431c66492729e64e6673c672133b1c128b33553 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 8 May 2025 06:13:09 +0700 Subject: [PATCH 13/15] Move all functionality to IAW level --- Thirdweb.Console/Program.cs | 82 +++--- .../EcosystemWallet/EcosystemWallet.cs | 147 ++++++++++- .../InAppWallet/InAppWallet.cs | 14 +- Thirdweb/Thirdweb.Wallets/SmarterWallet.cs | 239 ------------------ 4 files changed, 188 insertions(+), 294 deletions(-) delete mode 100644 Thirdweb/Thirdweb.Wallets/SmarterWallet.cs diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index 637079d..56828a5 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -327,48 +327,48 @@ #region EIP-7702 -var chain = 11155111; // sepolia - -// Connect to EOA -var userWallet = await InAppWallet.Create(client, email: "firekeeper+7702testing07@thirdweb.com"); -if (!await userWallet.IsConnected()) -{ - await userWallet.SendOTP(); - Console.WriteLine("Enter OTP:"); - var otp = Console.ReadLine(); - _ = await userWallet.LoginWithOtp(otp: otp); -} -Console.WriteLine($"User Wallet address: {await userWallet.GetAddress()}"); - -// Console.WriteLine("Send it some gas if testing with ExecutionMode.EOA"); -// Console.ReadLine(); +// var chain = 11155111; // sepolia -// Upgrade EOA - This wallet explicitly uses EIP-7702 delegation to the thirdweb MinimalAccount (will delegate upon first tx) -var smarterWallet = await SmarterWallet.Create(client: client, chainId: chain, userWallet: userWallet, sponsorGas: true); -var smarterWalletAddress = await smarterWallet.GetAddress(); -Console.WriteLine($"Thirdweb Wallet address: {smarterWalletAddress}"); // same as userWallet address, unlike when using EIP-4337 - -// Transact, will upgrade EOA -var receipt = await smarterWallet.Transfer(chainId: chain, toAddress: await Utils.GetAddressFromENS(client, "vitalik.eth"), weiAmount: 0); -Console.WriteLine($"Transfer Receipt: {receipt.TransactionHash}"); - -// Double check that it was upgraded -var isDelegated = await Utils.IsDelegatedAccount(client, chain, smarterWalletAddress); -Console.WriteLine($"Is delegated: {isDelegated}"); - -// Create a session key -var sessionKeyReceipt = await smarterWallet.CreateSessionKey( - new SessionSpec() - { - Signer = await Utils.GetAddressFromENS(client, "0xfirekeeper.eth"), - IsWildcard = true, - ExpiresAt = Utils.GetUnixTimeStampNow() + 86400, // 1 day - CallPolicies = new List(), - TransferPolicies = new List(), - Uid = Guid.NewGuid().ToByteArray().PadTo32Bytes() - } -); -Console.WriteLine($"Session key receipt: {sessionKeyReceipt.TransactionHash}"); +// // Connect to EOA +// var smartEoa = await InAppWallet.Create(client, authProvider: AuthProvider.Google, executionMode: ExecutionMode.EOA); +// if (!await smartEoa.IsConnected()) +// { +// _ = await smartEoa.LoginWithOauth( +// isMobile: false, +// (url) => +// { +// var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true }; +// _ = Process.Start(psi); +// } +// ); +// } +// var smartEoaAddress = await smartEoa.GetAddress(); +// Console.WriteLine($"User Wallet address: {await smartEoa.GetAddress()}"); + +// // Upgrade EOA - This wallet explicitly uses EIP-7702 delegation to the thirdweb MinimalAccount (will delegate upon first tx) + +// // Transact, will upgrade EOA +// var receipt = await smartEoa.Transfer(chainId: chain, toAddress: await Utils.GetAddressFromENS(client, "vitalik.eth"), weiAmount: 0); +// Console.WriteLine($"Transfer Receipt: {receipt.TransactionHash}"); + +// // Double check that it was upgraded +// var isDelegated = await Utils.IsDelegatedAccount(client, chain, smartEoaAddress); +// Console.WriteLine($"Is delegated: {isDelegated}"); + +// // Create a session key +// var sessionKeyReceipt = await smartEoa.CreateSessionKey( +// chain, +// new SessionSpec() +// { +// Signer = await Utils.GetAddressFromENS(client, "0xfirekeeper.eth"), +// IsWildcard = true, +// ExpiresAt = Utils.GetUnixTimeStampNow() + 86400, // 1 day +// CallPolicies = new List(), +// TransferPolicies = new List(), +// Uid = Guid.NewGuid().ToByteArray().PadTo32Bytes() +// } +// ); +// Console.WriteLine($"Session key receipt: {sessionKeyReceipt.TransactionHash}"); #endregion diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs index 819e422..555c313 100644 --- a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs @@ -4,19 +4,28 @@ using Nethereum.ABI.EIP712; using Nethereum.Signer; using Nethereum.Signer.EIP712; +using Nethereum.Util; using Newtonsoft.Json; using Newtonsoft.Json.Linq; +using Thirdweb.AccountAbstraction; using Thirdweb.EWS; namespace Thirdweb; +public enum ExecutionMode +{ + EOA, + EIP7702, + EIP7702Sponsored +} + /// /// Enclave based secure cross ecosystem wallet. /// public partial class EcosystemWallet : IThirdwebWallet { public ThirdwebClient Client { get; } - public ThirdwebAccountType AccountType => ThirdwebAccountType.PrivateKeyAccount; + public ThirdwebAccountType AccountType { get; } public virtual string WalletId => "ecosystem"; internal readonly EmbeddedWallet EmbeddedWallet; @@ -29,6 +38,7 @@ public partial class EcosystemWallet : IThirdwebWallet internal readonly string WalletSecret; internal string Address; + internal ExecutionMode ExecutionMode; private readonly string _ecosystemId; private readonly string _ecosystemPartnerId; @@ -49,7 +59,8 @@ internal EcosystemWallet( string authProvider, IThirdwebWallet siweSigner, string legacyEncryptionKey, - string walletSecret + string walletSecret, + ExecutionMode executionMode ) { this.Client = client; @@ -63,6 +74,9 @@ string walletSecret this.AuthProvider = authProvider; this.SiweSigner = siweSigner; this.WalletSecret = walletSecret; + this.ExecutionMode = executionMode; + this.AccountType = executionMode == ExecutionMode.EOA ? ThirdwebAccountType.PrivateKeyAccount : ThirdwebAccountType.ExternalAccount; + ; } #region Creation @@ -81,6 +95,7 @@ string walletSecret /// The encryption key that is no longer required but was used in the past. Only pass this if you had used custom auth before this was deprecated. /// The wallet secret for Backend authentication. /// The auth token to use for the session. This will automatically connect using a raw thirdweb auth token. + /// The execution mode for the wallet. EOA represents traditional direct calls, EIP7702 represents upgraded account self sponsored calls, and EIP7702Sponsored represents upgraded account calls with managed/sponsored execution. /// A task that represents the asynchronous operation. The task result contains the created in-app wallet. /// Thrown when required parameters are not provided. public static async Task Create( @@ -94,7 +109,8 @@ public static async Task Create( IThirdwebWallet siweSigner = null, string legacyEncryptionKey = null, string walletSecret = null, - string twAuthTokenOverride = null + string twAuthTokenOverride = null, + ExecutionMode executionMode = ExecutionMode.EOA ) { if (client == null) @@ -164,7 +180,20 @@ public static async Task Create( try { var userAddress = await ResumeEnclaveSession(enclaveHttpClient, embeddedWallet, email, phoneNumber, authproviderStr).ConfigureAwait(false); - return new EcosystemWallet(ecosystemId, ecosystemPartnerId, client, embeddedWallet, enclaveHttpClient, email, phoneNumber, authproviderStr, siweSigner, legacyEncryptionKey, walletSecret) + return new EcosystemWallet( + ecosystemId, + ecosystemPartnerId, + client, + embeddedWallet, + enclaveHttpClient, + email, + phoneNumber, + authproviderStr, + siweSigner, + legacyEncryptionKey, + walletSecret, + executionMode + ) { Address = userAddress }; @@ -172,7 +201,20 @@ public static async Task Create( catch { enclaveHttpClient.RemoveHeader("Authorization"); - return new EcosystemWallet(ecosystemId, ecosystemPartnerId, client, embeddedWallet, enclaveHttpClient, email, phoneNumber, authproviderStr, siweSigner, legacyEncryptionKey, walletSecret) + return new EcosystemWallet( + ecosystemId, + ecosystemPartnerId, + client, + embeddedWallet, + enclaveHttpClient, + email, + phoneNumber, + authproviderStr, + siweSigner, + legacyEncryptionKey, + walletSecret, + executionMode + ) { Address = null }; @@ -408,6 +450,21 @@ public string GenerateExternalLoginLink(string redirectUrl) return $"{redirectUrl}{queryString}"; } + public Task CreateSessionKey(BigInteger chainId, SessionSpec sessionKeyParams) + { + throw new NotImplementedException("CreateSessionKey via EIP7702 execution modes is not implemented yet, check back in later versions."); + // if (this.ExecutionMode is not ExecutionMode.EIP7702 and not ExecutionMode.EIP7702Sponsored) + // { + // throw new InvalidOperationException("CreateSessionKey is only supported for EIP7702 and EIP7702Sponsored execution modes."); + // } + + // var userWalletAddress = await this.GetAddress(); + // var sessionKeySig = await EIP712.GenerateSignature_SmartAccount_7702("MinimalAccount", "1", chainId, userWalletAddress, sessionKeyParams, this); + // var userContract = await ThirdwebContract.Create(this.Client, userWalletAddress, chainId, Constants.MINIMAL_ACCOUNT_7702_ABI); + // var sessionKeyCallData = userContract.CreateCallData("createSessionWithSig", sessionKeyParams, sessionKeySig.HexToBytes()); + // return await this.ExecuteTransaction(new ThirdwebTransactionInput(chainId: chainId, to: userWalletAddress, value: 0, data: sessionKeyCallData)); + } + #endregion #region Account Linking @@ -1084,14 +1141,86 @@ public Task IsConnected() return Task.FromResult(this.Address != null); } - public Task SendTransaction(ThirdwebTransactionInput transaction) + public async Task SendTransaction(ThirdwebTransactionInput transaction) { - throw new InvalidOperationException("SendTransaction is not supported for Ecosystem Wallets, please use the unified Contract or ThirdwebTransaction APIs."); + var userWalletAddress = await this.GetAddress(); + var userContract = await ThirdwebContract.Create(this.Client, userWalletAddress, transaction.ChainId, Constants.MINIMAL_ACCOUNT_7702_ABI); + var needsDelegation = !await Utils.IsDelegatedAccount(this.Client, transaction.ChainId, userWalletAddress); + EIP7702Authorization? authorization = needsDelegation + ? await this.SignAuthorization(transaction.ChainId, Constants.MINIMAL_ACCOUNT_7702, willSelfExecute: this.ExecutionMode != ExecutionMode.EIP7702Sponsored) + : null; + + var calls = new List + { + new() + { + Target = transaction.To, + Value = transaction.Value?.Value ?? BigInteger.Zero, + Data = transaction.Data.HexToBytes() + } + }; + + switch (this.ExecutionMode) + { + case ExecutionMode.EOA: + throw new NotImplementedException( + "SendTransaction is not supported for Ecosystem Wallets in EOA execution mode, please use the unified Contract or ThirdwebTransaction APIs or change to EIP7702 execution mode." + ); + case ExecutionMode.EIP7702: + BigInteger totalValue = 0; + foreach (var call in calls) + { + totalValue += call.Value; + } + var finalTx = await userContract.Prepare(wallet: this, method: "execute", weiValue: totalValue, parameters: new object[] { calls }); + finalTx.Input.AuthorizationList = authorization != null ? new List() { authorization.Value } : null; + finalTx = await ThirdwebTransaction.Prepare(finalTx); + var signedTx = await this.SignTransaction(finalTx.Input); + var rpc = ThirdwebRPC.GetRpcInstance(this.Client, transaction.ChainId); + return await rpc.SendRequestAsync("eth_sendRawTransaction", signedTx).ConfigureAwait(false); + case ExecutionMode.EIP7702Sponsored: + var wrappedCalls = new WrappedCalls() { Calls = calls, Uid = Guid.NewGuid().ToByteArray().PadTo32Bytes() }; + var signature = await EIP712.GenerateSignature_SmartAccount_7702_WrappedCalls("MinimalAccount", "1", transaction.ChainId, userWalletAddress, wrappedCalls, this); + var response = await BundlerClient.TwExecute( + client: this.Client, + url: $"https://{transaction.ChainId}.bundler.thirdweb.com", + requestId: 7702, + eoaAddress: userWalletAddress, + wrappedCalls: wrappedCalls, + signature: signature, + authorization: authorization != null && !await Utils.IsDelegatedAccount(this.Client, transaction.ChainId, userWalletAddress) ? authorization : null + ); + var queueId = response?.QueueId; + string txHash = null; + var ct = new CancellationTokenSource(this.Client.FetchTimeoutOptions.GetTimeout(TimeoutType.Other)); + try + { + while (txHash == null) + { + ct.Token.ThrowIfCancellationRequested(); + + var hashResponse = await BundlerClient + .TwGetTransactionHash(client: this.Client, url: $"https://{transaction.ChainId}.bundler.thirdweb.com", requestId: 7702, queueId) + .ConfigureAwait(false); + + txHash = hashResponse?.TransactionHash; + await ThirdwebTask.Delay(100, ct.Token).ConfigureAwait(false); + } + return txHash; + } + catch (OperationCanceledException) + { + throw new Exception($"EIP-7702 sponsored transaction timed out with queue id: {queueId}"); + } + default: + throw new ArgumentOutOfRangeException(nameof(this.ExecutionMode), "Invalid execution mode."); + } } - public Task ExecuteTransaction(ThirdwebTransactionInput transactionInput) + public async Task ExecuteTransaction(ThirdwebTransactionInput transactionInput) { - throw new InvalidOperationException("ExecuteTransaction is not supported for Ecosystem Wallets, please use the unified Contract or ThirdwebTransaction APIs."); + var hash = await this.SendTransaction(transactionInput); + return await Utils.WaitForTransactionReceipt(this.Client, transactionInput.ChainId, hash); } public async Task Disconnect() diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.cs index 5cf9034..fc75c81 100644 --- a/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.cs @@ -19,9 +19,10 @@ internal InAppWallet( IThirdwebWallet siweSigner, string address, string legacyEncryptionKey, - string walletSecret + string walletSecret, + ExecutionMode executionMode ) - : base(null, null, client, embeddedWallet, httpClient, email, phoneNumber, authProvider, siweSigner, legacyEncryptionKey, walletSecret) + : base(null, null, client, embeddedWallet, httpClient, email, phoneNumber, authProvider, siweSigner, legacyEncryptionKey, walletSecret, executionMode) { this.Address = address; } @@ -38,6 +39,7 @@ string walletSecret /// The encryption key that is no longer required but was used in the past. Only pass this if you had used custom auth before this was deprecated. /// The wallet secret for backend authentication. /// The auth token to use for the session. This will automatically connect using a raw thirdweb auth token. + /// The execution mode for the wallet. EOA represents traditional direct calls, EIP7702 represents upgraded account self sponsored calls, and EIP7702Sponsored represents upgraded account calls with managed/sponsored execution. /// A task that represents the asynchronous operation. The task result contains the created in-app wallet. /// Thrown when required parameters are not provided. public static async Task Create( @@ -49,11 +51,12 @@ public static async Task Create( IThirdwebWallet siweSigner = null, string legacyEncryptionKey = null, string walletSecret = null, - string twAuthTokenOverride = null + string twAuthTokenOverride = null, + ExecutionMode executionMode = ExecutionMode.EOA ) { storageDirectoryPath ??= Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Thirdweb", "InAppWallet"); - var ecoWallet = await Create(client, null, null, email, phoneNumber, authProvider, storageDirectoryPath, siweSigner, legacyEncryptionKey, walletSecret, twAuthTokenOverride); + var ecoWallet = await Create(client, null, null, email, phoneNumber, authProvider, storageDirectoryPath, siweSigner, legacyEncryptionKey, walletSecret, twAuthTokenOverride, executionMode); return new InAppWallet( ecoWallet.Client, ecoWallet.EmbeddedWallet, @@ -64,7 +67,8 @@ public static async Task Create( ecoWallet.SiweSigner, ecoWallet.Address, ecoWallet.LegacyEncryptionKey, - ecoWallet.WalletSecret + ecoWallet.WalletSecret, + ecoWallet.ExecutionMode ); } } diff --git a/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs b/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs deleted file mode 100644 index 8e0cb45..0000000 --- a/Thirdweb/Thirdweb.Wallets/SmarterWallet.cs +++ /dev/null @@ -1,239 +0,0 @@ -using System.Numerics; -using Nethereum.ABI.EIP712; -using Nethereum.Util; -using Thirdweb.AccountAbstraction; - -namespace Thirdweb; - -/// -/// Represents a 7702 delegated wallet with granular session key permissions and automatic session key execution. -/// -public class SmarterWallet : IThirdwebWallet -{ - public string WalletId => "smarter"; - - public ThirdwebClient Client { get; } - public ThirdwebAccountType AccountType => ThirdwebAccountType.ExternalAccount; - - internal IThirdwebWallet UserWallet { get; } - internal ThirdwebContract UserContract { get; } - internal BigInteger ChainId { get; } - internal bool SponsorGas { get; } - - private EIP7702Authorization? Authorization { get; set; } - - internal SmarterWallet(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, ThirdwebContract userContract, EIP7702Authorization? authorization, bool sponsorGas) - { - this.Client = client; - this.ChainId = chainId; - this.UserWallet = userWallet; - this.UserContract = userContract; - this.Authorization = authorization; - this.SponsorGas = sponsorGas; - } - - public static async Task Create(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, bool sponsorGas = true) - { - var userWalletAddress = await userWallet.GetAddress(); - var userContract = await ThirdwebContract.Create(client, userWalletAddress, chainId, Constants.MINIMAL_ACCOUNT_7702_ABI); - var needsDelegation = !await Utils.IsDelegatedAccount(client, chainId, userWalletAddress); - EIP7702Authorization? authorization = needsDelegation ? await userWallet.SignAuthorization(chainId, Constants.MINIMAL_ACCOUNT_7702, willSelfExecute: !sponsorGas) : null; - var wallet = new SmarterWallet(client, chainId, userWallet, userContract, authorization, sponsorGas); - Utils.TrackConnection(wallet); - return wallet; - } - - #region Wallet Specific - - public async Task CreateSessionKey(SessionSpec sessionKeyParams) - { - var userWalletAddress = await this.UserWallet.GetAddress(); - var sessionKeySig = await EIP712.GenerateSignature_SmartAccount_7702("MinimalAccount", "1", this.ChainId, userWalletAddress, sessionKeyParams, this.UserWallet); - var sessionKeyCallData = this.UserContract.CreateCallData("createSessionWithSig", sessionKeyParams, sessionKeySig.HexToBytes()); - return await this.ExecuteTransaction(new ThirdwebTransactionInput(chainId: this.ChainId, to: userWalletAddress, value: 0, data: sessionKeyCallData)); - } - - #endregion - - #region IThirdwebWallet - - public Task GetAddress() - { - return this.UserWallet.GetAddress(); - } - - public Task EthSign(byte[] rawMessage) - { - return this.UserWallet.EthSign(rawMessage); - } - - public Task EthSign(string message) - { - return this.UserWallet.EthSign(message); - } - - public Task RecoverAddressFromEthSign(string message, string signature) - { - return this.UserWallet.RecoverAddressFromEthSign(message, signature); - } - - public Task PersonalSign(byte[] rawMessage) - { - return this.UserWallet.PersonalSign(rawMessage); - } - - public Task PersonalSign(string message) - { - return this.UserWallet.PersonalSign(message); - } - - public Task RecoverAddressFromPersonalSign(string message, string signature) - { - return this.UserWallet.RecoverAddressFromPersonalSign(message, signature); - } - - public Task SignTypedDataV4(string json) - { - return this.UserWallet.SignTypedDataV4(json); - } - - public Task SignTypedDataV4(T data, TypedData typedData) - where TDomain : IDomain - { - return this.UserWallet.SignTypedDataV4(data, typedData); - } - - public Task RecoverAddressFromTypedDataV4(T data, TypedData typedData, string signature) - where TDomain : IDomain - { - return this.UserWallet.RecoverAddressFromTypedDataV4(data, typedData, signature); - } - - public Task IsConnected() - { - return this.UserWallet.IsConnected(); - } - - public Task SignTransaction(ThirdwebTransactionInput transaction) - { - return this.UserWallet.SignTransaction(transaction); - } - - public async Task SendTransaction(ThirdwebTransactionInput transaction) - { - var userWalletAddress = await this.UserWallet.GetAddress(); - - if (this.Authorization != null && await Utils.IsDelegatedAccount(this.Client, this.ChainId, userWalletAddress)) - { - this.Authorization = null; - } - - var calls = new List - { - new() - { - Target = transaction.To, - Value = transaction.Value?.Value ?? BigInteger.Zero, - Data = transaction.Data.HexToBytes() - } - }; - - if (this.SponsorGas) - { - var wrappedCalls = new WrappedCalls() { Calls = calls, Uid = Guid.NewGuid().ToByteArray().PadTo32Bytes() }; - var signature = await EIP712.GenerateSignature_SmartAccount_7702_WrappedCalls("MinimalAccount", "1", this.ChainId, userWalletAddress, wrappedCalls, this.UserWallet); - var response = await BundlerClient.TwExecute( - client: this.Client, - url: $"https://{this.ChainId}.bundler.thirdweb.com", - requestId: 7702, - eoaAddress: userWalletAddress, - wrappedCalls: wrappedCalls, - signature: signature, - authorization: this.Authorization != null && !await Utils.IsDelegatedAccount(this.Client, this.ChainId, userWalletAddress) ? this.Authorization : null - ); - var queueId = response?.QueueId; - string txHash = null; - var ct = new CancellationTokenSource(this.Client.FetchTimeoutOptions.GetTimeout(TimeoutType.Other)); - try - { - while (txHash == null) - { - ct.Token.ThrowIfCancellationRequested(); - - var hashResponse = await BundlerClient - .TwGetTransactionHash(client: this.Client, url: $"https://{this.ChainId}.bundler.thirdweb.com", requestId: 7702, queueId) - .ConfigureAwait(false); - - txHash = hashResponse?.TransactionHash; - await ThirdwebTask.Delay(100, ct.Token).ConfigureAwait(false); - } - return txHash; - } - catch (OperationCanceledException) - { - throw new Exception($"EIP-7702 sponsored transaction timed out with queue id: {queueId}"); - } - } - else - { - BigInteger totalValue = 0; - foreach (var call in calls) - { - totalValue += call.Value; - } - var finalTx = await this.UserContract.Prepare(wallet: this.UserWallet, method: "execute", weiValue: totalValue, parameters: new object[] { calls }); - finalTx.Input.AuthorizationList = this.Authorization != null ? new List() { this.Authorization.Value } : null; - return await ThirdwebTransaction.Send(finalTx); - } - } - - public async Task ExecuteTransaction(ThirdwebTransactionInput transaction) - { - var hash = await this.SendTransaction(transaction); - return await Utils.WaitForTransactionReceipt(this.Client, transaction.ChainId, hash); - } - - public Task Disconnect() - { - return this.UserWallet.Disconnect(); - } - - public Task> LinkAccount( - IThirdwebWallet walletToLink, - string otp = null, - bool? isMobile = null, - Action browserOpenAction = null, - string mobileRedirectScheme = "thirdweb://", - IThirdwebBrowser browser = null, - BigInteger? chainId = null, - string jwt = null, - string payload = null, - string defaultSessionIdOverride = null, - List forceWalletIds = null - ) - { - return this.UserWallet.LinkAccount(walletToLink, otp, isMobile, browserOpenAction, mobileRedirectScheme, browser, chainId, jwt, payload, defaultSessionIdOverride, forceWalletIds); - } - - public Task> UnlinkAccount(LinkedAccount accountToUnlink) - { - return this.UserWallet.UnlinkAccount(accountToUnlink); - } - - public Task> GetLinkedAccounts() - { - return this.UserWallet.GetLinkedAccounts(); - } - - public Task SignAuthorization(BigInteger chainId, string contractAddress, bool willSelfExecute) - { - return this.UserWallet.SignAuthorization(chainId, contractAddress, willSelfExecute); - } - - public Task SwitchNetwork(BigInteger chainId) - { - return this.UserWallet.SwitchNetwork(chainId); - } - - #endregion -} From 7af8b123f33c15fe894a882fb35c3ffcadf1e444 Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 8 May 2025 06:14:06 +0700 Subject: [PATCH 14/15] flaky test --- .../Thirdweb.AI/Thirdweb.AI.Tests.cs | 56 ++++++++----------- 1 file changed, 23 insertions(+), 33 deletions(-) diff --git a/Thirdweb.Tests/Thirdweb.AI/Thirdweb.AI.Tests.cs b/Thirdweb.Tests/Thirdweb.AI/Thirdweb.AI.Tests.cs index 44adcb6..28f03e8 100644 --- a/Thirdweb.Tests/Thirdweb.AI/Thirdweb.AI.Tests.cs +++ b/Thirdweb.Tests/Thirdweb.AI/Thirdweb.AI.Tests.cs @@ -6,7 +6,7 @@ namespace Thirdweb.Tests.AI; public class NebulaTests : BaseTests { - private const string NEBULA_TEST_USDC_ADDRESS = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"; + // private const string NEBULA_TEST_USDC_ADDRESS = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238"; private const string NEBULA_TEST_CONTRACT = "0xe2cb0eb5147b42095c2FfA6F7ec953bb0bE347D8"; @@ -46,16 +46,6 @@ public async Task Chat_Single_ReturnsResponse() Assert.Contains("CAT", response.Message); } - [Fact(Timeout = 120000)] - public async Task Chat_Single_NoContext_ReturnsResponse() - { - var nebula = await ThirdwebNebula.Create(this.Client); - var response = await nebula.Chat(message: $"What's the symbol of this contract: {NEBULA_TEST_CONTRACT} (Sepolia)?"); - Assert.NotNull(response); - Assert.NotNull(response.Message); - Assert.Contains("CAT", response.Message); - } - [Fact(Timeout = 120000)] public async Task Chat_UnderstandsWalletContext() { @@ -68,26 +58,26 @@ public async Task Chat_UnderstandsWalletContext() Assert.Contains(expectedAddress, response.Message); } - [Fact(Timeout = 120000)] - public async Task Execute_ReturnsMessageAndReceipt() - { - var signer = await PrivateKeyWallet.Generate(this.Client); - var wallet = await SmartWallet.Create(signer, NEBULA_TEST_CHAIN); - var nebula = await ThirdwebNebula.Create(this.Client); - var response = await nebula.Execute( - new List - { - new("What's the address of vitalik.eth", NebulaChatRole.User), - new("The address of vitalik.eth is 0xd8dA6BF26964aF8E437eEa5e3616511D7G3a3298", NebulaChatRole.Assistant), - new($"Approve 1 USDC (this contract: {NEBULA_TEST_USDC_ADDRESS}) to them", NebulaChatRole.User), - }, - wallet: wallet - ); - Assert.NotNull(response); - Assert.NotNull(response.Message); - Assert.NotNull(response.TransactionReceipts); - Assert.NotEmpty(response.TransactionReceipts); - Assert.NotNull(response.TransactionReceipts[0].TransactionHash); - Assert.True(response.TransactionReceipts[0].TransactionHash.Length == 66); - } + // [Fact(Timeout = 120000)] + // public async Task Execute_ReturnsMessageAndReceipt() + // { + // var signer = await PrivateKeyWallet.Generate(this.Client); + // var wallet = await SmartWallet.Create(signer, NEBULA_TEST_CHAIN); + // var nebula = await ThirdwebNebula.Create(this.Client); + // var response = await nebula.Execute( + // new List + // { + // new("What's the address of vitalik.eth", NebulaChatRole.User), + // new("The address of vitalik.eth is 0xd8dA6BF26964aF8E437eEa5e3616511D7G3a3298", NebulaChatRole.Assistant), + // new($"Approve 1 USDC (this contract: {NEBULA_TEST_USDC_ADDRESS}) to them", NebulaChatRole.User), + // }, + // wallet: wallet + // ); + // Assert.NotNull(response); + // Assert.NotNull(response.Message); + // Assert.NotNull(response.TransactionReceipts); + // Assert.NotEmpty(response.TransactionReceipts); + // Assert.NotNull(response.TransactionReceipts[0].TransactionHash); + // Assert.True(response.TransactionReceipts[0].TransactionHash.Length == 66); + // } } From 462e7180ae450f9b1b00145b12f41a05ddde4cbb Mon Sep 17 00:00:00 2001 From: 0xFirekeeper <0xFirekeeper@gmail.com> Date: Thu, 8 May 2025 06:15:07 +0700 Subject: [PATCH 15/15] Update Program.cs --- Thirdweb.Console/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index 56828a5..50ff598 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -330,7 +330,7 @@ // var chain = 11155111; // sepolia // // Connect to EOA -// var smartEoa = await InAppWallet.Create(client, authProvider: AuthProvider.Google, executionMode: ExecutionMode.EOA); +// var smartEoa = await InAppWallet.Create(client, authProvider: AuthProvider.Google, executionMode: ExecutionMode.EIP7702Sponsored); // if (!await smartEoa.IsConnected()) // { // _ = await smartEoa.LoginWithOauth(