tapimo 0.7.2 → 0.8.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 (294) hide show
  1. package/README.md +10 -7
  2. package/dist/ApiKey.d.ts +3 -0
  3. package/dist/ApiKey.d.ts.map +1 -1
  4. package/dist/ApiKey.js +10 -0
  5. package/dist/ApiKey.js.map +1 -1
  6. package/dist/ApiKeys.d.ts +7 -3
  7. package/dist/ApiKeys.d.ts.map +1 -1
  8. package/dist/ApiKeys.js +9 -2
  9. package/dist/ApiKeys.js.map +1 -1
  10. package/dist/App.d.ts +20434 -19484
  11. package/dist/App.d.ts.map +1 -1
  12. package/dist/App.js +62 -4
  13. package/dist/App.js.map +1 -1
  14. package/dist/admin/App.d.ts +84 -74
  15. package/dist/admin/App.d.ts.map +1 -1
  16. package/dist/admin/apps/api-keys.d.ts +14 -1
  17. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  18. package/dist/admin/apps/api-keys.js +6 -2
  19. package/dist/admin/apps/api-keys.js.map +1 -1
  20. package/dist/admin/apps/organizations.d.ts +2 -0
  21. package/dist/admin/apps/organizations.d.ts.map +1 -1
  22. package/dist/admin/apps/verified-tokens.d.ts +168 -168
  23. package/dist/admin/ui.generated.d.ts +1 -1
  24. package/dist/admin/ui.generated.js +67 -67
  25. package/dist/admin/ui.generated.js.map +1 -1
  26. package/dist/analytics/Analytics.d.ts +2 -0
  27. package/dist/analytics/Analytics.d.ts.map +1 -1
  28. package/dist/analytics/Analytics.js +3 -0
  29. package/dist/analytics/Analytics.js.map +1 -1
  30. package/dist/apps/data/App.d.ts +10305 -12785
  31. package/dist/apps/data/App.d.ts.map +1 -1
  32. package/dist/apps/data/App.js +4 -8
  33. package/dist/apps/data/App.js.map +1 -1
  34. package/dist/apps/data/routes/activities.d.ts +24 -24
  35. package/dist/apps/data/routes/addresses.d.ts +288 -288
  36. package/dist/apps/data/routes/balances.d.ts +6 -6
  37. package/dist/apps/data/routes/blocks.d.ts +39 -39
  38. package/dist/apps/data/routes/coingecko.d.ts +170 -170
  39. package/dist/apps/data/routes/earn.d.ts +436 -6
  40. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  41. package/dist/apps/data/routes/earn.js +631 -4
  42. package/dist/apps/data/routes/earn.js.map +1 -1
  43. package/dist/apps/data/routes/exchanges.d.ts +10 -10
  44. package/dist/apps/data/routes/fee-amm.d.ts +2 -2
  45. package/dist/apps/data/routes/indexer.d.ts +6 -6
  46. package/dist/apps/data/routes/indexer.js +4 -1
  47. package/dist/apps/data/routes/indexer.js.map +1 -1
  48. package/dist/apps/data/routes/receipts.d.ts +235 -235
  49. package/dist/apps/data/routes/rpc.d.ts +1 -1
  50. package/dist/apps/data/routes/rpc.js +4 -1
  51. package/dist/apps/data/routes/rpc.js.map +1 -1
  52. package/dist/apps/data/routes/tokenlist.d.ts +36 -36
  53. package/dist/apps/data/routes/tokens.d.ts +6 -6
  54. package/dist/apps/data/routes/transactions.d.ts +2 -2
  55. package/dist/apps/data/routes/transfers.d.ts +1 -1
  56. package/dist/apps/data/routes/valuation.d.ts +1 -1
  57. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  58. package/dist/apps/data/routes/webhooks.d.ts +9 -9
  59. package/dist/apps/data/routes/zones.d.ts +14 -14
  60. package/dist/apps/funding/Access.d.ts +16 -0
  61. package/dist/apps/funding/Access.d.ts.map +1 -0
  62. package/dist/apps/funding/Access.js +22 -0
  63. package/dist/apps/funding/Access.js.map +1 -0
  64. package/dist/apps/{data/routes/funding.d.ts → funding/App.d.ts} +1717 -3035
  65. package/dist/apps/funding/App.d.ts.map +1 -0
  66. package/dist/apps/funding/App.js +46 -0
  67. package/dist/apps/funding/App.js.map +1 -0
  68. package/dist/apps/funding/Inventory.d.ts +5 -0
  69. package/dist/apps/funding/Inventory.d.ts.map +1 -0
  70. package/dist/apps/funding/Inventory.js +25 -0
  71. package/dist/apps/funding/Inventory.js.map +1 -0
  72. package/dist/apps/funding/ProviderAttempts.d.ts +5 -0
  73. package/dist/apps/funding/ProviderAttempts.d.ts.map +1 -0
  74. package/dist/apps/funding/ProviderAttempts.js +24 -0
  75. package/dist/apps/funding/ProviderAttempts.js.map +1 -0
  76. package/dist/apps/funding/index.d.ts +2 -0
  77. package/dist/apps/funding/index.d.ts.map +1 -0
  78. package/dist/apps/funding/index.js +2 -0
  79. package/dist/apps/funding/index.js.map +1 -0
  80. package/dist/apps/funding/routes/chains.d.ts +169 -0
  81. package/dist/apps/funding/routes/chains.d.ts.map +1 -0
  82. package/dist/apps/funding/routes/chains.js +80 -0
  83. package/dist/apps/funding/routes/chains.js.map +1 -0
  84. package/dist/apps/funding/routes/deposit-addresses.d.ts +2388 -0
  85. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -0
  86. package/dist/apps/funding/routes/deposit-addresses.js +860 -0
  87. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -0
  88. package/dist/apps/funding/routes/providers.d.ts +91 -0
  89. package/dist/apps/funding/routes/providers.d.ts.map +1 -0
  90. package/dist/apps/funding/routes/providers.js +56 -0
  91. package/dist/apps/funding/routes/providers.js.map +1 -0
  92. package/dist/apps/funding/routes/quotes.d.ts +426 -0
  93. package/dist/apps/funding/routes/quotes.d.ts.map +1 -0
  94. package/dist/apps/funding/routes/quotes.js +188 -0
  95. package/dist/apps/funding/routes/quotes.js.map +1 -0
  96. package/dist/apps/funding/routes/transfers.d.ts +1999 -0
  97. package/dist/apps/funding/routes/transfers.d.ts.map +1 -0
  98. package/dist/apps/funding/routes/transfers.js +614 -0
  99. package/dist/apps/funding/routes/transfers.js.map +1 -0
  100. package/dist/apps/index.d.ts +1 -0
  101. package/dist/apps/index.d.ts.map +1 -1
  102. package/dist/apps/index.js +1 -0
  103. package/dist/apps/index.js.map +1 -1
  104. package/dist/apps/management/App.d.ts +430 -165
  105. package/dist/apps/management/App.d.ts.map +1 -1
  106. package/dist/apps/management/routes/api-keys.d.ts +298 -21
  107. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  108. package/dist/apps/management/routes/api-keys.js +75 -1
  109. package/dist/apps/management/routes/api-keys.js.map +1 -1
  110. package/dist/apps/management/routes/billing.d.ts +6 -6
  111. package/dist/apps/management/routes/faucet.d.ts +35 -35
  112. package/dist/apps/management/routes/invitations.d.ts +48 -48
  113. package/dist/apps/management/routes/invite-links.d.ts +38 -38
  114. package/dist/apps/management/routes/members.d.ts +69 -69
  115. package/dist/apps/management/routes/orgs.d.ts +37 -37
  116. package/dist/apps/management/routes/projects.d.ts +25 -25
  117. package/dist/apps/management/routes/scopes.d.ts +1 -1
  118. package/dist/apps/management/routes/usage.d.ts +72 -72
  119. package/dist/apps/management/routes/verified-token-requests.d.ts +18 -18
  120. package/dist/apps/management/routes/webhooks.d.ts +128 -128
  121. package/dist/apps/mcp.d.ts +2 -3
  122. package/dist/apps/mcp.d.ts.map +1 -1
  123. package/dist/apps/mcp.js +16 -14
  124. package/dist/apps/mcp.js.map +1 -1
  125. package/dist/apps/metadata.d.ts +4 -0
  126. package/dist/apps/metadata.d.ts.map +1 -1
  127. package/dist/apps/metadata.js +10 -0
  128. package/dist/apps/metadata.js.map +1 -1
  129. package/dist/apps/mpp/App.d.ts +2 -2
  130. package/dist/cloudflare.d.ts +50 -0
  131. package/dist/cloudflare.d.ts.map +1 -1
  132. package/dist/cloudflare.js +178 -0
  133. package/dist/cloudflare.js.map +1 -1
  134. package/dist/db/Db.d.ts +2 -0
  135. package/dist/db/Db.d.ts.map +1 -1
  136. package/dist/db/Db.js +37 -0
  137. package/dist/db/Db.js.map +1 -1
  138. package/dist/db/Schema.d.ts +12 -0
  139. package/dist/db/Schema.d.ts.map +1 -1
  140. package/dist/db/Schema.js +20 -0
  141. package/dist/db/Schema.js.map +1 -1
  142. package/dist/db/tables/fundingDepositAddresses.d.ts +13 -0
  143. package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -1
  144. package/dist/db/tables/fundingDepositAddresses.js +12 -0
  145. package/dist/db/tables/fundingDepositAddresses.js.map +1 -1
  146. package/dist/db/tables/fundingDepositRequestObservations.d.ts +45 -0
  147. package/dist/db/tables/fundingDepositRequestObservations.d.ts.map +1 -0
  148. package/dist/db/tables/fundingDepositRequestObservations.js +46 -0
  149. package/dist/db/tables/fundingDepositRequestObservations.js.map +1 -0
  150. package/dist/db/tables/fundingDeposits.d.ts +57 -1
  151. package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
  152. package/dist/db/tables/fundingDeposits.js +82 -0
  153. package/dist/db/tables/fundingDeposits.js.map +1 -1
  154. package/dist/internal/Auth.d.ts +11 -11
  155. package/dist/internal/Auth.d.ts.map +1 -1
  156. package/dist/internal/Auth.js +41 -15
  157. package/dist/internal/Auth.js.map +1 -1
  158. package/dist/internal/Cache.js +1 -1
  159. package/dist/internal/Cache.js.map +1 -1
  160. package/dist/internal/EdgeCache.d.ts +2 -2
  161. package/dist/internal/EdgeCache.d.ts.map +1 -1
  162. package/dist/internal/EdgeCache.js +10 -4
  163. package/dist/internal/EdgeCache.js.map +1 -1
  164. package/dist/internal/IpAllowlist.d.ts +15 -0
  165. package/dist/internal/IpAllowlist.d.ts.map +1 -0
  166. package/dist/internal/IpAllowlist.js +27 -0
  167. package/dist/internal/IpAllowlist.js.map +1 -0
  168. package/dist/internal/OpenApi.d.ts +1 -1
  169. package/dist/internal/OpenApi.js +1 -1
  170. package/dist/internal/OpenApi.js.map +1 -1
  171. package/dist/internal/Tidx.d.ts +6 -0
  172. package/dist/internal/Tidx.d.ts.map +1 -1
  173. package/dist/internal/Tidx.js +13 -3
  174. package/dist/internal/Tidx.js.map +1 -1
  175. package/dist/internal/Viem.d.ts +8 -0
  176. package/dist/internal/Viem.d.ts.map +1 -1
  177. package/dist/internal/Viem.js +12 -5
  178. package/dist/internal/Viem.js.map +1 -1
  179. package/dist/internal/funding/Deposit.d.ts +42 -0
  180. package/dist/internal/funding/Deposit.d.ts.map +1 -1
  181. package/dist/internal/funding/Deposit.js +37 -5
  182. package/dist/internal/funding/Deposit.js.map +1 -1
  183. package/dist/internal/funding/Evidence.d.ts +1 -5
  184. package/dist/internal/funding/Evidence.d.ts.map +1 -1
  185. package/dist/internal/funding/Evidence.js +4 -3
  186. package/dist/internal/funding/Evidence.js.map +1 -1
  187. package/dist/internal/funding/Provider.d.ts +102 -0
  188. package/dist/internal/funding/Provider.d.ts.map +1 -1
  189. package/dist/internal/funding/Provider.js +5 -0
  190. package/dist/internal/funding/Provider.js.map +1 -1
  191. package/dist/internal/funding/Reconciliation.d.ts +16 -2
  192. package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
  193. package/dist/internal/funding/Reconciliation.js +257 -48
  194. package/dist/internal/funding/Reconciliation.js.map +1 -1
  195. package/dist/internal/funding/SourceObservation.d.ts +108 -0
  196. package/dist/internal/funding/SourceObservation.d.ts.map +1 -0
  197. package/dist/internal/funding/SourceObservation.js +143 -0
  198. package/dist/internal/funding/SourceObservation.js.map +1 -0
  199. package/dist/internal/funding/Subsidy.d.ts +2 -2
  200. package/dist/internal/funding/Subsidy.d.ts.map +1 -1
  201. package/dist/internal/funding/Subsidy.js +1 -1
  202. package/dist/internal/funding/Subsidy.js.map +1 -1
  203. package/dist/internal/funding/index.d.ts +1 -0
  204. package/dist/internal/funding/index.d.ts.map +1 -1
  205. package/dist/internal/funding/index.js +1 -0
  206. package/dist/internal/funding/index.js.map +1 -1
  207. package/dist/internal/funding/providers/relay.d.ts +43 -1
  208. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  209. package/dist/internal/funding/providers/relay.js +434 -27
  210. package/dist/internal/funding/providers/relay.js.map +1 -1
  211. package/package.json +3 -3
  212. package/src/ApiKey.test.ts +35 -0
  213. package/src/ApiKey.ts +27 -0
  214. package/src/ApiKeys.test.ts +29 -0
  215. package/src/ApiKeys.ts +11 -3
  216. package/src/App.test.ts +23 -5
  217. package/src/App.ts +76 -5
  218. package/src/Client.test-d.ts +171 -19
  219. package/src/admin/apps/api-keys.test.ts +25 -1
  220. package/src/admin/apps/api-keys.ts +6 -2
  221. package/src/admin/ui.generated.ts +67 -67
  222. package/src/analytics/Analytics.ts +4 -0
  223. package/src/apps/composition.test.ts +100 -2
  224. package/src/apps/data/App.test.ts +0 -12
  225. package/src/apps/data/App.ts +4 -8
  226. package/src/apps/data/routes/earn.test.ts +283 -0
  227. package/src/apps/data/routes/earn.ts +800 -3
  228. package/src/apps/data/routes/indexer.ts +2 -1
  229. package/src/apps/data/routes/rpc.ts +2 -0
  230. package/src/apps/data/routes/tokens.test.ts +6 -8
  231. package/src/apps/funding/Access.ts +26 -0
  232. package/src/apps/funding/App.test.ts +78 -0
  233. package/src/apps/funding/App.ts +63 -0
  234. package/src/apps/funding/Inventory.ts +30 -0
  235. package/src/apps/funding/ProviderAttempts.ts +26 -0
  236. package/src/apps/funding/index.ts +1 -0
  237. package/src/apps/funding/routes/chains.test.ts +78 -0
  238. package/src/apps/funding/routes/chains.ts +101 -0
  239. package/src/apps/funding/routes/deposit-addresses.test.ts +1628 -0
  240. package/src/apps/funding/routes/deposit-addresses.ts +1015 -0
  241. package/src/apps/funding/routes/providers.test.ts +165 -0
  242. package/src/apps/funding/routes/providers.ts +73 -0
  243. package/src/apps/funding/routes/quotes.test.ts +511 -0
  244. package/src/apps/funding/routes/quotes.ts +205 -0
  245. package/src/apps/funding/routes/transfers.test.ts +1255 -0
  246. package/src/apps/funding/routes/transfers.ts +704 -0
  247. package/src/apps/index.ts +1 -0
  248. package/src/apps/management/routes/api-keys.test.ts +98 -0
  249. package/src/apps/management/routes/api-keys.ts +96 -1
  250. package/src/apps/mcp.test.ts +17 -2
  251. package/src/apps/mcp.ts +18 -13
  252. package/src/apps/metadata.ts +12 -0
  253. package/src/apps/mpp/internal/Policy.test.ts +7 -1
  254. package/src/cloudflare.ts +279 -0
  255. package/src/db/Db.ts +62 -0
  256. package/src/db/Schema.ts +24 -0
  257. package/src/db/tables/fundingDepositAddresses.ts +28 -0
  258. package/src/db/tables/fundingDepositRequestObservations.test.ts +106 -0
  259. package/src/db/tables/fundingDepositRequestObservations.ts +99 -0
  260. package/src/db/tables/fundingDeposits.test.ts +138 -0
  261. package/src/db/tables/fundingDeposits.ts +145 -1
  262. package/src/internal/Auth.test.ts +177 -5
  263. package/src/internal/Auth.ts +48 -18
  264. package/src/internal/Cache.test.ts +37 -3
  265. package/src/internal/Cache.ts +1 -1
  266. package/src/internal/EdgeCache.test.ts +7 -7
  267. package/src/internal/EdgeCache.ts +13 -4
  268. package/src/internal/IpAllowlist.test.ts +52 -0
  269. package/src/internal/IpAllowlist.ts +39 -0
  270. package/src/internal/Log.test.ts +7 -1
  271. package/src/internal/OpenApi.ts +1 -1
  272. package/src/internal/Tidx.test.ts +28 -0
  273. package/src/internal/Tidx.ts +19 -3
  274. package/src/internal/Viem.test.ts +28 -0
  275. package/src/internal/Viem.ts +21 -5
  276. package/src/internal/funding/Deposit.ts +74 -5
  277. package/src/internal/funding/Evidence.test.ts +2 -2
  278. package/src/internal/funding/Evidence.ts +4 -9
  279. package/src/internal/funding/Provider.ts +115 -0
  280. package/src/internal/funding/Reconciliation.local.test.ts +7 -0
  281. package/src/internal/funding/Reconciliation.test.ts +1052 -35
  282. package/src/internal/funding/Reconciliation.ts +328 -56
  283. package/src/internal/funding/SourceObservation.test.ts +301 -0
  284. package/src/internal/funding/SourceObservation.ts +267 -0
  285. package/src/internal/funding/Subsidy.ts +2 -2
  286. package/src/internal/funding/index.test.ts +1 -0
  287. package/src/internal/funding/index.ts +1 -0
  288. package/src/internal/funding/providers/relay.test.ts +94 -4
  289. package/src/internal/funding/providers/relay.ts +528 -27
  290. package/dist/apps/data/routes/funding.d.ts.map +0 -1
  291. package/dist/apps/data/routes/funding.js +0 -1557
  292. package/dist/apps/data/routes/funding.js.map +0 -1
  293. package/src/apps/data/routes/funding.test.ts +0 -2994
  294. package/src/apps/data/routes/funding.ts +0 -1821
@@ -52,7 +52,7 @@ describe('response', () => {
52
52
  expect(etag(first)).toMatchInlineSnapshot(`"<etag>"`)
53
53
  expect(first.headers.get('ratelimit-remaining')).toMatchInlineSnapshot(`"9"`)
54
54
  expect(first.headers.get('vary')).toMatchInlineSnapshot(
55
- `"Accept-Encoding, Authorization, Tempo-API-Key"`,
55
+ `"Accept-Encoding, Authorization, Tempo-API-Key, X-API-Key"`,
56
56
  )
57
57
 
58
58
  expect(await second.json()).toMatchInlineSnapshot(`
@@ -66,7 +66,7 @@ describe('response', () => {
66
66
  expect(second.headers.get('etag') === first.headers.get('etag')).toMatchInlineSnapshot(`true`)
67
67
  expect(second.headers.get('ratelimit-remaining')).toMatchInlineSnapshot(`"8"`)
68
68
  expect(second.headers.get('vary')).toMatchInlineSnapshot(
69
- `"Accept-Encoding, Authorization, Tempo-API-Key"`,
69
+ `"Accept-Encoding, Authorization, Tempo-API-Key, X-API-Key"`,
70
70
  )
71
71
 
72
72
  expect(third.status).toMatchInlineSnapshot(`304`)
@@ -77,11 +77,45 @@ describe('response', () => {
77
77
  expect(third.headers.get('etag') === first.headers.get('etag')).toMatchInlineSnapshot(`true`)
78
78
  expect(third.headers.get('ratelimit-remaining')).toMatchInlineSnapshot(`"8"`)
79
79
  expect(third.headers.get('vary')).toMatchInlineSnapshot(
80
- `"Accept-Encoding, Authorization, Tempo-API-Key"`,
80
+ `"Accept-Encoding, Authorization, Tempo-API-Key, X-API-Key"`,
81
81
  )
82
82
  })
83
83
  })
84
84
 
85
+ test('varies cached responses by the legacy API-key header', async () => {
86
+ await withCaches(async () => {
87
+ let calls = 0
88
+ const app = new Hono()
89
+ app.get('/', Cache.response({ key: (c) => c.req.url, name: 'test' }), (c) =>
90
+ c.json({ calls: ++calls, token: c.req.header('x-api-key') }),
91
+ )
92
+
93
+ const first = await app.request('/', { headers: { 'x-api-key': 'one' } })
94
+ const second = await app.request('/', { headers: { 'x-api-key': 'two' } })
95
+ const hit = await app.request('/', { headers: { 'x-api-key': 'one' } })
96
+
97
+ expect(calls).toMatchInlineSnapshot(`2`)
98
+ expect(await first.json()).toMatchInlineSnapshot(`
99
+ {
100
+ "calls": 1,
101
+ "token": "one",
102
+ }
103
+ `)
104
+ expect(await second.json()).toMatchInlineSnapshot(`
105
+ {
106
+ "calls": 2,
107
+ "token": "two",
108
+ }
109
+ `)
110
+ expect(await hit.json()).toMatchInlineSnapshot(`
111
+ {
112
+ "calls": 1,
113
+ "token": "one",
114
+ }
115
+ `)
116
+ })
117
+ })
118
+
85
119
  test('stores entries with the shared policy form, not the client form', async () => {
86
120
  await withCaches(async (stores) => {
87
121
  const app = new Hono()
@@ -57,7 +57,7 @@ export function response<environment extends Env>(
57
57
  options: response.Options<environment>,
58
58
  ): MiddlewareHandler<environment> {
59
59
  const defaultCacheControl = options.cacheControl ?? policies.default
60
- const vary = options.vary ?? ['Accept-Encoding', 'Authorization', 'Tempo-API-Key']
60
+ const vary = options.vary ?? ['Accept-Encoding', 'Authorization', 'Tempo-API-Key', 'X-API-Key']
61
61
  // hono/cache suffixes its key with `::<directive>=<value>` for every
62
62
  // configured vary directive (trimmed, lowercased, deduped). Mirror that
63
63
  // normalization so the `no-cache` purge below deletes exactly the entry the
@@ -37,18 +37,18 @@ describe('middleware', () => {
37
37
  expect(second.headers.get('vary')).toMatchInlineSnapshot(`"Accept-Encoding"`)
38
38
  })
39
39
 
40
- test('serves authenticated requests from the same shared cache entry', async () => {
40
+ test('bypasses the cache for credential headers', async () => {
41
41
  const { app, store, downstreamCalls } = createApp()
42
42
 
43
- // An authenticated request warms the cache; subsequent requests — whether
44
- // authenticated (with any key) or anonymous — are all served from the one
45
- // shared entry without re-running downstream auth/rate-limiting.
46
43
  await app.request('/cached', { headers: { authorization: 'Bearer one' } })
44
+ await app.request('/cached', { headers: { authorization: 'Basic malformed' } })
45
+ await app.request('/cached', { headers: { authorization: 'Payment paid' } })
47
46
  await app.request('/cached', { headers: { 'tempo-api-key': 'two' } })
48
- await app.request('/cached')
47
+ await app.request('/cached', { headers: { 'x-api-key': 'three' } })
49
48
 
50
- expect(downstreamCalls()).toMatchInlineSnapshot(`1`)
51
- expect(store.size).toMatchInlineSnapshot(`1`)
49
+ expect(downstreamCalls()).toMatchInlineSnapshot(`5`)
50
+ expect(store.gets).toMatchInlineSnapshot(`0`)
51
+ expect(store.size).toMatchInlineSnapshot(`0`)
52
52
  })
53
53
 
54
54
  test('bypasses the cache for query API key credentials', async () => {
@@ -48,8 +48,8 @@ const volatileHeaders = [
48
48
  /**
49
49
  * Serves GET responses from the edge cache store BEFORE auth and rate-limiting
50
50
  * run, so cache hits skip the per-request store round-trips that dominate
51
- * latency for every caller, authenticated or not. Place this ahead of the
52
- * auth middleware.
51
+ * latency. Credential-bearing requests bypass this layer so per-key policy
52
+ * enforcement always runs. Place this ahead of the auth middleware.
53
53
  *
54
54
  * Only 2xx responses from routes that opted into caching (via `Cache.response`,
55
55
  * which publishes the `edgeCache` policy) are stored, and the policy is only
@@ -84,8 +84,8 @@ export function middleware<environment extends Env>(
84
84
  store !== undefined &&
85
85
  c.req.method === 'GET' &&
86
86
  !c.req.raw.headers.has('range') &&
87
- // Query credentials must reach auth and never enter cache keys.
88
- !new URL(c.req.url).searchParams.has('key') &&
87
+ // Credential-bearing requests must reach auth for per-key policy enforcement.
88
+ !hasCredential(c.req.raw) &&
89
89
  isCacheableRoute(c)
90
90
  const key = eligible
91
91
  ? await routeCacheKey(c).catch(() => {
@@ -144,6 +144,15 @@ export function middleware<environment extends Env>(
144
144
  }
145
145
  }
146
146
 
147
+ function hasCredential(request: Request): boolean {
148
+ return (
149
+ request.headers.has('tempo-api-key') ||
150
+ request.headers.has('x-api-key') ||
151
+ request.headers.has('authorization') ||
152
+ new URL(request.url).searchParams.has('key')
153
+ )
154
+ }
155
+
147
156
  // Marks handlers whose route publishes an edge-cache policy; the middleware
148
157
  // pays the store lookup only when a matched handler carries it.
149
158
  const cacheableSymbol = Symbol('tempoApi.edgeCacheable')
@@ -0,0 +1,52 @@
1
+ import { Hono } from 'hono'
2
+
3
+ import * as IpAllowlist from './IpAllowlist.js'
4
+
5
+ describe('isRule', () => {
6
+ test.each([
7
+ ['203.0.113.7', true],
8
+ ['0.0.0.0/0', true],
9
+ ['203.0.113.0/24', true],
10
+ ['203.0.113.7/32', true],
11
+ ['2001:db8::7', true],
12
+ ['::/0', true],
13
+ ['2001:db8::/32', true],
14
+ ['2001:db8::7/128', true],
15
+ ['::ffff:192.0.2.1', true],
16
+ ['203.0.113.0/33', false],
17
+ ['2001:db8::/129', false],
18
+ ['203.0.113.0/-1', false],
19
+ ['203.0.113.999', false],
20
+ ['2001:db8::1%eth0', false],
21
+ ['203.0.113.0/24/1', false],
22
+ [' 203.0.113.7', false],
23
+ ['203.0.113.7 ', false],
24
+ ['*', false],
25
+ ['', false],
26
+ ])('%s -> %s', (rule, expected) => {
27
+ expect(IpAllowlist.isRule(rule)).toBe(expected)
28
+ })
29
+ })
30
+
31
+ describe('allows', () => {
32
+ test.each([
33
+ ['203.0.113.7', ['203.0.113.7'], true],
34
+ ['203.0.113.7', ['0.0.0.0/0'], true],
35
+ ['203.0.113.7', ['203.0.113.0/24'], true],
36
+ ['203.0.113.0', ['203.0.113.0/24'], true],
37
+ ['203.0.113.255', ['203.0.113.0/24'], true],
38
+ ['203.0.114.7', ['203.0.113.0/24'], false],
39
+ ['2001:db8:1::7', ['2001:db8::/32'], true],
40
+ ['2001:db8::7', ['2001:db8::7/128'], true],
41
+ ['2001:db8::8', ['2001:db8::7/128'], false],
42
+ ['2001:db9::7', ['2001:db8::/32'], false],
43
+ ['203.0.113.7', ['2001:db8::/32'], false],
44
+ ['::ffff:192.0.2.1', ['::ffff:192.0.2.1'], true],
45
+ ['not-an-ip', ['203.0.113.0/24'], false],
46
+ ])('%s in %j -> %s', async (address, rules, expected) => {
47
+ const app = new Hono().get('/', async (c) =>
48
+ c.json({ allowed: await IpAllowlist.allows(c, { address, rules }) }),
49
+ )
50
+ expect(await (await app.request('/')).json()).toEqual({ allowed: expected })
51
+ })
52
+ })
@@ -0,0 +1,39 @@
1
+ import type { Context } from 'hono'
2
+ import { ipRestriction } from 'hono/ip-restriction'
3
+
4
+ /** Returns whether a value is an IPv4/IPv6 address or CIDR range. */
5
+ export function isRule(value: string): boolean {
6
+ if (value === '*' || value.trim() !== value || value.split('/').length > 2) return false
7
+ try {
8
+ ipRestriction(() => '', { allowList: [value] })
9
+ return true
10
+ } catch {
11
+ return false
12
+ }
13
+ }
14
+
15
+ /** Returns whether the client address matches at least one allowlist rule. */
16
+ export async function allows(c: Context, options: allows.Options): Promise<boolean> {
17
+ const { address, rules } = options
18
+ if (!address) return false
19
+ try {
20
+ const denied = await ipRestriction(
21
+ () => address,
22
+ { allowList: [...rules] },
23
+ () => new Response(null, { status: 403 }),
24
+ )(c, async () => {})
25
+ return denied === undefined
26
+ } catch {
27
+ return false
28
+ }
29
+ }
30
+
31
+ export declare namespace allows {
32
+ /** Inputs used to check a client address. */
33
+ type Options = {
34
+ /** Trusted client IP address. */
35
+ address?: string | undefined
36
+ /** Exact IP and CIDR rules to match. */
37
+ rules: readonly string[]
38
+ }
39
+ }
@@ -102,7 +102,13 @@ describe('middleware', () => {
102
102
  const api = app({ emit: (entry) => void entries.push(entry) })
103
103
  api.use('*', async (c, next) => {
104
104
  c.set('principal', {
105
- apiKey: { environment: 'production', id: 'key_1', orgId: 'org_1', scopes: ['data:read'] },
105
+ apiKey: {
106
+ allowedIps: [],
107
+ environment: 'production',
108
+ id: 'key_1',
109
+ orgId: 'org_1',
110
+ scopes: ['data:read'],
111
+ },
106
112
  environment: 'production',
107
113
  id: 'key_1',
108
114
  orgId: 'org_1',
@@ -80,7 +80,7 @@ export const errorCodes = {
80
80
  'api_key_malformed',
81
81
  ],
82
82
  401: ['api_key_missing', 'api_key_invalid', 'unauthorized'],
83
- 403: ['api_key_forbidden', 'limit_exceeded'],
83
+ 403: ['api_key_forbidden', 'api_key_ip_forbidden', 'limit_exceeded'],
84
84
  404: [
85
85
  'not_found',
86
86
  'token_not_found',
@@ -460,6 +460,34 @@ describe('getClient', () => {
460
460
  fetch.mockRestore()
461
461
  }
462
462
  })
463
+
464
+ test('sends Zone headers', async () => {
465
+ const fetch = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
466
+ new Response(JSON.stringify({ columns: [], ok: true, row_count: 0, rows: [] }), {
467
+ headers: { 'content-type': 'application/json' },
468
+ }),
469
+ )
470
+ try {
471
+ const client = Tidx.getClient({
472
+ chainId: Zones.zoneE.id,
473
+ tidx: {
474
+ baseUrl: 'https://internal.indexer',
475
+ zoneHeaders: {
476
+ 'CF-Access-Client-Id': 'client-id',
477
+ 'CF-Access-Client-Secret': 'client-secret',
478
+ },
479
+ },
480
+ zone: Zones.zoneE,
481
+ })
482
+ await client.fetch({ query: 'select 1' })
483
+
484
+ const upstream = fetch.mock.calls[0]![0] as Request
485
+ expect(upstream.headers.get('CF-Access-Client-Id')).toBe('client-id')
486
+ expect(upstream.headers.get('CF-Access-Client-Secret')).toBe('client-secret')
487
+ } finally {
488
+ fetch.mockRestore()
489
+ }
490
+ })
463
491
  })
464
492
 
465
493
  describe('createGetClient', () => {
@@ -41,6 +41,7 @@ export function resolve(
41
41
  const config = typeof tidx === 'function' ? tidx(context) : tidx
42
42
  return {
43
43
  ...Auth.resolveCredential({ chainId: context.chainId, value: config?.auth }),
44
+ ...(context.zone && config?.zoneHeaders ? { zoneHeaders: config.zoneHeaders } : {}),
44
45
  baseUrl:
45
46
  Viem.resolveUrl(config?.baseUrl, context.chainId) ??
46
47
  context.zone?.rpcUrls['tidx']?.http[0] ??
@@ -61,18 +62,25 @@ export declare namespace resolve {
61
62
  basicAuth?: string | undefined
62
63
  /** Bearer auth credential, or undefined to send no Bearer auth. */
63
64
  bearerAuth?: string | undefined
65
+ /** Headers sent to Zone indexer upstreams. */
66
+ zoneHeaders?: Viem.ZoneHeaders | undefined
64
67
  }
65
68
  }
66
69
 
67
70
  export function getClient(options: getClient.Options): Client {
68
71
  const { chainId = Viem.defaultChainId, principal = null, tidx, zone } = options
69
- const { baseUrl, basicAuth, bearerAuth } = resolve(tidx, { chainId, principal, zone })
72
+ const { baseUrl, basicAuth, bearerAuth, zoneHeaders } = resolve(tidx, {
73
+ chainId,
74
+ principal,
75
+ zone,
76
+ })
70
77
  return withTransientRetry(
71
78
  Tidx.create({
72
79
  baseUrl,
73
80
  basicAuth,
74
81
  bearerAuth,
75
82
  chainId,
83
+ headers: zoneHeaders,
76
84
  }),
77
85
  )
78
86
  }
@@ -96,8 +104,12 @@ export function createGetClient(options: createGetClient.Options = {}): GetClien
96
104
  const { principal = null, zone } = options
97
105
  // Resolve to derive the cache key; `getClient` re-resolves to build the
98
106
  // client on a miss. `resolve` is pure and cheap, so the extra call is free.
99
- const { baseUrl, basicAuth, bearerAuth } = resolve(tidx, { chainId, principal, zone })
100
- const cacheKey = `${baseUrl}|${basicAuth ?? ''}|${bearerAuth ?? ''}|${chainId}`
107
+ const { baseUrl, basicAuth, bearerAuth, zoneHeaders } = resolve(tidx, {
108
+ chainId,
109
+ principal,
110
+ zone,
111
+ })
112
+ const cacheKey = `${baseUrl}|${basicAuth ?? ''}|${bearerAuth ?? ''}|${JSON.stringify(zoneHeaders)}|${chainId}`
101
113
  const existing = clients.get(cacheKey)
102
114
  if (existing) return existing
103
115
 
@@ -312,12 +324,16 @@ export declare namespace getClient {
312
324
  auth?: Auth.ChainCredential | undefined
313
325
  /** Resolves the TIDX base URL for a Tempo chain id, or a static base URL. */
314
326
  baseUrl?: UrlResolver | undefined
327
+ /** Headers sent to Zone indexer upstreams. */
328
+ zoneHeaders?: Viem.ZoneHeaders | undefined
315
329
  }
316
330
  | ((context: Viem.resolveRpc.Context) => {
317
331
  /** Shared or per-chain TIDX credentials, or undefined for none. */
318
332
  auth?: Auth.ChainCredential | undefined
319
333
  /** TIDX base URL to use; falls back to the built-in public host when omitted. */
320
334
  baseUrl?: string | undefined
335
+ /** Headers sent to Zone indexer upstreams. */
336
+ zoneHeaders?: Viem.ZoneHeaders | undefined
321
337
  })
322
338
  }
323
339
 
@@ -265,6 +265,34 @@ describe('getClient', () => {
265
265
  fetch.mockRestore()
266
266
  }
267
267
  })
268
+
269
+ test('sends Zone headers to both internal and public Zone URLs', async () => {
270
+ const fetch = vi
271
+ .spyOn(globalThis, 'fetch')
272
+ .mockResolvedValue(Response.json({ id: 1, jsonrpc: '2.0', result: '0x1079' }))
273
+ try {
274
+ const client = Viem.getClient({
275
+ chainId: zone.id,
276
+ rpc: {
277
+ publicZoneUrl: 'https://public.zone.rpc.test',
278
+ zoneHeaders: {
279
+ 'CF-Access-Client-Id': 'client-id',
280
+ 'CF-Access-Client-Secret': 'client-secret',
281
+ },
282
+ },
283
+ zone,
284
+ zoneToken: 'zone-token',
285
+ })
286
+ await client.getChainId()
287
+
288
+ const [input, init] = fetch.mock.calls[0]!
289
+ const upstream = new Request(input, init)
290
+ expect(upstream.headers.get('CF-Access-Client-Id')).toBe('client-id')
291
+ expect(upstream.headers.get('CF-Access-Client-Secret')).toBe('client-secret')
292
+ } finally {
293
+ fetch.mockRestore()
294
+ }
295
+ })
268
296
  })
269
297
 
270
298
  describe('createGetClient', () => {
@@ -47,6 +47,9 @@ export type UrlResolver =
47
47
  | Record<number, string>
48
48
  | ((chainId: ChainId) => string | undefined)
49
49
 
50
+ /** Headers required when connecting to a Zone upstream. */
51
+ export type ZoneHeaders = Record<string, string>
52
+
50
53
  /** Returns chain ids declared by a static chain-id-to-URL map. */
51
54
  export function configuredChainIds(value: UrlResolver | undefined): number[] {
52
55
  const urls = parseUrls(value)
@@ -83,6 +86,7 @@ export function resolveRpc(
83
86
  return {
84
87
  ...auth,
85
88
  ...(publicZoneUrl ? { publicZoneUrl } : {}),
89
+ ...(context.zone && config?.zoneHeaders ? { zoneHeaders: config.zoneHeaders } : {}),
86
90
  url:
87
91
  resolveUrl(config?.url, context.chainId) ??
88
92
  context.zone?.rpcUrls['internal']?.http[0] ??
@@ -114,6 +118,8 @@ export declare namespace resolveRpc {
114
118
  bearerAuth?: string | undefined
115
119
  /** Public Zone RPC URL that accepts a caller-provided authorization token. */
116
120
  publicZoneUrl?: string | undefined
121
+ /** Headers sent to Zone RPC upstreams. */
122
+ zoneHeaders?: ZoneHeaders | undefined
117
123
  /**
118
124
  * Resolved RPC URL, or `undefined` when neither a configured `rpc` nor the
119
125
  * built-in {@link url} map covers the chain (e.g. an unconfigured localnet).
@@ -125,7 +131,7 @@ export declare namespace resolveRpc {
125
131
 
126
132
  export function getClient(options: getClient.Options): getClient.ReturnType {
127
133
  const { chainId = defaultChainId, principal = null, rpc, zone, zoneToken } = options
128
- const { basicAuth, bearerAuth, publicZoneUrl, url } = resolveRpc(rpc, {
134
+ const { basicAuth, bearerAuth, publicZoneUrl, url, zoneHeaders } = resolveRpc(rpc, {
129
135
  chainId,
130
136
  principal,
131
137
  zone,
@@ -141,9 +147,10 @@ export function getClient(options: getClient.Options): getClient.ReturnType {
141
147
  ? `Bearer ${bearerAuth}`
142
148
  : undefined
143
149
  const fetchOptions =
144
- authorization || zoneToken
150
+ authorization || zoneToken || zoneHeaders
145
151
  ? {
146
152
  headers: {
153
+ ...(zoneHeaders ? Object.fromEntries(new Headers(zoneHeaders)) : {}),
147
154
  ...(authorization ? { Authorization: authorization } : {}),
148
155
  ...(zoneToken ? { [ZoneRpcAuthentication.headerName]: zoneToken } : {}),
149
156
  },
@@ -171,18 +178,23 @@ export function createGetClient(options: createGetClient.Options = {}): createGe
171
178
  const zones = new Map((options.zones ?? []).map((zone) => [zone.id, zone]))
172
179
 
173
180
  return (chainId = defaultChainId_option, principal = null) => {
174
- const { basicAuth, bearerAuth, url } = resolveRpc(rpc, {
181
+ const { basicAuth, bearerAuth, url, zoneHeaders } = resolveRpc(rpc, {
175
182
  chainId,
176
183
  principal,
177
184
  zone: zones.get(chainId),
178
185
  })
179
- const cacheKey = JSON.stringify([url, basicAuth, bearerAuth, chainId])
186
+ const cacheKey = JSON.stringify([url, basicAuth, bearerAuth, zoneHeaders, chainId])
180
187
  const existing = clients.get(cacheKey)
181
188
  if (existing) return existing
182
189
 
183
190
  const client = getClient({
184
191
  chainId,
185
- rpc: { auth: basicAuth ?? bearerAuth, url: () => url },
192
+ rpc: {
193
+ auth: basicAuth ?? bearerAuth,
194
+ url: () => url,
195
+ ...(zoneHeaders ? { zoneHeaders } : {}),
196
+ },
197
+ zone: zones.get(chainId),
186
198
  })
187
199
  clients.set(cacheKey, client)
188
200
  return client
@@ -240,6 +252,8 @@ export declare namespace getClient {
240
252
  auth?: Auth.ChainCredential | undefined
241
253
  /** Resolves the public Zone RPC URL used with caller-provided authorization. */
242
254
  publicZoneUrl?: UrlResolver | undefined
255
+ /** Headers sent to Zone RPC upstreams. */
256
+ zoneHeaders?: ZoneHeaders | undefined
243
257
  /** Resolves the RPC URL for a Tempo chain id. */
244
258
  url?: UrlResolver | undefined
245
259
  }
@@ -248,6 +262,8 @@ export declare namespace getClient {
248
262
  auth?: Auth.ChainCredential | undefined
249
263
  /** Public Zone RPC URL used with caller-provided authorization. */
250
264
  publicZoneUrl?: string | undefined
265
+ /** Headers sent to Zone RPC upstreams. */
266
+ zoneHeaders?: ZoneHeaders | undefined
251
267
  /** RPC URL to use; falls back to the built-in public host when omitted. */
252
268
  url?: string | undefined
253
269
  })
@@ -7,6 +7,12 @@ import * as Id from '../Id.js'
7
7
  import * as Schema from '../Schema.js'
8
8
  import * as Provider from './Provider.js'
9
9
 
10
+ /** Mechanisms that can first detect a funding deposit. */
11
+ export const detectionTriggers = ['chain', 'manual', 'poll', 'webhook'] as const
12
+
13
+ /** Mechanism that first detected a funding deposit. */
14
+ export type DetectionTrigger = (typeof detectionTriggers)[number]
15
+
10
16
  /**
11
17
  * Public funding deposit statuses:
12
18
  *
@@ -95,6 +101,11 @@ export namespace schema {
95
101
  .readonly(z.array(z.string()))
96
102
  .check(z.describe(description), z.meta({ examples: [[`0x${'aa'.repeat(32)}`]] }))
97
103
 
104
+ /** Mechanism that first detected a funding deposit. */
105
+ export const DetectionTrigger = z
106
+ .enum(detectionTriggers)
107
+ .check(z.describe('How the deposit was first detected.'), z.meta({ examples: ['webhook'] }))
108
+
98
109
  /** Funding deposit lifecycle status. */
99
110
  export const Status = z
100
111
  .enum(statuses)
@@ -187,6 +198,7 @@ export namespace schema {
187
198
  z.meta({ examples: ['2026-08-04T00:01:00.000Z'] }),
188
199
  ),
189
200
  depositAddressId: Snapshot.shape.depositAddressId,
201
+ detectionTrigger: z.optional(DetectionTrigger),
190
202
  destinationAmount: Snapshot.shape.destinationAmount,
191
203
  destinationAmountRequired: Snapshot.shape.destinationAmountRequired,
192
204
  destinationChain: Snapshot.shape.destinationChain,
@@ -238,6 +250,46 @@ export type Public = z.output<typeof schema.FundingDeposit>
238
250
 
239
251
  /** Creates a durable record for one provider-observed source transaction. */
240
252
  export async function create(db: Db.Db, input: create.Input): Promise<FundingDeposits.Record> {
253
+ return FundingDeposits.insert(db, await record(db, input))
254
+ }
255
+
256
+ /** Creates or returns the deposit for one verified chain transfer. */
257
+ export async function createFromSource(
258
+ db: Db.Db,
259
+ input: createFromSource.Input,
260
+ ): Promise<FundingDeposits.insertOrGetSource.Result> {
261
+ const stored = await record(db, {
262
+ detectionTrigger: 'chain',
263
+ now: input.now,
264
+ providerRequestId: null,
265
+ snapshot: input.snapshot,
266
+ sourceTransactionHash: input.sourceTransactionHash,
267
+ sourceTransferIndex: input.sourceTransferIndex,
268
+ })
269
+ return FundingDeposits.insertOrGetSource(db, {
270
+ ...stored,
271
+ sourceTransferIndex: input.sourceTransferIndex,
272
+ })
273
+ }
274
+
275
+ export declare namespace createFromSource {
276
+ /** Verified source transfer used to create a chain-detected deposit. */
277
+ type Input = {
278
+ /** Detection time override for deterministic tests. */
279
+ now?: Date | undefined
280
+ /** Public route and verified source amount. */
281
+ snapshot: Snapshot
282
+ /** Source transaction containing the deposited transfer. */
283
+ sourceTransactionHash: string
284
+ /** Verified transfer position within the source transaction. */
285
+ sourceTransferIndex: number
286
+ }
287
+ }
288
+
289
+ async function record(
290
+ db: Db.Db,
291
+ input: Omit<create.Input, 'providerRequestId'> & { providerRequestId: string | null },
292
+ ): Promise<FundingDeposits.Record> {
241
293
  const now = input.now ?? new Date()
242
294
  const depositAddressId = schema.Snapshot.shape.depositAddressId.parse(
243
295
  input.snapshot.depositAddressId,
@@ -256,18 +308,23 @@ export async function create(db: Db.Db, input: create.Input): Promise<FundingDep
256
308
  sourceToken: address.snapshot.sourceToken,
257
309
  sourceTransactionHashes: [input.sourceTransactionHash],
258
310
  })
259
- return FundingDeposits.insert(db, {
311
+ return {
260
312
  createdAt: now.toISOString(),
261
313
  depositAddressId: snapshot.depositAddressId,
314
+ detectionTrigger: input.detectionTrigger ?? null,
262
315
  environment: address.environment,
263
316
  id: input.id ?? generateId(now),
264
317
  orgId: address.orgId,
318
+ pollObservedAt: input.pollObservedAt ?? null,
265
319
  projectId: address.projectId,
266
320
  providerOutputAmount: input.providerOutputAmount ?? null,
267
321
  providerOutputToken: address.providerOutputToken,
268
322
  providerRequestId: input.providerRequestId,
269
323
  providerRequestIds: [
270
- ...new Set([input.providerRequestId, ...(input.providerRequestIds ?? [])]),
324
+ ...new Set([
325
+ ...(input.providerRequestId === null ? [] : [input.providerRequestId]),
326
+ ...(input.providerRequestIds ?? []),
327
+ ]),
271
328
  ],
272
329
  providerState: input.providerState ?? null,
273
330
  providerTransactionHashes: input.providerTransactionHashes ?? [],
@@ -285,16 +342,21 @@ export async function create(db: Db.Db, input: create.Input): Promise<FundingDep
285
342
  tempoGasPaid: input.tempoGasPaid ?? null,
286
343
  updatedAt: now.toISOString(),
287
344
  version: 1,
288
- })
345
+ webhookReceivedAt: input.webhookReceivedAt ?? null,
346
+ }
289
347
  }
290
348
 
291
349
  export declare namespace create {
292
350
  /** Fields required to persist a provider-observed deposit. */
293
351
  type Input = {
352
+ /** Mechanism that first detected the deposit. */
353
+ detectionTrigger?: DetectionTrigger | undefined
294
354
  /** Deposit id override for deterministic tests. */
295
355
  id?: string | undefined
296
356
  /** Detection time override for deterministic tests. */
297
357
  now?: Date | undefined
358
+ /** When polling first observed the provider request. */
359
+ pollObservedAt?: string | undefined
298
360
  /** Provider output amount confirmed on Tempo. */
299
361
  providerOutputAmount?: z.output<typeof Schema.TokenAmount> | undefined
300
362
  /** Provider request that reported the source transaction. */
@@ -323,6 +385,8 @@ export declare namespace create {
323
385
  subsidyAmount?: z.output<typeof Schema.TokenAmount> | undefined
324
386
  /** Tempo gas paid for settlement, in base units. */
325
387
  tempoGasPaid?: string | undefined
388
+ /** When Tempo first received an authenticated provider webhook. */
389
+ webhookReceivedAt?: string | undefined
326
390
  }
327
391
  }
328
392
 
@@ -362,12 +426,12 @@ export async function transition(
362
426
  expectedVersion: options.expectedVersion,
363
427
  id: options.id,
364
428
  providerOutputAmount: options.providerOutputAmount ?? current.providerOutputAmount,
365
- providerRequestId: current.providerRequestId,
429
+ providerRequestId: current.providerRequestId ?? options.providerRequestId ?? null,
366
430
  providerRequestIds: options.providerRequestIds ?? current.providerRequestIds,
367
431
  providerState: options.providerState ?? current.providerState,
368
432
  providerTransactionHashes:
369
433
  options.providerTransactionHashes ?? current.providerTransactionHashes,
370
- providerTransferIndex: current.providerTransferIndex,
434
+ providerTransferIndex: options.providerTransferIndex ?? current.providerTransferIndex,
371
435
  retryState: options.retryState ?? current.retryState,
372
436
  settlementTransaction: options.settlementTransaction ?? current.settlementTransaction,
373
437
  settlementTransactionHash:
@@ -395,12 +459,16 @@ export declare namespace transition {
395
459
  id: string
396
460
  /** Provider output amount confirmed on Tempo. */
397
461
  providerOutputAmount?: z.output<typeof Schema.TokenAmount> | undefined
462
+ /** Provider request attached after chain-first detection. */
463
+ providerRequestId?: string | undefined
398
464
  /** Private provider request identifiers. */
399
465
  providerRequestIds?: readonly string[] | undefined
400
466
  /** Bounded private provider state. */
401
467
  providerState?: PrivateState | undefined
402
468
  /** Provider-leg transaction references. */
403
469
  providerTransactionHashes?: readonly string[] | undefined
470
+ /** Deposit position within the attached provider request. */
471
+ providerTransferIndex?: number | undefined
404
472
  /** Bounded private retry state. */
405
473
  retryState?: PrivateState | undefined
406
474
  /** Persisted settlement transaction bytes for exactly-once rebroadcast. */
@@ -434,6 +502,7 @@ export function toPublic(record: FundingDeposits.Record): Public {
434
502
  return schema.FundingDeposit.parse({
435
503
  ...record.snapshot,
436
504
  createdAt: record.createdAt,
505
+ ...(record.detectionTrigger === null ? {} : { detectionTrigger: record.detectionTrigger }),
437
506
  id: record.id,
438
507
  ...(record.sourceTransferIndex === null
439
508
  ? {}