diff --git a/.changeset/cool-spiders-return.md b/.changeset/cool-spiders-return.md new file mode 100644 index 0000000000..74e8a19ff9 --- /dev/null +++ b/.changeset/cool-spiders-return.md @@ -0,0 +1,23 @@ +--- +"@rainbow-me/rainbowkit": patch +--- + +SafePal Wallet Support + +**Example usage** + +```ts +import { + getDefaultWallets, + connectorsForWallets, +} from '@rainbow-me/rainbowkit'; +import { safepalWallet } from '@rainbow-me/rainbowkit/wallets'; +const { wallets } = getDefaultWallets({ appName, chains }); +const connectors = connectorsForWallets([ + ...wallets, + { + groupName: 'Other', + wallets: [safepalWallet({ projectId, chains })], + }, +]); +``` diff --git a/packages/example/pages/_app.tsx b/packages/example/pages/_app.tsx index 1f8ab138e1..ad9f2e2620 100644 --- a/packages/example/pages/_app.tsx +++ b/packages/example/pages/_app.tsx @@ -37,6 +37,7 @@ import { phantomWallet, rabbyWallet, safeheronWallet, + safepalWallet, tahoWallet, talismanWallet, tokenPocketWallet, @@ -131,6 +132,7 @@ const connectors = connectorsForWallets([ phantomWallet({ chains }), rabbyWallet({ chains }), safeheronWallet({ chains }), + safepalWallet({ chains, projectId }), tahoWallet({ chains }), talismanWallet({ chains }), tokenPocketWallet({ chains, projectId }), diff --git a/packages/rainbowkit/src/locales/en_US.json b/packages/rainbowkit/src/locales/en_US.json index dab6d91691..febd7b521d 100644 --- a/packages/rainbowkit/src/locales/en_US.json +++ b/packages/rainbowkit/src/locales/en_US.json @@ -807,6 +807,37 @@ "description": "Once you set up your wallet, click below to refresh the browser and load up the extension." } } + }, + + "safepal": { + "extension": { + "step1": { + "title": "Install the SafePal Wallet extension", + "description": "Click at the top right of your browser and pin SafePal Wallet for easy access." + }, + "step2": { + "title": "Create or Import a wallet", + "description": "Create a new wallet or import an existing one." + }, + "step3": { + "title": "Refresh your browser", + "description": "Once you set up SafePal Wallet, click below to refresh the browser and load up the extension." + } + }, + "qr_code": { + "step1": { + "title": "Open the SafePal Wallet app", + "description": "Put SafePal Wallet on your home screen for faster access to your wallet." + }, + "step2": { + "title": "Create or Import a Wallet", + "description": "Create a new wallet or import an existing one." + }, + "step3": { + "title": "Tap WalletConnect in Settings", + "description": "Choose New Connection, then scan the QR code and confirm the prompt to connect." + } + } } } } diff --git a/packages/rainbowkit/src/wallets/walletConnectors/index.ts b/packages/rainbowkit/src/wallets/walletConnectors/index.ts index 7efa10e40f..7951e2f084 100644 --- a/packages/rainbowkit/src/wallets/walletConnectors/index.ts +++ b/packages/rainbowkit/src/wallets/walletConnectors/index.ts @@ -24,6 +24,7 @@ import { rabbyWallet } from './rabbyWallet/rabbyWallet'; import { rainbowWallet } from './rainbowWallet/rainbowWallet'; import { safeWallet } from './safeWallet/safeWallet'; import { safeheronWallet } from './safeheronWallet/safeheronWallet'; +import { safepalWallet } from './safepalWallet/safepalWallet'; import { tahoWallet } from './tahoWallet/tahoWallet'; import { talismanWallet } from './talismanWallet/talismanWallet'; import { tokenPocketWallet } from './tokenPocketWallet/tokenPocketWallet'; @@ -62,6 +63,7 @@ export { rainbowWallet, safeWallet, safeheronWallet, + safepalWallet, tahoWallet, talismanWallet, tokenPocketWallet, diff --git a/packages/rainbowkit/src/wallets/walletConnectors/safepalWallet/safepalWallet.svg b/packages/rainbowkit/src/wallets/walletConnectors/safepalWallet/safepalWallet.svg new file mode 100644 index 0000000000..4b16b1eabb --- /dev/null +++ b/packages/rainbowkit/src/wallets/walletConnectors/safepalWallet/safepalWallet.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/rainbowkit/src/wallets/walletConnectors/safepalWallet/safepalWallet.ts b/packages/rainbowkit/src/wallets/walletConnectors/safepalWallet/safepalWallet.ts new file mode 100644 index 0000000000..e582f3b950 --- /dev/null +++ b/packages/rainbowkit/src/wallets/walletConnectors/safepalWallet/safepalWallet.ts @@ -0,0 +1,200 @@ +import type { InjectedConnectorOptions } from '@wagmi/core/connectors/injected'; +import { InjectedConnector } from 'wagmi/connectors/injected'; +import { Chain } from '../../../components/RainbowKitProvider/RainbowKitChainContext'; +import { getWalletConnectUri } from '../../../utils/getWalletConnectUri'; +import { InstructionStepName, Wallet } from '../../Wallet'; +import { getWalletConnectConnector } from '../../getWalletConnectConnector'; +import type { + WalletConnectConnectorOptions, + WalletConnectLegacyConnectorOptions, +} from '../../getWalletConnectConnector'; + +declare global { + interface Window { + safepalProvider: Window['ethereum']; + } +} + +export interface SafepalWalletLegacyOptions { + projectId?: string; + chains: Chain[]; + walletConnectVersion: '1'; + walletConnectOptions?: WalletConnectLegacyConnectorOptions; +} + +export interface SafepalWalletOptions { + projectId: string; + chains: Chain[]; + walletConnectVersion?: '2'; + walletConnectOptions?: WalletConnectConnectorOptions; +} + +function getSafepalWalletInjectedProvider(): Window['ethereum'] { + const isSafePalWallet = (ethereum: NonNullable | any) => { + // Identify if SafePal Wallet injected provider is present. + const safepalWallet = !!ethereum.isSafePal; + + return safepalWallet; + }; + + const injectedProviderExist = + typeof window !== 'undefined' && typeof window.ethereum !== 'undefined'; + + // No injected providers exist. + if (!injectedProviderExist) { + return; + } + + // SafePal Wallet injected provider is available in the global scope. + // There are cases that some cases injected providers can replace window.ethereum + // without updating the ethereum.providers array. To prevent issues where + // the TW connector does not recognize the provider when TW extension is installed, + // we begin our checks by relying on TW's global object. + if (window['safepalProvider']) { + return window['safepalProvider']; + } + + // SafePal Wallet was injected into window.ethereum. + if (isSafePalWallet(window.ethereum!)) { + return window.ethereum; + } + + // SafePal Wallet provider might be replaced by another + // injected provider, check the providers array. + if (window.ethereum?.providers) { + // ethereum.providers array is a non-standard way to + // preserve multiple injected providers. Eventually, EIP-5749 + // will become a living standard and we will have to update this. + return window.ethereum.providers.find(isSafePalWallet); + } +} + +export const safepalWallet = ({ + chains, + projectId, + walletConnectOptions, + walletConnectVersion = '2', + ...options +}: (SafepalWalletLegacyOptions | SafepalWalletOptions) & + InjectedConnectorOptions): Wallet => { + const isSafePalWalletInjected = Boolean(getSafepalWalletInjectedProvider()); + const shouldUseWalletConnect = !isSafePalWalletInjected; + + return { + id: 'safepal', + name: 'SafePal Wallet', + iconUrl: async () => (await import('./safepalWallet.svg')).default, + // Note that we never resolve `installed` to `false` because the + // SafePal Wallet provider falls back to other connection methods if + // the injected connector isn't available + installed: isSafePalWalletInjected || undefined, + iconAccent: '#3375BB', + iconBackground: '#fff', + downloadUrls: { + android: + 'https://play.google.com/store/apps/details?id=io.safepal.wallet&referrer=utm_source%3Drainbowkit%26utm_medium%3Ddisplay%26utm_campaign%3Ddownload', + ios: 'https://apps.apple.com/app/apple-store/id1548297139?pt=122504219&ct=rainbowkit&mt=8', + mobile: 'https://www.safepal.com/en/download', + qrCode: 'https://www.safepal.com/en/download', + chrome: + 'https://chrome.google.com/webstore/detail/safepal-extension-wallet/lgmpcpglpngdoalbgeoldeajfclnhafa', + browserExtension: 'https://www.safepal.com/download?product=2', + }, + createConnector: () => { + const getUriMobile = async () => { + const uri = await getWalletConnectUri(connector, walletConnectVersion); + + return `safepalwallet://wc?uri=${encodeURIComponent(uri)}`; + }; + + const getUriQR = async () => { + const uri = await getWalletConnectUri(connector, walletConnectVersion); + + return uri; + }; + + const connector = shouldUseWalletConnect + ? getWalletConnectConnector({ + projectId, + chains, + version: walletConnectVersion, + options: walletConnectOptions, + }) + : new InjectedConnector({ + chains, + options: { + getProvider: getSafepalWalletInjectedProvider, + ...options, + }, + }); + + const mobileConnector = { + getUri: shouldUseWalletConnect ? getUriMobile : undefined, + }; + + let qrConnector = undefined; + + if (shouldUseWalletConnect) { + qrConnector = { + getUri: getUriQR, + instructions: { + learnMoreUrl: 'https://safepal.com/', + steps: [ + { + description: + 'wallet_connectors.safepal.qr_code.step1.description', + step: 'install' as InstructionStepName, + title: 'wallet_connectors.safepal.qr_code.step1.title', + }, + { + description: + 'wallet_connectors.safepal.qr_code.step2.description', + step: 'create' as InstructionStepName, + title: 'wallet_connectors.safepal.qr_code.step2.title', + }, + { + description: + 'wallet_connectors.safepal.qr_code.step3.description', + step: 'scan' as InstructionStepName, + title: 'wallet_connectors.safepal.qr_code.step3.title', + }, + ], + }, + }; + } + + const extensionConnector = { + instructions: { + learnMoreUrl: 'https://www.safepal.com/download?product=2', + steps: [ + { + description: + 'wallet_connectors.safepal.extension.step1.description', + step: 'install' as InstructionStepName, + title: 'wallet_connectors.safepal.extension.step1.title', + }, + { + description: + 'wallet_connectors.safepal.extension.step2.description', + step: 'create' as InstructionStepName, + title: 'wallet_connectors.safepal.extension.step2.title', + }, + { + description: + 'wallet_connectors.safepal.extension.step3.description', + step: 'refresh' as InstructionStepName, + title: 'wallet_connectors.safepal.extension.step3.title', + }, + ], + }, + }; + + return { + connector, + mobile: mobileConnector, + qrCode: qrConnector, + extension: extensionConnector, + }; + }, + }; +}; diff --git a/site/data/docs/custom-wallet-list.mdx b/site/data/docs/custom-wallet-list.mdx index 9ae0dc359a..43af870e14 100644 --- a/site/data/docs/custom-wallet-list.mdx +++ b/site/data/docs/custom-wallet-list.mdx @@ -374,6 +374,16 @@ safeheronWallet(options: { }); ``` +#### SafePal Wallet + +```tsx +import { safepalWallet } from '@rainbow-me/rainbowkit/wallets'; +safepalWallet(options: { + projectId: string; + chains: Chain[]; +}); +``` + #### Taho ```tsx