tapimo 0.12.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 (398) hide show
  1. package/dist/App.d.ts +11333 -9563
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +39 -3
  4. package/dist/App.js.map +1 -1
  5. package/dist/Scope.d.ts +2 -0
  6. package/dist/Scope.d.ts.map +1 -1
  7. package/dist/Scope.js +17 -0
  8. package/dist/Scope.js.map +1 -1
  9. package/dist/Webhooks.js +2 -2
  10. package/dist/Webhooks.js.map +1 -1
  11. package/dist/admin/App.d.ts +1064 -924
  12. package/dist/admin/App.d.ts.map +1 -1
  13. package/dist/admin/apps/api-keys.d.ts +80 -0
  14. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  15. package/dist/admin/apps/api-keys.js +54 -0
  16. package/dist/admin/apps/api-keys.js.map +1 -1
  17. package/dist/admin/apps/earn-vaults.d.ts +63 -15
  18. package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
  19. package/dist/admin/apps/earn-vaults.js +2 -1
  20. package/dist/admin/apps/earn-vaults.js.map +1 -1
  21. package/dist/admin/apps/organizations.d.ts +92 -3
  22. package/dist/admin/apps/organizations.d.ts.map +1 -1
  23. package/dist/admin/apps/organizations.js +69 -7
  24. package/dist/admin/apps/organizations.js.map +1 -1
  25. package/dist/admin/apps/verified-tokens.d.ts +7 -7
  26. package/dist/admin/ui.generated.d.ts +1 -1
  27. package/dist/admin/ui.generated.d.ts.map +1 -1
  28. package/dist/admin/ui.generated.js +151 -151
  29. package/dist/admin/ui.generated.js.map +1 -1
  30. package/dist/apps/Zones.d.ts +28 -28
  31. package/dist/apps/Zones.js +7 -7
  32. package/dist/apps/data/App.d.ts +2909 -2523
  33. package/dist/apps/data/App.d.ts.map +1 -1
  34. package/dist/apps/data/App.js +26 -21
  35. package/dist/apps/data/App.js.map +1 -1
  36. package/dist/apps/data/ZoneSelection.d.ts +8 -8
  37. package/dist/apps/data/ZoneSelection.d.ts.map +1 -1
  38. package/dist/apps/data/ZoneSelection.js +1 -1
  39. package/dist/apps/data/ZoneSelection.js.map +1 -1
  40. package/dist/apps/data/routes/activities.d.ts +588 -584
  41. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  42. package/dist/apps/data/routes/activities.js +1561 -1239
  43. package/dist/apps/data/routes/activities.js.map +1 -1
  44. package/dist/apps/data/routes/addresses.d.ts +22 -22
  45. package/dist/apps/data/routes/balances.d.ts +26 -26
  46. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  47. package/dist/apps/data/routes/balances.js +43 -16
  48. package/dist/apps/data/routes/balances.js.map +1 -1
  49. package/dist/apps/data/routes/blocks.d.ts +44 -44
  50. package/dist/apps/data/routes/blocks.d.ts.map +1 -1
  51. package/dist/apps/data/routes/blocks.js +21 -11
  52. package/dist/apps/data/routes/blocks.js.map +1 -1
  53. package/dist/apps/data/routes/coingecko.d.ts +3 -3
  54. package/dist/apps/data/routes/earn.d.ts +201 -25
  55. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  56. package/dist/apps/data/routes/earn.js +434 -75
  57. package/dist/apps/data/routes/earn.js.map +1 -1
  58. package/dist/apps/data/routes/exchanges.d.ts +160 -145
  59. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  60. package/dist/apps/data/routes/exchanges.js +180 -114
  61. package/dist/apps/data/routes/exchanges.js.map +1 -1
  62. package/dist/apps/data/routes/fee-amm.d.ts +2 -2
  63. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  64. package/dist/apps/data/routes/fee-amm.js +18 -19
  65. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  66. package/dist/apps/data/routes/receipts.d.ts +155 -55
  67. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  68. package/dist/apps/data/routes/receipts.js +28 -10
  69. package/dist/apps/data/routes/receipts.js.map +1 -1
  70. package/dist/apps/data/routes/tokenlist.d.ts +13 -13
  71. package/dist/apps/data/routes/tokens.d.ts +5 -5
  72. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  73. package/dist/apps/data/routes/tokens.js +51 -20
  74. package/dist/apps/data/routes/tokens.js.map +1 -1
  75. package/dist/apps/data/routes/transactions.d.ts +7 -2
  76. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  77. package/dist/apps/data/routes/transactions.js +37 -17
  78. package/dist/apps/data/routes/transactions.js.map +1 -1
  79. package/dist/apps/data/routes/transfers.d.ts +13 -13
  80. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  81. package/dist/apps/data/routes/transfers.js +18 -12
  82. package/dist/apps/data/routes/transfers.js.map +1 -1
  83. package/dist/apps/data/routes/valuation.d.ts +31 -31
  84. package/dist/apps/data/routes/valuation.d.ts.map +1 -1
  85. package/dist/apps/data/routes/valuation.js +12 -7
  86. package/dist/apps/data/routes/valuation.js.map +1 -1
  87. package/dist/apps/data/routes/verified-tokens.d.ts +8 -8
  88. package/dist/apps/data/routes/webhooks.d.ts +816 -113
  89. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  90. package/dist/apps/data/routes/webhooks.js +47 -5
  91. package/dist/apps/data/routes/webhooks.js.map +1 -1
  92. package/dist/apps/data/routes/zones.d.ts +14 -14
  93. package/dist/apps/data/routes/zones.js +1 -1
  94. package/dist/apps/funding/App.d.ts +466 -44
  95. package/dist/apps/funding/App.d.ts.map +1 -1
  96. package/dist/apps/funding/App.js +1 -1
  97. package/dist/apps/funding/App.js.map +1 -1
  98. package/dist/apps/funding/routes/deposit-addresses.d.ts +17 -17
  99. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
  100. package/dist/apps/funding/routes/deposit-addresses.js +17 -12
  101. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  102. package/dist/apps/funding/routes/transfers.d.ts +650 -57
  103. package/dist/apps/funding/routes/transfers.d.ts.map +1 -1
  104. package/dist/apps/funding/routes/transfers.js +238 -24
  105. package/dist/apps/funding/routes/transfers.js.map +1 -1
  106. package/dist/apps/management/App.d.ts +5120 -4152
  107. package/dist/apps/management/App.d.ts.map +1 -1
  108. package/dist/apps/management/Billing.d.ts +1 -1
  109. package/dist/apps/management/Billing.d.ts.map +1 -1
  110. package/dist/apps/management/Billing.js +17 -1
  111. package/dist/apps/management/Billing.js.map +1 -1
  112. package/dist/apps/management/routes/api-keys.d.ts +632 -81
  113. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  114. package/dist/apps/management/routes/api-keys.js +236 -4
  115. package/dist/apps/management/routes/api-keys.js.map +1 -1
  116. package/dist/apps/management/routes/billing.d.ts +76 -76
  117. package/dist/apps/management/routes/faucet.d.ts +6 -6
  118. package/dist/apps/management/routes/invitations.d.ts +24 -24
  119. package/dist/apps/management/routes/invite-links.d.ts +4 -4
  120. package/dist/apps/management/routes/members.d.ts +16 -16
  121. package/dist/apps/management/routes/orgs.d.ts +20 -20
  122. package/dist/apps/management/routes/projects.d.ts +4 -4
  123. package/dist/apps/management/routes/usage.d.ts +8 -8
  124. package/dist/apps/management/routes/verified-token-requests.d.ts +17 -17
  125. package/dist/apps/management/routes/webhooks.d.ts +675 -25
  126. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  127. package/dist/apps/management/routes/webhooks.js +21 -21
  128. package/dist/apps/management/routes/webhooks.js.map +1 -1
  129. package/dist/apps/relay/App.d.ts.map +1 -1
  130. package/dist/apps/relay/App.js +115 -23
  131. package/dist/apps/relay/App.js.map +1 -1
  132. package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
  133. package/dist/apps/relay/Sponsorships.js +27 -1
  134. package/dist/apps/relay/Sponsorships.js.map +1 -1
  135. package/dist/db/Db.d.ts.map +1 -1
  136. package/dist/db/Db.js +34 -1
  137. package/dist/db/Db.js.map +1 -1
  138. package/dist/db/Schema.d.ts +11 -5
  139. package/dist/db/Schema.d.ts.map +1 -1
  140. package/dist/db/Schema.js +21 -3
  141. package/dist/db/Schema.js.map +1 -1
  142. package/dist/db/tables/fundingCatalog.d.ts +4 -2
  143. package/dist/db/tables/fundingCatalog.d.ts.map +1 -1
  144. package/dist/db/tables/fundingCatalog.js +5 -1
  145. package/dist/db/tables/fundingCatalog.js.map +1 -1
  146. package/dist/db/tables/fundingTransferTransactions.d.ts +38 -0
  147. package/dist/db/tables/fundingTransferTransactions.d.ts.map +1 -1
  148. package/dist/db/tables/fundingTransferTransactions.js +44 -3
  149. package/dist/db/tables/fundingTransferTransactions.js.map +1 -1
  150. package/dist/db/tables/fundingTransfers.d.ts +17 -0
  151. package/dist/db/tables/fundingTransfers.d.ts.map +1 -1
  152. package/dist/db/tables/fundingTransfers.js +21 -0
  153. package/dist/db/tables/fundingTransfers.js.map +1 -1
  154. package/dist/db/tables/organizations.d.ts +24 -0
  155. package/dist/db/tables/organizations.d.ts.map +1 -1
  156. package/dist/db/tables/organizations.js +27 -0
  157. package/dist/db/tables/organizations.js.map +1 -1
  158. package/dist/db/tables/projects.d.ts +25 -1
  159. package/dist/db/tables/projects.d.ts.map +1 -1
  160. package/dist/db/tables/projects.js +26 -0
  161. package/dist/db/tables/projects.js.map +1 -1
  162. package/dist/db/tables/sponsoredTransactions.d.ts +44 -0
  163. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  164. package/dist/db/tables/sponsoredTransactions.js +146 -13
  165. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  166. package/dist/db/tables/webhookSubscriptions.d.ts +10 -6
  167. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  168. package/dist/db/tables/webhookSubscriptions.js +9 -6
  169. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  170. package/dist/handlers/internal/utils.d.ts.map +1 -1
  171. package/dist/handlers/internal/utils.js +9 -0
  172. package/dist/handlers/internal/utils.js.map +1 -1
  173. package/dist/internal/Auth.d.ts +3 -1
  174. package/dist/internal/Auth.d.ts.map +1 -1
  175. package/dist/internal/Auth.js +6 -3
  176. package/dist/internal/Auth.js.map +1 -1
  177. package/dist/internal/EarnVaults.d.ts +3 -3
  178. package/dist/internal/EarnVaults.js +5 -5
  179. package/dist/internal/EarnVaults.js.map +1 -1
  180. package/dist/internal/Fetch.d.ts +23 -0
  181. package/dist/internal/Fetch.d.ts.map +1 -0
  182. package/dist/internal/Fetch.js +26 -0
  183. package/dist/internal/Fetch.js.map +1 -0
  184. package/dist/internal/Log.d.ts +4 -2
  185. package/dist/internal/Log.d.ts.map +1 -1
  186. package/dist/internal/Log.js +16 -3
  187. package/dist/internal/Log.js.map +1 -1
  188. package/dist/internal/OpenApi.d.ts +5 -3
  189. package/dist/internal/OpenApi.d.ts.map +1 -1
  190. package/dist/internal/OpenApi.js +13 -3
  191. package/dist/internal/OpenApi.js.map +1 -1
  192. package/dist/internal/Response.d.ts +1 -1
  193. package/dist/internal/Response.d.ts.map +1 -1
  194. package/dist/internal/Schema.d.ts +6 -3
  195. package/dist/internal/Schema.d.ts.map +1 -1
  196. package/dist/internal/Schema.js +8 -2
  197. package/dist/internal/Schema.js.map +1 -1
  198. package/dist/internal/Store.d.ts +16 -3
  199. package/dist/internal/Store.d.ts.map +1 -1
  200. package/dist/internal/Store.js +82 -16
  201. package/dist/internal/Store.js.map +1 -1
  202. package/dist/internal/Tidx.d.ts.map +1 -1
  203. package/dist/internal/Tidx.js +7 -2
  204. package/dist/internal/Tidx.js.map +1 -1
  205. package/dist/internal/Timeout.d.ts +36 -0
  206. package/dist/internal/Timeout.d.ts.map +1 -0
  207. package/dist/internal/Timeout.js +37 -0
  208. package/dist/internal/Timeout.js.map +1 -0
  209. package/dist/internal/Timing.d.ts +9 -0
  210. package/dist/internal/Timing.d.ts.map +1 -1
  211. package/dist/internal/Timing.js +7 -0
  212. package/dist/internal/Timing.js.map +1 -1
  213. package/dist/internal/Viem.d.ts +16 -1
  214. package/dist/internal/Viem.d.ts.map +1 -1
  215. package/dist/internal/Viem.js +12 -2
  216. package/dist/internal/Viem.js.map +1 -1
  217. package/dist/internal/WebhookDestination.d.ts.map +1 -1
  218. package/dist/internal/WebhookDestination.js +3 -0
  219. package/dist/internal/WebhookDestination.js.map +1 -1
  220. package/dist/internal/funding/Action.d.ts +9 -0
  221. package/dist/internal/funding/Action.d.ts.map +1 -1
  222. package/dist/internal/funding/Action.js +16 -8
  223. package/dist/internal/funding/Action.js.map +1 -1
  224. package/dist/internal/funding/Catalog.d.ts +9 -2
  225. package/dist/internal/funding/Catalog.d.ts.map +1 -1
  226. package/dist/internal/funding/Catalog.js +19 -8
  227. package/dist/internal/funding/Catalog.js.map +1 -1
  228. package/dist/internal/funding/DepositWebhook.d.ts.map +1 -1
  229. package/dist/internal/funding/DepositWebhook.js +2 -1
  230. package/dist/internal/funding/DepositWebhook.js.map +1 -1
  231. package/dist/internal/funding/Evidence.d.ts.map +1 -1
  232. package/dist/internal/funding/Evidence.js +2 -10
  233. package/dist/internal/funding/Evidence.js.map +1 -1
  234. package/dist/internal/funding/Provider.d.ts +108 -13
  235. package/dist/internal/funding/Provider.d.ts.map +1 -1
  236. package/dist/internal/funding/Provider.js +34 -8
  237. package/dist/internal/funding/Provider.js.map +1 -1
  238. package/dist/internal/funding/SourceObservation.d.ts.map +1 -1
  239. package/dist/internal/funding/SourceObservation.js +5 -7
  240. package/dist/internal/funding/SourceObservation.js.map +1 -1
  241. package/dist/internal/funding/Transfer.d.ts +114 -18
  242. package/dist/internal/funding/Transfer.d.ts.map +1 -1
  243. package/dist/internal/funding/Transfer.js +185 -55
  244. package/dist/internal/funding/Transfer.js.map +1 -1
  245. package/dist/internal/funding/TransferReconciliation.d.ts +52 -0
  246. package/dist/internal/funding/TransferReconciliation.d.ts.map +1 -0
  247. package/dist/internal/funding/TransferReconciliation.js +192 -0
  248. package/dist/internal/funding/TransferReconciliation.js.map +1 -0
  249. package/dist/internal/funding/TransferWebhook.d.ts +21 -0
  250. package/dist/internal/funding/TransferWebhook.d.ts.map +1 -0
  251. package/dist/internal/funding/TransferWebhook.js +43 -0
  252. package/dist/internal/funding/TransferWebhook.js.map +1 -0
  253. package/dist/internal/funding/index.d.ts +1 -0
  254. package/dist/internal/funding/index.d.ts.map +1 -1
  255. package/dist/internal/funding/index.js +1 -0
  256. package/dist/internal/funding/index.js.map +1 -1
  257. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  258. package/dist/internal/funding/providers/relay.js +3 -12
  259. package/dist/internal/funding/providers/relay.js.map +1 -1
  260. package/dist/internal/funding/providers/stargate.d.ts +127 -9
  261. package/dist/internal/funding/providers/stargate.d.ts.map +1 -1
  262. package/dist/internal/funding/providers/stargate.js +548 -112
  263. package/dist/internal/funding/providers/stargate.js.map +1 -1
  264. package/dist/internal/index.d.ts +1 -0
  265. package/dist/internal/index.d.ts.map +1 -1
  266. package/dist/internal/index.js +1 -0
  267. package/dist/internal/index.js.map +1 -1
  268. package/dist/internal/webhookDestinations/slack.d.ts.map +1 -1
  269. package/dist/internal/webhookDestinations/slack.js +16 -0
  270. package/dist/internal/webhookDestinations/slack.js.map +1 -1
  271. package/package.json +2 -2
  272. package/src/App.test.ts +100 -0
  273. package/src/App.ts +43 -3
  274. package/src/Client.test-d.ts +129 -2
  275. package/src/Scope.test.ts +20 -0
  276. package/src/Scope.ts +16 -0
  277. package/src/Webhooks.ts +2 -2
  278. package/src/admin/apps/api-keys.test.ts +75 -0
  279. package/src/admin/apps/api-keys.ts +64 -0
  280. package/src/admin/apps/earn-vaults.test.ts +1 -1
  281. package/src/admin/apps/earn-vaults.ts +2 -1
  282. package/src/admin/apps/organizations.test.ts +53 -1
  283. package/src/admin/apps/organizations.ts +90 -5
  284. package/src/admin/ui/src/lib/api-keys.ts +19 -0
  285. package/src/admin/ui/src/lib/organizations.test.ts +112 -1
  286. package/src/admin/ui/src/lib/organizations.ts +55 -0
  287. package/src/admin/ui/src/routes/api-keys.tsx +89 -2
  288. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +116 -0
  289. package/src/admin/ui.generated.ts +189 -151
  290. package/src/apps/Zones.test.ts +7 -7
  291. package/src/apps/Zones.ts +7 -7
  292. package/src/apps/composition.test.ts +2 -1
  293. package/src/apps/data/App.test.ts +41 -0
  294. package/src/apps/data/App.ts +30 -21
  295. package/src/apps/data/ZoneSelection.test.ts +2 -2
  296. package/src/apps/data/ZoneSelection.ts +8 -8
  297. package/src/apps/data/routes/activities.test.ts +1526 -12
  298. package/src/apps/data/routes/activities.ts +1774 -1356
  299. package/src/apps/data/routes/balances.ts +133 -97
  300. package/src/apps/data/routes/blocks.test.ts +533 -0
  301. package/src/apps/data/routes/blocks.ts +35 -16
  302. package/src/apps/data/routes/earn.test.ts +373 -16
  303. package/src/apps/data/routes/earn.ts +548 -79
  304. package/src/apps/data/routes/earn.zone-access.test.ts +6 -6
  305. package/src/apps/data/routes/exchanges.test.ts +740 -15
  306. package/src/apps/data/routes/exchanges.ts +954 -753
  307. package/src/apps/data/routes/fee-amm.ts +17 -18
  308. package/src/apps/data/routes/receipts.ts +47 -16
  309. package/src/apps/data/routes/tokens.test.ts +578 -0
  310. package/src/apps/data/routes/tokens.ts +283 -225
  311. package/src/apps/data/routes/transactions.ts +68 -29
  312. package/src/apps/data/routes/transfers.test.ts +421 -0
  313. package/src/apps/data/routes/transfers.ts +45 -27
  314. package/src/apps/data/routes/valuation.ts +94 -78
  315. package/src/apps/data/routes/webhooks.test.ts +21 -5
  316. package/src/apps/data/routes/webhooks.ts +63 -8
  317. package/src/apps/data/routes/zones.test.ts +2 -2
  318. package/src/apps/data/routes/zones.ts +1 -1
  319. package/src/apps/funding/App.ts +12 -2
  320. package/src/apps/funding/routes/deposit-addresses.test.ts +29 -7
  321. package/src/apps/funding/routes/deposit-addresses.ts +19 -14
  322. package/src/apps/funding/routes/quotes.test.ts +5 -9
  323. package/src/apps/funding/routes/transfers.test.ts +901 -23
  324. package/src/apps/funding/routes/transfers.ts +347 -64
  325. package/src/apps/management/Billing.ts +18 -5
  326. package/src/apps/management/routes/api-keys.test.ts +220 -0
  327. package/src/apps/management/routes/api-keys.ts +280 -3
  328. package/src/apps/management/routes/webhooks.test.ts +3 -3
  329. package/src/apps/management/routes/webhooks.ts +24 -25
  330. package/src/apps/relay/App.test.ts +212 -1
  331. package/src/apps/relay/App.ts +158 -29
  332. package/src/apps/relay/Sponsorships.ts +24 -1
  333. package/src/db/Db.ts +48 -1
  334. package/src/db/Schema.ts +22 -4
  335. package/src/db/tables/earnVaults.test.ts +9 -9
  336. package/src/db/tables/fundingCatalog.test.ts +16 -2
  337. package/src/db/tables/fundingCatalog.ts +9 -3
  338. package/src/db/tables/fundingTransferTransactions.test.ts +43 -0
  339. package/src/db/tables/fundingTransferTransactions.ts +79 -3
  340. package/src/db/tables/fundingTransfers.test.ts +26 -0
  341. package/src/db/tables/fundingTransfers.ts +36 -0
  342. package/src/db/tables/organizations.test.ts +41 -0
  343. package/src/db/tables/organizations.ts +49 -0
  344. package/src/db/tables/projects.test.ts +55 -0
  345. package/src/db/tables/projects.ts +46 -1
  346. package/src/db/tables/sponsoredTransactions.test.ts +338 -0
  347. package/src/db/tables/sponsoredTransactions.ts +197 -13
  348. package/src/db/tables/webhookSubscriptions.ts +18 -11
  349. package/src/handlers/internal/utils.test.ts +53 -0
  350. package/src/handlers/internal/utils.ts +15 -0
  351. package/src/internal/Auth.ts +12 -4
  352. package/src/internal/EarnVaults.capabilities.test.ts +2 -2
  353. package/src/internal/EarnVaults.test.ts +1 -1
  354. package/src/internal/EarnVaults.ts +5 -5
  355. package/src/internal/Fetch.test.ts +46 -0
  356. package/src/internal/Fetch.ts +56 -0
  357. package/src/internal/Log.test-d.ts +2 -0
  358. package/src/internal/Log.test.ts +21 -0
  359. package/src/internal/Log.ts +22 -5
  360. package/src/internal/OpenApi.ts +14 -4
  361. package/src/internal/Response.ts +1 -1
  362. package/src/internal/Schema.test.ts +2 -0
  363. package/src/internal/Schema.ts +12 -2
  364. package/src/internal/Store.test.ts +178 -0
  365. package/src/internal/Store.ts +104 -18
  366. package/src/internal/Tidx.test.ts +2 -1
  367. package/src/internal/Tidx.ts +7 -2
  368. package/src/internal/Timeout.test.ts +35 -0
  369. package/src/internal/Timeout.ts +73 -0
  370. package/src/internal/Timing.ts +16 -0
  371. package/src/internal/Viem.ts +39 -3
  372. package/src/internal/WebhookDestination.ts +3 -0
  373. package/src/internal/Webhooks.test.ts +45 -0
  374. package/src/internal/funding/Action.test.ts +41 -1
  375. package/src/internal/funding/Action.ts +69 -41
  376. package/src/internal/funding/Catalog.test.ts +34 -0
  377. package/src/internal/funding/Catalog.ts +28 -11
  378. package/src/internal/funding/DepositWebhook.ts +2 -1
  379. package/src/internal/funding/Evidence.ts +2 -13
  380. package/src/internal/funding/Provider.test.ts +73 -0
  381. package/src/internal/funding/Provider.ts +125 -28
  382. package/src/internal/funding/SourceObservation.ts +5 -9
  383. package/src/internal/funding/Transfer.test.ts +155 -0
  384. package/src/internal/funding/Transfer.ts +387 -147
  385. package/src/internal/funding/TransferReconciliation.test.ts +147 -0
  386. package/src/internal/funding/TransferReconciliation.ts +264 -0
  387. package/src/internal/funding/TransferWebhook.test.ts +128 -0
  388. package/src/internal/funding/TransferWebhook.ts +67 -0
  389. package/src/internal/funding/index.test.ts +1 -0
  390. package/src/internal/funding/index.ts +1 -0
  391. package/src/internal/funding/providers/index.test.ts +4 -167
  392. package/src/internal/funding/providers/relay.test.ts +2 -2
  393. package/src/internal/funding/providers/relay.ts +3 -15
  394. package/src/internal/funding/providers/stargate.test.ts +328 -0
  395. package/src/internal/funding/providers/stargate.ts +807 -135
  396. package/src/internal/index.test.ts +1 -0
  397. package/src/internal/index.ts +1 -0
  398. package/src/internal/webhookDestinations/slack.ts +15 -0
@@ -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,13 +2128,16 @@ 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)
2135
+ const tokenReference = JSON.stringify(spec.components?.schemas?.['TokenReference'])
1784
2136
 
1785
2137
  expect(operation?.description).toContain('currently holds shares')
1786
2138
  expect(address?.schema?.examples).toStrictEqual([TestEarn.positionAddress])
1787
2139
  expect(denomination?.description).toContain('nominal value')
2140
+ expect(include?.description).toContain('`earnings`')
1788
2141
  expect(verified?.description).toContain('registry-curated')
1789
2142
  expect(verified?.schema?.examples).toStrictEqual([true])
1790
2143
  expect(errors).toContain('"chain_id_invalid"')
@@ -1795,11 +2148,13 @@ describe('OpenAPI', () => {
1795
2148
  expect(response).toContain('Current asset value of the held shares')
1796
2149
  expect(response).toContain('"assetAmount"')
1797
2150
  expect(response).toContain('"shareAmount"')
2151
+ expect(response).toContain('"lifetimeCashFlows"')
1798
2152
  expect(response).toContain('"valuation"')
1799
2153
  expect(response).toContain('"vaultAddress"')
1800
2154
  expect(response).toContain('"nextCursor"')
1801
- // Token references embed the trimmed token object, not a bare address.
1802
- expect(response).toContain('"symbol"')
2155
+ expect(response).toContain('#/components/schemas/TokenReference')
2156
+ // Token references retain display metadata through the shared component.
2157
+ expect(tokenReference).toContain('"symbol"')
1803
2158
  })
1804
2159
 
1805
2160
  test('documents vault-earnings variants', async () => {
@@ -1817,6 +2172,8 @@ describe('OpenAPI', () => {
1817
2172
  expect(errors).toContain('"earn_vault_not_found"')
1818
2173
  expect(response).toContain('Stable resource ID for these vault earnings')
1819
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')
1820
2177
  expect(response).toContain('weighted-average asset cost basis')
1821
2178
  expect(response).toContain('Ending share value plus realized assets')
1822
2179
  expect(response).toContain('cannot reconstruct the asset cost basis for the requested period')