Skip to content

Commit 7a639ba

Browse files
author
Ross
committed
fix regression in portfolio display
1 parent 2fb0e41 commit 7a639ba

File tree

6 files changed

+242
-25
lines changed

6 files changed

+242
-25
lines changed
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
type TKongVaultListItem = {
2+
chainId: number
3+
address: string
4+
name: string
5+
symbol: string | null
6+
origin?: string | null
7+
inclusion?: {
8+
isYearn?: boolean
9+
[key: string]: boolean | undefined
10+
} | null
11+
isHidden: boolean
12+
isRetired: boolean
13+
kind: string | null
14+
type: string | null
15+
category: string | null
16+
}
17+
18+
type TKongVaultSnapshot = {
19+
address: string
20+
chainId: number
21+
name?: string
22+
symbol?: string
23+
meta?: {
24+
address?: string
25+
chainId?: number
26+
name?: string
27+
isHidden?: boolean
28+
isRetired?: boolean
29+
}
30+
}
31+
32+
type TAuditRecord = {
33+
address: string
34+
chainId: number
35+
inListAll: boolean
36+
inListOriginYearn: boolean
37+
listAllMetadata: {
38+
origin: string | null
39+
inclusionIsYearn: boolean | null
40+
isHidden: boolean | null
41+
isRetired: boolean | null
42+
kind: string | null
43+
type: string | null
44+
category: string | null
45+
} | null
46+
snapshot: {
47+
found: boolean
48+
name: string | null
49+
symbol: string | null
50+
isHidden: boolean | null
51+
isRetired: boolean | null
52+
}
53+
likelyExclusionReason:
54+
| 'missing_upstream_list_item'
55+
| 'excluded_by_origin_filter'
56+
| 'excluded_by_catalog_tag_filter'
57+
| 'missing_snapshot'
58+
| 'included_in_catalog'
59+
}
60+
61+
const DEFAULT_CHAIN_ID = 1
62+
const DEFAULT_ADDRESSES = ['0x7A26C6c1628c86788526eFB81f37a2ffac243A98', '0xf91a9A1C782a1C11B627f6E576d92C7d72CDd4AF']
63+
const KONG_REST_BASE = (process.env.KONG_REST_BASE || 'https://kong.yearn.fi/api/rest').replace(/\/$/, '')
64+
65+
function normalizeAddress(address: string): string {
66+
return address.trim().toLowerCase()
67+
}
68+
69+
function parseArgs(argv: string[]): { chainId: number; addresses: string[] } {
70+
let chainId = DEFAULT_CHAIN_ID
71+
const addresses: string[] = []
72+
73+
for (let index = 0; index < argv.length; index++) {
74+
const current = argv[index]
75+
if (current === '--chain' || current === '-c') {
76+
const value = argv[index + 1]
77+
if (!value) {
78+
throw new Error('Missing value for --chain')
79+
}
80+
chainId = Number(value)
81+
index += 1
82+
continue
83+
}
84+
if (current.startsWith('--chain=')) {
85+
chainId = Number(current.split('=')[1])
86+
continue
87+
}
88+
addresses.push(current)
89+
}
90+
91+
if (!Number.isInteger(chainId) || chainId <= 0) {
92+
throw new Error(`Invalid chain id: ${chainId}`)
93+
}
94+
95+
return {
96+
chainId,
97+
addresses: addresses.length > 0 ? addresses : DEFAULT_ADDRESSES
98+
}
99+
}
100+
101+
async function fetchJson<T>(url: string): Promise<T> {
102+
const response = await fetch(url)
103+
if (!response.ok) {
104+
throw new Error(`HTTP ${response.status} for ${url}`)
105+
}
106+
return (await response.json()) as T
107+
}
108+
109+
async function tryFetchSnapshot(chainId: number, address: string): Promise<TKongVaultSnapshot | null> {
110+
const url = `${KONG_REST_BASE}/snapshot/${chainId}/${address}`
111+
const response = await fetch(url)
112+
if (response.status === 404) {
113+
return null
114+
}
115+
if (!response.ok) {
116+
throw new Error(`HTTP ${response.status} for ${url}`)
117+
}
118+
return (await response.json()) as TKongVaultSnapshot
119+
}
120+
121+
function indexByAddress(list: TKongVaultListItem[]): Map<string, TKongVaultListItem> {
122+
const map = new Map<string, TKongVaultListItem>()
123+
for (const item of list) {
124+
map.set(normalizeAddress(item.address), item)
125+
}
126+
return map
127+
}
128+
129+
function deriveLikelyReason(params: {
130+
inListAll: boolean
131+
inListOriginYearn: boolean
132+
snapshotFound: boolean
133+
origin: string | null
134+
inclusionIsYearn: boolean | null
135+
}): TAuditRecord['likelyExclusionReason'] {
136+
const { inListAll, inListOriginYearn, snapshotFound, origin, inclusionIsYearn } = params
137+
138+
if (!snapshotFound) {
139+
return 'missing_snapshot'
140+
}
141+
if (!inListAll) {
142+
return 'missing_upstream_list_item'
143+
}
144+
if (!inListOriginYearn && origin !== 'yearn') {
145+
return 'excluded_by_origin_filter'
146+
}
147+
if (origin !== 'yearn' && inclusionIsYearn !== true) {
148+
return 'excluded_by_catalog_tag_filter'
149+
}
150+
return 'included_in_catalog'
151+
}
152+
153+
async function main(): Promise<void> {
154+
const { chainId, addresses } = parseArgs(process.argv.slice(2))
155+
const [listAll, listOriginYearn] = await Promise.all([
156+
fetchJson<TKongVaultListItem[]>(`${KONG_REST_BASE}/list/vaults`),
157+
fetchJson<TKongVaultListItem[]>(`${KONG_REST_BASE}/list/vaults?origin=yearn`)
158+
])
159+
160+
const listAllByAddress = indexByAddress(listAll)
161+
const listOriginYearnByAddress = indexByAddress(listOriginYearn)
162+
163+
const results: TAuditRecord[] = []
164+
for (const addressInput of addresses) {
165+
const address = normalizeAddress(addressInput)
166+
const listAllItem = listAllByAddress.get(address)
167+
const listOriginYearnItem = listOriginYearnByAddress.get(address)
168+
const snapshot = await tryFetchSnapshot(chainId, address)
169+
170+
const origin = listAllItem?.origin ?? null
171+
const inclusionIsYearn = listAllItem?.inclusion?.isYearn ?? null
172+
const inListAll = Boolean(listAllItem)
173+
const inListOriginYearn = Boolean(listOriginYearnItem)
174+
175+
results.push({
176+
address,
177+
chainId,
178+
inListAll,
179+
inListOriginYearn,
180+
listAllMetadata: listAllItem
181+
? {
182+
origin,
183+
inclusionIsYearn,
184+
isHidden: listAllItem.isHidden ?? null,
185+
isRetired: listAllItem.isRetired ?? null,
186+
kind: listAllItem.kind ?? null,
187+
type: listAllItem.type ?? null,
188+
category: listAllItem.category ?? null
189+
}
190+
: null,
191+
snapshot: {
192+
found: Boolean(snapshot),
193+
name: snapshot?.name ?? snapshot?.meta?.name ?? null,
194+
symbol: snapshot?.symbol ?? null,
195+
isHidden: snapshot?.meta?.isHidden ?? null,
196+
isRetired: snapshot?.meta?.isRetired ?? null
197+
},
198+
likelyExclusionReason: deriveLikelyReason({
199+
inListAll,
200+
inListOriginYearn,
201+
snapshotFound: Boolean(snapshot),
202+
origin,
203+
inclusionIsYearn
204+
})
205+
})
206+
}
207+
208+
console.log(
209+
JSON.stringify(
210+
{
211+
chainId,
212+
base: KONG_REST_BASE,
213+
totals: {
214+
listAll: listAll.length,
215+
listOriginYearn: listOriginYearn.length
216+
},
217+
results
218+
},
219+
null,
220+
2
221+
)
222+
)
223+
}
224+
225+
void main()

src/components/pages/portfolio/hooks/usePortfolioModel.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,14 @@ export function usePortfolioModel(): TPortfolioModel {
6666
balances
6767
} = useWallet()
6868
const { isActive, openLoginModal, isUserConnecting, isIdentityLoading } = useWeb3()
69-
const { getPrice, katanaAprs, vaults, inclusionYearnVaults, isLoadingVaultList } = useYearn()
69+
const { getPrice, katanaAprs, vaults, allVaults, isLoadingVaultList } = useYearn()
7070
const [sortBy, setSortBy] = useState<TPossibleSortBy>('deposited')
7171
const [sortDirection, setSortDirection] = useState<TSortDirection>('desc')
7272

73-
const yearnHoldingsUniverse = useMemo((): Record<string, TYDaemonVault> => {
74-
return { ...vaults, ...inclusionYearnVaults }
75-
}, [vaults, inclusionYearnVaults])
76-
7773
const vaultLookup = useMemo(() => {
7874
const map = new Map<string, TYDaemonVault>()
7975

80-
Object.values(yearnHoldingsUniverse).forEach((vault) => {
76+
Object.values(allVaults).forEach((vault) => {
8177
const vaultKey = getVaultKey(vault)
8278
map.set(vaultKey, vault)
8379

@@ -88,7 +84,7 @@ export function usePortfolioModel(): TPortfolioModel {
8884
})
8985

9086
return map
91-
}, [yearnHoldingsUniverse])
87+
}, [allVaults])
9288

9389
const holdingsVaults = useMemo(() => {
9490
const result: TYDaemonVault[] = []

src/components/shared/contexts/useWallet.tsx

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,11 @@ export const WalletContextApp = memo(function WalletContextApp(props: {
4848
children: ReactElement
4949
shouldWorkOnTestnet?: boolean
5050
}): ReactElement {
51-
const { vaults, inclusionYearnVaults, isLoadingVaultList, getPrice } = useYearn()
51+
const { allVaults, isLoadingVaultList, getPrice } = useYearn()
5252
const { address: userAddress } = useWeb3()
53-
const yearnHoldingsVaults = useMemo(
54-
(): TDict<TYDaemonVault> => ({ ...vaults, ...inclusionYearnVaults }),
55-
[vaults, inclusionYearnVaults]
56-
)
5753

5854
const allTokens = useYearnTokens({
59-
vaults: yearnHoldingsVaults,
55+
vaults: allVaults,
6056
isLoadingVaultList,
6157
isEnabled: Boolean(userAddress)
6258
})
@@ -126,7 +122,7 @@ export const WalletContextApp = memo(function WalletContextApp(props: {
126122
)
127123

128124
const [cumulatedValueInV2Vaults, cumulatedValueInV3Vaults] = useMemo((): [number, number] => {
129-
const stakingToVault = Object.entries(yearnHoldingsVaults).reduce((acc, [vaultAddress, vault]) => {
125+
const stakingToVault = Object.entries(allVaults).reduce((acc, [vaultAddress, vault]) => {
130126
if (vault.staking?.address && !isZeroAddress(toAddress(vault.staking.address))) {
131127
acc.set(toAddress(vault.staking.address), vaultAddress)
132128
}
@@ -137,15 +133,15 @@ export const WalletContextApp = memo(function WalletContextApp(props: {
137133
.flatMap(([, perChain]) => Object.keys(perChain))
138134
.map((tokenAddress) => {
139135
const normalizedAddress = toAddress(tokenAddress)
140-
const directVault = yearnHoldingsVaults?.[normalizedAddress]
136+
const directVault = allVaults?.[normalizedAddress]
141137
if (directVault) {
142138
return directVault
143139
}
144140
const stakingVaultAddress = stakingToVault.get(normalizedAddress)
145141
if (!stakingVaultAddress) {
146142
return undefined
147143
}
148-
return yearnHoldingsVaults?.[stakingVaultAddress]
144+
return allVaults?.[stakingVaultAddress]
149145
})
150146
.filter((vault): vault is TYDaemonVault => Boolean(vault))
151147
.reduce((acc, vault) => {
@@ -162,7 +158,7 @@ export const WalletContextApp = memo(function WalletContextApp(props: {
162158
},
163159
[0, 0] as [number, number]
164160
)
165-
}, [yearnHoldingsVaults, balances, getBalance, getPrice, getToken])
161+
}, [allVaults, balances, getBalance, getPrice, getToken])
166162

167163
/***************************************************************************
168164
** Setup and render the Context provider to use in the app.

src/components/shared/hooks/useV2VaultFilter.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export function useV2VaultFilter(
6363
showHiddenVaults?: boolean,
6464
enabled?: boolean
6565
): TOptimizedV2VaultFilterResult {
66-
const { vaults, inclusionYearnVaults, getPrice, isLoadingVaultList } = useYearn()
66+
const { vaults, allVaults, getPrice, isLoadingVaultList } = useYearn()
6767
const { getBalance } = useWallet()
6868
const { shouldHideDust } = useAppSettings()
6969
const isEnabled = enabled ?? true
@@ -170,7 +170,7 @@ export function useV2VaultFilter(
170170
upsertVault(vault, { isActive: !isRetired, isRetired, isMigratable: Boolean(vault.migration?.available) })
171171
})
172172

173-
Object.values(inclusionYearnVaults).forEach((vault) => {
173+
Object.values(allVaults).forEach((vault) => {
174174
if (!shouldIncludeVault(vault)) {
175175
return
176176
}
@@ -191,7 +191,7 @@ export function useV2VaultFilter(
191191
})
192192

193193
return vaultMap
194-
}, [isEnabled, isEnabled ? vaults : null, isEnabled ? inclusionYearnVaults : null, checkHasRawHoldings])
194+
}, [isEnabled, isEnabled ? vaults : null, isEnabled ? allVaults : null, checkHasRawHoldings])
195195

196196
const walletFlags = useMemo(() => {
197197
const flags = new Map<string, TVaultWalletFlags>()

src/components/shared/hooks/useV3VaultFilter.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export function useV3VaultFilter(
6969
showHiddenVaults?: boolean,
7070
enabled?: boolean
7171
): TV3VaultFilterResult {
72-
const { vaults, inclusionYearnVaults, getPrice, isLoadingVaultList } = useYearn()
72+
const { vaults, allVaults, getPrice, isLoadingVaultList } = useYearn()
7373
const { getBalance } = useWallet()
7474
const { shouldHideDust } = useAppSettings()
7575
const isEnabled = enabled ?? true
@@ -176,7 +176,7 @@ export function useV3VaultFilter(
176176
upsertVault(vault, { isActive: !isRetired, isRetired, isMigratable: Boolean(vault.migration?.available) })
177177
})
178178

179-
Object.values(inclusionYearnVaults).forEach((vault) => {
179+
Object.values(allVaults).forEach((vault) => {
180180
if (!shouldIncludeVault(vault)) {
181181
return
182182
}
@@ -197,7 +197,7 @@ export function useV3VaultFilter(
197197
})
198198

199199
return vaultMap
200-
}, [isEnabled, isEnabled ? vaults : null, isEnabled ? inclusionYearnVaults : null, checkHasRawHoldings])
200+
}, [isEnabled, isEnabled ? vaults : null, isEnabled ? allVaults : null, checkHasRawHoldings])
201201

202202
const walletFlags = useMemo(() => {
203203
const flags = new Map<string, TVaultWalletFlags>()

src/components/shared/hooks/useVaultFilterUtils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export function createCheckHasHoldings(
4949
const hasVaultBalance = vaultBalance.raw > 0n
5050
const vaultPrice = getPrice({ address: vaultAddress, chainID })
5151

52-
if (staking.available && !isZeroAddress(staking.address)) {
52+
if (!isZeroAddress(staking.address)) {
5353
const stakingBalance = getBalance({
5454
address: staking.address,
5555
chainID
@@ -108,7 +108,7 @@ export function getVaultHoldingsUsdValue(
108108
const vaultDirectValue = Number(vaultToken.value || 0)
109109
const vaultShares = Number(getBalance({ address: vaultAddress, chainID }).normalized || 0)
110110

111-
const canUseStaking = staking.available && !isZeroAddress(staking.address)
111+
const canUseStaking = !isZeroAddress(staking.address)
112112
const stakingToken = canUseStaking ? getToken({ address: staking.address, chainID }) : null
113113
const stakingDirectValue = Number(stakingToken?.value || 0)
114114
const stakingShares = canUseStaking ? Number(getBalance({ address: staking.address, chainID }).normalized || 0) : 0

0 commit comments

Comments
 (0)