Skip to content

Commit

Permalink
Merge pull request #3 from thirdweb-dev/firekeeper/1271-712
Browse files Browse the repository at this point in the history
1271, 712 and Session Keys
  • Loading branch information
0xFirekeeper authored Apr 3, 2024
2 parents 028330d + 1a0016f commit f3075dc
Show file tree
Hide file tree
Showing 12 changed files with 371 additions and 22 deletions.
40 changes: 31 additions & 9 deletions Thirdweb.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,16 @@ private static async Task Main(string[] args)
var readResult = await ThirdwebContract.ReadContract<string>(contract, "name");
Console.WriteLine($"Contract read result: {readResult}");

// Create accounts
var privateKeyAccount = new PrivateKeyAccount(client, privateKey);
var embeddedAccount = new EmbeddedAccount(client, "[email protected]");
var smartAccount = new SmartAccount(client, privateKeyAccount, "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", true, 421614);
var embeddedAccount = new EmbeddedAccount(client, "firekeeper+7121271d@thirdweb.com");
var smartAccount = new SmartAccount(client, embeddedAccount, "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", true, 421614);

var accounts = new List<IThirdwebAccount> { privateKeyAccount, embeddedAccount, smartAccount };

foreach (var account in accounts)
{
await account.Connect();
}
// Attempt to connect pk accounts
await privateKeyAccount.Connect();
await embeddedAccount.Connect();

// Relog if embedded account not logged in
if (!await embeddedAccount.IsConnected())
{
await embeddedAccount.SendOTP();
Expand All @@ -59,11 +58,34 @@ private static async Task Main(string[] args)
}
}

// Connect the smart account with embedded signer and grant a session key to pk account
await smartAccount.Connect();
_ = await smartAccount.CreateSessionKey(
signerAddress: await privateKeyAccount.GetAddress(),
approvedTargets: new List<string>() { Constants.ADDRESS_ZERO },
nativeTokenLimitPerTransactionInWei: "0",
permissionStartTimestamp: "0",
permissionEndTimestamp: (Utils.GetUnixTimeStampNow() + 86400).ToString(),
reqValidityStartTimestamp: "0",
reqValidityEndTimestamp: Utils.GetUnixTimeStampIn10Years().ToString()
);

// Reconnect to same smart account with pk account as signer
smartAccount = new SmartAccount(client, privateKeyAccount, "0xbf1C9aA4B1A085f7DA890a44E82B0A1289A40052", true, 421614, await smartAccount.GetAddress());
await smartAccount.Connect();

// Log addresses
Console.WriteLine($"PrivateKey Account: {await privateKeyAccount.GetAddress()}");
Console.WriteLine($"Embedded Account: {await embeddedAccount.GetAddress()}");
Console.WriteLine($"Smart Account: {await smartAccount.GetAddress()}");

// Initialize wallet
var thirdwebWallet = new ThirdwebWallet();
await thirdwebWallet.Initialize(accounts);
await thirdwebWallet.Initialize(new List<IThirdwebAccount> { privateKeyAccount, embeddedAccount, smartAccount });
thirdwebWallet.SetActive(await smartAccount.GetAddress());
Console.WriteLine($"Active account: {await thirdwebWallet.GetAddress()}");

// Sign, triggering deploy as needed and 1271 verification
var message = "Hello, Thirdweb!";
var signature = await thirdwebWallet.PersonalSign(message);
Console.WriteLine($"Signed message: {signature}");
Expand Down
1 change: 1 addition & 0 deletions Thirdweb/Thirdweb.Contracts/ThirdwebContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public static async Task<string> WriteContract(ThirdwebWallet wallet, ThirdwebCo
{
throw new NotImplementedException("Account type not supported");
}
Console.WriteLine($"Transaction hash: {hash}");
return hash;
}
}
Expand Down
18 changes: 16 additions & 2 deletions Thirdweb/Thirdweb.RPC/ThirdwebRPC.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,22 @@ public async Task<TResponse> SendRequestAsync<TResponse>(string method, params o
}
}

object result = await tcs.Task;
return (TResponse)result;
var result = await tcs.Task;
if (result is TResponse response)
{
return response;
}
else
{
try
{
return JsonConvert.DeserializeObject<TResponse>(JsonConvert.SerializeObject(result));
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to deserialize RPC response.", ex);
}
}
}

static ThirdwebRPC()
Expand Down
4 changes: 4 additions & 0 deletions Thirdweb/Thirdweb.Utils/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
{
public static class Constants
{
public const string ADDRESS_ZERO = "0x0000000000000000000000000000000000000000";
public const string NATIVE_TOKEN_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
public const double DECIMALS_18 = 1000000000000000000;

internal const string VERSION = "0.0.1";
internal const int DEFAULT_FETCH_TIMEOUT = 60000;
internal const string DEFAULT_ENTRYPOINT_ADDRESS = "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789"; // v0.6
Expand Down
74 changes: 73 additions & 1 deletion Thirdweb/Thirdweb.Utils/Utils.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
using System.Security.Cryptography;
using System.Numerics;
using System.Security.Cryptography;
using System.Text;
using Nethereum.ABI.FunctionEncoding;
using Nethereum.Contracts;
using Nethereum.Hex.HexConvertors.Extensions;
using Nethereum.RPC.Eth.DTOs;
using Nethereum.Signer;
using Newtonsoft.Json;

namespace Thirdweb
{
Expand All @@ -23,5 +30,70 @@ public static string HexConcat(params string[] hexStrings)

return hex.ToString();
}

public static async Task<TransactionReceipt> GetTransactionReceipt(ThirdwebClient client, BigInteger chainId, string txHash, CancellationToken cancellationToken = default)
{
var rpc = ThirdwebRPC.GetRpcInstance(client, chainId);
var receipt = await rpc.SendRequestAsync<TransactionReceipt>("eth_getTransactionReceipt", txHash).ConfigureAwait(false);
while (receipt == null)
{
if (cancellationToken != CancellationToken.None)
{
await Task.Delay(1000, cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
}
else
{
await Task.Delay(1000, CancellationToken.None).ConfigureAwait(false);
}

receipt = await rpc.SendRequestAsync<TransactionReceipt>("eth_getTransactionReceipt", txHash).ConfigureAwait(false);
}

if (receipt.Failed())
{
throw new Exception($"Transaction {txHash} execution reverted.");
}

var userOpEvent = receipt.DecodeAllEvents<AccountAbstraction.UserOperationEventEventDTO>();
if (userOpEvent != null && userOpEvent.Count > 0 && userOpEvent[0].Event.Success == false)
{
var revertReasonEvent = receipt.DecodeAllEvents<AccountAbstraction.UserOperationRevertReasonEventDTO>();
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
{
throw new Exception($"Transaction {txHash} execution silently reverted with no reason string");
}
}

return receipt;
}

public static byte[] HashPrefixedMessage(this byte[] messageBytes)
{
var signer = new EthereumMessageSigner();
return signer.HashPrefixedMessage(messageBytes);
}

public static string HashPrefixedMessage(this string message)
{
var signer = new EthereumMessageSigner();
return signer.HashPrefixedMessage(Encoding.UTF8.GetBytes(message)).ToHex(true);
}

public static long GetUnixTimeStampNow()
{
return DateTimeOffset.UtcNow.ToUnixTimeSeconds();
}

public static long GetUnixTimeStampIn10Years()
{
return DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 60 * 60 * 24 * 365 * 10;
}
}
}
67 changes: 67 additions & 0 deletions Thirdweb/Thirdweb.Wallets/EIP712.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.Numerics;
using Nethereum.ABI.EIP712;

namespace Thirdweb
{
public static class EIP712
{
public async static Task<string> GenerateSignature_SmartAccount(
string domainName,
string version,
BigInteger chainId,
string verifyingContract,
AccountAbstraction.SignerPermissionRequest signerPermissionRequest,
IThirdwebAccount signer
)
{
var typedData = GetTypedDefinition_SmartAccount(domainName, version, chainId, verifyingContract);
return await signer.SignTypedDataV4(signerPermissionRequest, typedData);
}

public async static Task<string> GenerateSignature_SmartAccount_AccountMessage(
string domainName,
string version,
BigInteger chainId,
string verifyingContract,
byte[] message,
IThirdwebAccount signer
)
{
var typedData = GetTypedDefinition_SmartAccount_AccountMessage(domainName, version, chainId, verifyingContract);
var accountMessage = new AccountAbstraction.AccountMessage { Message = message };
return await signer.SignTypedDataV4(accountMessage, typedData);
}

public static TypedData<Domain> GetTypedDefinition_SmartAccount(string domainName, string version, BigInteger chainId, string verifyingContract)
{
return new TypedData<Domain>
{
Domain = new Domain
{
Name = domainName,
Version = version,
ChainId = chainId,
VerifyingContract = verifyingContract,
},
Types = MemberDescriptionFactory.GetTypesMemberDescription(typeof(Domain), typeof(AccountAbstraction.SignerPermissionRequest)),
PrimaryType = nameof(AccountAbstraction.SignerPermissionRequest),
};
}

public static TypedData<Domain> GetTypedDefinition_SmartAccount_AccountMessage(string domainName, string version, BigInteger chainId, string verifyingContract)
{
return new TypedData<Domain>
{
Domain = new Domain
{
Name = domainName,
Version = version,
ChainId = chainId,
VerifyingContract = verifyingContract,
},
Types = MemberDescriptionFactory.GetTypesMemberDescription(typeof(Domain), typeof(AccountAbstraction.AccountMessage)),
PrimaryType = nameof(AccountAbstraction.AccountMessage),
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ public Task<string> SignTypedDataV4(string json)
}

public Task<string> SignTypedDataV4<T, TDomain>(T data, TypedData<TDomain> typedData)
where TDomain : IDomain
{
if (data == null)
{
Expand Down
3 changes: 2 additions & 1 deletion Thirdweb/Thirdweb.Wallets/IThirdwebAccount.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ public interface IThirdwebAccount
public Task<string> PersonalSign(byte[] rawMessage);
public Task<string> PersonalSign(string message);
public Task<string> SignTypedDataV4(string json);
public Task<string> SignTypedDataV4<T, TDomain>(T data, TypedData<TDomain> typedData);
public Task<string> SignTypedDataV4<T, TDomain>(T data, TypedData<TDomain> typedData)
where TDomain : IDomain;
public Task<string> SignTransaction(TransactionInput transaction, BigInteger chainId);
public Task<bool> IsConnected();
public Task Disconnect();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ public Task<string> SignTypedDataV4(string json)
}

public Task<string> SignTypedDataV4<T, TDomain>(T data, TypedData<TDomain> typedData)
where TDomain : IDomain
{
if (data == null)
{
Expand Down
104 changes: 96 additions & 8 deletions Thirdweb/Thirdweb.Wallets/SmartAccount/SmartAccount.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -114,4 +114,81 @@ public class ThirdwebGetUserOperationGasPriceResponse
public string maxFeePerGas { get; set; }
public string maxPriorityFeePerGas { get; set; }
}

[Event("UserOperationEvent")]
public class UserOperationEventEventDTO : IEventDTO
{
[Parameter("bytes32", "userOpHash", 1, true)]
public virtual byte[] UserOpHash { get; set; }

[Parameter("address", "sender", 2, true)]
public virtual string Sender { get; set; }

[Parameter("address", "paymaster", 3, true)]
public virtual string Paymaster { get; set; }

[Parameter("uint256", "nonce", 4, false)]
public virtual BigInteger Nonce { get; set; }

[Parameter("bool", "success", 5, false)]
public virtual bool Success { get; set; }

[Parameter("uint256", "actualGasCost", 6, false)]
public virtual BigInteger ActualGasCost { get; set; }

[Parameter("uint256", "actualGasUsed", 7, false)]
public virtual BigInteger ActualGasUsed { get; set; }
}

[Event("UserOperationRevertReason")]
public class UserOperationRevertReasonEventDTO : IEventDTO
{
[Parameter("bytes32", "userOpHash", 1, true)]
public virtual byte[] UserOpHash { get; set; }

[Parameter("address", "sender", 2, true)]
public virtual string Sender { get; set; }

[Parameter("uint256", "nonce", 3, false)]
public virtual BigInteger Nonce { get; set; }

[Parameter("bytes", "revertReason", 4, false)]
public virtual byte[] RevertReason { get; set; }
}

public class SignerPermissionRequest
{
[Parameter("address", "signer", 1)]
public virtual string Signer { get; set; }

[Parameter("uint8", "isAdmin", 2)]
public virtual byte IsAdmin { get; set; }

[Parameter("address[]", "approvedTargets", 3)]
public virtual List<string> ApprovedTargets { get; set; }

[Parameter("uint256", "nativeTokenLimitPerTransaction", 4)]
public virtual BigInteger NativeTokenLimitPerTransaction { get; set; }

[Parameter("uint128", "permissionStartTimestamp", 5)]
public virtual BigInteger PermissionStartTimestamp { get; set; }

[Parameter("uint128", "permissionEndTimestamp", 6)]
public virtual BigInteger PermissionEndTimestamp { get; set; }

[Parameter("uint128", "reqValidityStartTimestamp", 7)]
public virtual BigInteger ReqValidityStartTimestamp { get; set; }

[Parameter("uint128", "reqValidityEndTimestamp", 8)]
public virtual BigInteger ReqValidityEndTimestamp { get; set; }

[Parameter("bytes32", "uid", 9)]
public virtual byte[] Uid { get; set; }
}

public class AccountMessage
{
[Nethereum.ABI.FunctionEncoding.Attributes.Parameter("bytes", "message", 1)]
public virtual byte[] Message { get; set; }
}
}
3 changes: 2 additions & 1 deletion Thirdweb/Thirdweb.Wallets/ThirdwebWallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ public async Task<string> SignTypedDataV4(string json)
return await ActiveAccount.SignTypedDataV4(json);
}

public async Task<string> SignTypedDataV4<T>(T data, TypedData<T> typedData)
public async Task<string> SignTypedDataV4<T, TDomain>(T data, TypedData<TDomain> typedData)
where TDomain : IDomain
{
return await ActiveAccount.SignTypedDataV4(data, typedData);
}
Expand Down

0 comments on commit f3075dc

Please sign in to comment.