tapimo 0.8.0 → 0.10.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 (223) hide show
  1. package/dist/App.d.ts +7410 -7613
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +0 -1
  4. package/dist/App.js.map +1 -1
  5. package/dist/Webhooks.d.ts +21 -20
  6. package/dist/Webhooks.d.ts.map +1 -1
  7. package/dist/Webhooks.js +66 -23
  8. package/dist/Webhooks.js.map +1 -1
  9. package/dist/admin/apps/verified-tokens.d.ts +74 -74
  10. package/dist/apps/Zones.d.ts +34 -22
  11. package/dist/apps/Zones.d.ts.map +1 -1
  12. package/dist/apps/Zones.js +9 -8
  13. package/dist/apps/Zones.js.map +1 -1
  14. package/dist/apps/data/App.d.ts +3120 -2782
  15. package/dist/apps/data/App.d.ts.map +1 -1
  16. package/dist/apps/data/FxOracle.d.ts +4 -5
  17. package/dist/apps/data/FxOracle.d.ts.map +1 -1
  18. package/dist/apps/data/FxOracle.js +19 -4
  19. package/dist/apps/data/FxOracle.js.map +1 -1
  20. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  21. package/dist/apps/data/routes/activities.js +32 -27
  22. package/dist/apps/data/routes/activities.js.map +1 -1
  23. package/dist/apps/data/routes/addresses.d.ts +283 -52
  24. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  25. package/dist/apps/data/routes/balances.d.ts +291 -21
  26. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  27. package/dist/apps/data/routes/balances.js +232 -66
  28. package/dist/apps/data/routes/balances.js.map +1 -1
  29. package/dist/apps/data/routes/blocks.d.ts +4 -4
  30. package/dist/apps/data/routes/coingecko.d.ts +54 -54
  31. package/dist/apps/data/routes/earn.d.ts +642 -126
  32. package/dist/apps/data/routes/earn.d.ts.map +1 -1
  33. package/dist/apps/data/routes/earn.js +627 -58
  34. package/dist/apps/data/routes/earn.js.map +1 -1
  35. package/dist/apps/data/routes/exchanges.d.ts +120 -120
  36. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  37. package/dist/apps/data/routes/exchanges.js +114 -118
  38. package/dist/apps/data/routes/exchanges.js.map +1 -1
  39. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  40. package/dist/apps/data/routes/receipts.js +68 -47
  41. package/dist/apps/data/routes/receipts.js.map +1 -1
  42. package/dist/apps/data/routes/tokens.d.ts +2 -0
  43. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  44. package/dist/apps/data/routes/transactions.d.ts +59 -51
  45. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  46. package/dist/apps/data/routes/transactions.js +25 -23
  47. package/dist/apps/data/routes/transactions.js.map +1 -1
  48. package/dist/apps/data/routes/transfers.d.ts +40 -18
  49. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  50. package/dist/apps/data/routes/transfers.js +618 -105
  51. package/dist/apps/data/routes/transfers.js.map +1 -1
  52. package/dist/apps/data/routes/valuation.d.ts +21 -21
  53. package/dist/apps/data/routes/valuation.d.ts.map +1 -1
  54. package/dist/apps/data/routes/valuation.js +2 -2
  55. package/dist/apps/data/routes/valuation.js.map +1 -1
  56. package/dist/apps/data/routes/verified-tokens.d.ts +37 -37
  57. package/dist/apps/data/routes/webhooks.d.ts +102 -505
  58. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  59. package/dist/apps/data/routes/webhooks.js +484 -342
  60. package/dist/apps/data/routes/webhooks.js.map +1 -1
  61. package/dist/apps/data/routes/zones.d.ts +12 -12
  62. package/dist/apps/data/routes/zones.d.ts.map +1 -1
  63. package/dist/apps/data/routes/zones.js +3 -1
  64. package/dist/apps/data/routes/zones.js.map +1 -1
  65. package/dist/apps/funding/App.d.ts +127 -386
  66. package/dist/apps/funding/App.d.ts.map +1 -1
  67. package/dist/apps/funding/App.js +20 -5
  68. package/dist/apps/funding/App.js.map +1 -1
  69. package/dist/apps/funding/routes/deposit-addresses.d.ts +141 -532
  70. package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
  71. package/dist/apps/funding/routes/deposit-addresses.js +21 -103
  72. package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
  73. package/dist/apps/funding/routes/transfers.d.ts +4 -4
  74. package/dist/apps/funding/routes/transfers.js +5 -5
  75. package/dist/apps/funding/routes/transfers.js.map +1 -1
  76. package/dist/apps/management/App.d.ts +429 -727
  77. package/dist/apps/management/App.d.ts.map +1 -1
  78. package/dist/apps/management/routes/api-keys.d.ts +24 -24
  79. package/dist/apps/management/routes/billing.d.ts +72 -72
  80. package/dist/apps/management/routes/faucet.d.ts +6 -6
  81. package/dist/apps/management/routes/invite-links.d.ts +38 -38
  82. package/dist/apps/management/routes/me.d.ts +6 -6
  83. package/dist/apps/management/routes/members.d.ts +30 -30
  84. package/dist/apps/management/routes/projects.d.ts +20 -20
  85. package/dist/apps/management/routes/usage.d.ts +24 -24
  86. package/dist/apps/management/routes/webhooks.d.ts +158 -573
  87. package/dist/apps/management/routes/webhooks.d.ts.map +1 -1
  88. package/dist/apps/mpp/App.d.ts +12 -0
  89. package/dist/apps/mpp/App.d.ts.map +1 -1
  90. package/dist/apps/mpp/Mpp.d.ts +3 -0
  91. package/dist/apps/mpp/Mpp.d.ts.map +1 -1
  92. package/dist/apps/mpp/Mpp.js +3 -0
  93. package/dist/apps/mpp/Mpp.js.map +1 -1
  94. package/dist/apps/mpp/internal/Adapter.d.ts.map +1 -1
  95. package/dist/apps/mpp/internal/Adapter.js +9 -2
  96. package/dist/apps/mpp/internal/Adapter.js.map +1 -1
  97. package/dist/db/Db.d.ts +2 -2
  98. package/dist/db/Db.d.ts.map +1 -1
  99. package/dist/db/Db.js +25 -15
  100. package/dist/db/Db.js.map +1 -1
  101. package/dist/db/tables/fundingDeposits.d.ts +18 -1
  102. package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
  103. package/dist/db/tables/fundingDeposits.js +46 -2
  104. package/dist/db/tables/fundingDeposits.js.map +1 -1
  105. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  106. package/dist/db/tables/sponsoredTransactions.js +4 -1
  107. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  108. package/dist/db/tables/webhookSubscriptions.d.ts +56 -3
  109. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  110. package/dist/db/tables/webhookSubscriptions.js +76 -3
  111. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  112. package/dist/internal/Auth.d.ts +4 -0
  113. package/dist/internal/Auth.d.ts.map +1 -1
  114. package/dist/internal/Auth.js +11 -2
  115. package/dist/internal/Auth.js.map +1 -1
  116. package/dist/internal/MetricSink.d.ts +112 -3
  117. package/dist/internal/MetricSink.d.ts.map +1 -1
  118. package/dist/internal/MetricSink.js +116 -3
  119. package/dist/internal/MetricSink.js.map +1 -1
  120. package/dist/internal/Schema.js +1 -1
  121. package/dist/internal/Schema.js.map +1 -1
  122. package/dist/internal/Tidx.d.ts +1 -3
  123. package/dist/internal/Tidx.d.ts.map +1 -1
  124. package/dist/internal/Tidx.js +1 -3
  125. package/dist/internal/Tidx.js.map +1 -1
  126. package/dist/internal/Viem.d.ts.map +1 -1
  127. package/dist/internal/Viem.js +2 -1
  128. package/dist/internal/Viem.js.map +1 -1
  129. package/dist/internal/WebhookDestination.d.ts +2 -0
  130. package/dist/internal/WebhookDestination.d.ts.map +1 -1
  131. package/dist/internal/WebhookDestination.js +11 -0
  132. package/dist/internal/WebhookDestination.js.map +1 -1
  133. package/dist/internal/WebhookTransfer.d.ts +55 -0
  134. package/dist/internal/WebhookTransfer.d.ts.map +1 -0
  135. package/dist/internal/WebhookTransfer.js +296 -0
  136. package/dist/internal/WebhookTransfer.js.map +1 -0
  137. package/dist/internal/Webhooks.d.ts +37 -0
  138. package/dist/internal/Webhooks.d.ts.map +1 -1
  139. package/dist/internal/Webhooks.js +87 -13
  140. package/dist/internal/Webhooks.js.map +1 -1
  141. package/dist/internal/funding/Provider.d.ts +10 -6
  142. package/dist/internal/funding/Provider.d.ts.map +1 -1
  143. package/dist/internal/funding/Provider.js +11 -7
  144. package/dist/internal/funding/Provider.js.map +1 -1
  145. package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
  146. package/dist/internal/funding/Reconciliation.js +229 -16
  147. package/dist/internal/funding/Reconciliation.js.map +1 -1
  148. package/dist/internal/funding/providers/relay.d.ts +3 -0
  149. package/dist/internal/funding/providers/relay.d.ts.map +1 -1
  150. package/dist/internal/funding/providers/relay.js +18 -3
  151. package/dist/internal/funding/providers/relay.js.map +1 -1
  152. package/package.json +3 -3
  153. package/src/App.test.ts +32 -0
  154. package/src/App.ts +4 -1
  155. package/src/Client.test-d.ts +116 -38
  156. package/src/Webhooks.bench.ts +714 -0
  157. package/src/Webhooks.memory.bench.ts +250 -0
  158. package/src/Webhooks.test.ts +77 -6
  159. package/src/Webhooks.ts +83 -29
  160. package/src/apps/Zones.test.ts +5 -4
  161. package/src/apps/Zones.ts +9 -8
  162. package/src/apps/composition.test.ts +39 -10
  163. package/src/apps/data/FxOracle.test.ts +18 -0
  164. package/src/apps/data/FxOracle.ts +28 -5
  165. package/src/apps/data/routes/activities.test.ts +19 -0
  166. package/src/apps/data/routes/activities.ts +34 -30
  167. package/src/apps/data/routes/balances.test.ts +161 -10
  168. package/src/apps/data/routes/balances.ts +339 -147
  169. package/src/apps/data/routes/blocks.test.ts +12 -6
  170. package/src/apps/data/routes/earn.test.ts +286 -9
  171. package/src/apps/data/routes/earn.ts +841 -58
  172. package/src/apps/data/routes/exchanges.test.ts +7 -6
  173. package/src/apps/data/routes/exchanges.ts +124 -117
  174. package/src/apps/data/routes/receipts.test.ts +24 -7
  175. package/src/apps/data/routes/receipts.ts +67 -50
  176. package/src/apps/data/routes/tokens.test.ts +51 -0
  177. package/src/apps/data/routes/transactions.test.ts +42 -0
  178. package/src/apps/data/routes/transactions.ts +44 -39
  179. package/src/apps/data/routes/transfers.test.ts +402 -24
  180. package/src/apps/data/routes/transfers.ts +708 -101
  181. package/src/apps/data/routes/valuation.test.ts +26 -0
  182. package/src/apps/data/routes/valuation.ts +6 -2
  183. package/src/apps/data/routes/webhooks.test.ts +414 -130
  184. package/src/apps/data/routes/webhooks.ts +603 -419
  185. package/src/apps/data/routes/zones.ts +6 -1
  186. package/src/apps/funding/App.ts +20 -5
  187. package/src/apps/funding/routes/deposit-addresses.test.ts +43 -22
  188. package/src/apps/funding/routes/deposit-addresses.ts +34 -117
  189. package/src/apps/funding/routes/quotes.test.ts +1 -1
  190. package/src/apps/funding/routes/transfers.test.ts +6 -25
  191. package/src/apps/funding/routes/transfers.ts +5 -5
  192. package/src/apps/mpp/Mpp.test.ts +13 -1
  193. package/src/apps/mpp/Mpp.ts +8 -0
  194. package/src/apps/mpp/internal/Adapter.ts +10 -2
  195. package/src/db/Db.test.ts +40 -6
  196. package/src/db/Db.ts +42 -19
  197. package/src/db/tables/fundingDeposits.test.ts +101 -0
  198. package/src/db/tables/fundingDeposits.ts +71 -2
  199. package/src/db/tables/sponsoredTransactions.ts +9 -2
  200. package/src/db/tables/webhookSubscriptions.ts +116 -4
  201. package/src/internal/Auth.test.ts +28 -0
  202. package/src/internal/Auth.ts +15 -1
  203. package/src/internal/EarnVaults.test.ts +2 -2
  204. package/src/internal/MetricSink.test.ts +518 -0
  205. package/src/internal/MetricSink.ts +266 -9
  206. package/src/internal/Schema.ts +1 -1
  207. package/src/internal/Tidx.test.ts +4 -4
  208. package/src/internal/Tidx.ts +1 -3
  209. package/src/internal/Viem.test.ts +8 -0
  210. package/src/internal/Viem.ts +2 -1
  211. package/src/internal/WebhookDestination.test.ts +20 -0
  212. package/src/internal/WebhookDestination.ts +10 -0
  213. package/src/internal/WebhookTransfer.ts +426 -0
  214. package/src/internal/Webhooks.test.ts +137 -0
  215. package/src/internal/Webhooks.ts +131 -17
  216. package/src/internal/funding/Provider.test.ts +47 -7
  217. package/src/internal/funding/Provider.ts +16 -11
  218. package/src/internal/funding/Reconciliation.local.test.ts +3 -1
  219. package/src/internal/funding/Reconciliation.test.ts +1532 -54
  220. package/src/internal/funding/Reconciliation.ts +296 -53
  221. package/src/internal/funding/SourceObservation.test.ts +3 -2
  222. package/src/internal/funding/providers/relay.test.ts +1 -4
  223. package/src/internal/funding/providers/relay.ts +23 -3
@@ -176,7 +176,12 @@ export function zones() {
176
176
  name: 'tempo-api:zone-withdrawals:v1',
177
177
  key: (c) => {
178
178
  const principal = Auth.getPrincipal(c)
179
- return `${principal?.type ?? 'unknown'}:${principal?.id ?? 'unknown'}:${Cache.urlKey(c, schema.listZoneWithdrawals.Query)}`
179
+ const url = new URL(Cache.urlKey(c, schema.listZoneWithdrawals.Query))
180
+ url.searchParams.set(
181
+ 'principal',
182
+ `${principal?.type ?? 'unknown'}:${principal?.id ?? 'unknown'}`,
183
+ )
184
+ return url.toString()
180
185
  },
181
186
  }),
182
187
  async (c) => {
@@ -42,22 +42,37 @@ export declare namespace funding {
42
42
 
43
43
  const openapi = {
44
44
  tags: [
45
- { name: 'Chains', description: 'Source chains and stablecoins supported for funding.' },
45
+ {
46
+ name: 'Chains',
47
+ description: 'Source chains and stablecoins supported for funding.',
48
+ 'x-pagePath': 'funding/chains',
49
+ },
46
50
  {
47
51
  name: 'Deposit Addresses',
48
52
  description: 'Reusable funding addresses and deposits detected at those addresses.',
53
+ 'x-pagePath': 'funding/deposit-addresses',
54
+ },
55
+ {
56
+ name: 'Providers',
57
+ description: 'Providers available for inbound funding.',
58
+ 'x-pagePath': 'funding/providers',
59
+ },
60
+ {
61
+ name: 'Quotes',
62
+ description: 'Live quotes for funding Tempo from other chains.',
63
+ 'x-pagePath': 'funding/quotes',
49
64
  },
50
- { name: 'Providers', description: 'Providers available for inbound funding.' },
51
- { name: 'Quotes', description: 'Live quotes for funding Tempo from other chains.' },
52
65
  {
53
- name: 'Transfers',
66
+ name: 'Funding Transfers',
54
67
  description: 'Inbound funding transfers.',
68
+ 'x-displayName': 'Transfers',
69
+ 'x-pagePath': 'funding/transfers',
55
70
  },
56
71
  ],
57
72
  'x-tagGroups': [
58
73
  {
59
74
  name: 'Funding & Bridge API',
60
- tags: ['Chains', 'Deposit Addresses', 'Providers', 'Quotes', 'Transfers'],
75
+ tags: ['Chains', 'Deposit Addresses', 'Providers', 'Quotes', 'Funding Transfers'],
61
76
  },
62
77
  ],
63
78
  } satisfies App.create.Metadata
@@ -1152,10 +1152,16 @@ describe('POST /funding/deposit-addresses/:id/reconcile', () => {
1152
1152
  })
1153
1153
  })
1154
1154
 
1155
- describe('GET /funding/deposit-addresses/:id/deposits', () => {
1155
+ describe('GET /funding/deposits?depositAddress', () => {
1156
1156
  test('pages deposits newest-first under an owned address', async () => {
1157
1157
  const app = transfersApp()
1158
- const address = await seedDepositAddress()
1158
+ const address = await seedDepositAddress({
1159
+ snapshot: TestFunding.depositAddressSnapshot({
1160
+ address: 'TQn9Y2khEsLJW1ChVWFMSMeRDow5KcbLSE',
1161
+ provider: { id: 'relay_list_deposit_address', name: 'Relay' },
1162
+ recipient: `0x${'41'.repeat(20)}`,
1163
+ }),
1164
+ })
1159
1165
  const first = await seedDeposit(address)
1160
1166
  const second = await seedDeposit(address)
1161
1167
  const third = await seedDeposit(address)
@@ -1164,7 +1170,7 @@ describe('GET /funding/deposit-addresses/:id/deposits', () => {
1164
1170
  const sixth = await seedDeposit(address)
1165
1171
 
1166
1172
  const response = await app.request(
1167
- `/v1/funding/deposit-addresses/${address.id}/deposits?limit=5`,
1173
+ `/v1/funding/deposits?depositAddress=${address.address}&limit=5`,
1168
1174
  as(fundingReader),
1169
1175
  )
1170
1176
  expect(response.status).toBe(200)
@@ -1177,10 +1183,7 @@ describe('GET /funding/deposit-addresses/:id/deposits', () => {
1177
1183
  "etag": null,
1178
1184
  }
1179
1185
  `)
1180
- const body = await TestApp.json(
1181
- response,
1182
- Funding.schema.listFundingDepositAddressDeposits.Response,
1183
- )
1186
+ const body = await TestApp.json(response, Funding.schema.listFundingDeposits.Response)
1184
1187
  expect(body.data.map((deposit) => deposit.id)).toEqual([
1185
1188
  sixth.id,
1186
1189
  fifth.id,
@@ -1192,10 +1195,10 @@ describe('GET /funding/deposit-addresses/:id/deposits', () => {
1192
1195
 
1193
1196
  const next = await TestApp.json(
1194
1197
  await app.request(
1195
- `/v1/funding/deposit-addresses/${address.id}/deposits?limit=5&cursor=${body.nextCursor}`,
1198
+ `/v1/funding/deposits?depositAddress=${address.address}&limit=5&cursor=${body.nextCursor}`,
1196
1199
  as(fundingReader),
1197
1200
  ),
1198
- Funding.schema.listFundingDepositAddressDeposits.Response,
1201
+ Funding.schema.listFundingDeposits.Response,
1199
1202
  )
1200
1203
  expect(next.data.map((deposit) => deposit.id)).toEqual([first.id])
1201
1204
  expect(next.nextCursor).toBeNull()
@@ -1205,6 +1208,7 @@ describe('GET /funding/deposit-addresses/:id/deposits', () => {
1205
1208
  const app = transfersApp()
1206
1209
  const address = await seedDepositAddress({
1207
1210
  snapshot: TestFunding.depositAddressSnapshot({
1211
+ address: 'TF17BgPaZYbz8oxbjhriubPDsA7ArKoLX3',
1208
1212
  provider: { id: 'relay_observed', name: 'Relay' },
1209
1213
  recipient: `0x${'31'.repeat(20)}`,
1210
1214
  }),
@@ -1228,14 +1232,11 @@ describe('GET /funding/deposit-addresses/:id/deposits', () => {
1228
1232
  })
1229
1233
 
1230
1234
  const response = await app.request(
1231
- `/v1/funding/deposit-addresses/${address.id}/deposits`,
1235
+ `/v1/funding/deposits?depositAddress=${address.address}`,
1232
1236
  as(fundingReader),
1233
1237
  )
1234
1238
  expect(response.status).toBe(200)
1235
- const body = await TestApp.json(
1236
- response,
1237
- Funding.schema.listFundingDepositAddressDeposits.Response,
1238
- )
1239
+ const body = await TestApp.json(response, Funding.schema.listFundingDeposits.Response)
1239
1240
  expect(body).toMatchInlineSnapshot(
1240
1241
  {
1241
1242
  data: [
@@ -1305,21 +1306,30 @@ describe('GET /funding/deposit-addresses/:id/deposits', () => {
1305
1306
  )
1306
1307
  })
1307
1308
 
1308
- test('returns the parent not-found error before listing deposits', async () => {
1309
+ test('hides a foreign deposit address', async () => {
1309
1310
  const app = transfersApp()
1310
- const address = await seedDepositAddress({ orgId: fundingForeign.orgId })
1311
+ const address = await seedDepositAddress({
1312
+ orgId: fundingForeign.orgId,
1313
+ snapshot: TestFunding.depositAddressSnapshot({
1314
+ address: 'TWd4WrZ9wn84f5x1hZhL4DHvk738ns5jwb',
1315
+ provider: { id: 'relay_foreign_deposit_address', name: 'Relay' },
1316
+ recipient: `0x${'42'.repeat(20)}`,
1317
+ }),
1318
+ })
1319
+ await seedDeposit(address)
1311
1320
  const response = await app.request(
1312
- `/v1/funding/deposit-addresses/${address.id}/deposits`,
1321
+ `/v1/funding/deposits?depositAddress=${address.address}`,
1313
1322
  as(fundingReader),
1314
1323
  )
1315
- expect(response.status).toBe(404)
1316
- expect(((await response.json()) as { error: { code: string } }).error.code).toBe(
1317
- 'funding_deposit_address_not_found',
1318
- )
1324
+ expect(response.status).toBe(200)
1325
+ expect(await TestApp.json(response, Funding.schema.listFundingDeposits.Response)).toEqual({
1326
+ data: [],
1327
+ nextCursor: null,
1328
+ })
1319
1329
  })
1320
1330
  })
1321
1331
 
1322
- describe('GET /funding/deposits', () => {
1332
+ describe('GET /funding/deposits?recipient', () => {
1323
1333
  test('requires funding:read and pages recipient deposits across addresses', async () => {
1324
1334
  const app = transfersApp()
1325
1335
  const recipient = '0xABcdEFABcdEFabcdEfAbCdefabcdeFABcDEFabCD'
@@ -1327,6 +1337,17 @@ describe('GET /funding/deposits', () => {
1327
1337
  expect((await app.request(path)).status).toBe(401)
1328
1338
  expect((await app.request(path, as(TestApp.key))).status).toBe(403)
1329
1339
  expect((await app.request('/v1/funding/deposits', as(fundingReader))).status).toBe(400)
1340
+ expect(
1341
+ (await app.request('/v1/funding/deposits?depositAddress=', as(fundingReader))).status,
1342
+ ).toBe(400)
1343
+ expect(
1344
+ (
1345
+ await app.request(
1346
+ `${path}&depositAddress=TJRabPrwbZy45sbavfcjinPJC18kjpRTv8`,
1347
+ as(fundingReader),
1348
+ )
1349
+ ).status,
1350
+ ).toBe(400)
1330
1351
 
1331
1352
  const firstAddress = await seedDepositAddress({
1332
1353
  snapshot: TestFunding.depositAddressSnapshot({
@@ -132,35 +132,36 @@ export namespace schema {
132
132
  export const Params = getFundingDepositAddress.Params
133
133
  }
134
134
 
135
- /** Schemas for the listFundingDepositAddressDeposits operation. */
136
- export namespace listFundingDepositAddressDeposits {
137
- /** Path parameters for listing one address's deposits. */
138
- export const Params = getFundingDepositAddress.Params
139
-
140
- /** Query parameters for listing one address's deposits. */
141
- export const Query = z
142
- .object({ cursor: Schema.Cursor, limit: Schema.Limit })
143
- .check(z.describe('Paging parameters for one funding deposit address.'))
144
-
145
- /** One page of detected deposits. */
146
- export const Response = z
147
- .object({
148
- data: z.array(Deposit).check(z.describe('Detected deposits on this page, newest first.')),
149
- nextCursor: Schema.NextCursor,
150
- })
151
- .check(z.describe('A page of deposits detected for one funding deposit address.'))
152
- }
153
-
154
135
  /** Schemas for the listFundingDeposits operation. */
155
136
  export namespace listFundingDeposits {
156
- /** Query parameters for listing one account's funding deposits. */
137
+ /** Query parameters for listing funding deposits. */
157
138
  export const Query = z
158
139
  .object({
140
+ depositAddress: z
141
+ .optional(z.string().check(z.minLength(1)))
142
+ .check(
143
+ z.describe('Reusable source-chain deposit address that received the funds.'),
144
+ z.meta({ examples: ['TJRabPrwbZy45sbavfcjinPJC18kjpRTv8'] }),
145
+ ),
159
146
  cursor: Schema.Cursor,
160
147
  limit: Schema.Limit,
161
- recipient: Schema.Address.check(z.describe('Tempo account that received the deposits.')),
148
+ recipient: z
149
+ .optional(Schema.Address)
150
+ .check(
151
+ z.describe('Tempo account that received the deposits.'),
152
+ z.meta({ examples: [`0x${'11'.repeat(20)}`] }),
153
+ ),
162
154
  })
163
- .check(z.describe('Recipient filter and paging parameters for funding deposits.'))
155
+ .check(
156
+ z.refine(
157
+ (query) => (query.depositAddress === undefined) !== (query.recipient === undefined),
158
+ {
159
+ error: 'Pass exactly one of `depositAddress` or `recipient`.',
160
+ path: ['depositAddress'],
161
+ },
162
+ ),
163
+ z.describe('Exactly one deposit filter and optional paging parameters.'),
164
+ )
164
165
 
165
166
  /** One page of detected deposits. */
166
167
  export const Response = z
@@ -168,7 +169,7 @@ export namespace schema {
168
169
  data: z.array(Deposit).check(z.describe('Detected deposits on this page, newest first.')),
169
170
  nextCursor: Schema.NextCursor,
170
171
  })
171
- .check(z.describe('A page of deposits received by one Tempo account.'))
172
+ .check(z.describe('A page of funding deposits matching the selected filter.'))
172
173
  }
173
174
 
174
175
  /** Schemas for the getFundingDeposit operation. */
@@ -273,11 +274,6 @@ const fundingDepositExample = {
273
274
  updatedAt: '2026-08-04T00:01:00.000Z',
274
275
  } satisfies z.output<typeof schema.Deposit>
275
276
 
276
- const fundingDepositAddressDepositsExample = {
277
- data: [fundingDepositExample],
278
- nextCursor: null,
279
- } satisfies z.output<typeof schema.listFundingDepositAddressDeposits.Response>
280
-
281
277
  const fundingDepositsExample = {
282
278
  data: [fundingDepositExample],
283
279
  nextCursor: null,
@@ -650,88 +646,6 @@ export function depositAddresses(options: depositAddresses.Options = {}) {
650
646
  }
651
647
  },
652
648
  )
653
- .get(
654
- '/v1/funding/deposit-addresses/:id{fda_[A-Za-z0-9_-]+}/deposits',
655
- Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
656
- OpenApi.validate('param', schema.listFundingDepositAddressDeposits.Params, {
657
- code: 'param_invalid',
658
- message: 'Check the path parameters and try again.',
659
- }),
660
- OpenApi.validate('query', schema.listFundingDepositAddressDeposits.Query, {
661
- code: 'query_invalid',
662
- message: 'Check the query parameters and try again.',
663
- }),
664
- OpenApi.describeRoute({
665
- description: 'Lists deposits detected for one funding deposit address, newest first.',
666
- operationId: 'listFundingDepositAddressDeposits',
667
- responses: OpenApi.responses({
668
- errors: {
669
- 400: { codes: ['param_invalid', 'query_invalid'] },
670
- 404: {
671
- codes: ['funding_deposit_address_not_found'],
672
- description: 'No visible funding deposit address exists for the identifier.',
673
- },
674
- },
675
- success: {
676
- description: 'A page of deposits detected for the address.',
677
- example: fundingDepositAddressDepositsExample,
678
- schema: schema.listFundingDepositAddressDeposits.Response,
679
- },
680
- }),
681
- summary: 'List deposits by deposit address',
682
- tags: ['Deposit Addresses'],
683
- }),
684
- async (c) => {
685
- if (Auth.narrowAccess) return Auth.accessError(c)
686
- if (OpenApi.narrowValidation)
687
- return OpenApi.validationError(c, {
688
- code: 'query_invalid',
689
- message: 'Check the request parameters and try again.',
690
- })
691
-
692
- const owner = Access.owner(c)
693
- if (!owner) return Access.unauthorized(c)
694
-
695
- const db = Db.get(c.get('db'))
696
- const { id } = c.req.valid('param')
697
- const { cursor, limit } = c.req.valid('query')
698
- const decoded = cursor ? Cursor.decode(cursor, ['timestamp', 'id']) : undefined
699
- try {
700
- const address = await FundingDepositAddresses.getOwned(db, owner, id)
701
- if (!address)
702
- return Response.error(c, {
703
- code: 'funding_deposit_address_not_found',
704
- message: 'Funding deposit address not found',
705
- status: 404,
706
- })
707
-
708
- const rows = await FundingDeposits.listByAddress(db, owner, id, {
709
- ...(decoded
710
- ? { cursor: { createdAt: String(decoded[0]), id: String(decoded[1]) } }
711
- : {}),
712
- limit: limit + 1,
713
- })
714
- const page = Cursor.paginate({
715
- key: (record) => [record.createdAt, record.id],
716
- limit,
717
- rows,
718
- })
719
- const data = page.rows.map(FundingDeposit.toPublic)
720
-
721
- c.header('Cache-Control', Cache.policies.noStore)
722
- c.header('Vary', 'Accept-Encoding, Authorization, Tempo-API-Key, X-API-Key')
723
- return c.json(
724
- Response.validated(schema.listFundingDepositAddressDeposits.Response, {
725
- data,
726
- nextCursor: page.nextCursor,
727
- }),
728
- 200,
729
- )
730
- } catch (cause) {
731
- return Response.upstream(c, cause)
732
- }
733
- },
734
- )
735
649
  .get(
736
650
  '/v1/funding/deposit-addresses/:id{fda_[A-Za-z0-9_-]+}',
737
651
  Auth.policy({ apiKey: { scopes: ['funding:read'] } }),
@@ -868,19 +782,19 @@ export function depositAddresses(options: depositAddresses.Options = {}) {
868
782
  message: 'Check the query parameters and try again.',
869
783
  }),
870
784
  OpenApi.describeRoute({
871
- description: 'Lists deposits received by one Tempo account, newest first.',
785
+ description: 'Lists deposits by deposit address or Tempo recipient, newest first.',
872
786
  operationId: 'listFundingDeposits',
873
787
  responses: OpenApi.responses({
874
788
  errors: {
875
789
  400: { codes: ['query_invalid'] },
876
790
  },
877
791
  success: {
878
- description: 'A page of funding deposits received by the account.',
792
+ description: 'A page of funding deposits matching the selected filter.',
879
793
  example: fundingDepositsExample,
880
794
  schema: schema.listFundingDeposits.Response,
881
795
  },
882
796
  }),
883
- summary: 'List deposits by recipient',
797
+ summary: 'List deposits',
884
798
  tags: ['Deposit Addresses'],
885
799
  }),
886
800
  async (c) => {
@@ -895,17 +809,20 @@ export function depositAddresses(options: depositAddresses.Options = {}) {
895
809
  if (!owner) return Access.unauthorized(c)
896
810
 
897
811
  const db = Db.get(c.get('db'))
898
- const { cursor, limit, recipient } = c.req.valid('query')
812
+ const { cursor, depositAddress, limit, recipient } = c.req.valid('query')
899
813
  const decoded = cursor ? Cursor.decode(cursor, ['timestamp', 'id']) : undefined
900
814
  try {
901
- const rows = await FundingDeposits.listByRecipient(db, {
815
+ const paging = {
902
816
  ...(decoded
903
817
  ? { cursor: { createdAt: String(decoded[0]), id: String(decoded[1]) } }
904
818
  : {}),
905
819
  limit: limit + 1,
906
820
  owner,
907
- recipient,
908
- })
821
+ }
822
+ const rows =
823
+ depositAddress === undefined
824
+ ? await FundingDeposits.listByRecipient(db, { ...paging, recipient: recipient! })
825
+ : await FundingDeposits.listByDepositAddress(db, { ...paging, depositAddress })
909
826
  const page = Cursor.paginate({
910
827
  key: (record) => [record.createdAt, record.id],
911
828
  limit,
@@ -271,7 +271,7 @@ describe('GET /funding/quotes', () => {
271
271
  const response_promise = app.request(quoteUrl({ ...baseUsdcQuery, provider: 'across' }), {
272
272
  headers: TestApp.auth.headers,
273
273
  })
274
- await vi.advanceTimersByTimeAsync(2_000)
274
+ await vi.advanceTimersByTimeAsync(5_000)
275
275
  const response = await response_promise
276
276
 
277
277
  expect(response.status).toBe(502)
@@ -830,9 +830,6 @@ describe('funding transfers OpenAPI', () => {
830
830
  !path.endsWith('/deposits') &&
831
831
  !path.endsWith('/reconcile'),
832
832
  )!
833
- const depositAddressDepositsPath = Object.keys(spec.paths).find(
834
- (path) => path.includes('/v1/funding/deposit-addresses/{') && path.endsWith('/deposits'),
835
- )!
836
833
  const depositPath = Object.keys(spec.paths).find((path) =>
837
834
  path.includes('/v1/funding/deposits/{'),
838
835
  )!
@@ -847,7 +844,6 @@ describe('funding transfers OpenAPI', () => {
847
844
  deposit: summarize(spec.paths[depositPath]!.get!),
848
845
  depositAddress: summarize(spec.paths[depositAddressPath]!.get!),
849
846
  depositAddressCreate: summarize(spec.paths['/v1/funding/deposit-addresses']!.post!),
850
- depositAddressDeposits: summarize(spec.paths[depositAddressDepositsPath]!.get!),
851
847
  depositAddressReconcile: summarize(spec.paths[reconcilePath]!.post!),
852
848
  deposits: summarize(spec.paths['/v1/funding/deposits']!.get!),
853
849
  detail: summarize(spec.paths[detailPath]!.get!),
@@ -896,17 +892,13 @@ describe('funding transfers OpenAPI', () => {
896
892
  "description": "Creates or returns a reusable deposit address for funding a Tempo account.",
897
893
  "summary": "Create deposit address",
898
894
  },
899
- "depositAddressDeposits": {
900
- "description": "Lists deposits detected for one funding deposit address, newest first.",
901
- "summary": "List deposits by deposit address",
902
- },
903
895
  "depositAddressReconcile": {
904
896
  "description": "Queues an immediate provider reconciliation for one reusable funding deposit address.",
905
897
  "summary": "Reconcile deposit address",
906
898
  },
907
899
  "deposits": {
908
- "description": "Lists deposits received by one Tempo account, newest first.",
909
- "summary": "List deposits by recipient",
900
+ "description": "Lists deposits by deposit address or Tempo recipient, newest first.",
901
+ "summary": "List deposits",
910
902
  },
911
903
  "detail": {
912
904
  "description": "Returns a transfer by ID.",
@@ -934,7 +926,6 @@ describe('funding transfers OpenAPI', () => {
934
926
  },
935
927
  },
936
928
  "sidebar": [
937
- "List transfers",
938
929
  "Get quotes",
939
930
  "Get chains",
940
931
  "Get providers",
@@ -947,8 +938,7 @@ describe('funding transfers OpenAPI', () => {
947
938
  "Get deposit address",
948
939
  "Get transfer",
949
940
  "Get deposit",
950
- "List deposits by deposit address",
951
- "List deposits by recipient",
941
+ "List deposits",
952
942
  ],
953
943
  }
954
944
  `)
@@ -1100,8 +1090,9 @@ describe('funding transfers OpenAPI', () => {
1100
1090
  const operation = spec.paths['/v1/funding/transfers']!.get
1101
1091
  const fields = Object.keys(operation.responses['200']!.content['application/json']!.example.data[0]!) // prettier-ignore
1102
1092
  expect(fields).toEqual([...fields].sort())
1103
- for (const parameter of operation.parameters ?? [])
1104
- expect(parameter.example ?? parameter.schema?.examples, parameter.name).toBeDefined()
1093
+ for (const route of [operation, spec.paths['/v1/funding/deposits']!.get])
1094
+ for (const parameter of route.parameters ?? [])
1095
+ expect(parameter.example ?? parameter.schema?.examples, parameter.name).toBeDefined()
1105
1096
  })
1106
1097
 
1107
1098
  test('publishes operation ids and required scopes', async () => {
@@ -1128,9 +1119,6 @@ describe('funding transfers OpenAPI', () => {
1128
1119
  !path.endsWith('/deposits') &&
1129
1120
  !path.endsWith('/reconcile'),
1130
1121
  )
1131
- const depositAddressDepositsPath = Object.keys(spec.paths).find(
1132
- (path) => path.includes('/v1/funding/deposit-addresses/{') && path.endsWith('/deposits'),
1133
- )
1134
1122
  const depositPath = Object.keys(spec.paths).find((path) =>
1135
1123
  path.includes('/v1/funding/deposits/{'),
1136
1124
  )
@@ -1141,11 +1129,6 @@ describe('funding transfers OpenAPI', () => {
1141
1129
  depositAddressCreate: spec.paths['/v1/funding/deposit-addresses']?.post?.operationId,
1142
1130
  depositAddressCreateScopes:
1143
1131
  spec.paths['/v1/funding/deposit-addresses']?.post?.['x-required-scopes'],
1144
- depositAddressDeposits:
1145
- depositAddressDepositsPath && spec.paths[depositAddressDepositsPath]?.get.operationId,
1146
- depositAddressDepositsScopes:
1147
- depositAddressDepositsPath &&
1148
- spec.paths[depositAddressDepositsPath]?.get['x-required-scopes'],
1149
1132
  depositAddressReconcile: reconcilePath && spec.paths[reconcilePath]?.post?.operationId,
1150
1133
  depositAddressReconcileScopes:
1151
1134
  reconcilePath && spec.paths[reconcilePath]?.post?.['x-required-scopes'],
@@ -1170,8 +1153,6 @@ describe('funding transfers OpenAPI', () => {
1170
1153
  depositAddress: 'getFundingDepositAddress',
1171
1154
  depositAddressCreate: 'createFundingDepositAddress',
1172
1155
  depositAddressCreateScopes: ['funding:write'],
1173
- depositAddressDeposits: 'listFundingDepositAddressDeposits',
1174
- depositAddressDepositsScopes: ['funding:read'],
1175
1156
  depositAddressReconcile: 'reconcileFundingDepositAddress',
1176
1157
  depositAddressReconcileScopes: ['funding:write'],
1177
1158
  depositAddressScopes: ['funding:read'],
@@ -290,7 +290,7 @@ export function transfers() {
290
290
  },
291
291
  }),
292
292
  summary: 'List transfers',
293
- tags: ['Transfers'],
293
+ tags: ['Funding Transfers'],
294
294
  }),
295
295
  fundingEtag,
296
296
  async (c) => {
@@ -373,7 +373,7 @@ export function transfers() {
373
373
  },
374
374
  }),
375
375
  summary: 'Get transfer',
376
- tags: ['Transfers'],
376
+ tags: ['Funding Transfers'],
377
377
  }),
378
378
  async (c) => {
379
379
  if (Auth.narrowAccess) return Auth.accessError(c)
@@ -448,7 +448,7 @@ export function transfers() {
448
448
  },
449
449
  }),
450
450
  summary: 'Create transfer',
451
- tags: ['Transfers'],
451
+ tags: ['Funding Transfers'],
452
452
  }),
453
453
  async (c) => {
454
454
  if (Auth.narrowAccess) return Auth.accessError(c)
@@ -637,7 +637,7 @@ export function transfers() {
637
637
  operationId: 'createFundingTransferVault',
638
638
  responses: unimplementedFundingTransferResponses,
639
639
  summary: 'Create transfer into vault',
640
- tags: ['Transfers'],
640
+ tags: ['Funding Transfers'],
641
641
  }),
642
642
  (c) => {
643
643
  if (Auth.narrowAccess) return Auth.accessError(c)
@@ -671,7 +671,7 @@ export function transfers() {
671
671
  operationId: 'createFundingTransferZone',
672
672
  responses: unimplementedFundingTransferResponses,
673
673
  summary: 'Create transfer into zone',
674
- tags: ['Transfers'],
674
+ tags: ['Funding Transfers'],
675
675
  }),
676
676
  (c) => {
677
677
  if (Auth.narrowAccess) return Auth.accessError(c)
@@ -91,6 +91,17 @@ describe('MPP relay', () => {
91
91
  expect(await response.json()).toEqual({ success: true })
92
92
  })
93
93
 
94
+ test('verifies a zero-amount proof credential', async () => {
95
+ const app = TestApp.create({ auth: { keys: [key] } })
96
+ const input = await credential({ amount: '0' })
97
+ const response = await post(app, '/v1/mpp/validate', input)
98
+
99
+ expect(payloadType(input)).toBe('proof')
100
+ expect(input.source).toBeDefined()
101
+ expect(response.status).toBe(200)
102
+ expect(await response.json()).toEqual({ success: true })
103
+ })
104
+
94
105
  test('validates and finalizes a pushed credential without a second submission', async () => {
95
106
  const app = TestApp.create({ auth: { keys: [key] } })
96
107
  const input = await credential({ mode: 'push', supportedModes: ['push'] })
@@ -619,7 +630,7 @@ type CredentialPayload = { hash?: string; type?: unknown }
619
630
 
620
631
  async function credential(options: credential.Options = {}): Promise<Mpp.RelayInput> {
621
632
  const issuer = createIssuer(options)
622
- const challenge = await issuer.challenge.tempo.charge({ amount: '1' })
633
+ const challenge = await issuer.challenge.tempo.charge({ amount: options.amount ?? '1' })
623
634
  const authorization = await createClient(options).createCredential(
624
635
  new Response(null, {
625
636
  headers: { 'www-authenticate': Mppx.Challenge.serialize(challenge) },
@@ -631,6 +642,7 @@ async function credential(options: credential.Options = {}): Promise<Mpp.RelayIn
631
642
 
632
643
  declare namespace credential {
633
644
  type Options = {
645
+ amount?: string | undefined
634
646
  feePayer?: boolean | undefined
635
647
  mode?: 'pull' | 'push' | undefined
636
648
  supportedModes?: ('pull' | 'push')[] | undefined
@@ -67,6 +67,8 @@ export type RelayInput = {
67
67
  challenge: Challenge
68
68
  /** Method-specific MPP credential payload. */
69
69
  payload: unknown
70
+ /** Optional payer identifier supplied with the credential. */
71
+ source?: string | undefined
70
72
  }
71
73
 
72
74
  /** Broadcast MPP payment receipt. */
@@ -200,6 +202,12 @@ export namespace schema {
200
202
  z.describe('The method-specific MPP credential payload.'),
201
203
  z.meta({ examples: [{ signature: `0x${'aa'.repeat(65)}`, type: 'transaction' }] }),
202
204
  ),
205
+ source: z
206
+ .optional(z.string())
207
+ .check(
208
+ z.describe('An optional payer identifier supplied with the credential.'),
209
+ z.meta({ examples: ['did:pkh:eip155:4217:0x1234567890abcdef1234567890abcdef12345678'] }),
210
+ ),
203
211
  }),
204
212
  'An MPP payment credential and its challenge.',
205
213
  )
@@ -49,7 +49,7 @@ export function create(options: create.Options): Adapter {
49
49
  /** Validates and screens a credential, rejecting when either check fails. */
50
50
  async function validateOrThrow(input: Mpp.RelayInput) {
51
51
  const result = await time('mpp_validate', () =>
52
- Mppx.Method.validateCredential(methods, Mppx.Credential.from(input)),
52
+ Mppx.Method.validateCredential(methods, credential(input)),
53
53
  )
54
54
  await time('mpp_screen', () => screenManager.screen(Utils.addresses(result)))
55
55
  }
@@ -65,7 +65,7 @@ export function create(options: create.Options): Adapter {
65
65
  try {
66
66
  await validateOrThrow(input)
67
67
  const result = await time('mpp_broadcast', () =>
68
- Mppx.Method.broadcastCredential(methods, Mppx.Credential.from(input)),
68
+ Mppx.Method.broadcastCredential(methods, credential(input)),
69
69
  )
70
70
  return {
71
71
  receipt: {
@@ -91,6 +91,14 @@ export function create(options: create.Options): Adapter {
91
91
  }
92
92
  }
93
93
 
94
+ function credential(input: Mpp.RelayInput) {
95
+ return Mppx.Credential.from({
96
+ challenge: input.challenge,
97
+ payload: input.payload,
98
+ ...(input.source === undefined ? {} : { source: input.source }),
99
+ })
100
+ }
101
+
94
102
  export declare namespace create {
95
103
  /** Dependencies used to validate and broadcast Tempo credentials. */
96
104
  type Options = {