Skip to content

Commit f626046

Browse files
kdupreydstaley
andauthored
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]>
1 parent 3d9e063 commit f626046

Some content is hidden

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

86 files changed

+2630
-341
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/bundlewatch.config.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
{
22
"files": [
3-
{ "path": "./dist/clerk.js", "maxSize": "840KB" },
4-
{ "path": "./dist/clerk.browser.js", "maxSize": "83KB" },
5-
{ "path": "./dist/clerk.legacy.browser.js", "maxSize": "127KB" },
6-
{ "path": "./dist/clerk.headless*.js", "maxSize": "65KB" },
7-
{ "path": "./dist/ui-common*.js", "maxSize": "119KB" },
8-
{ "path": "./dist/ui-common*.legacy.*.js", "maxSize": "122KB" },
9-
{ "path": "./dist/vendors*.js", "maxSize": "47KB" },
3+
{ "path": "./dist/clerk.js", "maxSize": "922.1KB" },
4+
{ "path": "./dist/clerk.browser.js", "maxSize": "87KB" },
5+
{ "path": "./dist/clerk.legacy.browser.js", "maxSize": "129KB" },
6+
{ "path": "./dist/clerk.headless*.js", "maxSize": "66KB" },
7+
{ "path": "./dist/ui-common*.js", "maxSize": "119.1KB" },
8+
{ "path": "./dist/ui-common*.legacy.*.js", "maxSize": "123KB" },
9+
{ "path": "./dist/vendors*.js", "maxSize": "50KB" },
1010
{ "path": "./dist/coinbase*.js", "maxSize": "38KB" },
1111
{ "path": "./dist/stripe-vendors*.js", "maxSize": "1KB" },
1212
{ "path": "./dist/createorganization*.js", "maxSize": "5KB" },
@@ -15,11 +15,11 @@
1515
{ "path": "./dist/organizationswitcher*.js", "maxSize": "5KB" },
1616
{ "path": "./dist/organizationlist*.js", "maxSize": "5.5KB" },
1717
{ "path": "./dist/signin*.js", "maxSize": "18KB" },
18-
{ "path": "./dist/signup*.js", "maxSize": "9.5KB" },
18+
{ "path": "./dist/signup*.js", "maxSize": "11KB" },
1919
{ "path": "./dist/userbutton*.js", "maxSize": "5KB" },
2020
{ "path": "./dist/userprofile*.js", "maxSize": "16KB" },
2121
{ "path": "./dist/userverification*.js", "maxSize": "5KB" },
22-
{ "path": "./dist/onetap*.js", "maxSize": "1KB" },
22+
{ "path": "./dist/onetap*.js", "maxSize": "3KB" },
2323
{ "path": "./dist/waitlist*.js", "maxSize": "1.5KB" },
2424
{ "path": "./dist/keylessPrompt*.js", "maxSize": "6.5KB" },
2525
{ "path": "./dist/enableOrganizationsPrompt*.js", "maxSize": "6.5KB" },

packages/clerk-js/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,13 @@
6868
"@floating-ui/react": "0.27.12",
6969
"@floating-ui/react-dom": "^2.1.3",
7070
"@formkit/auto-animate": "^0.8.2",
71+
"@solana/wallet-adapter-base": "0.9.27",
72+
"@solana/wallet-adapter-react": "0.15.39",
73+
"@solana/wallet-standard": "1.1.4",
7174
"@stripe/stripe-js": "5.6.0",
7275
"@swc/helpers": "^0.5.17",
7376
"@tanstack/query-core": "5.87.4",
77+
"@wallet-standard/core": "1.1.1",
7478
"@zxcvbn-ts/core": "3.0.4",
7579
"@zxcvbn-ts/language-common": "3.0.4",
7680
"alien-signals": "2.0.6",

packages/clerk-js/rspack.config.js

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,17 +126,41 @@ const common = ({ mode, variant, disableRHC = false }) => {
126126
signUp: {
127127
minChunks: 1,
128128
name: 'signup',
129-
test: module => !!(module.resource && module.resource.includes('/ui/components/SignUp')),
129+
test: module =>
130+
!!(
131+
module instanceof rspack.NormalModule &&
132+
module.resource &&
133+
module.resource.includes('/ui/components/SignUp')
134+
),
130135
},
131136
common: {
132137
minChunks: 1,
133138
name: 'ui-common',
134139
priority: -20,
135-
test: module => !!(module.resource && !module.resource.includes('/ui/components')),
140+
test: module =>
141+
!!(
142+
module instanceof rspack.NormalModule &&
143+
module.resource &&
144+
!module.resource.includes('/ui/components') &&
145+
!module.resource.includes('node_modules')
146+
),
136147
},
137148
defaultVendors: {
138149
minChunks: 1,
139-
test: /[\\/]node_modules[\\/]/,
150+
test: module => {
151+
if (!(module instanceof rspack.NormalModule) || !module.resource) {
152+
return false;
153+
}
154+
// Exclude Solana packages and their known transitive dependencies
155+
if (
156+
/[\\/]node_modules[\\/](@solana|@solana-mobile|@wallet-standard|bn\.js|borsh|buffer|superstruct|bs58|jayson|rpc-websockets|qrcode)[\\/]/.test(
157+
module.resource,
158+
)
159+
) {
160+
return false;
161+
}
162+
return /[\\/]node_modules[\\/]/.test(module.resource);
163+
},
140164
name: 'vendors',
141165
priority: -10,
142166
},

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

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import type {
3737
AuthenticateWithGoogleOneTapParams,
3838
AuthenticateWithMetamaskParams,
3939
AuthenticateWithOKXWalletParams,
40+
AuthenticateWithSolanaParams,
4041
BillingNamespace,
4142
Clerk as ClerkInterface,
4243
ClerkAPIError,
@@ -51,7 +52,7 @@ import type {
5152
EnvironmentJSON,
5253
EnvironmentJSONSnapshot,
5354
EnvironmentResource,
54-
GenerateSignatureParams,
55+
GenerateSignature,
5556
GoogleOneTapProps,
5657
HandleEmailLinkVerificationParams,
5758
HandleOAuthCallbackParams,
@@ -120,6 +121,7 @@ import {
120121
generateSignatureWithCoinbaseWallet,
121122
generateSignatureWithMetamask,
122123
generateSignatureWithOKXWallet,
124+
generateSignatureWithSolana,
123125
getClerkQueryParam,
124126
getWeb3Identifier,
125127
hasExternalAccountSignUpError,
@@ -2380,6 +2382,13 @@ export class Clerk implements ClerkInterface {
23802382
});
23812383
};
23822384

2385+
public authenticateWithSolana = async (props: AuthenticateWithSolanaParams): Promise<void> => {
2386+
await this.authenticateWithWeb3({
2387+
...props,
2388+
strategy: 'web3_solana_signature',
2389+
});
2390+
};
2391+
23832392
public authenticateWithWeb3 = async ({
23842393
redirectUrl,
23852394
signUpContinueUrl,
@@ -2388,6 +2397,7 @@ export class Clerk implements ClerkInterface {
23882397
strategy,
23892398
legalAccepted,
23902399
secondFactorUrl,
2400+
walletName,
23912401
}: ClerkAuthenticateWithWeb3Params): Promise<void> => {
23922402
if (!this.client || !this.environment) {
23932403
return;
@@ -2396,8 +2406,8 @@ export class Clerk implements ClerkInterface {
23962406
const { displayConfig } = this.environment;
23972407

23982408
const provider = strategy.replace('web3_', '').replace('_signature', '') as Web3Provider;
2399-
const identifier = await getWeb3Identifier({ provider });
2400-
let generateSignature: (params: GenerateSignatureParams) => Promise<string>;
2409+
const identifier = await getWeb3Identifier({ provider, walletName });
2410+
let generateSignature: GenerateSignature;
24012411
switch (provider) {
24022412
case 'metamask':
24032413
generateSignature = generateSignatureWithMetamask;
@@ -2408,6 +2418,15 @@ export class Clerk implements ClerkInterface {
24082418
case 'coinbase_wallet':
24092419
generateSignature = generateSignatureWithCoinbaseWallet;
24102420
break;
2421+
case 'solana':
2422+
if (!walletName) {
2423+
throw new ClerkRuntimeError('Wallet name is required for Solana authentication.', {
2424+
code: 'web3_solana_wallet_name_required',
2425+
});
2426+
}
2427+
// Solana requires walletName; bind it into the helper
2428+
generateSignature = params => generateSignatureWithSolana({ ...params, walletName });
2429+
break;
24112430
default:
24122431
generateSignature = generateSignatureWithOKXWallet;
24132432
break;
@@ -2437,6 +2456,7 @@ export class Clerk implements ClerkInterface {
24372456
identifier,
24382457
generateSignature,
24392458
strategy,
2459+
walletName,
24402460
});
24412461
} catch (err) {
24422462
if (isError(err, ERROR_CODES.FORM_IDENTIFIER_NOT_FOUND)) {
@@ -2446,6 +2466,7 @@ export class Clerk implements ClerkInterface {
24462466
unsafeMetadata,
24472467
strategy,
24482468
legalAccepted,
2469+
walletName,
24492470
});
24502471

24512472
if (

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

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import type {
1515
EmailLinkConfig,
1616
EmailLinkFactor,
1717
EnterpriseSSOConfig,
18+
GenerateSignature,
1819
PassKeyConfig,
1920
PasskeyFactor,
2021
PhoneCodeConfig,
@@ -25,6 +26,7 @@ import type {
2526
ResetPasswordParams,
2627
ResetPasswordPhoneCodeFactorConfig,
2728
SamlConfig,
29+
SignInAuthenticateWithSolanaParams,
2830
SignInCreateParams,
2931
SignInFirstFactor,
3032
SignInFutureBackupCodeVerifyParams,
@@ -69,12 +71,14 @@ import {
6971
generateSignatureWithCoinbaseWallet,
7072
generateSignatureWithMetamask,
7173
generateSignatureWithOKXWallet,
74+
generateSignatureWithSolana,
7275
getBaseIdentifier,
7376
getBrowserLocale,
7477
getClerkQueryParam,
7578
getCoinbaseWalletIdentifier,
7679
getMetamaskIdentifier,
7780
getOKXWalletIdentifier,
81+
getSolanaIdentifier,
7882
windowNavigate,
7983
} from '../../utils';
8084
import {
@@ -212,6 +216,7 @@ export class SignIn extends BaseResource implements SignInResource {
212216
case 'web3_base_signature':
213217
case 'web3_coinbase_wallet_signature':
214218
case 'web3_okx_wallet_signature':
219+
case 'web3_solana_signature':
215220
config = { web3WalletId: params.web3WalletId } as Web3SignatureConfig;
216221
break;
217222
case 'reset_password_phone_code':
@@ -379,13 +384,17 @@ export class SignIn extends BaseResource implements SignInResource {
379384
};
380385

381386
public authenticateWithWeb3 = async (params: AuthenticateWithWeb3Params): Promise<SignInResource> => {
382-
const { identifier, generateSignature, strategy = 'web3_metamask_signature' } = params || {};
387+
const { identifier, generateSignature, strategy = 'web3_metamask_signature', walletName } = params || {};
383388
const provider = strategy.replace('web3_', '').replace('_signature', '') as Web3Provider;
384389

385390
if (!(typeof generateSignature === 'function')) {
386391
clerkMissingOptionError('generateSignature');
387392
}
388393

394+
if (provider === 'solana' && !walletName) {
395+
clerkMissingOptionError('walletName');
396+
}
397+
389398
await this.create({ identifier });
390399

391400
const web3FirstFactor = this.supportedFirstFactors?.find(f => f.strategy === strategy) as Web3SignatureFactor;
@@ -403,7 +412,7 @@ export class SignIn extends BaseResource implements SignInResource {
403412

404413
let signature: string;
405414
try {
406-
signature = await generateSignature({ identifier, nonce: message, provider });
415+
signature = await generateSignature({ identifier, nonce: message, walletName, provider });
407416
} catch (err) {
408417
// There is a chance that as a user when you try to setup and use the Coinbase Wallet with an existing
409418
// Passkey in order to authenticate, the initial generate signature request to be rejected. For this
@@ -412,7 +421,7 @@ export class SignIn extends BaseResource implements SignInResource {
412421
// error code 4001 means the user rejected the request
413422
// Reference: https://docs.cdp.coinbase.com/wallet-sdk/docs/errors
414423
if (provider === 'coinbase_wallet' && err.code === 4001) {
415-
signature = await generateSignature({ identifier, nonce: message, provider });
424+
signature = await generateSignature({ identifier, nonce: message, provider, walletName });
416425
} else {
417426
throw err;
418427
}
@@ -460,6 +469,16 @@ export class SignIn extends BaseResource implements SignInResource {
460469
});
461470
};
462471

472+
public authenticateWithSolana = async (params: SignInAuthenticateWithSolanaParams): Promise<SignInResource> => {
473+
const identifier = await getSolanaIdentifier(params.walletName);
474+
return this.authenticateWithWeb3({
475+
identifier,
476+
generateSignature: p => generateSignatureWithSolana({ ...p, walletName: params.walletName }),
477+
strategy: 'web3_solana_signature',
478+
walletName: params.walletName,
479+
});
480+
};
481+
463482
public authenticateWithPasskey = async (params?: AuthenticateWithPasskeyParams): Promise<SignInResource> => {
464483
const { flow } = params || {};
465484

@@ -972,7 +991,7 @@ class SignInFuture implements SignInFutureResource {
972991

973992
return runAsyncResourceTask(this.#resource, async () => {
974993
let identifier;
975-
let generateSignature;
994+
let generateSignature: GenerateSignature;
976995
switch (provider) {
977996
case 'metamask':
978997
identifier = await getMetamaskIdentifier();
@@ -990,6 +1009,15 @@ class SignInFuture implements SignInFutureResource {
9901009
identifier = await getOKXWalletIdentifier();
9911010
generateSignature = generateSignatureWithOKXWallet;
9921011
break;
1012+
case 'solana':
1013+
if (!params.walletName) {
1014+
throw new ClerkRuntimeError('Wallet name is required for Solana authentication.', {
1015+
code: 'web3_solana_wallet_name_required',
1016+
});
1017+
}
1018+
identifier = await getSolanaIdentifier(params.walletName);
1019+
generateSignature = p => generateSignatureWithSolana({ ...p, walletName: params.walletName as string });
1020+
break;
9931021
default:
9941022
throw new Error(`Unsupported Web3 provider: ${provider}`);
9951023
}
@@ -1015,7 +1043,12 @@ class SignInFuture implements SignInFutureResource {
10151043

10161044
let signature: string;
10171045
try {
1018-
signature = await generateSignature({ identifier, nonce: message });
1046+
signature = await generateSignature({
1047+
identifier,
1048+
nonce: message,
1049+
walletName: params?.walletName,
1050+
provider,
1051+
});
10191052
} catch (err) {
10201053
// There is a chance that as a user when you try to setup and use the Coinbase Wallet with an existing
10211054
// Passkey in order to authenticate, the initial generate signature request to be rejected. For this
@@ -1024,7 +1057,11 @@ class SignInFuture implements SignInFutureResource {
10241057
// error code 4001 means the user rejected the request
10251058
// Reference: https://docs.cdp.coinbase.com/wallet-sdk/docs/errors
10261059
if (provider === 'coinbase_wallet' && err.code === 4001) {
1027-
signature = await generateSignature({ identifier, nonce: message });
1060+
signature = await generateSignature({
1061+
identifier,
1062+
nonce: message,
1063+
provider,
1064+
});
10281065
} else {
10291066
throw err;
10301067
}

0 commit comments

Comments
 (0)