Skip to content

Commit

Permalink
feat(clerk-js,types): Support Coinbase Wallet strategy during sign in…
Browse files Browse the repository at this point in the history
…/up flows (#4052)
  • Loading branch information
chanioxaris authored Aug 29, 2024
1 parent fece720 commit 3304dcc
Show file tree
Hide file tree
Showing 12 changed files with 189 additions and 33 deletions.
7 changes: 7 additions & 0 deletions .changeset/thin-gorillas-deliver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@clerk/clerk-js": minor
"@clerk/clerk-react": minor
"@clerk/types": minor
---

Add support for Coinbase Wallet strategy during sign in/up flows. Users can now authenticate using their Coinbase Wallet browser extension in the same way as MetaMask
32 changes: 27 additions & 5 deletions packages/clerk-js/src/core/clerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ import { logger } from '@clerk/shared/logger';
import { eventPrebuiltComponentMounted, TelemetryCollector } from '@clerk/shared/telemetry';
import type {
ActiveSessionResource,
AuthenticateWithCoinbaseParams,
AuthenticateWithGoogleOneTapParams,
AuthenticateWithMetamaskParams,
Clerk as ClerkInterface,
ClerkAPIError,
ClerkAuthenticateWithWeb3Params,
ClerkOptions,
ClientResource,
CreateOrganizationParams,
Expand Down Expand Up @@ -57,6 +59,7 @@ import type {
UserButtonProps,
UserProfileProps,
UserResource,
Web3Provider,
} from '@clerk/types';

import type { MountComponentRenderer } from '../ui/Components';
Expand All @@ -69,7 +72,10 @@ import {
createPageLifecycle,
disabledOrganizationsFeature,
errorThrower,
generateSignatureWithCoinbase,
generateSignatureWithMetamask,
getClerkQueryParam,
getWeb3Identifier,
hasExternalAccountSignUpError,
ignoreEventValue,
inActiveBrowserTab,
Expand Down Expand Up @@ -1333,25 +1339,41 @@ export class Clerk implements ClerkInterface {
}) as Promise<SignInResource | SignUpResource>;
};

public authenticateWithMetamask = async ({
public authenticateWithMetamask = async (props: AuthenticateWithMetamaskParams = {}): Promise<void> => {
await this.authenticateWithWeb3({ ...props, strategy: 'web3_metamask_signature' });
};

public authenticateWithCoinbase = async (props: AuthenticateWithCoinbaseParams = {}): Promise<void> => {
await this.authenticateWithWeb3({ ...props, strategy: 'web3_coinbase_signature' });
};

public authenticateWithWeb3 = async ({
redirectUrl,
signUpContinueUrl,
customNavigate,
unsafeMetadata,
}: AuthenticateWithMetamaskParams = {}): Promise<void> => {
strategy,
}: ClerkAuthenticateWithWeb3Params): Promise<void> => {
if (!this.client || !this.environment) {
return;
}

const provider = strategy.replace('web3_', '').replace('_signature', '') as Web3Provider;
const identifier = await getWeb3Identifier({ provider });
const generateSignature = provider === 'metamask' ? generateSignatureWithMetamask : generateSignatureWithCoinbase;
const navigate = (to: string) =>
customNavigate && typeof customNavigate === 'function' ? customNavigate(to) : this.navigate(to);

let signInOrSignUp: SignInResource | SignUpResource;
try {
signInOrSignUp = await this.client.signIn.authenticateWithMetamask();
signInOrSignUp = await this.client.signIn.authenticateWithWeb3({ identifier, generateSignature, strategy });
} catch (err) {
if (isError(err, ERROR_CODES.FORM_IDENTIFIER_NOT_FOUND)) {
signInOrSignUp = await this.client.signUp.authenticateWithMetamask({ unsafeMetadata });
signInOrSignUp = await this.client.signUp.authenticateWithWeb3({
identifier,
generateSignature,
unsafeMetadata,
strategy,
});

if (
signUpContinueUrl &&
Expand Down
41 changes: 33 additions & 8 deletions packages/clerk-js/src/core/resources/SignIn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,18 @@ import type {
SignInStartEmailLinkFlowParams,
SignInStatus,
VerificationResource,
Web3Provider,
Web3SignatureConfig,
Web3SignatureFactor,
} from '@clerk/types';

import { generateSignatureWithMetamask, getMetamaskIdentifier, windowNavigate } from '../../utils';
import {
generateSignatureWithCoinbase,
generateSignatureWithMetamask,
getCoinbaseIdentifier,
getMetamaskIdentifier,
windowNavigate,
} from '../../utils';
import {
ClerkWebAuthnError,
convertJSONToPublicKeyRequestOptions,
Expand Down Expand Up @@ -107,6 +114,9 @@ export class SignIn extends BaseResource implements SignInResource {
case 'web3_metamask_signature':
config = { web3WalletId: factor.web3WalletId } as Web3SignatureConfig;
break;
case 'web3_coinbase_signature':
config = { web3WalletId: factor.web3WalletId } as Web3SignatureConfig;
break;
case 'reset_password_phone_code':
config = { phoneNumberId: factor.phoneNumberId } as ResetPasswordPhoneCodeFactorConfig;
break;
Expand Down Expand Up @@ -223,16 +233,16 @@ export class SignIn extends BaseResource implements SignInResource {
};

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

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

await this.create({ identifier });

const web3FirstFactor = this.supportedFirstFactors?.find(
f => f.strategy === 'web3_metamask_signature',
) as Web3SignatureFactor;
const web3FirstFactor = this.supportedFirstFactors?.find(f => f.strategy === strategy) as Web3SignatureFactor;

if (!web3FirstFactor) {
clerkVerifyWeb3WalletCalledBeforeCreate('SignIn');
Expand All @@ -241,14 +251,19 @@ export class SignIn extends BaseResource implements SignInResource {
await this.prepareFirstFactor(web3FirstFactor);

const { nonce } = this.firstFactorVerification;
if (!nonce) {
clerkVerifyWeb3WalletCalledBeforeCreate('SignIn');
}

const signature = await generateSignature({
identifier: this.identifier!,
nonce: nonce!,
nonce: nonce,
provider,
});

return this.attemptFirstFactor({
signature,
strategy: 'web3_metamask_signature',
strategy,
});
};

Expand All @@ -257,6 +272,16 @@ export class SignIn extends BaseResource implements SignInResource {
return this.authenticateWithWeb3({
identifier,
generateSignature: generateSignatureWithMetamask,
strategy: 'web3_metamask_signature',
});
};

public authenticateWithCoinbase = async (): Promise<SignInResource> => {
const identifier = await getCoinbaseIdentifier();
return this.authenticateWithWeb3({
identifier,
generateSignature: generateSignatureWithCoinbase,
strategy: 'web3_coinbase_signature',
});
};

Expand Down Expand Up @@ -326,7 +351,7 @@ export class SignIn extends BaseResource implements SignInResource {
validatePassword: ReturnType<typeof createValidatePassword> = (password, cb) => {
if (SignIn.clerk.__unstable__environment?.userSettings.passwordSettings) {
return createValidatePassword({
...(SignIn.clerk.__unstable__environment?.userSettings.passwordSettings as any),
...SignIn.clerk.__unstable__environment?.userSettings.passwordSettings,
validatePassword: true,
})(password, cb);
}
Expand Down
50 changes: 38 additions & 12 deletions packages/clerk-js/src/core/resources/SignUp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import type {
PrepareEmailAddressVerificationParams,
PreparePhoneNumberVerificationParams,
PrepareVerificationParams,
SignUpAuthenticateWithMetamaskParams,
PrepareWeb3WalletVerificationParams,
SignUpAuthenticateWithWeb3Params,
SignUpCreateParams,
SignUpField,
SignUpIdentificationField,
Expand All @@ -19,14 +20,22 @@ import type {
SignUpStatus,
SignUpUpdateParams,
StartEmailLinkFlowParams,
Web3Provider,
} from '@clerk/types';

import { generateSignatureWithMetamask, getMetamaskIdentifier, windowNavigate } from '../../utils';
import {
generateSignatureWithCoinbase,
generateSignatureWithMetamask,
getCoinbaseIdentifier,
getMetamaskIdentifier,
windowNavigate,
} from '../../utils';
import { getCaptchaToken, retrieveCaptchaInfo } from '../../utils/captcha';
import { createValidatePassword } from '../../utils/passwords/password';
import { normalizeUnsafeMetadata } from '../../utils/resourceParams';
import {
clerkInvalidFAPIResponse,
clerkMissingOptionError,
clerkVerifyEmailAddressCalledBeforeCreate,
clerkVerifyWeb3WalletCalledBeforeCreate,
} from '../errors';
Expand Down Expand Up @@ -170,38 +179,55 @@ export class SignUp extends BaseResource implements SignUpResource {
return this.attemptVerification({ ...params, strategy: 'phone_code' });
};

prepareWeb3WalletVerification = (): Promise<SignUpResource> => {
return this.prepareVerification({ strategy: 'web3_metamask_signature' });
prepareWeb3WalletVerification = (params?: PrepareWeb3WalletVerificationParams): Promise<SignUpResource> => {
return this.prepareVerification({ strategy: 'web3_metamask_signature', ...params });
};

attemptWeb3WalletVerification = async (params: AttemptWeb3WalletVerificationParams): Promise<SignUpResource> => {
const { signature } = params;
return this.attemptVerification({ signature, strategy: 'web3_metamask_signature' });
const { signature, strategy = 'web3_metamask_signature' } = params;
return this.attemptVerification({ signature, strategy });
};

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

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

const web3Wallet = identifier || this.web3wallet!;
await this.create({ web3Wallet, unsafeMetadata });
await this.prepareWeb3WalletVerification();
await this.prepareWeb3WalletVerification({ strategy });

const { nonce } = this.verifications.web3Wallet;
if (!nonce) {
clerkVerifyWeb3WalletCalledBeforeCreate('SignUp');
}

const signature = await generateSignature({ identifier, nonce });
return this.attemptWeb3WalletVerification({ signature });
const signature = await generateSignature({ identifier, nonce, provider });
return this.attemptWeb3WalletVerification({ signature, strategy });
};

public authenticateWithMetamask = async (params?: SignUpAuthenticateWithMetamaskParams): Promise<SignUpResource> => {
public authenticateWithMetamask = async (params?: SignUpAuthenticateWithWeb3Params): Promise<SignUpResource> => {
const identifier = await getMetamaskIdentifier();
return this.authenticateWithWeb3({
identifier,
generateSignature: generateSignatureWithMetamask,
unsafeMetadata: params?.unsafeMetadata,
strategy: 'web3_metamask_signature',
});
};

public authenticateWithCoinbase = async (params?: SignUpAuthenticateWithWeb3Params): Promise<SignUpResource> => {
const identifier = await getCoinbaseIdentifier();
return this.authenticateWithWeb3({
identifier,
generateSignature: generateSignatureWithCoinbase,
unsafeMetadata: params?.unsafeMetadata,
strategy: 'web3_coinbase_signature',
});
};

Expand Down Expand Up @@ -245,7 +271,7 @@ export class SignUp extends BaseResource implements SignUpResource {
validatePassword: ReturnType<typeof createValidatePassword> = (password, cb) => {
if (SignUp.clerk.__unstable__environment?.userSettings.passwordSettings) {
return createValidatePassword({
...(SignUp.clerk.__unstable__environment?.userSettings.passwordSettings as any),
...SignUp.clerk.__unstable__environment?.userSettings.passwordSettings,
validatePassword: true,
})(password, cb);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ export const SignInSocialButtons = React.memo((props: SocialButtonsProps) => {
.authenticateWithRedirect({ strategy, redirectUrl, redirectUrlComplete })
.catch(err => handleError(err, [], card.setError));
}}
web3Callback={() => {
web3Callback={strategy => {
return clerk
.authenticateWithMetamask({
.authenticateWithWeb3({
customNavigate: navigate,
redirectUrl: redirectUrlComplete,
signUpContinueUrl: ctx.signUpContinueUrl,
strategy,
})
.catch(err => handleError(err, [], card.setError));
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,14 @@ export const SignUpSocialButtons = React.memo((props: SignUpSocialButtonsProps)
})
.catch(err => handleError(err, [], card.setError));
}}
web3Callback={() => {
web3Callback={strategy => {
return clerk
.authenticateWithMetamask({
.authenticateWithWeb3({
customNavigate: navigate,
redirectUrl: redirectUrlComplete,
signUpContinueUrl: 'continue',
unsafeMetadata: ctx.unsafeMetadata,
strategy,
})
.catch(err => handleError(err, [], card.setError));
}}
Expand Down
8 changes: 8 additions & 0 deletions packages/clerk-js/src/utils/web3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ export async function getMetamaskIdentifier(): Promise<string> {
return await getWeb3Identifier({ provider: 'metamask' });
}

export async function getCoinbaseIdentifier(): Promise<string> {
return await getWeb3Identifier({ provider: 'coinbase' });
}

type GenerateSignatureParams = {
identifier: string;
nonce: string;
Expand All @@ -53,3 +57,7 @@ type GenerateSignatureParams = {
export async function generateSignatureWithMetamask({ identifier, nonce }: GenerateSignatureParams): Promise<string> {
return await generateWeb3Signature({ identifier, nonce, provider: 'metamask' });
}

export async function generateSignatureWithCoinbase({ identifier, nonce }: GenerateSignatureParams): Promise<string> {
return await generateWeb3Signature({ identifier, nonce, provider: 'coinbase' });
}
Loading

0 comments on commit 3304dcc

Please sign in to comment.