tapimo 0.6.0 → 0.7.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 (250) 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 +95 -87
  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 +5016 -4985
  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 +113 -113
  48. package/dist/apps/data/routes/fee-amm.d.ts +105 -91
  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 +5 -5
  57. package/dist/apps/data/routes/receipts.d.ts +6 -6
  58. package/dist/apps/data/routes/rpc.d.ts +31 -31
  59. package/dist/apps/data/routes/tokens.d.ts +2 -2
  60. package/dist/apps/data/routes/tokens.js +1 -1
  61. package/dist/apps/data/routes/transactions.d.ts +4 -4
  62. package/dist/apps/data/routes/transfers.d.ts +99 -88
  63. package/dist/apps/data/routes/transfers.d.ts.map +1 -1
  64. package/dist/apps/data/routes/transfers.js +226 -141
  65. package/dist/apps/data/routes/transfers.js.map +1 -1
  66. package/dist/apps/data/routes/valuation.d.ts +34 -34
  67. package/dist/apps/data/routes/valuation.js +3 -3
  68. package/dist/apps/data/routes/valuation.js.map +1 -1
  69. package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
  70. package/dist/apps/data/routes/webhooks.d.ts +366 -366
  71. package/dist/apps/index.d.ts +1 -0
  72. package/dist/apps/index.d.ts.map +1 -1
  73. package/dist/apps/index.js +1 -0
  74. package/dist/apps/index.js.map +1 -1
  75. package/dist/apps/management/App.d.ts +460 -523
  76. package/dist/apps/management/App.d.ts.map +1 -1
  77. package/dist/apps/management/routes/api-keys.d.ts +7 -23
  78. package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
  79. package/dist/apps/management/routes/api-keys.js +20 -8
  80. package/dist/apps/management/routes/api-keys.js.map +1 -1
  81. package/dist/apps/management/routes/billing.d.ts +106 -151
  82. package/dist/apps/management/routes/billing.d.ts.map +1 -1
  83. package/dist/apps/management/routes/billing.js +28 -28
  84. package/dist/apps/management/routes/billing.js.map +1 -1
  85. package/dist/apps/management/routes/faucet.d.ts +40 -26
  86. package/dist/apps/management/routes/faucet.d.ts.map +1 -1
  87. package/dist/apps/management/routes/faucet.js +33 -18
  88. package/dist/apps/management/routes/faucet.js.map +1 -1
  89. package/dist/apps/management/routes/invitations.d.ts +131 -131
  90. package/dist/apps/management/routes/invitations.d.ts.map +1 -1
  91. package/dist/apps/management/routes/invitations.js +0 -3
  92. package/dist/apps/management/routes/invitations.js.map +1 -1
  93. package/dist/apps/management/routes/me.d.ts +6 -8
  94. package/dist/apps/management/routes/me.d.ts.map +1 -1
  95. package/dist/apps/management/routes/me.js +0 -4
  96. package/dist/apps/management/routes/me.js.map +1 -1
  97. package/dist/apps/management/routes/members.d.ts +45 -45
  98. package/dist/apps/management/routes/orgs.d.ts +94 -94
  99. package/dist/apps/management/routes/orgs.d.ts.map +1 -1
  100. package/dist/apps/management/routes/orgs.js +5 -0
  101. package/dist/apps/management/routes/orgs.js.map +1 -1
  102. package/dist/apps/management/routes/usage.d.ts +32 -32
  103. package/dist/apps/management/routes/verified-token-requests.d.ts +94 -94
  104. package/dist/apps/management/routes/webhooks.d.ts +1 -1
  105. package/dist/apps/mpp/App.d.ts +363 -0
  106. package/dist/apps/mpp/App.d.ts.map +1 -0
  107. package/dist/apps/mpp/App.js +72 -0
  108. package/dist/apps/mpp/App.js.map +1 -0
  109. package/dist/apps/mpp/Mpp.d.ts +178 -0
  110. package/dist/apps/mpp/Mpp.d.ts.map +1 -0
  111. package/dist/apps/mpp/Mpp.js +183 -0
  112. package/dist/apps/mpp/Mpp.js.map +1 -0
  113. package/dist/apps/mpp/index.d.ts +3 -0
  114. package/dist/apps/mpp/index.d.ts.map +1 -0
  115. package/dist/apps/mpp/index.js +3 -0
  116. package/dist/apps/mpp/index.js.map +1 -0
  117. package/dist/apps/relay/App.d.ts.map +1 -1
  118. package/dist/apps/relay/App.js +56 -9
  119. package/dist/apps/relay/App.js.map +1 -1
  120. package/dist/db/Db.d.ts +2 -0
  121. package/dist/db/Db.d.ts.map +1 -1
  122. package/dist/db/Db.js +27 -0
  123. package/dist/db/Db.js.map +1 -1
  124. package/dist/db/Schema.d.ts +12 -0
  125. package/dist/db/Schema.d.ts.map +1 -1
  126. package/dist/db/Schema.js +11 -0
  127. package/dist/db/Schema.js.map +1 -1
  128. package/dist/db/tables/enabledBillingSources.d.ts +73 -0
  129. package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
  130. package/dist/db/tables/enabledBillingSources.js +69 -0
  131. package/dist/db/tables/enabledBillingSources.js.map +1 -0
  132. package/dist/db/tables/invitations.d.ts +1 -12
  133. package/dist/db/tables/invitations.d.ts.map +1 -1
  134. package/dist/db/tables/invitations.js +3 -23
  135. package/dist/db/tables/invitations.js.map +1 -1
  136. package/dist/db/tables/organizations.d.ts +5 -2
  137. package/dist/db/tables/organizations.d.ts.map +1 -1
  138. package/dist/db/tables/organizations.js +13 -2
  139. package/dist/db/tables/organizations.js.map +1 -1
  140. package/dist/handlers/internal/utils.d.ts +1 -16
  141. package/dist/handlers/internal/utils.d.ts.map +1 -1
  142. package/dist/handlers/internal/utils.js +17 -6
  143. package/dist/handlers/internal/utils.js.map +1 -1
  144. package/dist/handlers/relay.d.ts.map +1 -1
  145. package/dist/handlers/relay.js +37 -5
  146. package/dist/handlers/relay.js.map +1 -1
  147. package/dist/internal/Auth.d.ts +0 -32
  148. package/dist/internal/Auth.d.ts.map +1 -1
  149. package/dist/internal/Auth.js +3 -51
  150. package/dist/internal/Auth.js.map +1 -1
  151. package/dist/internal/FundingQuotes.d.ts +78 -32
  152. package/dist/internal/FundingQuotes.d.ts.map +1 -1
  153. package/dist/internal/FundingQuotes.js +45 -8
  154. package/dist/internal/FundingQuotes.js.map +1 -1
  155. package/dist/internal/Log.d.ts +18 -4
  156. package/dist/internal/Log.d.ts.map +1 -1
  157. package/dist/internal/Log.js +50 -3
  158. package/dist/internal/Log.js.map +1 -1
  159. package/dist/internal/MetricSink.d.ts.map +1 -1
  160. package/dist/internal/MetricSink.js +15 -0
  161. package/dist/internal/MetricSink.js.map +1 -1
  162. package/dist/internal/Response.d.ts +2 -2
  163. package/dist/internal/Response.d.ts.map +1 -1
  164. package/dist/internal/Response.js +12 -4
  165. package/dist/internal/Response.js.map +1 -1
  166. package/dist/internal/Viem.d.ts +1 -1
  167. package/package.json +7 -2
  168. package/src/App.test.ts +9 -4
  169. package/src/App.ts +14 -2
  170. package/src/Client.test-d.ts +109 -28
  171. package/src/Scope.ts +5 -0
  172. package/src/admin/App.test.ts +4 -0
  173. package/src/admin/apps/early-access.ts +6 -5
  174. package/src/admin/apps/organizations.test.ts +86 -2
  175. package/src/admin/apps/organizations.ts +155 -31
  176. package/src/admin/apps/scopes.test.ts +5 -0
  177. package/src/admin/ui/src/lib/organizations.test.ts +81 -1
  178. package/src/admin/ui/src/lib/organizations.ts +92 -0
  179. package/src/admin/ui/src/routes/api-keys.tsx +1 -1
  180. package/src/admin/ui/src/routes/early-access.tsx +8 -3
  181. package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
  182. package/src/admin/ui.generated.ts +95 -109
  183. package/src/analytics/Analytics.test.ts +83 -0
  184. package/src/analytics/Analytics.ts +20 -0
  185. package/src/analytics/tables/requestEvents.ts +60 -7
  186. package/src/apps/composition.test.ts +11 -2
  187. package/src/apps/data/App.test.ts +1 -1
  188. package/src/apps/data/FundingQuoteProviders.test.ts +0 -5
  189. package/src/apps/data/FundingQuoteProviders.ts +2 -29
  190. package/src/apps/data/FxOracle.test.ts +2 -2
  191. package/src/apps/data/FxOracle.ts +2 -2
  192. package/src/apps/data/routes/activities.test.ts +6 -2
  193. package/src/apps/data/routes/activities.ts +39 -1
  194. package/src/apps/data/routes/balances.test.ts +6 -2
  195. package/src/apps/data/routes/exchanges.test.ts +7 -3
  196. package/src/apps/data/routes/fee-amm.test.ts +47 -23
  197. package/src/apps/data/routes/fee-amm.ts +98 -119
  198. package/src/apps/data/routes/funding.test.ts +29 -8
  199. package/src/apps/data/routes/funding.ts +42 -48
  200. package/src/apps/data/routes/receipts.test.ts +6 -2
  201. package/src/apps/data/routes/tokens.ts +1 -1
  202. package/src/apps/data/routes/transfers.test.ts +203 -35
  203. package/src/apps/data/routes/transfers.ts +294 -172
  204. package/src/apps/data/routes/valuation.test.ts +18 -3
  205. package/src/apps/data/routes/valuation.ts +4 -4
  206. package/src/apps/index.ts +1 -0
  207. package/src/apps/management/routes/api-keys.test.ts +2 -75
  208. package/src/apps/management/routes/api-keys.ts +30 -7
  209. package/src/apps/management/routes/billing.test.ts +106 -85
  210. package/src/apps/management/routes/billing.ts +32 -21
  211. package/src/apps/management/routes/faucet.test.ts +11 -3
  212. package/src/apps/management/routes/faucet.ts +46 -30
  213. package/src/apps/management/routes/invitations.test.ts +0 -172
  214. package/src/apps/management/routes/invitations.ts +0 -3
  215. package/src/apps/management/routes/me.test.ts +3 -145
  216. package/src/apps/management/routes/me.ts +0 -6
  217. package/src/apps/management/routes/orgs.test.ts +45 -2
  218. package/src/apps/management/routes/orgs.ts +6 -0
  219. package/src/apps/management/routes/scopes.test.ts +1 -0
  220. package/src/apps/mpp/App.ts +86 -0
  221. package/src/apps/mpp/Mpp.test.ts +193 -0
  222. package/src/apps/mpp/Mpp.ts +353 -0
  223. package/src/apps/mpp/index.ts +2 -0
  224. package/src/apps/mpp/internal/Adapter.ts +11 -0
  225. package/src/apps/mpp/internal/Error.ts +9 -0
  226. package/src/apps/mpp/internal/Idempotency.ts +7 -0
  227. package/src/apps/mpp/internal/Screening.ts +10 -0
  228. package/src/apps/relay/App.test.ts +88 -6
  229. package/src/apps/relay/App.ts +61 -9
  230. package/src/db/Db.test.ts +2 -2
  231. package/src/db/Db.ts +30 -0
  232. package/src/db/Schema.ts +15 -0
  233. package/src/db/tables/enabledBillingSources.test.ts +303 -0
  234. package/src/db/tables/enabledBillingSources.ts +118 -0
  235. package/src/db/tables/invitations.ts +4 -26
  236. package/src/db/tables/organizations.test.ts +103 -0
  237. package/src/db/tables/organizations.ts +18 -2
  238. package/src/handlers/internal/utils.ts +22 -6
  239. package/src/handlers/relay.errors.test.ts +153 -0
  240. package/src/handlers/relay.ts +42 -7
  241. package/src/internal/Auth.test.ts +3 -3
  242. package/src/internal/Auth.ts +3 -63
  243. package/src/internal/FundingQuotes.test.ts +18 -5
  244. package/src/internal/FundingQuotes.ts +46 -11
  245. package/src/internal/Log.test.ts +91 -0
  246. package/src/internal/Log.ts +69 -5
  247. package/src/internal/MetricSink.test.ts +55 -0
  248. package/src/internal/MetricSink.ts +14 -0
  249. package/src/internal/Response.test.ts +28 -0
  250. package/src/internal/Response.ts +12 -4
@@ -6,6 +6,7 @@ import * as ApiKeys from '../../../ApiKeys.js'
6
6
  import * as Auth from '../../../internal/Auth.js'
7
7
  import * as BillingSettings from '../../../db/tables/billingSettings.js'
8
8
  import * as Db from '../../../db/Db.js'
9
+ import * as EnabledBillingSources from '../../../db/tables/enabledBillingSources.js'
9
10
  import * as OpenApi from '../../../internal/OpenApi.js'
10
11
  import * as Response from '../../../internal/Response.js'
11
12
  import * as Schema from '../../../internal/Schema.js'
@@ -113,6 +114,12 @@ export namespace schema {
113
114
  /** An organization's billing state. */
114
115
  export const Billing = Schema.describe(
115
116
  z.object({
117
+ enabledSources: z
118
+ .array(z.enum(EnabledBillingSources.sources))
119
+ .check(
120
+ z.describe('Billing sources this organization may set up.'),
121
+ z.meta({ examples: [['stripe']] }),
122
+ ),
116
123
  spend: z
117
124
  .optional(
118
125
  z.object({
@@ -234,7 +241,7 @@ export namespace schema {
234
241
  }
235
242
 
236
243
  /**
237
- * Mounts billing routes. Reads require membership or scoped API key access; organizations without Stripe state return `none`.
244
+ * Mounts billing routes. Reads require membership or scoped API key access; organizations without provider state return `none`.
238
245
  */
239
246
  export function billing() {
240
247
  return new Hono<Environment>()
@@ -255,7 +262,6 @@ export function billing() {
255
262
  responses: OpenApi.responses({
256
263
  errors: {
257
264
  404: 'No accessible organization was found for this id.',
258
- 501: 'Billing is not configured on this deployment.',
259
265
  },
260
266
  success: { description: "The organization's billing state.", schema: schema.Billing },
261
267
  }),
@@ -276,10 +282,7 @@ export function billing() {
276
282
  message: 'Check the query parameters and try again.',
277
283
  })
278
284
 
279
- const billing = c.get('billing')
280
- if (!billing) return billingUnconfigured(c)
281
285
  const environment = c.req.valid('query').environment
282
- if (!stripeFor(billing, environment)) return billingUnconfigured(c)
283
286
 
284
287
  try {
285
288
  return c.json(
@@ -313,7 +316,6 @@ export function billing() {
313
316
  errors: {
314
317
  403: 'Requires the owner role.',
315
318
  404: 'No accessible organization was found for this id.',
316
- 501: 'Billing is not configured on this deployment.',
317
319
  },
318
320
  success: {
319
321
  description: "The organization's updated billing state.",
@@ -342,10 +344,7 @@ export function billing() {
342
344
  message: 'Check the request body and try again.',
343
345
  })
344
346
 
345
- const billing = c.get('billing')
346
- if (!billing) return billingUnconfigured(c)
347
347
  const environment = c.req.valid('query').environment
348
- if (!stripeFor(billing, environment)) return billingUnconfigured(c)
349
348
 
350
349
  const body = c.req.valid('json')
351
350
  const org = Auth.org(c)
@@ -380,7 +379,7 @@ export function billing() {
380
379
  )
381
380
  .post(
382
381
  '/v1/orgs/:orgId{org_[A-Za-z0-9_-]+}/billing/stripe/checkout',
383
- Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true, tmp_earlyAccess: true }), // prettier-ignore
382
+ Auth.policy({ apiKey: { scopes: ['management:write'] }, session: true }),
384
383
  Auth.ensureOrg({ role: 'owner' }),
385
384
  OpenApi.validate('param', schema.Params, {
386
385
  code: 'param_invalid',
@@ -395,8 +394,8 @@ export function billing() {
395
394
  responses: OpenApi.responses({
396
395
  errors: {
397
396
  403: {
398
- codes: ['api_key_forbidden', 'early_access_required', 'forbidden'],
399
- description: 'Requires the owner role and early access.',
397
+ codes: ['api_key_forbidden', 'billing_source_disabled', 'forbidden'],
398
+ description: 'Requires the owner role and Stripe billing enabled.',
400
399
  },
401
400
  404: 'No accessible organization was found for this id.',
402
401
  501: 'Billing is not configured on this deployment.',
@@ -413,7 +412,6 @@ export function billing() {
413
412
  if (Auth.narrowAccess) return Auth.accessError(c)
414
413
  if (Auth.narrowScope) return Auth.ensureOrgError(c)
415
414
  if (Auth.narrowOrgRole) return Auth.ensureOrgRoleError(c)
416
- if (Auth.tmp_narrowEarlyAccess) return Auth.tmp_ensureEarlyAccessError(c)
417
415
  if (OpenApi.narrowValidation)
418
416
  return OpenApi.validationError(c, {
419
417
  code: 'param_invalid',
@@ -425,16 +423,19 @@ export function billing() {
425
423
  message: 'Check the query parameters and try again.',
426
424
  })
427
425
 
428
- const billing = c.get('billing')
429
- if (!billing) return billingUnconfigured(c)
430
- const environment = c.req.valid('query').environment
431
- const source = stripeFor(billing, environment)
432
- if (!source) return billingUnconfigured(c)
433
-
434
- const { client } = source
435
426
  const org = Auth.org(c)
436
427
  const db = Db.get(c.get('db'))
437
428
  try {
429
+ if (!(await EnabledBillingSources.isEnabled(db, { orgId: org.id, source: 'stripe' })))
430
+ return billingSourceDisabled(c)
431
+
432
+ const billing = c.get('billing')
433
+ if (!billing) return billingUnconfigured(c)
434
+ const environment = c.req.valid('query').environment
435
+ const source = stripeFor(billing, environment)
436
+ if (!source) return billingUnconfigured(c)
437
+
438
+ const { client } = source
438
439
  const existing = await StripeCustomers.get(db, org.id, environment)
439
440
  const customerId = existing
440
441
  ? existing.stripeCustomerId
@@ -885,7 +886,8 @@ async function billingBody(
885
886
  environment: ResourceEnvironment,
886
887
  ) {
887
888
  const db = Db.get(c.get('db'))
888
- const [record, settings] = await Promise.all([
889
+ const [enabledSources, record, settings] = await Promise.all([
890
+ EnabledBillingSources.listByOrg(db, orgId),
889
891
  StripeCustomers.get(db, orgId, environment),
890
892
  BillingSettings.get(db, orgId, environment),
891
893
  ])
@@ -900,6 +902,7 @@ async function billingBody(
900
902
  since: core_Billing.periodStart(period),
901
903
  })
902
904
  return {
905
+ enabledSources: enabledSources.map((record) => record.source),
903
906
  spend: { amount: core_Billing.fromBaseUnits(spend), currency, period },
904
907
  ...(settings?.spendLimit != null
905
908
  ? { spendLimit: { amount: settings.spendLimit, currency, period } }
@@ -959,6 +962,14 @@ function billingNotFound(c: Context<Environment>) {
959
962
  })
960
963
  }
961
964
 
965
+ function billingSourceDisabled(c: Context<Environment>) {
966
+ return Response.error(c, {
967
+ code: 'billing_source_disabled',
968
+ message: 'Stripe billing is not enabled for this organization',
969
+ status: 403,
970
+ })
971
+ }
972
+
962
973
  function billingUnconfigured(c: Context<Environment>) {
963
974
  return Response.error(c, {
964
975
  code: 'billing_unconfigured',
@@ -43,9 +43,17 @@ describe('faucet', () => {
43
43
  expect(funded.status).toBe(200)
44
44
  const result = await TestApp.json(funded, Faucet.schema.FundResponse)
45
45
  expect(result.tokens.length).toBeGreaterThan(0)
46
- expect(result.tokens.every((token) => token.symbol.length > 0)).toBe(true)
47
- expect(result.tokens.every((token) => BigInt(token.amount) > 0n)).toBe(true)
48
- expect(result.transactionHashes).toEqual(result.tokens.map((token) => token.transactionHash))
46
+ expect(
47
+ result.tokens.every(
48
+ (entry) =>
49
+ entry.token.symbol.length > 0 &&
50
+ BigInt(entry.amount.baseUnits) > 0n &&
51
+ entry.amount.currency === entry.token.currency &&
52
+ entry.amount.decimals === entry.token.decimals &&
53
+ /^\d+(\.\d+)?$/.test(entry.amount.formatted),
54
+ ),
55
+ ).toBe(true)
56
+ expect(result.transactionHashes).toEqual(result.tokens.map((entry) => entry.transactionHash))
49
57
 
50
58
  const repeated = await app.request(`/v1/orgs/${org.id}/faucet`, {
51
59
  body: JSON.stringify({ address: recipient }),
@@ -6,6 +6,7 @@ import * as OpenApi from '../../../internal/OpenApi.js'
6
6
  import * as RateLimit from '../../../internal/RateLimit.js'
7
7
  import * as Response from '../../../internal/Response.js'
8
8
  import * as Schema from '../../../internal/Schema.js'
9
+ import * as Value from '../../../internal/Value.js'
9
10
  import * as Viem from '../../../internal/Viem.js'
10
11
  import type { Environment } from '../App.js'
11
12
 
@@ -14,6 +15,37 @@ const userRateLimit = { perMinute: 3 } satisfies RateLimit.Limit
14
15
 
15
16
  /** OpenAPI schemas owned by the sandbox faucet. */
16
17
  export namespace schema {
18
+ const Token = Schema.describe(
19
+ z.object({
20
+ address: Schema.TokenAddress.check(z.describe('The test token contract that minted funds.')),
21
+ currency: z
22
+ .string()
23
+ .check(z.describe('The token’s currency label.'), z.meta({ examples: ['USD'] })),
24
+ decimals: z
25
+ .number()
26
+ .check(
27
+ z.int(),
28
+ z.nonnegative(),
29
+ z.describe('The decimal places used by the token.'),
30
+ z.meta({ examples: [6] }),
31
+ ),
32
+ name: z
33
+ .string()
34
+ .check(z.describe('The token’s human-readable name.'), z.meta({ examples: ['Test USD'] })),
35
+ symbol: z.string().check(z.describe('The token’s symbol.'), z.meta({ examples: ['TUSD'] })),
36
+ }),
37
+ 'A test token minted by the faucet.',
38
+ )
39
+
40
+ const FundedToken = Schema.describe(
41
+ z.object({
42
+ amount: Schema.TokenAmount.check(z.describe('Amount minted by the faucet.')),
43
+ token: Token,
44
+ transactionHash: Schema.Hash.check(z.describe('The transaction that minted this token.')),
45
+ }),
46
+ 'One token amount minted by the faucet.',
47
+ )
48
+
17
49
  /** Faucet request body. */
18
50
  export const FundBody = Schema.describe(
19
51
  z.object({ address: Schema.Address }),
@@ -24,29 +56,7 @@ export namespace schema {
24
56
  export const FundResponse = Schema.describe(
25
57
  z.object({
26
58
  tokens: z
27
- .array(
28
- z.object({
29
- address: Schema.TokenAddress.check(
30
- z.describe('The test token contract that minted funds.'),
31
- ),
32
- amount: Schema.DecimalString.check(
33
- z.describe('The minted amount in the token’s smallest units.'),
34
- ),
35
- currency: z.string().check(z.describe('The token’s currency label.')),
36
- decimals: z
37
- .number()
38
- .check(
39
- z.int(),
40
- z.nonnegative(),
41
- z.describe('The number of decimal places the token uses.'),
42
- ),
43
- name: z.string().check(z.describe('The token’s human-readable name.')),
44
- symbol: z.string().check(z.describe('The token’s symbol.')),
45
- transactionHash: Schema.Hash.check(
46
- z.describe('The transaction that minted this token.'),
47
- ),
48
- }),
49
- )
59
+ .array(FundedToken)
50
60
  .check(z.describe('The configured test tokens minted to the account.')),
51
61
  transactionHashes: z
52
62
  .array(Schema.Hash)
@@ -124,17 +134,23 @@ export function faucet() {
124
134
  const event = client.token.mint.extractEvent(receipt.logs)
125
135
  const metadata = await client.token.getMetadata({ token: event.address })
126
136
  return {
127
- address: event.address,
128
- amount: event.args.amount.toString(),
129
- currency: metadata.currency,
130
- decimals: metadata.decimals,
131
- name: metadata.name,
132
- symbol: metadata.symbol,
137
+ amount: Value.tokenAmount({
138
+ baseUnits: event.args.amount,
139
+ currency: metadata.currency,
140
+ decimals: metadata.decimals,
141
+ }),
142
+ token: {
143
+ address: event.address,
144
+ currency: metadata.currency,
145
+ decimals: metadata.decimals,
146
+ name: metadata.name,
147
+ symbol: metadata.symbol,
148
+ },
133
149
  transactionHash: receipt.transactionHash,
134
150
  }
135
151
  }),
136
152
  )
137
- const transactionHashes = tokens.map((token) => token.transactionHash)
153
+ const transactionHashes = tokens.map((entry) => entry.transactionHash)
138
154
  return c.json(Response.validated(schema.FundResponse, { tokens, transactionHashes }), 200)
139
155
  } catch (cause) {
140
156
  return Response.upstream(c, cause)
@@ -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,