tapimo 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (288) hide show
  1. package/dist/App.d.ts +8641 -8263
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +9 -1
  4. package/dist/App.js.map +1 -1
  5. package/dist/Scope.d.ts +4 -0
  6. package/dist/Scope.d.ts.map +1 -1
  7. package/dist/Scope.js +5 -0
  8. package/dist/Scope.js.map +1 -1
  9. package/dist/admin/App.d.ts +112 -11
  10. package/dist/admin/App.d.ts.map +1 -1
  11. package/dist/admin/apps/api-keys.d.ts +2 -1
  12. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  13. package/dist/admin/apps/early-access.d.ts +2 -2
  14. package/dist/admin/apps/early-access.d.ts.map +1 -1
  15. package/dist/admin/apps/early-access.js +5 -5
  16. package/dist/admin/apps/early-access.js.map +1 -1
  17. package/dist/admin/apps/organizations.d.ts +124 -1
  18. package/dist/admin/apps/organizations.d.ts.map +1 -1
  19. package/dist/admin/apps/organizations.js +97 -6
  20. package/dist/admin/apps/organizations.js.map +1 -1
  21. package/dist/admin/ui.generated.d.ts +1 -1
  22. package/dist/admin/ui.generated.d.ts.map +1 -1
  23. package/dist/admin/ui.generated.js +47 -39
  24. package/dist/admin/ui.generated.js.map +1 -1
  25. package/dist/analytics/Analytics.d.ts.map +1 -1
  26. package/dist/analytics/Analytics.js +20 -0
  27. package/dist/analytics/Analytics.js.map +1 -1
  28. package/dist/analytics/tables/requestEvents.d.ts +18 -0
  29. package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
  30. package/dist/analytics/tables/requestEvents.js +36 -7
  31. package/dist/analytics/tables/requestEvents.js.map +1 -1
  32. package/dist/apps/data/App.d.ts +8050 -8019
  33. package/dist/apps/data/App.d.ts.map +1 -1
  34. package/dist/apps/data/FundingQuoteProviders.js +2 -15
  35. package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
  36. package/dist/apps/data/FxOracle.d.ts +1 -1
  37. package/dist/apps/data/FxOracle.d.ts.map +1 -1
  38. package/dist/apps/data/FxOracle.js +1 -1
  39. package/dist/apps/data/FxOracle.js.map +1 -1
  40. package/dist/apps/data/routes/activities.d.ts +27 -27
  41. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  42. package/dist/apps/data/routes/activities.js +21 -1
  43. package/dist/apps/data/routes/activities.js.map +1 -1
  44. package/dist/apps/data/routes/balances.d.ts +58 -58
  45. package/dist/apps/data/routes/blocks.d.ts +39 -39
  46. package/dist/apps/data/routes/coingecko.d.ts +176 -176
  47. package/dist/apps/data/routes/exchanges.d.ts +669 -669
  48. package/dist/apps/data/routes/fee-amm.d.ts +157 -143
  49. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  50. package/dist/apps/data/routes/fee-amm.js +76 -91
  51. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  52. package/dist/apps/data/routes/funding.d.ts +72 -34
  53. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  54. package/dist/apps/data/routes/funding.js +34 -26
  55. package/dist/apps/data/routes/funding.js.map +1 -1
  56. package/dist/apps/data/routes/indexer.d.ts +30 -30
  57. package/dist/apps/data/routes/receipts.d.ts +8 -8
  58. package/dist/apps/data/routes/rpc.d.ts +31 -31
  59. package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
  60. package/dist/apps/data/routes/tokenlist.js +12 -3
  61. package/dist/apps/data/routes/tokenlist.js.map +1 -1
  62. package/dist/apps/data/routes/tokens.d.ts +4 -4
  63. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  64. package/dist/apps/data/routes/tokens.js +5 -1
  65. package/dist/apps/data/routes/tokens.js.map +1 -1
  66. package/dist/apps/data/routes/transactions.d.ts +6 -6
  67. package/dist/apps/data/routes/transfers.d.ts +101 -90
  68. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  69. package/dist/apps/data/routes/transfers.js +226 -141
  70. package/dist/apps/data/routes/transfers.js.map +1 -1
  71. package/dist/apps/data/routes/valuation.d.ts +2 -2
  72. package/dist/apps/data/routes/valuation.js +3 -3
  73. package/dist/apps/data/routes/valuation.js.map +1 -1
  74. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  75. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
  76. package/dist/apps/data/routes/verified-tokens.js +34 -9
  77. package/dist/apps/data/routes/verified-tokens.js.map +1 -1
  78. package/dist/apps/data/routes/webhooks.d.ts +2 -2
  79. package/dist/apps/data/routes/webhooks.js +14 -6
  80. package/dist/apps/data/routes/webhooks.js.map +1 -1
  81. package/dist/apps/index.d.ts +1 -0
  82. package/dist/apps/index.d.ts.map +1 -1
  83. package/dist/apps/index.js +1 -0
  84. package/dist/apps/index.js.map +1 -1
  85. package/dist/apps/management/App.d.ts +460 -523
  86. package/dist/apps/management/App.d.ts.map +1 -1
  87. package/dist/apps/management/routes/api-keys.d.ts +7 -23
  88. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  89. package/dist/apps/management/routes/api-keys.js +20 -8
  90. package/dist/apps/management/routes/api-keys.js.map +1 -1
  91. package/dist/apps/management/routes/billing.d.ts +106 -151
  92. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  93. package/dist/apps/management/routes/billing.js +28 -28
  94. package/dist/apps/management/routes/billing.js.map +1 -1
  95. package/dist/apps/management/routes/faucet.d.ts +48 -34
  96. package/dist/apps/management/routes/faucet.d.ts.map +1 -1
  97. package/dist/apps/management/routes/faucet.js +33 -18
  98. package/dist/apps/management/routes/faucet.js.map +1 -1
  99. package/dist/apps/management/routes/invitations.d.ts +131 -131
  100. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  101. package/dist/apps/management/routes/invitations.js +0 -3
  102. package/dist/apps/management/routes/invitations.js.map +1 -1
  103. package/dist/apps/management/routes/me.d.ts +6 -8
  104. package/dist/apps/management/routes/me.d.ts.map +1 -1
  105. package/dist/apps/management/routes/me.js +0 -4
  106. package/dist/apps/management/routes/me.js.map +1 -1
  107. package/dist/apps/management/routes/members.d.ts +73 -73
  108. package/dist/apps/management/routes/orgs.d.ts +94 -94
  109. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  110. package/dist/apps/management/routes/orgs.js +5 -0
  111. package/dist/apps/management/routes/orgs.js.map +1 -1
  112. package/dist/apps/management/routes/usage.d.ts +72 -72
  113. package/dist/apps/management/routes/verified-token-requests.d.ts +94 -94
  114. package/dist/apps/management/routes/webhooks.d.ts +1 -1
  115. package/dist/apps/mpp/App.d.ts +363 -0
  116. package/dist/apps/mpp/App.d.ts.map +1 -0
  117. package/dist/apps/mpp/App.js +72 -0
  118. package/dist/apps/mpp/App.js.map +1 -0
  119. package/dist/apps/mpp/Mpp.d.ts +178 -0
  120. package/dist/apps/mpp/Mpp.d.ts.map +1 -0
  121. package/dist/apps/mpp/Mpp.js +183 -0
  122. package/dist/apps/mpp/Mpp.js.map +1 -0
  123. package/dist/apps/mpp/index.d.ts +3 -0
  124. package/dist/apps/mpp/index.d.ts.map +1 -0
  125. package/dist/apps/mpp/index.js +3 -0
  126. package/dist/apps/mpp/index.js.map +1 -0
  127. package/dist/apps/relay/App.d.ts.map +1 -1
  128. package/dist/apps/relay/App.js +57 -10
  129. package/dist/apps/relay/App.js.map +1 -1
  130. package/dist/apps/relay/Sponsorships.js +1 -1
  131. package/dist/apps/relay/Sponsorships.js.map +1 -1
  132. package/dist/db/Db.d.ts +2 -0
  133. package/dist/db/Db.d.ts.map +1 -1
  134. package/dist/db/Db.js +27 -0
  135. package/dist/db/Db.js.map +1 -1
  136. package/dist/db/Schema.d.ts +12 -0
  137. package/dist/db/Schema.d.ts.map +1 -1
  138. package/dist/db/Schema.js +11 -0
  139. package/dist/db/Schema.js.map +1 -1
  140. package/dist/db/tables/enabledBillingSources.d.ts +73 -0
  141. package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
  142. package/dist/db/tables/enabledBillingSources.js +69 -0
  143. package/dist/db/tables/enabledBillingSources.js.map +1 -0
  144. package/dist/db/tables/invitations.d.ts +1 -12
  145. package/dist/db/tables/invitations.d.ts.map +1 -1
  146. package/dist/db/tables/invitations.js +3 -23
  147. package/dist/db/tables/invitations.js.map +1 -1
  148. package/dist/db/tables/organizations.d.ts +5 -2
  149. package/dist/db/tables/organizations.d.ts.map +1 -1
  150. package/dist/db/tables/organizations.js +32 -2
  151. package/dist/db/tables/organizations.js.map +1 -1
  152. package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
  153. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  154. package/dist/db/tables/sponsoredTransactions.js +13 -3
  155. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  156. package/dist/handlers/internal/utils.d.ts +1 -16
  157. package/dist/handlers/internal/utils.d.ts.map +1 -1
  158. package/dist/handlers/internal/utils.js +17 -6
  159. package/dist/handlers/internal/utils.js.map +1 -1
  160. package/dist/handlers/relay.d.ts.map +1 -1
  161. package/dist/handlers/relay.js +37 -5
  162. package/dist/handlers/relay.js.map +1 -1
  163. package/dist/internal/Auth.d.ts +0 -32
  164. package/dist/internal/Auth.d.ts.map +1 -1
  165. package/dist/internal/Auth.js +3 -51
  166. package/dist/internal/Auth.js.map +1 -1
  167. package/dist/internal/EdgeCache.d.ts +1 -1
  168. package/dist/internal/EdgeCache.d.ts.map +1 -1
  169. package/dist/internal/EdgeCache.js +3 -2
  170. package/dist/internal/EdgeCache.js.map +1 -1
  171. package/dist/internal/FundingQuotes.d.ts +78 -32
  172. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  173. package/dist/internal/FundingQuotes.js +45 -8
  174. package/dist/internal/FundingQuotes.js.map +1 -1
  175. package/dist/internal/Log.d.ts +18 -4
  176. package/dist/internal/Log.d.ts.map +1 -1
  177. package/dist/internal/Log.js +50 -3
  178. package/dist/internal/Log.js.map +1 -1
  179. package/dist/internal/MetricSink.d.ts.map +1 -1
  180. package/dist/internal/MetricSink.js +15 -0
  181. package/dist/internal/MetricSink.js.map +1 -1
  182. package/dist/internal/Response.d.ts +2 -2
  183. package/dist/internal/Response.d.ts.map +1 -1
  184. package/dist/internal/Response.js +12 -4
  185. package/dist/internal/Response.js.map +1 -1
  186. package/dist/internal/Schema.d.ts +2 -2
  187. package/dist/internal/Schema.d.ts.map +1 -1
  188. package/dist/internal/Schema.js +4 -2
  189. package/dist/internal/Schema.js.map +1 -1
  190. package/dist/internal/Viem.d.ts +1 -1
  191. package/package.json +7 -2
  192. package/src/App.test.ts +9 -4
  193. package/src/App.ts +14 -2
  194. package/src/Client.test-d.ts +109 -28
  195. package/src/Scope.ts +5 -0
  196. package/src/admin/App.test.ts +4 -0
  197. package/src/admin/apps/early-access.ts +6 -5
  198. package/src/admin/apps/organizations.test.ts +86 -2
  199. package/src/admin/apps/organizations.ts +155 -31
  200. package/src/admin/apps/scopes.test.ts +5 -0
  201. package/src/admin/ui/src/lib/organizations.test.ts +81 -1
  202. package/src/admin/ui/src/lib/organizations.ts +92 -0
  203. package/src/admin/ui/src/routes/api-keys.tsx +1 -1
  204. package/src/admin/ui/src/routes/early-access.tsx +8 -3
  205. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
  206. package/src/admin/ui.generated.ts +49 -39
  207. package/src/analytics/Analytics.test.ts +83 -0
  208. package/src/analytics/Analytics.ts +20 -0
  209. package/src/analytics/tables/requestEvents.ts +60 -7
  210. package/src/apps/composition.test.ts +11 -2
  211. package/src/apps/data/App.test.ts +14 -2
  212. package/src/apps/data/FundingQuoteProviders.test.ts +0 -5
  213. package/src/apps/data/FundingQuoteProviders.ts +2 -29
  214. package/src/apps/data/FxOracle.test.ts +2 -2
  215. package/src/apps/data/FxOracle.ts +2 -2
  216. package/src/apps/data/routes/activities.test.ts +6 -2
  217. package/src/apps/data/routes/activities.ts +39 -1
  218. package/src/apps/data/routes/balances.test.ts +6 -2
  219. package/src/apps/data/routes/exchanges.test.ts +7 -3
  220. package/src/apps/data/routes/fee-amm.test.ts +47 -23
  221. package/src/apps/data/routes/fee-amm.ts +98 -119
  222. package/src/apps/data/routes/funding.test.ts +29 -8
  223. package/src/apps/data/routes/funding.ts +42 -48
  224. package/src/apps/data/routes/receipts.test.ts +6 -2
  225. package/src/apps/data/routes/tokenlist.test.ts +15 -0
  226. package/src/apps/data/routes/tokenlist.ts +18 -5
  227. package/src/apps/data/routes/tokens.ts +5 -1
  228. package/src/apps/data/routes/transfers.test.ts +203 -35
  229. package/src/apps/data/routes/transfers.ts +294 -172
  230. package/src/apps/data/routes/valuation.test.ts +18 -3
  231. package/src/apps/data/routes/valuation.ts +4 -4
  232. package/src/apps/data/routes/verified-tokens.test.ts +51 -0
  233. package/src/apps/data/routes/verified-tokens.ts +52 -15
  234. package/src/apps/data/routes/webhooks.test.ts +56 -0
  235. package/src/apps/data/routes/webhooks.ts +17 -8
  236. package/src/apps/index.ts +1 -0
  237. package/src/apps/management/routes/api-keys.test.ts +2 -75
  238. package/src/apps/management/routes/api-keys.ts +30 -7
  239. package/src/apps/management/routes/billing.test.ts +106 -85
  240. package/src/apps/management/routes/billing.ts +32 -21
  241. package/src/apps/management/routes/faucet.test.ts +11 -3
  242. package/src/apps/management/routes/faucet.ts +46 -30
  243. package/src/apps/management/routes/invitations.test.ts +0 -172
  244. package/src/apps/management/routes/invitations.ts +0 -3
  245. package/src/apps/management/routes/me.test.ts +3 -145
  246. package/src/apps/management/routes/me.ts +0 -6
  247. package/src/apps/management/routes/orgs.test.ts +45 -2
  248. package/src/apps/management/routes/orgs.ts +6 -0
  249. package/src/apps/management/routes/scopes.test.ts +1 -0
  250. package/src/apps/mpp/App.ts +86 -0
  251. package/src/apps/mpp/Mpp.test.ts +193 -0
  252. package/src/apps/mpp/Mpp.ts +353 -0
  253. package/src/apps/mpp/index.ts +2 -0
  254. package/src/apps/mpp/internal/Adapter.ts +11 -0
  255. package/src/apps/mpp/internal/Error.ts +9 -0
  256. package/src/apps/mpp/internal/Idempotency.ts +7 -0
  257. package/src/apps/mpp/internal/Screening.ts +10 -0
  258. package/src/apps/relay/App.test.ts +90 -8
  259. package/src/apps/relay/App.ts +62 -10
  260. package/src/apps/relay/Sponsorships.test.ts +2 -0
  261. package/src/apps/relay/Sponsorships.ts +1 -1
  262. package/src/db/Db.test.ts +2 -2
  263. package/src/db/Db.ts +30 -0
  264. package/src/db/Schema.ts +15 -0
  265. package/src/db/tables/enabledBillingSources.test.ts +303 -0
  266. package/src/db/tables/enabledBillingSources.ts +118 -0
  267. package/src/db/tables/invitations.ts +4 -26
  268. package/src/db/tables/organizations.test.ts +191 -0
  269. package/src/db/tables/organizations.ts +44 -3
  270. package/src/db/tables/sponsoredTransactions.test.ts +41 -2
  271. package/src/db/tables/sponsoredTransactions.ts +15 -3
  272. package/src/handlers/internal/utils.ts +22 -6
  273. package/src/handlers/relay.errors.test.ts +153 -0
  274. package/src/handlers/relay.ts +42 -7
  275. package/src/internal/Auth.test.ts +3 -3
  276. package/src/internal/Auth.ts +3 -63
  277. package/src/internal/EdgeCache.test.ts +18 -5
  278. package/src/internal/EdgeCache.ts +4 -2
  279. package/src/internal/FundingQuotes.test.ts +18 -5
  280. package/src/internal/FundingQuotes.ts +46 -11
  281. package/src/internal/Log.test.ts +91 -0
  282. package/src/internal/Log.ts +69 -5
  283. package/src/internal/MetricSink.test.ts +55 -0
  284. package/src/internal/MetricSink.ts +14 -0
  285. package/src/internal/Response.test.ts +28 -0
  286. package/src/internal/Response.ts +12 -4
  287. package/src/internal/Schema.test.ts +13 -0
  288. package/src/internal/Schema.ts +14 -7
@@ -17,6 +17,8 @@ export type Variables = {
17
17
  providerFailure?: Entry['provider'] | undefined
18
18
  /** Redacted provider failures when multiple upstream adapters fail. */
19
19
  providerFailures?: Entry['providerFailures'] | undefined
20
+ /** Bounded JSON-RPC failure metadata recorded by a composed RPC handler. */
21
+ rpcResponse?: Entry['rpc'] | undefined
20
22
  }
21
23
 
22
24
  /** Hono environment shape used by the request logger. */
@@ -71,7 +73,7 @@ export type Entry = {
71
73
  errorCode?: string | undefined
72
74
  /** Bounded funding provider attempts made while serving the request. */
73
75
  fundingProviderAttempts?: readonly FundingProviderAttempt[] | undefined
74
- /** Log level derived from the response status: 5xx `error`, 4xx `warn`, else `info`. */
76
+ /** Log level: 5xx `error`; 4xx or JSON-RPC failure `warn`; otherwise `info`. */
75
77
  level: Level
76
78
  /** Request method. */
77
79
  method: string
@@ -121,7 +123,16 @@ export type Entry = {
121
123
  requestId: string
122
124
  /** Matched handler path (bounded cardinality), e.g. `/v1/tokens/:address`; `/*` when a middleware (e.g. an edge-cache hit) short-circuited routing. */
123
125
  route: string
124
- /** Response status code. */
126
+ /** JSON-RPC failures returned in an otherwise successful HTTP response. */
127
+ rpc?: {
128
+ /** Numeric JSON-RPC error code when every failed response shares one code. */
129
+ code?: number | undefined
130
+ /** Stable application error code from `error.data.code`, when shared by every failed response. */
131
+ dataCode?: string | undefined
132
+ /** Number of failed responses in the single or batch JSON-RPC response. */
133
+ errors: number
134
+ }
135
+ /** HTTP response status code. JSON-RPC failures retain their protocol-native `200`. */
125
136
  status: number
126
137
  /** Upstream operation durations in milliseconds, from the request's Server-Timing metrics (e.g. RPC and TIDX calls). */
127
138
  timings?: Record<string, number> | undefined
@@ -138,8 +149,9 @@ export type Emit = (entry: Entry) => Promise<void> | void
138
149
  *
139
150
  * The entry derives everything from state other layers already produce —
140
151
  * `requestId`, the auth principal, `RateLimit-*` headers, Server-Timing
141
- * metrics, the `errorCode` recorded by `Response.error`, and the edge-cache
142
- * hit marker so handlers never log per-request lines themselves.
152
+ * metrics, the `errorCode` recorded by `Response.error`, JSON-RPC response
153
+ * errors, and the edge-cache hit marker, so handlers never log per-request
154
+ * lines themselves.
143
155
  */
144
156
  export function middleware<environment extends Environment = Environment>(
145
157
  options: middleware.Options = {},
@@ -164,6 +176,7 @@ export function middleware<environment extends Environment = Environment>(
164
176
  | undefined
165
177
  const provider = c.get('providerFailure' as never) as Entry['provider']
166
178
  const providerFailures = c.get('providerFailures' as never) as Entry['providerFailures']
179
+ const rpc = c.get('rpcResponse' as never) as Entry['rpc']
167
180
 
168
181
  // The `request` total metric duplicates `duration` (measured here, one
169
182
  // layer further out), so only operation timings are kept.
@@ -193,7 +206,12 @@ export function middleware<environment extends Environment = Environment>(
193
206
  duration: round(duration),
194
207
  ...(errorCode === undefined ? {} : { errorCode }),
195
208
  ...(fundingProviderAttempts === undefined ? {} : { fundingProviderAttempts }),
196
- level: status >= 500 ? 'error' : status >= 400 ? 'warn' : 'info',
209
+ level:
210
+ status >= 500 || rpcServerError(rpc?.code)
211
+ ? 'error'
212
+ : status >= 400 || rpc
213
+ ? 'warn'
214
+ : 'info',
197
215
  method: c.req.method,
198
216
  path: url.pathname,
199
217
  ...(principal?.payment === undefined ? {} : { payment: principal.payment.reason }),
@@ -220,6 +238,7 @@ export function middleware<environment extends Environment = Environment>(
220
238
  ...(rateLimit === undefined ? {} : { rateLimit }),
221
239
  requestId: c.get('requestId' as never) as string,
222
240
  route: c.req.routePath,
241
+ ...(rpc === undefined ? {} : { rpc }),
223
242
  status,
224
243
  ...(timings === undefined ? {} : { timings }),
225
244
  })
@@ -238,6 +257,51 @@ export function middleware<environment extends Environment = Environment>(
238
257
  }
239
258
  }
240
259
 
260
+ const rpcDataCode = /^[a-z][a-z0-9_]{0,63}$/
261
+
262
+ /** Extracts bounded error metadata from an already-parsed JSON-RPC response. */
263
+ export function rpcErrors(body: unknown): Entry['rpc'] {
264
+ const values = Array.isArray(body) ? body : [body]
265
+ const errors = values.flatMap((value) => {
266
+ if (!value || typeof value !== 'object') return []
267
+ const record = value as Record<string, unknown>
268
+ if (record['jsonrpc'] !== '2.0' || !record['error'] || typeof record['error'] !== 'object')
269
+ return []
270
+ const error = record['error'] as Record<string, unknown>
271
+ if (
272
+ !Number.isInteger(error['code']) ||
273
+ Number(error['code']) < -2_147_483_648 ||
274
+ Number(error['code']) > 2_147_483_647
275
+ )
276
+ return []
277
+ const data = error['data']
278
+ const dataCode_value =
279
+ data && typeof data === 'object' ? (data as Record<string, unknown>)['code'] : undefined
280
+ const dataCode = typeof dataCode_value === 'string' ? dataCode_value : undefined
281
+ return [
282
+ {
283
+ code: error['code'] as number,
284
+ ...(dataCode && rpcDataCode.test(dataCode) ? { dataCode } : {}),
285
+ },
286
+ ]
287
+ })
288
+ if (errors.length === 0) return undefined
289
+
290
+ const codes = new Set(errors.map((error) => error.code))
291
+ const dataCodes = new Set(errors.map((error) => error.dataCode).filter(Boolean))
292
+ return {
293
+ ...(codes.size === 1 ? { code: errors[0]!.code } : {}),
294
+ ...(dataCodes.size === 1 && errors.every((error) => error.dataCode !== undefined)
295
+ ? { dataCode: errors[0]!.dataCode }
296
+ : {}),
297
+ errors: Math.min(errors.length, 65_535),
298
+ }
299
+ }
300
+
301
+ function rpcServerError(code: number | undefined) {
302
+ return code === -32603 || (code !== undefined && code >= -32099 && code <= -32000)
303
+ }
304
+
241
305
  export declare namespace middleware {
242
306
  /** Options for the request logger middleware. */
243
307
  type Options = {
@@ -10,6 +10,61 @@ type MetricEvent = {
10
10
  }
11
11
 
12
12
  describe('requests', () => {
13
+ test('records JSON-RPC errors with bounded code tags', () => {
14
+ const events: MetricEvent[] = []
15
+ const metrics = Metrics.from({
16
+ count(name, value, tags) {
17
+ events.push({ name, tags, type: 'count', value })
18
+ },
19
+ flush() {},
20
+ gauge() {},
21
+ histogram() {},
22
+ })
23
+ const record = MetricSink.requests(metrics)
24
+
25
+ record({
26
+ duration: 10,
27
+ level: 'warn',
28
+ method: 'POST',
29
+ path: '/rpc/relay',
30
+ requestId: 'request_private',
31
+ route: '/rpc/relay',
32
+ rpc: { code: -32602, dataCode: 'billing_required', errors: 2 },
33
+ status: 200,
34
+ })
35
+
36
+ expect(events).toMatchInlineSnapshot(`
37
+ [
38
+ {
39
+ "name": "http_response_count",
40
+ "tags": {
41
+ "cache": "miss",
42
+ "method": "POST",
43
+ "principal_environment": "none",
44
+ "principal_type": "unknown",
45
+ "route": "/rpc/relay",
46
+ "status": 200,
47
+ },
48
+ "type": "count",
49
+ "value": 1,
50
+ },
51
+ {
52
+ "name": "rpc_response_error_count",
53
+ "tags": {
54
+ "cache": "miss",
55
+ "method": "POST",
56
+ "principal_environment": "none",
57
+ "principal_type": "unknown",
58
+ "route": "/rpc/relay",
59
+ "rpc_error_code": "-32602",
60
+ },
61
+ "type": "count",
62
+ "value": 2,
63
+ },
64
+ ]
65
+ `)
66
+ })
67
+
13
68
  test('records bounded funding provider attempts without caller data', () => {
14
69
  const events: MetricEvent[] = []
15
70
  let flushes = 0
@@ -85,6 +85,12 @@ export function requests(backend: Metrics): RequestSink {
85
85
  })
86
86
  backend.histogram('http_response_duration_ms', entry.duration, tags)
87
87
 
88
+ if (entry.rpc)
89
+ backend.count('rpc_response_error_count', entry.rpc.errors, {
90
+ ...tags,
91
+ rpc_error_code: rpcErrorCode(entry.rpc.code),
92
+ })
93
+
88
94
  if (entry.status === 429 || entry.errorCode === 'rate_limit_exceeded')
89
95
  backend.count('http_rate_limit_count', 1, {
90
96
  ...tags,
@@ -109,6 +115,14 @@ export function requests(backend: Metrics): RequestSink {
109
115
  }
110
116
  }
111
117
 
118
+ /** Maps arbitrary upstream JSON-RPC codes into bounded metric tags. */
119
+ function rpcErrorCode(code: number | undefined) {
120
+ if (code === undefined) return 'mixed'
121
+ if ([-32700, -32600, -32601, -32602, -32603].includes(code)) return String(code)
122
+ if (code >= -32099 && code <= -32000) return 'server'
123
+ return 'other'
124
+ }
125
+
112
126
  function routeTags(entry: Log.Entry): RouteTags {
113
127
  return {
114
128
  cache: entry.cache ?? 'miss',
@@ -0,0 +1,28 @@
1
+ import { Hono } from 'hono'
2
+
3
+ import * as Response from './Response.js'
4
+
5
+ describe('upstream', () => {
6
+ test('returns a stable message and preserves the cause on context', async () => {
7
+ const app = new Hono()
8
+ const cause = new Error('provider URL contains secret credentials')
9
+ let captured: Error | undefined
10
+
11
+ app.use(async (c, next) => {
12
+ await next()
13
+ captured = c.error
14
+ })
15
+ app.get('/', (c) => Response.upstream(c, cause))
16
+
17
+ const response = await app.request('/')
18
+ expect(await response.json()).toMatchInlineSnapshot(`
19
+ {
20
+ "error": {
21
+ "code": "upstream_error",
22
+ "message": "Upstream service could not complete the request",
23
+ },
24
+ }
25
+ `)
26
+ expect(captured).toBe(cause)
27
+ })
28
+ })
@@ -39,12 +39,20 @@ export function unsupportedChainId(c: Context, chainId: number, supported: Itera
39
39
  }
40
40
 
41
41
  /**
42
- * Maps an unexpected upstream failure to a 502 JSON error envelope. Rethrows
43
- * non-`Error` causes so the global handler can convert them.
42
+ * Maps an unexpected upstream failure to a stable 502 JSON error envelope.
43
+ * Preserves `Error` causes for reporting and rethrows non-`Error` causes.
44
44
  */
45
45
  export function upstream(c: Context, cause: unknown) {
46
- if (cause instanceof Error)
47
- return error(c, { code: 'upstream_error', message: cause.message, status: 502 })
46
+ if (cause instanceof Error) {
47
+ // Preserve the original error for error-reporting middleware without
48
+ // exposing provider URLs, payloads, or credentials in the response.
49
+ c.error = cause
50
+ return error(c, {
51
+ code: 'upstream_error',
52
+ message: 'Upstream service could not complete the request',
53
+ status: 502,
54
+ })
55
+ }
48
56
  throw cause
49
57
  }
50
58
 
@@ -32,3 +32,16 @@ describe('ChainIdQuery', () => {
32
32
  expect(Schema.ChainIdQuery.parse('42431')).toMatchInlineSnapshot(`42431`)
33
33
  })
34
34
  })
35
+
36
+ describe('timestampBound', () => {
37
+ test('normalizes RFC 3339 offsets to UTC', () => {
38
+ const bound = Schema.timestampBound('swaps', 'from')
39
+
40
+ expect(bound.parse('2026-06-04T12:00:00+05:30')).toMatchInlineSnapshot(
41
+ `"2026-06-04T06:30:00.000Z"`,
42
+ )
43
+ expect(bound.parse('2026-06-04T12:00:00-05:00')).toMatchInlineSnapshot(
44
+ `"2026-06-04T17:00:00.000Z"`,
45
+ )
46
+ })
47
+ })
@@ -495,16 +495,23 @@ export function blockNumberBound(noun: string, bound: 'from' | 'to') {
495
495
 
496
496
  /**
497
497
  * Optional inclusive ISO-8601 timestamp bound query parameter (`timestamp.from`
498
- * / `timestamp.to`). `noun` is the resource being listed (e.g. `transfers`).
498
+ * / `timestamp.to`), normalized to UTC. `noun` is the listed resource.
499
499
  */
500
500
  export function timestampBound(noun: string, bound: 'from' | 'to') {
501
501
  const direction = bound === 'from' ? 'at or after' : 'at or before'
502
- return z.optional(z.iso.datetime({ offset: true })).check(
503
- z.describe(`Only include ${noun} ${direction} this ISO 8601 timestamp.`),
504
- z.meta({
505
- examples: [bound === 'from' ? '2024-01-01T00:00:00Z' : '2024-12-31T23:59:59Z'],
506
- }),
507
- )
502
+ return z
503
+ .optional(
504
+ z.pipe(
505
+ z.iso.datetime({ offset: true }),
506
+ z.transform((value) => new Date(value).toISOString()),
507
+ ),
508
+ )
509
+ .check(
510
+ z.describe(`Only include ${noun} ${direction} this ISO 8601 timestamp.`),
511
+ z.meta({
512
+ examples: [bound === 'from' ? '2024-01-01T00:00:00Z' : '2024-12-31T23:59:59Z'],
513
+ }),
514
+ )
508
515
  }
509
516
 
510
517
  /**