Skip to content

Commit 0af4353

Browse files
kdupreydstaley
andcommitted
feat(clerk-js,clerk-react,shared,types): Add sign in with Solana feature support (#7293)
Signed-off-by: Kenton Duprey <[email protected]> Co-authored-by: Dylan Staley <[email protected]> Signed-off-by: Kenton Duprey <[email protected]>
1 parent 1eaef8e commit 0af4353

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+3335
-294
lines changed

.changeset/legal-jokes-beg.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@clerk/localizations': minor
3+
'@clerk/clerk-js': minor
4+
'@clerk/elements': minor
5+
'@clerk/shared': minor
6+
'@clerk/clerk-react': minor
7+
---
8+
9+
Add support for Sign in with Solana.

packages/clerk-js/bundle-check.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import path from 'node:path';
55
import { pipeline } from 'node:stream';
66
import zlib from 'node:zlib';
77

8-
import { chromium } from 'playwright';
8+
import { chromium } from '@playwright/test';
99

1010
/**
1111
* This script generates a CLI report detailing the gzipped size of JavaScript resources loaded by `clerk-js` for a
@@ -212,7 +212,7 @@ function report(url, responses) {
212212

213213
/**
214214
* Loads the given `url` in `browser`, capturing all HTTP requests that occur.
215-
* @param {import('playwright').Browser} browser
215+
* @param {import('@playwright/test').Browser} browser
216216
* @param {string} url
217217
*/
218218
async function getResponseSizes(browser, url) {

packages/clerk-js/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,13 @@
6262
"@base-org/account": "catalog:module-manager",
6363
"@clerk/shared": "workspace:^",
6464
"@coinbase/wallet-sdk": "catalog:module-manager",
65+
"@solana/wallet-adapter-base": "catalog:module-manager",
66+
"@solana/wallet-adapter-react": "catalog:module-manager",
67+
"@solana/wallet-standard": "catalog:module-manager",
6568
"@stripe/stripe-js": "5.6.0",
6669
"@swc/helpers": "^0.5.17",
6770
"@tanstack/query-core": "5.87.4",
71+
"@wallet-standard/core": "catalog:module-manager",
6872
"@zxcvbn-ts/core": "catalog:module-manager",
6973
"@zxcvbn-ts/language-common": "catalog:module-manager",
7074
"alien-signals": "2.0.6",

packages/clerk-js/rspack.config.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,20 @@ const common = ({ mode, variant, disableRHC = false }) => {
123123
},
124124
defaultVendors: {
125125
minChunks: 1,
126-
test: /[\\/]node_modules[\\/]/,
126+
test: module => {
127+
if (!(module instanceof rspack.NormalModule) || !module.resource) {
128+
return false;
129+
}
130+
// Exclude Solana packages and their known transitive dependencies
131+
if (
132+
/[\\/]node_modules[\\/](@solana|@solana-mobile|@wallet-standard|bn\.js|borsh|buffer|superstruct|bs58|jayson|rpc-websockets|qrcode)[\\/]/.test(
133+
module.resource,
134+
)
135+
) {
136+
return false;
137+
}
138+
return /[\\/]node_modules[\\/]/.test(module.resource);
139+
},
127140
name: 'vendors',
128141
priority: -10,
129142
},

packages/clerk-js/src/core/clerk.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import type {
5959
AuthenticateWithGoogleOneTapParams,
6060
AuthenticateWithMetamaskParams,
6161
AuthenticateWithOKXWalletParams,
62+
AuthenticateWithSolanaParams,
6263
BillingNamespace,
6364
CheckoutSignalValue,
6465
Clerk as ClerkInterface,
@@ -74,7 +75,7 @@ import type {
7475
EnvironmentJSON,
7576
EnvironmentJSONSnapshot,
7677
EnvironmentResource,
77-
GenerateSignatureParams,
78+
GenerateSignature,
7879
GoogleOneTapProps,
7980
HandleEmailLinkVerificationParams,
8081
HandleOAuthCallbackParams,
@@ -2338,6 +2339,13 @@ export class Clerk implements ClerkInterface {
23382339
});
23392340
};
23402341

2342+
public authenticateWithSolana = async (props: AuthenticateWithSolanaParams): Promise<void> => {
2343+
await this.authenticateWithWeb3({
2344+
...props,
2345+
strategy: 'web3_solana_signature',
2346+
});
2347+
};
2348+
23412349
public authenticateWithWeb3 = async ({
23422350
redirectUrl,
23432351
signUpContinueUrl,
@@ -2346,6 +2354,7 @@ export class Clerk implements ClerkInterface {
23462354
strategy,
23472355
legalAccepted,
23482356
secondFactorUrl,
2357+
walletName,
23492358
}: ClerkAuthenticateWithWeb3Params): Promise<void> => {
23502359
if (!this.client || !this.environment) {
23512360
return;
@@ -2354,8 +2363,8 @@ export class Clerk implements ClerkInterface {
23542363
const { displayConfig } = this.environment;
23552364

23562365
const provider = strategy.replace('web3_', '').replace('_signature', '') as Web3Provider;
2357-
const identifier = await web3().getWeb3Identifier({ provider });
2358-
let generateSignature: (params: GenerateSignatureParams) => Promise<string>;
2366+
const identifier = await web3().getWeb3Identifier({ provider, walletName });
2367+
let generateSignature: GenerateSignature;
23592368
switch (provider) {
23602369
case 'metamask':
23612370
generateSignature = web3().generateSignatureWithMetamask;
@@ -2366,6 +2375,15 @@ export class Clerk implements ClerkInterface {
23662375
case 'coinbase_wallet':
23672376
generateSignature = web3().generateSignatureWithCoinbaseWallet;
23682377
break;
2378+
case 'solana':
2379+
if (!walletName) {
2380+
throw new ClerkRuntimeError('Wallet name is required for Solana authentication.', {
2381+
code: 'web3_solana_wallet_name_required',
2382+
});
2383+
}
2384+
// Solana requires walletName; bind it into the helper
2385+
generateSignature = params => web3().generateSignatureWithSolana({ ...params, walletName });
2386+
break;
23692387
default:
23702388
generateSignature = web3().generateSignatureWithOKXWallet;
23712389
break;
@@ -2395,6 +2413,7 @@ export class Clerk implements ClerkInterface {
23952413
identifier,
23962414
generateSignature,
23972415
strategy,
2416+
walletName,
23982417
});
23992418
} catch (err) {
24002419
if (isError(err, ERROR_CODES.FORM_IDENTIFIER_NOT_FOUND)) {
@@ -2404,6 +2423,7 @@ export class Clerk implements ClerkInterface {
24042423
unsafeMetadata,
24052424
strategy,
24062425
legalAccepted,
2426+
walletName,
24072427
});
24082428

24092429
if (

packages/clerk-js/src/core/resources/SignIn.ts

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import type {
2323
EmailLinkConfig,
2424
EmailLinkFactor,
2525
EnterpriseSSOConfig,
26+
GenerateSignature,
2627
PassKeyConfig,
2728
PasskeyFactor,
2829
PhoneCodeConfig,
@@ -32,6 +33,7 @@ import type {
3233
ResetPasswordEmailCodeFactorConfig,
3334
ResetPasswordParams,
3435
ResetPasswordPhoneCodeFactorConfig,
36+
SignInAuthenticateWithSolanaParams,
3537
SignInCreateParams,
3638
SignInFirstFactor,
3739
SignInFutureBackupCodeVerifyParams,
@@ -202,6 +204,7 @@ export class SignIn extends BaseResource implements SignInResource {
202204
case 'web3_base_signature':
203205
case 'web3_coinbase_wallet_signature':
204206
case 'web3_okx_wallet_signature':
207+
case 'web3_solana_signature':
205208
config = { web3WalletId: params.web3WalletId } as Web3SignatureConfig;
206209
break;
207210
case 'reset_password_phone_code':
@@ -363,13 +366,17 @@ export class SignIn extends BaseResource implements SignInResource {
363366
};
364367

365368
public authenticateWithWeb3 = async (params: AuthenticateWithWeb3Params): Promise<SignInResource> => {
366-
const { identifier, generateSignature, strategy = 'web3_metamask_signature' } = params || {};
369+
const { identifier, generateSignature, strategy = 'web3_metamask_signature', walletName } = params || {};
367370
const provider = strategy.replace('web3_', '').replace('_signature', '') as Web3Provider;
368371

369372
if (!(typeof generateSignature === 'function')) {
370373
clerkMissingOptionError('generateSignature');
371374
}
372375

376+
if (provider === 'solana' && !walletName) {
377+
clerkMissingOptionError('walletName');
378+
}
379+
373380
await this.create({ identifier });
374381

375382
const web3FirstFactor = this.supportedFirstFactors?.find(f => f.strategy === strategy) as Web3SignatureFactor;
@@ -387,7 +394,7 @@ export class SignIn extends BaseResource implements SignInResource {
387394

388395
let signature: string;
389396
try {
390-
signature = await generateSignature({ identifier, nonce: message, provider });
397+
signature = await generateSignature({ identifier, nonce: message, walletName, provider });
391398
} catch (err) {
392399
// There is a chance that as a user when you try to setup and use the Coinbase Wallet with an existing
393400
// Passkey in order to authenticate, the initial generate signature request to be rejected. For this
@@ -396,7 +403,7 @@ export class SignIn extends BaseResource implements SignInResource {
396403
// error code 4001 means the user rejected the request
397404
// Reference: https://docs.cdp.coinbase.com/wallet-sdk/docs/errors
398405
if (provider === 'coinbase_wallet' && err.code === 4001) {
399-
signature = await generateSignature({ identifier, nonce: message, provider });
406+
signature = await generateSignature({ identifier, nonce: message, provider, walletName });
400407
} else {
401408
throw err;
402409
}
@@ -444,6 +451,16 @@ export class SignIn extends BaseResource implements SignInResource {
444451
});
445452
};
446453

454+
public authenticateWithSolana = async (params: SignInAuthenticateWithSolanaParams): Promise<SignInResource> => {
455+
const identifier = await web3().getSolanaIdentifier(params.walletName);
456+
return this.authenticateWithWeb3({
457+
identifier,
458+
generateSignature: p => web3().generateSignatureWithSolana({ ...p, walletName: params.walletName }),
459+
strategy: 'web3_solana_signature',
460+
walletName: params.walletName,
461+
});
462+
};
463+
447464
public authenticateWithPasskey = async (params?: AuthenticateWithPasskeyParams): Promise<SignInResource> => {
448465
const { flow } = params || {};
449466

@@ -961,7 +978,7 @@ class SignInFuture implements SignInFutureResource {
961978

962979
return runAsyncResourceTask(this.#resource, async () => {
963980
let identifier;
964-
let generateSignature;
981+
let generateSignature: GenerateSignature;
965982
switch (provider) {
966983
case 'metamask':
967984
identifier = await web3().getMetamaskIdentifier();
@@ -979,6 +996,16 @@ class SignInFuture implements SignInFutureResource {
979996
identifier = await web3().getOKXWalletIdentifier();
980997
generateSignature = web3().generateSignatureWithOKXWallet;
981998
break;
999+
case 'solana':
1000+
if (!params.walletName) {
1001+
throw new ClerkRuntimeError('Wallet name is required for Solana authentication.', {
1002+
code: 'web3_solana_wallet_name_required',
1003+
});
1004+
}
1005+
identifier = await web3().getSolanaIdentifier(params.walletName);
1006+
generateSignature = p =>
1007+
web3().generateSignatureWithSolana({ ...p, walletName: params.walletName as string });
1008+
break;
9821009
default:
9831010
throw new Error(`Unsupported Web3 provider: ${provider}`);
9841011
}
@@ -1004,7 +1031,12 @@ class SignInFuture implements SignInFutureResource {
10041031

10051032
let signature: string;
10061033
try {
1007-
signature = await generateSignature({ identifier, nonce: message });
1034+
signature = await generateSignature({
1035+
identifier,
1036+
nonce: message,
1037+
walletName: params?.walletName,
1038+
provider,
1039+
});
10081040
} catch (err) {
10091041
// There is a chance that as a user when you try to setup and use the Coinbase Wallet with an existing
10101042
// Passkey in order to authenticate, the initial generate signature request to be rejected. For this
@@ -1013,7 +1045,11 @@ class SignInFuture implements SignInFutureResource {
10131045
// error code 4001 means the user rejected the request
10141046
// Reference: https://docs.cdp.coinbase.com/wallet-sdk/docs/errors
10151047
if (provider === 'coinbase_wallet' && err.code === 4001) {
1016-
signature = await generateSignature({ identifier, nonce: message });
1048+
signature = await generateSignature({
1049+
identifier,
1050+
nonce: message,
1051+
provider,
1052+
});
10171053
} else {
10181054
throw err;
10191055
}

packages/clerk-js/src/core/resources/SignUp.ts

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import type {
1616
PreparePhoneNumberVerificationParams,
1717
PrepareVerificationParams,
1818
PrepareWeb3WalletVerificationParams,
19+
SignUpAuthenticateWithSolanaParams,
1920
SignUpAuthenticateWithWeb3Params,
2021
SignUpCreateParams,
2122
SignUpEnterpriseConnectionJSON,
@@ -268,6 +269,7 @@ export class SignUp extends BaseResource implements SignUpResource {
268269
unsafeMetadata,
269270
strategy = 'web3_metamask_signature',
270271
legalAccepted,
272+
walletName,
271273
} = params || {};
272274
const provider = strategy.replace('web3_', '').replace('_signature', '') as Web3Provider;
273275

@@ -287,7 +289,7 @@ export class SignUp extends BaseResource implements SignUpResource {
287289

288290
let signature: string;
289291
try {
290-
signature = await generateSignature({ identifier, nonce: message, provider });
292+
signature = await generateSignature({ identifier, nonce: message, provider, walletName });
291293
} catch (err) {
292294
// There is a chance that as a first time visitor when you try to setup and use the
293295
// Coinbase Wallet from scratch in order to authenticate, the initial generate
@@ -322,9 +324,7 @@ export class SignUp extends BaseResource implements SignUpResource {
322324
};
323325

324326
public authenticateWithCoinbaseWallet = async (
325-
params?: SignUpAuthenticateWithWeb3Params & {
326-
legalAccepted?: boolean;
327-
},
327+
params?: SignUpAuthenticateWithWeb3Params,
328328
): Promise<SignUpResource> => {
329329
const identifier = await web3().getCoinbaseWalletIdentifier();
330330
return this.authenticateWithWeb3({
@@ -336,11 +336,7 @@ export class SignUp extends BaseResource implements SignUpResource {
336336
});
337337
};
338338

339-
public authenticateWithBase = async (
340-
params?: SignUpAuthenticateWithWeb3Params & {
341-
legalAccepted?: boolean;
342-
},
343-
): Promise<SignUpResource> => {
339+
public authenticateWithBase = async (params?: SignUpAuthenticateWithWeb3Params): Promise<SignUpResource> => {
344340
const identifier = await web3().getBaseIdentifier();
345341
return this.authenticateWithWeb3({
346342
identifier,
@@ -351,11 +347,7 @@ export class SignUp extends BaseResource implements SignUpResource {
351347
});
352348
};
353349

354-
public authenticateWithOKXWallet = async (
355-
params?: SignUpAuthenticateWithWeb3Params & {
356-
legalAccepted?: boolean;
357-
},
358-
): Promise<SignUpResource> => {
350+
public authenticateWithOKXWallet = async (params?: SignUpAuthenticateWithWeb3Params): Promise<SignUpResource> => {
359351
const identifier = await web3().getOKXWalletIdentifier();
360352
return this.authenticateWithWeb3({
361353
identifier,
@@ -366,6 +358,18 @@ export class SignUp extends BaseResource implements SignUpResource {
366358
});
367359
};
368360

361+
public authenticateWithSolana = async (params: SignUpAuthenticateWithSolanaParams): Promise<SignUpResource> => {
362+
const identifier = await web3().getSolanaIdentifier(params.walletName);
363+
return this.authenticateWithWeb3({
364+
identifier,
365+
generateSignature: p => web3().generateSignatureWithSolana({ ...p, walletName: params.walletName }),
366+
unsafeMetadata: params?.unsafeMetadata,
367+
strategy: 'web3_solana_signature',
368+
legalAccepted: params?.legalAccepted,
369+
walletName: params.walletName,
370+
});
371+
};
372+
369373
private authenticateWithRedirectOrPopup = async (
370374
params: AuthenticateWithRedirectParams & {
371375
unsafeMetadata?: SignUpUnsafeMetadata;

packages/localizations/src/ar-SA.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -747,6 +747,10 @@ export const arSA: LocalizationResource = {
747747
subtitle: 'للمتابعة، يرجى إدخال رمز التحقق الذي تم إنشاؤه بواسطة تطبيق المصادقة الخاص بك',
748748
title: 'نظام التحقق بخطوتين',
749749
},
750+
web3Solana: {
751+
subtitle: undefined,
752+
title: undefined,
753+
},
750754
},
751755
signInEnterPasswordTitle: 'إدخل كلمة المرور',
752756
signUp: {
@@ -837,6 +841,10 @@ export const arSA: LocalizationResource = {
837841
title: 'أنشاء حساب جديد',
838842
titleCombined: 'أنشاء حساب جديد',
839843
},
844+
web3Solana: {
845+
subtitle: undefined,
846+
title: undefined,
847+
},
840848
},
841849
socialButtonsBlockButton: 'للمتابعة مع {{provider|titleize}}',
842850
socialButtonsBlockButtonManyInView: '{{provider|titleize}}',
@@ -941,6 +949,8 @@ export const arSA: LocalizationResource = {
941949
phone_number_exists: 'هذا الرقم مأخوذ الرجاء أختيار رقم آخر',
942950
session_exists: 'لقد قمت بتسجيل الدخول بالفعل',
943951
web3_missing_identifier: undefined,
952+
web3_signature_request_rejected: undefined,
953+
web3_solana_signature_generation_failed: undefined,
944954
zxcvbn: {
945955
couldBeStronger: 'كلمة مرورك سليمة من الأفضل ان تكون اقوى. الرجاء أضافة حروف أكثر',
946956
goodPassword: 'كلمة مرورك طابقت جميع المتطلبات الازمة',
@@ -1340,4 +1350,9 @@ export const arSA: LocalizationResource = {
13401350
title: undefined,
13411351
},
13421352
},
1353+
web3WalletButtons: {
1354+
connect: undefined,
1355+
continue: undefined,
1356+
noneAvailable: undefined,
1357+
},
13431358
} as const;

0 commit comments

Comments
 (0)