tapimo 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +47 -39
- 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 +8050 -8019
- 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 +669 -669
- package/dist/apps/data/routes/fee-amm.d.ts +157 -143
- 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 +30 -30
- package/dist/apps/data/routes/receipts.d.ts +8 -8
- package/dist/apps/data/routes/rpc.d.ts +31 -31
- package/dist/apps/data/routes/tokenlist.d.ts.map +1 -1
- package/dist/apps/data/routes/tokenlist.js +12 -3
- package/dist/apps/data/routes/tokenlist.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +4 -4
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +5 -1
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +6 -6
- package/dist/apps/data/routes/transfers.d.ts +101 -90
- 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 +2 -2
- 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/verified-tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/verified-tokens.js +34 -9
- package/dist/apps/data/routes/verified-tokens.js.map +1 -1
- package/dist/apps/data/routes/webhooks.d.ts +2 -2
- package/dist/apps/data/routes/webhooks.js +14 -6
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- 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 +48 -34
- 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 +73 -73
- 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 +72 -72
- 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 +57 -10
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/apps/relay/Sponsorships.js +1 -1
- package/dist/apps/relay/Sponsorships.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 +32 -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 +13 -3
- package/dist/db/tables/sponsoredTransactions.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/EdgeCache.d.ts +1 -1
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +3 -2
- package/dist/internal/EdgeCache.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/Schema.d.ts +2 -2
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +4 -2
- package/dist/internal/Schema.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 +49 -39
- 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 +14 -2
- 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/tokenlist.test.ts +15 -0
- package/src/apps/data/routes/tokenlist.ts +18 -5
- package/src/apps/data/routes/tokens.ts +5 -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/data/routes/verified-tokens.test.ts +51 -0
- package/src/apps/data/routes/verified-tokens.ts +52 -15
- package/src/apps/data/routes/webhooks.test.ts +56 -0
- package/src/apps/data/routes/webhooks.ts +17 -8
- 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 +90 -8
- package/src/apps/relay/App.ts +62 -10
- package/src/apps/relay/Sponsorships.test.ts +2 -0
- package/src/apps/relay/Sponsorships.ts +1 -1
- 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 +191 -0
- package/src/db/tables/organizations.ts +44 -3
- package/src/db/tables/sponsoredTransactions.test.ts +41 -2
- package/src/db/tables/sponsoredTransactions.ts +15 -3
- 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/EdgeCache.test.ts +18 -5
- package/src/internal/EdgeCache.ts +4 -2
- 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
- package/src/internal/Schema.test.ts +13 -0
- package/src/internal/Schema.ts +14 -7
|
@@ -450,7 +450,7 @@ describe.skipIf(runtime.mode !== 'localnet')('behavior: sponsorship', () => {
|
|
|
450
450
|
`)
|
|
451
451
|
})
|
|
452
452
|
|
|
453
|
-
test('behavior: non-mainnet production keys record billable rows', async () => {
|
|
453
|
+
test('behavior: non-mainnet production keys record non-billable rows', async () => {
|
|
454
454
|
const db = TestApp.database()
|
|
455
455
|
const app = TestApp.create({
|
|
456
456
|
auth: { keys },
|
|
@@ -466,7 +466,7 @@ describe.skipIf(runtime.mode !== 'localnet')('behavior: sponsorship', () => {
|
|
|
466
466
|
expect(body.error).toBeUndefined()
|
|
467
467
|
|
|
468
468
|
const [row] = await SponsoredTransactions.listPending(db)
|
|
469
|
-
expect(row?.billable).toBe(
|
|
469
|
+
expect(row?.billable).toBe(false)
|
|
470
470
|
expect(row?.environment).toMatchInlineSnapshot(`"production"`)
|
|
471
471
|
})
|
|
472
472
|
|
|
@@ -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.
|
|
@@ -193,7 +238,7 @@ function middleware(options: middleware.Options) {
|
|
|
193
238
|
|
|
194
239
|
const input = {
|
|
195
240
|
apiKeyId: attribution.apiKeyId,
|
|
196
|
-
billable: attribution.environment === 'production',
|
|
241
|
+
billable: mainnet && attribution.environment === 'production',
|
|
197
242
|
chainId: event.chainId,
|
|
198
243
|
environment: attribution.environment,
|
|
199
244
|
orgId: attribution.orgId,
|
|
@@ -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
|
|
|
@@ -4,6 +4,7 @@ import type { Client } from 'viem'
|
|
|
4
4
|
|
|
5
5
|
import * as Runtime from '../../../test/runtime.js'
|
|
6
6
|
import * as Db from '../../db/Db.js'
|
|
7
|
+
import * as Organizations from '../../db/tables/organizations.js'
|
|
7
8
|
import * as SponsoredTransactions from '../../db/tables/sponsoredTransactions.js'
|
|
8
9
|
import * as Sponsorships from './Sponsorships.js'
|
|
9
10
|
|
|
@@ -269,6 +270,7 @@ describe('reconcile', () => {
|
|
|
269
270
|
test('behavior: recovers recently failed intents by sign payload', async () => {
|
|
270
271
|
const db = create()
|
|
271
272
|
await db.migrate()
|
|
273
|
+
await Organizations.create(db, { id: 'org_1', name: 'One' })
|
|
272
274
|
const { transactionHash: _, ...rest } = input
|
|
273
275
|
const intent = await SponsoredTransactions.upsert(db, { ...rest, signPayload })
|
|
274
276
|
await SponsoredTransactions.fail(db, intent.id, '2026-01-01T00:00:00.000Z')
|
|
@@ -126,7 +126,7 @@ export async function reconcile(db: Db.Db, options: reconcile.Options): Promise<
|
|
|
126
126
|
const {
|
|
127
127
|
batchSize = 100,
|
|
128
128
|
candidateCap = 500,
|
|
129
|
-
failedIntentTtlMs =
|
|
129
|
+
failedIntentTtlMs = SponsoredTransactions.failedIntentRecoveryTtlMs,
|
|
130
130
|
feePayer,
|
|
131
131
|
getClient,
|
|
132
132
|
getTidx,
|
package/src/db/Db.test.ts
CHANGED
|
@@ -17,7 +17,7 @@ describe('postgres', () => {
|
|
|
17
17
|
await db.close()
|
|
18
18
|
})
|
|
19
19
|
|
|
20
|
-
test('behavior: legacy invitation inserts
|
|
20
|
+
test('behavior: legacy invitation inserts do not grant early access', async () => {
|
|
21
21
|
const db = create()
|
|
22
22
|
await db.migrate()
|
|
23
23
|
await Organizations.create(db, { id: 'org_legacy', name: 'Legacy' })
|
|
@@ -30,7 +30,7 @@ describe('postgres', () => {
|
|
|
30
30
|
)
|
|
31
31
|
`.execute(db.kysely)
|
|
32
32
|
|
|
33
|
-
expect((await Invitations.get(db, 'inv_legacy'))?.grantsEarlyAccess).toBe(
|
|
33
|
+
expect((await Invitations.get(db, 'inv_legacy'))?.grantsEarlyAccess).toBe(false)
|
|
34
34
|
await db.close()
|
|
35
35
|
})
|
|
36
36
|
|
package/src/db/Db.ts
CHANGED
|
@@ -13,6 +13,7 @@ import * as pg from 'pg'
|
|
|
13
13
|
import type * as AdminAuditLogs from './tables/adminAuditLogs.js'
|
|
14
14
|
import type * as BillingSettings from './tables/billingSettings.js'
|
|
15
15
|
import type * as EarlyAccess from './tables/earlyAccess.js'
|
|
16
|
+
import type * as EnabledBillingSources from './tables/enabledBillingSources.js'
|
|
16
17
|
import type * as Invitations from './tables/invitations.js'
|
|
17
18
|
import type * as InviteLinks from './tables/inviteLinks.js'
|
|
18
19
|
import type * as Memberships from './tables/memberships.js'
|
|
@@ -35,6 +36,7 @@ export type Database = {
|
|
|
35
36
|
admin_audit_logs: AdminAuditLogs.Table
|
|
36
37
|
billing_settings: BillingSettings.Table
|
|
37
38
|
early_access: EarlyAccess.Table
|
|
39
|
+
enabled_billing_sources: EnabledBillingSources.Table
|
|
38
40
|
invitations: Invitations.Table
|
|
39
41
|
invite_link_redemptions: InviteLinks.RedemptionTable
|
|
40
42
|
invite_links: InviteLinks.Table
|
|
@@ -740,6 +742,34 @@ export const migrations: readonly Migration[] = [
|
|
|
740
742
|
name: '0069_invite_links_allowed_email_domains',
|
|
741
743
|
up: (db) => raw(db, sql`ALTER TABLE invite_links ADD COLUMN IF NOT EXISTS allowed_email_domains jsonb`), // prettier-ignore
|
|
742
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
|
+
},
|
|
743
773
|
]
|
|
744
774
|
|
|
745
775
|
/**
|
package/src/db/Schema.ts
CHANGED
|
@@ -66,6 +66,21 @@ export const EarlyAccessEntry = z.object({
|
|
|
66
66
|
/** Columns of the `early_access` table. */
|
|
67
67
|
export type EarlyAccessEntry = z.output<typeof EarlyAccessEntry>
|
|
68
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
|
+
|
|
69
84
|
/** A pending, expiring offer of organization membership. */
|
|
70
85
|
export const Invitation = z.object({
|
|
71
86
|
acceptedAt: z
|