tapimo 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (520) hide show
  1. package/README.md +57 -16
  2. package/dist/ApiKey.d.ts +4 -1
  3. package/dist/ApiKey.d.ts.map +1 -1
  4. package/dist/ApiKey.js +6 -2
  5. package/dist/ApiKey.js.map +1 -1
  6. package/dist/ApiKeys.d.ts +98 -3
  7. package/dist/ApiKeys.d.ts.map +1 -1
  8. package/dist/ApiKeys.js +150 -1
  9. package/dist/ApiKeys.js.map +1 -1
  10. package/dist/App.d.ts +7917 -2315
  11. package/dist/App.d.ts.map +1 -1
  12. package/dist/App.js +141 -329
  13. package/dist/App.js.map +1 -1
  14. package/dist/Scope.d.ts +20 -4
  15. package/dist/Scope.d.ts.map +1 -1
  16. package/dist/Scope.js +23 -4
  17. package/dist/Scope.js.map +1 -1
  18. package/dist/Webhooks.d.ts +2 -2
  19. package/dist/Webhooks.d.ts.map +1 -1
  20. package/dist/Webhooks.js +2 -2
  21. package/dist/Webhooks.js.map +1 -1
  22. package/dist/admin/App.d.ts +203 -120
  23. package/dist/admin/App.d.ts.map +1 -1
  24. package/dist/admin/apps/api-keys.d.ts +111 -6
  25. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  26. package/dist/admin/apps/api-keys.js +67 -0
  27. package/dist/admin/apps/api-keys.js.map +1 -1
  28. package/dist/admin/apps/scopes.d.ts +12 -1
  29. package/dist/admin/apps/scopes.d.ts.map +1 -1
  30. package/dist/admin/apps/scopes.js +3 -0
  31. package/dist/admin/apps/scopes.js.map +1 -1
  32. package/dist/admin/apps/verified-tokens.d.ts +116 -116
  33. package/dist/admin/apps/verified-tokens.js +1 -1
  34. package/dist/admin/apps/verified-tokens.js.map +1 -1
  35. package/dist/admin/ui.generated.d.ts +1 -1
  36. package/dist/admin/ui.generated.js +13 -13
  37. package/dist/admin/ui.generated.js.map +1 -1
  38. package/dist/analytics/Analytics.d.ts +151 -0
  39. package/dist/analytics/Analytics.d.ts.map +1 -0
  40. package/dist/analytics/Analytics.js +212 -0
  41. package/dist/analytics/Analytics.js.map +1 -0
  42. package/dist/analytics/tables/requestEvents.d.ts +215 -0
  43. package/dist/analytics/tables/requestEvents.d.ts.map +1 -0
  44. package/dist/analytics/tables/requestEvents.js +264 -0
  45. package/dist/analytics/tables/requestEvents.js.map +1 -0
  46. package/dist/apps/data/App.d.ts +19576 -0
  47. package/dist/apps/data/App.d.ts.map +1 -0
  48. package/dist/apps/data/App.js +272 -0
  49. package/dist/apps/data/App.js.map +1 -0
  50. package/dist/apps/data/index.d.ts +3 -0
  51. package/dist/apps/data/index.d.ts.map +1 -0
  52. package/dist/apps/data/index.js +3 -0
  53. package/dist/apps/data/index.js.map +1 -0
  54. package/dist/apps/{activities.d.ts → data/routes/activities.d.ts} +32 -32
  55. package/dist/apps/{activities.d.ts.map → data/routes/activities.d.ts.map} +1 -1
  56. package/dist/apps/{activities.js → data/routes/activities.js} +13 -13
  57. package/dist/apps/data/routes/activities.js.map +1 -0
  58. package/dist/apps/{addresses.d.ts → data/routes/addresses.d.ts} +22 -22
  59. package/dist/apps/{addresses.d.ts.map → data/routes/addresses.d.ts.map} +1 -1
  60. package/dist/apps/{addresses.js → data/routes/addresses.js} +5 -5
  61. package/dist/apps/data/routes/addresses.js.map +1 -0
  62. package/dist/apps/{balances.d.ts → data/routes/balances.d.ts} +4 -4
  63. package/dist/apps/data/routes/balances.d.ts.map +1 -0
  64. package/dist/apps/{balances.js → data/routes/balances.js} +42 -11
  65. package/dist/apps/data/routes/balances.js.map +1 -0
  66. package/dist/apps/{blocks.d.ts → data/routes/blocks.d.ts} +47 -47
  67. package/dist/apps/data/routes/blocks.d.ts.map +1 -0
  68. package/dist/apps/{blocks.js → data/routes/blocks.js} +13 -13
  69. package/dist/apps/data/routes/blocks.js.map +1 -0
  70. package/dist/apps/{coingecko.d.ts → data/routes/coingecko.d.ts} +8 -8
  71. package/dist/apps/data/routes/coingecko.d.ts.map +1 -0
  72. package/dist/apps/{coingecko.js → data/routes/coingecko.js} +13 -13
  73. package/dist/apps/data/routes/coingecko.js.map +1 -0
  74. package/dist/apps/{exchanges.d.ts → data/routes/exchanges.d.ts} +108 -108
  75. package/dist/apps/data/routes/exchanges.d.ts.map +1 -0
  76. package/dist/apps/{exchanges.js → data/routes/exchanges.js} +19 -19
  77. package/dist/apps/data/routes/exchanges.js.map +1 -0
  78. package/dist/apps/{fee-amm.d.ts → data/routes/fee-amm.d.ts} +39 -39
  79. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -0
  80. package/dist/apps/{fee-amm.js → data/routes/fee-amm.js} +17 -17
  81. package/dist/apps/data/routes/fee-amm.js.map +1 -0
  82. package/dist/apps/data/routes/funding.d.ts +765 -0
  83. package/dist/apps/data/routes/funding.d.ts.map +1 -0
  84. package/dist/apps/data/routes/funding.js +383 -0
  85. package/dist/apps/data/routes/funding.js.map +1 -0
  86. package/dist/apps/{indexer.d.ts → data/routes/indexer.d.ts} +4 -4
  87. package/dist/apps/data/routes/indexer.d.ts.map +1 -0
  88. package/dist/apps/{indexer.js → data/routes/indexer.js} +13 -9
  89. package/dist/apps/data/routes/indexer.js.map +1 -0
  90. package/dist/apps/{receipts.d.ts → data/routes/receipts.d.ts} +49 -49
  91. package/dist/apps/data/routes/receipts.d.ts.map +1 -0
  92. package/dist/apps/{receipts.js → data/routes/receipts.js} +14 -20
  93. package/dist/apps/data/routes/receipts.js.map +1 -0
  94. package/dist/apps/{rpc.d.ts → data/routes/rpc.d.ts} +33 -40
  95. package/dist/apps/data/routes/rpc.d.ts.map +1 -0
  96. package/dist/apps/{rpc.js → data/routes/rpc.js} +35 -7
  97. package/dist/apps/data/routes/rpc.js.map +1 -0
  98. package/dist/apps/{tokenlist.d.ts → data/routes/tokenlist.d.ts} +40 -40
  99. package/dist/apps/data/routes/tokenlist.d.ts.map +1 -0
  100. package/dist/apps/{tokenlist.js → data/routes/tokenlist.js} +8 -8
  101. package/dist/apps/data/routes/tokenlist.js.map +1 -0
  102. package/dist/apps/{tokens.d.ts → data/routes/tokens.d.ts} +468 -468
  103. package/dist/apps/data/routes/tokens.d.ts.map +1 -0
  104. package/dist/apps/{tokens.js → data/routes/tokens.js} +19 -19
  105. package/dist/apps/data/routes/tokens.js.map +1 -0
  106. package/dist/apps/{transactions.d.ts → data/routes/transactions.d.ts} +304 -304
  107. package/dist/apps/data/routes/transactions.d.ts.map +1 -0
  108. package/dist/apps/{transactions.js → data/routes/transactions.js} +13 -13
  109. package/dist/apps/data/routes/transactions.js.map +1 -0
  110. package/dist/apps/{transfers.d.ts → data/routes/transfers.d.ts} +62 -62
  111. package/dist/apps/data/routes/transfers.d.ts.map +1 -0
  112. package/dist/apps/{transfers.js → data/routes/transfers.js} +13 -13
  113. package/dist/apps/data/routes/transfers.js.map +1 -0
  114. package/dist/apps/{verified-tokens.d.ts → data/routes/verified-tokens.d.ts} +6 -6
  115. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -0
  116. package/dist/apps/{verified-tokens.js → data/routes/verified-tokens.js} +9 -9
  117. package/dist/apps/data/routes/verified-tokens.js.map +1 -0
  118. package/dist/apps/{webhooks.d.ts → data/routes/webhooks.d.ts} +17 -17
  119. package/dist/apps/{webhooks.d.ts.map → data/routes/webhooks.d.ts.map} +1 -1
  120. package/dist/apps/{webhooks.js → data/routes/webhooks.js} +38 -34
  121. package/dist/apps/data/routes/webhooks.js.map +1 -0
  122. package/dist/apps/docs/App.d.ts +29 -0
  123. package/dist/apps/docs/App.d.ts.map +1 -0
  124. package/dist/apps/docs/App.js +54 -0
  125. package/dist/apps/docs/App.js.map +1 -0
  126. package/dist/{internal/OpenApi.fonts.d.ts → apps/docs/fonts.d.ts} +1 -1
  127. package/dist/apps/docs/fonts.d.ts.map +1 -0
  128. package/dist/{internal/OpenApi.fonts.js → apps/docs/fonts.js} +1 -1
  129. package/dist/apps/docs/fonts.js.map +1 -0
  130. package/dist/apps/docs/index.d.ts +2 -0
  131. package/dist/apps/docs/index.d.ts.map +1 -0
  132. package/dist/apps/docs/index.js +2 -0
  133. package/dist/apps/docs/index.js.map +1 -0
  134. package/dist/{internal/OpenApi.logo.d.ts → apps/docs/logo.d.ts} +1 -1
  135. package/dist/apps/docs/logo.d.ts.map +1 -0
  136. package/dist/{internal/OpenApi.logo.js → apps/docs/logo.js} +1 -1
  137. package/dist/apps/docs/logo.js.map +1 -0
  138. package/dist/apps/docs/reference.d.ts +21 -0
  139. package/dist/apps/docs/reference.d.ts.map +1 -0
  140. package/dist/apps/docs/reference.js +112 -0
  141. package/dist/apps/docs/reference.js.map +1 -0
  142. package/dist/apps/index.d.ts +5 -0
  143. package/dist/apps/index.d.ts.map +1 -0
  144. package/dist/apps/index.js +5 -0
  145. package/dist/apps/index.js.map +1 -0
  146. package/dist/apps/mcp.d.ts +76 -0
  147. package/dist/apps/mcp.d.ts.map +1 -0
  148. package/dist/apps/mcp.js +188 -0
  149. package/dist/apps/mcp.js.map +1 -0
  150. package/dist/apps/metadata.d.ts +7 -0
  151. package/dist/apps/metadata.d.ts.map +1 -0
  152. package/dist/apps/metadata.js +15 -0
  153. package/dist/apps/metadata.js.map +1 -0
  154. package/dist/apps/platform/App.d.ts +5262 -0
  155. package/dist/apps/platform/App.d.ts.map +1 -0
  156. package/dist/apps/platform/App.js +84 -0
  157. package/dist/apps/platform/App.js.map +1 -0
  158. package/dist/apps/platform/Billing.d.ts +279 -0
  159. package/dist/apps/platform/Billing.d.ts.map +1 -0
  160. package/dist/apps/platform/Billing.js +477 -0
  161. package/dist/apps/platform/Billing.js.map +1 -0
  162. package/dist/apps/platform/index.d.ts +3 -0
  163. package/dist/apps/platform/index.d.ts.map +1 -0
  164. package/dist/apps/platform/index.js +3 -0
  165. package/dist/apps/platform/index.js.map +1 -0
  166. package/dist/apps/platform/routes/api-keys.d.ts +973 -0
  167. package/dist/apps/platform/routes/api-keys.d.ts.map +1 -0
  168. package/dist/apps/platform/routes/api-keys.js +430 -0
  169. package/dist/apps/platform/routes/api-keys.js.map +1 -0
  170. package/dist/apps/platform/routes/billing.d.ts +1520 -0
  171. package/dist/apps/platform/routes/billing.d.ts.map +1 -0
  172. package/dist/apps/platform/routes/billing.js +795 -0
  173. package/dist/apps/platform/routes/billing.js.map +1 -0
  174. package/dist/apps/platform/routes/invitations.d.ts +851 -0
  175. package/dist/apps/platform/routes/invitations.d.ts.map +1 -0
  176. package/dist/apps/platform/routes/invitations.js +471 -0
  177. package/dist/apps/platform/routes/invitations.js.map +1 -0
  178. package/dist/apps/platform/routes/me.d.ts +95 -0
  179. package/dist/apps/platform/routes/me.d.ts.map +1 -0
  180. package/dist/apps/platform/routes/me.js +81 -0
  181. package/dist/apps/platform/routes/me.js.map +1 -0
  182. package/dist/apps/platform/routes/members.d.ts +554 -0
  183. package/dist/apps/platform/routes/members.d.ts.map +1 -0
  184. package/dist/apps/platform/routes/members.js +251 -0
  185. package/dist/apps/platform/routes/members.js.map +1 -0
  186. package/dist/apps/platform/routes/orgs.d.ts +596 -0
  187. package/dist/apps/platform/routes/orgs.d.ts.map +1 -0
  188. package/dist/apps/platform/routes/orgs.js +276 -0
  189. package/dist/apps/platform/routes/orgs.js.map +1 -0
  190. package/dist/apps/platform/routes/projects.d.ts +740 -0
  191. package/dist/apps/platform/routes/projects.d.ts.map +1 -0
  192. package/dist/apps/platform/routes/projects.js +286 -0
  193. package/dist/apps/platform/routes/projects.js.map +1 -0
  194. package/dist/apps/platform/routes/scopes.d.ts +107 -0
  195. package/dist/apps/platform/routes/scopes.d.ts.map +1 -0
  196. package/dist/apps/platform/routes/scopes.js +66 -0
  197. package/dist/apps/platform/routes/scopes.js.map +1 -0
  198. package/dist/apps/platform/routes/usage.d.ts +588 -0
  199. package/dist/apps/platform/routes/usage.d.ts.map +1 -0
  200. package/dist/apps/platform/routes/usage.js +391 -0
  201. package/dist/apps/platform/routes/usage.js.map +1 -0
  202. package/dist/apps/relay/App.d.ts +44 -0
  203. package/dist/apps/relay/App.d.ts.map +1 -0
  204. package/dist/apps/relay/App.js +211 -0
  205. package/dist/apps/relay/App.js.map +1 -0
  206. package/dist/apps/relay/Sponsorships.d.ts +107 -0
  207. package/dist/apps/relay/Sponsorships.d.ts.map +1 -0
  208. package/dist/apps/relay/Sponsorships.js +194 -0
  209. package/dist/apps/relay/Sponsorships.js.map +1 -0
  210. package/dist/apps/relay/index.d.ts +3 -0
  211. package/dist/apps/relay/index.d.ts.map +1 -0
  212. package/dist/apps/relay/index.js +3 -0
  213. package/dist/apps/relay/index.js.map +1 -0
  214. package/dist/cli/Cli.d.ts +9 -2
  215. package/dist/cli/Cli.d.ts.map +1 -1
  216. package/dist/cli/Cli.js +12 -4
  217. package/dist/cli/Cli.js.map +1 -1
  218. package/dist/cli/commands/admin.d.ts +5 -1
  219. package/dist/cli/commands/admin.d.ts.map +1 -1
  220. package/dist/cli/commands/admin.js +82 -3
  221. package/dist/cli/commands/admin.js.map +1 -1
  222. package/dist/db/Db.d.ts +17 -0
  223. package/dist/db/Db.d.ts.map +1 -1
  224. package/dist/db/Db.js +254 -0
  225. package/dist/db/Db.js.map +1 -1
  226. package/dist/db/Schema.d.ts +159 -0
  227. package/dist/db/Schema.d.ts.map +1 -1
  228. package/dist/db/Schema.js +187 -2
  229. package/dist/db/Schema.js.map +1 -1
  230. package/dist/db/tables/billingSettings.d.ts +55 -0
  231. package/dist/db/tables/billingSettings.d.ts.map +1 -0
  232. package/dist/db/tables/billingSettings.js +70 -0
  233. package/dist/db/tables/billingSettings.js.map +1 -0
  234. package/dist/db/tables/invitations.d.ts +84 -0
  235. package/dist/db/tables/invitations.d.ts.map +1 -0
  236. package/dist/db/tables/invitations.js +165 -0
  237. package/dist/db/tables/invitations.js.map +1 -0
  238. package/dist/db/tables/memberships.d.ts +108 -0
  239. package/dist/db/tables/memberships.d.ts.map +1 -0
  240. package/dist/db/tables/memberships.js +153 -0
  241. package/dist/db/tables/memberships.js.map +1 -0
  242. package/dist/db/tables/organizations.d.ts +48 -0
  243. package/dist/db/tables/organizations.d.ts.map +1 -1
  244. package/dist/db/tables/organizations.js +97 -2
  245. package/dist/db/tables/organizations.js.map +1 -1
  246. package/dist/db/tables/projects.d.ts +65 -0
  247. package/dist/db/tables/projects.d.ts.map +1 -0
  248. package/dist/db/tables/projects.js +75 -0
  249. package/dist/db/tables/projects.js.map +1 -0
  250. package/dist/db/tables/requestUsage.d.ts +121 -0
  251. package/dist/db/tables/requestUsage.d.ts.map +1 -0
  252. package/dist/db/tables/requestUsage.js +150 -0
  253. package/dist/db/tables/requestUsage.js.map +1 -0
  254. package/dist/db/tables/sponsoredTransactions.d.ts +239 -0
  255. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -0
  256. package/dist/db/tables/sponsoredTransactions.js +314 -0
  257. package/dist/db/tables/sponsoredTransactions.js.map +1 -0
  258. package/dist/db/tables/stripeCustomers.d.ts +81 -0
  259. package/dist/db/tables/stripeCustomers.d.ts.map +1 -0
  260. package/dist/db/tables/stripeCustomers.js +120 -0
  261. package/dist/db/tables/stripeCustomers.js.map +1 -0
  262. package/dist/db/tables/users.d.ts +48 -0
  263. package/dist/db/tables/users.d.ts.map +1 -0
  264. package/dist/db/tables/users.js +65 -0
  265. package/dist/db/tables/users.js.map +1 -0
  266. package/dist/handlers/internal/executionError.d.ts.map +1 -1
  267. package/dist/handlers/internal/executionError.js +2 -0
  268. package/dist/handlers/internal/executionError.js.map +1 -1
  269. package/dist/handlers/internal/multisig.d.ts +2 -6
  270. package/dist/handlers/internal/multisig.d.ts.map +1 -1
  271. package/dist/handlers/internal/multisig.js +11 -36
  272. package/dist/handlers/internal/multisig.js.map +1 -1
  273. package/dist/handlers/internal/sponsorship.d.ts +35 -2
  274. package/dist/handlers/internal/sponsorship.d.ts.map +1 -1
  275. package/dist/handlers/internal/sponsorship.js +53 -14
  276. package/dist/handlers/internal/sponsorship.js.map +1 -1
  277. package/dist/handlers/internal/utils.d.ts +10 -3
  278. package/dist/handlers/internal/utils.d.ts.map +1 -1
  279. package/dist/handlers/internal/utils.js +14 -6
  280. package/dist/handlers/internal/utils.js.map +1 -1
  281. package/dist/handlers/relay.d.ts +6 -2
  282. package/dist/handlers/relay.d.ts.map +1 -1
  283. package/dist/handlers/relay.js +16 -2
  284. package/dist/handlers/relay.js.map +1 -1
  285. package/dist/index.d.ts +1 -0
  286. package/dist/index.d.ts.map +1 -1
  287. package/dist/index.js +1 -0
  288. package/dist/index.js.map +1 -1
  289. package/dist/internal/Auth.d.ts +222 -6
  290. package/dist/internal/Auth.d.ts.map +1 -1
  291. package/dist/internal/Auth.js +625 -74
  292. package/dist/internal/Auth.js.map +1 -1
  293. package/dist/internal/Cache.d.ts.map +1 -1
  294. package/dist/internal/Cache.js +4 -1
  295. package/dist/internal/Cache.js.map +1 -1
  296. package/dist/internal/EdgeCache.d.ts +10 -0
  297. package/dist/internal/EdgeCache.d.ts.map +1 -1
  298. package/dist/internal/EdgeCache.js +39 -8
  299. package/dist/internal/EdgeCache.js.map +1 -1
  300. package/dist/internal/Fees.d.ts +27 -0
  301. package/dist/internal/Fees.d.ts.map +1 -0
  302. package/dist/internal/Fees.js +48 -0
  303. package/dist/internal/Fees.js.map +1 -0
  304. package/dist/internal/FundingRoutes.d.ts +183 -0
  305. package/dist/internal/FundingRoutes.d.ts.map +1 -0
  306. package/dist/internal/FundingRoutes.js +490 -0
  307. package/dist/internal/FundingRoutes.js.map +1 -0
  308. package/dist/internal/Id.d.ts +15 -0
  309. package/dist/internal/Id.d.ts.map +1 -0
  310. package/dist/internal/Id.js +22 -0
  311. package/dist/internal/Id.js.map +1 -0
  312. package/dist/internal/Log.d.ts +9 -3
  313. package/dist/internal/Log.d.ts.map +1 -1
  314. package/dist/internal/Log.js +7 -0
  315. package/dist/internal/Log.js.map +1 -1
  316. package/dist/internal/OpenApi.d.ts +10 -20
  317. package/dist/internal/OpenApi.d.ts.map +1 -1
  318. package/dist/internal/OpenApi.js +8 -58
  319. package/dist/internal/OpenApi.js.map +1 -1
  320. package/dist/internal/RateLimit.d.ts +25 -0
  321. package/dist/internal/RateLimit.d.ts.map +1 -1
  322. package/dist/internal/RateLimit.js +51 -0
  323. package/dist/internal/RateLimit.js.map +1 -1
  324. package/dist/internal/Response.d.ts +1 -1
  325. package/dist/internal/Response.d.ts.map +1 -1
  326. package/dist/internal/Schema.d.ts +8 -0
  327. package/dist/internal/Schema.d.ts.map +1 -1
  328. package/dist/internal/Schema.js +7 -0
  329. package/dist/internal/Schema.js.map +1 -1
  330. package/dist/internal/Store.d.ts +7 -1
  331. package/dist/internal/Store.d.ts.map +1 -1
  332. package/dist/internal/Store.js +16 -2
  333. package/dist/internal/Store.js.map +1 -1
  334. package/dist/internal/Viem.d.ts +84 -0
  335. package/dist/internal/Viem.d.ts.map +1 -1
  336. package/dist/internal/Webhooks.d.ts +1 -1
  337. package/dist/internal/Webhooks.d.ts.map +1 -1
  338. package/dist/internal/Webhooks.js +3 -3
  339. package/dist/internal/Webhooks.js.map +1 -1
  340. package/package.json +33 -6
  341. package/src/ApiKey.ts +10 -2
  342. package/src/ApiKeys.test.ts +155 -1
  343. package/src/ApiKeys.ts +203 -1
  344. package/src/App.test.ts +64 -75741
  345. package/src/App.ts +218 -420
  346. package/src/Client.test-d.ts +657 -15
  347. package/src/Scope.ts +26 -4
  348. package/src/Webhooks.ts +2 -2
  349. package/src/admin/apps/api-keys.test.ts +51 -0
  350. package/src/admin/apps/api-keys.ts +84 -0
  351. package/src/admin/apps/scopes.test.ts +11 -1
  352. package/src/admin/apps/scopes.ts +3 -0
  353. package/src/admin/apps/verified-tokens.ts +1 -1
  354. package/src/admin/ui/src/components/Page.tsx +2 -3
  355. package/src/admin/ui/src/routes/__root.tsx +2 -2
  356. package/src/admin/ui/src/routes/api-keys.tsx +2 -2
  357. package/src/admin/ui.generated.ts +13 -13
  358. package/src/analytics/Analytics.test.ts +527 -0
  359. package/src/analytics/Analytics.ts +318 -0
  360. package/src/analytics/tables/requestEvents.ts +523 -0
  361. package/src/apps/composition.test.ts +66 -0
  362. package/src/apps/data/App.test.ts +72 -0
  363. package/src/apps/data/App.ts +330 -0
  364. package/src/apps/data/index.ts +2 -0
  365. package/src/apps/{activities.test.ts → data/routes/activities.test.ts} +3 -3
  366. package/src/apps/{activities.ts → data/routes/activities.ts} +15 -15
  367. package/src/apps/{addresses.ts → data/routes/addresses.ts} +6 -6
  368. package/src/apps/{balances.test.ts → data/routes/balances.test.ts} +2 -2
  369. package/src/apps/{balances.ts → data/routes/balances.ts} +46 -12
  370. package/src/apps/{blocks.test.ts → data/routes/blocks.test.ts} +3 -3
  371. package/src/apps/{blocks.ts → data/routes/blocks.ts} +14 -14
  372. package/src/apps/{coingecko.test.ts → data/routes/coingecko.test.ts} +2 -2
  373. package/src/apps/{coingecko.ts → data/routes/coingecko.ts} +14 -14
  374. package/src/apps/{exchanges.test.ts → data/routes/exchanges.test.ts} +1 -1
  375. package/src/apps/{exchanges.ts → data/routes/exchanges.ts} +20 -20
  376. package/src/apps/{fee-amm.test.ts → data/routes/fee-amm.test.ts} +3 -3
  377. package/src/apps/{fee-amm.ts → data/routes/fee-amm.ts} +18 -18
  378. package/src/apps/data/routes/funding.test.ts +214 -0
  379. package/src/apps/data/routes/funding.ts +584 -0
  380. package/src/apps/{indexer.test.ts → data/routes/indexer.test.ts} +26 -5
  381. package/src/apps/{indexer.ts → data/routes/indexer.ts} +13 -10
  382. package/src/apps/{receipts.test.ts → data/routes/receipts.test.ts} +3 -3
  383. package/src/apps/{receipts.ts → data/routes/receipts.ts} +15 -22
  384. package/src/apps/{rpc.test.ts → data/routes/rpc.test.ts} +122 -7
  385. package/src/apps/{rpc.ts → data/routes/rpc.ts} +38 -8
  386. package/src/apps/{tokenlist.test.ts → data/routes/tokenlist.test.ts} +3 -3
  387. package/src/apps/{tokenlist.ts → data/routes/tokenlist.ts} +9 -9
  388. package/src/apps/{tokens.test.ts → data/routes/tokens.test.ts} +2 -2
  389. package/src/apps/{tokens.ts → data/routes/tokens.ts} +20 -20
  390. package/src/apps/{transactions.test.ts → data/routes/transactions.test.ts} +3 -3
  391. package/src/apps/{transactions.ts → data/routes/transactions.ts} +15 -15
  392. package/src/apps/{transfers.test.ts → data/routes/transfers.test.ts} +2 -2
  393. package/src/apps/{transfers.ts → data/routes/transfers.ts} +15 -15
  394. package/src/apps/{verified-tokens.test.ts → data/routes/verified-tokens.test.ts} +3 -3
  395. package/src/apps/{verified-tokens.ts → data/routes/verified-tokens.ts} +10 -10
  396. package/src/apps/{webhooks.test.ts → data/routes/webhooks.test.ts} +7 -7
  397. package/src/apps/{webhooks.ts → data/routes/webhooks.ts} +50 -43
  398. package/src/apps/docs/App.ts +65 -0
  399. package/src/apps/docs/index.ts +1 -0
  400. package/src/apps/docs/reference.ts +132 -0
  401. package/src/apps/index.ts +4 -0
  402. package/src/apps/mcp.test.ts +206 -0
  403. package/src/apps/mcp.ts +250 -0
  404. package/src/apps/metadata.ts +23 -0
  405. package/src/apps/platform/App.ts +169 -0
  406. package/src/apps/platform/Billing.test.ts +530 -0
  407. package/src/apps/platform/Billing.ts +682 -0
  408. package/src/apps/platform/index.ts +2 -0
  409. package/src/apps/platform/routes/api-keys.test.ts +493 -0
  410. package/src/apps/platform/routes/api-keys.ts +543 -0
  411. package/src/apps/platform/routes/billing.test.ts +1100 -0
  412. package/src/apps/platform/routes/billing.ts +966 -0
  413. package/src/apps/platform/routes/invitations.test.ts +409 -0
  414. package/src/apps/platform/routes/invitations.ts +587 -0
  415. package/src/apps/platform/routes/me.test.ts +351 -0
  416. package/src/apps/platform/routes/me.ts +106 -0
  417. package/src/apps/platform/routes/members.test.ts +195 -0
  418. package/src/apps/platform/routes/members.ts +313 -0
  419. package/src/apps/platform/routes/orgs.test.ts +313 -0
  420. package/src/apps/platform/routes/orgs.ts +365 -0
  421. package/src/apps/platform/routes/projects.test.ts +181 -0
  422. package/src/apps/platform/routes/projects.ts +367 -0
  423. package/src/apps/platform/routes/scopes.test.ts +29 -0
  424. package/src/apps/platform/routes/scopes.ts +85 -0
  425. package/src/apps/platform/routes/usage.test.ts +422 -0
  426. package/src/apps/platform/routes/usage.ts +527 -0
  427. package/src/apps/relay/App.test.ts +933 -0
  428. package/src/apps/relay/App.ts +236 -0
  429. package/src/apps/relay/Sponsorships.test.ts +375 -0
  430. package/src/apps/relay/Sponsorships.ts +306 -0
  431. package/src/apps/relay/index.ts +2 -0
  432. package/src/cli/Cli.test.ts +25 -2
  433. package/src/cli/Cli.ts +18 -3
  434. package/src/cli/commands/admin.test.ts +83 -0
  435. package/src/cli/commands/admin.ts +91 -3
  436. package/src/db/Db.ts +296 -0
  437. package/src/db/Schema.ts +241 -2
  438. package/src/db/tables/billingSettings.test.ts +87 -0
  439. package/src/db/tables/billingSettings.ts +108 -0
  440. package/src/db/tables/invitations.ts +198 -0
  441. package/src/db/tables/memberships.ts +208 -0
  442. package/src/db/tables/organizations.test.ts +38 -2
  443. package/src/db/tables/organizations.ts +117 -2
  444. package/src/db/tables/projects.ts +107 -0
  445. package/src/db/tables/requestUsage.ts +231 -0
  446. package/src/db/tables/sponsoredTransactions.test.ts +325 -0
  447. package/src/db/tables/sponsoredTransactions.ts +464 -0
  448. package/src/db/tables/stripeCustomers.test.ts +109 -0
  449. package/src/db/tables/stripeCustomers.ts +162 -0
  450. package/src/db/tables/users.ts +85 -0
  451. package/src/handlers/internal/executionError.ts +2 -0
  452. package/src/handlers/internal/multisig.test.ts +84 -10
  453. package/src/handlers/internal/multisig.ts +15 -50
  454. package/src/handlers/internal/sponsorship.ts +106 -20
  455. package/src/handlers/internal/utils.ts +19 -7
  456. package/src/handlers/{internal/relay.localnet.test.ts → relay.test.ts} +2 -2
  457. package/src/handlers/relay.ts +23 -5
  458. package/src/index.ts +1 -0
  459. package/src/internal/Auth.test.ts +491 -25
  460. package/src/internal/Auth.ts +839 -84
  461. package/src/internal/Cache.ts +5 -1
  462. package/src/internal/Cursor.test.ts +3 -2
  463. package/src/internal/EdgeCache.test.ts +27 -4
  464. package/src/internal/EdgeCache.ts +47 -9
  465. package/src/internal/Fees.test.ts +32 -0
  466. package/src/internal/Fees.ts +51 -0
  467. package/src/internal/FundingRoutes.ts +583 -0
  468. package/src/internal/Id.test.ts +17 -0
  469. package/src/internal/Id.ts +24 -0
  470. package/src/internal/Log.test.ts +2 -1
  471. package/src/internal/Log.ts +16 -3
  472. package/src/internal/MetricSink.ts +1 -1
  473. package/src/internal/OpenApi.ts +12 -60
  474. package/src/internal/RateLimit.test.ts +84 -0
  475. package/src/internal/RateLimit.ts +70 -0
  476. package/src/internal/Response.ts +1 -1
  477. package/src/internal/Schema.ts +8 -0
  478. package/src/internal/Store.test.ts +52 -0
  479. package/src/internal/Store.ts +24 -5
  480. package/src/internal/Tidx.local.test.ts +1 -1
  481. package/src/internal/VerifiedTokens.test.ts +1 -1
  482. package/src/internal/WebhookDestination.test.ts +1 -1
  483. package/src/internal/Webhooks.test.ts +7 -7
  484. package/src/internal/Webhooks.ts +4 -4
  485. package/dist/apps/activities.js.map +0 -1
  486. package/dist/apps/addresses.js.map +0 -1
  487. package/dist/apps/balances.d.ts.map +0 -1
  488. package/dist/apps/balances.js.map +0 -1
  489. package/dist/apps/blocks.d.ts.map +0 -1
  490. package/dist/apps/blocks.js.map +0 -1
  491. package/dist/apps/coingecko.d.ts.map +0 -1
  492. package/dist/apps/coingecko.js.map +0 -1
  493. package/dist/apps/exchanges.d.ts.map +0 -1
  494. package/dist/apps/exchanges.js.map +0 -1
  495. package/dist/apps/fee-amm.d.ts.map +0 -1
  496. package/dist/apps/fee-amm.js.map +0 -1
  497. package/dist/apps/indexer.d.ts.map +0 -1
  498. package/dist/apps/indexer.js.map +0 -1
  499. package/dist/apps/receipts.d.ts.map +0 -1
  500. package/dist/apps/receipts.js.map +0 -1
  501. package/dist/apps/rpc.d.ts.map +0 -1
  502. package/dist/apps/rpc.js.map +0 -1
  503. package/dist/apps/tokenlist.d.ts.map +0 -1
  504. package/dist/apps/tokenlist.js.map +0 -1
  505. package/dist/apps/tokens.d.ts.map +0 -1
  506. package/dist/apps/tokens.js.map +0 -1
  507. package/dist/apps/transactions.d.ts.map +0 -1
  508. package/dist/apps/transactions.js.map +0 -1
  509. package/dist/apps/transfers.d.ts.map +0 -1
  510. package/dist/apps/transfers.js.map +0 -1
  511. package/dist/apps/verified-tokens.d.ts.map +0 -1
  512. package/dist/apps/verified-tokens.js.map +0 -1
  513. package/dist/apps/webhooks.js.map +0 -1
  514. package/dist/internal/OpenApi.fonts.d.ts.map +0 -1
  515. package/dist/internal/OpenApi.fonts.js.map +0 -1
  516. package/dist/internal/OpenApi.logo.d.ts.map +0 -1
  517. package/dist/internal/OpenApi.logo.js.map +0 -1
  518. package/src/handlers/internal/multisig.localnet.test.ts +0 -146
  519. /package/src/{internal/OpenApi.fonts.ts → apps/docs/fonts.ts} +0 -0
  520. /package/src/{internal/OpenApi.logo.ts → apps/docs/logo.ts} +0 -0
@@ -1,20 +1,32 @@
1
- import type { Context as hono_Context, Hono, MiddlewareHandler } from 'hono'
1
+ import * as Accounts from 'accounts/server'
2
+ import { Hono } from 'hono'
3
+ import type { Context as hono_Context, MiddlewareHandler } from 'hono'
2
4
  import type { HonoBase } from 'hono/hono-base'
3
5
  import { createMiddleware } from 'hono/factory'
4
6
  import { matchedRoutes } from 'hono/route'
5
7
  import type { MergePath, RouterRoute, Schema as core_Schema } from 'hono/types'
6
8
  import { findTargetHandler } from 'hono/utils/handler'
9
+ import { decodeJwt } from 'jose'
7
10
  import { Credential, type Method } from 'mppx'
8
11
  import { Mppx, tempo } from 'mppx/hono'
12
+ import * as z from 'zod/mini'
9
13
 
14
+ import type * as App from '../App.js'
10
15
  import * as ApiKey from '../ApiKey.js'
11
16
  import * as ApiKeys from '../ApiKeys.js'
17
+ import * as Db from '../db/Db.js'
12
18
  import type * as Store from './Store.js'
19
+ import * as Invitations from '../db/tables/invitations.js'
20
+ import * as Memberships from '../db/tables/memberships.js'
21
+ import * as Metadata from '../apps/metadata.js'
13
22
  import * as OpenApi from './OpenApi.js'
23
+ import * as Organizations from '../db/tables/organizations.js'
24
+ import * as Projects from '../db/tables/projects.js'
14
25
  import * as RateLimit from './RateLimit.js'
15
26
  import * as Response from './Response.js'
16
27
  import * as Schema from './Schema.js'
17
28
  import * as Scope from '../Scope.js'
29
+ import * as Users from '../db/tables/users.js'
18
30
  import * as Viem from './Viem.js'
19
31
 
20
32
  const policySymbol = Symbol('tempo-api.auth.policy')
@@ -36,8 +48,19 @@ export type Context = {
36
48
  | undefined
37
49
  /** MPP payment configuration. */
38
50
  mpp?: Mpp | undefined
51
+ /**
52
+ * Configured default anonymous public quota. Used as the throttle ceiling for
53
+ * sandbox keys without active billing on routes that expose no public lane
54
+ * (where the per-route `policy.public` is absent). Omit to fall back to the
55
+ * framework default.
56
+ */
57
+ publicRateLimit?: RateLimit.Limit | undefined
39
58
  /** Rate-limit store used by API handlers. */
40
59
  rateLimit?: RateLimit.Store | undefined
60
+ /** Session lane resolution (the session surface's capability), or undefined when sessions are unmounted. */
61
+ session?: Session | undefined
62
+ /** Super-admin machine credential (sha256 of the configured secret), or undefined when disabled. */
63
+ superAdmin?: { tokenHash: string } | undefined
41
64
  }
42
65
 
43
66
  /** Hono environment variables set by auth middleware. */
@@ -87,9 +110,11 @@ type Mpp = {
87
110
  /**
88
111
  * Route-level auth policy override. Each lane is independent: `true` enables it
89
112
  * with the middleware defaults, `false` disables it, an object enables it with
90
- * the given overrides, and omitting it inherits the resolved policy. Lanes are
91
- * default-closed except `apiKey` (see {@link middleware}), so a route that
92
- * declares nothing — or declares only `apiKey` — is API-key-only.
113
+ * the given overrides, and omitting it inherits the resolved policy. All lanes
114
+ * are default-closed (see {@link middleware}).
115
+ *
116
+ * Routes grant API-key, public, or paid access explicitly via {@link policy};
117
+ * otherwise only `super_admin` reaches them.
93
118
  */
94
119
  export type PolicyOverride = {
95
120
  /** API-key lane: pass quota/scopes overrides, or a boolean to enable/disable. */
@@ -118,6 +143,8 @@ export type PolicyOverride = {
118
143
  }
119
144
  | boolean
120
145
  | undefined
146
+ /** Session lane: `true` allows signed-in wallet sessions, `false` disables. */
147
+ session?: boolean | undefined
121
148
  }
122
149
 
123
150
  /** Authenticated or anonymous caller. */
@@ -133,6 +160,8 @@ export type Principal =
133
160
  orgId: string
134
161
  /** Payment metadata for paid overflow requests. */
135
162
  payment?: Payment | undefined
163
+ /** Owning project id, when the key carries project attribution. */
164
+ projectId?: string | undefined
136
165
  /** Principal kind. */
137
166
  type: 'api_key'
138
167
  }
@@ -144,13 +173,46 @@ export type Principal =
144
173
  /** Principal kind. */
145
174
  type: 'public'
146
175
  }
176
+ | {
177
+ /** Verified email bound to the session, when present. */
178
+ email?: string | undefined
179
+ /** Principal id: the user id (`usr_…`). */
180
+ id: string
181
+ /** Identity that established the session, as a provider/subject pair (OIDC `iss`/`sub` semantics). */
182
+ identity: {
183
+ /** Identity provider: `wallet` for SIWE sign-in, an OIDC issuer otherwise. */
184
+ provider: string
185
+ /** Provider-scoped subject — the wallet address for `wallet`. */
186
+ subject: string
187
+ }
188
+ /** Payment metadata for paid overflow requests. */
189
+ payment?: Payment | undefined
190
+ /** Principal kind. */
191
+ type: 'session'
192
+ }
193
+ | {
194
+ /** Attributed super admin identity (`'super_admin'` for the configured machine secret). */
195
+ actor: string
196
+ /** Principal id. */
197
+ id: string
198
+ /** Payment metadata for paid overflow requests. */
199
+ payment?: Payment | undefined
200
+ /** Principal kind. */
201
+ type: 'super_admin'
202
+ }
147
203
 
148
204
  /** Hono variables set by auth middleware. */
149
205
  export type Variables = {
150
206
  /** Auth context consumed by protected handlers. */
151
207
  auth: Context
208
+ /** Caller's membership resolved by {@link ensureOrg}; absent for `super_admin`. */
209
+ membership?: Memberships.Record | undefined
210
+ /** Organization resolved by {@link ensureOrg} for the current request. */
211
+ org?: Organizations.Record | undefined
152
212
  /** Authenticated or anonymous caller. */
153
213
  principal?: Principal | undefined
214
+ /** Project resolved by {@link ensureProject} for the current request. */
215
+ project?: Projects.Record | undefined
154
216
  }
155
217
 
156
218
  /** Reads the authenticated principal from a Hono context. */
@@ -158,6 +220,147 @@ export function getPrincipal<environment extends Environment>(c: hono_Context<en
158
220
  return c.get('principal') ?? null
159
221
  }
160
222
 
223
+ /**
224
+ * Guards org-scoped routes (`:orgId`). Sessions reach only organizations they
225
+ * are members of; the super admin reaches any. Missing or foreign
226
+ * organizations return `404`, so foreign org ids are indistinguishable from
227
+ * absent ones; a member below the required role gets `403` (the resource is
228
+ * visible to them, the action is not). The resolved organization and
229
+ * membership land on context — read them with {@link org} and
230
+ * {@link membership}.
231
+ */
232
+ export function ensureOrg(options: ensureOrg.Options = {}): MiddlewareHandler {
233
+ const { role: minimum = 'member' } = options
234
+ // Typed loosely (like {@link policy}) so route chains keep their inferred
235
+ // schema; a concrete environment parameter collapses Hono's inference.
236
+ return createMiddleware<Environment>(async (c, next) => {
237
+ const notFound = () =>
238
+ Response.error(c, {
239
+ code: 'organization_not_found',
240
+ message: 'Organization not found',
241
+ status: 404,
242
+ })
243
+ const orgId = c.req.param('orgId' as never) as string | undefined
244
+ if (!orgId) return notFound()
245
+ const principal = getPrincipal(c)
246
+ if (principal?.type !== 'session' && principal?.type !== 'super_admin') return notFound()
247
+ const db = Db.get(contextDb(c))
248
+ const record = await Organizations.get(db, orgId)
249
+ if (!record) return notFound()
250
+ if (principal.type !== 'super_admin') {
251
+ const member = await Memberships.get(db, orgId, principal.id)
252
+ if (!member) return notFound()
253
+ if (Memberships.rank[member.role] < Memberships.rank[minimum])
254
+ return Response.error(c, {
255
+ code: 'forbidden',
256
+ message: `Requires the ${minimum} role`,
257
+ status: 403,
258
+ })
259
+ c.set('membership', member)
260
+ }
261
+ c.set('org', record)
262
+ await next()
263
+ return undefined
264
+ })
265
+ }
266
+
267
+ export declare namespace ensureOrg {
268
+ /** Options for the org guard. */
269
+ type Options = {
270
+ /** Minimum role required; defaults to `member` (any membership). */
271
+ role?: Memberships.Role | undefined
272
+ }
273
+ }
274
+
275
+ /**
276
+ * Guards project-scoped routes (`:projectId`) nested under {@link ensureOrg},
277
+ * which must run first. Projects resolve only within the scoped organization;
278
+ * missing or cross-org projects return `404`. The resolved project lands on
279
+ * context — read it with {@link project}.
280
+ */
281
+ export function ensureProject(): MiddlewareHandler {
282
+ // Typed loosely (like {@link policy}) so route chains keep their inferred
283
+ // schema; a concrete environment parameter collapses Hono's inference.
284
+ return createMiddleware<Environment>(async (c, next) => {
285
+ const notFound = () =>
286
+ Response.error(c, {
287
+ code: 'project_not_found',
288
+ message: 'Project not found',
289
+ status: 404,
290
+ })
291
+ const projectId = c.req.param('projectId' as never) as string | undefined
292
+ if (!projectId) return notFound()
293
+ const scoped = c.get('org')
294
+ if (!scoped) return notFound()
295
+ const record = await Projects.get(Db.get(contextDb(c)), projectId)
296
+ if (!record || record.orgId !== scoped.id) return notFound()
297
+ c.set('project', record)
298
+ await next()
299
+ return undefined
300
+ })
301
+ }
302
+
303
+ /**
304
+ * Always false at runtime, but typed as `boolean` so handlers can include a
305
+ * never-reached scope-error branch in Hono's inferred response union (the
306
+ * ensure middlewares produce these `404`s before the handler runs).
307
+ */
308
+ export const narrowScope = false as boolean
309
+
310
+ /** Typed org-scope error response for Hono client inference. Never reached at runtime. */
311
+ export function ensureOrgError(c: hono_Context) {
312
+ return Response.error(c, {
313
+ code: 'organization_not_found',
314
+ message: 'Organization not found',
315
+ status: 404,
316
+ })
317
+ }
318
+
319
+ /** Typed project-scope error response for Hono client inference. Never reached at runtime. */
320
+ export function ensureProjectError(c: hono_Context) {
321
+ // Project routes run the org scope first, so either not-found can surface.
322
+ const code =
323
+ c.req.header('tempo-narrow-scope-code') === 'organization_not_found'
324
+ ? ('organization_not_found' as const)
325
+ : ('project_not_found' as const)
326
+ return Response.error(c, {
327
+ code,
328
+ message: 'Project not found',
329
+ status: 404,
330
+ })
331
+ }
332
+
333
+ /** Reads the caller's membership resolved by {@link ensureOrg}; `undefined` for `super_admin`. */
334
+ export function membership<environment extends Environment>(
335
+ c: hono_Context<environment>,
336
+ ): Memberships.Record | undefined {
337
+ return c.get('membership')
338
+ }
339
+
340
+ /** Reads the organization resolved by {@link ensureOrg}; throws when the middleware did not run. */
341
+ export function org<environment extends Environment>(
342
+ c: hono_Context<environment>,
343
+ ): Organizations.Record {
344
+ const record = c.get('org')
345
+ if (!record) throw new Error('`orgScope` middleware must run before reading the organization')
346
+ return record
347
+ }
348
+
349
+ /** Reads the project resolved by {@link ensureProject}; throws when the middleware did not run. */
350
+ export function project<environment extends Environment>(
351
+ c: hono_Context<environment>,
352
+ ): Projects.Record {
353
+ const record = c.get('project')
354
+ if (!record) throw new Error('`projectScope` middleware must run before reading the project')
355
+ return record
356
+ }
357
+
358
+ // Reads the app's database source from request context (set by `App.create`
359
+ // from its `db` option).
360
+ function contextDb(c: hono_Context): Db.Source {
361
+ return (c.get as (key: string) => unknown)('db') as Db.Source
362
+ }
363
+
161
364
  /**
162
365
  * Always false at runtime, but typed as `boolean` so handlers can include a
163
366
  * never-reached access-error branch in Hono's inferred response union.
@@ -201,6 +404,33 @@ export function accessError(c: hono_Context) {
201
404
  })
202
405
  }
203
406
 
407
+ /** Typed lane-less policy error response for Hono client inference. Never reached at runtime. */
408
+ export function superAdminAccessError(c: hono_Context) {
409
+ const status = Number(c.req.header('tempo-narrow-access-status'))
410
+ if (status === 400)
411
+ return Response.error(c, {
412
+ code: 'api_key_malformed',
413
+ message: 'Malformed API key',
414
+ status: 400,
415
+ })
416
+ if (status === 401) {
417
+ const code =
418
+ c.req.header('tempo-narrow-access-code') === 'api_key_missing'
419
+ ? 'api_key_missing'
420
+ : 'api_key_invalid'
421
+ return Response.error(c, {
422
+ code,
423
+ message: 'Missing or invalid API key',
424
+ status: 401,
425
+ })
426
+ }
427
+ return Response.error(c, {
428
+ code: 'forbidden',
429
+ message: 'API key not permitted for this route',
430
+ status: 403,
431
+ })
432
+ }
433
+
204
434
  /** Typed auth/payment error response for Hono client inference. Never reached at runtime. */
205
435
  export function paidAccessError(c: hono_Context) {
206
436
  const status = Number(c.req.header('tempo-narrow-access-status'))
@@ -217,7 +447,9 @@ export function middleware<
217
447
  >(options: middleware.Options<endpoint>): MiddlewareHandler<environment> {
218
448
  const { charge = {}, ...mpp } = options.mpp === false ? {} : (options.mpp ?? {})
219
449
  const defaults = laneDefaults(options)
220
- const base = basePolicy(options, defaults)
450
+ // Default-deny: no lanes open until a route grants them via `policy`.
451
+ // Otherwise the `super_admin` principal bypasses lane checks entirely.
452
+ const base: require.Policy = {}
221
453
  // Keys resolve against the app's KV state store on request context
222
454
  // (`App.create({ kv })`), so the middleware needs no storage wiring; when no
223
455
  // store is configured, every token resolves to `null` (closed).
@@ -242,6 +474,10 @@ export function middleware<
242
474
  resolve,
243
475
  },
244
476
  }),
477
+ // The throttle ceiling for sandbox keys without active billing, so
478
+ // API-key-only routes (no per-route `policy.public`) still fall back to the
479
+ // deployment's configured public quota.
480
+ publicRateLimit: defaults.public.rateLimit,
245
481
  ...(options.mpp === false
246
482
  ? {}
247
483
  : {
@@ -252,6 +488,10 @@ export function middleware<
252
488
  }),
253
489
  }),
254
490
  rateLimit: RateLimit.memory(options.rateLimit),
491
+ ...(options.session ? { session: options.session } : {}),
492
+ ...(options.superAdmin?.secret
493
+ ? { superAdmin: { tokenHash: ApiKey.hash(options.superAdmin.secret) } }
494
+ : {}),
255
495
  }
256
496
 
257
497
  return createMiddleware<environment>((c, next) => {
@@ -314,6 +554,14 @@ export declare namespace middleware {
314
554
  | undefined
315
555
  /** Rate-limit store options. */
316
556
  rateLimit?: RateLimit.memory.Options | undefined
557
+ /** Session lane resolution — the session surface's capability; omit to leave the session lane closed. */
558
+ session?: Session | undefined
559
+ /**
560
+ * Super-admin machine credential. A presented API-key token matching this
561
+ * secret resolves to a `super_admin` principal — bypassing scope and quota
562
+ * checks — before the key lookup runs. Omit to disable the entry entirely.
563
+ */
564
+ superAdmin?: { secret: string } | undefined
317
565
  }
318
566
 
319
567
  /** Resolve-cache tuning for API-key resolution. */
@@ -333,6 +581,363 @@ export declare namespace middleware {
333
581
  }
334
582
  }
335
583
 
584
+ /** Zod schemas documenting the session sign-in surface (`/v1/auth/siwe`). */
585
+ namespace schema {
586
+ const siweMessage =
587
+ 'api.tempo.xyz wants you to sign in with your Ethereum account:\n0x0000000000000000000000000000000000000000\n\nURI: https://api.tempo.xyz\nVersion: 1\nChain ID: 0\nNonce: 3D0sZfnHqTBmc9tKR\nIssued At: 2026-01-01T00:00:00.000Z\nExpiration Time: 2026-01-01T00:10:00.000Z'
588
+
589
+ /** Error body returned by the sign-in handler (SDK shape, not the API error envelope). */
590
+ export const HandlerError = z.object({
591
+ error: z
592
+ .string()
593
+ .check(
594
+ z.describe('Human-readable failure reason.'),
595
+ z.meta({ examples: ['domain mismatch'] }),
596
+ ),
597
+ issues: z
598
+ .optional(z.array(z.unknown()))
599
+ .check(z.describe('Field-level validation issues, present on request-schema failures.')),
600
+ })
601
+
602
+ /** Schemas for `POST /v1/auth/siwe/challenge`. */
603
+ export namespace challenge {
604
+ /** Request body. Accepted fields beyond this document pass through to the SDK handler. */
605
+ export const Body = z.object({})
606
+
607
+ /** Response body. */
608
+ export const Response = z.object({
609
+ message: z
610
+ .string()
611
+ .check(
612
+ z.describe('Single-use EIP-4361 (SIWE) message to sign and submit for verification.'),
613
+ z.meta({ examples: [siweMessage] }),
614
+ ),
615
+ })
616
+ }
617
+
618
+ /** Schemas for `POST /v1/auth/siwe` (verify). */
619
+ export namespace verify {
620
+ /** Request body. */
621
+ export const Body = z.object({
622
+ address: z
623
+ .string()
624
+ .check(
625
+ z.describe('Wallet address that signed the message; becomes the session subject.'),
626
+ z.meta({ examples: ['0x0000000000000000000000000000000000000001'] }),
627
+ ),
628
+ idToken: z.optional(z.string()).check(
629
+ z.describe('Wallet-minted OIDC identity token (JWT) asserting a verified email, folded onto the session.'), // prettier-ignore
630
+ z.meta({ examples: ['eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIweDAwMDAifQ.c2lnbmF0dXJl'] }),
631
+ ),
632
+ keyAuthorization: z.optional(z.string()).check(
633
+ z.describe('RLP-serialized signed key authorization (TIP-1053) whose witness binds this message; verification recovers over its digest.'), // prettier-ignore
634
+ z.meta({ examples: [`0x${'aa'.repeat(60)}`] }),
635
+ ),
636
+ message: z
637
+ .string()
638
+ .check(
639
+ z.describe('The exact challenge message issued by `POST /v1/auth/siwe/challenge`.'),
640
+ z.meta({ examples: [siweMessage] }),
641
+ ),
642
+ returnToken: z.optional(z.boolean()).check(
643
+ z.describe('Return the session token in the body instead of setting the session cookie.'), // prettier-ignore
644
+ z.meta({ examples: [true] }),
645
+ ),
646
+ signature: z
647
+ .string()
648
+ .check(
649
+ z.describe('Signature over the challenge message (or the key-authorization digest).'),
650
+ z.meta({ examples: [`0x${'aa'.repeat(65)}`] }),
651
+ ),
652
+ })
653
+
654
+ /** Response body. */
655
+ export const Response = z.object({
656
+ token: z
657
+ .optional(z.string())
658
+ .check(
659
+ z.describe('Bearer session token; present only when `returnToken` is true.'),
660
+ z.meta({ examples: ['aa'.repeat(32)] }),
661
+ ),
662
+ })
663
+ }
664
+ }
665
+
666
+ /** `{ error }` response for a sign-in handler failure status. */
667
+ function handlerError(description: string) {
668
+ return {
669
+ content: { 'application/json': { schema: OpenApi.resolver(schema.HandlerError) } },
670
+ description,
671
+ }
672
+ }
673
+
674
+ /**
675
+ * Builds the built-in auth pieces from `App.create`'s `auth` option: the
676
+ * session-surface app to mount (when `session` is configured) and the
677
+ * enforcement middleware, wired with the surface's resolver. `auth: false`
678
+ * disables both.
679
+ */
680
+ export function install(options: install.Options) {
681
+ const { db, kv } = options
682
+ const { session: options_session, ...options_middleware } =
683
+ options.auth === false ? {} : (options.auth ?? {})
684
+ if (options_session && !kv)
685
+ throw new Error('`auth.session` requires the `kv` state store: sessions and sign-in challenges persist there.') // prettier-ignore
686
+
687
+ // Session sign-in surface (challenge, verify, logout — SIWE wallet sign-in
688
+ // today) plus the capability the session lane resolves through. Public but
689
+ // policy-gated: the hidden describeRoute engages the middleware for the
690
+ // mounted sub-app, and the public lane rate-limits challenge-store writes.
691
+ const app = (() => {
692
+ if (!options_session || !kv) return undefined
693
+ const { issuer, origin, requireEmail, ttl } =
694
+ (typeof options_session === 'object' ? options_session.wallet : undefined) ?? {}
695
+ // Case-insensitive exact-match email-domain allowlist for new-user sign-ups.
696
+ const emailAllowlist = (
697
+ typeof options_session === 'object' ? options_session.emailAllowlist : undefined
698
+ )
699
+ ?.map((domain) => domain.trim().toLowerCase())
700
+ .filter(Boolean)
701
+
702
+ const handler = Accounts.Handler.auth({
703
+ ...(issuer || requireEmail
704
+ ? { identity: { ...(issuer ? { issuer } : {}), ...(requireEmail ? { required: true } : {}) } } // prettier-ignore
705
+ : {}),
706
+ // Sign-in upserts the `users` row keyed by address.
707
+ onAuthenticate: async ({ address }) => {
708
+ await Users.upsertByAddress(Db.get(db), { address })
709
+ },
710
+ // No pinned origin: on Workers the edge proxy headers are trustworthy, so
711
+ // opt in explicitly (the SDK's guard ignores its own Workers default).
712
+ // Off-Workers the SDK keeps throwing until `wallet.origin` is set.
713
+ ...(origin
714
+ ? { origin }
715
+ : globalThis.navigator?.userAgent === 'Cloudflare-Workers'
716
+ ? { trustProxy: true }
717
+ : {}),
718
+ // The app's KV store under the SDK's Kv contract: JSON values,
719
+ // second-based TTLs, `auth:`-prefixed keys disjoint from API-key
720
+ // records. `take` is omitted; the SDK falls back to get+delete.
721
+ store: {
722
+ async delete(key) {
723
+ await kv.store.delete(`auth:${key}`)
724
+ },
725
+ async get(key) {
726
+ const value = await kv.store.get(`auth:${key}`)
727
+ return value === null ? undefined : (JSON.parse(value) as never)
728
+ },
729
+ async set(key, value, options) {
730
+ await kv.store.put(
731
+ `auth:${key}`,
732
+ JSON.stringify(value),
733
+ options?.ttl ? { ttl: options.ttl * 1_000 } : {},
734
+ )
735
+ },
736
+ },
737
+ ...(ttl ? { ttl } : {}),
738
+ })
739
+
740
+ const session: Session = {
741
+ resolve: async (c) => {
742
+ const payload = await handler.getSession(c.req.raw)
743
+ if (!payload) return null
744
+ const database = Db.get(db)
745
+ const user = await Users.getByAddress(database, payload.address)
746
+ if (!user) return null
747
+ // Fold a newly verified email onto the user row: identity tokens are
748
+ // optional per sign-in, so the column fills lazily.
749
+ if (payload.email && user.email !== payload.email)
750
+ await Users.setEmail(database, user.id, payload.email)
751
+ const email = payload.email ?? user.email ?? undefined
752
+ return {
753
+ ...(email ? { email } : {}),
754
+ id: user.id,
755
+ identity: { provider: 'wallet', subject: payload.address },
756
+ type: 'session',
757
+ }
758
+ },
759
+ }
760
+
761
+ const policy_public = policy({ public: true })
762
+ const policy_session = policy({ public: true, session: true })
763
+ const describe = OpenApi.describeRoute({ hide: true })
764
+
765
+ // Sign-up gate: new addresses need an allowlisted identity email domain
766
+ // or a pending invitation; existing users pass. The claim decodes
767
+ // unverified; the mounted handler verifies the token before any session
768
+ // or user is created, so a forged claim never authenticates.
769
+ const gate_signup = createMiddleware<App.Environment>(async (c, next) => {
770
+ if (!emailAllowlist?.length) return next()
771
+ const { address, idToken } = (await c.req.json()) as z.output<typeof schema.verify.Body>
772
+ const database = Db.get(db)
773
+ const user = await Users.getByAddress(database, address)
774
+ if (!user) {
775
+ const email = (() => {
776
+ if (!idToken) return undefined
777
+ try {
778
+ const { email } = decodeJwt<{ email?: string }>(idToken)
779
+ return typeof email === 'string' ? email.toLowerCase() : undefined
780
+ } catch {
781
+ return undefined
782
+ }
783
+ })()
784
+ const allowed = await (async () => {
785
+ if (!email) return false
786
+ const at = email.lastIndexOf('@')
787
+ if (at > 0 && emailAllowlist.includes(email.slice(at + 1))) return true
788
+ // A pending invitation admits the email regardless of domain.
789
+ return (await Invitations.listByEmail(database, email)).length > 0
790
+ })()
791
+ if (!allowed)
792
+ return c.json({ error: 'sign-ups are restricted to approved email domains or invited emails' }, 403) // prettier-ignore
793
+ }
794
+ return next()
795
+ })
796
+
797
+ const app = new Hono<App.Environment>()
798
+ .use('/v1/auth/siwe', policy_public, describe)
799
+ .use('/v1/auth/siwe/*', policy_public, describe)
800
+ // Documented operations: pre-routes carrying the OpenAPI metadata and the
801
+ // envelope-shaped body validation; the mounted handler owns behavior.
802
+ // Statement form keeps them off the app type (`hc` surface).
803
+ app.post(
804
+ '/v1/auth/siwe/challenge',
805
+ policy_public,
806
+ OpenApi.validate('json', schema.challenge.Body, {
807
+ code: 'body_invalid',
808
+ message: 'Invalid request body',
809
+ }),
810
+ OpenApi.describeRoute({
811
+ description:
812
+ 'Issues a single-use SIWE challenge message to sign. The challenge expires after a short TTL.',
813
+ // Overrides the surface-wide hidden mount guard for this documented route.
814
+ hide: false,
815
+ operationId: 'createSiweChallenge',
816
+ responses: {
817
+ 200: {
818
+ content: { 'application/json': { schema: OpenApi.resolver(schema.challenge.Response) } }, // prettier-ignore
819
+ description: 'The challenge message to sign.',
820
+ headers: { ...OpenApi.successHeaders },
821
+ },
822
+ 400: OpenApi.standardError(400, 'Malformed request body.', ['body_invalid']),
823
+ 429: OpenApi.standardError(429, 'Rate limited.'),
824
+ 500: OpenApi.standardError(500, 'Internal server error.'),
825
+ },
826
+ summary: 'Create challenge (SIWE)',
827
+ tags: ['Authentication'],
828
+ }),
829
+ )
830
+ app.post(
831
+ '/v1/auth/siwe',
832
+ policy_public,
833
+ OpenApi.validate('json', schema.verify.Body, {
834
+ code: 'body_invalid',
835
+ message: 'Invalid request body',
836
+ }),
837
+ gate_signup,
838
+ OpenApi.describeRoute({
839
+ description:
840
+ 'Verifies the signed challenge and establishes a session. Sets a session cookie by default; pass `returnToken` to receive a bearer token instead.',
841
+ hide: false,
842
+ operationId: 'verifySiwe',
843
+ responses: {
844
+ 200: {
845
+ content: { 'application/json': { schema: OpenApi.resolver(schema.verify.Response) } },
846
+ description: 'Session established. Cookie mode sets `Set-Cookie`; token mode returns `token`.', // prettier-ignore
847
+ headers: {
848
+ ...OpenApi.successHeaders,
849
+ 'Set-Cookie': {
850
+ description: 'Session cookie (`accounts_auth`); omitted when `returnToken` is true.', // prettier-ignore
851
+ schema: { type: 'string' },
852
+ },
853
+ },
854
+ },
855
+ 400: handlerError('Invalid, expired, or mismatched challenge message; or a required identity token is missing.'), // prettier-ignore
856
+ 401: handlerError('Signature or identity verification failed.'),
857
+ ...(emailAllowlist?.length
858
+ ? { 403: handlerError('Sign-up rejected: the identity email is neither on an allowlisted domain nor pending an invitation.') } // prettier-ignore
859
+ : {}),
860
+ 409: handlerError('Challenge nonce already used or unknown.'),
861
+ 429: OpenApi.standardError(429, 'Rate limited.'),
862
+ 500: OpenApi.standardError(500, 'Internal server error.'),
863
+ },
864
+ summary: 'Authenticate (SIWE)',
865
+ tags: ['Authentication'],
866
+ }),
867
+ )
868
+ app.post(
869
+ '/v1/auth/siwe/logout',
870
+ policy_session,
871
+ OpenApi.describeRoute({
872
+ description: 'Revokes the current session and clears the session cookie.',
873
+ hide: false,
874
+ operationId: 'siweLogout',
875
+ responses: {
876
+ 204: { description: 'Session revoked and cookie cleared.' },
877
+ 429: OpenApi.standardError(429, 'Rate limited.'),
878
+ 500: OpenApi.standardError(500, 'Internal server error.'),
879
+ },
880
+ summary: 'Sign out',
881
+ tags: ['Authentication'],
882
+ }),
883
+ )
884
+ return Object.assign(
885
+ // The tag joins the `Platform API` group at `platform()`'s chosen slot;
886
+ // group listings filter to defined tags, so it drops out when unmounted.
887
+ Metadata.attach(app.route('/v1/auth/siwe', handler), {
888
+ tags: [{ name: 'Authentication', description: 'Authenticate into the Tempo Platform.' }],
889
+ }),
890
+ { session },
891
+ )
892
+ })()
893
+
894
+ const middleware_ =
895
+ options.auth === false
896
+ ? undefined
897
+ : middleware({ ...options_middleware, ...(app ? { session: app.session } : {}) })
898
+
899
+ return { app, middleware: middleware_ }
900
+ }
901
+
902
+ export declare namespace install {
903
+ /** Options for building the built-in auth pieces. */
904
+ type Options = {
905
+ auth:
906
+ | (Omit<middleware.Options, 'session'> & {
907
+ /** Session sign-in surface: mounts `/v1/auth/siwe` (challenge/verify/logout) and enables the session lane. Pass true for defaults or a config object. Requires `kv`. */
908
+ session?: Session | undefined
909
+ })
910
+ | false
911
+ | undefined
912
+ /** Authoritative database holding user rows. */
913
+ db: Db.Source
914
+ /** KV state store, or undefined when key auth is closed. */
915
+ kv: { store: Store.State } | undefined
916
+ }
917
+
918
+ /** Session sign-in surface configuration: `true` for defaults, or a config object. */
919
+ type Session =
920
+ | boolean
921
+ | {
922
+ /** Restrict new-user sign-ups to verified emails on these domains (case-insensitive exact match); invited emails and existing users sign in unaffected. */
923
+ emailAllowlist?: readonly string[] | undefined
924
+ /** SIWE wallet sign-in configuration. */
925
+ wallet?: Wallet | undefined
926
+ }
927
+
928
+ /** SIWE wallet sign-in configuration. */
929
+ type Wallet = {
930
+ /** Identity (verified email) issuer override; defaults to the Tempo wallet's production OIDC mount. */
931
+ issuer?: string | undefined
932
+ /** Pinned absolute origin for SIWE domain binding. Required off-Workers; on Workers the edge proxy headers are trusted by default. */
933
+ origin?: string | undefined
934
+ /** Reject sign-ins that carry no valid identity token, so every session holds a verified email. */
935
+ requireEmail?: boolean | undefined
936
+ /** Session and challenge TTL overrides, in seconds. */
937
+ ttl?: { challenge?: number | undefined; session?: number | undefined } | undefined
938
+ }
939
+ }
940
+
336
941
  /** Creates route middleware that contributes an auth policy override. */
337
942
  export function policy(options: PolicyOverride): MiddlewareHandler {
338
943
  const middleware = createMiddleware((_, next) => next()) as MiddlewareHandler & PolicyHandler
@@ -376,6 +981,7 @@ type LaneDefaults = {
376
981
  apiKey: NonNullable<require.Policy['apiKey']>
377
982
  mpp: NonNullable<require.Policy['mpp']>
378
983
  public: NonNullable<require.Policy['public']>
984
+ session: NonNullable<require.Policy['session']>
379
985
  }
380
986
 
381
987
  type OpenApiHandler = {
@@ -409,19 +1015,10 @@ function laneDefaults(options: middleware.Options): LaneDefaults {
409
1015
  },
410
1016
  mpp: { charge: {} },
411
1017
  public: { rateLimit: public_?.rateLimit ?? { perMinute: 60 } },
1018
+ session: true,
412
1019
  }
413
1020
  }
414
1021
 
415
- /**
416
- * Base policy before route overrides. Default-closed except the API-key lane:
417
- * a route that declares nothing is API-key-only, and must opt into the public
418
- * and paid lanes explicitly. This makes a partial override fail safe — omitting
419
- * a lane leaves it closed rather than silently inheriting open access.
420
- */
421
- function basePolicy(options: middleware.Options, defaults: LaneDefaults): require.Policy {
422
- return options.apiKey === false ? {} : { apiKey: defaults.apiKey }
423
- }
424
-
425
1022
  function resolvePolicy(
426
1023
  base: require.Policy,
427
1024
  defaults: LaneDefaults,
@@ -431,6 +1028,7 @@ function resolvePolicy(
431
1028
  let apiKey = base.apiKey
432
1029
  let mpp = base.mpp
433
1030
  let public_ = base.public
1031
+ let session = base.session
434
1032
 
435
1033
  for (const override of overrides) {
436
1034
  if (override === false) return undefined
@@ -438,6 +1036,7 @@ function resolvePolicy(
438
1036
  apiKey = resolveApiKeyLane(apiKey, override.apiKey, defaults.apiKey)
439
1037
  mpp = resolveMppLane(mpp, override.mpp, defaults.mpp)
440
1038
  public_ = resolvePublicLane(public_, override.public, defaults.public)
1039
+ session = resolveSessionLane(session, override.session, defaults.session)
441
1040
  }
442
1041
 
443
1042
  // The OpenAPI summary/description rides along as the payment-challenge
@@ -446,11 +1045,13 @@ function resolvePolicy(
446
1045
  if (mpp && description !== undefined && mpp.charge.description === undefined)
447
1046
  mpp = { charge: { ...mpp.charge, description } }
448
1047
 
449
- if (!apiKey && !mpp && !public_) return undefined
1048
+ // A lane-less policy stays enforced for super admin; only an explicit
1049
+ // `false` override opts a route out of auth entirely.
450
1050
  return {
451
1051
  ...(apiKey ? { apiKey } : {}),
452
1052
  ...(mpp ? { mpp } : {}),
453
1053
  ...(public_ ? { public: public_ } : {}),
1054
+ ...(session ? { session } : {}),
454
1055
  }
455
1056
  }
456
1057
 
@@ -493,6 +1094,16 @@ function resolvePublicLane(
493
1094
  return { rateLimit: override.rateLimit ?? base.rateLimit }
494
1095
  }
495
1096
 
1097
+ function resolveSessionLane(
1098
+ current: require.Policy['session'],
1099
+ override: PolicyOverride['session'],
1100
+ default_: LaneDefaults['session'],
1101
+ ): require.Policy['session'] {
1102
+ if (override === undefined) return current
1103
+ if (override === false) return undefined
1104
+ return current ?? default_
1105
+ }
1106
+
496
1107
  function openApiRoute(c: hono_Context): OpenApiRoute | undefined {
497
1108
  const route = matchedRoutes(c)
498
1109
  .slice(c.req.routeIndex + 1)
@@ -530,20 +1141,24 @@ function policyFromRoute(route: RouterRoute) {
530
1141
 
531
1142
  /** Resolved access lanes for one documented operation. */
532
1143
  export type OperationAccess = {
1144
+ /** Whether API-key access is allowed. */
1145
+ apiKey: boolean
533
1146
  /** Whether MPP payment is accepted (enables the `402` payment-challenge response). */
534
1147
  mpp: boolean
535
1148
  /** Whether anonymous public-quota access is allowed (keeps the anonymous security option). */
536
1149
  public: boolean
537
1150
  /** Required API-key scopes (`[]` accepts any valid key); empty when the API-key lane is disabled. */
538
1151
  scopes: readonly Scope.Id[]
1152
+ /** Whether signed-in sessions are allowed. */
1153
+ session: boolean
539
1154
  }
540
1155
 
541
1156
  /**
542
1157
  * Maps each documented operation id to its resolved access lanes by reading the
543
1158
  * `policy` overrides co-located on the app's routes. This is the single source
544
1159
  * of truth for per-operation `security` and the `402` response in the generated
545
- * OpenAPI document (see `App.create`): the API-key lane is default-on, and the
546
- * public/MPP lanes apply only where a route opts in via {@link policy}.
1160
+ * OpenAPI document (see `App.create`): all lanes are default-closed, and an
1161
+ * operation surfaces only the lanes its route opts into via {@link policy}.
547
1162
  */
548
1163
  export function describeAccess(app: Hono<any, any>): Record<string, OperationAccess> {
549
1164
  const groups = new Map<string, { operationId?: string; overrides: PolicyOverride[] }>()
@@ -560,18 +1175,20 @@ export function describeAccess(app: Hono<any, any>): Record<string, OperationAcc
560
1175
  const access: Record<string, OperationAccess> = {}
561
1176
  for (const { operationId, overrides } of groups.values()) {
562
1177
  if (!operationId) continue
563
- let apiKey = true
1178
+ let apiKey = false
564
1179
  let scopes: readonly Scope.Id[] = []
565
1180
  let public_ = false
566
1181
  let mpp = false
1182
+ let session = false
567
1183
  for (const override of overrides) {
568
1184
  if (override.apiKey !== undefined) apiKey = override.apiKey !== false
569
1185
  if (override.apiKey && override.apiKey !== true && override.apiKey.scopes)
570
1186
  scopes = override.apiKey.scopes
571
1187
  if (override.public !== undefined) public_ = override.public !== false
572
1188
  if (override.mpp !== undefined) mpp = override.mpp !== false
1189
+ if (override.session !== undefined) session = override.session !== false
573
1190
  }
574
- access[operationId] = { mpp, public: public_, scopes: apiKey ? scopes : [] }
1191
+ access[operationId] = { apiKey, mpp, public: public_, scopes: apiKey ? scopes : [], session }
575
1192
  }
576
1193
  return access
577
1194
  }
@@ -627,82 +1244,133 @@ export function require<environment extends Environment = Environment>(
627
1244
 
628
1245
  const hasPayment = hasPaymentCredential(c) && isPaymentEnabled(auth, policy)
629
1246
  if (credential) {
1247
+ // The super admin secret precedes key lookup: it is not a stored key.
1248
+ // It must never reach KV resolution or scope/quota checks.
1249
+ if (auth.superAdmin && ApiKey.hash(credential.token) === auth.superAdmin.tokenHash) {
1250
+ c.set('principal', { actor: 'super_admin', id: 'super_admin', type: 'super_admin' })
1251
+ return next()
1252
+ }
1253
+
630
1254
  const apiKey = await auth.apiKey?.resolve(c, credential.token)
631
- if (!apiKey)
1255
+ if (!apiKey) {
1256
+ // A bearer session token is indistinguishable from an API key at the
1257
+ // header, so a failed key lookup falls through to the session lane
1258
+ // before rejecting.
1259
+ if (policy.session) {
1260
+ const session = await resolveSession(c, auth)
1261
+ if (session) {
1262
+ c.set('principal', session)
1263
+ return next()
1264
+ }
1265
+ }
632
1266
  return jsonError(c, {
633
1267
  code: 'api_key_invalid',
634
1268
  message: 'Invalid API key',
635
1269
  status: 401,
636
1270
  })
1271
+ }
637
1272
 
638
- // Sandbox keys are restricted to non-mainnet chains. An explicit
639
- // `chainId` of mainnet makes the key unusable for this request, so it is
640
- // ignored and the request falls through to the public policy below (as if
641
- // no key were presented). A request that omits `chainId` is steered to
642
- // testnet so a sandbox key still "just works" without passing `?chainId`.
1273
+ // Sandbox keys are testnet-only. A sandbox key that explicitly targets
1274
+ // mainnet (`?chainId`) is rejected with a clear message rather than
1275
+ // steered or downgraded to anonymous, so the caller learns their key does
1276
+ // not cover mainnet. The no-`chainId` default (mainnet) is enforced by
1277
+ // the data group's chain guard, which knows the route is chain-scoped;
1278
+ // the relay resolves its chain from the request body and enforces the
1279
+ // same rule in its sponsor validation.
643
1280
  const explicitChain = explicitChainId(c)
644
- const sandboxOnMainnet =
1281
+
1282
+ const principal = principalFromApiKey(apiKey)
1283
+ c.set('principal', principal)
1284
+
1285
+ if (
645
1286
  apiKey.environment === 'sandbox' &&
646
1287
  explicitChain !== undefined &&
647
1288
  Viem.isMainnet(explicitChain)
1289
+ )
1290
+ return jsonError(c, {
1291
+ code: 'api_key_forbidden',
1292
+ message: 'Sandbox API keys only support testnet. Pass a testnet `chainId`.',
1293
+ status: 403,
1294
+ })
648
1295
 
649
- if (!sandboxOnMainnet) {
650
- if (
651
- apiKey.environment === 'sandbox' &&
652
- explicitChain === undefined &&
653
- Viem.isMainnet((c.get('chainId' as never) as number | undefined) ?? Viem.defaultChainId)
654
- )
655
- // `chainId` lives on the app's `Variables`, not the narrower auth
656
- // contract, so it is read/written through an untyped cast.
657
- c.set('chainId' as never, Viem.chainId.testnet as never)
658
-
659
- const principal = principalFromApiKey(apiKey)
660
- c.set('principal', principal)
661
- const missing = apiKey.scopes.includes(Scope.wildcard)
662
- ? []
663
- : (policy.apiKey?.scopes.filter((scope) => !apiKey.scopes.includes(scope)) ?? [])
664
- if (missing.length > 0)
665
- return jsonError(c, {
666
- code: 'api_key_forbidden',
667
- message: 'API key missing required scope',
668
- status: 403,
669
- })
1296
+ // Default-deny: routes with no API-key lane allow only the super admin.
1297
+ // A valid key is authenticated but forbidden.
1298
+ if (!policy.apiKey)
1299
+ return jsonError(c, {
1300
+ code: 'forbidden',
1301
+ message: 'API key not permitted for this route',
1302
+ status: 403,
1303
+ })
1304
+ const missing = apiKey.scopes.includes(Scope.wildcard)
1305
+ ? []
1306
+ : (policy.apiKey?.scopes.filter((scope) => !apiKey.scopes.includes(scope)) ?? [])
1307
+ if (missing.length > 0)
1308
+ return jsonError(c, {
1309
+ code: 'api_key_forbidden',
1310
+ message: 'API key missing required scope',
1311
+ status: 403,
1312
+ })
1313
+
1314
+ // Sandbox keys without active billing are throttled to the public quota.
1315
+ // `billingActive` is snapshotted on the key record (stamped at mint,
1316
+ // re-synced when billing changes), so this needs no billing read on the
1317
+ // request path. Resolved before the paid lane so a payment credential
1318
+ // can't buy past the throttle: sandbox is non-billable testnet.
1319
+ const throttled = apiKey.environment === 'sandbox' && apiKey.billingActive !== true
1320
+
1321
+ if (hasPayment && !throttled)
1322
+ return requirePayment(c, next, {
1323
+ auth,
1324
+ payment: { reason: 'api_key_over_quota', type: 'mpp' },
1325
+ policy,
1326
+ principal,
1327
+ })
670
1328
 
671
- if (hasPayment)
672
- return requirePayment(c, next, {
1329
+ if (policy.apiKey) {
1330
+ // A route consumes exactly one quota bucket: its single required scope,
1331
+ // else `'*'` (zero scopes). Routes today require at most one scope; a
1332
+ // future multi-scope route buckets against its first scope.
1333
+ const scope = policy.apiKey.scopes[0] ?? Scope.wildcard
1334
+ // A throttled sandbox key shares the key's bucket but at the public
1335
+ // ceiling, so activating billing lifts the limit on the same counter.
1336
+ const limit = throttled
1337
+ ? (policy.public?.rateLimit ?? auth.publicRateLimit ?? { perMinute: 60 })
1338
+ : resolveApiKeyLimit({ apiKey, auth, policy: policy.apiKey, scope })
1339
+ const result = await consumeRateLimit(auth, {
1340
+ key: `api_key:${apiKey.id}:scope:${scope}`,
1341
+ limit,
1342
+ })
1343
+ if (result) {
1344
+ setRateLimitHeaders(c, result)
1345
+ // Surface the bucket so callers can correlate a 429 with the exhausted
1346
+ // quota. Set before the over-quota branch so it rides the 429 (an MPP
1347
+ // 402 challenge is owned by mppx and drops these headers — by design).
1348
+ c.header('RateLimit-Scope', scope)
1349
+ }
1350
+ if (result && !result.allowed) {
1351
+ // A throttled sandbox key gets a plain 429, never an MPP challenge —
1352
+ // it must activate billing to lift the ceiling, not pay per request.
1353
+ if (throttled) return rateLimitExceeded(c, result)
1354
+ return requirePaymentOrRateLimit(c, next, {
673
1355
  auth,
674
1356
  payment: { reason: 'api_key_over_quota', type: 'mpp' },
675
1357
  policy,
676
1358
  principal,
1359
+ rateLimit: result,
677
1360
  })
678
-
679
- if (policy.apiKey) {
680
- // A route consumes exactly one quota bucket: its single required scope,
681
- // else `'*'` (zero scopes). Routes today require at most one scope; a
682
- // future multi-scope route buckets against its first scope.
683
- const scope = policy.apiKey.scopes[0] ?? Scope.wildcard
684
- const limit = resolveApiKeyLimit({ apiKey, auth, policy: policy.apiKey, scope })
685
- const result = await consumeRateLimit(auth, {
686
- key: `api_key:${apiKey.id}:scope:${scope}`,
687
- limit,
688
- })
689
- if (result) {
690
- setRateLimitHeaders(c, result)
691
- // Surface the bucket so callers can correlate a 429 with the exhausted
692
- // quota. Set before the over-quota branch so it rides the 429 (an MPP
693
- // 402 challenge is owned by mppx and drops these headers — by design).
694
- c.header('RateLimit-Scope', scope)
695
- }
696
- if (result && !result.allowed)
697
- return requirePaymentOrRateLimit(c, next, {
698
- auth,
699
- payment: { reason: 'api_key_over_quota', type: 'mpp' },
700
- policy,
701
- principal,
702
- rateLimit: result,
703
- })
704
1361
  }
1362
+ }
705
1363
 
1364
+ return next()
1365
+ }
1366
+
1367
+ // Cookie sessions carry no Authorization credential; resolve the session
1368
+ // lane before the public/paid lanes so signed-in browsers skip anonymous
1369
+ // quotas.
1370
+ if (policy.session) {
1371
+ const session = await resolveSession(c, auth)
1372
+ if (session) {
1373
+ c.set('principal', session)
706
1374
  return next()
707
1375
  }
708
1376
  }
@@ -788,6 +1456,8 @@ export declare namespace require {
788
1456
  rateLimit: RateLimit.Limit
789
1457
  }
790
1458
  | undefined
1459
+ /** Session access policy: `true` allows signed-in wallet sessions. */
1460
+ session?: boolean | undefined
791
1461
  }
792
1462
  }
793
1463
 
@@ -806,19 +1476,68 @@ type RateLimitAccess = PaymentAccess & {
806
1476
  rateLimit: RateLimit.Result
807
1477
  }
808
1478
 
1479
+ /**
1480
+ * How long to wait for the rate-limit counter before failing open. A hot
1481
+ * counter backend (e.g. a single Durable Object serving one busy quota bucket)
1482
+ * can stall for tens of seconds during a storage-latency blip; without this cap
1483
+ * every request for that bucket blocks on it, turning a localized backend
1484
+ * hiccup into an API-wide latency spike. The abandoned increment still applies
1485
+ * server-side — we just stop waiting for it.
1486
+ */
1487
+ const rateLimitConsumeTimeout = 300
1488
+
809
1489
  function consumeRateLimit(
810
1490
  auth: Context,
811
1491
  options: RateLimit.Store.ConsumeOptions,
812
1492
  ): Promise<RateLimit.Result | null> {
813
1493
  if (!auth.rateLimit) return Promise.resolve(null)
814
- // Fail open: a rate-limit store error (e.g. a lost Durable Object
815
- // connection) degrades to unenforced quota instead of failing the request.
816
- return auth.rateLimit.consume(options).catch((error) => {
817
- console.error('[auth] rate-limit consume failed; failing open', error)
1494
+ // Fail open: a rate-limit store error (e.g. a lost Durable Object connection)
1495
+ // or a stall past `rateLimitConsumeTimeout` degrades to unenforced quota
1496
+ // instead of failing — or blocking — the request.
1497
+ return withTimeout(auth.rateLimit.consume(options), rateLimitConsumeTimeout).catch((error) => {
1498
+ // A timeout or transient Durable Object fault is an expected degradation,
1499
+ // logged at warn to stay out of error dashboards; an unexpected store
1500
+ // throw is a genuine error.
1501
+ if (error instanceof RateLimitTimeoutError || isDurableObjectFault(error))
1502
+ console.warn('[auth] rate-limit consume degraded; failing open', error)
1503
+ else console.error('[auth] rate-limit consume failed; failing open', error)
818
1504
  return null
819
1505
  })
820
1506
  }
821
1507
 
1508
+ function isDurableObjectFault(error: unknown): boolean {
1509
+ const { message, overloaded, retryable } = (error ?? {}) as {
1510
+ message?: string | undefined
1511
+ overloaded?: boolean | undefined
1512
+ retryable?: boolean | undefined
1513
+ }
1514
+ if (overloaded === true || retryable === true) return true
1515
+ // Cloudflare redacts internal Durable Object faults to "internal error;
1516
+ // reference = <token>", with no marker property to detect them by.
1517
+ return typeof message === 'string' && message.startsWith('internal error')
1518
+ }
1519
+
1520
+ /**
1521
+ * Rejects with a {@link RateLimitTimeoutError} if `promise` does not settle
1522
+ * within `ms`, clearing the timer on either outcome so it never keeps the
1523
+ * isolate alive.
1524
+ */
1525
+ function withTimeout<value>(promise: Promise<value>, ms: number): Promise<value> {
1526
+ return new Promise<value>((resolve, reject) => {
1527
+ const timer = setTimeout(() => reject(new RateLimitTimeoutError(ms)), ms)
1528
+ promise.then(
1529
+ (value) => {
1530
+ clearTimeout(timer)
1531
+ resolve(value)
1532
+ },
1533
+ (error) => {
1534
+ clearTimeout(timer)
1535
+ reject(error)
1536
+ },
1537
+ )
1538
+ })
1539
+ }
1540
+
822
1541
  /**
823
1542
  * Resolves the rate limit for an API-key request against a quota bucket.
824
1543
  * Most-specific wins: a per-key per-scope grant is the only thing that pierces a
@@ -959,6 +1678,7 @@ function principalFromApiKey(apiKey: ApiKey.ApiKey): Principal {
959
1678
  environment: apiKey.environment,
960
1679
  id: apiKey.id,
961
1680
  orgId: apiKey.orgId,
1681
+ ...(apiKey.projectId === undefined ? {} : { projectId: apiKey.projectId }),
962
1682
  type: 'api_key',
963
1683
  }
964
1684
  }
@@ -1008,11 +1728,12 @@ function requirePaymentOrRateLimit<environment extends Environment>(
1008
1728
  options: RateLimitAccess,
1009
1729
  ) {
1010
1730
  if (isPaymentEnabled(options.auth, options.policy)) return requirePayment(c, next, options)
1731
+ return rateLimitExceeded(c, options.rateLimit)
1732
+ }
1011
1733
 
1012
- c.header(
1013
- 'Retry-After',
1014
- String(Math.max(options.rateLimit.reset - Math.ceil(Date.now() / 1_000), 1)),
1015
- )
1734
+ /** Emits a `429` with a `Retry-After` derived from the window reset. */
1735
+ function rateLimitExceeded(c: hono_Context, result: RateLimit.Result) {
1736
+ c.header('Retry-After', String(Math.max(result.reset - Math.ceil(Date.now() / 1_000), 1)))
1016
1737
  return jsonError(c, {
1017
1738
  code: 'rate_limit_exceeded',
1018
1739
  message: 'Rate limit exceeded',
@@ -1032,6 +1753,32 @@ function contextKv(c: hono_Context): { store: Store.State } | undefined {
1032
1753
  return (c.get as (key: string) => unknown)('kv') as { store: Store.State } | undefined
1033
1754
  }
1034
1755
 
1756
+ /** Session principal resolved by the session lane. */
1757
+ export type SessionPrincipal = Extract<Principal, { type: 'session' }>
1758
+
1759
+ /**
1760
+ * Session resolution capability installed on app context (`session`) by
1761
+ * `App.create` when session auth is configured; `undefined` leaves the session
1762
+ * lane closed.
1763
+ */
1764
+ export type Session = {
1765
+ /** Resolves the request's session (cookie or bearer token) to a principal. */
1766
+ resolve: (c: hono_Context) => Promise<SessionPrincipal | null>
1767
+ }
1768
+
1769
+ // Resolve the session lane from the auth context's capability. Failures
1770
+ // resolve to `null` so the remaining lanes and the final `401` still apply.
1771
+ async function resolveSession(c: hono_Context, auth: Context): Promise<SessionPrincipal | null> {
1772
+ const session = auth.session
1773
+ if (!session) return null
1774
+ try {
1775
+ return await session.resolve(c)
1776
+ } catch (error) {
1777
+ console.error('[auth] session resolve failed; treating as no session', error)
1778
+ return null
1779
+ }
1780
+ }
1781
+
1035
1782
  /** Maximum resolve-cache entries held per isolate before eviction. */
1036
1783
  const cachedMaxEntries = 10_000
1037
1784
 
@@ -1083,3 +1830,11 @@ function cachedResolve(
1083
1830
  }
1084
1831
  }
1085
1832
  }
1833
+
1834
+ /** Thrown when a rate-limit consume exceeds {@link rateLimitConsumeTimeout}. */
1835
+ export class RateLimitTimeoutError extends Error {
1836
+ override name = 'Auth.RateLimitTimeoutError'
1837
+ constructor(ms: number) {
1838
+ super(`Rate-limit consume timed out after ${ms}ms; failing open.`)
1839
+ }
1840
+ }