tapimo 0.13.0 → 0.14.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 (230) hide show
  1. package/dist/App.d.ts +6064 -4829
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +1 -0
  4. package/dist/App.js.map +1 -1
  5. package/dist/Webhooks.js +2 -2
  6. package/dist/Webhooks.js.map +1 -1
  7. package/dist/admin/App.d.ts +1008 -917
  8. package/dist/admin/App.d.ts.map +1 -1
  9. package/dist/admin/apps/earn-vaults.d.ts +48 -0
  10. package/dist/admin/apps/earn-vaults.d.ts.map +1 -1
  11. package/dist/admin/apps/earn-vaults.js +2 -1
  12. package/dist/admin/apps/earn-vaults.js.map +1 -1
  13. package/dist/admin/apps/organizations.d.ts +92 -3
  14. package/dist/admin/apps/organizations.d.ts.map +1 -1
  15. package/dist/admin/apps/organizations.js +69 -7
  16. package/dist/admin/apps/organizations.js.map +1 -1
  17. package/dist/admin/apps/verified-tokens.d.ts +74 -74
  18. package/dist/admin/ui.generated.d.ts +1 -1
  19. package/dist/admin/ui.generated.js +69 -69
  20. package/dist/admin/ui.generated.js.map +1 -1
  21. package/dist/apps/Zones.d.ts +12 -12
  22. package/dist/apps/Zones.js +3 -3
  23. package/dist/apps/data/App.d.ts +2909 -2523
  24. package/dist/apps/data/App.d.ts.map +1 -1
  25. package/dist/apps/data/App.js +11 -6
  26. package/dist/apps/data/App.js.map +1 -1
  27. package/dist/apps/data/routes/addresses.d.ts +22 -22
  28. package/dist/apps/data/routes/earn.d.ts +183 -7
  29. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  30. package/dist/apps/data/routes/earn.js +434 -75
  31. package/dist/apps/data/routes/earn.js.map +1 -1
  32. package/dist/apps/data/routes/exchanges.d.ts +15 -0
  33. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  34. package/dist/apps/data/routes/exchanges.js +180 -114
  35. package/dist/apps/data/routes/exchanges.js.map +1 -1
  36. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  37. package/dist/apps/data/routes/fee-amm.js +18 -19
  38. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  39. package/dist/apps/data/routes/tokens.d.ts +93 -93
  40. package/dist/apps/data/routes/transfers.d.ts +12 -12
  41. package/dist/apps/data/routes/valuation.d.ts +30 -30
  42. package/dist/apps/data/routes/verified-tokens.d.ts +5 -5
  43. package/dist/apps/data/routes/webhooks.d.ts +805 -102
  44. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  45. package/dist/apps/data/routes/webhooks.js +47 -5
  46. package/dist/apps/data/routes/webhooks.js.map +1 -1
  47. package/dist/apps/funding/App.d.ts +456 -45
  48. package/dist/apps/funding/App.d.ts.map +1 -1
  49. package/dist/apps/funding/App.js +1 -1
  50. package/dist/apps/funding/App.js.map +1 -1
  51. package/dist/apps/funding/routes/deposit-addresses.d.ts +17 -17
  52. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
  53. package/dist/apps/funding/routes/deposit-addresses.js +1 -1
  54. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  55. package/dist/apps/funding/routes/transfers.d.ts +654 -73
  56. package/dist/apps/funding/routes/transfers.d.ts.map +1 -1
  57. package/dist/apps/funding/routes/transfers.js +211 -5
  58. package/dist/apps/funding/routes/transfers.js.map +1 -1
  59. package/dist/apps/management/App.d.ts +831 -387
  60. package/dist/apps/management/App.d.ts.map +1 -1
  61. package/dist/apps/management/Billing.d.ts +1 -1
  62. package/dist/apps/management/Billing.d.ts.map +1 -1
  63. package/dist/apps/management/Billing.js +17 -1
  64. package/dist/apps/management/Billing.js.map +1 -1
  65. package/dist/apps/management/routes/api-keys.d.ts +76 -76
  66. package/dist/apps/management/routes/billing.d.ts +76 -76
  67. package/dist/apps/management/routes/faucet.d.ts +12 -12
  68. package/dist/apps/management/routes/invitations.d.ts +24 -24
  69. package/dist/apps/management/routes/invite-links.d.ts +46 -46
  70. package/dist/apps/management/routes/members.d.ts +46 -46
  71. package/dist/apps/management/routes/orgs.d.ts +20 -20
  72. package/dist/apps/management/routes/projects.d.ts +44 -44
  73. package/dist/apps/management/routes/usage.d.ts +32 -32
  74. package/dist/apps/management/routes/verified-token-requests.d.ts +16 -16
  75. package/dist/apps/management/routes/webhooks.d.ts +707 -57
  76. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  77. package/dist/apps/management/routes/webhooks.js +21 -21
  78. package/dist/apps/management/routes/webhooks.js.map +1 -1
  79. package/dist/apps/relay/App.d.ts.map +1 -1
  80. package/dist/apps/relay/App.js +115 -23
  81. package/dist/apps/relay/App.js.map +1 -1
  82. package/dist/apps/relay/Sponsorships.d.ts.map +1 -1
  83. package/dist/apps/relay/Sponsorships.js +27 -1
  84. package/dist/apps/relay/Sponsorships.js.map +1 -1
  85. package/dist/db/Db.d.ts.map +1 -1
  86. package/dist/db/Db.js +22 -0
  87. package/dist/db/Db.js.map +1 -1
  88. package/dist/db/Schema.d.ts +8 -3
  89. package/dist/db/Schema.d.ts.map +1 -1
  90. package/dist/db/Schema.js +16 -1
  91. package/dist/db/Schema.js.map +1 -1
  92. package/dist/db/tables/fundingTransferTransactions.d.ts +38 -0
  93. package/dist/db/tables/fundingTransferTransactions.d.ts.map +1 -1
  94. package/dist/db/tables/fundingTransferTransactions.js +44 -3
  95. package/dist/db/tables/fundingTransferTransactions.js.map +1 -1
  96. package/dist/db/tables/fundingTransfers.d.ts +17 -0
  97. package/dist/db/tables/fundingTransfers.d.ts.map +1 -1
  98. package/dist/db/tables/fundingTransfers.js +21 -0
  99. package/dist/db/tables/fundingTransfers.js.map +1 -1
  100. package/dist/db/tables/organizations.d.ts +24 -0
  101. package/dist/db/tables/organizations.d.ts.map +1 -1
  102. package/dist/db/tables/organizations.js +27 -0
  103. package/dist/db/tables/organizations.js.map +1 -1
  104. package/dist/db/tables/projects.d.ts +25 -1
  105. package/dist/db/tables/projects.d.ts.map +1 -1
  106. package/dist/db/tables/projects.js +26 -0
  107. package/dist/db/tables/projects.js.map +1 -1
  108. package/dist/db/tables/sponsoredTransactions.d.ts +44 -0
  109. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  110. package/dist/db/tables/sponsoredTransactions.js +146 -13
  111. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  112. package/dist/db/tables/webhookSubscriptions.d.ts +10 -6
  113. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  114. package/dist/db/tables/webhookSubscriptions.js +9 -6
  115. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  116. package/dist/internal/EarnVaults.js +5 -5
  117. package/dist/internal/EarnVaults.js.map +1 -1
  118. package/dist/internal/Log.d.ts +1 -1
  119. package/dist/internal/Log.d.ts.map +1 -1
  120. package/dist/internal/Log.js.map +1 -1
  121. package/dist/internal/Viem.d.ts +16 -1
  122. package/dist/internal/Viem.d.ts.map +1 -1
  123. package/dist/internal/Viem.js +12 -2
  124. package/dist/internal/Viem.js.map +1 -1
  125. package/dist/internal/funding/DepositWebhook.d.ts.map +1 -1
  126. package/dist/internal/funding/DepositWebhook.js +2 -1
  127. package/dist/internal/funding/DepositWebhook.js.map +1 -1
  128. package/dist/internal/funding/Evidence.d.ts.map +1 -1
  129. package/dist/internal/funding/Evidence.js +2 -10
  130. package/dist/internal/funding/Evidence.js.map +1 -1
  131. package/dist/internal/funding/Provider.d.ts +40 -0
  132. package/dist/internal/funding/Provider.d.ts.map +1 -1
  133. package/dist/internal/funding/Provider.js +5 -0
  134. package/dist/internal/funding/Provider.js.map +1 -1
  135. package/dist/internal/funding/SourceObservation.d.ts.map +1 -1
  136. package/dist/internal/funding/SourceObservation.js +5 -7
  137. package/dist/internal/funding/SourceObservation.js.map +1 -1
  138. package/dist/internal/funding/Transfer.d.ts +101 -6
  139. package/dist/internal/funding/Transfer.d.ts.map +1 -1
  140. package/dist/internal/funding/Transfer.js +162 -34
  141. package/dist/internal/funding/Transfer.js.map +1 -1
  142. package/dist/internal/funding/TransferReconciliation.d.ts +52 -0
  143. package/dist/internal/funding/TransferReconciliation.d.ts.map +1 -0
  144. package/dist/internal/funding/TransferReconciliation.js +192 -0
  145. package/dist/internal/funding/TransferReconciliation.js.map +1 -0
  146. package/dist/internal/funding/TransferWebhook.d.ts +21 -0
  147. package/dist/internal/funding/TransferWebhook.d.ts.map +1 -0
  148. package/dist/internal/funding/TransferWebhook.js +43 -0
  149. package/dist/internal/funding/TransferWebhook.js.map +1 -0
  150. package/dist/internal/funding/index.d.ts +1 -0
  151. package/dist/internal/funding/index.d.ts.map +1 -1
  152. package/dist/internal/funding/index.js +1 -0
  153. package/dist/internal/funding/index.js.map +1 -1
  154. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  155. package/dist/internal/funding/providers/relay.js +3 -12
  156. package/dist/internal/funding/providers/relay.js.map +1 -1
  157. package/dist/internal/funding/providers/stargate.d.ts +99 -0
  158. package/dist/internal/funding/providers/stargate.d.ts.map +1 -1
  159. package/dist/internal/funding/providers/stargate.js +337 -35
  160. package/dist/internal/funding/providers/stargate.js.map +1 -1
  161. package/dist/internal/webhookDestinations/slack.d.ts.map +1 -1
  162. package/dist/internal/webhookDestinations/slack.js +16 -0
  163. package/dist/internal/webhookDestinations/slack.js.map +1 -1
  164. package/package.json +1 -1
  165. package/src/App.ts +1 -0
  166. package/src/Client.test-d.ts +83 -2
  167. package/src/Webhooks.ts +2 -2
  168. package/src/admin/apps/earn-vaults.ts +2 -1
  169. package/src/admin/apps/organizations.test.ts +53 -1
  170. package/src/admin/apps/organizations.ts +90 -5
  171. package/src/admin/ui/src/lib/organizations.test.ts +112 -1
  172. package/src/admin/ui/src/lib/organizations.ts +55 -0
  173. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +116 -0
  174. package/src/admin/ui.generated.ts +69 -69
  175. package/src/apps/Zones.test.ts +3 -3
  176. package/src/apps/Zones.ts +3 -3
  177. package/src/apps/composition.test.ts +1 -0
  178. package/src/apps/data/App.ts +15 -6
  179. package/src/apps/data/routes/earn.test.ts +369 -14
  180. package/src/apps/data/routes/earn.ts +548 -79
  181. package/src/apps/data/routes/exchanges.test.ts +740 -15
  182. package/src/apps/data/routes/exchanges.ts +954 -753
  183. package/src/apps/data/routes/fee-amm.ts +17 -18
  184. package/src/apps/data/routes/webhooks.test.ts +21 -5
  185. package/src/apps/data/routes/webhooks.ts +63 -8
  186. package/src/apps/funding/App.ts +12 -2
  187. package/src/apps/funding/routes/deposit-addresses.test.ts +4 -4
  188. package/src/apps/funding/routes/deposit-addresses.ts +2 -2
  189. package/src/apps/funding/routes/transfers.test.ts +510 -19
  190. package/src/apps/funding/routes/transfers.ts +271 -5
  191. package/src/apps/management/Billing.ts +18 -5
  192. package/src/apps/management/routes/webhooks.test.ts +3 -3
  193. package/src/apps/management/routes/webhooks.ts +24 -25
  194. package/src/apps/relay/App.test.ts +212 -1
  195. package/src/apps/relay/App.ts +158 -29
  196. package/src/apps/relay/Sponsorships.ts +24 -1
  197. package/src/db/Db.ts +35 -0
  198. package/src/db/Schema.ts +17 -2
  199. package/src/db/tables/fundingTransferTransactions.test.ts +43 -0
  200. package/src/db/tables/fundingTransferTransactions.ts +79 -3
  201. package/src/db/tables/fundingTransfers.test.ts +26 -0
  202. package/src/db/tables/fundingTransfers.ts +36 -0
  203. package/src/db/tables/organizations.test.ts +41 -0
  204. package/src/db/tables/organizations.ts +49 -0
  205. package/src/db/tables/projects.test.ts +55 -0
  206. package/src/db/tables/projects.ts +46 -1
  207. package/src/db/tables/sponsoredTransactions.test.ts +338 -0
  208. package/src/db/tables/sponsoredTransactions.ts +197 -13
  209. package/src/db/tables/webhookSubscriptions.ts +18 -11
  210. package/src/internal/EarnVaults.ts +5 -5
  211. package/src/internal/Log.test-d.ts +2 -0
  212. package/src/internal/Log.ts +2 -0
  213. package/src/internal/Viem.ts +39 -3
  214. package/src/internal/Webhooks.test.ts +45 -0
  215. package/src/internal/funding/DepositWebhook.ts +2 -1
  216. package/src/internal/funding/Evidence.ts +2 -13
  217. package/src/internal/funding/Provider.ts +43 -0
  218. package/src/internal/funding/SourceObservation.ts +5 -9
  219. package/src/internal/funding/Transfer.test.ts +155 -0
  220. package/src/internal/funding/Transfer.ts +254 -37
  221. package/src/internal/funding/TransferReconciliation.test.ts +147 -0
  222. package/src/internal/funding/TransferReconciliation.ts +264 -0
  223. package/src/internal/funding/TransferWebhook.test.ts +128 -0
  224. package/src/internal/funding/TransferWebhook.ts +67 -0
  225. package/src/internal/funding/index.test.ts +1 -0
  226. package/src/internal/funding/index.ts +1 -0
  227. package/src/internal/funding/providers/relay.ts +3 -15
  228. package/src/internal/funding/providers/stargate.test.ts +169 -4
  229. package/src/internal/funding/providers/stargate.ts +498 -51
  230. package/src/internal/webhookDestinations/slack.ts +15 -0
@@ -693,25 +693,24 @@ export async function feeTokenSet(
693
693
  const addresses = await Timing.time(c, 'fee_token_set', () =>
694
694
  Store.memoize(
695
695
  async () => {
696
- // Dynamic raw-`logs` query (hence the cast), mirroring the pools
697
- // listing's FeeManager `Mint` filter but collapsing to the distinct
698
- // user-side tokens — and scoped to the verified candidates via
699
- // `topic2 IN (…)` so the result is bounded by the verified list.
700
- const topics = candidates.map((address) => `'${padTopic(address as `0x${string}`)}'`)
701
- const result = await tidx.fetch({
702
- chainId,
703
- engine: 'clickhouse',
704
- query: `
705
- SELECT DISTINCT topic2
706
- FROM logs
707
- WHERE address = '${feeManager}' AND selector IN ('${mintTopic}', '${legacyMintTopic}')
708
- AND topic2 IN (${topics.join(', ')})
709
- ` as string,
710
- })
711
696
  const tokens = new Set<string>([pathUsd])
712
- for (const row of result.rows) {
713
- const token = unpadTopic(Value.toText(row['topic2']))?.toLowerCase()
714
- if (token !== undefined && Schema.Address.safeParse(token).success) tokens.add(token)
697
+ // PostgreSQL's `topic2` index proves negative lookups without scanning
698
+ // FeeManager history. Probe serially so an uncapped verified list
699
+ // cannot fan out indexer load.
700
+ for (const address of candidates) {
701
+ const topic = padTopic(address as `0x${string}`)
702
+ const result = await tidx.fetch({
703
+ chainId,
704
+ engine: 'postgres',
705
+ query: `
706
+ SELECT 1 AS present
707
+ FROM logs
708
+ WHERE address = '${feeManager}' AND selector IN ('${mintTopic}', '${legacyMintTopic}')
709
+ AND topic2 = '${topic}'
710
+ LIMIT 1
711
+ ` as string,
712
+ })
713
+ if (result.rows.length > 0) tokens.add(address)
715
714
  }
716
715
  return Array.from(tokens)
717
716
  },
@@ -130,6 +130,9 @@ describe('feature gate', () => {
130
130
  expect(catalogBody.data.map((eventType) => eventType.type)).not.toContain(
131
131
  'funding:deposit.updated',
132
132
  )
133
+ expect(catalogBody.data.map((eventType) => eventType.type)).not.toContain(
134
+ 'funding:transfer.updated',
135
+ )
133
136
  expect(created.status).toBe(400)
134
137
  })
135
138
  })
@@ -209,6 +212,7 @@ describe('access matrix', () => {
209
212
  [
210
213
  "block:created",
211
214
  "funding:deposit.updated",
215
+ "funding:transfer.updated",
212
216
  "log:emitted",
213
217
  "token:transfer",
214
218
  "transaction:included",
@@ -261,7 +265,7 @@ describe('CRUD lifecycle', () => {
261
265
  expect(body.error?.code).toBe('api_key_forbidden')
262
266
  })
263
267
 
264
- test('captures funding deposit ownership scope without reading the RPC head', async () => {
268
+ test('captures funding transfer ownership scope without reading the RPC head', async () => {
265
269
  const db = TestApp.database()
266
270
  const projectWriter = {
267
271
  id: 'key_funding_webhook_project',
@@ -275,7 +279,7 @@ describe('CRUD lifecycle', () => {
275
279
  {
276
280
  json: {
277
281
  destination: { type: 'url', url: 'https://example.com/funding' },
278
- eventType: 'funding:deposit.updated',
282
+ eventType: 'funding:transfer.updated',
279
283
  filters: { status: 'completed' },
280
284
  },
281
285
  },
@@ -341,7 +345,7 @@ describe('CRUD lifecycle', () => {
341
345
  chainId: 4217,
342
346
  destination: { type: 'url', url: 'https://example.com/project-b' },
343
347
  environment: 'production',
344
- eventType: 'funding:deposit.updated',
348
+ eventType: 'funding:transfer.updated',
345
349
  owner: { orgId: shared.orgId, type: 'api_key' },
346
350
  projectId: projectB.projectId,
347
351
  })
@@ -1158,8 +1162,12 @@ describe('retry delivery', () => {
1158
1162
  auth: { keys: [writer, reader, other] },
1159
1163
  db,
1160
1164
  })
1161
- const created = await app.v1.webhooks.$post({ json: validBody }, as(writer))
1162
- const { id } = await TestApp.json(created, Webhooks.schema.createWebhook.Response)
1165
+ const { id } = await Core.createSubscription(db, {
1166
+ chainId: 4217,
1167
+ destination: validBody.destination,
1168
+ eventType: validBody.eventType,
1169
+ owner: { orgId: writer.orgId, type: 'api_key' },
1170
+ })
1163
1171
  await Core.recordDelivery(db, {
1164
1172
  attempt: 1,
1165
1173
  createdAt: new Date(1_700_000_000_000).toISOString(),
@@ -1316,6 +1324,7 @@ describe('OpenAPI visibility', () => {
1316
1324
  "Log event",
1317
1325
  "Block event",
1318
1326
  "Funding deposit updated event",
1327
+ "Funding transfer updated event",
1319
1328
  "Ping (test delivery)",
1320
1329
  ]
1321
1330
  `)
@@ -1422,6 +1431,13 @@ describe('OpenAPI visibility', () => {
1422
1431
  ],
1423
1432
  "title": "Funding deposit subscription",
1424
1433
  },
1434
+ {
1435
+ "filters": [
1436
+ "id",
1437
+ "status",
1438
+ ],
1439
+ "title": "Funding transfer subscription",
1440
+ },
1425
1441
  ]
1426
1442
  `)
1427
1443
  })
@@ -18,6 +18,7 @@ import * as WebhookDestination from '../../../internal/WebhookDestination.js'
18
18
  import * as WebhookTransfer from '../../../internal/WebhookTransfer.js'
19
19
  import * as Webhooks from '../../../internal/Webhooks.js'
20
20
  import * as FundingDeposit from '../../../internal/funding/Deposit.js'
21
+ import * as FundingTransfer from '../../../internal/funding/Transfer.js'
21
22
  import * as Transactions from './transactions.js'
22
23
  import * as Transfers from './transfers.js'
23
24
 
@@ -42,6 +43,7 @@ const redactedSlackUrl = 'https://hooks.slack.com/…'
42
43
  export const eventTypes = [
43
44
  'block:created',
44
45
  'funding:deposit.updated',
46
+ 'funding:transfer.updated',
45
47
  'log:emitted',
46
48
  'token:transfer',
47
49
  'transaction:included',
@@ -50,6 +52,20 @@ export const eventTypes = [
50
52
  /** Event types a subscription can listen to. Derived from {@link eventTypes}. */
51
53
  export type EventType = (typeof eventTypes)[number]
52
54
 
55
+ /** Funding resource events produced by the API instead of chain scans. */
56
+ export const fundingEventTypes = [
57
+ 'funding:deposit.updated',
58
+ 'funding:transfer.updated',
59
+ ] as const satisfies readonly EventType[]
60
+
61
+ /** Event type carrying an owner-scoped funding resource. */
62
+ export type FundingEventType = (typeof fundingEventTypes)[number]
63
+
64
+ /** Returns whether an event type carries an owner-scoped funding resource. */
65
+ export function isFundingEventType(eventType: EventType): eventType is FundingEventType {
66
+ return (fundingEventTypes as readonly EventType[]).includes(eventType)
67
+ }
68
+
53
69
  /** Subscribable event types surfaced by `GET /webhooks/event-types`. */
54
70
  const eventTypeDescriptions = [
55
71
  {
@@ -62,6 +78,11 @@ const eventTypeDescriptions = [
62
78
  'A funding deposit materially changed after verified reconciliation, filterable by deposit address, recipient, or status.',
63
79
  type: 'funding:deposit.updated',
64
80
  },
81
+ {
82
+ description:
83
+ 'A funding transfer materially changed, filterable by transfer ID or lifecycle status.',
84
+ type: 'funding:transfer.updated',
85
+ },
65
86
  {
66
87
  description:
67
88
  'Any contract event log, filtered by emitting address, event signature/topics, or decoded arguments.',
@@ -465,6 +486,17 @@ export namespace schema {
465
486
  z.meta({ title: 'Funding deposit filters' }),
466
487
  )
467
488
 
489
+ /** `funding:transfer.updated` subscription filters. All are optional and AND-combined. */
490
+ export const FundingTransferFilters = z
491
+ .strictObject({
492
+ id: z.optional(FundingTransfer.schema.FundingTransfer.shape.id),
493
+ status: z.optional(FundingTransfer.schema.Status),
494
+ })
495
+ .check(
496
+ z.describe('Filters that narrow which funding transfer changes trigger this webhook.'),
497
+ z.meta({ title: 'Funding transfer filters' }),
498
+ )
499
+
468
500
  /**
469
501
  * Optional human context describing what a subscription is for. Surfaced by
470
502
  * destinations (Slack header + description, Better Stack log fields) and echoed
@@ -917,6 +949,9 @@ export namespace schema {
917
949
  /** Public funding deposit after a committed material change. */
918
950
  export const FundingDepositEvent = FundingDeposit.schema.FundingDeposit
919
951
 
952
+ /** Public funding transfer after a committed material change. */
953
+ export const FundingTransferEvent = FundingTransfer.schema.FundingTransfer
954
+
920
955
  /** A `ping` (synthetic test-delivery) payload, sent by `POST /webhooks/:id/ping`. */
921
956
  export const PingEvent = z
922
957
  .object({
@@ -982,6 +1017,13 @@ export namespace schema {
982
1017
  type: z.literal('funding:deposit.updated'),
983
1018
  })
984
1019
  .check(z.meta({ title: 'Funding deposit updated event' })),
1020
+ z
1021
+ .object({
1022
+ ...envelopeBase,
1023
+ data: FundingTransferEvent,
1024
+ type: z.literal('funding:transfer.updated'),
1025
+ })
1026
+ .check(z.meta({ title: 'Funding transfer updated event' })),
985
1027
  z
986
1028
  .object({ ...envelopeBase, data: PingEvent, type: z.literal('ping') })
987
1029
  .check(z.meta({ title: 'Ping (test delivery)' })),
@@ -1089,6 +1131,20 @@ export namespace schema {
1089
1131
  filters: z.optional(FundingDepositFilters),
1090
1132
  })
1091
1133
  .check(z.meta({ title: 'Funding deposit subscription' })),
1134
+ z
1135
+ .object({
1136
+ chainId: z.optional(Schema.ChainId).check(z.meta({ examples: [4217] })),
1137
+ context: z.optional(Context),
1138
+ destination: Destination,
1139
+ eventType: z
1140
+ .literal('funding:transfer.updated')
1141
+ .check(
1142
+ z.describe('Event type for committed funding transfer changes.'),
1143
+ z.meta({ examples: ['funding:transfer.updated'] }),
1144
+ ),
1145
+ filters: z.optional(FundingTransferFilters),
1146
+ })
1147
+ .check(z.meta({ title: 'Funding transfer subscription' })),
1092
1148
  ])
1093
1149
  .check(z.describe('Details for creating a webhook subscription.'))
1094
1150
 
@@ -1293,7 +1349,7 @@ export function webhooks(options: webhooks.Options = {}) {
1293
1349
  const hidden = !options.enabled
1294
1350
  const availableEventTypes = eventTypeDescriptions.filter(
1295
1351
  (eventType) =>
1296
- eventType.type !== 'funding:deposit.updated' ||
1352
+ !isFundingEventType(eventType.type) ||
1297
1353
  options.applicationEventTypes?.includes(eventType.type),
1298
1354
  )
1299
1355
  const availableEventTypeSet = new Set(availableEventTypes.map((eventType) => eventType.type))
@@ -1410,7 +1466,7 @@ export function webhooks(options: webhooks.Options = {}) {
1410
1466
  message: 'Webhook event type is not available in this deployment',
1411
1467
  status: 400,
1412
1468
  })
1413
- if (body.eventType === 'funding:deposit.updated') {
1469
+ if (isFundingEventType(body.eventType)) {
1414
1470
  if (principal?.type !== 'api_key') return unauthorized(c)
1415
1471
  if (
1416
1472
  !principal.apiKey.scopes.some(
@@ -1434,10 +1490,9 @@ export function webhooks(options: webhooks.Options = {}) {
1434
1490
  const maxPerOwner = webhook.maxPerOwner ?? defaultMaxPerOwner
1435
1491
  if ((await Webhooks.countSubscriptions(db, owner)) >= maxPerOwner)
1436
1492
  throw new Webhooks.LimitExceededError(maxPerOwner)
1437
- const startBlockNumber =
1438
- body.eventType === 'funding:deposit.updated'
1439
- ? undefined
1440
- : await getRpcHead(c.get('getClient')(chainId))
1493
+ const startBlockNumber = isFundingEventType(body.eventType)
1494
+ ? undefined
1495
+ : await getRpcHead(c.get('getClient')(chainId))
1441
1496
  const subscription = await Webhooks.createSubscription(
1442
1497
  db,
1443
1498
  {
@@ -2089,6 +2144,7 @@ export async function getRpcHead(client: Viem.getClient.ReturnType): Promise<num
2089
2144
  const filterSchemas = {
2090
2145
  'block:created': schema.BlockFilters,
2091
2146
  'funding:deposit.updated': schema.FundingDepositFilters,
2147
+ 'funding:transfer.updated': schema.FundingTransferFilters,
2092
2148
  'log:emitted': schema.LogFilters,
2093
2149
  'token:transfer': schema.TransferFilters,
2094
2150
  'transaction:included': schema.TxFilters,
@@ -2611,8 +2667,7 @@ function toPublic(subscription: Webhooks.Subscription) {
2611
2667
  ...(subscription.context === undefined ? {} : { context: subscription.context }),
2612
2668
  createdAt: subscription.createdAt,
2613
2669
  destination: redactDestination(subscription.destination),
2614
- ...(subscription.eventType === 'funding:deposit.updated' &&
2615
- subscription.environment !== undefined
2670
+ ...(isFundingEventType(subscription.eventType) && subscription.environment !== undefined
2616
2671
  ? { environment: subscription.environment }
2617
2672
  : {}),
2618
2673
  eventType: subscription.eventType,
@@ -3,6 +3,8 @@ import { Hono } from 'hono'
3
3
  import type * as App from '../../App.js'
4
4
  import type * as FundingDepositAddress from '../../internal/funding/DepositAddress.js'
5
5
  import type * as FundingProvider from '../../internal/funding/Provider.js'
6
+ import type * as FundingTransferReconciliation from '../../internal/funding/TransferReconciliation.js'
7
+ import type * as Webhooks from '../../internal/Webhooks.js'
6
8
  import * as Metadata from '../metadata.js'
7
9
  import { chains } from './routes/chains.js'
8
10
  import { depositAddresses } from './routes/deposit-addresses.js'
@@ -24,7 +26,7 @@ export function funding(options: funding.Options = {}) {
24
26
  .route('/', chains())
25
27
  .route('/', providers())
26
28
  .route('/', depositAddresses(options))
27
- .route('/', transfers())
29
+ .route('/', transfers(options))
28
30
  return Metadata.defaultChain(Metadata.attach(app, openapi))
29
31
  }
30
32
 
@@ -32,9 +34,17 @@ export declare namespace funding {
32
34
  /** Inbound funding route configuration. */
33
35
  type Options = {
34
36
  /** Enqueues one deposit address for asynchronous reconciliation. */
35
- dispatchReconciliation?:
37
+ dispatchDepositReconciliation?:
36
38
  | ((message: FundingProvider.Webhook.receive.Dispatchable) => Promise<void>)
37
39
  | undefined
40
+ /** Enqueues one registered transfer for destination reconciliation. */
41
+ dispatchTransferReconciliation?:
42
+ | ((message: FundingTransferReconciliation.Message) => Promise<void>)
43
+ | undefined
44
+ /** Enqueues webhook updates staged with transfer mutations. */
45
+ dispatchTransferUpdates?:
46
+ | ((references: readonly Webhooks.QueueReference[]) => Promise<number>)
47
+ | undefined
38
48
  /** Registers a created address with a source-chain observer. */
39
49
  registerDepositAddress?: ((address: FundingDepositAddress.Public) => Promise<void>) | undefined
40
50
  }
@@ -1497,7 +1497,7 @@ describe('POST /funding/deposit-addresses/:id/reconcile', () => {
1497
1497
  const dispatched: FundingProvider.Webhook.receive.Dispatchable[] = []
1498
1498
  const app = transfersApp({
1499
1499
  funding: {
1500
- dispatchReconciliation: async (message) => {
1500
+ dispatchDepositReconciliation: async (message) => {
1501
1501
  dispatched.push(message)
1502
1502
  },
1503
1503
  },
@@ -1546,7 +1546,7 @@ describe('POST /funding/deposit-addresses/:id/reconcile', () => {
1546
1546
  const dispatched: FundingProvider.Webhook.receive.Dispatchable[] = []
1547
1547
  const app = transfersApp({
1548
1548
  funding: {
1549
- dispatchReconciliation: async (message) => {
1549
+ dispatchDepositReconciliation: async (message) => {
1550
1550
  dispatched.push(message)
1551
1551
  },
1552
1552
  },
@@ -1574,7 +1574,7 @@ describe('POST /funding/deposit-addresses/:id/reconcile', () => {
1574
1574
  const dispatched: FundingProvider.Webhook.receive.Dispatchable[] = []
1575
1575
  const configured = transfersApp({
1576
1576
  funding: {
1577
- dispatchReconciliation: async (message) => {
1577
+ dispatchDepositReconciliation: async (message) => {
1578
1578
  dispatched.push(message)
1579
1579
  },
1580
1580
  },
@@ -1611,7 +1611,7 @@ describe('POST /funding/deposit-addresses/:id/reconcile', () => {
1611
1611
  test('returns an upstream error when the queue rejects the message', async () => {
1612
1612
  const app = transfersApp({
1613
1613
  funding: {
1614
- dispatchReconciliation: async () => {
1614
+ dispatchDepositReconciliation: async () => {
1615
1615
  throw new Error('queue unavailable')
1616
1616
  },
1617
1617
  },
@@ -981,7 +981,7 @@ export function depositAddresses(options: depositAddresses.Options = {}) {
981
981
  message: 'Funding deposit address is not active',
982
982
  status: 409,
983
983
  })
984
- const dispatch = options.dispatchReconciliation
984
+ const dispatch = options.dispatchDepositReconciliation
985
985
  if (!dispatch)
986
986
  return Response.error(c, {
987
987
  code: 'not_implemented',
@@ -1164,7 +1164,7 @@ export declare namespace depositAddresses {
1164
1164
  /** Deposit-address route dependencies. */
1165
1165
  type Options = {
1166
1166
  /** Enqueues one deposit address for asynchronous reconciliation. */
1167
- dispatchReconciliation?:
1167
+ dispatchDepositReconciliation?:
1168
1168
  | ((message: FundingProvider.Webhook.receive.Dispatchable) => Promise<void>)
1169
1169
  | undefined
1170
1170
  /** Registers a created address with a source-chain observer. */