Skip to content

Commit db6b18e

Browse files
kdupreydstaleyjacekradko
authored
feat(clerk-js,react,localizations,shared,ui): Add Solana feature support to core 3 (#7450)
Signed-off-by: Kenton Duprey <[email protected]> Co-authored-by: Dylan Staley <[email protected]> Co-authored-by: Jacek Radko <[email protected]>
1 parent 7c12ada commit db6b18e

Some content is hidden

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

94 files changed

+3886
-354
lines changed

.changeset/afraid-apes-cough.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@clerk/localizations': minor
3+
'@clerk/clerk-js': minor
4+
'@clerk/shared': minor
5+
---
6+
7+
Add Web3 Solana support to `<UserProfile />`

.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/shared': minor
5+
'@clerk/react': minor
6+
'@clerk/ui': 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: 57 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,31 @@ export class SignIn extends BaseResource implements SignInResource {
444451
});
445452
};
446453

454+
/**
455+
* Authenticates a user using a Solana Web3 wallet during sign-in.
456+
*
457+
* @param params - Configuration for Solana authentication
458+
* @param params.walletName - The name of the Solana wallet to use for authentication
459+
* @returns A promise that resolves to the updated SignIn resource
460+
* @throws {ClerkRuntimeError} If walletName is not provided or wallet connection fails
461+
*
462+
* @example
463+
* ```typescript
464+
* await signIn.authenticateWithSolana({ walletName: 'phantom' });
465+
* ```
466+
*/
467+
public authenticateWithSolana = async ({
468+
walletName,
469+
}: SignInAuthenticateWithSolanaParams): Promise<SignInResource> => {
470+
const identifier = await web3().getSolanaIdentifier(walletName);
471+
return this.authenticateWithWeb3({
472+
identifier,
473+
generateSignature: p => web3().generateSignatureWithSolana({ ...p, walletName: walletName }),
474+
strategy: 'web3_solana_signature',
475+
walletName: walletName,
476+
});
477+
};
478+
447479
public authenticateWithPasskey = async (params?: AuthenticateWithPasskeyParams): Promise<SignInResource> => {
448480
const { flow } = params || {};
449481

@@ -961,7 +993,7 @@ class SignInFuture implements SignInFutureResource {
961993

962994
return runAsyncResourceTask(this.#resource, async () => {
963995
let identifier;
964-
let generateSignature;
996+
let generateSignature: GenerateSignature;
965997
switch (provider) {
966998
case 'metamask':
967999
identifier = await web3().getMetamaskIdentifier();
@@ -979,6 +1011,16 @@ class SignInFuture implements SignInFutureResource {
9791011
identifier = await web3().getOKXWalletIdentifier();
9801012
generateSignature = web3().generateSignatureWithOKXWallet;
9811013
break;
1014+
case 'solana':
1015+
if (!params.walletName) {
1016+
throw new ClerkRuntimeError('Wallet name is required for Solana authentication.', {
1017+
code: 'web3_solana_wallet_name_required',
1018+
});
1019+
}
1020+
identifier = await web3().getSolanaIdentifier(params.walletName);
1021+
generateSignature = p =>
1022+
web3().generateSignatureWithSolana({ ...p, walletName: params.walletName as string });
1023+
break;
9821024
default:
9831025
throw new Error(`Unsupported Web3 provider: ${provider}`);
9841026
}
@@ -1004,7 +1046,12 @@ class SignInFuture implements SignInFutureResource {
10041046

10051047
let signature: string;
10061048
try {
1007-
signature = await generateSignature({ identifier, nonce: message });
1049+
signature = await generateSignature({
1050+
identifier,
1051+
nonce: message,
1052+
walletName: params?.walletName,
1053+
provider,
1054+
});
10081055
} catch (err) {
10091056
// There is a chance that as a user when you try to setup and use the Coinbase Wallet with an existing
10101057
// Passkey in order to authenticate, the initial generate signature request to be rejected. For this
@@ -1013,7 +1060,11 @@ class SignInFuture implements SignInFutureResource {
10131060
// error code 4001 means the user rejected the request
10141061
// Reference: https://docs.cdp.coinbase.com/wallet-sdk/docs/errors
10151062
if (provider === 'coinbase_wallet' && err.code === 4001) {
1016-
signature = await generateSignature({ identifier, nonce: message });
1063+
signature = await generateSignature({
1064+
identifier,
1065+
nonce: message,
1066+
provider,
1067+
});
10171068
} else {
10181069
throw err;
10191070
}

0 commit comments

Comments
 (0)