Skip to content

Commit

Permalink
Merge branch 'feat-cow-swap' into block-recipient
Browse files Browse the repository at this point in the history
  • Loading branch information
usame-algan authored May 6, 2024
2 parents fb984c7 + ec2e4e2 commit cb1c12f
Show file tree
Hide file tree
Showing 22 changed files with 956 additions and 74 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"@safe-global/safe-core-sdk": "^3.3.5",
"@safe-global/safe-core-sdk-utils": "^1.7.4",
"@safe-global/safe-ethers-lib": "^1.9.4",
"@safe-global/safe-gateway-typescript-sdk": "3.21.0-alpha.3",
"@safe-global/safe-gateway-typescript-sdk": "3.21.1",
"@safe-global/safe-modules-deployments": "^1.2.0",
"@sentry/react": "^7.91.0",
"@spindl-xyz/attribution-lite": "^1.4.0",
Expand Down
16 changes: 16 additions & 0 deletions public/images/common/safe-swap-dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions public/images/common/safe-swap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
67 changes: 67 additions & 0 deletions src/components/common/NamedAddressInfo/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { render, waitFor } from '@/tests/test-utils'
import NamedAddressInfo from '.'
import { faker } from '@faker-js/faker'
import { getContract, type ChainInfo } from '@safe-global/safe-gateway-typescript-sdk'

const mockChainInfo = {
chainId: '4',
shortName: 'tst',
blockExplorerUriTemplate: {
address: 'https://test.scan.eth/{address}',
api: 'https://test.scan.eth/',
txHash: 'https://test.scan.eth/{txHash}',
},
} as ChainInfo

jest.mock('@safe-global/safe-gateway-typescript-sdk', () => ({
...jest.requireActual('@safe-global/safe-gateway-typescript-sdk'),
getContract: jest.fn(),
__esModule: true,
}))

describe('NamedAddressInfo', () => {
const getContractMock = getContract as jest.Mock
it('should not fetch contract info if name / logo is given', async () => {
const result = render(
<NamedAddressInfo
address={faker.finance.ethereumAddress()}
name="TestAddressName"
customAvatar="https://img.test.safe.global"
/>,
{
initialReduxState: {
chains: {
loading: false,
data: [mockChainInfo],
},
},
},
)

expect(result.getByText('TestAddressName')).toBeVisible()
expect(getContractMock).not.toHaveBeenCalled()
})

it('should fetch contract info if name / logo is not given', async () => {
const address = faker.finance.ethereumAddress()
getContractMock.mockResolvedValue({
displayName: 'Resolved Test Name',
name: 'ResolvedTestName',
logoUri: 'https://img-resolved.test.safe.global',
})
const result = render(<NamedAddressInfo address={address} />, {
initialReduxState: {
chains: {
loading: false,
data: [mockChainInfo],
},
},
})

await waitFor(() => {
expect(result.getByText('Resolved Test Name')).toBeVisible()
})

expect(getContractMock).toHaveBeenCalledWith('4', address)
})
})
24 changes: 24 additions & 0 deletions src/components/common/NamedAddressInfo/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import useAsync from '@/hooks/useAsync'
import useChainId from '@/hooks/useChainId'
import { getContract } from '@safe-global/safe-gateway-typescript-sdk'
import EthHashInfo from '../EthHashInfo'
import type { EthHashInfoProps } from '../EthHashInfo/SrcEthHashInfo'

const NamedAddressInfo = ({ address, name, customAvatar, ...props }: EthHashInfoProps) => {
const chainId = useChainId()
const [contract] = useAsync(
() => (!name && !customAvatar ? getContract(chainId, address) : undefined),
[address, chainId, name, customAvatar],
)

return (
<EthHashInfo
address={address}
name={name || contract?.displayName || contract?.name}
customAvatar={customAvatar || contract?.logoUri}
{...props}
/>
)
}

export default NamedAddressInfo
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import accordionCss from '@/styles/accordion.module.css'
import CodeIcon from '@mui/icons-material/Code'
import { DelegateCallWarning } from '@/components/transactions/Warning'
import { InfoDetails } from '@/components/transactions/InfoDetails'
import EthHashInfo from '@/components/common/EthHashInfo'
import NamedAddressInfo from '@/components/common/NamedAddressInfo'

type SingleTxDecodedProps = {
tx: InternalTransaction
Expand Down Expand Up @@ -74,7 +74,7 @@ export const SingleTxDecoded = ({
{isDelegateCall && <DelegateCallWarning showWarning={!txData.trustedDelegateCallTarget} />}
{!isSpendingLimitMethod && (
<InfoDetails title={title}>
<EthHashInfo
<NamedAddressInfo
address={tx.to}
name={name}
customAvatar={avatarUrl}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import type { ReactElement } from 'react'
import type { TransactionDetails } from '@safe-global/safe-gateway-typescript-sdk'
import { isCustomTxInfo } from '@/utils/transaction-guards'
import { InfoDetails } from '@/components/transactions/InfoDetails'
import EthHashInfo from '@/components/common/EthHashInfo'
import { HexEncodedData } from '@/components/transactions/HexEncodedData'
import { MethodDetails } from '@/components/transactions/TxDetails/TxData/DecodedData/MethodDetails'
import NamedAddressInfo from '@/components/common/NamedAddressInfo'

interface Props {
txData: TransactionDetails['txData']
Expand All @@ -29,7 +29,7 @@ export const DecodedData = ({ txData, txInfo }: Props): ReactElement | null => {
return (
<>
<InfoDetails title="Interact with:">
<EthHashInfo
<NamedAddressInfo
address={txData.to.value}
name={isCustomTxInfo(txInfo) ? txInfo.to.name : undefined}
customAvatar={isCustomTxInfo(txInfo) ? txInfo.to.logoUri : undefined}
Expand Down
2 changes: 1 addition & 1 deletion src/components/transactions/TxDetails/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ const TxDetailsBlock = ({ txSummary, txDetails }: TxDetailsProps): ReactElement
</Box>
)}

{expiredSwap && (
{isQueue && expiredSwap && (
<Typography color="text.secondary" mt={2}>
This order has expired. Reject this transaction and try again.
</Typography>
Expand Down
2 changes: 1 addition & 1 deletion src/components/transactions/TxSummary/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ const TxSummary = ({ item, isGrouped }: TxSummaryProps): ReactElement => {
</Box>
)}

{expiredSwap && (
{isQueue && expiredSwap && (
<Box gridArea="status" justifyContent="flex-end" display="flex" className={css.status}>
<StatusLabel status="expired" />
</Box>
Expand Down
6 changes: 5 additions & 1 deletion src/components/tx-flow/flows/ConfirmTx/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
import { isSwapTxInfo } from '@/utils/transaction-guards'
import type { TransactionSummary } from '@safe-global/safe-gateway-typescript-sdk'
import TxLayout from '@/components/tx-flow/common/TxLayout'
import ConfirmProposedTx from './ConfirmProposedTx'
import { useTransactionType } from '@/hooks/useTransactionType'
import TxInfo from '@/components/transactions/TxInfo'
import SwapIcon from '@/public/images/common/swap.svg'

const ConfirmTxFlow = ({ txSummary }: { txSummary: TransactionSummary }) => {
const { text } = useTransactionType(txSummary)
const isSwapOrder = isSwapTxInfo(txSummary.txInfo)

return (
<TxLayout
title="Confirm transaction"
subtitle={
<>
{text}&nbsp;
<TxInfo info={txSummary.txInfo} withLogo={false} omitSign />
{!isSwapOrder && <TxInfo info={txSummary.txInfo} withLogo={false} omitSign />}
</>
}
icon={isSwapOrder && SwapIcon}
step={0}
txSummary={txSummary}
>
Expand Down
14 changes: 11 additions & 3 deletions src/components/tx-flow/flows/SignMessage/index.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import TxLayout from '@/components/tx-flow/common/TxLayout'
import SignMessage, { type ConfirmProps, type ProposeProps } from '@/components/tx-flow/flows/SignMessage/SignMessage'
import { getSwapTitle, SWAP_TITLE } from '@/features/swap'
import { selectSwapParams } from '@/features/swap/store/swapParamsSlice'
import { useAppSelector } from '@/store'
import { Box, Typography } from '@mui/material'
import SafeAppIconCard from '@/components/safe-apps/SafeAppIconCard'

const APP_LOGO_FALLBACK_IMAGE = '/images/apps/apps-icon.svg'
const APP_NAME_FALLBACK = 'Sign message'

export const AppTitle = ({ name, logoUri }: { name?: string | null; logoUri?: string | null }) => {
const swapParams = useAppSelector(selectSwapParams)

const appName = name || APP_NAME_FALLBACK
const appLogo = logoUri || APP_LOGO_FALLBACK_IMAGE

const title = name === SWAP_TITLE ? getSwapTitle(swapParams.tradeType) : appName

return (
<Box display="flex" alignItems="center">
<SafeAppIconCard src={appLogo} alt={name || 'The icon of the application'} width={24} height={24} />
<Typography variant="h4" pl={1}>
{appName}
<SafeAppIconCard src={appLogo} alt={name || 'The icon of the application'} width={32} height={32} />
<Typography variant="h4" pl={2} fontWeight="bold">
{title}
</Typography>
</Box>
)
Expand Down
12 changes: 2 additions & 10 deletions src/features/swap/components/SwapOrderConfirmationView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import type { CowSwapConfirmationView } from '@safe-global/safe-gateway-typescri
import SwapTokens from '@/features/swap/components/SwapTokens'
import AlertIcon from '@/public/images/common/alert.svg'
import EthHashInfo from '@/components/common/EthHashInfo'
import CowLogo from '@/public/images/swaps/cow-logo.png'
import css from './styles.module.css'
import NamedAddress from '@/components/common/NamedAddressInfo'

type SwapOrderProps = {
order: CowSwapConfirmationView
Expand Down Expand Up @@ -109,15 +109,7 @@ export const SwapOrderConfirmationView = ({ order, settlementContract }: SwapOrd
<OrderId orderId={uid} href={explorerUrl} />
</DataRow>,
<DataRow key="Interact with" title="Interact with">
<EthHashInfo
address={settlementContract}
name="Cow Protocol"
customAvatar={CowLogo.src}
avatarSize={24}
shortAddress={false}
hasExplorer
onlyName
/>
<NamedAddress address={settlementContract} onlyName hasExplorer shortAddress={false} avatarSize={24} />
</DataRow>,
receiver && owner !== receiver ? (
<>
Expand Down
4 changes: 2 additions & 2 deletions src/features/swap/hooks/useIsExpiredSwap.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState } from 'react'
import type { TransactionInfo } from '@safe-global/safe-gateway-typescript-sdk'
import useInterval from '@/hooks/useInterval'
import { isExpiredSwap as isSwapInfoExpired, isSwapTxInfo } from '@/utils/transaction-guards'
import { isSwapTxInfo } from '@/utils/transaction-guards'

const INTERVAL_IN_MS = 10_000

Expand All @@ -16,7 +16,7 @@ const useIsExpiredSwap = (txInfo: TransactionInfo) => {
const isExpiredSwap = () => {
if (!isSwapTxInfo(txInfo)) return

setIsExpired(Date.now() > txInfo.validUntil * 1000 && isSwapInfoExpired(txInfo))
setIsExpired(Date.now() > txInfo.validUntil * 1000)
}

useInterval(isExpiredSwap, INTERVAL_IN_MS)
Expand Down
Loading

0 comments on commit cb1c12f

Please sign in to comment.