Skip to content

Commit eced8af

Browse files
committed
[Beta] ThirdwebWallet - An All Encompassing Wallet
Test Only Attempt to create an ultimate 7702 account, with the best dx possible. Eventually will have an implied executor, improving creation DX. Closes TOOL-3195
1 parent 9b69090 commit eced8af

File tree

9 files changed

+701
-145
lines changed

9 files changed

+701
-145
lines changed

Thirdweb.Console/Program.cs

Lines changed: 23 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@
2525
var privateKey = Environment.GetEnvironmentVariable("PRIVATE_KEY");
2626

2727
// Fetch timeout options are optional, default is 120000ms
28-
var client = ThirdwebClient.Create(secretKey: secretKey, fetchTimeoutOptions: new TimeoutOptions(storage: 120000, rpc: 120000, other: 120000));
28+
var client = ThirdwebClient.Create(secretKey: secretKey, rpcOverrides: new Dictionary<BigInteger, string> { { 11155111, "https://eth-sepolia.public.blastapi.io" } });
2929

30-
// Create a private key wallet
31-
var privateKeyWallet = await PrivateKeyWallet.Generate(client: client);
30+
// Create a private key wallet
31+
var privateKeyWallet = await PrivateKeyWallet.Generate(client);
3232

3333
// var walletAddress = await privateKeyWallet.GetAddress();
3434
// Console.WriteLine($"PK Wallet address: {walletAddress}");
@@ -229,113 +229,33 @@
229229

230230
#region EIP-7702
231231

232-
// // --------------------------------------------------------------------------
233-
// // Configuration
234-
// // --------------------------------------------------------------------------
232+
// // The session key signer
233+
// var executorWallet = await PrivateKeyWallet.Create(client, privateKey); // needs to be funded, for now
235234

236-
// var chainWith7702 = 911867;
237-
// var delegationContractAddress = "0xb012446cba783d0f7723daf96cf4c49005022307"; // MinimalAccount
238-
239-
// // Required environment variables
240-
// var backendWalletAddress = Environment.GetEnvironmentVariable("ENGINE_BACKEND_WALLET_ADDRESS") ?? throw new Exception("ENGINE_BACKEND_WALLET_ADDRESS is required");
241-
// var engineUrl = Environment.GetEnvironmentVariable("ENGINE_URL") ?? throw new Exception("ENGINE_URL is required");
242-
// var engineAccessToken = Environment.GetEnvironmentVariable("ENGINE_ACCESS_TOKEN") ?? throw new Exception("ENGINE_ACCESS_TOKEN is required");
243-
244-
// // --------------------------------------------------------------------------
245-
// // Initialize Engine Wallet
246-
// // --------------------------------------------------------------------------
247-
248-
// var engineWallet = await EngineWallet.Create(client, engineUrl, engineAccessToken, backendWalletAddress, 15);
249-
250-
// // --------------------------------------------------------------------------
251-
// // Delegation Contract Implementation
252-
// // --------------------------------------------------------------------------
253-
254-
// var delegationContract = await ThirdwebContract.Create(client, delegationContractAddress, chainWith7702);
255-
256-
// // Initialize a (to-be) 7702 EOA
257-
// var eoaWallet = await PrivateKeyWallet.Generate(client);
258-
// var eoaWalletAddress = await eoaWallet.GetAddress();
259-
// Console.WriteLine($"EOA address: {eoaWalletAddress}");
260-
261-
// // Sign the authorization to point to the delegation contract
262-
// var authorization = await eoaWallet.SignAuthorization(chainWith7702, delegationContractAddress, willSelfExecute: false);
263-
// Console.WriteLine($"Authorization: {JsonConvert.SerializeObject(authorization, Formatting.Indented)}");
264-
265-
// // Sign message for session key
266-
// var sessionKeyParams = new SessionKeyParams_7702()
235+
// // Session key permissions
236+
// var sessionKeyParams = new SessionSpec()
267237
// {
268-
// Signer = backendWalletAddress,
269-
// NativeTokenLimitPerTransaction = 0,
270-
// StartTimestamp = 0,
271-
// EndTimestamp = Utils.GetUnixTimeStampNow() + (3600 * 24),
272-
// ApprovedTargets = new List<string> { Constants.ADDRESS_ZERO },
273-
// Uid = Guid.NewGuid().ToByteArray()
274-
// };
275-
// var sessionKeySig = await EIP712.GenerateSignature_SmartAccount_7702("MinimalAccount", "1", chainWith7702, eoaWalletAddress, sessionKeyParams, eoaWallet);
276-
277-
// // Create call data for the session key
278-
// var sessionKeyCallData = delegationContract.CreateCallData("createSessionKeyWithSig", sessionKeyParams, sessionKeySig.HexToBytes());
279-
280-
// // Execute the delegation & session key creation in one go, from the backend!
281-
// var delegationReceipt = await engineWallet.ExecuteTransaction(new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, data: sessionKeyCallData, authorization: authorization));
282-
// Console.WriteLine($"Delegation Execution Receipt: {JsonConvert.SerializeObject(delegationReceipt, Formatting.Indented)}");
283-
284-
// // Verify contract code deployed to the EOA
285-
// var rpc = ThirdwebRPC.GetRpcInstance(client, chainWith7702);
286-
// var code = await rpc.SendRequestAsync<string>("eth_getCode", eoaWalletAddress, "latest");
287-
// Console.WriteLine($"EOA code: {code}");
288-
289-
// // The EOA is now a contract
290-
// var eoaContract = await ThirdwebContract.Create(client, eoaWalletAddress, chainWith7702, delegationContract.Abi);
291-
292-
// // --------------------------------------------------------------------------
293-
// // Mint Tokens (DropERC20) to the EOA Using the backend session key
294-
// // --------------------------------------------------------------------------
295-
296-
// var erc20ContractAddress = "0xAA462a5BE0fc5214507FDB4fB2474a7d5c69065b"; // DropERC20
297-
// var erc20Contract = await ThirdwebContract.Create(client, erc20ContractAddress, chainWith7702);
298-
299-
// // Log ERC20 balance before mint
300-
// var eoaBalanceBefore = await erc20Contract.ERC20_BalanceOf(eoaWalletAddress);
301-
// Console.WriteLine($"EOA balance before: {eoaBalanceBefore}");
302-
303-
// // Create execution call data (calling 'claim' on the DropERC20)
304-
// var executeCallData = eoaContract.CreateCallData(
305-
// "execute",
306-
// new object[]
238+
// Signer = await executorWallet.GetAddress(),
239+
// ExpiresAt = Utils.GetUnixTimeStampNow() + (3600 * 24),
240+
// CallPolicies = new List<CallSpec>() { },
241+
// TransferPolicies = new List<TransferSpec>()
307242
// {
308-
// new List<Call>
243+
// new()
309244
// {
310-
// new()
311-
// {
312-
// Data = erc20Contract
313-
// .CreateCallData(
314-
// "claim",
315-
// new object[]
316-
// {
317-
// eoaWalletAddress, // receiver
318-
// 100, // quantity
319-
// Constants.NATIVE_TOKEN_ADDRESS, // currency
320-
// 0, // pricePerToken
321-
// new object[] { Array.Empty<byte>(), BigInteger.Zero, BigInteger.Zero, Constants.ADDRESS_ZERO }, // allowlistProof
322-
// Array.Empty<byte>() // data
323-
// }
324-
// )
325-
// .HexToBytes(),
326-
// To = erc20ContractAddress,
327-
// Value = BigInteger.Zero
328-
// }
245+
// Target = await Utils.GetAddressFromENS(client, "vitalik.eth"),
246+
// MaxValuePerUse = BigInteger.Zero,
247+
// ValueLimit = new()
329248
// }
330-
// }
331-
// );
249+
// },
250+
// Uid = Guid.NewGuid().ToByteArray()
251+
// };
332252

333-
// var executeReceipt = await engineWallet.ExecuteTransaction(new ThirdwebTransactionInput(chainId: chainWith7702, to: eoaWalletAddress, data: executeCallData));
334-
// Console.WriteLine($"Execute receipt: {JsonConvert.SerializeObject(executeReceipt, Formatting.Indented)}");
253+
// // This wallet explicitly uses 7702 delegation to the thirdweb MinimalAccount and creates a session key from which every tx will be executed
254+
// var thirdwebWallet = await ThirdwebWallet.Create(client, 11155111, privateKeyWallet, executorWallet, sessionKeyParams);
335255

336-
// // Log ERC20 balance after mint
337-
// var eoaBalanceAfter = await erc20Contract.ERC20_BalanceOf(eoaWalletAddress);
338-
// Console.WriteLine($"EOA balance after: {eoaBalanceAfter}");
256+
// // Simple transfer, will use the session key automatically
257+
// var receipt = await thirdwebWallet.Transfer(11155111, await Utils.GetAddressFromENS(client, "vitalik.eth"), 0);
258+
// Console.WriteLine($"Receipt: {receipt}");
339259

340260
#endregion
341261

Thirdweb/Thirdweb.Utils/Constants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ public static class Constants
1212
public const string NATIVE_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
1313
public const double DECIMALS_18 = 1000000000000000000;
1414

15+
public const string MINIMAL_ACCOUNT_7702 = "0xFabf2ca2377Bbc199EaE439b71fcD6a5925127e2";
16+
1517
public const string ENTRYPOINT_ADDRESS_V06 = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789";
1618
public const string ENTRYPOINT_ADDRESS_V07 = "0x0000000071727De22E5E9d8BAf0edAc6f37da032";
1719

Thirdweb/Thirdweb.Utils/Utils.cs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -540,7 +540,7 @@ public static string ToJsonExternalWalletFriendly<TMessage, TDomain>(TypedData<T
540540
[GeneratedRegex("int\\d+")]
541541
private static partial Regex IntRegex();
542542

543-
private static bool IsReferenceType(string typeName)
543+
internal static bool IsReferenceType(string typeName)
544544
{
545545
if (!BytesRegex().IsMatch(typeName))
546546
{
@@ -568,7 +568,7 @@ private static bool IsReferenceType(string typeName)
568568
return false;
569569
}
570570
#else
571-
private static bool IsReferenceType(string typeName)
571+
internal static bool IsReferenceType(string typeName)
572572
{
573573
if (!new Regex("bytes\\d+").IsMatch(typeName))
574574
{
@@ -1255,4 +1255,72 @@ public static async void TrackConnection(IThirdwebWallet wallet)
12551255
// Ignore
12561256
}
12571257
}
1258+
1259+
/// <summary>
1260+
/// Waits for the transaction receipt.
1261+
/// </summary>
1262+
/// <param name="client">The Thirdweb client.</param>
1263+
/// <param name="chainId">The chain ID.</param>
1264+
/// <param name="txHash">The transaction hash.</param>
1265+
/// <param name="cancellationToken">The cancellation token.</param>
1266+
/// <returns>The transaction receipt.</returns>
1267+
public static async Task<ThirdwebTransactionReceipt> WaitForTransactionReceipt(ThirdwebClient client, BigInteger chainId, string txHash, CancellationToken cancellationToken = default)
1268+
{
1269+
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
1270+
cts.CancelAfter(client.FetchTimeoutOptions.GetTimeout(TimeoutType.Other));
1271+
1272+
var rpc = ThirdwebRPC.GetRpcInstance(client, chainId);
1273+
ThirdwebTransactionReceipt receipt = null;
1274+
1275+
try
1276+
{
1277+
do
1278+
{
1279+
receipt = await rpc.SendRequestAsync<ThirdwebTransactionReceipt>("eth_getTransactionReceipt", txHash).ConfigureAwait(false);
1280+
if (receipt == null)
1281+
{
1282+
await ThirdwebTask.Delay(100, cancellationToken).ConfigureAwait(false);
1283+
}
1284+
} while (receipt == null && !cts.Token.IsCancellationRequested);
1285+
1286+
if (receipt == null)
1287+
{
1288+
throw new Exception($"Transaction {txHash} not found within the timeout period.");
1289+
}
1290+
1291+
if (receipt.Status != null && receipt.Status.Value == 0)
1292+
{
1293+
throw new Exception($"Transaction {txHash} execution reverted.");
1294+
}
1295+
1296+
var userOpEvent = receipt.DecodeAllEvents<AccountAbstraction.UserOperationEventEventDTO>();
1297+
if (userOpEvent != null && userOpEvent.Count > 0 && !userOpEvent[0].Event.Success)
1298+
{
1299+
var revertReasonEvent = receipt.DecodeAllEvents<AccountAbstraction.UserOperationRevertReasonEventDTO>();
1300+
var postOpRevertReasonEvent = receipt.DecodeAllEvents<AccountAbstraction.PostOpRevertReasonEventDTO>();
1301+
if (revertReasonEvent != null && revertReasonEvent.Count > 0)
1302+
{
1303+
var revertReason = revertReasonEvent[0].Event.RevertReason;
1304+
var revertReasonString = new FunctionCallDecoder().DecodeFunctionErrorMessage(revertReason.ToHex(true));
1305+
throw new Exception($"Transaction {txHash} execution silently reverted: {revertReasonString}");
1306+
}
1307+
else if (postOpRevertReasonEvent != null && postOpRevertReasonEvent.Count > 0)
1308+
{
1309+
var revertReason = postOpRevertReasonEvent[0].Event.RevertReason;
1310+
var revertReasonString = new FunctionCallDecoder().DecodeFunctionErrorMessage(revertReason.ToHex(true));
1311+
throw new Exception($"Transaction {txHash} execution silently reverted: {revertReasonString}");
1312+
}
1313+
else
1314+
{
1315+
throw new Exception($"Transaction {txHash} execution silently reverted with no reason string");
1316+
}
1317+
}
1318+
}
1319+
catch (OperationCanceledException)
1320+
{
1321+
throw new Exception($"Transaction receipt polling for hash {txHash} was cancelled.");
1322+
}
1323+
1324+
return receipt;
1325+
}
12581326
}

Thirdweb/Thirdweb.Wallets/EIP712.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public static async Task<string> GenerateSignature_SmartAccount_7702(
5151
string version,
5252
BigInteger chainId,
5353
string verifyingContract,
54-
AccountAbstraction.SessionKeyParams_7702 sessionKeyParams,
54+
AccountAbstraction.SessionSpec sessionKeyParams,
5555
IThirdwebWallet signer
5656
)
5757
{
@@ -268,8 +268,15 @@ public static TypedData<Domain> GetTypedDefinition_SmartAccount_7702(string doma
268268
ChainId = chainId,
269269
VerifyingContract = verifyingContract,
270270
},
271-
Types = MemberDescriptionFactory.GetTypesMemberDescription(typeof(Domain), typeof(AccountAbstraction.SessionKeyParams_7702)),
272-
PrimaryType = "SessionKeyParams",
271+
Types = MemberDescriptionFactory.GetTypesMemberDescription(
272+
typeof(Domain),
273+
typeof(AccountAbstraction.SessionSpec),
274+
typeof(AccountAbstraction.CallSpec),
275+
typeof(AccountAbstraction.Constraint),
276+
typeof(AccountAbstraction.TransferSpec),
277+
typeof(AccountAbstraction.UsageLimit)
278+
),
279+
PrimaryType = "SessionSpec",
273280
};
274281
}
275282

0 commit comments

Comments
 (0)