diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 70bb23d9..a40534bb 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,12 +1,19 @@ # This is a comment. + # Each line is a file pattern followed by one or more owners. # These owners will be the default owners for everything in + # the repo. Unless a later match takes precedence, + # @global-owner1 and @global-owner2 will be requested for + # review when someone opens a pull request. -* @baryon2 + +- @baryon2 # The CODEOWNERS file restricts approval capabilities to a subset + # of contributors. -.github/CODEOWNERS @baryon2 \ No newline at end of file + +.github/CODEOWNERS @baryon2 diff --git a/.gitignore b/.gitignore index 3a4cf984..307b99d9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ apps/extension/.env.production apps/extension/.env.development apps/extension/.env.production.compass apps/extension/.env.development.compass -apps/extension/staging-builds \ No newline at end of file +.zed +.zed +apps/extension/staging-builds diff --git a/.yarnrc b/.yarnrc deleted file mode 100644 index 3ea1491b..00000000 --- a/.yarnrc +++ /dev/null @@ -1 +0,0 @@ -"@leapwallet:registry" "https://registry.npmjs.org/" diff --git a/apps/demo/package.json b/apps/demo/package.json index 50ac9efa..d856f164 100644 --- a/apps/demo/package.json +++ b/apps/demo/package.json @@ -20,7 +20,7 @@ "dependencies": { "@leapwallet/cosmos-wallet-hooks": "*", "@leapwallet/cosmos-wallet-sdk": "*", - "@leapwallet/leap-ui": "0.1.9", + "@leapwallet/leap-ui": "0.1.35", "bignumber.js": "9.0.2", "classnames": "2.3.1", "dayjs": "1.11.5", diff --git a/apps/extension/.gitignore b/apps/extension/.gitignore index 43ce2105..e7324129 100644 --- a/apps/extension/.gitignore +++ b/apps/extension/.gitignore @@ -6,8 +6,10 @@ yalc.lock .env.canary .env.development .env.production.compass +.env.canary .env.canary.compass builds canary-builds +canary-builds.zip builds.zip coverage diff --git a/apps/extension/package.json b/apps/extension/package.json index 03221887..fc8ad1ec 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -9,13 +9,17 @@ "typecheck:watch": "tsc --watch --noEmit", "build:_production": "cross-env NODE_ENV=production webpack", "build:_compass": "cross-env NODE_ENV=production APP=compass webpack", + "build:_canary": "cross-env NODE_ENV=canary webpack", + "build:_canary-compass": "cross-env NODE_ENV=canary APP=compass webpack", "build:_staging": "webpack --env staging=true", "build": "run-p typecheck build:_production", "build:compass": "run-p typecheck build:_compass", "build:staging": "run-p typecheck build:_staging", + "build:canary": "run-p typecheck build:_canary", + "build:canary-compass": "run-p typecheck build:_canary-compass", "start:watcher": "node ./scripts/watcher.mjs", "start:_development": "cross-env NODE_ENV=development webpack", - "start:_dev:compass": "cross-env APP=compass webpack", + "start:_dev:compass": "cross-env NODE_ENV=development APP=compass webpack", "start": "run-p start:watcher typecheck:watch start:_development", "start:compass": "run-p start:watcher typecheck:watch start:_dev:compass", "format": "prettier --write 'src/**/*.{js,jsx,ts,tsx,css,md,json}' --config ./.prettierrc", @@ -90,7 +94,7 @@ "react-range": "1.8.14", "react-router": "6.3.0", "react-router-dom": "6.3.0", - "reaviz": "14.7.4", + "reaviz": "14.9.7", "recoil": "0.7.3", "remark-gfm": "3.0.1", "semver": "7.5.4", diff --git a/apps/extension/public/base_manifest.json b/apps/extension/public/base_manifest.json index 6a697021..3af35079 100644 --- a/apps/extension/public/base_manifest.json +++ b/apps/extension/public/base_manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "__NAME__", "description": "__DESCRIPTION__", - "version": "0.11.2", + "version": "0.11.4", "options_page": "index.html", "web_accessible_resources": [ { diff --git a/apps/extension/public/compass-canary/favicon.ico b/apps/extension/public/compass-canary/favicon.ico new file mode 100644 index 00000000..ccf5b970 Binary files /dev/null and b/apps/extension/public/compass-canary/favicon.ico differ diff --git a/apps/extension/public/compass-canary/icons/icon-128.png b/apps/extension/public/compass-canary/icons/icon-128.png new file mode 100644 index 00000000..1f206503 Binary files /dev/null and b/apps/extension/public/compass-canary/icons/icon-128.png differ diff --git a/apps/extension/public/compass-canary/icons/icon-16.png b/apps/extension/public/compass-canary/icons/icon-16.png new file mode 100644 index 00000000..ffcd836b Binary files /dev/null and b/apps/extension/public/compass-canary/icons/icon-16.png differ diff --git a/apps/extension/public/compass-canary/icons/icon-48.png b/apps/extension/public/compass-canary/icons/icon-48.png new file mode 100644 index 00000000..3c0dff97 Binary files /dev/null and b/apps/extension/public/compass-canary/icons/icon-48.png differ diff --git a/apps/extension/public/compass-canary/index.html b/apps/extension/public/compass-canary/index.html new file mode 100644 index 00000000..b5e58537 --- /dev/null +++ b/apps/extension/public/compass-canary/index.html @@ -0,0 +1,26 @@ + + + + + + + + + + Compass CANARY BUILD + + + + + +
+ + + + diff --git a/apps/extension/public/compass-canary/manifest.json b/apps/extension/public/compass-canary/manifest.json new file mode 100644 index 00000000..6473c721 --- /dev/null +++ b/apps/extension/public/compass-canary/manifest.json @@ -0,0 +1,46 @@ +{ + "manifest_version": 3, + "name": "Compass CANARY BUILD", + "description": "THIS IS THE CANARY DISTRIBUTION OF THE COMPASS EXTENSION, INTENDED FOR DEVELOPERS.", + "version": "0.11.1", + "options_page": "index.html", + "web_accessible_resources": [ + { + "resources": ["injectLeap.js"], + "matches": [""] + } + ], + "action": { + "default_icon": { + "128": "/icons/icon-128.png" + }, + "default_popup": "index.html" + }, + "icons": { + "16": "/icons/icon-16.png", + "48": "/icons/icon-48.png", + "128": "/icons/icon-128.png" + }, + "background": { + "service_worker": "background.js" + }, + "content_scripts": [ + { + "matches": ["https://*/*", "http://*/*"], + "js": ["contentScripts.js"], + "run_at": "document_start", + "all_frames": true + } + ], + "commands": { + "_execute_action": { + "suggested_key": { + "default": "Ctrl+Shift+L" + } + } + }, + "permissions": ["storage"], + "content_security_policy": { + "extension_pages": "object-src 'none'; script-src 'self' 'wasm-unsafe-eval'; img-src * 'self' data: https:; font-src https://fonts.gstatic.com; style-src 'self' https://fonts.googleapis.com data: 'unsafe-inline'; connect-src * data: blob: filesystem:; media-src * data: blob: filesystem:; form-action 'self'; frame-ancestors 'none';frame-src https://newassets.hcaptcha.com https://www.google.com/; base-uri 'self'; default-src 'none'" + } +} diff --git a/apps/extension/public/compass-canary/support.svg b/apps/extension/public/compass-canary/support.svg new file mode 100644 index 00000000..a92243ed --- /dev/null +++ b/apps/extension/public/compass-canary/support.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/apps/extension/public/compass/manifest.json b/apps/extension/public/compass/manifest.json index bd873dd4..cf95817e 100644 --- a/apps/extension/public/compass/manifest.json +++ b/apps/extension/public/compass/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "Compass Wallet for Sei", "description": "A crypto wallet for Sei Blockchain, brought to you by the Leap Wallet team.", - "version": "0.11.2", + "version": "0.11.4", "options_page": "index.html", "web_accessible_resources": [ { diff --git a/apps/extension/public/leap-cosmos/manifest.json b/apps/extension/public/leap-cosmos/manifest.json index 5c0a4b0d..b04aaea7 100644 --- a/apps/extension/public/leap-cosmos/manifest.json +++ b/apps/extension/public/leap-cosmos/manifest.json @@ -1,8 +1,8 @@ { "manifest_version": 3, - "name": "Leap Cosmos Wallet", - "description": "A crypto wallet for Cosmos blockchains.", - "version": "0.11.2", + "name": "Leap Cosmos Wallet DEVELOPMENT BUILD", + "description": "THIS IS THE DEVELOPMENT DISTRIBUTION OF THE LEAP COSMOS EXTENSION, INTENDED FOR LOCAL DEVELOPMENT.", + "version": "0.11.4", "options_page": "index.html", "web_accessible_resources": [ { diff --git a/apps/extension/src/Routes.tsx b/apps/extension/src/Routes.tsx index 56665c22..5e03988a 100644 --- a/apps/extension/src/Routes.tsx +++ b/apps/extension/src/Routes.tsx @@ -45,6 +45,7 @@ const OnboardingSuccess = React.lazy(() => import('pages/onboarding/success')) const AddSecretToken = React.lazy(() => import('pages/secret/addSecretToken')) const Send = React.lazy(() => import('pages/send-v2')) const Sign = React.lazy(() => import('pages/sign/sign-transaction')) +const SignSeiEvm = React.lazy(() => import('pages/sign-sei-evm/SignSeiEvmTransaction')) const Stake = React.lazy(() => import('pages/stake')) const CancelUndelegationPage = React.lazy(() => import('pages/stake/cancelUndelegation')) const ChooseValidator = React.lazy(() => import('pages/stake/chooseValidator')) @@ -90,7 +91,7 @@ export default function AppRoutes(): JSX.Element { if (activeWallet) { fetchAirdropsData() } - }, [activeWallet?.id]) + }, [activeWallet, activeWallet?.id, fetchAirdropsData]) return ( }> @@ -262,6 +263,14 @@ export default function AppRoutes(): JSX.Element { } /> + + + + } + /> { className?: string preview?: React.ReactNode | undefined invalid?: boolean + warning?: boolean rightElement?: React.ReactNode disabled?: boolean } @@ -34,6 +35,7 @@ export const ActionInputWithPreview = React.forwardRef( className = '', preview, invalid = false, + warning = false, rightElement, disabled = false, ...props @@ -70,11 +72,15 @@ export const ActionInputWithPreview = React.forwardRef( animate={{ opacity: 1 }} transition={{ duration: 0.2, ease: 'linear' }} exit={{ opacity: 0.9 }} - className={`border rounded-lg transition bg-gray-50 dark:bg-gray-800 outline-none text-gray-800 caret-gray-800 dark:text-white-100 w-full pl-4 pr-12 py-2 ${ - invalid - ? 'border-red-300 dark:border-red-300' - : 'border-gray-300 dark:border-gray-800 focus:border-gray-400 dark:focus:border-gray-500' - }'}`} + className={classNames( + 'border rounded-lg transition bg-gray-50 dark:bg-gray-800 outline-none text-gray-800 caret-gray-800 dark:text-white-100 w-full pl-4 pr-12 py-2', + { + 'border-red-300 dark:border-red-300': invalid, + 'border-yellow-600 dark:border-yellow-600': warning, + 'border-gray-300 dark:border-gray-800 focus:border-gray-400 dark:focus:border-gray-500': + !invalid && !warning, + }, + )} onClick={() => setShowPreview(false)} > {preview} @@ -90,13 +96,14 @@ export const ActionInputWithPreview = React.forwardRef( ref={ref} className={classNames( className, - 'border rounded-lg transition bg-white-30 dark:bg-black-50 outline-none text-gray-800 caret-gray-800 dark:text-white-100 dark:caret-white-100 disabled:cursor-not-allowed', - `w-full pl-4 py-2 ${ - invalid - ? 'border-red-300 dark:border-red-300' - : 'border-gray-300 dark:border-gray-800 focus:border-gray-400 dark:focus:border-gray-500' - }}`, + 'border rounded-lg transition bg-white-30 dark:bg-black-50 outline-none text-gray-800 caret-gray-800 dark:text-white-100 dark:caret-white-100 disabled:cursor-not-allowed w-full pl-4 py-2', `${!rightElement && disabled ? 'pr-4' : 'pr-12'}`, + { + 'border-red-300 dark:border-red-300': invalid, + 'border-yellow-600 dark:border-yellow-600': warning, + 'border-gray-300 dark:border-gray-800 focus:border-gray-400 dark:focus:border-gray-500': + !invalid && !warning, + }, )} value={value} onChange={onChange} diff --git a/apps/extension/src/components/bottom-nav/BottomNav.tsx b/apps/extension/src/components/bottom-nav/BottomNav.tsx index e6196439..0592069e 100644 --- a/apps/extension/src/components/bottom-nav/BottomNav.tsx +++ b/apps/extension/src/components/bottom-nav/BottomNav.tsx @@ -1,9 +1,5 @@ /* eslint-disable no-unused-vars */ -import { - useChainsStore, - useFeatureFlags, - useSelectedNetwork, -} from '@leapwallet/cosmos-wallet-hooks' +import { useChainsStore, useFeatureFlags } from '@leapwallet/cosmos-wallet-hooks' import { ThemeName, useTheme } from '@leapwallet/leap-ui' import classNames from 'classnames' import { useActiveChain } from 'hooks/settings/useActiveChain' @@ -34,24 +30,20 @@ export default function BottomNav({ label, disabled: disabledAll }: BottomNavPro const activeChain = useActiveChain() const { chains } = useChainsStore() const activeChainInfo = chains[activeChain] - const selectedNetwork = useSelectedNetwork() const { data: featureFlags } = useFeatureFlags() const { theme } = useTheme() const isDark = theme === ThemeName.DARK - const walletCtaDisabled = activeChain === 'nomic' - - const govRedirectHandler = useCallback(() => { - const redirectUrl = `https://cosmos.leapwallet.io/portfolio/gov?chain=${activeChainInfo?.key}` - window.open(redirectUrl, '_blank') - }, [activeChainInfo?.key]) const airdropRedirectHandler = useCallback(() => { const redirectUrl = `https://cosmos.leapwallet.io/airdrops` window.open(redirectUrl, '_blank') }, []) - const bottomNavItems = useMemo( - () => [ + const bottomNavItems = useMemo(() => { + const isSwapDisabled = + featureFlags?.swaps?.extension === 'disabled' || ['nomic', 'seiDevnet'].includes(activeChain) + + return [ { label: BottomNavLabel.Home, icon: 'account_balance_wallet', @@ -70,7 +62,7 @@ export default function BottomNav({ label, disabled: disabledAll }: BottomNavPro icon: 'sync_alt', path: '/swap', show: true, - disabled: featureFlags?.swaps?.extension === 'disabled' || walletCtaDisabled, + disabled: isSwapDisabled, }, { label: BottomNavLabel.NFTs, @@ -92,16 +84,14 @@ export default function BottomNav({ label, disabled: disabledAll }: BottomNavPro path: '/activity', show: true, }, - ], - [ - activeChainInfo?.disableStaking, - featureFlags?.swaps?.extension, - featureFlags?.airdrops?.extension, - govRedirectHandler, - selectedNetwork, - walletCtaDisabled, - ], - ) + ] + }, [ + activeChainInfo?.disableStaking, + featureFlags?.swaps?.extension, + featureFlags?.airdrops?.extension, + activeChain, + airdropRedirectHandler, + ]) return (
diff --git a/apps/extension/src/components/clickable-icons/index.tsx b/apps/extension/src/components/clickable-icons/index.tsx index 270d9c6d..ad1914f8 100644 --- a/apps/extension/src/components/clickable-icons/index.tsx +++ b/apps/extension/src/components/clickable-icons/index.tsx @@ -12,7 +12,7 @@ export type ClickableIconProps = ComponentPropsWithoutRef<'button'> & { } const ClickableIcon = forwardRef( - ({ type, image, darker, disabled, ...rest }, ref) => { + ({ type, image, disabled, ...rest }, ref) => { const { theme } = useTheme() const isDark = theme === ThemeName.DARK diff --git a/apps/extension/src/components/eth-copy-wallet-address/EthCopyWalletAddress.tsx b/apps/extension/src/components/eth-copy-wallet-address/EthCopyWalletAddress.tsx index 72410f22..7f1c4b33 100644 --- a/apps/extension/src/components/eth-copy-wallet-address/EthCopyWalletAddress.tsx +++ b/apps/extension/src/components/eth-copy-wallet-address/EthCopyWalletAddress.tsx @@ -32,6 +32,10 @@ export function EthCopyWalletAddress({ const copyIconSrc: string = useTheme().theme === ThemeName.DARK ? Images.Misc.CopyGray200 : Images.Misc.CopyGray600 + const redirectOnboarding = () => { + window.open(browser.runtime.getURL('index.html#/onboardEvmLedger')) + } + const handleClick: MouseEventHandler = async (event) => { if (!text) { event.stopPropagation() @@ -47,10 +51,6 @@ export function EthCopyWalletAddress({ onCopy && (await onCopy()) } - const redirectOnboarding = () => { - window.open(browser.runtime.getURL('index.html#/onboardEvmLedger')) - } - const handleTextClick: MouseEventHandler = (event) => { if (onTextClick) { event.stopPropagation() diff --git a/apps/extension/src/components/gas-price-options/index.tsx b/apps/extension/src/components/gas-price-options/index.tsx index 99add0d4..6dd6131b 100644 --- a/apps/extension/src/components/gas-price-options/index.tsx +++ b/apps/extension/src/components/gas-price-options/index.tsx @@ -16,9 +16,11 @@ import { useFeeTokens, useGasAdjustmentForChain, useGasPriceStepForChain, + useGetSeiEvmBalance, useGetTokenBalances, useLowGasPriceStep, useNativeFeeDenom, + useSeiLinkedAddressState, useSelectedNetwork, } from '@leapwallet/cosmos-wallet-hooks' import { @@ -36,6 +38,7 @@ import { ActionInputWithPreview } from 'components/action-input-with-preview' import Tooltip from 'components/better-tooltip' import { useFormatCurrency } from 'hooks/settings/useCurrency' import { useDefaultTokenLogo } from 'hooks/utility/useDefaultTokenLogo' +import { Wallet } from 'hooks/wallet/useWallet' import { Images } from 'images' import Long from 'long' import { useFeeValidation } from 'pages/sign/fee-validation' @@ -91,6 +94,7 @@ export type GasPriceOptionsProps = React.PropsWithChildren & { fee?: StdFee validateFee?: boolean onInvalidFees?: (feeData: NativeDenom, isFeesValid: boolean | null) => void + isSelectedTokenEvm?: boolean } const GasPriceOptions = ({ @@ -111,6 +115,7 @@ const GasPriceOptions = ({ validateFee = false, fee, onInvalidFees, + isSelectedTokenEvm, }: GasPriceOptionsProps) => { const [viewAdditionalOptions, setViewAdditionalOptions] = useState(false) const _activeChain = useActiveChain() @@ -198,14 +203,33 @@ const GasPriceOptions = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [feeTokensList]) + const getWallet = Wallet.useGetWallet() + const { addressLinkState } = useSeiLinkedAddressState(getWallet) + const { data: seiEvmBalance, status: seiEvmStatus } = useGetSeiEvmBalance() const { - allAssets: allTokens, + allAssets: _allTokens, s3IbcTokensStatus, nativeTokensStatus, } = useGetTokenBalances(activeChain, selectedNetwork) + const allTokens = useMemo(() => { + let allTokens = _allTokens + + if (isSelectedTokenEvm && !['done', 'unknown'].includes(addressLinkState)) { + allTokens = [...allTokens, ...(seiEvmBalance?.seiEvmBalance ?? [])].filter((token) => + new BigNumber(token.amount).gt(0), + ) + } + + return allTokens + }, [_allTokens, addressLinkState, isSelectedTokenEvm, seiEvmBalance?.seiEvmBalance]) + const feeTokenAsset = useMemo(() => { return allTokens.find((token) => { + if (isSelectedTokenEvm && token?.isEvm) { + return token.coinMinimalDenom === feeTokenData?.denom.coinMinimalDenom + } + if (token.ibcDenom) { if (chainInfo?.beta) { return Object.values(chainInfo.nativeDenoms).find( @@ -222,17 +246,32 @@ const GasPriceOptions = ({ return token.coinMinimalDenom === feeTokenData?.denom.coinMinimalDenom } }) - }, [allTokens, chainInfo?.beta, chainInfo?.nativeDenoms, feeTokenData]) + }, [ + allTokens, + chainInfo?.beta, + chainInfo.nativeDenoms, + feeTokenData?.denom.coinMinimalDenom, + feeTokenData?.ibcDenom, + isSelectedTokenEvm, + ]) const allTokensStatus = useMemo(() => { if (nativeTokensStatus === 'success' && s3IbcTokensStatus === 'success') { return 'success' } + if (isSelectedTokenEvm && addressLinkState !== 'done' && seiEvmStatus === 'success') { + return 'success' + } + if (nativeTokensStatus === 'loading' || s3IbcTokensStatus === 'loading') { return 'loading' } + if (isSelectedTokenEvm && addressLinkState !== 'done' && seiEvmStatus === 'loading') { + return 'loading' + } + return 'error' - }, [s3IbcTokensStatus, nativeTokensStatus]) + }, [nativeTokensStatus, s3IbcTokensStatus, isSelectedTokenEvm, addressLinkState, seiEvmStatus]) const [finalRecommendedGasLimit, setFinalRecommendedGasLimit] = useState(() => { return ( diff --git a/apps/extension/src/components/search-modal/SearchModal.tsx b/apps/extension/src/components/search-modal/SearchModal.tsx index adff4565..37a2f2ea 100644 --- a/apps/extension/src/components/search-modal/SearchModal.tsx +++ b/apps/extension/src/components/search-modal/SearchModal.tsx @@ -11,7 +11,6 @@ import { WALLETTYPE } from '@leapwallet/leap-keychain' import classNames from 'classnames' import { LoaderAnimation } from 'components/loader/Loader' import { EventName } from 'config/analytics' -import { LEDGER_DISABLED_COINTYPES } from 'config/config' import { useActiveChain } from 'hooks/settings/useActiveChain' import { Images } from 'images' import mixpanel from 'mixpanel-browser' diff --git a/apps/extension/src/components/search-modal/useHardCodedActions.ts b/apps/extension/src/components/search-modal/useHardCodedActions.ts index 9cfa36d9..ebec7553 100644 --- a/apps/extension/src/components/search-modal/useHardCodedActions.ts +++ b/apps/extension/src/components/search-modal/useHardCodedActions.ts @@ -39,8 +39,8 @@ export function useHardCodedActions() { const { data: kadoSupportedChainId = [] } = useGetKadoChains() const { data: kadoSupportedAssets = [] } = useGetKadoAssets() const isKadoSupported = - kadoSupportedChainId.includes(activeChainInfo.chainId) && - kadoSupportedAssets.includes(activeChainInfo.denom) + kadoSupportedChainId.includes(activeChainInfo?.chainId) && + kadoSupportedAssets.includes(activeChainInfo?.denom) const handleBuyClick = (type: 'leap' | 'compass') => { const buyUrlArgs: BuyUrlFuncParams = { diff --git a/apps/extension/src/components/token-card/TokenCard.tsx b/apps/extension/src/components/token-card/TokenCard.tsx index 8db33bcc..7a46ab2e 100644 --- a/apps/extension/src/components/token-card/TokenCard.tsx +++ b/apps/extension/src/components/token-card/TokenCard.tsx @@ -29,6 +29,8 @@ type TokenCardProps = { readonly size?: 'sm' | 'md' | 'lg' readonly ibcChainInfo?: IbcChainInfo | undefined readonly hasToShowIbcTag?: boolean + readonly hasToShowEvmTag?: boolean + readonly isEvm?: boolean readonly hideAmount?: boolean } @@ -46,6 +48,8 @@ export function TokenCard({ iconSrc, size, hasToShowIbcTag, + hasToShowEvmTag, + isEvm, hideAmount = false, }: TokenCardProps) { const [formatCurrency] = useFormatCurrency() @@ -75,6 +79,7 @@ export function TokenCard({ ) : null} {ibcChainInfo && hasToShowIbcTag ? : null} + {isEvm && hasToShowEvmTag ? : null}
} title2={ diff --git a/apps/extension/src/config/config.ts b/apps/extension/src/config/config.ts index aec88e6a..72e34f7d 100644 --- a/apps/extension/src/config/config.ts +++ b/apps/extension/src/config/config.ts @@ -45,3 +45,5 @@ export const LEDGER_ENABLED_EVM_CHAIN_IDS = [ ChainInfos.evmos.testnetChainId, ChainInfos.dymension.testnetChainId, ] + +export const COMPASS_CHAINS = ['seiTestnet2', 'seiDevnet'] diff --git a/apps/extension/src/config/constants.ts b/apps/extension/src/config/constants.ts index 19443255..415c8735 100644 --- a/apps/extension/src/config/constants.ts +++ b/apps/extension/src/config/constants.ts @@ -33,4 +33,4 @@ export const ETHERMINT_CHAINS = [ export const FIXED_FEE_CHAINS = ['mayachain', 'thorchain'] -export const SHOW_ETH_ADDRESS_CHAINS = ['dymension'] +export const SHOW_ETH_ADDRESS_CHAINS = ['dymension', 'seiDevnet'] diff --git a/apps/extension/src/config/message-types.ts b/apps/extension/src/config/message-types.ts index 09aaffba..fa08619a 100644 --- a/apps/extension/src/config/message-types.ts +++ b/apps/extension/src/config/message-types.ts @@ -2,4 +2,5 @@ export enum MessageTypes { signResponse = 'sign-response', signingPopupOpen = 'signing-popup-open', signTransaction = 'sign-transaction', + signSeiEvmResponse = 'sign-sei-evm-response', } diff --git a/apps/extension/src/content-scripts/content-scripts.ts b/apps/extension/src/content-scripts/content-scripts.ts index 00b5319b..191946bf 100644 --- a/apps/extension/src/content-scripts/content-scripts.ts +++ b/apps/extension/src/content-scripts/content-scripts.ts @@ -4,6 +4,8 @@ import PortStream from 'extension-port-stream' import { DEBUG } from 'utils/debug' import browser from 'webextension-polyfill' +import { isCompassWallet } from '../utils/isCompassWallet' + const WORKER_RESET_INTERVAL = 1_000 const WORKER_RESET_MESSAGE = 'WORKER_RESET_MESSAGE' @@ -136,7 +138,7 @@ function setupExtensionStream() { } function setUpPageStreams() { - const identifier = process.env.APP === 'compass' ? 'compass' : 'leap' + const identifier = isCompassWallet() ? 'compass' : 'leap' pageStream = new WindowPostMessageStream({ name: `${identifier}:content`, target: `${identifier}:inpage`, diff --git a/apps/extension/src/content-scripts/init.ts b/apps/extension/src/content-scripts/init.ts index 8644630e..52fe460e 100644 --- a/apps/extension/src/content-scripts/init.ts +++ b/apps/extension/src/content-scripts/init.ts @@ -1,4 +1,7 @@ import { Leap } from '@leapwallet/cosmos-wallet-provider' +import { Ethereum } from '@leapwallet/cosmos-wallet-provider/dist/provider/types' + +import { isCompassWallet } from '../utils/isCompassWallet' export interface CustomWindow extends Window { getEnigmaUtils: unknown @@ -8,13 +11,26 @@ export interface CustomWindow extends Window { leap: Leap keplr: Leap compass: Leap + ethereum: Ethereum + compassEvm: Ethereum } declare let window: CustomWindow -export function init(leap: Leap) { - if (process.env.APP === 'compass') { +export function init(leap: Leap, leapEvm?: Ethereum) { + if (isCompassWallet()) { window.compass = leap + + if (leapEvm) { + ;(function initEvm() { + window.compassEvm = leapEvm + + if (!window.ethereum) { + window.ethereum = leapEvm + window.ethereum.isMetaMask = true + } + })() + } } else { window.leap = leap if (!window.keplr) { diff --git a/apps/extension/src/content-scripts/inject-leap.ts b/apps/extension/src/content-scripts/inject-leap.ts index d53cff94..097c9eaf 100644 --- a/apps/extension/src/content-scripts/inject-leap.ts +++ b/apps/extension/src/content-scripts/inject-leap.ts @@ -1,8 +1,9 @@ -import { Leap } from '@leapwallet/cosmos-wallet-provider' +import { Leap, LeapEvm } from '@leapwallet/cosmos-wallet-provider' import manifest from '../../public/base_manifest.json' import { init } from './init' +const ethereum = new LeapEvm() const leap = new Leap(manifest.version, 'core') -init(leap) +init(leap, ethereum) diff --git a/apps/extension/src/extension-scripts/background.ts b/apps/extension/src/extension-scripts/background.ts index ee6b23dc..3ed4c347 100644 --- a/apps/extension/src/extension-scripts/background.ts +++ b/apps/extension/src/extension-scripts/background.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable no-use-before-define */ /* eslint-disable @typescript-eslint/no-explicit-any */ @@ -8,8 +9,18 @@ import './fetch-preserver' import { SUPPORTED_METHODS } from '@leapwallet/cosmos-wallet-provider/dist/provider/messaging/requester' -import { ChainInfo, ChainInfos, SupportedChain } from '@leapwallet/cosmos-wallet-sdk/dist/constants' -import { getRestUrl } from '@leapwallet/cosmos-wallet-sdk/dist/utils' +import { + ChainInfo, + ChainInfos, + SupportedChain, + getRestUrl, + getSeiEvmAddressToShow, + parseStandardTokenTransactionData, + formatEtherValue, + ARCTIC_COSMOS_CHAIN_ID, + ARCTIC_ETH_CHAIN_ID, + encodedUtf8HexToText, +} from '@leapwallet/cosmos-wallet-sdk' import { decrypt, initCrypto, initStorage, WALLETTYPE } from '@leapwallet/leap-keychain' import { @@ -21,7 +32,6 @@ import { ENCRYPTED_ACTIVE_WALLET, KEYSTORE, REDIRECT_REQUEST, - SIGN_REQUEST, V80_KEYSTORE_MIGRATION_COMPLETE, VIEWING_KEYS, } from 'config/storage-keys' @@ -64,11 +74,25 @@ import { formatWalletName } from 'utils/formatWalletName' import { getUpdatedKeyStore } from 'hooks/wallet/getUpdatedKeyStore' import { listenPendingSwapTx, trackPendingSwapTx } from './pending-swap-tx' import { MessageTypes } from 'config/message-types' +import { + ETHEREUM_METHOD_TYPE, + ETHEREUM_RPC_ERROR, + EthereumRequestMessage, + LINE_TYPE, + LineType, +} from '@leapwallet/cosmos-wallet-provider/dist/provider/types' +import { LeapEvmRpcError } from '@leapwallet/cosmos-wallet-provider/dist/provider/evm-error' + +global.window = self + const storageAdapter = getStorageAdapter() initStorage(storageAdapter) initCrypto() -global.window = self +type Data = EthereumRequestMessage & { + origin: string + ecosystem: LineType +} const windowIdForPayloadId: { [x: number | string]: { type: string; payloadId: number } } = {} @@ -92,20 +116,74 @@ const connectRemote = (remotePort: any) => { browser.runtime.onMessage.addListener(async (message, sender) => { if (sender.id !== browser.runtime.id) return - if (message.type === 'chain-enabled') { - sendResponse( - `on${SUPPORTED_METHODS.ENABLE_ACCESS}`, - { success: 'Chain enabled' }, - message.payload.payloadId, - ) - } else if (message.type === 'chain-approval-rejected') { - sendResponse( - `on${SUPPORTED_METHODS.ENABLE_ACCESS}`, - { error: 'Request rejected' }, - message.payload.payloadId, - ) - } else if (message.type === 'popup-closed') { - enableAccessRequests = {} + switch (message.type) { + case 'chain-enabled': + if (message.payload?.ecosystem === LINE_TYPE.ETHEREUM) { + const store = await browser.storage.local.get([ACTIVE_WALLET]) + const seiEvmAddress = getSeiEvmAddressToShow(store[ACTIVE_WALLET].pubKeys?.['seiDevnet']) + + if (seiEvmAddress.startsWith('0x')) { + const successResponse = + message.payload?.ethMethod === ETHEREUM_METHOD_TYPE.WALLET__REQUEST_PERMISSIONS + ? // Refer - https://docs.metamask.io/wallet/reference/wallet_requestpermissions + [ + { + id: message.payload?.payloadId, + parentCapability: ETHEREUM_METHOD_TYPE.ETH__ACCOUNTS, + invoker: message.payload?.origin, + caveats: [ + { + type: 'restrictReturnedAccounts', + value: [seiEvmAddress], + }, + ], + date: Date.now(), + }, + ] + : [seiEvmAddress] + + sendResponse( + `on${SUPPORTED_METHODS.ENABLE_ACCESS}`, + { success: successResponse }, + message.payload.payloadId, + ) + } else { + sendResponse( + `on${SUPPORTED_METHODS.ENABLE_ACCESS}`, + { error: new LeapEvmRpcError(ETHEREUM_RPC_ERROR.INTERNAL, seiEvmAddress) }, + message.payload.payloadId, + ) + } + } else { + sendResponse( + `on${SUPPORTED_METHODS.ENABLE_ACCESS}`, + { success: 'Chain enabled' }, + message.payload.payloadId, + ) + } + + break + + case 'chain-approval-rejected': + if (message.payload?.ecosystem === LINE_TYPE.ETHEREUM) { + sendResponse( + `on${SUPPORTED_METHODS.ENABLE_ACCESS}`, + { error: new LeapEvmRpcError(ETHEREUM_RPC_ERROR.USER_REJECTED_REQUEST) }, + message.payload.payloadId, + ) + } else { + sendResponse( + `on${SUPPORTED_METHODS.ENABLE_ACCESS}`, + { error: 'Request rejected' }, + message.payload.payloadId, + ) + } + + break + + case 'popup-closed': + enableAccessRequests = {} + break } }) @@ -367,7 +445,7 @@ const connectRemote = (remotePort: any) => { } if (isNewChainPresent) { - const windowId = await openPopup('approveConnection') + await openPopup('approveConnection') windowIdForPayloadId[popupWindowId] = { type: type.toUpperCase(), payloadId: payload.id, @@ -669,8 +747,409 @@ const connectRemote = (remotePort: any) => { } } + const evmRequestHandler = async (data: Data) => { + const { method, ...payload } = data + const popupWindowId = 0 + const sendResponseName = `on${method.toUpperCase()}` + + switch (method) { + case ETHEREUM_METHOD_TYPE.ETH__CHAIN_ID: { + const payloadId = payload.id as unknown as number + sendResponse( + sendResponseName, + { success: `0x${ARCTIC_ETH_CHAIN_ID.toString(16)}` }, + payloadId, + ) + break + } + + case ETHEREUM_METHOD_TYPE.ETH__SIGN: + case ETHEREUM_METHOD_TYPE.PERSONAL_SIGN: + case ETHEREUM_METHOD_TYPE.ETH__SIGN_TYPED_DATA_V4: { + const payloadId = payload.id as unknown as number + const store = await browser.storage.local.get([ACTIVE_WALLET]) + + if (!store[ACTIVE_WALLET]) { + try { + await openPopup('login', '?close-on-login=true') + await awaitUIResponse('user-logged-in') + } catch { + sendResponse( + sendResponseName, + { error: new LeapEvmRpcError(ETHEREUM_RPC_ERROR.USER_REJECTED_REQUEST) }, + payloadId, + ) + + break + } + } + + const seiEvmAddress = getSeiEvmAddressToShow(store[ACTIVE_WALLET].pubKeys?.['seiDevnet']) + + if (seiEvmAddress.startsWith('0x')) { + if (payload.params) { + // @ts-ignore + let payloadAddress = payload.params[1] + // @ts-ignore + let data = payload.params[0] + + if ( + [ + ETHEREUM_METHOD_TYPE.ETH__SIGN, + ETHEREUM_METHOD_TYPE.ETH__SIGN_TYPED_DATA_V4, + ].includes(method) + ) { + ;[payloadAddress, data] = [data, payloadAddress] + } + + if ( + seiEvmAddress.toLowerCase() !== payloadAddress.toLowerCase() || + !data || + (method !== ETHEREUM_METHOD_TYPE.ETH__SIGN_TYPED_DATA_V4 && !data?.startsWith('0x')) + ) { + sendResponse( + sendResponseName, + { error: new LeapEvmRpcError(ETHEREUM_RPC_ERROR.INVALID_PARAMS) }, + payloadId, + ) + + break + } + + if ( + method === ETHEREUM_METHOD_TYPE.ETH__SIGN_TYPED_DATA_V4 && + data.domain.chainId !== ARCTIC_ETH_CHAIN_ID + ) { + sendResponse( + sendResponseName, + { + error: new LeapEvmRpcError(ETHEREUM_RPC_ERROR.INVALID_PARAMS, 'Invalid chainId.'), + }, + payloadId, + ) + + break + } + + const details: any = + method === ETHEREUM_METHOD_TYPE.ETH__SIGN_TYPED_DATA_V4 + ? { [data.primaryType]: data.message } + : { Message: encodedUtf8HexToText(data) } + + const signTxnData = { + payloadAddress, + data, + methodType: method, + details, + } + + requestSignTransaction({ + origin: payload.origin, + ecosystem: payload.ecosystem, + signTxnData, + }) + + await openPopup('signSeiEvm') + + try { + const response = await awaitSigningResponse(MessageTypes.signSeiEvmResponse) + sendResponse(sendResponseName, { success: response }, payloadId) + } catch (e) { + sendResponse( + sendResponseName, + { error: new LeapEvmRpcError(ETHEREUM_RPC_ERROR.USER_REJECTED_REQUEST) }, + payloadId, + ) + } + } + } else { + sendResponse( + sendResponseName, + { error: new LeapEvmRpcError(ETHEREUM_RPC_ERROR.INTERNAL, seiEvmAddress) }, + payloadId, + ) + } + + break + } + + case ETHEREUM_METHOD_TYPE.ETH__ACCOUNTS: { + const payloadId = payload.id as unknown as number + const store = await browser.storage.local.get([ACTIVE_WALLET]) + + if (!store[ACTIVE_WALLET]) { + try { + await openPopup('login', '?close-on-login=true') + await awaitUIResponse('user-logged-in') + } catch { + sendResponse( + sendResponseName, + { error: new LeapEvmRpcError(ETHEREUM_RPC_ERROR.USER_REJECTED_REQUEST) }, + payloadId, + ) + + break + } + } + + const seiEvmAddress = getSeiEvmAddressToShow(store[ACTIVE_WALLET].pubKeys?.['seiDevnet']) + + if (seiEvmAddress.startsWith('0x')) { + sendResponse(sendResponseName, { success: [seiEvmAddress] }, payloadId) + } else { + sendResponse( + sendResponseName, + { error: new LeapEvmRpcError(ETHEREUM_RPC_ERROR.INTERNAL, seiEvmAddress) }, + payloadId, + ) + } + + break + } + + case ETHEREUM_METHOD_TYPE.ETH__SEND_TRANSACTION: { + const payloadId = payload.id as unknown as number + + if (payload.params) { + // @ts-ignore + const parsedData = parseStandardTokenTransactionData(payload.params[0].data) + let signTxnData = {} + + if (!parsedData) { + // @ts-ignore + const value = payload.params[0].value + // @ts-ignore + const to = payload.params[0].to + // @ts-ignore + const data = payload.params[0].data + + signTxnData = { + value: value ? formatEtherValue(value) : '0', + to, + data, + details: { + Value: value ? `${value} SEI` : '0', + 'Contract Interaction': to, + 'HEX Data': data, + }, + } + } else { + switch (parsedData.name) { + case 'approve': { + const value = parsedData.value.toString() + // @ts-ignore + const to = payload.params[0].to + // @ts-ignore + const data = payload.params[0].data + + signTxnData = { + value, + to, + data, + spendPermissionCapValue: parsedData.args[1].toString(), + details: { + 'Third Party': parsedData.args[0], + Data: { + Function: 'Approve', + HEX: data, + }, + }, + } + + break + } + } + } + + requestSignTransaction({ + origin: payload.origin, + ecosystem: payload.ecosystem, + signTxnData, + }) + + await openPopup('signSeiEvm') + + try { + const response = await awaitSigningResponse(MessageTypes.signSeiEvmResponse) + sendResponse(sendResponseName, { success: response }, payloadId) + } catch (e) { + sendResponse( + sendResponseName, + { error: new LeapEvmRpcError(ETHEREUM_RPC_ERROR.USER_REJECTED_REQUEST) }, + payloadId, + ) + } + } + + break + } + + case ETHEREUM_METHOD_TYPE.WALLET__REQUEST_PERMISSIONS: + case ETHEREUM_METHOD_TYPE.ETH__REQUEST_ACCOUNTS: { + if ( + method === ETHEREUM_METHOD_TYPE.WALLET__REQUEST_PERMISSIONS && + // @ts-ignore + !Object.keys(payload.params?.[0] ?? {}).includes(ETHEREUM_METHOD_TYPE.ETH__ACCOUNTS) + ) { + break + } + + const msg = payload + const payloadId = payload.id as unknown as number + const chainIds: [string] = [ARCTIC_COSMOS_CHAIN_ID] + + let queryString = `?origin=${msg?.origin}` + chainIds?.forEach((chainId: string) => { + queryString += `&chainIds=${chainId}` + }) + + const store = await browser.storage.local.get([ACTIVE_WALLET]) + + if (!store[ACTIVE_WALLET]) { + try { + await openPopup('login', '?close-on-login=true') + await awaitUIResponse('user-logged-in') + } catch { + sendResponse( + sendResponseName, + { error: new LeapEvmRpcError(ETHEREUM_RPC_ERROR.USER_REJECTED_REQUEST) }, + payloadId, + ) + break + } + } + + checkConnection(chainIds, msg) + .then(async ({ validChainIds, isNewChainPresent }) => { + if (validChainIds.length > 0) { + const seiEvmAddress = getSeiEvmAddressToShow( + store[ACTIVE_WALLET].pubKeys?.['seiDevnet'], + ) + + const successResponse = + method === ETHEREUM_METHOD_TYPE.WALLET__REQUEST_PERMISSIONS + ? // Refer - https://docs.metamask.io/wallet/reference/wallet_requestpermissions + [ + { + id: payloadId, + parentCapability: ETHEREUM_METHOD_TYPE.ETH__ACCOUNTS, + invoker: msg?.origin, + caveats: [ + { + type: 'restrictReturnedAccounts', + value: [seiEvmAddress], + }, + ], + date: Date.now(), + }, + ] + : [seiEvmAddress] + + if (isNewChainPresent) { + await browser.storage.local.set({ + [REDIRECT_REQUEST]: { type: method, msg: { ...msg, validChainIds } }, + }) + + const shouldOpenPopup = + Object.keys(enableAccessRequests).length === 0 || + !Object.keys(enableAccessRequests).some((key) => key.includes(msg.origin)) + + if (shouldOpenPopup) { + delete enableAccessRequests[queryString] + enableAccessRequests[queryString] = popupWindowId + await openPopup('approveConnection') + + requestEnableAccess({ + origin: msg.origin, + validChainIds, + payloadId: payloadId as unknown as string, + ecosystem: LINE_TYPE.ETHEREUM, + ethMethod: method, + }) + + windowIdForPayloadId[popupWindowId] = { + type: method.toUpperCase(), + payloadId: payloadId, + } + } else { + if (!enableAccessRequests[queryString]) { + requestEnableAccess({ + origin: msg.origin, + validChainIds, + payloadId: payloadId as unknown as string, + ecosystem: LINE_TYPE.ETHEREUM, + ethMethod: method, + }) + + enableAccessRequests[queryString] = popupWindowId + } + } + + try { + await awaitEnableChainResponse() + + if (seiEvmAddress.startsWith('0x')) { + sendResponse(sendResponseName, { success: successResponse }, payloadId) + } else { + sendResponse( + sendResponseName, + { error: new LeapEvmRpcError(ETHEREUM_RPC_ERROR.INTERNAL, seiEvmAddress) }, + payloadId, + ) + } + + delete enableAccessRequests[queryString] + } catch (error: any) { + sendResponse( + sendResponseName, + { error: new LeapEvmRpcError(ETHEREUM_RPC_ERROR.INTERNAL, error.error) }, + payloadId, + ) + delete enableAccessRequests[queryString] + } + } else { + if (seiEvmAddress.startsWith('0x')) { + sendResponse(sendResponseName, { success: successResponse }, payloadId) + } else { + sendResponse( + sendResponseName, + { error: new LeapEvmRpcError(ETHEREUM_RPC_ERROR.INTERNAL, seiEvmAddress) }, + payloadId, + ) + + delete enableAccessRequests[queryString] + } + } + } else { + sendResponse( + sendResponseName, + { error: new LeapEvmRpcError(ETHEREUM_RPC_ERROR.INTERNAL, 'Invalid chain id') }, + payloadId, + ) + + delete enableAccessRequests[queryString] + } + }) + .catch(() => { + sendResponse( + sendResponseName, + { error: new LeapEvmRpcError(ETHEREUM_RPC_ERROR.INTERNAL, 'Invalid chain id') }, + payloadId, + ) + + delete enableAccessRequests[queryString] + }) + break + } + } + } + portStream.on('data', async (data: any) => { - await requestHandler(data) + if (data?.ecosystem === LINE_TYPE.ETHEREUM) { + await evmRequestHandler(data) + } else { + await requestHandler(data) + } }) } @@ -804,33 +1283,33 @@ if (!browser.action.onClicked.hasListener(openAuthPage)) { browser.action.onClicked.addListener(openAuthPage) } -function awaitResponse(txType: 'direct' | 'amino') { - return new Promise((resolve, reject) => { - const directSignResponseListener = (changes: Record) => { - const { newValue } = changes[BG_RESPONSE] || {} - if (newValue) { - if (newValue.error) { - return reject(newValue) - } - const response = JSON.parse(newValue) - if (txType === 'direct') { - response.signed.authInfoBytes = new Uint8Array( - Object.values(response.signed.authInfoBytes), - ) - response.signed.bodyBytes = new Uint8Array(Object.values(response.signed.bodyBytes)) - - resolve(response) - } else { - resolve(response) - } - browser.storage.local.remove(BG_RESPONSE) - - browser.storage.onChanged.removeListener(directSignResponseListener) - } - } - browser.storage.onChanged.addListener(directSignResponseListener) - }) -} +// function awaitResponse(txType: 'direct' | 'amino') { +// return new Promise((resolve, reject) => { +// const directSignResponseListener = (changes: Record) => { +// const { newValue } = changes[BG_RESPONSE] || {} +// if (newValue) { +// if (newValue.error) { +// return reject(newValue) +// } +// const response = JSON.parse(newValue) +// if (txType === 'direct') { +// response.signed.authInfoBytes = new Uint8Array( +// Object.values(response.signed.authInfoBytes), +// ) +// response.signed.bodyBytes = new Uint8Array(Object.values(response.signed.bodyBytes)) + +// resolve(response) +// } else { +// resolve(response) +// } +// browser.storage.local.remove(BG_RESPONSE) + +// browser.storage.onChanged.removeListener(directSignResponseListener) +// } +// } +// browser.storage.onChanged.addListener(directSignResponseListener) +// }) +// } function awaitEnableChainResponse(): Promise { return new Promise((resolve, reject) => { diff --git a/apps/extension/src/extension-scripts/fetch-preserver.ts b/apps/extension/src/extension-scripts/fetch-preserver.ts index 6012ee1f..cf90dd5c 100644 --- a/apps/extension/src/extension-scripts/fetch-preserver.ts +++ b/apps/extension/src/extension-scripts/fetch-preserver.ts @@ -6,3 +6,4 @@ * * */ export const originalFetch = fetch +global.window = self diff --git a/apps/extension/src/extension-scripts/utils.ts b/apps/extension/src/extension-scripts/utils.ts index 0a3f231e..ccfee4dc 100644 --- a/apps/extension/src/extension-scripts/utils.ts +++ b/apps/extension/src/extension-scripts/utils.ts @@ -1,7 +1,9 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/ban-ts-comment */ import { Key } from '@leapwallet/cosmos-wallet-hooks' import { getChains } from '@leapwallet/cosmos-wallet-hooks' -import { chainIdToChain, ChainInfo, sleep, SupportedChain } from '@leapwallet/cosmos-wallet-sdk' +import { LineType } from '@leapwallet/cosmos-wallet-provider/dist/provider/types' +import { chainIdToChain, ChainInfo, SupportedChain } from '@leapwallet/cosmos-wallet-sdk' import { KeyChain } from '@leapwallet/leap-keychain' import { initStorage } from '@leapwallet/leap-keychain' import { MessageTypes } from 'config/message-types' @@ -116,7 +118,13 @@ export async function checkConnection(chainIds: [string], msg: any) { const popupIds: Record = {} const pendingPromises: Record> = {} -type Page = 'approveConnection' | 'suggestChain' | 'sign' | 'add-secret-token' | 'login' +type Page = + | 'approveConnection' + | 'suggestChain' + | 'sign' + | 'signSeiEvm' + | 'add-secret-token' + | 'login' async function getPopup() { if (popupIds.length === 0) { @@ -268,7 +276,10 @@ export async function getSeed(password: string) { const address = await getWalletAddress('secret-4') const storageAdapter = getStorageAdapter() initStorage(storageAdapter) - const signer = await KeyChain.getSigner(activeWallet.id, password, 'secret', '529') + const signer = await KeyChain.getSigner(activeWallet.id, password, { + addressPrefix: 'secret', + coinType: '529', + }) const seed = CryptoJs.SHA256( //@ts-ignore @@ -316,6 +327,8 @@ export function requestEnableAccess(payload: { origin: string validChainIds: string[] payloadId: string + ecosystem?: LineType + ethMethod?: string }) { // Store the listener function in a variable so we can remove it later // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -353,11 +366,13 @@ export async function awaitUIResponse(messageType: string) { export function requestSignTransaction(payload: any) { const listener = (message: any, sender: any) => { if (sender.id !== browser.runtime.id) throw new Error('Invalid Sender') + if (message.type === MessageTypes.signingPopupOpen) { browser.runtime.sendMessage({ type: MessageTypes.signTransaction, payload }) browser.runtime.onMessage.removeListener(listener) } } + browser.runtime.onMessage.addListener(listener) } @@ -370,9 +385,11 @@ export async function awaitSigningResponse(messageType: string) { } else { reject(message.payload.data) } + browser.runtime.onMessage.removeListener(listener) } } + browser.runtime.onMessage.addListener(listener) }) } diff --git a/apps/extension/src/hooks/onboarding/useOnboarding.ts b/apps/extension/src/hooks/onboarding/useOnboarding.ts index ba6ef5b4..b9959faa 100644 --- a/apps/extension/src/hooks/onboarding/useOnboarding.ts +++ b/apps/extension/src/hooks/onboarding/useOnboarding.ts @@ -83,6 +83,24 @@ export function useOnboarding() { setWalletAccounts(walletAccounts) } + function mergeAddresses(addressesNew: Addresses, addressesOriginal: Addresses) { + const addressIndexes = Object.keys(addressesOriginal).map((addressIndex) => + parseInt(addressIndex), + ) + const updatedAddresses = { ...addressesOriginal } + for (const addressIndex of addressIndexes) { + if (updatedAddresses[addressIndex]) { + updatedAddresses[addressIndex] = { + ...updatedAddresses[addressIndex], + ...addressesNew[addressIndex], + } + } else { + updatedAddresses[addressIndex] = addressesNew[addressIndex] + } + } + return updatedAddresses + } + const getEvmLedgerAccountDetails = async () => { const useEvmApp = true const defaultChainCosmos = isCompassWallet() ? 'seiTestnet2' : 'cosmos' @@ -94,15 +112,7 @@ export function useOnboarding() { LEDGER_ENABLED_EVM_CHAINS, ) - // setWalletAccounts( - // primaryChainAccount.map((account, index) => ({ - // address: account.address, - // pubkey: account.pubkey, - // index, - // })), - // ) const newAddresses: Addresses = {} - for (const [chain, chainAddresses] of Object.entries(chainWiseAddresses)) { let index = 0 for (const address of chainAddresses) { @@ -122,24 +132,6 @@ export function useOnboarding() { } } - function mergeAddresses(addressesNew: Addresses, addressesOriginal: Addresses) { - const addressIndexes = Object.keys(addressesOriginal).map((addressIndex) => - parseInt(addressIndex), - ) - const updatedAddresses = { ...addressesOriginal } - for (const addressIndex of addressIndexes) { - if (updatedAddresses[addressIndex]) { - updatedAddresses[addressIndex] = { - ...updatedAddresses[addressIndex], - ...addressesNew[addressIndex], - } - } else { - updatedAddresses[addressIndex] = addressesNew[addressIndex] - } - } - return updatedAddresses - } - const getLedgerAccountDetails = async (useEvmApp: boolean) => { const defaultChainCosmos = isCompassWallet() ? 'seiTestnet2' : 'cosmos' const defaultChainEth = 'injective' diff --git a/apps/extension/src/hooks/settings/useActiveChain.ts b/apps/extension/src/hooks/settings/useActiveChain.ts index 16625e87..9b79ff3e 100644 --- a/apps/extension/src/hooks/settings/useActiveChain.ts +++ b/apps/extension/src/hooks/settings/useActiveChain.ts @@ -1,12 +1,14 @@ import { Key, useActiveChain as useActiveChainWalletHooks, + useGetChains, usePendingTxState, useSetActiveChain as useSetActiveChainWalletHooks, useSetSelectedNetwork, } from '@leapwallet/cosmos-wallet-hooks' import { ChainInfo, SupportedChain } from '@leapwallet/cosmos-wallet-sdk' import { useQueryClient } from '@tanstack/react-query' +import { COMPASS_CHAINS } from 'config/config' import { ACTIVE_CHAIN, KEYSTORE } from 'config/storage-keys' import { useSetNetwork } from 'hooks/settings/useNetwork' import { useChainInfos } from 'hooks/useChainInfos' @@ -77,22 +79,27 @@ export function useSetActiveChain() { export function useInitActiveChain() { const chainInfos = useChainInfos() + const chains = useGetChains() const setActiveChain = useSetActiveChainWalletHooks() + useEffect(() => { browser.storage.local.get(ACTIVE_CHAIN).then((storage) => { let activeChain: SupportedChain = storage[ACTIVE_CHAIN] const defaultActiveChain = isCompassWallet() ? chainInfos.seiTestnet2.key : chainInfos.cosmos.key - if (!activeChain) { + + if (!activeChain || chains[activeChain] === undefined) { activeChain = defaultActiveChain } - if (isCompassWallet() && activeChain !== chainInfos.seiTestnet2.key) { + + if (isCompassWallet() && !COMPASS_CHAINS.includes(activeChain)) { activeChain = defaultActiveChain } + setActiveChain(activeChain) }) // eslint-disable-next-line react-hooks/exhaustive-deps - }, [chainInfos]) + }, [chainInfos, chains]) } diff --git a/apps/extension/src/hooks/settings/useNetwork.ts b/apps/extension/src/hooks/settings/useNetwork.ts index 3fbed82b..1dedfb8c 100644 --- a/apps/extension/src/hooks/settings/useNetwork.ts +++ b/apps/extension/src/hooks/settings/useNetwork.ts @@ -8,6 +8,7 @@ import { useSetRecoilState } from 'recoil' import browser from 'webextension-polyfill' import { selectedChainAlertState } from '../../atoms/selected-chain-alert' +import { isCompassWallet } from '../../utils/isCompassWallet' export type SelectedNetwork = 'mainnet' | 'testnet' @@ -31,7 +32,7 @@ export function useInitNetwork() { useEffect(() => { browser.storage.local.get(SELECTED_NETWORK).then((storage) => { const network = storage[SELECTED_NETWORK] - const defaultNetwork = process.env.APP === 'compass' ? 'testnet' : 'mainnet' + const defaultNetwork = isCompassWallet() ? 'testnet' : 'mainnet' setNetwork(network ?? defaultNetwork) }) diff --git a/apps/extension/src/hooks/useAirdropsData.ts b/apps/extension/src/hooks/useAirdropsData.ts index 0f5d9c84..3367bf6e 100644 --- a/apps/extension/src/hooks/useAirdropsData.ts +++ b/apps/extension/src/hooks/useAirdropsData.ts @@ -4,12 +4,13 @@ import { useInitAirdropsEligibilityData, } from '@leapwallet/cosmos-wallet-hooks' import { KeyChain } from '@leapwallet/leap-keychain' +import { useCallback } from 'react' export const useAirdropsData = () => { const activeWallet = useActiveWallet() const { fetchAirdropsEligibilityData } = useInitAirdropsEligibilityData() - const fetchAirdropsData = async () => { + const fetchAirdropsData = useCallback(async () => { const allWallets = await KeyChain.getAllWallets() const addresses = Object.values( Object.values(allWallets).filter((wallet: Key) => wallet.id === activeWallet?.id)?.[0] @@ -17,7 +18,7 @@ export const useAirdropsData = () => { ) const uniqueAddresses = [...new Set(addresses)] fetchAirdropsEligibilityData(uniqueAddresses) - } + }, [activeWallet?.id, fetchAirdropsEligibilityData]) return fetchAirdropsData } diff --git a/apps/extension/src/hooks/useChainInfos.ts b/apps/extension/src/hooks/useChainInfos.ts index 0b80f796..5450a585 100644 --- a/apps/extension/src/hooks/useChainInfos.ts +++ b/apps/extension/src/hooks/useChainInfos.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/ban-ts-comment */ import { useChainsStore, useCustomChains } from '@leapwallet/cosmos-wallet-hooks' import { ChainInfos } from '@leapwallet/cosmos-wallet-sdk' @@ -141,6 +142,8 @@ export function useInitChainInfos() { } }) } + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [customChains]) } diff --git a/apps/extension/src/hooks/useContacts.ts b/apps/extension/src/hooks/useContacts.ts index 537dc5a7..25a1841e 100644 --- a/apps/extension/src/hooks/useContacts.ts +++ b/apps/extension/src/hooks/useContacts.ts @@ -16,7 +16,7 @@ export const useContacts = () => { if (cancel) return const contactsToShow = Object.entries(allEntries) - .filter(([key, contact]) => { + .filter(([, contact]) => { if (contact.blockchain !== 'injective') return true if (contact.blockchain === 'injective' && contact.ethAddress === '') return true return false diff --git a/apps/extension/src/hooks/useGetKadoDetails.tsx b/apps/extension/src/hooks/useGetKadoDetails.tsx index 2e6eb941..3017f8cd 100644 --- a/apps/extension/src/hooks/useGetKadoDetails.tsx +++ b/apps/extension/src/hooks/useGetKadoDetails.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { useQuery } from '@tanstack/react-query' import axios from 'axios' diff --git a/apps/extension/src/hooks/useGetWalletAddresses.ts b/apps/extension/src/hooks/useGetWalletAddresses.ts index e58eb463..1928fdc0 100644 --- a/apps/extension/src/hooks/useGetWalletAddresses.ts +++ b/apps/extension/src/hooks/useGetWalletAddresses.ts @@ -1,23 +1,26 @@ -import { useActiveChain, useAddress, useChainsStore } from '@leapwallet/cosmos-wallet-hooks' -import { getEthereumAddress } from '@leapwallet/cosmos-wallet-sdk' +import { useActiveChain, useAddress, WALLETTYPE } from '@leapwallet/cosmos-wallet-hooks' +import { getEthereumAddress, getSeiEvmAddressToShow } from '@leapwallet/cosmos-wallet-sdk' import { SHOW_ETH_ADDRESS_CHAINS } from 'config/constants' +import useActiveWallet from 'hooks/settings/useActiveWallet' import { useMemo } from 'react' -import { isLedgerEnabled } from 'utils/isLedgerEnabled' export function useGetWalletAddresses() { + const { activeWallet } = useActiveWallet() const activeChain = useActiveChain() const address = useAddress() - const { chains } = useChainsStore() return useMemo(() => { if ( - address && - isLedgerEnabled(activeChain, chains?.[activeChain]?.bip44?.coinType) && + activeWallet && + activeWallet?.walletType !== WALLETTYPE.LEDGER && SHOW_ETH_ADDRESS_CHAINS.includes(activeChain) ) { - return [getEthereumAddress(address), address] - } + const seiEvmAddress = getSeiEvmAddressToShow(activeWallet.pubKeys?.['seiDevnet']) + const ethereumAddress = + activeChain === 'seiDevnet' ? seiEvmAddress : getEthereumAddress(address) + return [ethereumAddress, address] + } return [address] - }, [activeChain, address, chains]) + }, [activeChain, address, activeWallet]) } diff --git a/apps/extension/src/hooks/useWalletClient.ts b/apps/extension/src/hooks/useWalletClient.ts index 05b15b18..441553b0 100644 --- a/apps/extension/src/hooks/useWalletClient.ts +++ b/apps/extension/src/hooks/useWalletClient.ts @@ -1,13 +1,13 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { fromBase64 } from '@cosmjs/encoding' import { useActiveChain, useChainsStore, WALLETTYPE } from '@leapwallet/cosmos-wallet-hooks' import { SupportedChain } from '@leapwallet/cosmos-wallet-sdk' import { SignAminoMethod, SignDirectMethod, Signer } from '@leapwallet/elements-core' import { WalletClient } from '@leapwallet/elements-hooks' -import { LEDGER_ENABLED_EVM_CHAINS } from 'config/config' import { decodeChainIdToChain } from 'extension-scripts/utils' import useActiveWallet from 'hooks/settings/useActiveWallet' import { Wallet } from 'hooks/wallet/useWallet' -import { useMemo } from 'react' +import { useCallback, useMemo } from 'react' export const useWalletClient = () => { const { activeWallet } = useActiveWallet() @@ -17,41 +17,53 @@ export const useWalletClient = () => { const isLedgerTypeWallet = activeWallet?.walletType === WALLETTYPE.LEDGER - const signDirect: SignDirectMethod = async (signerAddress: string, signDoc: any) => { - const wallet = await getWallet((chains[activeChain]?.key ?? '') as SupportedChain) - if ('signDirect' in wallet) { - const result = await wallet.signDirect(signerAddress, signDoc) - return { - signature: new Uint8Array(Buffer.from(result.signature.signature, 'base64')), - signed: result.signed, + const signDirect: SignDirectMethod = useCallback( + async (signerAddress: string, signDoc: any) => { + const wallet = await getWallet((chains[activeChain]?.key ?? '') as SupportedChain) + if ('signDirect' in wallet) { + const result = await wallet.signDirect(signerAddress, signDoc) + return { + signature: new Uint8Array(Buffer.from(result.signature.signature, 'base64')), + signed: result.signed, + } + } else { + throw new Error('signDirect not supported') } - } else { - throw new Error('signDirect not supported') - } - } + }, + [activeChain, chains, getWallet], + ) - const signAmino: SignAminoMethod = async (address: string, signDoc: any) => { - const wallet = await getWallet((chains[activeChain]?.key ?? '') as SupportedChain) - if ('signAmino' in wallet) { - const res = await wallet.signAmino(address, signDoc) - return { - // @ts-ignore - signature: new Uint8Array(Buffer.from(res.signature.signature, 'base64')), - signed: res.signed, + const signAmino: SignAminoMethod = useCallback( + async (address: string, signDoc: any) => { + const wallet = await getWallet((chains[activeChain]?.key ?? '') as SupportedChain) + if ('signAmino' in wallet) { + const res = await wallet.signAmino(address, signDoc) + return { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + signature: new Uint8Array(Buffer.from(res.signature.signature, 'base64')), + signed: res.signed, + } + } else { + throw new Error('signAmino not supported') } - } else { - throw new Error('signAmino not supported') - } - } + }, + [activeChain, chains, getWallet], + ) - const signer: Signer = { - signDirect: signDirect, - signAmino: signAmino, - } + const signer: Signer = useMemo( + () => ({ + signDirect: signDirect, + signAmino: signAmino, + }), + [signAmino, signDirect], + ) const walletClient: WalletClient = useMemo(() => { return { - enable: async (chainIds: string | string[]) => {}, + enable: async () => { + // + }, getAccount: async (chainId: string) => { if (!activeWallet) throw new Error('No active wallet') const chainIdToChain = await decodeChainIdToChain() @@ -66,11 +78,11 @@ export const useWalletClient = () => { isNanoLedger: isLedgerTypeWallet, } }, - getSigner: async (chainId: string) => { + getSigner: async () => { return signer }, } - }, [activeWallet]) + }, [activeChain, activeWallet, isLedgerTypeWallet, signer]) return { walletClient } } diff --git a/apps/extension/src/hooks/wallet/useWallet.ts b/apps/extension/src/hooks/wallet/useWallet.ts index 77a9b0e9..a004c15e 100644 --- a/apps/extension/src/hooks/wallet/useWallet.ts +++ b/apps/extension/src/hooks/wallet/useWallet.ts @@ -1,4 +1,3 @@ -import { makeCosmoshubPath } from '@cosmjs/amino' import { AccountData, DirectSecp256k1HdWallet, OfflineSigner } from '@cosmjs/proto-signing' import { Key, useChainsStore, WALLETTYPE } from '@leapwallet/cosmos-wallet-hooks' import { @@ -462,7 +461,7 @@ export namespace Wallet { const { activeWallet } = useActiveWallet() const password = usePassword() return useCallback( - async (chain?: SupportedChain) => { + async (chain?: SupportedChain, ethWallet?: boolean) => { let _chain = activeChain if (chain && chainInfos[chain]) { _chain = chain @@ -488,12 +487,12 @@ export namespace Wallet { } } else if (activeWallet?.walletType !== WALLETTYPE.LEDGER) { const walletId = activeWallet?.id - const signer = await KeyChain.getSigner( - walletId as string, - password as string, - chainInfos[_chain].addressPrefix, - chainInfos[_chain].bip44.coinType, - ) + const signer = await KeyChain.getSigner(walletId as string, password as string, { + addressPrefix: chainInfos[_chain].addressPrefix, + coinType: chainInfos[_chain].bip44.coinType, + ethWallet, + pubKeyBech32Address: ethWallet, + }) return signer as unknown as OfflineSigner } else { throw new Error('Unable to get signer') diff --git a/apps/extension/src/images/logos/index.ts b/apps/extension/src/images/logos/index.ts index c22f9237..95a0e5b8 100644 --- a/apps/extension/src/images/logos/index.ts +++ b/apps/extension/src/images/logos/index.ts @@ -108,6 +108,7 @@ const ChainLogos: Record = { pryzmtestnet: ChainInfos.pryzmtestnet.chainSymbolImageUrl, thorchain: ChainInfos.thorchain.chainSymbolImageUrl, odin: ChainInfos.odin.chainSymbolImageUrl, + saga: ChainInfos.saga.chainSymbolImageUrl, } export const getChainImage = (name: string) => { diff --git a/apps/extension/src/index.tsx b/apps/extension/src/index.tsx index c0e540fd..e5e165f8 100644 --- a/apps/extension/src/index.tsx +++ b/apps/extension/src/index.tsx @@ -11,7 +11,6 @@ import { initCrypto, initStorage } from '@leapwallet/leap-keychain' import { createSentryConfig } from '@leapwallet/sentry-config/dist/extension' import * as Sentry from '@sentry/react' import { BrowserTracing } from '@sentry/tracing' -import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister' import { QueryClientProvider } from '@tanstack/react-query' import axios from 'axios' import ErrorBoundaryFallback from 'components/error-boundary-fallback' @@ -30,6 +29,7 @@ import browser from 'webextension-polyfill' import App from './App' import { queryClient } from './query-client' +import { isCompassWallet } from './utils/isCompassWallet' import { getStorageAdapter } from './utils/storageAdapter' axios.defaults.headers.common['x-requested-with'] = 'leap-client' @@ -39,7 +39,7 @@ setLeapapiBaseUrl(process.env.LEAP_WALLET_BACKEND_API_URL as string) setNumiaBannerBearer(process.env.NUMIA_BANNER_BEARER ?? '') // setAppName is for tx logging -setAppName(process.env.APP === 'compass' ? APP_NAME.Compass : APP_NAME.Cosmos) +setAppName(isCompassWallet() ? APP_NAME.Compass : APP_NAME.Cosmos) const storageAdapter = getStorageAdapter() setStorageLayer(storageAdapter) @@ -67,34 +67,33 @@ initCrypto() // }, // }, //}) -if (process.env.SENTRY_DSN) { - Sentry.init( - createSentryConfig({ - dsn: process.env.SENTRY_DSN, - environment: `${process.env.NODE_ENV}`, - ignoreErrors: [ - 'AxiosError: Network Error', - 'AxiosError: Request aborted', - 'AbortError: Aborted', - ], - release: `${browser.runtime.getManifest().version}`, - integrations: [ - new BrowserTracing({ - routingInstrumentation: Sentry.reactRouterV6Instrumentation( - React.useEffect, - useLocation, - useNavigationType, - createRoutesFromChildren, - matchRoutes, - ), - }), - ], - sampleRate: 0.3, - tracesSampleRate: 0.1, - enabled: process.env.NODE_ENV === 'production', - }), - ) -} + +Sentry.init( + createSentryConfig({ + dsn: process.env.SENTRY_DSN, + environment: `${process.env.NODE_ENV}`, + ignoreErrors: [ + 'AxiosError: Network Error', + 'AxiosError: Request aborted', + 'AbortError: Aborted', + ], + release: `${browser.runtime.getManifest().version}`, + integrations: [ + new BrowserTracing({ + routingInstrumentation: Sentry.reactRouterV6Instrumentation( + React.useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + ), + }), + ], + sampleRate: 0.3, + tracesSampleRate: 0.1, + enabled: process.env.NODE_ENV === 'production', + }), +) mixpanel.init(process.env.MIXPANEL_TOKEN as string, { ip: false, diff --git a/apps/extension/src/pages/ApproveConnection/ApproveConnection.tsx b/apps/extension/src/pages/ApproveConnection/ApproveConnection.tsx index 6d7719eb..653c1499 100644 --- a/apps/extension/src/pages/ApproveConnection/ApproveConnection.tsx +++ b/apps/extension/src/pages/ApproveConnection/ApproveConnection.tsx @@ -1,4 +1,5 @@ import { Key } from '@leapwallet/cosmos-wallet-hooks' +import { LineType } from '@leapwallet/cosmos-wallet-provider/dist/provider/types' import { SupportedChain } from '@leapwallet/cosmos-wallet-sdk' import { Buttons } from '@leapwallet/leap-ui' import { Header } from 'components/Header' @@ -90,7 +91,12 @@ const ApproveConnection = () => { browser.runtime.sendMessage({ type: 'chain-approval-rejected', - payload: { origin, chainsIds, payloadId: currentApprovalRequest.payloadId }, + payload: { + origin, + chainsIds, + payloadId: currentApprovalRequest.payloadId, + ecosystem: currentApprovalRequest.ecosystem, + }, }) } @@ -110,7 +116,14 @@ const ApproveConnection = () => { async function enableAccessEventHandler( message: { type: string - payload: { origin: string; chainId?: string; validChainIds?: string[]; payloadId: string } + payload: { + origin: string + chainId?: string + validChainIds?: string[] + payloadId: string + ecosystem: LineType + ethMethod: string + } }, // eslint-disable-next-line @typescript-eslint/no-explicit-any sender: any, @@ -137,7 +150,13 @@ const ApproveConnection = () => { } else { await browser.runtime.sendMessage({ type: 'chain-enabled', - payload: { origin, chainsIds: chainIds, payloadId: message.payload.payloadId }, + payload: { + origin, + chainsIds: chainIds, + payloadId: message.payload.payloadId, + ecosystem: message.payload.ecosystem, + ethMethod: message.payload.ethMethod, + }, }) // closeWindow() } @@ -162,7 +181,13 @@ const ApproveConnection = () => { await addToConnections(chainsIds, selectedWallets, currentApprovalRequest.origin) await sendMessage({ type: 'chain-enabled', - payload: { origin, chainsIds, payloadId: currentApprovalRequest.payloadId }, + payload: { + origin, + chainsIds, + payloadId: currentApprovalRequest.payloadId, + ecosystem: currentApprovalRequest.ecosystem, + ethMethod: currentApprovalRequest.ethMethod, + }, }) setApprovalRequests((prev) => prev.slice(1)) setRequestedChains((prev) => prev.slice(1)) diff --git a/apps/extension/src/pages/activity/ActivityLandingPage.tsx b/apps/extension/src/pages/activity/ActivityLandingPage.tsx index 096f637c..b75cd35e 100644 --- a/apps/extension/src/pages/activity/ActivityLandingPage.tsx +++ b/apps/extension/src/pages/activity/ActivityLandingPage.tsx @@ -85,7 +85,7 @@ export function ActivityLandingPage() { 'w-[48px] h-[40px] px-3 bg-[#FFFFFF] dark:bg-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-full', }} imgSrc={chainInfos[activeChain].chainSymbolImageUrl ?? defaultTokenLogo} - onImgClick={isCompassWallet() ? undefined : () => setShowChainSelector(true)} + onImgClick={() => setShowChainSelector(true)} title={'Activity'} topColor={themeColor} /> diff --git a/apps/extension/src/pages/airdrops/AirdropsDetails.tsx b/apps/extension/src/pages/airdrops/AirdropsDetails.tsx index 8414d54a..d84134a2 100644 --- a/apps/extension/src/pages/airdrops/AirdropsDetails.tsx +++ b/apps/extension/src/pages/airdrops/AirdropsDetails.tsx @@ -8,7 +8,6 @@ import { PageName } from 'config/analytics' import { motion } from 'framer-motion' import { usePageView } from 'hooks/analytics/usePageView' import { useActiveChain } from 'hooks/settings/useActiveChain' -import { Images } from 'images' import React, { useEffect, useState } from 'react' import { useLocation, useNavigate } from 'react-router' import { Colors } from 'theme/colors' @@ -51,6 +50,8 @@ export default function AirdropsDetails() { if (!selectedAirdrop) { navigate('/airdrops', { replace: true }) } + + // eslint-disable-next-line react-hooks/exhaustive-deps }, []) return ( diff --git a/apps/extension/src/pages/airdrops/index.tsx b/apps/extension/src/pages/airdrops/index.tsx index 8c0ec47b..a07a9104 100644 --- a/apps/extension/src/pages/airdrops/index.tsx +++ b/apps/extension/src/pages/airdrops/index.tsx @@ -5,7 +5,6 @@ import { PageName } from 'config/analytics' import { motion } from 'framer-motion' import { usePageView } from 'hooks/analytics/usePageView' import { useActiveChain } from 'hooks/settings/useActiveChain' -import { Images } from 'images' import SideNav from 'pages/home/side-nav' import React, { useState } from 'react' import { Colors } from 'theme/colors' diff --git a/apps/extension/src/pages/asset-details/components/chart-details/index.tsx b/apps/extension/src/pages/asset-details/components/chart-details/index.tsx index f45ab09c..1451f304 100644 --- a/apps/extension/src/pages/asset-details/components/chart-details/index.tsx +++ b/apps/extension/src/pages/asset-details/components/chart-details/index.tsx @@ -6,6 +6,7 @@ import { sliceWord, Token, useAssetDetails, + useERC20Tokens, useFeatureFlags, useformatCurrency, useUserPreferredCurrency, @@ -27,14 +28,12 @@ import useGetTopCGTokens from 'hooks/explore/useGetTopCGTokens' import { useChainInfos } from 'hooks/useChainInfos' import useQuery from 'hooks/useQuery' import { useDefaultTokenLogo } from 'hooks/utility/useDefaultTokenLogo' -import { Images } from 'images' import SelectChain from 'pages/home/SelectChain' import React, { useMemo, useState } from 'react' import Skeleton from 'react-loading-skeleton' import { useLocation, useNavigate } from 'react-router' import { Colors } from 'theme/colors' import { imgOnError } from 'utils/imgOnError' -import { isCompassWallet } from 'utils/isCompassWallet' import { capitalize } from 'utils/strings' import ChartSkeleton from '../chart-skeleton/ChartSkeleton' @@ -48,6 +47,7 @@ type TokenCTAsProps = { onStakeClick: () => void onBuyClick: () => void isBuyDisabled: boolean + isSendDisabled: boolean } function TokenCTAs({ @@ -56,6 +56,7 @@ function TokenCTAs({ onStakeClick, isSwapDisabled, isBuyDisabled, + isSendDisabled, onBuyClick, }: TokenCTAsProps) { return ( @@ -65,6 +66,7 @@ function TokenCTAs({ disabled={isBuyDisabled} onClick={onBuyClick} /> + - + + setShowChainSelector(true)} + onImgClick={() => setShowChainSelector(true)} title={Asset details} topColor={Colors.getChainColor(activeChain)} /> @@ -395,6 +403,9 @@ function TokensDetails() { onSendClick={() => { navigate('/send', { state }) }} + isSendDisabled={ + activeChain === 'seiDevnet' && !!erc20Tokens[denomInfo?.coinMinimalDenom ?? ''] + } onReceiveClick={() => { setShowReceiveSheet(true) }} diff --git a/apps/extension/src/pages/asset-details/index.tsx b/apps/extension/src/pages/asset-details/index.tsx index 234c42d5..d21430a8 100644 --- a/apps/extension/src/pages/asset-details/index.tsx +++ b/apps/extension/src/pages/asset-details/index.tsx @@ -41,7 +41,6 @@ import { import { Colors } from 'theme/colors' import { Token } from 'types/bank' import { imgOnError } from 'utils/imgOnError' -import { isCompassWallet } from 'utils/isCompassWallet' import { capitalize, formatTokenAmount, trim } from 'utils/strings' export default function AssetDetails() { @@ -112,7 +111,7 @@ export default function AssetDetails() { type: HeaderActionType.BACK, }} imgSrc={activeChainInfo.chainSymbolImageUrl ?? defaultTokenLogo} - onImgClick={isCompassWallet() ? undefined : () => setShowChainSelector(true)} + onImgClick={() => setShowChainSelector(true)} title={Asset details} topColor={Colors.getChainColor(activeChain)} /> diff --git a/apps/extension/src/pages/governance/Proposals.tsx b/apps/extension/src/pages/governance/Proposals.tsx index 389d11fc..543eb794 100644 --- a/apps/extension/src/pages/governance/Proposals.tsx +++ b/apps/extension/src/pages/governance/Proposals.tsx @@ -1,5 +1,5 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { - Proposal, useActiveChain, useGetChains, useGetNtrnProposals, @@ -11,7 +11,7 @@ import { ComingSoon } from 'components/coming-soon' import { PageName } from 'config/analytics' import { usePageView } from 'hooks/analytics/usePageView' import { usePerformanceMonitor } from 'hooks/perf-monitoring/usePerformanceMonitor' -import React, { useEffect, useMemo, useState } from 'react' +import React, { useMemo, useState } from 'react' import { NtrnProposalDetails, NtrnProposalList, NtrnProposalStatus } from './neutron' import ProposalDetails from './ProposalDetails' @@ -50,7 +50,6 @@ function GeneralProposals() { function NeutronProposals() { const [selectedProposal, setSelectedProposal] = useState() - const [allProposalsList, setAllProposalsList] = useState([]) const { data: proposalsList, status, shouldPreferFallback } = useGetNtrnProposals() usePerformanceMonitor({ diff --git a/apps/extension/src/pages/home/AddFromChainStore.tsx b/apps/extension/src/pages/home/AddFromChainStore.tsx index c8d003f2..50891788 100644 --- a/apps/extension/src/pages/home/AddFromChainStore.tsx +++ b/apps/extension/src/pages/home/AddFromChainStore.tsx @@ -24,6 +24,7 @@ import browser from 'webextension-polyfill' type AddFromChainStoreProps = { readonly isVisible: boolean readonly onClose: VoidFunction + // eslint-disable-next-line @typescript-eslint/no-explicit-any newAddChain: any } diff --git a/apps/extension/src/pages/home/AssetCard.tsx b/apps/extension/src/pages/home/AssetCard.tsx index 4bffad49..1b12970c 100644 --- a/apps/extension/src/pages/home/AssetCard.tsx +++ b/apps/extension/src/pages/home/AssetCard.tsx @@ -1,14 +1,14 @@ -import { isTerraClassic } from '@leapwallet/cosmos-wallet-hooks' +import { isTerraClassic, Token } from '@leapwallet/cosmos-wallet-hooks' import { CardDivider } from '@leapwallet/leap-ui' import { TokenCard } from 'components/token-card/TokenCard' import React from 'react' import { useNavigate } from 'react-router' -import { Token } from 'types/bank' type AssetCardProps = { isLast: boolean; asset: Token } const AssetCard: React.FC = ({ isLast, asset }) => { - const { symbol, amount, usdValue, img, ibcChainInfo, coinMinimalDenom, name, chain } = asset + const { symbol, amount, usdValue, img, ibcChainInfo, coinMinimalDenom, name, chain, isEvm } = + asset const navigate = useNavigate() let tokenChain = chain?.replace('cosmoshub', 'cosmos') @@ -39,6 +39,8 @@ const AssetCard: React.FC = ({ isLast, asset }) => { isRounded={isLast} onClick={handleCardClick} cardClassName='my-2' + isEvm={isEvm} + hasToShowEvmTag={isEvm} /> {!isLast && } diff --git a/apps/extension/src/pages/home/CopyAddressSheet.tsx b/apps/extension/src/pages/home/CopyAddressSheet.tsx index a36e8f73..5351f8ae 100644 --- a/apps/extension/src/pages/home/CopyAddressSheet.tsx +++ b/apps/extension/src/pages/home/CopyAddressSheet.tsx @@ -1,9 +1,17 @@ -import { sliceAddress, useChainInfo } from '@leapwallet/cosmos-wallet-hooks' +import { + sliceAddress, + useActiveChain, + useChainInfo, + useSeiLinkedAddressState, +} from '@leapwallet/cosmos-wallet-hooks' import { Buttons, CardDivider, GenericCard } from '@leapwallet/leap-ui' import BottomModal from 'components/bottom-modal' +import { ErrorCard } from 'components/ErrorCard' +import { LoaderAnimation } from 'components/loader/Loader' import { useDefaultTokenLogo } from 'hooks/utility/useDefaultTokenLogo' +import { Wallet } from 'hooks/wallet/useWallet' import { Images } from 'images' -import React, { useRef } from 'react' +import React, { useMemo, useRef, useState } from 'react' import { Colors } from 'theme/colors' import { UserClipboard } from 'utils/clipboard' import { imgOnError } from 'utils/imgOnError' @@ -15,11 +23,22 @@ type CopyAddressCardProps = { function CopyAddressCard({ address }: CopyAddressCardProps) { const activeChainInfo = useChainInfo() - const name = - activeChainInfo.key.slice(0, 1).toUpperCase() + activeChainInfo.key.slice(1).toLowerCase() const defaultTokenLogo = useDefaultTokenLogo() const copyAddressRef = useRef(null) + const name = useMemo(() => { + if (address.toLowerCase().startsWith('0x')) { + return '0x address' + } + + return ( + activeChainInfo.addressPrefix.slice(0, 1).toUpperCase() + + activeChainInfo.addressPrefix.slice(1).toLowerCase() + + ' ' + + 'address' + ) + }, [activeChainInfo.addressPrefix, address]) + return ( void + onClose: (refetch?: boolean) => void walletAddresses: string[] } +function AssociateAddressBtn({ onClose }: { onClose: (refetch: boolean) => void }) { + const getWallet = Wallet.useGetWallet() + const activeChain = useActiveChain() + const activeChainInfo = useChainInfo() + const [error, setError] = useState() + const { addressLinkState, updateAddressLinkState } = useSeiLinkedAddressState(getWallet) + + const handleLinkAddressClick = async () => { + await updateAddressLinkState(setError, onClose) + } + + if (['done', 'unknown'].includes(addressLinkState)) return null + const btnText = + addressLinkState === 'success' ? 'Addresses linked successfully' : 'Link Addresses' + + return ( + <> + {addressLinkState === 'error' ? : null} + + {addressLinkState === 'loading' ? : btnText} + + + ) +} + export function CopyAddressSheet({ isVisible, onClose, walletAddresses }: CopyAddressSheetProps) { + const isSeiAddress = walletAddresses?.[1]?.startsWith('sei') return (
{walletAddresses.map((address, index, array) => { @@ -77,6 +129,7 @@ export function CopyAddressSheet({ isVisible, onClose, walletAddresses }: CopyAd ) })}
+ {isSeiAddress ? : null}
) } diff --git a/apps/extension/src/pages/home/FundBanners.tsx b/apps/extension/src/pages/home/FundBanners.tsx index 51c435e2..a15825d8 100644 --- a/apps/extension/src/pages/home/FundBanners.tsx +++ b/apps/extension/src/pages/home/FundBanners.tsx @@ -2,7 +2,6 @@ import { useActiveWallet, useChainInfo, WALLETTYPE } from '@leapwallet/cosmos-wa import { useHardCodedActions } from 'components/search-modal' import Text from 'components/text' import { ButtonName, ButtonType, EventName } from 'config/analytics' -import { LEDGER_DISABLED_COINTYPES } from 'config/config' import { useAddress } from 'hooks/wallet/useAddress' import { Images } from 'images' import mixpanel from 'mixpanel-browser' diff --git a/apps/extension/src/pages/home/Home.tsx b/apps/extension/src/pages/home/Home.tsx index a4e0c0e5..705ec196 100644 --- a/apps/extension/src/pages/home/Home.tsx +++ b/apps/extension/src/pages/home/Home.tsx @@ -1,7 +1,9 @@ import { SecretToken, useGetAsteroidTokens, + useGetSeiEvmBalance, useGetTokenBalances, + useSeiLinkedAddressState, useSnipDenomsStore, useSnipGetSnip20TokenBalances, WALLETTYPE, @@ -36,6 +38,7 @@ import { useGetWalletAddresses } from 'hooks/useGetWalletAddresses' import useQuery from 'hooks/useQuery' import { useDefaultTokenLogo } from 'hooks/utility/useDefaultTokenLogo' import { useAddress } from 'hooks/wallet/useAddress' +import { Wallet } from 'hooks/wallet/useWallet' import { Images } from 'images' import mixpanel from 'mixpanel-browser' import React, { useMemo, useState } from 'react' @@ -131,24 +134,54 @@ export default function Home() { nativeTokensStatus, cw20TokensStatus, erc20TokensStatus, + refetchBalances, } = useGetTokenBalances() - const { status: asteroidsTokensStatus, data } = useGetAsteroidTokens() + const getWallet = Wallet.useGetWallet() + const { addressLinkState } = useSeiLinkedAddressState(getWallet) + const { data: seiEvmBalance, status: seiEvmStatus } = useGetSeiEvmBalance() + const { status: asteroidsTokensStatus, data: asteroidTokenBalance } = useGetAsteroidTokens() + const allAssets = useMemo(() => { - return [..._allAssets, ...(data?.asteroidTokens ?? [])] - }, [_allAssets, data?.asteroidTokens]) + let allAssets = [..._allAssets, ...(asteroidTokenBalance?.asteroidTokens ?? [])] + + if (!['done', 'unknown'].includes(addressLinkState)) { + allAssets = [...allAssets, ...(seiEvmBalance?.seiEvmBalance ?? [])] + } + + return allAssets + }, [ + _allAssets, + addressLinkState, + asteroidTokenBalance?.asteroidTokens, + seiEvmBalance?.seiEvmBalance, + ]) const totalCurrencyInPreferredFiatValue = useMemo(() => { - return _allAssetsCurrencyInFiat.plus(data?.currencyInFiatValue ?? 0) - }, [_allAssetsCurrencyInFiat, data?.currencyInFiatValue]) + let totalCurrencyInPreferredFiatValue = _allAssetsCurrencyInFiat.plus( + asteroidTokenBalance?.currencyInFiatValue ?? 0, + ) + + if (!['done', 'unknown'].includes(addressLinkState)) { + totalCurrencyInPreferredFiatValue = totalCurrencyInPreferredFiatValue.plus( + seiEvmBalance?.currencyInFiatValue ?? 0, + ) + } + + return totalCurrencyInPreferredFiatValue + }, [ + _allAssetsCurrencyInFiat, + addressLinkState, + asteroidTokenBalance?.currencyInFiatValue, + seiEvmBalance?.currencyInFiatValue, + ]) const activeChain = useActiveChain() const [showCopyAddressSheet, setShowCopyAddressSheet] = useState(false) const { activeWallet } = useActiveWallet() - const chain: ChainInfoProp = chainInfos[activeChain] - const isTestnet = useSelectedNetwork() === 'testnet' + const chain: ChainInfoProp = chainInfos[activeChain] const walletAddresses = useGetWalletAddresses() const walletAvatar = useMemo(() => { @@ -170,7 +203,8 @@ export default function Home() { s3IbcTokensStatus !== 'success' && nonS3IbcTokensStatus !== 'success' && nativeTokensStatus !== 'success' && - asteroidsTokensStatus !== 'success' + asteroidsTokensStatus !== 'success' && + seiEvmStatus !== 'success' ? 'loading' : '' @@ -180,7 +214,8 @@ export default function Home() { s3IbcTokensStatus === 'success' && nonS3IbcTokensStatus === 'success' && nativeTokensStatus === 'success' && - asteroidsTokensStatus === 'success' + asteroidsTokensStatus === 'success' && + seiEvmStatus === 'success' ? 'success' : status @@ -190,7 +225,8 @@ export default function Home() { s3IbcTokensStatus === 'error' && nonS3IbcTokensStatus === 'error' && nativeTokensStatus === 'error' && - asteroidsTokensStatus === 'error' + asteroidsTokensStatus === 'error' && + seiEvmStatus === 'error' ? 'error' : status @@ -202,6 +238,7 @@ export default function Home() { nativeTokensStatus, nonS3IbcTokensStatus, s3IbcTokensStatus, + seiEvmStatus, ]) usePerformanceMonitor({ @@ -257,7 +294,8 @@ export default function Home() { s3IbcTokensStatus !== 'success' && nonS3IbcTokensStatus !== 'success' && nativeTokensStatus !== 'success' && - asteroidsTokensStatus !== 'success' + asteroidsTokensStatus !== 'success' && + seiEvmStatus !== 'success' const disabled = activeWallet.walletType === WALLETTYPE.LEDGER && @@ -275,7 +313,8 @@ export default function Home() { cw20TokensStatus === 'loading' || s3IbcTokensStatus === 'loading' || nonS3IbcTokensStatus === 'loading' || - asteroidsTokensStatus === 'loading' + asteroidsTokensStatus === 'loading' || + seiEvmStatus === 'loading' const activeChainInfo = chainInfos[activeChain] @@ -330,7 +369,7 @@ export default function Home() { 'w-[48px] h-[40px] px-3 bg-[#FFFFFF] dark:bg-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-full', }} imgSrc={activeChainInfo.chainSymbolImageUrl ?? defaultTokenLogo} - onImgClick={isCompassWallet() ? undefined : () => setShowChainSelector(true)} + onImgClick={() => setShowChainSelector(true)} title={ : null} {asteroidsTokensStatus !== 'success' ? : null} + {seiEvmStatus !== 'success' ? : null} {!atLeastOneTokenIsLoading && cw20TokensStatus === 'success' && (
setShowCopyAddressSheet(false)} + onClose={(refetch?: boolean) => { + setShowCopyAddressSheet(false) + if (refetch) { + refetchBalances() + } + }} walletAddresses={walletAddresses} /> {!connectEVMLedger ? ( diff --git a/apps/extension/src/pages/home/SelectChain.tsx b/apps/extension/src/pages/home/SelectChain.tsx index f329e9f5..b3dacb41 100644 --- a/apps/extension/src/pages/home/SelectChain.tsx +++ b/apps/extension/src/pages/home/SelectChain.tsx @@ -67,7 +67,10 @@ export function ListChains({ preferenceOrder: 100 + index, })) - const showChains = searchedChain?.length > 0 ? [...chains, ..._customChains] : chains + const showChains = useMemo( + () => (searchedChain?.length > 0 ? [...chains, ..._customChains] : chains), + [_customChains, chains, searchedChain?.length], + ) const filteredChains = useMemo(() => { return showChains @@ -233,7 +236,7 @@ export default function SelectChain({ isVisible, onClose }: ChainSelectorProps) if (views.length === 0) navigate('/add-chain', { replace: true }) else window.open(extension.runtime.getURL('index.html#/add-chain')) trackCTAEvent(ButtonName.ADD_NEW_CHAIN, '/add-chain') - }, []) + }, [navigate]) return ( = ({ themeColor }) => { const [showTokenSelectSheet, setShowTokenSelectSheet] = useState(false) const [isMaxClicked, setIsMaxClicked] = useState(false) + // eslint-disable-next-line @typescript-eslint/no-explicit-any const [assetChain, setAssetChain] = useState(null) + const getWallet = Wallet.useGetWallet() + const { addressLinkState } = useSeiLinkedAddressState(getWallet) + const { data: seiEvmBalance, status: seiEvmStatus } = useGetSeiEvmBalance() const { allAssets, nativeTokensStatus, s3IbcTokensStatus } = useGetTokenBalances() const { snip20Tokens } = useSnipGetSnip20TokenBalances() @@ -69,11 +78,14 @@ export const AmountCard: React.FC = ({ themeColor }) => { fee, feeDenom, sameChain, + setFeeDenom, + addressWarning, transferData, pfmEnabled, setPfmEnabled, setSelectedAddress, isIbcUnwindingDisabled, + setGasError, } = useSendContext() const gasAdjustment = useGasAdjustmentForChain() @@ -87,41 +99,100 @@ export const AmountCard: React.FC = ({ themeColor }) => { ) const assets = useMemo(() => { + let _assets = allAssets + if (snip20Tokens && isSecretChainTargetAddress) { - return allAssets.concat(snip20Tokens) + _assets = [..._assets, ...snip20Tokens] + } + + if (!['done', 'unknown'].includes(addressLinkState)) { + _assets = [..._assets, ...(seiEvmBalance?.seiEvmBalance ?? [])].filter((token) => + new BigNumber(token.amount).gt(0), + ) + } + + return _assets + }, [ + addressLinkState, + allAssets, + isSecretChainTargetAddress, + seiEvmBalance?.seiEvmBalance, + snip20Tokens, + ]) + + const isTokenStatusSuccess = useMemo(() => { + let status = nativeTokensStatus === 'success' && s3IbcTokensStatus === 'success' + + if (!['done', 'unknown'].includes(addressLinkState)) { + status = status && seiEvmStatus === 'success' } - return allAssets - }, [allAssets, isSecretChainTargetAddress, snip20Tokens]) + + return status + }, [addressLinkState, nativeTokensStatus, s3IbcTokensStatus, seiEvmStatus]) + + const updateSelectedToken = useCallback( + (token) => { + setSelectedToken(token) + + if (token?.isEvm) { + setFeeDenom({ + coinMinimalDenom: token.coinMinimalDenom, + coinDecimals: token.coinDecimals ?? 6, + coinDenom: token.symbol, + icon: token.img, + coinGeckoId: token.coinGeckoId ?? '', + chain: token.chain ?? '', + }) + } + }, + [setFeeDenom, setSelectedToken], + ) useEffect(() => { if (!selectedToken && !assetCoinDenom) { if (locationState && (locationState as Token).coinMinimalDenom) { - setSelectedToken(locationState as Token) + const token = locationState as Token + updateSelectedToken(token) } else if (assets.length > 0) { const tokensWithBalance = assets.filter((token) => new BigNumber(token.amount).gt(0)) - setSelectedToken(tokensWithBalance[0]) + const token = tokensWithBalance[0] as Token + updateSelectedToken(token) } } }, [ assets, - s3IbcTokensStatus, locationState, - nativeTokensStatus, + isTokenStatusSuccess, selectedToken, - setSelectedToken, + updateSelectedToken, + assetCoinDenom, ]) + useEffect(() => { + if (addressLinkState === 'done' && selectedToken && selectedToken?.isEvm) { + const tokensWithBalance = assets.filter((token) => new BigNumber(token.amount).gt(0)) + const token = tokensWithBalance[0] as Token + updateSelectedToken(token) + } + }, [addressLinkState, assets, selectedToken, updateSelectedToken]) + useEffect(() => { if (assetCoinDenom) { const tokenFromParams: Token | null = assets.find((asset) => asset.ibcDenom === assetCoinDenom) || assets.find((asset) => asset.coinMinimalDenom === assetCoinDenom) || null - setSelectedToken(tokenFromParams) + + updateSelectedToken(tokenFromParams) } - }, [assetCoinDenom, activeChain]) + }, [assetCoinDenom, activeChain, assets, updateSelectedToken]) useEffect(() => { + if (addressWarning) { + setInputAmount('0.0001') + return + } + const isNativeToken = !!chainInfos[activeChain].nativeDenoms[selectedToken?.coinMinimalDenom ?? ''] @@ -172,7 +243,11 @@ export const AmountCard: React.FC = ({ themeColor }) => { return 'Insufficient balance' } - const feeDenomValue = allAssets.find((asset) => { + const feeDenomValue = assets.find((asset) => { + if (selectedToken?.isEvm && asset?.isEvm) { + return asset.coinMinimalDenom === feeDenom?.coinMinimalDenom + } + return ( asset.ibcDenom === feeDenom.coinMinimalDenom || asset.coinMinimalDenom === feeDenom.coinMinimalDenom @@ -199,7 +274,7 @@ export const AmountCard: React.FC = ({ themeColor }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [ - allAssets, + assets, fee?.amount, feeDenom?.coinMinimalDenom, inputAmount, @@ -229,10 +304,20 @@ export const AmountCard: React.FC = ({ themeColor }) => { setAssetChain(null) setPfmEnabled(true) } + return () => { setPfmEnabled(true) } - }, [skipSupportedChains, transferData]) + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + skipSupportedChains, + transferData?.isSkipTransfer, + // @ts-ignore + transferData?.routeResponse, + // @ts-ignore + transferData?.messages, + ]) // getting the wallet address from the assets for auto fill const wallet = useActiveWallet().activeWallet @@ -255,12 +340,18 @@ export const AmountCard: React.FC = ({ themeColor }) => { } return ( - +
Amount to Send - {nativeTokensStatus === 'success' ? ( + + {isTokenStatusSuccess ? ( selectedToken && (
- {nativeTokensStatus === 'success' ? ( + {isTokenStatusSuccess ? ( <> {selectedToken ? ( <> @@ -374,7 +465,8 @@ export const AmountCard: React.FC = ({ themeColor }) => { setShowTokenSelectSheet(false) }} onTokenSelect={(token) => { - setSelectedToken(token) + updateSelectedToken(token) + setGasError('') setInputAmount('') setIsMaxClicked(false) inputRef.current?.focus() diff --git a/apps/extension/src/pages/send-v2/components/amount-card/select-token-sheet.tsx b/apps/extension/src/pages/send-v2/components/amount-card/select-token-sheet.tsx index 79c66841..5fc2c251 100644 --- a/apps/extension/src/pages/send-v2/components/amount-card/select-token-sheet.tsx +++ b/apps/extension/src/pages/send-v2/components/amount-card/select-token-sheet.tsx @@ -4,6 +4,7 @@ import { useAutoFetchedCW20Tokens, useChainInfo, useDenoms, + useERC20Tokens, } from '@leapwallet/cosmos-wallet-hooks' import { CardDivider } from '@leapwallet/leap-ui' import BottomModal from 'components/bottom-modal' @@ -30,16 +31,18 @@ export const SelectTokenSheet: React.FC = ({ onTokenSelect, }) => { const activeChain = useActiveChain() - const [searchQuery, setSearchQuery] = useState('') + const erc20Tokens = useERC20Tokens() + const activeChainInfo = useChainInfo() const denoms = useDenoms() + const [searchQuery, setSearchQuery] = useState('') const autoFetchedCW20Tokens = useAutoFetchedCW20Tokens() + const combinedDenoms = useMemo(() => { return { ...denoms, ...autoFetchedCW20Tokens, } }, [denoms, autoFetchedCW20Tokens]) - const activeChainInfo = useChainInfo() const _assets = useMemo(() => { return assets.filter((token) => { @@ -55,9 +58,13 @@ export const SelectTokenSheet: React.FC = ({ const transferableTokens = useMemo(() => { return _assets.filter((asset) => { + if (activeChainInfo.key === 'seiDevnet' && erc20Tokens[asset.coinMinimalDenom]) { + return false + } + return asset.symbol.toLowerCase().includes(searchQuery.trim().toLowerCase()) }) - }, [_assets, searchQuery]) + }, [_assets, activeChainInfo.key, erc20Tokens, searchQuery]) const handleSelectToken = (token: Token) => { onTokenSelect(token) @@ -87,6 +94,9 @@ export const SelectTokenSheet: React.FC = ({ if (selectedToken?.ibcDenom || asset.ibcDenom) { isSelected = selectedToken?.ibcDenom === asset.ibcDenom } + if (selectedToken?.isEvm || asset?.isEvm) { + isSelected = isSelected && selectedToken?.isEvm === asset?.isEvm + } return ( @@ -104,6 +114,8 @@ export const SelectTokenSheet: React.FC = ({ isRounded={isFirst || isLast} size='md' usdValue={asset.usdValue} + isEvm={asset?.isEvm} + hasToShowEvmTag={asset?.isEvm} />
{!isLast && } diff --git a/apps/extension/src/pages/send-v2/components/fees-view/index.tsx b/apps/extension/src/pages/send-v2/components/fees-view/index.tsx index 17cfac7c..7d0a9299 100644 --- a/apps/extension/src/pages/send-v2/components/fees-view/index.tsx +++ b/apps/extension/src/pages/send-v2/components/fees-view/index.tsx @@ -17,10 +17,13 @@ export const FeesView: React.FC = () => { gasOption, setGasOption, setFeeDenom, + selectedToken, + gasError, + setGasError, + addressWarning, } = useSendContext() const defaultGasPrice = useDefaultGasPrice() - const [gasError, setGasError] = useState(null) const [gasPriceOption, setGasPriceOption] = useState({ option: gasOption, gasPrice: userPreferredGasPrice ?? defaultGasPrice.gasPrice, @@ -63,11 +66,13 @@ export const FeesView: React.FC = () => { onGasPriceOptionChange={handleGasPriceOptionChange} error={gasError} setError={setGasError} + isSelectedTokenEvm={selectedToken?.isEvm} > - + {addressWarning ? null : } {gasError && !showFeesSettingSheet ? (

{gasError}

) : null} + { const [showCEXSheet, setShowCEXSheet] = useState(false) - const { memo, setMemo, selectedAddress, selectedToken, isIBCTransfer } = useSendContext() + const { memo, setMemo, selectedAddress, selectedToken, isIBCTransfer, addressWarning } = + useSendContext() + const selectedAddressNativeDenoms: string[] = Object.keys( ChainInfos[selectedAddress?.chainName as SupportedChain]?.nativeDenoms ?? {}, ) @@ -21,7 +24,11 @@ export const Memo: React.FC = () => { return ( <> -
+

Memo

void } -export const IBCSettings: React.FC = ({ - className, - targetChain, - onSelectChannel, -}) => { +export const IBCSettings: React.FC = ({ targetChain, onSelectChannel }) => { const [isSettingsOpen, setIsSettingsOpen] = useState(false) // this will be null when data is loading const [defaultChannelId, setDefaultChannelId] = useState(null) @@ -151,7 +147,9 @@ export const IBCSettings: React.FC = ({ ) const path: string[] = [] + // eslint-disable-next-line @typescript-eslint/no-explicit-any Object.values(groupedTransactions)?.forEach((d: any[], index1: number) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any d.forEach((f: any, index2: number) => { if (index1 == 0 && index2 == 0) { path.push(f.sourceChain) diff --git a/apps/extension/src/pages/send-v2/components/recipient-card/index.tsx b/apps/extension/src/pages/send-v2/components/recipient-card/index.tsx index f1f95ca6..7c8e74f1 100644 --- a/apps/extension/src/pages/send-v2/components/recipient-card/index.tsx +++ b/apps/extension/src/pages/send-v2/components/recipient-card/index.tsx @@ -7,6 +7,7 @@ import { useAddressPrefixes, useChainsStore, useFeatureFlags, + useSeiLinkedAddressState, WALLETTYPE, } from '@leapwallet/cosmos-wallet-hooks' import { @@ -16,15 +17,16 @@ import { SupportedChain, } from '@leapwallet/cosmos-wallet-sdk' import { Asset, useChains, useSkipDestinationChains } from '@leapwallet/elements-hooks' +import { captureException } from '@sentry/react' import bech32 from 'bech32' import { ActionInputWithPreview } from 'components/action-input-with-preview' import Text from 'components/text' -import { LEDGER_DISABLED_COINTYPES } from 'config/config' import { motion } from 'framer-motion' import { useManageChainData } from 'hooks/settings/useManageChains' import { useSelectedNetwork } from 'hooks/settings/useNetwork' import { useContactsSearch } from 'hooks/useContacts' import { useDefaultTokenLogo } from 'hooks/utility/useDefaultTokenLogo' +import { Wallet } from 'hooks/wallet/useWallet' import { Images } from 'images' import { useSendContext } from 'pages/send-v2/context' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' @@ -56,6 +58,8 @@ export const RecipientCard: React.FC = ({ themeColor }) => { const [isAddContactSheetVisible, setIsAddContactSheetVisible] = useState(false) const [recipientInputValue, setRecipientInputValue] = useState('') + const getWallet = Wallet.useGetWallet() + const { addressLinkState } = useSeiLinkedAddressState(getWallet) const { ethAddress, selectedAddress, @@ -67,6 +71,8 @@ export const RecipientCard: React.FC = ({ themeColor }) => { setCustomIbcChannelId, setEthAddress, selectedToken, + addressWarning, + setAddressWarning, isIbcUnwindingDisabled, } = useSendContext() @@ -96,7 +102,11 @@ export const RecipientCard: React.FC = ({ themeColor }) => { const fillRecipientInputValue = useCallback( (value: string) => { - if ( + if (activeChainInfo.key === 'seiDevnet' && value.toLowerCase().startsWith('0x')) { + setAddressError(undefined) + setEthAddress(value) + setRecipientInputValue(value) + } else if ( Number(activeChainInfo.bip44.coinType) === 60 && value.toLowerCase().startsWith('0x') && activeChainInfo.key !== 'injective' @@ -107,7 +117,7 @@ export const RecipientCard: React.FC = ({ themeColor }) => { setEthAddress(value) setRecipientInputValue(bech32Address) return - } catch (_) { + } catch (e) { setAddressError('Invalid Address') } } @@ -327,16 +337,48 @@ export const RecipientCard: React.FC = ({ themeColor }) => { useEffect(() => { if (currentWalletAddress === recipientInputValue) { setAddressError('Cannot send to self') + } else if (activeChainInfo.key === 'seiDevnet' && recipientInputValue.length) { + if (selectedToken?.isEvm && !recipientInputValue.toLowerCase().startsWith('0x')) { + setAddressWarning( + 'To send Sei EVM tokens to Sei address, link your 0x and Sei addresses first.', + ) + return + } + + if (recipientInputValue.toLowerCase().startsWith('0x')) { + if ( + !selectedToken?.isEvm && + selectedToken?.coinMinimalDenom === 'usei' && + !['done', 'unknown'].includes(addressLinkState) + ) { + setAddressWarning( + 'To send Sei tokens to 0x address, link your 0x and Sei addresses first.', + ) + } + + return + } } else if ( recipientInputValue && !isValidAddress(recipientInputValue) && !showNameServiceResults ) { - setAddressError('Invalid address') + setAddressError('Invalid Address') } else { + setAddressWarning(undefined) setAddressError(undefined) } - }, [currentWalletAddress, recipientInputValue, setAddressError, showNameServiceResults]) + }, [ + activeChainInfo.key, + addressLinkState, + currentWalletAddress, + recipientInputValue, + selectedToken?.coinMinimalDenom, + selectedToken?.isEvm, + setAddressError, + setAddressWarning, + showNameServiceResults, + ]) useEffect(() => { // Autofill of recipientInputValue if passed in information @@ -356,6 +398,20 @@ export const RecipientCard: React.FC = ({ themeColor }) => { } try { + if (activeChainInfo.key === 'seiDevnet' && cleanInputValue.toLowerCase().startsWith('0x')) { + const img = activeChainInfo.chainSymbolImageUrl ?? '' + setSelectedAddress({ + ethAddress: cleanInputValue, + address: cleanInputValue, + name: sliceAddress(cleanInputValue), + avatarIcon: activeChainInfo.chainSymbolImageUrl ?? '', + emoji: undefined, + chainIcon: img ?? '', + chainName: activeChainInfo.key, + selectionType: 'notSaved', + }) + return + } const { prefix } = bech32.decode(cleanInputValue) const _chain = addressPrefixes[prefix] as SupportedChain const img = chains[_chain]?.chainSymbolImageUrl ?? defaultTokenLogo @@ -371,7 +427,7 @@ export const RecipientCard: React.FC = ({ themeColor }) => { selectionType: 'notSaved', }) } catch (err) { - // + captureException(err) } // eslint-disable-next-line react-hooks/exhaustive-deps @@ -441,7 +497,9 @@ export const RecipientCard: React.FC = ({ themeColor }) => { useEffect(() => { let destinationChain: string | undefined - + if (selectedAddress?.chainName === 'seiDevnet' && selectedAddress.address?.startsWith('0x')) { + return + } if (selectedAddress?.address) { const destChainAddrPrefix = getBlockChainFromAddress(selectedAddress.address) @@ -609,6 +667,7 @@ export const RecipientCard: React.FC = ({ themeColor }) => { = ({ themeColor }) => { ) : null} + {addressWarning ? ( + + {addressWarning} + + ) : null} + {showNameServiceResults ? ( = ({ const defaultTokenLogo = useDefaultTokenLogo() const activeWallet = useActiveWallet() + // eslint-disable-next-line @typescript-eslint/no-explicit-any const _displaySkipAccounts: any[][] = [] Object.keys(chainInfos).map((chain) => { if (skipSupportedDestinationChainsIDs?.includes(chainInfos[chain as SupportedChain]?.chainId)) { @@ -88,6 +89,8 @@ export const MyWalletSheet: React.FC = ({ selectionType: 'currentWallet', }) } + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [toChainId, displayAccounts?.length > 0, activeChain]) return ( diff --git a/apps/extension/src/pages/send-v2/components/recipient-card/secondary-action-button.tsx b/apps/extension/src/pages/send-v2/components/recipient-card/secondary-action-button.tsx index d67e519d..b8844619 100644 --- a/apps/extension/src/pages/send-v2/components/recipient-card/secondary-action-button.tsx +++ b/apps/extension/src/pages/send-v2/components/recipient-card/secondary-action-button.tsx @@ -8,10 +8,10 @@ type SecondaryActionButtonProps = { className?: string iconClassName?: string actionLabel: string + // eslint-disable-next-line @typescript-eslint/no-explicit-any } & React.PropsWithChildren export const SecondaryActionButton: React.FC = ({ - actionLabel, leftIcon, onClick, children, diff --git a/apps/extension/src/pages/send-v2/components/reivew-transfer/executeSkipTx.tsx b/apps/extension/src/pages/send-v2/components/reivew-transfer/executeSkipTx.tsx index 53249925..febefd81 100644 --- a/apps/extension/src/pages/send-v2/components/reivew-transfer/executeSkipTx.tsx +++ b/apps/extension/src/pages/send-v2/components/reivew-transfer/executeSkipTx.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { coin } from '@cosmjs/proto-signing' import { TxClient as InjectiveTxClient } from '@injectivelabs/sdk-ts' import { @@ -214,7 +215,7 @@ export const useExecuteSkipTx = () => { try { const submitResponse = await tx.submitTx( String(messageChain.chainId), - txBytesString! as string, + txBytesString as string, ) success = submitResponse.success if (!success) throw new Error('SubmitTx Failed') @@ -222,7 +223,7 @@ export const useExecuteSkipTx = () => { } catch (e: any) { if (e.message === 'SubmitTx Failed') { const { transactionHash, code, codespace } = await tx.broadcastTx( - txBytesString! as string, + txBytesString as string, ) txHash = transactionHash if (code !== 0) { @@ -305,6 +306,7 @@ export const useExecuteSkipTx = () => { feeQuantity: fee?.amount[0]?.amount, }) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore setPendingTx(pendingTx) diff --git a/apps/extension/src/pages/send-v2/components/reivew-transfer/index.tsx b/apps/extension/src/pages/send-v2/components/reivew-transfer/index.tsx index f74b623e..452a7e20 100644 --- a/apps/extension/src/pages/send-v2/components/reivew-transfer/index.tsx +++ b/apps/extension/src/pages/send-v2/components/reivew-transfer/index.tsx @@ -1,21 +1,25 @@ +/* eslint-disable @typescript-eslint/ban-ts-comment */ import { useActiveChain, useAddress, useChainsStore, useFeatureFlags, + useSeiLinkedAddressState, } from '@leapwallet/cosmos-wallet-hooks' import { Asset } from '@leapwallet/elements-core' import { useChains, useDebouncedValue, useTransfer } from '@leapwallet/elements-hooks' import { useTransferReturnType } from '@leapwallet/elements-hooks/dist/use-transfer' import { Buttons } from '@leapwallet/leap-ui' import { AutoAdjustAmountSheet } from 'components/auto-adjust-amount-sheet' -import BottomModal from 'components/bottom-modal' +import { LoaderAnimation } from 'components/loader/Loader' import { FIXED_FEE_CHAINS } from 'config/constants' import { useSelectedNetwork } from 'hooks/settings/useNetwork' import { useEffectiveAmountValue } from 'hooks/useEffectiveAmountValue' import { useWalletClient } from 'hooks/useWalletClient' +import { Wallet } from 'hooks/wallet/useWallet' import { useSendContext } from 'pages/send-v2/context' -import React, { useCallback, useEffect, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { Colors } from 'theme/colors' import { FeesView } from '../fees-view' import { FixedFee } from '../fees-view/FixedFee' @@ -26,6 +30,7 @@ type ReviewTransferProps = { } export const ReviewTransfer: React.FC = ({ themeColor }) => { + const getWallet = Wallet.useGetWallet() const [showReviewTxSheet, setShowReviewTxSheet] = useState(false) const [checkForAutoAdjust, setCheckForAutoAdjust] = useState(false) @@ -36,21 +41,16 @@ export const ReviewTransfer: React.FC = ({ themeColor }) => inputAmount, setInputAmount, selectedToken, + addressWarning, + setAddressWarning, + setGasError, selectedAddress, setTransferData, pfmEnabled, isIbcUnwindingDisabled, isIBCTransfer, - showLedgerPopup, } = useSendContext() - - const showAdjustmentSheet = useCallback(() => { - setCheckForAutoAdjust(true) - }, []) - - const hideAdjustmentSheet = useCallback(() => { - setCheckForAutoAdjust(false) - }, []) + const { addressLinkState, updateAddressLinkState } = useSeiLinkedAddressState(getWallet) const { chains } = useChainsStore() const userAddress = useAddress() @@ -90,7 +90,7 @@ export const ReviewTransfer: React.FC = ({ themeColor }) => useEffect(() => { //@ts-ignore - if (transferData && transferData.messages) { + if (transferData?.messages) { setTransferData(transferData) } else { setTransferData({ @@ -100,37 +100,69 @@ export const ReviewTransfer: React.FC = ({ themeColor }) => gasFeesError: undefined, }) } + + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ selectedToken?.coinMinimalDenom, selectedAddress?.chainName, // @ts-ignore - transferData?.isLoadingMessages, - // @ts-ignore - transferData?.isLoadingRoute, - // @ts-ignore - transferData?.isLoadingSkipGasFee, - transferData?.isSkipTransfer, - //@ts-ignore transferData?.messages, - //@ts-ignore - transferData?.routeResponse, ]) + const btnText = useMemo(() => { + if (addressWarning && addressLinkState === 'success') { + return 'Addresses linked successfully' + } + + if (addressWarning && !['done', 'unknown'].includes(addressLinkState)) { + return 'Link Addresses' + } + + return 'Review Transfer' + }, [addressLinkState, addressWarning]) + + const handleLinkAddressClick = async () => { + await updateAddressLinkState(setGasError) + setAddressWarning('') + } + + const showAdjustmentSheet = useCallback(() => { + setCheckForAutoAdjust(true) + }, []) + + const hideAdjustmentSheet = useCallback(() => { + setCheckForAutoAdjust(false) + }, []) + + const isReviewDisabled = addressWarning + ? ['loading', 'success'].includes(addressLinkState) + : sendDisabled || (!pfmEnabled && !isIbcUnwindingDisabled) + return ( <>

{FIXED_FEE_CHAINS.includes(activeChain) ? : } + - Review Transfer + {addressWarning && addressLinkState === 'loading' ? ( + + ) : ( + btnText + )}
+ {selectedToken && fee && checkForAutoAdjust ? ( = ({ const [formatCurrency] = useformatCurrency() const defaultTokenLogo = useDefaultTokenLogo() const activeChain = useActiveChain() + const getWallet = Wallet.useGetWallet() const { memo, @@ -55,6 +60,7 @@ export const ReviewTransferSheet: React.FC = ({ isIBCTransfer, sendDisabled, confirmSend, + confirmSendEth, clearTxError, userPreferredGasPrice, userPreferredGasLimit, @@ -109,11 +115,22 @@ export const ReviewTransferSheet: React.FC = ({ if (!fee || !selectedAddress?.address) { return } + try { - // If skiptranfer is supported and ibc unwinding in not disabled and it is ibc transfer - // we use Skip API for transfer or - // else we use default Cosmos API - if (transferData?.isSkipTransfer && !isIbcUnwindingDisabled && isIBCTransfer) { + if (isEthAddress(selectedAddress.address) && activeChain === 'seiDevnet') { + const wallet = await getWallet(ChainInfos.seiDevnet.key, true) + + await confirmSendEth( + selectedAddress.address, + inputAmount, + userPreferredGasLimit ?? gasEstimate, + wallet as unknown as EthWallet, + ) + } else if (transferData?.isSkipTransfer && !isIbcUnwindingDisabled && isIBCTransfer) { + // If skiptranfer is supported and ibc unwinding in not disabled and it is ibc transfer + // we use Skip API for transfer or + // else we use default Cosmos API + confirmSkipTx() } else { await confirmSend({ @@ -124,24 +141,26 @@ export const ReviewTransferSheet: React.FC = ({ fees: fee, }) } - } catch (err: any) { - // + } catch (err: unknown) { + captureException(err) } }, [ + activeChain, clearTxError, confirmSend, + confirmSendEth, + confirmSkipTx, fee, + gasEstimate, + getWallet, inputAmount, + isIBCTransfer, + isIbcUnwindingDisabled, memo, - selectedAddress, + selectedAddress?.address, selectedToken, - isIbcUnwindingDisabled, transferData?.isSkipTransfer, - // @ts-ignore - transferData?.messages, - // @ts-ignore - transferData?.routeResponse, - isIBCTransfer, + userPreferredGasLimit, ]) useCaptureTxError(txError) diff --git a/apps/extension/src/pages/send-v2/context.tsx b/apps/extension/src/pages/send-v2/context.tsx index 35db1ff7..26ab015f 100644 --- a/apps/extension/src/pages/send-v2/context.tsx +++ b/apps/extension/src/pages/send-v2/context.tsx @@ -5,6 +5,7 @@ import { SupportedChain, } from '@leapwallet/cosmos-wallet-sdk' import { useTransferReturnType } from '@leapwallet/elements-hooks/dist/use-transfer' +import { EthWallet } from '@leapwallet/leap-keychain' import { useSecretWallet } from 'hooks/wallet/useScrtWallet' import { Wallet } from 'hooks/wallet/useWallet' import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react' @@ -13,8 +14,8 @@ import { useTxCallBack } from 'utils/txCallback' const useGetWallet = Wallet.useGetWallet -export type SendContextType = Readonly< - { +export type SendContextType = ReturnType & + Readonly<{ confirmSend: ( // eslint-disable-next-line no-unused-vars args: Omit, @@ -28,8 +29,14 @@ export type SendContextType = Readonly< setPfmEnabled: (val: boolean) => void ethAddress: string setEthAddress: React.Dispatch> - } & ReturnType -> + confirmSendEth: ( + toAddress: string, + value: string, + gas: number, + wallet: EthWallet, + gasPrice?: number, + ) => void + }> export const SendContext = createContext(null) @@ -39,7 +46,8 @@ type SendContextProviderProps = { } & React.PropsWithChildren export const SendContextProvider: React.FC = ({ children }) => { - const { tokenFiatValue, feeTokenFiatValue, confirmSend, selectedToken, ...rest } = useSendModule() + const { tokenFiatValue, feeTokenFiatValue, confirmSend, confirmSendEth, selectedToken, ...rest } = + useSendModule() const txCallback = useTxCallBack() const getWallet = useGetWallet() const currentWalletAddress = useAddress() @@ -70,6 +78,16 @@ export const SendContextProvider: React.FC = ({ childr }, [confirmSend, getSscrtWallet, getWallet, selectedToken?.coinMinimalDenom, txCallback], ) + const confirmSendTxEth = useCallback( + async (toAddress: string, value: string, gas: number, wallet: EthWallet, gasPrice?: number) => { + confirmSendEth(toAddress, value, gas, wallet, txCallback, gasPrice) + }, + [confirmSendEth, txCallback], + ) + + useEffect(() => { + setIsIbcUnwindingDisabled(false) + }, [selectedToken, rest?.selectedAddress]) useEffect(() => { setIsIbcUnwindingDisabled(false) @@ -89,6 +107,7 @@ export const SendContextProvider: React.FC = ({ childr feeTokenFiatValue: feeTokenFiatValue ?? '', selectedToken, confirmSend: confirmSendTx, + confirmSendEth: confirmSendTxEth, sameChain, transferData, setTransferData, @@ -100,6 +119,7 @@ export const SendContextProvider: React.FC = ({ childr } as const }, [ confirmSendTx, + confirmSendTxEth, currentWalletAddress, ethAddress, feeTokenFiatValue, @@ -114,6 +134,8 @@ export const SendContextProvider: React.FC = ({ childr setPfmEnabled, ]) + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore return {children} } diff --git a/apps/extension/src/pages/send-v2/index.tsx b/apps/extension/src/pages/send-v2/index.tsx index 5aa551d7..44f22ce2 100644 --- a/apps/extension/src/pages/send-v2/index.tsx +++ b/apps/extension/src/pages/send-v2/index.tsx @@ -12,7 +12,6 @@ import { useThemeColor } from 'hooks/utility/useThemeColor' import SelectChain from 'pages/home/SelectChain' import React, { useEffect, useMemo, useState } from 'react' import { useLocation, useNavigate } from 'react-router' -import { isCompassWallet } from 'utils/isCompassWallet' import { AmountCard } from './components/amount-card' import { Memo } from './components/memo' @@ -45,7 +44,9 @@ const Send = () => { const chainKey = Object.values(chainInfos).find((chain) => chain.chainId === chainId)?.key chainKey && setActiveChain(chainKey) } - }, [chainId]) + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [chainId, chainInfos]) return (
@@ -58,7 +59,7 @@ const Send = () => { type: HeaderActionType.BACK, }} imgSrc={chainImage} - onImgClick={isCompassWallet() ? undefined : () => setShowChainSelector(true)} + onImgClick={() => setShowChainSelector(true)} title={location.pathname === '/ibc' ? 'IBC' : 'Send'} topColor={themeColor} /> diff --git a/apps/extension/src/pages/sign-sei-evm/SignSeiEvmTransaction.tsx b/apps/extension/src/pages/sign-sei-evm/SignSeiEvmTransaction.tsx new file mode 100644 index 00000000..85a186e4 --- /dev/null +++ b/apps/extension/src/pages/sign-sei-evm/SignSeiEvmTransaction.tsx @@ -0,0 +1,103 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + useActiveChain, + useSetActiveChain, + useSetSelectedNetwork, +} from '@leapwallet/cosmos-wallet-hooks' +import { ETHEREUM_METHOD_TYPE } from '@leapwallet/cosmos-wallet-provider/dist/provider/types' +import { + ARCTIC_CHAIN_KEY, + formatEtherUnits, + getErc20TokenDetails, +} from '@leapwallet/cosmos-wallet-sdk' +import { Header } from '@leapwallet/leap-ui' +import PopupLayout from 'components/layout/popup-layout' +import { LoaderAnimation } from 'components/loader/Loader' +import { MessageTypes } from 'config/message-types' +import React, { useCallback, useEffect, useState } from 'react' +import Browser from 'webextension-polyfill' + +import { MessageSignature, SignTransaction, SignTransactionProps } from './components' + +function SeiEvmTransaction({ txnData }: SignTransactionProps) { + switch (txnData.signTxnData.methodType) { + case ETHEREUM_METHOD_TYPE.PERSONAL_SIGN: + case ETHEREUM_METHOD_TYPE.ETH__SIGN: + case ETHEREUM_METHOD_TYPE.ETH__SIGN_TYPED_DATA_V4: + return + } + + return +} + +/** + * This HOC helps makes sure that the txn signing request is decoded and the chain is set + */ +const withSeiEvmTxnSigningRequest = (Component: React.FC) => { + const Wrapped = () => { + const activeChain = useActiveChain() + const setActiveChain = useSetActiveChain() + const setSelectedNetwork = useSetSelectedNetwork() + const [txnData, setTxnData] = useState | null>(null) + + const signSeiEvmTxEventHandler = useCallback( + async (message: any, sender: any) => { + if (sender.id !== Browser.runtime.id) return + + if (message.type === MessageTypes.signTransaction) { + const txnData = message.payload + + if (txnData?.signTxnData?.spendPermissionCapValue) { + const tokenDetails = await getErc20TokenDetails(txnData.signTxnData.to) + + txnData.signTxnData.details = { + Permission: `This allows the third party to spend ${formatEtherUnits( + txnData.signTxnData.spendPermissionCapValue, + tokenDetails.decimals, + )} ${tokenDetails.symbol} from your current balance.`, + + ...txnData.signTxnData.details, + } + } + + setTxnData(txnData) + if (activeChain !== ARCTIC_CHAIN_KEY) { + setActiveChain(ARCTIC_CHAIN_KEY) + setSelectedNetwork('mainnet') + } + } + }, + [activeChain, setActiveChain, setSelectedNetwork], + ) + + useEffect(() => { + Browser.runtime.sendMessage({ type: MessageTypes.signingPopupOpen }) + Browser.runtime.onMessage.addListener(signSeiEvmTxEventHandler) + + return () => { + Browser.runtime.onMessage.removeListener(signSeiEvmTxEventHandler) + } + }, [signSeiEvmTxEventHandler]) + + if (activeChain === ARCTIC_CHAIN_KEY && txnData) { + return + } + + return ( + } + > +
+ +
+
+ ) + } + + Wrapped.displayName = `withSeiEvmTxnSigningRequest(${Component.displayName})` + return Wrapped +} + +const signSeiEvmTx = withSeiEvmTxnSigningRequest(React.memo(SeiEvmTransaction)) +export default signSeiEvmTx diff --git a/apps/extension/src/pages/sign-sei-evm/components/MessageSignature.tsx b/apps/extension/src/pages/sign-sei-evm/components/MessageSignature.tsx new file mode 100644 index 00000000..6410d61f --- /dev/null +++ b/apps/extension/src/pages/sign-sei-evm/components/MessageSignature.tsx @@ -0,0 +1,188 @@ +import { useActiveChain, useActiveWallet, useChainInfo } from '@leapwallet/cosmos-wallet-hooks' +import { ETHEREUM_METHOD_TYPE } from '@leapwallet/cosmos-wallet-provider/dist/provider/types' +import { personalSign, signTypedData } from '@leapwallet/cosmos-wallet-sdk' +import { EthWallet } from '@leapwallet/leap-keychain' +import { Avatar, Buttons, Header } from '@leapwallet/leap-ui' +import assert from 'assert' +import { ErrorCard } from 'components/ErrorCard' +import PopupLayout from 'components/layout/popup-layout' +import { LoaderAnimation } from 'components/loader/Loader' +import { Tabs } from 'components/tabs' +import { MessageTypes } from 'config/message-types' +import { useSiteLogo } from 'hooks/utility/useSiteLogo' +import { Wallet } from 'hooks/wallet/useWallet' +import { Images } from 'images' +import { GenericLight } from 'images/logos' +import React, { useMemo, useState } from 'react' +import { Colors } from 'theme/colors' +import { TransactionStatus } from 'types/utility' +import { formatWalletName } from 'utils/formatWalletName' +import { imgOnError } from 'utils/imgOnError' +import { isCompassWallet } from 'utils/isCompassWallet' +import { trim } from 'utils/strings' +import Browser from 'webextension-polyfill' + +import { handleRejectClick } from '../utils' +import { SignTransactionProps } from './index' + +const useGetWallet = Wallet.useGetWallet + +export type MessageSignatureProps = { + txnData: SignTransactionProps['txnData'] +} + +export function MessageSignature({ txnData }: MessageSignatureProps) { + const chainInfo = useChainInfo() + const activeWallet = useActiveWallet() + const activeChain = useActiveChain() + + assert(activeWallet !== null, 'activeWallet is null') + const walletName = useMemo(() => { + return formatWalletName(activeWallet.name) + }, [activeWallet.name]) + + const siteOrigin = txnData?.origin as string | undefined + const siteName = siteOrigin?.split('//')?.at(-1)?.split('.')?.at(-2) + const siteLogo = useSiteLogo(siteOrigin) + + const getWallet = useGetWallet() + const [txStatus, setTxStatus] = useState('idle') + const [signingError, setSigningError] = useState(null) + + const handleSignClick = async () => { + try { + setSigningError(null) + setTxStatus('loading') + + const wallet = (await getWallet(activeChain, true)) as unknown as EthWallet + let signature: string + + if (txnData.signTxnData.methodType === ETHEREUM_METHOD_TYPE.ETH__SIGN_TYPED_DATA_V4) { + signature = signTypedData( + txnData.signTxnData.data, + activeWallet.addresses.seiDevnet, + wallet, + ) + } else { + signature = personalSign(txnData.signTxnData.data, activeWallet.addresses.seiDevnet, wallet) + } + + try { + Browser.runtime.sendMessage({ + type: MessageTypes.signSeiEvmResponse, + payload: { status: 'success', data: signature }, + }) + } catch { + throw new Error('Could not send transaction to the dApp') + } + + setTimeout(async () => { + window.close() + }, 1000) + } catch (error) { + setTxStatus('error') + setSigningError((error as Error).message) + } + } + + const isApproveBtnDisabled = !!signingError || txStatus === 'loading' + + return ( +
+
+ +
+ ) : undefined + } + title={trim(walletName, 10)} + className='pr-4 cursor-default' + /> + } + topColor={Colors.getChainColor(activeChain, chainInfo)} + /> +
+ } + > +
+

+ Signature request +

+ +

+ Only sign this message if you fully understand the content and trust the requesting + site +

+ +
+ +
+

+ {siteName} +

+

+ {siteOrigin} +

+
+
+ + + {JSON.stringify( + txnData.signTxnData.details, + (_, value) => (typeof value === 'bigint' ? value.toString() : value), + 2, + )} + + ), + }} + /> + + {signingError && txStatus === 'error' ? ( + + ) : null} +
+ +
+
+ + Reject + + + + {txStatus === 'loading' ? : 'Sign'} + +
+
+ +
+
+ ) +} diff --git a/apps/extension/src/pages/sign-sei-evm/components/SignTransaction.tsx b/apps/extension/src/pages/sign-sei-evm/components/SignTransaction.tsx new file mode 100644 index 00000000..323b424a --- /dev/null +++ b/apps/extension/src/pages/sign-sei-evm/components/SignTransaction.tsx @@ -0,0 +1,286 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { + GasOptions, + useActiveChain, + useActiveWallet, + useChainInfo, + useDefaultGasEstimates, + useGasAdjustmentForChain, +} from '@leapwallet/cosmos-wallet-hooks' +import { GasPrice, SeiEvmTx } from '@leapwallet/cosmos-wallet-sdk' +import { EthWallet } from '@leapwallet/leap-keychain' +import { Avatar, Buttons, Header } from '@leapwallet/leap-ui' +import Tooltip from 'components/better-tooltip' +import { ErrorCard } from 'components/ErrorCard' +import GasPriceOptions, { useDefaultGasPrice } from 'components/gas-price-options' +import PopupLayout from 'components/layout/popup-layout' +import { LoaderAnimation } from 'components/loader/Loader' +import { Tabs } from 'components/tabs' +import { MessageTypes } from 'config/message-types' +import { useSiteLogo } from 'hooks/utility/useSiteLogo' +import { Wallet } from 'hooks/wallet/useWallet' +import { Images } from 'images' +import { GenericLight } from 'images/logos' +import React, { useEffect, useMemo, useState } from 'react' +import { Colors } from 'theme/colors' +import { TransactionStatus } from 'types/utility' +import { assert } from 'utils/assert' +import { formatWalletName } from 'utils/formatWalletName' +import { imgOnError } from 'utils/imgOnError' +import { isCompassWallet } from 'utils/isCompassWallet' +import { trim } from 'utils/strings' +import Browser from 'webextension-polyfill' + +import { handleRejectClick } from '../utils' + +const useGetWallet = Wallet.useGetWallet + +export type SignTransactionProps = { + txnData: Record +} + +export function SignTransaction({ txnData }: SignTransactionProps) { + const chainInfo = useChainInfo() + const activeWallet = useActiveWallet() + const activeChain = useActiveChain() + + assert(activeWallet !== null, 'activeWallet is null') + const walletName = useMemo(() => { + return formatWalletName(activeWallet.name) + }, [activeWallet.name]) + + const defaultGasPrice = useDefaultGasPrice({ activeChain }) + const getWallet = useGetWallet() + + const [txStatus, setTxStatus] = useState('idle') + const [userPreferredGasLimit, setUserPreferredGasLimit] = useState('') + const [gasPriceError, setGasPriceError] = useState(null) + const [signingError, setSigningError] = useState(null) + + const [isLoadingGasLimit, setIsLoadingGasLimit] = useState(false) + const defaultGasEstimates = useDefaultGasEstimates() + const gasAdjustment = useGasAdjustmentForChain(activeChain) + const [recommendedGasLimit, setRecommendedGasLimit] = useState( + defaultGasEstimates[activeChain].DEFAULT_GAS_IBC, + ) + + const [gasPriceOption, setGasPriceOption] = useState<{ + option: GasOptions + gasPrice: GasPrice + }>({ gasPrice: defaultGasPrice.gasPrice, option: GasOptions.LOW }) + + const siteOrigin = txnData?.origin as string | undefined + const siteName = siteOrigin?.split('//')?.at(-1)?.split('.')?.at(-2) + const siteLogo = useSiteLogo(siteOrigin) + + useEffect(() => { + ;(async function fetchGasEstimate() { + try { + setIsLoadingGasLimit(true) + const gasUsed = await SeiEvmTx.SimulateTransaction( + txnData.signTxnData.to, + txnData.signTxnData.value, + '', + txnData.signTxnData.data, + gasAdjustment, + ) + + setRecommendedGasLimit(gasUsed) + } catch (_) { + setRecommendedGasLimit(defaultGasEstimates[activeChain].DEFAULT_GAS_IBC) + } finally { + setIsLoadingGasLimit(false) + } + })() + }, [ + activeChain, + defaultGasEstimates, + gasAdjustment, + txnData.signTxnData.data, + txnData.signTxnData.to, + txnData.signTxnData.value, + ]) + + const handleApproveClick = async () => { + try { + setSigningError(null) + setTxStatus('loading') + + const wallet = (await getWallet(activeChain, true)) as unknown as EthWallet + const seiEvmTx = SeiEvmTx.GetSeiEvmClient(wallet) + + const result = await seiEvmTx.sendTransaction( + '', + txnData.signTxnData.to, + txnData.signTxnData.value, + Number(userPreferredGasLimit || recommendedGasLimit), + undefined, + txnData.signTxnData.data, + ) + + setTxStatus('success') + + try { + Browser.runtime.sendMessage({ + type: MessageTypes.signSeiEvmResponse, + payload: { status: 'success', data: result.hash }, + }) + } catch { + throw new Error('Could not send transaction to the dApp') + } + + setTimeout(async () => { + window.close() + }, 1000) + } catch (error) { + setTxStatus('error') + setSigningError((error as Error).message) + } + } + + const isApproveBtnDisabled = + !!signingError || txStatus === 'loading' || !!gasPriceError || isLoadingGasLimit + + return ( +
+
+ +
+ ) : undefined + } + title={trim(walletName, 10)} + className='pr-4 cursor-default' + /> + } + topColor={Colors.getChainColor(activeChain, chainInfo)} + /> +
+ } + > +
+

+ Approve Transaction +

+
+ +
+

+ {siteName} +

+

+ {siteOrigin} +

+
+
+ + setUserPreferredGasLimit(value.toString())} + recommendedGasLimit={recommendedGasLimit} + gasPriceOption={gasPriceOption} + onGasPriceOptionChange={(value: any) => setGasPriceOption(value)} + error={gasPriceError} + setError={setGasPriceError} + considerGasAdjustment={false} + chain={activeChain} + > + +
+

+ Gas Fees ({gasPriceOption.option}) +

+ + You can choose higher gas fees for faster transaction processing. +

+ } + > +
+ Hint +
+
+
+ + +
+ +
+ + + {gasPriceError ? ( +

+ {gasPriceError} +

+ ) : null} +
+ ), + details: ( +
+                      {JSON.stringify(
+                        txnData.signTxnData.details,
+                        (_, value) => (typeof value === 'bigint' ? value.toString() : value),
+                        2,
+                      )}
+                    
+ ), + }} + /> + + + {signingError && txStatus === 'error' ? ( + + ) : null} +
+ +
+
+ + Reject + + + + {txStatus === 'loading' ? : 'Approve'} + +
+
+ +
+
+ ) +} diff --git a/apps/extension/src/pages/sign-sei-evm/components/index.ts b/apps/extension/src/pages/sign-sei-evm/components/index.ts new file mode 100644 index 00000000..c8ac5cc5 --- /dev/null +++ b/apps/extension/src/pages/sign-sei-evm/components/index.ts @@ -0,0 +1,2 @@ +export * from './MessageSignature' +export * from './SignTransaction' diff --git a/apps/extension/src/pages/sign-sei-evm/utils/index.ts b/apps/extension/src/pages/sign-sei-evm/utils/index.ts new file mode 100644 index 00000000..7a339cd1 --- /dev/null +++ b/apps/extension/src/pages/sign-sei-evm/utils/index.ts @@ -0,0 +1 @@ +export * from './shared-functions' diff --git a/apps/extension/src/pages/sign-sei-evm/utils/shared-functions.ts b/apps/extension/src/pages/sign-sei-evm/utils/shared-functions.ts new file mode 100644 index 00000000..859cd8fa --- /dev/null +++ b/apps/extension/src/pages/sign-sei-evm/utils/shared-functions.ts @@ -0,0 +1,13 @@ +import { MessageTypes } from 'config/message-types' +import Browser from 'webextension-polyfill' + +export function handleRejectClick() { + Browser.runtime.sendMessage({ + type: MessageTypes.signSeiEvmResponse, + payload: { status: 'error', data: 'User rejected the transaction' }, + }) + + setTimeout(() => { + window.close() + }, 1000) +} diff --git a/apps/extension/src/pages/sign/fee-validation.tsx b/apps/extension/src/pages/sign/fee-validation.tsx index 4f8f47e6..bc1c04cf 100644 --- a/apps/extension/src/pages/sign/fee-validation.tsx +++ b/apps/extension/src/pages/sign/fee-validation.tsx @@ -66,7 +66,6 @@ export function useFeeValidation(chain: SupportedChain): UseFeeValidationReturn const { data: feeTokens, status } = useFeeTokens(chain, selectedNetwork ?? 'mainnet') const { lcdUrl } = useChainApis(chain) - const activeChain = useActiveChain() const chainId = useChainId() return useCallback( diff --git a/apps/extension/src/pages/sign/sign-transaction.tsx b/apps/extension/src/pages/sign/sign-transaction.tsx index 88daf86d..8fc8751b 100644 --- a/apps/extension/src/pages/sign/sign-transaction.tsx +++ b/apps/extension/src/pages/sign/sign-transaction.tsx @@ -661,7 +661,6 @@ const SignTransaction = ({ } isApprovedRef.current = true - await sleep(100) try { diff --git a/apps/extension/src/pages/stake/Stake.tsx b/apps/extension/src/pages/stake/Stake.tsx index 1fbfbf35..13163cbb 100644 --- a/apps/extension/src/pages/stake/Stake.tsx +++ b/apps/extension/src/pages/stake/Stake.tsx @@ -571,7 +571,7 @@ export default function Stake() { 'w-[48px] h-[40px] px-3 bg-[#FFFFFF] dark:bg-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-full', }} imgSrc={activeChainInfo.chainSymbolImageUrl ?? defaultTokenLogo} - onImgClick={isCompassWallet() ? undefined : () => setShowChainSelector(true)} + onImgClick={() => setShowChainSelector(true)} title={'Staking'} topColor={themeColor} /> diff --git a/apps/extension/src/pages/swaps-v2/index.tsx b/apps/extension/src/pages/swaps-v2/index.tsx index 6c6cb72a..819c2575 100644 --- a/apps/extension/src/pages/swaps-v2/index.tsx +++ b/apps/extension/src/pages/swaps-v2/index.tsx @@ -13,7 +13,7 @@ import { SwapVert } from 'images/misc' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import { useNavigate } from 'react-router' import { Colors } from 'theme/colors' -import { isLedgerEnabled, isLedgerEnabledChainId } from 'utils/isLedgerEnabled' +import { isLedgerEnabledChainId } from 'utils/isLedgerEnabled' import { FeesView, diff --git a/apps/extension/src/types/utility.ts b/apps/extension/src/types/utility.ts index 4f76c369..0b7113f2 100644 --- a/apps/extension/src/types/utility.ts +++ b/apps/extension/src/types/utility.ts @@ -6,3 +6,5 @@ export type Dict = { export type StrictDict = { [key: string | number | symbol]: T } + +export type TransactionStatus = 'loading' | 'success' | 'error' | 'idle' diff --git a/apps/extension/src/utils/isCompassWallet.ts b/apps/extension/src/utils/isCompassWallet.ts index 71f848ea..835c5169 100644 --- a/apps/extension/src/utils/isCompassWallet.ts +++ b/apps/extension/src/utils/isCompassWallet.ts @@ -1,3 +1,3 @@ export function isCompassWallet() { - return process.env.APP === 'compass' + return !!process.env.APP?.includes('compass') } diff --git a/apps/extension/styles/globals.css b/apps/extension/styles/globals.css index dbf21a61..cbae5efd 100644 --- a/apps/extension/styles/globals.css +++ b/apps/extension/styles/globals.css @@ -279,8 +279,6 @@ input[type='number']::-webkit-outer-spin-button { color: #726fdc; } - - .ripple-loader button { position: absolute; border-radius: 100%; diff --git a/apps/extension/webpack.config.js b/apps/extension/webpack.config.js index cdc04aa2..5f828197 100644 --- a/apps/extension/webpack.config.js +++ b/apps/extension/webpack.config.js @@ -9,14 +9,75 @@ const { DeleteSourceMapsPlugin } = require('webpack-delete-sourcemaps-plugin') const { ESBuildMinifyPlugin } = require('esbuild-loader') const fs = require('fs') -const defaultEnvFileName = - process.env.NODE_ENV === 'production' ? '.env.production' : '.env.development' +const buildTypes = { + production: { + type: 'production', + defaultEnvFile: '.env.production', + compassEnvFile: '.env.production.compass', + outDirPath: 'builds', + manifestData: { + leap: { + name: 'Leap Cosmos Wallet', + description: 'A crypto wallet for Cosmos blockchains.', + }, + compass: { + name: 'Compass Wallet for Sei', + description: 'A crypto wallet for Sei Blockchain, brought to you by the Leap Wallet team.', + }, + }, + }, + canary: { + type: 'canary', + defaultEnvFile: '.env.canary', + compassEnvFile: '.env.canary.compass', + outDirPath: 'canary-builds', + manifestData: { + leap: { + name: 'Leap Cosmos Wallet CANARY BUILD', + description: + 'THIS IS THE CANARY DISTRIBUTION OF THE LEAP COSMOS EXTENSION, INTENDED FOR DEVELOPERS.', + }, + compass: { + name: 'Compass CANARY BUILD', + description: + 'THIS IS THE CANARY DISTRIBUTION OF THE COMPASS EXTENSION, INTENDED FOR DEVELOPERS.', + }, + }, + }, + development: { + type: 'development', + defaultEnvFile: '.env.development', + compassEnvFile: '.env.development.compass', + manifestData: { + leap: { + name: 'Leap Cosmos Wallet DEVELOPMENT BUILD', + description: + 'THIS IS THE DEVELOPMENT DISTRIBUTION OF THE LEAP COSMOS EXTENSION, INTENDED FOR LOCAL DEVELOPMENT.', + }, + compass: { + name: 'Compass DEVELOPMENT BUILD', + description: + 'THIS IS THE DEVELOPMENT DISTRIBUTION OF THE COMPASS EXTENSION, INTENDED FOR LOCAL DEVELOPMENT.', + }, + }, + }, +} + +const buildType = buildTypes[process.env.NODE_ENV] +const isProdBuild = buildType.type === 'production' +const isCanaryBuild = buildType.type === 'canary' +const isDevelopmentBuild = buildType.type === 'development' +const isCompassBuild = process.env.APP && process.env.APP.includes('compass') -const compassEnvFileName = - process.env.NODE_ENV === 'production' ? '.env.production.compass' : '.env.development.compass' +const defaultEnvFileName = buildType.defaultEnvFile -const isProdBuild = process.env.NODE_ENV === 'production' -const isCompassBuild = process.env.APP === 'compass' +const compassEnvFileName = buildType.compassEnvFile + +let publicDir = isCompassBuild ? 'public/compass' : 'public/leap-cosmos' + +if (isCanaryBuild && isCompassBuild) { + publicDir = 'public/compass-canary' +} const defaultEnvFilePath = path.join(__dirname, defaultEnvFileName) const appEnvFilePath = path.join( @@ -110,13 +171,13 @@ const base_config = { }, plugins: [ new HtmlWebpackPlugin({ - template: isCompassBuild ? './public/compass/index.html' : './public/leap-cosmos/index.html', + template: `./${publicDir}/index.html`, chunks: ['index'], }), new CopyPlugin({ patterns: [ { - from: isCompassBuild ? 'public/compass' : 'public/leap-cosmos', + from: publicDir, to: '.', filter: (filepath) => !filepath.endsWith('index.html'), }, @@ -165,25 +226,25 @@ module.exports = (env, argv) => { let config const staging = env?.staging === 'true' + const canary = env?.canary === 'true' const baseManifest = fs.readFileSync(path.join(__dirname, 'public/base_manifest.json'), 'utf-8') // Set values based on the --env.name flag provided in the build command - let name, description, publicPath + let name, description + if (isCompassBuild) { - name = 'Compass Wallet for Sei' - description = 'A crypto wallet for Sei Blockchain, brought to you by the Leap Wallet team.' - publicPath = 'public/compass' + name = buildType.manifestData.compass.name + description = buildType.manifestData.compass.description } else { - name = 'Leap Cosmos Wallet' - description = 'A crypto wallet for Cosmos blockchains.' - publicPath = 'public/leap-cosmos' + name = buildType.manifestData.leap.name + description = buildType.manifestData.leap.description } const manifest = baseManifest.replace('__NAME__', name).replace('__DESCRIPTION__', description) const manifestObj = JSON.parse(manifest) // Write manifest file - fs.writeFileSync(path.join(__dirname, `${publicPath}/manifest.json`), manifest) + fs.writeFileSync(path.join(__dirname, `./${publicDir}/manifest.json`), manifest) if (staging) { config = Object.assign({}, base_config, { @@ -213,7 +274,7 @@ module.exports = (env, argv) => { }, }) } else { - if (!isProdBuild) { + if (isDevelopmentBuild) { config = Object.assign({}, base_config, { mode: 'development', devtool: 'inline-source-map', @@ -230,13 +291,15 @@ module.exports = (env, argv) => { }), ], }) - } else if (isProdBuild) { + } else if (isProdBuild || isCanaryBuild) { config = Object.assign({}, base_config, { mode: 'production', output: { path: path.join( __dirname, - isCompassBuild ? 'builds/compass-build' : 'builds/cosmos-build', + isCompassBuild + ? `${buildType.outDirPath}/compass-build` + : `${buildType.outDirPath}/cosmos-build`, ), filename: '[name].js', clean: true, @@ -252,7 +315,9 @@ module.exports = (env, argv) => { new SentryWebpackPlugin({ org: appEnvConfig.SENTRY_ORG, project: appEnvConfig.SENTRY_PROJECT, - include: isCompassBuild ? './builds/compass-build' : './builds/cosmos-build', + include: isCompassBuild + ? `./${buildType.outDirPath}/compass-build` + : `./${buildType.outDirPath}/cosmos-build`, // Auth tokens can be obtained from https://sentry.io/settings/account/api/auth-tokens/ // and needs the `project:releases` and `org:read` scopes token: envConfig.SENTRY_AUTH_TOKEN, diff --git a/package.json b/package.json index 1301d3ab..f4c8a506 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,14 @@ "build:packages": "run-s build:walletsdk build:walletapi build:wallethooks build:walletprovider", "build:ext": "yarn workspace @leap-cosmos/extension build", "build:ext:compass": "yarn workspace @leap-cosmos/extension build:compass", + "build:ext-canary": "yarn workspace @leap-cosmos/extension build:canary", + "build:ext:canary-compass": "yarn workspace @leap-cosmos/extension build:canary-compass", "build:extension": "run-s build:packages build:ext", "build:extension:compass": "run-s build:packages build:ext:compass", + "build:extension-canary": "run-s build:packages build:ext-canary", + "build:extension:canary-compass": "run-s build:packages build:ext:canary-compass", "build:extension:all": "run-s build:extension build:extension:compass", + "build:extension:canary-all": "run-s build:extension-canary build:extension:canary-compass", "build:ext:staging": "yarn workspace @leap-cosmos/extension build:staging", "build:extension:staging": "run-s build:packages build:ext:staging", "build:demo": "yarn workspace @leap-cosmos/demo build", @@ -46,6 +51,7 @@ "test:extension": "yarn workspace @leap-cosmos/extension test", "test": "yarn workspaces run test --passWithNoTests", "publish:walletsdk": "yarn workspace @leapwallet/cosmos-wallet-sdk publish --non-interactive", + "publish:types": "yarn workspace @leapwallet/types publish --non-interactive", "bootstrap": "yarn --ignore-engines", "publish:ci": "lerna publish from-package --yes", "version:ci": "lerna version --yes --conventional-commits --create-release github --message 'chore(release): publish'", @@ -69,11 +75,11 @@ "lerna": "7.1.5", "markdown-it": "13.0.1", "npm-run-all": "4.1.5", + "patch-package": "8.0.0", + "postinstall-postinstall": "2.1.0", "prettier": "2.7.1", "vitepress": "1.0.0-alpha.29", - "vue": "3.3.4" , - "patch-package": "8.0.0", - "postinstall-postinstall": "2.1.0" + "vue": "3.3.4" }, "dependencies": { "axios": "1.2.1", diff --git a/packages/types/.eslintrc b/packages/types/.eslintrc new file mode 100644 index 00000000..d670b996 --- /dev/null +++ b/packages/types/.eslintrc @@ -0,0 +1,11 @@ +{ + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint", "jsdoc", "simple-import-sort"], + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + "rules": { + "simple-import-sort/imports": "error", + "simple-import-sort/exports": "error", + "@typescript-eslint/no-unused-vars": "warn", + "@typescript-eslint/no-explicit-any": "off" + } +} diff --git a/packages/types/.gitignore b/packages/types/.gitignore new file mode 100644 index 00000000..de4d1f00 --- /dev/null +++ b/packages/types/.gitignore @@ -0,0 +1,2 @@ +dist +node_modules diff --git a/packages/types/README.md b/packages/types/README.md new file mode 100644 index 00000000..6f3a69e8 --- /dev/null +++ b/packages/types/README.md @@ -0,0 +1,20 @@ +## @leapwallet/types +This package provides type definitions for the Leap extension provider, making it easier to work with in TypeScript projects. + +### Installation +Before using @leapwallet/types, ensure you've installed it via your package manager. + +```bash +npm install @leapwallet/types --save-dev +``` +or if you're using Yarn: + +```bash +yarn add @leapwallet/types --dev +``` +# Usage +Import the package at the top level of your application, such as in your index.ts or the main entry file of your project. + +```typescript +import "@leapwallet/types"; +``` diff --git a/packages/types/package.json b/packages/types/package.json new file mode 100644 index 00000000..e20d7710 --- /dev/null +++ b/packages/types/package.json @@ -0,0 +1,18 @@ +{ + "name": "@leapwallet/types", + "version": "0.0.5", + "description": "Types for leap provider", + "scripts": { + "build": "tsc -p tsconfig.json", + "build:watch": "tsc tsconfig.json -w", + "eslint:lint": "eslint ./src", + "eslint:fix": "eslint --fix ./src", + "prettier:check": "prettier -c ./src", + "prettier:write": "prettier -w ./src", + "prepublish": "yarn build" + }, + "main": "dist/index.js", + "devDependencies": { + "typescript": "5.2.2" + } +} diff --git a/packages/types/src/chain-info.ts b/packages/types/src/chain-info.ts new file mode 100644 index 00000000..ddf5ad54 --- /dev/null +++ b/packages/types/src/chain-info.ts @@ -0,0 +1,38 @@ +export type Bech32Config = { + bech32PrefixAccAddr: string; + bech32PrefixAccPub: string; + bech32PrefixValAddr: string; + bech32PrefixValPub: string; + bech32PrefixConsAddr: string; + bech32PrefixConsPub: string; +}; +export type Currency = { + coinDenom: string; + coinMinimalDenom: string; + coinDecimals: number; + coinGeckoId?: string; + coinImageUrl?: string; +}; +export type ChainInfo = { + rpc: string; + rest: string; + chainId: string; + chainName: string; + stakeCurrency: Currency; + walletUrl?: string; + walletUrlForStaking?: string; + bip44: { + coinType: number; + }; + bech32Config: Bech32Config; + currencies: Currency[]; + feeCurrencies: Currency[]; + coinType?: number; + gasPriceStep?: { + low: number; + average: number; + high: number; + }; + readonly features?: string[]; + readonly beta?: boolean; +}; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts new file mode 100644 index 00000000..fcf224fd --- /dev/null +++ b/packages/types/src/index.ts @@ -0,0 +1,9 @@ +export * from './chain-info'; +export * from './leap'; +export * from './signer'; + +import { LeapWindow } from './leap'; + +declare global { + interface Window extends LeapWindow {} +} diff --git a/packages/types/src/leap.ts b/packages/types/src/leap.ts new file mode 100644 index 00000000..81a9128b --- /dev/null +++ b/packages/types/src/leap.ts @@ -0,0 +1,97 @@ +import { ChainInfo } from 'chain-info'; +import { AminoSignResponse, DirectSignResponse, OfflineDirectSigner, StdSignature, StdSignDoc } from 'signer'; + +export type LeapEnigmaUtils = { + getPubkey(): Promise; + getTxEncryptionKey(nonce: Uint8Array): Promise; + encrypt(contractCodeHash: string, msg: object): Promise; + decrypt(ciphertext: Uint8Array, nonce: Uint8Array): Promise; +}; +export type LeapMode = 'core' | 'extension' | 'mobile-web' | 'walletconnect'; +export type LeapSignOptions = { + readonly preferNoSetFee?: boolean; + readonly preferNoSetMemo?: boolean; + readonly disableBalanceCheck?: boolean; +}; +export type LeapIntereactionOptions = { + readonly sign?: LeapSignOptions; +}; + +export type Key = { + readonly name: string; + readonly algo: string; + readonly pubKey: Uint8Array; + readonly address: Uint8Array; + readonly bech32Address: string; + readonly isNanoLedger: boolean; +}; + +export enum BroadcastMode { + BROADCAST_MODE_UNSPECIFIED = 0, + BROADCAST_MODE_BLOCK = 1, + BROADCAST_MODE_SYNC = 2, + BROADCAST_MODE_ASYNC = 3, + UNRECOGNIZED = -1, +} + +export type OfflineSigner = OfflineDirectSigner; + +export type EthSignType = 'message' | 'transaction' | 'eip-712'; +export interface Leap { + defaultOptions: LeapIntereactionOptions; + version: string; + mode: LeapMode; + enable(chainds: string | string[]): Promise; + disconnect: (chainId: string) => Promise; + isConnected: (chainId: string) => Promise; + experimentalSuggestChain(chainInfo: ChainInfo): Promise; + getKey(chainId: string): Promise; + signAmino( + chainId: string, + signer: string, + signDoc: StdSignDoc, + signOptions?: LeapSignOptions, + ): Promise; + signDirect( + chainId: string, + signer: string, + signDoc: { + bodyBytes?: Uint8Array | null; + authInfoBytes?: Uint8Array | null; + chainId?: string | null; + accountNumber?: Long | null; + }, + signOptions?: LeapSignOptions, + ): Promise; + signArbitrary(chainId: string, signer: string, data: string | Uint8Array): Promise; + signEthereum(chainId: string, signer: string, data: string | Uint8Array, type: EthSignType): Promise; + experimentalSignEip712CosmosTx_v0( + chainId: string, + signer: string, + eip712: { + types: Record; + domain: Record; + primaryType: string; + }, + signDoc: StdSignDoc, + signOptions: LeapSignOptions, + ): Promise; + sendTx(chainId: string, tx: Uint8Array, mode: BroadcastMode): Promise; + suggestToken(chainId: string, contractAddress: string, viewingKey?: string): Promise; + suggestCW20Token(chainId: string, contractAddress: string): Promise; + getSecret20ViewingKey(chainId: string, contractAddress: string): Promise; + updateViewingKey(chainId: string, contractAddress: string, viewingKey: string): Promise; + getEnigmaPubKey(chainId: string): Promise; + getEnigmaTxEncryptionKey(chainId: string, nonce: Uint8Array): Promise; + enigmaEncrypt(chainId: string, contractCodeHash: string, msg: object): Promise; + enigmaDecrypt(chainId: string, ciphertext: Uint8Array, nonce: Uint8Array): Promise; + getOfflineSigner: (chainId: string, signOptions: LeapSignOptions) => OfflineSigner & OfflineDirectSigner; + getOfflineSignerAmino: (chainId: string, signOptions: LeapSignOptions) => OfflineSigner; + getOfflineSignerAuto: (chainId: string, signOption: LeapSignOptions) => Promise; + getEnigmaUtils: (chainId: string) => LeapEnigmaUtils; + getSupportedChains: () => Promise; +} + +export interface LeapWindow { + leap: Leap; +} diff --git a/packages/types/src/signer.ts b/packages/types/src/signer.ts new file mode 100644 index 00000000..5a4ef981 --- /dev/null +++ b/packages/types/src/signer.ts @@ -0,0 +1,64 @@ +export type StdFee = { + amount: Array<{ denom: string; amount: string }>; + gas: string; + granter?: string; + payer?: string; +}; + +export type AminoMsg = { + type: string; + value: any; +}; + +export type StdSignDoc = { + chain_id: string; + account_number: string; + sequence: string; + fee: StdFee; + msgs: readonly AminoMsg[]; + memo: string; + timeout_height?: string; +}; + +export type AccountData = { + address: string; + algo: string; + pubkey: Uint8Array; +}; + +export type AminoSignResponse = { + signed: StdSignDoc; + signature: StdSignature; +}; + +export type OfflineAminoSigner = { + getAccounts: () => Promise; + signAmino: (signerAddress: string, signDoc: StdSignDoc) => Promise; +}; + +export type OfflineDirectSigner = { + getAccounts: () => Promise; + signDirect: (signerAddress: string, signDoc: SignDoc) => Promise; +}; + +export type Pubkey = { + type: string; + value: string; +}; + +export type StdSignature = { + pub_key: Pubkey; + signature: string; +}; + +export type SignDoc = { + bodyBytes: Uint8Array; + authInfoBytes: Uint8Array; + chainId: string; + accountNumber: Long; +}; + +export type DirectSignResponse = { + signed: SignDoc; + signature: StdSignature; +}; diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json new file mode 100644 index 00000000..fa5f2675 --- /dev/null +++ b/packages/types/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "es6", + "useUnknownInCatchVariables": false, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "allowUnreachableCode": false, + "module": "ESNext", + "rootDir": "src", + "moduleResolution": "node", + "outDir": "dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "baseUrl": "src/", + "lib": ["es2020", "DOM"], + "strict": true, + "skipLibCheck": true + }, + "include": ["src"], + "exclude": ["node_modules", "../../node_modules", ".yalc"] +} diff --git a/packages/wallet-hooks/package.json b/packages/wallet-hooks/package.json index 763885c7..8901eb5a 100644 --- a/packages/wallet-hooks/package.json +++ b/packages/wallet-hooks/package.json @@ -1,6 +1,6 @@ { "name": "@leapwallet/cosmos-wallet-hooks", - "version": "0.9.6", + "version": "0.9.7", "description": "Wallet hooks for cosmos mobile app and extension.", "main": "dist/index.js", "scripts": { @@ -25,12 +25,12 @@ }, "dependencies": { "@leapwallet/parser-parfait": "0.7.0", - "@leapwallet/cosmos-wallet-sdk": "0.9.6", + "@leapwallet/cosmos-wallet-sdk": "0.9.7", "@tanstack/react-query": "4.2.3", "@tanstack/react-query-devtools": "4.2.3", "dotenv": "16.3.1", "zustand": "4.1.1", - "@leapwallet/wallet-api": "*" + "@leapwallet/leap-api-js": "0.21.4" }, "devDependencies": { "@babel/preset-env": "7.18.0", @@ -42,4 +42,4 @@ "jest": "28.1.0", "typescript": "5.2.2" } -} \ No newline at end of file +} diff --git a/packages/wallet-hooks/src/apis/LeapWalletApi.ts b/packages/wallet-hooks/src/apis/LeapWalletApi.ts index 9d6f7371..85d4ff4e 100644 --- a/packages/wallet-hooks/src/apis/LeapWalletApi.ts +++ b/packages/wallet-hooks/src/apis/LeapWalletApi.ts @@ -365,6 +365,7 @@ export namespace LeapWalletApi { ['union Testnet']: 'UNION' as CosmosBlockchain, ['Cronos POS Chain']: 'CRYPTO_ORG_CHAIN' as CosmosBlockchain, ['Haqq Network']: 'HAQQ_NETWORK' as CosmosBlockchain, + saga: 'SAGA' as CosmosBlockchain, }; return blockchains[activeChain] ?? activeChain?.toUpperCase(); } diff --git a/packages/wallet-hooks/src/apis/platforms-mapping.ts b/packages/wallet-hooks/src/apis/platforms-mapping.ts index 1921f8e1..b30baeb6 100644 --- a/packages/wallet-hooks/src/apis/platforms-mapping.ts +++ b/packages/wallet-hooks/src/apis/platforms-mapping.ts @@ -103,6 +103,8 @@ export const platforms: Record = { pryzmtestnet: 'PRYZM', // @ts-ignore thorchain: 'THOR_CHAIN', + // @ts-ignore + saga: 'SAGA', }; export const platformToChain: Record = { @@ -179,4 +181,5 @@ export const platformToChain: Record = { PRYZM: 'pryzmtestnet', THOR_CHAIN: 'thorchain', ODIN_CHAIN: 'odin', + SAGA: 'saga', }; diff --git a/packages/wallet-hooks/src/asset-details/useAssetDetails.ts b/packages/wallet-hooks/src/asset-details/useAssetDetails.ts index a1642f17..4e6401b4 100644 --- a/packages/wallet-hooks/src/asset-details/useAssetDetails.ts +++ b/packages/wallet-hooks/src/asset-details/useAssetDetails.ts @@ -33,7 +33,7 @@ export function useAssetDetails({ denom, tokenChain }: UseAssetDetailsProps) { const denoms = useDenoms(); const chainInfo = useChainInfo(tokenChain); const selectedNetwork = useSelectedNetwork(); - const chainId = selectedNetwork === 'mainnet' ? chainInfo.chainId : chainInfo.testnetChainId; + const chainId = selectedNetwork === 'mainnet' ? chainInfo?.chainId : chainInfo?.testnetChainId; const autoFetchedCW20Tokens = useAutoFetchedCW20Tokens(); const combinedDenoms = useMemo(() => { return { @@ -102,7 +102,7 @@ export function useAssetDetails({ denom, tokenChain }: UseAssetDetailsProps) { isLoading: loadingPrice, error: errorInfo, } = useQuery( - ['assetData', denom], + ['assetData', denom, chainId], async () => { if (!denom) { return; diff --git a/packages/wallet-hooks/src/bank/index.ts b/packages/wallet-hooks/src/bank/index.ts index dfbb8bec..a1ef20e5 100644 --- a/packages/wallet-hooks/src/bank/index.ts +++ b/packages/wallet-hooks/src/bank/index.ts @@ -1,4 +1,5 @@ export * from './queryIds'; export * from './useGetAsteroidTokens'; +export * from './useGetSeiEvmBalance'; export * from './useGetSnip20TokenBalances'; export * from './useGetTokenBalances'; diff --git a/packages/wallet-hooks/src/bank/useGetSeiEvmBalance.ts b/packages/wallet-hooks/src/bank/useGetSeiEvmBalance.ts new file mode 100644 index 00000000..d518e23e --- /dev/null +++ b/packages/wallet-hooks/src/bank/useGetSeiEvmBalance.ts @@ -0,0 +1,112 @@ +import { fetchSeiEvmBalances, getSeiEvmAddressToShow, SupportedChain } from '@leapwallet/cosmos-wallet-sdk'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; +import { BigNumber } from 'bignumber.js'; +import { useCallback } from 'react'; + +import { currencyDetail, useUserPreferredCurrency } from '../settings'; +import { useActiveChain, useActiveWallet, useAddress, useChainApis, useDenoms } from '../store'; +import { SupportedCurrencies, Token } from '../types'; +import { balanceCalculator, fetchCurrency, useGetStorageLayer } from '../utils'; +import { SEI_EVM_LINKED_ADDRESS_STATE_KEY } from '../utils-hooks'; + +export function useGetSeiEvmBalance(forceChain?: SupportedChain, forceCurrencyPreferred?: SupportedCurrencies) { + const _activeChain = useActiveChain(); + const denoms = useDenoms(); + const [_preferredCurrency] = useUserPreferredCurrency(); + + const activeChain = forceChain || _activeChain; + const uSeiToken = denoms.usei; + const preferredCurrency = forceCurrencyPreferred || _preferredCurrency; + + const { evmJsonRpc } = useChainApis(); + const activeWallet = useActiveWallet(); + const storage = useGetStorageLayer(); + const address = useAddress(); + + return useQuery( + [ + QUERY_SEI_EVM_BALANCE_KEY, + activeChain, + activeWallet?.pubKeys?.seiDevnet, + uSeiToken, + preferredCurrency, + storage, + address, + ], + async function getSeiEvmBalance() { + const seiEvmBalance: Token[] = []; + let currencyInFiatValue = new BigNumber(0); + + try { + if (evmJsonRpc) { + const isSeiEvm = activeChain === 'seiDevnet'; + + if (isSeiEvm) { + const fetchSeiEvmBalance = async () => { + const ethWalletAddress = getSeiEvmAddressToShow(activeWallet?.pubKeys?.seiDevnet); + + if (ethWalletAddress.startsWith('0x')) { + const balance = await fetchSeiEvmBalances(evmJsonRpc, ethWalletAddress); + + const usdValue = + parseFloat(balance.amount) > 0 && uSeiToken.coinGeckoId + ? await fetchCurrency( + balance.amount, + uSeiToken.coinGeckoId, + uSeiToken.chain as SupportedChain, + currencyDetail[preferredCurrency].currencyPointer, + ) + : undefined; + const usdPrice = + parseFloat(balance.amount) > 0 && usdValue + ? (parseFloat(usdValue) / parseFloat(balance.amount)).toString() + : '0'; + + seiEvmBalance.push({ + chain: uSeiToken?.chain ?? '', + name: uSeiToken?.name, + amount: balance.amount, + symbol: uSeiToken?.coinDenom, + usdValue: usdValue ?? '', + coinMinimalDenom: uSeiToken?.coinMinimalDenom, + img: uSeiToken?.icon, + ibcDenom: '', + usdPrice, + coinDecimals: uSeiToken?.coinDecimals, + coinGeckoId: uSeiToken?.coinGeckoId, + isEvm: true, + }); + } + }; + + const storedLinkedAddressState = await storage.get(SEI_EVM_LINKED_ADDRESS_STATE_KEY); + if (storedLinkedAddressState) { + const linkedAddressState = JSON.parse(storedLinkedAddressState); + + if (linkedAddressState[address] !== 'done') { + await fetchSeiEvmBalance(); + } + } else { + await fetchSeiEvmBalance(); + } + } + } + + currencyInFiatValue = balanceCalculator(seiEvmBalance); + return { seiEvmBalance, currencyInFiatValue }; + } catch (error) { + return { seiEvmBalance, currencyInFiatValue }; + } + }, + ); +} + +const QUERY_SEI_EVM_BALANCE_KEY = 'query-get-sei-evm-balance'; + +export function useInvalidateSeiEvmBalance() { + const queryClient = useQueryClient(); + + return useCallback(() => { + queryClient.invalidateQueries([QUERY_SEI_EVM_BALANCE_KEY]); + }, [queryClient]); +} diff --git a/packages/wallet-hooks/src/bank/useGetTokenBalances.ts b/packages/wallet-hooks/src/bank/useGetTokenBalances.ts index 09e18ced..9065d582 100644 --- a/packages/wallet-hooks/src/bank/useGetTokenBalances.ts +++ b/packages/wallet-hooks/src/bank/useGetTokenBalances.ts @@ -6,13 +6,14 @@ import { fetchERC20Balances, fromSmall, getBlockChainFromAddress, + getEthereumAddress, + getSeiEvmAddressToShow, NativeDenom, SupportedChain, } from '@leapwallet/cosmos-wallet-sdk'; import { useQueries, useQuery, useQueryClient } from '@tanstack/react-query'; import { BigNumber } from 'bignumber.js'; import { useCallback, useMemo } from 'react'; -import { Token } from 'types/bank'; import { LeapWalletApi } from '../apis'; import { Currency } from '../connectors'; @@ -22,6 +23,7 @@ import { getCoingeckoPricesStoreSnapshot, IbcDenomData, useActiveChain, + useActiveWallet, useAddress, useAutoFetchedCW20Tokens, useBetaCW20Tokens, @@ -38,6 +40,7 @@ import { useIbcTraceStore, useSelectedNetwork, } from '../store'; +import { Token } from '../types'; import { balanceCalculator, fetchCurrency, @@ -222,8 +225,6 @@ export function useGetNativeTokensBalances( const selectedNetwork = forceNetwork ?? _selectedNetwork; const { chains } = useChainsStore(); - const isApiUnavailable = chains?.[activeChain as SupportedChain]?.apiStatus === false; - const retry = isApiUnavailable ? false : 10; const address = useAddress(activeChain); const { lcdUrl } = useChainApis(activeChain, selectedNetwork); @@ -366,6 +367,7 @@ function useGetERC20TokenBalances(currencyPreferred: Currency) { const { evmJsonRpc } = useChainApis(); const erc20TokenAddresses = useGetERC20TokenAddresses(); + const activeWallet = useActiveWallet(); const activeChain = useActiveChain(); const address = useAddress(); const denoms = useDenoms(); @@ -375,10 +377,24 @@ function useGetERC20TokenBalances(currencyPreferred: Currency) { const retry = isApiUnavailable ? false : 10; const { data, status: rawBalancesStatus } = useQuery( - [`${activeChain}-${bankQueryIds.erc20TokenBalancesRaw}`, activeChain, address, erc20TokenAddresses?.length], + [ + `${activeChain}-${bankQueryIds.erc20TokenBalancesRaw}`, + activeChain, + address, + erc20TokenAddresses?.length, + activeWallet?.pubKeys?.seiDevnet, + ], async () => { if (erc20TokenAddresses.length && evmJsonRpc) { - return await fetchERC20Balances(evmJsonRpc, address, erc20TokenAddresses); + const isSeiEvm = activeChain === 'seiDevnet'; + const ethWalletAddress = isSeiEvm + ? getSeiEvmAddressToShow(activeWallet?.pubKeys?.seiDevnet) + : getEthereumAddress(address); + + if (ethWalletAddress.startsWith('0x')) { + const erc20Balances = await fetchERC20Balances(evmJsonRpc, ethWalletAddress, erc20TokenAddresses); + return erc20Balances.filter((balance) => balance.amount.gt(0)); + } } return []; @@ -793,7 +809,14 @@ export function useGetTokenBalances(forceChain?: SupportedChain, forceNetwork?: return sortedNativeTokensBalance.concat(sortNonNativeTokens); } - }, [_nativeTokensBalance, s3IbcTokensBalances, nonS3IbcTokensBalances, _cw20TokensBalances, rawBalancesData]); + }, [ + _nativeTokensBalance, + s3IbcTokensBalances, + nonS3IbcTokensBalances, + _cw20TokensBalances, + rawBalancesData, + erc20TokensBalances, + ]); const refetchBalances = () => Promise.all([ diff --git a/packages/wallet-hooks/src/gov/useGetNtrnProposals.ts b/packages/wallet-hooks/src/gov/useGetNtrnProposals.ts index 78953a44..f7304103 100644 --- a/packages/wallet-hooks/src/gov/useGetNtrnProposals.ts +++ b/packages/wallet-hooks/src/gov/useGetNtrnProposals.ts @@ -6,6 +6,7 @@ import { useEffect, useMemo, useState } from 'react'; import { ProposalApi } from 'types'; import { useActiveChain, useChainApis, useGetChains, useSpamProposals } from '../store'; +import { getLeapapiBaseUrl } from '../utils'; export function useGetNtrnProposals() { const { rpcUrl, lcdUrl } = useChainApis(); @@ -35,6 +36,7 @@ export function useGetNtrnProposals() { } = useInfiniteQuery( ['neutron-proposals', lcdUrl, chains], async ({ pageParam: paginationKey }): Promise<{ proposals: ProposalApi[]; key?: string }> => { + const leapApiBaseUrl = getLeapapiBaseUrl(); const query = qs.stringify({ timestamp: Date.now(), @@ -42,13 +44,10 @@ export function useGetNtrnProposals() { offset: Number(paginationKey ?? 0), }); try { - const { data } = await axios.post( - `${process.env.LEAP_WALLET_BACKEND_API_URL}/gov/proposals/${chains.neutron.chainId}?${query}`, - { - lcdUrl, - rpcUrl, - }, - ); + const { data } = await axios.post(`${leapApiBaseUrl}/gov/proposals/${chains.neutron.chainId}?${query}`, { + lcdUrl, + rpcUrl, + }); return { proposals: data?.proposals?.sort((a: any, b: any) => Number(b.proposal_id) - Number(a.proposal_id)), diff --git a/packages/wallet-hooks/src/gov/useInitGovProposals.ts b/packages/wallet-hooks/src/gov/useInitGovProposals.ts index 3e86367c..3f04791f 100644 --- a/packages/wallet-hooks/src/gov/useInitGovProposals.ts +++ b/packages/wallet-hooks/src/gov/useInitGovProposals.ts @@ -12,6 +12,7 @@ import { useSpamProposals, } from '../store'; import { Proposal, Proposal2, ProposalApi } from '../types'; +import { getLeapapiBaseUrl } from '../utils'; import { formatProposal } from './formatProposal'; import { proposalHasContentMessages } from './utils'; @@ -52,9 +53,8 @@ export function useInitGovProposals( offset: Number(paginationKey ?? 0), }); - const { data } = await axios.post( - `${process.env.LEAP_WALLET_BACKEND_API_URL}/gov/proposals/${activeChainInfo.chainId}?${query}`, - ); + const leapApiBaseUrl = getLeapapiBaseUrl(); + const { data } = await axios.post(`${leapApiBaseUrl}/gov/proposals/${activeChainInfo.chainId}?${query}`); paginationCountRef.current = data?.key; const proposals = data?.proposals?.sort((a: any, b: any) => Number(b.proposal_id) - Number(a.proposal_id)); diff --git a/packages/wallet-hooks/src/index.ts b/packages/wallet-hooks/src/index.ts index 5b0dcbd0..8177dfe6 100644 --- a/packages/wallet-hooks/src/index.ts +++ b/packages/wallet-hooks/src/index.ts @@ -23,3 +23,4 @@ export * from './swaps'; export * from './tx'; export * from './types'; export * from './utils'; +export * from './utils-hooks'; diff --git a/packages/wallet-hooks/src/send/useSendModule.ts b/packages/wallet-hooks/src/send/useSendModule.ts index a7d40a1c..af3f7b08 100644 --- a/packages/wallet-hooks/src/send/useSendModule.ts +++ b/packages/wallet-hooks/src/send/useSendModule.ts @@ -1,6 +1,7 @@ import { coin, StdFee } from '@cosmjs/amino'; import { calculateFee, GasPrice } from '@cosmjs/stargate'; import { + ARCTICE_EVM_GAS_LIMIT, ChainInfos, DefaultGasEstimates, Dict, @@ -8,10 +9,12 @@ import { getSimulationFee, isEthAddress, NativeDenom, + SeiEvmTx, simulateIbcTransfer, simulateSend, SupportedChain, } from '@leapwallet/cosmos-wallet-sdk'; +import { EthWallet } from '@leapwallet/leap-keychain'; import { useQuery } from '@tanstack/react-query'; import bech32 from 'bech32'; import { BigNumber } from 'bignumber.js'; @@ -85,9 +88,13 @@ export type SendModuleType = Readonly<{ userPreferredGasLimit: number | undefined; setUserPreferredGasLimit: React.Dispatch>; addressError: string | undefined; + addressWarning: string | undefined; amountError: string | undefined; + gasError: string | null; txError: string | undefined; setAddressError: React.Dispatch>; + setAddressWarning: React.Dispatch>; + setGasError: React.Dispatch>; setAmountError: React.Dispatch>; isIBCTransfer: boolean; sendDisabled: boolean; @@ -99,6 +106,14 @@ export type SendModuleType = Readonly<{ args: Omit, callback: (status: 'success' | 'txDeclined') => void, ) => Promise; + confirmSendEth: ( + toAddress: string, + value: string, + gas: number, + wallet: EthWallet, + txCallback: TxCallback, + gasPrice?: number, + ) => void; clearTxError: () => void; }>; @@ -129,8 +144,10 @@ export function useSendModule(): SendModuleType { const [gasEstimate, setGasEstimate] = useState( defaultGasEstimates[activeChain]?.DEFAULT_GAS_TRANSFER ?? DefaultGasEstimates.DEFAULT_GAS_TRANSFER, ); + const [gasError, setGasError] = useState(null); const [addressError, setAddressError] = useState(undefined); + const [addressWarning, setAddressWarning] = useState(undefined); const [amountError, setAmountError] = useState(undefined); const [txError, setTxError] = useState(undefined); @@ -142,7 +159,7 @@ export function useSendModule(): SendModuleType { const { setPendingTx } = usePendingTxState(); const getIbcChannelId = useGetIbcChannelId(); const txPostToDB = LeapWalletApi.useOperateCosmosTx(); - const { isSending, sendTokens, showLedgerPopup } = useSimpleSend(); + const { isSending, sendTokens, showLedgerPopup, sendTokenEth } = useSimpleSend(); const { data: ibcSupportData, isLoading: isIbcSupportDataLoading } = useGetIBCSupport(activeChain); const nativeFeeDenom = useNativeFeeDenom(); const gasAdjustment = useGasAdjustmentForChain(activeChain); @@ -313,12 +330,39 @@ export function useSendModule(): SendModuleType { ], ); + const confirmSendEth = useCallback( + async ( + toAddress: string, + value: string, + gas: number, + wallet: EthWallet, + callback: TxCallback, + gasPrice?: number, + ) => { + const result = await sendTokenEth(fromAddress, toAddress, value, gas, wallet, gasPrice); + if (result.success) { + result.pendingTx && setPendingTx(result.pendingTx); + callback('success'); + } else { + result.errors && setTxError(result.errors.join(',\n')); + } + }, + [fromAddress], + ); + const clearTxError = useCallback(() => { setTxError(undefined); }, []); useEffect(() => { const fn = async () => { + const defaultIbcGasEstimate = + defaultGasEstimates[activeChain]?.DEFAULT_GAS_IBC ?? DefaultGasEstimates.DEFAULT_GAS_IBC; + const defaultNonIbcGasEstimate = + (defaultGasEstimates[activeChain]?.DEFAULT_GAS_TRANSFER ?? DefaultGasEstimates.DEFAULT_GAS_TRANSFER) * + (selectedToken && isCW20Tx(selectedToken) ? 2 : 1); + + setGasEstimate(isIBCTransfer ? defaultIbcGasEstimate : defaultNonIbcGasEstimate); const inputAmountNumber = new BigNumber(inputAmount); if ( @@ -340,19 +384,24 @@ export function useSendModule(): SendModuleType { token = isEthAddress(token) ? `erc20/${token}` : token; const amountOfCoins = coin(normalizedAmount, token); - const defaultIbcGasEstimate = - defaultGasEstimates[activeChain]?.DEFAULT_GAS_IBC ?? DefaultGasEstimates.DEFAULT_GAS_IBC; - const defaultNonIbcGasEstimate = - (defaultGasEstimates[activeChain]?.DEFAULT_GAS_TRANSFER ?? DefaultGasEstimates.DEFAULT_GAS_TRANSFER) * - (selectedToken && isCW20Tx(selectedToken) ? 2 : 1); - - setGasEstimate(isIBCTransfer ? defaultIbcGasEstimate : defaultNonIbcGasEstimate); - const channelId = customIbcChannelId ?? ibcChannelId ?? ''; try { - const fee = getSimulationFee(feeDenom.ibcDenom ?? feeDenom.coinMinimalDenom); + if (isEthAddress(selectedAddress.address) && activeChain === 'seiDevnet') { + try { + const gasUsed = await SeiEvmTx.SimulateTransaction( + selectedAddress.address ?? '', + inputAmountNumber.toString(), + ); + setGasEstimate(gasUsed); + } catch (_) { + setGasEstimate(ARCTICE_EVM_GAS_LIMIT); + } + + return; + } + const fee = getSimulationFee(feeDenom.ibcDenom ?? feeDenom.coinMinimalDenom); const { gasUsed } = isIBCTransfer ? await simulateIbcTransfer( lcdUrl ?? '', @@ -417,6 +466,10 @@ export function useSendModule(): SendModuleType { addressError, amountError, setAddressError, + addressWarning, + setAddressWarning, + gasError, + setGasError, setAmountError, isIBCTransfer, sendDisabled, @@ -427,5 +480,6 @@ export function useSendModule(): SendModuleType { clearTxError, customIbcChannelId, setCustomIbcChannelId, + confirmSendEth, } as const; } diff --git a/packages/wallet-hooks/src/send/useSimpleSend.ts b/packages/wallet-hooks/src/send/useSimpleSend.ts index 3f0be1ec..55861597 100644 --- a/packages/wallet-hooks/src/send/useSimpleSend.ts +++ b/packages/wallet-hooks/src/send/useSimpleSend.ts @@ -10,6 +10,7 @@ import { isValidAddressWithPrefix, MayaTx, NativeDenom, + SeiEvmTx, SigningSscrt, SupportedChain, ThorTx, @@ -18,6 +19,7 @@ import { Tx, txDeclinedErrorUser, } from '@leapwallet/cosmos-wallet-sdk'; +import { EthWallet } from '@leapwallet/leap-keychain'; import * as bech32 from 'bech32'; import { BigNumber } from 'bignumber.js'; import { useCallback, useState } from 'react'; @@ -25,7 +27,14 @@ import { Wallet } from 'secretjs'; import { CosmosTxType } from '../connectors'; import { useValidateIbcChannelId } from '../ibc/useValidateIbcChannelId'; -import { useActiveChain, useActiveWalletStore, useAddressPrefixes, useChainsStore, useDenoms } from '../store'; +import { + PendingTx, + useActiveChain, + useActiveWalletStore, + useAddressPrefixes, + useChainsStore, + useDenoms, +} from '../store'; import { useChainInfo } from '../store/useChainInfo'; import { useCWTxHandler, useScrtTxHandler, useTxHandler } from '../tx'; import { WALLETTYPE } from '../types'; @@ -283,6 +292,56 @@ export const useSimpleSend = () => { [], ); + const sendTokenEth = useCallback( + async ( + fromAddress: string, + toAddress: string, + value: string, + gas: number, + wallet: EthWallet, + gasPrice?: number, + ) => { + try { + setIsSending(true); + const seiEvmTx = SeiEvmTx.GetSeiEvmClient(wallet); + const result = await seiEvmTx.sendTransaction(fromAddress, toAddress, value, gas, gasPrice); + + const pendingTx: PendingTx = { + txHash: result.hash, + img: chainInfo.chainSymbolImageUrl, + sentAmount: value.toString(), + sentTokenInfo: denoms.usei, + sentUsdValue: '', + subtitle1: `to ${sliceAddress(toAddress)}`, + title1: `Sent Sei`, + txStatus: 'success', + txType: 'send', + promise: new Promise((resolve) => { + resolve({ code: 0 } as any); + }), + }; + + return { + success: true, + pendingTx: pendingTx, + data: { + txHash: result.hash, + txType: 'send', + metadata: {}, + }, + }; + } catch (e: any) { + return { + success: false, + errors: [e.message?.slice(0, 200)], + }; + } finally { + setIsSending(false); + } + }, + [], + ); + const send = useCallback( async ({ wallet, @@ -574,6 +633,7 @@ export const useSimpleSend = () => { return { showLedgerPopup, sendTokens, + sendTokenEth, isSending, }; }; diff --git a/packages/wallet-hooks/src/store/useActiveChain.ts b/packages/wallet-hooks/src/store/useActiveChain.ts index 93af2c96..a55a579c 100644 --- a/packages/wallet-hooks/src/store/useActiveChain.ts +++ b/packages/wallet-hooks/src/store/useActiveChain.ts @@ -7,7 +7,7 @@ type ActiveChainState = { }; export const useActiveChainStore = create((set) => ({ - activeChain: process.env.APP === 'compass' ? 'seiTestnet2' : 'cosmos', + activeChain: process.env.APP?.includes('compass') ? 'seiTestnet2' : 'cosmos', setActiveChain: (chain: SupportedChain) => set(() => ({ activeChain: chain })), })); diff --git a/packages/wallet-hooks/src/store/useERC20Tokens.ts b/packages/wallet-hooks/src/store/useERC20Tokens.ts index bf8d69a8..27a23c92 100644 --- a/packages/wallet-hooks/src/store/useERC20Tokens.ts +++ b/packages/wallet-hooks/src/store/useERC20Tokens.ts @@ -16,5 +16,5 @@ export const useERC20TokensStore = create((set) => ({ export const useERC20Tokens = () => { const { erc20Tokens } = useERC20TokensStore(); - return erc20Tokens; + return erc20Tokens ?? {}; }; diff --git a/packages/wallet-hooks/src/tx/useTxHandler.ts b/packages/wallet-hooks/src/tx/useTxHandler.ts index 7ba41bdc..cb84c1d3 100644 --- a/packages/wallet-hooks/src/tx/useTxHandler.ts +++ b/packages/wallet-hooks/src/tx/useTxHandler.ts @@ -38,7 +38,10 @@ export function useTxHandler({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore return new EthermintTxHandler(lcdUrl, wallet, chainId, evmChainId); - } else if (chainInfo.chainId.toLowerCase().includes('atlantic-2')) { + } else if ( + chainInfo.chainId.toLowerCase().includes('atlantic-2') || + chainInfo.chainId.toLowerCase().includes('arctic-1') + ) { const _tx = new SeiTxHandler(lcdUrl, rpcUrl ?? '', wallet); await _tx.initClient(); return _tx; diff --git a/packages/wallet-hooks/src/types/bank.ts b/packages/wallet-hooks/src/types/bank.ts index dad6ab11..020bde7f 100644 --- a/packages/wallet-hooks/src/types/bank.ts +++ b/packages/wallet-hooks/src/types/bank.ts @@ -12,6 +12,8 @@ export type Token = { coinDecimals?: number; invalidKey?: boolean; chain?: string; + coinGeckoId?: string; + isEvm?: boolean; }; export type IbcChainInfo = { diff --git a/packages/wallet-hooks/src/utils-hooks/index.ts b/packages/wallet-hooks/src/utils-hooks/index.ts new file mode 100644 index 00000000..3b2de33c --- /dev/null +++ b/packages/wallet-hooks/src/utils-hooks/index.ts @@ -0,0 +1 @@ +export * from './useSeiLinkedAddressState'; diff --git a/packages/wallet-hooks/src/utils-hooks/useSeiLinkedAddressState.ts b/packages/wallet-hooks/src/utils-hooks/useSeiLinkedAddressState.ts new file mode 100644 index 00000000..f3e2b36b --- /dev/null +++ b/packages/wallet-hooks/src/utils-hooks/useSeiLinkedAddressState.ts @@ -0,0 +1,135 @@ +import { OfflineSigner } from '@cosmjs/proto-signing'; +import { ARCTIC_CHAIN_KEY, SeiEvmTx, SupportedChain } from '@leapwallet/cosmos-wallet-sdk'; +import { EthWallet } from '@leapwallet/leap-keychain'; +import { useCallback, useEffect, useState } from 'react'; + +import { useInvalidateSeiEvmBalance, useInvalidateTokenBalances } from '../bank'; +import { useActiveChain, useActiveWallet, useAddress, useIsCompassWallet } from '../store'; +import { WALLETTYPE } from '../types'; +import { storage, useGetStorageLayer } from '../utils'; + +export function useSeiLinkedAddressState(wallet: SeiLinkedAddressStateHookParams) { + const [addressLinkState, setAddressLinkState] = useState('loading'); + const storage = useGetStorageLayer(); + const address = useAddress(); + const invalidateSeiEvmBalance = useInvalidateSeiEvmBalance(); + const invalidateTokenBalances = useInvalidateTokenBalances(); + + const isCompassWallet = useIsCompassWallet(); + const activeWallet = useActiveWallet(); + const activeChain = useActiveChain(); + + useEffect(() => { + (async function getLinkedAddressState() { + // We might need to change this condition when sei evm launches on mainnet + if (!isCompassWallet || activeWallet?.walletType === WALLETTYPE.LEDGER) { + setAddressLinkState('unknown'); + return; + } + + const storedLinkedAddressState = await storage.get(SEI_EVM_LINKED_ADDRESS_STATE_KEY); + if (storedLinkedAddressState) { + const linkedAddressState = JSON.parse(storedLinkedAddressState); + if (linkedAddressState[address] === 'done') { + setAddressLinkState('done'); + return; + } + } + + try { + if (!(wallet instanceof EthWallet)) { + wallet = (await wallet(ARCTIC_CHAIN_KEY, true)) as unknown as EthWallet; + } + + const seiEvm = SeiEvmTx.GetSeiEvmClient(wallet as EthWallet); + const response = await seiEvm.getAssociation(); + + if (response.error) { + setAddressLinkState('pending'); + } else if (response.result) { + await updateSeiLinkedAddressStateInStore(storage, address); + setAddressLinkState('done'); + } else { + setAddressLinkState('unknown'); + } + } catch (_) { + setAddressLinkState('unknown'); + } + })(); + }, [wallet, storage, address]); + + const updateAddressLinkState = useCallback( + async function handleLinkAddressClick(setError?, onClose?) { + if (!isCompassWallet || activeWallet?.walletType === WALLETTYPE.LEDGER) return; + try { + if (!(wallet instanceof EthWallet)) { + wallet = (await wallet(ARCTIC_CHAIN_KEY, true)) as unknown as EthWallet; + } + const seiEvmTxHandler = SeiEvmTx.GetSeiEvmClient(wallet); + + const result = await seiEvmTxHandler.associateEvmAddress(); + setAddressLinkState('loading'); + + if (result.error) { + const errorMessage = result.error?.message; + const errorMessageToShow = getErrorMessageToShow(errorMessage, true); + + setError && setError(errorMessageToShow); + setAddressLinkState('error'); + } else { + setAddressLinkState('success'); + + setTimeout(async () => { + onClose && onClose(true); + setError && setError(''); + + await updateSeiLinkedAddressStateInStore(storage, address); + invalidateSeiEvmBalance(); + invalidateTokenBalances(activeChain); + setAddressLinkState('done'); + }, 2000); + } + } catch (error) { + const _error = error as Error; + const errorMessageToShow = getErrorMessageToShow(_error.message); + + setError && setError(errorMessageToShow); + setAddressLinkState('error'); + } + }, + [wallet, setAddressLinkState, address, storage, activeChain], + ); + + return { addressLinkState, setAddressLinkState, updateAddressLinkState }; +} + +export type AddressLinkState = 'unknown' | 'pending' | 'loading' | 'success' | 'error' | 'done'; +export type SeiLinkedAddressStateHookParams = + | EthWallet + | ((chain?: SupportedChain | undefined, ethWallet?: boolean | undefined) => Promise); + +export const SEI_EVM_LINKED_ADDRESS_STATE_KEY = 'sei-evm-linked-address-state'; +export const INSUFFICIENT_FUNDS_ERROR_MESSAGE = + "Ensure there's at least 1 SEI in your 0x wallet to link addresses or enough balance to cover gas fees in both Sei and 0x address."; + +export function getErrorMessageToShow(errorMessage: string, replaceColon?: boolean) { + if (errorMessage.trim().toLowerCase().includes('insufficient funds')) { + return INSUFFICIENT_FUNDS_ERROR_MESSAGE; + } else if (replaceColon) { + return errorMessage.replace(':', ''); + } + + return errorMessage; +} + +export async function updateSeiLinkedAddressStateInStore(storage: storage, address: string) { + const storedLinkedAddressState = await storage.get(SEI_EVM_LINKED_ADDRESS_STATE_KEY); + + await storage.set( + SEI_EVM_LINKED_ADDRESS_STATE_KEY, + JSON.stringify({ + ...(storedLinkedAddressState ? JSON.parse(storedLinkedAddressState) : {}), + [address]: 'done', + }), + ); +} diff --git a/packages/wallet-hooks/src/utils/useFetchERC20Tokens.ts b/packages/wallet-hooks/src/utils/useFetchERC20Tokens.ts index 0b3cca62..2e9e3952 100644 --- a/packages/wallet-hooks/src/utils/useFetchERC20Tokens.ts +++ b/packages/wallet-hooks/src/utils/useFetchERC20Tokens.ts @@ -1,14 +1,22 @@ -import { ChainInfos } from '@leapwallet/cosmos-wallet-sdk'; import { Erc20Denoms } from '@leapwallet/cosmos-wallet-sdk/dist/constants/erc20-denoms'; import { useQuery } from '@tanstack/react-query'; import { useActiveChain, useERC20TokensStore } from '../store'; -import { useGetStorageLayer } from './global-vars'; +import { cachedRemoteDataWithLastModified } from './cached-remote-data'; +import { storage, useGetStorageLayer } from './global-vars'; import { initResourceFromS3 } from './initResourceFromS3'; const ERC20_TOKENS = 'erc20-tokens'; const ERC20_TOKENS_LAST_UPDATED_AT = 'erc20-tokens-last-updated-at'; +export function getErc20TokensSupportedChains(storage: storage): Promise<{ chains: string[] }> { + return cachedRemoteDataWithLastModified({ + remoteUrl: 'https://assets.leapwallet.io/cosmos-registry/v1/denoms/erc20-chains.json', + storageKey: 'erc20-tokens-supported-chains', + storage, + }); +} + export function useFetchERC20Tokens() { const activeChain = useActiveChain(); const storage = useGetStorageLayer(); @@ -16,8 +24,10 @@ export function useFetchERC20Tokens() { useQuery( ['fetch-erc20-tokens', activeChain], - () => { - if (activeChain) { + async () => { + const { chains: erc20TokensSupportedChains } = await getErc20TokensSupportedChains(storage); + + if (erc20TokensSupportedChains.includes(activeChain)) { const resourceKey = `${activeChain}-${ERC20_TOKENS}`; const resourceURL = `https://assets.leapwallet.io/cosmos-registry/v1/denoms/${activeChain}/erc20.json`; @@ -36,7 +46,6 @@ export function useFetchERC20Tokens() { } }, { - enabled: !!activeChain && activeChain === ChainInfos.evmos.key, retry: (failureCount: number, error: any) => { if (error.response?.status === 404 || error.response?.status === 403 || error.response?.status === 429) { return false; diff --git a/packages/wallet-hooks/src/utils/useGetGasPrice.ts b/packages/wallet-hooks/src/utils/useGetGasPrice.ts index f4322dfe..0188b3d8 100644 --- a/packages/wallet-hooks/src/utils/useGetGasPrice.ts +++ b/packages/wallet-hooks/src/utils/useGetGasPrice.ts @@ -36,10 +36,10 @@ async function getSeiGasPrice(gasPriceSteps: GasPriceStepsRecord, chainId: strin const seiGasJSON = 'https://raw.githubusercontent.com/sei-protocol/chain-registry/main/gas.json'; const { data } = await axios.get(seiGasJSON); - const minGas = data[chainId]?.min_gas_price; + const minGas = data[chainId]?.min_gas_price ?? 0; const defaultGasPrice = gasPriceSteps[chainKey]?.low; - return minGas ?? defaultGasPrice; + return Math.max(minGas, defaultGasPrice); } async function getOsmosisGasPrice(lcdUrl: string, gasPriceSteps: GasPriceStepsRecord) { diff --git a/packages/wallet-hooks/src/utils/useInitAirdropsEligibilityData.ts b/packages/wallet-hooks/src/utils/useInitAirdropsEligibilityData.ts index 21725f6f..2e6c0061 100644 --- a/packages/wallet-hooks/src/utils/useInitAirdropsEligibilityData.ts +++ b/packages/wallet-hooks/src/utils/useInitAirdropsEligibilityData.ts @@ -1,5 +1,5 @@ import { fromSmall } from '@leapwallet/cosmos-wallet-sdk'; -import { useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { useAirdropsDataStore, useAirdropsEligibilityDataStore, useDenoms } from '../store'; import { queryAddresses } from './airdropsUtils'; @@ -58,7 +58,7 @@ export function useInitAirdropsEligibilityData() { const [data, setData] = useState>(); const addresses = useRef([]); - const fetchAirdropsEligibilityData = (receivedAddresses: string[]) => { + const fetchAirdropsEligibilityData = useCallback((receivedAddresses: string[]) => { addresses.current = receivedAddresses; setAirdropsEligibilityData(null); @@ -79,7 +79,7 @@ export function useInitAirdropsEligibilityData() { lastUpdatedAtKey: AIRDROPS_DATA_ELIGIBILITY_LAST_UPDATED_AT, lastUpdatedAtURL: AIRDROPS_DATA_ELIGIBILITY_LAST_UPDATED_AT_URL, }); - }; + }, []); const formatData = async () => { const airdropsMetadata: Record = {}; diff --git a/packages/wallet-provider/package.json b/packages/wallet-provider/package.json index 287bc82d..b86487b1 100644 --- a/packages/wallet-provider/package.json +++ b/packages/wallet-provider/package.json @@ -1,6 +1,6 @@ { "name": "@leapwallet/cosmos-wallet-provider", - "version": "0.9.6", + "version": "0.9.7", "description": "Cosmos Wallet Provider for Leap Wallet.", "main": "dist/index.js", "files": [ @@ -17,7 +17,7 @@ "prepublish": "yarn build" }, "dependencies": { - "@leapwallet/cosmos-wallet-sdk": "0.9.6", + "@leapwallet/cosmos-wallet-sdk": "0.9.7", "@metamask/post-message-stream": "6.0.0", "uuid": "9.0.0" }, diff --git a/packages/wallet-provider/src/provider/core-evm.ts b/packages/wallet-provider/src/provider/core-evm.ts new file mode 100644 index 00000000..b7f998a7 --- /dev/null +++ b/packages/wallet-provider/src/provider/core-evm.ts @@ -0,0 +1,67 @@ +import { WindowPostMessageStream } from '@metamask/post-message-stream'; +import { v4 as uuidv4 } from 'uuid'; + +import { + Ethereum, + ETHEREUM_METHOD_TYPE, + EthereumListenerType, + EthereumRequestMessage, + EthRequestAccountsResponse, + LINE_TYPE, +} from './types'; + +const IDENTIFIER = 'compass'; + +export class LeapEvm implements Ethereum { + public isCompass: boolean = true; + private inpageStream = new WindowPostMessageStream({ + name: `${IDENTIFIER}:inpage`, + target: `${IDENTIFIER}:content`, + }); + private origin: string = window.location.origin; + + private static generateId() { + return uuidv4(); + } + + send(method: any, data?: any): string { + const id = LeapEvm.generateId(); + + this.inpageStream.write({ + ...data, + id, + method, + }); + + return id; + } + + async request(message: EthereumRequestMessage) { + return new Promise((res, rej) => { + const { method, ...restMessage } = message; + const id = this.send(method, { ...restMessage, origin: this.origin, ecosystem: LINE_TYPE.ETHEREUM }); + + this.inpageStream.on('data', (data: any) => { + if (data.id === id) { + if (data.payload?.error) { + rej(data.payload.error); + } else { + res(data.payload.success); + } + } + }); + }); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + on(eventName: EthereumListenerType, eventHandler: (data: unknown) => void) { + // + } + + enable() { + return this.request({ + method: ETHEREUM_METHOD_TYPE.ETH__REQUEST_ACCOUNTS, + params: [], + }) as Promise; + } +} diff --git a/packages/wallet-provider/src/provider/core.ts b/packages/wallet-provider/src/provider/core.ts index 92b9ac39..745c79ec 100644 --- a/packages/wallet-provider/src/provider/core.ts +++ b/packages/wallet-provider/src/provider/core.ts @@ -26,7 +26,7 @@ import { SuggestChainInfoMsg, } from './types'; -const requester = new InExtensionMessageRequester(process.env.APP === 'compass' ? 'compass' : 'leap'); +const requester = new InExtensionMessageRequester(process.env.APP?.includes('compass') ? 'compass' : 'leap'); export class Leap implements ILeap { public defaultOptions: LeapIntereactionOptions = {}; @@ -47,7 +47,7 @@ export class Leap implements ILeap { } async experimentalSuggestChain(chainInfo: ChainInfo): Promise { - if (process.env.APP === 'compass') return; + if (process.env.APP?.includes('compass')) return; const suggestChainMessage = new SuggestChainInfoMsg(chainInfo); return await requester.experimentalSuggestChain(suggestChainMessage); } diff --git a/packages/wallet-provider/src/provider/evm-error.ts b/packages/wallet-provider/src/provider/evm-error.ts new file mode 100644 index 00000000..259d2905 --- /dev/null +++ b/packages/wallet-provider/src/provider/evm-error.ts @@ -0,0 +1,13 @@ +import { ETHEREUM_RPC_ERROR_MESSAGE } from './types'; + +export class LeapEvmRpcError extends Error { + public code: number; + public data?: unknown; + + constructor(code: number, message?: string, data?: unknown) { + super(message ?? ETHEREUM_RPC_ERROR_MESSAGE[code]); + this.code = code; + this.data = data; + this.name = 'LeapEvmRpcError'; + } +} diff --git a/packages/wallet-provider/src/provider/index.ts b/packages/wallet-provider/src/provider/index.ts index 4b0e0413..6570552f 100644 --- a/packages/wallet-provider/src/provider/index.ts +++ b/packages/wallet-provider/src/provider/index.ts @@ -1 +1,3 @@ export * from './core'; +export * from './core-evm'; +export * from './evm-error'; diff --git a/packages/wallet-provider/src/provider/types/ethereum.ts b/packages/wallet-provider/src/provider/types/ethereum.ts new file mode 100644 index 00000000..42e30dd6 --- /dev/null +++ b/packages/wallet-provider/src/provider/types/ethereum.ts @@ -0,0 +1,96 @@ +export const ETHEREUM_POPUP_METHOD_TYPE = { + ETH__REQUEST_ACCOUNTS: 'eth_requestAccounts', + ETH__SEND_TRANSACTION: 'eth_sendTransaction', + ETH__SIGN: 'eth_sign', + ETH__SIGN_TYPED_DATA_V4: 'eth_signTypedData_v4', + + PERSONAL_SIGN: 'personal_sign', + WALLET__REQUEST_PERMISSIONS: 'wallet_requestPermissions', +}; + +export const ETHEREUM_NO_POPUP_METHOD_TYPE = { + ETH__ACCOUNTS: 'eth_accounts', + ETH__CHAIN_ID: 'eth_chainId', +}; + +export type ValueOf = T[keyof T]; +export type EthereumPopupMethodType = ValueOf; +export type EthereumNoPopupMethodType = ValueOf; + +export const ETHEREUM_METHOD_TYPE = { + ...ETHEREUM_NO_POPUP_METHOD_TYPE, + ...ETHEREUM_POPUP_METHOD_TYPE, +}; +export type EthRequestAccountsResponse = string[]; + +export type EthereumTx = { + value?: string | number; + gasPrice?: string | number; + maxPriorityFeePerGas?: string | number; + maxFeePerGas?: string | number; + from?: string | number; + to?: string; + gas?: number | string; + data?: string; + nonce?: number; + v?: string | number; + r?: string | number; + s?: string | number; +}; + +export type EthRequestAccounts = { + method: typeof ETHEREUM_METHOD_TYPE.ETH__REQUEST_ACCOUNTS; + params: unknown; + id?: number | string; +}; + +export type EthSendTransactionParams = [EthereumTx]; + +export type EthSendTransaction = { + method: typeof ETHEREUM_METHOD_TYPE.ETH__SEND_TRANSACTION; + params: EthSendTransactionParams; + id?: number | string; +}; + +export type EthSign = { + method: typeof ETHEREUM_METHOD_TYPE.ETH__SIGN; + /** + * [0] - Address + * [1] - Hex-encoded UTF-8 message + */ + params: [string, string]; + id?: number | string; +}; + +export type EthSignTypedData = { + method: typeof ETHEREUM_METHOD_TYPE.ETH__SIGN_TYPED_DATA_V4; + params: [string, string]; + id?: number | string; +}; + +export type PersonalSign = { + method: typeof ETHEREUM_METHOD_TYPE.PERSONAL_SIGN; + /** + * [0] - Hex-encoded UTF-8 message + * [1] - Address + */ + params: [string, string]; + id?: number | string; +}; + +export type WalletRequestPermissions = { + method: typeof ETHEREUM_METHOD_TYPE.WALLET__REQUEST_PERMISSIONS; + params: unknown; + id?: number | string; +}; + +export const ETHEREUM_RPC_ERROR = { + USER_REJECTED_REQUEST: 4001, + INVALID_PARAMS: -32602, + INTERNAL: -32603, +}; + +export const ETHEREUM_RPC_ERROR_MESSAGE = { + [ETHEREUM_RPC_ERROR.USER_REJECTED_REQUEST]: 'User rejected the request.', + [ETHEREUM_RPC_ERROR.INVALID_PARAMS]: 'Invalid method parameter(s).', +}; diff --git a/packages/wallet-provider/src/provider/types/index.ts b/packages/wallet-provider/src/provider/types/index.ts index 8184d20f..eb4bbd53 100644 --- a/packages/wallet-provider/src/provider/types/index.ts +++ b/packages/wallet-provider/src/provider/types/index.ts @@ -2,7 +2,9 @@ export * from './bech32'; export * from './bip44'; export * from './chain-info'; export * from './currency'; +export * from './ethereum'; export * from './key'; export * from './leap'; +export * from './leap-evm'; export * from './msgs'; export * from './PostMessageStream'; diff --git a/packages/wallet-provider/src/provider/types/leap-evm.ts b/packages/wallet-provider/src/provider/types/leap-evm.ts new file mode 100644 index 00000000..ff962f13 --- /dev/null +++ b/packages/wallet-provider/src/provider/types/leap-evm.ts @@ -0,0 +1,34 @@ +import { + EthRequestAccounts, + EthSendTransaction, + EthSign, + EthSignTypedData, + PersonalSign, + ValueOf, + WalletRequestPermissions, +} from './ethereum'; + +export const ETHEREUM_LISTENER_TYPE = { + ACCOUNTS_CHANGED: 'accountsChanged', + CHAIN_CHANGED: 'chainChanged', +}; +export type EthereumListenerType = ValueOf; + +export const LINE_TYPE = { ETHEREUM: 'ETHEREUM' }; +export type LineType = ValueOf; +export type EthereumRequestMessage = + | EthRequestAccounts + | EthSendTransaction + | EthSign + | EthSignTypedData + | PersonalSign + | WalletRequestPermissions; + +export interface Ethereum { + request: (message: EthereumRequestMessage) => Promise; + enable: () => Promise; + isMetaMask?: boolean; + isCompass: boolean; + chainId?: string; + networkVersion?: string; +} diff --git a/packages/wallet-sdk/package.json b/packages/wallet-sdk/package.json index 181e9a46..aebd857c 100644 --- a/packages/wallet-sdk/package.json +++ b/packages/wallet-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@leapwallet/cosmos-wallet-sdk", - "version": "0.9.6", + "version": "0.9.7", "description": "TypeScript library to create and manage non-custodial wallets for cosmos blockchains.", "main": "dist/index.js", "scripts": { @@ -13,7 +13,9 @@ "prettier:write": "prettier -w ./src", "prepublish": "yarn build" }, - "files": ["dist/**/*"], + "files": [ + "dist/**/*" + ], "repository": { "url": "git@github.com:leapwallet/leap-cosmos.git" }, @@ -32,13 +34,15 @@ "@cosmjs/stargate": "0.29.5", "@cosmjs/tendermint-rpc": "0.29.5", "@cosmos-client/core": "0.47.4", + "@ethersproject/abi": "5.7.0", "@ethersproject/bytes": "5.7.0", "@ethersproject/hash": "5.7.0", + "@ethersproject/providers": "5.7.2", "@ethersproject/transactions": "5.7.0", "@ethersproject/wallet": "5.6.2", "@injectivelabs/networks": "1.14.6", "@injectivelabs/sdk-ts": "1.14.7", - "@leapwallet/leap-keychain": "0.2.2", + "@leapwallet/leap-keychain": "0.2.3", "@ledgerhq/hw-app-eth": "6.29.3", "@ledgerhq/hw-transport-webhid": "6.27.15", "@ledgerhq/hw-transport-webusb": "6.27.15", @@ -71,7 +75,8 @@ "parse-duration": "1.0.2", "secretjs": "1.12.0", "secure-random": "1.1.2", - "tslib": "2.4.0" + "tslib": "2.4.0", + "viem": "2.5.0" }, "devDependencies": { "@babel/node": "7.19.1", diff --git a/packages/wallet-sdk/src/constants/address-prefixes.ts b/packages/wallet-sdk/src/constants/address-prefixes.ts index b05d967f..5e7bf65b 100644 --- a/packages/wallet-sdk/src/constants/address-prefixes.ts +++ b/packages/wallet-sdk/src/constants/address-prefixes.ts @@ -104,4 +104,5 @@ export const addressPrefixes: Record = { pryzm: 'pryzmtestnet', centauri: 'composable', dym: 'dymension', + saga: 'saga', }; diff --git a/packages/wallet-sdk/src/constants/chain-infos.ts b/packages/wallet-sdk/src/constants/chain-infos.ts index 9ddd08d9..26e48894 100644 --- a/packages/wallet-sdk/src/constants/chain-infos.ts +++ b/packages/wallet-sdk/src/constants/chain-infos.ts @@ -1,5 +1,6 @@ import { defaultGasPriceStep } from './default-gasprice-step'; import { denoms, NativeDenom } from './denoms'; +import { ARCTIC_EVM_RPC_URL } from './sei-evm'; /** * @@ -86,7 +87,8 @@ export type SupportedChain = | 'dymension' | 'pryzmtestnet' | 'thorchain' - | 'odin'; + | 'odin' + | 'saga'; export type AddressPrefix = | 'cosmos' @@ -165,7 +167,8 @@ export type AddressPrefix = | 'dym' | 'pryzm' | 'thor' - | 'odin'; + | 'odin' + | 'saga'; export type Denom = | 'JUNO' @@ -245,7 +248,8 @@ export type Denom = | 'DYM' | 'PRYZM' | 'RUNE' - | 'ODIN'; + | 'ODIN' + | 'SAGA'; export type CoinType = | '118' @@ -2740,6 +2744,7 @@ export const ChainInfos: Record = { apis: { rest: 'https://rest.cosmos.directory/planq', rpc: 'https://rpc.cosmos.directory/planq', + evmJsonRpc: 'https://evm-rpc.planq.network', }, denom: 'PLQ', gasPriceStep: { @@ -2921,6 +2926,43 @@ export const ChainInfos: Record = { }, enabled: true, }, + saga: { + chainId: 'ssc-1', + chainName: 'Saga', + chainRegistryPath: 'saga', + key: 'saga', + chainSymbolImageUrl: 'https://assets.leapwallet.io/saga.svg', + bip44: { + coinType: '118', + }, + txExplorer: { + mainnet: { + name: 'Mintscan', + txUrl: 'https://www.mintscan.io/saga/txs', + accountUrl: 'https://www.mintscan.io/saga/accounts', + }, + }, + apis: { + rest: 'https://ssc-lcd.sagarpc.io', + rpc: 'https://ssc-rpc.sagarpc.io', + }, + denom: 'SAGA', + addressPrefix: 'saga', + gasPriceStep: { + low: 0.01, + average: 0.025, + high: 0.04, + }, + ibcChannelIds: {}, + nativeDenoms: { + usaga: denoms.usaga, + }, + theme: { + gradient: 'linear-gradient(180deg, rgba(50, 129, 250, 0.32) 0%, rgba(50, 129, 250, 0) 100%)', + primaryColor: '#3281fa', + }, + enabled: true, + }, secret: { chainId: 'secret-4', testnetChainId: 'pulsar-3', @@ -3055,8 +3097,10 @@ export const ChainInfos: Record = { chainSymbolImageUrl: 'https://assets.leapwallet.io/sei.png', apis: { // rpc: 'https://rpc.wallet.arctic-1.sei.io', - rpc: 'https://rpc.arctic-1.seinetwork.io', - rest: 'https://rest.wallet.arctic-1.sei.io', + rpc: 'https://rpc-arctic-1.sei-apis.com', + // rest: 'https://rest.wallet.arctic-1.sei.io', + rest: 'https://rest-arctic-1.sei-apis.com', + evmJsonRpc: ARCTIC_EVM_RPC_URL, }, denom: 'SEI', txExplorer: {}, diff --git a/packages/wallet-sdk/src/constants/chainIds.ts b/packages/wallet-sdk/src/constants/chainIds.ts index e87d5fcf..f21e8e06 100644 --- a/packages/wallet-sdk/src/constants/chainIds.ts +++ b/packages/wallet-sdk/src/constants/chainIds.ts @@ -162,4 +162,5 @@ export const chainIdToChain: Record = { 'indigo-1': 'pryzmtestnet', 'union-testnet-6': 'uniontestnet', 'blumbus_111-1': 'dymension', + 'ssc-1': 'saga', }; diff --git a/packages/wallet-sdk/src/constants/default-gasprice-step.ts b/packages/wallet-sdk/src/constants/default-gasprice-step.ts index eb5b17f9..9ddea7a2 100644 --- a/packages/wallet-sdk/src/constants/default-gasprice-step.ts +++ b/packages/wallet-sdk/src/constants/default-gasprice-step.ts @@ -116,4 +116,5 @@ export const defaultGasEstimates: Record = { pryzmtestnet: DefaultGasEstimates, thorchain: DefaultGasEstimates, odin: DefaultGasEstimates, + saga: DefaultGasEstimates, }; diff --git a/packages/wallet-sdk/src/constants/denoms.ts b/packages/wallet-sdk/src/constants/denoms.ts index fdbc4fbf..897ad866 100644 --- a/packages/wallet-sdk/src/constants/denoms.ts +++ b/packages/wallet-sdk/src/constants/denoms.ts @@ -16,6 +16,14 @@ export type NativeDenom = { export type Denoms = Record; export const denoms = { + usaga: { + coinDenom: 'SAGA', + coinMinimalDenom: 'usaga', + coinDecimals: 6, + icon: 'https://assets.leapwallet.io/saga.svg', + chain: 'saga', + coinGeckoId: 'saga-2', + }, udym: { coinDenom: 'DYM', coinDecimals: 18, @@ -779,11 +787,11 @@ export const denoms = { icon: 'https://assets.leapwallet.io/flix.png', chain: 'omniflixhub', }, - uorai: { + orai: { coinDenom: 'ORAI', - coinMinimalDenom: 'uorai', + coinMinimalDenom: 'orai', coinDecimals: 6, - coinGeckoId: 'orai', + coinGeckoId: 'oraichain-token', icon: 'https://assets.leapwallet.io/orai.svg', chain: 'oraichain', }, diff --git a/packages/wallet-sdk/src/constants/fee-denoms.ts b/packages/wallet-sdk/src/constants/fee-denoms.ts index c7463ab1..4adf6afd 100644 --- a/packages/wallet-sdk/src/constants/fee-denoms.ts +++ b/packages/wallet-sdk/src/constants/fee-denoms.ts @@ -91,6 +91,7 @@ export const feeDenoms: FeeDenoms = { pryzmtestnet: denoms.upryzm, thorchain: denoms.rune, odin: denoms.loki, + saga: denoms.usaga, }, testnet: { akash: denoms.uakt, @@ -172,5 +173,6 @@ export const feeDenoms: FeeDenoms = { pryzmtestnet: denoms.upryzm, thorchain: denoms.rune, odin: denoms.loki, + saga: denoms.usaga, }, }; diff --git a/packages/wallet-sdk/src/constants/gas-adjustments.ts b/packages/wallet-sdk/src/constants/gas-adjustments.ts index cc8fe8ad..165cbf72 100644 --- a/packages/wallet-sdk/src/constants/gas-adjustments.ts +++ b/packages/wallet-sdk/src/constants/gas-adjustments.ts @@ -85,4 +85,5 @@ export const gasAdjustments: GasAdjustments = { pryzmtestnet: 1.5, thorchain: 1.5, odin: 1.5, + saga: 1.5, }; diff --git a/packages/wallet-sdk/src/constants/gas-price-steps.ts b/packages/wallet-sdk/src/constants/gas-price-steps.ts index af4f6d84..a3a78e32 100644 --- a/packages/wallet-sdk/src/constants/gas-price-steps.ts +++ b/packages/wallet-sdk/src/constants/gas-price-steps.ts @@ -425,4 +425,5 @@ export const GAS_PRICE_STEPS = { high: 0.03, }, dymension: { low: 0.25, average: 0.4, high: 0.55 }, + saga: { low: 0.01, average: 0.025, high: 0.04 }, }; diff --git a/packages/wallet-sdk/src/constants/index.ts b/packages/wallet-sdk/src/constants/index.ts index 27eda54b..30becbe1 100644 --- a/packages/wallet-sdk/src/constants/index.ts +++ b/packages/wallet-sdk/src/constants/index.ts @@ -8,5 +8,6 @@ export * from './gas-adjustments'; export * from './gas-price-steps'; export * from './native-fee-denoms'; export * from './networks'; +export * from './sei-evm'; export * from './snip20-denoms'; export * from './staking-denoms'; diff --git a/packages/wallet-sdk/src/constants/native-fee-denoms.ts b/packages/wallet-sdk/src/constants/native-fee-denoms.ts index 2f493b7a..c5bef021 100644 --- a/packages/wallet-sdk/src/constants/native-fee-denoms.ts +++ b/packages/wallet-sdk/src/constants/native-fee-denoms.ts @@ -86,6 +86,7 @@ export const nativeFeeDenoms: FeeDenoms = { thorchain: 'rune', odin: 'loki', dymension: 'udym', + saga: 'usaga', }, testnet: { akash: 'uakt', @@ -167,5 +168,6 @@ export const nativeFeeDenoms: FeeDenoms = { thorchain: 'rune', odin: 'loki', dymension: 'udym', + saga: 'usaga', }, }; diff --git a/packages/wallet-sdk/src/constants/networks.ts b/packages/wallet-sdk/src/constants/networks.ts index 76931b86..d8fb7157 100644 --- a/packages/wallet-sdk/src/constants/networks.ts +++ b/packages/wallet-sdk/src/constants/networks.ts @@ -297,6 +297,7 @@ export const networkData: NetworkData[] = [ { enabled: true, name: 'pryzmtestnet' }, { enabled: true, name: 'thorchain' }, { enabled: true, name: 'odin' }, + { enabled: true, name: 'saga' }, ]; export default networkData; diff --git a/packages/wallet-sdk/src/constants/sei-evm/abis/abiERC1155.ts b/packages/wallet-sdk/src/constants/sei-evm/abis/abiERC1155.ts new file mode 100644 index 00000000..22e2ce55 --- /dev/null +++ b/packages/wallet-sdk/src/constants/sei-evm/abis/abiERC1155.ts @@ -0,0 +1,325 @@ +export const abiERC1155 = [ + { + inputs: [ + { + internalType: 'string', + name: 'uri_', + type: 'string', + }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'account', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + indexed: false, + internalType: 'bool', + name: 'approved', + type: 'bool', + }, + ], + name: 'ApprovalForAll', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256[]', + name: 'ids', + type: 'uint256[]', + }, + { + indexed: false, + internalType: 'uint256[]', + name: 'values', + type: 'uint256[]', + }, + ], + name: 'TransferBatch', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'from', + type: 'address', + }, + { + indexed: true, + internalType: 'address', + name: 'to', + type: 'address', + }, + { + indexed: false, + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + { + indexed: false, + internalType: 'uint256', + name: 'value', + type: 'uint256', + }, + ], + name: 'TransferSingle', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'string', + name: 'value', + type: 'string', + }, + { + indexed: true, + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + ], + name: 'URI', + type: 'event', + }, + { + inputs: [ + { + internalType: 'address', + name: 'account', + type: 'address', + }, + { + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + ], + name: 'balanceOf', + outputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address[]', + name: 'accounts', + type: 'address[]', + }, + { + internalType: 'uint256[]', + name: 'ids', + type: 'uint256[]', + }, + ], + name: 'balanceOfBatch', + outputs: [ + { + internalType: 'uint256[]', + name: '', + type: 'uint256[]', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'account', + type: 'address', + }, + { + internalType: 'address', + name: 'operator', + type: 'address', + }, + ], + name: 'isApprovedForAll', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256[]', + name: 'ids', + type: 'uint256[]', + }, + { + internalType: 'uint256[]', + name: 'amounts', + type: 'uint256[]', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + name: 'safeBatchTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'from', + type: 'address', + }, + { + internalType: 'address', + name: 'to', + type: 'address', + }, + { + internalType: 'uint256', + name: 'id', + type: 'uint256', + }, + { + internalType: 'uint256', + name: 'amount', + type: 'uint256', + }, + { + internalType: 'bytes', + name: 'data', + type: 'bytes', + }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'address', + name: 'operator', + type: 'address', + }, + { + internalType: 'bool', + name: 'approved', + type: 'bool', + }, + ], + name: 'setApprovalForAll', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [ + { + internalType: 'bytes4', + name: 'interfaceId', + type: 'bytes4', + }, + ], + name: 'supportsInterface', + outputs: [ + { + internalType: 'bool', + name: '', + type: 'bool', + }, + ], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [ + { + internalType: 'uint256', + name: '', + type: 'uint256', + }, + ], + name: 'uri', + outputs: [ + { + internalType: 'string', + name: '', + type: 'string', + }, + ], + stateMutability: 'view', + type: 'function', + }, +] as const; diff --git a/packages/wallet-sdk/src/constants/sei-evm/abis/abiERC20.ts b/packages/wallet-sdk/src/constants/sei-evm/abis/abiERC20.ts new file mode 100644 index 00000000..5e1439dc --- /dev/null +++ b/packages/wallet-sdk/src/constants/sei-evm/abis/abiERC20.ts @@ -0,0 +1,272 @@ +export const abiERC20 = [ + { + constant: true, + inputs: [], + name: 'name', + outputs: [ + { + name: '', + type: 'string', + }, + ], + payable: false, + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_spender', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + name: 'approve', + outputs: [ + { + name: 'success', + type: 'bool', + }, + ], + payable: false, + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'totalSupply', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_from', + type: 'address', + }, + { + name: '_to', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + name: 'transferFrom', + outputs: [ + { + name: 'success', + type: 'bool', + }, + ], + payable: false, + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'decimals', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'version', + outputs: [ + { + name: '', + type: 'string', + }, + ], + payable: false, + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: '_owner', + type: 'address', + }, + ], + name: 'balanceOf', + outputs: [ + { + name: 'balance', + type: 'uint256', + }, + ], + payable: false, + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'symbol', + outputs: [ + { + name: '', + type: 'string', + }, + ], + payable: false, + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_to', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + ], + name: 'transfer', + outputs: [ + { + name: 'success', + type: 'bool', + }, + ], + payable: false, + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_spender', + type: 'address', + }, + { + name: '_value', + type: 'uint256', + }, + { + name: '_extraData', + type: 'bytes', + }, + ], + name: 'approveAndCall', + outputs: [ + { + name: 'success', + type: 'bool', + }, + ], + payable: false, + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: '_owner', + type: 'address', + }, + { + name: '_spender', + type: 'address', + }, + ], + name: 'allowance', + outputs: [ + { + name: 'remaining', + type: 'uint256', + }, + ], + payable: false, + type: 'function', + }, + { + inputs: [ + { + name: '_initialAmount', + type: 'uint256', + }, + { + name: '_tokenName', + type: 'string', + }, + { + name: '_decimalUnits', + type: 'uint8', + }, + { + name: '_tokenSymbol', + type: 'string', + }, + ], + type: 'constructor', + }, + { + payable: false, + type: 'fallback', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: '_from', + type: 'address', + }, + { + indexed: true, + name: '_to', + type: 'address', + }, + { + indexed: false, + name: '_value', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: '_owner', + type: 'address', + }, + { + indexed: true, + name: '_spender', + type: 'address', + }, + { + indexed: false, + name: '_value', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, +] as const; diff --git a/packages/wallet-sdk/src/constants/sei-evm/abis/abiERC721.ts b/packages/wallet-sdk/src/constants/sei-evm/abis/abiERC721.ts new file mode 100644 index 00000000..4bf66030 --- /dev/null +++ b/packages/wallet-sdk/src/constants/sei-evm/abis/abiERC721.ts @@ -0,0 +1,376 @@ +export const abiERC721 = [ + { + constant: true, + inputs: [ + { + name: 'interfaceID', + type: 'bytes4', + }, + ], + name: 'supportsInterface', + outputs: [ + { + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'name', + outputs: [ + { + name: '_name', + type: 'string', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: '_tokenId', + type: 'uint256', + }, + ], + name: 'getApproved', + outputs: [ + { + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_approved', + type: 'address', + }, + { + name: '_tokenId', + type: 'uint256', + }, + ], + name: 'approve', + outputs: [], + payable: true, + stateMutability: 'payable', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'totalSupply', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_from', + type: 'address', + }, + { + name: '_to', + type: 'address', + }, + { + name: '_tokenId', + type: 'uint256', + }, + ], + name: 'transferFrom', + outputs: [], + payable: true, + stateMutability: 'payable', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: '_owner', + type: 'address', + }, + { + name: '_index', + type: 'uint256', + }, + ], + name: 'tokenOfOwnerByIndex', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_from', + type: 'address', + }, + { + name: '_to', + type: 'address', + }, + { + name: '_tokenId', + type: 'uint256', + }, + ], + name: 'safeTransferFrom', + outputs: [], + payable: true, + stateMutability: 'payable', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: '_index', + type: 'uint256', + }, + ], + name: 'tokenByIndex', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: '_tokenId', + type: 'uint256', + }, + ], + name: 'ownerOf', + outputs: [ + { + name: '', + type: 'address', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: '_owner', + type: 'address', + }, + ], + name: 'balanceOf', + outputs: [ + { + name: '', + type: 'uint256', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [], + name: 'symbol', + outputs: [ + { + name: '_symbol', + type: 'string', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_operator', + type: 'address', + }, + { + name: '_approved', + type: 'bool', + }, + ], + name: 'setApprovalForAll', + outputs: [], + payable: false, + stateMutability: 'nonpayable', + type: 'function', + }, + { + constant: false, + inputs: [ + { + name: '_from', + type: 'address', + }, + { + name: '_to', + type: 'address', + }, + { + name: '_tokenId', + type: 'uint256', + }, + { + name: 'data', + type: 'bytes', + }, + ], + name: 'safeTransferFrom', + outputs: [], + payable: true, + stateMutability: 'payable', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: '_tokenId', + type: 'uint256', + }, + ], + name: 'tokenURI', + outputs: [ + { + name: '', + type: 'string', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + constant: true, + inputs: [ + { + name: '_owner', + type: 'address', + }, + { + name: '_operator', + type: 'address', + }, + ], + name: 'isApprovedForAll', + outputs: [ + { + name: '', + type: 'bool', + }, + ], + payable: false, + stateMutability: 'view', + type: 'function', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: '_from', + type: 'address', + }, + { + indexed: true, + name: '_to', + type: 'address', + }, + { + indexed: true, + name: '_tokenId', + type: 'uint256', + }, + ], + name: 'Transfer', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: '_owner', + type: 'address', + }, + { + indexed: true, + name: '_approved', + type: 'address', + }, + { + indexed: true, + name: '_tokenId', + type: 'uint256', + }, + ], + name: 'Approval', + type: 'event', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: '_owner', + type: 'address', + }, + { + indexed: true, + name: '_operator', + type: 'address', + }, + { + indexed: false, + name: '_approved', + type: 'bool', + }, + ], + name: 'ApprovalForAll', + type: 'event', + }, +] as const; diff --git a/packages/wallet-sdk/src/constants/sei-evm/abis/index.ts b/packages/wallet-sdk/src/constants/sei-evm/abis/index.ts new file mode 100644 index 00000000..136c8482 --- /dev/null +++ b/packages/wallet-sdk/src/constants/sei-evm/abis/index.ts @@ -0,0 +1,3 @@ +export * from './abiERC20'; +export * from './abiERC721'; +export * from './abiERC1155'; diff --git a/packages/wallet-sdk/src/constants/sei-evm/config.ts b/packages/wallet-sdk/src/constants/sei-evm/config.ts new file mode 100644 index 00000000..0c8f6f05 --- /dev/null +++ b/packages/wallet-sdk/src/constants/sei-evm/config.ts @@ -0,0 +1,5 @@ +export const ARCTIC_COSMOS_CHAIN_ID = 'arctic-1'; +export const ARCTIC_ETH_CHAIN_ID = 713715; +export const ARCTIC_CHAIN_KEY = 'seiDevnet'; +export const ARCTIC_EVM_RPC_URL = 'https://evm-rpc.arctic-1.seinetwork.io'; +export const ARCTICE_EVM_GAS_LIMIT = 21_000; diff --git a/packages/wallet-sdk/src/constants/sei-evm/index.ts b/packages/wallet-sdk/src/constants/sei-evm/index.ts new file mode 100644 index 00000000..c79e8b7e --- /dev/null +++ b/packages/wallet-sdk/src/constants/sei-evm/index.ts @@ -0,0 +1,2 @@ +export * from './abis'; +export * from './config'; diff --git a/packages/wallet-sdk/src/constants/staking-denoms.ts b/packages/wallet-sdk/src/constants/staking-denoms.ts index 60ce18e0..53f979d3 100644 --- a/packages/wallet-sdk/src/constants/staking-denoms.ts +++ b/packages/wallet-sdk/src/constants/staking-denoms.ts @@ -85,6 +85,7 @@ export const stakingDenoms: StakingDenoms = { thorchain: ['rune'], odin: ['loki'], dymension: ['udym'], + saga: ['usaga'], }, testnet: { akash: ['uakt'], @@ -166,5 +167,6 @@ export const stakingDenoms: StakingDenoms = { thorchain: ['rune'], odin: ['loki'], dymension: ['udym'], + saga: ['usaga'], }, }; diff --git a/packages/wallet-sdk/src/ledger/ledger.ts b/packages/wallet-sdk/src/ledger/ledger.ts index 61d328df..5e5741db 100644 --- a/packages/wallet-sdk/src/ledger/ledger.ts +++ b/packages/wallet-sdk/src/ledger/ledger.ts @@ -10,7 +10,6 @@ import { Secp256k1Signature } from '@cosmjs/crypto'; import { Algo } from '@cosmjs/proto-signing'; import * as bytes from '@ethersproject/bytes'; import { _TypedDataEncoder as TypedDataEncoder } from '@ethersproject/hash'; -import { verifyTypedData } from '@ethersproject/wallet'; import { encodeSecp256k1Signature, Secp256k1 } from '@leapwallet/leap-keychain'; import EthereumApp from '@ledgerhq/hw-app-eth'; import Transport from '@ledgerhq/hw-transport'; diff --git a/packages/wallet-sdk/src/tokens/balances.ts b/packages/wallet-sdk/src/tokens/balances.ts index 2336ca9e..11e5ccee 100644 --- a/packages/wallet-sdk/src/tokens/balances.ts +++ b/packages/wallet-sdk/src/tokens/balances.ts @@ -1,7 +1,6 @@ import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate'; import { createProtobufRpcClient, QueryClient, StargateClient } from '@cosmjs/stargate'; import { Tendermint34Client } from '@cosmjs/tendermint-rpc'; -import { evmosToEth } from '@evmos/address-converter'; import BigNumber from 'bignumber.js'; import { QueryClientImpl, QueryDenomTraceResponse } from 'cosmjs-types/ibc/applications/transfer/v1/query'; import { Contract, ethers } from 'ethers'; @@ -9,6 +8,7 @@ import { Contract, ethers } from 'ethers'; import { ChainInfo, ChainInfos, SupportedChain } from '../constants'; import { axiosWrapper } from '../healthy-nodes'; import { BalancesResponse } from '../types/bank'; +import { formatEtherValue } from '../utils'; export type IQueryDenomTraceResponse = QueryDenomTraceResponse; @@ -43,6 +43,12 @@ export async function fetchAllBalances(rpcUrl: string, address: string) { }); } +export async function fetchSeiEvmBalances(evmJsonRpc: string, ethWalletAddress: string) { + const provider = new ethers.providers.JsonRpcProvider(evmJsonRpc); + const balance = await provider.getBalance(ethWalletAddress); + return { denom: 'usei', amount: formatEtherValue(balance.toString()) }; +} + export async function fetchCW20Balances(rpcUrl: string, address: string, cw20Tokens: Array) { const client = await CosmWasmClient.connect(rpcUrl); const promises = cw20Tokens.map(async (tokenAddress) => { @@ -65,10 +71,9 @@ export async function fetchCW20Balances(rpcUrl: string, address: string, cw20Tok return fulfilledBalances; } -export async function fetchERC20Balances(evmJsonRpc: string, walletAddress: string, erc20Tokens: Array) { +export async function fetchERC20Balances(evmJsonRpc: string, ethWalletAddress: string, erc20Tokens: Array) { const provider = new ethers.providers.JsonRpcProvider(evmJsonRpc); const contractAbi = ['function balanceOf(address account) view returns (uint256)']; - const ethWalletAddress = evmosToEth(walletAddress); const promises = erc20Tokens.map(async (tokenAddress) => { const contract = new Contract(tokenAddress, contractAbi, provider); diff --git a/packages/wallet-sdk/src/tx/index.ts b/packages/wallet-sdk/src/tx/index.ts index 208d7c78..044d4d03 100644 --- a/packages/wallet-sdk/src/tx/index.ts +++ b/packages/wallet-sdk/src/tx/index.ts @@ -4,6 +4,7 @@ export * from './injectiveTx'; export * from './mayaTx'; export * from './msgs'; export * from './nft-transfer'; +export * from './seiEvmTx'; export * from './seiTx'; export * from './simulate'; export * from './strideTx'; diff --git a/packages/wallet-sdk/src/tx/seiEvmTx.ts b/packages/wallet-sdk/src/tx/seiEvmTx.ts new file mode 100644 index 00000000..a250fa4c --- /dev/null +++ b/packages/wallet-sdk/src/tx/seiEvmTx.ts @@ -0,0 +1,163 @@ +import { BytesLike } from '@ethersproject/bytes'; +import { JsonRpcProvider } from '@ethersproject/providers'; +import { parseEther } from '@ethersproject/units'; +import { EthWallet, pubkeyToAddress } from '@leapwallet/leap-keychain'; +import { hashPersonalMessage, intToHex } from 'ethereumjs-util'; + +import { ARCTIC_ETH_CHAIN_ID } from '../constants'; +import { ARCTIC_EVM_RPC_URL } from '../constants'; +import { trimLeadingZeroes } from '../utils'; + +export type JsonRpcResponse = { + jsonrpc: string; + id: number; + result?: string; + error?: { code: number; message: string }; +}; + +export class SeiEvmTx { + private constructor(private rpc: string, private wallet: EthWallet) {} + + public static GetSeiEvmClient(wallet: EthWallet, rpc?: string) { + const rpcUrl = rpc || ARCTIC_EVM_RPC_URL; + const chainId = ARCTIC_ETH_CHAIN_ID; + const provider = new JsonRpcProvider(rpcUrl, chainId); + wallet.setProvider(provider); + return new SeiEvmTx(rpcUrl, wallet); + } + + async sendTransaction( + bech32Address: string, + toAddress: string, + value: string, + gas: number, + gasPrice?: number, + data?: BytesLike, + ) { + const weiValue = parseEther(value); + return this.wallet.sendTransaction({ + to: toAddress, + value: weiValue.toHexString(), + gasLimit: intToHex(gas), + gasPrice: intToHex(gasPrice ?? 1000_000_000), + data, + }); + } + + public static async SimulateTransaction( + toAddress: string, + value: string, + rpc?: string, + data?: BytesLike, + gasAdjustment?: number, + ) { + const rpcUrl = rpc || ARCTIC_EVM_RPC_URL; + const txParams: { to: string; value?: string; data?: BytesLike } = { + to: toAddress, + }; + + if (Number(value)) { + const weiValue = parseEther(value); + txParams.value = trimLeadingZeroes(weiValue.toHexString(), true); + } + + if (data) { + txParams.data = data; + } + + const res = await fetch(rpcUrl, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + id: 1, + jsonrpc: '2.0', + method: 'eth_estimateGas', + params: [txParams], + }), + }); + + const responseData = await res.json(); + if (responseData?.error) { + throw new Error(responseData.error.message); + } + + const gasEstimate = Number(responseData.result); + if (gasAdjustment) { + return gasEstimate * gasAdjustment; + } + + return gasEstimate; + } + + async associateEvmAddress() { + const accounts = await this.wallet.getAccounts(); + const account = accounts[0]; + + const data = Buffer.alloc(32); + const msgHash = hashPersonalMessage(data); + + const signature = this.wallet.sign(account.address, msgHash); + const compactV = Number(signature.v) - 27; + + const params = { + r: signature.r, + s: signature.s, + v: `0x${compactV}`, + custom_message: `\x19Ethereum Signed Message:\n32${data.toString()}`, + }; + const response = await this.submitAssociationTx(params); + return response; + } + + async submitAssociationTx(params: { + r: string; + s: string; + v: string; + custom_message: string; + }): Promise { + const res = await fetch(this.rpc, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + id: 1, + jsonrpc: '2.0', + method: 'sei_associate', + params: [params], + }), + }); + + return res.json(); + } + + async getAssociation(): Promise<{ + jsonrpc: string; + id: number; + result?: string; + error?: { code: number; message: string }; + }> { + const accounts = await this.wallet.getAccounts(); + const pubKey = accounts[0].pubkey; + const seiAddress = pubkeyToAddress('sei', pubKey); + + const res = await fetch(this.rpc, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + id: 1, + jsonrpc: '2.0', + method: 'sei_getEVMAddress', + params: [seiAddress], + }), + }); + return res.json(); + } +} diff --git a/packages/wallet-sdk/src/utils/address-conversion.ts b/packages/wallet-sdk/src/utils/address-conversion.ts index fddcb78a..07478b5e 100644 --- a/packages/wallet-sdk/src/utils/address-conversion.ts +++ b/packages/wallet-sdk/src/utils/address-conversion.ts @@ -1,5 +1,7 @@ +import { fromBase64 } from '@cosmjs/encoding'; +import { Secp256k1 } from '@leapwallet/leap-keychain'; import bech32 from 'bech32'; -import { Address } from 'ethereumjs-util'; +import { Address, pubToAddress } from 'ethereumjs-util'; function toHex(array: Uint8Array) { return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join(''); @@ -19,3 +21,18 @@ export function getBech32Address(prefix: string, address: string): string { return bech32.encode(prefix, bech32.toWords(addressBuffer)); } + +export function getSeiEvmAddress(decompressedPubKey: Uint8Array): string { + const address = pubToAddress(Buffer.from(decompressedPubKey), true); + return `0x${address.toString('hex')}`; +} + +export function getSeiEvmAddressToShow(pubKeyString: string | undefined): string { + const pubKeyBytes = pubKeyString ? fromBase64(pubKeyString) : null; + + const seiEvmAddress = pubKeyBytes + ? getSeiEvmAddress(Secp256k1.publicKeyConvert(pubKeyBytes, false)) + : 'Unable to show EVM address'; + + return seiEvmAddress; +} diff --git a/packages/wallet-sdk/src/utils/index.ts b/packages/wallet-sdk/src/utils/index.ts index d340cdca..8c16142c 100644 --- a/packages/wallet-sdk/src/utils/index.ts +++ b/packages/wallet-sdk/src/utils/index.ts @@ -5,6 +5,7 @@ export * from './getBaseAccount'; export * from './getErrorMessageFromCode'; export { getRestUrl } from './getRestURL'; export * from './mathUtil'; +export * from './sei-evm'; export * from './sleep'; export * from './token-converter'; export * from './validate-ibc-channel'; diff --git a/packages/wallet-sdk/src/utils/sei-evm.ts b/packages/wallet-sdk/src/utils/sei-evm.ts new file mode 100644 index 00000000..69592952 --- /dev/null +++ b/packages/wallet-sdk/src/utils/sei-evm.ts @@ -0,0 +1,104 @@ +import { Interface } from '@ethersproject/abi'; +import { arrayify } from '@ethersproject/bytes'; +import { formatEther, parseEther } from '@ethersproject/units'; +import { EthWallet } from '@leapwallet/leap-keychain'; +import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util'; +import { hashPersonalMessage, isHexString, stripHexPrefix, toBuffer, toRpcSig } from 'ethereumjs-util'; +import { Contract, ethers } from 'ethers'; + +import { abiERC20, abiERC721, abiERC1155, ARCTIC_ETH_CHAIN_ID, ARCTIC_EVM_RPC_URL } from '../constants'; + +const erc20Interface = new Interface(abiERC20); +const erc721Interface = new Interface(abiERC721); +const erc1155Interface = new Interface(abiERC1155); + +export function parseStandardTokenTransactionData(data: string) { + try { + return erc20Interface.parseTransaction({ data }); + } catch { + // ignore and next try to parse with erc721 ABI + } + + try { + return erc721Interface.parseTransaction({ data }); + } catch { + // ignore and next try to parse with erc1155 ABI + } + + try { + return erc1155Interface.parseTransaction({ data }); + } catch { + // ignore and return undefined + } + + return undefined; +} + +export function parseEtherValue(value: string) { + return parseEther(value).toString(); +} + +export function formatEtherValue(value: string) { + return formatEther(value); +} + +export function formatEtherUnits(value: string, decimals: number) { + return ethers.utils.formatUnits(value, decimals); +} + +export function trimLeadingZeroes(value: string, isHex?: boolean) { + function removeLeadingZeroes(value: string) { + return value.replace(/^0+/, ''); + } + + if (isHex && value.toLowerCase().startsWith('0x')) { + value = value.slice(2); + return `0x${removeLeadingZeroes(value)}`; + } + + return removeLeadingZeroes(value); +} + +export async function getErc20TokenDetails(contractAddress: string) { + const provider = new ethers.providers.JsonRpcProvider(ARCTIC_EVM_RPC_URL, ARCTIC_ETH_CHAIN_ID); + const contract = new Contract(contractAddress, abiERC20, provider); + const [name, symbol, decimals] = await Promise.all([contract.name(), contract.symbol(), contract.decimals()]); + return { name, symbol, decimals: Number(decimals) }; +} + +export function encodedUtf8HexToText(hexValue: string) { + try { + const strippedHexValue = stripHexPrefix(hexValue); + const hexBuffer = Buffer.from(strippedHexValue, 'hex'); + return hexBuffer.length === 32 ? hexBuffer : hexBuffer.toString('utf8'); + } catch (_) { + return hexValue; + } +} + +export function personalSign(data: string, signerAddress: string, wallet: EthWallet) { + const message = isHexString(data) ? toBuffer(data) : Buffer.from(data); + const msgHash = hashPersonalMessage(message); + const signature = wallet.sign(signerAddress, msgHash); + + const rpcSigArgs = { + v: signature.v, + r: Buffer.from(arrayify(signature.r)), + s: Buffer.from(arrayify(signature.s)), + }; + const rpcSignature = toRpcSig(rpcSigArgs.v, rpcSigArgs.r, rpcSigArgs.s); + return rpcSignature; +} + +export function signTypedData(data: any, signerAddress: string, wallet: EthWallet) { + const messageHash = TypedDataUtils.eip712Hash(data, SignTypedDataVersion.V4); + const signature = wallet.sign(signerAddress, messageHash); + + const rpcSigArgs = { + v: signature.v, + r: Buffer.from(arrayify(signature.r)), + s: Buffer.from(arrayify(signature.s)), + }; + const rpcSignature = toRpcSig(rpcSigArgs.v, rpcSigArgs.r, rpcSigArgs.s); + return rpcSignature; +} diff --git a/packages/wallet-sdk/src/utils/token-converter.ts b/packages/wallet-sdk/src/utils/token-converter.ts index 0bb185f2..ced11eb4 100644 --- a/packages/wallet-sdk/src/utils/token-converter.ts +++ b/packages/wallet-sdk/src/utils/token-converter.ts @@ -12,3 +12,7 @@ export function fromSmall(quantity: string, decimals: number = defaultDecimals): export function fromSmallBN(quantity: string, decimals: number = defaultDecimals): BN { return new BN(quantity).div(Math.pow(10, decimals)); } + +export function toSmallBN(quantity: string, decimals: number = defaultDecimals): BN { + return new BN(quantity).times(Math.pow(10, decimals)); +} diff --git a/yarn.lock b/yarn.lock index 0fae5e1c..57730060 100644 --- a/yarn.lock +++ b/yarn.lock @@ -48,109 +48,126 @@ resolved "https://registry.yarnpkg.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz#2e22e830d36f0a9cf2c0ccd3c7f6d59435b77dfa" integrity sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ== -"@algolia/cache-browser-local-storage@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.22.1.tgz#14b6dc9abc9e3a304a5fffb063d15f30af1032d1" - integrity sha512-Sw6IAmOCvvP6QNgY9j+Hv09mvkvEIDKjYW8ow0UDDAxSXy664RBNQk3i/0nt7gvceOJ6jGmOTimaZoY1THmU7g== - dependencies: - "@algolia/cache-common" "4.22.1" - -"@algolia/cache-common@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.22.1.tgz#c625dff4bc2a74e79f9aed67b4e053b0ef1b3ec1" - integrity sha512-TJMBKqZNKYB9TptRRjSUtevJeQVXRmg6rk9qgFKWvOy8jhCPdyNZV1nB3SKGufzvTVbomAukFR8guu/8NRKBTA== - -"@algolia/cache-in-memory@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.22.1.tgz#858a3d887f521362e87d04f3943e2810226a0d71" - integrity sha512-ve+6Ac2LhwpufuWavM/aHjLoNz/Z/sYSgNIXsinGofWOysPilQZPUetqLj8vbvi+DHZZaYSEP9H5SRVXnpsNNw== - dependencies: - "@algolia/cache-common" "4.22.1" - -"@algolia/client-account@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.22.1.tgz#a7fb8b66b9a4f0a428e1426b2561144267d76d43" - integrity sha512-k8m+oegM2zlns/TwZyi4YgCtyToackkOpE+xCaKCYfBfDtdGOaVZCM5YvGPtK+HGaJMIN/DoTL8asbM3NzHonw== - dependencies: - "@algolia/client-common" "4.22.1" - "@algolia/client-search" "4.22.1" - "@algolia/transporter" "4.22.1" - -"@algolia/client-analytics@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.22.1.tgz#506558740b4d49b1b1e3393861f729a8ce921851" - integrity sha512-1ssi9pyxyQNN4a7Ji9R50nSdISIumMFDwKNuwZipB6TkauJ8J7ha/uO60sPJFqQyqvvI+px7RSNRQT3Zrvzieg== - dependencies: - "@algolia/client-common" "4.22.1" - "@algolia/client-search" "4.22.1" - "@algolia/requester-common" "4.22.1" - "@algolia/transporter" "4.22.1" - -"@algolia/client-common@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.22.1.tgz#042b19c1b6157c485fa1b551349ab313944d2b05" - integrity sha512-IvaL5v9mZtm4k4QHbBGDmU3wa/mKokmqNBqPj0K7lcR8ZDKzUorhcGp/u8PkPC/e0zoHSTvRh7TRkGX3Lm7iOQ== - dependencies: - "@algolia/requester-common" "4.22.1" - "@algolia/transporter" "4.22.1" - -"@algolia/client-personalization@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.22.1.tgz#ff088d797648224fb582e9fe5828f8087835fa3d" - integrity sha512-sl+/klQJ93+4yaqZ7ezOttMQ/nczly/3GmgZXJ1xmoewP5jmdP/X/nV5U7EHHH3hCUEHeN7X1nsIhGPVt9E1cQ== - dependencies: - "@algolia/client-common" "4.22.1" - "@algolia/requester-common" "4.22.1" - "@algolia/transporter" "4.22.1" - -"@algolia/client-search@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.22.1.tgz#508cc6ab3d1f4e9c02735a630d4dff6fbb8514a2" - integrity sha512-yb05NA4tNaOgx3+rOxAmFztgMTtGBi97X7PC3jyNeGiwkAjOZc2QrdZBYyIdcDLoI09N0gjtpClcackoTN0gPA== - dependencies: - "@algolia/client-common" "4.22.1" - "@algolia/requester-common" "4.22.1" - "@algolia/transporter" "4.22.1" - -"@algolia/logger-common@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.22.1.tgz#79cf4cd295de0377a94582c6aaac59b1ded731d9" - integrity sha512-OnTFymd2odHSO39r4DSWRFETkBufnY2iGUZNrMXpIhF5cmFE8pGoINNPzwg02QLBlGSaLqdKy0bM8S0GyqPLBg== - -"@algolia/logger-console@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.22.1.tgz#0355345f6940f67aaa78ae9b81c06e44e49f2336" - integrity sha512-O99rcqpVPKN1RlpgD6H3khUWylU24OXlzkavUAMy6QZd1776QAcauE3oP8CmD43nbaTjBexZj2nGsBH9Tc0FVA== - dependencies: - "@algolia/logger-common" "4.22.1" - -"@algolia/requester-browser-xhr@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.22.1.tgz#f04df6fe9690a071b267c77d26b83a3be9280361" - integrity sha512-dtQGYIg6MteqT1Uay3J/0NDqD+UciHy3QgRbk7bNddOJu+p3hzjTRYESqEnoX/DpEkaNYdRHUKNylsqMpgwaEw== - dependencies: - "@algolia/requester-common" "4.22.1" - -"@algolia/requester-common@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.22.1.tgz#27be35f3718aafcb6b388ff9c3aa2defabd559ff" - integrity sha512-dgvhSAtg2MJnR+BxrIFqlLtkLlVVhas9HgYKMk2Uxiy5m6/8HZBL40JVAMb2LovoPFs9I/EWIoFVjOrFwzn5Qg== - -"@algolia/requester-node-http@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.22.1.tgz#589a6fa828ad0f325e727a6fcaf4e1a2343cc62b" - integrity sha512-JfmZ3MVFQkAU+zug8H3s8rZ6h0ahHZL/SpMaSasTCGYR5EEJsCc8SI5UZ6raPN2tjxa5bxS13BRpGSBUens7EA== - dependencies: - "@algolia/requester-common" "4.22.1" - -"@algolia/transporter@4.22.1": - version "4.22.1" - resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.22.1.tgz#8843841b857dc021668f31647aa557ff19cd9cb1" - integrity sha512-kzWgc2c9IdxMa3YqA6TN0NW5VrKYYW/BELIn7vnLyn+U/RFdZ4lxxt9/8yq3DKV5snvoDzzO4ClyejZRdV3lMQ== - dependencies: - "@algolia/cache-common" "4.22.1" - "@algolia/logger-common" "4.22.1" - "@algolia/requester-common" "4.22.1" +"@algolia/cache-browser-local-storage@4.23.2": + version "4.23.2" + resolved "https://registry.yarnpkg.com/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.23.2.tgz#060d15e89588fcac18e73643201fce0f4f7d5ca0" + integrity sha512-PvRQdCmtiU22dw9ZcTJkrVKgNBVAxKgD0/cfiqyxhA5+PHzA2WDt6jOmZ9QASkeM2BpyzClJb/Wr1yt2/t78Kw== + dependencies: + "@algolia/cache-common" "4.23.2" + +"@algolia/cache-common@4.23.2": + version "4.23.2" + resolved "https://registry.yarnpkg.com/@algolia/cache-common/-/cache-common-4.23.2.tgz#c68706ce34b18377e56e71ac13cce2dd5662dcee" + integrity sha512-OUK/6mqr6CQWxzl/QY0/mwhlGvS6fMtvEPyn/7AHUx96NjqDA4X4+Ju7aXFQKh+m3jW9VPB0B9xvEQgyAnRPNw== + +"@algolia/cache-in-memory@4.23.2": + version "4.23.2" + resolved "https://registry.yarnpkg.com/@algolia/cache-in-memory/-/cache-in-memory-4.23.2.tgz#94cd828275d7a12186959bf1b95a13247e103b23" + integrity sha512-rfbi/SnhEa3MmlqQvgYz/9NNJ156NkU6xFxjbxBtLWnHbpj+qnlMoKd+amoiacHRITpajg6zYbLM9dnaD3Bczw== + dependencies: + "@algolia/cache-common" "4.23.2" + +"@algolia/client-account@4.23.2": + version "4.23.2" + resolved "https://registry.yarnpkg.com/@algolia/client-account/-/client-account-4.23.2.tgz#b53cb14e730fd8e0a0a227cf650b287b570a08bc" + integrity sha512-VbrOCLIN/5I7iIdskSoSw3uOUPF516k4SjDD4Qz3BFwa3of7D9A0lzBMAvQEJJEPHWdVraBJlGgdJq/ttmquJQ== + dependencies: + "@algolia/client-common" "4.23.2" + "@algolia/client-search" "4.23.2" + "@algolia/transporter" "4.23.2" + +"@algolia/client-analytics@4.23.2": + version "4.23.2" + resolved "https://registry.yarnpkg.com/@algolia/client-analytics/-/client-analytics-4.23.2.tgz#7fdcf1cb27f0ae93e5da6beb4e612fc06a880b0c" + integrity sha512-lLj7irsAztGhMoEx/SwKd1cwLY6Daf1Q5f2AOsZacpppSvuFvuBrmkzT7pap1OD/OePjLKxicJS8wNA0+zKtuw== + dependencies: + "@algolia/client-common" "4.23.2" + "@algolia/client-search" "4.23.2" + "@algolia/requester-common" "4.23.2" + "@algolia/transporter" "4.23.2" + +"@algolia/client-common@4.23.2": + version "4.23.2" + resolved "https://registry.yarnpkg.com/@algolia/client-common/-/client-common-4.23.2.tgz#e5f86fc2de707eb6bf9f1109b70187dae179c72c" + integrity sha512-Q2K1FRJBern8kIfZ0EqPvUr3V29ICxCm/q42zInV+VJRjldAD9oTsMGwqUQ26GFMdFYmqkEfCbY4VGAiQhh22g== + dependencies: + "@algolia/requester-common" "4.23.2" + "@algolia/transporter" "4.23.2" + +"@algolia/client-personalization@4.23.2": + version "4.23.2" + resolved "https://registry.yarnpkg.com/@algolia/client-personalization/-/client-personalization-4.23.2.tgz#0472d9c207402eefcc9c98f7ffba5d26fe8e2fd0" + integrity sha512-vwPsgnCGhUcHhhQG5IM27z8q7dWrN9itjdvgA6uKf2e9r7vB+WXt4OocK0CeoYQt3OGEAExryzsB8DWqdMK5wg== + dependencies: + "@algolia/client-common" "4.23.2" + "@algolia/requester-common" "4.23.2" + "@algolia/transporter" "4.23.2" + +"@algolia/client-search@4.23.2": + version "4.23.2" + resolved "https://registry.yarnpkg.com/@algolia/client-search/-/client-search-4.23.2.tgz#9b2741f0a209596459f06a44583118207ea287f7" + integrity sha512-CxSB29OVGSE7l/iyoHvamMonzq7Ev8lnk/OkzleODZ1iBcCs3JC/XgTIKzN/4RSTrJ9QybsnlrN/bYCGufo7qw== + dependencies: + "@algolia/client-common" "4.23.2" + "@algolia/requester-common" "4.23.2" + "@algolia/transporter" "4.23.2" + +"@algolia/logger-common@4.23.2": + version "4.23.2" + resolved "https://registry.yarnpkg.com/@algolia/logger-common/-/logger-common-4.23.2.tgz#5441a828f0fad1ceaae3a27caec7b663d40dd27f" + integrity sha512-jGM49Q7626cXZ7qRAWXn0jDlzvoA1FvN4rKTi1g0hxKsTTSReyYk0i1ADWjChDPl3Q+nSDhJuosM2bBUAay7xw== + +"@algolia/logger-console@4.23.2": + version "4.23.2" + resolved "https://registry.yarnpkg.com/@algolia/logger-console/-/logger-console-4.23.2.tgz#fda4252bb02df7c52a92c63f1e357bf7370cc8db" + integrity sha512-oo+lnxxEmlhTBTFZ3fGz1O8PJ+G+8FiAoMY2Qo3Q4w23xocQev6KqDTA1JQAGPDxAewNA2VBwWOsVXeXFjrI/Q== + dependencies: + "@algolia/logger-common" "4.23.2" + +"@algolia/recommend@4.23.2": + version "4.23.2" + resolved "https://registry.yarnpkg.com/@algolia/recommend/-/recommend-4.23.2.tgz#02bf57f836ced2c850633239d493a0414be76a7f" + integrity sha512-Q75CjnzRCDzgIlgWfPnkLtrfF4t82JCirhalXkSSwe/c1GH5pWh4xUyDOR3KTMo+YxxX3zTlrL/FjHmUJEWEcg== + dependencies: + "@algolia/cache-browser-local-storage" "4.23.2" + "@algolia/cache-common" "4.23.2" + "@algolia/cache-in-memory" "4.23.2" + "@algolia/client-common" "4.23.2" + "@algolia/client-search" "4.23.2" + "@algolia/logger-common" "4.23.2" + "@algolia/logger-console" "4.23.2" + "@algolia/requester-browser-xhr" "4.23.2" + "@algolia/requester-common" "4.23.2" + "@algolia/requester-node-http" "4.23.2" + "@algolia/transporter" "4.23.2" + +"@algolia/requester-browser-xhr@4.23.2": + version "4.23.2" + resolved "https://registry.yarnpkg.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.23.2.tgz#2d0a6b642e2a2bbfb2e2ff3d1e82158e3e143def" + integrity sha512-TO9wLlp8+rvW9LnIfyHsu8mNAMYrqNdQ0oLF6eTWFxXfxG3k8F/Bh7nFYGk2rFAYty4Fw4XUtrv/YjeNDtM5og== + dependencies: + "@algolia/requester-common" "4.23.2" + +"@algolia/requester-common@4.23.2": + version "4.23.2" + resolved "https://registry.yarnpkg.com/@algolia/requester-common/-/requester-common-4.23.2.tgz#9c2e5da4dc15e65f9b9bbe5bedb419cf23092ef1" + integrity sha512-3EfpBS0Hri0lGDB5H/BocLt7Vkop0bTTLVUBB844HH6tVycwShmsV6bDR7yXbQvFP1uNpgePRD3cdBCjeHmk6Q== + +"@algolia/requester-node-http@4.23.2": + version "4.23.2" + resolved "https://registry.yarnpkg.com/@algolia/requester-node-http/-/requester-node-http-4.23.2.tgz#718ae71f58949eab3b5fcfc440be42af41bd640f" + integrity sha512-SVzgkZM/malo+2SB0NWDXpnT7nO5IZwuDTaaH6SjLeOHcya1o56LSWXk+3F3rNLz2GVH+I/rpYKiqmHhSOjerw== + dependencies: + "@algolia/requester-common" "4.23.2" + +"@algolia/transporter@4.23.2": + version "4.23.2" + resolved "https://registry.yarnpkg.com/@algolia/transporter/-/transporter-4.23.2.tgz#61e7b9288d4f561b2015ddde689ba31e08c21644" + integrity sha512-GY3aGKBy+8AK4vZh8sfkatDciDVKad5rTY2S10Aefyjh7e7UGBP4zigf42qVXwU8VOPwi7l/L7OACGMOFcjB0Q== + dependencies: + "@algolia/cache-common" "4.23.2" + "@algolia/logger-common" "4.23.2" + "@algolia/requester-common" "4.23.2" "@ampproject/remapping@^2.1.0", "@ampproject/remapping@^2.2.0": version "2.3.0" @@ -161,9 +178,9 @@ "@jridgewell/trace-mapping" "^0.3.24" "@apollo/client@^3.5.8": - version "3.9.8" - resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.9.8.tgz#d038ce72e26e75ab3624669664d79bc1dc842b15" - integrity sha512-ausPftEb2xAUkZqz+VkSSIhNxKraShJXdV2/NJ7JbHAAciGsFlapGtZ++b7lF0/+3Jp/p34g/i6dvO8b4WjQig== + version "3.9.10" + resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.9.10.tgz#f381f67f3559cb5f5b66ce9183f84f49616acbe4" + integrity sha512-w8i/Lk1P0vvWZF0Xb00XPonn79/0rgRJ1vopBlVudVuy9QP29/NZXK0rI2xJIN6VrKuEqJZaVGJC+7k23I2sfA== dependencies: "@graphql-typed-document-node/core" "^3.1.1" "@wry/caches" "^1.0.0" @@ -3455,10 +3472,10 @@ crypto-js "4.1.1" typedi "0.10.0" -"@leapwallet/leap-keychain@0.2.2": - version "0.2.2" - resolved "https://npm.pkg.github.com/download/@leapwallet/leap-keychain/0.2.2/f58cb6a2138eeb3f34c78795c3f10a9c615a8358#f58cb6a2138eeb3f34c78795c3f10a9c615a8358" - integrity sha512-uxbijcLaKci9bNB6n5oB1k2cUjmg6fKJqdPMQMXhNWK1Q5UKNZknERvcMYX6DPFt1rcfYYd8MvfBaWX/5LpEhg== +"@leapwallet/leap-keychain@0.2.3": + version "0.2.3" + resolved "https://npm.pkg.github.com/download/@leapwallet/leap-keychain/0.2.3/1a794f341dc27609f9eab8d86a2aa02e9f75f8ee#1a794f341dc27609f9eab8d86a2aa02e9f75f8ee" + integrity sha512-70Ilbz3XyZjNcMEuLExw917Kzr3lGFWbREd1PfLkud9OEnycBa29S88WG/H9neNHrAl9Y9p75qkZzA/SXK37Xw== dependencies: "@ethersproject/bytes" "5.7.0" "@ethersproject/hdnode" "5.7.0" @@ -3486,16 +3503,6 @@ react "17.0.2" react-dom "17.0.2" -"@leapwallet/leap-ui@0.1.9": - version "0.1.9" - resolved "https://npm.pkg.github.com/download/@leapwallet/leap-ui/0.1.9/fc8e49c2a0ff5c1adc522b68ee890f87c2143e08#fc8e49c2a0ff5c1adc522b68ee890f87c2143e08" - integrity sha512-RPal043+IFcvTVK2/8PLX7nv+pZxOkFQh5M9yxb/a6jcyw4XgrX+gn4tOogGqtWrkkdZS42+TMkHNW2X9f7RaQ== - dependencies: - classnames "2.3.1" - qr-code-styling "1.6.0-rc.1" - react "17.0.2" - react-dom "17.0.2" - "@leapwallet/name-matcha@1.6.0": version "1.6.0" resolved "https://npm.pkg.github.com/download/@leapwallet/name-matcha/1.6.0/00ef8470404016e41b4e156167bf3eb347fc548f#00ef8470404016e41b4e156167bf3eb347fc548f" @@ -3680,9 +3687,9 @@ integrity sha512-ExDoj1QV5eC6TEbMdLUMMk9cfvNKhhv5gXol4SmULRVCx/3iyCPhJ74nsb3S0Vb+/f+XujBEj3vQn5+cwS0fNA== "@leichtgewicht/ip-codec@^2.0.1": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" - integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== + version "2.0.5" + resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1" + integrity sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw== "@lerna/child-process@7.1.5": version "7.1.5" @@ -4933,9 +4940,9 @@ buffer "~6.0.3" "@solana/web3.js@^1.70.1": - version "1.91.1" - resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.91.1.tgz#d49d2f982b52070be3b987fd8d892fcbddd064b5" - integrity sha512-cPgjZXm688oM9cULvJ8u2VH6Qp5rvptE1N1VODVxn2mAbpZsWrvWNPjmASkMYT/HzyrtqFkPvFdSHg8Xjt7aQA== + version "1.91.3" + resolved "https://registry.yarnpkg.com/@solana/web3.js/-/web3.js-1.91.3.tgz#25b8b9dfd88bfa6441c0c02bbdb67abd32bfa0f4" + integrity sha512-Z6FZyW8SWm7RXW5ZSyr1kmpR+eH/F4DhgxV4WPaq5AbAAMnCiiGm36Jb7ACHFXtWzq1a24hBkJ1wnVANjsmdPA== dependencies: "@babel/runtime" "^7.23.4" "@noble/curves" "^1.2.0" @@ -5096,18 +5103,18 @@ tslib "^2.4.0" "@swc/helpers@^0.5.0": - version "0.5.7" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.7.tgz#36c05f61b412abcff3616ecc8634623bcc7c9618" - integrity sha512-BVvNZhx362+l2tSwSuyEUV4h7+jk9raNdoTSdLfwTshXJSaGmYKluGRJznziCI3KX02Z19DdsQrdfrpXAU3Hfg== + version "0.5.8" + resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.8.tgz#65d56b1961487fd99795ffd8c68edb7a591571fb" + integrity sha512-lruDGw3pnfM3wmZHeW7JuhkGQaJjPyiKjxeGhdmfoOT53Ic9qb5JLDNaK2HUdl1zLDeX28H221UvKjfdvSLVMg== dependencies: tslib "^2.4.0" "@tanstack/match-sorter-utils@^8.0.0-alpha.82": - version "8.11.8" - resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.11.8.tgz#9132c2a21cf18ca2f0071b604ddadb7a66e73367" - integrity sha512-3VPh0SYMGCa5dWQEqNab87UpCMk+ANWHDP4ALs5PeEW9EpfTAbrezzaOk/OiM52IESViefkoAOYuxdoa04p6aA== + version "8.15.1" + resolved "https://registry.yarnpkg.com/@tanstack/match-sorter-utils/-/match-sorter-utils-8.15.1.tgz#715e028ff43cf79ece10bd5a757047a1016c3bba" + integrity sha512-PnVV3d2poenUM31ZbZi/yXkBu3J7kd5k2u51CGwwNojag451AjTH9N6n41yjXz2fpLeewleyLBmNS6+HcGDlXw== dependencies: - remove-accents "0.4.2" + remove-accents "0.5.0" "@tanstack/query-async-storage-persister@4.12.0": version "4.12.0" @@ -5328,9 +5335,9 @@ "@types/estree" "*" "@types/eslint@*": - version "8.56.6" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.6.tgz#d5dc16cac025d313ee101108ba5714ea10eb3ed0" - integrity sha512-ymwc+qb1XkjT/gfoQwxIeHZ6ixH23A+tCT2ADSA/DPVKzAjwYkTXBMCQ/f6fe4wEa85Lhp26VPeUxI7wMhAi7A== + version "8.56.7" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.7.tgz#c33b5b5a9cfb66881beb7b5be6c34aa3e81d3366" + integrity sha512-SjDvI/x3zsZnOkYZ3lCt9lOZWZLB2jIlNKz+LBgCtDurK0JZcwucxYHn1w2BJkD34dgX9Tjnak0txtq4WTggEA== dependencies: "@types/estree" "*" "@types/json-schema" "*" @@ -5366,9 +5373,9 @@ "@types/serve-static" "*" "@types/filesystem@*": - version "0.0.35" - resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.35.tgz#6d6766626083e2b397c09bdc57092827120db11d" - integrity sha512-1eKvCaIBdrD2mmMgy5dwh564rVvfEhZTWVQQGRNn0Nt4ZEnJ0C8oSUCzvMKRA4lGde5oEVo+q2MrTTbV/GHDCQ== + version "0.0.36" + resolved "https://registry.yarnpkg.com/@types/filesystem/-/filesystem-0.0.36.tgz#7227c2d76bfed1b21819db310816c7821d303857" + integrity sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA== dependencies: "@types/filewriter" "*" @@ -5537,11 +5544,6 @@ resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.5.tgz#3e0d2db570e9fb6ccb2dc8fde0be1d79ac810d39" integrity sha512-6L6VymKTzYSrEf4Nev4Xa1LCHKrlTlYCBMTlQKFuddo1CvQcE52I0mwfOJayueUC7MJuXOeHTcIU683lzd0cUA== -"@types/mime@*": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.4.tgz#2198ac274de6017b44d941e00261d5bc6a0e0a45" - integrity sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw== - "@types/mime@^1": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" @@ -5575,9 +5577,9 @@ "@types/node" "*" "@types/node@*", "@types/node@>=13.7.0": - version "20.11.30" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.30.tgz#9c33467fc23167a347e73834f788f4b9f399d66f" - integrity sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw== + version "20.12.3" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.3.tgz#d6658c2c7776c1cad93534bb45428195ed840c65" + integrity sha512-sD+ia2ubTeWrOu+YMF+MTAB7E+O7qsMqAbMfW7DG3K1URwhZ5hN1pLlRVGbf4wDFzSfikL05M17EyorS86jShw== dependencies: undici-types "~5.26.4" @@ -5631,9 +5633,9 @@ integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== "@types/prop-types@*", "@types/prop-types@^15.0.0": - version "15.7.11" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" - integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== + version "15.7.12" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" + integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== "@types/qs@*": version "6.9.14" @@ -5767,13 +5769,13 @@ "@types/express" "*" "@types/serve-static@*", "@types/serve-static@^1.13.10": - version "1.15.5" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.5.tgz#15e67500ec40789a1e8c9defc2d32a896f05b033" - integrity sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ== + version "1.15.7" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" + integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== dependencies: "@types/http-errors" "*" - "@types/mime" "*" "@types/node" "*" + "@types/send" "*" "@types/sockjs@^0.3.33": version "0.3.36" @@ -7174,6 +7176,11 @@ abitype@0.9.8: resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.9.8.tgz#1f120b6b717459deafd213dfbf3a3dd1bf10ae8c" integrity sha512-puLifILdm+8sjyss4S+fsUN09obiT1g2YW6CtcQF+QDzxR0euzgEB29MZujC6zMk2a6SVmtttq1fc6+YFA7WYQ== +abitype@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.0.tgz#237176dace81d90d018bebf3a45cb42f2a2d9e97" + integrity sha512-NMeMah//6bJ56H5XRj8QCV4AwuW6hB6zqz2LnhhLdcWVQOsXki6/Pn3APeqxCma62nXIcmZWdu1DlHWS74umVQ== + accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -7248,10 +7255,10 @@ agent-base@6, agent-base@^6.0.2: dependencies: debug "4" -agent-base@^7.0.2, agent-base@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.0.tgz#536802b76bc0b34aa50195eb2442276d613e3434" - integrity sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg== +agent-base@^7.0.2, agent-base@^7.1.0, agent-base@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.1.tgz#bdbded7dfb096b751a2a087eeeb9664725b2e317" + integrity sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA== dependencies: debug "^4.3.4" @@ -7310,24 +7317,25 @@ ajv@^8.0.0, ajv@^8.9.0: uri-js "^4.2.2" algoliasearch@^4.19.1: - version "4.22.1" - resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.22.1.tgz#f10fbecdc7654639ec20d62f109c1b3a46bc6afc" - integrity sha512-jwydKFQJKIx9kIZ8Jm44SdpigFwRGPESaxZBaHSV0XWN2yBJAOT4mT7ppvlrpA4UGzz92pqFnVKr/kaZXrcreg== - dependencies: - "@algolia/cache-browser-local-storage" "4.22.1" - "@algolia/cache-common" "4.22.1" - "@algolia/cache-in-memory" "4.22.1" - "@algolia/client-account" "4.22.1" - "@algolia/client-analytics" "4.22.1" - "@algolia/client-common" "4.22.1" - "@algolia/client-personalization" "4.22.1" - "@algolia/client-search" "4.22.1" - "@algolia/logger-common" "4.22.1" - "@algolia/logger-console" "4.22.1" - "@algolia/requester-browser-xhr" "4.22.1" - "@algolia/requester-common" "4.22.1" - "@algolia/requester-node-http" "4.22.1" - "@algolia/transporter" "4.22.1" + version "4.23.2" + resolved "https://registry.yarnpkg.com/algoliasearch/-/algoliasearch-4.23.2.tgz#3b7bc93d98f3965628c73a06cbf9203531324a9d" + integrity sha512-8aCl055IsokLuPU8BzLjwzXjb7ty9TPcUFFOk0pYOwsE5DMVhE3kwCMFtsCFKcnoPZK7oObm+H5mbnSO/9ioxQ== + dependencies: + "@algolia/cache-browser-local-storage" "4.23.2" + "@algolia/cache-common" "4.23.2" + "@algolia/cache-in-memory" "4.23.2" + "@algolia/client-account" "4.23.2" + "@algolia/client-analytics" "4.23.2" + "@algolia/client-common" "4.23.2" + "@algolia/client-personalization" "4.23.2" + "@algolia/client-search" "4.23.2" + "@algolia/logger-common" "4.23.2" + "@algolia/logger-console" "4.23.2" + "@algolia/recommend" "4.23.2" + "@algolia/requester-browser-xhr" "4.23.2" + "@algolia/requester-common" "4.23.2" + "@algolia/requester-node-http" "4.23.2" + "@algolia/transporter" "4.23.2" align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" @@ -8423,15 +8431,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001599: - version "1.0.30001600" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz#93a3ee17a35aa6a9f0c6ef1b2ab49507d1ab9079" - integrity sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ== - -caniuse-lite@^1.0.30001317, caniuse-lite@^1.0.30001587: - version "1.0.30001599" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001599.tgz#571cf4f3f1506df9bf41fcbb6d10d5d017817bce" - integrity sha512-LRAQHZ4yT1+f9LemSMeqdMpMxZcc4RMWdj4tiFe3G8tNkWK+E58g+/tzotb5cU6TbcVJLr4fySiAW7XmxQvZQA== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001317, caniuse-lite@^1.0.30001587, caniuse-lite@^1.0.30001599: + version "1.0.30001605" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001605.tgz#ca12d7330dd8bcb784557eb9aa64f0037870d9d6" + integrity sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ== case-sensitive-paths-webpack-plugin@^2.3.0: version "2.4.0" @@ -9013,9 +9016,9 @@ convert-source-map@^2.0.0: integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== cookie-es@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/cookie-es/-/cookie-es-1.0.0.tgz#4759684af168dfc54365b2c2dda0a8d7ee1e4865" - integrity sha512-mWYvfOLrfEc996hlKcdABeIiPHUPC6DM2QYZdGGOvhOTbA3tjm2eBwqlJpoFdjC89NI4Qt6h0Pu06Mp+1Pj5OQ== + version "1.1.0" + resolved "https://registry.yarnpkg.com/cookie-es/-/cookie-es-1.1.0.tgz#68f8d9f48aeb5a534f3896f80e792760d3d20def" + integrity sha512-L2rLOcK0wzWSfSDA33YR+PUHDG10a8px7rUHKWbGLP4YfbsMed2KFUw5fczvDPbT98DDe3LEzviswl810apTEw== cookie-signature@1.0.6: version "1.0.6" @@ -9890,7 +9893,7 @@ des.js@^1.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" -destr@^2.0.1, destr@^2.0.3: +destr@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/destr/-/destr-2.0.3.tgz#7f9e97cb3d16dbdca7be52aca1644ce402cfe449" integrity sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ== @@ -10162,9 +10165,9 @@ ejs@^3.1.7: jake "^10.8.5" electron-to-chromium@^1.4.668: - version "1.4.713" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.713.tgz#7cd8e4083c948f8d0cc686fcfdde97d97fd76556" - integrity sha512-vDarADhwntXiULEdmWd77g2dV6FrNGa8ecAC29MZ4TwPut2fvosD0/5sJd1qWNNe8HcJFAC+F5Lf9jW1NPtWmw== + version "1.4.724" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.724.tgz#e0a86fe4d3d0e05a4d7b032549d79608078f830d" + integrity sha512-RTRvkmRkGhNBPPpdrgtDKvmOEYTrPlXDfc0J/Nfq5s29tEahAwhiX4mmhNzj6febWMleulxVYPh7QwCSL/EldA== ellipsize@^0.2.0: version "0.2.0" @@ -10317,57 +10320,10 @@ error-stack-parser@^2.0.6: dependencies: stackframe "^1.3.4" -es-abstract@^1.22.1, es-abstract@^1.22.3: - version "1.22.5" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.5.tgz#1417df4e97cc55f09bf7e58d1e614bc61cb8df46" - integrity sha512-oW69R+4q2wG+Hc3KZePPZxOiisRIqfKBVo/HLx94QcJeWGU/8sZhCvc829rd1kS366vlJbzBfXf9yWwf0+Ko7w== - dependencies: - array-buffer-byte-length "^1.0.1" - arraybuffer.prototype.slice "^1.0.3" - available-typed-arrays "^1.0.7" - call-bind "^1.0.7" - es-define-property "^1.0.0" - es-errors "^1.3.0" - es-set-tostringtag "^2.0.3" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.4" - get-symbol-description "^1.0.2" - globalthis "^1.0.3" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" - has-proto "^1.0.3" - has-symbols "^1.0.3" - hasown "^2.0.1" - internal-slot "^1.0.7" - is-array-buffer "^3.0.4" - is-callable "^1.2.7" - is-negative-zero "^2.0.3" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.3" - is-string "^1.0.7" - is-typed-array "^1.1.13" - is-weakref "^1.0.2" - object-inspect "^1.13.1" - object-keys "^1.1.1" - object.assign "^4.1.5" - regexp.prototype.flags "^1.5.2" - safe-array-concat "^1.1.0" - safe-regex-test "^1.0.3" - string.prototype.trim "^1.2.8" - string.prototype.trimend "^1.0.7" - string.prototype.trimstart "^1.0.7" - typed-array-buffer "^1.0.2" - typed-array-byte-length "^1.0.1" - typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.5" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.14" - -es-abstract@^1.23.0, es-abstract@^1.23.2: - version "1.23.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.2.tgz#693312f3940f967b8dd3eebacb590b01712622e0" - integrity sha512-60s3Xv2T2p1ICykc7c+DNDPLDMm9t4QxCOUU0K9JxiLjM3C1zB9YVdN7tjxrFd4+AkZ8CdX1ovUga4P2+1e+/w== +es-abstract@^1.22.1, es-abstract@^1.22.3, es-abstract@^1.23.0, es-abstract@^1.23.2: + version "1.23.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.23.3.tgz#8f0c5a35cd215312573c5a27c87dfd6c881a0aa0" + integrity sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A== dependencies: array-buffer-byte-length "^1.0.1" arraybuffer.prototype.slice "^1.0.3" @@ -10408,11 +10364,11 @@ es-abstract@^1.23.0, es-abstract@^1.23.2: safe-regex-test "^1.0.3" string.prototype.trim "^1.2.9" string.prototype.trimend "^1.0.8" - string.prototype.trimstart "^1.0.7" + string.prototype.trimstart "^1.0.8" typed-array-buffer "^1.0.2" typed-array-byte-length "^1.0.1" typed-array-byte-offset "^1.0.2" - typed-array-length "^1.0.5" + typed-array-length "^1.0.6" unbox-primitive "^1.0.2" which-typed-array "^1.1.15" @@ -10439,9 +10395,9 @@ es-module-lexer@^0.9.0: integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== es-module-lexer@^1.2.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.4.2.tgz#ba1a62255ff9b41023aaf9bd08c016a5f1a3fef3" - integrity sha512-7nOqkomXZEaxUDJw21XZNtRk739QvrPSoZoRtbsEfcii00vdzZUh6zh1CQwHhrib8MdEtJfv5rJiGeb4KuV/vw== + version "1.5.0" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.0.tgz#4878fee3789ad99e065f975fdd3c645529ff0236" + integrity sha512-pqrTKmwEIgafsYZAGw9kszYzmagcE/n4dbgwGWLEXg7J4QFJVQRBld8j3Q3GNez79jzxZshq0bcT962QHOghjw== es-object-atoms@^1.0.0: version "1.0.0" @@ -11180,9 +11136,9 @@ exponential-backoff@^3.1.1: integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== express@^4.17.3: - version "4.19.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.19.1.tgz#4700635795e911600a45596138cf5b0320e78256" - integrity sha512-K4w1/Bp7y8iSiVObmCrtq8Cs79XjJc/RU2YYkZQ7wpUu5ZyZ7MtPHkqoMz4pf+mgXfNvo2qft8D9OnrH2ABk9w== + version "4.19.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" + integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== dependencies: accepts "~1.3.8" array-flatten "1.1.1" @@ -11573,7 +11529,7 @@ fraction.js@^4.2.0, fraction.js@^4.3.7: resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== -framer-motion@6.3.3, framer-motion@7.8.0, framer-motion@^11.0.6, framer-motion@^6.3.3, framer-motion@^7.6.7: +framer-motion@6.3.3, framer-motion@7.8.0, framer-motion@^10.16.16, framer-motion@^11.0.6, framer-motion@^6.3.3, framer-motion@^7.6.7: version "6.3.3" resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-6.3.3.tgz#58978fe6955d13000351ea4c134c9bc052638967" integrity sha512-wo0dCnoq5vn4L8YVOPO9W54dliH78vDaX0Lj+bSPUys6Nt5QaehrS3uaYa0q5eVeikUgtTjz070UhQ94thI5Sw== @@ -11896,15 +11852,15 @@ glob@7.1.4: path-is-absolute "^1.0.0" glob@^10.2.2: - version "10.3.10" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b" - integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== + version "10.3.12" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.12.tgz#3a65c363c2e9998d220338e88a5f6ac97302960b" + integrity sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg== dependencies: foreground-child "^3.1.0" - jackspeak "^2.3.5" + jackspeak "^2.3.6" minimatch "^9.0.1" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - path-scurry "^1.10.1" + minipass "^7.0.4" + path-scurry "^1.10.2" glob@^7.0.0, glob@^7.0.5, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.2.3: version "7.2.3" @@ -13121,7 +13077,7 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jackspeak@^2.3.5: +jackspeak@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== @@ -13918,9 +13874,9 @@ jiti@^1.21.0: integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== joi@*, joi@^17.4.0: - version "17.12.2" - resolved "https://registry.yarnpkg.com/joi/-/joi-17.12.2.tgz#283a664dabb80c7e52943c557aab82faea09f521" - integrity sha512-RonXAIzCiHLc8ss3Ibuz45u28GOsWE1UpfDXLbN/9NKbL4tCJf8TWYVKsoYuuh+sAUt7fsSNpA+r2+TBA6Wjmw== + version "17.12.3" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.12.3.tgz#944646979cd3b460178547b12ba37aca8482f63d" + integrity sha512-2RRziagf555owrm9IRVtdKynOBeITiDpuZqIpgwqXShPncPKNiRQoiGsl/T8SQdq+8ugRzH2LqY67irr2y/d+g== dependencies: "@hapi/hoek" "^9.3.0" "@hapi/topo" "^5.1.0" @@ -14640,7 +14596,7 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" -lru-cache@^10.2.0, "lru-cache@^9.1.1 || ^10.0.0": +lru-cache@^10.2.0: version "10.2.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== @@ -15428,9 +15384,9 @@ minimatch@^8.0.2: brace-expansion "^2.0.1" minimatch@^9.0.0, minimatch@^9.0.1: - version "9.0.3" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" - integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + version "9.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" + integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== dependencies: brace-expansion "^2.0.1" @@ -15523,7 +15479,7 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.3: +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.0.3, minipass@^7.0.4: version "7.0.4" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.4.tgz#dbce03740f50a4786ba994c1fb908844d27b038c" integrity sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ== @@ -15691,7 +15647,7 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -mute-stream@~1.0.0: +mute-stream@^1.0.0, mute-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-1.0.0.tgz#e31bd9fe62f0aed23520aa4324ea6671531e013e" integrity sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA== @@ -15781,10 +15737,10 @@ node-environment-flags@^1.0.5: object.getownpropertydescriptors "^2.0.3" semver "^5.7.0" -node-fetch-native@^1.4.0, node-fetch-native@^1.6.1, node-fetch-native@^1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.2.tgz#f439000d972eb0c8a741b65dcda412322955e1c6" - integrity sha512-69mtXOFZ6hSkYiXAVB5SqaRvrbITC/NPyqv7yuu/qw0nmgPyYbIMYYNIDhNtwPrzk0ptrimrLz/hhjvm4w5Z+w== +node-fetch-native@^1.6.1, node-fetch-native@^1.6.2, node-fetch-native@^1.6.3: + version "1.6.4" + resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.4.tgz#679fc8fd8111266d47d7e72c379f1bed9acff06e" + integrity sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ== node-fetch@2.6.7: version "2.6.7" @@ -16200,12 +16156,13 @@ object.getownpropertydescriptors@^2.0.3: safe-array-concat "^1.1.2" object.hasown@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.3.tgz#6a5f2897bb4d3668b8e79364f98ccf971bda55ae" - integrity sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA== + version "1.1.4" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.4.tgz#e270ae377e4c120cdcb7656ce66884a6218283dc" + integrity sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg== dependencies: - define-properties "^1.2.0" - es-abstract "^1.22.1" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" object.values@^1.1.5, object.values@^1.1.6: version "1.2.0" @@ -16222,13 +16179,13 @@ obuf@^1.0.0, obuf@^1.1.2: integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== ofetch@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/ofetch/-/ofetch-1.3.3.tgz#588cb806a28e5c66c2c47dd8994f9059a036d8c0" - integrity sha512-s1ZCMmQWXy4b5K/TW9i/DtiN8Ku+xCiHcjQ6/J/nDdssirrQNOoB165Zu8EqLMA2lln1JUth9a0aW9Ap2ctrUg== + version "1.3.4" + resolved "https://registry.yarnpkg.com/ofetch/-/ofetch-1.3.4.tgz#7ea65ced3c592ec2b9906975ae3fe1d26a56f635" + integrity sha512-KLIET85ik3vhEfS+3fDlc/BAZiAp+43QEC/yCo5zkNoY2YaKvNkOaFr/6wCFgFH1kuYQM5pMNi0Tg8koiIemtw== dependencies: - destr "^2.0.1" - node-fetch-native "^1.4.0" - ufo "^1.3.0" + destr "^2.0.3" + node-fetch-native "^1.6.3" + ufo "^1.5.3" ohash@^1.1.3: version "1.1.3" @@ -16713,12 +16670,12 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^1.10.1, path-scurry@^1.6.1: - version "1.10.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.1.tgz#9ba6bf5aa8500fe9fd67df4f0d9483b2b0bfc698" - integrity sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ== +path-scurry@^1.10.2, path-scurry@^1.6.1: + version "1.10.2" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.10.2.tgz#8f6357eb1239d5fa1da8b9f70e9c080675458ba7" + integrity sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA== dependencies: - lru-cache "^9.1.1 || ^10.0.0" + lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" path-to-regexp@0.1.7: @@ -17047,18 +17004,18 @@ postcss-modules-extract-imports@^3.0.0: integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== postcss-modules-local-by-default@^4.0.0, postcss-modules-local-by-default@^4.0.4: - version "4.0.4" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.4.tgz#7cbed92abd312b94aaea85b68226d3dec39a14e6" - integrity sha512-L4QzMnOdVwRm1Qb8m4x8jsZzKAaPAgrUF1r/hjDR2Xj7R+8Zsf97jAlSQzWtKx5YNiNGN8QxmPFIc/sh+RQl+Q== + version "4.0.5" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz#f1b9bd757a8edf4d8556e8d0f4f894260e3df78f" + integrity sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw== dependencies: icss-utils "^5.0.0" postcss-selector-parser "^6.0.2" postcss-value-parser "^4.1.0" postcss-modules-scope@^3.0.0, postcss-modules-scope@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.1.1.tgz#32cfab55e84887c079a19bbb215e721d683ef134" - integrity sha512-uZgqzdTleelWjzJY+Fhti6F3C9iF1JR/dODLs/JDefozYcKTBCdD8BIl6nNPbTbcLnGrk56hzwZC2DaGNvYjzA== + version "3.1.2" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.1.2.tgz#e7d360c0a1f6493134f00a0483bafacba0a297bd" + integrity sha512-hHTYdjVmywC2TAmz134e9M4WZ6i9bh9OoHD8l2qVqc/fXxti1+4UDfZ+Pw/WCdkNIi811eJiTQ+RsfTFEuAjXw== dependencies: postcss-selector-parser "^6.0.4" @@ -17229,9 +17186,9 @@ postinstall-postinstall@2.1.0: integrity sha512-7hQX6ZlZXIoRiWNrbMQaLzUUfH+sSx39u8EJ9HYuDc1kLo9IXKWjM5RSquZN1ad5GnH8CGFM78fsAAQi3OKEEQ== preact@^10.0.0, preact@^10.12.0, preact@^10.5.9: - version "10.20.0" - resolved "https://registry.yarnpkg.com/preact/-/preact-10.20.0.tgz#191c10a2ee3b9fca1a7ded6375266266380212f6" - integrity sha512-wU7iZw2BjsaKDal3pDRDy/HpPB6cuFOnVUCcw9aIPKG98+ZrXx3F+szkos8BVME5bquyKDKvRlOJFG8kMkcAbg== + version "10.20.1" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.20.1.tgz#1bc598ab630d8612978f7533da45809a8298542b" + integrity sha512-JIFjgFg9B2qnOoGiYMVBtrcFxHqn+dNXbq76bVmcaHYJFYR4lW67AOcXgAYQQTDYXDOg/kTZrKPNCdRgJ2UJmw== prelude-ls@^1.2.1: version "1.2.1" @@ -17357,11 +17314,11 @@ prompts@^2.0.1: sisteransi "^1.0.5" promzard@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/promzard/-/promzard-1.0.0.tgz#3246f8e6c9895a77c0549cefb65828ac0f6c006b" - integrity sha512-KQVDEubSUHGSt5xLakaToDFrSoZhStB8dXLzk2xvwR67gJktrHFvpR63oZgHyK19WKbHFLXJqCPXdVR3aBP8Ig== + version "1.0.1" + resolved "https://registry.yarnpkg.com/promzard/-/promzard-1.0.1.tgz#3b77251a24f988c0886f5649d4f642bcdd53e558" + integrity sha512-ulDF77aULEHUoJkN5XZgRV5loHXBaqd9eorMvLNLvi2gXMuRAtwH6Gh4zsMHQY1kTt7tyv/YZwZW5C2gtj8F2A== dependencies: - read "^2.0.0" + read "^3.0.1" prop-types@^15.0.0, prop-types@^15.5.8, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" @@ -17646,9 +17603,9 @@ r2@^2.0.1: typedarray-to-buffer "^3.1.2" radix3@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/radix3/-/radix3-1.1.1.tgz#60a56876ffec62c88a22396a6a1c4c7efe9eb4b1" - integrity sha512-yUUd5VTiFtcMEx0qFUxGAv5gbMc1un4RvEO1JZdP7ZUl/RHygZK6PknIKntmQRZxnMY3ZXD2ISaw1ij8GYW1yg== + version "1.1.2" + resolved "https://registry.yarnpkg.com/radix3/-/radix3-1.1.2.tgz#fd27d2af3896c6bf4bcdfab6427c69c2afc69ec0" + integrity sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA== raf-schd@^4.0.2: version "4.0.3" @@ -17686,16 +17643,16 @@ raw-body@2.5.2: unpipe "1.0.0" rdk@>=6.4.4, rdk@>=6.6.2, rdk@^6.0.2, rdk@^6.1.0: - version "6.6.2" - resolved "https://registry.yarnpkg.com/rdk/-/rdk-6.6.2.tgz#4884da94ebc0af37414434bb6cf43b36722dddbd" - integrity sha512-yQ0HrIUTp25G/yxZXCHfOmvgIQKWp9/YqlAtjYla2JBleQvpfacDfdO31vJf3fyQAx53x1v2ZaxdkQ0V3jrOzA== + version "6.6.3" + resolved "https://registry.yarnpkg.com/rdk/-/rdk-6.6.3.tgz#0f13420154eb7aaca8adfc5dc2e566e448a3faae" + integrity sha512-+l6HyGiPDZnFMYci6/qv6cXxLEKiPrPPngAUV1iCBmtxMvEgMlhRi20x4SRAOwCUIsZDpjniibYbDOZ9/PfBcg== dependencies: body-scroll-lock-upgrade "^1.1.0" classnames "^2.3.2" - framer-motion "^11.0.6" + framer-motion "^10.16.16" popper.js "^1.16.1" -reablocks@>=4.3.4: +reablocks@^5.1.0: version "5.10.4" resolved "https://registry.yarnpkg.com/reablocks/-/reablocks-5.10.4.tgz#f622aaa960e94e04ba55b94b386f1b33e78cf585" integrity sha512-N2YRqVdH/7YbgVOs4KxxhUdrlmF6gGnoeMHeJkFoY1YG25Sm0nepeHawaMAiy3TflfpRgHsi6xlsP8f1N8FMIg== @@ -17998,6 +17955,13 @@ read@^2.0.0: dependencies: mute-stream "~1.0.0" +read@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/read/-/read-3.0.1.tgz#926808f0f7c83fa95f1ef33c0e2c09dbb28fd192" + integrity sha512-SLBrDU/Srs/9EoWhU5GdbAoxG1GzpQHo/6qiGItaoLJ1thmYpcNIM1qISEUvyHBzfGlWIyd6p2DNi1oV1VmAuw== + dependencies: + mute-stream "^1.0.0" + readable-stream@2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" @@ -18102,10 +18066,10 @@ reaviz@13.1.4: realayers "^3.0.3" transformation-matrix "^2.9.0" -reaviz@14.7.4: - version "14.7.4" - resolved "https://registry.yarnpkg.com/reaviz/-/reaviz-14.7.4.tgz#2075976ae9868416ed943e75d32d06d40953a0a2" - integrity sha512-mOuuxNpQC0UCJ+Rsa37fTuolZBcviYF/6DGaI3A2IuvsFxPFXeqHblC7s0KeVKDaX6kzz6eEnSApBMl2cJr7qA== +reaviz@14.9.7: + version "14.9.7" + resolved "https://registry.yarnpkg.com/reaviz/-/reaviz-14.9.7.tgz#275f3a4eef85afc8b2416d2437026ed7c0add67d" + integrity sha512-aY4cS5+30K1agWj4+hT+eqY4ajyCUfeXlZVmlQPpLfHswWnYB7z5W0v4y2dh9KCmbx9wmB8Vll6y9q9Ep4IF2A== dependencies: "@upsetjs/venn.js" "^1.3.0" big-integer "1.6.51" @@ -18127,7 +18091,7 @@ reaviz@14.7.4: memoize-bind "^1.0.3" memoize-one "^6.0.0" rdk ">=6.4.4" - reablocks ">=4.3.4" + reablocks "^5.1.0" react-cool-dimensions "^3.0.1" react-fast-compare "^3.2.1" safe-identifier "^0.4.2" @@ -18278,10 +18242,10 @@ remark-rehype@^10.0.0: mdast-util-to-hast "^12.1.0" unified "^10.0.0" -remove-accents@0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" - integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA== +remove-accents@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.5.0.tgz#77991f37ba212afba162e375b627631315bed687" + integrity sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A== renderkid@^3.0.0: version "3.0.0" @@ -18522,7 +18486,7 @@ sade@^1.7.3: dependencies: mri "^1.1.0" -safe-array-concat@^1.1.0, safe-array-concat@^1.1.2: +safe-array-concat@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.1.2.tgz#81d77ee0c4e8b863635227c721278dd524c20edb" integrity sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q== @@ -18983,11 +18947,11 @@ socks-proxy-agent@^7.0.0: socks "^2.6.2" socks-proxy-agent@^8.0.1: - version "8.0.2" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz#5acbd7be7baf18c46a3f293a840109a430a640ad" - integrity sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g== + version "8.0.3" + resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz#6b2da3d77364fde6292e810b496cb70440b9b89d" + integrity sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A== dependencies: - agent-base "^7.0.2" + agent-base "^7.1.1" debug "^4.3.4" socks "^2.7.1" @@ -19328,15 +19292,16 @@ string.prototype.matchall@^4.0.7: side-channel "^1.0.6" string.prototype.padend@^3.0.0: - version "3.1.5" - resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.5.tgz#311ef3a4e3c557dd999cdf88fbdde223f2ac0f95" - integrity sha512-DOB27b/2UTTD+4myKUFh+/fXWcu/UDyASIXfg+7VzoCNNGOfWvoyU/x5pvVHr++ztyt/oSYI1BcWBBG/hmlNjA== + version "3.1.6" + resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz#ba79cf8992609a91c872daa47c6bb144ee7f62a5" + integrity sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-object-atoms "^1.0.0" -string.prototype.trim@^1.2.8, string.prototype.trim@^1.2.9, string.prototype.trim@~1.2.8: +string.prototype.trim@^1.2.9, string.prototype.trim@~1.2.8: version "1.2.9" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz#b6fa326d72d2c78b6df02f7759c73f8f6274faa4" integrity sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw== @@ -19346,7 +19311,7 @@ string.prototype.trim@^1.2.8, string.prototype.trim@^1.2.9, string.prototype.tri es-abstract "^1.23.0" es-object-atoms "^1.0.0" -string.prototype.trimend@^1.0.7, string.prototype.trimend@^1.0.8: +string.prototype.trimend@^1.0.8: version "1.0.8" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz#3651b8513719e8a9f48de7f2f77640b26652b229" integrity sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ== @@ -19355,14 +19320,14 @@ string.prototype.trimend@^1.0.7, string.prototype.trimend@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -string.prototype.trimstart@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" - integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== +string.prototype.trimstart@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz#7ee834dda8c7c17eff3118472bb35bfedaa34dde" + integrity sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg== dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" + call-bind "^1.0.7" + define-properties "^1.2.1" + es-object-atoms "^1.0.0" string_decoder@^1.1.1: version "1.3.0" @@ -19726,9 +19691,9 @@ tar@6.1.11: yallist "^4.0.0" tar@^6.1.11, tar@^6.1.2: - version "6.2.0" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz#b14ce49a79cb1cd23bc9b016302dea5474493f73" - integrity sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ== + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0" @@ -19762,9 +19727,9 @@ terser-webpack-plugin@^5.1.1, terser-webpack-plugin@^5.1.3, terser-webpack-plugi terser "^5.26.0" terser@^5.10.0, terser@^5.26.0: - version "5.29.2" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.29.2.tgz#c17d573ce1da1b30f21a877bffd5655dd86fdb35" - integrity sha512-ZiGkhUBIM+7LwkNjXYJq8svgkd+QK3UUr0wJqY4MieaezBSAIPgbSPZyIx0idM6XWK5CMzSWa8MJIzmRcB8Caw== + version "5.30.3" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.30.3.tgz#f1bb68ded42408c316b548e3ec2526d7dd03f4d2" + integrity sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -20148,10 +20113,10 @@ typed-array-byte-offset@^1.0.2: has-proto "^1.0.3" is-typed-array "^1.1.13" -typed-array-length@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.5.tgz#57d44da160296d8663fd63180a1802ebf25905d5" - integrity sha512-yMi0PlwuznKHxKmcpoOdeLwxBoVPkqZxd7q2FgMkmD3bNwvF5VW0+UlUQ1k1vmktTu4Yu13Q0RIxEP8+B+wloA== +typed-array-length@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.6.tgz#57155207c76e64a3457482dfdc1c9d1d3c4c73a3" + integrity sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g== dependencies: call-bind "^1.0.7" for-each "^0.3.3" @@ -20197,7 +20162,7 @@ uc.micro@^1.0.1, uc.micro@^1.0.5: resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== -ufo@^1.3.0, ufo@^1.3.2, ufo@^1.4.0: +ufo@^1.3.2, ufo@^1.4.0, ufo@^1.5.3: version "1.5.3" resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.3.tgz#3325bd3c977b6c6cd3160bf4ff52989adc9d3344" integrity sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw== @@ -20641,6 +20606,20 @@ vfile@^5.0.0: unist-util-stringify-position "^3.0.0" vfile-message "^3.0.0" +viem@2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.5.0.tgz#1d7bd5333a6b9387d42c1c2d368d0b88c2961ee1" + integrity sha512-ytHXIWtlgPs4mcsGxXjJrQ25v+N4dE2hBzgCU8CVv4iXNh3PRFRgyYa7igZlmxiMVzkfSHHADOtivS980JhilA== + dependencies: + "@adraffy/ens-normalize" "1.10.0" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@scure/bip32" "1.3.2" + "@scure/bip39" "1.2.1" + abitype "1.0.0" + isows "1.0.3" + ws "8.13.0" + viem@^1.0.0, viem@^1.6.0: version "1.21.4" resolved "https://registry.yarnpkg.com/viem/-/viem-1.21.4.tgz#883760e9222540a5a7e0339809202b45fe6a842d" @@ -20668,9 +20647,9 @@ vite@3.1.0: fsevents "~2.3.2" vite@^3.2.3: - version "3.2.8" - resolved "https://registry.yarnpkg.com/vite/-/vite-3.2.8.tgz#0697e13addf99ed44b838b8462a3a922fdd9d37b" - integrity sha512-EtQU16PLIJpAZol2cTLttNP1mX6L0SyI0pgQB1VOoWeQnMSvtiwovV3D6NcjN8CZQWWyESD2v5NGnpz5RvgOZA== + version "3.2.10" + resolved "https://registry.yarnpkg.com/vite/-/vite-3.2.10.tgz#7ac79fead82cfb6b5bf65613cd82fba6dcc81340" + integrity sha512-Dx3olBo/ODNiMVk/cA5Yft9Ws+snLOXrhLtrI3F4XLt4syz2Yg8fayZMWScPKoz12v5BUv7VEmQHnsfpY80fYw== dependencies: esbuild "^0.15.9" postcss "^8.4.18"