Skip to content

Commit 6596fd3

Browse files
ci(release): publish latest release
1 parent 5d1b539 commit 6596fd3

458 files changed

Lines changed: 17923 additions & 9836 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ Uniswap Universe is a monorepo containing all Uniswap front-end interfaces:
1515
### Setup
1616

1717
```bash
18-
# Initial setup (requires 1Password CLI)
18+
# Initial setup
1919
bun install
2020
bun local:check
21-
bun lfg # Sets up mobile and extension
21+
bun config:login
2222
```
2323

2424
### Development Servers

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ This is the **public** repository for Uniswap Labs’ front-end interfaces, incl
1212
```bash
1313
git clone git@github.com:Uniswap/interface.git
1414
bun install
15-
bun lfg
1615
bun web start
1716
```
1817

RELEASE

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
IPFS hash of the deployment:
2-
- CIDv0: `QmNoqk5DfwqHkqD2EP3ERTTGdGo17763whQcL7RQJ7RnFi`
3-
- CIDv1: `bafybeiag64ug4dvjjfpciyi3uq6cxqjh3vmqxkhwbcnnfpwscikymp7ife`
2+
- CIDv0: `Qmco7hGW4dsJKfhoLv8sGHAXypm7L39jNEPrvPg5RKB9Ef`
3+
- CIDv1: `bafybeigwzm5qyldf3pbjcpr7jf4lswxajkj3cgqlyvfo7ep3wbhokgksna`
44

55
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
66

@@ -10,5 +10,5 @@ You can also access the Uniswap Interface from an IPFS gateway.
1010
Your Uniswap settings are never remembered across different URLs.
1111

1212
IPFS gateways:
13-
- https://bafybeiag64ug4dvjjfpciyi3uq6cxqjh3vmqxkhwbcnnfpwscikymp7ife.ipfs.dweb.link/
14-
- [ipfs://QmNoqk5DfwqHkqD2EP3ERTTGdGo17763whQcL7RQJ7RnFi/](ipfs://QmNoqk5DfwqHkqD2EP3ERTTGdGo17763whQcL7RQJ7RnFi/)
13+
- https://bafybeigwzm5qyldf3pbjcpr7jf4lswxajkj3cgqlyvfo7ep3wbhokgksna.ipfs.dweb.link/
14+
- [ipfs://Qmco7hGW4dsJKfhoLv8sGHAXypm7L39jNEPrvPg5RKB9Ef/](ipfs://Qmco7hGW4dsJKfhoLv8sGHAXypm7L39jNEPrvPg5RKB9Ef/)

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
web/5.151.5
1+
web/5.152.1

apps/extension/README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@
22

33
## Developer Quickstart
44

5-
### Environment variables
6-
7-
Before running the extension, you need to get the environment variables from 1password in order to get full functionality. Run the command `bun extension env:local:download` to copy them to your root folder.
8-
95
### Build System
106

117
The extension is built with **WXT** (Vite-based) for both local development and production. The dev server opens a browser automatically.

apps/extension/package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,6 @@
9797
"clean": "nx clean extension",
9898
"dev": "nx dev extension",
9999
"dev:firefox": "nx dev extension -c dev-firefox",
100-
"env:local:download": "nx env:local:download extension",
101-
"env:local:upload": "nx env:local:upload extension",
102100
"format": "nx run extension:format",
103101
"format:check": "nx run extension:format:check",
104102
"lint": "nx lint extension",

apps/extension/project.json

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,6 @@
6060
}
6161
}
6262
},
63-
"env:local:download": {
64-
"command": "bash ../../scripts/downloadEnvLocal.sh m4dhqfltt3dokkqi3hqwigmf2a .env",
65-
"options": {
66-
"cwd": "{projectRoot}"
67-
}
68-
},
69-
"env:local:upload": {
70-
"command": "bash ../../scripts/uploadEnvLocal.sh m4dhqfltt3dokkqi3hqwigmf2a .env",
71-
"options": {
72-
"cwd": "{projectRoot}"
73-
}
74-
},
7563
"format": {},
7664
"format:check": {},
7765
"lint:oxlint": {},
@@ -100,7 +88,7 @@
10088
"outputs": ["{projectRoot}/.wxt/types", "{projectRoot}/.wxt/tsconfig.json", "{projectRoot}/.wxt/wxt.d.ts"]
10189
},
10290
"config:pull": {
103-
"command": "if [ \"$USE_NEW_CONFIGS\" = \"true\" ] && [ \"$SKIP_CONFIG_PULL\" != \"true\" ]; then bun config:pull extension --env \"${CONFIG_ENV:-development}\"; fi",
91+
"command": "if [ \"$USE_NEW_CONFIGS\" != \"false\" ] && [ \"$SKIP_CONFIG_PULL\" != \"true\" ]; then bun config:pull extension --env \"${CONFIG_ENV:-development}\"; fi",
10492
"options": { "cwd": "{workspaceRoot}" },
10593
"defaultConfiguration": "development",
10694
"configurations": {

apps/extension/src/app/components/tabs/PoolsTab.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
PositionStatusFilter,
1515
PositionStatusFilterValue,
1616
} from 'uniswap/src/features/positions/components/PositionStatusFilter'
17+
import { usePoolsPositionsReport } from 'uniswap/src/features/positions/hooks/usePoolsPositionsReport'
1718
import { useWalletPositions } from 'uniswap/src/features/positions/hooks/useWalletPositions'
1819
import { filterAndSortPositions, getPositionKey } from 'uniswap/src/features/positions/utils'
1920
import { useEvent } from 'utilities/src/react/hooks'
@@ -54,6 +55,7 @@ export const PoolsTab = memo(function PoolsTabInner({
5455
hasNextPage,
5556
isFetching,
5657
isFetchingNextPage,
58+
pagesLoaded,
5759
} = useWalletPositions({
5860
account: address,
5961
chainIds: chains,
@@ -79,6 +81,14 @@ export const PoolsTab = memo(function PoolsTabInner({
7981
const isLoadingFirstPage = isFetchingFirstPage && !hasData
8082
const hasErrorWithoutData = !!error && !hasData && !isFetchingFirstPage
8183

84+
usePoolsPositionsReport({
85+
positions,
86+
pagesLoaded,
87+
hasMore: hasNextPage,
88+
isLoading: isLoadingFirstPage,
89+
enabled: !skip,
90+
})
91+
8292
const visiblePositions = useMemo(() => filterAndSortPositions(positions, filterStatuses), [positions, filterStatuses])
8393
const filteredHiddenPositions = useMemo(
8494
() => filterAndSortPositions(hiddenPositions, filterStatuses),
@@ -96,7 +106,9 @@ export const PoolsTab = memo(function PoolsTabInner({
96106

97107
return (
98108
<ScrollView showsVerticalScrollIndicator={false} width="100%">
99-
<PositionStatusFilter value={statusFilter} onChange={setStatusFilter} disabled={hasErrorWithoutData} />
109+
<Flex mb="$spacing8">
110+
<PositionStatusFilter value={statusFilter} onChange={setStatusFilter} disabled={hasErrorWithoutData} />
111+
</Flex>
100112

101113
{hasErrorWithoutData ? (
102114
<BaseCard.ErrorState
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { select } from '@redux-saga/core/effects'
2+
import { expectSaga } from 'redux-saga-test-plan'
3+
import * as matchers from 'redux-saga-test-plan/matchers'
4+
import type { StaticProvider } from 'redux-saga-test-plan/providers'
5+
import { saveDappConnection } from 'src/app/features/dapp/actions'
6+
import { type DappInfo, dappStore } from 'src/app/features/dapp/store'
7+
import { saveAccount } from 'src/app/features/dappRequests/accounts'
8+
import { type SenderTabInfo } from 'src/app/features/dappRequests/shared'
9+
import { UniverseChainId } from 'uniswap/src/features/chains/types'
10+
import { pushNotification } from 'uniswap/src/features/notifications/slice/slice'
11+
import { AppNotificationType } from 'uniswap/src/features/notifications/slice/types'
12+
import { Platform } from 'uniswap/src/features/platforms/types/Platform'
13+
import { getEnabledChainIdsSaga } from 'uniswap/src/features/settings/saga'
14+
import { extractBaseUrl } from 'utilities/src/format/urls'
15+
import { type Account } from 'wallet/src/features/wallet/accounts/types'
16+
import { getProvider } from 'wallet/src/features/wallet/context'
17+
import { selectActiveAccount } from 'wallet/src/features/wallet/selectors'
18+
import { ACCOUNT, ACCOUNT2 } from 'wallet/src/test/fixtures'
19+
20+
const SENDER_TAB_INFO: SenderTabInfo = {
21+
id: 1,
22+
url: 'https://app.uniswap.org/swap',
23+
favIconUrl: 'https://app.uniswap.org/favicon.ico',
24+
}
25+
const DAPP_URL = extractBaseUrl(SENDER_TAB_INFO.url)
26+
const PROVIDER_URL = 'https://rpc.example/'
27+
28+
function getProviders({
29+
activeAccount,
30+
dappInfo,
31+
orderedAddresses,
32+
}: {
33+
activeAccount: Account
34+
dappInfo: DappInfo | undefined
35+
orderedAddresses: Address[]
36+
}): StaticProvider[] {
37+
return [
38+
[select(selectActiveAccount), activeAccount],
39+
[matchers.call.fn(getEnabledChainIdsSaga), { defaultChainId: UniverseChainId.Mainnet }],
40+
[matchers.call.fn(dappStore.getDappInfo), dappInfo],
41+
[matchers.call.fn(getProvider), { connection: { url: PROVIDER_URL } }],
42+
[matchers.call.fn(saveDappConnection), undefined],
43+
[matchers.call.fn(dappStore.getDappOrderedConnectedAddresses), orderedAddresses],
44+
]
45+
}
46+
47+
describe('saveAccount', () => {
48+
// Security regression: a dapp connected only to Account A must not be able to silently absorb the
49+
// extension's active Account B via an auto-confirmed eth_requestAccounts / wallet_requestPermissions.
50+
it('does not expand an already-connected origin to the unapproved active account', async () => {
51+
const dappInfo: DappInfo = {
52+
lastChainId: UniverseChainId.Mainnet,
53+
connectedAccounts: [ACCOUNT],
54+
activeConnectedAddress: ACCOUNT.address,
55+
}
56+
57+
const { returnValue } = await expectSaga(saveAccount, SENDER_TAB_INFO)
58+
.provide(getProviders({ activeAccount: ACCOUNT2, dappInfo, orderedAddresses: [ACCOUNT.address] }))
59+
.not.call.fn(saveDappConnection)
60+
.run()
61+
62+
// Only the already-approved account is returned to the dapp; Account B was never added.
63+
expect(returnValue).toEqual({
64+
dappUrl: DAPP_URL,
65+
connectedAddresses: [ACCOUNT.address],
66+
chainId: UniverseChainId.Mainnet,
67+
providerUrl: PROVIDER_URL,
68+
})
69+
})
70+
71+
it('persists the connection when the active account is already approved for the origin', async () => {
72+
const dappInfo: DappInfo = {
73+
lastChainId: UniverseChainId.Mainnet,
74+
connectedAccounts: [ACCOUNT, ACCOUNT2],
75+
activeConnectedAddress: ACCOUNT.address,
76+
}
77+
78+
await expectSaga(saveAccount, SENDER_TAB_INFO)
79+
.provide(
80+
getProviders({ activeAccount: ACCOUNT2, dappInfo, orderedAddresses: [ACCOUNT2.address, ACCOUNT.address] }),
81+
)
82+
.call.fn(saveDappConnection)
83+
.run()
84+
})
85+
86+
it('saves the connection and notifies on a first-time connection', async () => {
87+
await expectSaga(saveAccount, SENDER_TAB_INFO)
88+
.provide(getProviders({ activeAccount: ACCOUNT, dappInfo: undefined, orderedAddresses: [ACCOUNT.address] }))
89+
.call.fn(saveDappConnection)
90+
.put(pushNotification({ type: AppNotificationType.DappConnected, dappIconUrl: SENDER_TAB_INFO.favIconUrl }))
91+
.run()
92+
})
93+
})

apps/extension/src/app/features/dappRequests/accounts.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { type JsonRpcProvider } from '@ethersproject/providers'
22
import { providerErrors, serializeError } from '@metamask/rpc-errors'
33
import { saveDappConnection } from 'src/app/features/dapp/actions'
44
import { type DappInfo, dappStore } from 'src/app/features/dapp/store'
5-
import { getOrderedConnectedAddresses } from 'src/app/features/dapp/utils'
5+
import { getOrderedConnectedAddresses, isConnectedAccount } from 'src/app/features/dapp/utils'
66
import type { SenderTabInfo } from 'src/app/features/dappRequests/shared'
77
import {
88
type AccountResponse,
@@ -108,15 +108,24 @@ export function* saveAccount({ url, favIconUrl }: SenderTabInfo) {
108108
return undefined
109109
}
110110

111-
yield* call(saveDappConnection, { dappUrl, account: activeAccount, iconUrl: favIconUrl })
112-
// No dapp info means that this is a first time connection request
113-
if (!dappInfo) {
114-
yield* put(
115-
pushNotification({
116-
type: AppNotificationType.DappConnected,
117-
dappIconUrl: favIconUrl,
118-
}),
119-
)
111+
// Block an auto-confirmed account request from silently adding the active account to an origin that is already
112+
// connected to a different account. New accounts must be connected through the user-driven flow.
113+
const wouldExpandConnectionToUnapprovedAccount =
114+
!!dappInfo &&
115+
dappInfo.connectedAccounts.length > 0 &&
116+
!isConnectedAccount(dappInfo.connectedAccounts, activeAccount.address)
117+
118+
if (!wouldExpandConnectionToUnapprovedAccount) {
119+
yield* call(saveDappConnection, { dappUrl, account: activeAccount, iconUrl: favIconUrl })
120+
// No dapp info means that this is a first time connection request
121+
if (!dappInfo) {
122+
yield* put(
123+
pushNotification({
124+
type: AppNotificationType.DappConnected,
125+
dappIconUrl: favIconUrl,
126+
}),
127+
)
128+
}
120129
}
121130

122131
const chainId = dappInfo?.lastChainId ?? defaultChainId
@@ -125,7 +134,6 @@ export function* saveAccount({ url, favIconUrl }: SenderTabInfo) {
125134

126135
return {
127136
dappUrl,
128-
activeAccount,
129137
connectedAddresses,
130138
chainId,
131139
providerUrl: provider.connection.url,
@@ -148,7 +156,7 @@ export function* getAccountRequest(request: RequestAccountRequest, senderTabInfo
148156

149157
yield* call(dappResponseMessageChannel.sendMessageToTab, senderTabInfo.id, errorResponse)
150158
} else {
151-
const { dappUrl, activeAccount, connectedAddresses, chainId, providerUrl } = accountInfo
159+
const { dappUrl, connectedAddresses, chainId, providerUrl } = accountInfo
152160

153161
const accountResponse: AccountResponse = {
154162
type: DappResponseType.AccountResponse,
@@ -164,7 +172,7 @@ export function* getAccountRequest(request: RequestAccountRequest, senderTabInfo
164172
sendAnalyticsEvent(ExtensionEventName.DappConnect, {
165173
dappUrl,
166174
chainId,
167-
activeConnectedAddress: activeAccount.address,
175+
activeConnectedAddress: connectedAddresses[0],
168176
connectedAddresses,
169177
})
170178
}

0 commit comments

Comments
 (0)