Skip to content
Open
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
7 changes: 7 additions & 0 deletions .changeset/afraid-apes-cough.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@clerk/localizations': minor
'@clerk/clerk-js': minor
'@clerk/shared': minor
---

Add Web3 Solana support to `<UserProfile />`
9 changes: 9 additions & 0 deletions .changeset/legal-jokes-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@clerk/localizations': minor
'@clerk/clerk-js': minor
'@clerk/shared': minor
'@clerk/react': minor
'@clerk/ui': minor
---

Add support for Sign in with Solana.
4 changes: 2 additions & 2 deletions packages/clerk-js/bundle-check.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import path from 'node:path';
import { pipeline } from 'node:stream';
import zlib from 'node:zlib';

import { chromium } from 'playwright';
import { chromium } from '@playwright/test';

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

/**
* Loads the given `url` in `browser`, capturing all HTTP requests that occur.
* @param {import('playwright').Browser} browser
* @param {import('@playwright/test').Browser} browser
* @param {string} url
*/
async function getResponseSizes(browser, url) {
Expand Down
4 changes: 4 additions & 0 deletions packages/clerk-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,13 @@
"@base-org/account": "catalog:module-manager",
"@clerk/shared": "workspace:^",
"@coinbase/wallet-sdk": "catalog:module-manager",
"@solana/wallet-adapter-base": "catalog:module-manager",
"@solana/wallet-adapter-react": "catalog:module-manager",
"@solana/wallet-standard": "catalog:module-manager",
"@stripe/stripe-js": "5.6.0",
"@swc/helpers": "^0.5.17",
"@tanstack/query-core": "5.87.4",
"@wallet-standard/core": "catalog:module-manager",
"@zxcvbn-ts/core": "catalog:module-manager",
"@zxcvbn-ts/language-common": "catalog:module-manager",
"alien-signals": "2.0.6",
Expand Down
15 changes: 14 additions & 1 deletion packages/clerk-js/rspack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,20 @@ const common = ({ mode, variant, disableRHC = false }) => {
},
defaultVendors: {
minChunks: 1,
test: /[\\/]node_modules[\\/]/,
test: module => {
if (!(module instanceof rspack.NormalModule) || !module.resource) {
return false;
}
// Exclude Solana packages and their known transitive dependencies
if (
/[\\/]node_modules[\\/](@solana|@solana-mobile|@wallet-standard|bn\.js|borsh|buffer|superstruct|bs58|jayson|rpc-websockets|qrcode)[\\/]/.test(
module.resource,
)
) {
return false;
}
return /[\\/]node_modules[\\/]/.test(module.resource);
},
name: 'vendors',
priority: -10,
},
Expand Down
26 changes: 23 additions & 3 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import type {
AuthenticateWithGoogleOneTapParams,
AuthenticateWithMetamaskParams,
AuthenticateWithOKXWalletParams,
AuthenticateWithSolanaParams,
BillingNamespace,
CheckoutSignalValue,
Clerk as ClerkInterface,
Expand All @@ -74,7 +75,7 @@ import type {
EnvironmentJSON,
EnvironmentJSONSnapshot,
EnvironmentResource,
GenerateSignatureParams,
GenerateSignature,
GoogleOneTapProps,
HandleEmailLinkVerificationParams,
HandleOAuthCallbackParams,
Expand Down Expand Up @@ -2338,6 +2339,13 @@ export class Clerk implements ClerkInterface {
});
};

public authenticateWithSolana = async (props: AuthenticateWithSolanaParams): Promise<void> => {
await this.authenticateWithWeb3({
...props,
strategy: 'web3_solana_signature',
});
};

public authenticateWithWeb3 = async ({
redirectUrl,
signUpContinueUrl,
Expand All @@ -2346,6 +2354,7 @@ export class Clerk implements ClerkInterface {
strategy,
legalAccepted,
secondFactorUrl,
walletName,
}: ClerkAuthenticateWithWeb3Params): Promise<void> => {
if (!this.client || !this.environment) {
return;
Expand All @@ -2354,8 +2363,8 @@ export class Clerk implements ClerkInterface {
const { displayConfig } = this.environment;

const provider = strategy.replace('web3_', '').replace('_signature', '') as Web3Provider;
const identifier = await web3().getWeb3Identifier({ provider });
let generateSignature: (params: GenerateSignatureParams) => Promise<string>;
const identifier = await web3().getWeb3Identifier({ provider, walletName });
let generateSignature: GenerateSignature;
switch (provider) {
case 'metamask':
generateSignature = web3().generateSignatureWithMetamask;
Expand All @@ -2366,6 +2375,15 @@ export class Clerk implements ClerkInterface {
case 'coinbase_wallet':
generateSignature = web3().generateSignatureWithCoinbaseWallet;
break;
case 'solana':
if (!walletName) {
throw new ClerkRuntimeError('Wallet name is required for Solana authentication.', {
code: 'web3_solana_wallet_name_required',
});
}
// Solana requires walletName; bind it into the helper
generateSignature = params => web3().generateSignatureWithSolana({ ...params, walletName });
break;
default:
generateSignature = web3().generateSignatureWithOKXWallet;
break;
Expand Down Expand Up @@ -2395,6 +2413,7 @@ export class Clerk implements ClerkInterface {
identifier,
generateSignature,
strategy,
walletName,
});
} catch (err) {
if (isError(err, ERROR_CODES.FORM_IDENTIFIER_NOT_FOUND)) {
Expand All @@ -2404,6 +2423,7 @@ export class Clerk implements ClerkInterface {
unsafeMetadata,
strategy,
legalAccepted,
walletName,
});

if (
Expand Down
48 changes: 42 additions & 6 deletions packages/clerk-js/src/core/resources/SignIn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type {
EmailLinkConfig,
EmailLinkFactor,
EnterpriseSSOConfig,
GenerateSignature,
PassKeyConfig,
PasskeyFactor,
PhoneCodeConfig,
Expand All @@ -32,6 +33,7 @@ import type {
ResetPasswordEmailCodeFactorConfig,
ResetPasswordParams,
ResetPasswordPhoneCodeFactorConfig,
SignInAuthenticateWithSolanaParams,
SignInCreateParams,
SignInFirstFactor,
SignInFutureBackupCodeVerifyParams,
Expand Down Expand Up @@ -202,6 +204,7 @@ export class SignIn extends BaseResource implements SignInResource {
case 'web3_base_signature':
case 'web3_coinbase_wallet_signature':
case 'web3_okx_wallet_signature':
case 'web3_solana_signature':
config = { web3WalletId: params.web3WalletId } as Web3SignatureConfig;
break;
case 'reset_password_phone_code':
Expand Down Expand Up @@ -363,13 +366,17 @@ export class SignIn extends BaseResource implements SignInResource {
};

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

if (!(typeof generateSignature === 'function')) {
clerkMissingOptionError('generateSignature');
}

if (provider === 'solana' && !walletName) {
clerkMissingOptionError('walletName');
}

await this.create({ identifier });

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

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

public authenticateWithSolana = async (params: SignInAuthenticateWithSolanaParams): Promise<SignInResource> => {
const identifier = await web3().getSolanaIdentifier(params.walletName);
return this.authenticateWithWeb3({
identifier,
generateSignature: p => web3().generateSignatureWithSolana({ ...p, walletName: params.walletName }),
strategy: 'web3_solana_signature',
walletName: params.walletName,
});
};
Comment on lines +454 to +462
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add JSDoc documentation for the new public API.

The new authenticateWithSolana method is a public API and should include JSDoc comments explaining parameters, return value, and usage.

As per coding guidelines, all public APIs must be documented with JSDoc.

Add documentation like this:

+  /**
+   * Authenticates a user using a Solana Web3 wallet.
+   *
+   * @param params - Configuration for Solana authentication
+   * @param params.walletName - The name of the Solana wallet to use for authentication
+   * @returns A promise that resolves to the updated SignIn resource
+   * @throws {ClerkRuntimeError} If walletName is not provided or wallet connection fails
+   *
+   * @example
+   * ```typescript
+   * await signIn.authenticateWithSolana({ walletName: 'phantom' });
+   * ```
+   */
   public authenticateWithSolana = async (params: SignInAuthenticateWithSolanaParams): Promise<SignInResource> => {
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 454 to 462, the
new public method authenticateWithSolana lacks JSDoc; add a JSDoc block
immediately above the method that documents the params
(SignInAuthenticateWithSolanaParams and walletName), the Promise<SignInResource>
return value, a short description of what the method does (authenticates using a
Solana wallet), and a usage example (e.g. await signIn.authenticateWithSolana({
walletName: 'phantom' })); keep tags @param and @returns and format the example
inside a code fence as in project guidelines.


public authenticateWithPasskey = async (params?: AuthenticateWithPasskeyParams): Promise<SignInResource> => {
const { flow } = params || {};

Expand Down Expand Up @@ -961,7 +978,7 @@ class SignInFuture implements SignInFutureResource {

return runAsyncResourceTask(this.#resource, async () => {
let identifier;
let generateSignature;
let generateSignature: GenerateSignature;
switch (provider) {
case 'metamask':
identifier = await web3().getMetamaskIdentifier();
Expand All @@ -979,6 +996,16 @@ class SignInFuture implements SignInFutureResource {
identifier = await web3().getOKXWalletIdentifier();
generateSignature = web3().generateSignatureWithOKXWallet;
break;
case 'solana':
if (!params.walletName) {
throw new ClerkRuntimeError('Wallet name is required for Solana authentication.', {
code: 'web3_solana_wallet_name_required',
});
}
identifier = await web3().getSolanaIdentifier(params.walletName);
generateSignature = p =>
web3().generateSignatureWithSolana({ ...p, walletName: params.walletName as string });
break;
default:
throw new Error(`Unsupported Web3 provider: ${provider}`);
}
Expand All @@ -1004,7 +1031,12 @@ class SignInFuture implements SignInFutureResource {

let signature: string;
try {
signature = await generateSignature({ identifier, nonce: message });
signature = await generateSignature({
identifier,
nonce: message,
walletName: params?.walletName,
provider,
});
} catch (err) {
// There is a chance that as a user when you try to setup and use the Coinbase Wallet with an existing
// Passkey in order to authenticate, the initial generate signature request to be rejected. For this
Expand All @@ -1013,7 +1045,11 @@ class SignInFuture implements SignInFutureResource {
// error code 4001 means the user rejected the request
// Reference: https://docs.cdp.coinbase.com/wallet-sdk/docs/errors
if (provider === 'coinbase_wallet' && err.code === 4001) {
signature = await generateSignature({ identifier, nonce: message });
signature = await generateSignature({
identifier,
nonce: message,
provider,
});
} else {
throw err;
}
Expand Down
32 changes: 18 additions & 14 deletions packages/clerk-js/src/core/resources/SignUp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type {
PreparePhoneNumberVerificationParams,
PrepareVerificationParams,
PrepareWeb3WalletVerificationParams,
SignUpAuthenticateWithSolanaParams,
SignUpAuthenticateWithWeb3Params,
SignUpCreateParams,
SignUpEnterpriseConnectionJSON,
Expand Down Expand Up @@ -268,6 +269,7 @@ export class SignUp extends BaseResource implements SignUpResource {
unsafeMetadata,
strategy = 'web3_metamask_signature',
legalAccepted,
walletName,
} = params || {};
const provider = strategy.replace('web3_', '').replace('_signature', '') as Web3Provider;

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

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

public authenticateWithCoinbaseWallet = async (
params?: SignUpAuthenticateWithWeb3Params & {
legalAccepted?: boolean;
},
params?: SignUpAuthenticateWithWeb3Params,
): Promise<SignUpResource> => {
const identifier = await web3().getCoinbaseWalletIdentifier();
return this.authenticateWithWeb3({
Expand All @@ -336,11 +336,7 @@ export class SignUp extends BaseResource implements SignUpResource {
});
};

public authenticateWithBase = async (
params?: SignUpAuthenticateWithWeb3Params & {
legalAccepted?: boolean;
},
): Promise<SignUpResource> => {
public authenticateWithBase = async (params?: SignUpAuthenticateWithWeb3Params): Promise<SignUpResource> => {
const identifier = await web3().getBaseIdentifier();
return this.authenticateWithWeb3({
identifier,
Expand All @@ -351,11 +347,7 @@ export class SignUp extends BaseResource implements SignUpResource {
});
};

public authenticateWithOKXWallet = async (
params?: SignUpAuthenticateWithWeb3Params & {
legalAccepted?: boolean;
},
): Promise<SignUpResource> => {
public authenticateWithOKXWallet = async (params?: SignUpAuthenticateWithWeb3Params): Promise<SignUpResource> => {
const identifier = await web3().getOKXWalletIdentifier();
return this.authenticateWithWeb3({
identifier,
Expand All @@ -366,6 +358,18 @@ export class SignUp extends BaseResource implements SignUpResource {
});
};

public authenticateWithSolana = async (params: SignUpAuthenticateWithSolanaParams): Promise<SignUpResource> => {
const identifier = await web3().getSolanaIdentifier(params.walletName);
return this.authenticateWithWeb3({
identifier,
generateSignature: p => web3().generateSignatureWithSolana({ ...p, walletName: params.walletName }),
unsafeMetadata: params?.unsafeMetadata,
strategy: 'web3_solana_signature',
legalAccepted: params?.legalAccepted,
walletName: params.walletName,
});
};
Comment on lines +361 to +371
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add JSDoc documentation for the new public API.

The new authenticateWithSolana method is a public API and should include JSDoc comments explaining parameters, return value, and usage.

As per coding guidelines, all public APIs must be documented with JSDoc.

Add documentation like this:

+  /**
+   * Authenticates a user using a Solana Web3 wallet during sign-up.
+   *
+   * @param params - Configuration for Solana authentication
+   * @param params.walletName - The name of the Solana wallet to use (e.g., 'phantom', 'solflare')
+   * @param params.unsafeMetadata - Optional unsafe metadata to attach to the user
+   * @param params.legalAccepted - Optional flag indicating legal terms acceptance
+   * @returns A promise that resolves to the updated SignUp resource
+   * @throws {ClerkRuntimeError} If wallet connection fails
+   *
+   * @example
+   * ```typescript
+   * await signUp.authenticateWithSolana({ 
+   *   walletName: 'phantom',
+   *   legalAccepted: true 
+   * });
+   * ```
+   */
   public authenticateWithSolana = async (params: SignUpAuthenticateWithSolanaParams): Promise<SignUpResource> => {
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignUp.ts around lines 361 to 371, the
new public method authenticateWithSolana is missing JSDoc; add a JSDoc block
immediately above the method that describes the method purpose, lists @param
entries for the SignUpAuthenticateWithSolanaParams fields (at least walletName,
legalAccepted, unsafeMetadata), specifies the @returns Promise<SignUpResource>,
and includes a short usage example showing await signUp.authenticateWithSolana({
walletName: 'phantom', legalAccepted: true });; ensure the JSDoc is properly
closed and formatted to satisfy the project's documentation standards.


private authenticateWithRedirectOrPopup = async (
params: AuthenticateWithRedirectParams & {
unsafeMetadata?: SignUpUnsafeMetadata;
Expand Down
Loading
Loading