@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.
Files changed (63) hide show
  1. package/dist/client.d.ts +1 -12
  2. package/dist/client.js +1 -10
  3. package/dist/client.js.map +1 -1
  4. package/dist/index.d.ts +6 -5
  5. package/dist/index.js +1 -4
  6. package/dist/index.js.map +1 -1
  7. package/dist/metadata/types.d.ts +3 -1
  8. package/dist/metadata/vaults.js +17 -15
  9. package/dist/metadata/vaults.js.map +1 -1
  10. package/dist/queries/fragments/vaultDetail.d.ts +265 -0
  11. package/dist/queries/fragments/vaultDetail.js +148 -0
  12. package/dist/queries/fragments/vaultDetail.js.map +1 -0
  13. package/dist/queries/getDetailedVault.d.ts +591 -0
  14. package/dist/queries/getDetailedVault.js +92 -0
  15. package/dist/queries/getDetailedVault.js.map +1 -0
  16. package/dist/queries/getVaults.d.ts +274 -13
  17. package/dist/queries/getVaults.js +36 -10
  18. package/dist/queries/getVaults.js.map +1 -1
  19. package/dist/queries/index.d.ts +6 -5
  20. package/dist/queries/index.js +6 -8
  21. package/dist/queries/index.js.map +1 -1
  22. package/dist/queries/steakhouseMetadata.d.ts +13 -0
  23. package/dist/queries/steakhouseMetadata.js +12 -0
  24. package/dist/queries/steakhouseMetadata.js.map +1 -0
  25. package/dist/queries/types.d.ts +2 -4
  26. package/dist/react/hooks/index.d.ts +8 -5
  27. package/dist/react/hooks/index.js +2 -0
  28. package/dist/react/hooks/index.js.map +1 -1
  29. package/dist/react/hooks/useDetailedVault.d.ts +12 -0
  30. package/dist/react/hooks/useDetailedVault.js +16 -0
  31. package/dist/react/hooks/useDetailedVault.js.map +1 -0
  32. package/dist/react/hooks/useSteakhouseQuery.d.ts +2 -4
  33. package/dist/react/hooks/useSteakhouseQuery.js +1 -1
  34. package/dist/react/hooks/useSteakhouseQuery.js.map +1 -1
  35. package/dist/react/hooks/useVaults.d.ts +7 -14
  36. package/dist/react/hooks/useVaults.js +3 -1
  37. package/dist/react/hooks/useVaults.js.map +1 -1
  38. package/dist/react/index.d.ts +8 -5
  39. package/dist/react/index.js +1 -8
  40. package/dist/react/index.js.map +1 -1
  41. package/dist/react/provider.d.ts +3 -5
  42. package/package.json +9 -4
  43. package/src/client.ts +3 -19
  44. package/src/index.ts +1 -2
  45. package/src/metadata/types.ts +3 -1
  46. package/src/metadata/vaults.ts +17 -17
  47. package/src/queries/fragments/vaultDetail.ts +151 -0
  48. package/src/queries/getDetailedVault.test.ts +212 -0
  49. package/src/queries/getDetailedVault.ts +132 -0
  50. package/src/queries/getVaults.test.ts +186 -0
  51. package/src/queries/getVaults.ts +62 -13
  52. package/src/queries/index.ts +9 -7
  53. package/src/queries/steakhouseMetadata.ts +19 -0
  54. package/src/react/hooks/index.ts +1 -0
  55. package/src/react/hooks/useDetailedVault.ts +19 -0
  56. package/src/react/hooks/useSteakhouseQuery.ts +3 -3
  57. package/src/react/hooks/useVaults.ts +7 -2
  58. package/src/react/index.ts +1 -6
  59. package/dist/queries/fragments/vault.d.ts +0 -19
  60. package/dist/queries/fragments/vault.js +0 -16
  61. package/dist/queries/fragments/vault.js.map +0 -1
  62. package/src/client.test.ts +0 -27
  63. package/src/queries/fragments/vault.ts +0 -15
@@ -1,14 +1,12 @@
1
1
  import React, { PropsWithChildren } from 'react';
2
- import { SteakhouseClient } from '../client.js';
3
- import '@urql/core';
4
- import '@whisk/client';
2
+ import { WhiskClient } from '@whisk/client';
5
3
 
6
4
  interface SteakhouseContextValue {
7
- client: SteakhouseClient;
5
+ client: WhiskClient;
8
6
  }
9
7
  declare function useSteakhouse(): SteakhouseContextValue;
10
8
  interface SteakhouseProviderProps extends PropsWithChildren {
11
- client: SteakhouseClient;
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.2",
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": "^6.0.1",
37
+ "@urql/core": "6.0.1",
33
38
  "gql.tada": "1.9.0",
34
- "@whisk/client": "0.0.7",
35
- "@whisk/graphql": "0.0.7"
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
- import type { AnyVariables, TypedDocumentNode } from "@urql/core"
2
- import { WhiskClient, type WhiskClientConfig } from "@whisk/client"
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
@@ -1,3 +1,2 @@
1
- export { SteakhouseClient, type SteakhouseClientConfig } from "./client.js"
1
+ export * from "./client.js"
2
2
  export * from "./queries/index.js"
3
- export type { SteakhouseQueryFn } from "./queries/types.js"
@@ -1,10 +1,12 @@
1
1
  export type Address = `0x${string}`
2
2
 
3
- export type VaultProtocol = "morpho" | "steakhouse"
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
@@ -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
- // Example placeholder vault
7
- // {
8
- // address: "0x...",
9
- // protocol: "steakhouse",
10
- // name: "Steakhouse USDC Vault",
11
- // tag: "featured",
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
- // TODO: Add real Steakhouse vault addresses
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
+ }