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.
+
+ }
+ >
+
+

+
+
+
+
+
+
+
+
+
+
+ {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'}
+
+
+
+
+