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
@@ -0,0 +1,91 @@
1
+ import * as WebhookDestination from '../WebhookDestination.js'
2
+
3
+ /**
4
+ * Builds a `betterstack` destination. Its `deliver` formats the envelope into a
5
+ * structured log event and POSTs it to the Better Stack ingest URL authenticated
6
+ * with the source token (`Authorization: Bearer …`; host-locked to
7
+ * `*.betterstackdata.com`). Deliver with `.deliver({ envelope })`.
8
+ */
9
+ export function betterStack(
10
+ options: betterStack.Options,
11
+ ): WebhookDestination.Instance<WebhookDestination.BetterStack> {
12
+ const { token, url } = options
13
+ assertBetterstackUrl(url)
14
+ return {
15
+ deliver(deliverOptions) {
16
+ const { envelope } = deliverOptions
17
+ return WebhookDestination.send({
18
+ // A structured log event: a human-readable `message`, a `dt` timestamp
19
+ // (Better Stack indexes this as the event time), and the raw event
20
+ // fields flattened alongside for querying.
21
+ body: JSON.stringify({
22
+ chainId: envelope.chainId,
23
+ data: envelope.data,
24
+ ...(envelope.context?.description === undefined
25
+ ? {}
26
+ : { description: envelope.context.description }),
27
+ dt: envelope.createdAt,
28
+ event: envelope.type,
29
+ eventId: envelope.id,
30
+ // The subscription's `context.title` (what the webhook is for) becomes
31
+ // the log `message` when set; otherwise fall back to the event identity.
32
+ message:
33
+ envelope.context?.title ??
34
+ (envelope.type === 'ping'
35
+ ? 'Tempo webhook test ping'
36
+ : `Tempo event: ${envelope.type}`),
37
+ ...(envelope.context?.metadata === undefined
38
+ ? {}
39
+ : { metadata: envelope.context.metadata }),
40
+ subscriptionId: envelope.subscriptionId,
41
+ }),
42
+ errorPrefix: 'betterstack ',
43
+ fetch: deliverOptions.fetch,
44
+ headers: { authorization: `Bearer ${token}` },
45
+ now: deliverOptions.now,
46
+ timeoutMs: deliverOptions.timeoutMs,
47
+ url,
48
+ })
49
+ },
50
+ token,
51
+ type: 'betterstack',
52
+ url,
53
+ }
54
+ }
55
+
56
+ export declare namespace betterStack {
57
+ /** Options for {@link betterStack}. */
58
+ type Options = {
59
+ /** Better Stack source token, sent as `Authorization: Bearer …`. */
60
+ token: string
61
+ /** Better Stack ingest host URL (`https://<host>.betterstackdata.com`). */
62
+ url: string
63
+ }
64
+ }
65
+
66
+ /** Host suffix that Better Stack ingest URLs must use. */
67
+ const betterstackHostSuffix = '.betterstackdata.com'
68
+
69
+ /**
70
+ * Validates a `betterstack` destination's ingest URL: must be `https` and hosted
71
+ * under {@link betterstackHostSuffix} (`s95.eu-nbg-2.betterstackdata.com` etc.).
72
+ * As with Slack, the host lock keeps `betterstack` from becoming a generic
73
+ * "POST anywhere with a bearer token" primitive — the source token is attached
74
+ * server-side, so the request must only ever reach Better Stack.
75
+ */
76
+ export function assertBetterstackUrl(input: string): URL {
77
+ let url: URL
78
+ try {
79
+ url = new URL(input)
80
+ } catch {
81
+ throw new WebhookDestination.InvalidUrlError('malformed', input)
82
+ }
83
+
84
+ if (url.protocol !== 'https:') throw new WebhookDestination.InvalidUrlError('protocol', input)
85
+ if (url.username || url.password)
86
+ throw new WebhookDestination.InvalidUrlError('credentials', input)
87
+ if (!url.hostname.endsWith(betterstackHostSuffix))
88
+ throw new WebhookDestination.InvalidUrlError('betterstack_host', input)
89
+
90
+ return url
91
+ }
@@ -0,0 +1,496 @@
1
+ import { formatUnits } from 'viem'
2
+
3
+ import type * as Webhooks from '../Webhooks.js'
4
+ import * as Viem from '../Viem.js'
5
+ import * as WebhookDestination from '../WebhookDestination.js'
6
+
7
+ /**
8
+ * Builds a `slack` destination. Its `deliver` formats the envelope into a
9
+ * per-event Block Kit message ({@link formatBlocks}) and POSTs it to the Slack
10
+ * incoming-webhook URL unsigned (the URL is the shared secret; host-locked to
11
+ * `hooks.slack.com`). Deliver with `.deliver({ envelope })`.
12
+ */
13
+ export function slack(url: string): WebhookDestination.Instance<WebhookDestination.Slack> {
14
+ assertSlackUrl(url)
15
+ return {
16
+ deliver(options) {
17
+ return WebhookDestination.send({
18
+ body: JSON.stringify(formatBlocks(options.envelope)),
19
+ errorPrefix: 'slack ',
20
+ fetch: options.fetch,
21
+ now: options.now,
22
+ timeoutMs: options.timeoutMs,
23
+ url,
24
+ })
25
+ },
26
+ type: 'slack',
27
+ url,
28
+ }
29
+ }
30
+
31
+ /** A Slack `section.fields` / `context.elements` entry. */
32
+ type Field = { text: string; type: 'mrkdwn' }
33
+
34
+ /** A Slack Block Kit block (loosely typed; only the shapes we emit). */
35
+ type Block =
36
+ | { elements: Field[]; type: 'context' }
37
+ | { fields: Field[]; type: 'section' }
38
+ | { text: { emoji: true; text: string; type: 'plain_text' }; type: 'header' }
39
+ | { text: Field; type: 'section' }
40
+
41
+ /** Per-event emoji + friendly header name; also the notification fallback lead. */
42
+ const friendlyName: Record<Webhooks.Envelope['type'], string> = {
43
+ 'block:created': ':link: Block created',
44
+ 'log:emitted': ':label: Log emitted',
45
+ ping: ':bell: Webhook test ping',
46
+ 'token:transfer': ':money_with_wings: Token transfer',
47
+ 'transaction:included': ':package: Transaction included',
48
+ }
49
+
50
+ /**
51
+ * Chain id → block-explorer base URL, derived from {@link Viem.chains}. Used to
52
+ * render addresses/hashes/tokens/blocks as explorer links; an unknown chain id
53
+ * (no base) degrades to plain truncated text.
54
+ */
55
+ const explorerBase: Record<number, string> = Object.fromEntries(
56
+ Viem.chains.flatMap((chain) => {
57
+ const base = chain.blockExplorers?.default.url
58
+ return base ? [[chain.id, base] as const] : []
59
+ }),
60
+ )
61
+
62
+ /**
63
+ * Formats an envelope into a Slack Block Kit message (`{ blocks, text }`) using a
64
+ * per-event layout. Every message shares the cross-cutting rules: a header (the
65
+ * subscription's `context.title` when set, else the event's friendly name), an
66
+ * optional description section, an event-specific lead + fields grid, and a
67
+ * trailing context line that always preserves the raw event type/id/chain/time.
68
+ * Pure and synchronous — all inputs come from the envelope or static chain data.
69
+ */
70
+ export function formatBlocks(envelope: Webhooks.Envelope): { blocks: Block[]; text: string } {
71
+ const base = explorerBase[envelope.chainId]
72
+ const data =
73
+ typeof envelope.data === 'object' && envelope.data !== null
74
+ ? (envelope.data as Record<string, unknown>)
75
+ : {}
76
+
77
+ const layout = renderEvent(envelope.type, data, base)
78
+
79
+ const blocks: Block[] = [
80
+ {
81
+ text: {
82
+ emoji: true,
83
+ text: clamp(envelope.context?.title ?? layout.header, 150),
84
+ type: 'plain_text',
85
+ },
86
+ type: 'header',
87
+ },
88
+ ]
89
+ if (envelope.context?.description !== undefined)
90
+ blocks.push({
91
+ text: { text: escape(envelope.context.description), type: 'mrkdwn' },
92
+ type: 'section',
93
+ })
94
+ if (layout.lead !== undefined)
95
+ blocks.push({ text: { text: layout.lead, type: 'mrkdwn' }, type: 'section' })
96
+ if (layout.fields.length > 0) blocks.push({ fields: layout.fields.slice(0, 10), type: 'section' })
97
+ const metadata = envelope.context?.metadata
98
+ if (metadata && Object.keys(metadata).length > 0)
99
+ blocks.push({
100
+ fields: Object.entries(metadata)
101
+ .slice(0, 10)
102
+ .map(([key, value]) => ({
103
+ text: `*${escape(key)}*\n${escape(value)}`,
104
+ type: 'mrkdwn' as const,
105
+ })),
106
+ type: 'section',
107
+ })
108
+ blocks.push({
109
+ // Keep the raw event type/id/chain/time here so event identity survives a
110
+ // custom `context.title` header.
111
+ elements: [
112
+ {
113
+ text: `\`${escape(envelope.id)}\` · \`${escape(envelope.type)}\` · chain ${envelope.chainId}${
114
+ date(envelope.createdAt) ? ` · ${date(envelope.createdAt)}` : ''
115
+ }`,
116
+ type: 'mrkdwn',
117
+ },
118
+ ],
119
+ type: 'context',
120
+ })
121
+
122
+ // The notification/push preview: a custom title when set, else the event's
123
+ // one-line summary (with the chain appended). Never contains mrkdwn link /
124
+ // `<!date>` syntax.
125
+ return {
126
+ blocks,
127
+ text: envelope.context?.title ?? `${layout.text} · chain ${envelope.chainId}`,
128
+ }
129
+ }
130
+
131
+ /** The event-specific parts of a message, before the shared header/context wrap. */
132
+ type Layout = {
133
+ /** Field grid entries (capped to 10 by the caller). */
134
+ fields: Field[]
135
+ /** Friendly header name (emoji + label) when there's no `context.title`. */
136
+ header: string
137
+ /** Optional summary section under the header. */
138
+ lead?: string | undefined
139
+ /** One-line notification fallback (plain text; no links/`<!date>`). */
140
+ text: string
141
+ }
142
+
143
+ /** Dispatches to the per-event layout builder. */
144
+ function renderEvent(
145
+ type: Webhooks.Envelope['type'],
146
+ data: Record<string, unknown>,
147
+ base: string | undefined,
148
+ ): Layout {
149
+ if (type === 'token:transfer') return transferLayout(data, base)
150
+ if (type === 'transaction:included') return transactionLayout(data, base)
151
+ if (type === 'log:emitted') return logLayout(data, base)
152
+ if (type === 'block:created') return blockLayout(data, base)
153
+ return pingLayout()
154
+ }
155
+
156
+ function transferLayout(data: Record<string, unknown>, base: string | undefined): Layout {
157
+ const amount = str(data['amount'])
158
+ const token =
159
+ typeof data['token'] === 'object' && data['token'] !== null
160
+ ? (data['token'] as { decimals?: number | undefined; symbol?: string | undefined })
161
+ : undefined
162
+ const human = humanAmount(amount, token)
163
+ const sender = str(data['sender'])
164
+ const recipient = str(data['recipient'])
165
+ const tokenAddress = str(data['address'])
166
+
167
+ const fields: Field[] = []
168
+ if (tokenAddress !== undefined)
169
+ field(fields, 'Token', tokenLink(base, tokenAddress, token?.symbol))
170
+ if (amount !== undefined) field(fields, 'Amount', escape(human ?? amount))
171
+ if (sender !== undefined) field(fields, 'Sender', addressLink(base, sender))
172
+ if (recipient !== undefined) field(fields, 'Recipient', addressLink(base, recipient))
173
+ field(fields, 'Block', blockLink(base, data['blockNumber']))
174
+ if (str(data['transactionHash']) !== undefined)
175
+ field(fields, 'Tx', txLink(base, str(data['transactionHash'])!))
176
+ field(fields, 'Timestamp', date(str(data['timestamp'])))
177
+
178
+ const lead =
179
+ sender !== undefined && recipient !== undefined
180
+ ? `*${escape(human ?? amount ?? 'Transfer')}* from ${addressLink(base, sender)} → ${addressLink(base, recipient)}`
181
+ : undefined
182
+
183
+ return {
184
+ fields,
185
+ header: friendlyName['token:transfer'],
186
+ ...(lead === undefined ? {} : { lead }),
187
+ text: `Token transfer · ${human ?? amount ?? 'transfer'}`,
188
+ }
189
+ }
190
+
191
+ function transactionLayout(data: Record<string, unknown>, base: string | undefined): Layout {
192
+ const meta = typeof data['meta'] === 'object' && data['meta'] !== null ? data['meta'] : undefined
193
+ const receipt =
194
+ meta && typeof (meta as Record<string, unknown>)['receipt'] === 'object'
195
+ ? ((meta as Record<string, unknown>)['receipt'] as Record<string, unknown>)
196
+ : undefined
197
+ const status = receipt ? str(receipt['status']) : undefined
198
+ const reverted = status === 'reverted'
199
+
200
+ const type = str(data['type'])
201
+ const sender = str(data['sender'])
202
+ const contractCreation = data['recipient'] === null
203
+ const recipient = str(data['recipient'])
204
+ const recipientText = contractCreation
205
+ ? 'contract creation'
206
+ : recipient && addressLink(base, recipient)
207
+ const calls = Array.isArray(data['calls']) ? (data['calls'] as unknown[]) : undefined
208
+
209
+ const fields: Field[] = []
210
+ if (status !== undefined) field(fields, 'Status', escape(status))
211
+ if (type !== undefined) field(fields, 'Type', escape(type))
212
+ if (sender !== undefined) field(fields, 'Sender', addressLink(base, sender))
213
+ if (recipientText) field(fields, 'Recipient', recipientText)
214
+ if (str(data['value']) !== undefined) field(fields, 'Value', escape(str(data['value'])!))
215
+ if (str(data['hash']) !== undefined) field(fields, 'Tx', txLink(base, str(data['hash'])!))
216
+ field(
217
+ fields,
218
+ 'Block',
219
+ data['blockNumber'] === null ? 'pending' : blockLink(base, data['blockNumber']),
220
+ )
221
+ const gas = scalar(data['gas'])
222
+ const gasPrice = scalar(data['gasPrice'])
223
+ if (gas !== undefined)
224
+ field(
225
+ fields,
226
+ 'Gas',
227
+ gasPrice === undefined ? escape(gas) : `${escape(gas)} @ ${escape(gasPrice)}`,
228
+ )
229
+ if (calls && calls.length > 0) field(fields, 'Calls', String(calls.length))
230
+ field(fields, 'Timestamp', date(str(data['timestamp'])))
231
+
232
+ const accent = reverted ? ':x: *reverted* · ' : status === 'success' ? ':white_check_mark: ' : ''
233
+ const flow =
234
+ sender !== undefined && recipientText
235
+ ? `${addressLink(base, sender)} → ${recipientText}`
236
+ : undefined
237
+ const lead =
238
+ flow === undefined && type === undefined && accent === ''
239
+ ? undefined
240
+ : `${accent}${type ? `${escape(type)} ` : ''}${flow ?? ''}`.trim()
241
+
242
+ return {
243
+ fields,
244
+ header: reverted ? ':x: Transaction reverted' : friendlyName['transaction:included'],
245
+ ...(lead ? { lead } : {}),
246
+ text: `Transaction included${status ? ` · ${status}` : ''}`,
247
+ }
248
+ }
249
+
250
+ function logLayout(data: Record<string, unknown>, base: string | undefined): Layout {
251
+ const event =
252
+ typeof data['event'] === 'object' && data['event'] !== null
253
+ ? (data['event'] as Record<string, unknown>)
254
+ : undefined
255
+ const name = event ? str(event['name']) : undefined
256
+ const decoded = name !== undefined
257
+ const address = str(data['address'])
258
+ const args =
259
+ typeof data['args'] === 'object' && data['args'] !== null
260
+ ? (data['args'] as Record<string, unknown>)
261
+ : undefined
262
+
263
+ // Reserve the always-present coordinates first, then fill remaining slots (up
264
+ // to 10 total) with decoded args, with a trailing "…and N more" if truncated.
265
+ const fields: Field[] = []
266
+ if (address !== undefined) field(fields, 'Contract', addressLink(base, address))
267
+ if (!decoded && event && str(event['topic0']) !== undefined)
268
+ field(fields, 'Topic0', escape(truncateHash(str(event['topic0'])!)))
269
+ field(fields, 'Block', blockLink(base, data['blockNumber']))
270
+ if (str(data['transactionHash']) !== undefined)
271
+ field(fields, 'Tx', txLink(base, str(data['transactionHash'])!))
272
+ if (!decoded) field(fields, 'Log index', scalar(data['logIndex']))
273
+ field(fields, 'Timestamp', date(str(data['timestamp'])))
274
+
275
+ if (decoded && args) {
276
+ const entries = Object.entries(args)
277
+ const room = Math.max(0, 10 - fields.length)
278
+ for (const [key, value] of entries.slice(0, room)) field(fields, key, argValue(value, base))
279
+ if (entries.length > room) field(fields, '', `…and ${entries.length - room} more`)
280
+ }
281
+
282
+ const lead = decoded
283
+ ? `*${escape(name)}* on ${address ? addressLink(base, address) : 'contract'}`
284
+ : address !== undefined
285
+ ? `Log on ${addressLink(base, address)}${
286
+ event && str(event['topic0'])
287
+ ? ` · topic0 ${escape(truncateHash(str(event['topic0'])!))}`
288
+ : ''
289
+ }`
290
+ : undefined
291
+
292
+ return {
293
+ fields,
294
+ header: decoded ? clamp(`:label: Log: ${name}`, 150) : friendlyName['log:emitted'],
295
+ ...(lead === undefined ? {} : { lead }),
296
+ text: decoded
297
+ ? `Log: ${name}${address ? ` · ${truncateAddress(address)}` : ''}`
298
+ : `Log emitted${address ? ` · ${truncateAddress(address)}` : ''}`,
299
+ }
300
+ }
301
+
302
+ function blockLayout(data: Record<string, unknown>, base: string | undefined): Layout {
303
+ const number = scalar(data['number'])
304
+ const txCount = scalar(data['transactionCount'])
305
+ const gasUsedNum = data['gasUsed']
306
+ const gasLimitNum = data['gasLimit']
307
+ const gasUsed = scalar(gasUsedNum)
308
+ const gasLimit = scalar(gasLimitNum)
309
+ const miner = str(data['miner'])
310
+ const proposer = str(data['proposer'])
311
+ const hash = str(data['hash'])
312
+
313
+ const fields: Field[] = []
314
+ field(fields, 'Number', blockLink(base, data['number']))
315
+ field(fields, 'Txns', txCount)
316
+ if (gasUsed !== undefined && gasLimit !== undefined)
317
+ field(fields, 'Gas', `${escape(gasUsed)} / ${escape(gasLimit)}`)
318
+ if (miner !== undefined) field(fields, 'Producer', addressLink(base, miner))
319
+ if (proposer !== undefined) field(fields, 'Proposer', addressLink(base, proposer))
320
+ if (hash !== undefined) field(fields, 'Hash', blockHashLink(base, hash))
321
+ field(fields, 'Timestamp', date(str(data['timestamp'])))
322
+
323
+ const percent =
324
+ typeof gasUsedNum === 'number' && typeof gasLimitNum === 'number' && gasLimitNum > 0
325
+ ? ` (${((gasUsedNum / gasLimitNum) * 100).toFixed(1)}%)`
326
+ : ''
327
+ const lead =
328
+ txCount !== undefined
329
+ ? `*${escape(txCount)}* transactions${
330
+ gasUsed !== undefined && gasLimit !== undefined
331
+ ? ` · gas ${escape(gasUsed)} / ${escape(gasLimit)}${percent}`
332
+ : ''
333
+ }`
334
+ : undefined
335
+
336
+ return {
337
+ fields,
338
+ header:
339
+ number === undefined
340
+ ? friendlyName['block:created']
341
+ : clamp(`:link: Block created #${number}`, 150),
342
+ ...(lead === undefined ? {} : { lead }),
343
+ text: `Block created${number === undefined ? '' : ` #${number}`}${
344
+ txCount === undefined ? '' : ` · ${txCount} txns`
345
+ }`,
346
+ }
347
+ }
348
+
349
+ function pingLayout(): Layout {
350
+ return {
351
+ fields: [],
352
+ header: friendlyName.ping,
353
+ lead: 'Your Slack destination is wired up correctly.',
354
+ text: 'Webhook test ping',
355
+ }
356
+ }
357
+
358
+ /** Appends a `*Label*\nvalue` field when the value is present. */
359
+ function field(fields: Field[], label: string, value: string | undefined): void {
360
+ if (value === undefined) return
361
+ fields.push({ text: label ? `*${label}*\n${value}` : value, type: 'mrkdwn' })
362
+ }
363
+
364
+ /** Renders a human token amount (`10.00 USDC`) from base units + metadata. */
365
+ function humanAmount(
366
+ amount: string | undefined,
367
+ token: { decimals?: number | undefined; symbol?: string | undefined } | undefined,
368
+ ): string | undefined {
369
+ if (amount === undefined) return undefined
370
+ if (token?.decimals === undefined) return amount
371
+ try {
372
+ const formatted = formatUnits(BigInt(amount), token.decimals)
373
+ return token.symbol ? `${formatted} ${token.symbol}` : formatted
374
+ } catch {
375
+ return amount
376
+ }
377
+ }
378
+
379
+ /** Renders a decoded log arg: address args link, everything else escaped text. */
380
+ function argValue(value: unknown, base: string | undefined): string {
381
+ if (typeof value === 'string' && /^0x[0-9a-fA-F]{40}$/.test(value))
382
+ return addressLink(base, value)
383
+ return escape(scalar(value) ?? JSON.stringify(value))
384
+ }
385
+
386
+ /** `<base/address/…|0x…>` when an explorer is known, else plain truncated text. */
387
+ function addressLink(base: string | undefined, address: string): string {
388
+ return link(base, 'address', address, truncateAddress(address))
389
+ }
390
+
391
+ /** `<base/tx/…|0x…>` for a transaction hash. */
392
+ function txLink(base: string | undefined, hash: string): string {
393
+ return link(base, 'tx', hash, truncateHash(hash))
394
+ }
395
+
396
+ /** `<base/token/…|SYMBOL>` (or truncated address) for a token contract. */
397
+ function tokenLink(base: string | undefined, address: string, symbol: string | undefined): string {
398
+ return link(base, 'token', address, symbol ?? truncateAddress(address))
399
+ }
400
+
401
+ /** `<base/block/N|N>` for a block number (accepts number/string). */
402
+ function blockLink(base: string | undefined, number: unknown): string | undefined {
403
+ const id = scalar(number)
404
+ if (id === undefined) return undefined
405
+ return link(base, 'block', id, id)
406
+ }
407
+
408
+ /** `<base/block/0x…|0x…>` linking a block by its hash. */
409
+ function blockHashLink(base: string | undefined, hash: string): string {
410
+ return link(base, 'block', hash, truncateHash(hash))
411
+ }
412
+
413
+ /** Builds a Slack mrkdwn link, falling back to escaped plain text with no base. */
414
+ function link(base: string | undefined, path: string, id: string, label: string): string {
415
+ if (!base) return escape(label)
416
+ return `<${base}/${path}/${encodeURIComponent(id)}|${escape(label)}>`
417
+ }
418
+
419
+ /** Localized time via Slack's `<!date>` token; `undefined` for a bad timestamp. */
420
+ function date(iso: string | undefined): string | undefined {
421
+ if (iso === undefined) return undefined
422
+ const ms = Date.parse(iso)
423
+ if (Number.isNaN(ms)) return undefined
424
+ return `<!date^${Math.floor(ms / 1_000)}^{date_short_pretty} {time}|${escape(iso)}>`
425
+ }
426
+
427
+ /** Middle-truncates an address to `0x9e39…1754` (6+4). */
428
+ function truncateAddress(value: string): string {
429
+ return value.length > 12 ? `${value.slice(0, 6)}…${value.slice(-4)}` : value
430
+ }
431
+
432
+ /** Middle-truncates a hash to `0x3d24a706…24bbf9d3` (10+8). */
433
+ function truncateHash(value: string): string {
434
+ return value.length > 20 ? `${value.slice(0, 10)}…${value.slice(-8)}` : value
435
+ }
436
+
437
+ /** Escapes the three mrkdwn-significant characters in interpolated values. */
438
+ function escape(value: string): string {
439
+ return value.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
440
+ }
441
+
442
+ /** Clamps a header string to Slack's length cap. */
443
+ function clamp(value: string, max: number): string {
444
+ return value.length > max ? `${value.slice(0, max - 1)}…` : value
445
+ }
446
+
447
+ /** Reads a string value, or `undefined` when the key isn't a string. */
448
+ function str(value: unknown): string | undefined {
449
+ return typeof value === 'string' ? value : undefined
450
+ }
451
+
452
+ /** Reads a scalar (number/bigint/boolean/string) value as a plain string. */
453
+ function scalar(value: unknown): string | undefined {
454
+ if (typeof value === 'number' || typeof value === 'bigint' || typeof value === 'boolean')
455
+ return String(value)
456
+ return typeof value === 'string' ? value : undefined
457
+ }
458
+
459
+ /** Host that Slack incoming-webhook URLs must use. */
460
+ const slackWebhookHost = 'hooks.slack.com'
461
+
462
+ /**
463
+ * Validates a `slack` destination's incoming-webhook URL: must be `https` and
464
+ * hosted at {@link slackWebhookHost}. The host lock keeps `slack` destinations
465
+ * from becoming a generic "POST an unsigned message anywhere" primitive — a
466
+ * `slack` delivery carries no signature, so it must only ever reach Slack.
467
+ */
468
+ export function assertSlackUrl(input: string): URL {
469
+ let url: URL
470
+ try {
471
+ url = new URL(input)
472
+ } catch {
473
+ throw new WebhookDestination.InvalidUrlError('malformed', input)
474
+ }
475
+
476
+ if (url.protocol !== 'https:') throw new WebhookDestination.InvalidUrlError('protocol', input)
477
+ if (url.username || url.password)
478
+ throw new WebhookDestination.InvalidUrlError('credentials', input)
479
+ if (url.hostname !== slackWebhookHost)
480
+ throw new WebhookDestination.InvalidUrlError('slack_host', input)
481
+
482
+ return url
483
+ }
484
+
485
+ /**
486
+ * Masks the secret path of a Slack incoming-webhook URL for logging, keeping the
487
+ * host so the destination is still recognizable: `https://hooks.slack.com/…`.
488
+ * Falls back to the bare origin if the URL can't be parsed.
489
+ */
490
+ export function redactSlackUrl(url: string): string {
491
+ try {
492
+ return `${new URL(url).origin}/…`
493
+ } catch {
494
+ return 'https://hooks.slack.com/…'
495
+ }
496
+ }