@whisk/steakhouse 0.3.9 → 0.5.0

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.
@@ -0,0 +1,163 @@
1
+ import { graphql } from "@whisk/graphql"
2
+ import { isAddressEqual } from "viem"
3
+ import { STEAKHOUSE_VAULTS } from "../metadata/generated/vaults.js"
4
+ import type { VaultConfig } from "../metadata/types.js"
5
+
6
+ export type Prettify<T> = { [K in keyof T]: T[K] } & {}
7
+
8
+ export interface VaultKeyVariables {
9
+ chainId: number
10
+ vaultAddress: `0x${string}`
11
+ }
12
+
13
+ export function resolveVaultConfig(variables: VaultKeyVariables): VaultConfig | undefined {
14
+ return STEAKHOUSE_VAULTS.find(
15
+ (v) => v.chainId === variables.chainId && isAddressEqual(v.address, variables.vaultAddress),
16
+ )
17
+ }
18
+
19
+ export const chainFragment = graphql(`
20
+ fragment ChainFields on Chain {
21
+ id
22
+ name
23
+ icon
24
+ }
25
+ `)
26
+
27
+ export const erc20Fragment = graphql(`
28
+ fragment Erc20Fields on Erc20 {
29
+ address
30
+ name
31
+ symbol
32
+ decimals
33
+ icon
34
+ priceUsd
35
+ }
36
+ `)
37
+
38
+ export const tokenAmountFragment = graphql(`
39
+ fragment TokenAmountFields on TokenAmount {
40
+ raw
41
+ formatted
42
+ usd
43
+ }
44
+ `)
45
+
46
+ export const riskAssessmentFragment = graphql(`
47
+ fragment RiskAssessmentFields on RiskAssessment {
48
+ steakhouse {
49
+ score
50
+ rating
51
+ }
52
+ }
53
+ `)
54
+
55
+ export const apyFragment = graphql(
56
+ `
57
+ fragment ApyFields on Apy {
58
+ base
59
+ rewards {
60
+ asset {
61
+ ...Erc20Fields
62
+ }
63
+ apr
64
+ }
65
+ fee
66
+ total
67
+ }
68
+ `,
69
+ [erc20Fragment],
70
+ )
71
+
72
+ export const morphoMarketAllocationFragment = graphql(
73
+ `
74
+ fragment MorphoMarketAllocationFields on MorphoMarket {
75
+ marketId
76
+ name
77
+ loanAsset {
78
+ ...Erc20Fields
79
+ }
80
+ collateralAsset {
81
+ ...Erc20Fields
82
+ yield {
83
+ intrinsicApy
84
+ }
85
+ }
86
+ lltv {
87
+ raw
88
+ formatted
89
+ }
90
+ riskAssessment {
91
+ ...RiskAssessmentFields
92
+ }
93
+ supplyApy {
94
+ ...ApyFields
95
+ }
96
+ supplyApy1d {
97
+ ...ApyFields
98
+ }
99
+ supplyApy7d {
100
+ ...ApyFields
101
+ }
102
+ supplyApy30d {
103
+ ...ApyFields
104
+ }
105
+ }
106
+ `,
107
+ [erc20Fragment, riskAssessmentFragment, apyFragment],
108
+ )
109
+
110
+ export const morphoVaultHistoricalEntryFragment = graphql(
111
+ `
112
+ fragment MorphoVaultHistoricalEntryFields on MorphoVaultHistoricalEntry {
113
+ bucketTimestamp
114
+ totalSupplied {
115
+ ...TokenAmountFields
116
+ }
117
+ supplyApy1d {
118
+ ...ApyFields
119
+ }
120
+ supplyApy7d {
121
+ ...ApyFields
122
+ }
123
+ supplyApy30d {
124
+ ...ApyFields
125
+ }
126
+ }
127
+ `,
128
+ [tokenAmountFragment, apyFragment],
129
+ )
130
+
131
+ export const vaultSummaryFragment = graphql(
132
+ `
133
+ fragment VaultSummaryFields on Erc4626Vault {
134
+ __typename
135
+ chain {
136
+ ...ChainFields
137
+ }
138
+ ...Erc20Fields
139
+ asset {
140
+ ...Erc20Fields
141
+ }
142
+ totalAssets {
143
+ ...TokenAmountFields
144
+ }
145
+ apyInstant: apy(timeframe: instant) {
146
+ ...ApyFields
147
+ }
148
+ apy1d: apy(timeframe: one_day) {
149
+ ...ApyFields
150
+ }
151
+ apy7d: apy(timeframe: seven_days) {
152
+ ...ApyFields
153
+ }
154
+ apy30d: apy(timeframe: thirty_days) {
155
+ ...ApyFields
156
+ }
157
+ riskAssessment {
158
+ ...RiskAssessmentFields
159
+ }
160
+ }
161
+ `,
162
+ [chainFragment, erc20Fragment, tokenAmountFragment, apyFragment, riskAssessmentFragment],
163
+ )
@@ -0,0 +1,222 @@
1
+ import { graphql, type ResultOf } from "@whisk/graphql"
2
+ import type { SteakhouseClient } from "../client.js"
3
+ import type { VaultConfig } from "../metadata/types.js"
4
+ import {
5
+ apyFragment,
6
+ erc20Fragment,
7
+ morphoMarketAllocationFragment,
8
+ type Prettify,
9
+ resolveVaultConfig,
10
+ riskAssessmentFragment,
11
+ tokenAmountFragment,
12
+ type VaultKeyVariables,
13
+ vaultSummaryFragment,
14
+ } from "./fragments.js"
15
+
16
+ const vaultDetailQuery = graphql(
17
+ `
18
+ query GetSteakhouseVault($keys: [Erc4626VaultKey!]!) {
19
+ erc4626Vaults(where: { keys: $keys }) {
20
+ items {
21
+ ...VaultSummaryFields
22
+
23
+ # Morpho Vault V1 - full details
24
+ ... on MorphoVault {
25
+ totalLiquidity {
26
+ ...TokenAmountFields
27
+ }
28
+ deploymentTimestamp
29
+ performanceFeeV1: performanceFee
30
+ curatorAddress
31
+ guardianAddress
32
+ allocations: marketAllocations {
33
+ supplyCap {
34
+ ...TokenAmountFields
35
+ }
36
+ market {
37
+ ...MorphoMarketAllocationFields
38
+ }
39
+ position {
40
+ supplyAmount {
41
+ ...TokenAmountFields
42
+ }
43
+ }
44
+ }
45
+ }
46
+
47
+ # Box Vault - full details
48
+ ... on BoxVault {
49
+ leverage
50
+ allocations {
51
+ token {
52
+ ...Erc20Fields
53
+ yield {
54
+ intrinsicApy
55
+ }
56
+ }
57
+ balance {
58
+ ...TokenAmountFields
59
+ }
60
+ }
61
+ fundingModules {
62
+ fundingModuleAddress
63
+ nav {
64
+ ...TokenAmountFields
65
+ }
66
+ ... on BoxMorphoFundingModule {
67
+ positions {
68
+ market {
69
+ ...MorphoMarketAllocationFields
70
+ borrowApy {
71
+ ...ApyFields
72
+ }
73
+ borrowApy1d {
74
+ ...ApyFields
75
+ }
76
+ borrowApy7d {
77
+ ...ApyFields
78
+ }
79
+ borrowApy30d {
80
+ ...ApyFields
81
+ }
82
+ }
83
+ loopingLeverage
84
+ loopingNetApy
85
+ collateralAmount {
86
+ ...TokenAmountFields
87
+ }
88
+ borrowAmount {
89
+ ...TokenAmountFields
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }
95
+
96
+ # Morpho Vault V2 - full details for market adapter, user meant to fetch vault adapter details separately if they want those
97
+ ... on MorphoVaultV2 {
98
+ deploymentTimestamp
99
+ performanceFee {
100
+ raw
101
+ formatted
102
+ }
103
+ managementFee {
104
+ raw
105
+ formatted
106
+ }
107
+ nav {
108
+ ...TokenAmountFields
109
+ }
110
+ liquidityAssets {
111
+ ...TokenAmountFields
112
+ }
113
+ idleAssets {
114
+ ...TokenAmountFields
115
+ }
116
+ allocations {
117
+ adapterAddress
118
+ name
119
+ riskAssessment {
120
+ ...RiskAssessmentFields
121
+ }
122
+ adapterCap {
123
+ allocation {
124
+ ...TokenAmountFields
125
+ }
126
+ absoluteCap {
127
+ ...TokenAmountFields
128
+ }
129
+ relativeCap {
130
+ raw
131
+ formatted
132
+ }
133
+ }
134
+
135
+ # For ERC-4626, expect user to call this again if they want full details on the adapters vault
136
+ ... on Erc4626VaultAdapter {
137
+ vault {
138
+ __typename
139
+ address
140
+ }
141
+ }
142
+
143
+ ... on MarketV1Adapter {
144
+ marketCaps {
145
+ allocation {
146
+ ...TokenAmountFields
147
+ }
148
+ absoluteCap {
149
+ ...TokenAmountFields
150
+ }
151
+ market {
152
+ ...MorphoMarketAllocationFields
153
+ }
154
+ }
155
+ }
156
+ }
157
+ }
158
+ }
159
+ }
160
+ }
161
+ `,
162
+ [
163
+ vaultSummaryFragment,
164
+ erc20Fragment,
165
+ tokenAmountFragment,
166
+ riskAssessmentFragment,
167
+ apyFragment,
168
+ morphoMarketAllocationFragment,
169
+ ],
170
+ )
171
+
172
+ type VaultDetailItem = NonNullable<
173
+ ResultOf<typeof vaultDetailQuery>["erc4626Vaults"]["items"][number]
174
+ >
175
+
176
+ export type SteakhouseVaultDetail = Prettify<
177
+ VaultDetailItem & {
178
+ strategy: VaultConfig["strategy"] | undefined
179
+ description: VaultConfig["description"] | undefined
180
+ isListed: boolean
181
+ }
182
+ >
183
+
184
+ export interface GetVaultVariables extends VaultKeyVariables {
185
+ isBox?: boolean
186
+ }
187
+
188
+ export async function getVault(
189
+ client: SteakhouseClient,
190
+ variables: GetVaultVariables,
191
+ ): Promise<SteakhouseVaultDetail> {
192
+ const config = resolveVaultConfig(variables)
193
+
194
+ if (!config && !variables.isBox) {
195
+ throw new Error(
196
+ `Vault ${variables.vaultAddress} on chain ${variables.chainId} is not a known Steakhouse vault`,
197
+ )
198
+ }
199
+
200
+ const result = await client.query(vaultDetailQuery, {
201
+ keys: [
202
+ {
203
+ chainId: variables.chainId,
204
+ vaultAddress: config?.address ?? variables.vaultAddress,
205
+ protocol: config?.protocol ?? "box",
206
+ },
207
+ ],
208
+ })
209
+
210
+ const item = result.erc4626Vaults.items[0]
211
+ if (!item) {
212
+ throw new Error(`Vault ${variables.vaultAddress} on chain ${variables.chainId} was not found`)
213
+ }
214
+
215
+ return {
216
+ ...item,
217
+ name: config?.name ?? item.name,
218
+ strategy: config?.strategy,
219
+ description: config?.description,
220
+ isListed: config?.isListed ?? false,
221
+ }
222
+ }
@@ -0,0 +1,72 @@
1
+ import { type FragmentOf, graphql } from "@whisk/graphql"
2
+ import type { SteakhouseClient } from "../client.js"
3
+ import {
4
+ morphoVaultHistoricalEntryFragment,
5
+ resolveVaultConfig,
6
+ type VaultKeyVariables,
7
+ } from "./fragments.js"
8
+
9
+ const vaultHistoricalQuery = graphql(
10
+ `
11
+ query GetSteakhouseVaultHistory($keys: [Erc4626VaultKey!]!) {
12
+ erc4626Vaults(where: { keys: $keys }) {
13
+ items {
14
+ address
15
+ historical {
16
+ ... on MorphoVaultHistorical {
17
+ daily {
18
+ ...MorphoVaultHistoricalEntryFields
19
+ }
20
+ weekly {
21
+ ...MorphoVaultHistoricalEntryFields
22
+ }
23
+ }
24
+ }
25
+ }
26
+ }
27
+ }
28
+ `,
29
+ [morphoVaultHistoricalEntryFragment],
30
+ )
31
+
32
+ type HistoricalEntry = FragmentOf<typeof morphoVaultHistoricalEntryFragment>
33
+
34
+ export interface SteakhouseVaultHistory {
35
+ daily: ReadonlyArray<HistoricalEntry> | null
36
+ weekly: ReadonlyArray<HistoricalEntry> | null
37
+ }
38
+
39
+ export type GetVaultHistoryVariables = VaultKeyVariables
40
+
41
+ export async function getVaultHistory(
42
+ client: SteakhouseClient,
43
+ variables: GetVaultHistoryVariables,
44
+ ): Promise<SteakhouseVaultHistory> {
45
+ const config = resolveVaultConfig(variables)
46
+
47
+ if (!config) {
48
+ return { daily: null, weekly: null }
49
+ }
50
+
51
+ const result = await client.query(vaultHistoricalQuery, {
52
+ keys: [
53
+ {
54
+ chainId: config.chainId,
55
+ vaultAddress: config.address,
56
+ protocol: config.protocol,
57
+ },
58
+ ],
59
+ })
60
+
61
+ const item = result.erc4626Vaults.items[0]
62
+ if (!item?.historical) {
63
+ return { daily: null, weekly: null }
64
+ }
65
+
66
+ const historical = item.historical
67
+
68
+ return {
69
+ daily: "daily" in historical ? (historical.daily ?? null) : null,
70
+ weekly: "weekly" in historical ? (historical.weekly ?? null) : null,
71
+ }
72
+ }
@@ -0,0 +1,64 @@
1
+ import { graphql, type ResultOf } from "@whisk/graphql"
2
+ import type { SteakhouseClient } from "../client.js"
3
+ import { STEAKHOUSE_VAULTS } from "../metadata/generated/vaults.js"
4
+ import type { VaultConfig } from "../metadata/types.js"
5
+ import { type Prettify, vaultSummaryFragment } from "./fragments.js"
6
+
7
+ const vaultsQuery = graphql(
8
+ `
9
+ query GetSteakhouseVaults($keys: [Erc4626VaultKey!]!) {
10
+ erc4626Vaults(where: { keys: $keys }) {
11
+ items {
12
+ ...VaultSummaryFields
13
+ }
14
+ }
15
+ }
16
+ `,
17
+ [vaultSummaryFragment],
18
+ )
19
+
20
+ type VaultItem = NonNullable<ResultOf<typeof vaultsQuery>["erc4626Vaults"]["items"][number]>
21
+
22
+ export type SteakhouseVaultSummary = Prettify<
23
+ VaultItem & {
24
+ strategy: VaultConfig["strategy"]
25
+ isListed: boolean
26
+ }
27
+ >
28
+
29
+ export async function getVaults(client: SteakhouseClient): Promise<SteakhouseVaultSummary[]> {
30
+ const configs = STEAKHOUSE_VAULTS
31
+
32
+ const keys = configs.map((v) => ({
33
+ chainId: v.chainId,
34
+ vaultAddress: v.address,
35
+ protocol: v.protocol,
36
+ }))
37
+
38
+ const result = await client.query(vaultsQuery, { keys })
39
+
40
+ const configByChainAndAddress = new Map(
41
+ configs.map((v) => [`${v.chainId}:${v.address.toLowerCase()}`, v]),
42
+ )
43
+
44
+ const vaults: SteakhouseVaultSummary[] = []
45
+ for (const item of result.erc4626Vaults.items) {
46
+ if (!item) continue
47
+ const config = configByChainAndAddress.get(`${item.chain.id}:${item.address.toLowerCase()}`)
48
+ if (!config) {
49
+ console.warn(
50
+ `[getVaults] Vault ${item.address} on chain ${item.chain.id} has no matching config, skipping`,
51
+ )
52
+ continue
53
+ }
54
+
55
+ vaults.push({
56
+ ...item,
57
+ name: config.name ?? item.name,
58
+ strategy: config.strategy,
59
+ isListed: config.isListed,
60
+ })
61
+ }
62
+
63
+ return vaults
64
+ }
@@ -1 +1,4 @@
1
1
  export * from "./getStats.js"
2
+ export * from "./getVault.js"
3
+ export * from "./getVaultHistory.js"
4
+ export * from "./getVaults.js"