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.
- package/dist/App.d.ts +8641 -8263
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +9 -1
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +4 -0
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +5 -0
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +112 -11
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +2 -1
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/early-access.d.ts +2 -2
- package/dist/admin/apps/early-access.d.ts.map +1 -1
- package/dist/admin/apps/early-access.js +5 -5
- package/dist/admin/apps/early-access.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +124 -1
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/organizations.js +97 -6
- package/dist/admin/apps/organizations.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +95 -87
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +20 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +18 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +36 -7
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +5016 -4985
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.js +2 -15
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +1 -1
- package/dist/apps/data/FxOracle.d.ts.map +1 -1
- package/dist/apps/data/FxOracle.js +1 -1
- package/dist/apps/data/FxOracle.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +27 -27
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +21 -1
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +58 -58
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/coingecko.d.ts +176 -176
- package/dist/apps/data/routes/exchanges.d.ts +113 -113
- package/dist/apps/data/routes/fee-amm.d.ts +105 -91
- package/dist/apps/data/routes/fee-amm.d.ts.map +1 -1
- package/dist/apps/data/routes/fee-amm.js +76 -91
- package/dist/apps/data/routes/fee-amm.js.map +1 -1
- package/dist/apps/data/routes/funding.d.ts +72 -34
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +34 -26
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/indexer.d.ts +5 -5
- package/dist/apps/data/routes/receipts.d.ts +6 -6
- package/dist/apps/data/routes/rpc.d.ts +31 -31
- package/dist/apps/data/routes/tokens.d.ts +2 -2
- package/dist/apps/data/routes/tokens.js +1 -1
- package/dist/apps/data/routes/transactions.d.ts +4 -4
- package/dist/apps/data/routes/transfers.d.ts +99 -88
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +226 -141
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +34 -34
- package/dist/apps/data/routes/valuation.js +3 -3
- package/dist/apps/data/routes/valuation.js.map +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/data/routes/webhooks.d.ts +366 -366
- package/dist/apps/index.d.ts +1 -0
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +1 -0
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/management/App.d.ts +460 -523
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +7 -23
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +20 -8
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +106 -151
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +28 -28
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +40 -26
- package/dist/apps/management/routes/faucet.d.ts.map +1 -1
- package/dist/apps/management/routes/faucet.js +33 -18
- package/dist/apps/management/routes/faucet.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +131 -131
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +0 -3
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +6 -8
- package/dist/apps/management/routes/me.d.ts.map +1 -1
- package/dist/apps/management/routes/me.js +0 -4
- package/dist/apps/management/routes/me.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +45 -45
- package/dist/apps/management/routes/orgs.d.ts +94 -94
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +5 -0
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +32 -32
- package/dist/apps/management/routes/verified-token-requests.d.ts +94 -94
- package/dist/apps/management/routes/webhooks.d.ts +1 -1
- package/dist/apps/mpp/App.d.ts +363 -0
- package/dist/apps/mpp/App.d.ts.map +1 -0
- package/dist/apps/mpp/App.js +72 -0
- package/dist/apps/mpp/App.js.map +1 -0
- package/dist/apps/mpp/Mpp.d.ts +178 -0
- package/dist/apps/mpp/Mpp.d.ts.map +1 -0
- package/dist/apps/mpp/Mpp.js +183 -0
- package/dist/apps/mpp/Mpp.js.map +1 -0
- package/dist/apps/mpp/index.d.ts +3 -0
- package/dist/apps/mpp/index.d.ts.map +1 -0
- package/dist/apps/mpp/index.js +3 -0
- package/dist/apps/mpp/index.js.map +1 -0
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +56 -9
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/db/Db.d.ts +2 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +27 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +12 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +11 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/enabledBillingSources.d.ts +73 -0
- package/dist/db/tables/enabledBillingSources.d.ts.map +1 -0
- package/dist/db/tables/enabledBillingSources.js +69 -0
- package/dist/db/tables/enabledBillingSources.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +1 -12
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/invitations.js +3 -23
- package/dist/db/tables/invitations.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +5 -2
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +13 -2
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/handlers/internal/utils.d.ts +1 -16
- package/dist/handlers/internal/utils.d.ts.map +1 -1
- package/dist/handlers/internal/utils.js +17 -6
- package/dist/handlers/internal/utils.js.map +1 -1
- package/dist/handlers/relay.d.ts.map +1 -1
- package/dist/handlers/relay.js +37 -5
- package/dist/handlers/relay.js.map +1 -1
- package/dist/internal/Auth.d.ts +0 -32
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +3 -51
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/FundingQuotes.d.ts +78 -32
- package/dist/internal/FundingQuotes.d.ts.map +1 -1
- package/dist/internal/FundingQuotes.js +45 -8
- package/dist/internal/FundingQuotes.js.map +1 -1
- package/dist/internal/Log.d.ts +18 -4
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +50 -3
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +15 -0
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/Response.d.ts +2 -2
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Response.js +12 -4
- package/dist/internal/Response.js.map +1 -1
- package/dist/internal/Viem.d.ts +1 -1
- package/package.json +7 -2
- package/src/App.test.ts +9 -4
- package/src/App.ts +14 -2
- package/src/Client.test-d.ts +109 -28
- package/src/Scope.ts +5 -0
- package/src/admin/App.test.ts +4 -0
- package/src/admin/apps/early-access.ts +6 -5
- package/src/admin/apps/organizations.test.ts +86 -2
- package/src/admin/apps/organizations.ts +155 -31
- package/src/admin/apps/scopes.test.ts +5 -0
- package/src/admin/ui/src/lib/organizations.test.ts +81 -1
- package/src/admin/ui/src/lib/organizations.ts +92 -0
- package/src/admin/ui/src/routes/api-keys.tsx +1 -1
- package/src/admin/ui/src/routes/early-access.tsx +8 -3
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
- package/src/admin/ui.generated.ts +95 -109
- package/src/analytics/Analytics.test.ts +83 -0
- package/src/analytics/Analytics.ts +20 -0
- package/src/analytics/tables/requestEvents.ts +60 -7
- package/src/apps/composition.test.ts +11 -2
- package/src/apps/data/App.test.ts +1 -1
- package/src/apps/data/FundingQuoteProviders.test.ts +0 -5
- package/src/apps/data/FundingQuoteProviders.ts +2 -29
- package/src/apps/data/FxOracle.test.ts +2 -2
- package/src/apps/data/FxOracle.ts +2 -2
- package/src/apps/data/routes/activities.test.ts +6 -2
- package/src/apps/data/routes/activities.ts +39 -1
- package/src/apps/data/routes/balances.test.ts +6 -2
- package/src/apps/data/routes/exchanges.test.ts +7 -3
- package/src/apps/data/routes/fee-amm.test.ts +47 -23
- package/src/apps/data/routes/fee-amm.ts +98 -119
- package/src/apps/data/routes/funding.test.ts +29 -8
- package/src/apps/data/routes/funding.ts +42 -48
- package/src/apps/data/routes/receipts.test.ts +6 -2
- package/src/apps/data/routes/tokens.ts +1 -1
- package/src/apps/data/routes/transfers.test.ts +203 -35
- package/src/apps/data/routes/transfers.ts +294 -172
- package/src/apps/data/routes/valuation.test.ts +18 -3
- package/src/apps/data/routes/valuation.ts +4 -4
- package/src/apps/index.ts +1 -0
- package/src/apps/management/routes/api-keys.test.ts +2 -75
- package/src/apps/management/routes/api-keys.ts +30 -7
- package/src/apps/management/routes/billing.test.ts +106 -85
- package/src/apps/management/routes/billing.ts +32 -21
- package/src/apps/management/routes/faucet.test.ts +11 -3
- package/src/apps/management/routes/faucet.ts +46 -30
- package/src/apps/management/routes/invitations.test.ts +0 -172
- package/src/apps/management/routes/invitations.ts +0 -3
- package/src/apps/management/routes/me.test.ts +3 -145
- package/src/apps/management/routes/me.ts +0 -6
- package/src/apps/management/routes/orgs.test.ts +45 -2
- package/src/apps/management/routes/orgs.ts +6 -0
- package/src/apps/management/routes/scopes.test.ts +1 -0
- package/src/apps/mpp/App.ts +86 -0
- package/src/apps/mpp/Mpp.test.ts +193 -0
- package/src/apps/mpp/Mpp.ts +353 -0
- package/src/apps/mpp/index.ts +2 -0
- package/src/apps/mpp/internal/Adapter.ts +11 -0
- package/src/apps/mpp/internal/Error.ts +9 -0
- package/src/apps/mpp/internal/Idempotency.ts +7 -0
- package/src/apps/mpp/internal/Screening.ts +10 -0
- package/src/apps/relay/App.test.ts +88 -6
- package/src/apps/relay/App.ts +61 -9
- package/src/db/Db.test.ts +2 -2
- package/src/db/Db.ts +30 -0
- package/src/db/Schema.ts +15 -0
- package/src/db/tables/enabledBillingSources.test.ts +303 -0
- package/src/db/tables/enabledBillingSources.ts +118 -0
- package/src/db/tables/invitations.ts +4 -26
- package/src/db/tables/organizations.test.ts +103 -0
- package/src/db/tables/organizations.ts +18 -2
- package/src/handlers/internal/utils.ts +22 -6
- package/src/handlers/relay.errors.test.ts +153 -0
- package/src/handlers/relay.ts +42 -7
- package/src/internal/Auth.test.ts +3 -3
- package/src/internal/Auth.ts +3 -63
- package/src/internal/FundingQuotes.test.ts +18 -5
- package/src/internal/FundingQuotes.ts +46 -11
- package/src/internal/Log.test.ts +91 -0
- package/src/internal/Log.ts +69 -5
- package/src/internal/MetricSink.test.ts +55 -0
- package/src/internal/MetricSink.ts +14 -0
- package/src/internal/Response.test.ts +28 -0
- 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
|
|
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
|
|
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', '
|
|
399
|
-
description: 'Requires the owner role and
|
|
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(
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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((
|
|
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,
|