diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index 36c007f5..50ff5986 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; @@ -26,10 +27,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); -// 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}"); @@ -326,113 +327,48 @@ #region EIP-7702 -// // -------------------------------------------------------------------------- -// // Configuration -// // -------------------------------------------------------------------------- +// var chain = 11155111; // sepolia -// 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() +// // Connect to EOA +// var smartEoa = await InAppWallet.Create(client, authProvider: AuthProvider.Google, executionMode: ExecutionMode.EIP7702Sponsored); +// if (!await smartEoa.IsConnected()) // { -// 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); +// _ = 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()}"); -// // -------------------------------------------------------------------------- -// // Mint Tokens (DropERC20) to the EOA Using the backend session key -// // -------------------------------------------------------------------------- +// // Upgrade EOA - This wallet explicitly uses EIP-7702 delegation to the thirdweb MinimalAccount (will delegate upon first tx) -// var erc20ContractAddress = "0xAA462a5BE0fc5214507FDB4fB2474a7d5c69065b"; // DropERC20 -// var erc20Contract = await ThirdwebContract.Create(client, erc20ContractAddress, chainWith7702); +// // 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}"); -// // Log ERC20 balance before mint -// var eoaBalanceBefore = await erc20Contract.ERC20_BalanceOf(eoaWalletAddress); -// Console.WriteLine($"EOA balance before: {eoaBalanceBefore}"); +// // Double check that it was upgraded +// var isDelegated = await Utils.IsDelegatedAccount(client, chain, smartEoaAddress); +// Console.WriteLine($"Is delegated: {isDelegated}"); -// // Create execution call data (calling 'claim' on the DropERC20) -// var executeCallData = eoaContract.CreateCallData( -// "execute", -// new object[] +// // Create a session key +// var sessionKeyReceipt = await smartEoa.CreateSessionKey( +// chain, +// new SessionSpec() // { -// new List -// { -// 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 -// } -// } +// 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() // } // ); - -// var executeReceipt = await engineWallet.ExecuteTransaction(new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, data: executeCallData)); -// Console.WriteLine($"Execute receipt: {JsonConvert.SerializeObject(executeReceipt, Formatting.Indented)}"); - -// // Log ERC20 balance after mint -// var eoaBalanceAfter = await erc20Contract.ERC20_BalanceOf(eoaWalletAddress); -// Console.WriteLine($"EOA balance after: {eoaBalanceAfter}"); +// Console.WriteLine($"Session key receipt: {sessionKeyReceipt.TransactionHash}"); #endregion diff --git a/Thirdweb.Tests/Thirdweb.AI/Thirdweb.AI.Tests.cs b/Thirdweb.Tests/Thirdweb.AI/Thirdweb.AI.Tests.cs index 44adcb68..28f03e8e 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); + // } } diff --git a/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs b/Thirdweb/Thirdweb.Transactions/ThirdwebTransactionInput.cs index 7ef2571a..03ec1d3e 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.Utils/Constants.cs b/Thirdweb/Thirdweb.Utils/Constants.cs index ace91a24..68f400e1 100644 --- a/Thirdweb/Thirdweb.Utils/Constants.cs +++ b/Thirdweb/Thirdweb.Utils/Constants.cs @@ -5,20 +5,22 @@ 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 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"; @@ -27,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 = "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\"}]"; + + 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 9266999e..efd33154 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; + } + + public static async Task IsDelegatedAccount(ThirdwebClient client, BigInteger chainId, string address) + { + 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/EIP712.cs b/Thirdweb/Thirdweb.Wallets/EIP712.cs index da749f15..8588ecc9 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 00000000..c9520d58 --- /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/InAppWallet/EcosystemWallet/EcosystemWallet.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs index 819e4222..555c3135 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 5cf90340..fc75c814 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/PrivateKeyWallet/PrivateKeyWallet.cs b/Thirdweb/Thirdweb.Wallets/PrivateKeyWallet/PrivateKeyWallet.cs index ce3ae549..1529d492 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) @@ -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/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs index 4da64e62..28855533 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs @@ -240,6 +240,18 @@ public class EthGetUserOperationReceiptResponse public ThirdwebTransactionReceipt Receipt { get; set; } } +public class TwExecuteResponse +{ + [JsonProperty("queueId")] + public string QueueId { get; set; } +} + +public class TwGetTransactionHashResponse +{ + [JsonProperty("transactionHash")] + public string TransactionHash { get; set; } +} + public class EntryPointWrapper { [JsonProperty("entryPoint")] @@ -486,58 +498,153 @@ 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; } + public virtual string Signer { get; set; } - [Parameter("uint256", "nativeTokenLimitPerTransaction", 2)] - [JsonProperty("nativeTokenLimitPerTransaction")] - public BigInteger NativeTokenLimitPerTransaction { get; set; } + [Parameter("bool", "isWildcard", 2)] + [JsonProperty("isWildcard")] + public virtual bool IsWildcard { get; set; } - [Parameter("uint256", "startTimestamp", 3)] - [JsonProperty("startTimestamp")] - public BigInteger StartTimestamp { get; set; } + [Parameter("uint256", "expiresAt", 3)] + [JsonProperty("expiresAt")] + public virtual BigInteger ExpiresAt { get; set; } - [Parameter("uint256", "endTimestamp", 4)] - [JsonProperty("endTimestamp")] - public BigInteger EndTimestamp { get; set; } + [Parameter("tuple[]", "callPolicies", 4, structTypeName: "CallSpec[]")] + [JsonProperty("callPolicies")] + public virtual List CallPolicies { get; set; } - [Parameter("address[]", "approvedTargets", 5)] - [JsonProperty("approvedTargets")] - public List ApprovedTargets { get; set; } + [Parameter("tuple[]", "transferPolicies", 5, structTypeName: "TransferSpec[]")] + [JsonProperty("transferPolicies")] + public virtual List TransferPolicies { get; set; } [Parameter("bytes32", "uid", 6)] [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; } + + 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, "Call[]")] + [Parameter("tuple[]", "calls", 1, structTypeName: "Call[]")] [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; } + + 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 620d9250..eb58d9f5 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/BundlerClient.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/BundlerClient.cs @@ -5,6 +5,47 @@ 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().ToString(), + yParity = authorization?.YParity.HexToNumber(), + r = authorization?.R.HexToNumber().ToString(), + s = authorization?.S.HexToNumber().ToString() + } + ) + .ConfigureAwait(false); + 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)