@whisk/steakhouse 0.0.2 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +1 -12
- package/dist/client.js +1 -10
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +6 -5
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/dist/metadata/types.d.ts +3 -1
- package/dist/metadata/vaults.js +17 -15
- package/dist/metadata/vaults.js.map +1 -1
- package/dist/queries/fragments/vaultDetail.d.ts +265 -0
- package/dist/queries/fragments/vaultDetail.js +148 -0
- package/dist/queries/fragments/vaultDetail.js.map +1 -0
- package/dist/queries/getDetailedVault.d.ts +591 -0
- package/dist/queries/getDetailedVault.js +92 -0
- package/dist/queries/getDetailedVault.js.map +1 -0
- package/dist/queries/getVaults.d.ts +274 -13
- package/dist/queries/getVaults.js +36 -10
- package/dist/queries/getVaults.js.map +1 -1
- package/dist/queries/index.d.ts +6 -5
- package/dist/queries/index.js +6 -8
- package/dist/queries/index.js.map +1 -1
- package/dist/queries/steakhouseMetadata.d.ts +13 -0
- package/dist/queries/steakhouseMetadata.js +12 -0
- package/dist/queries/steakhouseMetadata.js.map +1 -0
- package/dist/queries/types.d.ts +2 -4
- package/dist/react/hooks/index.d.ts +8 -5
- package/dist/react/hooks/index.js +2 -0
- package/dist/react/hooks/index.js.map +1 -1
- package/dist/react/hooks/useDetailedVault.d.ts +12 -0
- package/dist/react/hooks/useDetailedVault.js +16 -0
- package/dist/react/hooks/useDetailedVault.js.map +1 -0
- package/dist/react/hooks/useSteakhouseQuery.d.ts +2 -4
- package/dist/react/hooks/useSteakhouseQuery.js +1 -1
- package/dist/react/hooks/useSteakhouseQuery.js.map +1 -1
- package/dist/react/hooks/useVaults.d.ts +7 -14
- package/dist/react/hooks/useVaults.js +3 -1
- package/dist/react/hooks/useVaults.js.map +1 -1
- package/dist/react/index.d.ts +8 -5
- package/dist/react/index.js +1 -8
- package/dist/react/index.js.map +1 -1
- package/dist/react/provider.d.ts +3 -5
- package/package.json +9 -4
- package/src/client.ts +3 -19
- package/src/index.ts +1 -2
- package/src/metadata/types.ts +3 -1
- package/src/metadata/vaults.ts +17 -17
- package/src/queries/fragments/vaultDetail.ts +151 -0
- package/src/queries/getDetailedVault.test.ts +212 -0
- package/src/queries/getDetailedVault.ts +132 -0
- package/src/queries/getVaults.test.ts +186 -0
- package/src/queries/getVaults.ts +62 -13
- package/src/queries/index.ts +9 -7
- package/src/queries/steakhouseMetadata.ts +19 -0
- package/src/react/hooks/index.ts +1 -0
- package/src/react/hooks/useDetailedVault.ts +19 -0
- package/src/react/hooks/useSteakhouseQuery.ts +3 -3
- package/src/react/hooks/useVaults.ts +7 -2
- package/src/react/index.ts +1 -6
- package/dist/queries/fragments/vault.d.ts +0 -19
- package/dist/queries/fragments/vault.js +0 -16
- package/dist/queries/fragments/vault.js.map +0 -1
- package/src/client.test.ts +0 -27
- package/src/queries/fragments/vault.ts +0 -15
package/dist/react/provider.d.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import React, { PropsWithChildren } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import '@urql/core';
|
|
4
|
-
import '@whisk/client';
|
|
2
|
+
import { WhiskClient } from '@whisk/client';
|
|
5
3
|
|
|
6
4
|
interface SteakhouseContextValue {
|
|
7
|
-
client:
|
|
5
|
+
client: WhiskClient;
|
|
8
6
|
}
|
|
9
7
|
declare function useSteakhouse(): SteakhouseContextValue;
|
|
10
8
|
interface SteakhouseProviderProps extends PropsWithChildren {
|
|
11
|
-
client:
|
|
9
|
+
client: WhiskClient;
|
|
12
10
|
}
|
|
13
11
|
declare function SteakhouseProvider({ children, client, }: SteakhouseProviderProps): React.JSX.Element;
|
|
14
12
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whisk/steakhouse",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/office-supply-ventures/whisk-sdk.git",
|
|
7
|
+
"directory": "packages/steakhouse"
|
|
8
|
+
},
|
|
4
9
|
"type": "module",
|
|
5
10
|
"main": "./dist/index.js",
|
|
6
11
|
"license": "MIT",
|
|
@@ -29,10 +34,10 @@
|
|
|
29
34
|
}
|
|
30
35
|
},
|
|
31
36
|
"dependencies": {
|
|
32
|
-
"@urql/core": "
|
|
37
|
+
"@urql/core": "6.0.1",
|
|
33
38
|
"gql.tada": "1.9.0",
|
|
34
|
-
"@whisk/client": "0.0.
|
|
35
|
-
"@whisk/graphql": "0.0.
|
|
39
|
+
"@whisk/client": "0.0.10",
|
|
40
|
+
"@whisk/graphql": "0.0.10"
|
|
36
41
|
},
|
|
37
42
|
"peerDependencies": {
|
|
38
43
|
"graphql": ">=16",
|
package/src/client.ts
CHANGED
|
@@ -1,19 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export interface SteakhouseClientConfig extends WhiskClientConfig {}
|
|
5
|
-
|
|
6
|
-
export class SteakhouseClient {
|
|
7
|
-
private readonly client: WhiskClient
|
|
8
|
-
|
|
9
|
-
constructor(config: SteakhouseClientConfig) {
|
|
10
|
-
this.client = new WhiskClient(config)
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
public async query<TValue, TVariables extends AnyVariables>(
|
|
14
|
-
document: TypedDocumentNode<TValue, TVariables>,
|
|
15
|
-
variables: TVariables,
|
|
16
|
-
): Promise<TValue> {
|
|
17
|
-
return this.client.query(document, variables)
|
|
18
|
-
}
|
|
19
|
-
}
|
|
1
|
+
// Re-export from @whisk/client
|
|
2
|
+
export type { WhiskClientConfig as SteakhouseClientConfig } from "@whisk/client"
|
|
3
|
+
export { WhiskClient as SteakhouseClient } from "@whisk/client"
|
package/src/index.ts
CHANGED
package/src/metadata/types.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export type Address = `0x${string}`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/** Protocol type matching Erc4626VaultProtocol enum */
|
|
4
|
+
export type VaultProtocol = "morpho_v1" | "morpho_v2" | "generic" | "box"
|
|
4
5
|
|
|
5
6
|
export type VaultTag = "featured" | "new" | "deprecated"
|
|
6
7
|
|
|
7
8
|
export interface VaultConfig {
|
|
9
|
+
readonly chainId: number
|
|
8
10
|
readonly address: Address
|
|
9
11
|
readonly protocol: VaultProtocol
|
|
10
12
|
readonly name?: string
|
package/src/metadata/vaults.ts
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
import type { VaultConfig } from "./types.js"
|
|
2
2
|
|
|
3
3
|
// Mainnet vault configurations
|
|
4
|
-
// TODO: Add real Steakhouse vault addresses
|
|
5
4
|
export const MAINNET_VAULTS: readonly VaultConfig[] = [
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
{
|
|
6
|
+
chainId: 1,
|
|
7
|
+
address: "0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB",
|
|
8
|
+
protocol: "morpho_v1",
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
chainId: 1,
|
|
12
|
+
address: "0xbeef0046fcab1dE47E41fB75BB3dC4Dfc94108E3",
|
|
13
|
+
protocol: "morpho_v2",
|
|
14
|
+
tag: "featured",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
chainId: 1,
|
|
18
|
+
address: "0xbeeff2C5bF38f90e3482a8b19F12E5a6D2FCa757",
|
|
19
|
+
protocol: "morpho_v2",
|
|
20
|
+
},
|
|
13
21
|
] as const
|
|
14
22
|
|
|
15
23
|
// Base vault configurations
|
|
16
|
-
|
|
17
|
-
export const BASE_VAULTS: readonly VaultConfig[] = [
|
|
18
|
-
// Example placeholder vault
|
|
19
|
-
// {
|
|
20
|
-
// address: "0x...",
|
|
21
|
-
// protocol: "steakhouse",
|
|
22
|
-
// name: "Steakhouse USDC Vault",
|
|
23
|
-
// },
|
|
24
|
-
] as const
|
|
24
|
+
export const BASE_VAULTS: readonly VaultConfig[] = [] as const
|
|
25
25
|
|
|
26
26
|
// All vaults across all chains
|
|
27
27
|
export const ALL_VAULTS = [...MAINNET_VAULTS, ...BASE_VAULTS] as const
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { type FragmentOf, graphql } from "@whisk/graphql"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Full fragment for vault detail view.
|
|
5
|
+
* Uses inline fragments to handle both MorphoVault (v1) and MorphoVaultV2.
|
|
6
|
+
*/
|
|
7
|
+
export const vaultDetailFragment = graphql(`
|
|
8
|
+
fragment VaultDetailFragment on Erc4626Vault {
|
|
9
|
+
chain {
|
|
10
|
+
id
|
|
11
|
+
name
|
|
12
|
+
icon
|
|
13
|
+
}
|
|
14
|
+
vaultAddress
|
|
15
|
+
name
|
|
16
|
+
symbol
|
|
17
|
+
decimals
|
|
18
|
+
asset {
|
|
19
|
+
address
|
|
20
|
+
symbol
|
|
21
|
+
name
|
|
22
|
+
icon
|
|
23
|
+
priceUsd
|
|
24
|
+
decimals
|
|
25
|
+
}
|
|
26
|
+
totalAssets {
|
|
27
|
+
raw
|
|
28
|
+
formatted
|
|
29
|
+
usd
|
|
30
|
+
}
|
|
31
|
+
apy(timeframe: seven_days) {
|
|
32
|
+
base
|
|
33
|
+
total
|
|
34
|
+
rewards {
|
|
35
|
+
asset {
|
|
36
|
+
symbol
|
|
37
|
+
icon
|
|
38
|
+
}
|
|
39
|
+
apr
|
|
40
|
+
}
|
|
41
|
+
fee
|
|
42
|
+
}
|
|
43
|
+
... on MorphoVault {
|
|
44
|
+
totalLiquidity {
|
|
45
|
+
formatted
|
|
46
|
+
usd
|
|
47
|
+
}
|
|
48
|
+
v1PerformanceFee: performanceFee
|
|
49
|
+
feeRecipientAddress
|
|
50
|
+
ownerAddress
|
|
51
|
+
curatorAddress
|
|
52
|
+
guardianAddress
|
|
53
|
+
metadata {
|
|
54
|
+
description
|
|
55
|
+
image
|
|
56
|
+
forumLink
|
|
57
|
+
curator {
|
|
58
|
+
name
|
|
59
|
+
image
|
|
60
|
+
url
|
|
61
|
+
}
|
|
62
|
+
curators {
|
|
63
|
+
name
|
|
64
|
+
image
|
|
65
|
+
url
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
marketAllocations {
|
|
69
|
+
market {
|
|
70
|
+
marketId
|
|
71
|
+
name
|
|
72
|
+
loanAsset {
|
|
73
|
+
symbol
|
|
74
|
+
icon
|
|
75
|
+
}
|
|
76
|
+
collateralAsset {
|
|
77
|
+
symbol
|
|
78
|
+
icon
|
|
79
|
+
}
|
|
80
|
+
isIdle
|
|
81
|
+
}
|
|
82
|
+
enabled
|
|
83
|
+
supplyCap {
|
|
84
|
+
formatted
|
|
85
|
+
usd
|
|
86
|
+
}
|
|
87
|
+
vaultSupplyShare
|
|
88
|
+
marketSupplyShare
|
|
89
|
+
position {
|
|
90
|
+
supplyAmount {
|
|
91
|
+
formatted
|
|
92
|
+
usd
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
riskAssessment {
|
|
97
|
+
steakhouse {
|
|
98
|
+
score
|
|
99
|
+
rating
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
... on MorphoVaultV2 {
|
|
104
|
+
curatorAddress
|
|
105
|
+
ownerAddress
|
|
106
|
+
metadata {
|
|
107
|
+
description
|
|
108
|
+
image
|
|
109
|
+
forumLink
|
|
110
|
+
curator {
|
|
111
|
+
name
|
|
112
|
+
image
|
|
113
|
+
url
|
|
114
|
+
}
|
|
115
|
+
curators {
|
|
116
|
+
name
|
|
117
|
+
image
|
|
118
|
+
url
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
liquidityAssets {
|
|
122
|
+
formatted
|
|
123
|
+
usd
|
|
124
|
+
}
|
|
125
|
+
idleAssets {
|
|
126
|
+
formatted
|
|
127
|
+
usd
|
|
128
|
+
}
|
|
129
|
+
v2PerformanceFee: performanceFee {
|
|
130
|
+
formatted
|
|
131
|
+
}
|
|
132
|
+
managementFee {
|
|
133
|
+
formatted
|
|
134
|
+
}
|
|
135
|
+
adapters {
|
|
136
|
+
... on MarketV1Adapter {
|
|
137
|
+
adapterAddress
|
|
138
|
+
name
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
riskAssessment {
|
|
142
|
+
steakhouse {
|
|
143
|
+
score
|
|
144
|
+
rating
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
`)
|
|
150
|
+
|
|
151
|
+
export type VaultDetail = FragmentOf<typeof vaultDetailFragment>
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest"
|
|
2
|
+
import { createMockClient } from "../../test/mocks.js"
|
|
3
|
+
import { getDetailedVault } from "./getDetailedVault.js"
|
|
4
|
+
|
|
5
|
+
// Mock the metadata module
|
|
6
|
+
vi.mock("../metadata/vaults.js", () => ({
|
|
7
|
+
ALL_VAULTS: [
|
|
8
|
+
{
|
|
9
|
+
chainId: 1,
|
|
10
|
+
address: "0x1111111111111111111111111111111111111111",
|
|
11
|
+
protocol: "morpho_v1",
|
|
12
|
+
name: "Steakhouse USDC",
|
|
13
|
+
description: "A USDC vault",
|
|
14
|
+
tag: "featured",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
chainId: 1,
|
|
18
|
+
address: "0x2222222222222222222222222222222222222222",
|
|
19
|
+
protocol: "morpho_v1",
|
|
20
|
+
isHidden: true, // Should be excluded
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
}))
|
|
24
|
+
|
|
25
|
+
function createMockVaultDetailResponse(vault: { vaultAddress: string } | null) {
|
|
26
|
+
if (!vault) {
|
|
27
|
+
return { erc4626Vaults: { items: [] } }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
erc4626Vaults: {
|
|
32
|
+
items: [
|
|
33
|
+
{
|
|
34
|
+
chain: { id: 1, name: "Ethereum", icon: "https://eth.icon" },
|
|
35
|
+
vaultAddress: vault.vaultAddress,
|
|
36
|
+
name: "API Vault Name",
|
|
37
|
+
symbol: "VAULT",
|
|
38
|
+
decimals: 18,
|
|
39
|
+
asset: {
|
|
40
|
+
address: "0xasset",
|
|
41
|
+
symbol: "USDC",
|
|
42
|
+
name: "USD Coin",
|
|
43
|
+
icon: "https://icon.url",
|
|
44
|
+
priceUsd: 1.0,
|
|
45
|
+
decimals: 6,
|
|
46
|
+
},
|
|
47
|
+
totalAssets: { raw: "1000000000000", formatted: "1000000", usd: 1000000 },
|
|
48
|
+
totalLiquidity: { formatted: "500000", usd: 500000 },
|
|
49
|
+
apy: {
|
|
50
|
+
base: 0.05,
|
|
51
|
+
total: 0.08,
|
|
52
|
+
rewards: [{ token: { symbol: "MORPHO", icon: "https://morpho.icon" }, apr: 0.03 }],
|
|
53
|
+
fee: 0.01,
|
|
54
|
+
},
|
|
55
|
+
performanceFee: 0.1,
|
|
56
|
+
feeRecipientAddress: "0xfee",
|
|
57
|
+
ownerAddress: "0xowner",
|
|
58
|
+
curatorAddress: "0xcurator",
|
|
59
|
+
guardianAddress: "0xguardian",
|
|
60
|
+
metadata: {
|
|
61
|
+
description: "API description",
|
|
62
|
+
image: "https://image.url",
|
|
63
|
+
forumLink: "https://forum.url",
|
|
64
|
+
curator: {
|
|
65
|
+
name: "Curator",
|
|
66
|
+
image: "https://curator.image",
|
|
67
|
+
url: "https://curator.url",
|
|
68
|
+
},
|
|
69
|
+
curators: [],
|
|
70
|
+
},
|
|
71
|
+
marketAllocations: [],
|
|
72
|
+
riskAssessment: { steakhouse: { score: 0.8, rating: "A" } },
|
|
73
|
+
// Historical data (only in historical query)
|
|
74
|
+
historical: {
|
|
75
|
+
daily: [
|
|
76
|
+
{
|
|
77
|
+
bucketTimestamp: 1704067200,
|
|
78
|
+
totalSupplied: { formatted: "1000000", usd: 1000000 },
|
|
79
|
+
supplyApy7d: { total: 0.08 },
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
describe("getDetailedVault", () => {
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
vi.clearAllMocks()
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it("returns null for vault not in whitelist", async () => {
|
|
95
|
+
const client = createMockClient({})
|
|
96
|
+
|
|
97
|
+
const result = await getDetailedVault(client, {
|
|
98
|
+
chainId: 1,
|
|
99
|
+
address: "0x9999999999999999999999999999999999999999",
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
expect(result).toBeNull()
|
|
103
|
+
expect(client.query).not.toHaveBeenCalled()
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it("returns null for hidden vault", async () => {
|
|
107
|
+
const client = createMockClient({})
|
|
108
|
+
|
|
109
|
+
const result = await getDetailedVault(client, {
|
|
110
|
+
chainId: 1,
|
|
111
|
+
address: "0x2222222222222222222222222222222222222222",
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
expect(result).toBeNull()
|
|
115
|
+
expect(client.query).not.toHaveBeenCalled()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
it("returns null for vault on wrong chain", async () => {
|
|
119
|
+
const client = createMockClient({})
|
|
120
|
+
|
|
121
|
+
const result = await getDetailedVault(client, {
|
|
122
|
+
chainId: 8453, // Wrong chain
|
|
123
|
+
address: "0x1111111111111111111111111111111111111111",
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
expect(result).toBeNull()
|
|
127
|
+
expect(client.query).not.toHaveBeenCalled()
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it("returns vault with historical data by default", async () => {
|
|
131
|
+
const client = createMockClient(
|
|
132
|
+
createMockVaultDetailResponse({
|
|
133
|
+
vaultAddress: "0x1111111111111111111111111111111111111111",
|
|
134
|
+
}),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
const result = await getDetailedVault(client, {
|
|
138
|
+
chainId: 1,
|
|
139
|
+
address: "0x1111111111111111111111111111111111111111",
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
expect(result).not.toBeNull()
|
|
143
|
+
expect(result?.historical).toBeDefined()
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it("can exclude historical data", async () => {
|
|
147
|
+
const mockResponse = createMockVaultDetailResponse({
|
|
148
|
+
vaultAddress: "0x1111111111111111111111111111111111111111",
|
|
149
|
+
})
|
|
150
|
+
// Remove historical from response
|
|
151
|
+
delete (mockResponse.erc4626Vaults.items[0] as Record<string, unknown>).historical
|
|
152
|
+
|
|
153
|
+
const client = createMockClient(mockResponse)
|
|
154
|
+
|
|
155
|
+
const result = await getDetailedVault(client, {
|
|
156
|
+
chainId: 1,
|
|
157
|
+
address: "0x1111111111111111111111111111111111111111",
|
|
158
|
+
historical: false,
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
expect(result).not.toBeNull()
|
|
162
|
+
// Different query should be called (without historical fragment)
|
|
163
|
+
expect(client.query).toHaveBeenCalled()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it("augments result with steakhouse metadata", async () => {
|
|
167
|
+
const client = createMockClient(
|
|
168
|
+
createMockVaultDetailResponse({
|
|
169
|
+
vaultAddress: "0x1111111111111111111111111111111111111111",
|
|
170
|
+
}),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
const result = await getDetailedVault(client, {
|
|
174
|
+
chainId: 1,
|
|
175
|
+
address: "0x1111111111111111111111111111111111111111",
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
expect(result?.steakhouseMetadata).toEqual({
|
|
179
|
+
name: "Steakhouse USDC",
|
|
180
|
+
description: "A USDC vault",
|
|
181
|
+
tag: "featured",
|
|
182
|
+
protocol: "morpho_v1",
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it("handles case-insensitive address matching", async () => {
|
|
187
|
+
const client = createMockClient(
|
|
188
|
+
createMockVaultDetailResponse({
|
|
189
|
+
vaultAddress: "0x1111111111111111111111111111111111111111",
|
|
190
|
+
}),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
const result = await getDetailedVault(client, {
|
|
194
|
+
chainId: 1,
|
|
195
|
+
// Uppercase address
|
|
196
|
+
address: "0x1111111111111111111111111111111111111111".toUpperCase() as `0x${string}`,
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
expect(result).not.toBeNull()
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it("returns null when API returns no results", async () => {
|
|
203
|
+
const client = createMockClient(createMockVaultDetailResponse(null))
|
|
204
|
+
|
|
205
|
+
const result = await getDetailedVault(client, {
|
|
206
|
+
chainId: 1,
|
|
207
|
+
address: "0x1111111111111111111111111111111111111111",
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
expect(result).toBeNull()
|
|
211
|
+
})
|
|
212
|
+
})
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { graphql } from "@whisk/graphql"
|
|
2
|
+
import type { SteakhouseClient } from "../client.js"
|
|
3
|
+
import type { Address } from "../metadata/types.js"
|
|
4
|
+
import { ALL_VAULTS } from "../metadata/vaults.js"
|
|
5
|
+
import { type VaultDetail, vaultDetailFragment } from "./fragments/vaultDetail.js"
|
|
6
|
+
import { buildSteakhouseMetadata, type SteakhouseMetadata } from "./steakhouseMetadata.js"
|
|
7
|
+
|
|
8
|
+
/** Historical data fragment for vault charts (works for both v1 and v2) */
|
|
9
|
+
const vaultHistoricalFragment = graphql(`
|
|
10
|
+
fragment VaultHistoricalFragment on Erc4626Vault {
|
|
11
|
+
... on MorphoVault {
|
|
12
|
+
historical {
|
|
13
|
+
daily {
|
|
14
|
+
bucketTimestamp
|
|
15
|
+
totalSupplied {
|
|
16
|
+
formatted
|
|
17
|
+
usd
|
|
18
|
+
}
|
|
19
|
+
supplyApy7d {
|
|
20
|
+
total
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
... on MorphoVaultV2 {
|
|
26
|
+
historical {
|
|
27
|
+
daily {
|
|
28
|
+
bucketTimestamp
|
|
29
|
+
totalSupplied {
|
|
30
|
+
formatted
|
|
31
|
+
usd
|
|
32
|
+
}
|
|
33
|
+
supplyApy7d {
|
|
34
|
+
total
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
`)
|
|
41
|
+
|
|
42
|
+
/** Historical data type extracted from the fragment */
|
|
43
|
+
type VaultHistoricalData = {
|
|
44
|
+
daily: Array<{
|
|
45
|
+
bucketTimestamp: number
|
|
46
|
+
totalSupplied: { formatted: string; usd: number | null }
|
|
47
|
+
supplyApy7d: { total: number }
|
|
48
|
+
}>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Query for fetching a single vault without historical data */
|
|
52
|
+
export const vaultQuery = graphql(
|
|
53
|
+
`
|
|
54
|
+
query GetVault($where: Erc4626VaultFilter) {
|
|
55
|
+
erc4626Vaults(where: $where, limit: 1) {
|
|
56
|
+
items {
|
|
57
|
+
...VaultDetailFragment
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
`,
|
|
62
|
+
[vaultDetailFragment],
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
/** Query for fetching a single vault with historical data */
|
|
66
|
+
export const vaultWithHistoricalQuery = graphql(
|
|
67
|
+
`
|
|
68
|
+
query GetVaultWithHistorical($where: Erc4626VaultFilter) {
|
|
69
|
+
erc4626Vaults(where: $where, limit: 1) {
|
|
70
|
+
items {
|
|
71
|
+
...VaultDetailFragment
|
|
72
|
+
...VaultHistoricalFragment
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
`,
|
|
77
|
+
[vaultDetailFragment, vaultHistoricalFragment],
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
export type GetDetailedVaultVariables = {
|
|
81
|
+
chainId: number
|
|
82
|
+
address: Address
|
|
83
|
+
/** Include historical data for charts (default: true) */
|
|
84
|
+
historical?: boolean
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Vault detail with Steakhouse metadata and optional historical data */
|
|
88
|
+
export type DetailedVault = VaultDetail & {
|
|
89
|
+
historical?: VaultHistoricalData | null
|
|
90
|
+
steakhouseMetadata: SteakhouseMetadata
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type GetDetailedVaultResult = DetailedVault | null
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get full details for a single Steakhouse vault with optional historical data.
|
|
97
|
+
* Returns null if the vault is not in the whitelist.
|
|
98
|
+
*/
|
|
99
|
+
export async function getDetailedVault(
|
|
100
|
+
client: SteakhouseClient,
|
|
101
|
+
variables: GetDetailedVaultVariables,
|
|
102
|
+
): Promise<GetDetailedVaultResult> {
|
|
103
|
+
const { chainId, address, historical = true } = variables
|
|
104
|
+
|
|
105
|
+
const vaultConfig = ALL_VAULTS.find(
|
|
106
|
+
(v) =>
|
|
107
|
+
v.chainId === chainId && v.address.toLowerCase() === address.toLowerCase() && !v.isHidden,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
if (!vaultConfig) {
|
|
111
|
+
return null
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const where = {
|
|
115
|
+
keys: [
|
|
116
|
+
{
|
|
117
|
+
chainId,
|
|
118
|
+
vaultAddress: address,
|
|
119
|
+
protocol: vaultConfig.protocol,
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
}
|
|
123
|
+
const query = historical ? vaultWithHistoricalQuery : vaultQuery
|
|
124
|
+
const result = await client.query(query, { where })
|
|
125
|
+
const vault = result.erc4626Vaults.items[0]
|
|
126
|
+
|
|
127
|
+
if (!vault) {
|
|
128
|
+
return null
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return { ...vault, steakhouseMetadata: buildSteakhouseMetadata(vaultConfig) }
|
|
132
|
+
}
|