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
@@ -0,0 +1,236 @@
1
+ import { Hono } from 'hono'
2
+ import { createMiddleware } from 'hono/factory'
3
+ import { RpcResponse } from 'ox'
4
+ import { Addresses, TokenId, Transaction } from 'viem/tempo'
5
+
6
+ import type * as App from '../../App.js'
7
+ import * as BillingSettings from '../../db/tables/billingSettings.js'
8
+ import * as Db from '../../db/Db.js'
9
+ import * as Fees from '../../internal/Fees.js'
10
+ import * as SponsoredTransactions from '../../db/tables/sponsoredTransactions.js'
11
+ import * as Billing from '../platform/Billing.js'
12
+ import * as Handler from '../../Handler.js'
13
+ import * as Auth from '../../internal/Auth.js'
14
+ import * as OpenApi from '../../internal/OpenApi.js'
15
+ import * as Path from '../../internal/Path.js'
16
+ import * as VerifiedTokens from '../../internal/VerifiedTokens.js'
17
+ import * as Viem from '../../internal/Viem.js'
18
+ import * as Scope from '../../Scope.js'
19
+
20
+ /**
21
+ * The relay route group: the wallet relay at `/rpc/relay`. Mount
22
+ * after `data()`, whose published chain clients and supported-chain set the
23
+ * middleware reads from request context.
24
+ *
25
+ * ```ts
26
+ * App.create(options).route('/', data()).route('/', relay())
27
+ * ```
28
+ */
29
+ export function relay(options: relay.Options = {}) {
30
+ const handler = middleware(options)
31
+ return new Hono<App.Environment>()
32
+ .use(
33
+ '/rpc/relay',
34
+ Auth.policy({ apiKey: { scopes: ['rpc-relay:read'] }, mpp: false, public: false }),
35
+ OpenApi.describeRoute({ hide: true, summary: 'Relay RPC' }),
36
+ )
37
+ .use(
38
+ '/rpc/relay/*',
39
+ Auth.policy({ apiKey: { scopes: ['rpc-relay:read'] }, mpp: false, public: false }),
40
+ OpenApi.describeRoute({ hide: true, summary: 'Relay RPC' }),
41
+ )
42
+ .all('/rpc/relay', handler)
43
+ .all('/rpc/relay/:chainId', handler)
44
+ }
45
+
46
+ export declare namespace relay {
47
+ /** Options for the relay route group. */
48
+ type Options = {
49
+ feePayer?:
50
+ | (Pick<
51
+ NonNullable<Handler.relay.Options['feePayer']>,
52
+ 'account' | 'feeToken' | 'name' | 'url'
53
+ > & {
54
+ /** Per-transaction fee cap (decimal USD string); orgs tighten it, never loosen. @default '1.00' */
55
+ txFeeLimit?: string | undefined
56
+ })
57
+ | undefined
58
+ }
59
+ }
60
+
61
+ // The wallet relay middleware: proxies wallet JSON-RPC (fills, sponsored
62
+ // sends) with wallet-aware enrichment. Built per request from context rather
63
+ // than closed over group locals; relay traffic is low-QPS and the viem
64
+ // clients are memoized upstream, so per-request handler construction is cheap.
65
+ function middleware(options: relay.Options = {}) {
66
+ // Platform-wide per-transaction cap; org settings tighten it, never loosen.
67
+ const txFeeLimit = options.feePayer
68
+ ? Billing.toBaseUnits(options.feePayer.txFeeLimit ?? '1.00')
69
+ : 0n
70
+ return createMiddleware<App.Environment>(async (c) => {
71
+ const verifiedTokens = c.get('verifiedTokens')
72
+
73
+ const feePayer = (() => {
74
+ if (!options.feePayer) return undefined
75
+ const principal = Auth.getPrincipal(c)
76
+ const attribution =
77
+ principal?.type === 'api_key' && principal.projectId !== undefined
78
+ ? {
79
+ apiKeyId: principal.id,
80
+ environment: principal.environment,
81
+ orgId: principal.orgId,
82
+ projectId: principal.projectId,
83
+ }
84
+ : undefined
85
+ const sponsorable =
86
+ attribution !== undefined &&
87
+ principal?.type === 'api_key' &&
88
+ (principal.apiKey.scopes.includes('rpc-relay:sponsor') ||
89
+ principal.apiKey.scopes.includes(Scope.wildcard))
90
+ // Lifted so validation and the sponsored-token default read one value.
91
+ const feeToken_sponsor = options.feePayer.feeToken ?? Addresses.pathUsd
92
+ return {
93
+ ...options.feePayer,
94
+ feeToken: feeToken_sponsor,
95
+ validate: async (transaction) => {
96
+ // Allowlist the chains this deployment serves rather than denylisting
97
+ // only mainnet: the relay resolves chainId from the request body, so
98
+ // the query-string chain guard never sees it.
99
+ const id = Number(transaction.chainId)
100
+ if (!attribution || !sponsorable || !c.get('supportedChainIds').has(id)) return false
101
+ if (!Viem.isMainnet(id)) return true
102
+ // Mainnet is production-only: a sandbox key resolves chainId from the
103
+ // request body, so the auth-layer sandbox/mainnet guard never sees
104
+ // it. Refuse here, or sandbox spend would sponsor mainnet unbilled.
105
+ if (attribution.environment !== 'production') return false
106
+
107
+ // Enforcement reads hit the primary, never the cached replica: a
108
+ // stale `past_due`, a lowered limit, or another isolate's pending
109
+ // spend must not keep the gate open.
110
+ const db = Db.get(c.get('db'))
111
+ // Mainnet sponsorship requires an active billing source; deployments
112
+ // without billing configured have no rows, so mainnet stays closed.
113
+ // `past_due` gets its own reason: the caller must settle an invoice,
114
+ // not attach a card.
115
+ const billing = await Billing.status(db, attribution.orgId)
116
+ if (billing === 'past_due') return 'billing_past_due'
117
+ if (billing !== 'active') return 'billing_required'
118
+
119
+ const feeToken = (() => {
120
+ try {
121
+ return TokenId.toAddress(transaction.feeToken ?? feeToken_sponsor)
122
+ } catch {
123
+ return undefined
124
+ }
125
+ })()
126
+ const verified = feeToken
127
+ ? (await VerifiedTokens.read(Db.get(c.get('dbCached')), id))?.byAddress.get(feeToken.toLowerCase()) // prettier-ignore
128
+ : undefined
129
+ if (
130
+ !verified ||
131
+ verified.currency.toLowerCase() !== 'usd' ||
132
+ verified.decimals !== Fees.tokenDecimals
133
+ )
134
+ // prettier-ignore
135
+ return 'fee_token_unsupported'
136
+
137
+ // Fail closed: a transaction whose signed cap cannot be computed
138
+ // cannot be bounded, so it never sponsors on mainnet.
139
+ const feeMax = Fees.maxOf(transaction)
140
+ if (feeMax === undefined) return 'tx_fee_limit_exceeded'
141
+ const settings = await BillingSettings.get(db, attribution.orgId)
142
+ const cap = (() => {
143
+ if (settings?.txFeeLimit == null) return txFeeLimit
144
+ const org = Billing.toBaseUnits(settings.txFeeLimit)
145
+ return org < txFeeLimit ? org : txFeeLimit
146
+ })()
147
+ if (feeMax > cap) return 'tx_fee_limit_exceeded'
148
+ // The period spend limit is enforced atomically at recording time
149
+ // (`onSponsored` → `SponsoredTransactions.reserve`); a check here
150
+ // would be a check-then-act race a burst could slip through.
151
+ return true
152
+ },
153
+ onSponsored: async (event) => {
154
+ if (!attribution) return
155
+ const mainnet = Viem.isMainnet(event.chainId)
156
+ // Defense in depth: `validate` already refuses non-production
157
+ // mainnet, so reaching here with that pair is a wiring bug.
158
+ if (mainnet && attribution.environment !== 'production')
159
+ throw new RpcResponse.InvalidParamsError({ message: 'Sponsorship rejected.' })
160
+
161
+ // The cap recomputes from canonical envelope bytes, mirroring the
162
+ // reconciliation convention; pending rows charge it to spend limits.
163
+ const feeMax = Fees.maxOf(Transaction.deserialize(event.transaction as `0x76${string}`))
164
+ const db = Db.get(c.get('db'))
165
+
166
+ const feeToken = (() => {
167
+ try {
168
+ return event.feeToken !== undefined ? TokenId.toAddress(event.feeToken) : undefined
169
+ } catch {
170
+ return undefined
171
+ }
172
+ })()
173
+
174
+ const currency = feeToken
175
+ ? (await VerifiedTokens.read(Db.get(c.get('dbCached')), event.chainId))?.byAddress
176
+ .get(feeToken.toLowerCase())
177
+ ?.currency.toLowerCase()
178
+ : undefined
179
+
180
+ const input = {
181
+ apiKeyId: attribution.apiKeyId,
182
+ billable: attribution.environment === 'production',
183
+ chainId: event.chainId,
184
+ environment: attribution.environment,
185
+ orgId: attribution.orgId,
186
+ projectId: attribution.projectId,
187
+ signPayload: event.signPayload,
188
+ transaction: event.transaction,
189
+ ...(currency ? { currency } : {}),
190
+ ...(feeMax !== undefined ? { feeMax: feeMax.toString() } : {}),
191
+ ...(feeToken ? { feeToken } : {}),
192
+ ...(event.transactionHash ? { transactionHash: event.transactionHash } : {}),
193
+ }
194
+ // Re-read settings on the authoritative path: the limit may have
195
+ // changed since `validate`, and only mainnet spend is limited.
196
+ const settings = mainnet ? await BillingSettings.get(db, attribution.orgId) : undefined
197
+ if (settings?.spendLimit == null) return void (await SponsoredTransactions.upsert(db, input)) // prettier-ignore
198
+ try {
199
+ await SponsoredTransactions.reserve(db, input, {
200
+ chainIds: [Viem.chainId.mainnet],
201
+ max: Billing.toBaseUnits(settings.spendLimit),
202
+ since: Billing.periodStart(settings.period),
203
+ })
204
+ } catch (error) {
205
+ if (error instanceof SponsoredTransactions.PeriodSpendLimitError)
206
+ throw new RpcResponse.InvalidParamsError({
207
+ data: { code: 'spend_limit_exceeded' },
208
+ message: 'Spend limit exceeded.',
209
+ })
210
+ throw error
211
+ }
212
+ },
213
+ } satisfies Handler.relay.Options['feePayer']
214
+ })()
215
+
216
+ // Dispatching a standalone Hono via `fetch(c.req.raw)` bypasses Hono's
217
+ // mount-path stripping, so its internal routes register at the request's
218
+ // full external path (`basePath` + `/rpc/relay`).
219
+ const handler = Handler.relay({
220
+ cache: c.get('store'),
221
+ features: 'all',
222
+ getClient: (chainId) => c.get('getClient')(chainId as Viem.ChainId | undefined),
223
+ path: Path.join(c.get('basePath'), 'rpc/relay'),
224
+ ...(feePayer ? { feePayer } : {}),
225
+ ...(verifiedTokens
226
+ ? {
227
+ resolveTokens: async (chainId: number) => {
228
+ const snapshot = await VerifiedTokens.read(Db.get(c.get('dbCached')), chainId)
229
+ return snapshot?.list.map((token) => token.address) ?? []
230
+ },
231
+ }
232
+ : {}),
233
+ })
234
+ return handler.fetch(c.req.raw)
235
+ })
236
+ }
@@ -0,0 +1,375 @@
1
+ import { nanoid } from 'nanoid'
2
+ import { TxEnvelopeTempo } from 'ox/tempo'
3
+ import type { Client } from 'viem'
4
+
5
+ import { postgresUrl } from '../../../test/containers.js'
6
+ import * as Db from '../../db/Db.js'
7
+ import * as SponsoredTransactions from '../../db/tables/sponsoredTransactions.js'
8
+ import * as Sponsorships from './Sponsorships.js'
9
+
10
+ const create = () => Db.postgres({ connectionString: postgresUrl, schema: `t_${nanoid()}` })
11
+
12
+ /** Baseline insert input; tests override per case. */
13
+ const input = {
14
+ apiKeyId: 'key_1',
15
+ billable: true,
16
+ chainId: 42431,
17
+ environment: 'production',
18
+ orgId: 'org_1',
19
+ projectId: 'prj_1',
20
+ signPayload: `0x${'bb'.repeat(32)}`,
21
+ transaction: `0x76${'cc'.repeat(16)}`,
22
+ transactionHash: `0x${'aa'.repeat(32)}`,
23
+ } satisfies SponsoredTransactions.upsert.Input
24
+
25
+ /** Stubs the receipt lookup: hashes present in `receipts` resolve, others null. */
26
+ function client(receipts: Record<string, { effectiveGasPrice: string; gasUsed: string }>): Client {
27
+ return {
28
+ request: async ({ params }: { params: readonly [string] }) => receipts[params[0]] ?? null,
29
+ } as never
30
+ }
31
+
32
+ describe('finalize', () => {
33
+ test('behavior: finalizes mined rows with gasUsed × effectiveGasPrice', async () => {
34
+ const db = create()
35
+ await db.migrate()
36
+ const record = await SponsoredTransactions.upsert(db, input)
37
+
38
+ const result = await Sponsorships.finalize(db, {
39
+ getClient: () =>
40
+ client({ [input.transactionHash]: { effectiveGasPrice: '0x3', gasUsed: '0x5208' } }),
41
+ })
42
+ expect(result).toMatchInlineSnapshot(`
43
+ {
44
+ "failed": 0,
45
+ "finalized": 1,
46
+ "pending": 0,
47
+ }
48
+ `)
49
+ const finalized = await SponsoredTransactions.get(db, record.id)
50
+ expect(finalized?.status).toMatchInlineSnapshot(`"finalized"`)
51
+ // 21000 gas × 3 = 63000 base units.
52
+ expect(finalized?.feeAmount).toMatchInlineSnapshot(`"1"`)
53
+ expect(finalized?.finalizedAt).not.toBeNull()
54
+ await db.close()
55
+ })
56
+
57
+ test('behavior: leaves young unmined rows pending', async () => {
58
+ const db = create()
59
+ await db.migrate()
60
+ const record = await SponsoredTransactions.upsert(db, input)
61
+
62
+ const result = await Sponsorships.finalize(db, { getClient: () => client({}) })
63
+ expect(result).toMatchInlineSnapshot(`
64
+ {
65
+ "failed": 0,
66
+ "finalized": 0,
67
+ "pending": 1,
68
+ }
69
+ `)
70
+ expect((await SponsoredTransactions.get(db, record.id))?.status).toMatchInlineSnapshot(
71
+ `"pending"`,
72
+ )
73
+ await db.close()
74
+ })
75
+
76
+ test('behavior: hash-less fill intents settle by TTL without a receipt lookup', async () => {
77
+ const db = create()
78
+ await db.migrate()
79
+ const { transactionHash: _, ...intent } = input
80
+ const record = await SponsoredTransactions.upsert(db, intent)
81
+
82
+ // Young intent: left pending; the stub client would throw if queried.
83
+ const young = await Sponsorships.finalize(db, {
84
+ getClient: () => {
85
+ throw new Error('unexpected receipt lookup for a hash-less intent')
86
+ },
87
+ })
88
+ expect(young).toMatchInlineSnapshot(`
89
+ {
90
+ "failed": 0,
91
+ "finalized": 0,
92
+ "pending": 1,
93
+ }
94
+ `)
95
+
96
+ // Past the TTL: fails with no fee.
97
+ const settled = await Sponsorships.finalize(db, {
98
+ getClient: () => {
99
+ throw new Error('unexpected receipt lookup for a hash-less intent')
100
+ },
101
+ now: () => new Date(Date.now() + 2 * 3_600_000),
102
+ })
103
+ expect(settled).toMatchInlineSnapshot(`
104
+ {
105
+ "failed": 1,
106
+ "finalized": 0,
107
+ "pending": 0,
108
+ }
109
+ `)
110
+ expect((await SponsoredTransactions.get(db, record.id))?.status).toMatchInlineSnapshot(
111
+ `"failed"`,
112
+ )
113
+ await db.close()
114
+ })
115
+
116
+ test('behavior: fails unmined rows past the pending TTL', async () => {
117
+ const db = create()
118
+ await db.migrate()
119
+ const record = await SponsoredTransactions.upsert(db, input)
120
+
121
+ const result = await Sponsorships.finalize(db, {
122
+ getClient: () => client({}),
123
+ now: () => new Date(Date.now() + 2 * 3_600_000),
124
+ })
125
+ expect(result).toMatchInlineSnapshot(`
126
+ {
127
+ "failed": 1,
128
+ "finalized": 0,
129
+ "pending": 0,
130
+ }
131
+ `)
132
+ const failed = await SponsoredTransactions.get(db, record.id)
133
+ expect(failed?.status).toMatchInlineSnapshot(`"failed"`)
134
+ expect(failed?.feeAmount).toBeNull()
135
+ await db.close()
136
+ })
137
+
138
+ test('behavior: an erroring receipt lookup leaves the row pending, even past the TTL', async () => {
139
+ const db = create()
140
+ await db.migrate()
141
+ const record = await SponsoredTransactions.upsert(db, input)
142
+
143
+ const result = await Sponsorships.finalize(db, {
144
+ getClient: () =>
145
+ ({
146
+ request: async () => {
147
+ throw new Error('rpc outage')
148
+ },
149
+ }) as never,
150
+ now: () => new Date(Date.now() + 2 * 3_600_000),
151
+ })
152
+ expect(result).toMatchInlineSnapshot(`
153
+ {
154
+ "failed": 0,
155
+ "finalized": 0,
156
+ "pending": 1,
157
+ }
158
+ `)
159
+ expect((await SponsoredTransactions.get(db, record.id))?.status).toMatchInlineSnapshot(
160
+ `"pending"`,
161
+ )
162
+ await db.close()
163
+ })
164
+
165
+ test('behavior: an erroring receipt lookup fails past the error TTL', async () => {
166
+ const db = create()
167
+ await db.migrate()
168
+ const record = await SponsoredTransactions.upsert(db, input)
169
+
170
+ const result = await Sponsorships.finalize(db, {
171
+ getClient: () =>
172
+ ({
173
+ request: async () => {
174
+ throw new Error('rpc outage')
175
+ },
176
+ }) as never,
177
+ now: () => new Date(Date.now() + 25 * 3_600_000),
178
+ })
179
+ expect(result).toMatchInlineSnapshot(`
180
+ {
181
+ "failed": 1,
182
+ "finalized": 0,
183
+ "pending": 0,
184
+ }
185
+ `)
186
+ expect((await SponsoredTransactions.get(db, record.id))?.status).toMatchInlineSnapshot(
187
+ `"failed"`,
188
+ )
189
+ await db.close()
190
+ })
191
+
192
+ test('behavior: batchSize caps rows processed per pass', async () => {
193
+ const db = create()
194
+ await db.migrate()
195
+ await SponsoredTransactions.upsert(db, { ...input, signPayload: `0x${'b1'.repeat(32)}`, transactionHash: `0x${'a1'.repeat(32)}` }) // prettier-ignore
196
+ await SponsoredTransactions.upsert(db, { ...input, signPayload: `0x${'b2'.repeat(32)}`, transactionHash: `0x${'a2'.repeat(32)}` }) // prettier-ignore
197
+
198
+ // Both hashes are mined so the outcome is order-independent.
199
+ const receipts = {
200
+ [`0x${'a1'.repeat(32)}`]: { effectiveGasPrice: '0x1', gasUsed: '0x1' },
201
+ [`0x${'a2'.repeat(32)}`]: { effectiveGasPrice: '0x1', gasUsed: '0x1' },
202
+ }
203
+ const result = await Sponsorships.finalize(db, {
204
+ batchSize: 1,
205
+ getClient: () => client(receipts),
206
+ })
207
+ expect(result).toMatchInlineSnapshot(`
208
+ {
209
+ "failed": 0,
210
+ "finalized": 1,
211
+ "pending": 0,
212
+ }
213
+ `)
214
+ expect(await SponsoredTransactions.listPending(db)).toHaveLength(1)
215
+ await db.close()
216
+ })
217
+ })
218
+
219
+ describe('reconcile', () => {
220
+ const sender = '0x407368320192a21238825437c02c2e2a34ffc38d'
221
+ const minedHash = `0x${'dd'.repeat(32)}`
222
+
223
+ /** The mined sponsored envelope, in ox form. */
224
+ const mined = TxEnvelopeTempo.from({
225
+ calls: [{ data: '0x', to: '0x20c0000000000000000000000000000000000001', value: 0n }],
226
+ chainId: 42431,
227
+ gas: 100_000n,
228
+ maxFeePerGas: 1_000_000n,
229
+ maxPriorityFeePerGas: 0n,
230
+ nonce: 0n,
231
+ nonceKey: 0n,
232
+ })
233
+
234
+ /** The intent's identity, computed exactly as the recording sites do. */
235
+ const signPayload = TxEnvelopeTempo.getFeePayerSignPayload(mined, { sender })
236
+
237
+ /** Fakes the node: `eth_getRawTransactionByHash` serves the mined bytes. */
238
+ const rpc = {
239
+ request: async () => TxEnvelopeTempo.serialize(mined),
240
+ } as never as Client
241
+
242
+ /** Fakes the indexer: the fee payer's recent transactions. */
243
+ const tidx = (rows: readonly { from: string; hash: string }[]) =>
244
+ ({ fetch: async () => ({ rows }) }) as never
245
+
246
+ test('behavior: matches intents by sign payload and fills the hash', async () => {
247
+ const db = create()
248
+ await db.migrate()
249
+ const { transactionHash: _, ...rest } = input
250
+ const intent = await SponsoredTransactions.upsert(db, { ...rest, signPayload })
251
+
252
+ const result = await Sponsorships.reconcile(db, {
253
+ feePayer: '0xFee0000000000000000000000000000000000000',
254
+ getClient: () => rpc,
255
+ getTidx: () => tidx([{ from: sender, hash: minedHash }]),
256
+ })
257
+ expect(result).toMatchInlineSnapshot(`
258
+ {
259
+ "matched": 1,
260
+ "scanned": 1,
261
+ }
262
+ `)
263
+ const matched = await SponsoredTransactions.get(db, intent.id)
264
+ expect(matched?.transactionHash).toBe(minedHash)
265
+ expect(matched?.status).toMatchInlineSnapshot(`"pending"`)
266
+ await db.close()
267
+ })
268
+
269
+ test('behavior: recovers recently failed intents by sign payload', async () => {
270
+ const db = create()
271
+ await db.migrate()
272
+ const { transactionHash: _, ...rest } = input
273
+ const intent = await SponsoredTransactions.upsert(db, { ...rest, signPayload })
274
+ await SponsoredTransactions.fail(db, intent.id, '2026-01-01T00:00:00.000Z')
275
+
276
+ vi.useFakeTimers({ now: new Date('2026-01-01T00:01:00.000Z') })
277
+ const result = await (async () => {
278
+ try {
279
+ return await Sponsorships.reconcile(db, {
280
+ feePayer: '0xFee0000000000000000000000000000000000000',
281
+ getClient: () => rpc,
282
+ getTidx: () => tidx([{ from: sender, hash: minedHash }]),
283
+ })
284
+ } finally {
285
+ vi.useRealTimers()
286
+ }
287
+ })()
288
+ expect(result).toMatchInlineSnapshot(`
289
+ {
290
+ "matched": 1,
291
+ "scanned": 1,
292
+ }
293
+ `)
294
+ const matched = await SponsoredTransactions.get(db, intent.id)
295
+ expect(matched?.transactionHash).toBe(minedHash)
296
+ expect(matched?.status).toMatchInlineSnapshot(`"pending"`)
297
+ expect(matched?.finalizedAt).toBeNull()
298
+ await db.close()
299
+ })
300
+
301
+ test('behavior: unmatched candidates leave intents pending', async () => {
302
+ const db = create()
303
+ await db.migrate()
304
+ const { transactionHash: _, ...rest } = input
305
+ const intent = await SponsoredTransactions.upsert(db, { ...rest, signPayload: `0x${'ee'.repeat(32)}` }) // prettier-ignore
306
+
307
+ const result = await Sponsorships.reconcile(db, {
308
+ feePayer: '0xFee0000000000000000000000000000000000000',
309
+ getClient: () => rpc,
310
+ getTidx: () => tidx([{ from: sender, hash: minedHash }]),
311
+ })
312
+ expect(result).toMatchInlineSnapshot(`
313
+ {
314
+ "matched": 0,
315
+ "scanned": 1,
316
+ }
317
+ `)
318
+ expect((await SponsoredTransactions.get(db, intent.id))?.transactionHash).toBeNull()
319
+ await db.close()
320
+ })
321
+
322
+ test('behavior: queries on-chain candidates oldest first', async () => {
323
+ const db = create()
324
+ await db.migrate()
325
+ const { transactionHash: _, ...rest } = input
326
+ await SponsoredTransactions.upsert(db, { ...rest, signPayload })
327
+ let query: string | undefined
328
+
329
+ await Sponsorships.reconcile(db, {
330
+ candidateCap: 17,
331
+ feePayer: '0xFee0000000000000000000000000000000000000',
332
+ getClient: () => rpc,
333
+ getTidx: () =>
334
+ ({
335
+ fetch: async (options: { query: string }) => {
336
+ query = options.query
337
+ return { rows: [] }
338
+ },
339
+ }) as never,
340
+ })
341
+
342
+ expect(
343
+ query?.replace(/block_timestamp >= '[^']+'/, "block_timestamp >= '<since>'"),
344
+ ).toMatchInlineSnapshot(
345
+ `"SELECT hash, "from" FROM txs WHERE fee_payer = '0xfee0000000000000000000000000000000000000' AND block_timestamp >= '<since>' ORDER BY block_timestamp ASC LIMIT 17"`,
346
+ )
347
+ await db.close()
348
+ })
349
+
350
+ test('behavior: an indexer error leaves intents for the next pass', async () => {
351
+ const db = create()
352
+ await db.migrate()
353
+ const { transactionHash: _, ...rest } = input
354
+ await SponsoredTransactions.upsert(db, { ...rest, signPayload })
355
+
356
+ const result = await Sponsorships.reconcile(db, {
357
+ feePayer: '0xFee0000000000000000000000000000000000000',
358
+ getClient: () => rpc,
359
+ getTidx: () =>
360
+ ({
361
+ fetch: async () => {
362
+ throw new Error('indexer outage')
363
+ },
364
+ }) as never,
365
+ })
366
+ expect(result).toMatchInlineSnapshot(`
367
+ {
368
+ "matched": 0,
369
+ "scanned": 0,
370
+ }
371
+ `)
372
+ expect(await SponsoredTransactions.listIntents(db)).toHaveLength(1)
373
+ await db.close()
374
+ })
375
+ })