diff --git a/.changeset/purple-rice-tell.md b/.changeset/purple-rice-tell.md new file mode 100644 index 00000000000..3937076b244 --- /dev/null +++ b/.changeset/purple-rice-tell.md @@ -0,0 +1,7 @@ +--- +"@clerk/localizations": minor +"@clerk/clerk-js": minor +"@clerk/types": minor +--- + +Support connecting Coinbase Wallet via diff --git a/packages/clerk-js/src/core/resources/User.ts b/packages/clerk-js/src/core/resources/User.ts index 857778d0991..e30b96ecc7f 100644 --- a/packages/clerk-js/src/core/resources/User.ts +++ b/packages/clerk-js/src/core/resources/User.ts @@ -392,6 +392,10 @@ export class User extends BaseResource implements UserResource { return this.externalAccounts.filter(externalAccount => externalAccount.verification?.status != 'verified'); } + get verifiedWeb3Wallets() { + return this.web3Wallets.filter(web3Wallet => web3Wallet.verification?.status == 'verified'); + } + get hasVerifiedEmailAddress() { return this.emailAddresses.filter(email => email.verification.status === 'verified').length > 0; } diff --git a/packages/clerk-js/src/ui/components/UserProfile/Web3Form.tsx b/packages/clerk-js/src/ui/components/UserProfile/Web3Form.tsx index b2f8509e40d..3f0178c420e 100644 --- a/packages/clerk-js/src/ui/components/UserProfile/Web3Form.tsx +++ b/packages/clerk-js/src/ui/components/UserProfile/Web3Form.tsx @@ -1,7 +1,7 @@ import { useUser } from '@clerk/shared/react'; -import type { Web3Strategy } from '@clerk/types'; +import type { Web3Provider, Web3Strategy } from '@clerk/types'; -import { generateSignatureWithMetamask, getMetamaskIdentifier } from '../../../utils/web3'; +import { generateWeb3Signature, getWeb3Identifier } from '../../../utils/web3'; import { descriptors, Image, localizationKeys } from '../../customizables'; import { ProfileSection, useCardState, withCardStateProvider } from '../../elements'; import { useEnabledThirdPartyProviders } from '../../hooks'; @@ -10,33 +10,33 @@ import { getFieldError, handleError } from '../../utils'; export const AddWeb3WalletActionMenu = withCardStateProvider(() => { const card = useCardState(); const { user } = useUser(); - const { strategyToDisplayData } = useEnabledThirdPartyProviders(); + const { strategies, strategyToDisplayData } = useEnabledThirdPartyProviders(); + + const enabledStrategies = strategies.filter(s => s.startsWith('web3')) as Web3Strategy[]; + const connectedStrategies = user?.verifiedWeb3Wallets.map(w => w.verification.strategy) as Web3Strategy[]; + const unconnectedStrategies = enabledStrategies.filter(strategy => { + return !connectedStrategies.includes(strategy); + }); - // TODO: This logic is very similar to AddConnectedAccount but only metamask is supported right now - // const enabledStrategies = strategies.filter(s => s.startsWith('web3')) as Web3Strategy[]; - // const connectedStrategies = user.web3Wallets.map(w => w.web3Wallet) as OAuthStrategy[]; - const unconnectedStrategies: Web3Strategy[] = - user?.web3Wallets.filter(w => w.verification?.status === 'verified').length === 0 - ? ['web3_metamask_signature'] - : []; const connect = async (strategy: Web3Strategy) => { + const provider = strategy.replace('web3_', '').replace('_signature', '') as Web3Provider; + try { card.setLoading(strategy); - const identifier = await getMetamaskIdentifier(); + const identifier = await getWeb3Identifier({ provider }); if (!user) { throw new Error('user is not defined'); } let web3Wallet = await user.createWeb3Wallet({ web3Wallet: identifier }); - web3Wallet = await web3Wallet.prepareVerification({ strategy: 'web3_metamask_signature' }); + web3Wallet = await web3Wallet.prepareVerification({ strategy }); const nonce = web3Wallet.verification.nonce as string; - const signature = await generateSignatureWithMetamask({ identifier, nonce }); + const signature = await generateWeb3Signature({ identifier, nonce, provider }); await web3Wallet.attemptVerification({ signature }); card.setIdle(); } catch (err) { card.setIdle(); - console.log(err); const fieldError = getFieldError(err); if (fieldError) { card.setError(fieldError.longMessage); @@ -62,7 +62,13 @@ export const AddWeb3WalletActionMenu = withCardStateProvider(() => { onClick={() => connect(strategy)} isLoading={card.loadingMetadata === strategy} isDisabled={card.isLoading} - localizationKey={`Connect ${strategyToDisplayData[strategy].name} wallet`} + localizationKey={localizationKeys('userProfile.web3WalletPage.web3WalletButtonsBlockButton', { + provider: strategyToDisplayData[strategy].name, + })} + sx={t => ({ + justifyContent: 'start', + gap: t.space.$2, + })} leftIcon={ { +type GetWeb3IdentifierParams = { + provider: Web3Provider; +}; + +export async function getWeb3Identifier(_: GetWeb3IdentifierParams): Promise { // @ts-ignore if (!global.ethereum) { - // Do nothing when ethereum doesn't exist. We might revise this in the future - // to offer an Install Metamask prompt to our users. + // Do nothing when ethereum doesn't exist. return ''; } @@ -16,24 +21,35 @@ export async function getMetamaskIdentifier(): Promise { return (identifiers && identifiers[0]) || ''; } -export type GenerateSignatureParams = { +type GenerateWeb3SignatureParams = { identifier: string; nonce: string; + provider: Web3Provider; }; -export async function generateSignatureWithMetamask({ identifier, nonce }: GenerateSignatureParams): Promise { +export async function generateWeb3Signature({ identifier, nonce }: GenerateWeb3SignatureParams): Promise { // @ts-ignore if (!global.ethereum) { - // Do nothing when ethereum doesn't exist. We might revise this in the future - // to offer an Install Metamask prompt to our users. + // Do nothing when ethereum doesn't exist. return ''; } // @ts-ignore - const signature: string = await global.ethereum.request({ + return await global.ethereum.request({ method: 'personal_sign', params: [`0x${toHex(nonce)}`, identifier], }); +} + +export async function getMetamaskIdentifier(): Promise { + return await getWeb3Identifier({ provider: 'metamask' }); +} - return signature; +type GenerateSignatureParams = { + identifier: string; + nonce: string; +}; + +export async function generateSignatureWithMetamask({ identifier, nonce }: GenerateSignatureParams): Promise { + return await generateWeb3Signature({ identifier, nonce, provider: 'metamask' }); } diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 42aba213888..e9686b12a03 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -792,7 +792,7 @@ export const enUS: LocalizationResource = { }, web3WalletsSection: { destructiveAction: 'Remove wallet', - primaryButton: 'Web3 wallets', + primaryButton: 'Connect wallet', title: 'Web3 wallets', }, }, @@ -810,6 +810,7 @@ export const enUS: LocalizationResource = { }, subtitle__availableWallets: 'Select a web3 wallet to connect to your account.', subtitle__unavailableWallets: 'There are no available web3 wallets.', + web3WalletButtonsBlockButton: '{{provider|titleize}}', successMessage: 'The wallet has been added to your account.', title: 'Add web3 wallet', }, diff --git a/packages/types/src/localization.ts b/packages/types/src/localization.ts index 712a68f7615..6c04fdbed95 100644 --- a/packages/types/src/localization.ts +++ b/packages/types/src/localization.ts @@ -461,6 +461,7 @@ type _LocalizationResource = { title: LocalizationValue; subtitle__availableWallets: LocalizationValue; subtitle__unavailableWallets: LocalizationValue; + web3WalletButtonsBlockButton: LocalizationValue; successMessage: LocalizationValue; removeResource: { title: LocalizationValue; diff --git a/packages/types/src/user.ts b/packages/types/src/user.ts index f4d58491408..588efc7f145 100644 --- a/packages/types/src/user.ts +++ b/packages/types/src/user.ts @@ -144,6 +144,8 @@ export interface UserResource extends ClerkResource { get unverifiedExternalAccounts(): ExternalAccountResource[]; + get verifiedWeb3Wallets(): Web3WalletResource[]; + get hasVerifiedEmailAddress(): boolean; get hasVerifiedPhoneNumber(): boolean;