tapimo 0.13.0 → 0.14.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.
Files changed (230) hide show
  1. package/dist/App.d.ts +6064 -4829
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +1 -0
  4. package/dist/App.js.map +1 -1
  5. package/dist/Webhooks.js +2 -2
  6. package/dist/Webhooks.js.map +1 -1
  7. package/dist/admin/App.d.ts +1008 -917
  8. package/dist/admin/App.d.ts.map +1 -1
  9. package/dist/admin/apps/earn-vaults.d.ts +48 -0
  10. package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
  11. package/dist/admin/apps/earn-vaults.js +2 -1
  12. package/dist/admin/apps/earn-vaults.js.map +1 -1
  13. package/dist/admin/apps/organizations.d.ts +92 -3
  14. package/dist/admin/apps/organizations.d.ts.map +1 -1
  15. package/dist/admin/apps/organizations.js +69 -7
  16. package/dist/admin/apps/organizations.js.map +1 -1
  17. package/dist/admin/apps/verified-tokens.d.ts +74 -74
  18. package/dist/admin/ui.generated.d.ts +1 -1
  19. package/dist/admin/ui.generated.js +69 -69
  20. package/dist/admin/ui.generated.js.map +1 -1
  21. package/dist/apps/Zones.d.ts +12 -12
  22. package/dist/apps/Zones.js +3 -3
  23. package/dist/apps/data/App.d.ts +2909 -2523
  24. package/dist/apps/data/App.d.ts.map +1 -1
  25. package/dist/apps/data/App.js +11 -6
  26. package/dist/apps/data/App.js.map +1 -1
  27. package/dist/apps/data/routes/addresses.d.ts +22 -22
  28. package/dist/apps/data/routes/earn.d.ts +183 -7
  29. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  30. package/dist/apps/data/routes/earn.js +434 -75
  31. package/dist/apps/data/routes/earn.js.map +1 -1
  32. package/dist/apps/data/routes/exchanges.d.ts +15 -0
  33. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  34. package/dist/apps/data/routes/exchanges.js +180 -114
  35. package/dist/apps/data/routes/exchanges.js.map +1 -1
  36. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  37. package/dist/apps/data/routes/fee-amm.js +18 -19
  38. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  39. package/dist/apps/data/routes/tokens.d.ts +93 -93
  40. package/dist/apps/data/routes/transfers.d.ts +12 -12
  41. package/dist/apps/data/routes/valuation.d.ts +30 -30
  42. package/dist/apps/data/routes/verified-tokens.d.ts +5 -5
  43. package/dist/apps/data/routes/webhooks.d.ts +805 -102
  44. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  45. package/dist/apps/data/routes/webhooks.js +47 -5
  46. package/dist/apps/data/routes/webhooks.js.map +1 -1
  47. package/dist/apps/funding/App.d.ts +456 -45
  48. package/dist/apps/funding/App.d.ts.map +1 -1
  49. package/dist/apps/funding/App.js +1 -1
  50. package/dist/apps/funding/App.js.map +1 -1
  51. package/dist/apps/funding/routes/deposit-addresses.d.ts +17 -17
  52. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
  53. package/dist/apps/funding/routes/deposit-addresses.js +1 -1
  54. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  55. package/dist/apps/funding/routes/transfers.d.ts +654 -73
  56. package/dist/apps/funding/routes/transfers.d.ts.map +1 -1
  57. package/dist/apps/funding/routes/transfers.js +211 -5
  58. package/dist/apps/funding/routes/transfers.js.map +1 -1
  59. package/dist/apps/management/App.d.ts +831 -387
  60. package/dist/apps/management/App.d.ts.map +1 -1
  61. package/dist/apps/management/Billing.d.ts +1 -1
  62. package/dist/apps/management/Billing.d.ts.map +1 -1
  63. package/dist/apps/management/Billing.js +17 -1
  64. package/dist/apps/management/Billing.js.map +1 -1
  65. package/dist/apps/management/routes/api-keys.d.ts +76 -76
  66. package/dist/apps/management/routes/billing.d.ts +76 -76
  67. package/dist/apps/management/routes/faucet.d.ts +12 -12
  68. package/dist/apps/management/routes/invitations.d.ts +24 -24
  69. package/dist/apps/management/routes/invite-links.d.ts +46 -46
  70. package/dist/apps/management/routes/members.d.ts +46 -46
  71. package/dist/apps/management/routes/orgs.d.ts +20 -20
  72. package/dist/apps/management/routes/projects.d.ts +44 -44
  73. package/dist/apps/management/routes/usage.d.ts +32 -32
  74. package/dist/apps/management/routes/verified-token-requests.d.ts +16 -16
  75. package/dist/apps/management/routes/webhooks.d.ts +707 -57
  76. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  77. package/dist/apps/management/routes/webhooks.js +21 -21
  78. package/dist/apps/management/routes/webhooks.js.map +1 -1
  79. package/dist/apps/relay/App.d.ts.map +1 -1
  80. package/dist/apps/relay/App.js +115 -23
  81. package/dist/apps/relay/App.js.map +1 -1
  82. package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
  83. package/dist/apps/relay/Sponsorships.js +27 -1
  84. package/dist/apps/relay/Sponsorships.js.map +1 -1
  85. package/dist/db/Db.d.ts.map +1 -1
  86. package/dist/db/Db.js +22 -0
  87. package/dist/db/Db.js.map +1 -1
  88. package/dist/db/Schema.d.ts +8 -3
  89. package/dist/db/Schema.d.ts.map +1 -1
  90. package/dist/db/Schema.js +16 -1
  91. package/dist/db/Schema.js.map +1 -1
  92. package/dist/db/tables/fundingTransferTransactions.d.ts +38 -0
  93. package/dist/db/tables/fundingTransferTransactions.d.ts.map +1 -1
  94. package/dist/db/tables/fundingTransferTransactions.js +44 -3
  95. package/dist/db/tables/fundingTransferTransactions.js.map +1 -1
  96. package/dist/db/tables/fundingTransfers.d.ts +17 -0
  97. package/dist/db/tables/fundingTransfers.d.ts.map +1 -1
  98. package/dist/db/tables/fundingTransfers.js +21 -0
  99. package/dist/db/tables/fundingTransfers.js.map +1 -1
  100. package/dist/db/tables/organizations.d.ts +24 -0
  101. package/dist/db/tables/organizations.d.ts.map +1 -1
  102. package/dist/db/tables/organizations.js +27 -0
  103. package/dist/db/tables/organizations.js.map +1 -1
  104. package/dist/db/tables/projects.d.ts +25 -1
  105. package/dist/db/tables/projects.d.ts.map +1 -1
  106. package/dist/db/tables/projects.js +26 -0
  107. package/dist/db/tables/projects.js.map +1 -1
  108. package/dist/db/tables/sponsoredTransactions.d.ts +44 -0
  109. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  110. package/dist/db/tables/sponsoredTransactions.js +146 -13
  111. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  112. package/dist/db/tables/webhookSubscriptions.d.ts +10 -6
  113. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  114. package/dist/db/tables/webhookSubscriptions.js +9 -6
  115. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  116. package/dist/internal/EarnVaults.js +5 -5
  117. package/dist/internal/EarnVaults.js.map +1 -1
  118. package/dist/internal/Log.d.ts +1 -1
  119. package/dist/internal/Log.d.ts.map +1 -1
  120. package/dist/internal/Log.js.map +1 -1
  121. package/dist/internal/Viem.d.ts +16 -1
  122. package/dist/internal/Viem.d.ts.map +1 -1
  123. package/dist/internal/Viem.js +12 -2
  124. package/dist/internal/Viem.js.map +1 -1
  125. package/dist/internal/funding/DepositWebhook.d.ts.map +1 -1
  126. package/dist/internal/funding/DepositWebhook.js +2 -1
  127. package/dist/internal/funding/DepositWebhook.js.map +1 -1
  128. package/dist/internal/funding/Evidence.d.ts.map +1 -1
  129. package/dist/internal/funding/Evidence.js +2 -10
  130. package/dist/internal/funding/Evidence.js.map +1 -1
  131. package/dist/internal/funding/Provider.d.ts +40 -0
  132. package/dist/internal/funding/Provider.d.ts.map +1 -1
  133. package/dist/internal/funding/Provider.js +5 -0
  134. package/dist/internal/funding/Provider.js.map +1 -1
  135. package/dist/internal/funding/SourceObservation.d.ts.map +1 -1
  136. package/dist/internal/funding/SourceObservation.js +5 -7
  137. package/dist/internal/funding/SourceObservation.js.map +1 -1
  138. package/dist/internal/funding/Transfer.d.ts +101 -6
  139. package/dist/internal/funding/Transfer.d.ts.map +1 -1
  140. package/dist/internal/funding/Transfer.js +162 -34
  141. package/dist/internal/funding/Transfer.js.map +1 -1
  142. package/dist/internal/funding/TransferReconciliation.d.ts +52 -0
  143. package/dist/internal/funding/TransferReconciliation.d.ts.map +1 -0
  144. package/dist/internal/funding/TransferReconciliation.js +192 -0
  145. package/dist/internal/funding/TransferReconciliation.js.map +1 -0
  146. package/dist/internal/funding/TransferWebhook.d.ts +21 -0
  147. package/dist/internal/funding/TransferWebhook.d.ts.map +1 -0
  148. package/dist/internal/funding/TransferWebhook.js +43 -0
  149. package/dist/internal/funding/TransferWebhook.js.map +1 -0
  150. package/dist/internal/funding/index.d.ts +1 -0
  151. package/dist/internal/funding/index.d.ts.map +1 -1
  152. package/dist/internal/funding/index.js +1 -0
  153. package/dist/internal/funding/index.js.map +1 -1
  154. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  155. package/dist/internal/funding/providers/relay.js +3 -12
  156. package/dist/internal/funding/providers/relay.js.map +1 -1
  157. package/dist/internal/funding/providers/stargate.d.ts +99 -0
  158. package/dist/internal/funding/providers/stargate.d.ts.map +1 -1
  159. package/dist/internal/funding/providers/stargate.js +337 -35
  160. package/dist/internal/funding/providers/stargate.js.map +1 -1
  161. package/dist/internal/webhookDestinations/slack.d.ts.map +1 -1
  162. package/dist/internal/webhookDestinations/slack.js +16 -0
  163. package/dist/internal/webhookDestinations/slack.js.map +1 -1
  164. package/package.json +1 -1
  165. package/src/App.ts +1 -0
  166. package/src/Client.test-d.ts +83 -2
  167. package/src/Webhooks.ts +2 -2
  168. package/src/admin/apps/earn-vaults.ts +2 -1
  169. package/src/admin/apps/organizations.test.ts +53 -1
  170. package/src/admin/apps/organizations.ts +90 -5
  171. package/src/admin/ui/src/lib/organizations.test.ts +112 -1
  172. package/src/admin/ui/src/lib/organizations.ts +55 -0
  173. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +116 -0
  174. package/src/admin/ui.generated.ts +69 -69
  175. package/src/apps/Zones.test.ts +3 -3
  176. package/src/apps/Zones.ts +3 -3
  177. package/src/apps/composition.test.ts +1 -0
  178. package/src/apps/data/App.ts +15 -6
  179. package/src/apps/data/routes/earn.test.ts +369 -14
  180. package/src/apps/data/routes/earn.ts +548 -79
  181. package/src/apps/data/routes/exchanges.test.ts +740 -15
  182. package/src/apps/data/routes/exchanges.ts +954 -753
  183. package/src/apps/data/routes/fee-amm.ts +17 -18
  184. package/src/apps/data/routes/webhooks.test.ts +21 -5
  185. package/src/apps/data/routes/webhooks.ts +63 -8
  186. package/src/apps/funding/App.ts +12 -2
  187. package/src/apps/funding/routes/deposit-addresses.test.ts +4 -4
  188. package/src/apps/funding/routes/deposit-addresses.ts +2 -2
  189. package/src/apps/funding/routes/transfers.test.ts +510 -19
  190. package/src/apps/funding/routes/transfers.ts +271 -5
  191. package/src/apps/management/Billing.ts +18 -5
  192. package/src/apps/management/routes/webhooks.test.ts +3 -3
  193. package/src/apps/management/routes/webhooks.ts +24 -25
  194. package/src/apps/relay/App.test.ts +212 -1
  195. package/src/apps/relay/App.ts +158 -29
  196. package/src/apps/relay/Sponsorships.ts +24 -1
  197. package/src/db/Db.ts +35 -0
  198. package/src/db/Schema.ts +17 -2
  199. package/src/db/tables/fundingTransferTransactions.test.ts +43 -0
  200. package/src/db/tables/fundingTransferTransactions.ts +79 -3
  201. package/src/db/tables/fundingTransfers.test.ts +26 -0
  202. package/src/db/tables/fundingTransfers.ts +36 -0
  203. package/src/db/tables/organizations.test.ts +41 -0
  204. package/src/db/tables/organizations.ts +49 -0
  205. package/src/db/tables/projects.test.ts +55 -0
  206. package/src/db/tables/projects.ts +46 -1
  207. package/src/db/tables/sponsoredTransactions.test.ts +338 -0
  208. package/src/db/tables/sponsoredTransactions.ts +197 -13
  209. package/src/db/tables/webhookSubscriptions.ts +18 -11
  210. package/src/internal/EarnVaults.ts +5 -5
  211. package/src/internal/Log.test-d.ts +2 -0
  212. package/src/internal/Log.ts +2 -0
  213. package/src/internal/Viem.ts +39 -3
  214. package/src/internal/Webhooks.test.ts +45 -0
  215. package/src/internal/funding/DepositWebhook.ts +2 -1
  216. package/src/internal/funding/Evidence.ts +2 -13
  217. package/src/internal/funding/Provider.ts +43 -0
  218. package/src/internal/funding/SourceObservation.ts +5 -9
  219. package/src/internal/funding/Transfer.test.ts +155 -0
  220. package/src/internal/funding/Transfer.ts +254 -37
  221. package/src/internal/funding/TransferReconciliation.test.ts +147 -0
  222. package/src/internal/funding/TransferReconciliation.ts +264 -0
  223. package/src/internal/funding/TransferWebhook.test.ts +128 -0
  224. package/src/internal/funding/TransferWebhook.ts +67 -0
  225. package/src/internal/funding/index.test.ts +1 -0
  226. package/src/internal/funding/index.ts +1 -0
  227. package/src/internal/funding/providers/relay.ts +3 -15
  228. package/src/internal/funding/providers/stargate.test.ts +169 -4
  229. package/src/internal/funding/providers/stargate.ts +498 -51
  230. package/src/internal/webhookDestinations/slack.ts +15 -0
@@ -20,9 +20,9 @@ describe('chains', () => {
20
20
  },
21
21
  {
22
22
  contracts: {
23
- messenger: { address: '0x171699d2B5211414C23E5Cd306d05046b78FdD16' },
24
- portal: { address: '0xe5208b9604972CA6a2E1EF656a9d2bDF2e530B9A' },
25
- verifier: { address: '0x3E7C1cC9C049052eb061116A4Ee7a7C281fa1F30' },
23
+ messenger: { address: '0x5A4d000000000000000000000000000000000000' },
24
+ portal: { address: '0x5ad0000000000000000000000000000000000001' },
25
+ verifier: { address: '0x5a56000000000000000000000000000000000000' },
26
26
  },
27
27
  id: 421_700_001,
28
28
  name: 'zone-mainnet-internal',
package/src/apps/Zones.ts CHANGED
@@ -25,9 +25,9 @@ export const zoneMainnetInternal = defineChain({
25
25
  ...tempo,
26
26
  contracts: {
27
27
  ...tempo.contracts,
28
- messenger: { address: '0x171699d2B5211414C23E5Cd306d05046b78FdD16' },
29
- portal: { address: '0xe5208b9604972CA6a2E1EF656a9d2bDF2e530B9A' },
30
- verifier: { address: '0x3E7C1cC9C049052eb061116A4Ee7a7C281fa1F30' },
28
+ messenger: { address: '0x5A4d000000000000000000000000000000000000' },
29
+ portal: { address: '0x5ad0000000000000000000000000000000000001' },
30
+ verifier: { address: '0x5a56000000000000000000000000000000000000' },
31
31
  },
32
32
  id: 421_700_001,
33
33
  name: 'zone-mainnet-internal',
@@ -309,6 +309,7 @@ describe('funding()', () => {
309
309
  '/v1/funding/transfers/vault',
310
310
  '/v1/funding/transfers/zone',
311
311
  '/v1/funding/transfers/{id}',
312
+ '/v1/funding/transfers/{id}/source-transactions',
312
313
  ].sort(),
313
314
  )
314
315
  expect(Object.keys(spec.paths).some((path) => path.startsWith('/v1/tokens'))).toBe(false)
@@ -23,7 +23,7 @@ import { transactions as transactionActivities } from './routes/activities.js'
23
23
  import { addresses } from './routes/addresses.js'
24
24
  import { blocks, blocksByTimestampCompat } from './routes/blocks.js'
25
25
  import { coingecko } from './routes/coingecko.js'
26
- import { earn } from './routes/earn.js'
26
+ import * as Earn from './routes/earn.js'
27
27
  import { exchanges } from './routes/exchanges.js'
28
28
  import { feeAmm } from './routes/fee-amm.js'
29
29
  import { indexer } from './routes/indexer.js'
@@ -60,7 +60,7 @@ export function data(options: data.Options = {}) {
60
60
 
61
61
  const getTidx = Tidx.createGetClient({ tidx })
62
62
  const addresses_routes = addresses({ fx: options.fx })
63
- const earn_routes = earn({ fx: options.fx })
63
+ const earn_routes = Earn.earn({ fx: options.fx })
64
64
  const indexer_routes = indexer()
65
65
  const transactionActivities_routes = transactionActivities({ fx: options.fx })
66
66
  const indexerRouteHandlers = new Set(indexer_routes.routes.map((route) => route.handler))
@@ -130,14 +130,23 @@ export function data(options: data.Options = {}) {
130
130
  const rpc_config = c.get('rpc')
131
131
  const tidx_config = c.get('tidx')
132
132
  const zones = c.get('zones')
133
- // Raw passthroughs and RPC-only reads need only RPC; composed data routes
134
- // require both upstreams to avoid falling through to unrelated defaults.
133
+ const query = Earn.schema.getEarnVaultPosition.Query.safeParse(
134
+ Object.fromEntries(
135
+ Object.entries(c.req.queries()).map(([key, values]) => [
136
+ key,
137
+ values.length === 1 ? values[0] : values,
138
+ ]),
139
+ ),
140
+ )
141
+ const requiresTidx = selection.rpcOnly && query.success && query.data.asOf !== undefined
142
+ // Raw passthroughs and current RPC-only reads need RPC. Historical
143
+ // position reads and composed data routes require both upstreams.
135
144
  const narrowedChainIds = new Set(
136
145
  [...supportedChainIds].filter((chainId) => {
137
146
  const context = { chainId, principal, zone: zones.get(chainId) }
138
147
  const hasRpc = Viem.resolveRpc(rpc_config, context).url !== undefined
139
148
  const hasTidx = Tidx.resolve(tidx_config, context).baseUrl !== undefined
140
- if (selection.rpc || selection.rpcOnly) return hasRpc
149
+ if (selection.rpc || (selection.rpcOnly && !requiresTidx)) return hasRpc
141
150
  if (selection.indexer) return hasTidx
142
151
  return hasRpc && hasTidx
143
152
  }),
@@ -500,7 +509,7 @@ function webhooksBlock() {
500
509
  'tempo-event-id',
501
510
  'A unique, stable id for this event (`evt_…`). The same event may be delivered more than once, so use this id to skip duplicates.',
502
511
  ),
503
- header('tempo-event-type', 'What happened: `token:transfer`, `transaction:included`, `log:emitted`, `block:created`, `funding:deposit.updated`, or `ping`.'), // prettier-ignore
512
+ header('tempo-event-type', 'What happened: `token:transfer`, `transaction:included`, `log:emitted`, `block:created`, `funding:deposit.updated`, `funding:transfer.updated`, or `ping`.'), // prettier-ignore
504
513
  ],
505
514
  requestBody: {
506
515
  content: { 'application/json': { schema: envelope } },
@@ -7,6 +7,8 @@ import * as TestEarn from '../../../../test/Earn.js'
7
7
  import * as Runtime from '../../../../test/runtime.js'
8
8
  import * as Cursor from '../../../internal/Cursor.js'
9
9
  import * as Store from '../../../internal/Store.js'
10
+ import * as Tidx from '../../../internal/Tidx.js'
11
+ import * as Value from '../../../internal/Value.js'
10
12
  import * as Earn from './earn.js'
11
13
 
12
14
  /**
@@ -32,7 +34,10 @@ type OpenApiOperation = {
32
34
  name?: string
33
35
  schema?: { enum?: readonly unknown[]; examples?: readonly unknown[]; pattern?: string }
34
36
  }[]
35
- responses?: Record<string, { content?: { 'application/json'?: { schema?: OpenApiSchema } } }>
37
+ responses?: Record<
38
+ string,
39
+ { content?: { 'application/json'?: { schema?: OpenApiSchema } }; description?: string }
40
+ >
36
41
  }
37
42
 
38
43
  type OpenApiDocument = {
@@ -106,6 +111,40 @@ describe('schema.getEarnVaults.Query', () => {
106
111
  })
107
112
  })
108
113
 
114
+ describe('parseIndexedBlockAtOrBefore', () => {
115
+ test('rejects an empty upstream result before classifying the boundary', () => {
116
+ expect(() =>
117
+ Earn.parseIndexedBlockAtOrBefore({
118
+ asOf: '2026-07-21T09:00:00.000Z',
119
+ rows: [],
120
+ }),
121
+ ).toThrowErrorMatchingInlineSnapshot(
122
+ `[Error: TIDX returned no indexed head for a historical Earn position.]`,
123
+ )
124
+ })
125
+
126
+ test('classifies a missing boundary only after validating the indexed head', () => {
127
+ expect(() =>
128
+ Earn.parseIndexedBlockAtOrBefore({
129
+ asOf: '2026-07-21T09:00:00.000Z',
130
+ rows: [{ kind: 'head', num: 10, timestamp: '2026-07-21T10:00:00.000Z' }],
131
+ }),
132
+ ).toThrowErrorMatchingInlineSnapshot(
133
+ `[Earn.IndexedBlockNotFoundError: No indexed block exists at or before 2026-07-21T09:00:00.000Z.]`,
134
+ )
135
+ })
136
+ })
137
+
138
+ describe('schema.getEarnVaultPosition.Query', () => {
139
+ test('accepts offset-bearing ISO 8601 timestamps', () => {
140
+ expect(
141
+ Earn.schema.getEarnVaultPosition.Query.parse({
142
+ asOf: '2026-07-21T10:00:00+01:00',
143
+ }).asOf,
144
+ ).toBe('2026-07-21T10:00:00+01:00')
145
+ })
146
+ })
147
+
109
148
  describe('schema.getEarnAddressPositions.Query', () => {
110
149
  test('parses collection filters', () => {
111
150
  const query = Earn.schema.getEarnAddressPositions.Query.parse({
@@ -181,6 +220,8 @@ describe('schema.getEarnVaultEarnings.Response', () => {
181
220
  lifetimeEarnings: '0',
182
221
  period: 'lifetime',
183
222
  status: 'complete',
223
+ totalDeposited: '1000000',
224
+ totalWithdrawn: '500000',
184
225
  }).success,
185
226
  pending: Earn.schema.getEarnVaultEarnings.Response.safeParse({
186
227
  ...response,
@@ -199,9 +240,10 @@ describe('schema.getEarnVaultEarnings.Response', () => {
199
240
  }).success,
200
241
  pendingWithEarnings: Earn.schema.getEarnVaultEarnings.Response.safeParse({
201
242
  ...response,
202
- lifetimeEarnings: '0',
203
243
  period: 'lifetime',
204
244
  status: 'pending_redemption',
245
+ totalDeposited: '1000000',
246
+ totalWithdrawn: '500000',
205
247
  }).success,
206
248
  window: Earn.schema.getEarnVaultEarnings.Response.safeParse({
207
249
  ...response,
@@ -229,10 +271,10 @@ describe('schema.getEarnVaultEarnings.Response', () => {
229
271
  "incompleteWindow": true,
230
272
  "incompleteWithEarnings": false,
231
273
  "lifetime": true,
232
- "pending": true,
274
+ "pending": false,
233
275
  "pendingActive": false,
234
276
  "pendingWindow": true,
235
- "pendingWithEarnings": false,
277
+ "pendingWithEarnings": true,
236
278
  "window": true,
237
279
  "windowWithActiveEarnings": false,
238
280
  "windowWithoutEarnings": false,
@@ -241,6 +283,75 @@ describe('schema.getEarnVaultEarnings.Response', () => {
241
283
  })
242
284
  })
243
285
 
286
+ describe('serializeVault', () => {
287
+ test('includes a non-null instant-liquidity valuation with TVL', () => {
288
+ const assetToken = {
289
+ address: TestEarn.assetTokenAddress,
290
+ currency: 'USD',
291
+ decimals: 6,
292
+ id: TestEarn.assetTokenAddress,
293
+ name: 'Bridge Test PATHUSD',
294
+ symbol: 'btPATHUSD',
295
+ verified: true,
296
+ } as const
297
+ const shareTokenAddress = `0x20c0${'11'.repeat(18)}` as const
298
+ const vault = Earn.serializeVault({
299
+ apy: null,
300
+ assetToken,
301
+ discovery: {
302
+ assetToken,
303
+ chainId: TestEarn.chain.id,
304
+ engine: { address: TestEarn.vaultAddress, type: 'erc4626', venue: null },
305
+ instantLiquidity: '500000000',
306
+ sharePrice: '1000000',
307
+ shareToken: {
308
+ address: shareTokenAddress,
309
+ currency: 'USD',
310
+ decimals: 6,
311
+ name: 'Bridge Test PATHUSD Earn',
312
+ symbol: 'btPATHUSDE',
313
+ },
314
+ state: {
315
+ depositsPaused: false,
316
+ engineShares: '1000000000',
317
+ feesActive: true,
318
+ isAccountingAligned: true,
319
+ openRedeemRequestCount: 0,
320
+ totalAssets: '1000000000',
321
+ totalEarnShares: '1000000000',
322
+ },
323
+ vaultAddress: TestEarn.vaultAddress,
324
+ },
325
+ include: ['tvl'],
326
+ instantLiquidityValue: {
327
+ amount: '500000000',
328
+ currency: 'USD',
329
+ decimals: 6,
330
+ formatted: '500',
331
+ },
332
+ shareToken: {
333
+ address: shareTokenAddress,
334
+ currency: 'USD',
335
+ decimals: 6,
336
+ id: shareTokenAddress,
337
+ name: 'Bridge Test PATHUSD Earn',
338
+ symbol: 'btPATHUSDE',
339
+ verified: false,
340
+ },
341
+ tvl: { amount: '1000000000', currency: 'USD', decimals: 6, formatted: '1000' },
342
+ zoneRoutes: [],
343
+ zones: [],
344
+ })
345
+
346
+ expect(vault.instantLiquidityValue).toStrictEqual({
347
+ amount: '500000000',
348
+ currency: 'USD',
349
+ decimals: 6,
350
+ formatted: '500',
351
+ })
352
+ })
353
+ })
354
+
244
355
  describe('schema.getEarnVaultEarnings.Query', () => {
245
356
  test('defaults to lifetime and accepts active and trailing earnings', () => {
246
357
  expect([
@@ -721,6 +832,45 @@ describe('GET /v1/earn/vaults/verified', () => {
721
832
  })
722
833
 
723
834
  describe('GET /v1/earn/addresses/:address/positions', () => {
835
+ test.runIf(Runtime.get().mode === 'testnet')(
836
+ 'batches lifetime cash flows for multiple positions into one query',
837
+ async () => {
838
+ const tidx = Tidx.getClient({
839
+ chainId: TestEarn.chain.id,
840
+ tidx: { auth: process.env.TIDX_AUTH, baseUrl: Runtime.get().tidxUrl },
841
+ })
842
+ let queryCount = 0
843
+ const client = new Proxy(tidx, {
844
+ get(target, property, receiver) {
845
+ if (property !== 'fetch') return Reflect.get(target, property, receiver)
846
+ return (...options: Parameters<Tidx.Client['fetch']>) => {
847
+ queryCount += 1
848
+ return target.fetch(...options)
849
+ }
850
+ },
851
+ })
852
+ const emptyVault = Schema.Address.parse(`0x${'11'.repeat(20)}`)
853
+ const cashFlows = await Earn.getVaultLifetimeCashFlows(client, {
854
+ account: TestEarn.earningsAddress,
855
+ blockNumber: 1_000_000_000,
856
+ vaults: [TestEarn.vaultAddress, emptyVault],
857
+ })
858
+
859
+ expect(queryCount).toBe(1)
860
+ expect(cashFlows.get(TestEarn.vaultAddress)).toMatchObject({
861
+ status: 'complete',
862
+ totalDeposited: expect.stringMatching(/^[1-9]\d*$/),
863
+ totalWithdrawn: expect.stringMatching(/^\d+$/),
864
+ })
865
+ expect(cashFlows.get(emptyVault)).toStrictEqual({
866
+ status: 'complete',
867
+ totalDeposited: '0',
868
+ totalWithdrawn: '0',
869
+ })
870
+ },
871
+ 60_000,
872
+ )
873
+
724
874
  test('returns chain selection errors', async () => {
725
875
  const app = TestApp.create({ auth: false, cache })
726
876
  const invalid = await app.request(
@@ -750,7 +900,7 @@ describe('GET /v1/earn/addresses/:address/positions', () => {
750
900
  test('returns an empty verified page without curated vaults', async () => {
751
901
  const app = TestApp.create({ auth: false, cache })
752
902
  const response = await app.request(
753
- `/v1/earn/addresses/${TestEarn.positionAddress}/positions?verified=true`,
903
+ `/v1/earn/addresses/${TestEarn.positionAddress}/positions?include=earnings&verified=true`,
754
904
  )
755
905
 
756
906
  expect(response.status).toBe(200)
@@ -774,7 +924,7 @@ describe('GET /v1/earn/addresses/:address/positions', () => {
774
924
  })
775
925
  const app = TestApp.create({ auth: false, cache, db, zones: [TestEarn.zone] })
776
926
  const response = await app.request(
777
- `/v1/earn/addresses/${TestEarn.activeEarningsAddress}/positions?chainId=${TestEarn.chain.id}&verified=true&valuation.currency=USD`,
927
+ `/v1/earn/addresses/${TestEarn.activeEarningsAddress}/positions?chainId=${TestEarn.chain.id}&include=earnings&verified=true&valuation.currency=USD`,
778
928
  )
779
929
 
780
930
  expect(response.status).toBe(200)
@@ -790,6 +940,7 @@ describe('GET /v1/earn/addresses/:address/positions', () => {
790
940
  expect(row.shareAmount.decimals).toBe(row.shareToken.decimals)
791
941
  expect(row.assetAmount.currency).toBe(row.assetToken.currency)
792
942
  expect(row.assetAmount.decimals).toBe(row.assetToken.decimals)
943
+ expect(row.lifetimeCashFlows?.status).toMatch(/^(complete|incomplete_history)$/)
793
944
  // A USD-denominated asset values identically without consulting rates.
794
945
  expect(row.valuation).toStrictEqual({ amount: row.assetAmount.formatted, currency: 'USD' })
795
946
  expect(body.meta).toBeUndefined()
@@ -1008,6 +1159,7 @@ describe('GET /v1/earn/vaults/:vaultId', () => {
1008
1159
  // The pinned venue exposes no withdrawal-limit view, so liquidity is
1009
1160
  // absent rather than a restatement of the backing.
1010
1161
  instantLiquidity: vault.instantLiquidity,
1162
+ instantLiquidityValue: vault.instantLiquidityValue,
1011
1163
  isAccountingAligned: vault.state.isAccountingAligned,
1012
1164
  openRedeemRequestCount: vault.state.openRedeemRequestCount,
1013
1165
  pricedPerShare: vault.sharePrice !== null && BigInt(vault.sharePrice.amount) > 0n,
@@ -1019,6 +1171,7 @@ describe('GET /v1/earn/vaults/:vaultId', () => {
1019
1171
  "engineType": "erc4626",
1020
1172
  "feesActive": true,
1021
1173
  "instantLiquidity": null,
1174
+ "instantLiquidityValue": null,
1022
1175
  "isAccountingAligned": true,
1023
1176
  "openRedeemRequestCount": 0,
1024
1177
  "pricedPerShare": true,
@@ -1089,16 +1242,22 @@ describe('GET /v1/earn/vaults/:vaultId', () => {
1089
1242
  )
1090
1243
 
1091
1244
  test.runIf(Runtime.get().mode === 'testnet')(
1092
- 'leaves the rate unmeasured before the vault was deployed',
1245
+ 'leaves the rate unmeasured when the window starts before deployment',
1093
1246
  async () => {
1094
- const app = TestApp.create({ auth: false, cache, zones: [TestEarn.zone] })
1095
- const response = await app.request(
1096
- `/v1/earn/vaults/${TestEarn.vaultAddress}?chainId=${TestEarn.chain.id}&apy.window=30d`,
1097
- )
1247
+ // This vault was deployed on 2026-07-23, so the pinned window begins before deployment.
1248
+ vi.useFakeTimers({ now: new Date('2026-07-24T10:00:00Z'), toFake: ['Date'] })
1249
+ try {
1250
+ const app = TestApp.create({ auth: false, cache, zones: [TestEarn.zone] })
1251
+ const response = await app.request(
1252
+ `/v1/earn/vaults/${TestEarn.vaultAddress}?chainId=${TestEarn.chain.id}&apy.window=30d`,
1253
+ )
1098
1254
 
1099
- expect(response.status).toBe(200)
1100
- const vault = await TestApp.json(response, Earn.schema.getEarnVault.Response)
1101
- expect(vault.apy).toBeNull()
1255
+ expect(response.status).toBe(200)
1256
+ const vault = await TestApp.json(response, Earn.schema.getEarnVault.Response)
1257
+ expect(vault.apy).toBeNull()
1258
+ } finally {
1259
+ vi.useRealTimers()
1260
+ }
1102
1261
  },
1103
1262
  60_000,
1104
1263
  )
@@ -1300,6 +1459,25 @@ describe('GET /v1/earn/vaults/:vaultId', () => {
1300
1459
  })
1301
1460
 
1302
1461
  describe('GET /v1/earn/vaults/:vaultId/positions/:address', () => {
1462
+ test('rejects invalid as-of timestamps', async () => {
1463
+ const app = TestApp.create({ auth: false, cache })
1464
+ const [malformed, unix] = await Promise.all([
1465
+ app.request(
1466
+ `/v1/earn/vaults/${TestEarn.vaultAddress}/positions/${TestEarn.positionAddress}?asOf=not-a-time`,
1467
+ ),
1468
+ app.request(
1469
+ `/v1/earn/vaults/${TestEarn.vaultAddress}/positions/${TestEarn.positionAddress}?asOf=1787312400`,
1470
+ ),
1471
+ ])
1472
+
1473
+ for (const response of [malformed, unix]) {
1474
+ expect(response.status).toBe(400)
1475
+ expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({
1476
+ error: { code: 'query_invalid' },
1477
+ })
1478
+ }
1479
+ })
1480
+
1303
1481
  test('returns chain selection errors', async () => {
1304
1482
  const app = TestApp.create({ auth: false, cache })
1305
1483
  const invalid = await app.request(
@@ -1329,6 +1507,59 @@ describe('GET /v1/earn/vaults/:vaultId/positions/:address', () => {
1329
1507
  })
1330
1508
  })
1331
1509
 
1510
+ test('requires TIDX for historical reads on an RPC-only chain', async () => {
1511
+ const response = await TestApp.create({
1512
+ auth: false,
1513
+ cache,
1514
+ rpc: { url: { 31_337: 'http://127.0.0.1:1' } },
1515
+ tidx: {},
1516
+ }).request(
1517
+ `/v1/earn/vaults/${TestEarn.vaultAddress}/positions/${TestEarn.positionAddress}?chainId=31337&asOf=2026-07-21T09%3A00Z`,
1518
+ )
1519
+
1520
+ expect(response.status).toBe(400)
1521
+ expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({
1522
+ error: { code: 'chain_id_unsupported' },
1523
+ })
1524
+ })
1525
+
1526
+ test('validates malformed historical timestamps on an RPC-only chain', async () => {
1527
+ const response = await TestApp.create({
1528
+ auth: false,
1529
+ cache,
1530
+ rpc: { url: { 31_337: 'http://127.0.0.1:1' } },
1531
+ tidx: {},
1532
+ }).request(
1533
+ `/v1/earn/vaults/${TestEarn.vaultAddress}/positions/${TestEarn.positionAddress}?chainId=31337&asOf=not-a-time`,
1534
+ )
1535
+
1536
+ expect(response.status).toBe(400)
1537
+ expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({
1538
+ error: { code: 'query_invalid' },
1539
+ })
1540
+ })
1541
+
1542
+ test('validates the complete historical query on an RPC-only chain', async () => {
1543
+ const app = TestApp.create({
1544
+ auth: false,
1545
+ cache,
1546
+ rpc: { url: { 31_337: 'http://127.0.0.1:1' } },
1547
+ tidx: {},
1548
+ })
1549
+ const path = `/v1/earn/vaults/${TestEarn.vaultAddress}/positions/${TestEarn.positionAddress}?chainId=31337&asOf=2026-07-21T09%3A00Z`
1550
+ const [duplicate, unknown] = await Promise.all([
1551
+ app.request(`${path}&asOf=2026-07-21T10%3A00Z`),
1552
+ app.request(`${path}&unknown=1`),
1553
+ ])
1554
+
1555
+ for (const response of [duplicate, unknown]) {
1556
+ expect(response.status).toBe(400)
1557
+ expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({
1558
+ error: { code: 'query_invalid' },
1559
+ })
1560
+ }
1561
+ })
1562
+
1332
1563
  test('rejects malformed vault position parameters', async () => {
1333
1564
  const response = await TestApp.create({ auth: false, cache }).request(
1334
1565
  '/v1/earn/vaults/not-an-address/positions/not-an-address',
@@ -1349,6 +1580,7 @@ describe('GET /v1/earn/vaults/:vaultId/positions/:address', () => {
1349
1580
  )
1350
1581
 
1351
1582
  expect(response.status).toBe(200)
1583
+ expect(response.headers.get('cache-control')).toBe('no-store')
1352
1584
  const position = await TestApp.json(response, Earn.schema.getEarnVaultPosition.Response)
1353
1585
  expect({
1354
1586
  account: position.account,
@@ -1389,6 +1621,107 @@ describe('GET /v1/earn/vaults/:vaultId/positions/:address', () => {
1389
1621
  },
1390
1622
  60_000,
1391
1623
  )
1624
+
1625
+ test.runIf(Runtime.get().mode === 'testnet')(
1626
+ 'returns a position as of an indexed ISO 8601 timestamp',
1627
+ async () => {
1628
+ const runtime = Runtime.get()
1629
+ const tidx = Tidx.getClient({
1630
+ chainId: runtime.chainId,
1631
+ tidx: { auth: process.env.TIDX_AUTH, baseUrl: runtime.tidxUrl },
1632
+ })
1633
+ const indexed = await tidx.fetch({
1634
+ engine: 'clickhouse',
1635
+ query: 'SELECT num, timestamp FROM blocks ORDER BY num DESC LIMIT 1 OFFSET 100',
1636
+ })
1637
+ const blockNumber = Value.toNumber(indexed.rows[0]?.['num'])
1638
+ const asOf = Value.toIsoDateTime(indexed.rows[0]?.['timestamp'])
1639
+ if (blockNumber === undefined || asOf === undefined)
1640
+ throw new Error('expected an indexed block')
1641
+ const response = await TestApp.create({ auth: false, cache }).request(
1642
+ `/v1/earn/vaults/${TestEarn.vaultAddress}/positions/${TestEarn.positionAddress}?chainId=${TestEarn.chain.id}&asOf=${encodeURIComponent(asOf)}`,
1643
+ )
1644
+
1645
+ expect(response.status).toBe(200)
1646
+ const position = await TestApp.json(response, Earn.schema.getEarnVaultPosition.Response)
1647
+ expect(position.asOf).toBe(asOf)
1648
+ expect(position.block?.number).toBeGreaterThanOrEqual(blockNumber)
1649
+ expect(position.block?.timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T/)
1650
+ expect(Date.parse(position.block?.timestamp ?? '')).toBeLessThanOrEqual(Date.parse(asOf))
1651
+ },
1652
+ 60_000,
1653
+ )
1654
+
1655
+ test.runIf(Runtime.get().mode === 'testnet')(
1656
+ 'returns not found before the vault was deployed',
1657
+ async () => {
1658
+ const runtime = Runtime.get()
1659
+ const tidx = Tidx.getClient({
1660
+ chainId: runtime.chainId,
1661
+ tidx: { auth: process.env.TIDX_AUTH, baseUrl: runtime.tidxUrl },
1662
+ })
1663
+ const indexed = await tidx.fetch({
1664
+ engine: 'clickhouse',
1665
+ query: 'SELECT timestamp FROM blocks ORDER BY num ASC LIMIT 1',
1666
+ })
1667
+ const asOf = Value.toIsoDateTime(indexed.rows[0]?.['timestamp'])
1668
+ if (asOf === undefined) throw new Error('expected the pre-deployment block to be indexed')
1669
+ const response = await TestApp.create({ auth: false, cache }).request(
1670
+ `/v1/earn/vaults/${TestEarn.indexedDeployment.vaultAddress}/positions/${TestEarn.positionAddress}?chainId=${TestEarn.chain.id}&asOf=${encodeURIComponent(asOf)}`,
1671
+ )
1672
+
1673
+ expect(response.status).toBe(404)
1674
+ expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({
1675
+ error: { code: 'earn_vault_not_found' },
1676
+ })
1677
+ },
1678
+ 60_000,
1679
+ )
1680
+
1681
+ test.runIf(Runtime.get().mode === 'testnet')(
1682
+ 'returns not found for a deployed incompatible contract at a historical block',
1683
+ async () => {
1684
+ const runtime = Runtime.get()
1685
+ const tidx = Tidx.getClient({
1686
+ chainId: runtime.chainId,
1687
+ tidx: { auth: process.env.TIDX_AUTH, baseUrl: runtime.tidxUrl },
1688
+ })
1689
+ const indexed = await tidx.fetch({
1690
+ engine: 'clickhouse',
1691
+ query: 'SELECT timestamp FROM blocks ORDER BY num DESC LIMIT 1',
1692
+ })
1693
+ const asOf = Value.toIsoDateTime(indexed.rows[0]?.['timestamp'])
1694
+ if (asOf === undefined) throw new Error('expected an indexed block')
1695
+ const response = await TestApp.create({ auth: false, cache }).request(
1696
+ `/v1/earn/vaults/${TestEarn.assetTokenAddress}/positions/${TestEarn.positionAddress}?chainId=${TestEarn.chain.id}&asOf=${encodeURIComponent(asOf)}`,
1697
+ )
1698
+
1699
+ expect(response.status).toBe(404)
1700
+ expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({
1701
+ error: { code: 'earn_vault_not_found' },
1702
+ })
1703
+ },
1704
+ 60_000,
1705
+ )
1706
+
1707
+ test.runIf(Runtime.get().mode === 'testnet')(
1708
+ 'rejects timestamps beyond indexed coverage',
1709
+ async () => {
1710
+ const asOf = new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString()
1711
+ const response = await TestApp.create({ auth: false, cache }).request(
1712
+ `/v1/earn/vaults/${TestEarn.vaultAddress}/positions/${TestEarn.positionAddress}?chainId=${TestEarn.chain.id}&asOf=${encodeURIComponent(asOf)}`,
1713
+ )
1714
+
1715
+ expect(response.status).toBe(400)
1716
+ expect(await TestApp.json(response, Schema.ErrorResponse)).toMatchObject({
1717
+ error: {
1718
+ code: 'query_invalid',
1719
+ message: `Indexed history does not yet cover ${asOf}.`,
1720
+ },
1721
+ })
1722
+ },
1723
+ 60_000,
1724
+ )
1392
1725
  })
1393
1726
 
1394
1727
  describe('GET /v1/earn/vaults/:vaultId/earnings/:address', () => {
@@ -1437,6 +1770,8 @@ describe('GET /v1/earn/vaults/:vaultId/earnings/:address', () => {
1437
1770
  lifetimeEarnings: expect.stringMatching(/^-?\d+$/),
1438
1771
  period: 'lifetime',
1439
1772
  status: 'complete',
1773
+ totalDeposited: expect.stringMatching(/^\d+$/),
1774
+ totalWithdrawn: expect.stringMatching(/^\d+$/),
1440
1775
  })
1441
1776
  },
1442
1777
  60_000,
@@ -1747,6 +2082,9 @@ describe('OpenAPI', () => {
1747
2082
  expect(JSON.stringify(include?.schema)).toContain('"apy"')
1748
2083
  expect(JSON.stringify(include?.schema)).toContain('"tvl"')
1749
2084
  expect(JSON.stringify(include?.schema)).toContain('"zones"')
2085
+ expect(JSON.stringify(spec.paths['/v1/earn/vaults/{vaultId}']?.get?.responses)).toContain(
2086
+ 'Instant liquidity valued in USD',
2087
+ )
1750
2088
  })
1751
2089
 
1752
2090
  test('documents vault-position inputs and responses', async () => {
@@ -1754,6 +2092,7 @@ describe('OpenAPI', () => {
1754
2092
  const spec = (await (await app.request('/openapi.json')).json()) as OpenApiDocument
1755
2093
  const operation = spec.paths['/v1/earn/vaults/{vaultId}/positions/{address}']?.get
1756
2094
  const address = operation?.parameters?.find((parameter) => parameter.name === 'address')
2095
+ const asOf = operation?.parameters?.find((parameter) => parameter.name === 'asOf')
1757
2096
  const errors = errorSchemas(spec, operation)
1758
2097
  const response = JSON.stringify(operation?.responses)
1759
2098
  const vaultId = operation?.parameters?.find((parameter) => parameter.name === 'vaultId')
@@ -1767,6 +2106,17 @@ describe('OpenAPI', () => {
1767
2106
  expect(response).toContain('0x20c000000000000000000000b9537d11c60e8b50')
1768
2107
  expect(response).toContain('0x20c000000000000000000000300e14ab91a10769')
1769
2108
  expect(address?.schema?.examples).toStrictEqual([TestEarn.positionAddress])
2109
+ expect(asOf?.description).toContain('latest indexed block at or before')
2110
+ expect(asOf?.schema?.examples).toStrictEqual(['2026-07-21T09:00:00.000Z'])
2111
+ expect(response).toContain('Indexed block used for a historical Earn position observation')
2112
+ expect(response).toContain('Assets held by this account at the observation block')
2113
+ expect(response).toContain('Earn shares held by this account at the observation block')
2114
+ expect(response).toContain('Asset value of this account’s earn shares at the observation block')
2115
+ expect(response).not.toContain('Assets this account currently holds')
2116
+ expect(response).not.toContain('Earn shares this account currently holds')
2117
+ expect(operation?.responses?.['200']?.description).toBe(
2118
+ 'Current or historical earn position for one account in one vault.',
2119
+ )
1770
2120
  expect(vaultId?.schema?.examples).toStrictEqual([TestEarn.mainnetVaultAddress])
1771
2121
  })
1772
2122
 
@@ -1778,6 +2128,7 @@ describe('OpenAPI', () => {
1778
2128
  const denomination = operation?.parameters?.find(
1779
2129
  (parameter) => parameter.name === 'valuation.currency',
1780
2130
  )
2131
+ const include = operation?.parameters?.find((parameter) => parameter.name === 'include')
1781
2132
  const verified = operation?.parameters?.find((parameter) => parameter.name === 'verified')
1782
2133
  const errors = errorSchemas(spec, operation)
1783
2134
  const response = JSON.stringify(operation?.responses)
@@ -1786,6 +2137,7 @@ describe('OpenAPI', () => {
1786
2137
  expect(operation?.description).toContain('currently holds shares')
1787
2138
  expect(address?.schema?.examples).toStrictEqual([TestEarn.positionAddress])
1788
2139
  expect(denomination?.description).toContain('nominal value')
2140
+ expect(include?.description).toContain('`earnings`')
1789
2141
  expect(verified?.description).toContain('registry-curated')
1790
2142
  expect(verified?.schema?.examples).toStrictEqual([true])
1791
2143
  expect(errors).toContain('"chain_id_invalid"')
@@ -1796,6 +2148,7 @@ describe('OpenAPI', () => {
1796
2148
  expect(response).toContain('Current asset value of the held shares')
1797
2149
  expect(response).toContain('"assetAmount"')
1798
2150
  expect(response).toContain('"shareAmount"')
2151
+ expect(response).toContain('"lifetimeCashFlows"')
1799
2152
  expect(response).toContain('"valuation"')
1800
2153
  expect(response).toContain('"vaultAddress"')
1801
2154
  expect(response).toContain('"nextCursor"')
@@ -1819,6 +2172,8 @@ describe('OpenAPI', () => {
1819
2172
  expect(errors).toContain('"earn_vault_not_found"')
1820
2173
  expect(response).toContain('Stable resource ID for these vault earnings')
1821
2174
  expect(response).toContain('Current share value plus completed redemptions')
2175
+ expect(response).toContain('Assets deposited with this account as receiver')
2176
+ expect(response).toContain('Assets returned by completed redemptions')
1822
2177
  expect(response).toContain('weighted-average asset cost basis')
1823
2178
  expect(response).toContain('Ending share value plus realized assets')
1824
2179
  expect(response).toContain('cannot reconstruct the asset cost basis for the requested period')