diff --git a/Thirdweb.Console/Program.cs b/Thirdweb.Console/Program.cs
index f292cbfa..9ce02980 100644
--- a/Thirdweb.Console/Program.cs
+++ b/Thirdweb.Console/Program.cs
@@ -27,7 +27,7 @@
var privateKey = Environment.GetEnvironmentVariable("PRIVATE_KEY");
// Fetch timeout options are optional, default is 120000ms
-var client = ThirdwebClient.Create(secretKey: secretKey);
+var client = ThirdwebClient.Create(secretKey: "4qXoZMCqQo9SD8YkrdvO5Ci9gYKrgRADHSY84Q0wwKHZS53_R1QNcIs2XbFBWR0xE7HTQPER45T1sN1JvdFKlA");
// Create a private key wallet
var privateKeyWallet = await PrivateKeyWallet.Generate(client);
@@ -340,21 +340,46 @@
#endregion
-#region Engine Wallet
+#region Server Wallet
-// // EngineWallet is compatible with IThirdwebWallet and can be used with any SDK method/extension
-// var engineWallet = await EngineWallet.Create(
+// // ServerWallet is compatible with IThirdwebWallet and can be used with any SDK method/extension
+// var serverWallet = await ServerWallet.Create(
// client: client,
-// engineUrl: Environment.GetEnvironmentVariable("ENGINE_URL"),
-// authToken: Environment.GetEnvironmentVariable("ENGINE_ACCESS_TOKEN"),
-// walletAddress: Environment.GetEnvironmentVariable("ENGINE_BACKEND_WALLET_ADDRESS"),
-// timeoutSeconds: null, // no timeout
-// additionalHeaders: null // can set things like x-account-address if using basic session keys
+// label: "Test",
+// // Optional, defaults to Auto - we choose between EIP-7702, EIP-4337 or native zkSync AA execution / EOA is also available
+// executionOptions: new AutoExecutionOptions()
// );
+// var serverWalletAddress = await serverWallet.GetAddress();
+// Console.WriteLine($"Server Wallet address: {serverWalletAddress}");
+
+// var serverWalletPersonalSig = await serverWallet.PersonalSign("Hello, Thirdweb!");
+// Console.WriteLine($"Server Wallet personal sign: {serverWalletPersonalSig}");
+
+// var json =
+// /*lang=json,strict*/
+// "{\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Person\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"wallet\",\"type\":\"address\"}],\"Mail\":[{\"name\":\"from\",\"type\":\"Person\"},{\"name\":\"to\",\"type\":\"Person\"},{\"name\":\"contents\",\"type\":\"string\"}]},\"primaryType\":\"Mail\",\"domain\":{\"name\":\"Ether Mail\",\"version\":\"1\",\"chainId\":84532,\"verifyingContract\":\"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC\"},\"message\":{\"from\":{\"name\":\"Cow\",\"wallet\":\"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826\"},\"to\":{\"name\":\"Bob\",\"wallet\":\"0xbBbBBBBbbBBBbbbBbbBbbBBbBbbBbBbBbBbbBBbB\"},\"contents\":\"Hello, Bob!\"}}";
+// var serverWalletTypedDataSign = await serverWallet.SignTypedDataV4(json);
+// Console.WriteLine($"Server Wallet typed data sign: {serverWalletTypedDataSign}");
+
// // Simple self transfer
-// var receipt = await engineWallet.Transfer(chainId: 11155111, toAddress: await engineWallet.GetAddress(), weiAmount: 0);
-// Console.WriteLine($"Receipt: {receipt}");
+// var serverWalletReceipt = await serverWallet.Transfer(chainId: 84532, toAddress: await serverWallet.GetAddress(), weiAmount: 0);
+// Console.WriteLine($"Server Wallet Hash: {serverWalletReceipt.TransactionHash}");
+
+// // ServerWallet forcing ERC-4337 Execution Mode
+// var smartServerWallet = await ServerWallet.Create(client: client, label: "Test", executionOptions: new ERC4337ExecutionOptions(chainId: 84532, signerAddress: serverWalletAddress));
+// var smartServerWalletAddress = await smartServerWallet.GetAddress();
+// Console.WriteLine($"Smart Server Wallet address: {smartServerWalletAddress}");
+
+// var smartServerWalletPersonalSig = await smartServerWallet.PersonalSign("Hello, Thirdweb!");
+// Console.WriteLine($"Smart Server Wallet personal sign: {smartServerWalletPersonalSig}");
+
+// var smartServerWalletTypedDataSign = await smartServerWallet.SignTypedDataV4(json);
+// Console.WriteLine($"Smart Server Wallet typed data sign: {smartServerWalletTypedDataSign}");
+
+// // Simple self transfer
+// var smartServerWalletReceipt = await smartServerWallet.Transfer(chainId: 84532, toAddress: await smartServerWallet.GetAddress(), weiAmount: 0);
+// Console.WriteLine($"Server Wallet Hash: {smartServerWalletReceipt.TransactionHash}");
#endregion
diff --git a/Thirdweb/Thirdweb.Utils/Constants.cs b/Thirdweb/Thirdweb.Utils/Constants.cs
index 8cdf33e5..d8370b73 100644
--- a/Thirdweb/Thirdweb.Utils/Constants.cs
+++ b/Thirdweb/Thirdweb.Utils/Constants.cs
@@ -10,6 +10,7 @@ public static class Constants
internal const string PIN_URI = "https://storage.thirdweb.com/ipfs/upload";
internal const string FALLBACK_IPFS_GATEWAY = "https://ipfs.io/ipfs/";
internal const string NEBULA_API_URL = "https://nebula-api.thirdweb.com";
+ internal const string ENGINE_API_URL = "https://engine.thirdweb.com";
internal const string NEBULA_DEFAULT_MODEL = "t0-003";
internal const int DEFAULT_FETCH_TIMEOUT = 120000;
diff --git a/Thirdweb/Thirdweb.Wallets/EngineWallet/EngineWallet.cs b/Thirdweb/Thirdweb.Wallets/EngineWallet/EngineWallet.cs
index 876fcd76..1de38e59 100644
--- a/Thirdweb/Thirdweb.Wallets/EngineWallet/EngineWallet.cs
+++ b/Thirdweb/Thirdweb.Wallets/EngineWallet/EngineWallet.cs
@@ -11,6 +11,7 @@ namespace Thirdweb;
///
/// Enclave based secure cross ecosystem wallet.
///
+[Obsolete("The EngineWallet is deprecated and will be removed in a future version. Please use ServerWallet instead.")]
public partial class EngineWallet : IThirdwebWallet
{
public ThirdwebClient Client { get; }
diff --git a/Thirdweb/Thirdweb.Wallets/ServerWallet/ServerWallet.Types.cs b/Thirdweb/Thirdweb.Wallets/ServerWallet/ServerWallet.Types.cs
new file mode 100644
index 00000000..711d6901
--- /dev/null
+++ b/Thirdweb/Thirdweb.Wallets/ServerWallet/ServerWallet.Types.cs
@@ -0,0 +1,145 @@
+using System.Numerics;
+using Newtonsoft.Json;
+
+namespace Thirdweb;
+
+///
+/// Base class for execution options
+///
+[JsonObject]
+public class ExecutionOptions
+{
+ [JsonProperty("chainId")]
+ public BigInteger? ChainId { get; set; } = null;
+
+ [JsonProperty("idempotencyKey")]
+ public string IdempotencyKey { get; set; }
+}
+
+///
+/// Auto determine execution options
+///
+[JsonObject]
+public class AutoExecutionOptions : ExecutionOptions
+{
+ [JsonProperty("type")]
+ public string Type { get; set; } = "auto";
+
+ [JsonProperty("from")]
+ public string From { get; set; }
+}
+
+///
+/// Externally Owned Account (EOA) execution options
+///
+[JsonObject]
+public class EIP7702ExecutionOptions : ExecutionOptions
+{
+ [JsonProperty("type")]
+ public string Type { get; set; } = "EIP7702";
+
+ [JsonProperty("from")]
+ public string From { get; set; }
+}
+
+///
+/// Externally Owned Account (EOA) execution options
+///
+[JsonObject]
+public class EOAExecutionOptions : ExecutionOptions
+{
+ [JsonProperty("type")]
+ public string Type { get; set; } = "EOA";
+
+ [JsonProperty("from")]
+ public string From { get; set; }
+}
+
+///
+/// ERC-4337 execution options
+///
+[JsonObject]
+public class ERC4337ExecutionOptions : ExecutionOptions
+{
+ [JsonProperty("type")]
+ public string Type { get; set; } = "ERC4337";
+
+ [JsonProperty("signerAddress")]
+ public string SignerAddress { get; set; }
+
+ [JsonProperty("accountSalt")]
+ public string AccountSalt { get; set; }
+
+ [JsonProperty("smartAccountAddress")]
+ public string SmartAccountAddress { get; set; }
+
+ [JsonProperty("entrypointAddress")]
+ public string EntrypointAddress { get; set; }
+
+ [JsonProperty("entrypointVersion")]
+ public string EntrypointVersion { get; set; }
+
+ [JsonProperty("factoryAddress")]
+ public string FactoryAddress { get; set; }
+
+ public ERC4337ExecutionOptions(BigInteger chainId, string signerAddress)
+ {
+ this.ChainId = chainId;
+ this.SignerAddress = signerAddress;
+ }
+}
+
+///
+/// Response wrapper for queued transactions
+///
+[JsonObject]
+internal class QueuedTransactionResponse
+{
+ [JsonProperty("result")]
+ public QueuedTransactionResult Result { get; set; }
+}
+
+///
+/// Result containing the transactions array
+///
+[JsonObject]
+internal class QueuedTransactionResult
+{
+ [JsonProperty("transactions")]
+ public QueuedTransaction[] Transactions { get; set; }
+}
+
+///
+/// Queued transaction response
+///
+[JsonObject]
+internal class QueuedTransaction
+{
+ [JsonProperty("id")]
+ public string Id { get; set; }
+
+ [JsonProperty("batchIndex")]
+ public long BatchIndex { get; set; }
+
+ [JsonProperty("executionParams")]
+ public ExecutionOptions ExecutionParams { get; set; }
+
+ [JsonProperty("transactionParams")]
+ public InnerTransaction[] TransactionParams { get; set; }
+}
+
+///
+/// Inner transaction data
+///
+[JsonObject]
+internal class InnerTransaction
+{
+ [JsonProperty("to")]
+ public string To { get; set; }
+
+ [JsonProperty("data")]
+ public string Data { get; set; }
+
+ [JsonProperty("value")]
+ public string Value { get; set; }
+}
diff --git a/Thirdweb/Thirdweb.Wallets/ServerWallet/ServerWallet.cs b/Thirdweb/Thirdweb.Wallets/ServerWallet/ServerWallet.cs
new file mode 100644
index 00000000..58040591
--- /dev/null
+++ b/Thirdweb/Thirdweb.Wallets/ServerWallet/ServerWallet.cs
@@ -0,0 +1,455 @@
+using System.Numerics;
+using System.Text;
+using Nethereum.ABI.EIP712;
+using Nethereum.Signer;
+using Nethereum.Signer.EIP712;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Thirdweb;
+
+///
+/// Enclave based secure cross ecosystem wallet.
+///
+public partial class ServerWallet : IThirdwebWallet
+{
+ public ThirdwebClient Client { get; }
+ public ThirdwebAccountType AccountType => ThirdwebAccountType.ExternalAccount;
+ public string WalletId => "server";
+
+ private readonly string _walletAddress;
+ private readonly IThirdwebHttpClient _engineClient;
+ private readonly ExecutionOptions _executionOptions;
+
+ private readonly JsonSerializerSettings _jsonSerializerSettings = new() { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.Indented };
+
+ internal ServerWallet(ThirdwebClient client, IThirdwebHttpClient engineClient, string walletAddress, ExecutionOptions executionOptions)
+ {
+ this.Client = client;
+ this._walletAddress = walletAddress;
+ this._engineClient = engineClient;
+ this._executionOptions = executionOptions;
+ }
+
+ #region Creation
+
+ ///
+ /// Creates an instance of the ServerWallet.
+ ///
+ /// The Thirdweb client.
+ /// The label of your created server wallet.
+ /// The execution options for the server wallet, defaults to auto if not passed.
+ /// The vault access token for the server wallet if self-managed.
+ /// A new instance of the ServerWallet.
+ /// Thrown when client or label is null or empty.
+ /// Thrown when no server wallets are found or the specified label does not match any existing server wallet.
+ public static async Task Create(ThirdwebClient client, string label, ExecutionOptions executionOptions = null, string vaultAccessToken = null)
+ {
+ if (client == null)
+ {
+ throw new ArgumentNullException(nameof(client), "Client cannot be null.");
+ }
+
+ if (string.IsNullOrEmpty(label))
+ {
+ throw new ArgumentNullException(nameof(label), "Label cannot be null or empty.");
+ }
+
+ var engineClient = Utils.ReconstructHttpClient(client.HttpClient, new Dictionary { { "X-Secret-Key", client.SecretKey } });
+ if (!string.IsNullOrEmpty(vaultAccessToken))
+ {
+ engineClient.AddHeader("X-Vault-Access-Token", vaultAccessToken);
+ }
+ var serverWalletListResponse = await engineClient.GetAsync($"{Constants.ENGINE_API_URL}/v1/accounts").ConfigureAwait(false);
+ _ = serverWalletListResponse.EnsureSuccessStatusCode();
+ var content = await serverWalletListResponse.Content.ReadAsStringAsync().ConfigureAwait(false);
+
+ var responseObj = JObject.Parse(content);
+ var accounts = responseObj["result"]?["accounts"]?.ToObject(); // TODO: Support pagination
+
+ if (accounts == null || accounts.Count == 0)
+ {
+ throw new InvalidOperationException("No server wallets found in the account.");
+ }
+
+ var matchingAccount =
+ accounts.FirstOrDefault(account => account["label"]?.ToString() == label)
+ ?? throw new InvalidOperationException(
+ $"Server wallet with label '{label}' not found. Available labels: {string.Join(", ", accounts.Select(a => a["label"]?.ToString()).Where(l => !string.IsNullOrEmpty(l)))}"
+ );
+
+ var signerWalletAddress = matchingAccount["address"]?.ToString().ToChecksumAddress();
+ var smartWalletAddress = executionOptions is ERC4337ExecutionOptions ? matchingAccount["smartAccountAddress"]?.ToString() : null;
+ if (string.IsNullOrEmpty(signerWalletAddress))
+ {
+ throw new InvalidOperationException($"Server wallet with label '{label}' found but has no address.");
+ }
+
+ executionOptions ??= new AutoExecutionOptions { IdempotencyKey = Guid.NewGuid().ToString(), From = signerWalletAddress.ToChecksumAddress() };
+ if (executionOptions is ERC4337ExecutionOptions erc4337ExecutionOptions)
+ {
+ erc4337ExecutionOptions.SmartAccountAddress = smartWalletAddress;
+ erc4337ExecutionOptions.SignerAddress = signerWalletAddress;
+ }
+ else if (executionOptions is EIP7702ExecutionOptions eip7702ExecutionOptions)
+ {
+ eip7702ExecutionOptions.From = signerWalletAddress.ToChecksumAddress();
+ }
+ else if (executionOptions is EOAExecutionOptions eoaExecutionOptions)
+ {
+ eoaExecutionOptions.From = signerWalletAddress.ToChecksumAddress();
+ }
+ else if (executionOptions is AutoExecutionOptions autoExecutionOptions)
+ {
+ autoExecutionOptions.From ??= signerWalletAddress.ToChecksumAddress();
+ }
+ else
+ {
+ throw new InvalidOperationException(
+ $"Unsupported execution options type: {executionOptions.GetType().Name}. Supported types are AutoExecutionOptions, EIP7702ExecutionOptions, EOAExecutionOptions, and ERC4337ExecutionOptions."
+ );
+ }
+
+ var wallet = new ServerWallet(client, engineClient, smartWalletAddress ?? signerWalletAddress, executionOptions);
+ Utils.TrackConnection(wallet);
+ return wallet;
+ }
+
+ #endregion
+
+ #region Wallet Specific
+
+ public async Task WaitForTransactionHash(string txid)
+ {
+ var cancellationToken = new CancellationTokenSource();
+ cancellationToken.CancelAfter(this.Client.FetchTimeoutOptions.GetTimeout(TimeoutType.Other));
+ var transactionHash = string.Empty;
+ while (string.IsNullOrEmpty(transactionHash) && !cancellationToken.IsCancellationRequested)
+ {
+ await ThirdwebTask.Delay(100);
+
+ var statusResponse = await this._engineClient.GetAsync($"{Constants.ENGINE_API_URL}/v1/transactions?id={txid}").ConfigureAwait(false);
+ var content = await statusResponse.Content.ReadAsStringAsync();
+ var response = JObject.Parse(content);
+ var transaction = (response["result"]?["transactions"]?.FirstOrDefault()) ?? throw new Exception($"Failed to fetch transaction status for ID: {txid}");
+ var errorMessage = transaction?["errorMessage"]?.ToString();
+ if (!string.IsNullOrEmpty(errorMessage))
+ {
+ throw new Exception($"Sending transaction errored: {errorMessage}");
+ }
+
+ transactionHash = transaction?["transactionHash"]?.ToString();
+ }
+ return transactionHash;
+ }
+
+ private object ToEngineTransaction(ThirdwebTransactionInput transaction)
+ {
+ if (transaction == null)
+ {
+ throw new ArgumentNullException(nameof(transaction));
+ }
+
+ this._executionOptions.ChainId = transaction.ChainId;
+
+ return new
+ {
+ executionOptions = this._executionOptions,
+ @params = new[]
+ {
+ new
+ {
+ to = transaction.To,
+ data = transaction.Data ?? "0x",
+ value = transaction.Value?.HexValue ?? "0x00",
+ authorizationList = transaction.AuthorizationList != null && transaction.AuthorizationList.Count > 0
+ ? transaction
+ .AuthorizationList.Select(authorization => new
+ {
+ chainId = authorization.ChainId.HexToNumber(),
+ address = authorization.Address,
+ nonce = authorization.Nonce.HexToNumber(),
+ yParity = authorization.YParity.HexToNumber(),
+ r = authorization.R,
+ s = authorization.S,
+ })
+ .ToArray()
+ : null,
+ },
+ },
+ };
+ }
+
+ #endregion
+
+ #region IThirdwebWallet
+
+ public Task GetAddress()
+ {
+ if (!string.IsNullOrEmpty(this._walletAddress))
+ {
+ return Task.FromResult(this._walletAddress.ToChecksumAddress());
+ }
+ else
+ {
+ return Task.FromResult(this._walletAddress);
+ }
+ }
+
+ public Task EthSign(byte[] rawMessage)
+ {
+ if (rawMessage == null)
+ {
+ throw new ArgumentNullException(nameof(rawMessage), "Message to sign cannot be null.");
+ }
+
+ throw new NotImplementedException();
+ }
+
+ public Task EthSign(string message)
+ {
+ if (message == null)
+ {
+ throw new ArgumentNullException(nameof(message), "Message to sign cannot be null.");
+ }
+
+ throw new NotImplementedException();
+ }
+
+ public async Task PersonalSign(byte[] rawMessage)
+ {
+ if (rawMessage == null)
+ {
+ throw new ArgumentNullException(nameof(rawMessage), "Message to sign cannot be null.");
+ }
+
+ var url = $"{Constants.ENGINE_API_URL}/v1/sign/message";
+
+ var address = await this.GetAddress();
+
+ var payload = new
+ {
+ signingOptions = new
+ {
+ type = "auto",
+ from = address,
+ chainId = this._executionOptions.ChainId,
+ },
+ @params = new[] { new { message = rawMessage.BytesToHex(), format = "hex" } },
+ };
+
+ var requestContent = new StringContent(JsonConvert.SerializeObject(payload, this._jsonSerializerSettings), Encoding.UTF8, "application/json");
+
+ var response = await this._engineClient.PostAsync(url, requestContent).ConfigureAwait(false);
+ _ = response.EnsureSuccessStatusCode();
+
+ var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ return JObject.Parse(content)["result"]?[0]?["result"]?["signature"].Value();
+ }
+
+ public async Task PersonalSign(string message)
+ {
+ if (string.IsNullOrEmpty(message))
+ {
+ throw new ArgumentNullException(nameof(message), "Message to sign cannot be null.");
+ }
+
+ var url = $"{Constants.ENGINE_API_URL}/v1/sign/message";
+
+ var address = await this.GetAddress();
+
+ var payload = new
+ {
+ signingOptions = new
+ {
+ type = "auto",
+ from = address,
+ chainId = this._executionOptions.ChainId,
+ },
+ @params = new[] { new { message, format = "text" } },
+ };
+
+ var requestContent = new StringContent(JsonConvert.SerializeObject(payload, this._jsonSerializerSettings), Encoding.UTF8, "application/json");
+
+ var response = await this._engineClient.PostAsync(url, requestContent).ConfigureAwait(false);
+ _ = response.EnsureSuccessStatusCode();
+
+ var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ return JObject.Parse(content)["result"]?[0]?["result"]?["signature"].Value();
+ }
+
+ public async Task SignTypedDataV4(string json)
+ {
+ if (string.IsNullOrEmpty(json))
+ {
+ throw new ArgumentNullException(nameof(json), "Json to sign cannot be null.");
+ }
+
+ var processedJson = Utils.PreprocessTypedDataJson(json);
+
+ var url = $"{Constants.ENGINE_API_URL}/v1/sign/typed-data";
+
+ var address = await this.GetAddress();
+
+ var payload = new
+ {
+ signingOptions = new
+ {
+ type = "auto",
+ from = address,
+ chainId = BigInteger.Parse(JObject.Parse(processedJson)["domain"]?["chainId"]?.Value()),
+ },
+ @params = new[] { processedJson },
+ };
+ var requestContent = new StringContent(JsonConvert.SerializeObject(payload, this._jsonSerializerSettings), Encoding.UTF8, "application/json");
+
+ var response = await this._engineClient.PostAsync(url, requestContent).ConfigureAwait(false);
+ _ = response.EnsureSuccessStatusCode();
+
+ var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ return JObject.Parse(content)["result"]?[0]?["result"]?["signature"].Value();
+ }
+
+ public async Task SignTypedDataV4(T data, TypedData typedData)
+ where TDomain : IDomain
+ {
+ if (data == null)
+ {
+ throw new ArgumentNullException(nameof(data), "Data to sign cannot be null.");
+ }
+
+ var safeJson = Utils.ToJsonExternalWalletFriendly(typedData, data);
+ return await this.SignTypedDataV4(safeJson).ConfigureAwait(false);
+ }
+
+ public Task SignTransaction(ThirdwebTransactionInput transaction)
+ {
+ throw new NotImplementedException("SignTransaction is not implemented for ServerWallet. Use SendTransaction instead.");
+ }
+
+ public Task IsConnected()
+ {
+ return Task.FromResult(this._walletAddress != null);
+ }
+
+ public async Task SendTransaction(ThirdwebTransactionInput transaction)
+ {
+ if (transaction == null)
+ {
+ throw new ArgumentNullException(nameof(transaction));
+ }
+
+ var payload = this.ToEngineTransaction(transaction);
+
+ var url = $"{Constants.ENGINE_API_URL}/v1/write/transaction";
+
+ var requestContent = new StringContent(JsonConvert.SerializeObject(payload, this._jsonSerializerSettings), Encoding.UTF8, "application/json");
+
+ var response = await this._engineClient.PostAsync(url, requestContent).ConfigureAwait(false);
+ _ = response.EnsureSuccessStatusCode();
+
+ var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
+ var queuedTransactionResponse = JsonConvert.DeserializeObject(content);
+ var txid = queuedTransactionResponse.Result?.Transactions?.FirstOrDefault()?.Id;
+ if (string.IsNullOrEmpty(txid))
+ {
+ throw new Exception("Failed to queue the transaction. No transaction ID returned.");
+ }
+ return await this.WaitForTransactionHash(txid).ConfigureAwait(false);
+ }
+
+ public async Task ExecuteTransaction(ThirdwebTransactionInput transactionInput)
+ {
+ var hash = await this.SendTransaction(transactionInput);
+ return await ThirdwebTransaction.WaitForTransactionReceipt(this.Client, transactionInput.ChainId.Value, hash).ConfigureAwait(false);
+ }
+
+ public Task Disconnect()
+ {
+ return Task.CompletedTask;
+ }
+
+ public virtual Task RecoverAddressFromEthSign(string message, string signature)
+ {
+ throw new InvalidOperationException();
+ }
+
+ public virtual Task RecoverAddressFromPersonalSign(string message, string signature)
+ {
+ if (string.IsNullOrEmpty(message))
+ {
+ throw new ArgumentNullException(nameof(message), "Message to sign cannot be null.");
+ }
+
+ if (string.IsNullOrEmpty(signature))
+ {
+ throw new ArgumentNullException(nameof(signature), "Signature cannot be null.");
+ }
+
+ var signer = new EthereumMessageSigner();
+ var address = signer.EncodeUTF8AndEcRecover(message, signature);
+ return Task.FromResult(address);
+ }
+
+ public virtual Task RecoverAddressFromTypedDataV4(T data, TypedData typedData, string signature)
+ where TDomain : IDomain
+ {
+ if (data == null)
+ {
+ throw new ArgumentNullException(nameof(data), "Data to sign cannot be null.");
+ }
+
+ if (typedData == null)
+ {
+ throw new ArgumentNullException(nameof(typedData), "Typed data cannot be null.");
+ }
+
+ if (signature == null)
+ {
+ throw new ArgumentNullException(nameof(signature), "Signature cannot be null.");
+ }
+
+ var signer = new Eip712TypedDataSigner();
+ var address = signer.RecoverFromSignatureV4(data, typedData, signature);
+ return Task.FromResult(address);
+ }
+
+ public Task SignAuthorization(BigInteger chainId, string contractAddress, bool willSelfExecute)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task SwitchNetwork(BigInteger chainId)
+ {
+ return Task.CompletedTask;
+ }
+
+ public Task> LinkAccount(
+ IThirdwebWallet walletToLink,
+ string otp = null,
+ bool? isMobile = null,
+ Action browserOpenAction = null,
+ string mobileRedirectScheme = "thirdweb://",
+ IThirdwebBrowser browser = null,
+ BigInteger? chainId = null,
+ string jwt = null,
+ string payload = null,
+ string defaultSessionIdOverride = null,
+ List forceWalletIds = null
+ )
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task> UnlinkAccount(LinkedAccount accountToUnlink)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task> GetLinkedAccounts()
+ {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+}