tapimo 0.5.3 → 0.6.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 (289) hide show
  1. package/dist/App.d.ts +13987 -5755
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js.map +1 -1
  4. package/dist/admin/App.d.ts +910 -412
  5. package/dist/admin/App.d.ts.map +1 -1
  6. package/dist/admin/App.js +4 -0
  7. package/dist/admin/App.js.map +1 -1
  8. package/dist/admin/apps/early-access.d.ts +148 -0
  9. package/dist/admin/apps/early-access.d.ts.map +1 -0
  10. package/dist/admin/apps/early-access.js +133 -0
  11. package/dist/admin/apps/early-access.js.map +1 -0
  12. package/dist/admin/apps/verified-tokens.d.ts +526 -1
  13. package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
  14. package/dist/admin/apps/verified-tokens.js +214 -1
  15. package/dist/admin/apps/verified-tokens.js.map +1 -1
  16. package/dist/admin/ui.generated.d.ts +1 -1
  17. package/dist/admin/ui.generated.d.ts.map +1 -1
  18. package/dist/admin/ui.generated.js +45 -33
  19. package/dist/admin/ui.generated.js.map +1 -1
  20. package/dist/apps/data/App.d.ts +3733 -2428
  21. package/dist/apps/data/App.d.ts.map +1 -1
  22. package/dist/apps/data/App.js +11 -6
  23. package/dist/apps/data/App.js.map +1 -1
  24. package/dist/apps/data/FundingQuoteProviders.d.ts +34 -1
  25. package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
  26. package/dist/apps/data/FundingQuoteProviders.js +193 -2
  27. package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
  28. package/dist/apps/data/FxOracle.d.ts +71 -0
  29. package/dist/apps/data/FxOracle.d.ts.map +1 -0
  30. package/dist/apps/data/FxOracle.js +97 -0
  31. package/dist/apps/data/FxOracle.js.map +1 -0
  32. package/dist/apps/data/index.d.ts +1 -0
  33. package/dist/apps/data/index.d.ts.map +1 -1
  34. package/dist/apps/data/index.js +1 -0
  35. package/dist/apps/data/index.js.map +1 -1
  36. package/dist/apps/data/routes/activities.d.ts +3575 -1358
  37. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  38. package/dist/apps/data/routes/activities.js +230 -88
  39. package/dist/apps/data/routes/activities.js.map +1 -1
  40. package/dist/apps/data/routes/addresses.d.ts +872 -251
  41. package/dist/apps/data/routes/addresses.d.ts.map +1 -1
  42. package/dist/apps/data/routes/addresses.js +11 -9
  43. package/dist/apps/data/routes/addresses.js.map +1 -1
  44. package/dist/apps/data/routes/balances.d.ts +108 -57
  45. package/dist/apps/data/routes/balances.d.ts.map +1 -1
  46. package/dist/apps/data/routes/balances.js +88 -45
  47. package/dist/apps/data/routes/balances.js.map +1 -1
  48. package/dist/apps/data/routes/coingecko.d.ts +169 -169
  49. package/dist/apps/data/routes/exchanges.d.ts +952 -201
  50. package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
  51. package/dist/apps/data/routes/exchanges.js +518 -76
  52. package/dist/apps/data/routes/exchanges.js.map +1 -1
  53. package/dist/apps/data/routes/fee-amm.d.ts +24 -24
  54. package/dist/apps/data/routes/funding.d.ts +8 -2
  55. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  56. package/dist/apps/data/routes/funding.js +32 -6
  57. package/dist/apps/data/routes/funding.js.map +1 -1
  58. package/dist/apps/data/routes/indexer.d.ts +5 -5
  59. package/dist/apps/data/routes/receipts.d.ts +392 -315
  60. package/dist/apps/data/routes/receipts.d.ts.map +1 -1
  61. package/dist/apps/data/routes/receipts.js +152 -77
  62. package/dist/apps/data/routes/receipts.js.map +1 -1
  63. package/dist/apps/data/routes/rpc.d.ts +31 -31
  64. package/dist/apps/data/routes/tokens.d.ts +166 -173
  65. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  66. package/dist/apps/data/routes/tokens.js +14 -12
  67. package/dist/apps/data/routes/tokens.js.map +1 -1
  68. package/dist/apps/data/routes/transactions.d.ts +277 -536
  69. package/dist/apps/data/routes/transactions.d.ts.map +1 -1
  70. package/dist/apps/data/routes/transactions.js +96 -77
  71. package/dist/apps/data/routes/transactions.js.map +1 -1
  72. package/dist/apps/data/routes/transfers.d.ts +82 -17
  73. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  74. package/dist/apps/data/routes/transfers.js +70 -13
  75. package/dist/apps/data/routes/transfers.js.map +1 -1
  76. package/dist/apps/data/routes/valuation.d.ts +307 -0
  77. package/dist/apps/data/routes/valuation.d.ts.map +1 -0
  78. package/dist/apps/data/routes/valuation.js +292 -0
  79. package/dist/apps/data/routes/valuation.js.map +1 -0
  80. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
  81. package/dist/apps/data/routes/verified-tokens.js +7 -5
  82. package/dist/apps/data/routes/verified-tokens.js.map +1 -1
  83. package/dist/apps/data/routes/webhooks.d.ts +459 -479
  84. package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
  85. package/dist/apps/data/routes/webhooks.js +124 -25
  86. package/dist/apps/data/routes/webhooks.js.map +1 -1
  87. package/dist/apps/index.d.ts +1 -1
  88. package/dist/apps/index.d.ts.map +1 -1
  89. package/dist/apps/index.js +1 -1
  90. package/dist/apps/index.js.map +1 -1
  91. package/dist/apps/management/App.d.ts +8606 -1681
  92. package/dist/apps/management/App.d.ts.map +1 -1
  93. package/dist/apps/management/App.js +12 -1
  94. package/dist/apps/management/App.js.map +1 -1
  95. package/dist/apps/management/routes/api-keys.d.ts +23 -0
  96. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  97. package/dist/apps/management/routes/api-keys.js +5 -3
  98. package/dist/apps/management/routes/api-keys.js.map +1 -1
  99. package/dist/apps/management/routes/billing.d.ts +88 -48
  100. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  101. package/dist/apps/management/routes/billing.js +10 -2
  102. package/dist/apps/management/routes/billing.js.map +1 -1
  103. package/dist/apps/management/routes/faucet.d.ts +218 -0
  104. package/dist/apps/management/routes/faucet.d.ts.map +1 -0
  105. package/dist/apps/management/routes/faucet.js +139 -0
  106. package/dist/apps/management/routes/faucet.js.map +1 -0
  107. package/dist/apps/management/routes/invitations.d.ts +121 -121
  108. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  109. package/dist/apps/management/routes/invitations.js +3 -0
  110. package/dist/apps/management/routes/invitations.js.map +1 -1
  111. package/dist/apps/management/routes/invite-links.d.ts +49 -20
  112. package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
  113. package/dist/apps/management/routes/invite-links.js +26 -1
  114. package/dist/apps/management/routes/invite-links.js.map +1 -1
  115. package/dist/apps/management/routes/me.d.ts +2 -0
  116. package/dist/apps/management/routes/me.d.ts.map +1 -1
  117. package/dist/apps/management/routes/me.js +4 -0
  118. package/dist/apps/management/routes/me.js.map +1 -1
  119. package/dist/apps/management/routes/members.d.ts +49 -49
  120. package/dist/apps/management/routes/orgs.d.ts +91 -91
  121. package/dist/apps/management/routes/projects.d.ts +44 -44
  122. package/dist/apps/management/routes/usage.d.ts +35 -32
  123. package/dist/apps/management/routes/usage.d.ts.map +1 -1
  124. package/dist/apps/management/routes/usage.js +4 -0
  125. package/dist/apps/management/routes/usage.js.map +1 -1
  126. package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
  127. package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
  128. package/dist/apps/management/routes/verified-token-requests.js +271 -0
  129. package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
  130. package/dist/apps/management/routes/webhooks.d.ts +6977 -0
  131. package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
  132. package/dist/apps/management/routes/webhooks.js +573 -0
  133. package/dist/apps/management/routes/webhooks.js.map +1 -0
  134. package/dist/db/Db.d.ts +4 -0
  135. package/dist/db/Db.d.ts.map +1 -1
  136. package/dist/db/Db.js +83 -0
  137. package/dist/db/Db.js.map +1 -1
  138. package/dist/db/Schema.d.ts +41 -0
  139. package/dist/db/Schema.d.ts.map +1 -1
  140. package/dist/db/Schema.js +45 -0
  141. package/dist/db/Schema.js.map +1 -1
  142. package/dist/db/tables/earlyAccess.d.ts +50 -0
  143. package/dist/db/tables/earlyAccess.d.ts.map +1 -0
  144. package/dist/db/tables/earlyAccess.js +63 -0
  145. package/dist/db/tables/earlyAccess.js.map +1 -0
  146. package/dist/db/tables/invitations.d.ts +12 -1
  147. package/dist/db/tables/invitations.d.ts.map +1 -1
  148. package/dist/db/tables/invitations.js +23 -0
  149. package/dist/db/tables/invitations.js.map +1 -1
  150. package/dist/db/tables/inviteLinks.d.ts +10 -2
  151. package/dist/db/tables/inviteLinks.d.ts.map +1 -1
  152. package/dist/db/tables/inviteLinks.js +10 -1
  153. package/dist/db/tables/inviteLinks.js.map +1 -1
  154. package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
  155. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  156. package/dist/db/tables/sponsoredTransactions.js +2 -0
  157. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  158. package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
  159. package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
  160. package/dist/db/tables/verifiedTokenRequests.js +120 -0
  161. package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
  162. package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
  163. package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
  164. package/dist/db/tables/webhookSubscriptions.js +16 -0
  165. package/dist/db/tables/webhookSubscriptions.js.map +1 -1
  166. package/dist/internal/Auth.d.ts +43 -2
  167. package/dist/internal/Auth.d.ts.map +1 -1
  168. package/dist/internal/Auth.js +60 -45
  169. package/dist/internal/Auth.js.map +1 -1
  170. package/dist/internal/FundingQuotes.d.ts +69 -15
  171. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  172. package/dist/internal/FundingQuotes.js +74 -25
  173. package/dist/internal/FundingQuotes.js.map +1 -1
  174. package/dist/internal/Log.d.ts +19 -1
  175. package/dist/internal/Log.d.ts.map +1 -1
  176. package/dist/internal/Log.js +2 -0
  177. package/dist/internal/Log.js.map +1 -1
  178. package/dist/internal/MetricSink.d.ts.map +1 -1
  179. package/dist/internal/MetricSink.js +9 -0
  180. package/dist/internal/MetricSink.js.map +1 -1
  181. package/dist/internal/OpenApi.d.ts +2 -2
  182. package/dist/internal/OpenApi.d.ts.map +1 -1
  183. package/dist/internal/OpenApi.js +3 -0
  184. package/dist/internal/OpenApi.js.map +1 -1
  185. package/dist/internal/Schema.d.ts +14 -5
  186. package/dist/internal/Schema.d.ts.map +1 -1
  187. package/dist/internal/Schema.js +23 -5
  188. package/dist/internal/Schema.js.map +1 -1
  189. package/dist/internal/Value.d.ts +18 -0
  190. package/dist/internal/Value.d.ts.map +1 -1
  191. package/dist/internal/Value.js +11 -1
  192. package/dist/internal/Value.js.map +1 -1
  193. package/dist/internal/VerifiedTokens.d.ts.map +1 -1
  194. package/dist/internal/VerifiedTokens.js +10 -1
  195. package/dist/internal/VerifiedTokens.js.map +1 -1
  196. package/dist/internal/Viem.d.ts +1 -1
  197. package/dist/internal/Webhooks.d.ts +24 -3
  198. package/dist/internal/Webhooks.d.ts.map +1 -1
  199. package/dist/internal/Webhooks.js +34 -8
  200. package/dist/internal/Webhooks.js.map +1 -1
  201. package/package.json +1 -1
  202. package/src/App.ts +9 -1
  203. package/src/Client.test-d.ts +638 -6
  204. package/src/admin/App.ts +4 -0
  205. package/src/admin/apps/early-access.test.ts +125 -0
  206. package/src/admin/apps/early-access.ts +175 -0
  207. package/src/admin/apps/verified-tokens.test.ts +201 -0
  208. package/src/admin/apps/verified-tokens.ts +532 -286
  209. package/src/admin/ui/plugin.ts +2 -1
  210. package/src/admin/ui/src/lib/early-access.ts +63 -0
  211. package/src/admin/ui/src/lib/verified-tokens.ts +79 -0
  212. package/src/admin/ui/src/routeTree.gen.ts +33 -2
  213. package/src/admin/ui/src/routes/__root.tsx +2 -1
  214. package/src/admin/ui/src/routes/early-access.tsx +157 -0
  215. package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
  216. package/src/admin/ui.generated.ts +48 -33
  217. package/src/apps/data/App.ts +22 -14
  218. package/src/apps/data/FundingQuoteProviders.test.ts +298 -0
  219. package/src/apps/data/FundingQuoteProviders.ts +257 -4
  220. package/src/apps/data/FxOracle.test.ts +118 -0
  221. package/src/apps/data/FxOracle.ts +153 -0
  222. package/src/apps/data/index.ts +1 -0
  223. package/src/apps/data/routes/activities.test.ts +173 -3
  224. package/src/apps/data/routes/activities.ts +403 -132
  225. package/src/apps/data/routes/addresses.ts +19 -9
  226. package/src/apps/data/routes/balances.test.ts +144 -11
  227. package/src/apps/data/routes/balances.ts +140 -51
  228. package/src/apps/data/routes/exchanges.test.ts +540 -9
  229. package/src/apps/data/routes/exchanges.ts +690 -103
  230. package/src/apps/data/routes/funding.test.ts +84 -0
  231. package/src/apps/data/routes/funding.ts +38 -6
  232. package/src/apps/data/routes/receipts.test.ts +125 -23
  233. package/src/apps/data/routes/receipts.ts +205 -100
  234. package/src/apps/data/routes/tokens.test.ts +43 -0
  235. package/src/apps/data/routes/tokens.ts +27 -12
  236. package/src/apps/data/routes/transactions.test.ts +72 -19
  237. package/src/apps/data/routes/transactions.ts +162 -92
  238. package/src/apps/data/routes/transfers.test.ts +146 -4
  239. package/src/apps/data/routes/transfers.ts +91 -13
  240. package/src/apps/data/routes/valuation.test.ts +422 -0
  241. package/src/apps/data/routes/valuation.ts +423 -0
  242. package/src/apps/data/routes/verified-tokens.ts +14 -2
  243. package/src/apps/data/routes/webhooks.test.ts +56 -8
  244. package/src/apps/data/routes/webhooks.ts +144 -27
  245. package/src/apps/index.ts +1 -1
  246. package/src/apps/management/App.ts +13 -0
  247. package/src/apps/management/routes/api-keys.test.ts +87 -0
  248. package/src/apps/management/routes/api-keys.ts +4 -3
  249. package/src/apps/management/routes/billing.test.ts +112 -1
  250. package/src/apps/management/routes/billing.ts +7 -2
  251. package/src/apps/management/routes/faucet.test.ts +79 -0
  252. package/src/apps/management/routes/faucet.ts +185 -0
  253. package/src/apps/management/routes/invitations.test.ts +172 -0
  254. package/src/apps/management/routes/invitations.ts +3 -0
  255. package/src/apps/management/routes/invite-links.test.ts +117 -0
  256. package/src/apps/management/routes/invite-links.ts +43 -1
  257. package/src/apps/management/routes/me.test.ts +140 -88
  258. package/src/apps/management/routes/me.ts +6 -0
  259. package/src/apps/management/routes/usage.test.ts +6 -0
  260. package/src/apps/management/routes/usage.ts +7 -0
  261. package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
  262. package/src/apps/management/routes/verified-token-requests.ts +397 -0
  263. package/src/apps/management/routes/webhooks.test.ts +426 -0
  264. package/src/apps/management/routes/webhooks.ts +672 -0
  265. package/src/db/Db.test.ts +19 -0
  266. package/src/db/Db.ts +95 -0
  267. package/src/db/Schema.ts +53 -0
  268. package/src/db/tables/earlyAccess.ts +86 -0
  269. package/src/db/tables/invitations.ts +26 -1
  270. package/src/db/tables/inviteLinks.ts +22 -3
  271. package/src/db/tables/sponsoredTransactions.ts +4 -0
  272. package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
  273. package/src/db/tables/verifiedTokenRequests.ts +182 -0
  274. package/src/db/tables/webhookSubscriptions.ts +31 -0
  275. package/src/internal/Auth.ts +73 -45
  276. package/src/internal/FundingQuotes.test.ts +143 -1
  277. package/src/internal/FundingQuotes.ts +95 -28
  278. package/src/internal/Log.test-d.ts +21 -0
  279. package/src/internal/Log.test.ts +50 -0
  280. package/src/internal/Log.ts +24 -1
  281. package/src/internal/MetricSink.test.ts +160 -0
  282. package/src/internal/MetricSink.ts +10 -0
  283. package/src/internal/OpenApi.ts +3 -0
  284. package/src/internal/Schema.ts +50 -5
  285. package/src/internal/Value.test.ts +20 -0
  286. package/src/internal/Value.ts +24 -1
  287. package/src/internal/VerifiedTokens.ts +9 -1
  288. package/src/internal/Webhooks.test.ts +140 -0
  289. package/src/internal/Webhooks.ts +53 -9
@@ -0,0 +1,426 @@
1
+ import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
2
+
3
+ import * as App from '../../../App.js'
4
+ import * as Memberships from '../../../db/tables/memberships.js'
5
+ import * as core_Webhooks from '../../../internal/Webhooks.js'
6
+ import * as TestApp from '../../../../test/App.js'
7
+ import { management } from '../App.js'
8
+ import * as Orgs from './orgs.js'
9
+ import * as Webhooks from './webhooks.js'
10
+
11
+ const origin = 'http://localhost'
12
+ const webhook = { supportedChainIds: [4217, 42431] } satisfies App.Webhook
13
+
14
+ function json(method: 'PATCH' | 'POST', cookie: string, body: unknown) {
15
+ return {
16
+ body: JSON.stringify(body),
17
+ headers: { 'content-type': 'application/json', cookie },
18
+ method,
19
+ }
20
+ }
21
+
22
+ function createApp(options: createApp.Options = {}) {
23
+ return App.create({
24
+ auth: { session: { wallet: { origin } } },
25
+ db: options.db ?? TestApp.database(),
26
+ kv: { store: TestApp.kvStore() },
27
+ }).route('/', management({ webhook: options.webhook ?? webhook }))
28
+ }
29
+
30
+ declare namespace createApp {
31
+ type Options = {
32
+ db?: App.create.Options['db'] | undefined
33
+ webhook?: App.Webhook | false | undefined
34
+ }
35
+ }
36
+
37
+ async function session(app: TestApp.signIn.App) {
38
+ const account = privateKeyToAccount(generatePrivateKey())
39
+ const { cookie } = await TestApp.signIn(app, account)
40
+ return cookie!
41
+ }
42
+
43
+ async function organization(app: ReturnType<typeof createApp>, cookie: string) {
44
+ const response = await app.request('/v1/orgs', json('POST', cookie, { name: 'Acme' }))
45
+ return TestApp.json(response, Orgs.schema.Organization)
46
+ }
47
+
48
+ describe('webhooks', () => {
49
+ test('creates, lists, pauses, and deletes an organization webhook', async () => {
50
+ const app = createApp()
51
+ const cookie = await session(app)
52
+ const org = await organization(app, cookie)
53
+ const collection = `/v1/orgs/${org.id}/webhooks`
54
+
55
+ const createdResponse = await app.request(
56
+ collection,
57
+ json('POST', cookie, {
58
+ chainId: 4217,
59
+ context: { title: 'Payments' },
60
+ destination: { type: 'url', url: 'https://example.com/webhooks' },
61
+ eventType: 'token:transfer',
62
+ filters: {},
63
+ }),
64
+ )
65
+ expect(createdResponse.status).toBe(200)
66
+ const created = await TestApp.json(createdResponse, Webhooks.schema.createWebhook.Response)
67
+ expect(created).toHaveProperty('secret', expect.stringMatching(/^whsec_/))
68
+
69
+ const listedResponse = await app.request(collection, { headers: { cookie } })
70
+ expect(listedResponse.status).toBe(200)
71
+ const listed = await TestApp.json(listedResponse, Webhooks.schema.ListResponse)
72
+ expect(listed.data).toMatchObject([
73
+ {
74
+ context: { title: 'Payments' },
75
+ id: created.id,
76
+ status: 'active',
77
+ },
78
+ ])
79
+
80
+ const item = `${collection}/${created.id}`
81
+ const pausedResponse = await app.request(item, json('PATCH', cookie, { status: 'paused' }))
82
+ expect(pausedResponse.status).toBe(200)
83
+ expect((await TestApp.json(pausedResponse, Webhooks.schema.Subscription)).status).toBe('paused')
84
+
85
+ const deletedResponse = await app.request(item, {
86
+ headers: { cookie },
87
+ method: 'DELETE',
88
+ })
89
+ expect(deletedResponse.status).toBe(200)
90
+ expect(await TestApp.json(deletedResponse, Webhooks.schema.DeleteResponse)).toEqual({
91
+ id: created.id,
92
+ })
93
+
94
+ const empty = await TestApp.json(
95
+ await app.request(collection, { headers: { cookie } }),
96
+ Webhooks.schema.ListResponse,
97
+ )
98
+ expect(empty.data).toEqual([])
99
+ })
100
+
101
+ test('lists paginated delivery history with destination credentials redacted', async () => {
102
+ const db = TestApp.database()
103
+ const app = createApp({ db })
104
+ const cookie = await session(app)
105
+ const org = await organization(app, cookie)
106
+ const subscription = await core_Webhooks.createSubscription(db, {
107
+ chainId: 4217,
108
+ destination: {
109
+ type: 'url',
110
+ url: 'https://example.com/hooks/capability?token=secret#fragment',
111
+ },
112
+ eventType: 'token:transfer',
113
+ owner: { orgId: org.id, type: 'api_key' },
114
+ })
115
+ for (let attempt = 1; attempt <= 6; attempt++) {
116
+ const createdAt = new Date(1_700_000_000_000 + attempt * 1_000).toISOString()
117
+ await core_Webhooks.recordDelivery(db, {
118
+ attempt,
119
+ createdAt,
120
+ envelope: {
121
+ chainId: 4217,
122
+ createdAt,
123
+ data: {
124
+ address: '0x20c0000000000000000000008f5425160ebe5525',
125
+ amount: String(attempt),
126
+ blockNumber: attempt,
127
+ recipient: '0x9e39034aae71fb89f66061a2602eb6efec271754',
128
+ sender: '0xe7687128b0a808c2831ff94d4f7b2fb35c65af38',
129
+ timestamp: createdAt,
130
+ transactionHash: `0x${attempt.toString(16).padStart(64, '0')}`,
131
+ },
132
+ id: `evt_${attempt}`,
133
+ subscriptionId: subscription.id,
134
+ type: 'token:transfer',
135
+ },
136
+ eventId: `evt_${attempt}`,
137
+ id: `whd_${String(1_700_000_000_000 + attempt * 1_000).padStart(15, '0')}_${'0'.repeat(16)}`,
138
+ requestUrl: subscription.destination.url,
139
+ responseMs: attempt,
140
+ responseStatus: 200,
141
+ status: 'succeeded',
142
+ subscriptionId: subscription.id,
143
+ })
144
+ }
145
+
146
+ const path = `/v1/orgs/${org.id}/webhooks/${subscription.id}/deliveries?limit=5`
147
+ const response = await app.request(path, { headers: { cookie } })
148
+ expect(response.status).toBe(200)
149
+ const body = await TestApp.json(response, Webhooks.schema.listWebhookDeliveries.Response)
150
+ expect(body.data.map((delivery) => delivery.eventId)).toEqual([
151
+ 'evt_6',
152
+ 'evt_5',
153
+ 'evt_4',
154
+ 'evt_3',
155
+ 'evt_2',
156
+ ])
157
+ expect(body.nextCursor).toBeTruthy()
158
+ expect(body.data[0]?.requestUrl).toBe('https://example.com/…')
159
+ expect(JSON.stringify(body)).not.toContain('capability')
160
+ expect(JSON.stringify(body)).not.toContain('token=secret')
161
+ expect(JSON.stringify(body)).not.toContain('amount')
162
+
163
+ const detailResponse = await app.request(`${path.split('?')[0]}/${body.data[0]!.id}`, {
164
+ headers: { cookie },
165
+ })
166
+ expect(detailResponse.status).toBe(200)
167
+ const detail = await TestApp.json(detailResponse, Webhooks.schema.getWebhookDelivery.Response)
168
+ expect(detail.envelope).toMatchObject({
169
+ data: { amount: '6' },
170
+ id: 'evt_6',
171
+ subscriptionId: subscription.id,
172
+ type: 'token:transfer',
173
+ })
174
+ expect(detail.requestUrl).toBe('https://example.com/…')
175
+ expect(JSON.stringify(detail)).not.toContain('capability')
176
+ expect(JSON.stringify(detail)).not.toContain('token=secret')
177
+ expect(JSON.stringify(detail)).not.toContain(subscription.secret)
178
+ })
179
+
180
+ test('returns delivery_not_found when loading or replaying an unknown delivery', async () => {
181
+ const db = TestApp.database()
182
+ const app = createApp({ db })
183
+ const cookie = await session(app)
184
+ const org = await organization(app, cookie)
185
+ const subscription = await core_Webhooks.createSubscription(db, {
186
+ chainId: 4217,
187
+ destination: { type: 'url', url: 'https://example.com/webhooks' },
188
+ eventType: 'token:transfer',
189
+ owner: { orgId: org.id, type: 'api_key' },
190
+ })
191
+
192
+ const detailResponse = await app.request(
193
+ `/v1/orgs/${org.id}/webhooks/${subscription.id}/deliveries/whd_missing`,
194
+ { headers: { cookie } },
195
+ )
196
+ expect(detailResponse.status).toBe(404)
197
+ expect(await detailResponse.json()).toMatchObject({
198
+ error: { code: 'delivery_not_found' },
199
+ })
200
+
201
+ const response = await app.request(
202
+ `/v1/orgs/${org.id}/webhooks/${subscription.id}/deliveries/whd_missing/retry`,
203
+ { headers: { cookie }, method: 'POST' },
204
+ )
205
+ expect(response.status).toBe(404)
206
+ expect(await response.json()).toMatchObject({ error: { code: 'delivery_not_found' } })
207
+ })
208
+
209
+ test('hides webhooks behind foreign organizations', async () => {
210
+ const app = createApp()
211
+ const cookie = await session(app)
212
+ const org = await organization(app, cookie)
213
+ const stranger = await session(app)
214
+
215
+ const response = await app.request(`/v1/orgs/${org.id}/webhooks`, {
216
+ headers: { cookie: stranger },
217
+ })
218
+ expect(response.status).toBe(404)
219
+ })
220
+
221
+ test('rejects chains the host does not poll', async () => {
222
+ const app = createApp()
223
+ const cookie = await session(app)
224
+ const org = await organization(app, cookie)
225
+ const response = await app.request(
226
+ `/v1/orgs/${org.id}/webhooks`,
227
+ json('POST', cookie, {
228
+ chainId: 1,
229
+ destination: { type: 'url', url: 'https://example.com/webhooks' },
230
+ eventType: 'token:transfer',
231
+ filters: {},
232
+ }),
233
+ )
234
+
235
+ expect(response.status).toBe(400)
236
+ expect(await response.json()).toMatchObject({
237
+ error: { code: 'chain_id_unsupported' },
238
+ })
239
+ })
240
+
241
+ test('requires an explicit chain id', async () => {
242
+ const app = createApp()
243
+ const cookie = await session(app)
244
+ const org = await organization(app, cookie)
245
+ const response = await app.request(
246
+ `/v1/orgs/${org.id}/webhooks`,
247
+ json('POST', cookie, {
248
+ destination: { type: 'url', url: 'https://example.com/webhooks' },
249
+ eventType: 'token:transfer',
250
+ filters: {},
251
+ }),
252
+ )
253
+
254
+ expect(response.status).toBe(400)
255
+ expect(await response.json()).toMatchObject({ error: { code: 'body_invalid' } })
256
+ })
257
+
258
+ test('redacts managed destination credentials and only returns secrets for URLs', async () => {
259
+ const app = createApp()
260
+ const cookie = await session(app)
261
+ const org = await organization(app, cookie)
262
+ const collection = `/v1/orgs/${org.id}/webhooks`
263
+
264
+ const url = await TestApp.json(
265
+ await app.request(
266
+ collection,
267
+ json('POST', cookie, {
268
+ chainId: 4217,
269
+ destination: {
270
+ type: 'url',
271
+ url: 'https://example.com/hooks/capability?token=secret#fragment',
272
+ },
273
+ eventType: 'token:transfer',
274
+ filters: {},
275
+ }),
276
+ ),
277
+ Webhooks.schema.createWebhook.Response,
278
+ )
279
+ expect(url).toMatchObject({
280
+ destination: {
281
+ type: 'url',
282
+ url: 'https://example.com/hooks/capability?token=secret#fragment',
283
+ },
284
+ secret: expect.stringMatching(/^whsec_/),
285
+ })
286
+ const listed = await TestApp.json(
287
+ await app.request(collection, { headers: { cookie } }),
288
+ Webhooks.schema.ListResponse,
289
+ )
290
+ expect(listed.data.find(({ id }) => id === url.id)?.destination).toEqual({
291
+ type: 'url',
292
+ url: 'https://example.com/…',
293
+ })
294
+ expect(JSON.stringify(listed)).not.toContain('capability')
295
+ expect(JSON.stringify(listed)).not.toContain('token=secret')
296
+
297
+ const slack = await TestApp.json(
298
+ await app.request(
299
+ collection,
300
+ json('POST', cookie, {
301
+ chainId: 4217,
302
+ destination: {
303
+ type: 'slack',
304
+ url: 'https://hooks.slack.com/services/T000/B000/XXXXXXXX',
305
+ },
306
+ eventType: 'token:transfer',
307
+ filters: {},
308
+ }),
309
+ ),
310
+ Webhooks.schema.createWebhook.Response,
311
+ )
312
+ expect(slack).toMatchObject({
313
+ destination: { type: 'slack', url: 'https://hooks.slack.com/…' },
314
+ })
315
+ expect('secret' in slack).toBe(false)
316
+
317
+ const betterstack = await TestApp.json(
318
+ await app.request(
319
+ collection,
320
+ json('POST', cookie, {
321
+ chainId: 4217,
322
+ destination: {
323
+ token: 'source-token',
324
+ type: 'betterstack',
325
+ url: 'https://s1234567.eu-nbg-2.betterstackdata.com',
326
+ },
327
+ eventType: 'token:transfer',
328
+ filters: {},
329
+ }),
330
+ ),
331
+ Webhooks.schema.createWebhook.Response,
332
+ )
333
+ expect(betterstack).toMatchObject({
334
+ destination: {
335
+ token: '[redacted]',
336
+ type: 'betterstack',
337
+ url: 'https://s1234567.eu-nbg-2.betterstackdata.com',
338
+ },
339
+ })
340
+ expect('secret' in betterstack).toBe(false)
341
+ })
342
+
343
+ test('uses the shared per-owner limit', async () => {
344
+ const app = createApp({
345
+ webhook: { maxPerOwner: 1, supportedChainIds: webhook.supportedChainIds },
346
+ })
347
+ const cookie = await session(app)
348
+ const org = await organization(app, cookie)
349
+ const collection = `/v1/orgs/${org.id}/webhooks`
350
+ const body = {
351
+ chainId: 4217,
352
+ destination: { type: 'url', url: 'https://example.com/webhooks' },
353
+ eventType: 'token:transfer',
354
+ filters: {},
355
+ }
356
+
357
+ expect((await app.request(collection, json('POST', cookie, body))).status).toBe(200)
358
+ const response = await app.request(collection, json('POST', cookie, body))
359
+ expect(response.status).toBe(403)
360
+ expect(await response.json()).toMatchObject({ error: { code: 'limit_exceeded' } })
361
+ })
362
+
363
+ test('lists every webhook when the creation limit is lowered', async () => {
364
+ const db = TestApp.database()
365
+ const app = createApp({
366
+ db,
367
+ webhook: { maxPerOwner: 1, supportedChainIds: webhook.supportedChainIds },
368
+ })
369
+ const cookie = await session(app)
370
+ const org = await organization(app, cookie)
371
+ const input = {
372
+ chainId: 4217,
373
+ destination: { type: 'url', url: 'https://example.com/webhooks' } as const,
374
+ eventType: 'token:transfer' as const,
375
+ owner: { orgId: org.id, type: 'api_key' as const },
376
+ }
377
+ await core_Webhooks.createSubscription(db, input)
378
+ await core_Webhooks.createSubscription(db, input)
379
+
380
+ const response = await app.request(`/v1/orgs/${org.id}/webhooks`, {
381
+ headers: { cookie },
382
+ })
383
+ expect(response.status).toBe(200)
384
+ expect((await TestApp.json(response, Webhooks.schema.ListResponse)).data).toHaveLength(2)
385
+ })
386
+
387
+ test('keeps management disabled unless the capability is supplied', async () => {
388
+ const app = createApp({ webhook: false })
389
+ const cookie = await session(app)
390
+ const org = await organization(app, cookie)
391
+ const response = await app.request(`/v1/orgs/${org.id}/webhooks`, {
392
+ headers: { cookie },
393
+ })
394
+
395
+ expect(response.status).toBe(404)
396
+ expect(await response.json()).toMatchObject({ error: { code: 'webhooks_not_enabled' } })
397
+ const spec = (await (await app.request('/openapi.json')).json()) as {
398
+ paths: Record<string, unknown>
399
+ }
400
+ expect(spec.paths[`/v1/orgs/{orgId}/webhooks`]).toBeUndefined()
401
+ })
402
+
403
+ test('requires an admin role for mutations', async () => {
404
+ const db = TestApp.database()
405
+ const app = createApp({ db })
406
+ const ownerCookie = await session(app)
407
+ const org = await organization(app, ownerCookie)
408
+ const memberAccount = privateKeyToAccount(generatePrivateKey())
409
+ const { cookie: memberCookie } = await TestApp.signIn(app, memberAccount)
410
+ const me = (await (
411
+ await app.request('/v1/me', { headers: { cookie: memberCookie! } })
412
+ ).json()) as { id: string }
413
+ await Memberships.create(db, { orgId: org.id, role: 'member', userId: me.id })
414
+
415
+ const response = await app.request(
416
+ `/v1/orgs/${org.id}/webhooks`,
417
+ json('POST', memberCookie!, {
418
+ destination: { type: 'url', url: 'https://example.com/webhooks' },
419
+ eventType: 'token:transfer',
420
+ filters: {},
421
+ }),
422
+ )
423
+ expect(response.status).toBe(403)
424
+ expect(await response.json()).toMatchObject({ error: { code: 'forbidden' } })
425
+ })
426
+ })