tapimo 0.12.0 → 0.13.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 (257) hide show
  1. package/dist/App.d.ts +5561 -5026
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +38 -3
  4. package/dist/App.js.map +1 -1
  5. package/dist/Scope.d.ts +2 -0
  6. package/dist/Scope.d.ts.map +1 -1
  7. package/dist/Scope.js +17 -0
  8. package/dist/Scope.js.map +1 -1
  9. package/dist/admin/App.d.ts +123 -74
  10. package/dist/admin/App.d.ts.map +1 -1
  11. package/dist/admin/apps/api-keys.d.ts +80 -0
  12. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  13. package/dist/admin/apps/api-keys.js +54 -0
  14. package/dist/admin/apps/api-keys.js.map +1 -1
  15. package/dist/admin/apps/earn-vaults.d.ts +15 -15
  16. package/dist/admin/apps/earn-vaults.js +1 -1
  17. package/dist/admin/apps/verified-tokens.d.ts +81 -81
  18. package/dist/admin/ui.generated.d.ts +1 -1
  19. package/dist/admin/ui.generated.d.ts.map +1 -1
  20. package/dist/admin/ui.generated.js +151 -151
  21. package/dist/admin/ui.generated.js.map +1 -1
  22. package/dist/apps/Zones.d.ts +16 -16
  23. package/dist/apps/Zones.js +4 -4
  24. package/dist/apps/data/App.js +18 -18
  25. package/dist/apps/data/App.js.map +1 -1
  26. package/dist/apps/data/ZoneSelection.d.ts +8 -8
  27. package/dist/apps/data/ZoneSelection.d.ts.map +1 -1
  28. package/dist/apps/data/ZoneSelection.js +1 -1
  29. package/dist/apps/data/ZoneSelection.js.map +1 -1
  30. package/dist/apps/data/routes/activities.d.ts +588 -584
  31. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  32. package/dist/apps/data/routes/activities.js +1561 -1239
  33. package/dist/apps/data/routes/activities.js.map +1 -1
  34. package/dist/apps/data/routes/balances.d.ts +26 -26
  35. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  36. package/dist/apps/data/routes/balances.js +43 -16
  37. package/dist/apps/data/routes/balances.js.map +1 -1
  38. package/dist/apps/data/routes/blocks.d.ts +44 -44
  39. package/dist/apps/data/routes/blocks.d.ts.map +1 -1
  40. package/dist/apps/data/routes/blocks.js +21 -11
  41. package/dist/apps/data/routes/blocks.js.map +1 -1
  42. package/dist/apps/data/routes/coingecko.d.ts +3 -3
  43. package/dist/apps/data/routes/earn.d.ts +18 -18
  44. package/dist/apps/data/routes/earn.js +13 -13
  45. package/dist/apps/data/routes/exchanges.d.ts +145 -145
  46. package/dist/apps/data/routes/fee-amm.d.ts +2 -2
  47. package/dist/apps/data/routes/receipts.d.ts +155 -55
  48. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  49. package/dist/apps/data/routes/receipts.js +28 -10
  50. package/dist/apps/data/routes/receipts.js.map +1 -1
  51. package/dist/apps/data/routes/tokenlist.d.ts +13 -13
  52. package/dist/apps/data/routes/tokens.d.ts +98 -98
  53. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  54. package/dist/apps/data/routes/tokens.js +51 -20
  55. package/dist/apps/data/routes/tokens.js.map +1 -1
  56. package/dist/apps/data/routes/transactions.d.ts +7 -2
  57. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  58. package/dist/apps/data/routes/transactions.js +37 -17
  59. package/dist/apps/data/routes/transactions.js.map +1 -1
  60. package/dist/apps/data/routes/transfers.d.ts +1 -1
  61. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  62. package/dist/apps/data/routes/transfers.js +18 -12
  63. package/dist/apps/data/routes/transfers.js.map +1 -1
  64. package/dist/apps/data/routes/valuation.d.ts +1 -1
  65. package/dist/apps/data/routes/valuation.d.ts.map +1 -1
  66. package/dist/apps/data/routes/valuation.js +12 -7
  67. package/dist/apps/data/routes/valuation.js.map +1 -1
  68. package/dist/apps/data/routes/verified-tokens.d.ts +3 -3
  69. package/dist/apps/data/routes/webhooks.d.ts +11 -11
  70. package/dist/apps/data/routes/zones.d.ts +14 -14
  71. package/dist/apps/data/routes/zones.js +1 -1
  72. package/dist/apps/funding/App.d.ts +11 -0
  73. package/dist/apps/funding/App.d.ts.map +1 -1
  74. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
  75. package/dist/apps/funding/routes/deposit-addresses.js +16 -11
  76. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  77. package/dist/apps/funding/routes/transfers.d.ts +60 -48
  78. package/dist/apps/funding/routes/transfers.d.ts.map +1 -1
  79. package/dist/apps/funding/routes/transfers.js +27 -19
  80. package/dist/apps/funding/routes/transfers.js.map +1 -1
  81. package/dist/apps/management/App.d.ts +4487 -3963
  82. package/dist/apps/management/App.d.ts.map +1 -1
  83. package/dist/apps/management/routes/api-keys.d.ts +710 -159
  84. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  85. package/dist/apps/management/routes/api-keys.js +236 -4
  86. package/dist/apps/management/routes/api-keys.js.map +1 -1
  87. package/dist/apps/management/routes/faucet.d.ts +6 -6
  88. package/dist/apps/management/routes/invite-links.d.ts +42 -42
  89. package/dist/apps/management/routes/members.d.ts +30 -30
  90. package/dist/apps/management/routes/projects.d.ts +40 -40
  91. package/dist/apps/management/routes/usage.d.ts +24 -24
  92. package/dist/apps/management/routes/verified-token-requests.d.ts +1 -1
  93. package/dist/apps/management/routes/webhooks.d.ts +55 -55
  94. package/dist/db/Db.d.ts.map +1 -1
  95. package/dist/db/Db.js +12 -1
  96. package/dist/db/Db.js.map +1 -1
  97. package/dist/db/Schema.d.ts +3 -2
  98. package/dist/db/Schema.d.ts.map +1 -1
  99. package/dist/db/Schema.js +5 -2
  100. package/dist/db/Schema.js.map +1 -1
  101. package/dist/db/tables/fundingCatalog.d.ts +4 -2
  102. package/dist/db/tables/fundingCatalog.d.ts.map +1 -1
  103. package/dist/db/tables/fundingCatalog.js +5 -1
  104. package/dist/db/tables/fundingCatalog.js.map +1 -1
  105. package/dist/handlers/internal/utils.d.ts.map +1 -1
  106. package/dist/handlers/internal/utils.js +9 -0
  107. package/dist/handlers/internal/utils.js.map +1 -1
  108. package/dist/internal/Auth.d.ts +3 -1
  109. package/dist/internal/Auth.d.ts.map +1 -1
  110. package/dist/internal/Auth.js +6 -3
  111. package/dist/internal/Auth.js.map +1 -1
  112. package/dist/internal/EarnVaults.d.ts +3 -3
  113. package/dist/internal/EarnVaults.js +3 -3
  114. package/dist/internal/Fetch.d.ts +23 -0
  115. package/dist/internal/Fetch.d.ts.map +1 -0
  116. package/dist/internal/Fetch.js +26 -0
  117. package/dist/internal/Fetch.js.map +1 -0
  118. package/dist/internal/Log.d.ts +3 -1
  119. package/dist/internal/Log.d.ts.map +1 -1
  120. package/dist/internal/Log.js +16 -3
  121. package/dist/internal/Log.js.map +1 -1
  122. package/dist/internal/OpenApi.d.ts +5 -3
  123. package/dist/internal/OpenApi.d.ts.map +1 -1
  124. package/dist/internal/OpenApi.js +13 -3
  125. package/dist/internal/OpenApi.js.map +1 -1
  126. package/dist/internal/Response.d.ts +1 -1
  127. package/dist/internal/Response.d.ts.map +1 -1
  128. package/dist/internal/Schema.d.ts +6 -3
  129. package/dist/internal/Schema.d.ts.map +1 -1
  130. package/dist/internal/Schema.js +8 -2
  131. package/dist/internal/Schema.js.map +1 -1
  132. package/dist/internal/Store.d.ts +16 -3
  133. package/dist/internal/Store.d.ts.map +1 -1
  134. package/dist/internal/Store.js +82 -16
  135. package/dist/internal/Store.js.map +1 -1
  136. package/dist/internal/Tidx.d.ts.map +1 -1
  137. package/dist/internal/Tidx.js +7 -2
  138. package/dist/internal/Tidx.js.map +1 -1
  139. package/dist/internal/Timeout.d.ts +36 -0
  140. package/dist/internal/Timeout.d.ts.map +1 -0
  141. package/dist/internal/Timeout.js +37 -0
  142. package/dist/internal/Timeout.js.map +1 -0
  143. package/dist/internal/Timing.d.ts +9 -0
  144. package/dist/internal/Timing.d.ts.map +1 -1
  145. package/dist/internal/Timing.js +7 -0
  146. package/dist/internal/Timing.js.map +1 -1
  147. package/dist/internal/WebhookDestination.d.ts.map +1 -1
  148. package/dist/internal/WebhookDestination.js +3 -0
  149. package/dist/internal/WebhookDestination.js.map +1 -1
  150. package/dist/internal/funding/Action.d.ts +9 -0
  151. package/dist/internal/funding/Action.d.ts.map +1 -1
  152. package/dist/internal/funding/Action.js +16 -8
  153. package/dist/internal/funding/Action.js.map +1 -1
  154. package/dist/internal/funding/Catalog.d.ts +9 -2
  155. package/dist/internal/funding/Catalog.d.ts.map +1 -1
  156. package/dist/internal/funding/Catalog.js +19 -8
  157. package/dist/internal/funding/Catalog.js.map +1 -1
  158. package/dist/internal/funding/Provider.d.ts +68 -13
  159. package/dist/internal/funding/Provider.d.ts.map +1 -1
  160. package/dist/internal/funding/Provider.js +29 -8
  161. package/dist/internal/funding/Provider.js.map +1 -1
  162. package/dist/internal/funding/Transfer.d.ts +13 -12
  163. package/dist/internal/funding/Transfer.d.ts.map +1 -1
  164. package/dist/internal/funding/Transfer.js +23 -21
  165. package/dist/internal/funding/Transfer.js.map +1 -1
  166. package/dist/internal/funding/providers/stargate.d.ts +28 -9
  167. package/dist/internal/funding/providers/stargate.d.ts.map +1 -1
  168. package/dist/internal/funding/providers/stargate.js +251 -117
  169. package/dist/internal/funding/providers/stargate.js.map +1 -1
  170. package/dist/internal/index.d.ts +1 -0
  171. package/dist/internal/index.d.ts.map +1 -1
  172. package/dist/internal/index.js +1 -0
  173. package/dist/internal/index.js.map +1 -1
  174. package/package.json +2 -2
  175. package/src/App.test.ts +100 -0
  176. package/src/App.ts +42 -3
  177. package/src/Client.test-d.ts +46 -0
  178. package/src/Scope.test.ts +20 -0
  179. package/src/Scope.ts +16 -0
  180. package/src/admin/apps/api-keys.test.ts +75 -0
  181. package/src/admin/apps/api-keys.ts +64 -0
  182. package/src/admin/apps/earn-vaults.test.ts +1 -1
  183. package/src/admin/apps/earn-vaults.ts +1 -1
  184. package/src/admin/ui/src/lib/api-keys.ts +19 -0
  185. package/src/admin/ui/src/routes/api-keys.tsx +89 -2
  186. package/src/admin/ui.generated.ts +189 -151
  187. package/src/apps/Zones.test.ts +4 -4
  188. package/src/apps/Zones.ts +4 -4
  189. package/src/apps/composition.test.ts +1 -1
  190. package/src/apps/data/App.test.ts +41 -0
  191. package/src/apps/data/App.ts +18 -18
  192. package/src/apps/data/ZoneSelection.test.ts +2 -2
  193. package/src/apps/data/ZoneSelection.ts +8 -8
  194. package/src/apps/data/routes/activities.test.ts +1526 -12
  195. package/src/apps/data/routes/activities.ts +1774 -1356
  196. package/src/apps/data/routes/balances.ts +133 -97
  197. package/src/apps/data/routes/blocks.test.ts +533 -0
  198. package/src/apps/data/routes/blocks.ts +35 -16
  199. package/src/apps/data/routes/earn.test.ts +4 -2
  200. package/src/apps/data/routes/earn.ts +13 -13
  201. package/src/apps/data/routes/earn.zone-access.test.ts +6 -6
  202. package/src/apps/data/routes/receipts.ts +47 -16
  203. package/src/apps/data/routes/tokens.test.ts +578 -0
  204. package/src/apps/data/routes/tokens.ts +283 -225
  205. package/src/apps/data/routes/transactions.ts +68 -29
  206. package/src/apps/data/routes/transfers.test.ts +421 -0
  207. package/src/apps/data/routes/transfers.ts +45 -27
  208. package/src/apps/data/routes/valuation.ts +94 -78
  209. package/src/apps/data/routes/zones.test.ts +2 -2
  210. package/src/apps/data/routes/zones.ts +1 -1
  211. package/src/apps/funding/routes/deposit-addresses.test.ts +25 -3
  212. package/src/apps/funding/routes/deposit-addresses.ts +17 -12
  213. package/src/apps/funding/routes/quotes.test.ts +5 -9
  214. package/src/apps/funding/routes/transfers.test.ts +395 -8
  215. package/src/apps/funding/routes/transfers.ts +76 -59
  216. package/src/apps/management/routes/api-keys.test.ts +220 -0
  217. package/src/apps/management/routes/api-keys.ts +280 -3
  218. package/src/db/Db.ts +13 -1
  219. package/src/db/Schema.ts +5 -2
  220. package/src/db/tables/earnVaults.test.ts +9 -9
  221. package/src/db/tables/fundingCatalog.test.ts +16 -2
  222. package/src/db/tables/fundingCatalog.ts +9 -3
  223. package/src/handlers/internal/utils.test.ts +53 -0
  224. package/src/handlers/internal/utils.ts +15 -0
  225. package/src/internal/Auth.ts +12 -4
  226. package/src/internal/EarnVaults.capabilities.test.ts +2 -2
  227. package/src/internal/EarnVaults.test.ts +1 -1
  228. package/src/internal/EarnVaults.ts +3 -3
  229. package/src/internal/Fetch.test.ts +46 -0
  230. package/src/internal/Fetch.ts +56 -0
  231. package/src/internal/Log.test.ts +21 -0
  232. package/src/internal/Log.ts +20 -5
  233. package/src/internal/OpenApi.ts +14 -4
  234. package/src/internal/Response.ts +1 -1
  235. package/src/internal/Schema.test.ts +2 -0
  236. package/src/internal/Schema.ts +12 -2
  237. package/src/internal/Store.test.ts +178 -0
  238. package/src/internal/Store.ts +104 -18
  239. package/src/internal/Tidx.test.ts +2 -1
  240. package/src/internal/Tidx.ts +7 -2
  241. package/src/internal/Timeout.test.ts +35 -0
  242. package/src/internal/Timeout.ts +73 -0
  243. package/src/internal/Timing.ts +16 -0
  244. package/src/internal/WebhookDestination.ts +3 -0
  245. package/src/internal/funding/Action.test.ts +41 -1
  246. package/src/internal/funding/Action.ts +69 -41
  247. package/src/internal/funding/Catalog.test.ts +34 -0
  248. package/src/internal/funding/Catalog.ts +28 -11
  249. package/src/internal/funding/Provider.test.ts +73 -0
  250. package/src/internal/funding/Provider.ts +82 -28
  251. package/src/internal/funding/Transfer.ts +133 -110
  252. package/src/internal/funding/providers/index.test.ts +4 -167
  253. package/src/internal/funding/providers/relay.test.ts +2 -2
  254. package/src/internal/funding/providers/stargate.test.ts +163 -0
  255. package/src/internal/funding/providers/stargate.ts +359 -134
  256. package/src/internal/index.test.ts +1 -0
  257. package/src/internal/index.ts +1 -0
@@ -71,8 +71,12 @@ describe('publish', () => {
71
71
  catalog: head,
72
72
  chainTokens: catalog.chainTokens,
73
73
  chains: catalog.chains,
74
- // Absent `capabilities` persists as null (indicative-only).
75
- routes: catalog.routes.map((route) => ({ ...route, capabilities: null })),
74
+ // Optional route JSON persists as null.
75
+ routes: catalog.routes.map((route) => ({
76
+ ...route,
77
+ capabilities: null,
78
+ configuration: null,
79
+ })),
76
80
  tokens: catalog.tokens,
77
81
  })
78
82
  })
@@ -87,6 +91,16 @@ describe('publish', () => {
87
91
  expect((await FundingCatalog.read(db)).routes[0]?.capabilities).toStrictEqual(capabilities)
88
92
  })
89
93
 
94
+ test('round-trips route configuration', async () => {
95
+ const db = TestApp.database()
96
+ const configuration = { poolAddress: `0x${'11'.repeat(20)}` }
97
+ await FundingCatalog.publish(db, {
98
+ ...catalog,
99
+ routes: [{ ...catalog.routes[0]!, configuration }],
100
+ })
101
+ expect((await FundingCatalog.read(db)).routes[0]?.configuration).toStrictEqual(configuration)
102
+ })
103
+
90
104
  test('replaces rows and advances the catalog version', async () => {
91
105
  const db = TestApp.database()
92
106
  const first = await FundingCatalog.publish(db, catalog)
@@ -28,10 +28,12 @@ export type ChainTokenTable = db_Schema.FundingChainToken
28
28
  /** Columns of the `funding_routes` table. */
29
29
  export type RouteTable = db_Schema.FundingRoute
30
30
 
31
- /** A route row whose `capabilities` may be absent (validated input, fixtures). */
32
- export type RouteRow = Omit<db_Schema.FundingRoute, 'capabilities'> & {
31
+ /** A route row whose optional JSON fields may be absent in validated input. */
32
+ export type RouteRow = Omit<db_Schema.FundingRoute, 'capabilities' | 'configuration'> & {
33
33
  /** Executable capability; absent and null both mean indicative-only. */
34
34
  capabilities?: db_Schema.FundingRoute['capabilities'] | undefined
35
+ /** Provider-specific route configuration; absent and null are equivalent. */
36
+ configuration?: db_Schema.FundingRoute['configuration'] | undefined
35
37
  }
36
38
 
37
39
  /** Stored funding catalog rows and version metadata. */
@@ -117,7 +119,11 @@ export function publish(db: Db.Db, input: publish.Input): Promise<db_Schema.Fund
117
119
  await tx.kysely
118
120
  .insertInto('funding_routes')
119
121
  .values(
120
- input.routes.map((route) => ({ ...route, capabilities: route.capabilities ?? null })),
122
+ input.routes.map((route) => ({
123
+ ...route,
124
+ capabilities: route.capabilities ?? null,
125
+ configuration: route.configuration ?? null,
126
+ })),
121
127
  )
122
128
  .execute()
123
129
 
@@ -0,0 +1,53 @@
1
+ import { RpcRequest } from 'ox'
2
+
3
+ import * as Utils from './utils.js'
4
+
5
+ const request = RpcRequest.from({ id: 1, method: 'eth_sendRawTransaction', params: ['0x'] })
6
+
7
+ describe('rpcErrorJson', () => {
8
+ test('maps upstream expired transactions to a transaction-rejected error', () => {
9
+ const error = {
10
+ cause: {
11
+ code: -32603,
12
+ message:
13
+ 'Revm error: transaction expired: current block timestamp 1755616740 >= validBefore 1755616739',
14
+ },
15
+ }
16
+
17
+ expect(Utils.rpcErrorJson(request, error)).toMatchInlineSnapshot(`
18
+ {
19
+ "error": {
20
+ "code": -32003,
21
+ "data": {
22
+ "code": "transaction_expired",
23
+ },
24
+ "message": "Transaction expired.",
25
+ },
26
+ "id": 1,
27
+ "jsonrpc": "2.0",
28
+ }
29
+ `)
30
+ })
31
+
32
+ test('preserves unrelated upstream internal errors', () => {
33
+ const error = {
34
+ code: -32603,
35
+ data: { reason: 'database unavailable' },
36
+ message: 'Revm error: database unavailable',
37
+ }
38
+
39
+ expect(Utils.rpcErrorJson(request, error)).toMatchInlineSnapshot(`
40
+ {
41
+ "error": {
42
+ "code": -32603,
43
+ "data": {
44
+ "reason": "database unavailable",
45
+ },
46
+ "message": "Revm error: database unavailable",
47
+ },
48
+ "id": 1,
49
+ "jsonrpc": "2.0",
50
+ }
51
+ `)
52
+ })
53
+ })
@@ -127,6 +127,21 @@ export function rpcErrorJson(request: RpcRequest.RpcRequest, error: unknown) {
127
127
  { request },
128
128
  )
129
129
 
130
+ if (
131
+ inner.code === RpcResponse.InternalError.code &&
132
+ /^Revm error: transaction expired(?:\.|: .+)?$/.test(inner.message)
133
+ )
134
+ return RpcResponse.from(
135
+ {
136
+ error: {
137
+ code: RpcResponse.TransactionRejectedError.code,
138
+ data: { code: 'transaction_expired' },
139
+ message: 'Transaction expired.',
140
+ },
141
+ },
142
+ { request },
143
+ )
144
+
130
145
  const { code, data, message } = inner
131
146
  return RpcResponse.from(
132
147
  {
@@ -676,7 +676,10 @@ export declare namespace middleware {
676
676
  }
677
677
 
678
678
  /** MPP payment options used by auth middleware. */
679
- type MppOptions = Omit<NonNullable<Parameters<typeof Mppx.create>[0]>, 'methods'> & {
679
+ type MppOptions = Omit<
680
+ Parameters<typeof Mppx.create<readonly [ReturnType<typeof tempo.session>]>>[0],
681
+ 'methods'
682
+ > & {
680
683
  /** Paid-request quota per access principal. Defaults to 100 per minute. */
681
684
  rateLimit?: RateLimit.Limit | undefined
682
685
  /** Global Tempo Session options passed to the MPP method instantiator. */
@@ -1451,8 +1454,8 @@ export function require<environment extends Environment = Environment>(
1451
1454
  return proceed()
1452
1455
  }
1453
1456
 
1454
- const apiKey = await auth.apiKey?.resolve(c, credential.token)
1455
- if (!apiKey) {
1457
+ const apiKey_resolved = await auth.apiKey?.resolve(c, credential.token)
1458
+ if (!apiKey_resolved) {
1456
1459
  // A bearer session token is indistinguishable from an API key at the
1457
1460
  // header, so a failed key lookup falls through to the session lane
1458
1461
  // before rejecting.
@@ -1470,6 +1473,10 @@ export function require<environment extends Environment = Environment>(
1470
1473
  })
1471
1474
  }
1472
1475
 
1476
+ const scopes = Scope.expandAliases(apiKey_resolved.scopes)
1477
+ const apiKey =
1478
+ scopes === apiKey_resolved.scopes ? apiKey_resolved : { ...apiKey_resolved, scopes }
1479
+
1473
1480
  // Sandbox keys are testnet-only. A sandbox key that explicitly targets
1474
1481
  // mainnet (`?chainId`) is rejected with a clear message rather than
1475
1482
  // steered or downgraded to anonymous, so the caller learns their key does
@@ -1847,7 +1854,8 @@ function getRequestId(c: hono_Context) {
1847
1854
  return typeof variables['requestId'] === 'string' ? variables['requestId'] : undefined
1848
1855
  }
1849
1856
 
1850
- function hasPaymentCredential(c: hono_Context) {
1857
+ /** True when the request carries an `Authorization: Payment` MPP credential. */
1858
+ export function hasPaymentCredential(c: hono_Context) {
1851
1859
  const authorization = c.req.header('authorization')
1852
1860
  if (!authorization) return false
1853
1861
  return !!Credential.extractPaymentScheme(authorization)
@@ -21,8 +21,8 @@ describe('deriveZoneId', () => {
21
21
  test('derives IDs from mainnet and testnet Zone chain schemes', () => {
22
22
  expect([
23
23
  EarnVaults.deriveZoneId(421_700_006),
24
- EarnVaults.deriveZoneId(1_424_310_001),
25
- ]).toStrictEqual([6, 1])
24
+ EarnVaults.deriveZoneId(1_424_310_003),
25
+ ]).toStrictEqual([6, 3])
26
26
  })
27
27
 
28
28
  test('rejects a source chain ID', () => {
@@ -245,7 +245,7 @@ describe('resolveZoneRoutes', () => {
245
245
  zones: [TestEarn.zone],
246
246
  }),
247
247
  ).rejects.toThrowErrorMatchingInlineSnapshot(
248
- `[EarnVaults.InvalidRegistryError: Zone 1424310001 is configured more than once.]`,
248
+ `[EarnVaults.InvalidRegistryError: Zone 1424310003 is configured more than once.]`,
249
249
  )
250
250
  await expect(
251
251
  EarnVaults.resolveZoneRoutes({
@@ -167,7 +167,7 @@ export namespace schema {
167
167
  .strictObject({
168
168
  chainId: Schema.ChainId.check(
169
169
  z.describe('Private Zone chain id.'),
170
- z.meta({ examples: [1424310001] }),
170
+ z.meta({ examples: [1424310003] }),
171
171
  ),
172
172
  deploymentBlock: z
173
173
  .number()
@@ -175,11 +175,11 @@ export namespace schema {
175
175
  z.int(),
176
176
  z.nonnegative(),
177
177
  z.describe('Parent-chain block that deployed the Earn router.'),
178
- z.meta({ examples: [31039238] }),
178
+ z.meta({ examples: [31730087] }),
179
179
  ),
180
180
  earnRouter: Schema.Address.check(
181
181
  z.describe('Parent-chain router for this Zone and Earn vault.'),
182
- z.meta({ examples: ['0x9144086a79dea966d8bf1b52125ebed8919fc45c'] }),
182
+ z.meta({ examples: ['0xebedc74ffeead186acc6cac05eb82fb9e093a7c7'] }),
183
183
  ),
184
184
  })
185
185
  .check(z.describe('Curated Earn route through one private Zone.'))
@@ -0,0 +1,46 @@
1
+ import * as http from 'node:http'
2
+
3
+ import * as Fetch from './Fetch.js'
4
+
5
+ describe('withTimeout', () => {
6
+ test('aborts a fetch after the configured duration', async () => {
7
+ const server = http.createServer(() => {})
8
+ const url = await listen(server)
9
+
10
+ try {
11
+ const fetch = Fetch.withTimeout(globalThis.fetch, { timeout: 10 })
12
+ await expect(fetch(url)).rejects.toMatchObject({ name: 'TimeoutError' })
13
+ } finally {
14
+ server.closeAllConnections()
15
+ await close(server)
16
+ }
17
+ })
18
+ })
19
+
20
+ describe('resolveSignal', () => {
21
+ test('preserves caller and Request signals', () => {
22
+ const signal = AbortSignal.timeout(1_000)
23
+ const request = new Request('https://api.tempo.xyz', { signal })
24
+
25
+ expect(Fetch.resolveSignal('https://api.tempo.xyz', { init: { signal }, timeout: 10 })).toBe(
26
+ signal,
27
+ )
28
+ expect(Fetch.resolveSignal(request, { timeout: 10 })).toBe(request.signal)
29
+ })
30
+ })
31
+
32
+ function close(server: http.Server): Promise<void> {
33
+ return new Promise((resolve, reject) => {
34
+ server.close((error) => (error ? reject(error) : resolve()))
35
+ })
36
+ }
37
+
38
+ function listen(server: http.Server): Promise<string> {
39
+ return new Promise((resolve) => {
40
+ server.listen(0, '127.0.0.1', () => {
41
+ const address = server.address()
42
+ if (!address || typeof address === 'string') throw new Error('Expected a TCP server address.')
43
+ resolve(`http://127.0.0.1:${address.port}`)
44
+ })
45
+ })
46
+ }
@@ -0,0 +1,56 @@
1
+ import * as Timeout from './Timeout.js'
2
+
3
+ /** Installs the default outbound fetch timeout once for this runtime. */
4
+ export function install(): void {
5
+ if (isInstalled(globalThis.fetch)) return
6
+
7
+ const fetch = withTimeout(globalThis.fetch.bind(globalThis), { timeout: Timeout.duration })
8
+ Object.defineProperty(fetch, timeoutMarker, { value: true })
9
+ globalThis.fetch = fetch
10
+ }
11
+
12
+ /** Adds a default maximum duration to fetch calls that do not carry a signal. */
13
+ export function withTimeout(
14
+ fetch: typeof globalThis.fetch,
15
+ options: withTimeout.Options,
16
+ ): typeof globalThis.fetch {
17
+ return ((input, init) => {
18
+ const signal = resolveSignal(input, { init, timeout: options.timeout })
19
+ return fetch(input, { ...init, signal })
20
+ }) as typeof globalThis.fetch
21
+ }
22
+
23
+ /** Resolves a caller signal or creates the default fetch deadline signal. */
24
+ export function resolveSignal(
25
+ input: Parameters<typeof globalThis.fetch>[0],
26
+ options: resolveSignal.Options,
27
+ ): AbortSignal {
28
+ return (
29
+ options.init?.signal ??
30
+ (input instanceof Request ? input.signal : Timeout.signal({ timeout: options.timeout }))
31
+ )
32
+ }
33
+
34
+ export declare namespace resolveSignal {
35
+ /** Options for {@link resolveSignal}. */
36
+ type Options = {
37
+ /** Optional fetch initialization carrying a caller signal. */
38
+ init?: Parameters<typeof globalThis.fetch>[1]
39
+ /** Default maximum duration in milliseconds. */
40
+ timeout: number
41
+ }
42
+ }
43
+
44
+ export declare namespace withTimeout {
45
+ /** Options for {@link withTimeout}. */
46
+ type Options = {
47
+ /** Maximum fetch duration in milliseconds. */
48
+ timeout: number
49
+ }
50
+ }
51
+
52
+ const timeoutMarker = Symbol.for('tempo.api.fetch.timeout')
53
+
54
+ function isInstalled(fetch: typeof globalThis.fetch): boolean {
55
+ return timeoutMarker in fetch
56
+ }
@@ -457,6 +457,27 @@ describe('middleware', () => {
457
457
  `)
458
458
  })
459
459
 
460
+ test('preserves the request error and unfinished stages', async () => {
461
+ const entries: Log.Entry[] = []
462
+ const causes: Error[] = []
463
+ const api = app({
464
+ emit: (entry, cause) => {
465
+ entries.push(entry)
466
+ if (cause) causes.push(cause)
467
+ },
468
+ })
469
+ const cause = new Error('Memoized cache-miss flight timed out after 15000ms.')
470
+ api.get('/activities', (c) => {
471
+ void Timing.time(c, 'transaction_activity_mpp', () => new Promise(() => {}))
472
+ return Response.upstream(c, cause)
473
+ })
474
+
475
+ await api.request('/activities')
476
+
477
+ expect(causes[0]).toBe(cause)
478
+ expect(entries[0]?.pendingTimings?.['transaction_activity_mpp']).toBeGreaterThanOrEqual(0)
479
+ })
480
+
460
481
  test('includes bounded funding provider attempts', async () => {
461
482
  const entries: Log.Entry[] = []
462
483
  const api = app({ emit: (entry) => void entries.push(entry) })
@@ -151,6 +151,8 @@ export type Entry = {
151
151
  path: string
152
152
  /** MPP payment reason when the request was paid per call. */
153
153
  payment?: Auth.Payment['reason'] | undefined
154
+ /** Operations still running when the response completed, with elapsed milliseconds. */
155
+ pendingTimings?: Record<string, number> | undefined
154
156
  /** Bounded outcome for an MPP relay operation. */
155
157
  mpp?: {
156
158
  /** Resolved Tempo chain selected by the credential. */
@@ -228,7 +230,7 @@ export type Entry = {
228
230
  }
229
231
 
230
232
  /** Sink receiving one {@link Entry} per request. */
231
- export type Emit = (entry: Entry) => Promise<void> | void
233
+ export type Emit = (entry: Entry, cause?: Error) => Promise<void> | void
232
234
 
233
235
  /**
234
236
  * Emits one canonical structured log entry per request, after the response is
@@ -258,7 +260,8 @@ export function middleware<environment extends Environment = Environment>(
258
260
  // Hono cannot resolve variable types through the generic `environment`,
259
261
  // so shared-variable reads are cast from the environment contract above
260
262
  // (the same pattern EdgeCache uses for its cross-layer reads).
261
- const error = c.error as ErrorMetadata | undefined
263
+ const cause = c.error
264
+ const error = cause as (Error & ErrorMetadata) | undefined
262
265
  const principal = c.get('principal' as never) as Auth.Principal | undefined
263
266
  const errorCaptured = c.get('errorCaptured' as never) as boolean | undefined
264
267
  const chainId = c.get('chainId' as never) as number | undefined
@@ -279,7 +282,6 @@ export function middleware<environment extends Environment = Environment>(
279
282
  error?.providerFailures ?? (c.get('providerFailures' as never) as Entry['providerFailures'])
280
283
  const rpc = c.get('rpcResponse' as never) as Entry['rpc']
281
284
  const sponsorship = c.get('sponsorship' as never) as Entry['sponsorship']
282
-
283
285
  // The `request` total metric duplicates `duration` (measured here, one
284
286
  // layer further out), so only operation timings are kept.
285
287
  const metrics = (c.get('serverTiming' as never) as Timing.Metric[] | undefined)?.filter(
@@ -295,6 +297,17 @@ export function middleware<environment extends Environment = Environment>(
295
297
  }, new Map<string, number>()),
296
298
  )
297
299
  : undefined
300
+ const activeTimings = c.get('activeTimings' as never) as Timing.Active[] | undefined
301
+ const pendingTimings = activeTimings?.length
302
+ ? Object.fromEntries(
303
+ activeTimings.reduce((result, timing) => {
304
+ const duration = round(performance.now() - timing.start)
305
+ const existing = result.get(timing.name)
306
+ if (existing === undefined || duration > existing) result.set(timing.name, duration)
307
+ return result
308
+ }, new Map<string, number>()),
309
+ )
310
+ : undefined
298
311
 
299
312
  const rateLimit = (() => {
300
313
  const limit = c.res.headers.get('RateLimit-Limit')
@@ -310,7 +323,7 @@ export function middleware<environment extends Environment = Environment>(
310
323
  }
311
324
  })()
312
325
 
313
- const result = fn({
326
+ const entry = {
314
327
  ...(c.get('edgeCacheStatus' as never) === 'hit' ? { cache: 'hit' as const } : {}),
315
328
  ...(chainId === undefined ? {} : { chainId }),
316
329
  duration: round(duration),
@@ -333,6 +346,7 @@ export function middleware<environment extends Environment = Environment>(
333
346
  ...(mpp === undefined ? {} : { mpp }),
334
347
  path: url.pathname,
335
348
  ...(principal?.payment === undefined ? {} : { payment: principal.payment.reason }),
349
+ ...(pendingTimings === undefined ? {} : { pendingTimings }),
336
350
  ...(principal === undefined
337
351
  ? {}
338
352
  : {
@@ -360,7 +374,8 @@ export function middleware<environment extends Environment = Environment>(
360
374
  ...(sponsorship === undefined ? {} : { sponsorship }),
361
375
  status,
362
376
  ...(timings === undefined ? {} : { timings }),
363
- })
377
+ } satisfies Entry
378
+ const result = fn(entry, cause)
364
379
 
365
380
  if (result instanceof Promise) {
366
381
  const promise = result.catch((error) => console.error('request log sink failed', error))
@@ -148,6 +148,7 @@ export const errorCodes = {
148
148
  500: ['internal_error'],
149
149
  501: ['billing_unconfigured'],
150
150
  502: ['upstream_error'],
151
+ 504: ['request_timeout'],
151
152
  } as const satisfies Record<number, readonly [string, ...string[]]>
152
153
 
153
154
  /**
@@ -220,6 +221,7 @@ const standardErrorNames = {
220
221
  500: 'InternalError',
221
222
  501: 'NotImplementedError',
222
223
  502: 'UpstreamError',
224
+ 504: 'RequestTimeoutError',
223
225
  } as const satisfies Record<keyof typeof errorCodes, string>
224
226
 
225
227
  const reservedErrorNames = new Set<string>(Object.values(standardErrorNames))
@@ -300,12 +302,12 @@ export const paymentChallenge = {
300
302
  headers: { 'WWW-Authenticate': headerRef('WwwAuthenticate') },
301
303
  } as const
302
304
 
303
- type ResponseComponentName = 'InternalError' | 'PaymentRequired' | 'RateLimited'
305
+ type ResponseComponentName = 'InternalError' | 'PaymentRequired' | 'RateLimited' | 'RequestTimeout'
304
306
 
305
307
  /**
306
308
  * Reusable responses registered under `components.responses` (see `App.create`)
307
- * and `$ref`-erenced from operations, so the uniform `402`/`429`/`500` responses
308
- * are defined once instead of inlined on every operation.
309
+ * and `$ref`-erenced from operations, so the uniform `402`/`429`/`500`/`504`
310
+ * responses are defined once instead of inlined on every operation.
309
311
  */
310
312
  export function responseComponents(): ResponsesWithResolver {
311
313
  return {
@@ -320,6 +322,11 @@ export function responseComponents(): ResponsesWithResolver {
320
322
  'Rate limit exceeded. On endpoints that accept MPP, over-quota unpaid requests return `402 Payment Required` instead.',
321
323
  headers: { ...rateLimitHeaders },
322
324
  },
325
+ RequestTimeout: {
326
+ content: errorContent(504, errorCodes[504], 'Request timed out'),
327
+ description:
328
+ 'Request timed out. The request did not complete within the 60-second deadline; retry it.',
329
+ },
323
330
  }
324
331
  }
325
332
 
@@ -344,6 +351,7 @@ export function standardError(
344
351
  ): ResponsesWithResolver[string] {
345
352
  if (status === 429) return responseRef('RateLimited')
346
353
  if (status === 500) return responseRef('InternalError')
354
+ if (status === 504) return responseRef('RequestTimeout')
347
355
  return { content: errorContent(status, mergeErrorCodes(status, codes), description), description }
348
356
  }
349
357
 
@@ -352,7 +360,8 @@ export function standardError(
352
360
  * pre-filled, plus the success response (with rate-limit headers):
353
361
  *
354
362
  * - `400` invalid request, `401` missing/invalid key, `403` forbidden, `429`
355
- * rate limited, `500` internal error, and `502` upstream failure on every operation;
363
+ * rate limited, `500` internal error, `502` upstream failure, and `504`
364
+ * request timeout on every operation;
356
365
  * - `404`/`409`/`412`/`413`/`415` only when an `errors` entry is set.
357
366
  *
358
367
  * `error.code` is enumerated per status from {@link errorCodes}. Operation-level
@@ -404,6 +413,7 @@ export function responses<schema extends z.ZodMiniType>(
404
413
  500: responseRef('InternalError'),
405
414
  ...(errors?.[501] && { 501: error(501, 'Not implemented.') }),
406
415
  502: error(502, 'Upstream data failure.'),
416
+ 504: responseRef('RequestTimeout'),
407
417
  }
408
418
  }
409
419
 
@@ -142,7 +142,7 @@ export declare namespace error {
142
142
  }
143
143
 
144
144
  /** HTTP status code accepted by the public error helper. */
145
- type Status = 400 | 401 | 403 | 404 | 409 | 412 | 413 | 415 | 429 | 500 | 501 | 502 | 503
145
+ type Status = 400 | 401 | 403 | 404 | 409 | 412 | 413 | 415 | 429 | 500 | 501 | 502 | 503 | 504
146
146
 
147
147
  /** Options for creating a JSON error response. */
148
148
  type Options<status extends Status = Status, code extends string = string> = {
@@ -6,6 +6,8 @@ describe('ChainId', () => {
6
6
  test('resolves aliases to their chain id', () => {
7
7
  expect(Schema.ChainId.parse('mainnet')).toMatchInlineSnapshot(`4217`)
8
8
  expect(Schema.ChainId.parse('testnet')).toMatchInlineSnapshot(`42431`)
9
+ expect(Schema.ChainId.parse('1424310001')).toMatchInlineSnapshot(`1424310003`)
10
+ expect(Schema.ChainId.parse(1_424_310_001)).toMatchInlineSnapshot(`1424310003`)
9
11
  })
10
12
 
11
13
  test('accepts numeric chain ids as numbers or strings', () => {
@@ -90,12 +90,19 @@ export function tokenAddress(example: core_Hex.Hex) {
90
90
  )
91
91
  }
92
92
 
93
- /** Maps human-readable chain aliases to their Tempo chain id. */
93
+ /** Maps caller-facing chain aliases to their current Tempo chain id. */
94
94
  export const chainIdAlias = {
95
+ // TODO: Remove this alias after clients migrate to chain 1424310003.
96
+ 1_424_310_001: 1_424_310_003,
95
97
  mainnet: 4217,
96
98
  testnet: 42431,
97
99
  } as const
98
100
 
101
+ /** Resolves a numeric chain id through the caller-facing alias map. */
102
+ export function resolveChainId(chainId: number): number {
103
+ return chainIdAlias[chainId as keyof typeof chainIdAlias] ?? chainId
104
+ }
105
+
99
106
  /**
100
107
  * Tempo chain id. Accepts a chain alias (`mainnet`/`testnet`) or any positive
101
108
  * integer chain id (a number or numeric string), normalizing all forms to a
@@ -110,7 +117,10 @@ export const ChainId = z
110
117
  z.enum(['mainnet', 'testnet']),
111
118
  z.transform((alias) => chainIdAlias[alias]),
112
119
  ),
113
- z.coerce.number(),
120
+ z.pipe(
121
+ z.coerce.number(),
122
+ z.transform((chainId) => resolveChainId(chainId)),
123
+ ),
114
124
  ]),
115
125
  z.number().check(z.int(), z.positive()),
116
126
  )