Skip to content

Commit 3431c66

Browse files
committed
Move all functionality to IAW level
1 parent a728961 commit 3431c66

File tree

4 files changed

+188
-294
lines changed

4 files changed

+188
-294
lines changed

Thirdweb.Console/Program.cs

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -327,48 +327,48 @@
327327

328328
#region EIP-7702
329329

330-
var chain = 11155111; // sepolia
331-
332-
// Connect to EOA
333-
var userWallet = await InAppWallet.Create(client, email: "[email protected]");
334-
if (!await userWallet.IsConnected())
335-
{
336-
await userWallet.SendOTP();
337-
Console.WriteLine("Enter OTP:");
338-
var otp = Console.ReadLine();
339-
_ = await userWallet.LoginWithOtp(otp: otp);
340-
}
341-
Console.WriteLine($"User Wallet address: {await userWallet.GetAddress()}");
342-
343-
// Console.WriteLine("Send it some gas if testing with ExecutionMode.EOA");
344-
// Console.ReadLine();
330+
// var chain = 11155111; // sepolia
345331

346-
// Upgrade EOA - This wallet explicitly uses EIP-7702 delegation to the thirdweb MinimalAccount (will delegate upon first tx)
347-
var smarterWallet = await SmarterWallet.Create(client: client, chainId: chain, userWallet: userWallet, sponsorGas: true);
348-
var smarterWalletAddress = await smarterWallet.GetAddress();
349-
Console.WriteLine($"Thirdweb Wallet address: {smarterWalletAddress}"); // same as userWallet address, unlike when using EIP-4337
350-
351-
// Transact, will upgrade EOA
352-
var receipt = await smarterWallet.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, smarterWalletAddress);
357-
Console.WriteLine($"Is delegated: {isDelegated}");
358-
359-
// Create a session key
360-
var sessionKeyReceipt = await smarterWallet.CreateSessionKey(
361-
new SessionSpec()
362-
{
363-
Signer = await Utils.GetAddressFromENS(client, "0xfirekeeper.eth"),
364-
IsWildcard = true,
365-
ExpiresAt = Utils.GetUnixTimeStampNow() + 86400, // 1 day
366-
CallPolicies = new List<CallSpec>(),
367-
TransferPolicies = new List<TransferSpec>(),
368-
Uid = Guid.NewGuid().ToByteArray().PadTo32Bytes()
369-
}
370-
);
371-
Console.WriteLine($"Session key receipt: {sessionKeyReceipt.TransactionHash}");
332+
// // Connect to EOA
333+
// var smartEoa = await InAppWallet.Create(client, authProvider: AuthProvider.Google, executionMode: ExecutionMode.EOA);
334+
// if (!await smartEoa.IsConnected())
335+
// {
336+
// _ = await smartEoa.LoginWithOauth(
337+
// isMobile: false,
338+
// (url) =>
339+
// {
340+
// var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true };
341+
// _ = Process.Start(psi);
342+
// }
343+
// );
344+
// }
345+
// var smartEoaAddress = await smartEoa.GetAddress();
346+
// Console.WriteLine($"User Wallet address: {await smartEoa.GetAddress()}");
347+
348+
// // Upgrade EOA - This wallet explicitly uses EIP-7702 delegation to the thirdweb MinimalAccount (will delegate upon first tx)
349+
350+
// // Transact, will upgrade EOA
351+
// var receipt = await smartEoa.Transfer(chainId: chain, toAddress: await Utils.GetAddressFromENS(client, "vitalik.eth"), weiAmount: 0);
352+
// Console.WriteLine($"Transfer Receipt: {receipt.TransactionHash}");
353+
354+
// // Double check that it was upgraded
355+
// var isDelegated = await Utils.IsDelegatedAccount(client, chain, smartEoaAddress);
356+
// Console.WriteLine($"Is delegated: {isDelegated}");
357+
358+
// // Create a session key
359+
// var sessionKeyReceipt = await smartEoa.CreateSessionKey(
360+
// chain,
361+
// new SessionSpec()
362+
// {
363+
// Signer = await Utils.GetAddressFromENS(client, "0xfirekeeper.eth"),
364+
// IsWildcard = true,
365+
// ExpiresAt = Utils.GetUnixTimeStampNow() + 86400, // 1 day
366+
// CallPolicies = new List<CallSpec>(),
367+
// TransferPolicies = new List<TransferSpec>(),
368+
// Uid = Guid.NewGuid().ToByteArray().PadTo32Bytes()
369+
// }
370+
// );
371+
// Console.WriteLine($"Session key receipt: {sessionKeyReceipt.TransactionHash}");
372372

373373
#endregion
374374

Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs

Lines changed: 138 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,28 @@
44
using Nethereum.ABI.EIP712;
55
using Nethereum.Signer;
66
using Nethereum.Signer.EIP712;
7+
using Nethereum.Util;
78
using Newtonsoft.Json;
89
using Newtonsoft.Json.Linq;
10+
using Thirdweb.AccountAbstraction;
911
using Thirdweb.EWS;
1012

1113
namespace Thirdweb;
1214

15+
public enum ExecutionMode
16+
{
17+
EOA,
18+
EIP7702,
19+
EIP7702Sponsored
20+
}
21+
1322
/// <summary>
1423
/// Enclave based secure cross ecosystem wallet.
1524
/// </summary>
1625
public partial class EcosystemWallet : IThirdwebWallet
1726
{
1827
public ThirdwebClient Client { get; }
19-
public ThirdwebAccountType AccountType => ThirdwebAccountType.PrivateKeyAccount;
28+
public ThirdwebAccountType AccountType { get; }
2029
public virtual string WalletId => "ecosystem";
2130

2231
internal readonly EmbeddedWallet EmbeddedWallet;
@@ -29,6 +38,7 @@ public partial class EcosystemWallet : IThirdwebWallet
2938
internal readonly string WalletSecret;
3039

3140
internal string Address;
41+
internal ExecutionMode ExecutionMode;
3242

3343
private readonly string _ecosystemId;
3444
private readonly string _ecosystemPartnerId;
@@ -49,7 +59,8 @@ internal EcosystemWallet(
4959
string authProvider,
5060
IThirdwebWallet siweSigner,
5161
string legacyEncryptionKey,
52-
string walletSecret
62+
string walletSecret,
63+
ExecutionMode executionMode
5364
)
5465
{
5566
this.Client = client;
@@ -63,6 +74,9 @@ string walletSecret
6374
this.AuthProvider = authProvider;
6475
this.SiweSigner = siweSigner;
6576
this.WalletSecret = walletSecret;
77+
this.ExecutionMode = executionMode;
78+
this.AccountType = executionMode == ExecutionMode.EOA ? ThirdwebAccountType.PrivateKeyAccount : ThirdwebAccountType.ExternalAccount;
79+
;
6680
}
6781

6882
#region Creation
@@ -81,6 +95,7 @@ string walletSecret
8195
/// <param name="legacyEncryptionKey">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.</param>
8296
/// <param name="walletSecret">The wallet secret for Backend authentication.</param>
8397
/// <param name="twAuthTokenOverride">The auth token to use for the session. This will automatically connect using a raw thirdweb auth token.</param>
98+
/// <param name="executionMode">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.</param>
8499
/// <returns>A task that represents the asynchronous operation. The task result contains the created in-app wallet.</returns>
85100
/// <exception cref="ArgumentException">Thrown when required parameters are not provided.</exception>
86101
public static async Task<EcosystemWallet> Create(
@@ -94,7 +109,8 @@ public static async Task<EcosystemWallet> Create(
94109
IThirdwebWallet siweSigner = null,
95110
string legacyEncryptionKey = null,
96111
string walletSecret = null,
97-
string twAuthTokenOverride = null
112+
string twAuthTokenOverride = null,
113+
ExecutionMode executionMode = ExecutionMode.EOA
98114
)
99115
{
100116
if (client == null)
@@ -164,15 +180,41 @@ public static async Task<EcosystemWallet> Create(
164180
try
165181
{
166182
var userAddress = await ResumeEnclaveSession(enclaveHttpClient, embeddedWallet, email, phoneNumber, authproviderStr).ConfigureAwait(false);
167-
return new EcosystemWallet(ecosystemId, ecosystemPartnerId, client, embeddedWallet, enclaveHttpClient, email, phoneNumber, authproviderStr, siweSigner, legacyEncryptionKey, walletSecret)
183+
return new EcosystemWallet(
184+
ecosystemId,
185+
ecosystemPartnerId,
186+
client,
187+
embeddedWallet,
188+
enclaveHttpClient,
189+
email,
190+
phoneNumber,
191+
authproviderStr,
192+
siweSigner,
193+
legacyEncryptionKey,
194+
walletSecret,
195+
executionMode
196+
)
168197
{
169198
Address = userAddress
170199
};
171200
}
172201
catch
173202
{
174203
enclaveHttpClient.RemoveHeader("Authorization");
175-
return new EcosystemWallet(ecosystemId, ecosystemPartnerId, client, embeddedWallet, enclaveHttpClient, email, phoneNumber, authproviderStr, siweSigner, legacyEncryptionKey, walletSecret)
204+
return new EcosystemWallet(
205+
ecosystemId,
206+
ecosystemPartnerId,
207+
client,
208+
embeddedWallet,
209+
enclaveHttpClient,
210+
email,
211+
phoneNumber,
212+
authproviderStr,
213+
siweSigner,
214+
legacyEncryptionKey,
215+
walletSecret,
216+
executionMode
217+
)
176218
{
177219
Address = null
178220
};
@@ -408,6 +450,21 @@ public string GenerateExternalLoginLink(string redirectUrl)
408450
return $"{redirectUrl}{queryString}";
409451
}
410452

453+
public Task<ThirdwebTransactionReceipt> CreateSessionKey(BigInteger chainId, SessionSpec sessionKeyParams)
454+
{
455+
throw new NotImplementedException("CreateSessionKey via EIP7702 execution modes is not implemented yet, check back in later versions.");
456+
// if (this.ExecutionMode is not ExecutionMode.EIP7702 and not ExecutionMode.EIP7702Sponsored)
457+
// {
458+
// throw new InvalidOperationException("CreateSessionKey is only supported for EIP7702 and EIP7702Sponsored execution modes.");
459+
// }
460+
461+
// var userWalletAddress = await this.GetAddress();
462+
// var sessionKeySig = await EIP712.GenerateSignature_SmartAccount_7702("MinimalAccount", "1", chainId, userWalletAddress, sessionKeyParams, this);
463+
// var userContract = await ThirdwebContract.Create(this.Client, userWalletAddress, chainId, Constants.MINIMAL_ACCOUNT_7702_ABI);
464+
// var sessionKeyCallData = userContract.CreateCallData("createSessionWithSig", sessionKeyParams, sessionKeySig.HexToBytes());
465+
// return await this.ExecuteTransaction(new ThirdwebTransactionInput(chainId: chainId, to: userWalletAddress, value: 0, data: sessionKeyCallData));
466+
}
467+
411468
#endregion
412469

413470
#region Account Linking
@@ -1084,14 +1141,86 @@ public Task<bool> IsConnected()
10841141
return Task.FromResult(this.Address != null);
10851142
}
10861143

1087-
public Task<string> SendTransaction(ThirdwebTransactionInput transaction)
1144+
public async Task<string> SendTransaction(ThirdwebTransactionInput transaction)
10881145
{
1089-
throw new InvalidOperationException("SendTransaction is not supported for Ecosystem Wallets, please use the unified Contract or ThirdwebTransaction APIs.");
1146+
var userWalletAddress = await this.GetAddress();
1147+
var userContract = await ThirdwebContract.Create(this.Client, userWalletAddress, transaction.ChainId, Constants.MINIMAL_ACCOUNT_7702_ABI);
1148+
var needsDelegation = !await Utils.IsDelegatedAccount(this.Client, transaction.ChainId, userWalletAddress);
1149+
EIP7702Authorization? authorization = needsDelegation
1150+
? await this.SignAuthorization(transaction.ChainId, Constants.MINIMAL_ACCOUNT_7702, willSelfExecute: this.ExecutionMode != ExecutionMode.EIP7702Sponsored)
1151+
: null;
1152+
1153+
var calls = new List<Call>
1154+
{
1155+
new()
1156+
{
1157+
Target = transaction.To,
1158+
Value = transaction.Value?.Value ?? BigInteger.Zero,
1159+
Data = transaction.Data.HexToBytes()
1160+
}
1161+
};
1162+
1163+
switch (this.ExecutionMode)
1164+
{
1165+
case ExecutionMode.EOA:
1166+
throw new NotImplementedException(
1167+
"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."
1168+
);
1169+
case ExecutionMode.EIP7702:
1170+
BigInteger totalValue = 0;
1171+
foreach (var call in calls)
1172+
{
1173+
totalValue += call.Value;
1174+
}
1175+
var finalTx = await userContract.Prepare(wallet: this, method: "execute", weiValue: totalValue, parameters: new object[] { calls });
1176+
finalTx.Input.AuthorizationList = authorization != null ? new List<EIP7702Authorization>() { authorization.Value } : null;
1177+
finalTx = await ThirdwebTransaction.Prepare(finalTx);
1178+
var signedTx = await this.SignTransaction(finalTx.Input);
1179+
var rpc = ThirdwebRPC.GetRpcInstance(this.Client, transaction.ChainId);
1180+
return await rpc.SendRequestAsync<string>("eth_sendRawTransaction", signedTx).ConfigureAwait(false);
1181+
case ExecutionMode.EIP7702Sponsored:
1182+
var wrappedCalls = new WrappedCalls() { Calls = calls, Uid = Guid.NewGuid().ToByteArray().PadTo32Bytes() };
1183+
var signature = await EIP712.GenerateSignature_SmartAccount_7702_WrappedCalls("MinimalAccount", "1", transaction.ChainId, userWalletAddress, wrappedCalls, this);
1184+
var response = await BundlerClient.TwExecute(
1185+
client: this.Client,
1186+
url: $"https://{transaction.ChainId}.bundler.thirdweb.com",
1187+
requestId: 7702,
1188+
eoaAddress: userWalletAddress,
1189+
wrappedCalls: wrappedCalls,
1190+
signature: signature,
1191+
authorization: authorization != null && !await Utils.IsDelegatedAccount(this.Client, transaction.ChainId, userWalletAddress) ? authorization : null
1192+
);
1193+
var queueId = response?.QueueId;
1194+
string txHash = null;
1195+
var ct = new CancellationTokenSource(this.Client.FetchTimeoutOptions.GetTimeout(TimeoutType.Other));
1196+
try
1197+
{
1198+
while (txHash == null)
1199+
{
1200+
ct.Token.ThrowIfCancellationRequested();
1201+
1202+
var hashResponse = await BundlerClient
1203+
.TwGetTransactionHash(client: this.Client, url: $"https://{transaction.ChainId}.bundler.thirdweb.com", requestId: 7702, queueId)
1204+
.ConfigureAwait(false);
1205+
1206+
txHash = hashResponse?.TransactionHash;
1207+
await ThirdwebTask.Delay(100, ct.Token).ConfigureAwait(false);
1208+
}
1209+
return txHash;
1210+
}
1211+
catch (OperationCanceledException)
1212+
{
1213+
throw new Exception($"EIP-7702 sponsored transaction timed out with queue id: {queueId}");
1214+
}
1215+
default:
1216+
throw new ArgumentOutOfRangeException(nameof(this.ExecutionMode), "Invalid execution mode.");
1217+
}
10901218
}
10911219

1092-
public Task<ThirdwebTransactionReceipt> ExecuteTransaction(ThirdwebTransactionInput transactionInput)
1220+
public async Task<ThirdwebTransactionReceipt> ExecuteTransaction(ThirdwebTransactionInput transactionInput)
10931221
{
1094-
throw new InvalidOperationException("ExecuteTransaction is not supported for Ecosystem Wallets, please use the unified Contract or ThirdwebTransaction APIs.");
1222+
var hash = await this.SendTransaction(transactionInput);
1223+
return await Utils.WaitForTransactionReceipt(this.Client, transactionInput.ChainId, hash);
10951224
}
10961225

10971226
public async Task Disconnect()

Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ internal InAppWallet(
1919
IThirdwebWallet siweSigner,
2020
string address,
2121
string legacyEncryptionKey,
22-
string walletSecret
22+
string walletSecret,
23+
ExecutionMode executionMode
2324
)
24-
: base(null, null, client, embeddedWallet, httpClient, email, phoneNumber, authProvider, siweSigner, legacyEncryptionKey, walletSecret)
25+
: base(null, null, client, embeddedWallet, httpClient, email, phoneNumber, authProvider, siweSigner, legacyEncryptionKey, walletSecret, executionMode)
2526
{
2627
this.Address = address;
2728
}
@@ -38,6 +39,7 @@ string walletSecret
3839
/// <param name="legacyEncryptionKey">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.</param>
3940
/// <param name="walletSecret">The wallet secret for backend authentication.</param>
4041
/// <param name="twAuthTokenOverride">The auth token to use for the session. This will automatically connect using a raw thirdweb auth token.</param>
42+
/// <param name="executionMode">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.</param>
4143
/// <returns>A task that represents the asynchronous operation. The task result contains the created in-app wallet.</returns>
4244
/// <exception cref="ArgumentException">Thrown when required parameters are not provided.</exception>
4345
public static async Task<InAppWallet> Create(
@@ -49,11 +51,12 @@ public static async Task<InAppWallet> Create(
4951
IThirdwebWallet siweSigner = null,
5052
string legacyEncryptionKey = null,
5153
string walletSecret = null,
52-
string twAuthTokenOverride = null
54+
string twAuthTokenOverride = null,
55+
ExecutionMode executionMode = ExecutionMode.EOA
5356
)
5457
{
5558
storageDirectoryPath ??= Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Thirdweb", "InAppWallet");
56-
var ecoWallet = await Create(client, null, null, email, phoneNumber, authProvider, storageDirectoryPath, siweSigner, legacyEncryptionKey, walletSecret, twAuthTokenOverride);
59+
var ecoWallet = await Create(client, null, null, email, phoneNumber, authProvider, storageDirectoryPath, siweSigner, legacyEncryptionKey, walletSecret, twAuthTokenOverride, executionMode);
5760
return new InAppWallet(
5861
ecoWallet.Client,
5962
ecoWallet.EmbeddedWallet,
@@ -64,7 +67,8 @@ public static async Task<InAppWallet> Create(
6467
ecoWallet.SiweSigner,
6568
ecoWallet.Address,
6669
ecoWallet.LegacyEncryptionKey,
67-
ecoWallet.WalletSecret
70+
ecoWallet.WalletSecret,
71+
ecoWallet.ExecutionMode
6872
);
6973
}
7074
}

0 commit comments

Comments
 (0)