ci(release): publish latest release

parent 463d6270
mobile/1.39 extension/1.9.0
\ No newline at end of file \ No newline at end of file
...@@ -31,6 +31,9 @@ export async function initializeDatadog(appName: string): Promise<void> { ...@@ -31,6 +31,9 @@ export async function initializeDatadog(appName: string): Promise<void> {
// otherwise DataDog will ignore error events // otherwise DataDog will ignore error events
event.view.url = event.view.url.replace(/^chrome-extension:\/\/[a-z]{32}\//i, '') event.view.url = event.view.url.replace(/^chrome-extension:\/\/[a-z]{32}\//i, '')
if (event.error && event.type === 'error') { if (event.error && event.type === 'error') {
if (event.error.source === 'console') {
return false
}
Object.defineProperty(event.error, 'stack', { Object.defineProperty(event.error, 'stack', {
value: event.error.stack?.replace(/chrome-extension:\/\/[a-z]{32}/gi, ''), value: event.error.stack?.replace(/chrome-extension:\/\/[a-z]{32}/gi, ''),
writable: false, writable: false,
......
import { BigNumber } from 'ethers'
import { useCallback } from 'react' import { useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useDappLastChainId } from 'src/app/features/dapp/hooks' import { useDappLastChainId } from 'src/app/features/dapp/hooks'
import { DappRequestContent } from 'src/app/features/dappRequests/DappRequestContent' import { DappRequestContent } from 'src/app/features/dappRequests/DappRequestContent'
import { useDappRequestQueueContext } from 'src/app/features/dappRequests/DappRequestQueueContext' import { useDappRequestQueueContext } from 'src/app/features/dappRequests/DappRequestQueueContext'
import { isNonZeroBigNumber } from 'src/app/features/dappRequests/requestContent/EthSend/Swap/utils'
import { SendTransactionRequest } from 'src/app/features/dappRequests/types/DappRequestTypes' import { SendTransactionRequest } from 'src/app/features/dappRequests/types/DappRequestTypes'
import { useCopyToClipboard } from 'src/app/hooks/useOnCopyToClipboard' import { useCopyToClipboard } from 'src/app/hooks/useOnCopyToClipboard'
import { Anchor, Flex, Text, TouchableArea } from 'ui/src' import { Anchor, Flex, Text, TouchableArea } from 'ui/src'
...@@ -55,6 +55,7 @@ export function FallbackEthSendRequestContent({ ...@@ -55,6 +55,7 @@ export function FallbackEthSendRequestContent({
) )
const { parsedTransactionData } = useNoYoloParser(dappRequest.transaction, chainId) const { parsedTransactionData } = useNoYoloParser(dappRequest.transaction, chainId)
const transactionCurrencies = useTransactionCurrencies({ chainId, to: toAddress, parsedTransactionData }) const transactionCurrencies = useTransactionCurrencies({ chainId, to: toAddress, parsedTransactionData })
const showSpendingEthDetails = isNonZeroBigNumber(sending) && chainId
return ( return (
<DappRequestContent <DappRequestContent
...@@ -74,9 +75,7 @@ export function FallbackEthSendRequestContent({ ...@@ -74,9 +75,7 @@ export function FallbackEthSendRequestContent({
p="$spacing16" p="$spacing16"
width="100%" width="100%"
> >
{sending && !BigNumber.from(sending).eq(0) && chainId && ( {showSpendingEthDetails && <SpendingEthDetails chainId={chainId} value={sending} />}
<SpendingEthDetails chainId={chainId} value={sending} />
)}
{transactionCurrencies?.map((currencyInfo, i) => ( {transactionCurrencies?.map((currencyInfo, i) => (
<SpendingDetails <SpendingDetails
key={currencyInfo.currencyId} key={currencyInfo.currencyId}
......
...@@ -162,20 +162,6 @@ function extractTokenAddresses(commands: UniversalRouterCommand[]): { ...@@ -162,20 +162,6 @@ function extractTokenAddresses(commands: UniversalRouterCommand[]): {
return { inputAddress, outputAddress } return { inputAddress, outputAddress }
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function isZeroBigNumber(bigNumberObj: any): boolean {
// The true type of bigNumberObj is { type: string; hex: string } but param.value is any type
try {
if (!bigNumberObj) {
return true
}
const bigNumber = BigNumber.from(bigNumberObj.hex)
return bigNumber.isZero()
} catch (error) {
return true // Treat as zero if there's any error
}
}
function getTokenAmounts(commands: UniversalRouterCommand[]): { function getTokenAmounts(commands: UniversalRouterCommand[]): {
inputValue: string inputValue: string
outputValue: string outputValue: string
...@@ -205,7 +191,9 @@ function getTokenAmounts(commands: UniversalRouterCommand[]): { ...@@ -205,7 +191,9 @@ function getTokenAmounts(commands: UniversalRouterCommand[]): {
const inputValue = firstAmountInParam?.value const inputValue = firstAmountInParam?.value
const fallbackOutputValue = sweepAmountOutParam?.value || unwrapWethAmountOutParam?.value const fallbackOutputValue = sweepAmountOutParam?.value || unwrapWethAmountOutParam?.value
const outputValue = const outputValue =
fallbackOutputValue && isZeroBigNumber(lastAmountOutParam?.value) ? fallbackOutputValue : lastAmountOutParam?.value fallbackOutputValue && isZeroBigNumberParam(lastAmountOutParam?.value)
? fallbackOutputValue
: lastAmountOutParam?.value
return { return {
inputValue: inputValue || '0', // Safe due to assert inputValue: inputValue || '0', // Safe due to assert
...@@ -384,3 +372,34 @@ export function getTokenDetailsFromV4SwapCommands(command: UniversalRouterComman ...@@ -384,3 +372,34 @@ export function getTokenDetailsFromV4SwapCommands(command: UniversalRouterComman
return { inputAddress, outputAddress, inputValue, outputValue } return { inputAddress, outputAddress, inputValue, outputValue }
} }
export function isNonZeroBigNumber(value: string | undefined): boolean {
if (!value) {
return false
}
try {
const valueBN = BigNumber.from(value)
return !valueBN.isZero()
} catch {
return false
}
}
interface BigNumberParam {
type: string
hex: string
}
const isBigNumberParam = (obj: unknown): obj is BigNumberParam =>
typeof obj === 'object' && !!obj && 'hex' in obj && typeof (obj as BigNumberParam).hex === 'string'
// We have to type this as unknown because BigNumberSchema is any (as defined in apps/extension/src/app/features/dappRequests/types/EthersTypes.ts)
function isZeroBigNumberParam(bigNumberObj: unknown): boolean {
// We treat an undefined or badly formatted param as zero
if (!bigNumberObj || !isBigNumberParam(bigNumberObj)) {
return true
}
return !isNonZeroBigNumber(bigNumberObj.hex)
}
import { SortButton } from 'src/components/explore/SortButton' import { SortButton } from 'src/components/explore/SortButton'
import { act, render } from 'src/test/test-utils' import { render } from 'src/test/test-utils'
import { CustomRankingType, ExploreOrderBy, RankingType } from 'wallet/src/features/wallet/types' import { CustomRankingType, ExploreOrderBy, RankingType } from 'wallet/src/features/wallet/types'
jest.mock('react-native-context-menu-view', () => { jest.mock('react-native-context-menu-view', () => {
...@@ -9,21 +9,9 @@ jest.mock('react-native-context-menu-view', () => { ...@@ -9,21 +9,9 @@ jest.mock('react-native-context-menu-view', () => {
}) })
describe('SortButton', () => { describe('SortButton', () => {
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.useRealTimers()
})
it('renders without error', () => { it('renders without error', () => {
const tree = render(<SortButton orderBy={RankingType.Volume} />) const tree = render(<SortButton orderBy={RankingType.Volume} />)
act(async () => {
jest.runAllTimers()
})
expect(tree).toMatchSnapshot() expect(tree).toMatchSnapshot()
}) })
...@@ -46,9 +34,6 @@ describe('SortButton', () => { ...@@ -46,9 +34,6 @@ describe('SortButton', () => {
describe.each(cases)('when ordering by $test', ({ orderBy, label }) => { describe.each(cases)('when ordering by $test', ({ orderBy, label }) => {
it(`renders ${label} as the selected option`, () => { it(`renders ${label} as the selected option`, () => {
const { queryByText } = render(<SortButton orderBy={orderBy} />) const { queryByText } = render(<SortButton orderBy={orderBy} />)
act(async () => {
jest.runAllTimers()
})
const selectedOption = queryByText(label) const selectedOption = queryByText(label)
expect(selectedOption).toBeTruthy() expect(selectedOption).toBeTruthy()
......
...@@ -10,31 +10,22 @@ import { selectBackupReminderLastSeenTs } from 'wallet/src/features/behaviorHist ...@@ -10,31 +10,22 @@ import { selectBackupReminderLastSeenTs } from 'wallet/src/features/behaviorHist
import { Account } from 'wallet/src/features/wallet/accounts/types' import { Account } from 'wallet/src/features/wallet/accounts/types'
const BACKUP_REMINDER_DELAY_MS = 20 * ONE_SECOND_MS const BACKUP_REMINDER_DELAY_MS = 20 * ONE_SECOND_MS
const BACKUP_REMINDER_MIN_TIMEOUT_MS = 2 * ONE_SECOND_MS
export function useOpenBackupReminderModal(activeAccount: Account): void { export function useOpenBackupReminderModal(activeAccount: Account): void {
const dispatch = useDispatch() const dispatch = useDispatch()
const txns = useSelectAddressTransactions(activeAccount.address) const txns = useSelectAddressTransactions(activeAccount.address)
const { isOpen: isBackupReminderModalOpen } = useSelector(selectModalState(ModalName.BackupReminder)) const { isOpen: isBackupReminderModalOpen } = useSelector(selectModalState(ModalName.BackupReminder))
const { isOpen: isBackupReminderWarningModalOpen } = useSelector(selectModalState(ModalName.BackupReminderWarning))
const backupReminderLastSeenTs = useSelector(selectBackupReminderLastSeenTs) const backupReminderLastSeenTs = useSelector(selectBackupReminderLastSeenTs)
const isSignerAccount = activeAccount.type === AccountType.SignerMnemonic const isSignerAccount = activeAccount.type === AccountType.SignerMnemonic
const shouldOpenBackupReminderModal = const shouldOpenBackupReminderModal =
!isBackupReminderModalOpen && !isBackupReminderModalOpen && isSignerAccount && !!txns && !activeAccount.backups
!isBackupReminderWarningModalOpen &&
isSignerAccount &&
!!txns &&
!activeAccount.backups
useEffect(() => { useEffect(() => {
if (shouldOpenBackupReminderModal && backupReminderLastSeenTs === undefined) { if (shouldOpenBackupReminderModal && backupReminderLastSeenTs === undefined) {
// Get the min addedTime from the transactions (i.e. the user's first transaction) // Get the min addedTime from the transactions (i.e. the user's first transaction)
const minAddedTime = Math.min(...txns.map((txn) => txn.addedTime)) const minAddedTime = Math.min(...txns.map((txn) => txn.addedTime))
const remainingTimeMs = Math.max( const remainingTimeMs = Math.max(minAddedTime + BACKUP_REMINDER_DELAY_MS - Date.now(), 0)
minAddedTime + BACKUP_REMINDER_DELAY_MS - Date.now(),
BACKUP_REMINDER_MIN_TIMEOUT_MS,
)
const timeoutId = setTimeout(() => { const timeoutId = setTimeout(() => {
dispatch(openModal({ name: ModalName.BackupReminder })) dispatch(openModal({ name: ModalName.BackupReminder }))
}, remainingTimeMs) }, remainingTimeMs)
......
...@@ -20,8 +20,7 @@ import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard' ...@@ -20,8 +20,7 @@ import { BaseCard } from 'uniswap/src/components/BaseCard/BaseCard'
import { Scrollbar } from 'uniswap/src/components/misc/Scrollbar' import { Scrollbar } from 'uniswap/src/components/misc/Scrollbar'
import { MenuItemProp } from 'uniswap/src/components/modals/ActionSheetModal' import { MenuItemProp } from 'uniswap/src/components/modals/ActionSheetModal'
import { useAppInsets } from 'uniswap/src/hooks/useAppInsets' import { useAppInsets } from 'uniswap/src/hooks/useAppInsets'
import { isAndroid, isInterface, isTouchable } from 'utilities/src/platform' import { isAndroid, isInterface, isMobileApp, isTouchable } from 'utilities/src/platform'
import { useTimeout } from 'utilities/src/time/timing'
const DEFAULT_MIN_WIDTH = 225 const DEFAULT_MIN_WIDTH = 225
...@@ -177,22 +176,17 @@ const ActionSheetBackdropWithContent = memo(function ActionSheetBackdropWithCont ...@@ -177,22 +176,17 @@ const ActionSheetBackdropWithContent = memo(function ActionSheetBackdropWithCont
toggleMeasurements: DropdownState['toggleMeasurements'] toggleMeasurements: DropdownState['toggleMeasurements']
contentProps: ActionSheetDropdownProps contentProps: ActionSheetDropdownProps
closeOnSelect: boolean closeOnSelect: boolean
}): JSX.Element | null { }): JSX.Element {
/* /*
There is a race condition when we switch from a view with one Portal to another view with a Portal. We need to add key to Portal on mobile, becuase of a bug in tamagui.
It seems that if we mount a second Portal while the first is still mounted, the second would not work properly. Remove when https://linear.app/uniswap/issue/WALL-4817/tamaguis-portal-stops-reacting-to-re-renders is done
setTimeout with 0ms is a workaround to avoid this issue for now
Remove when https://linear.app/uniswap/issue/WALL-4817 is resolved
*/ */
const [shouldRender, setShouldRender] = useState(false) const key = useMemo(
useTimeout(() => setShouldRender(true), 0) () => (isMobileApp ? Math.random() : undefined), // eslint-disable-next-line react-hooks/exhaustive-deps
[closeDropdown, styles, isOpen, toggleMeasurements, contentProps, closeOnSelect],
if (!shouldRender) { )
return null
}
return ( return (
<Portal zIndex={styles?.dropdownZIndex || zIndices.popover}> <Portal key={key} zIndex={styles?.dropdownZIndex || zIndices.popover}>
<AnimatePresence custom={{ isOpen }}> <AnimatePresence custom={{ isOpen }}>
{isOpen && toggleMeasurements && ( {isOpen && toggleMeasurements && (
<> <>
......
import { NetworkFilter } from 'uniswap/src/components/network/NetworkFilter' import { NetworkFilter } from 'uniswap/src/components/network/NetworkFilter'
import { act, render } from 'uniswap/src/test/test-utils' import { render } from 'uniswap/src/test/test-utils'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import { SUPPORTED_CHAIN_IDS } from 'uniswap/src/features/chains/types' import { SUPPORTED_CHAIN_IDS } from 'uniswap/src/features/chains/types'
...@@ -9,19 +9,8 @@ ReactDOM.createPortal = jest.fn((element) => { ...@@ -9,19 +9,8 @@ ReactDOM.createPortal = jest.fn((element) => {
}) })
describe(NetworkFilter, () => { describe(NetworkFilter, () => {
beforeEach(() => {
jest.useFakeTimers()
})
afterEach(() => {
jest.useRealTimers()
})
it('renders a NetworkFilter', () => { it('renders a NetworkFilter', () => {
const tree = render(<NetworkFilter chainIds={SUPPORTED_CHAIN_IDS} selectedChain={null} onPressChain={() => null} />) const tree = render(<NetworkFilter chainIds={SUPPORTED_CHAIN_IDS} selectedChain={null} onPressChain={() => null} />)
act(async () => {
jest.runAllTimers()
})
expect(tree).toMatchSnapshot() expect(tree).toMatchSnapshot()
}) })
}) })
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment