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
|
@@ -3,6 +3,7 @@ import { sql, type Selectable } from 'kysely'
|
|
|
3
3
|
import * as Id from '../../internal/Id.js'
|
|
4
4
|
import type * as Db from '../Db.js'
|
|
5
5
|
import type * as db_Schema from '../Schema.js'
|
|
6
|
+
import type * as EnabledBillingSources from './enabledBillingSources.js'
|
|
6
7
|
import type * as Memberships from './memberships.js'
|
|
7
8
|
|
|
8
9
|
/** Columns of the `organizations` table, derived from `Schema.Organization`. */
|
|
@@ -50,8 +51,8 @@ export declare namespace create {
|
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
/**
|
|
53
|
-
* Inserts an organization
|
|
54
|
-
*
|
|
54
|
+
* Inserts an organization, its owner membership, and initial billing sources
|
|
55
|
+
* atomically.
|
|
55
56
|
*
|
|
56
57
|
* @param db - The database.
|
|
57
58
|
* @param input - The organization to insert.
|
|
@@ -82,6 +83,18 @@ export function createOwned(db: Db.Db, input: createOwned.Input): Promise<Record
|
|
|
82
83
|
userId: input.userId,
|
|
83
84
|
})
|
|
84
85
|
.execute()
|
|
86
|
+
if (input.enabledBillingSources?.length)
|
|
87
|
+
await trx
|
|
88
|
+
.insertInto('enabled_billing_sources')
|
|
89
|
+
.values(
|
|
90
|
+
input.enabledBillingSources.map((source) => ({
|
|
91
|
+
createdAt: now,
|
|
92
|
+
createdBy: input.userId,
|
|
93
|
+
orgId: record.id,
|
|
94
|
+
source,
|
|
95
|
+
})),
|
|
96
|
+
)
|
|
97
|
+
.execute()
|
|
85
98
|
return record
|
|
86
99
|
})
|
|
87
100
|
}
|
|
@@ -89,6 +102,8 @@ export function createOwned(db: Db.Db, input: createOwned.Input): Promise<Record
|
|
|
89
102
|
export declare namespace createOwned {
|
|
90
103
|
/** Fields accepted when inserting a user-owned organization. */
|
|
91
104
|
type Input = {
|
|
105
|
+
/** Billing sources enabled when the organization is created. */
|
|
106
|
+
enabledBillingSources?: readonly EnabledBillingSources.Source[] | undefined
|
|
92
107
|
/** Human-readable organization name. */
|
|
93
108
|
name: string
|
|
94
109
|
/** Owning user id (`usr_…`), granted the `owner` membership. */
|
|
@@ -121,6 +136,7 @@ export function deleteOrganization(db: Db.Db, id: string): Promise<boolean> {
|
|
|
121
136
|
.where('ownerId', '=', id)
|
|
122
137
|
.execute()
|
|
123
138
|
await trx.deleteFrom('billing_settings').where('orgId', '=', id).execute()
|
|
139
|
+
await trx.deleteFrom('enabled_billing_sources').where('orgId', '=', id).execute()
|
|
124
140
|
await trx.deleteFrom('invitations').where('orgId', '=', id).execute()
|
|
125
141
|
await trx.deleteFrom('invite_link_redemptions').where('orgId', '=', id).execute()
|
|
126
142
|
await trx.deleteFrom('invite_links').where('orgId', '=', id).execute()
|
|
@@ -3,6 +3,8 @@ import { KeyAuthorization, Transaction as core_Transaction, TxEnvelopeTempo } fr
|
|
|
3
3
|
import { type Client, BaseError } from 'viem'
|
|
4
4
|
import * as z from 'zod/mini'
|
|
5
5
|
|
|
6
|
+
import * as ApiResponse from '../../internal/Response.js'
|
|
7
|
+
|
|
6
8
|
export function resolveChainId(value: unknown) {
|
|
7
9
|
if (typeof value === 'number') return value
|
|
8
10
|
if (typeof value === 'bigint') return Number(value)
|
|
@@ -97,24 +99,38 @@ export function rpcErrorJson(request: RpcRequest.RpcRequest, error: unknown) {
|
|
|
97
99
|
{ request },
|
|
98
100
|
)
|
|
99
101
|
|
|
100
|
-
if (
|
|
102
|
+
if (error instanceof z.core.$ZodError)
|
|
101
103
|
return RpcResponse.from(
|
|
102
104
|
{
|
|
103
105
|
error: {
|
|
104
106
|
code: RpcResponse.InvalidParamsError.code,
|
|
105
|
-
|
|
107
|
+
data: {
|
|
108
|
+
code: 'invalid_params',
|
|
109
|
+
issues: ApiResponse.validationDetails(error.issues.slice(0, 10)),
|
|
110
|
+
},
|
|
111
|
+
message: 'Invalid params.',
|
|
106
112
|
},
|
|
107
113
|
},
|
|
108
114
|
{ request },
|
|
109
115
|
)
|
|
110
116
|
|
|
111
117
|
const inner = resolveError(error)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
118
|
+
if (inner.code === undefined || inner.message === undefined)
|
|
119
|
+
return RpcResponse.from(
|
|
120
|
+
{
|
|
121
|
+
error: {
|
|
122
|
+
code: RpcResponse.InternalError.code,
|
|
123
|
+
data: { code: 'internal_error' },
|
|
124
|
+
message: 'Internal error',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{ request },
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
const { code, data, message } = inner
|
|
115
131
|
return RpcResponse.from(
|
|
116
132
|
{
|
|
117
|
-
error: { code, message, ...(data ? {
|
|
133
|
+
error: { code, message, ...(data === undefined ? {} : { data }) },
|
|
118
134
|
},
|
|
119
135
|
{ request },
|
|
120
136
|
)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import * as z from 'zod/mini'
|
|
2
|
+
|
|
3
|
+
import * as Relay from '../../test/Relay.js'
|
|
4
|
+
|
|
5
|
+
describe('request errors', () => {
|
|
6
|
+
test('returns Parse error for invalid JSON', async () => {
|
|
7
|
+
const server = await Relay.createServer((await Relay.relay()).listener)
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const response = await fetch(server.url, {
|
|
11
|
+
body: '{',
|
|
12
|
+
headers: { 'Content-Type': 'application/json' },
|
|
13
|
+
method: 'POST',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
expect(await response.json()).toMatchInlineSnapshot(`
|
|
17
|
+
{
|
|
18
|
+
"error": {
|
|
19
|
+
"code": -32700,
|
|
20
|
+
"message": "Parse error",
|
|
21
|
+
},
|
|
22
|
+
"id": null,
|
|
23
|
+
"jsonrpc": "2.0",
|
|
24
|
+
}
|
|
25
|
+
`)
|
|
26
|
+
} finally {
|
|
27
|
+
await server.closeAsync()
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test.each([
|
|
32
|
+
['non-object request', 'null'],
|
|
33
|
+
['invalid version', JSON.stringify({ id: 1, jsonrpc: '1.0', method: 'eth_chainId' })],
|
|
34
|
+
['empty batch', '[]'],
|
|
35
|
+
])('returns Invalid Request for %s', async (_name, body) => {
|
|
36
|
+
const server = await Relay.createServer((await Relay.relay()).listener)
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const response = await fetch(server.url, {
|
|
40
|
+
body,
|
|
41
|
+
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
method: 'POST',
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
expect(await response.json()).toMatchInlineSnapshot(`
|
|
46
|
+
{
|
|
47
|
+
"error": {
|
|
48
|
+
"code": -32600,
|
|
49
|
+
"message": "Invalid Request",
|
|
50
|
+
},
|
|
51
|
+
"id": null,
|
|
52
|
+
"jsonrpc": "2.0",
|
|
53
|
+
}
|
|
54
|
+
`)
|
|
55
|
+
} finally {
|
|
56
|
+
await server.closeAsync()
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('returns one Invalid Request error for a malformed batch member', async () => {
|
|
61
|
+
const server = await Relay.createServer(
|
|
62
|
+
(
|
|
63
|
+
await Relay.relay({
|
|
64
|
+
getClient: () => {
|
|
65
|
+
throw new Error('transport credential must not leak')
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
).listener,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const response = await fetch(server.url, {
|
|
73
|
+
body: JSON.stringify([
|
|
74
|
+
{ id: 1, jsonrpc: '2.0', method: 'eth_chainId' },
|
|
75
|
+
{ id: 2, jsonrpc: '1.0', method: 'eth_chainId' },
|
|
76
|
+
]),
|
|
77
|
+
headers: { 'Content-Type': 'application/json' },
|
|
78
|
+
method: 'POST',
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
expect(await response.json()).toMatchInlineSnapshot(`
|
|
82
|
+
[
|
|
83
|
+
{
|
|
84
|
+
"error": {
|
|
85
|
+
"code": -32603,
|
|
86
|
+
"data": {
|
|
87
|
+
"code": "internal_error",
|
|
88
|
+
},
|
|
89
|
+
"message": "Internal error",
|
|
90
|
+
},
|
|
91
|
+
"id": 1,
|
|
92
|
+
"jsonrpc": "2.0",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"error": {
|
|
96
|
+
"code": -32600,
|
|
97
|
+
"message": "Invalid Request",
|
|
98
|
+
},
|
|
99
|
+
"id": null,
|
|
100
|
+
"jsonrpc": "2.0",
|
|
101
|
+
},
|
|
102
|
+
]
|
|
103
|
+
`)
|
|
104
|
+
} finally {
|
|
105
|
+
await server.closeAsync()
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('returns bounded structured issues for invalid params', async () => {
|
|
110
|
+
const schema = z.object({ value: z.string() })
|
|
111
|
+
const server = await Relay.createServer(
|
|
112
|
+
(
|
|
113
|
+
await Relay.relay({
|
|
114
|
+
onRequest: async () => {
|
|
115
|
+
schema.parse({ value: 1 })
|
|
116
|
+
},
|
|
117
|
+
})
|
|
118
|
+
).listener,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const response = await fetch(server.url, {
|
|
123
|
+
body: JSON.stringify({ id: 1, jsonrpc: '2.0', method: 'eth_chainId' }),
|
|
124
|
+
headers: { 'Content-Type': 'application/json' },
|
|
125
|
+
method: 'POST',
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
expect(await response.json()).toMatchInlineSnapshot(`
|
|
129
|
+
{
|
|
130
|
+
"error": {
|
|
131
|
+
"code": -32602,
|
|
132
|
+
"data": {
|
|
133
|
+
"code": "invalid_params",
|
|
134
|
+
"issues": [
|
|
135
|
+
{
|
|
136
|
+
"message": "Invalid input",
|
|
137
|
+
"path": [
|
|
138
|
+
"value",
|
|
139
|
+
],
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
"message": "Invalid params.",
|
|
144
|
+
},
|
|
145
|
+
"id": 1,
|
|
146
|
+
"jsonrpc": "2.0",
|
|
147
|
+
}
|
|
148
|
+
`)
|
|
149
|
+
} finally {
|
|
150
|
+
await server.closeAsync()
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
})
|
package/src/handlers/relay.ts
CHANGED
|
@@ -639,20 +639,30 @@ export function relay(options: relay.Options = {}): Handler {
|
|
|
639
639
|
|
|
640
640
|
async function handlePost(c: { req: { raw: Request; param: (key: string) => string } }) {
|
|
641
641
|
const chainId = Utils.resolveChainId(c.req.param('chainId'))
|
|
642
|
-
const body = await c.req.raw.json()
|
|
642
|
+
const body = await c.req.raw.json().catch(() => undefined)
|
|
643
|
+
if (body === undefined) return Response.json(rpcProtocolError('parse'))
|
|
644
|
+
|
|
643
645
|
const isBatch = Array.isArray(body)
|
|
644
646
|
|
|
645
647
|
if (!isBatch) {
|
|
648
|
+
if (!isRpcRequest(body)) return Response.json(rpcProtocolError('invalid_request'))
|
|
646
649
|
const request = RpcRequest.from(body as never) as RpcRequest.RpcRequest<RpcSchema.Ox>
|
|
647
|
-
|
|
650
|
+
const response = await handleRequest(request, { chainId }).catch((error) =>
|
|
651
|
+
Utils.rpcErrorJson(request, error),
|
|
652
|
+
)
|
|
653
|
+
return Response.json(response)
|
|
648
654
|
}
|
|
649
655
|
|
|
656
|
+
if (body.length === 0) return Response.json(rpcProtocolError('invalid_request'))
|
|
657
|
+
|
|
650
658
|
const responses = await Promise.all(
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
659
|
+
body.map((item) => {
|
|
660
|
+
if (!isRpcRequest(item)) return Promise.resolve(rpcProtocolError('invalid_request'))
|
|
661
|
+
return handleRequest(
|
|
662
|
+
RpcRequest.from(item as never) as RpcRequest.RpcRequest<RpcSchema.Ox>,
|
|
663
|
+
{ chainId },
|
|
664
|
+
).catch((error) => Utils.rpcErrorJson(item as RpcRequest.RpcRequest, error))
|
|
665
|
+
}),
|
|
656
666
|
)
|
|
657
667
|
return Response.json(responses)
|
|
658
668
|
}
|
|
@@ -663,6 +673,31 @@ export function relay(options: relay.Options = {}): Handler {
|
|
|
663
673
|
return router
|
|
664
674
|
}
|
|
665
675
|
|
|
676
|
+
function isRpcRequest(value: unknown): value is Record<string, unknown> {
|
|
677
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false
|
|
678
|
+
const request = value as Record<string, unknown>
|
|
679
|
+
if (request['jsonrpc'] !== '2.0' || typeof request['method'] !== 'string') return false
|
|
680
|
+
|
|
681
|
+
const id = request['id']
|
|
682
|
+
if (id !== undefined && id !== null && typeof id !== 'number' && typeof id !== 'string')
|
|
683
|
+
return false
|
|
684
|
+
|
|
685
|
+
const params = request['params']
|
|
686
|
+
if (params === undefined || Array.isArray(params)) return true
|
|
687
|
+
return Boolean(params) && typeof params === 'object'
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function rpcProtocolError(type: 'invalid_request' | 'parse') {
|
|
691
|
+
return {
|
|
692
|
+
error:
|
|
693
|
+
type === 'parse'
|
|
694
|
+
? { code: RpcResponse.ParseError.code, message: 'Parse error' }
|
|
695
|
+
: { code: RpcResponse.InvalidRequestError.code, message: 'Invalid Request' },
|
|
696
|
+
id: null,
|
|
697
|
+
jsonrpc: '2.0',
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
666
701
|
function defaultGetClient(options: { apiKey?: string | undefined }) {
|
|
667
702
|
const clients = new Map<number, Client>()
|
|
668
703
|
for (const chain of defaultChains) {
|
|
@@ -671,7 +671,7 @@ describe('require', () => {
|
|
|
671
671
|
headers: { 'tempo-api-key': sandboxToken },
|
|
672
672
|
})
|
|
673
673
|
expect(response.status).toBe(200)
|
|
674
|
-
// The public ceiling (60), not the API-key default (
|
|
674
|
+
// The public ceiling (60), not the API-key default (10,000).
|
|
675
675
|
expect(response.headers.get('RateLimit-Limit')).toMatchInlineSnapshot(`"60"`)
|
|
676
676
|
})
|
|
677
677
|
|
|
@@ -684,8 +684,8 @@ describe('require', () => {
|
|
|
684
684
|
headers: { 'tempo-api-key': sandboxToken },
|
|
685
685
|
})
|
|
686
686
|
expect(response.status).toBe(200)
|
|
687
|
-
// The API-key default (
|
|
688
|
-
expect(response.headers.get('RateLimit-Limit')).toMatchInlineSnapshot(`"
|
|
687
|
+
// The API-key default (10,000), not the public ceiling (60).
|
|
688
|
+
expect(response.headers.get('RateLimit-Limit')).toMatchInlineSnapshot(`"10000"`)
|
|
689
689
|
})
|
|
690
690
|
|
|
691
691
|
test('throttles a sandbox key to the public quota when billing is unknown', async () => {
|
package/src/internal/Auth.ts
CHANGED
|
@@ -16,8 +16,6 @@ import * as ApiKey from '../ApiKey.js'
|
|
|
16
16
|
import * as ApiKeys from '../ApiKeys.js'
|
|
17
17
|
import * as Db from '../db/Db.js'
|
|
18
18
|
import type * as Store from './Store.js'
|
|
19
|
-
import * as EarlyAccess from '../db/tables/earlyAccess.js'
|
|
20
|
-
import * as Invitations from '../db/tables/invitations.js'
|
|
21
19
|
import * as Memberships from '../db/tables/memberships.js'
|
|
22
20
|
import * as Metadata from '../apps/metadata.js'
|
|
23
21
|
import * as OpenApi from './OpenApi.js'
|
|
@@ -32,6 +30,7 @@ import * as Viem from './Viem.js'
|
|
|
32
30
|
|
|
33
31
|
const policySymbol = Symbol('tempo-api.auth.policy')
|
|
34
32
|
const mppManagementSymbol = Symbol('tempo-api.auth.mpp-management')
|
|
33
|
+
const defaultApiKeyRateLimit = { perMinute: 10_000 } satisfies RateLimit.Limit
|
|
35
34
|
const defaultMppRateLimit = { perMinute: 100 } satisfies RateLimit.Limit
|
|
36
35
|
|
|
37
36
|
/** Auth context consumed by Tempo API handlers. */
|
|
@@ -159,8 +158,6 @@ export type PolicyOverride = {
|
|
|
159
158
|
| undefined
|
|
160
159
|
/** Session lane: `true` allows signed-in wallet sessions, `false` disables. */
|
|
161
160
|
session?: boolean | undefined
|
|
162
|
-
/** Temporary early-access gate: `true` rejects sessions without early access ({@link tmp_earlyAccess}). TODO: remove after GA. */
|
|
163
|
-
tmp_earlyAccess?: boolean | undefined
|
|
164
161
|
}
|
|
165
162
|
|
|
166
163
|
/** Authenticated or anonymous caller. */
|
|
@@ -229,8 +226,6 @@ export type Variables = {
|
|
|
229
226
|
principal?: Principal | undefined
|
|
230
227
|
/** Project resolved by {@link ensureProject} for the current request. */
|
|
231
228
|
project?: Projects.Record | undefined
|
|
232
|
-
/** Temporary early-access gate toggle; unset or false grants everyone early access. TODO: remove after GA. */
|
|
233
|
-
tmp_earlyAccess?: boolean | undefined
|
|
234
229
|
}
|
|
235
230
|
|
|
236
231
|
/** Reads the authenticated principal from a Hono context. */
|
|
@@ -385,42 +380,6 @@ export function ensureProjectError(c: hono_Context) {
|
|
|
385
380
|
})
|
|
386
381
|
}
|
|
387
382
|
|
|
388
|
-
/** Whether the caller can grant early access, independent of the temporary route gate. TODO: remove after GA. */
|
|
389
|
-
export async function tmp_earlyAccessGrant(c: hono_Context): Promise<boolean> {
|
|
390
|
-
const principal = getPrincipal(c)
|
|
391
|
-
// Trust chain: `management:write` keys are only mintable by early-access owners.
|
|
392
|
-
if (principal?.type === 'api_key' || principal?.type === 'super_admin') return true
|
|
393
|
-
if (principal?.type !== 'session' || !principal.email) return false
|
|
394
|
-
const email = principal.email.trim().toLowerCase()
|
|
395
|
-
const db = Db.get(contextDb(c))
|
|
396
|
-
if (await EarlyAccess.matches(db, email)) return true
|
|
397
|
-
return Invitations.hasEarlyAccessGrant(db, email)
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/** Whether the caller has effective early access, including a dormant temporary gate. TODO: remove after GA. */
|
|
401
|
-
export async function tmp_earlyAccess(c: hono_Context): Promise<boolean> {
|
|
402
|
-
const enabled = (c.get as (key: string) => unknown)('tmp_earlyAccess') as boolean | undefined
|
|
403
|
-
if (!enabled) return true
|
|
404
|
-
return tmp_earlyAccessGrant(c)
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Always false at runtime, but typed as `boolean` so handlers can include a
|
|
409
|
-
* never-reached early-access-error branch in Hono's inferred response union
|
|
410
|
-
* (the `tmp_earlyAccess` policy flag produces this `403` before the handler
|
|
411
|
-
* runs). TODO: remove after GA.
|
|
412
|
-
*/
|
|
413
|
-
export const tmp_narrowEarlyAccess = false as boolean
|
|
414
|
-
|
|
415
|
-
/** Typed early-access error response for Hono client inference; also the live gate rejection. TODO: remove after GA. */
|
|
416
|
-
export function tmp_ensureEarlyAccessError(c: hono_Context) {
|
|
417
|
-
return Response.error(c, {
|
|
418
|
-
code: 'early_access_required',
|
|
419
|
-
message: 'Requires early access. Ask a teammate with access to invite you by email, or contact Tempo.', // prettier-ignore
|
|
420
|
-
status: 403,
|
|
421
|
-
})
|
|
422
|
-
}
|
|
423
|
-
|
|
424
383
|
/** Reads the caller's membership resolved by the organization or project guard; `undefined` for API keys and `super_admin`. */
|
|
425
384
|
export function membership<environment extends Environment>(
|
|
426
385
|
c: hono_Context<environment>,
|
|
@@ -629,9 +588,6 @@ export function middleware<
|
|
|
629
588
|
if (management && (!auth.mpp || !endpointPolicy.mpp)) return next()
|
|
630
589
|
|
|
631
590
|
c.set('auth', auth)
|
|
632
|
-
// Consumed by `tmp_earlyAccess`; absent (or false) leaves the gate dormant.
|
|
633
|
-
// TODO: remove after GA.
|
|
634
|
-
c.set('tmp_earlyAccess', options.tmp_earlyAccess)
|
|
635
591
|
return require<environment>(endpointPolicy)(c, next)
|
|
636
592
|
})
|
|
637
593
|
}
|
|
@@ -687,12 +643,6 @@ export declare namespace middleware {
|
|
|
687
643
|
* checks — before the key lookup runs. Omit to disable the entry entirely.
|
|
688
644
|
*/
|
|
689
645
|
superAdmin?: { secret: string } | undefined
|
|
690
|
-
/**
|
|
691
|
-
* Temporary invite-only gate for routes flagged via {@link policy}.
|
|
692
|
-
* Allowlist entries live in the `early_access` table (admin dash); unset
|
|
693
|
-
* grants everyone early access. TODO: remove after GA.
|
|
694
|
-
*/
|
|
695
|
-
tmp_earlyAccess?: boolean | undefined
|
|
696
646
|
}
|
|
697
647
|
|
|
698
648
|
/** Resolve-cache tuning for API-key resolution. */
|
|
@@ -1196,7 +1146,6 @@ function resolvePolicy(
|
|
|
1196
1146
|
let mpp = base.mpp
|
|
1197
1147
|
let public_ = base.public
|
|
1198
1148
|
let session = base.session
|
|
1199
|
-
let tmp_earlyAccess = base.tmp_earlyAccess
|
|
1200
1149
|
|
|
1201
1150
|
for (const override of overrides) {
|
|
1202
1151
|
if (override === false) return undefined
|
|
@@ -1205,7 +1154,6 @@ function resolvePolicy(
|
|
|
1205
1154
|
mpp = resolveMppLane(mpp, override.mpp, defaults.mpp)
|
|
1206
1155
|
public_ = resolvePublicLane(public_, override.public, defaults.public)
|
|
1207
1156
|
session = resolveSessionLane(session, override.session, defaults.session)
|
|
1208
|
-
tmp_earlyAccess = override.tmp_earlyAccess ?? tmp_earlyAccess
|
|
1209
1157
|
}
|
|
1210
1158
|
|
|
1211
1159
|
// The OpenAPI summary/description rides along as the payment-challenge
|
|
@@ -1221,7 +1169,6 @@ function resolvePolicy(
|
|
|
1221
1169
|
...(mpp ? { mpp } : {}),
|
|
1222
1170
|
...(public_ ? { public: public_ } : {}),
|
|
1223
1171
|
...(session ? { session } : {}),
|
|
1224
|
-
...(tmp_earlyAccess ? { tmp_earlyAccess } : {}),
|
|
1225
1172
|
}
|
|
1226
1173
|
}
|
|
1227
1174
|
|
|
@@ -1444,12 +1391,6 @@ export function require<environment extends Environment = Environment>(
|
|
|
1444
1391
|
)
|
|
1445
1392
|
return Response.unsupportedChainId(c, chainId, supportedChainIds)
|
|
1446
1393
|
const proceed = async () => {
|
|
1447
|
-
// Runs after principal resolution, so api_key and super_admin pass by
|
|
1448
|
-
// type; only gated sessions are rejected. TODO: remove after GA.
|
|
1449
|
-
if (policy.tmp_earlyAccess && !(await tmp_earlyAccess(c))) {
|
|
1450
|
-
c.res = tmp_ensureEarlyAccessError(c)
|
|
1451
|
-
return
|
|
1452
|
-
}
|
|
1453
1394
|
await next()
|
|
1454
1395
|
// Query credential responses must not enter shared caches.
|
|
1455
1396
|
const cacheControl = c.res.headers.get('Cache-Control')?.toLowerCase()
|
|
@@ -1685,8 +1626,6 @@ export declare namespace require {
|
|
|
1685
1626
|
| undefined
|
|
1686
1627
|
/** Session access policy: `true` allows signed-in wallet sessions. */
|
|
1687
1628
|
session?: boolean | undefined
|
|
1688
|
-
/** Temporary early-access gate for this route; see {@link tmp_earlyAccess}. TODO: remove after GA. */
|
|
1689
|
-
tmp_earlyAccess?: boolean | undefined
|
|
1690
1629
|
}
|
|
1691
1630
|
}
|
|
1692
1631
|
|
|
@@ -1782,7 +1721,8 @@ function resolveApiKeyLimit(options: resolveApiKeyLimit.Options): RateLimit.Limi
|
|
|
1782
1721
|
policy?.rateLimit ?? // 2. route override (protective cap)
|
|
1783
1722
|
apiKey.rateLimits?.[Scope.wildcard] ?? // 3. per-key default
|
|
1784
1723
|
auth.apiKey?.rateLimits?.[scope] ?? // 4. config per-scope
|
|
1785
|
-
auth.apiKey?.rateLimits?.[Scope.wildcard] ??
|
|
1724
|
+
auth.apiKey?.rateLimits?.[Scope.wildcard] ?? // 5. config default
|
|
1725
|
+
defaultApiKeyRateLimit // 6. hardcoded fallback
|
|
1786
1726
|
)
|
|
1787
1727
|
}
|
|
1788
1728
|
|
|
@@ -40,16 +40,29 @@ describe('listQuotes', () => {
|
|
|
40
40
|
expect(seen?.candidate.id).toBe('base-usdc-tempo-usdce-relay')
|
|
41
41
|
expect(quotes).toHaveLength(1)
|
|
42
42
|
expect(quotes[0]).toMatchObject({
|
|
43
|
+
destinationAmount: {
|
|
44
|
+
baseUnits: '990000',
|
|
45
|
+
currency: 'USD',
|
|
46
|
+
decimals: 6,
|
|
47
|
+
formatted: '0.99',
|
|
48
|
+
},
|
|
49
|
+
destinationAmountMin: {
|
|
50
|
+
baseUnits: '980000',
|
|
51
|
+
currency: 'USD',
|
|
52
|
+
decimals: 6,
|
|
53
|
+
formatted: '0.98',
|
|
54
|
+
},
|
|
43
55
|
id: 'base-usdc-tempo-usdce-relay',
|
|
44
56
|
quality: {
|
|
45
57
|
liquiditySource: 'providerQuote',
|
|
46
58
|
tier: 'liquid',
|
|
47
59
|
},
|
|
48
|
-
quote: {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
60
|
+
quote: { sampledAt: '2026-01-01T00:00:00Z' },
|
|
61
|
+
sourceAmount: {
|
|
62
|
+
baseUnits: '1000000',
|
|
63
|
+
currency: 'USD',
|
|
64
|
+
decimals: 6,
|
|
65
|
+
formatted: '1',
|
|
53
66
|
},
|
|
54
67
|
})
|
|
55
68
|
expect(attempts.map(stableAttempt)).toEqual([{ outcome: 'available', provider: 'relay' }])
|