Skip to content

Commit 1899360

Browse files
authored
In-App Wallet - Login with SIWE (#54)
1 parent 7fe9a2b commit 1899360

File tree

6 files changed

+128
-47
lines changed

6 files changed

+128
-47
lines changed

Thirdweb.Console/Program.cs

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -27,33 +27,44 @@
2727
var walletAddress = await privateKeyWallet.GetAddress();
2828
Console.WriteLine($"PK Wallet address: {walletAddress}");
2929

30-
var erc20SmartWalletSepolia = await SmartWallet.Create(
31-
personalWallet: privateKeyWallet,
32-
chainId: 11155111, // sepolia
33-
gasless: true,
34-
erc20PaymasterAddress: "0xEc87d96E3F324Dcc828750b52994C6DC69C8162b", // deposit paymaster
35-
erc20PaymasterToken: "0x94a9D9AC8a22534E3FaCa9F4e7F2E2cf85d5E4C8" // usdc
36-
);
37-
var erc20SmartWalletSepoliaAddress = await erc20SmartWalletSepolia.GetAddress();
38-
Console.WriteLine($"ERC20 Smart Wallet Sepolia address: {erc20SmartWalletSepoliaAddress}");
39-
40-
var selfTransfer = await ThirdwebTransaction.Create(
41-
wallet: erc20SmartWalletSepolia,
42-
txInput: new ThirdwebTransactionInput() { From = erc20SmartWalletSepoliaAddress, To = erc20SmartWalletSepoliaAddress, },
43-
chainId: 11155111
44-
);
45-
46-
var estimateGas = await ThirdwebTransaction.EstimateGasCosts(selfTransfer);
47-
Console.WriteLine($"Self transfer gas estimate: {estimateGas.ether}");
48-
Console.WriteLine("Make sure you have enough USDC!");
49-
Console.ReadLine();
50-
51-
var receipt = await ThirdwebTransaction.SendAndWaitForTransactionReceipt(selfTransfer);
52-
Console.WriteLine($"Self transfer receipt: {JsonConvert.SerializeObject(receipt, Formatting.Indented)}");
30+
var smartWalletSigner = await SmartWallet.Create(personalWallet: privateKeyWallet, chainId: 421614, gasless: true); // because why not
5331

54-
// var chainData = await Utils.FetchThirdwebChainDataAsync(client, 421614);
55-
// Console.WriteLine($"Chain data: {JsonConvert.SerializeObject(chainData, Formatting.Indented)}");
56-
Console.WriteLine($"Wallet address: {walletAddress}");
32+
var inAppWalletSiwe = await InAppWallet.Create(client: client, authProvider: AuthProvider.Siwe);
33+
if (!await inAppWalletSiwe.IsConnected())
34+
{
35+
_ = await inAppWalletSiwe.LoginWithSiwe(smartWalletSigner, 421614);
36+
}
37+
var inAppWalletSiweAddress = await inAppWalletSiwe.GetAddress();
38+
Console.WriteLine($"InAppWallet Siwe address: {inAppWalletSiweAddress}");
39+
40+
41+
// var erc20SmartWalletSepolia = await SmartWallet.Create(
42+
// personalWallet: privateKeyWallet,
43+
// chainId: 11155111, // sepolia
44+
// gasless: true,
45+
// erc20PaymasterAddress: "0xEc87d96E3F324Dcc828750b52994C6DC69C8162b", // deposit paymaster
46+
// erc20PaymasterToken: "0x94a9D9AC8a22534E3FaCa9F4e7F2E2cf85d5E4C8" // usdc
47+
// );
48+
// var erc20SmartWalletSepoliaAddress = await erc20SmartWalletSepolia.GetAddress();
49+
// Console.WriteLine($"ERC20 Smart Wallet Sepolia address: {erc20SmartWalletSepoliaAddress}");
50+
51+
// var selfTransfer = await ThirdwebTransaction.Create(
52+
// wallet: erc20SmartWalletSepolia,
53+
// txInput: new ThirdwebTransactionInput() { From = erc20SmartWalletSepoliaAddress, To = erc20SmartWalletSepoliaAddress, },
54+
// chainId: 11155111
55+
// );
56+
57+
// var estimateGas = await ThirdwebTransaction.EstimateGasCosts(selfTransfer);
58+
// Console.WriteLine($"Self transfer gas estimate: {estimateGas.ether}");
59+
// Console.WriteLine("Make sure you have enough USDC!");
60+
// Console.ReadLine();
61+
62+
// var receipt = await ThirdwebTransaction.SendAndWaitForTransactionReceipt(selfTransfer);
63+
// Console.WriteLine($"Self transfer receipt: {JsonConvert.SerializeObject(receipt, Formatting.Indented)}");
64+
65+
// // var chainData = await Utils.FetchThirdwebChainDataAsync(client, 421614);
66+
// // Console.WriteLine($"Chain data: {JsonConvert.SerializeObject(chainData, Formatting.Indented)}");
67+
// Console.WriteLine($"Wallet address: {walletAddress}");
5768

5869
// // Self transfer 0 on chain 842
5970
// var tx = await ThirdwebTransaction.Create(

Thirdweb/Thirdweb.Wallets/IThirdwebWallet.cs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,6 @@ public struct LoginPayload
162162
[Serializable]
163163
public class LoginPayloadData
164164
{
165-
/// <summary>
166-
/// Gets or sets the type of the login payload.
167-
/// </summary>
168-
[JsonProperty("type")]
169-
public string Type { get; set; }
170-
171165
/// <summary>
172166
/// Gets or sets the domain of the login payload.
173167
/// </summary>
@@ -237,9 +231,6 @@ public class LoginPayloadData
237231
/// <summary>
238232
/// Initializes a new instance of the <see cref="LoginPayloadData"/> class.
239233
/// </summary>
240-
public LoginPayloadData()
241-
{
242-
Type = "evm";
243-
}
234+
public LoginPayloadData() { }
244235
}
245236
}

Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet.Authentication/Server.cs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ internal abstract class ServerBase
1212
internal abstract Task<(string authShare, string recoveryShare)> FetchAuthAndRecoverySharesAsync(string authToken);
1313
internal abstract Task<string> FetchAuthShareAsync(string authToken);
1414

15-
internal abstract Task<string> FetchHeadlessOauthLoginLinkAsync(string authProvider, string platform);
15+
internal abstract Task<LoginPayloadData> FetchSiwePayloadAsync(string address, string chainId);
16+
internal abstract Task<Server.VerifyResult> VerifySiweAsync(LoginPayloadData payload, string signature);
1617

1718
internal abstract Task<string> SendEmailOtpAsync(string emailAddress);
1819
internal abstract Task<Server.VerifyResult> VerifyEmailOtpAsync(string emailAddress, string otp);
@@ -22,6 +23,7 @@ internal abstract class ServerBase
2223

2324
internal abstract Task<Server.VerifyResult> VerifyJwtAsync(string jwtToken);
2425

26+
internal abstract Task<string> FetchHeadlessOauthLoginLinkAsync(string authProvider, string platform);
2527
internal abstract Task<Server.VerifyResult> VerifyOAuthAsync(string authResultStr);
2628

2729
internal abstract Task<Server.VerifyResult> VerifyAuthEndpointAsync(string payload);
@@ -142,7 +144,28 @@ private async Task<IdTokenResponse> FetchCognitoIdTokenAsync(string authToken)
142144
return await DeserializeAsync<IdTokenResponse>(response).ConfigureAwait(false);
143145
}
144146

145-
// embedded-wallet/headless-oauth-login-link
147+
// login/siwe
148+
internal override async Task<LoginPayloadData> FetchSiwePayloadAsync(string address, string chainId)
149+
{
150+
var uri = MakeUri2024("/login/siwe", new Dictionary<string, string> { { "address", address }, { "chainId", chainId } });
151+
var response = await httpClient.GetAsync(uri.ToString()).ConfigureAwait(false);
152+
await CheckStatusCodeAsync(response).ConfigureAwait(false);
153+
154+
return await DeserializeAsync<LoginPayloadData>(response).ConfigureAwait(false);
155+
}
156+
157+
internal override async Task<VerifyResult> VerifySiweAsync(LoginPayloadData payload, string signature)
158+
{
159+
var uri = MakeUri2024("/login/siwe/callback");
160+
var content = MakeHttpContent(new { signature = signature, payload = payload });
161+
var response = await httpClient.PostAsync(uri.ToString(), content).ConfigureAwait(false);
162+
await CheckStatusCodeAsync(response).ConfigureAwait(false);
163+
164+
var authResult = await DeserializeAsync<AuthResultType>(response).ConfigureAwait(false);
165+
return await InvokeAuthResultLambdaAsync(authResult).ConfigureAwait(false);
166+
}
167+
168+
// login/oauthprovider
146169
internal override Task<string> FetchHeadlessOauthLoginLinkAsync(string authProvider, string platform)
147170
{
148171
return Task.FromResult(MakeUri2024($"/login/{authProvider}", new Dictionary<string, string> { { "clientId", clientId }, { "platform", platform } }).ToString());

Thirdweb/Thirdweb.Wallets/InAppWallet/EmbeddedWallet/EmbeddedWallet.OAuth.cs

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,5 @@ public async Task<string> FetchHeadlessOauthLoginLinkAsync(string authProvider,
1414
{
1515
return await server.FetchHeadlessOauthLoginLinkAsync(authProvider, platform).ConfigureAwait(false);
1616
}
17-
18-
public async Task<bool> IsRecoveryCodeNeededAsync(string authResultStr)
19-
{
20-
var authResult = JsonConvert.DeserializeObject<Server.AuthResultType>(authResultStr);
21-
var userWallet = await server.FetchUserDetailsAsync(authResult.StoredToken.AuthDetails.Email, null).ConfigureAwait(false);
22-
return userWallet.RecoveryShareManagement == "USER_MANAGED" && !userWallet.IsNewUser && localStorage.Data?.DeviceShare == null;
23-
}
2417
}
2518
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.Numerics;
2+
3+
namespace Thirdweb.EWS
4+
{
5+
internal partial class EmbeddedWallet
6+
{
7+
public async Task<VerifyResult> SignInWithSiweAsync(IThirdwebWallet signer, BigInteger chainId)
8+
{
9+
var address = await signer.GetAddress().ConfigureAwait(false);
10+
var payload = await server.FetchSiwePayloadAsync(address, chainId.ToString()).ConfigureAwait(false);
11+
var payloadMsg = Utils.GenerateSIWE(payload);
12+
var signature = await signer.PersonalSign(payloadMsg).ConfigureAwait(false);
13+
14+
var result = await server.VerifySiweAsync(payload, signature).ConfigureAwait(false);
15+
return await PostAuthSetup(result, address, null).ConfigureAwait(false);
16+
}
17+
}
18+
}

Thirdweb/Thirdweb.Wallets/InAppWallet/InAppWallet.cs

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Numerics;
12
using System.Web;
23
using Nethereum.Signer;
34
using Thirdweb.EWS;
@@ -17,7 +18,8 @@ public enum AuthProvider
1718
AuthEndpoint,
1819
Discord,
1920
Farcaster,
20-
Telegram
21+
Telegram,
22+
Siwe
2123
}
2224

2325
/// <summary>
@@ -72,6 +74,7 @@ public static async Task<InAppWallet> Create(
7274
AuthProvider.Discord => "Discord",
7375
AuthProvider.Farcaster => "Farcaster",
7476
AuthProvider.Telegram => "Telegram",
77+
AuthProvider.Siwe => "Siwe",
7578
AuthProvider.Default => string.IsNullOrEmpty(email) ? "Phone" : "Email",
7679
_ => throw new ArgumentException("Invalid AuthProvider"),
7780
};
@@ -257,14 +260,56 @@ public Task<string> GetPhoneNumber()
257260

258261
#endregion
259262

263+
#region SIWE Flow
264+
265+
/// <summary>
266+
/// Logs in with SIWE (Sign-In with Ethereum).
267+
/// </summary>
268+
/// <param name="signer">The wallet that will be used to sign the SIWE payload</param>
269+
/// <param name="chainId">The chain ID to use for signing the SIWE payload</param>
270+
/// <returns>A task representing the asynchronous operation. The task result contains the address.</returns>
271+
/// <exception cref="ArgumentNullException">Thrown when external wallet is not provided.</exception>
272+
/// <exception cref="InvalidOperationException">Thrown when the external wallet is not connected.</exception>
273+
/// <exception cref="ArgumentException">Thrown when chain ID is invalid.</exception>
274+
public async Task<string> LoginWithSiwe(IThirdwebWallet signer, BigInteger chainId)
275+
{
276+
if (signer == null)
277+
{
278+
throw new ArgumentNullException(nameof(signer), "Signer wallet cannot be null.");
279+
}
280+
281+
if (!await signer.IsConnected().ConfigureAwait(false))
282+
{
283+
throw new InvalidOperationException("Signer wallet must be connected as this operation requires it to sign a message.");
284+
}
285+
286+
if (chainId <= 0)
287+
{
288+
throw new ArgumentException(nameof(chainId), "Chain ID must be greater than 0.");
289+
}
290+
291+
var res = await _embeddedWallet.SignInWithSiweAsync(signer, chainId);
292+
293+
if (res.User == null)
294+
{
295+
throw new Exception("Failed to login with SIWE");
296+
}
297+
298+
_ecKey = new EthECKey(res.User.Account.PrivateKey);
299+
300+
return await GetAddress();
301+
}
302+
303+
#endregion
304+
260305
#region JWT Flow
261306

262307
/// <summary>
263308
/// Logs in with a JWT.
264309
/// </summary>
265310
/// <param name="jwt">The JWT to use for authentication.</param>
266311
/// <param name="encryptionKey">The encryption key to use.</param>
267-
/// <returns>A task representing the asynchronous operation. The task result contains the login result.</returns>
312+
/// <returns>A task representing the asynchronous operation. The task result contains the address.</returns>
268313
/// <exception cref="ArgumentException">Thrown when JWT or encryption key is not provided.</exception>
269314
/// <exception cref="Exception">Thrown when the login fails.</exception>
270315
public async Task<string> LoginWithJWT(string jwt, string encryptionKey)

0 commit comments

Comments
 (0)