Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions Thirdweb.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,31 @@

#endregion

#region InAppWallet - SiweExternal

// var inAppWalletSiweExternal = await InAppWallet.Create(client: client, authProvider: AuthProvider.SiweExternal);
// if (!await inAppWalletSiweExternal.IsConnected())
// {
// _ = await inAppWalletSiweExternal.LoginWithSiweExternal(
// isMobile: false,
// browserOpenAction: (url) =>
// {
// var psi = new ProcessStartInfo { FileName = url, UseShellExecute = true };
// _ = Process.Start(psi);
// },
// forceWalletIds: new List<string> { "io.metamask", "com.coinbase.wallet", "xyz.abs" }
// );
// }
// var inAppWalletOAuthAddress = await inAppWalletSiweExternal.GetAddress();
// Console.WriteLine($"InAppWallet SiweExternal address: {inAppWalletOAuthAddress}");

// var inAppWalletAuthDetails = inAppWalletSiweExternal.GetUserAuthDetails();
// Console.WriteLine($"InAppWallet OAuth auth details: {JsonConvert.SerializeObject(inAppWalletAuthDetails, Formatting.Indented)}");

// await inAppWalletSiweExternal.Disconnect();

#endregion

#region Smart Wallet - Gasless Transaction

// var smartWallet = await SmartWallet.Create(privateKeyWallet, 78600);
Expand Down
49 changes: 27 additions & 22 deletions Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,69 +12,69 @@ public interface IThirdwebWallet
/// <summary>
/// Gets the Thirdweb client associated with the wallet.
/// </summary>
ThirdwebClient Client { get; }
public ThirdwebClient Client { get; }

/// <summary>
/// Gets the account type of the wallet.
/// </summary>
ThirdwebAccountType AccountType { get; }
public ThirdwebAccountType AccountType { get; }

/// <summary>
/// Gets the address of the wallet.
/// </summary>
/// <returns>The wallet address.</returns>
Task<string> GetAddress();
public Task<string> GetAddress();

/// <summary>
/// Signs a raw message using Ethereum's signing method.
/// </summary>
/// <param name="rawMessage">The raw message to sign.</param>
/// <returns>The signed message.</returns>
Task<string> EthSign(byte[] rawMessage);
public Task<string> EthSign(byte[] rawMessage);

/// <summary>
/// Signs a message using Ethereum's signing method.
/// </summary>
/// <param name="message">The message to sign.</param>
/// <returns>The signed message.</returns>
Task<string> EthSign(string message);
public Task<string> EthSign(string message);

/// <summary>
/// Recovers the address from a signed message using Ethereum's signing method.
/// </summary>
/// <param name="message">The UTF-8 encoded message.</param>
/// <param name="signature">The signature.</param>
/// <returns>The recovered address.</returns>
Task<string> RecoverAddressFromEthSign(string message, string signature);
public Task<string> RecoverAddressFromEthSign(string message, string signature);

/// <summary>
/// Signs a raw message using personal signing.
/// </summary>
/// <param name="rawMessage">The raw message to sign.</param>
/// <returns>The signed message.</returns>
Task<string> PersonalSign(byte[] rawMessage);
public Task<string> PersonalSign(byte[] rawMessage);

/// <summary>
/// Signs a message using personal signing.
/// </summary>
/// <param name="message">The message to sign.</param>
/// <returns>The signed message.</returns>
Task<string> PersonalSign(string message);
public Task<string> PersonalSign(string message);

/// <summary>
/// Recovers the address from a signed message using personal signing.
/// </summary>
/// <param name="message">The UTF-8 encoded and prefixed message.</param>
/// <param name="signature">The signature.</param>
/// <returns>The recovered address.</returns>
Task<string> RecoverAddressFromPersonalSign(string message, string signature);
public Task<string> RecoverAddressFromPersonalSign(string message, string signature);

/// <summary>
/// Signs typed data (version 4).
/// </summary>
/// <param name="json">The JSON representation of the typed data.</param>
/// <returns>The signed data.</returns>
Task<string> SignTypedDataV4(string json);
public Task<string> SignTypedDataV4(string json);

/// <summary>
/// Signs typed data (version 4).
Expand All @@ -84,7 +84,7 @@ public interface IThirdwebWallet
/// <param name="data">The data to sign.</param>
/// <param name="typedData">The typed data.</param>
/// <returns>The signed data.</returns>
Task<string> SignTypedDataV4<T, TDomain>(T data, TypedData<TDomain> typedData)
public Task<string> SignTypedDataV4<T, TDomain>(T data, TypedData<TDomain> typedData)
where TDomain : IDomain;

/// <summary>
Expand All @@ -96,40 +96,40 @@ Task<string> SignTypedDataV4<T, TDomain>(T data, TypedData<TDomain> typedData)
/// <param name="typedData">The typed data.</param>
/// <param name="signature">The signature.</param>
/// <returns>The recovered address.</returns>
Task<string> RecoverAddressFromTypedDataV4<T, TDomain>(T data, TypedData<TDomain> typedData, string signature)
public Task<string> RecoverAddressFromTypedDataV4<T, TDomain>(T data, TypedData<TDomain> typedData, string signature)
where TDomain : IDomain;

/// <summary>
/// Checks if the wallet is connected.
/// </summary>
/// <returns>True if connected, otherwise false.</returns>
Task<bool> IsConnected();
public Task<bool> IsConnected();

/// <summary>
/// Signs a transaction.
/// </summary>
/// <param name="transaction">The transaction to sign.</param>
/// <returns>The signed transaction.</returns>
Task<string> SignTransaction(ThirdwebTransactionInput transaction);
public Task<string> SignTransaction(ThirdwebTransactionInput transaction);

/// <summary>
/// Sends a transaction.
/// </summary>
/// <param name="transaction">The transaction to send.</param>
/// <returns>The transaction hash.</returns>
Task<string> SendTransaction(ThirdwebTransactionInput transaction);
public Task<string> SendTransaction(ThirdwebTransactionInput transaction);

/// <summary>
/// Sends a transaction and waits for its receipt.
/// </summary>
/// <param name="transaction">The transaction to execute.</param>
/// <returns>The transaction receipt.</returns>
Task<ThirdwebTransactionReceipt> ExecuteTransaction(ThirdwebTransactionInput transaction);
public Task<ThirdwebTransactionReceipt> ExecuteTransaction(ThirdwebTransactionInput transaction);

/// <summary>
/// Disconnects the wallet (if using InAppWallet, clears session)
/// </summary>
Task Disconnect();
public Task Disconnect();

/// <summary>
/// Links a new account (auth method) to the current wallet. The current wallet must be connected and the wallet being linked must not be fully connected ie created.
Expand All @@ -144,7 +144,7 @@ Task<string> RecoverAddressFromTypedDataV4<T, TDomain>(T data, TypedData<TDomain
/// <param name="jwt">The JWT token if linking custom JWT auth.</param>
/// <param name="payload">The login payload if linking custom AuthEndpoint auth.</param>
/// <returns>A list of <see cref="LinkedAccount"/> objects.</returns>
Task<List<LinkedAccount>> LinkAccount(
public Task<List<LinkedAccount>> LinkAccount(
IThirdwebWallet walletToLink,
string otp = null,
bool? isMobile = null,
Expand All @@ -160,13 +160,13 @@ Task<List<LinkedAccount>> LinkAccount(
/// Unlinks an account (auth method) from the current wallet.
/// </summary>
/// <param name="accountToUnlink">The linked account to unlink. Same type returned by <see cref="GetLinkedAccounts"/>.</param>
Task<List<LinkedAccount>> UnlinkAccount(LinkedAccount accountToUnlink);
public Task<List<LinkedAccount>> UnlinkAccount(LinkedAccount accountToUnlink);

/// <summary>
/// Returns a list of linked accounts to the current wallet.
/// </summary>
/// <returns>A list of <see cref="LinkedAccount"/> objects.</returns>
Task<List<LinkedAccount>> GetLinkedAccounts();
public Task<List<LinkedAccount>> GetLinkedAccounts();

/// <summary>
/// Signs an EIP-7702 authorization to invoke contract functions to an externally owned account.
Expand All @@ -175,13 +175,13 @@ Task<List<LinkedAccount>> LinkAccount(
/// <param name="contractAddress">The address of the contract.</param>
/// <param name="willSelfExecute">Set to true if the wallet will also be the executor of the transaction, otherwise false.</param>
/// <returns>The signed authorization as an <see cref="EIP7702Authorization"/> that can be used with <see cref="ThirdwebTransactionInput.AuthorizationList"/>.</returns>
Task<EIP7702Authorization> SignAuthorization(BigInteger chainId, string contractAddress, bool willSelfExecute);
public Task<EIP7702Authorization> SignAuthorization(BigInteger chainId, string contractAddress, bool willSelfExecute);

/// <summary>
/// Attempts to set the active network to the specified chain ID.
/// </summary>
/// <param name="chainId">The chain ID to switch to.</param>
Task SwitchNetwork(BigInteger chainId);
public Task SwitchNetwork(BigInteger chainId);
}

/// <summary>
Expand Down Expand Up @@ -280,4 +280,9 @@ public class LoginPayloadData
/// Initializes a new instance of the <see cref="LoginPayloadData"/> class.
/// </summary>
public LoginPayloadData() { }

public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ public static async Task<EcosystemWallet> Create(
Thirdweb.AuthProvider.Twitch => "Twitch",
Thirdweb.AuthProvider.Steam => "Steam",
Thirdweb.AuthProvider.Backend => "Backend",
Thirdweb.AuthProvider.SiweExternal => "SiweExternal",
Thirdweb.AuthProvider.Default => string.IsNullOrEmpty(email) ? "Phone" : "Email",
_ => throw new ArgumentException("Invalid AuthProvider"),
};
Expand Down Expand Up @@ -497,6 +498,10 @@ public async Task<List<LinkedAccount>> LinkAccount(
case "Guest":
serverRes = await ecosystemWallet.PreAuth_Guest().ConfigureAwait(false);
break;
case "SiweExternal":
// TODO: Allow enforcing wallet ids in linking flow?
serverRes = await ecosystemWallet.PreAuth_SiweExternal(isMobile ?? false, browserOpenAction, null, mobileRedirectScheme, browser).ConfigureAwait(false);
break;
case "Google":
case "Apple":
case "Facebook":
Expand Down Expand Up @@ -700,6 +705,81 @@ public async Task<string> LoginWithOauth(

#endregion

#region SiweExternal

private async Task<Server.VerifyResult> PreAuth_SiweExternal(
bool isMobile,
Action<string> browserOpenAction,
List<string> forceWalletIds = null,
string mobileRedirectScheme = "thirdweb://",
IThirdwebBrowser browser = null,
CancellationToken cancellationToken = default
)
{
var redirectUrl = isMobile ? mobileRedirectScheme : "http://localhost:8789/";
var loginUrl = $"https://static.thirdweb.com/auth/siwe?redirectUrl={redirectUrl}";
if (forceWalletIds != null && forceWalletIds.Count > 0)
{
loginUrl += $"&wallets={string.Join(",", forceWalletIds)}";
}

browser ??= new InAppWalletBrowser();
var browserResult = await browser.Login(this.Client, loginUrl, redirectUrl, browserOpenAction, cancellationToken).ConfigureAwait(false);
switch (browserResult.Status)
{
case BrowserStatus.Success:
break;
case BrowserStatus.UserCanceled:
throw new TaskCanceledException(browserResult.Error ?? "LoginWithSiwe was cancelled.");
case BrowserStatus.Timeout:
throw new TimeoutException(browserResult.Error ?? "LoginWithSiwe 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("LoginWithSiwe was cancelled.");
}
await ThirdwebTask.Delay(100, cancellationToken).ConfigureAwait(false);
}

string signature;
string payload;
var decodedUrl = HttpUtility.UrlDecode(callbackUrl);
Uri uri = new(decodedUrl);
var queryString = uri.Query;
var queryDict = HttpUtility.ParseQueryString(queryString);
signature = queryDict["signature"];
payload = HttpUtility.UrlDecode(queryDict["payload"]);
var payloadData = JsonConvert.DeserializeObject<LoginPayloadData>(payload);

var serverRes = await this.EmbeddedWallet.SignInWithSiweRawAsync(payloadData, signature).ConfigureAwait(false);
return serverRes;
}

public async Task<string> LoginWithSiweExternal(
bool isMobile,
Action<string> browserOpenAction,
List<string> forceWalletIds = null,
string mobileRedirectScheme = "thirdweb://",
IThirdwebBrowser browser = null,
CancellationToken cancellationToken = default
)
{
var serverRes = await this.PreAuth_SiweExternal(isMobile, browserOpenAction, forceWalletIds, mobileRedirectScheme, browser, cancellationToken).ConfigureAwait(false);
return await this.PostAuth(serverRes).ConfigureAwait(false);
}

#endregion

#region Siwe

private async Task<Server.VerifyResult> PreAuth_Siwe(IThirdwebWallet siweSigner, BigInteger chainId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,7 @@ internal Server(ThirdwebClient client, IThirdwebHttpClient httpClient)
internal override async Task<List<LinkedAccount>> UnlinkAccountAsync(string currentAccountToken, LinkedAccount linkedAccount)
{
var uri = MakeUri2024("/account/disconnect");
var request = new HttpRequestMessage(HttpMethod.Post, uri)
{
Content = MakeHttpContent(linkedAccount)
};
var request = new HttpRequestMessage(HttpMethod.Post, uri) { Content = MakeHttpContent(linkedAccount) };
var response = await this.SendHttpWithAuthAsync(request, currentAccountToken).ConfigureAwait(false);
await CheckStatusCodeAsync(response).ConfigureAwait(false);

Expand All @@ -72,10 +69,7 @@ internal override async Task<List<LinkedAccount>> UnlinkAccountAsync(string curr
internal override async Task<List<LinkedAccount>> LinkAccountAsync(string currentAccountToken, string authTokenToConnect)
{
var uri = MakeUri2024("/account/connect");
var request = new HttpRequestMessage(HttpMethod.Post, uri)
{
Content = MakeHttpContent(new { accountAuthTokenToConnect = authTokenToConnect })
};
var request = new HttpRequestMessage(HttpMethod.Post, uri) { Content = MakeHttpContent(new { accountAuthTokenToConnect = authTokenToConnect }) };
var response = await this.SendHttpWithAuthAsync(request, currentAccountToken).ConfigureAwait(false);
await CheckStatusCodeAsync(response).ConfigureAwait(false);

Expand All @@ -91,7 +85,7 @@ internal override async Task<List<LinkedAccount>> GetLinkedAccountsAsync(string
await CheckStatusCodeAsync(response).ConfigureAwait(false);

var res = await DeserializeAsync<AccountConnectResponse>(response).ConfigureAwait(false);
return res == null || res.LinkedAccounts == null || res.LinkedAccounts.Count == 0 ? [] : res.LinkedAccounts;
return res == null || res.LinkedAccounts == null || res.LinkedAccounts.Count == 0 ? new List<LinkedAccount>() : res.LinkedAccounts;
}

// embedded-wallet/embedded-wallet-shares GET
Expand Down Expand Up @@ -150,7 +144,16 @@ internal override async Task<VerifyResult> VerifySiweAsync(LoginPayloadData payl
{
var uri = MakeUri2024("/login/siwe/callback");
var content = MakeHttpContent(new { signature, payload });
var response = await this._httpClient.PostAsync(uri.ToString(), content).ConfigureAwait(false);
this._httpClient.AddHeader("origin", payload.Domain);
ThirdwebHttpResponseMessage response = null;
try
{
response = await this._httpClient.PostAsync(uri.ToString(), content).ConfigureAwait(false);
}
finally
{
this._httpClient.RemoveHeader("origin");
}
await CheckStatusCodeAsync(response).ConfigureAwait(false);

var authResult = await DeserializeAsync<AuthResultType>(response).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,9 @@ internal partial class EmbeddedWallet

return await this._server.VerifySiweAsync(payload, signature).ConfigureAwait(false);
}

public async Task<Server.VerifyResult> SignInWithSiweRawAsync(LoginPayloadData payload, string signature)
{
return await this._server.VerifySiweAsync(payload, signature).ConfigureAwait(false);
}
}
3 changes: 2 additions & 1 deletion Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public enum AuthProvider
Github,
Twitch,
Steam,
Backend
Backend,
SiweExternal,
}

/// <summary>
Expand Down
Loading