tapimo 0.5.3 → 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 +15830 -7220
- 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 +1011 -412
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +4 -0
- package/dist/admin/App.js.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 +148 -0
- package/dist/admin/apps/early-access.d.ts.map +1 -0
- package/dist/admin/apps/early-access.js +133 -0
- package/dist/admin/apps/early-access.js.map +1 -0
- 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/apps/verified-tokens.d.ts +526 -1
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.js +214 -1
- package/dist/admin/apps/verified-tokens.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 -75
- 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 +6260 -4924
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +11 -6
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts +34 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.js +194 -16
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +71 -0
- package/dist/apps/data/FxOracle.d.ts.map +1 -0
- package/dist/apps/data/FxOracle.js +97 -0
- package/dist/apps/data/FxOracle.js.map +1 -0
- package/dist/apps/data/index.d.ts +1 -0
- package/dist/apps/data/index.d.ts.map +1 -1
- package/dist/apps/data/index.js +1 -0
- package/dist/apps/data/index.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +3599 -1382
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +250 -88
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +872 -251
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/addresses.js +11 -9
- package/dist/apps/data/routes/addresses.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +77 -26
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +88 -45
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/coingecko.d.ts +54 -54
- package/dist/apps/data/routes/exchanges.d.ts +856 -105
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +518 -76
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts +81 -67
- 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 +80 -36
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +65 -31
- package/dist/apps/data/routes/funding.js.map +1 -1
- package/dist/apps/data/routes/receipts.d.ts +392 -315
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +152 -77
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +167 -174
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +15 -13
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +277 -536
- package/dist/apps/data/routes/transactions.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.js +96 -77
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +157 -81
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +272 -130
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +307 -0
- package/dist/apps/data/routes/valuation.d.ts.map +1 -0
- package/dist/apps/data/routes/valuation.js +292 -0
- package/dist/apps/data/routes/valuation.js.map +1 -0
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/verified-tokens.js +7 -5
- package/dist/apps/data/routes/verified-tokens.js.map +1 -1
- package/dist/apps/data/routes/webhooks.d.ts +103 -123
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +124 -25
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/index.d.ts +2 -1
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +2 -1
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/management/App.d.ts +8554 -1692
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +12 -1
- package/dist/apps/management/App.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +7 -0
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +17 -3
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +157 -162
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +31 -23
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +232 -0
- package/dist/apps/management/routes/faucet.d.ts.map +1 -0
- package/dist/apps/management/routes/faucet.js +154 -0
- package/dist/apps/management/routes/faucet.js.map +1 -0
- package/dist/apps/management/routes/invitations.d.ts +60 -60
- package/dist/apps/management/routes/invite-links.d.ts +49 -20
- package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
- package/dist/apps/management/routes/invite-links.js +26 -1
- package/dist/apps/management/routes/invite-links.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +6 -6
- package/dist/apps/management/routes/members.d.ts +6 -6
- package/dist/apps/management/routes/orgs.d.ts +42 -42
- 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/projects.d.ts +44 -44
- package/dist/apps/management/routes/usage.d.ts +3 -0
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +4 -0
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
- package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
- package/dist/apps/management/routes/verified-token-requests.js +271 -0
- package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
- package/dist/apps/management/routes/webhooks.d.ts +6977 -0
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
- package/dist/apps/management/routes/webhooks.js +573 -0
- package/dist/apps/management/routes/webhooks.js.map +1 -0
- 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 +6 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +110 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +53 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +56 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/earlyAccess.d.ts +50 -0
- package/dist/db/tables/earlyAccess.d.ts.map +1 -0
- package/dist/db/tables/earlyAccess.js +63 -0
- package/dist/db/tables/earlyAccess.js.map +1 -0
- 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.map +1 -1
- package/dist/db/tables/invitations.js +3 -0
- package/dist/db/tables/invitations.js.map +1 -1
- package/dist/db/tables/inviteLinks.d.ts +10 -2
- package/dist/db/tables/inviteLinks.d.ts.map +1 -1
- package/dist/db/tables/inviteLinks.js +10 -1
- package/dist/db/tables/inviteLinks.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/db/tables/sponsoredTransactions.d.ts +2 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +2 -0
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
- package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
- package/dist/db/tables/verifiedTokenRequests.js +120 -0
- package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
- package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +16 -0
- package/dist/db/tables/webhookSubscriptions.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 +11 -2
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +14 -47
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/FundingQuotes.d.ts +147 -47
- package/dist/internal/FundingQuotes.d.ts.map +1 -1
- package/dist/internal/FundingQuotes.js +119 -33
- package/dist/internal/FundingQuotes.js.map +1 -1
- package/dist/internal/Log.d.ts +37 -5
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +52 -3
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +24 -0
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +2 -2
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +3 -0
- package/dist/internal/OpenApi.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/Schema.d.ts +14 -5
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +23 -5
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Value.d.ts +18 -0
- package/dist/internal/Value.d.ts.map +1 -1
- package/dist/internal/Value.js +11 -1
- package/dist/internal/Value.js.map +1 -1
- package/dist/internal/VerifiedTokens.d.ts.map +1 -1
- package/dist/internal/VerifiedTokens.js +10 -1
- package/dist/internal/VerifiedTokens.js.map +1 -1
- package/dist/internal/Webhooks.d.ts +24 -3
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +34 -8
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +7 -2
- package/src/App.test.ts +9 -4
- package/src/App.ts +23 -3
- package/src/Client.test-d.ts +733 -20
- package/src/Scope.ts +5 -0
- package/src/admin/App.test.ts +4 -0
- package/src/admin/App.ts +4 -0
- package/src/admin/apps/early-access.test.ts +125 -0
- package/src/admin/apps/early-access.ts +176 -0
- 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/apps/verified-tokens.test.ts +201 -0
- package/src/admin/apps/verified-tokens.ts +532 -286
- package/src/admin/ui/plugin.ts +2 -1
- package/src/admin/ui/src/lib/early-access.ts +63 -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/lib/verified-tokens.ts +79 -0
- package/src/admin/ui/src/routeTree.gen.ts +33 -2
- package/src/admin/ui/src/routes/__root.tsx +2 -1
- package/src/admin/ui/src/routes/api-keys.tsx +1 -1
- package/src/admin/ui/src/routes/early-access.tsx +162 -0
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +103 -0
- package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
- package/src/admin/ui.generated.ts +95 -94
- 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/App.ts +22 -14
- package/src/apps/data/FundingQuoteProviders.test.ts +297 -4
- package/src/apps/data/FundingQuoteProviders.ts +255 -29
- package/src/apps/data/FxOracle.test.ts +118 -0
- package/src/apps/data/FxOracle.ts +153 -0
- package/src/apps/data/index.ts +1 -0
- package/src/apps/data/routes/activities.test.ts +177 -3
- package/src/apps/data/routes/activities.ts +441 -132
- package/src/apps/data/routes/addresses.ts +19 -9
- package/src/apps/data/routes/balances.test.ts +148 -11
- package/src/apps/data/routes/balances.ts +140 -51
- package/src/apps/data/routes/exchanges.test.ts +544 -9
- package/src/apps/data/routes/exchanges.ts +690 -103
- 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 +113 -8
- package/src/apps/data/routes/funding.ts +77 -51
- package/src/apps/data/routes/receipts.test.ts +129 -23
- package/src/apps/data/routes/receipts.ts +205 -100
- package/src/apps/data/routes/tokens.test.ts +43 -0
- package/src/apps/data/routes/tokens.ts +28 -13
- package/src/apps/data/routes/transactions.test.ts +72 -19
- package/src/apps/data/routes/transactions.ts +162 -92
- package/src/apps/data/routes/transfers.test.ts +341 -31
- package/src/apps/data/routes/transfers.ts +358 -158
- package/src/apps/data/routes/valuation.test.ts +437 -0
- package/src/apps/data/routes/valuation.ts +423 -0
- package/src/apps/data/routes/verified-tokens.ts +14 -2
- package/src/apps/data/routes/webhooks.test.ts +56 -8
- package/src/apps/data/routes/webhooks.ts +144 -27
- package/src/apps/index.ts +2 -1
- package/src/apps/management/App.ts +13 -0
- package/src/apps/management/routes/api-keys.test.ts +14 -0
- package/src/apps/management/routes/api-keys.ts +27 -3
- package/src/apps/management/routes/billing.test.ts +152 -20
- package/src/apps/management/routes/billing.ts +34 -18
- package/src/apps/management/routes/faucet.test.ts +87 -0
- package/src/apps/management/routes/faucet.ts +201 -0
- package/src/apps/management/routes/invite-links.test.ts +117 -0
- package/src/apps/management/routes/invite-links.ts +43 -1
- package/src/apps/management/routes/me.test.ts +9 -99
- 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/management/routes/usage.test.ts +6 -0
- package/src/apps/management/routes/usage.ts +7 -0
- package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
- package/src/apps/management/routes/verified-token-requests.ts +397 -0
- package/src/apps/management/routes/webhooks.test.ts +426 -0
- package/src/apps/management/routes/webhooks.ts +672 -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 +19 -0
- package/src/db/Db.ts +125 -0
- package/src/db/Schema.ts +68 -0
- package/src/db/tables/earlyAccess.ts +86 -0
- package/src/db/tables/enabledBillingSources.test.ts +303 -0
- package/src/db/tables/enabledBillingSources.ts +118 -0
- package/src/db/tables/invitations.ts +3 -0
- package/src/db/tables/inviteLinks.ts +22 -3
- package/src/db/tables/organizations.test.ts +103 -0
- package/src/db/tables/organizations.ts +18 -2
- package/src/db/tables/sponsoredTransactions.ts +4 -0
- package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
- package/src/db/tables/verifiedTokenRequests.ts +182 -0
- package/src/db/tables/webhookSubscriptions.ts +31 -0
- 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 +15 -47
- package/src/internal/FundingQuotes.test.ts +161 -6
- package/src/internal/FundingQuotes.ts +141 -39
- package/src/internal/Log.test-d.ts +21 -0
- package/src/internal/Log.test.ts +141 -0
- package/src/internal/Log.ts +93 -6
- package/src/internal/MetricSink.test.ts +215 -0
- package/src/internal/MetricSink.ts +24 -0
- package/src/internal/OpenApi.ts +3 -0
- package/src/internal/Response.test.ts +28 -0
- package/src/internal/Response.ts +12 -4
- package/src/internal/Schema.ts +50 -5
- package/src/internal/Value.test.ts +20 -0
- package/src/internal/Value.ts +24 -1
- package/src/internal/VerifiedTokens.ts +9 -1
- package/src/internal/Webhooks.test.ts +140 -0
- package/src/internal/Webhooks.ts +53 -9
|
@@ -656,6 +656,19 @@ describe.skipIf(runtime.mode !== 'localnet')('behavior: sponsorship', () => {
|
|
|
656
656
|
// Chain-free (`eth_signRawTransaction` signs locally; the sponsor fee token
|
|
657
657
|
// short-circuits resolution), so the gate matrix runs in every mode.
|
|
658
658
|
describe('behavior: mainnet billing gate', () => {
|
|
659
|
+
const fill = {
|
|
660
|
+
calls: [{ to: Tempo.accounts[8]!.address, value: '0x0' }],
|
|
661
|
+
capabilities: { balanceDiffs: false },
|
|
662
|
+
chainId: Hex.fromNumber(Viem.chainId.mainnet),
|
|
663
|
+
feePayer: true,
|
|
664
|
+
feeToken: '0x20c0000000000000000000000000000000000000',
|
|
665
|
+
from: senderAccount.address,
|
|
666
|
+
gas: Hex.fromNumber(100_000),
|
|
667
|
+
maxFeePerGas: Hex.fromNumber(1_000_000),
|
|
668
|
+
maxPriorityFeePerGas: '0x0',
|
|
669
|
+
nonce: '0x0',
|
|
670
|
+
} as const
|
|
671
|
+
|
|
659
672
|
function createApp(db: Db.Source = TestApp.database()) {
|
|
660
673
|
return TestApp.create({
|
|
661
674
|
auth: { keys },
|
|
@@ -692,6 +705,64 @@ describe('behavior: mainnet billing gate', () => {
|
|
|
692
705
|
expect(body.error?.data).toStrictEqual({ code: 'billing_required' })
|
|
693
706
|
})
|
|
694
707
|
|
|
708
|
+
test('behavior: returns the billing reason for sponsor fills', async () => {
|
|
709
|
+
const body = await relayRequest(createApp(), {
|
|
710
|
+
method: 'eth_fillTransaction',
|
|
711
|
+
params: [fill],
|
|
712
|
+
token: 'secret_sponsor_prod',
|
|
713
|
+
})
|
|
714
|
+
expect(body.error).toStrictEqual({
|
|
715
|
+
code: -32602,
|
|
716
|
+
data: { code: 'billing_required' },
|
|
717
|
+
message: 'Billing required.',
|
|
718
|
+
})
|
|
719
|
+
})
|
|
720
|
+
|
|
721
|
+
test('behavior: relay fills still fall back to self-payment', async () => {
|
|
722
|
+
const server = await Relay.createServer(
|
|
723
|
+
RequestListener.fromFetchHandler(async (request) => {
|
|
724
|
+
type Rpc = {
|
|
725
|
+
id: number
|
|
726
|
+
method: string
|
|
727
|
+
params: readonly [Record<string, unknown>]
|
|
728
|
+
}
|
|
729
|
+
const rpc = (await request.json()) as Rpc
|
|
730
|
+
if (rpc.method !== 'eth_fillTransaction')
|
|
731
|
+
return Response.json({ id: rpc.id, jsonrpc: '2.0', result: '0x' })
|
|
732
|
+
return Response.json({
|
|
733
|
+
id: rpc.id,
|
|
734
|
+
jsonrpc: '2.0',
|
|
735
|
+
result: { capabilities: {}, tx: rpc.params[0] },
|
|
736
|
+
})
|
|
737
|
+
}),
|
|
738
|
+
)
|
|
739
|
+
try {
|
|
740
|
+
const body = await relayRequest(
|
|
741
|
+
TestApp.create({
|
|
742
|
+
auth: { keys },
|
|
743
|
+
db: TestApp.database(),
|
|
744
|
+
relay: { feePayer: { account: feePayerAccount } },
|
|
745
|
+
rpc: { url: () => server.url },
|
|
746
|
+
supportedChainIds: [Viem.chainId.mainnet],
|
|
747
|
+
}),
|
|
748
|
+
{
|
|
749
|
+
method: 'eth_fillTransaction',
|
|
750
|
+
mount: 'relay',
|
|
751
|
+
params: [fill],
|
|
752
|
+
token: 'secret_wildcard_prod',
|
|
753
|
+
},
|
|
754
|
+
)
|
|
755
|
+
type Result = {
|
|
756
|
+
capabilities?: { sponsored?: boolean | undefined } | undefined
|
|
757
|
+
}
|
|
758
|
+
const result = body.result as unknown as Result
|
|
759
|
+
expect(body.error).toBeUndefined()
|
|
760
|
+
expect(result.capabilities?.sponsored).toBe(false)
|
|
761
|
+
} finally {
|
|
762
|
+
await server.closeAsync()
|
|
763
|
+
}
|
|
764
|
+
})
|
|
765
|
+
|
|
695
766
|
test('behavior: refuses mainnet sponsorship while billing is inactive', async () => {
|
|
696
767
|
const db = TestApp.database()
|
|
697
768
|
await StripeCustomers.create(db, { orgId: 'org_1', stripeCustomerId: 'cus_gate' })
|
|
@@ -1110,13 +1181,10 @@ async function signSponsorable(
|
|
|
1110
1181
|
})
|
|
1111
1182
|
}
|
|
1112
1183
|
|
|
1113
|
-
/** Posts one JSON-RPC request to
|
|
1114
|
-
async function relayRequest(
|
|
1115
|
-
app: ReturnType<typeof TestApp.create>,
|
|
1116
|
-
options: { method: string; params: readonly unknown[]; token?: string | undefined },
|
|
1117
|
-
) {
|
|
1184
|
+
/** Posts one JSON-RPC request to a composed relay mount. */
|
|
1185
|
+
async function relayRequest(app: ReturnType<typeof TestApp.create>, options: relayRequest.Options) {
|
|
1118
1186
|
const response = await app.fetch(
|
|
1119
|
-
new Request(
|
|
1187
|
+
new Request(`http://tempo-api.test/rpc/${options.mount ?? 'sponsor'}`, {
|
|
1120
1188
|
body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: options.method, params: options.params }), // prettier-ignore
|
|
1121
1189
|
headers: {
|
|
1122
1190
|
'content-type': 'application/json',
|
|
@@ -1131,6 +1199,20 @@ async function relayRequest(
|
|
|
1131
1199
|
}
|
|
1132
1200
|
}
|
|
1133
1201
|
|
|
1202
|
+
declare namespace relayRequest {
|
|
1203
|
+
/** Options for one composed relay request. */
|
|
1204
|
+
type Options = {
|
|
1205
|
+
/** JSON-RPC method. */
|
|
1206
|
+
method: string
|
|
1207
|
+
/** Relay mount to call. */
|
|
1208
|
+
mount?: 'relay' | 'sponsor' | undefined
|
|
1209
|
+
/** JSON-RPC parameters. */
|
|
1210
|
+
params: readonly unknown[]
|
|
1211
|
+
/** API-key credential. */
|
|
1212
|
+
token?: string | undefined
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1134
1216
|
// The full billing-to-invoice flow with nothing synthetic: a disposable node
|
|
1135
1217
|
// runs under mainnet's chain id (dev consensus, funded dev alloc), so a real
|
|
1136
1218
|
// card attach opens the real gate, a real sponsored transaction mines with a
|
package/src/apps/relay/App.ts
CHANGED
|
@@ -8,6 +8,7 @@ import type * as App from '../../App.js'
|
|
|
8
8
|
import * as BillingSettings from '../../db/tables/billingSettings.js'
|
|
9
9
|
import * as Db from '../../db/Db.js'
|
|
10
10
|
import * as Fees from '../../internal/Fees.js'
|
|
11
|
+
import * as Log from '../../internal/Log.js'
|
|
11
12
|
import * as SponsoredTransactions from '../../db/tables/sponsoredTransactions.js'
|
|
12
13
|
import * as Billing from '../management/Billing.js'
|
|
13
14
|
import * as Handler from '../../Handler.js'
|
|
@@ -75,6 +76,9 @@ export declare namespace relay {
|
|
|
75
76
|
// Builds request-aware RPC middleware. Viem clients are memoized upstream, so
|
|
76
77
|
// constructing `Handler.relay` per request stays cheap.
|
|
77
78
|
function middleware(options: middleware.Options) {
|
|
79
|
+
// The sponsor mount promises a fee-payer signature. The relay mount may
|
|
80
|
+
// fall back to self-payment when policy refuses sponsorship.
|
|
81
|
+
const sponsorshipRequired = options.path === 'rpc/sponsor'
|
|
78
82
|
// Platform-wide per-transaction cap; org settings tighten it, never loosen.
|
|
79
83
|
const txFeeLimit = options.feePayer
|
|
80
84
|
? Billing.toBaseUnits(options.feePayer.txFeeLimit ?? '1.00')
|
|
@@ -111,12 +115,20 @@ function middleware(options: middleware.Options) {
|
|
|
111
115
|
// only mainnet: the relay resolves chainId from the request body, so
|
|
112
116
|
// the query-string chain guard never sees it.
|
|
113
117
|
const id = Number(transaction.chainId)
|
|
114
|
-
if (!attribution || !sponsorable || !c.get('supportedChainIds').has(id))
|
|
118
|
+
if (!attribution || !sponsorable || !c.get('supportedChainIds').has(id)) {
|
|
119
|
+
if (sponsorshipRequired)
|
|
120
|
+
throw new RpcResponse.InvalidParamsError({ message: 'Sponsorship rejected.' })
|
|
121
|
+
return false
|
|
122
|
+
}
|
|
115
123
|
if (!Viem.isMainnet(id)) return true
|
|
116
124
|
// Mainnet is production-only: a sandbox key resolves chainId from the
|
|
117
125
|
// request body, so the auth-layer sandbox/mainnet guard never sees
|
|
118
126
|
// it. Refuse here, or sandbox spend would sponsor mainnet unbilled.
|
|
119
|
-
if (attribution.environment !== 'production')
|
|
127
|
+
if (attribution.environment !== 'production') {
|
|
128
|
+
if (sponsorshipRequired)
|
|
129
|
+
throw new RpcResponse.InvalidParamsError({ message: 'Sponsorship rejected.' })
|
|
130
|
+
return false
|
|
131
|
+
}
|
|
120
132
|
|
|
121
133
|
// Enforcement reads hit the primary, never the cached replica: a
|
|
122
134
|
// stale `past_due`, a lowered limit, or another isolate's pending
|
|
@@ -127,8 +139,22 @@ function middleware(options: middleware.Options) {
|
|
|
127
139
|
// `past_due` gets its own reason: the caller must settle an invoice,
|
|
128
140
|
// not attach a card.
|
|
129
141
|
const billing = await Billing.status(db, attribution.orgId)
|
|
130
|
-
if (billing === 'past_due')
|
|
131
|
-
|
|
142
|
+
if (billing === 'past_due') {
|
|
143
|
+
if (sponsorshipRequired)
|
|
144
|
+
throw new RpcResponse.InvalidParamsError({
|
|
145
|
+
data: { code: 'billing_past_due' },
|
|
146
|
+
message: 'Billing past due.',
|
|
147
|
+
})
|
|
148
|
+
return 'billing_past_due'
|
|
149
|
+
}
|
|
150
|
+
if (billing !== 'active') {
|
|
151
|
+
if (sponsorshipRequired)
|
|
152
|
+
throw new RpcResponse.InvalidParamsError({
|
|
153
|
+
data: { code: 'billing_required' },
|
|
154
|
+
message: 'Billing required.',
|
|
155
|
+
})
|
|
156
|
+
return 'billing_required'
|
|
157
|
+
}
|
|
132
158
|
|
|
133
159
|
const feeToken = (() => {
|
|
134
160
|
try {
|
|
@@ -144,21 +170,40 @@ function middleware(options: middleware.Options) {
|
|
|
144
170
|
!verified ||
|
|
145
171
|
verified.currency.toLowerCase() !== 'usd' ||
|
|
146
172
|
verified.decimals !== Fees.tokenDecimals
|
|
147
|
-
)
|
|
148
|
-
|
|
173
|
+
) {
|
|
174
|
+
if (sponsorshipRequired)
|
|
175
|
+
throw new RpcResponse.InvalidParamsError({
|
|
176
|
+
data: { code: 'fee_token_unsupported' },
|
|
177
|
+
message: 'Fee token unsupported.',
|
|
178
|
+
})
|
|
149
179
|
return 'fee_token_unsupported'
|
|
180
|
+
}
|
|
150
181
|
|
|
151
182
|
// Fail closed: a transaction whose signed cap cannot be computed
|
|
152
183
|
// cannot be bounded, so it never sponsors on mainnet.
|
|
153
184
|
const feeMax = Fees.maxOf(transaction)
|
|
154
|
-
if (feeMax === undefined)
|
|
185
|
+
if (feeMax === undefined) {
|
|
186
|
+
if (sponsorshipRequired)
|
|
187
|
+
throw new RpcResponse.InvalidParamsError({
|
|
188
|
+
data: { code: 'tx_fee_limit_exceeded' },
|
|
189
|
+
message: 'Transaction fee limit exceeded.',
|
|
190
|
+
})
|
|
191
|
+
return 'tx_fee_limit_exceeded'
|
|
192
|
+
}
|
|
155
193
|
const settings = await BillingSettings.get(db, attribution.orgId)
|
|
156
194
|
const cap = (() => {
|
|
157
195
|
if (settings?.txFeeLimit == null) return txFeeLimit
|
|
158
196
|
const org = Billing.toBaseUnits(settings.txFeeLimit)
|
|
159
197
|
return org < txFeeLimit ? org : txFeeLimit
|
|
160
198
|
})()
|
|
161
|
-
if (feeMax > cap)
|
|
199
|
+
if (feeMax > cap) {
|
|
200
|
+
if (sponsorshipRequired)
|
|
201
|
+
throw new RpcResponse.InvalidParamsError({
|
|
202
|
+
data: { code: 'tx_fee_limit_exceeded' },
|
|
203
|
+
message: 'Transaction fee limit exceeded.',
|
|
204
|
+
})
|
|
205
|
+
return 'tx_fee_limit_exceeded'
|
|
206
|
+
}
|
|
162
207
|
// The period spend limit is enforced atomically at recording time
|
|
163
208
|
// (`onSponsored` → `SponsoredTransactions.reserve`); a check here
|
|
164
209
|
// would be a check-then-act race a burst could slip through.
|
|
@@ -278,7 +323,14 @@ function middleware(options: middleware.Options) {
|
|
|
278
323
|
}
|
|
279
324
|
: {}),
|
|
280
325
|
})
|
|
281
|
-
|
|
326
|
+
const response = await handler.fetch(c.req.raw)
|
|
327
|
+
const body: unknown = await response
|
|
328
|
+
.clone()
|
|
329
|
+
.json()
|
|
330
|
+
.catch(() => undefined)
|
|
331
|
+
const rpc = Log.rpcErrors(body)
|
|
332
|
+
if (rpc) c.set('rpcResponse', rpc)
|
|
333
|
+
return response
|
|
282
334
|
})
|
|
283
335
|
}
|
|
284
336
|
|
package/src/db/Db.test.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { sql } from 'kysely'
|
|
1
2
|
import { nanoid } from 'nanoid'
|
|
2
3
|
|
|
3
4
|
import * as Runtime from '../../test/runtime.js'
|
|
4
5
|
import * as Db from './Db.js'
|
|
6
|
+
import * as Invitations from './tables/invitations.js'
|
|
5
7
|
import * as Organizations from './tables/organizations.js'
|
|
6
8
|
|
|
7
9
|
const create = () => Db.postgres({ connectionString: Runtime.postgresUrl, schema: `t_${nanoid()}` })
|
|
@@ -15,6 +17,23 @@ describe('postgres', () => {
|
|
|
15
17
|
await db.close()
|
|
16
18
|
})
|
|
17
19
|
|
|
20
|
+
test('behavior: legacy invitation inserts do not grant early access', async () => {
|
|
21
|
+
const db = create()
|
|
22
|
+
await db.migrate()
|
|
23
|
+
await Organizations.create(db, { id: 'org_legacy', name: 'Legacy' })
|
|
24
|
+
await sql`
|
|
25
|
+
INSERT INTO invitations (
|
|
26
|
+
id, org_id, email, role, invited_by, expires_at, accepted_at, revoked_at, created_at
|
|
27
|
+
) VALUES (
|
|
28
|
+
'inv_legacy', 'org_legacy', 'legacy@example.org', 'member', 'usr_legacy',
|
|
29
|
+
'2027-01-01T00:00:00.000Z', NULL, NULL, '2026-07-20T00:00:00.000Z'
|
|
30
|
+
)
|
|
31
|
+
`.execute(db.kysely)
|
|
32
|
+
|
|
33
|
+
expect((await Invitations.get(db, 'inv_legacy'))?.grantsEarlyAccess).toBe(false)
|
|
34
|
+
await db.close()
|
|
35
|
+
})
|
|
36
|
+
|
|
18
37
|
test('behavior: creates a Postgres db without schema scoping', async () => {
|
|
19
38
|
const db = Db.postgres({ connectionString: Runtime.postgresUrl })
|
|
20
39
|
await db.close()
|
package/src/db/Db.ts
CHANGED
|
@@ -12,6 +12,8 @@ import * as pg from 'pg'
|
|
|
12
12
|
|
|
13
13
|
import type * as AdminAuditLogs from './tables/adminAuditLogs.js'
|
|
14
14
|
import type * as BillingSettings from './tables/billingSettings.js'
|
|
15
|
+
import type * as EarlyAccess from './tables/earlyAccess.js'
|
|
16
|
+
import type * as EnabledBillingSources from './tables/enabledBillingSources.js'
|
|
15
17
|
import type * as Invitations from './tables/invitations.js'
|
|
16
18
|
import type * as InviteLinks from './tables/inviteLinks.js'
|
|
17
19
|
import type * as Memberships from './tables/memberships.js'
|
|
@@ -21,6 +23,7 @@ import type * as RequestUsage from './tables/requestUsage.js'
|
|
|
21
23
|
import type * as SponsoredTransactions from './tables/sponsoredTransactions.js'
|
|
22
24
|
import type * as StripeCustomers from './tables/stripeCustomers.js'
|
|
23
25
|
import type * as Users from './tables/users.js'
|
|
26
|
+
import type * as VerifiedTokenRequests from './tables/verifiedTokenRequests.js'
|
|
24
27
|
import type * as VerifiedTokens from './tables/verifiedTokens.js'
|
|
25
28
|
import type * as WebhookDeliveries from './tables/webhookDeliveries.js'
|
|
26
29
|
import type * as WebhookSubscriptions from './tables/webhookSubscriptions.js'
|
|
@@ -32,6 +35,8 @@ import type * as WebhookSubscriptions from './tables/webhookSubscriptions.js'
|
|
|
32
35
|
export type Database = {
|
|
33
36
|
admin_audit_logs: AdminAuditLogs.Table
|
|
34
37
|
billing_settings: BillingSettings.Table
|
|
38
|
+
early_access: EarlyAccess.Table
|
|
39
|
+
enabled_billing_sources: EnabledBillingSources.Table
|
|
35
40
|
invitations: Invitations.Table
|
|
36
41
|
invite_link_redemptions: InviteLinks.RedemptionTable
|
|
37
42
|
invite_links: InviteLinks.Table
|
|
@@ -51,6 +56,7 @@ export type Database = {
|
|
|
51
56
|
stripe_customers: StripeCustomers.Table
|
|
52
57
|
users: Users.Table
|
|
53
58
|
verified_token_lists: VerifiedTokens.ListTable
|
|
59
|
+
verified_token_requests: VerifiedTokenRequests.Table
|
|
54
60
|
verified_tokens: VerifiedTokens.Table
|
|
55
61
|
webhook_deliveries: WebhookDeliveries.Table
|
|
56
62
|
webhook_subscriptions: WebhookSubscriptions.Table
|
|
@@ -645,6 +651,125 @@ export const migrations: readonly Migration[] = [
|
|
|
645
651
|
name: '0058_request_usage_sequence_bigint',
|
|
646
652
|
up: (db) => raw(db, sql`ALTER TABLE request_usage_meter_events ALTER COLUMN sequence TYPE bigint`), // prettier-ignore
|
|
647
653
|
},
|
|
654
|
+
{
|
|
655
|
+
name: '0059_verified_token_requests',
|
|
656
|
+
up: (db) =>
|
|
657
|
+
db.schema
|
|
658
|
+
.createTable('verifiedTokenRequests')
|
|
659
|
+
.ifNotExists()
|
|
660
|
+
.addColumn('id', 'text', (c) => c.primaryKey())
|
|
661
|
+
.addColumn('orgId', 'text', (c) => c.notNull())
|
|
662
|
+
.addColumn('requestedBy', 'text', (c) => c.notNull())
|
|
663
|
+
.addColumn('chainId', 'integer', (c) => c.notNull())
|
|
664
|
+
.addColumn('address', 'text', (c) => c.notNull())
|
|
665
|
+
.addColumn('symbol', 'text', (c) => c.notNull())
|
|
666
|
+
.addColumn('name', 'text', (c) => c.notNull())
|
|
667
|
+
.addColumn('currency', 'text', (c) => c.notNull())
|
|
668
|
+
.addColumn('decimals', 'integer', (c) => c.notNull())
|
|
669
|
+
.addColumn('logoUri', 'text')
|
|
670
|
+
.addColumn('note', 'text')
|
|
671
|
+
.addColumn('status', 'text', (c) => c.notNull().defaultTo('pending'))
|
|
672
|
+
.addColumn('reviewNote', 'text')
|
|
673
|
+
.addColumn('reviewedBy', 'text')
|
|
674
|
+
.addColumn('reviewedAt', 'text')
|
|
675
|
+
.addColumn('createdAt', 'text', (c) => c.notNull())
|
|
676
|
+
.addColumn('updatedAt', 'text', (c) => c.notNull()),
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
name: '0060_verified_token_requests_org_index',
|
|
680
|
+
up: (db) =>
|
|
681
|
+
db.schema
|
|
682
|
+
.createIndex('verified_token_requests_org_idx')
|
|
683
|
+
.ifNotExists()
|
|
684
|
+
.on('verifiedTokenRequests')
|
|
685
|
+
.columns(['orgId', 'createdAt']),
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
name: '0061_verified_token_requests_pending_unique',
|
|
689
|
+
// Raw SQL: a partial unique index (snake_case; the CamelCase plugin does
|
|
690
|
+
// not map raw statements). At most one live pending request per
|
|
691
|
+
// (chain, address) globally; addresses are stored lowercase.
|
|
692
|
+
up: (db) =>
|
|
693
|
+
raw(
|
|
694
|
+
db,
|
|
695
|
+
sql`CREATE UNIQUE INDEX IF NOT EXISTS verified_token_requests_pending_unique ON verified_token_requests (chain_id, address) WHERE status = 'pending'`,
|
|
696
|
+
),
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
name: '0062_verified_token_requests_status_index',
|
|
700
|
+
up: (db) =>
|
|
701
|
+
db.schema
|
|
702
|
+
.createIndex('verified_token_requests_status_idx')
|
|
703
|
+
.ifNotExists()
|
|
704
|
+
.on('verifiedTokenRequests')
|
|
705
|
+
.columns(['status', 'createdAt']),
|
|
706
|
+
},
|
|
707
|
+
{
|
|
708
|
+
name: '0063_verified_token_requests_logo',
|
|
709
|
+
up: (db) => raw(db, sql`ALTER TABLE verified_token_requests ADD COLUMN IF NOT EXISTS logo text`), // prettier-ignore
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
name: '0064_invitations_grants_early_access',
|
|
713
|
+
up: (db) => raw(db, sql`ALTER TABLE invitations ADD COLUMN IF NOT EXISTS grants_early_access boolean NOT NULL DEFAULT false`), // prettier-ignore
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
name: '0065_invitations_grants_early_access_backfill',
|
|
717
|
+
// Pre-gate invitations came from trusted-era users; the fixed cutoff keeps
|
|
718
|
+
// re-runs (test DBs replay every migration) from flipping newer rows.
|
|
719
|
+
up: (db) => raw(db, sql`UPDATE invitations SET grants_early_access = true WHERE created_at < '2026-07-20T00:00:00.000Z'`), // prettier-ignore
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
name: '0066_early_access',
|
|
723
|
+
up: (db) =>
|
|
724
|
+
db.schema
|
|
725
|
+
.createTable('earlyAccess')
|
|
726
|
+
.ifNotExists()
|
|
727
|
+
.addColumn('entry', 'text', (c) => c.primaryKey())
|
|
728
|
+
.addColumn('createdAt', 'text', (c) => c.notNull())
|
|
729
|
+
.addColumn('createdBy', 'text', (c) => c.notNull()),
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
name: '0067_invitations_grants_early_access_cutover',
|
|
733
|
+
// Every invitation present at deployment came from a trusted-era user.
|
|
734
|
+
up: (db) => raw(db, sql`UPDATE invitations SET grants_early_access = true`),
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
name: '0068_invitations_grants_early_access_legacy_default',
|
|
738
|
+
// Old workers omit this column during deploy; new writes set it explicitly.
|
|
739
|
+
up: (db) => raw(db, sql`ALTER TABLE invitations ALTER COLUMN grants_early_access SET DEFAULT true`), // prettier-ignore
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
name: '0069_invite_links_allowed_email_domains',
|
|
743
|
+
up: (db) => raw(db, sql`ALTER TABLE invite_links ADD COLUMN IF NOT EXISTS allowed_email_domains jsonb`), // prettier-ignore
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
name: '0070_enabled_billing_sources',
|
|
747
|
+
up: (db) =>
|
|
748
|
+
db.schema
|
|
749
|
+
.createTable('enabledBillingSources')
|
|
750
|
+
.ifNotExists()
|
|
751
|
+
.addColumn('createdAt', 'text', (c) => c.notNull())
|
|
752
|
+
.addColumn('createdBy', 'text', (c) => c.notNull())
|
|
753
|
+
.addColumn('orgId', 'text', (c) => c.notNull())
|
|
754
|
+
.addColumn('source', 'text', (c) => c.notNull())
|
|
755
|
+
.addPrimaryKeyConstraint('enabled_billing_sources_pkey', ['orgId', 'source']),
|
|
756
|
+
},
|
|
757
|
+
{
|
|
758
|
+
name: '0071_enabled_billing_sources_stripe_backfill',
|
|
759
|
+
up: (db) => raw(db, sql`INSERT INTO enabled_billing_sources (created_at, created_by, org_id, source) SELECT min(created_at), 'migration', org_id, 'stripe' FROM stripe_customers GROUP BY org_id ON CONFLICT DO NOTHING`), // prettier-ignore
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
name: '0072_enabled_billing_sources_stripe_catch_up',
|
|
763
|
+
up: (db) => raw(db, sql`INSERT INTO enabled_billing_sources (created_at, created_by, org_id, source) SELECT min(created_at), 'migration', org_id, 'stripe' FROM stripe_customers GROUP BY org_id ON CONFLICT DO NOTHING`), // prettier-ignore
|
|
764
|
+
},
|
|
765
|
+
{
|
|
766
|
+
name: '0073_enabled_billing_sources_early_access_backfill',
|
|
767
|
+
up: (db) => raw(db, sql`INSERT INTO enabled_billing_sources (created_at, created_by, org_id, source) SELECT organizations.created_at, 'migration', organizations.id, 'stripe' FROM organizations INNER JOIN users ON users.id = organizations.user_id WHERE users.email IS NOT NULL AND EXISTS (SELECT 1 FROM early_access WHERE early_access.entry IN (lower(btrim(users.email)), regexp_replace(lower(btrim(users.email)), '^.*@', ''))) ON CONFLICT DO NOTHING`), // prettier-ignore
|
|
768
|
+
},
|
|
769
|
+
{
|
|
770
|
+
name: '0074_invitations_early_access_default',
|
|
771
|
+
up: (db) => raw(db, sql`ALTER TABLE invitations ALTER COLUMN grants_early_access SET DEFAULT false`), // prettier-ignore
|
|
772
|
+
},
|
|
648
773
|
]
|
|
649
774
|
|
|
650
775
|
/**
|
package/src/db/Schema.ts
CHANGED
|
@@ -52,6 +52,35 @@ export const BillingSettings = z.object({
|
|
|
52
52
|
/** Columns of the `billing_settings` table. */
|
|
53
53
|
export type BillingSettings = z.output<typeof BillingSettings>
|
|
54
54
|
|
|
55
|
+
/** An early-access allowlist entry: a lowercase domain or exact email. */
|
|
56
|
+
export const EarlyAccessEntry = z.object({
|
|
57
|
+
createdAt: z.string().check(z.describe('When the entry was added (ISO 8601).')),
|
|
58
|
+
createdBy: z
|
|
59
|
+
.string()
|
|
60
|
+
.check(z.describe('Admin email that added the entry, or `migration` for seeded rows.')),
|
|
61
|
+
entry: z
|
|
62
|
+
.string()
|
|
63
|
+
.check(z.describe('Lowercase domain or exact email (contains `@`) granted early access.')),
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
/** Columns of the `early_access` table. */
|
|
67
|
+
export type EarlyAccessEntry = z.output<typeof EarlyAccessEntry>
|
|
68
|
+
|
|
69
|
+
/** One billing-source type enabled for an organization. */
|
|
70
|
+
export const EnabledBillingSource = z.object({
|
|
71
|
+
createdAt: z.string().check(z.describe('When the billing source was enabled (ISO 8601).')),
|
|
72
|
+
createdBy: z
|
|
73
|
+
.string()
|
|
74
|
+
.check(z.describe('Identity that enabled the source, or `migration` for backfilled rows.')),
|
|
75
|
+
orgId: z.string().check(z.describe('Organization id (`org_…`) this source is enabled for.')),
|
|
76
|
+
source: z
|
|
77
|
+
.enum(['stripe', 'tempo'])
|
|
78
|
+
.check(z.describe('Enabled billing-source type; `stripe` or `tempo`.')),
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
/** Columns of the `enabled_billing_sources` table. */
|
|
82
|
+
export type EnabledBillingSource = z.output<typeof EnabledBillingSource>
|
|
83
|
+
|
|
55
84
|
/** A pending, expiring offer of organization membership. */
|
|
56
85
|
export const Invitation = z.object({
|
|
57
86
|
acceptedAt: z
|
|
@@ -62,6 +91,9 @@ export const Invitation = z.object({
|
|
|
62
91
|
.string()
|
|
63
92
|
.check(z.describe('Invitee email (stored lowercase); matched against verified emails.')),
|
|
64
93
|
expiresAt: z.string().check(z.describe('When the invitation expires (ISO 8601).')),
|
|
94
|
+
grantsEarlyAccess: z
|
|
95
|
+
.boolean()
|
|
96
|
+
.check(z.describe("Whether the invitation grants early access; stamped from the inviter's own access at creation.")), // prettier-ignore
|
|
65
97
|
id: z
|
|
66
98
|
.string()
|
|
67
99
|
.check(
|
|
@@ -83,6 +115,9 @@ export type Invitation = z.output<typeof Invitation>
|
|
|
83
115
|
|
|
84
116
|
/** A reusable bearer link granting organization membership. */
|
|
85
117
|
export const InviteLink = z.object({
|
|
118
|
+
allowedEmailDomains: z
|
|
119
|
+
.nullable(z.readonly(z.array(z.string())))
|
|
120
|
+
.check(z.describe('Lowercase email domains allowed to redeem, or null for unrestricted.')),
|
|
86
121
|
createdAt: z.string().check(z.describe('When the link was created (ISO 8601).')),
|
|
87
122
|
createdBy: z.string().check(z.describe('User id that created the link, or `super_admin`.')),
|
|
88
123
|
deletedAt: z.nullable(z.string()).check(z.describe('When the link was soft deleted, or null.')),
|
|
@@ -363,6 +398,39 @@ export const VerifiedTokenList = z.object({
|
|
|
363
398
|
/** Columns of the `verified_token_lists` table. */
|
|
364
399
|
export type VerifiedTokenList = z.output<typeof VerifiedTokenList>
|
|
365
400
|
|
|
401
|
+
/**
|
|
402
|
+
* An organization's request to add a token to the curated verified list.
|
|
403
|
+
* Addresses are stored lowercase; a partial unique index allows at most one
|
|
404
|
+
* pending request per (chain, address) globally.
|
|
405
|
+
*/
|
|
406
|
+
export const VerifiedTokenRequest = z.object({
|
|
407
|
+
address: z.string().check(z.describe('Requested TIP-20 token contract address (lowercase).')),
|
|
408
|
+
chainId: z.number().check(z.int(), z.describe('Chain the token belongs to.')),
|
|
409
|
+
createdAt: z.string().check(z.describe('When the request was created (ISO 8601).')),
|
|
410
|
+
currency: z.string().check(z.describe('Display currency, e.g. `USD`.')),
|
|
411
|
+
decimals: z.number().check(z.int(), z.describe('Decimal precision.')),
|
|
412
|
+
id: z.string().check(z.describe('Opaque request id (`vtr_…`).')),
|
|
413
|
+
logo: z
|
|
414
|
+
.nullable(z.string())
|
|
415
|
+
.check(z.describe('Inline SVG logo markup uploaded by the requester, or null.')),
|
|
416
|
+
logoUri: z.nullable(z.string()).check(z.describe('HTTPS logo URL from chain metadata, or null.')),
|
|
417
|
+
name: z.string().check(z.describe('Display name.')),
|
|
418
|
+
note: z.nullable(z.string()).check(z.describe('Requester note to reviewers, or null.')),
|
|
419
|
+
orgId: z.string().check(z.describe('Requesting organization id (`org_…`).')),
|
|
420
|
+
requestedBy: z
|
|
421
|
+
.string()
|
|
422
|
+
.check(z.describe('User id that submitted the request, or `super_admin`.')),
|
|
423
|
+
reviewNote: z.nullable(z.string()).check(z.describe('Reviewer note shown on denial, or null.')),
|
|
424
|
+
reviewedAt: z.nullable(z.string()).check(z.describe('When the request was reviewed, or null.')),
|
|
425
|
+
reviewedBy: z.nullable(z.string()).check(z.describe('Reviewing admin email, or null.')),
|
|
426
|
+
status: z.enum(['approved', 'denied', 'pending']).check(z.describe('Review status.')),
|
|
427
|
+
symbol: z.string().check(z.describe('Ticker symbol.'), z.meta({ examples: ['USDC'] })),
|
|
428
|
+
updatedAt: z.string().check(z.describe('When the request was last changed (ISO 8601).')),
|
|
429
|
+
})
|
|
430
|
+
|
|
431
|
+
/** Columns of the `verified_token_requests` table. */
|
|
432
|
+
export type VerifiedTokenRequest = z.output<typeof VerifiedTokenRequest>
|
|
433
|
+
|
|
366
434
|
/**
|
|
367
435
|
* A webhook subscription. The `Webhooks.Owner` union maps to (`ownerType`,
|
|
368
436
|
* `ownerId`); jsonb object columns round-trip as parsed values. `pollerCursor`
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Selectable } from 'kysely'
|
|
2
|
+
|
|
3
|
+
import type * as Db from '../Db.js'
|
|
4
|
+
import type * as db_Schema from '../Schema.js'
|
|
5
|
+
|
|
6
|
+
/** Columns of the `early_access` table, derived from `Schema.EarlyAccessEntry`. */
|
|
7
|
+
export type Table = db_Schema.EarlyAccessEntry
|
|
8
|
+
|
|
9
|
+
/** A stored early-access allowlist row. */
|
|
10
|
+
export type Record = Selectable<Table>
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Inserts an allowlist entry (a domain or exact email, normalized lowercase).
|
|
14
|
+
* Returns the stored record, or `undefined` when the entry already exists.
|
|
15
|
+
*
|
|
16
|
+
* @param db - The database.
|
|
17
|
+
* @param input - The entry to insert.
|
|
18
|
+
* @returns The stored record, or `undefined` when already present.
|
|
19
|
+
*/
|
|
20
|
+
export function add(db: Db.Db, input: add.Input): Promise<Record | undefined> {
|
|
21
|
+
return db.kysely
|
|
22
|
+
.insertInto('early_access')
|
|
23
|
+
.values({
|
|
24
|
+
createdAt: new Date().toISOString(),
|
|
25
|
+
createdBy: input.createdBy,
|
|
26
|
+
entry: input.entry.trim().toLowerCase(),
|
|
27
|
+
})
|
|
28
|
+
.onConflict((oc) => oc.column('entry').doNothing())
|
|
29
|
+
.returningAll()
|
|
30
|
+
.executeTakeFirst()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export declare namespace add {
|
|
34
|
+
/** Fields accepted when inserting an allowlist entry. */
|
|
35
|
+
type Input = {
|
|
36
|
+
/** Admin email adding the entry, or `migration` for seeded rows. */
|
|
37
|
+
createdBy: string
|
|
38
|
+
/** Domain or exact email to grant early access; normalized lowercase. */
|
|
39
|
+
entry: string
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Lists all allowlist entries, ordered by entry.
|
|
45
|
+
*
|
|
46
|
+
* @param db - The database.
|
|
47
|
+
* @returns The records.
|
|
48
|
+
*/
|
|
49
|
+
export function list(db: Db.Db): Promise<Record[]> {
|
|
50
|
+
return db.kysely.selectFrom('early_access').selectAll().orderBy('entry', 'asc').execute()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Removes an allowlist entry.
|
|
55
|
+
*
|
|
56
|
+
* @param db - The database.
|
|
57
|
+
* @param entry - The entry to remove (matched lowercase).
|
|
58
|
+
* @returns The removed record, or `undefined` when absent.
|
|
59
|
+
*/
|
|
60
|
+
export function remove(db: Db.Db, entry: string): Promise<Record | undefined> {
|
|
61
|
+
return db.kysely
|
|
62
|
+
.deleteFrom('early_access')
|
|
63
|
+
.where('entry', '=', entry.trim().toLowerCase())
|
|
64
|
+
.returningAll()
|
|
65
|
+
.executeTakeFirst()
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Whether an allowlist entry matches `email`: the exact email or its domain.
|
|
70
|
+
* Two primary-key lookups via `IN`.
|
|
71
|
+
*
|
|
72
|
+
* @param db - The database.
|
|
73
|
+
* @param email - The email to match (case-insensitive).
|
|
74
|
+
* @returns Whether an entry matches.
|
|
75
|
+
*/
|
|
76
|
+
export async function matches(db: Db.Db, email: string): Promise<boolean> {
|
|
77
|
+
const normalized = email.trim().toLowerCase()
|
|
78
|
+
const domain = normalized.slice(normalized.lastIndexOf('@') + 1)
|
|
79
|
+
const row = await db.kysely
|
|
80
|
+
.selectFrom('early_access')
|
|
81
|
+
.select('entry')
|
|
82
|
+
.where('entry', 'in', [normalized, domain])
|
|
83
|
+
.limit(1)
|
|
84
|
+
.executeTakeFirst()
|
|
85
|
+
return row !== undefined
|
|
86
|
+
}
|