Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
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
4 changes: 4 additions & 0 deletions packages/clerk-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,13 @@
"@floating-ui/react": "0.27.12",
"@floating-ui/react-dom": "^2.1.3",
"@formkit/auto-animate": "^0.8.2",
"@solana/wallet-adapter-base": "0.9.27",
"@solana/wallet-adapter-react": "0.15.39",
"@solana/wallet-standard": "1.1.4",
"@stripe/stripe-js": "5.6.0",
"@swc/helpers": "^0.5.17",
"@tanstack/query-core": "5.87.4",
"@wallet-standard/core": "1.1.1",
"@zxcvbn-ts/core": "3.0.4",
"@zxcvbn-ts/language-common": "3.0.4",
"alien-signals": "2.0.6",
Expand Down
21 changes: 18 additions & 3 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import type {
AuthenticateWithGoogleOneTapParams,
AuthenticateWithMetamaskParams,
AuthenticateWithOKXWalletParams,
AuthenticateWithSolanaParams,
BillingNamespace,
Clerk as ClerkInterface,
ClerkAPIError,
Expand All @@ -48,7 +49,7 @@ import type {
EnvironmentJSON,
EnvironmentJSONSnapshot,
EnvironmentResource,
GenerateSignatureParams,
GenerateSignature,
GoogleOneTapProps,
HandleEmailLinkVerificationParams,
HandleOAuthCallbackParams,
Expand Down Expand Up @@ -116,6 +117,7 @@ import {
generateSignatureWithCoinbaseWallet,
generateSignatureWithMetamask,
generateSignatureWithOKXWallet,
generateSignatureWithSolana,
getClerkQueryParam,
getWeb3Identifier,
hasExternalAccountSignUpError,
Expand Down Expand Up @@ -2255,6 +2257,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 @@ -2263,6 +2272,7 @@ export class Clerk implements ClerkInterface {
strategy,
legalAccepted,
secondFactorUrl,
walletName,
}: ClerkAuthenticateWithWeb3Params): Promise<void> => {
if (!this.client || !this.environment) {
return;
Expand All @@ -2271,8 +2281,8 @@ export class Clerk implements ClerkInterface {
const { displayConfig } = this.environment;

const provider = strategy.replace('web3_', '').replace('_signature', '') as Web3Provider;
const identifier = await getWeb3Identifier({ provider });
let generateSignature: (params: GenerateSignatureParams) => Promise<string>;
const identifier = await getWeb3Identifier({ provider, walletName });
let generateSignature: GenerateSignature;
switch (provider) {
case 'metamask':
generateSignature = generateSignatureWithMetamask;
Expand All @@ -2283,6 +2293,9 @@ export class Clerk implements ClerkInterface {
case 'coinbase_wallet':
generateSignature = generateSignatureWithCoinbaseWallet;
break;
case 'solana':
generateSignature = generateSignatureWithSolana;
break;
default:
generateSignature = generateSignatureWithOKXWallet;
break;
Expand Down Expand Up @@ -2312,6 +2325,7 @@ export class Clerk implements ClerkInterface {
identifier,
generateSignature,
strategy,
walletName,
});
} catch (err) {
if (isError(err, ERROR_CODES.FORM_IDENTIFIER_NOT_FOUND)) {
Expand All @@ -2321,6 +2335,7 @@ export class Clerk implements ClerkInterface {
unsafeMetadata,
strategy,
legalAccepted,
walletName,
});

if (
Expand Down
44 changes: 38 additions & 6 deletions packages/clerk-js/src/core/resources/SignIn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
AuthenticateWithPasskeyParams,
AuthenticateWithPopupParams,
AuthenticateWithRedirectParams,
AuthenticateWithSolanaParams,
AuthenticateWithWeb3Params,
ClientTrustState,
CreateEmailLinkFlowReturn,
Expand All @@ -15,6 +16,7 @@ import type {
EmailLinkConfig,
EmailLinkFactor,
EnterpriseSSOConfig,
GenerateSignature,
PassKeyConfig,
PasskeyFactor,
PhoneCodeConfig,
Expand Down Expand Up @@ -69,12 +71,14 @@ import {
generateSignatureWithCoinbaseWallet,
generateSignatureWithMetamask,
generateSignatureWithOKXWallet,
generateSignatureWithSolana,
getBaseIdentifier,
getBrowserLocale,
getClerkQueryParam,
getCoinbaseWalletIdentifier,
getMetamaskIdentifier,
getOKXWalletIdentifier,
getSolanaIdentifier,
windowNavigate,
} from '../../utils';
import {
Expand Down Expand Up @@ -212,6 +216,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 @@ -379,13 +384,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 @@ -403,7 +412,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 @@ -412,7 +421,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 @@ -460,6 +469,16 @@ export class SignIn extends BaseResource implements SignInResource {
});
};

public authenticateWithSolana = async (params: AuthenticateWithSolanaParams): Promise<SignInResource> => {
const identifier = await getSolanaIdentifier(params.walletName);
return this.authenticateWithWeb3({
identifier,
generateSignature: generateSignatureWithSolana,
strategy: 'web3_solana_signature',
walletName: params.walletName,
});
};

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

Expand Down Expand Up @@ -968,7 +987,7 @@ class SignInFuture implements SignInFutureResource {

return runAsyncResourceTask(this.resource, async () => {
let identifier;
let generateSignature;
let generateSignature: GenerateSignature;
switch (provider) {
case 'metamask':
identifier = await getMetamaskIdentifier();
Expand All @@ -986,6 +1005,10 @@ class SignInFuture implements SignInFutureResource {
identifier = await getOKXWalletIdentifier();
generateSignature = generateSignatureWithOKXWallet;
break;
case 'solana':
identifier = await getSolanaIdentifier(params.walletName);
generateSignature = generateSignatureWithSolana;
break;
default:
throw new Error(`Unsupported Web3 provider: ${provider}`);
}
Expand All @@ -1011,7 +1034,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 @@ -1020,7 +1048,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
22 changes: 21 additions & 1 deletion packages/clerk-js/src/core/resources/SignUp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@ import {
generateSignatureWithCoinbaseWallet,
generateSignatureWithMetamask,
generateSignatureWithOKXWallet,
generateSignatureWithSolana,
getBaseIdentifier,
getBrowserLocale,
getClerkQueryParam,
getCoinbaseWalletIdentifier,
getMetamaskIdentifier,
getOKXWalletIdentifier,
getSolanaIdentifier,
windowNavigate,
} from '../../utils';
import {
Expand Down Expand Up @@ -278,6 +280,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 @@ -297,7 +300,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 @@ -376,6 +379,23 @@ export class SignUp extends BaseResource implements SignUpResource {
});
};

public authenticateWithSolana = async (
params: SignUpAuthenticateWithWeb3Params & {
walletName: string;
legalAccepted?: boolean;
},
): Promise<SignUpResource> => {
const identifier = await getSolanaIdentifier(params.walletName);
return this.authenticateWithWeb3({
identifier,
generateSignature: generateSignatureWithSolana,
unsafeMetadata: params?.unsafeMetadata,
strategy: 'web3_solana_signature',
legalAccepted: params?.legalAccepted,
walletName: params.walletName,
});
};

private authenticateWithRedirectOrPopup = async (
params: AuthenticateWithRedirectParams & {
unsafeMetadata?: SignUpUnsafeMetadata;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useClerk } from '@clerk/shared/react';

import { withRedirectToAfterSignIn, withRedirectToSignInTask } from '@/ui/common/withRedirect';
import { descriptors, Flex, Flow } from '@/ui/customizables';
import { BackLink } from '@/ui/elements/BackLink';
import { Card } from '@/ui/elements/Card';
import { useCardState, withCardStateProvider } from '@/ui/elements/contexts';
import { Header } from '@/ui/elements/Header';
import { Web3WalletButtons } from '@/ui/elements/Web3WalletButtons';
import { web3CallbackErrorHandler } from '@/ui/utils/web3CallbackErrorHandler';

import { useSignInContext } from '../../contexts';
import { useRouter } from '../../router';

const SignInFactorOneSolanaWalletsCardInner = () => {
const clerk = useClerk();
const card = useCardState();
const router = useRouter();
const ctx = useSignInContext();

const onBackLinkClick = () => {
void router.navigate('../');
};

return (
<Flow.Part part='choose-wallet'>
<Card.Root>
<Card.Content>
<Header.Root showLogo>
<Header.Title>Continue with Solana Wallet</Header.Title>
<Header.Subtitle>Select a wallet below to sign in</Header.Subtitle>
</Header.Root>
<Card.Alert>{card.error}</Card.Alert>
<Flex
direction='col'
gap={4}
>
<Web3WalletButtons
web3AuthCallback={({ walletName }) => {
return clerk
.authenticateWithWeb3({
customNavigate: router.navigate,
redirectUrl: ctx.afterSignInUrl || '/',
secondFactorUrl: 'factor-two',
signUpContinueUrl: ctx.isCombinedFlow ? '../create/continue' : ctx.signUpContinueUrl,
strategy: 'web3_solana_signature',
walletName,
})
.catch(err => web3CallbackErrorHandler(err, card.setError));
}}
/>

<BackLink
boxElementDescriptor={descriptors.backRow}
linkElementDescriptor={descriptors.backLink}
onClick={onBackLinkClick}
/>
</Flex>
</Card.Content>
<Card.Footer />
</Card.Root>
</Flow.Part>
);
};

export const SignInFactorOneSolanaWalletsCard = withRedirectToSignInTask(
withRedirectToAfterSignIn(withCardStateProvider(SignInFactorOneSolanaWalletsCardInner)),
);
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ export const SignInSocialButtons = React.memo((props: SignInSocialButtonsProps)
.catch(err => handleError(err, [], card.setError));
}}
web3Callback={strategy => {
if (strategy === 'web3_solana_signature') {
return navigate(`choose-wallet?strategy=${strategy}`);
}

return clerk
.authenticateWithWeb3({
customNavigate: navigate,
Expand Down
4 changes: 4 additions & 0 deletions packages/clerk-js/src/ui/components/SignIn/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { SignInModalProps, SignInProps } from '@clerk/shared/types';
import React from 'react';

import { SignInEmailLinkFlowComplete, SignUpEmailLinkFlowComplete } from '@/ui/common/EmailLinkCompleteFlowCard';
import { SignInFactorOneSolanaWalletsCard } from '@/ui/components/SignIn/SignInFactorOneSolanaWalletsCard';
import {
SignInContext,
SignUpContext,
Expand Down Expand Up @@ -77,6 +78,9 @@ function SignInRoutes(): JSX.Element {
<Route path='choose'>
<SignInAccountSwitcher />
</Route>
<Route path='choose-wallet'>
<SignInFactorOneSolanaWalletsCard />
</Route>
<Route path='verify'>
<SignInEmailLinkFlowComplete
redirectUrlComplete={signInContext.afterSignInUrl}
Expand Down
Loading