diff --git a/.changeset/curvy-mangos-begin.md b/.changeset/curvy-mangos-begin.md new file mode 100644 index 00000000..4f2bf129 --- /dev/null +++ b/.changeset/curvy-mangos-begin.md @@ -0,0 +1,6 @@ +--- +'@sei-js/react': major +'@sei-js/core': minor +--- + +Refactored wallet provider and UI components, added hook to programatically open wallet connect modal, implemented a new wallet interface, replaced custom styles with tint colors diff --git a/.prettierrc b/.prettierrc index 544138be..bc7cfd37 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,3 +1,11 @@ { - "singleQuote": true + "tabWidth": 2, + "bracketSpacing": true, + "jsxBracketSameLine": true, + "printWidth": 164, + "singleQuote": true, + "jsxSingleQuote": true, + "trailingComma": "none", + "arrowParens": "always", + "useTabs": true } diff --git a/packages/core/src/lib/wallet/connect.spec.ts b/packages/core/src/lib/wallet/connect.spec.ts index bd004b4a..c2130b5c 100644 --- a/packages/core/src/lib/wallet/connect.spec.ts +++ b/packages/core/src/lib/wallet/connect.spec.ts @@ -6,50 +6,48 @@ import { connect } from './connect'; global.TextEncoder = TextEncoder; describe('connect', () => { - let windowSpy: jest.SpyInstance; - - beforeEach(() => { - windowSpy = jest.spyOn(window, 'window', 'get'); - }); - - afterEach(() => { - windowSpy.mockRestore(); - }); - - it('should throw an error if window is undefined', async () => { - windowSpy.mockImplementation(() => undefined); - - const key = 'keplr'; - const chainId = 'atlantic-1'; - await expect(connect(key, chainId)).rejects.toThrowError(); - }); - - it('should throw an error if no wallet is installed', async () => { - windowSpy.mockImplementation(() => ({})); - - const key = 'keplr'; - const chainId = 'atlantic-1'; - await expect(connect(key, chainId)).rejects.toThrowError(); - }); - - it('should return offlineSigner and accounts', async () => { - const offlineSigner = await DirectSecp256k1HdWallet.fromMnemonic( - 'trip parent program index any save apple extra marble nothing please pulp' - ); - const accounts = await offlineSigner.getAccounts(); - windowSpy.mockImplementation(() => ({ - keplr: { - getOfflineSignerAuto: () => offlineSigner, - experimentalSuggestChain: () => undefined, - enable: () => undefined, - }, - })); - - const key = 'keplr'; - const chainId = 'atlantic-1'; - await expect(connect(key, chainId)).resolves.toEqual({ - offlineSigner, - accounts, - }); - }); + let windowSpy: jest.SpyInstance; + + beforeEach(() => { + windowSpy = jest.spyOn(window, 'window', 'get'); + }); + + afterEach(() => { + windowSpy.mockRestore(); + }); + + it('should throw an error if window is undefined', async () => { + windowSpy.mockImplementation(() => undefined); + + const key = 'keplr'; + const chainId = 'atlantic-1'; + await expect(connect(key, chainId)).rejects.toThrowError(); + }); + + it('should throw an error if no wallet is installed', async () => { + windowSpy.mockImplementation(() => ({})); + + const key = 'keplr'; + const chainId = 'atlantic-2'; + await expect(connect(key, chainId)).rejects.toThrowError(); + }); + + it('should return offlineSigner and accounts', async () => { + const offlineSigner = await DirectSecp256k1HdWallet.fromMnemonic('trip parent program index any save apple extra marble nothing please pulp'); + const accounts = await offlineSigner.getAccounts(); + windowSpy.mockImplementation(() => ({ + keplr: { + getOfflineSignerAuto: () => offlineSigner, + experimentalSuggestChain: () => undefined, + enable: () => undefined + } + })); + + const key = 'keplr'; + const chainId = 'atlantic-1'; + await expect(connect(key, chainId)).resolves.toEqual({ + offlineSigner, + accounts + }); + }); }); diff --git a/packages/core/src/lib/wallet/connect.ts b/packages/core/src/lib/wallet/connect.ts index 6534f7e6..24f01295 100644 --- a/packages/core/src/lib/wallet/connect.ts +++ b/packages/core/src/lib/wallet/connect.ts @@ -2,37 +2,29 @@ import { Keplr as KeplrWindow } from '@keplr-wallet/types'; import { WalletConnect, WalletWindowInterface, WalletWindowKey } from './types'; declare global { - interface Window { - coin98?: { - keplr: KeplrWindow; - }; - fin?: WalletWindowInterface; - falcon?: WalletWindowInterface; - keplr?: KeplrWindow; - leap?: WalletWindowInterface; - compass?: WalletWindowInterface; - } + interface Window { + compass?: WalletWindowInterface; + fin?: WalletWindowInterface; + keplr?: KeplrWindow; + leap?: WalletWindowInterface; + } } -export const connect = async ( - inputWallet: WalletWindowKey, - chainId: string -): Promise => { - if (typeof window === 'undefined' || !window) { - throw new Error('Window is undefined.'); - } +export const connect = async (inputWallet: WalletWindowKey, chainId: string): Promise => { + if (typeof window === 'undefined' || !window) { + throw new Error('Window is undefined.'); + } - const walletProvider = - inputWallet === 'coin98' ? window[inputWallet]?.keplr : window[inputWallet]; - if (!walletProvider) { - throw new Error(`Wallet ${inputWallet} is not installed.`); - } + const walletProvider = inputWallet === 'coin98' ? window[inputWallet]?.keplr : window[inputWallet]; + if (!walletProvider) { + throw new Error(`Wallet ${inputWallet} is not installed.`); + } - // Enable wallet before attempting to call any methods - await walletProvider.enable(chainId); + // Enable wallet before attempting to call any methods + await walletProvider.enable(chainId); - const offlineSigner = await walletProvider.getOfflineSignerAuto(chainId); - const accounts = await offlineSigner.getAccounts(); + const offlineSigner = await walletProvider.getOfflineSignerAuto(chainId); + const accounts = await offlineSigner.getAccounts(); - return { offlineSigner, accounts }; + return { offlineSigner, accounts }; }; diff --git a/packages/core/src/lib/wallet/types.ts b/packages/core/src/lib/wallet/types.ts index 129fa6fc..c5e7a956 100644 --- a/packages/core/src/lib/wallet/types.ts +++ b/packages/core/src/lib/wallet/types.ts @@ -3,6 +3,7 @@ import { OfflineSigner, AccountData } from '@cosmjs/proto-signing'; export type WalletWindowInterface = { enable: (chainId: string) => Promise; + disable: (chainId: string) => Promise; getOfflineSigner: (chainId: string) => Promise; // Will return a signer that only supports Amino if the account is a Ledger-based account, // and returns a signer that is compatible for both Amino and Protobuf otherwise @@ -39,9 +40,16 @@ export type SupportedWallet = { windowKey: WalletWindowKey; }; +type GasPriceStep = { + low: number; + average: number; + high: number; +}; + export type ChainInfo = { chainName?: string; chainId?: string; restUrl?: string; rpcUrl?: string; + gasPriceStep?: GasPriceStep; }; diff --git a/packages/react/README.md b/packages/react/README.md index 874b7523..1150c758 100644 --- a/packages/react/README.md +++ b/packages/react/README.md @@ -50,8 +50,6 @@ const { offlineSigner, accounts, connectedWallet } = useWallet(); | Property | Type | Description | |------------------|-----------|---------------------------------------------------------| | connectedWallet | string? | The currently connected wallet | -| supportedWallets | string[] | List of supported wallets | -| installedWallets | string[] | List of wallets installed | | chainId | string | Sei chain id | | restUrl | string | The rest url associated with the connected wallet | | rpcUrl | string | The rpc url associated with the connected wallet | @@ -96,10 +94,68 @@ const { cosmWasmClient } = useSeiCosmWasmClient(); |----------------|-----------------|-----------------------------------------| | cosmWasmClient | CosmWasmClient? | A cosm wasm client for smart contracts. | +# UI Components +This package contains two helpful UI components for connecting to a wallet provider. +## \ +This component renders a button that will open a modal to connect to a wallet provider. + +```javascript +import React from "react"; +import {useWallet, WalletConnectButton} from "../lib"; + +const Component = () => { + const { connectedWallet } = useWallet(); + + return ( +
+ +

Connected wallet: {connectedWallet?.walletInfo?.name || "---"}

+
+ ); +}; + +export default Component; + +``` + + +| Property | Type | Description | +|-----------------|--------------|-----------------------------------------------------------------------------| +| wallets | SeiWallet[]? | A stargate signing client. | +| buttonClassName | string | A css class name for styling the button | +| primaryColor | string | A hex value of the color you want to tint the text and icons with | +| secondaryColor | string | A secondary hex value of the color you want to tint the text and icons with | +| backgroundColor | string | A hex value of the color you want to use as a background | + +*If your page has a on the page it can be opened programmatically by calling the hook "useSelectWallet"* + + +## useSelectWallet() +This hook allows you to programmatically open and close the wallet modal. + +```javascript +import React from "react"; +import { useWallet, useSelectWallet } from "../lib"; + +const Component = () => { + const { connectedWallet } = useWallet(); + const { openModal, closeModal } = useSelectWallet(); + + return ( +
+ +

Connected wallet: {connectedWallet?.walletInfo?.name || "---"}

+
+ ); +}; + +export default Component; + +``` ### Other helpful packages - [@sei-js/core](https://www.npmjs.com/package/@sei-js/core) - TypeScript library containing helper functions for wallet connection, transaction sig ning, and RPC querying. -- [@sei-js/proto](https://www.npmjs.com/package/@sei-js/proto) - TypeScript library for Sei protobufs generated using Telescope \ No newline at end of file +- [@sei-js/proto](https://www.npmjs.com/package/@sei-js/proto) - TypeScript library for Sei protobufs generated using Telescope diff --git a/packages/react/package.json b/packages/react/package.json index 81a6d15b..cc28234b 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -24,7 +24,8 @@ "private": false, "dependencies": { "@sei-js/core": "1.3.6", - "react-outside-click-handler": "^1.3.0" + "react-outside-click-handler": "^1.3.0", + "react-icons": "4.9.0" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0", diff --git a/packages/react/src/lib/assets/coin98.png b/packages/react/src/lib/assets/coin98.png deleted file mode 100644 index 7db2b3e2..00000000 Binary files a/packages/react/src/lib/assets/coin98.png and /dev/null differ diff --git a/packages/react/src/lib/assets/default.svg b/packages/react/src/lib/assets/default.svg deleted file mode 100644 index 8e01c3bf..00000000 --- a/packages/react/src/lib/assets/default.svg +++ /dev/null @@ -1,3 +0,0 @@ - \ No newline at end of file diff --git a/packages/react/src/lib/assets/falcon.png b/packages/react/src/lib/assets/falcon.png deleted file mode 100644 index ef255f43..00000000 Binary files a/packages/react/src/lib/assets/falcon.png and /dev/null differ diff --git a/packages/react/src/lib/assets/keplr.png b/packages/react/src/lib/assets/keplr.png deleted file mode 100644 index 07434574..00000000 Binary files a/packages/react/src/lib/assets/keplr.png and /dev/null differ diff --git a/packages/react/src/lib/assets/leap.png b/packages/react/src/lib/assets/leap.png deleted file mode 100644 index 33dca9db..00000000 Binary files a/packages/react/src/lib/assets/leap.png and /dev/null differ diff --git a/packages/react/src/lib/components/WalletConnectButton/WalletConnectButton.tsx b/packages/react/src/lib/components/WalletConnectButton/WalletConnectButton.tsx index 9e1a508e..48f96f83 100644 --- a/packages/react/src/lib/components/WalletConnectButton/WalletConnectButton.tsx +++ b/packages/react/src/lib/components/WalletConnectButton/WalletConnectButton.tsx @@ -1,113 +1,101 @@ -import React, { useContext, useState } from 'react'; -import { styles } from './styles'; +import React, { useContext, useEffect, useState } from 'react'; import OutsideClickHandler from 'react-outside-click-handler'; +import { IconContext } from 'react-icons'; +import { IoCopyOutline, IoLogOutOutline, IoWalletOutline } from 'react-icons/io5'; import { SeiWalletContext } from '../../provider'; -import { WalletSelectModal } from '../WalletSelectModal'; import { WalletConnectButtonProps } from './types'; +import './styles.css'; +import { isValidCSSColor } from '../../utils'; -export const truncateAddress = (address: string) => - `${address.slice(0, 3)}....${address.slice(address.length - 5)}`; +export const truncateAddress = (address: string) => `${address.slice(0, 3)}....${address.slice(address.length - 5)}`; -const WalletConnectButton = ({ - buttonStyles, - buttonClassName, - wallets, -}: WalletConnectButtonProps) => { - const [showMenu, setShowMenu] = useState(false); - const [recentlyCopied, setRecentlyCopied] = useState(false); - const [showConnectModal, setShowConnectModal] = useState(false); +const WalletConnectButton = ({ buttonClassName, primaryColor, secondaryColor, backgroundColor }: WalletConnectButtonProps) => { + const [showMenu, setShowMenu] = useState(false); + const [recentlyCopied, setRecentlyCopied] = useState(false); - const { connectedWallet, accounts, setInputWallet } = - useContext(SeiWalletContext); + const { connectedWallet, accounts, setTargetWallet, setShowConnectModal } = useContext(SeiWalletContext); - const changeWallet = () => { - if (setInputWallet) setInputWallet(undefined); - setShowConnectModal(true); - setShowMenu(false); - }; + useEffect(() => { + const color = primaryColor && isValidCSSColor(primaryColor) ? primaryColor : '#121212'; + document.documentElement.style.setProperty('--wallet-primary-color', color); + document.documentElement.style.setProperty('--wallet-primary-color-11', `${color}11`); + document.documentElement.style.setProperty('--wallet-primary-color-22', `${color}22`); + document.documentElement.style.setProperty('--wallet-primary-color-33', `${color}33`); + document.documentElement.style.setProperty('--wallet-primary-color-44', `${color}44`); + }, [primaryColor]); - const copyAddress = async () => { - setRecentlyCopied(true); - await navigator.clipboard.writeText(accounts?.[0]?.address); - setTimeout(() => { - setRecentlyCopied(false); - }, 600); - }; + useEffect(() => { + const color = secondaryColor && isValidCSSColor(secondaryColor) ? secondaryColor : '#8C8C8C'; + document.documentElement.style.setProperty('--wallet-secondary-color', color); + }, [secondaryColor]); - const disconnect = () => { - if (setInputWallet) setInputWallet(undefined); - }; + useEffect(() => { + const color = backgroundColor && isValidCSSColor(backgroundColor) ? backgroundColor : '#F1F1F1'; + document.documentElement.style.setProperty('--wallet-background-color', color); + }, [backgroundColor]); - const renderButton = () => { - if (!connectedWallet) { - return ( - - ); - } + const changeWallet = () => { + setShowConnectModal(true); + setShowMenu(false); + }; - const accountLabel = - accounts?.[0] === undefined - ? 'connecting...' - : truncateAddress(accounts[0].address); + const copyAddress = async () => { + setRecentlyCopied(true); + await navigator.clipboard.writeText(accounts?.[0]?.address); + setTimeout(() => { + setRecentlyCopied(false); + }, 1500); + }; - return ( -
- - {showMenu && ( - setShowMenu(false)}> -
- {accounts && ( -

- - {recentlyCopied ? 'copied' : 'copy address'} - -

- )} -

- change wallet -

-

- disconnect -

-
-
- )} -
- ); - }; + const disconnect = () => { + if (setTargetWallet) setTargetWallet(undefined); + }; - return ( - <> - {renderButton()} - {showConnectModal && ( - - )} - - ); + const renderButton = () => { + if (!connectedWallet) { + return ( + + ); + } + + const accountLabel = accounts?.[0] === undefined ? 'connecting...' : truncateAddress(accounts[0].address); + + return ( +
+ + {showMenu && ( + setShowMenu(false)}> +
+ {accounts && ( +
+ + {recentlyCopied ? 'copied' : 'copy address'} +
+ )} +
+ + change wallet +
+
+ + disconnect +
+
+
+ )} +
+ ); + }; + + return ( + <> + {renderButton()} + + ); }; export default WalletConnectButton; diff --git a/packages/react/src/lib/components/WalletConnectButton/styles.css b/packages/react/src/lib/components/WalletConnectButton/styles.css new file mode 100644 index 00000000..77ce4a1c --- /dev/null +++ b/packages/react/src/lib/components/WalletConnectButton/styles.css @@ -0,0 +1,38 @@ +.connect_wrapper { + position: relative; +} + +.wallet__menu { + position: absolute; + display: flex; + flex-direction: column; + top: 46px; + right: 0; + padding: 12px; + gap: 12px; + background-color: var(--wallet-background-color); + border-radius: 4px; +} + +.wallet__menu--item { + display: flex; + align-items: center; + gap: 12px; + font-weight: 500; + font-size: 1.15rem; + white-space: nowrap; + color: var(--wallet-primary-color); + cursor: pointer; + border-radius: 6px; + padding: 9px; +} + +.wallet__menu--item:hover { + background-color: var(--wallet-primary-color-44); +} + +.wallet__menu--item-icon { + width: 20px; + height: 20px; + color: var(--wallet-primary-color); +} diff --git a/packages/react/src/lib/components/WalletConnectButton/styles.ts b/packages/react/src/lib/components/WalletConnectButton/styles.ts deleted file mode 100644 index 91d3c704..00000000 --- a/packages/react/src/lib/components/WalletConnectButton/styles.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { CSSProperties } from 'react'; - -export const styles: { [key: string]: CSSProperties } = { - wrapper: { - position: 'relative', - }, - menu: { - position: 'absolute', - display: 'flex', - flexDirection: 'column', - top: 46, - right: 0, - padding: 18, - gap: 12, - backgroundColor: '#222222', - borderRadius: 4, - }, - menuItem: { - fontWeight: 'bold', - fontSize: '1.15rem', - whiteSpace: 'nowrap', - color: '#d1d1d1', - cursor: 'pointer', - }, -}; diff --git a/packages/react/src/lib/components/WalletConnectButton/types.ts b/packages/react/src/lib/components/WalletConnectButton/types.ts index 3cff3a7c..b5e7e2df 100644 --- a/packages/react/src/lib/components/WalletConnectButton/types.ts +++ b/packages/react/src/lib/components/WalletConnectButton/types.ts @@ -1,14 +1,9 @@ -import { CSSProperties } from 'react'; -import { WalletSelectStyles } from '../WalletSelectModal/types'; -import { WalletWindowKey } from '@sei-js/core'; +import { SeiWallet } from '../../provider'; export type WalletConnectButtonProps = { - buttonClassName?: string; - wallets?: WalletWindowKey[]; - buttonStyles?: { - button?: CSSProperties; - menu?: CSSProperties; - menuItem?: CSSProperties; - walletSelectStyles?: WalletSelectStyles; - }; + buttonClassName?: string; + wallets?: SeiWallet[]; + primaryColor?: string; + secondaryColor?: string; + backgroundColor?: string; }; diff --git a/packages/react/src/lib/components/WalletSelectModal/WalletSelectModal.tsx b/packages/react/src/lib/components/WalletSelectModal/WalletSelectModal.tsx index 395ef3be..39badcf3 100644 --- a/packages/react/src/lib/components/WalletSelectModal/WalletSelectModal.tsx +++ b/packages/react/src/lib/components/WalletSelectModal/WalletSelectModal.tsx @@ -1,97 +1,141 @@ -import React, { useContext } from 'react'; -import OutsideClickHandler from 'react-outside-click-handler'; -import { WalletWindowKey } from '@sei-js/core'; +import React, { useContext, useEffect, useState } from 'react'; import { WalletSelectModalProps } from './types'; -import { styles } from './styles'; -import { SeiWalletContext } from '../../provider'; - -// TODO: Refactor this to a separate assets repo -// Wallet logos -import finLogo from '../../assets/fin.png'; -import coin98Logo from '../../assets/coin98.png'; -import falconLogo from '../../assets/falcon.png'; -import keplrLogo from '../../assets/keplr.png'; -import leapLogo from '../../assets/leap.png'; -import compassLogo from '../../assets/compass.png'; -import defaultIcon from '../../assets/default.svg'; -const getWalletIcon = (wallet: WalletWindowKey) => { - if (wallet === 'coin98') { - return coin98Logo; - } else if (wallet === 'fin') { - return finLogo; - } else if (wallet === 'falcon') { - return falconLogo; - } else if (wallet === 'keplr') { - return keplrLogo; - } else if (wallet === 'leap') { - return leapLogo; - } else if (wallet === 'compass') { - return compassLogo; - } else { - return defaultIcon; - } -}; +import { SeiWallet, SeiWalletContext } from '../../provider'; +import './styles.css'; +import { AiFillCloseCircle } from 'react-icons/ai'; +import { BiError, BiErrorAlt } from 'react-icons/bi'; +import { FaCheckCircle } from 'react-icons/fa'; + +const WalletSelectModal = ({ wallets: inputWallets }: WalletSelectModalProps) => { + const { connectedWallet, setTargetWallet, wallets, connectionError, targetWallet, setConnectionError, showConnectModal, setShowConnectModal } = + useContext(SeiWalletContext); + + const visibleWallets = inputWallets || wallets || []; + + const [isConnecting, setIsConnecting] = useState(false); + + useEffect(() => { + if (connectedWallet || connectionError) { + setIsConnecting(false); + } + }, [connectedWallet, connectionError]); + + const closeModal = () => { + setConnectionError(undefined); + setShowConnectModal(false); + }; + + const renderWallet = (wallet: SeiWallet) => { + const isConnectedWallet = connectedWallet?.walletInfo.name === wallet.walletInfo.name; + + const renderConnection = () => { + if (isConnectedWallet) return ; + return null; + }; + + const selectWallet = async () => { + if (wallet.walletInfo.name === targetWallet?.walletInfo.name) return; + if (setTargetWallet) setTargetWallet(wallet); + setIsConnecting(true); + setConnectionError(undefined); + }; + + return ( +
+
+ {wallet.walletInfo.name} +

{wallet.walletInfo.name}

+
+ {renderConnection()} +
+ ); + }; + + const renderRightSide = () => { + if (isConnecting) { + return ( +
+ {targetWallet?.walletInfo.icon} +

Connecting to {targetWallet?.walletInfo.name}...

+
+ ); + } + + const isWalletNotInstalled = targetWallet && !window[targetWallet.walletInfo.windowKey as never]; + + if (isWalletNotInstalled) { + return ( +
+ +

{targetWallet?.walletInfo?.name || 'Wallet'} not installed

+ {targetWallet?.walletInfo.website && ( + + Download {targetWallet?.walletInfo.name} + + )} +
+ ); + } + + if (connectionError) { + return ( +
+ +

We couldn't connect to {targetWallet?.walletInfo?.name || 'your wallet'}

+
+

How to resolve this issue?

+

A pending action or a locked wallet can cause issues. Please open the extension manually and try again.

+
+
+ ); + } + + if (connectedWallet) { + return ( +
+ {targetWallet?.walletInfo.icon} +

Connected to {targetWallet?.walletInfo.name}

+
+ ); + } + + return ( +
+

New to using a wallet?

+
+

A Secure Hub for Digital Transactions

+

Wallets provide a secure environment for signing and sending transactions involving your tokens and NFTs.

+
+
+

A modern way to log in

+

Rather than generating new accounts and passwords for each website, simply link your wallet.

+
+
+ ); + }; + + if (!showConnectModal) return null; -const WalletSelectModal = ({ - setShowConnectModal, - inputWallets, - walletSelectStyles, -}: WalletSelectModalProps) => { - const { - installedWallets, - connectedWallet, - setInputWallet, - supportedWallets, - } = useContext(SeiWalletContext); - - const wallets = inputWallets || supportedWallets; - - const renderWallet = (wallet: WalletWindowKey) => { - const renderConnection = () => { - if (connectedWallet === wallet) return

connected

; - if (installedWallets.includes(wallet)) return

detected

; - return null; - }; - - const selectWallet = () => { - if (setInputWallet) setInputWallet(wallet); - setShowConnectModal(false); - }; - - return ( -
-
- {wallet} -

- {wallet} -

-
- {renderConnection()} -
- ); - }; - return ( -
- { - e.stopPropagation(); - setShowConnectModal(false); - }} - > -
-

Connect your wallet

-
- {wallets.map(renderWallet)} -
-
-
- ); + return ( +
+
e.stopPropagation()} className='modal__card'> +
+

Connect a wallet

+ +
+
+
{visibleWallets.map(renderWallet)}
+
+ {renderRightSide()} +
+
+
+ ); }; export default WalletSelectModal; diff --git a/packages/react/src/lib/components/WalletSelectModal/styles.css b/packages/react/src/lib/components/WalletSelectModal/styles.css new file mode 100644 index 00000000..80c9b295 --- /dev/null +++ b/packages/react/src/lib/components/WalletSelectModal/styles.css @@ -0,0 +1,248 @@ +@keyframes walletFadeIn { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +:root { + --wallet-primary-color: #121212; + --wallet-primary-color-11: #12121211; + --wallet-primary-color-22: #12121222; + --wallet-primary-color-33: #12121233; + --wallet-primary-color-44: #12121244; + --wallet-secondary-color: #8C8C8C; + --wallet-background-color: #F1F1F1; +} + +.modal__background { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + backdrop-filter: blur(3px); + background-color: #f1f1f133; + z-index: 2; + padding: 24px; +} + +.modal__background * { + display: flex; + flex-direction: row; + margin: 0; + font-family: Roboto, Helvetica, sans-serif; + box-sizing: border-box; + line-height: 1.5; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +.modal__card { + display: flex; + flex-direction: column; + background-color: var(--wallet-background-color); + border-radius: 12px; + z-index: 10; + padding: 24px; + max-height: 100%; + width: 100%; + max-width: 720px; + min-height: 440px; + box-shadow: rgba(14, 15, 16, 0.1) 3px 9px 24px; + overflow-x: hidden; + gap: 48px; +} + +.card__header { + display: flex; + min-height: 26px; + height: 26px; + width: 100%; + justify-content: space-between; +} + +.card__header--title { + color: var(--wallet-primary-color); +} + +.card__header--close { + transition: 70ms all ease-out; + color: var(--wallet-primary-color); + width: 26px; + height: 26px; + cursor: pointer; +} + +.card__header--close:hover { + transform: scale(1.2); +} + +.card__header--close:active { + opacity: 0.75; +} + +.card__content { + display: flex; + flex-direction: row; + flex: 1; + gap: 24px; + margin-bottom: 26px; +} + +.card__content--wallets { + display: flex; + flex-direction: column; + gap: 12px; + width: 280px; + max-width: 280px; + min-width: 280px; +} + +.card__content--separator { + height: 100%; + width: 2px; + border-radius: 2px; + background-color: var(--wallet-primary-color-22); +} + +.card__right { + display: flex; + flex-direction: column; + gap: 24px; + align-items: center; + margin-bottom: 48px; + width: 100%; + padding: 0 36px 0 24px; +} + +.card__right-centered { + display: flex; + flex-direction: column; + justify-content: center; + gap: 24px; + align-items: center; + width: 100%; + padding: 0 36px 0 24px; + opacity: 0; + animation: walletFadeIn 0.15s 0.3s forwards; +} + +.card__right--item { + display: flex; + flex-direction: column; + gap: 3px; + max-width: 300px; +} + +.card__right--item-title { + color: var(--wallet-primary-color); + font-weight: 600; +} + +.card__right--item-description { + color: var(--wallet-secondary-color); +} + +.card__right--icon{ + color: var(--wallet-primary-color); + width: 72px; + height: 72px; +} + +.card__right--title { + color: var(--wallet-primary-color); + text-align: center; + font-weight: 600; + font-size: 1.25rem; + margin-bottom: 24px; +} + +.card__right--download { + padding: 0.5rem 1rem; + border-radius: 30px; + background-color: var(--wallet-primary-color); + color: var(--wallet-background-color); + font-weight: 700; + transition: 50ms all ease-out; +} + +.card__right--download:hover { + opacity: 0.8; +} + +.card__right--download:active { + opacity: 1; +} + +.card__right--connecting-icon { + width: 48px; + height: 48px; + max-width: 48px; + max-height: 48px; + min-width: 48px; + min-height: 48px; + border-radius: 12px; +} + +.wallet__item { + padding: 0 12px; + justify-content: space-between; + align-items: center; + border-radius: 6px; + border: transparent solid 2px; + transition: 50ms all ease-out; + user-select: none; +} + +.wallet__item:active { + opacity: 0.75; +} + +.wallet__item:hover { + background-color: var(--wallet-primary-color-11); + cursor: pointer; +} + +.wallet__item-connected { + background-color: var(--wallet-primary-color-11); +} + +.wallet__item-targeted { + border: var(--wallet-primary-color-33) solid 2px !important; +} + +.wallet__item--info { + display: flex; + flex-direction: row; + align-items: center; + gap: 12px; + padding: 0.5rem; +} + +.wallet__item--info-icon { + border-radius: 50%; + width: 24px; + height: 24px; +} + +.wallet__item--info-name { + color: var(--wallet-primary-color); + text-transform: uppercase; + font-size: 1.25rem; +} + +.wallet__item--info-icon { + color: var(--wallet-primary-color-44); + height: 18px; + width: 18px; +} diff --git a/packages/react/src/lib/components/WalletSelectModal/styles.ts b/packages/react/src/lib/components/WalletSelectModal/styles.ts deleted file mode 100644 index a10b8ed4..00000000 --- a/packages/react/src/lib/components/WalletSelectModal/styles.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { CSSProperties } from 'react'; - -export const styles: { [key: string]: CSSProperties } = { - background: { - position: 'absolute', - top: 0, - right: 0, - bottom: 0, - left: 0, - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - backdropFilter: 'blur(3px)', - backgroundColor: '#f1f1f133', - zIndex: 2, - padding: 24, - }, - card: { - flexDirection: 'column', - backgroundColor: 'black', - borderRadius: 12, - zIndex: 10, - padding: 24, - maxHeight: '100%', - width: '100%', - maxWidth: 360, - boxShadow: 'rgba(14, 15, 16, 0.1) 3px 9px 24px', - overflowX: 'hidden', - gap: 12, - animation: 'fadeIn 150ms linear', - }, - row: { - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', - gap: 60, - cursor: 'pointer', - padding: 12, - borderRadius: 3, - }, - info: { - flexDirection: 'row', - alignItems: 'center', - gap: 12, - }, - icon: { - borderRadius: '50%', - }, - name: { - textTransform: 'uppercase', - fontSize: '18pt', - }, -}; diff --git a/packages/react/src/lib/components/WalletSelectModal/types.ts b/packages/react/src/lib/components/WalletSelectModal/types.ts index d05f52e8..77806a20 100644 --- a/packages/react/src/lib/components/WalletSelectModal/types.ts +++ b/packages/react/src/lib/components/WalletSelectModal/types.ts @@ -1,14 +1,5 @@ -import { WalletWindowKey } from '@sei-js/core'; -import { CSSProperties } from 'react'; - -export type WalletSelectStyles = { - background?: CSSProperties; - card?: CSSProperties; - name?: CSSProperties; -}; +import { SeiWallet } from '../../provider'; export type WalletSelectModalProps = { - setShowConnectModal: (show: boolean) => void; - inputWallets?: WalletWindowKey[]; - walletSelectStyles?: WalletSelectStyles; + wallets?: SeiWallet[]; }; diff --git a/packages/react/src/lib/config/supportedWallets.ts b/packages/react/src/lib/config/supportedWallets.ts new file mode 100644 index 00000000..32846627 --- /dev/null +++ b/packages/react/src/lib/config/supportedWallets.ts @@ -0,0 +1,84 @@ +import { SeiWallet } from '../provider'; + +export const FIN_WALLET: SeiWallet = { + getAccounts: async (chainId) => { + const offlineSigner = await window?.['fin']?.getOfflineSignerAuto(chainId); + return offlineSigner?.getAccounts() || []; + }, + connect: async (chainId) => await window?.['fin']?.enable(chainId), + disconnect: async (chainId) => await window?.['fin']?.disable(chainId), + getOfflineSigner: async (chainId) => window?.['fin']?.getOfflineSignerAuto(chainId), + signArbitrary: window?.['fin']?.signArbitrary, + walletInfo: { + windowKey: 'fin', + name: 'Fin', + website: 'https://chrome.google.com/webstore/detail/fin-wallet-for-sei/dbgnhckhnppddckangcjbkjnlddbjkna', + icon: 'https://sei-js-assets.s3.us-west-2.amazonaws.com/fin.png' + } +}; + +export const COMPASS_WALLET: SeiWallet = { + getAccounts: async (chainId) => { + const offlineSigner = await window?.['compass']?.getOfflineSignerAuto(chainId); + return offlineSigner?.getAccounts() || []; + }, + connect: async (chainId) => await window?.['compass']?.enable(chainId), + disconnect: async (chainId) => await window?.['compass']?.disable(chainId), + getOfflineSigner: async (chainId) => window?.['compass']?.getOfflineSignerAuto(chainId), + signArbitrary: window?.['compass']?.signArbitrary, + walletInfo: { + windowKey: 'compass', + name: 'Compass', + website: 'https://chrome.google.com/webstore/detail/compass-wallet/anokgmphncpekkhclmingpimjmcooifb', + icon: 'https://sei-js-assets.s3.us-west-2.amazonaws.com/compass.png' + } +}; + +export const KEPLR_WALLET: SeiWallet = { + getAccounts: async (chainId) => { + const offlineSigner = await window?.['keplr']?.getOfflineSignerAuto(chainId); + return offlineSigner?.getAccounts() || []; + }, + connect: async (chainId) => await window?.['keplr']?.enable(chainId), + disconnect: async (chainId) => await window?.['keplr']?.disable(chainId), + getOfflineSigner: async (chainId) => window?.['keplr']?.getOfflineSignerAuto(chainId), + signArbitrary: window?.['keplr']?.signArbitrary, + walletInfo: { + windowKey: 'keplr', + name: 'Keplr', + website: 'https://www.keplr.app/download', + icon: 'https://sei-js-assets.s3.us-west-2.amazonaws.com/keplr.png' + } +}; + +export const LEAP_WALLET: SeiWallet = { + getAccounts: async (chainId) => { + const offlineSigner = await window?.['leap']?.getOfflineSignerAuto(chainId); + return offlineSigner?.getAccounts() || []; + }, + connect: async (chainId) => await window?.['leap']?.enable(chainId), + disconnect: async (chainId) => await window?.['leap']?.disable(chainId), + getOfflineSigner: async (chainId) => window?.['leap']?.getOfflineSignerAuto(chainId), + signArbitrary: window?.['leap']?.signArbitrary, + walletInfo: { + windowKey: 'leap', + name: 'Leap', + website: 'https://www.leapwallet.io/download', + icon: 'https://sei-js-assets.s3.us-west-2.amazonaws.com/leap.png' + } +}; + +export const findWalletByWindowKey = (windowKey: string): SeiWallet | undefined => { + switch (windowKey) { + case 'compass': + return COMPASS_WALLET; + case 'leap': + return LEAP_WALLET; + case 'keplr': + return KEPLR_WALLET; + case 'fin': + return FIN_WALLET; + default: + return undefined; + } +}; diff --git a/packages/react/src/lib/hooks/index.ts b/packages/react/src/lib/hooks/index.ts index 397a1fad..139b31e3 100644 --- a/packages/react/src/lib/hooks/index.ts +++ b/packages/react/src/lib/hooks/index.ts @@ -4,3 +4,4 @@ export * from './useSigningClient'; export * from './useSigningCosmWasmClient'; export * from './useStargateClient'; export * from './useWallet'; +export * from './useSelectWallet'; diff --git a/packages/react/src/lib/hooks/useCosmWasmClient/index.ts b/packages/react/src/lib/hooks/useCosmWasmClient/index.ts index ad9817d0..c37104b7 100644 --- a/packages/react/src/lib/hooks/useCosmWasmClient/index.ts +++ b/packages/react/src/lib/hooks/useCosmWasmClient/index.ts @@ -1,4 +1,2 @@ -export { - default as useCosmWasmClient, - UseCosmWasmClient, -} from './useCosmWasmClient'; +export { default as useCosmWasmClient } from './useCosmWasmClient'; +export type { UseCosmWasmClient } from './useCosmWasmClient'; diff --git a/packages/react/src/lib/hooks/useQueryClient/index.ts b/packages/react/src/lib/hooks/useQueryClient/index.ts index 70473568..9684a60f 100644 --- a/packages/react/src/lib/hooks/useQueryClient/index.ts +++ b/packages/react/src/lib/hooks/useQueryClient/index.ts @@ -1,5 +1,2 @@ -export { - default as useQueryClient, - QueryClient, - UseQueryClient, -} from './useQueryClient'; +export type { QueryClient, UseQueryClient } from './useQueryClient'; +export { default as useQueryClient } from './useQueryClient'; diff --git a/packages/react/src/lib/hooks/useSelectWallet/index.ts b/packages/react/src/lib/hooks/useSelectWallet/index.ts new file mode 100644 index 00000000..4e6f4dbf --- /dev/null +++ b/packages/react/src/lib/hooks/useSelectWallet/index.ts @@ -0,0 +1 @@ +export { default as useSelectWallet } from './useSelectWallet'; diff --git a/packages/react/src/lib/hooks/useSelectWallet/useSelectWallet.ts b/packages/react/src/lib/hooks/useSelectWallet/useSelectWallet.ts new file mode 100644 index 00000000..bea55af6 --- /dev/null +++ b/packages/react/src/lib/hooks/useSelectWallet/useSelectWallet.ts @@ -0,0 +1,14 @@ +import { useContext } from 'react'; +import { SeiWalletContext } from '../../provider'; + +const useSelectWallet = () => { + const { setShowConnectModal } = useContext(SeiWalletContext); + + const openModal = () => setShowConnectModal(true); + + const closeModal = () => setShowConnectModal(false); + + return { openModal, closeModal }; +}; + +export default useSelectWallet; diff --git a/packages/react/src/lib/hooks/useSigningClient/index.ts b/packages/react/src/lib/hooks/useSigningClient/index.ts index 6e0cd8a3..cd150781 100644 --- a/packages/react/src/lib/hooks/useSigningClient/index.ts +++ b/packages/react/src/lib/hooks/useSigningClient/index.ts @@ -1,4 +1,2 @@ -export { - default as useSigningClient, - UseSigningClient, -} from './useSigningClient'; +export { default as useSigningClient } from './useSigningClient'; +export type { UseSigningClient } from './useSigningClient'; diff --git a/packages/react/src/lib/hooks/useSigningCosmWasmClient/index.ts b/packages/react/src/lib/hooks/useSigningCosmWasmClient/index.ts index abb81a49..32ec7e89 100644 --- a/packages/react/src/lib/hooks/useSigningCosmWasmClient/index.ts +++ b/packages/react/src/lib/hooks/useSigningCosmWasmClient/index.ts @@ -1,4 +1,2 @@ -export { - default as useSigningCosmWasmClient, - UseSigningCosmWasmClient, -} from './useSigningCosmWasmClient'; +export { default as useSigningCosmWasmClient } from './useSigningCosmWasmClient'; +export type { UseSigningCosmWasmClient } from './useSigningCosmWasmClient'; diff --git a/packages/react/src/lib/hooks/useStargateClient/index.ts b/packages/react/src/lib/hooks/useStargateClient/index.ts index fc9a18c2..e6783f1f 100644 --- a/packages/react/src/lib/hooks/useStargateClient/index.ts +++ b/packages/react/src/lib/hooks/useStargateClient/index.ts @@ -1,4 +1,2 @@ -export { - default as useStargateClient, - UseStargateClient, -} from './useStargateClient'; +export { default as useStargateClient } from './useStargateClient'; +export type { UseStargateClient } from './useStargateClient'; diff --git a/packages/react/src/lib/hooks/useWallet/useWallet.ts b/packages/react/src/lib/hooks/useWallet/useWallet.ts index 791e888b..960397f9 100644 --- a/packages/react/src/lib/hooks/useWallet/useWallet.ts +++ b/packages/react/src/lib/hooks/useWallet/useWallet.ts @@ -1,6 +1,19 @@ import { useContext } from 'react'; -import { SeiWalletContext, WalletProvider } from '../../provider'; +import { SeiWallet, SeiWalletContext } from '../../provider'; +import { AccountData, OfflineSigner } from '@cosmjs/proto-signing'; -const useWallet = (): WalletProvider => useContext(SeiWalletContext); +type UseWallet = { + connectedWallet?: SeiWallet; + chainId: string; + restUrl: string; + rpcUrl: string; + offlineSigner?: OfflineSigner; + accounts: readonly AccountData[]; +}; +const useWallet = (): UseWallet => { + const { connectedWallet, chainId, restUrl, rpcUrl, offlineSigner, accounts } = useContext(SeiWalletContext); + + return { connectedWallet, chainId, restUrl, rpcUrl, offlineSigner, accounts }; +}; export default useWallet; diff --git a/packages/react/src/lib/provider/SeiWalletProvider.tsx b/packages/react/src/lib/provider/SeiWalletProvider.tsx index 8659cb53..fbfcd5a7 100644 --- a/packages/react/src/lib/provider/SeiWalletProvider.tsx +++ b/packages/react/src/lib/provider/SeiWalletProvider.tsx @@ -1,120 +1,102 @@ -import React, { - createContext, - useCallback, - useEffect, - useMemo, - useState, -} from 'react'; -import { SeiWalletProviderProps, WalletProvider } from './types'; -import { - WalletWindowKey, - connect as connectWallet, - SUPPORTED_WALLETS, - suggestChain, -} from '@sei-js/core'; +import React, { createContext, useEffect, useState } from 'react'; import { AccountData, OfflineSigner } from '@cosmjs/proto-signing'; - -const supportedWallets = SUPPORTED_WALLETS.map((wallet) => wallet.windowKey); +import { SeiWallet, SeiWalletProviderProps, WalletProvider } from './types'; +import { findWalletByWindowKey } from '../config/supportedWallets'; +import { WalletSelectModal } from '../components'; export const SeiWalletContext = createContext({ - supportedWallets, - accounts: [], - installedWallets: [], - connect: () => undefined, - disconnect: () => undefined, + chainId: '', + restUrl: '', + rpcUrl: '', + accounts: [], + connect: () => undefined, + disconnect: () => undefined, + setConnectionError: () => undefined, + setTargetWallet: () => undefined, + setShowConnectModal: () => undefined, + wallets: [] }); -const SeiWalletProvider = ({ - children, - chainConfiguration, -}: SeiWalletProviderProps) => { - const [inputWallet, setInputWallet] = useState(); - const [offlineSigner, setOfflineSigner] = useState(); - const [accounts, setAccounts] = useState([]); - const [connectedWallet, setConnectedWallet] = useState< - WalletWindowKey | undefined - >(); +const SeiWalletProvider = ({ children, chainConfiguration, wallets, autoConnect }: SeiWalletProviderProps) => { + const autoConnectSeiWallet = typeof autoConnect === 'string' ? findWalletByWindowKey(autoConnect) : autoConnect; + + const [targetWallet, setTargetWallet] = useState(autoConnectSeiWallet); + const [offlineSigner, setOfflineSigner] = useState(); + const [connectionError, setConnectionError] = useState(); + const [accounts, setAccounts] = useState([]); + const [showConnectModal, setShowConnectModal] = useState(false); + const [connectedWallet, setConnectedWallet] = useState(); + + const filteredWallets = wallets.reduce((acc: SeiWallet[], wallet) => { + const seiWallet = typeof wallet === 'string' ? findWalletByWindowKey(wallet) : wallet; + if (seiWallet !== undefined) acc.push(seiWallet); + return acc; + }, []); - const suggestAndConnect = useCallback(async () => { - if (!inputWallet) return; - if (inputWallet === 'keplr' || inputWallet === 'leap') { - await suggestChain(inputWallet, { - chainName: `Sei ${chainConfiguration.chainId}`, - chainId: chainConfiguration.chainId, - rpcUrl: chainConfiguration.rpcUrl, - restUrl: chainConfiguration.restUrl, - }); - } + const connectToChain = async () => { + if (!targetWallet) return; - const connectedWallet = await connectWallet( - inputWallet, - chainConfiguration.chainId - ); - if (!connectedWallet) return; - setOfflineSigner(connectedWallet.offlineSigner); - setAccounts(connectedWallet.accounts); - if (connectedWallet.accounts.length > 0) { - setConnectedWallet(inputWallet); - } - }, [ - inputWallet, - chainConfiguration.chainId, - chainConfiguration.restUrl, - chainConfiguration.rpcUrl, - ]); + try { + if (!window[targetWallet.walletInfo.windowKey as never]) { + setConnectionError(targetWallet.walletInfo.windowKey); + return; + } - useEffect(() => { - if (inputWallet) { - suggestAndConnect().then(() => setConnectedWallet(inputWallet)); - } else { - setConnectedWallet(undefined); - setAccounts([]); - setOfflineSigner(undefined); - } - }, [ - inputWallet, - chainConfiguration.chainId, - chainConfiguration.restUrl, - chainConfiguration.rpcUrl, - ]); + await targetWallet.connect(chainConfiguration.chainId); + const fetchedOfflineSigner = await targetWallet.getOfflineSigner(chainConfiguration.chainId); + const fetchedAccounts = await targetWallet.getAccounts(chainConfiguration.chainId); - const installedWallets = useMemo( - () => - window - ? SUPPORTED_WALLETS.filter((wallet) => window?.[wallet.windowKey]).map( - (wallet) => wallet.windowKey - ) - : [], - [window] - ); + if (fetchedAccounts.length > 0 && fetchedOfflineSigner) { + setShowConnectModal(false); + setOfflineSigner(fetchedOfflineSigner); + setAccounts(fetchedAccounts); + setConnectedWallet(targetWallet); + } + } catch (e) { + console.log('Error connecting to wallet', e); + setConnectionError(targetWallet.walletInfo.windowKey); + return; + } + }; - const connect = (walletKey: WalletWindowKey) => { - setInputWallet(walletKey); - }; + useEffect(() => { + if (targetWallet) { + connectToChain().then(); + } else { + setOfflineSigner(undefined); + setAccounts([]); + setConnectedWallet(undefined); + } + }, [targetWallet, chainConfiguration.chainId]); - const disconnect = () => { - setInputWallet(undefined); - }; + const disconnect = () => { + setTargetWallet(undefined); + }; - const contextValue: WalletProvider = { - chainId: chainConfiguration.chainId, - restUrl: chainConfiguration.restUrl, - rpcUrl: chainConfiguration.rpcUrl, - supportedWallets, - accounts, - offlineSigner, - connectedWallet, - installedWallets, - setInputWallet, - connect, - disconnect, - }; + const contextValue: WalletProvider = { + chainId: chainConfiguration.chainId, + restUrl: chainConfiguration.restUrl, + rpcUrl: chainConfiguration.rpcUrl, + wallets: filteredWallets, + connect: setTargetWallet, + disconnect, + accounts, + offlineSigner, + connectedWallet, + targetWallet, + setTargetWallet, + showConnectModal, + setShowConnectModal, + connectionError, + setConnectionError + }; - return ( - - {children} - - ); + return ( + + {children} + + + ); }; export default SeiWalletProvider; diff --git a/packages/react/src/lib/provider/keplrVerification.ts b/packages/react/src/lib/provider/keplrVerification.ts new file mode 100644 index 00000000..b26327cf --- /dev/null +++ b/packages/react/src/lib/provider/keplrVerification.ts @@ -0,0 +1,53 @@ +export const getVerifiedSuggestChain = (chainId: string) => ({ + chainId: chainId, + chainName: `Sei (${chainId})`, + rpc: `https://rpc.wallet.${chainId}.sei.io`, + rest: `https://rest.wallet.${chainId}.sei.io`, + bip44: { + coinType: 118, + }, + bech32Config: { + bech32PrefixAccAddr: 'sei', + bech32PrefixAccPub: `seipub`, + bech32PrefixValAddr: `seivaloper`, + bech32PrefixValPub: `seivaloperpub`, + bech32PrefixConsAddr: `seivalcons`, + bech32PrefixConsPub: `seivalconspub`, + }, + currencies: [ + { + coinDenom: 'SEI', + coinMinimalDenom: 'usei', + coinDecimals: 6, + }, + ], + feeCurrencies: [ + { + coinDenom: 'SEI', + coinMinimalDenom: 'usei', + coinDecimals: 6, + gasPriceStep: { + low: 0.001, + average: 0.02, + high: 0.03, + }, + }, + ], + stakeCurrency: { + coinDenom: 'SEI', + coinMinimalDenom: 'usei', + coinDecimals: 6, + }, + coinType: 118, + features: ['stargate', 'ibc-transfer', 'cosmwasm'], +}); + +export const checkKeplrForChain = async (chainId: string) => { + const keplrChainInformation = await window[ + 'keplr' + ]?.getChainInfosWithoutEndpoints(); + const chainInfo = keplrChainInformation?.find( + (chainInfo) => chainInfo.chainId === chainId + ); + return !!chainInfo; +}; diff --git a/packages/react/src/lib/provider/types.ts b/packages/react/src/lib/provider/types.ts index 5d0b119b..d96108cd 100644 --- a/packages/react/src/lib/provider/types.ts +++ b/packages/react/src/lib/provider/types.ts @@ -1,23 +1,46 @@ -import { WalletWindowKey } from '@sei-js/core'; import { AccountData, OfflineSigner } from '@cosmjs/proto-signing'; -import { ReactNode } from 'react'; +import { Dispatch, ReactNode, SetStateAction } from 'react'; import { ChainConfiguration } from '../types'; +import { StdSignature } from '@cosmjs/amino'; export type WalletProvider = { - chainId?: string; - restUrl?: string; - rpcUrl?: string; - supportedWallets: WalletWindowKey[]; - accounts: readonly AccountData[]; - offlineSigner?: OfflineSigner; - connectedWallet?: WalletWindowKey; - installedWallets: WalletWindowKey[]; - setInputWallet?: (inputWallet: WalletWindowKey | undefined) => void; - connect: (walletKey: WalletWindowKey) => void; - disconnect: () => void; + chainId: string; + restUrl: string; + rpcUrl: string; + connectionError?: string; + setConnectionError: Dispatch>; + accounts: readonly AccountData[]; + offlineSigner?: OfflineSigner; + connectedWallet?: SeiWallet; + targetWallet?: SeiWallet; + setTargetWallet: Dispatch>; + showConnectModal?: boolean; + setShowConnectModal: Dispatch>; + connect: (walletKey: SeiWallet) => void; + wallets: SeiWallet[]; + disconnect: () => void; }; +export type SupportedWalletInput = 'compass' | 'leap' | 'fin' | 'keplr' | SeiWallet; + +export interface SeiWallet { + walletInfo: { + windowKey: string; + name: string; + icon: string; + website: string; + }; + getOfflineSigner: (chainId: string) => Promise; + getAccounts: (chainId: string) => Promise; + connect: (chainId: string) => Promise; + disconnect: (chainId: string) => Promise; + suggestChain?: (chainId: string) => void; + signArbitrary?: (chainId: string, signer: string, message: string) => Promise; +} + export type SeiWalletProviderProps = { - children: ReactNode; - chainConfiguration: ChainConfiguration; + children: ReactNode; + chainConfiguration: ChainConfiguration; + wallets: SupportedWalletInput[]; + autoConnect?: SupportedWalletInput; }; diff --git a/packages/react/src/lib/utils/css.ts b/packages/react/src/lib/utils/css.ts new file mode 100644 index 00000000..6c75b270 --- /dev/null +++ b/packages/react/src/lib/utils/css.ts @@ -0,0 +1,6 @@ +export const isValidCSSColor = (color) => { + //validate hex + const s = new Option().style; + s.color = color; + return s.color !== '' && color.startsWith('#'); +}; diff --git a/packages/react/src/lib/utils/index.ts b/packages/react/src/lib/utils/index.ts index d61b7aad..8359c082 100644 --- a/packages/react/src/lib/utils/index.ts +++ b/packages/react/src/lib/utils/index.ts @@ -1 +1,2 @@ +export * from './css'; export * from './shouldUseTm34Client'; diff --git a/yarn.lock b/yarn.lock index b6507b4b..e91c1d79 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6873,6 +6873,11 @@ quick-lru@^4.0.1: resolved "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== +react-icons@4.9.0: + version "4.9.0" + resolved "https://registry.npmjs.org/react-icons/-/react-icons-4.9.0.tgz#ba44f436a053393adb1bdcafbc5c158b7b70d2a3" + integrity sha512-ijUnFr//ycebOqujtqtV9PFS7JjhWg0QU6ykURVHuL4cbofvRCf3f6GMn9+fBktEFQOIVZnuAYLZdiyadRQRFg== + react-is@^16.13.1: version "16.13.1" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"