tapimo 0.6.0 → 0.7.1

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 (288) hide show
  1. package/dist/App.d.ts +8641 -8263
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +9 -1
  4. package/dist/App.js.map +1 -1
  5. package/dist/Scope.d.ts +4 -0
  6. package/dist/Scope.d.ts.map +1 -1
  7. package/dist/Scope.js +5 -0
  8. package/dist/Scope.js.map +1 -1
  9. package/dist/admin/App.d.ts +112 -11
  10. package/dist/admin/App.d.ts.map +1 -1
  11. package/dist/admin/apps/api-keys.d.ts +2 -1
  12. package/dist/admin/apps/api-keys.d.ts.map +1 -1
  13. package/dist/admin/apps/early-access.d.ts +2 -2
  14. package/dist/admin/apps/early-access.d.ts.map +1 -1
  15. package/dist/admin/apps/early-access.js +5 -5
  16. package/dist/admin/apps/early-access.js.map +1 -1
  17. package/dist/admin/apps/organizations.d.ts +124 -1
  18. package/dist/admin/apps/organizations.d.ts.map +1 -1
  19. package/dist/admin/apps/organizations.js +97 -6
  20. package/dist/admin/apps/organizations.js.map +1 -1
  21. package/dist/admin/ui.generated.d.ts +1 -1
  22. package/dist/admin/ui.generated.d.ts.map +1 -1
  23. package/dist/admin/ui.generated.js +47 -39
  24. package/dist/admin/ui.generated.js.map +1 -1
  25. package/dist/analytics/Analytics.d.ts.map +1 -1
  26. package/dist/analytics/Analytics.js +20 -0
  27. package/dist/analytics/Analytics.js.map +1 -1
  28. package/dist/analytics/tables/requestEvents.d.ts +18 -0
  29. package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
  30. package/dist/analytics/tables/requestEvents.js +36 -7
  31. package/dist/analytics/tables/requestEvents.js.map +1 -1
  32. package/dist/apps/data/App.d.ts +8050 -8019
  33. package/dist/apps/data/App.d.ts.map +1 -1
  34. package/dist/apps/data/FundingQuoteProviders.js +2 -15
  35. package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
  36. package/dist/apps/data/FxOracle.d.ts +1 -1
  37. package/dist/apps/data/FxOracle.d.ts.map +1 -1
  38. package/dist/apps/data/FxOracle.js +1 -1
  39. package/dist/apps/data/FxOracle.js.map +1 -1
  40. package/dist/apps/data/routes/activities.d.ts +27 -27
  41. package/dist/apps/data/routes/activities.d.ts.map +1 -1
  42. package/dist/apps/data/routes/activities.js +21 -1
  43. package/dist/apps/data/routes/activities.js.map +1 -1
  44. package/dist/apps/data/routes/balances.d.ts +58 -58
  45. package/dist/apps/data/routes/blocks.d.ts +39 -39
  46. package/dist/apps/data/routes/coingecko.d.ts +176 -176
  47. package/dist/apps/data/routes/exchanges.d.ts +669 -669
  48. package/dist/apps/data/routes/fee-amm.d.ts +157 -143
  49. package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
  50. package/dist/apps/data/routes/fee-amm.js +76 -91
  51. package/dist/apps/data/routes/fee-amm.js.map +1 -1
  52. package/dist/apps/data/routes/funding.d.ts +72 -34
  53. package/dist/apps/data/routes/funding.d.ts.map +1 -1
  54. package/dist/apps/data/routes/funding.js +34 -26
  55. package/dist/apps/data/routes/funding.js.map +1 -1
  56. package/dist/apps/data/routes/indexer.d.ts +30 -30
  57. package/dist/apps/data/routes/receipts.d.ts +8 -8
  58. package/dist/apps/data/routes/rpc.d.ts +31 -31
  59. package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
  60. package/dist/apps/data/routes/tokenlist.js +12 -3
  61. package/dist/apps/data/routes/tokenlist.js.map +1 -1
  62. package/dist/apps/data/routes/tokens.d.ts +4 -4
  63. package/dist/apps/data/routes/tokens.d.ts.map +1 -1
  64. package/dist/apps/data/routes/tokens.js +5 -1
  65. package/dist/apps/data/routes/tokens.js.map +1 -1
  66. package/dist/apps/data/routes/transactions.d.ts +6 -6
  67. package/dist/apps/data/routes/transfers.d.ts +101 -90
  68. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  69. package/dist/apps/data/routes/transfers.js +226 -141
  70. package/dist/apps/data/routes/transfers.js.map +1 -1
  71. package/dist/apps/data/routes/valuation.d.ts +2 -2
  72. package/dist/apps/data/routes/valuation.js +3 -3
  73. package/dist/apps/data/routes/valuation.js.map +1 -1
  74. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  75. package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
  76. package/dist/apps/data/routes/verified-tokens.js +34 -9
  77. package/dist/apps/data/routes/verified-tokens.js.map +1 -1
  78. package/dist/apps/data/routes/webhooks.d.ts +2 -2
  79. package/dist/apps/data/routes/webhooks.js +14 -6
  80. package/dist/apps/data/routes/webhooks.js.map +1 -1
  81. package/dist/apps/index.d.ts +1 -0
  82. package/dist/apps/index.d.ts.map +1 -1
  83. package/dist/apps/index.js +1 -0
  84. package/dist/apps/index.js.map +1 -1
  85. package/dist/apps/management/App.d.ts +460 -523
  86. package/dist/apps/management/App.d.ts.map +1 -1
  87. package/dist/apps/management/routes/api-keys.d.ts +7 -23
  88. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  89. package/dist/apps/management/routes/api-keys.js +20 -8
  90. package/dist/apps/management/routes/api-keys.js.map +1 -1
  91. package/dist/apps/management/routes/billing.d.ts +106 -151
  92. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  93. package/dist/apps/management/routes/billing.js +28 -28
  94. package/dist/apps/management/routes/billing.js.map +1 -1
  95. package/dist/apps/management/routes/faucet.d.ts +48 -34
  96. package/dist/apps/management/routes/faucet.d.ts.map +1 -1
  97. package/dist/apps/management/routes/faucet.js +33 -18
  98. package/dist/apps/management/routes/faucet.js.map +1 -1
  99. package/dist/apps/management/routes/invitations.d.ts +131 -131
  100. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  101. package/dist/apps/management/routes/invitations.js +0 -3
  102. package/dist/apps/management/routes/invitations.js.map +1 -1
  103. package/dist/apps/management/routes/me.d.ts +6 -8
  104. package/dist/apps/management/routes/me.d.ts.map +1 -1
  105. package/dist/apps/management/routes/me.js +0 -4
  106. package/dist/apps/management/routes/me.js.map +1 -1
  107. package/dist/apps/management/routes/members.d.ts +73 -73
  108. package/dist/apps/management/routes/orgs.d.ts +94 -94
  109. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  110. package/dist/apps/management/routes/orgs.js +5 -0
  111. package/dist/apps/management/routes/orgs.js.map +1 -1
  112. package/dist/apps/management/routes/usage.d.ts +72 -72
  113. package/dist/apps/management/routes/verified-token-requests.d.ts +94 -94
  114. package/dist/apps/management/routes/webhooks.d.ts +1 -1
  115. package/dist/apps/mpp/App.d.ts +363 -0
  116. package/dist/apps/mpp/App.d.ts.map +1 -0
  117. package/dist/apps/mpp/App.js +72 -0
  118. package/dist/apps/mpp/App.js.map +1 -0
  119. package/dist/apps/mpp/Mpp.d.ts +178 -0
  120. package/dist/apps/mpp/Mpp.d.ts.map +1 -0
  121. package/dist/apps/mpp/Mpp.js +183 -0
  122. package/dist/apps/mpp/Mpp.js.map +1 -0
  123. package/dist/apps/mpp/index.d.ts +3 -0
  124. package/dist/apps/mpp/index.d.ts.map +1 -0
  125. package/dist/apps/mpp/index.js +3 -0
  126. package/dist/apps/mpp/index.js.map +1 -0
  127. package/dist/apps/relay/App.d.ts.map +1 -1
  128. package/dist/apps/relay/App.js +57 -10
  129. package/dist/apps/relay/App.js.map +1 -1
  130. package/dist/apps/relay/Sponsorships.js +1 -1
  131. package/dist/apps/relay/Sponsorships.js.map +1 -1
  132. package/dist/db/Db.d.ts +2 -0
  133. package/dist/db/Db.d.ts.map +1 -1
  134. package/dist/db/Db.js +27 -0
  135. package/dist/db/Db.js.map +1 -1
  136. package/dist/db/Schema.d.ts +12 -0
  137. package/dist/db/Schema.d.ts.map +1 -1
  138. package/dist/db/Schema.js +11 -0
  139. package/dist/db/Schema.js.map +1 -1
  140. package/dist/db/tables/enabledBillingSources.d.ts +73 -0
  141. package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
  142. package/dist/db/tables/enabledBillingSources.js +69 -0
  143. package/dist/db/tables/enabledBillingSources.js.map +1 -0
  144. package/dist/db/tables/invitations.d.ts +1 -12
  145. package/dist/db/tables/invitations.d.ts.map +1 -1
  146. package/dist/db/tables/invitations.js +3 -23
  147. package/dist/db/tables/invitations.js.map +1 -1
  148. package/dist/db/tables/organizations.d.ts +5 -2
  149. package/dist/db/tables/organizations.d.ts.map +1 -1
  150. package/dist/db/tables/organizations.js +32 -2
  151. package/dist/db/tables/organizations.js.map +1 -1
  152. package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
  153. package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
  154. package/dist/db/tables/sponsoredTransactions.js +13 -3
  155. package/dist/db/tables/sponsoredTransactions.js.map +1 -1
  156. package/dist/handlers/internal/utils.d.ts +1 -16
  157. package/dist/handlers/internal/utils.d.ts.map +1 -1
  158. package/dist/handlers/internal/utils.js +17 -6
  159. package/dist/handlers/internal/utils.js.map +1 -1
  160. package/dist/handlers/relay.d.ts.map +1 -1
  161. package/dist/handlers/relay.js +37 -5
  162. package/dist/handlers/relay.js.map +1 -1
  163. package/dist/internal/Auth.d.ts +0 -32
  164. package/dist/internal/Auth.d.ts.map +1 -1
  165. package/dist/internal/Auth.js +3 -51
  166. package/dist/internal/Auth.js.map +1 -1
  167. package/dist/internal/EdgeCache.d.ts +1 -1
  168. package/dist/internal/EdgeCache.d.ts.map +1 -1
  169. package/dist/internal/EdgeCache.js +3 -2
  170. package/dist/internal/EdgeCache.js.map +1 -1
  171. package/dist/internal/FundingQuotes.d.ts +78 -32
  172. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  173. package/dist/internal/FundingQuotes.js +45 -8
  174. package/dist/internal/FundingQuotes.js.map +1 -1
  175. package/dist/internal/Log.d.ts +18 -4
  176. package/dist/internal/Log.d.ts.map +1 -1
  177. package/dist/internal/Log.js +50 -3
  178. package/dist/internal/Log.js.map +1 -1
  179. package/dist/internal/MetricSink.d.ts.map +1 -1
  180. package/dist/internal/MetricSink.js +15 -0
  181. package/dist/internal/MetricSink.js.map +1 -1
  182. package/dist/internal/Response.d.ts +2 -2
  183. package/dist/internal/Response.d.ts.map +1 -1
  184. package/dist/internal/Response.js +12 -4
  185. package/dist/internal/Response.js.map +1 -1
  186. package/dist/internal/Schema.d.ts +2 -2
  187. package/dist/internal/Schema.d.ts.map +1 -1
  188. package/dist/internal/Schema.js +4 -2
  189. package/dist/internal/Schema.js.map +1 -1
  190. package/dist/internal/Viem.d.ts +1 -1
  191. package/package.json +7 -2
  192. package/src/App.test.ts +9 -4
  193. package/src/App.ts +14 -2
  194. package/src/Client.test-d.ts +109 -28
  195. package/src/Scope.ts +5 -0
  196. package/src/admin/App.test.ts +4 -0
  197. package/src/admin/apps/early-access.ts +6 -5
  198. package/src/admin/apps/organizations.test.ts +86 -2
  199. package/src/admin/apps/organizations.ts +155 -31
  200. package/src/admin/apps/scopes.test.ts +5 -0
  201. package/src/admin/ui/src/lib/organizations.test.ts +81 -1
  202. package/src/admin/ui/src/lib/organizations.ts +92 -0
  203. package/src/admin/ui/src/routes/api-keys.tsx +1 -1
  204. package/src/admin/ui/src/routes/early-access.tsx +8 -3
  205. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
  206. package/src/admin/ui.generated.ts +49 -39
  207. package/src/analytics/Analytics.test.ts +83 -0
  208. package/src/analytics/Analytics.ts +20 -0
  209. package/src/analytics/tables/requestEvents.ts +60 -7
  210. package/src/apps/composition.test.ts +11 -2
  211. package/src/apps/data/App.test.ts +14 -2
  212. package/src/apps/data/FundingQuoteProviders.test.ts +0 -5
  213. package/src/apps/data/FundingQuoteProviders.ts +2 -29
  214. package/src/apps/data/FxOracle.test.ts +2 -2
  215. package/src/apps/data/FxOracle.ts +2 -2
  216. package/src/apps/data/routes/activities.test.ts +6 -2
  217. package/src/apps/data/routes/activities.ts +39 -1
  218. package/src/apps/data/routes/balances.test.ts +6 -2
  219. package/src/apps/data/routes/exchanges.test.ts +7 -3
  220. package/src/apps/data/routes/fee-amm.test.ts +47 -23
  221. package/src/apps/data/routes/fee-amm.ts +98 -119
  222. package/src/apps/data/routes/funding.test.ts +29 -8
  223. package/src/apps/data/routes/funding.ts +42 -48
  224. package/src/apps/data/routes/receipts.test.ts +6 -2
  225. package/src/apps/data/routes/tokenlist.test.ts +15 -0
  226. package/src/apps/data/routes/tokenlist.ts +18 -5
  227. package/src/apps/data/routes/tokens.ts +5 -1
  228. package/src/apps/data/routes/transfers.test.ts +203 -35
  229. package/src/apps/data/routes/transfers.ts +294 -172
  230. package/src/apps/data/routes/valuation.test.ts +18 -3
  231. package/src/apps/data/routes/valuation.ts +4 -4
  232. package/src/apps/data/routes/verified-tokens.test.ts +51 -0
  233. package/src/apps/data/routes/verified-tokens.ts +52 -15
  234. package/src/apps/data/routes/webhooks.test.ts +56 -0
  235. package/src/apps/data/routes/webhooks.ts +17 -8
  236. package/src/apps/index.ts +1 -0
  237. package/src/apps/management/routes/api-keys.test.ts +2 -75
  238. package/src/apps/management/routes/api-keys.ts +30 -7
  239. package/src/apps/management/routes/billing.test.ts +106 -85
  240. package/src/apps/management/routes/billing.ts +32 -21
  241. package/src/apps/management/routes/faucet.test.ts +11 -3
  242. package/src/apps/management/routes/faucet.ts +46 -30
  243. package/src/apps/management/routes/invitations.test.ts +0 -172
  244. package/src/apps/management/routes/invitations.ts +0 -3
  245. package/src/apps/management/routes/me.test.ts +3 -145
  246. package/src/apps/management/routes/me.ts +0 -6
  247. package/src/apps/management/routes/orgs.test.ts +45 -2
  248. package/src/apps/management/routes/orgs.ts +6 -0
  249. package/src/apps/management/routes/scopes.test.ts +1 -0
  250. package/src/apps/mpp/App.ts +86 -0
  251. package/src/apps/mpp/Mpp.test.ts +193 -0
  252. package/src/apps/mpp/Mpp.ts +353 -0
  253. package/src/apps/mpp/index.ts +2 -0
  254. package/src/apps/mpp/internal/Adapter.ts +11 -0
  255. package/src/apps/mpp/internal/Error.ts +9 -0
  256. package/src/apps/mpp/internal/Idempotency.ts +7 -0
  257. package/src/apps/mpp/internal/Screening.ts +10 -0
  258. package/src/apps/relay/App.test.ts +90 -8
  259. package/src/apps/relay/App.ts +62 -10
  260. package/src/apps/relay/Sponsorships.test.ts +2 -0
  261. package/src/apps/relay/Sponsorships.ts +1 -1
  262. package/src/db/Db.test.ts +2 -2
  263. package/src/db/Db.ts +30 -0
  264. package/src/db/Schema.ts +15 -0
  265. package/src/db/tables/enabledBillingSources.test.ts +303 -0
  266. package/src/db/tables/enabledBillingSources.ts +118 -0
  267. package/src/db/tables/invitations.ts +4 -26
  268. package/src/db/tables/organizations.test.ts +191 -0
  269. package/src/db/tables/organizations.ts +44 -3
  270. package/src/db/tables/sponsoredTransactions.test.ts +41 -2
  271. package/src/db/tables/sponsoredTransactions.ts +15 -3
  272. package/src/handlers/internal/utils.ts +22 -6
  273. package/src/handlers/relay.errors.test.ts +153 -0
  274. package/src/handlers/relay.ts +42 -7
  275. package/src/internal/Auth.test.ts +3 -3
  276. package/src/internal/Auth.ts +3 -63
  277. package/src/internal/EdgeCache.test.ts +18 -5
  278. package/src/internal/EdgeCache.ts +4 -2
  279. package/src/internal/FundingQuotes.test.ts +18 -5
  280. package/src/internal/FundingQuotes.ts +46 -11
  281. package/src/internal/Log.test.ts +91 -0
  282. package/src/internal/Log.ts +69 -5
  283. package/src/internal/MetricSink.test.ts +55 -0
  284. package/src/internal/MetricSink.ts +14 -0
  285. package/src/internal/Response.test.ts +28 -0
  286. package/src/internal/Response.ts +12 -4
  287. package/src/internal/Schema.test.ts +13 -0
  288. package/src/internal/Schema.ts +14 -7
@@ -2,7 +2,6 @@ import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
2
2
 
3
3
  import * as App from '../App.js'
4
4
  import * as TestApp from '../../../../test/App.js'
5
- import * as EarlyAccess from '../../../db/tables/earlyAccess.js'
6
5
  import * as Invitations from '../../../db/tables/invitations.js'
7
6
  import * as InvitationsApp from './invitations.js'
8
7
  import * as Memberships from '../../../db/tables/memberships.js'
@@ -51,7 +50,6 @@ function createApp(
51
50
  db?: ReturnType<typeof TestApp.database>
52
51
  email?: App.Email
53
52
  kv?: { store: ReturnType<typeof TestApp.kvStore> }
54
- tmp_earlyAccess?: boolean
55
53
  } = {},
56
54
  ) {
57
55
  return TestApp.create({
@@ -60,7 +58,6 @@ function createApp(
60
58
  ...(options.email ? { email: options.email } : {}),
61
59
  ...(options.kv ? { kv: options.kv } : {}),
62
60
  session: { wallet: { origin } },
63
- ...(options.tmp_earlyAccess === undefined ? {} : { tmp_earlyAccess: options.tmp_earlyAccess }),
64
61
  })
65
62
  }
66
63
 
@@ -349,7 +346,6 @@ describe('invitations', () => {
349
346
  const expired = await Invitations.create(db, {
350
347
  email: 'dev@example.com',
351
348
  expiresAt: '2020-01-01T00:00:00.000Z',
352
- grantsEarlyAccess: false,
353
349
  invitedBy: owner.userId,
354
350
  orgId: org.id,
355
351
  role: 'member',
@@ -512,171 +508,3 @@ describe('invitations', () => {
512
508
  )
513
509
  })
514
510
  })
515
-
516
- describe('early access stamping', () => {
517
- test('does not persist a dormant gate bypass', async () => {
518
- const db = TestApp.database()
519
- const app = createApp({ db })
520
- const owner = await session(app)
521
- await Users.setEmail(db, owner.userId, 'owner@example.net')
522
- const created = await app.request('/v1/orgs', json('POST', owner.cookie, { name: 'Acme' }))
523
- const org = await TestApp.json(created, Orgs.schema.Organization)
524
-
525
- const invited = await app.request(
526
- `/v1/orgs/${org.id}/invitations`,
527
- json('POST', owner.cookie, { email: 'owner@example.net', role: 'member' }),
528
- )
529
- const invitation = await TestApp.json(invited, InvitationsApp.schema.Invitation)
530
-
531
- expect((await Invitations.get(db, invitation.id))?.grantsEarlyAccess).toBe(false)
532
- })
533
-
534
- test('stamps grants_early_access from the session inviter', async () => {
535
- const db = TestApp.database()
536
- const app = createApp({ db, tmp_earlyAccess: true })
537
- await EarlyAccess.add(db, { createdBy: 'test', entry: 'example.org' })
538
-
539
- // A non-early-access inviter stamps no grant.
540
- const plain = await session(app)
541
- const createdPlain = await app.request('/v1/orgs', json('POST', plain.cookie, { name: 'Plain' })) // prettier-ignore
542
- const orgPlain = await TestApp.json(createdPlain, Orgs.schema.Organization)
543
- const invitedPlain = await app.request(
544
- `/v1/orgs/${orgPlain.id}/invitations`,
545
- json('POST', plain.cookie, { email: 'a@example.net', role: 'member' }),
546
- )
547
- const invitationPlain = await TestApp.json(invitedPlain, InvitationsApp.schema.Invitation)
548
- expect((await Invitations.get(db, invitationPlain.id))?.grantsEarlyAccess).toBe(false)
549
-
550
- // An early-access inviter stamps the grant.
551
- const granted = await session(app)
552
- await Users.setEmail(db, granted.userId, 'owner@example.org')
553
- const createdGranted = await app.request('/v1/orgs', json('POST', granted.cookie, { name: 'Granted' })) // prettier-ignore
554
- const orgGranted = await TestApp.json(createdGranted, Orgs.schema.Organization)
555
- const invitedGranted = await app.request(
556
- `/v1/orgs/${orgGranted.id}/invitations`,
557
- json('POST', granted.cookie, { email: 'b@example.net', role: 'member' }),
558
- )
559
- const invitationGranted = await TestApp.json(invitedGranted, InvitationsApp.schema.Invitation)
560
- expect((await Invitations.get(db, invitationGranted.id))?.grantsEarlyAccess).toBe(true)
561
- })
562
-
563
- test('stamps grants_early_access true for API-key and super-admin inviters', async () => {
564
- const db = TestApp.database()
565
- await Organizations.create(db, { id: management.orgId, name: 'Acme' })
566
- const app = createApp({
567
- db,
568
- tmp_earlyAccess: true,
569
- kv: { store: TestApp.kvStore({ keys: [management] }) },
570
- })
571
-
572
- const viaKey = await app.request(`/v1/orgs/${management.orgId}/invitations`, {
573
- body: JSON.stringify({ email: 'key@example.net', role: 'member' }),
574
- headers: { 'content-type': 'application/json', 'tempo-api-key': management.token },
575
- method: 'POST',
576
- })
577
- expect(viaKey.status).toBe(200)
578
- const keyInvitation = await TestApp.json(viaKey, InvitationsApp.schema.Invitation)
579
- expect((await Invitations.get(db, keyInvitation.id))?.grantsEarlyAccess).toBe(true)
580
-
581
- const created = await app.request('/v1/orgs', {
582
- body: JSON.stringify({ name: 'Provisioned' }),
583
- headers: { 'content-type': 'application/json', 'tempo-api-key': secret },
584
- method: 'POST',
585
- })
586
- const org = await TestApp.json(created, Orgs.schema.Organization)
587
- const viaAdmin = await app.request(`/v1/orgs/${org.id}/invitations`, {
588
- body: JSON.stringify({ email: 'admin@example.net', role: 'owner' }),
589
- headers: { 'content-type': 'application/json', 'tempo-api-key': secret },
590
- method: 'POST',
591
- })
592
- expect(viaAdmin.status).toBe(200)
593
- const adminInvitation = await TestApp.json(viaAdmin, InvitationsApp.schema.Invitation)
594
- expect((await Invitations.get(db, adminInvitation.id))?.grantsEarlyAccess).toBe(true)
595
- })
596
-
597
- test('re-invites upgrade the grant and never downgrade it', async () => {
598
- const db = TestApp.database()
599
- const app = createApp({ db, tmp_earlyAccess: true })
600
- await EarlyAccess.add(db, { createdBy: 'test', entry: 'example.org' })
601
- const owner = await session(app)
602
- const created = await app.request('/v1/orgs', json('POST', owner.cookie, { name: 'Acme' }))
603
- const org = await TestApp.json(created, Orgs.schema.Organization)
604
- const admin = await session(app)
605
- await Users.setEmail(db, admin.userId, 'admin@example.org')
606
- await Memberships.create(db, { orgId: org.id, role: 'admin', userId: admin.userId })
607
-
608
- const first = await app.request(
609
- `/v1/orgs/${org.id}/invitations`,
610
- json('POST', owner.cookie, { email: 'x@example.net', role: 'member' }),
611
- )
612
- const firstInvite = await TestApp.json(first, InvitationsApp.schema.Invitation)
613
- expect((await Invitations.get(db, firstInvite.id))?.grantsEarlyAccess).toBe(false)
614
-
615
- // An early-access re-invite upgrades the same pending row.
616
- const second = await app.request(
617
- `/v1/orgs/${org.id}/invitations`,
618
- json('POST', admin.cookie, { email: 'x@example.net', role: 'member' }),
619
- )
620
- const secondInvite = await TestApp.json(second, InvitationsApp.schema.Invitation)
621
- expect(secondInvite.id).toBe(firstInvite.id)
622
- expect((await Invitations.get(db, firstInvite.id))?.grantsEarlyAccess).toBe(true)
623
-
624
- // A later non-early-access re-invite never withdraws the grant.
625
- const third = await app.request(
626
- `/v1/orgs/${org.id}/invitations`,
627
- json('POST', owner.cookie, { email: 'x@example.net', role: 'member' }),
628
- )
629
- expect(third.status).toBe(200)
630
- expect((await Invitations.get(db, firstInvite.id))?.grantsEarlyAccess).toBe(true)
631
- })
632
-
633
- test('hasEarlyAccessGrant survives acceptance and expiry, dies on revoke', async () => {
634
- const db = TestApp.database()
635
- const app = createApp({ db, tmp_earlyAccess: true })
636
- await EarlyAccess.add(db, { createdBy: 'test', entry: 'example.org' })
637
- const owner = await session(app)
638
- await Users.setEmail(db, owner.userId, 'owner@example.org')
639
- const created = await app.request('/v1/orgs', json('POST', owner.cookie, { name: 'Acme' }))
640
- const org = await TestApp.json(created, Orgs.schema.Organization)
641
-
642
- // Acceptance settles the invitation but keeps the grant.
643
- const invited = await app.request(
644
- `/v1/orgs/${org.id}/invitations`,
645
- json('POST', owner.cookie, { email: 'z@example.net', role: 'member' }),
646
- )
647
- const invitation = await TestApp.json(invited, InvitationsApp.schema.Invitation)
648
- const invitee = await session(app)
649
- await Users.setEmail(db, invitee.userId, 'z@example.net')
650
- const accepted = await app.request(`/v1/invitations/${invitation.id}/accept`, {
651
- headers: { cookie: invitee.cookie },
652
- method: 'POST',
653
- })
654
- expect(accepted.status).toBe(200)
655
- expect(await Invitations.hasEarlyAccessGrant(db, 'z@example.net')).toBe(true)
656
-
657
- // Expiry is ignored by the grant lookup.
658
- await Invitations.create(db, {
659
- email: 'e@example.net',
660
- expiresAt: '2020-01-01T00:00:00.000Z',
661
- grantsEarlyAccess: true,
662
- invitedBy: owner.userId,
663
- orgId: org.id,
664
- role: 'member',
665
- })
666
- expect(await Invitations.hasEarlyAccessGrant(db, 'e@example.net')).toBe(true)
667
-
668
- // Revocation withdraws the grant.
669
- const pending = await app.request(
670
- `/v1/orgs/${org.id}/invitations`,
671
- json('POST', owner.cookie, { email: 'r@example.net', role: 'member' }),
672
- )
673
- const pendingInvite = await TestApp.json(pending, InvitationsApp.schema.Invitation)
674
- expect(await Invitations.hasEarlyAccessGrant(db, 'r@example.net')).toBe(true)
675
- const revoked = await app.request(`/v1/orgs/${org.id}/invitations/${pendingInvite.id}`, {
676
- headers: { cookie: owner.cookie },
677
- method: 'DELETE',
678
- })
679
- expect(revoked.status).toBe(200)
680
- expect(await Invitations.hasEarlyAccessGrant(db, 'r@example.net')).toBe(false)
681
- })
682
- })
@@ -261,15 +261,12 @@ export function invitations() {
261
261
  const principal = Auth.getPrincipal(c)
262
262
  const invitedBy =
263
263
  principal?.type === 'api_key' ? principal.id : (caller?.userId ?? 'super_admin')
264
- // Elevation transfers only from inviters who themselves have early access.
265
- const grantsEarlyAccess = await Auth.tmp_earlyAccessGrant(c)
266
264
 
267
265
  const db = Db.get(c.get('db'))
268
266
  try {
269
267
  const record = await Invitations.create(db, {
270
268
  email: body.email,
271
269
  expiresAt: new Date(Date.now() + ttlDays * 86_400_000).toISOString(),
272
- grantsEarlyAccess,
273
270
  invitedBy,
274
271
  orgId: Auth.org(c).id,
275
272
  role: body.role,
@@ -4,8 +4,6 @@ import { exportJWK, generateKeyPair } from 'jose'
4
4
  import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
5
5
 
6
6
  import * as TestApp from '../../../../test/App.js'
7
- import * as EarlyAccess from '../../../db/tables/earlyAccess.js'
8
- import * as Users from '../../../db/tables/users.js'
9
7
  import * as Me from './me.js'
10
8
 
11
9
  /** Origin pinned for SIWE domain binding; Hono test requests use this host. */
@@ -20,40 +18,6 @@ function createApp() {
20
18
  })
21
19
  }
22
20
 
23
- /** App with the early-access gate enabled; allowlist entries seed through `EarlyAccess.add`. */
24
- function createGatedApp(db = TestApp.database()) {
25
- return TestApp.create({
26
- auth: { keys: [TestApp.key] },
27
- db,
28
- session: { wallet: { origin } },
29
- tmp_earlyAccess: true,
30
- })
31
- }
32
-
33
- /** RequestInit for a JSON mutation carrying the session cookie. */
34
- function json(method: 'POST', cookie: string, body: unknown) {
35
- return {
36
- body: JSON.stringify(body),
37
- headers: { 'content-type': 'application/json', cookie },
38
- method,
39
- }
40
- }
41
-
42
- /** Signs in a fresh scripted account; returns its session cookie and user id. */
43
- async function session(app: TestApp.signIn.App) {
44
- const account = privateKeyToAccount(generatePrivateKey())
45
- const { cookie } = await TestApp.signIn(app, account)
46
- const me = await app.request('/v1/me', { headers: { cookie: cookie! } })
47
- const { id } = (await me.json()) as { id: string }
48
- return { cookie: cookie!, userId: id }
49
- }
50
-
51
- /** Reads the session's `tmp_earlyAccess` flag from `/v1/me`. TODO: remove after GA. */
52
- async function tmp_earlyAccess(app: TestApp.signIn.App, cookie: string) {
53
- const response = await app.request('/v1/me', { headers: { cookie } })
54
- return (await TestApp.json(response, Me.schema.User)).tmp_earlyAccess
55
- }
56
-
57
21
  describe('GET /me', () => {
58
22
  test('returns the signed-in user', async () => {
59
23
  const app = createApp()
@@ -219,13 +183,12 @@ describe('sign-in email requirement', () => {
219
183
  })
220
184
 
221
185
  describe('open sign-up', () => {
222
- test('allows new users on any domain with the gate on', async () => {
186
+ test('allows new users on any domain', async () => {
223
187
  const oidc = await createIssuer({ email: 'dev@example.com' })
224
188
  try {
225
189
  const app = TestApp.create({
226
190
  auth: { keys: [TestApp.key] },
227
191
  session: { wallet: { issuer: oidc.url, origin } },
228
- tmp_earlyAccess: true,
229
192
  })
230
193
  const account = privateKeyToAccount(generatePrivateKey())
231
194
  const { cookie, verifyResponse } = await TestApp.signIn(app, account, {
@@ -242,8 +205,8 @@ describe('open sign-up', () => {
242
205
  }
243
206
  })
244
207
 
245
- test('allows new users without an identity token with the gate on', async () => {
246
- const app = createGatedApp()
208
+ test('allows new users without an identity token', async () => {
209
+ const app = createApp()
247
210
  const account = privateKeyToAccount(generatePrivateKey())
248
211
  const { cookie, verifyResponse } = await TestApp.signIn(app, account)
249
212
 
@@ -252,111 +215,6 @@ describe('open sign-up', () => {
252
215
  })
253
216
  })
254
217
 
255
- describe('tmp_earlyAccess', () => {
256
- test('defaults to true while the gate is dormant', async () => {
257
- const app = createApp()
258
- const { cookie } = await session(app)
259
- expect(await tmp_earlyAccess(app, cookie)).toBe(true)
260
- })
261
-
262
- test('matches domain entries case-insensitively', async () => {
263
- const db = TestApp.database()
264
- const app = createGatedApp(db)
265
- await EarlyAccess.add(db, { createdBy: 'test', entry: 'Example.ORG' })
266
- const user = await session(app)
267
- await Users.setEmail(db, user.userId, 'Dev@EXAMPLE.org')
268
-
269
- expect(await tmp_earlyAccess(app, user.cookie)).toBe(true)
270
- })
271
-
272
- test('exact-email entries grant only that address', async () => {
273
- const db = TestApp.database()
274
- const app = createGatedApp(db)
275
- await EarlyAccess.add(db, { createdBy: 'test', entry: 'dev@example.org' })
276
- const granted = await session(app)
277
- await Users.setEmail(db, granted.userId, 'dev@example.org')
278
- const denied = await session(app)
279
- await Users.setEmail(db, denied.userId, 'other@example.org')
280
-
281
- expect(await tmp_earlyAccess(app, granted.cookie)).toBe(true)
282
- expect(await tmp_earlyAccess(app, denied.cookie)).toBe(false)
283
- })
284
-
285
- test('stays false without a matching entry', async () => {
286
- const db = TestApp.database()
287
- const app = createGatedApp(db)
288
- const user = await session(app)
289
- await Users.setEmail(db, user.userId, 'dev@example.net')
290
-
291
- expect(await tmp_earlyAccess(app, user.cookie)).toBe(false)
292
- })
293
-
294
- test('stays false without a verified email', async () => {
295
- const app = createGatedApp()
296
- const { cookie } = await session(app)
297
-
298
- expect(await tmp_earlyAccess(app, cookie)).toBe(false)
299
- })
300
-
301
- test('flips false once the entry is removed', async () => {
302
- const db = TestApp.database()
303
- const app = createGatedApp(db)
304
- await EarlyAccess.add(db, { createdBy: 'test', entry: 'example.org' })
305
- const user = await session(app)
306
- await Users.setEmail(db, user.userId, 'dev@example.org')
307
- expect(await tmp_earlyAccess(app, user.cookie)).toBe(true)
308
-
309
- await EarlyAccess.remove(db, 'example.org')
310
- expect(await tmp_earlyAccess(app, user.cookie)).toBe(false)
311
- })
312
-
313
- test('invitations from early-access inviters grant access until revoked', async () => {
314
- const db = TestApp.database()
315
- const app = createGatedApp(db)
316
- await EarlyAccess.add(db, { createdBy: 'test', entry: 'example.org' })
317
- const inviter = await session(app)
318
- await Users.setEmail(db, inviter.userId, 'owner@example.org')
319
- const created = await app.request('/v1/orgs', json('POST', inviter.cookie, { name: 'Acme' }))
320
- const org = (await created.json()) as { id: string }
321
- const invited = await app.request(
322
- `/v1/orgs/${org.id}/invitations`,
323
- json('POST', inviter.cookie, { email: 'invitee@example.net', role: 'member' }),
324
- )
325
- expect(invited.status).toBe(200)
326
- const invitation = (await invited.json()) as { id: string }
327
-
328
- const invitee = await session(app)
329
- await Users.setEmail(db, invitee.userId, 'invitee@example.net')
330
- expect(await tmp_earlyAccess(app, invitee.cookie)).toBe(true)
331
-
332
- // Revoking the invitation withdraws the grant.
333
- const revoked = await app.request(`/v1/orgs/${org.id}/invitations/${invitation.id}`, {
334
- headers: { cookie: inviter.cookie },
335
- method: 'DELETE',
336
- })
337
- expect(revoked.status).toBe(200)
338
- expect(await tmp_earlyAccess(app, invitee.cookie)).toBe(false)
339
- })
340
-
341
- test('invitations from non-early-access inviters do not grant access', async () => {
342
- const db = TestApp.database()
343
- const app = createGatedApp(db)
344
- const inviter = await session(app)
345
- await Users.setEmail(db, inviter.userId, 'owner@example.net')
346
- const created = await app.request('/v1/orgs', json('POST', inviter.cookie, { name: 'Acme' }))
347
- const org = (await created.json()) as { id: string }
348
- const invited = await app.request(
349
- `/v1/orgs/${org.id}/invitations`,
350
- json('POST', inviter.cookie, { email: 'friend@example.net', role: 'member' }),
351
- )
352
- expect(invited.status).toBe(200)
353
-
354
- const invitee = await session(app)
355
- await Users.setEmail(db, invitee.userId, 'friend@example.net')
356
- expect(await tmp_earlyAccess(app, invitee.cookie)).toBe(false)
357
- })
358
- })
359
-
360
218
  /** Local OIDC issuer standing in for the wallet: the SDK's own provider (discovery, JWKS, token mint). */
361
219
  async function createIssuer(options: { email: string }) {
362
220
  // Extractable: the provider takes key material as JWK strings.
@@ -35,11 +35,6 @@ export namespace schema {
35
35
  z.describe('Opaque user id (`usr_…`).'),
36
36
  z.meta({ examples: ['usr_1a2b3c4d5e6f7g8h9j0k1m2n'] }),
37
37
  ),
38
- // TODO: remove after GA.
39
- tmp_earlyAccess: z.boolean().check(
40
- z.describe('Whether the user has early access; gated features require it while the platform is invite-only.'), // prettier-ignore
41
- z.meta({ examples: [true] }),
42
- ),
43
38
  updatedAt: z
44
39
  .string()
45
40
  .check(
@@ -99,7 +94,6 @@ export function me() {
99
94
  createdAt: user.createdAt,
100
95
  ...(user.email ? { email: user.email } : {}),
101
96
  id: user.id,
102
- tmp_earlyAccess: await Auth.tmp_earlyAccess(c),
103
97
  updatedAt: user.updatedAt,
104
98
  }),
105
99
  200,
@@ -1,8 +1,11 @@
1
1
  import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'
2
2
 
3
3
  import * as TestApp from '../../../../test/App.js'
4
+ import * as EarlyAccess from '../../../db/tables/earlyAccess.js'
5
+ import * as EnabledBillingSources from '../../../db/tables/enabledBillingSources.js'
4
6
  import * as Organizations from '../../../db/tables/organizations.js'
5
7
  import * as Projects from '../../../db/tables/projects.js'
8
+ import * as Users from '../../../db/tables/users.js'
6
9
  import type * as Log from '../../../internal/Log.js'
7
10
  import * as Orgs from './orgs.js'
8
11
 
@@ -196,7 +199,8 @@ describe('GET /orgs', () => {
196
199
 
197
200
  describe('POST /orgs', () => {
198
201
  test('creates a session-owned organization', async () => {
199
- const app = createApp()
202
+ const db = TestApp.database()
203
+ const app = createApp(db)
200
204
  const cookie = await session(app)
201
205
 
202
206
  const response = await app.request('/v1/orgs', json('POST', cookie, { name: 'Acme' }))
@@ -204,10 +208,48 @@ describe('POST /orgs', () => {
204
208
  const body = await TestApp.json(response, Orgs.schema.Organization)
205
209
  expect(body.id.startsWith('org_')).toBe(true)
206
210
  expect(body.name).toBe('Acme')
211
+ expect(await EnabledBillingSources.listByOrg(db, body.id)).toEqual([])
212
+ })
213
+
214
+ test.each([
215
+ { email: 'Dev@Example.NET', entry: 'dev@example.net', kind: 'email' },
216
+ { email: 'Dev@Example.ORG', entry: 'example.org', kind: 'domain' },
217
+ ])('enables Stripe for a session matching an early-access $kind', async ({ email, entry }) => {
218
+ const db = TestApp.database()
219
+ const app = createApp(db)
220
+ await EarlyAccess.add(db, { createdBy: 'test', entry })
221
+ const account = privateKeyToAccount(generatePrivateKey())
222
+ const { cookie } = await TestApp.signIn(app, account)
223
+ const user = await Users.getByAddress(db, account.address)
224
+ if (!user) throw new Error('sign-in did not create a user')
225
+ await Users.setEmail(db, user.id, email)
226
+
227
+ const response = await app.request('/v1/orgs', json('POST', cookie!, { name: 'Acme' }))
228
+ expect(response.status).toBe(200)
229
+ const org = await TestApp.json(response, Orgs.schema.Organization)
230
+ const sources = await EnabledBillingSources.listByOrg(db, org.id)
231
+ expect(sources.map(({ createdBy, source }) => ({ createdBy, source }))).toEqual([
232
+ { createdBy: user.id, source: 'stripe' },
233
+ ])
234
+ })
235
+
236
+ test('does not enable Stripe without an early-access grant', async () => {
237
+ const db = TestApp.database()
238
+ const app = createApp(db)
239
+ const account = privateKeyToAccount(generatePrivateKey())
240
+ const { cookie } = await TestApp.signIn(app, account)
241
+ const user = await Users.getByAddress(db, account.address)
242
+ if (!user) throw new Error('sign-in did not create a user')
243
+ await Users.setEmail(db, user.id, 'dev@example.net')
244
+
245
+ const response = await app.request('/v1/orgs', json('POST', cookie!, { name: 'Acme' }))
246
+ const org = await TestApp.json(response, Orgs.schema.Organization)
247
+ expect(await EnabledBillingSources.listByOrg(db, org.id)).toEqual([])
207
248
  })
208
249
 
209
250
  test('creates a memberless org as the super admin', async () => {
210
- const app = createApp()
251
+ const db = TestApp.database()
252
+ const app = createApp(db)
211
253
  const response = await app.request('/v1/orgs', {
212
254
  body: JSON.stringify({ name: 'Ops' }),
213
255
  headers: { 'content-type': 'application/json', 'tempo-api-key': secret },
@@ -216,6 +258,7 @@ describe('POST /orgs', () => {
216
258
  expect(response.status).toBe(200)
217
259
  const org = await TestApp.json(response, Orgs.schema.Organization)
218
260
  expect(org.name).toBe('Ops')
261
+ expect(await EnabledBillingSources.listByOrg(db, org.id)).toEqual([])
219
262
 
220
263
  // Memberless: invisible to sessions until an owner is seated.
221
264
  const cookie = await session(app)
@@ -5,6 +5,7 @@ import * as ApiKeys from '../../../ApiKeys.js'
5
5
  import type * as App from '../../../App.js'
6
6
  import * as Auth from '../../../internal/Auth.js'
7
7
  import * as Db from '../../../db/Db.js'
8
+ import * as EarlyAccess from '../../../db/tables/earlyAccess.js'
8
9
  import * as Memberships from '../../../db/tables/memberships.js'
9
10
  import * as OpenApi from '../../../internal/OpenApi.js'
10
11
  import * as Organizations from '../../../db/tables/organizations.js'
@@ -189,6 +190,11 @@ export function orgs() {
189
190
  ? await Organizations.create(db, { createdBy: 'super_admin', name: body.name })
190
191
  : {
191
192
  ...(await Organizations.createOwned(db, {
193
+ enabledBillingSources:
194
+ principal.email &&
195
+ (await EarlyAccess.matches(db, principal.email.trim().toLowerCase()))
196
+ ? ['stripe']
197
+ : [],
192
198
  name: body.name,
193
199
  userId: principal.id,
194
200
  })),
@@ -21,6 +21,7 @@ describe('scopes', () => {
21
21
  "indexer:query",
22
22
  "management:read",
23
23
  "management:write",
24
+ "mpp:write",
24
25
  "rpc-relay:read",
25
26
  "rpc-relay:sponsor",
26
27
  "webhooks:read",
@@ -0,0 +1,86 @@
1
+ import { Hono } from 'hono'
2
+ import type * as z from 'zod/mini'
3
+
4
+ import type * as App from '../../App.js'
5
+ import * as Auth from '../../internal/Auth.js'
6
+ import * as OpenApi from '../../internal/OpenApi.js'
7
+ import * as Metadata from '../metadata.js'
8
+ import * as Mpp from './Mpp.js'
9
+
10
+ /** Mounts MPP credential verification and broadcast routes. */
11
+ export function mpp() {
12
+ const app = new Hono<App.Environment>()
13
+ .post(
14
+ '/v1/mpp/verify',
15
+ Auth.policy({ apiKey: { scopes: ['mpp:write'] }, mpp: false, public: false }),
16
+ OpenApi.validate('json', Mpp.schema.RelayInput, bodyValidation),
17
+ OpenApi.describeRoute({
18
+ operationId: 'verifyMppCredential',
19
+ responses: responses({
20
+ description: 'The MPP credential validation result.',
21
+ schema: Mpp.schema.VerifyResponse,
22
+ }),
23
+ summary: 'Verify MPP credential',
24
+ tags: ['MPP Relay'],
25
+ }),
26
+ (c) => {
27
+ if (Auth.narrowAccess) return Auth.accessError(c)
28
+ if (OpenApi.narrowValidation) return OpenApi.validationError(c, bodyValidation)
29
+ return Mpp.unavailable<Mpp.VerifyResponse>(c)
30
+ },
31
+ )
32
+ .post(
33
+ '/v1/mpp/broadcast',
34
+ Auth.policy({ apiKey: { scopes: ['mpp:write'] }, mpp: false, public: false }),
35
+ OpenApi.validate('json', Mpp.schema.RelayInput, bodyValidation),
36
+ OpenApi.describeRoute({
37
+ operationId: 'broadcastMppCredential',
38
+ responses: responses({
39
+ description: 'The MPP credential broadcast result.',
40
+ schema: Mpp.schema.BroadcastResponse,
41
+ }),
42
+ summary: 'Broadcast MPP credential',
43
+ tags: ['MPP Relay'],
44
+ }),
45
+ (c) => {
46
+ if (Auth.narrowAccess) return Auth.accessError(c)
47
+ if (OpenApi.narrowValidation) return OpenApi.validationError(c, bodyValidation)
48
+ return Mpp.unavailable<Mpp.BroadcastResponse>(c)
49
+ },
50
+ )
51
+ return Metadata.attach(app, openapi)
52
+ }
53
+
54
+ const bodyValidation = {
55
+ code: 'body_invalid',
56
+ message: 'Check the MPP credential and challenge, then try again.',
57
+ } as const
58
+
59
+ function responses<schema extends z.ZodMiniType>(options: { description: string; schema: schema }) {
60
+ return {
61
+ ...OpenApi.responses({
62
+ errors: { 403: 'The API key does not grant MPP relay access.' },
63
+ success: options,
64
+ }),
65
+ 503: {
66
+ content: { 'application/json': { schema: OpenApi.resolver(Mpp.schema.Unavailable) } },
67
+ description: 'MPP relay settlement is temporarily unavailable.',
68
+ headers: {
69
+ 'Retry-After': {
70
+ description: 'How many seconds to wait before retrying the relay.',
71
+ schema: { type: 'integer' as const },
72
+ },
73
+ },
74
+ },
75
+ }
76
+ }
77
+
78
+ const openapi = {
79
+ tags: [
80
+ {
81
+ name: 'MPP Relay',
82
+ description: 'Validate and broadcast MPP payment credentials through Tempo.',
83
+ },
84
+ ],
85
+ 'x-tagGroups': [{ name: 'Payments', tags: ['MPP Relay'] }],
86
+ } satisfies App.create.Metadata