tapimo 0.0.0 → 0.0.2

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 (422) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +119 -0
  3. package/dist/ApiKey.d.ts +65 -0
  4. package/dist/ApiKey.d.ts.map +1 -0
  5. package/dist/ApiKey.js +105 -0
  6. package/dist/ApiKey.js.map +1 -0
  7. package/dist/ApiKeySource.d.ts +166 -0
  8. package/dist/ApiKeySource.d.ts.map +1 -0
  9. package/dist/ApiKeySource.js +234 -0
  10. package/dist/ApiKeySource.js.map +1 -0
  11. package/dist/App.d.ts +19317 -0
  12. package/dist/App.d.ts.map +1 -0
  13. package/dist/App.js +612 -0
  14. package/dist/App.js.map +1 -0
  15. package/dist/Assets.d.ts +44 -0
  16. package/dist/Assets.d.ts.map +1 -0
  17. package/dist/Assets.js +23 -0
  18. package/dist/Assets.js.map +1 -0
  19. package/dist/Auth.d.ts +2 -0
  20. package/dist/Auth.d.ts.map +1 -0
  21. package/dist/Auth.js +2 -0
  22. package/dist/Auth.js.map +1 -0
  23. package/dist/Cli.d.ts +21 -0
  24. package/dist/Cli.d.ts.map +1 -0
  25. package/dist/Cli.js +13 -0
  26. package/dist/Cli.js.map +1 -0
  27. package/dist/Client.d.ts +20 -0
  28. package/dist/Client.d.ts.map +1 -0
  29. package/dist/Client.js +24 -0
  30. package/dist/Client.js.map +1 -0
  31. package/dist/Metrics.d.ts +70 -0
  32. package/dist/Metrics.d.ts.map +1 -0
  33. package/dist/Metrics.js +91 -0
  34. package/dist/Metrics.js.map +1 -0
  35. package/dist/Scope.d.ts +35 -0
  36. package/dist/Scope.d.ts.map +1 -0
  37. package/dist/Scope.js +38 -0
  38. package/dist/Scope.js.map +1 -0
  39. package/dist/Webhooks.d.ts +79 -0
  40. package/dist/Webhooks.d.ts.map +1 -0
  41. package/dist/Webhooks.js +107 -0
  42. package/dist/Webhooks.js.map +1 -0
  43. package/dist/admin/Access.d.ts +41 -0
  44. package/dist/admin/Access.d.ts.map +1 -0
  45. package/dist/admin/Access.js +75 -0
  46. package/dist/admin/Access.js.map +1 -0
  47. package/dist/admin/App.d.ts +1221 -0
  48. package/dist/admin/App.d.ts.map +1 -0
  49. package/dist/admin/App.js +113 -0
  50. package/dist/admin/App.js.map +1 -0
  51. package/dist/admin/apps/api-keys.d.ts +209 -0
  52. package/dist/admin/apps/api-keys.d.ts.map +1 -0
  53. package/dist/admin/apps/api-keys.js +129 -0
  54. package/dist/admin/apps/api-keys.js.map +1 -0
  55. package/dist/admin/apps/scopes.d.ts +46 -0
  56. package/dist/admin/apps/scopes.d.ts.map +1 -0
  57. package/dist/admin/apps/scopes.js +41 -0
  58. package/dist/admin/apps/scopes.js.map +1 -0
  59. package/dist/admin/apps/verified-tokens.d.ts +1206 -0
  60. package/dist/admin/apps/verified-tokens.d.ts.map +1 -0
  61. package/dist/admin/apps/verified-tokens.js +485 -0
  62. package/dist/admin/apps/verified-tokens.js.map +1 -0
  63. package/dist/admin/index.d.ts +3 -0
  64. package/dist/admin/index.d.ts.map +1 -0
  65. package/dist/admin/index.js +3 -0
  66. package/dist/admin/index.js.map +1 -0
  67. package/dist/admin/ui.generated.d.ts +11 -0
  68. package/dist/admin/ui.generated.d.ts.map +1 -0
  69. package/dist/admin/ui.generated.js +43 -0
  70. package/dist/admin/ui.generated.js.map +1 -0
  71. package/dist/apps/activities.d.ts +18725 -0
  72. package/dist/apps/activities.d.ts.map +1 -0
  73. package/dist/apps/activities.js +3138 -0
  74. package/dist/apps/activities.js.map +1 -0
  75. package/dist/apps/addresses.d.ts +3662 -0
  76. package/dist/apps/addresses.d.ts.map +1 -0
  77. package/dist/apps/addresses.js +22 -0
  78. package/dist/apps/addresses.js.map +1 -0
  79. package/dist/apps/balances.d.ts +342 -0
  80. package/dist/apps/balances.d.ts.map +1 -0
  81. package/dist/apps/balances.js +393 -0
  82. package/dist/apps/balances.js.map +1 -0
  83. package/dist/apps/blocks.d.ts +931 -0
  84. package/dist/apps/blocks.d.ts.map +1 -0
  85. package/dist/apps/blocks.js +689 -0
  86. package/dist/apps/blocks.js.map +1 -0
  87. package/dist/apps/coingecko.d.ts +1053 -0
  88. package/dist/apps/coingecko.d.ts.map +1 -0
  89. package/dist/apps/coingecko.js +1015 -0
  90. package/dist/apps/coingecko.js.map +1 -0
  91. package/dist/apps/exchanges.d.ts +3000 -0
  92. package/dist/apps/exchanges.d.ts.map +1 -0
  93. package/dist/apps/exchanges.js +3570 -0
  94. package/dist/apps/exchanges.js.map +1 -0
  95. package/dist/apps/fee-amm.d.ts +640 -0
  96. package/dist/apps/fee-amm.d.ts.map +1 -0
  97. package/dist/apps/fee-amm.js +815 -0
  98. package/dist/apps/fee-amm.js.map +1 -0
  99. package/dist/apps/indexer.d.ts +116 -0
  100. package/dist/apps/indexer.d.ts.map +1 -0
  101. package/dist/apps/indexer.js +270 -0
  102. package/dist/apps/indexer.js.map +1 -0
  103. package/dist/apps/receipts.d.ts +1438 -0
  104. package/dist/apps/receipts.d.ts.map +1 -0
  105. package/dist/apps/receipts.js +874 -0
  106. package/dist/apps/receipts.js.map +1 -0
  107. package/dist/apps/rpc.d.ts +173 -0
  108. package/dist/apps/rpc.d.ts.map +1 -0
  109. package/dist/apps/rpc.js +191 -0
  110. package/dist/apps/rpc.js.map +1 -0
  111. package/dist/apps/tokenlist.d.ts +192 -0
  112. package/dist/apps/tokenlist.d.ts.map +1 -0
  113. package/dist/apps/tokenlist.js +153 -0
  114. package/dist/apps/tokenlist.js.map +1 -0
  115. package/dist/apps/tokens.d.ts +2246 -0
  116. package/dist/apps/tokens.d.ts.map +1 -0
  117. package/dist/apps/tokens.js +1789 -0
  118. package/dist/apps/tokens.js.map +1 -0
  119. package/dist/apps/transactions.d.ts +2327 -0
  120. package/dist/apps/transactions.d.ts.map +1 -0
  121. package/dist/apps/transactions.js +975 -0
  122. package/dist/apps/transactions.js.map +1 -0
  123. package/dist/apps/transfers.d.ts +589 -0
  124. package/dist/apps/transfers.d.ts.map +1 -0
  125. package/dist/apps/transfers.js +769 -0
  126. package/dist/apps/transfers.js.map +1 -0
  127. package/dist/apps/verified-tokens.d.ts +544 -0
  128. package/dist/apps/verified-tokens.d.ts.map +1 -0
  129. package/dist/apps/verified-tokens.js +234 -0
  130. package/dist/apps/verified-tokens.js.map +1 -0
  131. package/dist/apps/webhooks.d.ts +7684 -0
  132. package/dist/apps/webhooks.d.ts.map +1 -0
  133. package/dist/apps/webhooks.js +2112 -0
  134. package/dist/apps/webhooks.js.map +1 -0
  135. package/dist/cloudflare.d.ts +32 -0
  136. package/dist/cloudflare.d.ts.map +1 -0
  137. package/dist/cloudflare.js +45 -0
  138. package/dist/cloudflare.js.map +1 -0
  139. package/dist/index.d.ts +16 -0
  140. package/dist/index.d.ts.map +1 -0
  141. package/dist/index.js +16 -0
  142. package/dist/index.js.map +1 -0
  143. package/dist/internal/Auth.d.ts +298 -0
  144. package/dist/internal/Auth.d.ts.map +1 -0
  145. package/dist/internal/Auth.js +618 -0
  146. package/dist/internal/Auth.js.map +1 -0
  147. package/dist/internal/Cache.d.ts +74 -0
  148. package/dist/internal/Cache.d.ts.map +1 -0
  149. package/dist/internal/Cache.js +215 -0
  150. package/dist/internal/Cache.js.map +1 -0
  151. package/dist/internal/Cursor.d.ts +79 -0
  152. package/dist/internal/Cursor.d.ts.map +1 -0
  153. package/dist/internal/Cursor.js +116 -0
  154. package/dist/internal/Cursor.js.map +1 -0
  155. package/dist/internal/EdgeCache.d.ts +59 -0
  156. package/dist/internal/EdgeCache.d.ts.map +1 -0
  157. package/dist/internal/EdgeCache.js +174 -0
  158. package/dist/internal/EdgeCache.js.map +1 -0
  159. package/dist/internal/Log.d.ts +104 -0
  160. package/dist/internal/Log.d.ts.map +1 -0
  161. package/dist/internal/Log.js +118 -0
  162. package/dist/internal/Log.js.map +1 -0
  163. package/dist/internal/MetricSink.d.ts +65 -0
  164. package/dist/internal/MetricSink.d.ts.map +1 -0
  165. package/dist/internal/MetricSink.js +146 -0
  166. package/dist/internal/MetricSink.js.map +1 -0
  167. package/dist/internal/Mpp.d.ts +71 -0
  168. package/dist/internal/Mpp.d.ts.map +1 -0
  169. package/dist/internal/Mpp.js +105 -0
  170. package/dist/internal/Mpp.js.map +1 -0
  171. package/dist/internal/OpenApi.d.ts +313 -0
  172. package/dist/internal/OpenApi.d.ts.map +1 -0
  173. package/dist/internal/OpenApi.fonts.d.ts +2 -0
  174. package/dist/internal/OpenApi.fonts.d.ts.map +1 -0
  175. package/dist/internal/OpenApi.fonts.js +39 -0
  176. package/dist/internal/OpenApi.fonts.js.map +1 -0
  177. package/dist/internal/OpenApi.js +335 -0
  178. package/dist/internal/OpenApi.js.map +1 -0
  179. package/dist/internal/OpenApi.logo.d.ts +6 -0
  180. package/dist/internal/OpenApi.logo.d.ts.map +1 -0
  181. package/dist/internal/OpenApi.logo.js +19 -0
  182. package/dist/internal/OpenApi.logo.js.map +1 -0
  183. package/dist/internal/Path.d.ts +5 -0
  184. package/dist/internal/Path.d.ts.map +1 -0
  185. package/dist/internal/Path.js +16 -0
  186. package/dist/internal/Path.js.map +1 -0
  187. package/dist/internal/RateLimit.d.ts +60 -0
  188. package/dist/internal/RateLimit.d.ts.map +1 -0
  189. package/dist/internal/RateLimit.js +48 -0
  190. package/dist/internal/RateLimit.js.map +1 -0
  191. package/dist/internal/Response.d.ts +132 -0
  192. package/dist/internal/Response.d.ts.map +1 -0
  193. package/dist/internal/Response.js +72 -0
  194. package/dist/internal/Response.js.map +1 -0
  195. package/dist/internal/Schema.d.ts +257 -0
  196. package/dist/internal/Schema.d.ts.map +1 -0
  197. package/dist/internal/Schema.js +360 -0
  198. package/dist/internal/Schema.js.map +1 -0
  199. package/dist/internal/Store.d.ts +204 -0
  200. package/dist/internal/Store.d.ts.map +1 -0
  201. package/dist/internal/Store.js +291 -0
  202. package/dist/internal/Store.js.map +1 -0
  203. package/dist/internal/Tidx.d.ts +120 -0
  204. package/dist/internal/Tidx.d.ts.map +1 -0
  205. package/dist/internal/Tidx.js +156 -0
  206. package/dist/internal/Tidx.js.map +1 -0
  207. package/dist/internal/Timing.d.ts +22 -0
  208. package/dist/internal/Timing.d.ts.map +1 -0
  209. package/dist/internal/Timing.js +32 -0
  210. package/dist/internal/Timing.js.map +1 -0
  211. package/dist/internal/Ttl.d.ts +25 -0
  212. package/dist/internal/Ttl.d.ts.map +1 -0
  213. package/dist/internal/Ttl.js +49 -0
  214. package/dist/internal/Ttl.js.map +1 -0
  215. package/dist/internal/Value.d.ts +47 -0
  216. package/dist/internal/Value.d.ts.map +1 -0
  217. package/dist/internal/Value.js +122 -0
  218. package/dist/internal/Value.js.map +1 -0
  219. package/dist/internal/VerifiedTokens.d.ts +228 -0
  220. package/dist/internal/VerifiedTokens.d.ts.map +1 -0
  221. package/dist/internal/VerifiedTokens.js +450 -0
  222. package/dist/internal/VerifiedTokens.js.map +1 -0
  223. package/dist/internal/Viem.d.ts +2082 -0
  224. package/dist/internal/Viem.d.ts.map +1 -0
  225. package/dist/internal/Viem.js +63 -0
  226. package/dist/internal/Viem.js.map +1 -0
  227. package/dist/internal/WebhookDestination.d.ts +198 -0
  228. package/dist/internal/WebhookDestination.d.ts.map +1 -0
  229. package/dist/internal/WebhookDestination.js +170 -0
  230. package/dist/internal/WebhookDestination.js.map +1 -0
  231. package/dist/internal/Webhooks.d.ts +433 -0
  232. package/dist/internal/Webhooks.d.ts.map +1 -0
  233. package/dist/internal/Webhooks.js +489 -0
  234. package/dist/internal/Webhooks.js.map +1 -0
  235. package/dist/internal/webhookDestinations/betterStack.d.ts +26 -0
  236. package/dist/internal/webhookDestinations/betterStack.d.ts.map +1 -0
  237. package/dist/internal/webhookDestinations/betterStack.js +76 -0
  238. package/dist/internal/webhookDestinations/betterStack.js.map +1 -0
  239. package/dist/internal/webhookDestinations/slack.d.ts +59 -0
  240. package/dist/internal/webhookDestinations/slack.d.ts.map +1 -0
  241. package/dist/internal/webhookDestinations/slack.js +422 -0
  242. package/dist/internal/webhookDestinations/slack.js.map +1 -0
  243. package/dist/internal/webhookDestinations/url.d.ts +17 -0
  244. package/dist/internal/webhookDestinations/url.d.ts.map +1 -0
  245. package/dist/internal/webhookDestinations/url.js +130 -0
  246. package/dist/internal/webhookDestinations/url.js.map +1 -0
  247. package/dist/openrpc.json +5302 -0
  248. package/package.json +46 -4
  249. package/seeds/31318/icons/0x20c0000000000000000000000000000000000000.svg +1 -0
  250. package/seeds/31318/icons/0x20c0000000000000000000000000000000000001.svg +1 -0
  251. package/seeds/31318/icons/0x20c0000000000000000000000000000000000002.svg +1 -0
  252. package/seeds/31318/icons/0x20c0000000000000000000000000000000000003.svg +1 -0
  253. package/seeds/31318/verified-tokens.json +30 -0
  254. package/seeds/4217/icons/0x20c0000000000000000000000000000000000000.svg +25 -0
  255. package/seeds/4217/icons/0x20c000000000000000000000048c8f36df1c9a4a.svg +1 -0
  256. package/seeds/4217/icons/0x20c0000000000000000000000520792dcccccccc.svg +1 -0
  257. package/seeds/4217/icons/0x20c0000000000000000000000a6da882d075a4c3.svg +4 -0
  258. package/seeds/4217/icons/0x20c000000000000000000000111111111e910f0f.svg +30 -0
  259. package/seeds/4217/icons/0x20c00000000000000000000014f22ca97301eb73.svg +1 -0
  260. package/seeds/4217/icons/0x20c0000000000000000000001621e21f71cf12fb.svg +1 -0
  261. package/seeds/4217/icons/0x20c0000000000000000000002f52d5cc21a3207b.svg +1 -0
  262. package/seeds/4217/icons/0x20c0000000000000000000003158081efd85bfc2.svg +11 -0
  263. package/seeds/4217/icons/0x20c0000000000000000000003554d28269e0f3c2.svg +1 -0
  264. package/seeds/4217/icons/0x20c000000000000000000000383a23bacb546ab9.svg +1 -0
  265. package/seeds/4217/icons/0x20c00000000000000000000042109aef2f8b28e1.png +0 -0
  266. package/seeds/4217/icons/0x20c0000000000000000000005c0bac7cef389a11.svg +1 -0
  267. package/seeds/4217/icons/0x20c0000000000000000000006fd9a167923ba194.png +0 -0
  268. package/seeds/4217/icons/0x20c0000000000000000000007f7ba549dd0251b9.svg +1 -0
  269. package/seeds/4217/icons/0x20c0000000000000000000008191667423f70e67.svg +5 -0
  270. package/seeds/4217/icons/0x20c0000000000000000000008ee4fcff88888888.svg +1 -0
  271. package/seeds/4217/icons/0x20c0000000000000000000009a4a4b17e0dc6651.svg +1 -0
  272. package/seeds/4217/icons/0x20c000000000000000000000ab02d39df30bd17e.svg +1 -0
  273. package/seeds/4217/icons/0x20c000000000000000000000ae247a1130450f09.svg +1 -0
  274. package/seeds/4217/icons/0x20c000000000000000000000aeed2ec36a54d0e5.svg +1 -0
  275. package/seeds/4217/icons/0x20c000000000000000000000b9537d11c60e8b50.svg +1 -0
  276. package/seeds/4217/icons/0x20c000000000000000000000bd95bfb69fbe6ce3.svg +1 -0
  277. package/seeds/4217/icons/0x20c000000000000000000000c412ec89d0c08be5.svg +1 -0
  278. package/seeds/4217/icons/0x20c000000000000000000000f047dd7018e50367.png +0 -0
  279. package/seeds/4217/verified-tokens.json +177 -0
  280. package/seeds/42431/icons/0x20c0000000000000000000000000000000000000.svg +25 -0
  281. package/seeds/42431/icons/0x20c0000000000000000000000000000000000001.svg +1 -0
  282. package/seeds/42431/icons/0x20c0000000000000000000000000000000000002.svg +1 -0
  283. package/seeds/42431/icons/0x20c0000000000000000000000000000000000003.svg +1 -0
  284. package/seeds/42431/icons/0x20c0000000000000000000009e8d7eb59b783726.svg +1 -0
  285. package/seeds/42431/icons/0x20c000000000000000000000d72572838bbee59c.svg +1 -0
  286. package/seeds/42431/verified-tokens.json +44 -0
  287. package/seeds/openrpc/README.md +27 -0
  288. package/seeds/openrpc/execution-apis.json +8989 -0
  289. package/src/ApiKey.test.ts +91 -0
  290. package/src/ApiKey.ts +122 -0
  291. package/src/ApiKeySource.test-d.ts +11 -0
  292. package/src/ApiKeySource.test.ts +295 -0
  293. package/src/ApiKeySource.ts +303 -0
  294. package/src/App.test-d.ts +38 -0
  295. package/src/App.test.ts +65927 -0
  296. package/src/App.ts +855 -0
  297. package/src/Assets.ts +58 -0
  298. package/src/Auth.ts +1 -0
  299. package/src/Cli.test.ts +81 -0
  300. package/src/Cli.ts +38 -0
  301. package/src/Client.test-d.ts +1438 -0
  302. package/src/Client.test.ts +134 -0
  303. package/src/Client.ts +45 -0
  304. package/src/Metrics.ts +154 -0
  305. package/src/Scope.ts +50 -0
  306. package/src/Webhooks.test.ts +49 -0
  307. package/src/Webhooks.ts +172 -0
  308. package/src/admin/Access.test.ts +130 -0
  309. package/src/admin/Access.ts +87 -0
  310. package/src/admin/App.test.ts +225 -0
  311. package/src/admin/App.ts +216 -0
  312. package/src/admin/apps/api-keys.test.ts +156 -0
  313. package/src/admin/apps/api-keys.ts +157 -0
  314. package/src/admin/apps/scopes.test.ts +52 -0
  315. package/src/admin/apps/scopes.ts +47 -0
  316. package/src/admin/apps/verified-tokens.test.ts +415 -0
  317. package/src/admin/apps/verified-tokens.ts +566 -0
  318. package/src/admin/index.ts +2 -0
  319. package/src/admin/ui/plugin.ts +135 -0
  320. package/src/admin/ui/src/components/Checkbox.tsx +22 -0
  321. package/src/admin/ui/src/components/Dialog.tsx +32 -0
  322. package/src/admin/ui/src/components/MobileProfile.tsx +31 -0
  323. package/src/admin/ui/src/components/Page.tsx +53 -0
  324. package/src/admin/ui/src/components/TempoLogo.tsx +37 -0
  325. package/src/admin/ui/src/fonts/Pilat-Demi.woff2 +0 -0
  326. package/src/admin/ui/src/fonts/Pilat-Regular.woff2 +0 -0
  327. package/src/admin/ui/src/lib/api-keys.ts +63 -0
  328. package/src/admin/ui/src/lib/client.ts +15 -0
  329. package/src/admin/ui/src/lib/config.ts +57 -0
  330. package/src/admin/ui/src/lib/query-client.ts +26 -0
  331. package/src/admin/ui/src/lib/scopes.ts +25 -0
  332. package/src/admin/ui/src/lib/verified-tokens.ts +89 -0
  333. package/src/admin/ui/src/lib/wagmi.ts +18 -0
  334. package/src/admin/ui/src/routeTree.gen.ts +104 -0
  335. package/src/admin/ui/src/router.tsx +25 -0
  336. package/src/admin/ui/src/routes/__root.tsx +219 -0
  337. package/src/admin/ui/src/routes/api-keys.tsx +329 -0
  338. package/src/admin/ui/src/routes/index.tsx +7 -0
  339. package/src/admin/ui/src/routes/verified-tokens.tsx +331 -0
  340. package/src/admin/ui/src/styles.css +20 -0
  341. package/src/admin/ui/tsconfig.json +16 -0
  342. package/src/admin/ui/vite.config.ts +29 -0
  343. package/src/admin/ui.generated.ts +60 -0
  344. package/src/apps/activities.test.ts +1020 -0
  345. package/src/apps/activities.ts +3934 -0
  346. package/src/apps/addresses.ts +24 -0
  347. package/src/apps/balances.test.ts +274 -0
  348. package/src/apps/balances.ts +533 -0
  349. package/src/apps/blocks.test.ts +385 -0
  350. package/src/apps/blocks.ts +896 -0
  351. package/src/apps/coingecko.test.ts +218 -0
  352. package/src/apps/coingecko.ts +1383 -0
  353. package/src/apps/exchanges.test.ts +958 -0
  354. package/src/apps/exchanges.ts +4773 -0
  355. package/src/apps/fee-amm.test.ts +321 -0
  356. package/src/apps/fee-amm.ts +1121 -0
  357. package/src/apps/indexer.test.ts +153 -0
  358. package/src/apps/indexer.ts +330 -0
  359. package/src/apps/receipts.test.ts +285 -0
  360. package/src/apps/receipts.ts +1193 -0
  361. package/src/apps/rpc.test.ts +302 -0
  362. package/src/apps/rpc.ts +222 -0
  363. package/src/apps/tokenlist.test.ts +80 -0
  364. package/src/apps/tokenlist.ts +207 -0
  365. package/src/apps/tokens.test.ts +1062 -0
  366. package/src/apps/tokens.ts +2292 -0
  367. package/src/apps/transactions.test.ts +408 -0
  368. package/src/apps/transactions.ts +1363 -0
  369. package/src/apps/transfers.test.ts +371 -0
  370. package/src/apps/transfers.ts +1116 -0
  371. package/src/apps/verified-tokens.test.ts +114 -0
  372. package/src/apps/verified-tokens.ts +295 -0
  373. package/src/apps/webhooks.test.ts +1937 -0
  374. package/src/apps/webhooks.ts +2926 -0
  375. package/src/cloudflare.ts +55 -0
  376. package/src/index.ts +15 -0
  377. package/src/internal/Auth.test.ts +980 -0
  378. package/src/internal/Auth.ts +1012 -0
  379. package/src/internal/Cache.test.ts +240 -0
  380. package/src/internal/Cache.ts +262 -0
  381. package/src/internal/Cursor.test.ts +100 -0
  382. package/src/internal/Cursor.ts +149 -0
  383. package/src/internal/EdgeCache.test.ts +234 -0
  384. package/src/internal/EdgeCache.ts +224 -0
  385. package/src/internal/Errors.ts +14 -0
  386. package/src/internal/Log.test.ts +244 -0
  387. package/src/internal/Log.ts +216 -0
  388. package/src/internal/MetricSink.ts +220 -0
  389. package/src/internal/Mpp.test.ts +64 -0
  390. package/src/internal/Mpp.ts +141 -0
  391. package/src/internal/OpenApi.fonts.ts +38 -0
  392. package/src/internal/OpenApi.logo.ts +18 -0
  393. package/src/internal/OpenApi.ts +488 -0
  394. package/src/internal/Path.test.ts +14 -0
  395. package/src/internal/Path.ts +15 -0
  396. package/src/internal/RateLimit.test.ts +93 -0
  397. package/src/internal/RateLimit.ts +93 -0
  398. package/src/internal/Response.ts +131 -0
  399. package/src/internal/Schema.test.ts +34 -0
  400. package/src/internal/Schema.ts +523 -0
  401. package/src/internal/Store.test.ts +545 -0
  402. package/src/internal/Store.ts +526 -0
  403. package/src/internal/Tidx.local.test.ts +33 -0
  404. package/src/internal/Tidx.test.ts +294 -0
  405. package/src/internal/Tidx.ts +238 -0
  406. package/src/internal/Timing.test.ts +22 -0
  407. package/src/internal/Timing.ts +62 -0
  408. package/src/internal/Ttl.test.ts +18 -0
  409. package/src/internal/Ttl.ts +59 -0
  410. package/src/internal/Value.ts +114 -0
  411. package/src/internal/VerifiedTokens.test.ts +333 -0
  412. package/src/internal/VerifiedTokens.ts +645 -0
  413. package/src/internal/Viem.test.ts +115 -0
  414. package/src/internal/Viem.ts +165 -0
  415. package/src/internal/WebhookDestination.test.ts +648 -0
  416. package/src/internal/WebhookDestination.ts +331 -0
  417. package/src/internal/Webhooks.test.ts +668 -0
  418. package/src/internal/Webhooks.ts +940 -0
  419. package/src/internal/webhookDestinations/betterStack.ts +91 -0
  420. package/src/internal/webhookDestinations/slack.ts +496 -0
  421. package/src/internal/webhookDestinations/url.ts +128 -0
  422. package/src/openrpc.json +5302 -0
package/src/App.ts ADDED
@@ -0,0 +1,855 @@
1
+ import type { Context, MiddlewareHandler } from 'hono'
2
+ import { Hono } from 'hono'
3
+ import { compress as hono_compress } from 'hono/compress'
4
+ import { cors as hono_cors } from 'hono/cors'
5
+ import { HTTPException } from 'hono/http-exception'
6
+ import { requestId, type RequestIdVariables } from 'hono/request-id'
7
+ import { generateSpecs } from 'hono-openapi'
8
+ import { Handler } from 'vocs/server'
9
+ import * as z from 'zod/mini'
10
+
11
+ import * as ApiKey from './ApiKey.js'
12
+ import * as Assets from './Assets.js'
13
+ import * as Auth from './internal/Auth.js'
14
+ import * as EdgeCache from './internal/EdgeCache.js'
15
+ import * as Log from './internal/Log.js'
16
+ import * as MetricSink from './internal/MetricSink.js'
17
+ import type * as Metrics from './Metrics.js'
18
+ import * as OpenApi from './internal/OpenApi.js'
19
+ import * as Store from './internal/Store.js'
20
+ import * as Path from './internal/Path.js'
21
+ import * as Response from './internal/Response.js'
22
+ import * as Schema from './internal/Schema.js'
23
+ import * as Tidx from './internal/Tidx.js'
24
+ import * as Timing from './internal/Timing.js'
25
+ import * as Viem from './internal/Viem.js'
26
+
27
+ import openrpcSpec from './openrpc.json' with { type: 'json' }
28
+
29
+ import { transactions as transactionActivities } from './apps/activities.js'
30
+ import { addresses } from './apps/addresses.js'
31
+ import { blocks, blocksByTimestampCompat } from './apps/blocks.js'
32
+ import { coingecko } from './apps/coingecko.js'
33
+ import { exchanges } from './apps/exchanges.js'
34
+ import { feeAmm } from './apps/fee-amm.js'
35
+ import { indexer } from './apps/indexer.js'
36
+ import { receipts } from './apps/receipts.js'
37
+ import { rpc as rpcApp } from './apps/rpc.js'
38
+ import { tokens } from './apps/tokens.js'
39
+ import { transactions } from './apps/transactions.js'
40
+ import { transfers } from './apps/transfers.js'
41
+ import { verifiedTokens } from './apps/verified-tokens.js'
42
+ import { tokenlist } from './apps/tokenlist.js'
43
+ import { schema as webhookSchema, webhooks } from './apps/webhooks.js'
44
+
45
+ /** Hono environment variables set by the Tempo API app. */
46
+ export type Environment = {
47
+ Variables: Auth.Variables &
48
+ RequestIdVariables & {
49
+ /** Base path for this API app. */
50
+ basePath: string
51
+ /** Default Tempo chain id for this API app. */
52
+ chainId: Viem.ChainId
53
+ /** Gets one static API asset. */
54
+ getAsset: GetAsset
55
+ /** Gets a Tempo RPC client for a chain id. */
56
+ getClient: Viem.GetClient
57
+ /** Gets a TIDX query client for a chain id. */
58
+ getTidx: Tidx.GetClient
59
+ /** Configured RPC client options used by the raw RPC passthrough. */
60
+ rpc: Viem.getClient.Rpc | undefined
61
+ /** Cache store: handler `Store.memoize` and the edge response cache. */
62
+ store: Store.Store
63
+ /** Chain ids this deployment serves; a request outside this set is rejected with a clear 400. */
64
+ supportedChainIds: ReadonlySet<number>
65
+ /** Configured TIDX client options used by the raw indexer passthrough. */
66
+ tidx: Tidx.getClient.Tidx | undefined
67
+ /** Verified-token feature configuration (state store + refresh window), or undefined when verified tokens are bundle-backed. */
68
+ verifiedTokens: { refreshMs?: number | undefined; store: Store.State } | undefined
69
+ /** Webhook feature configuration (state store, etc.), or undefined when webhooks are disabled. */
70
+ webhook: { maxPerOwner?: number | undefined; store: Store.State } | undefined
71
+ } & EdgeCache.Variables &
72
+ Log.Variables &
73
+ Timing.Variables
74
+ }
75
+
76
+ /**
77
+ * Tempo API Hono app with shared API dependencies. Inferred from `create`.
78
+ * The `path` generic is pinned to `'/'` (its default) so the resulting Hono
79
+ * `BasePath` is a concrete literal; without the explicit type argument
80
+ * `ReturnType<typeof create>` would resolve `path` to its constraint
81
+ * (`string`), and Hono's `MergePath<string, '/foo'>` would surface every
82
+ * endpoint key as `` `… ${string}/foo` `` — which can be satisfied but never
83
+ * autocompleted.
84
+ */
85
+ export type App = ReturnType<typeof create<'/'>>
86
+
87
+ /** Node.js request listener for `node:http` and compatible interfaces. */
88
+ export type Listener = (incoming: unknown, outgoing: unknown) => Promise<void>
89
+
90
+ /** HTTP endpoints owned by Tempo API handlers, used for typed auth overrides. */
91
+ export type Endpoint = Auth.Endpoint<App>
92
+
93
+ /** One static API asset resolved from packaged data. */
94
+ export type Asset = {
95
+ /** Loaded asset response. */
96
+ response: Response
97
+ /** Absolute public API URL for the asset (scheme + host + path). */
98
+ uri: string
99
+ }
100
+
101
+ /** Gets one static API asset for a chain, or undefined when it is missing. */
102
+ export type GetAsset = (
103
+ chainId: Viem.ChainId,
104
+ path: string,
105
+ ) => Asset | undefined | Promise<Asset | undefined>
106
+
107
+ /** Top-level OpenAPI `info.description`, rendered above the reference. */
108
+ const infoDescription = 'REST API for reading and interacting with Tempo'
109
+
110
+ /** Creates the Tempo API Hono app. */
111
+ export function create<const path extends string = '/'>(options: create.Options<path> = {}) {
112
+ const { assets, defaultChainId = Viem.defaultChainId, path = '/' as path, rpc } = options
113
+
114
+ // Capture as the literal generic so `Hono.basePath(...)` preserves the
115
+ // typed prefix. testClient's `Client<T, ...>` inference collapses to a
116
+ // `Record<string, ...>` index signature when the BasePath generic is the
117
+ // unconstrained `string`, which strips per-route typing.
118
+ const basePath = Path.normalize(path) as path
119
+
120
+ // The cache store backs both caching layers: handler `memoize` (upstream
121
+ // RPC/indexer reads, keyed by data identity) and the edge response cache
122
+ // (whole responses, keyed by URL). Both are caches, not durable storage — a
123
+ // miss simply re-fetches — so it defaults to in-memory. Point it at a
124
+ // colo-local store (`Store.cache(caches.default)`) in production; never the
125
+ // Durable Object, or edge-cache hits re-incur the round-trip they skip.
126
+ // Authoritative state (webhooks) lives in its own state store, configured
127
+ // separately via `options.webhook.store` (a Durable Object in production).
128
+ const store = options.cache?.store ?? Store.memory()
129
+
130
+ // Memoize one viem client per resolved upstream. Clients are stateless HTTP
131
+ // transports, so reusing them avoids per-request construction cost. The cache
132
+ // key is the resolved output (`url` + `basicAuth` + `chainId`), not the
133
+ // principal, so a resolver `rpc` may branch on any principal field while
134
+ // client cardinality stays bounded by the number of distinct upstreams —
135
+ // callers that resolve to the same upstream share a client.
136
+ const clients = new Map<string, ReturnType<typeof Viem.getClient>>()
137
+ const getClient = (chainId = defaultChainId, principal: Auth.Principal | null = null) => {
138
+ // Resolve to derive the cache key; `Viem.getClient` re-resolves to build the
139
+ // client on a miss. `resolve` is pure and cheap, so the extra call is free.
140
+ const { url, basicAuth } = Viem.resolve(rpc, { chainId, principal })
141
+ const cacheKey = `${url}|${basicAuth ?? ''}|${chainId}`
142
+ const existing = clients.get(cacheKey)
143
+ if (existing) return existing
144
+ const client = Viem.getClient({ chainId, principal, rpc })
145
+ clients.set(cacheKey, client)
146
+ return client
147
+ }
148
+
149
+ // Memoize TIDX clients per `chainId` so the base URL resolver runs once per
150
+ // chain instead of on every request. The same factory backs the scheduled
151
+ // webhook poller (`Webhooks.createPoller`), keeping client construction in
152
+ // one place.
153
+ const getTidx = Tidx.createGetClient({ defaultChainId, tidx: options.tidx })
154
+
155
+ // Build the app in two phases: register middleware imperatively, then chain
156
+ // route mounts so the merged Hono schema flows through the return type. The
157
+ // chained type is what makes `testClient(create())` produce typed path
158
+ // proxies in tests. `basePath` is typed as the literal `path` generic above,
159
+ // which keeps the merged schema keys typed (vs. the unconstrained `string`
160
+ // basePath that would collapse them to an index signature).
161
+ const base = new Hono<Environment>().basePath(basePath)
162
+
163
+ if (options.cors !== false)
164
+ base.use('*', hono_cors(typeof options.cors === 'object' ? options.cors : undefined))
165
+ if (options.compress)
166
+ base.use(
167
+ '*',
168
+ hono_compress(typeof options.compress === 'object' ? options.compress : undefined),
169
+ )
170
+ base.use('*', requestId({ headerName: 'tempo-request-id' }))
171
+ // One canonical structured log line per request, plus request metrics.
172
+ // Registered after `requestId` (the entry embeds it) and before the timing and
173
+ // edge-cache layers so the entry covers edge-cache hits and includes the
174
+ // Server-Timing metrics collected inside the request.
175
+ //
176
+ // Logging and metrics are separate concerns sharing one entry: `logger` emits
177
+ // the structured log line, the request metrics sink records HTTP series, and
178
+ // the optional `metrics.analytics` hook ships the full wide-event entry. The
179
+ // webhook poller/queue run outside this app, so they wire their own
180
+ // `MetricSink.webhooks` sink over the same backend.
181
+ // Wire the request (HTTP) metrics sink over the provided backend.
182
+ const recordRequest = options.metrics ? MetricSink.requests(options.metrics) : undefined
183
+ // Per-request analytics hook (e.g. ClickHouse wide events), if the backend
184
+ // provides one. Its promise is returned from `emit` below so `Log.middleware`
185
+ // keeps it alive via `waitUntil` without holding the response open.
186
+ const recordAnalytics = options.metrics?.analytics
187
+ // Resolve `logger` to a single emit sink: a custom function as-is, `true` to
188
+ // the level-matched console emit, a level string to that emit filtered to the
189
+ // level and above (e.g. `'warn'` drops successful-request info lines).
190
+ const log: Log.Emit | undefined = (() => {
191
+ if (typeof options.logger === 'function') return options.logger
192
+ if (options.logger === true) return Log.emit
193
+ if (typeof options.logger === 'string') return Log.withMinLevel(options.logger)
194
+ return undefined
195
+ })()
196
+ if (log || recordRequest || recordAnalytics)
197
+ base.use(
198
+ '*',
199
+ Log.middleware<Environment>({
200
+ emit: (entry) => {
201
+ log?.(entry)
202
+ recordRequest?.(entry)
203
+ // Return the analytics promise so `Log.middleware` extends the
204
+ // response with `waitUntil`; otherwise the async sink's `queue.send`
205
+ // is a floating promise the Workers runtime cancels once the response
206
+ // is returned, and no analytics ever reach the sink.
207
+ return recordAnalytics?.(entry)
208
+ },
209
+ }),
210
+ )
211
+ // The chain ids this deployment serves: the built-in public chains plus the
212
+ // configured `defaultChainId` and any explicit `supportedChainIds`. Exposed on
213
+ // context so mounted apps with a path `chainId` (e.g. GeckoTerminal) can guard
214
+ // it too.
215
+ const supportedChainIds = new Set<number>([
216
+ ...Object.keys(Viem.url).map(Number),
217
+ ...Object.keys(Tidx.url).map(Number),
218
+ defaultChainId,
219
+ ...(options.supportedChainIds ?? []),
220
+ ])
221
+
222
+ base.use('*', Timing.middleware())
223
+ base.use('*', async (c, next) => {
224
+ // Put app-level dependencies on Hono context so mounted handlers only
225
+ // receive request-local state from `c`.
226
+ c.set('basePath', basePath)
227
+ c.set('chainId', defaultChainId)
228
+ c.set('getAsset', createGetAsset({ assets, basePath, origin: new URL(c.req.url).origin }))
229
+ c.set('getClient', (chainId) => getClient(chainId, Auth.getPrincipal(c)))
230
+ c.set('getTidx', (chainId) => getTidx(chainId, Auth.getPrincipal(c)))
231
+ c.set('rpc', rpc)
232
+ c.set('store', store)
233
+ c.set('supportedChainIds', supportedChainIds)
234
+ c.set('tidx', options.tidx)
235
+ c.set('verifiedTokens', options.verifiedTokens)
236
+ c.set('webhook', options.webhook)
237
+ await next()
238
+ })
239
+
240
+ // Reject a syntactically valid `chainId` this deployment does not serve before
241
+ // it reaches an upstream — an unknown chain otherwise hits the configured
242
+ // RPC/indexer (a static `tidx.baseUrl` answers for any chain id) and comes
243
+ // back as an opaque `upstream_error`.
244
+ base.use('*', async (c, next) => {
245
+ const raw = c.req.query('chainId') ?? c.req.query('chain_id')
246
+ if (raw === undefined) return next()
247
+ // Only reject a well-formed-but-unsupported chain here; malformed values
248
+ // (e.g. `abc`, `0`) fall through to the route's own query validation.
249
+ const parsed = Schema.ChainId.safeParse(raw)
250
+ if (!parsed.success || supportedChainIds.has(parsed.data)) return next()
251
+ return Response.unsupportedChainId(c, parsed.data, supportedChainIds)
252
+ })
253
+ // Skip the static asset route entirely when no store is configured; token
254
+ // logo URIs simply go unresolved.
255
+ if (assets)
256
+ base.use('/assets/*', async (c, next) => {
257
+ const key = stripAssetPath(c.req.path, basePath).replace(/^\/+/, '')
258
+ const asset = key ? await assets.get(key) : undefined
259
+ if (!asset) return next()
260
+ return c.body(asset.body, 200, {
261
+ 'Cache-Control': 'public, max-age=86400',
262
+ 'Content-Type': asset.contentType,
263
+ })
264
+ })
265
+
266
+ // Safety net: any non-2xx response (auth/validation errors, MPP `402`
267
+ // challenges, rate-limit `429`s, upstream `502`s, etc.) must never be
268
+ // cached. This wraps endpoint middleware and handlers registered below.
269
+ base.use('*', async (c, next) => {
270
+ await next()
271
+ if (c.res.status >= 200 && c.res.status < 300) return
272
+ if (c.res.headers.has('Cache-Control')) return
273
+ c.res.headers.set('Cache-Control', 'no-store')
274
+ })
275
+
276
+ // Serve GETs from the cache store before user middleware (auth, rate limiting)
277
+ // runs, so cache hits skip the per-request store round-trips that dominate
278
+ // latency.
279
+ if (options.cache?.edge !== false) base.use('*', EdgeCache.middleware<Environment>({ store }))
280
+
281
+ const base_routed = base.get('/health', OpenApi.describeRoute({ hide: true }), (c) =>
282
+ c.json({ status: 'ok' }, 200),
283
+ )
284
+
285
+ for (const middleware of options.middleware ?? []) base_routed.use('*', middleware)
286
+
287
+ const app = base_routed
288
+ .route('/v1/addresses', addresses())
289
+ .route('/v1/blocks', blocks())
290
+ .route('/v1/exchange', exchanges())
291
+ .route('/v1/fee-amm', feeAmm())
292
+ .route('/v1/indexer', indexer())
293
+ .route('/v1/tokenlist', tokenlist())
294
+ .route('/v1/tokens', tokens())
295
+ .route('/v1/transactions', receipts())
296
+ .route('/v1/transactions', transactions())
297
+ .route('/v1/transactions', transactionActivities())
298
+ .route('/v1/transfers', transfers())
299
+ .route('/v1/verified-tokens', verifiedTokens())
300
+ .route('/v1/webhooks', webhooks({ enabled: options.webhook !== undefined }))
301
+ .route('/rpc', rpcApp())
302
+ .route('/gecko', coingecko())
303
+ // Hidden compatibility route for the prior Tempo API's
304
+ // `GET /chains/:chainId/blocks/by-timestamp` (chain id in the path).
305
+ .route('/chains', blocksByTimestampCompat())
306
+
307
+ const spec = (async () => {
308
+ // OAS 3.1 `webhooks`: document the signed envelope Tempo API POSTs to subscriber
309
+ // URLs (an outbound payload, not an endpoint of this API). Only when enabled.
310
+ const webhooksDoc = (() => {
311
+ if (!options.webhook) return {}
312
+ // `io: 'output'` documents the delivered (serialized) shape; `unrepresentable: 'any'`
313
+ // tolerates the transaction payload's transforms (e.g. lazy fee-token metadata),
314
+ // which have no JSON-Schema form, by emitting `{}` for those nodes rather than throwing.
315
+ const envelope = z.toJSONSchema(webhookSchema.Envelope, {
316
+ io: 'output',
317
+ target: 'draft-2020-12',
318
+ unrepresentable: 'any',
319
+ })
320
+ delete envelope.$schema
321
+ const header = (name: string, description: string) => ({
322
+ description,
323
+ in: 'header' as const,
324
+ name,
325
+ required: true,
326
+ schema: { type: 'string' as const },
327
+ })
328
+ return {
329
+ webhooks: {
330
+ event: {
331
+ post: {
332
+ description:
333
+ 'When an event you subscribed to happens, Tempo sends this signed message to your webhook URL as an HTTP POST (the same shape is used by `POST /webhooks/:id/ping` so you can test your endpoint). Always verify the `tempo-signature` header before trusting the body, then reply with any 2xx status to confirm you received it. If your endpoint returns a non-2xx status, times out, or redirects, Tempo retries with increasing delays; an endpoint that keeps failing is eventually disabled.',
334
+ parameters: [
335
+ header(
336
+ 'tempo-signature',
337
+ 'Signature proving the message really came from Tempo. An HMAC-SHA256 of the body using your webhook secret, formatted `t=<unix>,v1=<hex>`. Verify this before trusting the payload.',
338
+ ),
339
+ header(
340
+ 'tempo-event-id',
341
+ 'A unique, stable id for this event (`evt_…`). The same event may be delivered more than once, so use this id to skip duplicates.',
342
+ ),
343
+ header('tempo-event-type', 'What happened: `token:transfer`, `transaction:included`, `log:emitted`, `block:created`, or `ping`.'), // prettier-ignore
344
+ ],
345
+ requestBody: {
346
+ content: { 'application/json': { schema: envelope } },
347
+ required: true,
348
+ },
349
+ responses: { '2xx': { description: 'Delivery acknowledged.' } },
350
+ summary: 'Webhook event delivery',
351
+ tags: ['Webhooks'],
352
+ },
353
+ },
354
+ },
355
+ }
356
+ })()
357
+ const generated = await generateSpecs(app, {
358
+ documentation: {
359
+ components: {
360
+ headers: OpenApi.headerComponents,
361
+ responses: OpenApi.responseComponents,
362
+ securitySchemes: {
363
+ apiKey: {
364
+ description:
365
+ 'Your Tempo API key, sent in the `tempo-api-key` request header. This is the canonical and recommended API-key header.',
366
+ in: 'header',
367
+ name: 'tempo-api-key',
368
+ type: 'apiKey',
369
+ },
370
+ bearerAuth: {
371
+ description: 'Compatibility alternative for API keys: send `Authorization: Bearer <key>`. Prefer the `tempo-api-key` header when your HTTP client supports custom headers.', // prettier-ignore
372
+ scheme: 'bearer',
373
+ type: 'http',
374
+ },
375
+ },
376
+ },
377
+ info: {
378
+ description: infoDescription,
379
+ title: 'Tempo API',
380
+ version: '1.0.0',
381
+ },
382
+ security: [{ apiKey: [] }, { bearerAuth: [] }],
383
+ servers: [
384
+ ...(options.servers ?? [
385
+ { url: basePath || '/', description: 'Relative to the host serving this document.' },
386
+ ]),
387
+ ],
388
+ tags: [
389
+ {
390
+ name: 'Activities',
391
+ description: 'A readable feed of what an account did onchain.',
392
+ },
393
+ {
394
+ name: 'Balances',
395
+ description: 'How much of each token an account holds.',
396
+ },
397
+ {
398
+ name: 'Blocks',
399
+ description: 'The ordered batches of transactions making up the chain.',
400
+ },
401
+ {
402
+ name: 'Exchange',
403
+ description: "Tempo's built-in stablecoin exchange: pairs, swaps, orders, prices.",
404
+ },
405
+ {
406
+ name: 'Fee AMM',
407
+ description: 'Pools that convert stablecoins to pay fees.',
408
+ },
409
+ {
410
+ name: 'Tokens',
411
+ description: 'TIP-20 token details, supply, and holders.',
412
+ },
413
+ {
414
+ name: 'Transactions',
415
+ description: 'Transactions submitted to Tempo, and their receipts.',
416
+ },
417
+ {
418
+ name: 'Transfers',
419
+ description: 'Token movements from executing transactions.',
420
+ },
421
+ {
422
+ name: 'Verified Tokens',
423
+ description: 'A curated, trusted list of TIP-20 tokens.',
424
+ },
425
+ ...(options.webhook
426
+ ? [
427
+ {
428
+ name: 'Webhooks',
429
+ description: 'Get signed callbacks when onchain events happen.',
430
+ },
431
+ ]
432
+ : []),
433
+ {
434
+ name: 'Indexer',
435
+ description: "Run read-only SQL queries against Tempo's indexed data.",
436
+ },
437
+ {
438
+ name: 'RPC',
439
+ description:
440
+ 'Direct access to the chain over Ethereum JSON-RPC. Not part of the stable API contract; best-effort support only. No compatibility, latency, availability, or data-freshness guarantees. Breaking changes may happen with limited notice.',
441
+ },
442
+ {
443
+ name: 'CoinGecko',
444
+ description: "Exchange data in CoinGecko's GeckoTerminal format.",
445
+ },
446
+ ],
447
+ },
448
+ })
449
+ // Set per-operation `security` and the `402` payment-challenge response from
450
+ // each route's resolved auth policy (the single source of truth), so the
451
+ // anonymous option and `402` appear only where the public/MPP lanes are
452
+ // enabled — not on every operation via the document default.
453
+ applyAccess(generated.paths, Auth.describeAccess(app))
454
+ // Point the `/rpc` operation at the OpenRPC document via `x-openrpc`.
455
+ applyOpenrpcLink(generated.paths, basePath)
456
+ // Re-order paths so list endpoints appear before get endpoints inside each
457
+ // tag group in the sidebar. We classify by operation summary, which every
458
+ // handler sets as either "List ..." or "Get ...". The reference groups by
459
+ // tag and preserves document path order within a tag, so reordering here
460
+ // drives the sidebar sub-item order.
461
+ return { ...generated, ...webhooksDoc, paths: sortPaths(generated.paths) }
462
+ })()
463
+
464
+ app.get('/openapi.json', async (c) => c.json(await spec))
465
+
466
+ // The OpenRPC document is a committed build artifact (no runtime generator);
467
+ // see scripts/openrpc/build.ts and `pnpm openrpc:build`.
468
+ app.get('/openrpc.json', (c) => c.json(openrpcSpec))
469
+
470
+ if (options.docs !== false)
471
+ app.route(
472
+ '/',
473
+ Handler.openApi(
474
+ {
475
+ // Inline the OpenRPC doc so vocs expands it without a self-subrequest
476
+ // (see `inlineOpenrpc`); the public `/openapi.json` keeps the URL ref.
477
+ spec: spec.then(inlineOpenrpc),
478
+ sidebar: { collapsed: true },
479
+ vocs: { logoUrl: OpenApi.logoUrl, title: 'Tempo API Reference', ...options.docs },
480
+ },
481
+ { fallback: 'next', css: OpenApi.css },
482
+ ),
483
+ )
484
+
485
+ app.notFound((c) =>
486
+ Response.error(c, { code: 'not_found', message: 'Route not found', status: 404 }),
487
+ )
488
+
489
+ app.onError((cause, c) => {
490
+ if (cause instanceof HTTPException) return errorFromException(c, cause)
491
+
492
+ // Redact any leaked API-key token from the logged error/stack.
493
+ console.error(
494
+ ApiKey.redact(cause instanceof Error ? (cause.stack ?? cause.message) : String(cause)),
495
+ )
496
+ return Response.error(c, {
497
+ code: 'internal_error',
498
+ message: 'Internal server error',
499
+ status: 500,
500
+ })
501
+ })
502
+
503
+ // Return the typed chained Hono directly. Spreading `store`/`listener` onto
504
+ // the app would intersect the inferred Hono type and breaks `testClient`'s
505
+ // schema inference for downstream consumers. The cache `store` is owned by
506
+ // the caller (pass `options.cache.store`), and a Node listener is produced on
507
+ // demand via `App.listener(app)`.
508
+ return app
509
+ }
510
+
511
+ /**
512
+ * Wraps a Tempo API app with a `node:http`-compatible request listener.
513
+ * Lifted out of `create()` so the returned `app` keeps its precise chained
514
+ * Hono type (which `testClient` needs); intersecting the listener onto the
515
+ * app degrades the typed proxy inference.
516
+ */
517
+ export function listener(app: { fetch: NodeFetch }): Listener {
518
+ return getRequestListener(app.fetch)
519
+ }
520
+
521
+ export declare namespace create {
522
+ /** Options for creating the Tempo API Hono app. */
523
+ type Options<path extends string = '/'> = {
524
+ /** Static API asset store for the current runtime (e.g. `Assets.cloudflareR2(...)`). Omit to skip static assets — token logo URIs go unresolved. */
525
+ assets?: Assets | undefined
526
+ /** Caching configuration: the cache store plus the edge response cache toggle. */
527
+ cache?: Cache | undefined
528
+ /** Response compression for API handlers. Disabled by default; pass true for defaults or a config object to customize. Leave disabled when fronted by a proxy/CDN that already compresses. */
529
+ compress?: boolean | Compress | undefined
530
+ /** CORS for API handlers. Enabled by default (wildcard origin, no credentials); pass a config object to customize, true for defaults, or false to disable (e.g. when CORS is handled by a fronting gateway/CDN). */
531
+ cors?: boolean | Cors | undefined
532
+ /** Default Tempo chain id for requests that omit `chainId`. */
533
+ defaultChainId?: Viem.ChainId | undefined
534
+ /** Vocs API reference options. Set to false to disable the `/` docs page. */
535
+ docs?: Docs | false | undefined
536
+ /** Request logging. Disabled by default. Pass `true` to emit one canonical structured log entry per request through the level-matched console method (indexed by Workers Logs); a level (e.g. `'warn'`) to emit only that level and above (dropping successful-request info lines); or an emit function for a custom sink. */
537
+ logger?: boolean | Log.Level | Logger | undefined
538
+ /** Metrics backend. Disabled by default; pass a backend (e.g. `Metrics.cloudflare()`) to emit request (HTTP) metrics per response. Pass the same backend to the webhook poller/queue handlers so they emit over it too. */
539
+ metrics?: Metrics.Metrics | undefined
540
+ /** Hono middleware to run before API handlers mount. */
541
+ middleware?: readonly MiddlewareHandler[] | undefined
542
+ /** Base path to mount API handlers under. Provided as a literal via the `path` generic so the resulting Hono schema keeps typed route paths for `testClient`. */
543
+ path?: path | undefined
544
+ /** OpenAPI `servers` for the generated document. Defaults to a single relative server (the host serving the doc), so the reference targets the deployment it is served from. Set the canonical public URL(s) here, e.g. `[{ url: 'https://api.tempo.xyz' }]`. */
545
+ servers?: readonly Server[] | undefined
546
+ /** Extra Tempo chain ids this deployment serves, in addition to the built-in public chains and the configured `defaultChainId`. A request for a `chainId` outside this set is rejected with a clear 400 instead of reaching an upstream. */
547
+ supportedChainIds?: readonly number[] | undefined
548
+ /** Tempo RPC options. */
549
+ rpc?: Rpc | undefined
550
+ /** TIDX query client options. */
551
+ tidx?: Tidx.getClient.Tidx | undefined
552
+ /** Verified-token feature configuration. Omit to serve an empty verified list (nothing is verified). Provide a state `store` (Cloudflare KV or a Durable Object in production; never the cache, which a miss would silently drop) to make the curated list API-managed and durable. */
553
+ verifiedTokens?: VerifiedTokens | undefined
554
+ /** Webhook feature configuration. Omit to disable webhooks: the `/webhooks` routes stay mounted (for a stable schema and typed test client) but return `404` and are hidden from the OpenAPI document. Requires a state `store` (a Durable Object in production; never the cache, which a miss would silently drop). */
555
+ webhook?: Webhook | undefined
556
+ }
557
+
558
+ /** Caching configuration. */
559
+ type Cache = {
560
+ /** Edge response caching for public GET endpoints, serving cache hits from `store` before auth/rate-limiting for all callers (hits bypass origin metering). Enabled by default; set false to disable. */
561
+ edge?: boolean | undefined
562
+ /** Cache store backing both the handler read cache and the edge response cache. This is a cache, not durable storage — a miss simply re-fetches — and defaults to in-memory. Use `Store.cache(caches.default)` in production for a colo-local store; never the Durable Object. */
563
+ store?: Store.Store | undefined
564
+ }
565
+
566
+ /** Verified-token feature configuration. */
567
+ type VerifiedTokens = {
568
+ /**
569
+ * Per-isolate soft-refresh window in milliseconds. Within this window
570
+ * verified-token lookups are served from the in-memory snapshot with no
571
+ * store I/O; after it, the next lookup re-checks the store's `head` version
572
+ * (single-flight) and recompiles only when it changed. Defaults to 10_000.
573
+ */
574
+ refreshMs?: number | undefined
575
+ /**
576
+ * State store backing the curated verified-token snapshots. Must be a
577
+ * {@link Store.State} (e.g. `Store.cloudflareKv(...)`, `Store.durableObject(...)`,
578
+ * or `Store.memory()`); the Web Cache adapter (`Store.cache`) is rejected at
579
+ * the type level since a miss there would silently drop authoritative state.
580
+ */
581
+ store: Store.State
582
+ }
583
+
584
+ /** Webhook feature configuration. */
585
+ type Webhook = {
586
+ /**
587
+ * Maximum active subscriptions a single owner may hold. Creation is rejected
588
+ * with `403 limit_exceeded` once reached. Defaults to 100; bounds the
589
+ * spam/SSRF surface and unbounded downstream delivery cost per owner.
590
+ */
591
+ maxPerOwner?: number | undefined
592
+ /**
593
+ * State store backing webhook subscriptions, cursors, and the delivery
594
+ * log. Must be a {@link Store.State} (e.g. `Store.durableObject(...)` or
595
+ * `Store.memory()`); the Web Cache adapter (`Store.cache`) is rejected at
596
+ * the type level since a miss there would silently drop authoritative state.
597
+ */
598
+ store: Store.State
599
+ }
600
+
601
+ /** Binary asset store for static API assets (e.g. `Assets.cloudflareR2(...)`). */
602
+ type Assets = Assets.Assets
603
+
604
+ /** Response compression middleware options. */
605
+ type Compress = NonNullable<Parameters<typeof hono_compress>[0]>
606
+
607
+ /** CORS middleware options. */
608
+ type Cors = NonNullable<Parameters<typeof hono_cors>[0]>
609
+
610
+ /** Vocs API reference options (theme, top navigation, logo, socials, …). */
611
+ type Docs = NonNullable<Parameters<typeof Handler.openApi>[0]['vocs']>
612
+
613
+ /** Custom request-log sink, receiving one canonical entry per request. */
614
+ type Logger = Log.Emit
615
+
616
+ /** OpenAPI server entry for the generated document. */
617
+ type Server = {
618
+ /** Server URL (absolute, or relative to the requesting host). */
619
+ url: string
620
+ /** Optional human-readable description. */
621
+ description?: string
622
+ }
623
+
624
+ /**
625
+ * Tempo RPC options: a static config, or a per-request resolver function that
626
+ * routes by request principal to a separate (e.g. public, no-auth) RPC.
627
+ */
628
+ type Rpc = Viem.getClient.Rpc
629
+ }
630
+
631
+ function getRequestId(c: Context<Environment>) {
632
+ return c.get('requestId')
633
+ }
634
+
635
+ async function errorFromException(c: Context<Environment>, cause: HTTPException) {
636
+ const requestId = getRequestId(c)
637
+ const response = cause.getResponse()
638
+ response.headers.set('tempo-request-id', requestId)
639
+
640
+ if (!isJson(response)) return response
641
+
642
+ const body = await response
643
+ .clone()
644
+ .json()
645
+ .catch(() => null)
646
+ if (!isRecord(body)) return response
647
+
648
+ return c.json({ ...body, requestId }, cause.status, Object.fromEntries(response.headers))
649
+ }
650
+
651
+ // Reorder OpenAPI `paths` so reads come before writes within each tag group,
652
+ // and shallower paths come before deeper paths inside each bucket so root-level
653
+ // routes ("List tokens" — `/tokens`) win over nested ones ("List address
654
+ // tokens" — `/addresses/{address}/tokens`). Tie-break by original document
655
+ // order to keep registered grouping.
656
+ //
657
+ // A path is ranked as a unit because REST groups verbs onto shared paths (and
658
+ // the reference renders all methods of a path adjacently, in the fixed canonical
659
+ // method order GET→POST→PATCH→DELETE), so two operations on the same path can
660
+ // never be separated or reordered relative to each other.
661
+ //
662
+ // Read-only tags keep lists-before-gets (tiers 0/2). The `Webhooks` tag instead
663
+ // groups all reads (lists, then the item GET) ahead of the item writes, scoped
664
+ // to that tag so the read tags are untouched. The shared-path constraint means
665
+ // the desired "Create, all GETs (lists first), Update, Delete" can only be
666
+ // approximated: `Create webhook` shares `/webhooks` with `List webhooks`, and
667
+ // vocs renders GET before POST, so "List webhooks" unavoidably precedes
668
+ // "Create". `Get`/`Update`/`Delete` all share `/webhooks/{id}`, so they render
669
+ // adjacently as Get→Update→Delete. The `{id}` item is tiered right after the
670
+ // collection root, so `Get/Update/Delete` sit just below `List webhooks/Create`,
671
+ // ahead of the remaining lists and action POSTs. Tiers:
672
+ // 0 collection root (`GET`+`POST /webhooks`) → "List webhooks, Create webhook"
673
+ // 1 item (`GET`+`PATCH`+`DELETE /webhooks/{id}`) → "Get, Update, Delete"
674
+ // 2 other list-only paths (`/event-types`, `/deliveries`)
675
+ // 3 other writes/actions (`POST .../ping`, `.../retry`)
676
+ function sortPaths(paths: PathsObject): PathsObject {
677
+ const entries = Object.entries(paths)
678
+ const rank = (entry: [string, PathItem]) => {
679
+ const operations = Object.values(entry[1])
680
+ const summaries = operations.flatMap((op) =>
681
+ isOperation(op) && op.summary ? [op.summary] : [],
682
+ )
683
+ const has = (prefix: string) => summaries.some((summary) => summary.startsWith(prefix))
684
+ if (operations.some((op) => isOperation(op) && op.tags?.includes('Webhooks'))) {
685
+ if (has('Create')) return 0
686
+ if (has('Get')) return 1
687
+ if (has('List')) return 2
688
+ return 3
689
+ }
690
+ if (has('List')) return has('Create') ? 1 : 0
691
+ if (has('Get')) return 2
692
+ if (has('Create')) return 3
693
+ return 4
694
+ }
695
+ const depth = (path: string) =>
696
+ path.endsWith('/rpc') ? 2 : path.split('/').filter(Boolean).length
697
+ const indexed = entries.map(
698
+ (entry, index) => [entry, index, rank(entry), depth(entry[0])] as const,
699
+ )
700
+ indexed.sort(([, ai, ar, ad], [, bi, br, bd]) => ar - br || ad - bd || ai - bi)
701
+ return Object.fromEntries(indexed.map(([entry]) => entry))
702
+ }
703
+
704
+ /**
705
+ * Sets per-operation `security` and the `402` payment-challenge response from
706
+ * each route's resolved access lanes (see {@link Auth.describeAccess}). The
707
+ * anonymous (`{}`) security option and the `402` response are emitted only where
708
+ * the route's policy enables the public and MPP lanes respectively; required
709
+ * API-key scopes are surfaced as the `x-required-scopes` extension.
710
+ */
711
+ function applyAccess(paths: PathsObject, access: Record<string, Auth.OperationAccess>) {
712
+ for (const item of Object.values(paths)) {
713
+ for (const op of Object.values(item as Record<string, unknown>)) {
714
+ if (!isMutableOperation(op)) continue
715
+ const lanes = access[op.operationId]
716
+ if (!lanes) continue
717
+ op.security = [{ apiKey: [] }, { bearerAuth: [] }, ...(lanes.public ? [{}] : [])]
718
+ if (lanes.scopes.length > 0) op['x-required-scopes'] = [...lanes.scopes]
719
+ if (lanes.mpp) op.responses = { ...op.responses, 402: OpenApi.paymentChallengeRef }
720
+ else if (op.responses) delete op.responses['402']
721
+ }
722
+ }
723
+ }
724
+
725
+ /**
726
+ * Links the JSON-RPC passthrough operation to the OpenRPC document (served at
727
+ * `{basePath}/openrpc.json`) via an `x-openrpc` vendor extension. The raw `/rpc`
728
+ * request body is opaque to OpenAPI, so this lets consumers discover the
729
+ * method/param/return schemas it accepts.
730
+ *
731
+ * The public `/openapi.json` keeps a URL reference (small, conventional). The
732
+ * docs handler instead receives the document inlined — see `inlineOpenrpc`.
733
+ */
734
+ function applyOpenrpcLink(paths: PathsObject, basePath: string) {
735
+ // `basePath` is `/` at the root mount and `/api` etc. otherwise; strip any
736
+ // trailing slash so the join never doubles up (`//openrpc.json`).
737
+ const url = `${basePath.replace(/\/$/, '')}/openrpc.json`
738
+ for (const item of Object.values(paths))
739
+ for (const op of Object.values(item as Record<string, unknown>))
740
+ if (isMutableOperation(op) && isRpcOperation(op)) op['x-openrpc'] = url
741
+ }
742
+
743
+ /**
744
+ * Returns a shallow copy of `spec` with the JSON-RPC passthrough operation's
745
+ * `x-openrpc` replaced by the inline OpenRPC document, for the docs handler.
746
+ *
747
+ * Vocs expands `x-openrpc` at request time. A URL reference (as kept in the
748
+ * public `/openapi.json`) would make the docs worker fetch its own
749
+ * `/openrpc.json` — a self-subrequest that is unreliable on Cloudflare Workers
750
+ * and silently falls back to a single, unexpanded operation. Inlining the
751
+ * committed artifact keeps expansion deterministic across local and deployed
752
+ * environments, without bloating the public spec. Only the affected path/op are
753
+ * cloned; the rest of the (large) document is shared by reference.
754
+ */
755
+ function inlineOpenrpc<spec extends { paths: Record<string, unknown> }>(spec: spec): spec {
756
+ const paths: Record<string, unknown> = {}
757
+ for (const [pathname, item] of Object.entries(spec.paths)) {
758
+ let next = item
759
+ if (isRecord(item))
760
+ for (const [method, op] of Object.entries(item))
761
+ if (isMutableOperation(op) && isRpcOperation(op))
762
+ next = { ...item, [method]: { ...op, 'x-openrpc': openrpcSpec } }
763
+ paths[pathname] = next
764
+ }
765
+ return { ...spec, paths }
766
+ }
767
+
768
+ type PathsObject = Awaited<ReturnType<typeof generateSpecs>>['paths']
769
+ type PathItem = PathsObject[string]
770
+ type Operation = { summary?: string | undefined }
771
+ type MutableOperation = {
772
+ operationId: string
773
+ responses?: Record<string, unknown> | undefined
774
+ security?: unknown
775
+ 'x-openrpc'?: unknown
776
+ 'x-required-scopes'?: unknown
777
+ }
778
+
779
+ function isRpcOperation(op: MutableOperation) {
780
+ return op.operationId === 'rpcRequest'
781
+ }
782
+
783
+ function isOperation(value: unknown): value is Operation {
784
+ return !!value && typeof value === 'object' && 'summary' in value
785
+ }
786
+
787
+ function isMutableOperation(value: unknown): value is MutableOperation {
788
+ return isRecord(value) && typeof value['operationId'] === 'string'
789
+ }
790
+
791
+ function isJson(response: globalThis.Response) {
792
+ return response.headers.get('content-type')?.includes('application/json') ?? false
793
+ }
794
+
795
+ function isRecord(value: unknown): value is Record<string, unknown> {
796
+ return !!value && typeof value === 'object' && !Array.isArray(value)
797
+ }
798
+
799
+ function createGetAsset(options: createGetAsset.Options): GetAsset {
800
+ const { assets, basePath, origin } = options
801
+ // Without an asset store, every lookup misses so callers fall back to their
802
+ // "no static data" behavior (undefined logo URIs).
803
+ if (!assets) return () => undefined
804
+ return async (chainId, path) => {
805
+ // Public URLs include `/assets/:chainId`, but the store is keyed by
806
+ // `:chainId/:path` (e.g. an R2 object key), decoupled from the URL shape.
807
+ // `path` may be absolute (`Path.join` returns a leading slash); strip it so
808
+ // the key is `:chainId/:path`, not `:chainId//:path`.
809
+ const pathname = Path.join(basePath, 'assets', String(chainId), path)
810
+ const key = `${chainId}/${path.replace(/^\/+/, '')}`
811
+ const asset = await assets.get(key)
812
+ if (!asset) return undefined
813
+ // Return an absolute public URL (scheme + host) against the request origin
814
+ // so logo URLs work for any consumer regardless of the API's domain.
815
+ return {
816
+ response: new globalThis.Response(asset.body, {
817
+ headers: { 'Content-Type': asset.contentType },
818
+ }),
819
+ uri: new URL(pathname, origin).toString(),
820
+ }
821
+ }
822
+ }
823
+
824
+ declare namespace createGetAsset {
825
+ type Options = {
826
+ assets?: create.Assets | undefined
827
+ basePath: string
828
+ origin: string
829
+ }
830
+ }
831
+
832
+ function stripAssetPath(path: string, basePath: string) {
833
+ for (const prefix of [`${Path.join(basePath, 'assets')}/`, '/assets/'])
834
+ if (path.startsWith(prefix)) return `/${path.slice(prefix.length)}`
835
+ return path
836
+ }
837
+
838
+ type GetNodeRequestListener = (typeof import('@hono/node-server'))['getRequestListener']
839
+ type NodeFetch = Parameters<GetNodeRequestListener>[0]
840
+ type NodeRequestListener = ReturnType<GetNodeRequestListener>
841
+
842
+ function getRequestListener(fetch: NodeFetch): Listener {
843
+ let listener: NodeRequestListener | undefined
844
+ return async (incoming_, outgoing_) => {
845
+ // Resolve the Node-only adapter at call time. The split specifier keeps
846
+ // Worker bundlers from eagerly including a server adapter they never run.
847
+ if (listener === undefined) {
848
+ const nodeServer = (await import(
849
+ /* @vite-ignore */ ['@hono', 'node-server'].join('/')
850
+ )) as typeof import('@hono/node-server')
851
+ listener = nodeServer.getRequestListener(fetch)
852
+ }
853
+ return listener(incoming_ as never, outgoing_ as never)
854
+ }
855
+ }