tapimo 0.9.0 → 0.10.1

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 (319) hide show
  1. package/dist/ApiKey.d.ts +2 -1
  2. package/dist/ApiKey.d.ts.map +1 -1
  3. package/dist/ApiKeys.d.ts +4 -2
  4. package/dist/ApiKeys.d.ts.map +1 -1
  5. package/dist/ApiKeys.js +26 -1
  6. package/dist/ApiKeys.js.map +1 -1
  7. package/dist/App.d.ts +4292 -3708
  8. package/dist/App.d.ts.map +1 -1
  9. package/dist/App.js +1 -0
  10. package/dist/App.js.map +1 -1
  11. package/dist/Webhooks.d.ts +21 -20
  12. package/dist/Webhooks.d.ts.map +1 -1
  13. package/dist/Webhooks.js +66 -23
  14. package/dist/Webhooks.js.map +1 -1
  15. package/dist/admin/App.d.ts +91 -84
  16. package/dist/admin/App.d.ts.map +1 -1
  17. package/dist/admin/apps/api-keys.d.ts +26 -17
  18. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  19. package/dist/admin/apps/api-keys.js +6 -0
  20. package/dist/admin/apps/api-keys.js.map +1 -1
  21. package/dist/admin/apps/organizations.d.ts +4 -2
  22. package/dist/admin/apps/organizations.d.ts.map +1 -1
  23. package/dist/admin/apps/verified-tokens.d.ts +168 -168
  24. package/dist/admin/ui.generated.d.ts +1 -1
  25. package/dist/admin/ui.generated.js +69 -69
  26. package/dist/admin/ui.generated.js.map +1 -1
  27. package/dist/analytics/Analytics.d.ts.map +1 -1
  28. package/dist/analytics/Analytics.js +26 -0
  29. package/dist/analytics/Analytics.js.map +1 -1
  30. package/dist/apps/Zones.d.ts +4280 -84
  31. package/dist/apps/Zones.d.ts.map +1 -1
  32. package/dist/apps/Zones.js +30 -10
  33. package/dist/apps/Zones.js.map +1 -1
  34. package/dist/apps/data/App.d.ts +5799 -4929
  35. package/dist/apps/data/App.d.ts.map +1 -1
  36. package/dist/apps/data/FxOracle.d.ts +4 -5
  37. package/dist/apps/data/FxOracle.d.ts.map +1 -1
  38. package/dist/apps/data/FxOracle.js +19 -4
  39. package/dist/apps/data/FxOracle.js.map +1 -1
  40. package/dist/apps/data/routes/activities.d.ts +3391 -492
  41. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  42. package/dist/apps/data/routes/activities.js +372 -29
  43. package/dist/apps/data/routes/activities.js.map +1 -1
  44. package/dist/apps/data/routes/addresses.d.ts +853 -339
  45. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  46. package/dist/apps/data/routes/balances.d.ts +10 -10
  47. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  48. package/dist/apps/data/routes/balances.js +145 -83
  49. package/dist/apps/data/routes/balances.js.map +1 -1
  50. package/dist/apps/data/routes/blocks.d.ts +123 -123
  51. package/dist/apps/data/routes/coingecko.d.ts +169 -169
  52. package/dist/apps/data/routes/earn.d.ts +455 -1
  53. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  54. package/dist/apps/data/routes/earn.js +369 -45
  55. package/dist/apps/data/routes/earn.js.map +1 -1
  56. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  57. package/dist/apps/data/routes/exchanges.js +114 -118
  58. package/dist/apps/data/routes/exchanges.js.map +1 -1
  59. package/dist/apps/data/routes/fee-amm.d.ts +24 -24
  60. package/dist/apps/data/routes/indexer.d.ts +35 -34
  61. package/dist/apps/data/routes/indexer.d.ts.map +1 -1
  62. package/dist/apps/data/routes/indexer.js +3 -2
  63. package/dist/apps/data/routes/indexer.js.map +1 -1
  64. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  65. package/dist/apps/data/routes/receipts.js +68 -47
  66. package/dist/apps/data/routes/receipts.js.map +1 -1
  67. package/dist/apps/data/routes/rpc.d.ts +31 -31
  68. package/dist/apps/data/routes/rpc.d.ts.map +1 -1
  69. package/dist/apps/data/routes/rpc.js +5 -1
  70. package/dist/apps/data/routes/rpc.js.map +1 -1
  71. package/dist/apps/data/routes/tokenlist.d.ts +12 -12
  72. package/dist/apps/data/routes/tokens.d.ts +461 -459
  73. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  74. package/dist/apps/data/routes/transactions.d.ts +313 -305
  75. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  76. package/dist/apps/data/routes/transactions.js +25 -23
  77. package/dist/apps/data/routes/transactions.js.map +1 -1
  78. package/dist/apps/data/routes/transfers.d.ts +108 -86
  79. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  80. package/dist/apps/data/routes/transfers.js +618 -105
  81. package/dist/apps/data/routes/transfers.js.map +1 -1
  82. package/dist/apps/data/routes/valuation.d.ts +21 -21
  83. package/dist/apps/data/routes/valuation.d.ts.map +1 -1
  84. package/dist/apps/data/routes/valuation.js +2 -2
  85. package/dist/apps/data/routes/valuation.js.map +1 -1
  86. package/dist/apps/data/routes/webhooks.d.ts +519 -868
  87. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  88. package/dist/apps/data/routes/webhooks.js +642 -353
  89. package/dist/apps/data/routes/webhooks.js.map +1 -1
  90. package/dist/apps/data/routes/zones.d.ts +31 -31
  91. package/dist/apps/data/routes/zones.d.ts.map +1 -1
  92. package/dist/apps/data/routes/zones.js +4 -2
  93. package/dist/apps/data/routes/zones.js.map +1 -1
  94. package/dist/apps/funding/routes/deposit-addresses.js +1 -1
  95. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  96. package/dist/apps/funding/routes/providers.d.ts +13 -13
  97. package/dist/apps/funding/routes/quotes.d.ts +89 -89
  98. package/dist/apps/funding/routes/transfers.d.ts +36 -36
  99. package/dist/apps/funding/routes/transfers.js +1 -1
  100. package/dist/apps/funding/routes/transfers.js.map +1 -1
  101. package/dist/apps/management/App.d.ts +11 -309
  102. package/dist/apps/management/App.d.ts.map +1 -1
  103. package/dist/apps/management/routes/api-keys.d.ts +74 -74
  104. package/dist/apps/management/routes/billing.js +1 -1
  105. package/dist/apps/management/routes/billing.js.map +1 -1
  106. package/dist/apps/management/routes/faucet.d.ts +35 -35
  107. package/dist/apps/management/routes/faucet.js +2 -2
  108. package/dist/apps/management/routes/faucet.js.map +1 -1
  109. package/dist/apps/management/routes/invitations.d.ts +134 -134
  110. package/dist/apps/management/routes/invite-links.d.ts +80 -80
  111. package/dist/apps/management/routes/members.d.ts +69 -69
  112. package/dist/apps/management/routes/orgs.d.ts +98 -98
  113. package/dist/apps/management/routes/projects.d.ts +60 -60
  114. package/dist/apps/management/routes/usage.d.ts +72 -72
  115. package/dist/apps/management/routes/verified-token-requests.d.ts +83 -83
  116. package/dist/apps/management/routes/webhooks.d.ts +203 -618
  117. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  118. package/dist/apps/mcp.d.ts.map +1 -1
  119. package/dist/apps/mcp.js +4 -1
  120. package/dist/apps/mcp.js.map +1 -1
  121. package/dist/apps/mpp/App.d.ts +56 -44
  122. package/dist/apps/mpp/App.d.ts.map +1 -1
  123. package/dist/apps/mpp/Mpp.d.ts +3 -0
  124. package/dist/apps/mpp/Mpp.d.ts.map +1 -1
  125. package/dist/apps/mpp/Mpp.js +3 -0
  126. package/dist/apps/mpp/Mpp.js.map +1 -1
  127. package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -1
  128. package/dist/apps/mpp/internal/Adapter.js +9 -2
  129. package/dist/apps/mpp/internal/Adapter.js.map +1 -1
  130. package/dist/db/Db.d.ts +4 -2
  131. package/dist/db/Db.d.ts.map +1 -1
  132. package/dist/db/Db.js +75 -15
  133. package/dist/db/Db.js.map +1 -1
  134. package/dist/db/Schema.d.ts +19 -0
  135. package/dist/db/Schema.d.ts.map +1 -1
  136. package/dist/db/Schema.js +24 -0
  137. package/dist/db/Schema.js.map +1 -1
  138. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  139. package/dist/db/tables/sponsoredTransactions.js +4 -1
  140. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  141. package/dist/db/tables/webhookQueueEvents.d.ts +122 -0
  142. package/dist/db/tables/webhookQueueEvents.d.ts.map +1 -0
  143. package/dist/db/tables/webhookQueueEvents.js +160 -0
  144. package/dist/db/tables/webhookQueueEvents.js.map +1 -0
  145. package/dist/db/tables/webhookSubscriptions.d.ts +58 -3
  146. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  147. package/dist/db/tables/webhookSubscriptions.js +86 -3
  148. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  149. package/dist/internal/Auth.d.ts +3 -0
  150. package/dist/internal/Auth.d.ts.map +1 -1
  151. package/dist/internal/Auth.js +6 -4
  152. package/dist/internal/Auth.js.map +1 -1
  153. package/dist/internal/MetricSink.d.ts +139 -3
  154. package/dist/internal/MetricSink.d.ts.map +1 -1
  155. package/dist/internal/MetricSink.js +144 -4
  156. package/dist/internal/MetricSink.js.map +1 -1
  157. package/dist/internal/RateLimit.d.ts +4 -6
  158. package/dist/internal/RateLimit.d.ts.map +1 -1
  159. package/dist/internal/RateLimit.js +15 -14
  160. package/dist/internal/RateLimit.js.map +1 -1
  161. package/dist/internal/Schema.js +1 -1
  162. package/dist/internal/Schema.js.map +1 -1
  163. package/dist/internal/Store.d.ts +2 -1
  164. package/dist/internal/Store.d.ts.map +1 -1
  165. package/dist/internal/Store.js +57 -18
  166. package/dist/internal/Store.js.map +1 -1
  167. package/dist/internal/Tidx.d.ts +1 -3
  168. package/dist/internal/Tidx.d.ts.map +1 -1
  169. package/dist/internal/Tidx.js +1 -3
  170. package/dist/internal/Tidx.js.map +1 -1
  171. package/dist/internal/Value.d.ts +2 -0
  172. package/dist/internal/Value.d.ts.map +1 -1
  173. package/dist/internal/Value.js +25 -0
  174. package/dist/internal/Value.js.map +1 -1
  175. package/dist/internal/Viem.d.ts.map +1 -1
  176. package/dist/internal/Viem.js +2 -1
  177. package/dist/internal/Viem.js.map +1 -1
  178. package/dist/internal/WebhookDestination.d.ts +2 -0
  179. package/dist/internal/WebhookDestination.d.ts.map +1 -1
  180. package/dist/internal/WebhookDestination.js +11 -0
  181. package/dist/internal/WebhookDestination.js.map +1 -1
  182. package/dist/internal/WebhookTransfer.d.ts +75 -0
  183. package/dist/internal/WebhookTransfer.d.ts.map +1 -0
  184. package/dist/internal/WebhookTransfer.js +299 -0
  185. package/dist/internal/WebhookTransfer.js.map +1 -0
  186. package/dist/internal/Webhooks.d.ts +176 -0
  187. package/dist/internal/Webhooks.d.ts.map +1 -1
  188. package/dist/internal/Webhooks.js +216 -20
  189. package/dist/internal/Webhooks.js.map +1 -1
  190. package/dist/internal/funding/Provider.d.ts +4 -0
  191. package/dist/internal/funding/Provider.d.ts.map +1 -1
  192. package/dist/internal/funding/Provider.js +11 -7
  193. package/dist/internal/funding/Provider.js.map +1 -1
  194. package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
  195. package/dist/internal/funding/Reconciliation.js +128 -0
  196. package/dist/internal/funding/Reconciliation.js.map +1 -1
  197. package/dist/internal/funding/providers/relay.d.ts +6 -4
  198. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  199. package/dist/internal/funding/providers/relay.js +71 -9
  200. package/dist/internal/funding/providers/relay.js.map +1 -1
  201. package/package.json +4 -4
  202. package/src/ApiKeys.test.ts +20 -0
  203. package/src/ApiKeys.ts +28 -1
  204. package/src/App.test.ts +10 -8
  205. package/src/App.ts +1 -0
  206. package/src/Client.test-d.ts +61 -5
  207. package/src/Webhooks.bench.ts +714 -0
  208. package/src/Webhooks.memory.bench.ts +250 -0
  209. package/src/Webhooks.test.ts +77 -6
  210. package/src/Webhooks.ts +85 -29
  211. package/src/admin/apps/api-keys.test.ts +9 -0
  212. package/src/admin/apps/api-keys.ts +15 -0
  213. package/src/admin/ui/src/lib/query-client.ts +3 -2
  214. package/src/admin/ui/src/routes/api-keys.tsx +31 -9
  215. package/src/admin/ui.generated.ts +69 -69
  216. package/src/analytics/Analytics.test.ts +8 -0
  217. package/src/analytics/Analytics.ts +26 -0
  218. package/src/apps/Zones.test.ts +23 -5
  219. package/src/apps/Zones.ts +31 -10
  220. package/src/apps/composition.test.ts +1 -0
  221. package/src/apps/data/App.test.ts +2 -2
  222. package/src/apps/data/FxOracle.test.ts +18 -0
  223. package/src/apps/data/FxOracle.ts +28 -5
  224. package/src/apps/data/routes/activities.bench.ts +41 -0
  225. package/src/apps/data/routes/activities.test.ts +122 -2
  226. package/src/apps/data/routes/activities.ts +435 -32
  227. package/src/apps/data/routes/balances.bench.ts +21 -0
  228. package/src/apps/data/routes/balances.test.ts +119 -10
  229. package/src/apps/data/routes/balances.ts +164 -97
  230. package/src/apps/data/routes/blocks.bench.ts +34 -0
  231. package/src/apps/data/routes/blocks.test.ts +12 -6
  232. package/src/apps/data/routes/coingecko.bench.ts +53 -0
  233. package/src/apps/data/routes/earn.bench.ts +71 -0
  234. package/src/apps/data/routes/earn.test.ts +197 -0
  235. package/src/apps/data/routes/earn.ts +487 -45
  236. package/src/apps/data/routes/exchanges.bench.ts +85 -0
  237. package/src/apps/data/routes/exchanges.test.ts +7 -6
  238. package/src/apps/data/routes/exchanges.ts +124 -117
  239. package/src/apps/data/routes/fee-amm.bench.ts +13 -0
  240. package/src/apps/data/routes/indexer.bench.ts +8 -0
  241. package/src/apps/data/routes/indexer.test.ts +2 -1
  242. package/src/apps/data/routes/indexer.ts +3 -2
  243. package/src/apps/data/routes/receipts.bench.ts +39 -0
  244. package/src/apps/data/routes/receipts.test.ts +24 -7
  245. package/src/apps/data/routes/receipts.ts +67 -50
  246. package/src/apps/data/routes/rpc.bench.ts +14 -0
  247. package/src/apps/data/routes/rpc.test.ts +27 -0
  248. package/src/apps/data/routes/rpc.ts +5 -1
  249. package/src/apps/data/routes/tokenlist.bench.ts +3 -0
  250. package/src/apps/data/routes/tokens.bench.ts +76 -0
  251. package/src/apps/data/routes/tokens.test.ts +51 -0
  252. package/src/apps/data/routes/transactions.bench.ts +40 -0
  253. package/src/apps/data/routes/transactions.test.ts +42 -0
  254. package/src/apps/data/routes/transactions.ts +44 -39
  255. package/src/apps/data/routes/transfers.bench.ts +28 -0
  256. package/src/apps/data/routes/transfers.test.ts +402 -24
  257. package/src/apps/data/routes/transfers.ts +708 -101
  258. package/src/apps/data/routes/valuation.bench.ts +14 -0
  259. package/src/apps/data/routes/valuation.test.ts +26 -0
  260. package/src/apps/data/routes/valuation.ts +6 -2
  261. package/src/apps/data/routes/verified-tokens.bench.ts +8 -0
  262. package/src/apps/data/routes/webhooks.bench.ts +26 -0
  263. package/src/apps/data/routes/webhooks.scan.test.ts +326 -0
  264. package/src/apps/data/routes/webhooks.test.ts +466 -135
  265. package/src/apps/data/routes/webhooks.ts +820 -429
  266. package/src/apps/data/routes/zones.bench.ts +32 -0
  267. package/src/apps/data/routes/zones.test.ts +1 -1
  268. package/src/apps/data/routes/zones.ts +7 -2
  269. package/src/apps/funding/routes/chains.test.ts +1 -1
  270. package/src/apps/funding/routes/deposit-addresses.test.ts +1 -1
  271. package/src/apps/funding/routes/deposit-addresses.ts +1 -1
  272. package/src/apps/funding/routes/providers.test.ts +1 -1
  273. package/src/apps/funding/routes/quotes.test.ts +2 -2
  274. package/src/apps/funding/routes/transfers.test.ts +3 -6
  275. package/src/apps/funding/routes/transfers.ts +1 -1
  276. package/src/apps/management/routes/billing.ts +1 -1
  277. package/src/apps/management/routes/faucet.ts +2 -2
  278. package/src/apps/management/routes/me.test.ts +31 -0
  279. package/src/apps/mcp.ts +4 -1
  280. package/src/apps/mpp/Mpp.test.ts +13 -1
  281. package/src/apps/mpp/Mpp.ts +8 -0
  282. package/src/apps/mpp/internal/Adapter.ts +10 -2
  283. package/src/db/Db.test.ts +40 -6
  284. package/src/db/Db.ts +114 -19
  285. package/src/db/Schema.ts +32 -0
  286. package/src/db/tables/sponsoredTransactions.ts +9 -2
  287. package/src/db/tables/webhookQueueEvents.ts +285 -0
  288. package/src/db/tables/webhookSubscriptions.ts +131 -4
  289. package/src/internal/Auth.test.ts +61 -50
  290. package/src/internal/Auth.ts +9 -4
  291. package/src/internal/EarnVaults.test.ts +2 -2
  292. package/src/internal/MetricSink.test.ts +575 -0
  293. package/src/internal/MetricSink.ts +325 -10
  294. package/src/internal/RateLimit.test.ts +23 -18
  295. package/src/internal/RateLimit.ts +23 -15
  296. package/src/internal/Schema.ts +1 -1
  297. package/src/internal/Store.test.ts +99 -13
  298. package/src/internal/Store.ts +52 -17
  299. package/src/internal/Tidx.test.ts +4 -4
  300. package/src/internal/Tidx.ts +1 -3
  301. package/src/internal/Value.test.ts +25 -0
  302. package/src/internal/Value.ts +29 -0
  303. package/src/internal/Viem.test.ts +8 -0
  304. package/src/internal/Viem.ts +2 -1
  305. package/src/internal/WebhookDestination.test.ts +20 -0
  306. package/src/internal/WebhookDestination.ts +10 -0
  307. package/src/internal/WebhookTransfer.test.ts +89 -0
  308. package/src/internal/WebhookTransfer.ts +430 -0
  309. package/src/internal/Webhooks.test.ts +616 -0
  310. package/src/internal/Webhooks.ts +412 -24
  311. package/src/internal/funding/Provider.test.ts +48 -8
  312. package/src/internal/funding/Provider.ts +12 -7
  313. package/src/internal/funding/Reconciliation.local.test.ts +3 -1
  314. package/src/internal/funding/Reconciliation.test.ts +1240 -0
  315. package/src/internal/funding/Reconciliation.ts +136 -0
  316. package/src/internal/funding/SourceObservation.test.ts +1 -0
  317. package/src/internal/funding/providers/index.test.ts +73 -3
  318. package/src/internal/funding/providers/relay.test.ts +2 -9
  319. package/src/internal/funding/providers/relay.ts +72 -13
@@ -0,0 +1,14 @@
1
+ import * as QueryBench from '../../../../test/QueryBench.js'
2
+
3
+ const address = {
4
+ 4217: '0xc24357119432595a547cb2c1e41c51d753645519',
5
+ 42431: '0x15496d3123afb2cfadc0ac068eadd11d0f8ae799',
6
+ } as const
7
+
8
+ QueryBench.bench({
9
+ name: 'getAddressValuation',
10
+ path: {
11
+ 4217: `/v1/addresses/${address[4217]}/valuation`,
12
+ 42431: `/v1/addresses/${address[42431]}/valuation`,
13
+ },
14
+ })
@@ -10,6 +10,7 @@ import { Actions } from 'viem/tempo'
10
10
  import * as TestApp from '../../../../test/App.js'
11
11
  import * as Runtime from '../../../../test/runtime.js'
12
12
  import * as TestViem from '../../../../test/Viem.js'
13
+ import * as Store from '../../../internal/Store.js'
13
14
  import * as Tokens from './tokens.js'
14
15
  import * as Valuation from './valuation.js'
15
16
 
@@ -111,6 +112,31 @@ async function chainSum(holder: `0x${string}`, currency: string) {
111
112
  }
112
113
 
113
114
  describe('GET /addresses/:address/valuation', () => {
115
+ test('reuses rates populated by another app', async () => {
116
+ const store = Store.memory()
117
+ const warm = app({ cache: { edge: false, store } })
118
+ const holder = await holderWithBalance(warm)
119
+
120
+ const first = await warm.v1.addresses[':address'].valuation.$get(
121
+ { param: { address: holder }, query: { currency: 'AUD' } },
122
+ TestApp.auth,
123
+ )
124
+ expect(first.status).toBe(200)
125
+
126
+ const reuse = app({
127
+ cache: { edge: false, store },
128
+ fx: { oracle: FxOracle.ecb({ url: 'http://127.0.0.1:1' }) },
129
+ })
130
+ const second = await reuse.v1.addresses[':address'].valuation.$get(
131
+ { param: { address: holder }, query: { currency: 'EUR' } },
132
+ TestApp.auth,
133
+ )
134
+ const body = await TestApp.json(second, Valuation.schema.getAddressValuation.Response)
135
+
136
+ expect(second.status).toBe(200)
137
+ expect(body.currency).toBe('EUR')
138
+ })
139
+
114
140
  test('requires a full pricing timestamp', () => {
115
141
  const pricing = { asOf: '2026-01-01', basis: 'nominal', source: 'fixed' }
116
142
  expect(Valuation.schema.Pricing.safeParse(pricing).success).toBe(false)
@@ -80,7 +80,11 @@ export namespace schema {
80
80
  export const Query = z
81
81
  .strictObject({
82
82
  chainId: Schema.ChainIdQuery,
83
- currency: Schema.Denomination,
83
+ currency: Schema.Denomination.check(
84
+ z.describe(
85
+ 'Currency to denominate the account value in. Defaults to `USD` and must be priced by the configured FX oracle.',
86
+ ),
87
+ ),
84
88
  })
85
89
  .check(z.describe('Query parameters for valuing an account’s verified holdings.'))
86
90
 
@@ -329,7 +333,7 @@ declare namespace getAddressValuation {
329
333
  export function rateSet(c: Context<App.Environment>, oracle: FxOracle.Oracle) {
330
334
  return Timing.time(c, 'valuation_rates', () =>
331
335
  Store.memoize(() => oracle.rates(), {
332
- key: `fx:v1:${oracle.name}`,
336
+ key: `fx:v2:${oracle.cacheKey?.() ?? oracle.name}`,
333
337
  store: c.get('store'),
334
338
  ttl: oracle.ttl,
335
339
  }),
@@ -0,0 +1,8 @@
1
+ import * as QueryBench from '../../../../test/QueryBench.js'
2
+
3
+ QueryBench.bench({ name: 'listVerifiedTokens', path: '/v1/verified-tokens' })
4
+ QueryBench.bench({ name: 'getVerifiedTokenCurrencies', path: '/v1/verified-tokens/currencies' })
5
+ QueryBench.bench({
6
+ name: 'getVerifiedToken',
7
+ path: `/v1/verified-tokens/${QueryBench.pathUsd}`,
8
+ })
@@ -0,0 +1,26 @@
1
+ import * as QueryBench from '../../../../test/QueryBench.js'
2
+
3
+ QueryBench.bench({ name: 'getWebhookEventTypes', path: '/v1/webhooks/event-types' })
4
+
5
+ QueryBench.bench({
6
+ name: 'listWebhooks',
7
+ path: '/v1/webhooks',
8
+ queries: ['include=totalCount'],
9
+ })
10
+
11
+ QueryBench.bench({
12
+ name: 'getWebhook',
13
+ path: {
14
+ 4217: `/v1/webhooks/${QueryBench.webhookId[4217]}`,
15
+ 42431: `/v1/webhooks/${QueryBench.webhookId[42431]}`,
16
+ },
17
+ })
18
+
19
+ QueryBench.bench({
20
+ name: 'listWebhookDeliveries',
21
+ path: {
22
+ 4217: `/v1/webhooks/${QueryBench.webhookId[4217]}/deliveries`,
23
+ 42431: `/v1/webhooks/${QueryBench.webhookId[42431]}/deliveries`,
24
+ },
25
+ queries: ['include=totalCount'],
26
+ })
@@ -0,0 +1,326 @@
1
+ import { Hex } from 'ox'
2
+ import { encodeAbiParameters, encodeEventTopics, getAddress, parseAbiItem } from 'viem'
3
+
4
+ import * as AppWebhooks from './webhooks.js'
5
+
6
+ const sender = `0x${'aa'.repeat(20)}`
7
+ const recipient = `0x${'bb'.repeat(20)}`
8
+ const callTarget = `0x${'cc'.repeat(20)}`
9
+ const token = '0x20c0000000000000000000000000000000000001'
10
+ const transactionHash = `0x${'ef'.repeat(32)}`
11
+ const transferTopic = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
12
+ const customTopic = `0x${'12'.repeat(32)}`
13
+ // 2026-01-01T00:00:00Z in seconds.
14
+ const timestampHex = Hex.fromNumber(1_767_225_600)
15
+
16
+ const addressTopic = (address: string) => `0x${'0'.repeat(24)}${address.slice(2)}`
17
+ const oneToken = `0x${(10n ** 18n).toString(16).padStart(64, '0')}`
18
+
19
+ function transaction(overrides: Record<string, unknown> = {}): Record<string, unknown> {
20
+ return {
21
+ blockHash: `0x${'ab'.repeat(32)}`,
22
+ blockNumber: '0x7b',
23
+ calls: [{ data: '0x', input: '0x', to: callTarget, value: '0x0' }],
24
+ from: sender,
25
+ gas: '0x5208',
26
+ hash: transactionHash,
27
+ input: `0xa9059cbb${'00'.repeat(8)}`,
28
+ nonce: '0x1',
29
+ to: recipient,
30
+ transactionIndex: '0x0',
31
+ type: '0x2',
32
+ value: `0x${(10n ** 18n).toString(16)}`,
33
+ ...overrides,
34
+ }
35
+ }
36
+
37
+ function transferLog(overrides: Record<string, unknown> = {}) {
38
+ return {
39
+ address: token,
40
+ blockNumber: '0x7b',
41
+ data: oneToken,
42
+ logIndex: '0x4',
43
+ topics: [transferTopic, addressTopic(sender), addressTopic(recipient)],
44
+ transactionHash,
45
+ transactionIndex: '0x0',
46
+ ...overrides,
47
+ } as never
48
+ }
49
+
50
+ function customLog(overrides: Record<string, unknown> = {}) {
51
+ return {
52
+ address: callTarget,
53
+ blockNumber: '0x7b',
54
+ data: `0x${'0'.repeat(63)}1`,
55
+ logIndex: '0x7',
56
+ topics: [customTopic, addressTopic(sender)],
57
+ transactionHash,
58
+ transactionIndex: '0x0',
59
+ ...overrides,
60
+ } as never
61
+ }
62
+
63
+ function block(overrides: Record<string, unknown> = {}): Record<string, unknown> {
64
+ return {
65
+ gasLimit: '0x1c9c380',
66
+ gasUsed: '0x5208',
67
+ hash: `0x${'ab'.repeat(32)}`,
68
+ miner: `0x${'12'.repeat(20)}`,
69
+ number: '0x7b',
70
+ parentHash: `0x${'cd'.repeat(32)}`,
71
+ timestamp: timestampHex,
72
+ transactions: [transaction()],
73
+ ...overrides,
74
+ }
75
+ }
76
+
77
+ function subscription(eventType: AppWebhooks.EventType, filters: Record<string, unknown> = {}) {
78
+ return { eventType, filters, id: `wh_${eventType}` }
79
+ }
80
+
81
+ describe('scanBlock', () => {
82
+ test('delivers block, transaction, log, and transfer rows from one block', () => {
83
+ const result = AppWebhooks.scanBlock({
84
+ block: block(),
85
+ logs: [transferLog(), customLog()],
86
+ subscriptions: [
87
+ subscription('block:created'),
88
+ subscription('transaction:included', { from: sender }),
89
+ subscription('log:emitted', { address: callTarget, topic0: customTopic }),
90
+ subscription('token:transfer', { token }),
91
+ ],
92
+ })
93
+
94
+ expect(result.invalid).toEqual([])
95
+ expect(
96
+ result.matches.map(({ blockNumber, logIndex, subscription }) => ({
97
+ blockNumber,
98
+ logIndex,
99
+ subscription: subscription.id,
100
+ })),
101
+ ).toMatchInlineSnapshot(`
102
+ [
103
+ {
104
+ "blockNumber": 123,
105
+ "logIndex": 0,
106
+ "subscription": "wh_block:created",
107
+ },
108
+ {
109
+ "blockNumber": 123,
110
+ "logIndex": 0,
111
+ "subscription": "wh_transaction:included",
112
+ },
113
+ {
114
+ "blockNumber": 123,
115
+ "logIndex": 7,
116
+ "subscription": "wh_log:emitted",
117
+ },
118
+ {
119
+ "blockNumber": 123,
120
+ "logIndex": 4,
121
+ "subscription": "wh_token:transfer",
122
+ },
123
+ ]
124
+ `)
125
+
126
+ const [blockRow, txRow, logRow, transferRow] = result.matches.map((match) => match.data)
127
+ expect(blockRow).toMatchObject({
128
+ gasUsed: 21000,
129
+ miner: `0x${'12'.repeat(20)}`,
130
+ number: 123,
131
+ timestamp: '2026-01-01T00:00:00.000Z',
132
+ transactionCount: 1,
133
+ })
134
+ expect(txRow).toMatchObject({
135
+ hash: transactionHash,
136
+ id: transactionHash,
137
+ timestamp: '2026-01-01T00:00:00.000Z',
138
+ })
139
+ expect(logRow).toMatchObject({
140
+ address: callTarget,
141
+ logIndex: 7,
142
+ topics: [customTopic, addressTopic(sender)],
143
+ })
144
+ expect(transferRow).toMatchObject({
145
+ address: token,
146
+ amount: (10n ** 18n).toString(),
147
+ recipient,
148
+ sender,
149
+ timestamp: '2026-01-01T00:00:00.000Z',
150
+ })
151
+ })
152
+
153
+ test('evaluates every filter operator against transactions', () => {
154
+ const cases: readonly [Record<string, unknown>, boolean][] = [
155
+ [{ from: sender }, true],
156
+ [{ from: { eq: recipient } }, false],
157
+ [{ from: { not: sender } }, false],
158
+ [{ from: { not: recipient } }, true],
159
+ [{ to: { in: [callTarget, recipient] } }, true],
160
+ [{ to: { in: [callTarget] } }, true], // inner call target via includeCalls
161
+ [{ to: { in: [callTarget] }, includeCalls: false }, false],
162
+ [{ value: { gt: '0xde0b6b3a763ffff' } }, true],
163
+ [{ value: { gte: `0x${(10n ** 18n).toString(16)}` } }, true],
164
+ [{ value: { lt: '0x1' } }, false],
165
+ [{ value: { lte: `0x${(10n ** 18n).toString(16)}` } }, true],
166
+ [{ gasLimit: { gte: '0x5208' } }, true],
167
+ [{ nonce: { lte: '0x0' } }, false],
168
+ [{ input: { selector: '0xa9059cbb' } }, true],
169
+ [{ input: { startsWith: '0xa905' } }, true],
170
+ [{ hash: transactionHash }, true],
171
+ [{ calls: { to: callTarget } }, true],
172
+ [{ txType: '0x2' }, true],
173
+ [{ blockNumber: { eq: '0x7b' } }, true],
174
+ [{ timestamp: { gte: timestampHex } }, true],
175
+ ]
176
+
177
+ for (const [filters, expected] of cases) {
178
+ const result = AppWebhooks.scanBlock({
179
+ block: block(),
180
+ logs: [],
181
+ subscriptions: [subscription('transaction:included', filters)],
182
+ })
183
+ expect(result.invalid, JSON.stringify(filters)).toEqual([])
184
+ expect(result.matches.length, JSON.stringify(filters)).toBe(expected ? 1 : 0)
185
+ }
186
+ })
187
+
188
+ test('matches decoded event arguments and raw topics for logs', () => {
189
+ const signature = 'event Transfer(address indexed from, address indexed to, uint256 value)'
190
+ const scan = (filters: Record<string, unknown>) =>
191
+ AppWebhooks.scanBlock({
192
+ block: block(),
193
+ logs: [transferLog()],
194
+ subscriptions: [subscription('log:emitted', filters)],
195
+ })
196
+
197
+ const decoded = scan({ args: { from: sender }, signature })
198
+ expect(decoded.invalid).toEqual([])
199
+ expect(decoded.matches).toHaveLength(1)
200
+ expect(decoded.matches[0]?.data).toMatchObject({
201
+ // Decoding checksums addresses.
202
+ args: { from: getAddress(sender), to: getAddress(recipient), value: (10n ** 18n).toString() },
203
+ event: { signature, topic0: transferTopic },
204
+ })
205
+
206
+ expect(scan({ args: { from: recipient }, signature }).matches).toHaveLength(0)
207
+ expect(scan({ topic0: transferTopic, topic1: addressTopic(sender) }).matches).toHaveLength(1)
208
+ expect(
209
+ scan({
210
+ topic0: transferTopic,
211
+ topic1: addressTopic(sender),
212
+ topic2: { not: addressTopic(recipient) },
213
+ }).matches,
214
+ ).toHaveLength(0)
215
+ expect(scan({ address: token, topic0: transferTopic }).matches).toHaveLength(1)
216
+
217
+ // An args name absent from the ABI invalidates only that subscription.
218
+ const unknownArg = AppWebhooks.scanBlock({
219
+ block: block(),
220
+ logs: [transferLog()],
221
+ subscriptions: [
222
+ subscription('log:emitted', { args: { bogus: sender }, signature }),
223
+ subscription('token:transfer', { token }),
224
+ ],
225
+ })
226
+ expect(unknownArg.invalid).toHaveLength(1)
227
+ expect(unknownArg.matches).toHaveLength(1)
228
+
229
+ // Arg names fail closed even when the block has no decodable logs.
230
+ const unknownArgEmpty = AppWebhooks.scanBlock({
231
+ block: block(),
232
+ logs: [],
233
+ subscriptions: [subscription('log:emitted', { args: { bogus: sender }, signature })],
234
+ })
235
+ expect(unknownArgEmpty.invalid).toHaveLength(1)
236
+
237
+ const invalid = scan({ signature: 'not a signature' })
238
+ expect(invalid.matches).toHaveLength(0)
239
+ expect(invalid.invalid).toHaveLength(1)
240
+ })
241
+
242
+ test('rolls back partial matches when a matcher throws mid-block', () => {
243
+ const signature = 'event Named(string value)'
244
+ const namedTopic = encodeEventTopics({ abi: [parseAbiItem(signature)] })[0]
245
+ const namedLog = (value: string, logIndex: string) =>
246
+ ({
247
+ address: callTarget,
248
+ blockNumber: '0x7b',
249
+ data: encodeAbiParameters([{ name: 'value', type: 'string' }], [value]),
250
+ logIndex,
251
+ topics: [namedTopic],
252
+ transactionHash,
253
+ transactionIndex: '0x0',
254
+ }) as never
255
+
256
+ // The first log matches the numeric comparison; the second's nonnumeric
257
+ // string makes the matcher throw after that match was recorded.
258
+ const result = AppWebhooks.scanBlock({
259
+ block: block(),
260
+ logs: [namedLog('0x2', '0x1'), namedLog('not numeric', '0x2'), transferLog()],
261
+ subscriptions: [
262
+ subscription('log:emitted', { args: { value: { gt: '0x1' } }, signature }),
263
+ subscription('token:transfer', { token }),
264
+ ],
265
+ })
266
+ expect(result.invalid).toHaveLength(1)
267
+ expect(result.invalid[0]?.subscription.id).toBe('wh_log:emitted')
268
+ expect(result.matches.map((match) => match.subscription.id)).toEqual(['wh_token:transfer'])
269
+ })
270
+
271
+ test('matches transfers on each side and ignores non-transfer logs', () => {
272
+ const scan = (filters: Record<string, unknown>, logs = [transferLog()]) =>
273
+ AppWebhooks.scanBlock({
274
+ block: block(),
275
+ logs,
276
+ subscriptions: [subscription('token:transfer', filters)],
277
+ }).matches
278
+
279
+ expect(scan({ sender })).toHaveLength(1)
280
+ expect(scan({ recipient })).toHaveLength(1)
281
+ expect(scan({ address: sender })).toHaveLength(1)
282
+ expect(scan({ address: callTarget })).toHaveLength(0)
283
+ expect(scan({ token: '0x20c0000000000000000000000000000000000002' })).toHaveLength(0)
284
+ // Outside the TIP-20 range and malformed topics both fail decoding.
285
+ expect(scan({}, [transferLog({ address: callTarget })])).toHaveLength(0)
286
+ expect(scan({}, [transferLog({ topics: [transferTopic] })])).toHaveLength(0)
287
+ })
288
+
289
+ test('fails closed on invalid filters and unparsable blocks', () => {
290
+ const mixed = AppWebhooks.scanBlock({
291
+ block: block(),
292
+ logs: [transferLog()],
293
+ subscriptions: [
294
+ subscription('token:transfer', { token: 123 }),
295
+ subscription('token:transfer', { token }),
296
+ ],
297
+ })
298
+ expect(mixed.invalid).toHaveLength(1)
299
+ expect(mixed.invalid[0]?.subscription.filters).toEqual({ token: 123 })
300
+ expect(mixed.matches).toHaveLength(1)
301
+
302
+ const unparsable = AppWebhooks.scanBlock({
303
+ block: block({ number: undefined }),
304
+ logs: [transferLog()],
305
+ subscriptions: [subscription('block:created'), subscription('token:transfer', {})],
306
+ })
307
+ expect(unparsable.matches).toEqual([])
308
+ })
309
+
310
+ test('matches blocks on header fields and never on proposer', () => {
311
+ const scan = (filters: Record<string, unknown>) =>
312
+ AppWebhooks.scanBlock({
313
+ block: block(),
314
+ logs: [],
315
+ subscriptions: [subscription('block:created', filters)],
316
+ }).matches
317
+
318
+ expect(scan({})).toHaveLength(1)
319
+ expect(scan({ number: { eq: '0x7b' } })).toHaveLength(1)
320
+ expect(scan({ miner: `0x${'12'.repeat(20)}` })).toHaveLength(1)
321
+ expect(scan({ gasUsed: { gt: '0x5207' } })).toHaveLength(1)
322
+ expect(scan({ gasUsed: { lt: '0x5208' } })).toHaveLength(0)
323
+ expect(scan({ timestamp: { gte: timestampHex } })).toHaveLength(1)
324
+ expect(scan({ proposer: `0x${'12'.repeat(20)}` })).toHaveLength(0)
325
+ })
326
+ })