Skip to content

Commit 5b490b6

Browse files
committed
latest implementation, unmanaged execution, managed wip
1 parent 1a5843c commit 5b490b6

File tree

5 files changed

+120
-91
lines changed

5 files changed

+120
-91
lines changed

Thirdweb.Console/Program.cs

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
var privateKey = Environment.GetEnvironmentVariable("PRIVATE_KEY");
2727

2828
// Fetch timeout options are optional, default is 120000ms
29-
var client = ThirdwebClient.Create(secretKey: secretKey, rpcOverrides: new Dictionary<BigInteger, string> { { 11155111, "https://eth-sepolia.public.blastapi.io" } });
29+
var client = ThirdwebClient.Create(secretKey: secretKey);
3030

3131
// Create a private key wallet
3232
var privateKeyWallet = await PrivateKeyWallet.Generate(client);
@@ -326,33 +326,35 @@
326326

327327
#region EIP-7702
328328

329-
// // The session key signer
330-
// var executorWallet = await PrivateKeyWallet.Create(client, privateKey); // needs to be funded, for now
329+
// var chain = 11155111; // sepolia
331330

332-
// // Session key permissions
333-
// var sessionKeyParams = new SessionSpec()
331+
// // Connect to EOA
332+
// var userWallet = await InAppWallet.Create(client, authProvider: AuthProvider.Github);
333+
// if (!await userWallet.IsConnected())
334334
// {
335-
// Signer = await executorWallet.GetAddress(),
336-
// ExpiresAt = Utils.GetUnixTimeStampNow() + (3600 * 24),
337-
// CallPolicies = new List<CallSpec>() { },
338-
// TransferPolicies = new List<TransferSpec>()
339-
// {
340-
// new()
335+
// _ = await userWallet.LoginWithOauth(
336+
// isMobile: false,
337+
// browserOpenAction: (url) =>
341338
// {
342-
// Target = await Utils.GetAddressFromENS(client, "vitalik.eth"),
343-
// MaxValuePerUse = BigInteger.Zero,
344-
// ValueLimit = new()
339+
// var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true };
340+
// _ = Process.Start(psi);
345341
// }
346-
// },
347-
// Uid = Guid.NewGuid().ToByteArray()
348-
// };
342+
// );
343+
// }
344+
// Console.WriteLine($"User Wallet address: {await userWallet.GetAddress()}");
349345

350-
// // This wallet explicitly uses 7702 delegation to the thirdweb MinimalAccount and creates a session key from which every tx will be executed
351-
// var thirdwebWallet = await ThirdwebWallet.Create(client, 11155111, privateKeyWallet, executorWallet, sessionKeyParams);
346+
// // Upgrade EOA - This wallet explicitly uses EIP-7702 delegation to the thirdweb MinimalAccount (will delegate upon first tx)
347+
// var thirdwebWallet = await ThirdwebWallet.Create(client, chain, userWallet, managedExecution: false);
348+
// var thirdwebWalletAddress = await thirdwebWallet.GetAddress();
349+
// Console.WriteLine($"Thirdweb Wallet address: {thirdwebWalletAddress}"); // same as userWallet address, unlike when using EIP-4337
352350

353-
// // Simple transfer, will use the session key automatically
354-
// var receipt = await thirdwebWallet.Transfer(11155111, await Utils.GetAddressFromENS(client, "vitalik.eth"), 0);
355-
// Console.WriteLine($"Receipt: {receipt}");
351+
// // Transact, will upgrade EOA
352+
// var receipt = await thirdwebWallet.Transfer(chainId: chain, toAddress: await Utils.GetAddressFromENS(client, "vitalik.eth"), weiAmount: 0);
353+
// Console.WriteLine($"Transfer Receipt: {receipt.TransactionHash}");
354+
355+
// // Double check that it was upgraded
356+
// var isDelegated = await Utils.IsDelegatedAccount(client, chain, thirdwebWalletAddress);
357+
// Console.WriteLine($"Is delegated: {isDelegated}");
356358

357359
#endregion
358360

Thirdweb/Thirdweb.Utils/Constants.cs

Lines changed: 17 additions & 14 deletions
Large diffs are not rendered by default.

Thirdweb/Thirdweb.Utils/Utils.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,8 +1328,10 @@ public static async Task<ThirdwebTransactionReceipt> WaitForTransactionReceipt(T
13281328
return receipt;
13291329
}
13301330

1331-
public static bool IsDelegatedAccount(string accountCode)
1331+
public static async Task<bool> IsDelegatedAccount(ThirdwebClient client, BigInteger chainId, string address)
13321332
{
1333-
return !accountCode.Equals($"0xef0100{Constants.MINIMAL_ACCOUNT_7702[2..]}", StringComparison.OrdinalIgnoreCase);
1333+
var rpc = ThirdwebRPC.GetRpcInstance(client, chainId);
1334+
var code = await rpc.SendRequestAsync<string>("eth_getCode", address, "latest");
1335+
return !code.Equals($"0xef0100{Constants.MINIMAL_ACCOUNT_7702[2..]}", StringComparison.OrdinalIgnoreCase);
13341336
}
13351337
}

Thirdweb/Thirdweb.Wallets/SmartWallet/Thirdweb.AccountAbstraction/AATypes.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -495,19 +495,23 @@ public class SessionSpec
495495
[JsonProperty("signer")]
496496
public virtual string Signer { get; set; }
497497

498-
[Parameter("uint256", "expiresAt", 2)]
498+
[Parameter("bool", "isWildcard", 2)]
499+
[JsonProperty("isWildcard")]
500+
public virtual bool IsWildcard { get; set; }
501+
502+
[Parameter("uint256", "expiresAt", 3)]
499503
[JsonProperty("expiresAt")]
500504
public virtual BigInteger ExpiresAt { get; set; }
501505

502-
[Parameter("tuple[]", "callPolicies", 3, structTypeName: "CallSpec[]")]
506+
[Parameter("tuple[]", "callPolicies", 4, structTypeName: "CallSpec[]")]
503507
[JsonProperty("callPolicies")]
504508
public virtual List<CallSpec> CallPolicies { get; set; }
505509

506-
[Parameter("tuple[]", "transferPolicies", 4, structTypeName: "TransferSpec[]")]
510+
[Parameter("tuple[]", "transferPolicies", 5, structTypeName: "TransferSpec[]")]
507511
[JsonProperty("transferPolicies")]
508512
public virtual List<TransferSpec> TransferPolicies { get; set; }
509513

510-
[Parameter("bytes32", "uid", 5)]
514+
[Parameter("bytes32", "uid", 6)]
511515
[JsonProperty("uid")]
512516
public virtual byte[] Uid { get; set; }
513517
}

Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs

Lines changed: 67 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -15,61 +15,46 @@ public class ThirdwebWallet : IThirdwebWallet
1515
public ThirdwebAccountType AccountType => ThirdwebAccountType.ExternalAccount;
1616

1717
internal IThirdwebWallet UserWallet { get; }
18-
internal IThirdwebWallet ExecutorWallet { get; }
1918
internal ThirdwebContract UserContract { get; }
19+
internal BigInteger ChainId { get; }
20+
internal bool ManagedExecution { get; }
2021

21-
internal ThirdwebWallet(ThirdwebClient client, IThirdwebWallet userWallet, IThirdwebWallet executorWallet, ThirdwebContract userContract)
22+
private EIP7702Authorization? Authorization { get; set; }
23+
24+
internal ThirdwebWallet(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, ThirdwebContract userContract, EIP7702Authorization? authorization, bool managedExecution)
2225
{
2326
this.Client = client;
27+
this.ChainId = chainId;
2428
this.UserWallet = userWallet;
25-
this.ExecutorWallet = executorWallet;
2629
this.UserContract = userContract;
30+
this.Authorization = authorization;
31+
this.ManagedExecution = managedExecution;
2732
}
2833

29-
public static async Task<ThirdwebWallet> Create(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, IThirdwebWallet executorWallet, SessionSpec sessionKeyParams)
34+
public static async Task<ThirdwebWallet> Create(ThirdwebClient client, BigInteger chainId, IThirdwebWallet userWallet, bool managedExecution)
3035
{
3136
var userWalletAddress = await userWallet.GetAddress();
32-
var executorWalletAddress = await executorWallet.GetAddress();
33-
if (sessionKeyParams != null && sessionKeyParams.Signer != executorWalletAddress)
34-
{
35-
throw new Exception("Session key signer must be the executor wallet");
36-
}
37-
var delegationContract = await ThirdwebContract.Create(client, Constants.MINIMAL_ACCOUNT_7702, chainId);
38-
39-
var rpc = ThirdwebRPC.GetRpcInstance(client, chainId);
40-
var code = await rpc.SendRequestAsync<string>("eth_getCode", userWalletAddress, "latest");
41-
var needsDelegation = !Utils.IsDelegatedAccount(code);
42-
43-
// Sign authorization if needed
44-
EIP7702Authorization? authorization = needsDelegation ? await userWallet.SignAuthorization(chainId, Constants.MINIMAL_ACCOUNT_7702, willSelfExecute: false) : null;
45-
46-
// TODO: We don't always need to create a session key when creating this wallet, handle with a flag or null check
47-
48-
// Sign message for session key
49-
var sessionKeySig = await EIP712.GenerateSignature_SmartAccount_7702("MinimalAccount", "1", chainId, userWalletAddress, sessionKeyParams, userWallet);
50-
51-
// Create call data for the session
52-
var sessionKeyCallData = delegationContract.CreateCallData("createSessionWithSig", sessionKeyParams, sessionKeySig.HexToBytes());
53-
54-
// Execute the delegation & session creation in one go
55-
var delegationTx = await ThirdwebTransaction.Create(
56-
executorWallet,
57-
new ThirdwebTransactionInput(chainId: chainId, to: userWalletAddress, data: sessionKeyCallData, authorization: authorization)
58-
);
59-
_ = await ThirdwebTransaction.SendAndWaitForTransactionReceipt(delegationTx);
60-
61-
var newCode = await rpc.SendRequestAsync<string>("eth_getCode", userWalletAddress, "latest");
62-
if (!Utils.IsDelegatedAccount(newCode))
63-
{
64-
throw new Exception("Delegation failed, code was not set.");
65-
}
66-
67-
var userContract = await ThirdwebContract.Create(client, userWalletAddress, chainId, delegationContract.Abi);
68-
var wallet = new ThirdwebWallet(client, userWallet, executorWallet, userContract);
37+
var userContract = await ThirdwebContract.Create(client, userWalletAddress, chainId, Constants.MINIMAL_ACCOUNT_7702_ABI);
38+
var needsDelegation = !await Utils.IsDelegatedAccount(client, chainId, userWalletAddress);
39+
EIP7702Authorization? authorization = needsDelegation ? await userWallet.SignAuthorization(chainId, Constants.MINIMAL_ACCOUNT_7702, willSelfExecute: !managedExecution) : null;
40+
var wallet = new ThirdwebWallet(client, chainId, userWallet, userContract, authorization, managedExecution);
6941
Utils.TrackConnection(wallet);
7042
return wallet;
7143
}
7244

45+
#region Wallet Specific
46+
47+
public async Task<ThirdwebTransactionReceipt> CreateSessionKey(SessionSpec sessionKeyParams)
48+
{
49+
var userWalletAddress = await this.UserWallet.GetAddress();
50+
var sessionKeySig = await EIP712.GenerateSignature_SmartAccount_7702("MinimalAccount", "1", this.ChainId, userWalletAddress, sessionKeyParams, this.UserWallet);
51+
var sessionKeyCallData = this.UserContract.CreateCallData("createSessionWithSig", sessionKeyParams, sessionKeySig.HexToBytes());
52+
var sessionKeyTx = await ThirdwebTransaction.Create(this, new ThirdwebTransactionInput(chainId: this.ChainId, to: userWalletAddress, data: sessionKeyCallData));
53+
return await ThirdwebTransaction.SendAndWaitForTransactionReceipt(sessionKeyTx);
54+
}
55+
56+
#endregion
57+
7358
#region IThirdwebWallet
7459

7560
public Task<string> GetAddress()
@@ -136,17 +121,50 @@ public Task<string> SignTransaction(ThirdwebTransactionInput transaction)
136121

137122
public async Task<string> SendTransaction(ThirdwebTransactionInput transaction)
138123
{
139-
var calls = new List<Call>
124+
// TODO: managed execution - executeWithSig
125+
if (this.ManagedExecution)
126+
{
127+
throw new NotImplementedException("Managed execution is not yet implemented.");
128+
129+
// 1. Create payload with eoa address, wrapped calls, signature and optional authorizationList
130+
// 2. Send to https://{chainId}.bundler.thirdweb.com as RpcRequest w/ method tw_execute
131+
// 3. Retrieve tx hash or queue id from response
132+
// 4. Return tx hash
133+
}
134+
else
140135
{
141-
new()
136+
var calls = new List<Call>
142137
{
143-
Target = transaction.To,
144-
Value = transaction.Value?.Value ?? BigInteger.Zero,
145-
Data = transaction.Data.HexToBytes()
138+
new()
139+
{
140+
Target = transaction.To,
141+
Value = transaction.Value?.Value ?? BigInteger.Zero,
142+
Data = transaction.Data.HexToBytes()
143+
}
144+
};
145+
146+
BigInteger totalValue = 0;
147+
foreach (var call in calls)
148+
{
149+
totalValue += call.Value;
146150
}
147-
};
148-
var tx = await this.UserContract.Prepare(this.ExecutorWallet, "execute", calls[0].Value, calls);
149-
return await ThirdwebTransaction.Send(tx);
151+
152+
var tx = await this.UserContract.Prepare(wallet: this.UserWallet, method: "execute", weiValue: totalValue, parameters: new object[] { calls });
153+
154+
if (this.Authorization != null)
155+
{
156+
if (!await Utils.IsDelegatedAccount(this.Client, this.ChainId, await this.UserWallet.GetAddress()))
157+
{
158+
tx.Input.AuthorizationList = new List<EIP7702Authorization>() { this.Authorization.Value };
159+
}
160+
else
161+
{
162+
this.Authorization = null;
163+
}
164+
}
165+
166+
return await ThirdwebTransaction.Send(tx);
167+
}
150168
}
151169

152170
public async Task<ThirdwebTransactionReceipt> ExecuteTransaction(ThirdwebTransactionInput transaction)

0 commit comments

Comments
 (0)