From 59945f1bd1e7a18a1ae01fb1818f1162fb5b167e Mon Sep 17 00:00:00 2001
From: 0xFirekeeper <0xFirekeeper@gmail.com>
Date: Thu, 24 Oct 2024 00:03:07 +0700
Subject: [PATCH 1/5] InAppWallet -> Enclave
Merge when backend is ready
---
.../EcosystemWallet/EcosystemWallet.cs | 250 ++++----
.../InAppWallet/InAppWallet.Types.cs | 46 ++
.../InAppWallet/InAppWallet.cs | 553 +-----------------
3 files changed, 202 insertions(+), 647 deletions(-)
create mode 100644 Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.Types.cs
diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs
index be760fa2..e8866953 100644
--- a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs
+++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs
@@ -2,6 +2,8 @@
using System.Text;
using System.Web;
using Nethereum.ABI.EIP712;
+using Nethereum.Signer;
+using Nethereum.Signer.EIP712;
using Newtonsoft.Json;
using Thirdweb.EWS;
@@ -10,14 +12,18 @@ namespace Thirdweb;
///
/// Enclave based secure cross ecosystem wallet.
///
-public partial class EcosystemWallet : PrivateKeyWallet
+public partial class EcosystemWallet : IThirdwebWallet
{
- private readonly EmbeddedWallet _embeddedWallet;
- private readonly IThirdwebHttpClient _httpClient;
- private readonly IThirdwebWallet _siweSigner;
- private readonly string _email;
- private readonly string _phoneNumber;
- private readonly string _authProvider;
+ public ThirdwebClient Client { get; }
+ public ThirdwebAccountType AccountType => ThirdwebAccountType.PrivateKeyAccount;
+
+ internal readonly EmbeddedWallet EmbeddedWallet;
+ internal readonly IThirdwebHttpClient HttpClient;
+ internal readonly IThirdwebWallet SiweSigner;
+ internal readonly string Email;
+ internal readonly string PhoneNumber;
+ internal readonly string AuthProvider;
+
private readonly string _ecosystemId;
private readonly string _ecosystemPartnerId;
@@ -28,7 +34,7 @@ public partial class EcosystemWallet : PrivateKeyWallet
private const string EMBEDDED_WALLET_PATH_V1 = $"{EMBEDDED_WALLET_BASE_PATH}/v1";
private const string ENCLAVE_PATH = $"{EMBEDDED_WALLET_PATH_V1}/enclave-wallet";
- private EcosystemWallet(
+ internal EcosystemWallet(
string ecosystemId,
string ecosystemPartnerId,
ThirdwebClient client,
@@ -39,16 +45,16 @@ private EcosystemWallet(
string authProvider,
IThirdwebWallet siweSigner
)
- : base(client, null)
{
+ this.Client = client;
this._ecosystemId = ecosystemId;
this._ecosystemPartnerId = ecosystemPartnerId;
- this._embeddedWallet = embeddedWallet;
- this._httpClient = httpClient;
- this._email = email;
- this._phoneNumber = phoneNumber;
- this._authProvider = authProvider;
- this._siweSigner = siweSigner;
+ this.EmbeddedWallet = embeddedWallet;
+ this.HttpClient = httpClient;
+ this.Email = email;
+ this.PhoneNumber = phoneNumber;
+ this.AuthProvider = authProvider;
+ this.SiweSigner = siweSigner;
}
#region Creation
@@ -59,7 +65,7 @@ public static async Task Create(
string ecosystemPartnerId = null,
string email = null,
string phoneNumber = null,
- AuthProvider authProvider = AuthProvider.Default,
+ AuthProvider authProvider = Thirdweb.AuthProvider.Default,
string storageDirectoryPath = null,
IThirdwebWallet siweSigner = null
)
@@ -69,32 +75,27 @@ public static async Task Create(
throw new ArgumentNullException(nameof(client), "Client cannot be null.");
}
- if (string.IsNullOrEmpty(ecosystemId))
- {
- throw new ArgumentNullException(nameof(ecosystemId), "Ecosystem ID cannot be null or empty.");
- }
-
- if (string.IsNullOrEmpty(email) && string.IsNullOrEmpty(phoneNumber) && authProvider == AuthProvider.Default)
+ if (string.IsNullOrEmpty(email) && string.IsNullOrEmpty(phoneNumber) && authProvider == Thirdweb.AuthProvider.Default)
{
throw new ArgumentException("Email, Phone Number, or OAuth Provider must be provided to login.");
}
var authproviderStr = authProvider switch
{
- AuthProvider.Google => "Google",
- AuthProvider.Apple => "Apple",
- AuthProvider.Facebook => "Facebook",
- AuthProvider.JWT => "JWT",
- AuthProvider.AuthEndpoint => "AuthEndpoint",
- AuthProvider.Discord => "Discord",
- AuthProvider.Farcaster => "Farcaster",
- AuthProvider.Telegram => "Telegram",
- AuthProvider.Siwe => "Siwe",
- AuthProvider.Line => "Line",
- AuthProvider.Guest => "Guest",
- AuthProvider.X => "X",
- AuthProvider.Coinbase => "Coinbase",
- AuthProvider.Default => string.IsNullOrEmpty(email) ? "Phone" : "Email",
+ Thirdweb.AuthProvider.Google => "Google",
+ Thirdweb.AuthProvider.Apple => "Apple",
+ Thirdweb.AuthProvider.Facebook => "Facebook",
+ Thirdweb.AuthProvider.JWT => "JWT",
+ Thirdweb.AuthProvider.AuthEndpoint => "AuthEndpoint",
+ Thirdweb.AuthProvider.Discord => "Discord",
+ Thirdweb.AuthProvider.Farcaster => "Farcaster",
+ Thirdweb.AuthProvider.Telegram => "Telegram",
+ Thirdweb.AuthProvider.Siwe => "Siwe",
+ Thirdweb.AuthProvider.Line => "Line",
+ Thirdweb.AuthProvider.Guest => "Guest",
+ Thirdweb.AuthProvider.X => "X",
+ Thirdweb.AuthProvider.Coinbase => "Coinbase",
+ Thirdweb.AuthProvider.Default => string.IsNullOrEmpty(email) ? "Phone" : "Email",
_ => throw new ArgumentException("Invalid AuthProvider"),
};
@@ -195,16 +196,16 @@ private static async Task GenerateWallet(IThirdwebHttpClient httpClient)
private async Task PostAuth(Server.VerifyResult result)
{
- this._httpClient.AddHeader("Authorization", $"Bearer embedded-wallet-token:{result.AuthToken}");
+ this.HttpClient.AddHeader("Authorization", $"Bearer embedded-wallet-token:{result.AuthToken}");
string address;
if (result.IsNewUser)
{
- address = await GenerateWallet(this._httpClient).ConfigureAwait(false);
+ address = await GenerateWallet(this.HttpClient).ConfigureAwait(false);
}
else
{
- var userStatus = await GetUserStatus(this._httpClient).ConfigureAwait(false);
+ var userStatus = await GetUserStatus(this.HttpClient).ConfigureAwait(false);
if (userStatus.Wallets[0].Type == "enclave")
{
address = userStatus.Wallets[0].Address;
@@ -221,7 +222,7 @@ private async Task PostAuth(Server.VerifyResult result)
}
else
{
- CreateEnclaveSession(this._embeddedWallet, result.AuthToken, this._email, this._phoneNumber, this._authProvider, result.AuthIdentifier);
+ CreateEnclaveSession(this.EmbeddedWallet, result.AuthToken, this.Email, this.PhoneNumber, this.AuthProvider, result.AuthIdentifier);
this._address = address.ToChecksumAddress();
return this._address;
}
@@ -230,7 +231,7 @@ private async Task PostAuth(Server.VerifyResult result)
private async Task MigrateShardToEnclave(Server.VerifyResult authResult)
{
// TODO: For recovery code, allow old encryption keys as overrides to migrate sharded custom auth?
- var (address, encryptedPrivateKeyB64, ivB64, kmsCiphertextB64) = await this._embeddedWallet.GenerateEncryptionDataAsync(authResult.AuthToken, authResult.RecoveryCode).ConfigureAwait(false);
+ var (address, encryptedPrivateKeyB64, ivB64, kmsCiphertextB64) = await this.EmbeddedWallet.GenerateEncryptionDataAsync(authResult.AuthToken, authResult.RecoveryCode).ConfigureAwait(false);
var url = $"{ENCLAVE_PATH}/migrate";
var payload = new
@@ -242,10 +243,10 @@ private async Task MigrateShardToEnclave(Server.VerifyResult authResult)
};
var requestContent = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");
- var response = await this._httpClient.PostAsync(url, requestContent).ConfigureAwait(false);
+ var response = await this.HttpClient.PostAsync(url, requestContent).ConfigureAwait(false);
_ = response.EnsureSuccessStatusCode();
- var userStatus = await GetUserStatus(this._httpClient).ConfigureAwait(false);
+ var userStatus = await GetUserStatus(this.HttpClient).ConfigureAwait(false);
return userStatus.Wallets[0].Address;
}
@@ -255,19 +256,19 @@ private async Task MigrateShardToEnclave(Server.VerifyResult authResult)
public string GetEmail()
{
- return this._email;
+ return this.Email;
}
public string GetPhoneNumber()
{
- return this._phoneNumber;
+ return this.PhoneNumber;
}
#endregion
#region Account Linking
- public override async Task> LinkAccount(
+ public async Task> LinkAccount(
IThirdwebWallet walletToLink,
string otp = null,
bool? isMobile = null,
@@ -300,28 +301,28 @@ public override async Task> LinkAccount(
}
Server.VerifyResult serverRes = null;
- switch (ecosystemWallet._authProvider)
+ switch (ecosystemWallet.AuthProvider)
{
case "Email":
- if (string.IsNullOrEmpty(ecosystemWallet._email))
+ if (string.IsNullOrEmpty(ecosystemWallet.Email))
{
throw new ArgumentException("Cannot link account with an email wallet that does not have an email address.");
}
serverRes = await ecosystemWallet.PreAuth_Otp(otp).ConfigureAwait(false);
break;
case "Phone":
- if (string.IsNullOrEmpty(ecosystemWallet._phoneNumber))
+ if (string.IsNullOrEmpty(ecosystemWallet.PhoneNumber))
{
throw new ArgumentException("Cannot link account with a phone wallet that does not have a phone number.");
}
serverRes = await ecosystemWallet.PreAuth_Otp(otp).ConfigureAwait(false);
break;
case "Siwe":
- if (ecosystemWallet._siweSigner == null || chainId == null)
+ if (ecosystemWallet.SiweSigner == null || chainId == null)
{
throw new ArgumentException("Cannot link account with a Siwe wallet without a signer and chain ID.");
}
- serverRes = await ecosystemWallet.PreAuth_Siwe(ecosystemWallet._siweSigner, chainId.Value).ConfigureAwait(false);
+ serverRes = await ecosystemWallet.PreAuth_Siwe(ecosystemWallet.SiweSigner, chainId.Value).ConfigureAwait(false);
break;
case "JWT":
if (string.IsNullOrEmpty(jwt))
@@ -352,13 +353,13 @@ public override async Task> LinkAccount(
serverRes = await ecosystemWallet.PreAuth_OAuth(isMobile ?? false, browserOpenAction, mobileRedirectScheme, browser).ConfigureAwait(false);
break;
default:
- throw new ArgumentException($"Cannot link account with an unsupported authentication provider:", ecosystemWallet._authProvider);
+ throw new ArgumentException($"Cannot link account with an unsupported authentication provider:", ecosystemWallet.AuthProvider);
}
- var currentAccountToken = this._embeddedWallet.GetSessionData()?.AuthToken;
+ var currentAccountToken = this.EmbeddedWallet.GetSessionData()?.AuthToken;
var authTokenToConnect = serverRes.AuthToken;
- var serverLinkedAccounts = await this._embeddedWallet.LinkAccountAsync(currentAccountToken, authTokenToConnect).ConfigureAwait(false);
+ var serverLinkedAccounts = await this.EmbeddedWallet.LinkAccountAsync(currentAccountToken, authTokenToConnect).ConfigureAwait(false);
var linkedAccounts = new List();
foreach (var linkedAccount in serverLinkedAccounts)
{
@@ -379,10 +380,10 @@ public override async Task> LinkAccount(
return linkedAccounts;
}
- public override async Task> GetLinkedAccounts()
+ public async Task> GetLinkedAccounts()
{
- var currentAccountToken = this._embeddedWallet.GetSessionData()?.AuthToken;
- var serverLinkedAccounts = await this._embeddedWallet.GetLinkedAccountsAsync(currentAccountToken).ConfigureAwait(false);
+ var currentAccountToken = this.EmbeddedWallet.GetSessionData()?.AuthToken;
+ var serverLinkedAccounts = await this.EmbeddedWallet.GetLinkedAccountsAsync(currentAccountToken).ConfigureAwait(false);
var linkedAccounts = new List();
foreach (var linkedAccount in serverLinkedAccounts)
{
@@ -409,16 +410,16 @@ public override async Task> GetLinkedAccounts()
public async Task<(bool isNewUser, bool isNewDevice)> SendOTP()
{
- if (string.IsNullOrEmpty(this._email) && string.IsNullOrEmpty(this._phoneNumber))
+ if (string.IsNullOrEmpty(this.Email) && string.IsNullOrEmpty(this.PhoneNumber))
{
throw new Exception("Email or Phone Number is required for OTP login");
}
try
{
- return this._email == null
- ? await this._embeddedWallet.SendPhoneOtpAsync(this._phoneNumber).ConfigureAwait(false)
- : await this._embeddedWallet.SendEmailOtpAsync(this._email).ConfigureAwait(false);
+ return this.Email == null
+ ? await this.EmbeddedWallet.SendPhoneOtpAsync(this.PhoneNumber).ConfigureAwait(false)
+ : await this.EmbeddedWallet.SendEmailOtpAsync(this.Email).ConfigureAwait(false);
}
catch (Exception e)
{
@@ -434,11 +435,11 @@ public override async Task> GetLinkedAccounts()
}
var serverRes =
- string.IsNullOrEmpty(this._email) && string.IsNullOrEmpty(this._phoneNumber)
+ string.IsNullOrEmpty(this.Email) && string.IsNullOrEmpty(this.PhoneNumber)
? throw new Exception("Email or Phone Number is required for OTP login")
- : this._email == null
- ? await this._embeddedWallet.VerifyPhoneOtpAsync(this._phoneNumber, otp).ConfigureAwait(false)
- : await this._embeddedWallet.VerifyEmailOtpAsync(this._email, otp).ConfigureAwait(false);
+ : this.Email == null
+ ? await this.EmbeddedWallet.VerifyPhoneOtpAsync(this.PhoneNumber, otp).ConfigureAwait(false)
+ : await this.EmbeddedWallet.VerifyEmailOtpAsync(this.Email, otp).ConfigureAwait(false);
return serverRes;
}
@@ -466,14 +467,17 @@ public async Task LoginWithOtp(string otp)
throw new ArgumentNullException(nameof(mobileRedirectScheme), "Mobile redirect scheme cannot be null or empty on this platform.");
}
- var platform = this._httpClient?.Headers?["x-sdk-name"] == "UnitySDK_WebGL" ? "web" : "dotnet";
+ var platform = this.HttpClient?.Headers?["x-sdk-name"] == "UnitySDK_WebGL" ? "web" : "dotnet";
var redirectUrl = isMobile ? mobileRedirectScheme : "http://localhost:8789/";
- var loginUrl = await this._embeddedWallet.FetchHeadlessOauthLoginLinkAsync(this._authProvider, platform).ConfigureAwait(false);
- loginUrl = platform == "web" ? loginUrl : $"{loginUrl}&redirectUrl={redirectUrl}&developerClientId={this.Client.ClientId}&authOption={this._authProvider}";
- loginUrl = $"{loginUrl}&ecosystemId={this._ecosystemId}";
- if (!string.IsNullOrEmpty(this._ecosystemPartnerId))
+ var loginUrl = await this.EmbeddedWallet.FetchHeadlessOauthLoginLinkAsync(this.AuthProvider, platform).ConfigureAwait(false);
+ loginUrl = platform == "web" ? loginUrl : $"{loginUrl}&redirectUrl={redirectUrl}&developerClientId={this.Client.ClientId}&authOption={this.AuthProvider}";
+ if (!string.IsNullOrEmpty(this._ecosystemId))
{
- loginUrl = $"{loginUrl}&ecosystemPartnerId={this._ecosystemPartnerId}";
+ loginUrl = $"{loginUrl}&ecosystemId={this._ecosystemId}";
+ if (!string.IsNullOrEmpty(this._ecosystemPartnerId))
+ {
+ loginUrl = $"{loginUrl}&ecosystemPartnerId={this._ecosystemPartnerId}";
+ }
}
browser ??= new InAppWalletBrowser();
@@ -488,11 +492,11 @@ public async Task LoginWithOtp(string otp)
throw new TimeoutException(browserResult.Error ?? "LoginWithOauth timed out.");
case BrowserStatus.UnknownError:
default:
- throw new Exception($"Failed to login with {this._authProvider}: {browserResult.Status} | {browserResult.Error}");
+ throw new Exception($"Failed to login with {this.AuthProvider}: {browserResult.Status} | {browserResult.Error}");
}
var callbackUrl =
browserResult.Status != BrowserStatus.Success
- ? throw new Exception($"Failed to login with {this._authProvider}: {browserResult.Status} | {browserResult.Error}")
+ ? throw new Exception($"Failed to login with {this.AuthProvider}: {browserResult.Status} | {browserResult.Error}")
: browserResult.CallbackUrl;
while (string.IsNullOrEmpty(callbackUrl))
@@ -514,7 +518,7 @@ public async Task LoginWithOtp(string otp)
authResultJson = queryDict["authResult"];
}
- var serverRes = await this._embeddedWallet.SignInWithOauthAsync(authResultJson).ConfigureAwait(false);
+ var serverRes = await this.EmbeddedWallet.SignInWithOauthAsync(authResultJson).ConfigureAwait(false);
return serverRes;
}
@@ -536,25 +540,25 @@ public async Task LoginWithOauth(
private async Task PreAuth_Siwe(IThirdwebWallet siweSigner, BigInteger chainId)
{
- if (this._siweSigner == null)
+ if (this.SiweSigner == null)
{
throw new ArgumentNullException(nameof(siweSigner), "SIWE Signer wallet cannot be null.");
}
- if (!await this._siweSigner.IsConnected().ConfigureAwait(false))
+ if (!await this.SiweSigner.IsConnected().ConfigureAwait(false))
{
throw new InvalidOperationException("SIWE Signer wallet must be connected as this operation requires it to sign a message.");
}
var serverRes =
- chainId <= 0 ? throw new ArgumentException("Chain ID must be greater than 0.", nameof(chainId)) : await this._embeddedWallet.SignInWithSiweAsync(siweSigner, chainId).ConfigureAwait(false);
+ chainId <= 0 ? throw new ArgumentException("Chain ID must be greater than 0.", nameof(chainId)) : await this.EmbeddedWallet.SignInWithSiweAsync(siweSigner, chainId).ConfigureAwait(false);
return serverRes;
}
public async Task LoginWithSiwe(BigInteger chainId)
{
- var serverRes = await this.PreAuth_Siwe(this._siweSigner, chainId).ConfigureAwait(false);
+ var serverRes = await this.PreAuth_Siwe(this.SiweSigner, chainId).ConfigureAwait(false);
return await this.PostAuth(serverRes).ConfigureAwait(false);
}
@@ -564,7 +568,7 @@ public async Task LoginWithSiwe(BigInteger chainId)
private async Task PreAuth_Guest()
{
- var sessionData = this._embeddedWallet.GetSessionData();
+ var sessionData = this.EmbeddedWallet.GetSessionData();
string sessionId;
if (sessionData != null && sessionData.AuthProvider == "Guest" && !string.IsNullOrEmpty(sessionData.AuthIdentifier))
{
@@ -574,7 +578,7 @@ public async Task LoginWithSiwe(BigInteger chainId)
{
sessionId = Guid.NewGuid().ToString();
}
- var serverRes = await this._embeddedWallet.SignInWithGuestAsync(sessionId).ConfigureAwait(false);
+ var serverRes = await this.EmbeddedWallet.SignInWithGuestAsync(sessionId).ConfigureAwait(false);
return serverRes;
}
@@ -590,12 +594,12 @@ public async Task LoginWithGuest()
private async Task PreAuth_JWT(string jwt)
{
- return string.IsNullOrEmpty(jwt) ? throw new ArgumentException(nameof(jwt), "JWT cannot be null or empty.") : await this._embeddedWallet.SignInWithJwtAsync(jwt).ConfigureAwait(false);
+ return string.IsNullOrEmpty(jwt) ? throw new ArgumentException(nameof(jwt), "JWT cannot be null or empty.") : await this.EmbeddedWallet.SignInWithJwtAsync(jwt).ConfigureAwait(false);
}
public async Task LoginWithJWT(string jwt)
{
- var serverRes = string.IsNullOrEmpty(jwt) ? throw new ArgumentException("JWT cannot be null or empty.", nameof(jwt)) : await this._embeddedWallet.SignInWithJwtAsync(jwt).ConfigureAwait(false);
+ var serverRes = string.IsNullOrEmpty(jwt) ? throw new ArgumentException("JWT cannot be null or empty.", nameof(jwt)) : await this.EmbeddedWallet.SignInWithJwtAsync(jwt).ConfigureAwait(false);
return await this.PostAuth(serverRes).ConfigureAwait(false);
}
@@ -608,7 +612,7 @@ public async Task LoginWithJWT(string jwt)
{
var serverRes = string.IsNullOrEmpty(payload)
? throw new ArgumentNullException(nameof(payload), "Payload cannot be null or empty.")
- : await this._embeddedWallet.SignInWithAuthEndpointAsync(payload).ConfigureAwait(false);
+ : await this.EmbeddedWallet.SignInWithAuthEndpointAsync(payload).ConfigureAwait(false);
return serverRes;
}
@@ -623,7 +627,7 @@ public async Task LoginWithAuthEndpoint(string payload)
#region IThirdwebWallet
- public override Task GetAddress()
+ public Task GetAddress()
{
if (!string.IsNullOrEmpty(this._address))
{
@@ -635,7 +639,7 @@ public override Task GetAddress()
}
}
- public override Task EthSign(byte[] rawMessage)
+ public Task EthSign(byte[] rawMessage)
{
if (rawMessage == null)
{
@@ -645,7 +649,7 @@ public override Task EthSign(byte[] rawMessage)
throw new NotImplementedException();
}
- public override Task EthSign(string message)
+ public Task EthSign(string message)
{
if (message == null)
{
@@ -655,7 +659,7 @@ public override Task EthSign(string message)
throw new NotImplementedException();
}
- public override async Task PersonalSign(byte[] rawMessage)
+ public async Task PersonalSign(byte[] rawMessage)
{
if (rawMessage == null)
{
@@ -667,7 +671,7 @@ public override async Task PersonalSign(byte[] rawMessage)
var requestContent = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");
- var response = await this._httpClient.PostAsync(url, requestContent).ConfigureAwait(false);
+ var response = await this.HttpClient.PostAsync(url, requestContent).ConfigureAwait(false);
_ = response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
@@ -675,7 +679,7 @@ public override async Task PersonalSign(byte[] rawMessage)
return res.Signature;
}
- public override async Task PersonalSign(string message)
+ public async Task PersonalSign(string message)
{
if (string.IsNullOrEmpty(message))
{
@@ -687,7 +691,7 @@ public override async Task PersonalSign(string message)
var requestContent = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");
- var response = await this._httpClient.PostAsync(url, requestContent).ConfigureAwait(false);
+ var response = await this.HttpClient.PostAsync(url, requestContent).ConfigureAwait(false);
_ = response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
@@ -695,7 +699,7 @@ public override async Task PersonalSign(string message)
return res.Signature;
}
- public override async Task SignTypedDataV4(string json)
+ public async Task SignTypedDataV4(string json)
{
if (string.IsNullOrEmpty(json))
{
@@ -706,7 +710,7 @@ public override async Task SignTypedDataV4(string json)
var requestContent = new StringContent(json, Encoding.UTF8, "application/json");
- var response = await this._httpClient.PostAsync(url, requestContent).ConfigureAwait(false);
+ var response = await this.HttpClient.PostAsync(url, requestContent).ConfigureAwait(false);
_ = response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
@@ -714,7 +718,8 @@ public override async Task SignTypedDataV4(string json)
return res.Signature;
}
- public override async Task SignTypedDataV4(T data, TypedData typedData)
+ public async Task SignTypedDataV4(T data, TypedData typedData)
+ where TDomain : IDomain
{
if (data == null)
{
@@ -725,7 +730,7 @@ public override async Task SignTypedDataV4(T data, TypedData
return await this.SignTypedDataV4(safeJson).ConfigureAwait(false);
}
- public override async Task SignTransaction(ThirdwebTransactionInput transaction)
+ public async Task SignTransaction(ThirdwebTransactionInput transaction)
{
if (transaction == null)
{
@@ -748,7 +753,7 @@ public override async Task SignTransaction(ThirdwebTransactionInput tran
var requestContent = new StringContent(JsonConvert.SerializeObject(payload), Encoding.UTF8, "application/json");
- var response = await this._httpClient.PostAsync(url, requestContent).ConfigureAwait(false);
+ var response = await this.HttpClient.PostAsync(url, requestContent).ConfigureAwait(false);
_ = response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
@@ -756,25 +761,70 @@ public override async Task SignTransaction(ThirdwebTransactionInput tran
return res.Signature;
}
- public override Task IsConnected()
+ public Task IsConnected()
{
return Task.FromResult(this._address != null);
}
- public override Task SendTransaction(ThirdwebTransactionInput transaction)
+ public Task SendTransaction(ThirdwebTransactionInput transaction)
{
throw new InvalidOperationException("SendTransaction is not supported for Ecosystem Wallets, please use the unified Contract or ThirdwebTransaction APIs.");
}
- public override Task ExecuteTransaction(ThirdwebTransactionInput transactionInput)
+ public Task ExecuteTransaction(ThirdwebTransactionInput transactionInput)
{
throw new InvalidOperationException("ExecuteTransaction is not supported for Ecosystem Wallets, please use the unified Contract or ThirdwebTransaction APIs.");
}
- public override async Task Disconnect()
+ public async Task Disconnect()
{
this._address = null;
- await this._embeddedWallet.SignOutAsync().ConfigureAwait(false);
+ await this.EmbeddedWallet.SignOutAsync().ConfigureAwait(false);
+ }
+
+ 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);
}
#endregion
diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.Types.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.Types.cs
new file mode 100644
index 00000000..c18e6e3b
--- /dev/null
+++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.Types.cs
@@ -0,0 +1,46 @@
+using Newtonsoft.Json;
+
+namespace Thirdweb;
+
+///
+/// Specifies the authentication providers available for the in-app wallet.
+///
+public enum AuthProvider
+{
+ Default,
+ Google,
+ Apple,
+ Facebook,
+ JWT,
+ AuthEndpoint,
+ Discord,
+ Farcaster,
+ Telegram,
+ Siwe,
+ Line,
+ Guest,
+ X,
+ Coinbase
+}
+
+///
+/// Represents a linked account.
+///
+public struct LinkedAccount
+{
+ public string Type { get; set; }
+ public LinkedAccountDetails Details { get; set; }
+
+ public struct LinkedAccountDetails
+ {
+ public string Email { get; set; }
+ public string Address { get; set; }
+ public string Phone { get; set; }
+ public string Id { get; set; }
+ }
+
+ public override readonly string ToString()
+ {
+ return JsonConvert.SerializeObject(this);
+ }
+}
diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.cs
index 51b3e661..46053ffa 100644
--- a/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.cs
+++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.cs
@@ -1,71 +1,14 @@
-using System.Numerics;
-using System.Web;
-using Nethereum.Signer;
-using Newtonsoft.Json;
using Thirdweb.EWS;
namespace Thirdweb;
///
-/// Specifies the authentication providers available for the in-app wallet.
+/// Represents an in-app wallet that supports email, phone, social, SIWE and custom authentication.
///
-public enum AuthProvider
+public class InAppWallet : EcosystemWallet
{
- Default,
- Google,
- Apple,
- Facebook,
- JWT,
- AuthEndpoint,
- Discord,
- Farcaster,
- Telegram,
- Siwe,
- Line,
- Guest,
- X,
- Coinbase
-}
-
-public struct LinkedAccount
-{
- public string Type { get; set; }
- public LinkedAccountDetails Details { get; set; }
-
- public struct LinkedAccountDetails
- {
- public string Email { get; set; }
- public string Address { get; set; }
- public string Phone { get; set; }
- public string Id { get; set; }
- }
-
- public override readonly string ToString()
- {
- return JsonConvert.SerializeObject(this);
- }
-}
-
-///
-/// Represents an in-app wallet that extends the functionality of a private key wallet.
-///
-public class InAppWallet : PrivateKeyWallet
-{
- internal EmbeddedWallet EmbeddedWallet;
- internal string Email;
- internal string PhoneNumber;
- internal string AuthProvider;
- internal IThirdwebWallet SiweSigner;
-
- internal InAppWallet(ThirdwebClient client, string email, string phoneNumber, string authProvider, EmbeddedWallet embeddedWallet, EthECKey ecKey, IThirdwebWallet siweSigner)
- : base(client, ecKey)
- {
- this.Email = email?.ToLower();
- this.PhoneNumber = phoneNumber;
- this.EmbeddedWallet = embeddedWallet;
- this.AuthProvider = authProvider;
- this.SiweSigner = siweSigner;
- }
+ internal InAppWallet(ThirdwebClient client, EmbeddedWallet embeddedWallet, IThirdwebHttpClient httpClient, string email, string phoneNumber, string authProvider, IThirdwebWallet siweSigner)
+ : base(null, null, client, embeddedWallet, httpClient, email, phoneNumber, authProvider, siweSigner) { }
///
/// Creates a new instance of the class.
@@ -87,492 +30,8 @@ public static async Task Create(
IThirdwebWallet siweSigner = null
)
{
- if (string.IsNullOrEmpty(email) && string.IsNullOrEmpty(phoneNumber) && authProvider == Thirdweb.AuthProvider.Default)
- {
- throw new ArgumentException("Email, Phone Number, or OAuth Provider must be provided to login.");
- }
-
- var authproviderStr = authProvider switch
- {
- Thirdweb.AuthProvider.Google => "Google",
- Thirdweb.AuthProvider.Apple => "Apple",
- Thirdweb.AuthProvider.Facebook => "Facebook",
- Thirdweb.AuthProvider.JWT => "JWT",
- Thirdweb.AuthProvider.AuthEndpoint => "AuthEndpoint",
- Thirdweb.AuthProvider.Discord => "Discord",
- Thirdweb.AuthProvider.Farcaster => "Farcaster",
- Thirdweb.AuthProvider.Telegram => "Telegram",
- Thirdweb.AuthProvider.Siwe => "Siwe",
- Thirdweb.AuthProvider.Line => "Line",
- Thirdweb.AuthProvider.Guest => "Guest",
- Thirdweb.AuthProvider.X => "X",
- Thirdweb.AuthProvider.Coinbase => "Coinbase",
- Thirdweb.AuthProvider.Default => string.IsNullOrEmpty(email) ? "Phone" : "Email",
- _ => throw new ArgumentException("Invalid AuthProvider"),
- };
-
storageDirectoryPath ??= Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Thirdweb", "InAppWallet");
- var embeddedWallet = new EmbeddedWallet(client, storageDirectoryPath);
- EthECKey ecKey;
- try
- {
- var user = await embeddedWallet.GetUserAsync(email, phoneNumber, authproviderStr).ConfigureAwait(false);
- ecKey = new EthECKey(user.Account.PrivateKey);
- }
- catch
- {
- ecKey = null;
- }
- return new InAppWallet(client, email, phoneNumber, authproviderStr, embeddedWallet, ecKey, siweSigner);
- }
-
- ///
- /// Disconnects the wallet.
- ///
- /// A task representing the asynchronous operation.
- public override async Task Disconnect()
- {
- await base.Disconnect().ConfigureAwait(false);
- await this.EmbeddedWallet.SignOutAsync().ConfigureAwait(false);
- }
-
- ///
- /// Gets the email associated with the in-app wallet.
- ///
- /// A task representing the asynchronous operation. The task result contains the email address.
- public Task GetEmail()
- {
- return Task.FromResult(this.Email);
- }
-
- ///
- /// Gets the phone number associated with the in-app wallet.
- ///
- /// A task representing the asynchronous operation. The task result contains the phone number.
- public Task GetPhoneNumber()
- {
- return Task.FromResult(this.PhoneNumber);
- }
-
- #region Account Linking
-
- public override async 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
- )
- {
- if (!await this.IsConnected().ConfigureAwait(false))
- {
- throw new InvalidOperationException("Cannot link account with a wallet that is not connected. Please login to the wallet before linking other wallets.");
- }
-
- if (walletToLink == null)
- {
- throw new ArgumentNullException(nameof(walletToLink), "Wallet to link cannot be null.");
- }
-
- if (walletToLink is not InAppWallet inAppWallet)
- {
- throw new ArgumentException("Cannot link account with a wallet that is not an InAppWallet.");
- }
-
- if (await inAppWallet.IsConnected().ConfigureAwait(false))
- {
- throw new ArgumentException("Cannot link account with a wallet that is already created and connected.");
- }
-
- Server.VerifyResult serverRes = null;
- switch (inAppWallet.AuthProvider)
- {
- case "Email":
- if (string.IsNullOrEmpty(inAppWallet.Email))
- {
- throw new ArgumentException("Cannot link account with an email wallet that does not have an email address.");
- }
- serverRes = await inAppWallet.PreAuth_Otp(otp).ConfigureAwait(false);
- break;
- case "Phone":
- if (string.IsNullOrEmpty(inAppWallet.PhoneNumber))
- {
- throw new ArgumentException("Cannot link account with a phone wallet that does not have a phone number.");
- }
- serverRes = await inAppWallet.PreAuth_Otp(otp).ConfigureAwait(false);
- break;
- case "Siwe":
- if (inAppWallet.SiweSigner == null || chainId == null)
- {
- throw new ArgumentException("Cannot link account with a Siwe wallet without a signer and chain ID.");
- }
- serverRes = await inAppWallet.PreAuth_Siwe(inAppWallet.SiweSigner, chainId.Value).ConfigureAwait(false);
- break;
- case "JWT":
- if (string.IsNullOrEmpty(jwt))
- {
- throw new ArgumentException("Cannot link account with a JWT wallet without a JWT.");
- }
- serverRes = await inAppWallet.PreAuth_JWT(jwt).ConfigureAwait(false);
- break;
- case "AuthEndpoint":
- if (string.IsNullOrEmpty(payload))
- {
- throw new ArgumentException("Cannot link account with an AuthEndpoint wallet without a payload.");
- }
- serverRes = await inAppWallet.PreAuth_AuthEndpoint(payload).ConfigureAwait(false);
- break;
- case "Guest":
- serverRes = await inAppWallet.PreAuth_Guest().ConfigureAwait(false);
- break;
- case "Google":
- case "Apple":
- case "Facebook":
- case "Discord":
- case "Farcaster":
- case "Telegram":
- case "Line":
- case "X":
- case "Coinbase":
- serverRes = await inAppWallet.PreAuth_OAuth(isMobile ?? false, browserOpenAction, mobileRedirectScheme, browser).ConfigureAwait(false);
- break;
- default:
- throw new ArgumentException($"Cannot link account with an unsupported authentication provider:", inAppWallet.AuthProvider);
- }
-
- var currentAccountToken = this.EmbeddedWallet.GetSessionData()?.AuthToken;
- var authTokenToConnect = serverRes.AuthToken;
-
- var serverLinkedAccounts = await this.EmbeddedWallet.LinkAccountAsync(currentAccountToken, authTokenToConnect).ConfigureAwait(false);
- var linkedAccounts = new List();
- foreach (var linkedAccount in serverLinkedAccounts)
- {
- linkedAccounts.Add(
- new LinkedAccount
- {
- Type = linkedAccount.Type,
- Details = new LinkedAccount.LinkedAccountDetails
- {
- Email = linkedAccount.Details?.Email,
- Address = linkedAccount.Details?.Address,
- Phone = linkedAccount.Details?.Phone,
- Id = linkedAccount.Details?.Id
- }
- }
- );
- }
- return linkedAccounts;
- }
-
- public override async Task> GetLinkedAccounts()
- {
- var currentAccountToken = this.EmbeddedWallet.GetSessionData()?.AuthToken;
- var serverLinkedAccounts = await this.EmbeddedWallet.GetLinkedAccountsAsync(currentAccountToken).ConfigureAwait(false);
- var linkedAccounts = new List();
- foreach (var linkedAccount in serverLinkedAccounts)
- {
- linkedAccounts.Add(
- new LinkedAccount
- {
- Type = linkedAccount.Type,
- Details = new LinkedAccount.LinkedAccountDetails
- {
- Email = linkedAccount.Details?.Email,
- Address = linkedAccount.Details?.Address,
- Phone = linkedAccount.Details?.Phone,
- Id = linkedAccount.Details?.Id
- }
- }
- );
- }
- return linkedAccounts;
- }
-
- #endregion
-
- #region OAuth2 Flow
-
- ///
- /// Logs in with OAuth2.
- ///
- /// Indicates if the login is from a mobile device.
- /// The action to open the browser.
- /// The mobile redirect scheme.
- /// The browser instance.
- /// The cancellation token.
- /// A task representing the asynchronous operation. The task result contains the login result.
- /// Thrown when required parameters are not provided.
- /// Thrown when the operation is canceled.
- /// Thrown when the operation times out.
- public virtual async Task LoginWithOauth(
- bool isMobile,
- Action browserOpenAction,
- string mobileRedirectScheme = "thirdweb://",
- IThirdwebBrowser browser = null,
- CancellationToken cancellationToken = default
- )
- {
- var serverRes = await this.PreAuth_OAuth(isMobile, browserOpenAction, mobileRedirectScheme, browser, cancellationToken).ConfigureAwait(false);
- return await this.PostAuth(serverRes, null, this.AuthProvider).ConfigureAwait(false);
- }
-
- private async Task PreAuth_OAuth(
- bool isMobile,
- Action browserOpenAction,
- string mobileRedirectScheme = "thirdweb://",
- IThirdwebBrowser browser = null,
- CancellationToken cancellationToken = default
- )
- {
- if (isMobile && string.IsNullOrEmpty(mobileRedirectScheme))
- {
- throw new ArgumentNullException(nameof(mobileRedirectScheme), "Mobile redirect scheme cannot be null or empty on this platform.");
- }
-
- var platform = this.Client.HttpClient?.Headers?["x-sdk-name"] == "UnitySDK_WebGL" ? "web" : "dotnet";
- var redirectUrl = isMobile ? mobileRedirectScheme : "http://localhost:8789/";
- var loginUrl = await this.EmbeddedWallet.FetchHeadlessOauthLoginLinkAsync(this.AuthProvider, platform);
- loginUrl = platform == "web" ? loginUrl : $"{loginUrl}&redirectUrl={redirectUrl}&developerClientId={this.Client.ClientId}&authOption={this.AuthProvider}";
-
- browser ??= new InAppWalletBrowser();
- var browserResult = await browser.Login(this.Client, loginUrl, redirectUrl, browserOpenAction, cancellationToken);
- switch (browserResult.Status)
- {
- case BrowserStatus.Success:
- break;
- case BrowserStatus.UserCanceled:
- throw new TaskCanceledException(browserResult.Error ?? "LoginWithOauth was cancelled.");
- case BrowserStatus.Timeout:
- throw new TimeoutException(browserResult.Error ?? "LoginWithOauth timed out.");
- case BrowserStatus.UnknownError:
- default:
- throw new Exception($"Failed to login with {this.AuthProvider}: {browserResult.Status} | {browserResult.Error}");
- }
- var callbackUrl =
- browserResult.Status != BrowserStatus.Success
- ? throw new Exception($"Failed to login with {this.AuthProvider}: {browserResult.Status} | {browserResult.Error}")
- : browserResult.CallbackUrl;
-
- while (string.IsNullOrEmpty(callbackUrl))
- {
- if (cancellationToken.IsCancellationRequested)
- {
- throw new TaskCanceledException("LoginWithOauth was cancelled.");
- }
- await ThirdwebTask.Delay(100, cancellationToken).ConfigureAwait(false);
- }
-
- var authResultJson = callbackUrl;
- if (!authResultJson.StartsWith('{'))
- {
- var decodedUrl = HttpUtility.UrlDecode(callbackUrl);
- Uri uri = new(decodedUrl);
- var queryString = uri.Query;
- var queryDict = HttpUtility.ParseQueryString(queryString);
- authResultJson = queryDict["authResult"];
- }
-
- return await this.EmbeddedWallet.SignInWithOauthAsync(authResultJson);
- }
-
- #endregion
-
- #region OTP Flow
-
- ///
- /// Sends an OTP to the user's email or phone number.
- ///
- /// A task representing the asynchronous operation. The task result contains a boolean indicating if the user is new and a boolean indicating if the device is new.
- /// Thrown when email or phone number is not provided.
- public async Task<(bool isNewUser, bool isNewDevice)> SendOTP()
- {
- if (string.IsNullOrEmpty(this.Email) && string.IsNullOrEmpty(this.PhoneNumber))
- {
- throw new Exception("Email or Phone Number is required for OTP login");
- }
-
- try
- {
- return this.Email == null
- ? await this.EmbeddedWallet.SendPhoneOtpAsync(this.PhoneNumber).ConfigureAwait(false)
- : await this.EmbeddedWallet.SendEmailOtpAsync(this.Email).ConfigureAwait(false);
- }
- catch (Exception e)
- {
- throw new Exception("Failed to send OTP", e);
- }
- }
-
- ///
- /// Submits the OTP for verification.
- ///
- /// The OTP to submit.
- /// A task representing the asynchronous operation. The task result contains the address and a boolean indicating if retry is possible.
- /// Thrown when OTP is not provided.
- /// Thrown when email or phone number is not provided.
- public async Task<(string address, bool canRetry)> LoginWithOtp(string otp)
- {
- if (string.IsNullOrEmpty(otp))
- {
- throw new ArgumentNullException(nameof(otp), "OTP cannot be null or empty.");
- }
-
- var serverRes = await this.PreAuth_Otp(otp).ConfigureAwait(false);
- try
- {
- return (await this.PostAuth(serverRes, null, this.Email == null ? "Email" : "Phone").ConfigureAwait(false), false);
- }
- catch (VerificationException e)
- {
- return (null, e.CanRetry);
- }
- }
-
- private async Task PreAuth_Otp(string otp)
- {
- if (string.IsNullOrEmpty(otp))
- {
- throw new ArgumentNullException(nameof(otp), "OTP cannot be null or empty.");
- }
-
- return string.IsNullOrEmpty(this.Email) && string.IsNullOrEmpty(this.PhoneNumber)
- ? throw new Exception("Email or Phone Number is required for OTP login")
- : this.Email == null
- ? await this.EmbeddedWallet.VerifyPhoneOtpAsync(this.PhoneNumber, otp).ConfigureAwait(false)
- : await this.EmbeddedWallet.VerifyEmailOtpAsync(this.Email, otp).ConfigureAwait(false);
- }
-
- #endregion
-
- #region SIWE Flow
-
- ///
- /// Logs in with SIWE (Sign-In with Ethereum).
- ///
- /// The chain ID to use for signing the SIWE payload
- /// A task representing the asynchronous operation. The task result contains the address.
- /// Thrown when external wallet is not provided.
- /// Thrown when the external wallet is not connected.
- /// Thrown when chain ID is invalid.
- public async Task LoginWithSiwe(BigInteger chainId)
- {
- var serverRes = await this.PreAuth_Siwe(this.SiweSigner, chainId).ConfigureAwait(false);
- return await this.PostAuth(serverRes, null, "Siwe").ConfigureAwait(false);
- }
-
- private async Task PreAuth_Siwe(IThirdwebWallet signer, BigInteger chainId)
- {
- if (signer == null)
- {
- throw new ArgumentNullException(nameof(signer), "SIWE Signer wallet cannot be null.");
- }
-
- if (!await signer.IsConnected().ConfigureAwait(false))
- {
- throw new InvalidOperationException("SIWE Signer wallet must be connected as this operation requires it to sign a message.");
- }
-
- return chainId <= 0 ? throw new ArgumentException(nameof(chainId), "Chain ID must be greater than 0.") : await this.EmbeddedWallet.SignInWithSiweAsync(signer, chainId).ConfigureAwait(false);
- }
-
- #endregion
-
- #region Guest
-
- public async Task LoginWithGuest()
- {
- var serverRes = await this.PreAuth_Guest().ConfigureAwait(false);
- return await this.PostAuth(serverRes, null, "Guest").ConfigureAwait(false);
- }
-
- private async Task PreAuth_Guest()
- {
- var sessionData = this.EmbeddedWallet.GetSessionData();
- string sessionId;
- if (sessionData != null && sessionData.AuthProvider == "Guest" && !string.IsNullOrEmpty(sessionData.AuthIdentifier))
- {
- sessionId = sessionData.AuthIdentifier;
- }
- else
- {
- sessionId = Guid.NewGuid().ToString();
- }
- var serverRes = await this.EmbeddedWallet.SignInWithGuestAsync(sessionId).ConfigureAwait(false);
- return serverRes;
- }
-
- #endregion
-
- #region JWT Flow
-
- ///
- /// Logs in with a JWT.
- ///
- /// The JWT to use for authentication.
- /// The encryption key to use.
- /// A task representing the asynchronous operation. The task result contains the address.
- /// Thrown when JWT or encryption key is not provided.
- /// Thrown when the login fails.
- public async Task LoginWithJWT(string jwt, string encryptionKey)
- {
- if (string.IsNullOrEmpty(encryptionKey))
- {
- throw new ArgumentException(nameof(encryptionKey), "Encryption key cannot be null or empty.");
- }
-
- var serverRes = await this.PreAuth_JWT(jwt).ConfigureAwait(false);
- return await this.PostAuth(serverRes, encryptionKey, "JWT").ConfigureAwait(false);
- }
-
- private async Task PreAuth_JWT(string jwt)
- {
- return string.IsNullOrEmpty(jwt) ? throw new ArgumentException(nameof(jwt), "JWT cannot be null or empty.") : await this.EmbeddedWallet.SignInWithJwtAsync(jwt).ConfigureAwait(false);
- }
-
- #endregion
-
- #region Auth Endpoint Flow
-
- ///
- /// Logs in with an authentication endpoint.
- ///
- /// The payload to use for authentication.
- /// The encryption key to use.
- /// A task representing the asynchronous operation. The task result contains the login result.
- /// Thrown when payload or encryption key is not provided.
- /// Thrown when the login fails.
- public async Task LoginWithAuthEndpoint(string payload, string encryptionKey)
- {
- if (string.IsNullOrEmpty(encryptionKey))
- {
- throw new ArgumentException(nameof(encryptionKey), "Encryption key cannot be null or empty.");
- }
-
- var serverRes = await this.PreAuth_AuthEndpoint(payload).ConfigureAwait(false);
- return await this.PostAuth(serverRes, encryptionKey, "AuthEndpoint").ConfigureAwait(false);
- }
-
- private async Task PreAuth_AuthEndpoint(string payload)
- {
- return string.IsNullOrEmpty(payload)
- ? throw new ArgumentException(nameof(payload), "Payload cannot be null or empty.")
- : await this.EmbeddedWallet.SignInWithAuthEndpointAsync(payload).ConfigureAwait(false);
- }
-
- #endregion
-
- private async Task PostAuth(Server.VerifyResult serverRes, string encryptionKey, string authProvider)
- {
- var res = await this.EmbeddedWallet.PostAuthSetup(serverRes, encryptionKey, authProvider).ConfigureAwait(false);
- if (res.User == null)
- {
- throw new Exception($"Failed to login with {authProvider}");
- }
- this.EcKey = new EthECKey(res.User.Account.PrivateKey);
- return await this.GetAddress().ConfigureAwait(false);
+ var ecoWallet = await Create(client, null, null, email, phoneNumber, authProvider, storageDirectoryPath, siweSigner);
+ return new InAppWallet(ecoWallet.Client, ecoWallet.EmbeddedWallet, ecoWallet.HttpClient, ecoWallet.Email, ecoWallet.PhoneNumber, ecoWallet.AuthProvider, ecoWallet.SiweSigner);
}
}
From 13624058ad9dccacb074ba3c504174177b777b1c Mon Sep 17 00:00:00 2001
From: 0xFirekeeper <0xFirekeeper@gmail.com>
Date: Tue, 29 Oct 2024 06:25:31 +0700
Subject: [PATCH 2/5] enclave iaw unlocked, cleanup
---
.../EcosystemWallet/EcosystemWallet.Types.cs | 2 +-
.../EcosystemWallet/EcosystemWallet.cs | 47 +++++++----
.../EmbeddedWallet.Authentication/Server.cs | 43 ----------
.../EmbeddedWallet/EmbeddedWallet.EmailOTP.cs | 5 +-
.../EmbeddedWallet/EmbeddedWallet.Misc.cs | 81 -------------------
.../EmbeddedWallet/EmbeddedWallet.PhoneOTP.cs | 5 +-
.../InAppWallet/InAppWallet.cs | 27 ++++++-
7 files changed, 59 insertions(+), 151 deletions(-)
diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.Types.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.Types.cs
index 7eaada74..431ef95c 100644
--- a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.Types.cs
+++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.Types.cs
@@ -4,7 +4,7 @@ namespace Thirdweb;
public partial class EcosystemWallet
{
- internal class EnclaveUserStatusResponse
+ public class EnclaveUserStatusResponse
{
[JsonProperty("linkedAccounts")]
internal List LinkedAccounts { get; set; }
diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs
index e8866953..f52aebc0 100644
--- a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs
+++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs
@@ -24,11 +24,11 @@ public partial class EcosystemWallet : IThirdwebWallet
internal readonly string PhoneNumber;
internal readonly string AuthProvider;
+ internal string Address;
+
private readonly string _ecosystemId;
private readonly string _ecosystemPartnerId;
- private string _address;
-
private const string EMBEDDED_WALLET_BASE_PATH = "https://embedded-wallet.thirdweb.com/api";
private const string EMBEDDED_WALLET_PATH_2024 = $"{EMBEDDED_WALLET_BASE_PATH}/2024-05-05";
private const string EMBEDDED_WALLET_PATH_V1 = $"{EMBEDDED_WALLET_BASE_PATH}/v1";
@@ -128,12 +128,12 @@ public static async Task Create(
try
{
var userAddress = await ResumeEnclaveSession(enclaveHttpClient, embeddedWallet, email, phoneNumber, authproviderStr).ConfigureAwait(false);
- return new EcosystemWallet(ecosystemId, ecosystemPartnerId, client, embeddedWallet, enclaveHttpClient, email, phoneNumber, authproviderStr, siweSigner) { _address = userAddress };
+ return new EcosystemWallet(ecosystemId, ecosystemPartnerId, client, embeddedWallet, enclaveHttpClient, email, phoneNumber, authproviderStr, siweSigner) { Address = userAddress };
}
catch
{
enclaveHttpClient.RemoveHeader("Authorization");
- return new EcosystemWallet(ecosystemId, ecosystemPartnerId, client, embeddedWallet, enclaveHttpClient, email, phoneNumber, authproviderStr, siweSigner) { _address = null };
+ return new EcosystemWallet(ecosystemId, ecosystemPartnerId, client, embeddedWallet, enclaveHttpClient, email, phoneNumber, authproviderStr, siweSigner) { Address = null };
}
}
@@ -156,6 +156,7 @@ private static async Task ResumeEnclaveSession(IThirdwebHttpClient httpC
httpClient.AddHeader("Authorization", $"Bearer embedded-wallet-token:{sessionData.AuthToken}");
var userStatus = await GetUserStatus(httpClient).ConfigureAwait(false);
+ Console.WriteLine($"User status: {JsonConvert.SerializeObject(userStatus)}");
if (userStatus.Wallets[0].Type == "enclave")
{
return userStatus.Wallets[0].Address.ToChecksumAddress();
@@ -223,8 +224,8 @@ private async Task PostAuth(Server.VerifyResult result)
else
{
CreateEnclaveSession(this.EmbeddedWallet, result.AuthToken, this.Email, this.PhoneNumber, this.AuthProvider, result.AuthIdentifier);
- this._address = address.ToChecksumAddress();
- return this._address;
+ this.Address = address.ToChecksumAddress();
+ return this.Address;
}
}
@@ -254,11 +255,22 @@ private async Task MigrateShardToEnclave(Server.VerifyResult authResult)
#region Wallet Specific
+ ///
+ /// Gets the user details from the enclave wallet.
+ ///
+ /// A task that represents the asynchronous operation. The task result contains the user details.
+ public async Task GetUserDetails()
+ {
+ return await GetUserStatus(this.HttpClient).ConfigureAwait(false);
+ }
+
+ [Obsolete("Use GetUserDetails instead.")]
public string GetEmail()
{
return this.Email;
}
+ [Obsolete("Use GetUserDetails instead.")]
public string GetPhoneNumber()
{
return this.PhoneNumber;
@@ -408,7 +420,7 @@ public async Task> GetLinkedAccounts()
#region OTP Auth
- public async Task<(bool isNewUser, bool isNewDevice)> SendOTP()
+ public async Task SendOTP()
{
if (string.IsNullOrEmpty(this.Email) && string.IsNullOrEmpty(this.PhoneNumber))
{
@@ -417,9 +429,14 @@ public async Task> GetLinkedAccounts()
try
{
- return this.Email == null
- ? await this.EmbeddedWallet.SendPhoneOtpAsync(this.PhoneNumber).ConfigureAwait(false)
- : await this.EmbeddedWallet.SendEmailOtpAsync(this.Email).ConfigureAwait(false);
+ if (this.Email == null)
+ {
+ await this.EmbeddedWallet.SendPhoneOtpAsync(this.PhoneNumber).ConfigureAwait(false);
+ }
+ else
+ {
+ await this.EmbeddedWallet.SendEmailOtpAsync(this.Email).ConfigureAwait(false);
+ }
}
catch (Exception e)
{
@@ -629,13 +646,13 @@ public async Task LoginWithAuthEndpoint(string payload)
public Task GetAddress()
{
- if (!string.IsNullOrEmpty(this._address))
+ if (!string.IsNullOrEmpty(this.Address))
{
- return Task.FromResult(this._address.ToChecksumAddress());
+ return Task.FromResult(this.Address.ToChecksumAddress());
}
else
{
- return Task.FromResult(this._address);
+ return Task.FromResult(this.Address);
}
}
@@ -763,7 +780,7 @@ public async Task SignTransaction(ThirdwebTransactionInput transaction)
public Task IsConnected()
{
- return Task.FromResult(this._address != null);
+ return Task.FromResult(this.Address != null);
}
public Task SendTransaction(ThirdwebTransactionInput transaction)
@@ -778,7 +795,7 @@ public Task ExecuteTransaction(ThirdwebTransactionIn
public async Task Disconnect()
{
- this._address = null;
+ this.Address = null;
await this.EmbeddedWallet.SignOutAsync().ConfigureAwait(false);
}
diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet.Authentication/Server.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet.Authentication/Server.cs
index 1277945b..680c9141 100644
--- a/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet.Authentication/Server.cs
+++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet.Authentication/Server.cs
@@ -9,9 +9,6 @@ internal abstract class ServerBase
internal abstract Task> LinkAccountAsync(string currentAccountToken, string authTokenToConnect);
internal abstract Task> GetLinkedAccountsAsync(string currentAccountToken);
- internal abstract Task FetchUserDetailsAsync(string emailAddress, string authToken);
- internal abstract Task StoreAddressAndSharesAsync(string walletAddress, string authShare, string encryptedRecoveryShare, string authToken);
-
internal abstract Task<(string authShare, string recoveryShare)> FetchAuthAndRecoverySharesAsync(string authToken);
internal abstract Task FetchAuthShareAsync(string authToken);
@@ -79,46 +76,6 @@ internal override async Task> GetLinkedAccountsAsync(string
return res == null || res.LinkedAccounts == null || res.LinkedAccounts.Count == 0 ? [] : res.LinkedAccounts;
}
- // embedded-wallet/embedded-wallet-user-details
- internal override async Task FetchUserDetailsAsync(string emailOrPhone, string authToken)
- {
- Dictionary queryParams = new();
- if (emailOrPhone == null && authToken == null)
- {
- throw new InvalidOperationException("Must provide either email address or auth token");
- }
-
- queryParams.Add("email", emailOrPhone ?? "uninitialized");
- queryParams.Add("clientId", this._clientId);
-
- var uri = MakeUri2023("/embedded-wallet/embedded-wallet-user-details", queryParams);
- var response = await this.SendHttpWithAuthAsync(uri, authToken ?? "").ConfigureAwait(false);
- await CheckStatusCodeAsync(response).ConfigureAwait(false);
- var rv = await DeserializeAsync(response).ConfigureAwait(false);
- return rv;
- }
-
- // embedded-wallet/embedded-wallet-shares POST
- internal override async Task StoreAddressAndSharesAsync(string walletAddress, string authShare, string encryptedRecoveryShare, string authToken)
- {
- var encryptedRecoveryShares = new[] { new { share = encryptedRecoveryShare, isClientEncrypted = "true" } };
-
- HttpRequestMessage httpRequestMessage =
- new(HttpMethod.Post, MakeUri2023("/embedded-wallet/embedded-wallet-shares"))
- {
- Content = MakeHttpContent(
- new
- {
- authShare,
- maybeEncryptedRecoveryShares = encryptedRecoveryShares,
- walletAddress,
- }
- ),
- };
- var response = await this.SendHttpWithAuthAsync(httpRequestMessage, authToken).ConfigureAwait(false);
- await CheckStatusCodeAsync(response).ConfigureAwait(false);
- }
-
// embedded-wallet/embedded-wallet-shares GET
internal override async Task<(string authShare, string recoveryShare)> FetchAuthAndRecoverySharesAsync(string authToken)
{
diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.EmailOTP.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.EmailOTP.cs
index c03a6f01..c6d5729f 100644
--- a/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.EmailOTP.cs
+++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.EmailOTP.cs
@@ -2,13 +2,10 @@
internal partial class EmbeddedWallet
{
- public async Task<(bool isNewUser, bool isNewDevice)> SendEmailOtpAsync(string emailAddress)
+ public async Task SendEmailOtpAsync(string emailAddress)
{
emailAddress = emailAddress.ToLower();
- var userWallet = await this._server.FetchUserDetailsAsync(emailAddress, null).ConfigureAwait(false);
_ = await this._server.SendEmailOtpAsync(emailAddress).ConfigureAwait(false);
- var isNewDevice = userWallet.IsNewUser || this._localStorage.Data?.WalletUserId != userWallet.WalletUserId;
- return (userWallet.IsNewUser, isNewDevice);
}
public async Task VerifyEmailOtpAsync(string emailAddress, string otp)
diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.Misc.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.Misc.cs
index aa4c452b..6e6edb00 100644
--- a/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.Misc.cs
+++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.Misc.cs
@@ -15,93 +15,12 @@ internal async void UpdateSessionData(LocalStorage.DataStorage data)
await this._localStorage.SaveDataAsync(data).ConfigureAwait(false);
}
- internal async Task PostAuthSetup(Server.VerifyResult result, string twManagedRecoveryCodeOverride, string authProvider)
- {
- var mainRecoveryCode = (twManagedRecoveryCodeOverride ?? result.RecoveryCode) ?? throw new InvalidOperationException("Server failed to return recovery code.");
-
- (var account, var deviceShare) = result.IsNewUser
- ? await this.CreateAccountAsync(result.AuthToken, mainRecoveryCode).ConfigureAwait(false)
- : await this.RecoverAccountAsync(result.AuthToken, mainRecoveryCode).ConfigureAwait(false);
- var user = this.MakeUserAsync(result.Email, result.PhoneNumber, account, result.AuthToken, result.WalletUserId, deviceShare, authProvider, result.AuthIdentifier);
- return new VerifyResult(user, mainRecoveryCode);
- }
-
public async Task SignOutAsync()
{
this._user = null;
await this._localStorage.SaveDataAsync(new LocalStorage.DataStorage(null, null, null, null, null, null, null)).ConfigureAwait(false);
}
- public async Task GetUserAsync(string email, string phone, string authProvider)
- {
- email = email?.ToLower();
-
- if (this._user != null)
- {
- return this._user;
- }
- else if (this._localStorage.Data?.AuthToken == null)
- {
- throw new InvalidOperationException("User is not signed in");
- }
-
- var userWallet = await this._server.FetchUserDetailsAsync(null, this._localStorage.Data.AuthToken).ConfigureAwait(false);
- switch (userWallet.Status)
- {
- case "Logged Out":
- throw new InvalidOperationException("User is logged out");
- case "Logged In, Wallet Uninitialized":
- throw new InvalidOperationException("User is logged in but wallet is uninitialized");
- case "Logged In, Wallet Initialized":
- if (string.IsNullOrEmpty(this._localStorage.Data?.DeviceShare))
- {
- throw new InvalidOperationException("User is logged in but wallet is uninitialized");
- }
-
- var authShare = await this._server.FetchAuthShareAsync(this._localStorage.Data.AuthToken).ConfigureAwait(false);
- var emailAddress = userWallet.StoredToken?.AuthDetails.Email;
- var phoneNumber = userWallet.StoredToken?.AuthDetails.PhoneNumber;
-
- if ((email != null && email != emailAddress) || (phone != null && phone != phoneNumber))
- {
- throw new InvalidOperationException("User email or phone number do not match");
- }
- else if (email == null && this._localStorage.Data.AuthProvider != authProvider)
- {
- throw new InvalidOperationException($"User auth provider does not match. Expected {this._localStorage.Data.AuthProvider}, got {authProvider}");
- }
- else if (authShare == null)
- {
- throw new InvalidOperationException("Server failed to return auth share");
- }
-
- this._user = new User(MakeAccountFromShares(new[] { authShare, this._localStorage.Data.DeviceShare }), emailAddress, phoneNumber);
- return this._user;
- default:
- break;
- }
- throw new InvalidOperationException($"Unexpected user status '{userWallet.Status}'");
- }
-
- private User MakeUserAsync(string emailAddress, string phoneNumber, Account account, string authToken, string walletUserId, string deviceShare, string authProvider, string authIdentifier)
- {
- var data = new LocalStorage.DataStorage(authToken, deviceShare, emailAddress, phoneNumber, walletUserId, authProvider, authIdentifier);
- this.UpdateSessionData(data);
- this._user = new User(account, emailAddress, phoneNumber);
- return this._user;
- }
-
- private async Task<(Account account, string deviceShare)> CreateAccountAsync(string authToken, string recoveryCode)
- {
- var secret = Secrets.Random(KEY_SIZE);
-
- (var deviceShare, var recoveryShare, var authShare) = CreateShares(secret);
- var encryptedRecoveryShare = await this.EncryptShareAsync(recoveryShare, recoveryCode).ConfigureAwait(false);
- Account account = new(secret);
- await this._server.StoreAddressAndSharesAsync(account.Address, authShare, encryptedRecoveryShare, authToken).ConfigureAwait(false);
- return (account, deviceShare);
- }
-
internal async Task<(Account account, string deviceShare)> RecoverAccountAsync(string authToken, string recoveryCode)
{
(var authShare, var encryptedRecoveryShare) = await this._server.FetchAuthAndRecoverySharesAsync(authToken).ConfigureAwait(false);
diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.PhoneOTP.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.PhoneOTP.cs
index 4711ad5e..a841bcfe 100644
--- a/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.PhoneOTP.cs
+++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.PhoneOTP.cs
@@ -2,12 +2,9 @@
internal partial class EmbeddedWallet
{
- public async Task<(bool isNewUser, bool isNewDevice)> SendPhoneOtpAsync(string phoneNumber)
+ public async Task SendPhoneOtpAsync(string phoneNumber)
{
- var userWallet = await this._server.FetchUserDetailsAsync(phoneNumber, null).ConfigureAwait(false);
_ = await this._server.SendPhoneOtpAsync(phoneNumber).ConfigureAwait(false);
- var isNewDevice = userWallet.IsNewUser || this._localStorage.Data?.WalletUserId != userWallet.WalletUserId;
- return (userWallet.IsNewUser, isNewDevice);
}
public async Task VerifyPhoneOtpAsync(string phoneNumber, string otp)
diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.cs
index 46053ffa..c7076bed 100644
--- a/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.cs
+++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.cs
@@ -7,8 +7,20 @@ namespace Thirdweb;
///
public class InAppWallet : EcosystemWallet
{
- internal InAppWallet(ThirdwebClient client, EmbeddedWallet embeddedWallet, IThirdwebHttpClient httpClient, string email, string phoneNumber, string authProvider, IThirdwebWallet siweSigner)
- : base(null, null, client, embeddedWallet, httpClient, email, phoneNumber, authProvider, siweSigner) { }
+ internal InAppWallet(
+ ThirdwebClient client,
+ EmbeddedWallet embeddedWallet,
+ IThirdwebHttpClient httpClient,
+ string email,
+ string phoneNumber,
+ string authProvider,
+ IThirdwebWallet siweSigner,
+ string address
+ )
+ : base(null, null, client, embeddedWallet, httpClient, email, phoneNumber, authProvider, siweSigner)
+ {
+ this.Address = address;
+ }
///
/// Creates a new instance of the class.
@@ -32,6 +44,15 @@ public static async Task Create(
{
storageDirectoryPath ??= Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Thirdweb", "InAppWallet");
var ecoWallet = await Create(client, null, null, email, phoneNumber, authProvider, storageDirectoryPath, siweSigner);
- return new InAppWallet(ecoWallet.Client, ecoWallet.EmbeddedWallet, ecoWallet.HttpClient, ecoWallet.Email, ecoWallet.PhoneNumber, ecoWallet.AuthProvider, ecoWallet.SiweSigner);
+ return new InAppWallet(
+ ecoWallet.Client,
+ ecoWallet.EmbeddedWallet,
+ ecoWallet.HttpClient,
+ ecoWallet.Email,
+ ecoWallet.PhoneNumber,
+ ecoWallet.AuthProvider,
+ ecoWallet.SiweSigner,
+ ecoWallet.Address
+ );
}
}
From 8ef9df73f39613237f9d528eeb98e552266e9092 Mon Sep 17 00:00:00 2001
From: 0xFirekeeper <0xFirekeeper@gmail.com>
Date: Tue, 29 Oct 2024 06:35:27 +0700
Subject: [PATCH 3/5] fix build
---
.../EcosystemWallet/EcosystemWallet.cs | 34 +++++++++----------
.../EmbeddedWallet/EmbeddedWallet.Misc.cs | 1 -
.../EmbeddedWallet/EmbeddedWallet.cs | 1 -
.../InAppWallet/InAppWallet.Types.cs | 4 ++-
4 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs
index 439ae9ce..e6a59753 100644
--- a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs
+++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs
@@ -82,22 +82,22 @@ public static async Task Create(
var authproviderStr = authProvider switch
{
- AuthProvider.Google => "Google",
- AuthProvider.Apple => "Apple",
- AuthProvider.Facebook => "Facebook",
- AuthProvider.JWT => "JWT",
- AuthProvider.AuthEndpoint => "AuthEndpoint",
- AuthProvider.Discord => "Discord",
- AuthProvider.Farcaster => "Farcaster",
- AuthProvider.Telegram => "Telegram",
- AuthProvider.Siwe => "Siwe",
- AuthProvider.Line => "Line",
- AuthProvider.Guest => "Guest",
- AuthProvider.X => "X",
- AuthProvider.Coinbase => "Coinbase",
- AuthProvider.Github => "Github",
- AuthProvider.Twitch => "Twitch",
- AuthProvider.Default => string.IsNullOrEmpty(email) ? "Phone" : "Email",
+ Thirdweb.AuthProvider.Google => "Google",
+ Thirdweb.AuthProvider.Apple => "Apple",
+ Thirdweb.AuthProvider.Facebook => "Facebook",
+ Thirdweb.AuthProvider.JWT => "JWT",
+ Thirdweb.AuthProvider.AuthEndpoint => "AuthEndpoint",
+ Thirdweb.AuthProvider.Discord => "Discord",
+ Thirdweb.AuthProvider.Farcaster => "Farcaster",
+ Thirdweb.AuthProvider.Telegram => "Telegram",
+ Thirdweb.AuthProvider.Siwe => "Siwe",
+ Thirdweb.AuthProvider.Line => "Line",
+ Thirdweb.AuthProvider.Guest => "Guest",
+ Thirdweb.AuthProvider.X => "X",
+ Thirdweb.AuthProvider.Coinbase => "Coinbase",
+ Thirdweb.AuthProvider.Github => "Github",
+ Thirdweb.AuthProvider.Twitch => "Twitch",
+ Thirdweb.AuthProvider.Default => string.IsNullOrEmpty(email) ? "Phone" : "Email",
_ => throw new ArgumentException("Invalid AuthProvider"),
};
@@ -281,7 +281,7 @@ public string GetPhoneNumber()
public async Task GetEcosystemDetails()
{
var url = $"{EMBEDDED_WALLET_PATH_2024}/ecosystem-wallet";
- var response = await this._httpClient.GetAsync(url).ConfigureAwait(false);
+ var response = await this.HttpClient.GetAsync(url).ConfigureAwait(false);
_ = response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
return JsonConvert.DeserializeObject(content);
diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.Misc.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.Misc.cs
index 6e6edb00..ac230732 100644
--- a/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.Misc.cs
+++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.Misc.cs
@@ -17,7 +17,6 @@ internal async void UpdateSessionData(LocalStorage.DataStorage data)
public async Task SignOutAsync()
{
- this._user = null;
await this._localStorage.SaveDataAsync(new LocalStorage.DataStorage(null, null, null, null, null, null, null)).ConfigureAwait(false);
}
diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.cs
index 5e7ae073..8f470942 100644
--- a/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.cs
+++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.cs
@@ -5,7 +5,6 @@ internal partial class EmbeddedWallet
private readonly LocalStorage _localStorage;
private readonly Server _server;
private readonly IvGenerator _ivGenerator;
- private User _user;
private const int DEVICE_SHARE_ID = 1;
private const int KEY_SIZE = 256 / 8;
diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.Types.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.Types.cs
index c18e6e3b..0abd7d00 100644
--- a/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.Types.cs
+++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.Types.cs
@@ -20,7 +20,9 @@ public enum AuthProvider
Line,
Guest,
X,
- Coinbase
+ Coinbase,
+ Github,
+ Twitch
}
///
From 92dc138662958ad5daea7254acb33481b585a89a Mon Sep 17 00:00:00 2001
From: 0xFirekeeper <0xFirekeeper@gmail.com>
Date: Tue, 29 Oct 2024 06:36:20 +0700
Subject: [PATCH 4/5] log
---
.../InAppWallet/EcosystemWallet/EcosystemWallet.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs
index e6a59753..ab07e844 100644
--- a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs
+++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs
@@ -158,7 +158,6 @@ private static async Task ResumeEnclaveSession(IThirdwebHttpClient httpC
httpClient.AddHeader("Authorization", $"Bearer embedded-wallet-token:{sessionData.AuthToken}");
var userStatus = await GetUserStatus(httpClient).ConfigureAwait(false);
- Console.WriteLine($"User status: {JsonConvert.SerializeObject(userStatus)}");
if (userStatus.Wallets[0].Type == "enclave")
{
return userStatus.Wallets[0].Address.ToChecksumAddress();
From b6c60caf63c80d27000cc8f912b2eb937b7e534d Mon Sep 17 00:00:00 2001
From: 0xFirekeeper <0xFirekeeper@gmail.com>
Date: Wed, 6 Nov 2024 01:42:34 +0700
Subject: [PATCH 5/5] Add custom auth migration
---
.../EcosystemWallet/EcosystemWallet.Types.cs | 2 +-
.../EcosystemWallet/EcosystemWallet.cs | 42 +++++++++++++++----
.../InAppWallet/InAppWallet.cs | 14 ++++---
3 files changed, 44 insertions(+), 14 deletions(-)
diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.Types.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.Types.cs
index 1a8a3883..e1ccfd4b 100644
--- a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.Types.cs
+++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.Types.cs
@@ -5,7 +5,7 @@ namespace Thirdweb;
public partial class EcosystemWallet
{
- public class EnclaveUserStatusResponse
+ public class UserStatusResponse
{
[JsonProperty("linkedAccounts")]
internal List LinkedAccounts { get; set; }
diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs
index ab07e844..94708c45 100644
--- a/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs
+++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/EcosystemWallet/EcosystemWallet.cs
@@ -23,6 +23,7 @@ public partial class EcosystemWallet : IThirdwebWallet
internal readonly string Email;
internal readonly string PhoneNumber;
internal readonly string AuthProvider;
+ internal readonly string LegacyEncryptionKey;
internal string Address;
@@ -43,12 +44,14 @@ internal EcosystemWallet(
string email,
string phoneNumber,
string authProvider,
- IThirdwebWallet siweSigner
+ IThirdwebWallet siweSigner,
+ string legacyEncryptionKey
)
{
this.Client = client;
this._ecosystemId = ecosystemId;
this._ecosystemPartnerId = ecosystemPartnerId;
+ this.LegacyEncryptionKey = legacyEncryptionKey;
this.EmbeddedWallet = embeddedWallet;
this.HttpClient = httpClient;
this.Email = email;
@@ -59,6 +62,20 @@ IThirdwebWallet siweSigner
#region Creation
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// Your ecosystem ID (see thirdweb dashboard e.g. ecosystem.the-bonfire).
+ /// Your ecosystem partner ID (required if you are integrating someone else's ecosystem).
+ /// The Thirdweb client instance.
+ /// The email address for Email OTP authentication.
+ /// The phone number for Phone OTP authentication.
+ /// The authentication provider to use.
+ /// The path to the storage directory.
+ /// The SIWE signer wallet for SIWE authentication.
+ /// 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.
+ /// A task that represents the asynchronous operation. The task result contains the created in-app wallet.
+ /// Thrown when required parameters are not provided.
public static async Task Create(
ThirdwebClient client,
string ecosystemId,
@@ -67,7 +84,8 @@ public static async Task Create(
string phoneNumber = null,
AuthProvider authProvider = Thirdweb.AuthProvider.Default,
string storageDirectoryPath = null,
- IThirdwebWallet siweSigner = null
+ IThirdwebWallet siweSigner = null,
+ string legacyEncryptionKey = null
)
{
if (client == null)
@@ -130,12 +148,18 @@ public static async Task Create(
try
{
var userAddress = await ResumeEnclaveSession(enclaveHttpClient, embeddedWallet, email, phoneNumber, authproviderStr).ConfigureAwait(false);
- return new EcosystemWallet(ecosystemId, ecosystemPartnerId, client, embeddedWallet, enclaveHttpClient, email, phoneNumber, authproviderStr, siweSigner) { Address = userAddress };
+ return new EcosystemWallet(ecosystemId, ecosystemPartnerId, client, embeddedWallet, enclaveHttpClient, email, phoneNumber, authproviderStr, siweSigner, legacyEncryptionKey)
+ {
+ Address = userAddress
+ };
}
catch
{
enclaveHttpClient.RemoveHeader("Authorization");
- return new EcosystemWallet(ecosystemId, ecosystemPartnerId, client, embeddedWallet, enclaveHttpClient, email, phoneNumber, authproviderStr, siweSigner) { Address = null };
+ return new EcosystemWallet(ecosystemId, ecosystemPartnerId, client, embeddedWallet, enclaveHttpClient, email, phoneNumber, authproviderStr, siweSigner, legacyEncryptionKey)
+ {
+ Address = null
+ };
}
}
@@ -175,13 +199,13 @@ private static void CreateEnclaveSession(EmbeddedWallet embeddedWallet, string a
embeddedWallet.UpdateSessionData(data);
}
- private static async Task GetUserStatus(IThirdwebHttpClient httpClient)
+ private static async Task GetUserStatus(IThirdwebHttpClient httpClient)
{
var url = $"{EMBEDDED_WALLET_PATH_2024}/accounts";
var response = await httpClient.GetAsync(url).ConfigureAwait(false);
_ = response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
- var userStatus = JsonConvert.DeserializeObject(content);
+ var userStatus = JsonConvert.DeserializeObject(content);
return userStatus;
}
@@ -233,7 +257,9 @@ private async Task PostAuth(Server.VerifyResult result)
private async Task MigrateShardToEnclave(Server.VerifyResult authResult)
{
// TODO: For recovery code, allow old encryption keys as overrides to migrate sharded custom auth?
- var (address, encryptedPrivateKeyB64, ivB64, kmsCiphertextB64) = await this.EmbeddedWallet.GenerateEncryptionDataAsync(authResult.AuthToken, authResult.RecoveryCode).ConfigureAwait(false);
+ var (address, encryptedPrivateKeyB64, ivB64, kmsCiphertextB64) = await this.EmbeddedWallet
+ .GenerateEncryptionDataAsync(authResult.AuthToken, this.LegacyEncryptionKey ?? authResult.RecoveryCode)
+ .ConfigureAwait(false);
var url = $"{ENCLAVE_PATH}/migrate";
var payload = new
@@ -260,7 +286,7 @@ private async Task MigrateShardToEnclave(Server.VerifyResult authResult)
/// Gets the user details from the enclave wallet.
///
/// A task that represents the asynchronous operation. The task result contains the user details.
- public async Task GetUserDetails()
+ public async Task GetUserDetails()
{
return await GetUserStatus(this.HttpClient).ConfigureAwait(false);
}
diff --git a/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.cs b/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.cs
index c7076bed..8a7f8b80 100644
--- a/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.cs
+++ b/Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.cs
@@ -15,9 +15,10 @@ internal InAppWallet(
string phoneNumber,
string authProvider,
IThirdwebWallet siweSigner,
- string address
+ string address,
+ string legacyEncryptionKey
)
- : base(null, null, client, embeddedWallet, httpClient, email, phoneNumber, authProvider, siweSigner)
+ : base(null, null, client, embeddedWallet, httpClient, email, phoneNumber, authProvider, siweSigner, legacyEncryptionKey)
{
this.Address = address;
}
@@ -31,6 +32,7 @@ string address
/// The authentication provider to use.
/// The path to the storage directory.
/// The SIWE signer wallet for SIWE authentication.
+ /// 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.
/// A task that represents the asynchronous operation. The task result contains the created in-app wallet.
/// Thrown when required parameters are not provided.
public static async Task Create(
@@ -39,11 +41,12 @@ public static async Task Create(
string phoneNumber = null,
AuthProvider authProvider = Thirdweb.AuthProvider.Default,
string storageDirectoryPath = null,
- IThirdwebWallet siweSigner = null
+ IThirdwebWallet siweSigner = null,
+ string legacyEncryptionKey = null
)
{
storageDirectoryPath ??= Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Thirdweb", "InAppWallet");
- var ecoWallet = await Create(client, null, null, email, phoneNumber, authProvider, storageDirectoryPath, siweSigner);
+ var ecoWallet = await Create(client, null, null, email, phoneNumber, authProvider, storageDirectoryPath, siweSigner, legacyEncryptionKey);
return new InAppWallet(
ecoWallet.Client,
ecoWallet.EmbeddedWallet,
@@ -52,7 +55,8 @@ public static async Task Create(
ecoWallet.PhoneNumber,
ecoWallet.AuthProvider,
ecoWallet.SiweSigner,
- ecoWallet.Address
+ ecoWallet.Address,
+ ecoWallet.LegacyEncryptionKey
);
}
}