diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs index 0875dcd..f292cbf 100644 --- a/Thirdweb.Console/Program.cs +++ b/Thirdweb.Console/Program.cs @@ -372,9 +372,10 @@ // 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) +// var signerAddress = await Utils.GetAddressFromENS(client, "vitalik.eth"); // // Transact, will upgrade EOA -// var receipt = await smartEoa.Transfer(chainId: chain, toAddress: await Utils.GetAddressFromENS(client, "vitalik.eth"), weiAmount: 0); +// var receipt = await smartEoa.Transfer(chainId: chain, toAddress: signerAddress, weiAmount: 0); // Console.WriteLine($"Transfer Receipt: {receipt.TransactionHash}"); // // Double check that it was upgraded @@ -382,9 +383,48 @@ // Console.WriteLine($"Is delegated: {isDelegated}"); // // Create a session key -// var sessionKeyReceipt = await smartEoa.CreateSessionKey(chainId: chain, signerAddress: await Utils.GetAddressFromENS(client, "vitalik.eth"), durationInSeconds: 86400, grantFullPermissions: true); +// var sessionKeyReceipt = await smartEoa.CreateSessionKey(chainId: chain, signerAddress: signerAddress, durationInSeconds: 86400, grantFullPermissions: true); // Console.WriteLine($"Session key receipt: {sessionKeyReceipt.TransactionHash}"); +// // Validate session key config +// var hasFullPermissions = await smartEoa.SignerHasFullPermissions(chain, signerAddress); +// Console.WriteLine($"Signer has full permissions: {hasFullPermissions}"); + +// var sessionExpiration = await smartEoa.GetSessionExpirationForSigner(chain, signerAddress); +// Console.WriteLine($"Session expires in {sessionExpiration - Utils.GetUnixTimeStampNow()} seconds"); + +// // Create a session key with granular permissions +// var granularSessionKeyReceipt = await smartEoa.CreateSessionKey( +// chainId: chain, +// signerAddress: signerAddress, +// durationInSeconds: 86400, +// grantFullPermissions: false, +// transferPolicies: new List +// { +// new() +// { +// Target = signerAddress, +// MaxValuePerUse = BigInteger.Parse("0.001".ToWei()), +// ValueLimit = new UsageLimit +// { +// LimitType = 1, // Lifetime +// Limit = BigInteger.Parse("0.01".ToWei()), +// Period = 86400, // 1 day +// } +// } +// } +// ); + +// // Validate session key config +// var sessionState = await smartEoa.GetSessionStateForSigner(chain, signerAddress); +// Console.WriteLine($"Session state: {JsonConvert.SerializeObject(sessionState, Formatting.Indented)}"); + +// var transferPolcies = await smartEoa.GetTransferPoliciesForSigner(chain, signerAddress); +// Console.WriteLine($"Transfer policies: {JsonConvert.SerializeObject(transferPolcies, Formatting.Indented)}"); + +// var callPolicies = await smartEoa.GetCallPoliciesForSigner(chain, signerAddress); +// Console.WriteLine($"Call policies: {JsonConvert.SerializeObject(callPolicies, Formatting.Indented)}"); + #endregion #region Smart Ecosystem Wallet diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs index 3388028..53c5ae5 100644 --- a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs +++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs @@ -450,6 +450,19 @@ public string GenerateExternalLoginLink(string redirectUrl) return $"{redirectUrl}{queryString}"; } + /// + /// Creates a session key for the user wallet. This is only supported for EIP7702 and EIP7702Sponsored execution modes. + /// + /// The chain ID for the session key. + /// The address of the signer for the session key. + /// Duration in seconds for which the session key will be valid. + /// Whether to grant full permissions to the session key. If false, only the specified call and transfer policies will be applied. + /// List of call policies to apply to the session key. If null, no call policies will be applied. + /// List of transfer policies to apply to the session key. If null, no transfer policies will be applied. + /// A unique identifier for the session key. If null, a new GUID will be generated. + /// A task that represents the asynchronous operation. The task result contains the transaction receipt for the session key creation. + /// Thrown when the execution mode is not EIP7702 or EIP7702Sponsored. + /// Thrown when the signer address is null or empty, or when the duration is less than or equal to zero. public async Task CreateSessionKey( BigInteger chainId, string signerAddress, @@ -460,10 +473,7 @@ public async Task CreateSessionKey( byte[] uid = null ) { - if (this.ExecutionMode is not ExecutionMode.EIP7702 and not ExecutionMode.EIP7702Sponsored) - { - throw new InvalidOperationException("CreateSessionKey is only supported for EIP7702 and EIP7702Sponsored execution modes."); - } + await this.Ensure7702(chainId, false); if (string.IsNullOrEmpty(signerAddress)) { @@ -492,6 +502,121 @@ public async Task CreateSessionKey( return await this.ExecuteTransaction(new ThirdwebTransactionInput(chainId: chainId, to: userWalletAddress, value: 0, data: sessionKeyCallData)); } + /// + /// Checks if the signer has full permissions on the EIP7702 account. + /// + /// The chain ID of the EIP7702 account. + /// The address of the signer to check permissions for. + /// A task that represents the asynchronous operation. The task result contains a boolean indicating whether the signer has full permissions. + /// Thrown when the execution mode is not EIP7702 or EIP7702Sponsored. + /// Thrown when the signer address is null or empty. + public async Task SignerHasFullPermissions(BigInteger chainId, string signerAddress) + { + await this.Ensure7702(chainId, true); + + if (string.IsNullOrEmpty(signerAddress)) + { + throw new ArgumentException("Signer address cannot be null or empty.", nameof(signerAddress)); + } + + var userWalletAddress = await this.GetAddress(); + var userContract = await ThirdwebContract.Create(this.Client, userWalletAddress, chainId, Constants.MINIMAL_ACCOUNT_7702_ABI); + var isWildcard = await userContract.Read("isWildcardSigner", signerAddress); + return isWildcard; + } + + /// + /// Gets the call policies for a specific signer on the EIP7702 account. + /// + /// The chain ID of the EIP7702 account. + /// The address of the signer to get call policies for. + /// A task that represents the asynchronous operation. The task result contains a list of call policies for the signer. + /// Thrown when the execution mode is not EIP7702 or EIP7702Sponsored. + /// Thrown when the signer address is null or empty. + public async Task> GetCallPoliciesForSigner(BigInteger chainId, string signerAddress) + { + await this.Ensure7702(chainId, true); + + if (string.IsNullOrEmpty(signerAddress)) + { + throw new ArgumentException("Signer address cannot be null or empty.", nameof(signerAddress)); + } + + var userWalletAddress = await this.GetAddress(); + var userContract = await ThirdwebContract.Create(this.Client, userWalletAddress, chainId, Constants.MINIMAL_ACCOUNT_7702_ABI); + var callPolicies = await userContract.Read>("getCallPoliciesForSigner", signerAddress); + return callPolicies; + } + + /// + /// Gets the transfer policies for a specific signer on the EIP7702 account. + /// + /// The chain ID of the EIP7702 account. + /// The address of the signer to get transfer policies for. + /// A task that represents the asynchronous operation. The task result contains a list of transfer policies for the signer. + /// Thrown when the execution mode is not EIP7702 or EIP7702Sponsored. + /// Thrown when the signer address is null or empty. + public async Task> GetTransferPoliciesForSigner(BigInteger chainId, string signerAddress) + { + await this.Ensure7702(chainId, true); + + if (string.IsNullOrEmpty(signerAddress)) + { + throw new ArgumentException("Signer address cannot be null or empty.", nameof(signerAddress)); + } + + var userWalletAddress = await this.GetAddress(); + var userContract = await ThirdwebContract.Create(this.Client, userWalletAddress, chainId, Constants.MINIMAL_ACCOUNT_7702_ABI); + var transferPolicies = await userContract.Read>("getTransferPoliciesForSigner", signerAddress); + return transferPolicies; + } + + /// + /// Gets the session expiration timestamp for a specific signer on the EIP7702 account. + /// + /// The chain ID of the EIP7702 account. + /// The address of the signer to get session expiration for. + /// A task that represents the asynchronous operation. The task result contains the session expiration timestamp. + /// Thrown when the execution mode is not EIP7702 or EIP7702Sponsored. + /// Thrown when the signer address is null or empty. + public async Task GetSessionExpirationForSigner(BigInteger chainId, string signerAddress) + { + await this.Ensure7702(chainId, true); + + if (string.IsNullOrEmpty(signerAddress)) + { + throw new ArgumentException("Signer address cannot be null or empty.", nameof(signerAddress)); + } + + var userWalletAddress = await this.GetAddress(); + var userContract = await ThirdwebContract.Create(this.Client, userWalletAddress, chainId, Constants.MINIMAL_ACCOUNT_7702_ABI); + var expirationTimestamp = await userContract.Read("getSessionExpirationForSigner", signerAddress); + return expirationTimestamp; + } + + /// + /// Gets the complete session state for a specific signer on the EIP7702 account, including remaining limits and usage information. + /// + /// The chain ID of the EIP7702 account. + /// The address of the signer to get session state for. + /// A task that represents the asynchronous operation. The task result contains the session state with transfer value limits, call value limits, and call parameter limits. + /// Thrown when the execution mode is not EIP7702 or EIP7702Sponsored. + /// Thrown when the signer address is null or empty. + public async Task GetSessionStateForSigner(BigInteger chainId, string signerAddress) + { + await this.Ensure7702(chainId, true); + + if (string.IsNullOrEmpty(signerAddress)) + { + throw new ArgumentException("Signer address cannot be null or empty.", nameof(signerAddress)); + } + + var userWalletAddress = await this.GetAddress(); + var userContract = await ThirdwebContract.Create(this.Client, userWalletAddress, chainId, Constants.MINIMAL_ACCOUNT_7702_ABI); + var sessionState = await userContract.Read("getSessionStateForSigner", signerAddress); + return sessionState; + } + #endregion #region Account Linking @@ -1343,4 +1468,20 @@ public Task SwitchNetwork(BigInteger chainId) } #endregion + + private async Task Ensure7702(BigInteger chainId, bool ensureDelegated) + { + if (this.ExecutionMode is not ExecutionMode.EIP7702 and not ExecutionMode.EIP7702Sponsored) + { + throw new InvalidOperationException("This operation is only supported for EIP7702 and EIP7702Sponsored execution modes."); + } + + if (!await Utils.IsDelegatedAccount(this.Client, chainId, this.Address).ConfigureAwait(false)) + { + if (ensureDelegated) + { + throw new InvalidOperationException("This operation requires a delegated account. Please ensure you have transacted at least once with the account to set up delegation."); + } + } + } } diff --git a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs index 2885553..748d4b2 100644 --- a/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs +++ b/Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs @@ -647,4 +647,40 @@ public object EncodeForHttp() } } +[Struct("LimitState")] +public class LimitState +{ + [Parameter("uint256", "remaining", 1)] + [JsonProperty("remaining")] + public virtual BigInteger Remaining { get; set; } + + [Parameter("address", "target", 2)] + [JsonProperty("target")] + public virtual string Target { get; set; } + + [Parameter("bytes4", "selector", 3)] + [JsonProperty("selector")] + public virtual byte[] Selector { get; set; } + + [Parameter("uint256", "index", 4)] + [JsonProperty("index")] + public virtual BigInteger Index { get; set; } +} + +[Struct("SessionState")] +public class SessionState +{ + [Parameter("tuple[]", "transferValue", 1, structTypeName: "LimitState[]")] + [JsonProperty("transferValue")] + public virtual List TransferValue { get; set; } + + [Parameter("tuple[]", "callValue", 2, structTypeName: "LimitState[]")] + [JsonProperty("callValue")] + public virtual List CallValue { get; set; } + + [Parameter("tuple[]", "callParams", 3, structTypeName: "LimitState[]")] + [JsonProperty("callParams")] + public virtual List CallParams { get; set; } +} + #endregion