Skip to content

Commit

Permalink
ci(release): publish latest release
Browse files Browse the repository at this point in the history
  • Loading branch information
hello-happy-puppy committed Dec 16, 2024
1 parent 8bcc843 commit b7dae66
Show file tree
Hide file tree
Showing 350 changed files with 18,908 additions and 9,605 deletions.
5 changes: 5 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ types

apps/mobile/ios
apps/mobile/android
apps/mobile/.storybook/storybook.requires.ts

# extension

Expand All @@ -42,3 +43,7 @@ packages/uniswap/codegen.ts
# eslint partials

packages/eslint-config/restrictedImports.js

# generated

packages/uniswap/src/data/rest/conversionTracking/api
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @uniswap/web-admins
28 changes: 21 additions & 7 deletions RELEASE
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
We are back with some new updates! Here’s the latest:
IPFS hash of the deployment:
- CIDv0: `QmYVT8bXNHTgPdSTegLQ62Z6f7E51fQbeMvn2nJAxMRGBE`
- CIDv1: `bafybeiew2yawtry3lld6g5ankyq3bsbvjfchxxhboph45u6os6t2babtru`

Token Warnings: See more information about the tokens you’re attempting to swap, enriched with data from Blockaid.
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).

You can also access the Uniswap Interface from an IPFS gateway.
**BEWARE**: The Uniswap interface uses [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) to remember your settings, such as which tokens you have imported.
**You should always use an IPFS gateway that enforces origin separation**, or our hosted deployment of the latest release at [app.uniswap.org](https://app.uniswap.org).
Your Uniswap settings are never remembered across different URLs.

IPFS gateways:
- https://bafybeiew2yawtry3lld6g5ankyq3bsbvjfchxxhboph45u6os6t2babtru.ipfs.dweb.link/
- https://bafybeiew2yawtry3lld6g5ankyq3bsbvjfchxxhboph45u6os6t2babtru.ipfs.cf-ipfs.com/
- [ipfs://QmYVT8bXNHTgPdSTegLQ62Z6f7E51fQbeMvn2nJAxMRGBE/](ipfs://QmYVT8bXNHTgPdSTegLQ62Z6f7E51fQbeMvn2nJAxMRGBE/)

### 5.62.4 (2024-12-16)


### Bug Fixes

* **web:** fix disabled swap button for previously-dismissed warning tokens (#14560) 2b04e71

Other changes:

- Increased manual slippage tolerance up to 50%
- Support for toggling between fiat and token input for fiat onramp
- Better dapp signing support
- Various bug fixes and performance improvements
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
mobile/1.41
web/5.62.4
8 changes: 4 additions & 4 deletions apps/extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@
"@svgr/webpack": "8.0.1",
"@tamagui/core": "1.114.4",
"@types/uuid": "9.0.1",
"@uniswap/analytics-events": "2.39.0",
"@uniswap/analytics-events": "2.40.0",
"@uniswap/uniswapx-sdk": "2.1.0-beta.18",
"@uniswap/universal-router-sdk": "4.5.2",
"@uniswap/v3-sdk": "3.18.1",
"@uniswap/v4-sdk": "1.10.3",
"@uniswap/universal-router-sdk": "4.7.0",
"@uniswap/v3-sdk": "3.19.0",
"@uniswap/v4-sdk": "1.12.0",
"dotenv-webpack": "8.0.1",
"ethers": "5.7.2",
"eventemitter3": "5.0.1",
Expand Down
13 changes: 2 additions & 11 deletions apps/extension/src/app/features/accounts/AccountSwitcherScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ScreenHeader } from 'src/app/components/layout/ScreenHeader'
import { AccountItem } from 'src/app/features/accounts/AccountItem'
import { CreateWalletModal } from 'src/app/features/accounts/CreateWalletModal'
import { EditLabelModal } from 'src/app/features/accounts/EditLabelModal'
import { useSortedAccountList } from 'src/app/features/accounts/useSortedAccountList'
import { useDappContext } from 'src/app/features/dapp/DappContext'
import { updateDappConnectedAddressFromExtension } from 'src/app/features/dapp/actions'
import { useDappConnectedAccounts } from 'src/app/features/dapp/hooks'
Expand Down Expand Up @@ -33,7 +34,6 @@ import { AddressDisplay } from 'wallet/src/components/accounts/AddressDisplay'
import { PlusCircle } from 'wallet/src/components/icons/PlusCircle'
import { MenuContent } from 'wallet/src/components/menu/MenuContent'
import { MenuContentItem } from 'wallet/src/components/menu/types'
import { useAccountList } from 'wallet/src/features/accounts/hooks'
import { createOnboardingAccount } from 'wallet/src/features/onboarding/createOnboardingAccount'
import { BackupType, SignerMnemonicAccount } from 'wallet/src/features/wallet/accounts/types'
import { createAccountsActions } from 'wallet/src/features/wallet/create/createAccountsSaga'
Expand Down Expand Up @@ -148,17 +148,8 @@ export function AccountSwitcherScreen(): JSX.Element {
onPress: (): void => setShowRemoveWalletModal(true),
},
]
const { data: accountBalanceData } = useAccountList({
addresses: accountAddresses,
notifyOnNetworkStatusChange: true,
})

const sortedAddressesByBalance = accountAddresses
.map((address) => {
const wallet = accountBalanceData?.portfolios?.find((portfolio) => portfolio?.ownerAddress === address)
return { address, balance: wallet?.tokensTotalDenominatedValue?.value }
})
.sort((a, b) => (b.balance ?? 0) - (a.balance ?? 0))
const sortedAddressesByBalance = useSortedAccountList(accountAddresses)

const contentShadowProps = {
shadowColor: colors.shadowColor.val,
Expand Down
127 changes: 127 additions & 0 deletions apps/extension/src/app/features/accounts/useSortedAccountList.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { useSortedAccountList } from 'src/app/features/accounts/useSortedAccountList'
import { act, renderHook } from 'src/test/test-utils'
import { useAccountList } from 'wallet/src/features/accounts/hooks'

jest.mock('wallet/src/features/accounts/hooks')
const mockUseAccountList = useAccountList as jest.MockedFunction<typeof useAccountList>

describe('useSortedAccountList', () => {
beforeEach(() => {
jest.clearAllMocks()
})

it('should sort addresses by balance in descending order', () => {
mockAccountList([mockPortfolio('address1', 100), mockPortfolio('address2', 200), mockPortfolio('address3', 150)])

const addresses = ['address1', 'address2', 'address3']
const { result } = renderHook(() => useSortedAccountList(addresses))

expect(result.current).toEqual([
{ address: 'address2', balance: 200 },
{ address: 'address3', balance: 150 },
{ address: 'address1', balance: 100 },
])
})

it('should handle undefined portfolios', () => {
mockAccountList(undefined)

const addresses = ['address1', 'address2']
const { result } = renderHook(() => useSortedAccountList(addresses))

expect(result.current).toEqual([
{ address: 'address1', balance: 0 },
{ address: 'address2', balance: 0 },
])
})

it('should use previous data during balance updates', () => {
mockAccountList([mockPortfolio('address1', 100), mockPortfolio('address2', 200)])

const addresses = ['address1', 'address2']
const { result, rerender } = renderHook((props) => useSortedAccountList(props), { initialProps: addresses })

expect(result.current).toEqual([
{ address: 'address2', balance: 200 },
{ address: 'address1', balance: 100 },
])

mockAccountList([mockPortfolio('address1', 100)], true)
rerender(['address1'])

expect(result.current).toEqual([{ address: 'address1', balance: 100 }])
})

it('should keep list order when an account is removed', async () => {
mockAccountList([mockPortfolio('address1', 100), mockPortfolio('address2', 200), mockPortfolio('address3', 300)])

const addresses = ['address1', 'address2', 'address3']
const { result, rerender } = renderHook((props) => useSortedAccountList(props), { initialProps: addresses })

expect(result.current).toEqual([
{ address: 'address3', balance: 300 },
{ address: 'address2', balance: 200 },
{ address: 'address1', balance: 100 },
])

mockAccountListUndefined()

await act(async () => {
rerender(['address1', 'address2'])
await new Promise((resolve) => setTimeout(resolve, 100))
})

expect(result.current).toEqual([
{ address: 'address2', balance: 200 },
{ address: 'address1', balance: 100 },
])

mockAccountList([mockPortfolio('address1', 100), mockPortfolio('address2', 200)])

await act(async () => {
rerender(['address1', 'address2'])
})

expect(result.current).toEqual([
{ address: 'address2', balance: 200 },
{ address: 'address1', balance: 100 },
])
})
})

function mockPortfolio(
ownerAddress: Address,
balance: number,
): {
id: string
ownerAddress: Address
tokensTotalDenominatedValue: { __typename?: 'Amount'; value: number }
} {
return {
id: ownerAddress,
ownerAddress,
tokensTotalDenominatedValue: { __typename: 'Amount', value: balance },
}
}

function mockAccountList(portfolios: ReturnType<typeof mockPortfolio>[] | undefined, loading = false): void {
mockUseAccountList.mockReturnValue({
data: { portfolios },
loading,
networkStatus: 7,
refetch: jest.fn(),
startPolling: jest.fn(),
stopPolling: jest.fn(),
})
}

function mockAccountListUndefined(): void {
mockUseAccountList.mockReturnValue({
data: undefined,
loading: true,
networkStatus: 7,
refetch: jest.fn(),
startPolling: jest.fn(),
stopPolling: jest.fn(),
})
}
47 changes: 47 additions & 0 deletions apps/extension/src/app/features/accounts/useSortedAccountList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useMemo } from 'react'
import { usePrevious } from 'utilities/src/react/hooks'
import { useAccountList } from 'wallet/src/features/accounts/hooks'

interface AddressWithBalance {
address: Address
balance: number
}

export function useSortedAccountList(addresses: Address[]): AddressWithBalance[] {
const { data: accountBalanceData } = useAccountList({
addresses,
})

/*
Why are we using previousAccountBalanceData?
This is a workaround for a data fetching inefficiency. When removing an address, we send a new query
with the updated address array, causing Apollo to refetch ALL balances. During this refetch, balances
temporarily show as 0, causing the list to re-sort momentarily.
We use previousAccountBalanceData to maintain the last known good balances during this refetch. The balances
will be updated once the new query completes.
*/
const previousAccountBalanceData = usePrevious(accountBalanceData)

const balanceRecord: Record<Address, number> = useMemo(() => {
const data = accountBalanceData || previousAccountBalanceData
if (!data?.portfolios) {
return {}
}
return Object.fromEntries(
data.portfolios
.filter((portfolio): portfolio is NonNullable<typeof portfolio> => Boolean(portfolio))
.map((portfolio) => [portfolio.ownerAddress, portfolio.tokensTotalDenominatedValue?.value ?? 0]),
)
}, [accountBalanceData, previousAccountBalanceData])

return useMemo(() => {
return addresses
.map((address) => ({
address,
balance: balanceRecord[address] ?? 0,
}))
.sort((a, b) => b.balance - a.balance)
}, [addresses, balanceRecord])
}
114 changes: 114 additions & 0 deletions apps/extension/src/app/features/appRating/AppRatingModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { useEffect, useState } from 'react'
import { useDispatch } from 'react-redux'
import { Button, Flex, Text, TouchableArea } from 'ui/src'
import { Feedback, LikeSquare, MessageText, X } from 'ui/src/components/icons'
import { IconSizeTokens, zIndices } from 'ui/src/theme'
import { Modal } from 'uniswap/src/components/modals/Modal'
import { uniswapUrls } from 'uniswap/src/constants/urls'
import { ModalName } from 'uniswap/src/features/telemetry/constants'
import { useTranslation } from 'uniswap/src/i18n'
import { setAppRating } from 'wallet/src/features/wallet/slice'

interface AppRatingModalProps {
onClose: () => void
}

enum State {
Initial,
NotReally,
Yes,
}

export default function AppRatingModal({ onClose }: AppRatingModalProps): JSX.Element | null {
const { t } = useTranslation()
const [state, setState] = useState(State.Initial)
const dispatch = useDispatch()

const stateConfig = {
[State.Initial]: {
title: t('appRating.extension.title'),
description: t('appRating.description'),
secondaryButtonText: t('appRating.button.notReally'),
primaryButtonText: t('common.button.yes'),
Icon: LikeSquare,
iconSize: '$icon.24' as IconSizeTokens,
onSecondaryButtonPress: () => setState(State.NotReally),
onPrimaryButtonPress: () => setState(State.Yes),
},
[State.NotReally]: {
title: t('appRating.feedback.title'),
description: t('appRating.feedback.description'),
secondaryButtonText: t('common.button.notNow'),
primaryButtonText: t('appRating.feedback.button.send'),
Icon: MessageText,
iconSize: '$icon.18' as IconSizeTokens,
onSecondaryButtonPress: () => onClose(),
onPrimaryButtonPress: (): void => {
// eslint-disable-next-line security/detect-non-literal-fs-filename
window.open(uniswapUrls.walletFeedbackForm)
dispatch(setAppRating({ feedbackProvided: true }))
onClose()
},
},
[State.Yes]: {
title: t('appRating.extension.review.title'),
description: t('appRating.extension.review.description'),
secondaryButtonText: t('common.button.notNow'),
primaryButtonText: t('common.button.review'),
Icon: Feedback,
iconSize: '$icon.24' as IconSizeTokens,
onSecondaryButtonPress: () => onClose(),
onPrimaryButtonPress: (): void => {
// eslint-disable-next-line security/detect-non-literal-fs-filename
window.open(`https://chromewebstore.google.com/detail/uniswap-extension/${chrome.runtime.id}/reviews`)
dispatch(setAppRating({ ratingProvided: true }))
onClose()
},
},
}

const {
title,
description,
secondaryButtonText,
primaryButtonText,
Icon,
iconSize,
onSecondaryButtonPress,
onPrimaryButtonPress,
} = stateConfig[state]

useEffect(() => {
// just to set that prompt has been shown
dispatch(setAppRating({}))
}, [dispatch])

return (
<Modal isDismissible isModalOpen name={ModalName.TokenWarningModal} backgroundColor="$surface1" onClose={onClose}>
<TouchableArea p="$spacing16" position="absolute" right={0} top={0} zIndex={zIndices.default} onPress={onClose}>
<X color="$neutral2" size="$icon.20" />
</TouchableArea>
<Flex alignItems="center" gap="$spacing8" pt="$spacing16">
<Flex centered backgroundColor="$accent2" width="$spacing48" height="$spacing48" borderRadius="$rounded12">
<Icon color="$accent1" size={iconSize} />
</Flex>
<Flex alignItems="center" gap="$spacing8" pb="$spacing16" pt="$spacing8" px="$spacing4">
<Text color="$neutral1" textAlign="center" variant="subheading2">
{title}
</Text>
<Text color="$neutral2" textAlign="center" variant="body3">
{description}
</Text>
</Flex>
<Flex row width="100%" gap="$spacing12">
<Button flex={1} flexBasis={1} size="small" theme="secondary" onPress={onSecondaryButtonPress}>
{secondaryButtonText}
</Button>
<Button flex={1} flexBasis={1} size="small" theme="primary" onPress={onPrimaryButtonPress}>
{primaryButtonText}
</Button>
</Flex>
</Flex>
</Modal>
)
}
Loading

0 comments on commit b7dae66

Please sign in to comment.