tapimo 0.12.0 → 0.13.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 +5561 -5026
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +38 -3
- package/dist/App.js.map +1 -1
- package/dist/Scope.d.ts +2 -0
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +17 -0
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +123 -74
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +80 -0
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +54 -0
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/earn-vaults.d.ts +15 -15
- package/dist/admin/apps/earn-vaults.js +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +81 -81
- 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 +151 -151
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/apps/Zones.d.ts +16 -16
- package/dist/apps/Zones.js +4 -4
- package/dist/apps/data/App.js +18 -18
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/ZoneSelection.d.ts +8 -8
- package/dist/apps/data/ZoneSelection.d.ts.map +1 -1
- package/dist/apps/data/ZoneSelection.js +1 -1
- package/dist/apps/data/ZoneSelection.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +588 -584
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +1561 -1239
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +26 -26
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +43 -16
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/blocks.d.ts +44 -44
- package/dist/apps/data/routes/blocks.d.ts.map +1 -1
- package/dist/apps/data/routes/blocks.js +21 -11
- package/dist/apps/data/routes/blocks.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +3 -3
- package/dist/apps/data/routes/earn.d.ts +18 -18
- package/dist/apps/data/routes/earn.js +13 -13
- package/dist/apps/data/routes/exchanges.d.ts +145 -145
- package/dist/apps/data/routes/fee-amm.d.ts +2 -2
- package/dist/apps/data/routes/receipts.d.ts +155 -55
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +28 -10
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +13 -13
- package/dist/apps/data/routes/tokens.d.ts +98 -98
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +51 -20
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +7 -2
- package/dist/apps/data/routes/transactions.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.js +37 -17
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +1 -1
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +18 -12
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +1 -1
- package/dist/apps/data/routes/valuation.d.ts.map +1 -1
- package/dist/apps/data/routes/valuation.js +12 -7
- package/dist/apps/data/routes/valuation.js.map +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +3 -3
- package/dist/apps/data/routes/webhooks.d.ts +11 -11
- package/dist/apps/data/routes/zones.d.ts +14 -14
- package/dist/apps/data/routes/zones.js +1 -1
- package/dist/apps/funding/App.d.ts +11 -0
- package/dist/apps/funding/App.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -1
- package/dist/apps/funding/routes/deposit-addresses.js +16 -11
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -1
- package/dist/apps/funding/routes/transfers.d.ts +60 -48
- package/dist/apps/funding/routes/transfers.d.ts.map +1 -1
- package/dist/apps/funding/routes/transfers.js +27 -19
- package/dist/apps/funding/routes/transfers.js.map +1 -1
- package/dist/apps/management/App.d.ts +4487 -3963
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +710 -159
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +236 -4
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +6 -6
- package/dist/apps/management/routes/invite-links.d.ts +42 -42
- package/dist/apps/management/routes/members.d.ts +30 -30
- package/dist/apps/management/routes/projects.d.ts +40 -40
- package/dist/apps/management/routes/usage.d.ts +24 -24
- package/dist/apps/management/routes/verified-token-requests.d.ts +1 -1
- package/dist/apps/management/routes/webhooks.d.ts +55 -55
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +12 -1
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +3 -2
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +5 -2
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/fundingCatalog.d.ts +4 -2
- package/dist/db/tables/fundingCatalog.d.ts.map +1 -1
- package/dist/db/tables/fundingCatalog.js +5 -1
- package/dist/db/tables/fundingCatalog.js.map +1 -1
- package/dist/handlers/internal/utils.d.ts.map +1 -1
- package/dist/handlers/internal/utils.js +9 -0
- package/dist/handlers/internal/utils.js.map +1 -1
- package/dist/internal/Auth.d.ts +3 -1
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +6 -3
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/EarnVaults.d.ts +3 -3
- package/dist/internal/EarnVaults.js +3 -3
- package/dist/internal/Fetch.d.ts +23 -0
- package/dist/internal/Fetch.d.ts.map +1 -0
- package/dist/internal/Fetch.js +26 -0
- package/dist/internal/Fetch.js.map +1 -0
- package/dist/internal/Log.d.ts +3 -1
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +16 -3
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +5 -3
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +13 -3
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/Response.d.ts +1 -1
- package/dist/internal/Response.d.ts.map +1 -1
- package/dist/internal/Schema.d.ts +6 -3
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +8 -2
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Store.d.ts +16 -3
- package/dist/internal/Store.d.ts.map +1 -1
- package/dist/internal/Store.js +82 -16
- package/dist/internal/Store.js.map +1 -1
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +7 -2
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Timeout.d.ts +36 -0
- package/dist/internal/Timeout.d.ts.map +1 -0
- package/dist/internal/Timeout.js +37 -0
- package/dist/internal/Timeout.js.map +1 -0
- package/dist/internal/Timing.d.ts +9 -0
- package/dist/internal/Timing.d.ts.map +1 -1
- package/dist/internal/Timing.js +7 -0
- package/dist/internal/Timing.js.map +1 -1
- package/dist/internal/WebhookDestination.d.ts.map +1 -1
- package/dist/internal/WebhookDestination.js +3 -0
- package/dist/internal/WebhookDestination.js.map +1 -1
- package/dist/internal/funding/Action.d.ts +9 -0
- package/dist/internal/funding/Action.d.ts.map +1 -1
- package/dist/internal/funding/Action.js +16 -8
- package/dist/internal/funding/Action.js.map +1 -1
- package/dist/internal/funding/Catalog.d.ts +9 -2
- package/dist/internal/funding/Catalog.d.ts.map +1 -1
- package/dist/internal/funding/Catalog.js +19 -8
- package/dist/internal/funding/Catalog.js.map +1 -1
- package/dist/internal/funding/Provider.d.ts +68 -13
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Provider.js +29 -8
- package/dist/internal/funding/Provider.js.map +1 -1
- package/dist/internal/funding/Transfer.d.ts +13 -12
- package/dist/internal/funding/Transfer.d.ts.map +1 -1
- package/dist/internal/funding/Transfer.js +23 -21
- package/dist/internal/funding/Transfer.js.map +1 -1
- package/dist/internal/funding/providers/stargate.d.ts +28 -9
- package/dist/internal/funding/providers/stargate.d.ts.map +1 -1
- package/dist/internal/funding/providers/stargate.js +251 -117
- package/dist/internal/funding/providers/stargate.js.map +1 -1
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal/index.d.ts.map +1 -1
- package/dist/internal/index.js +1 -0
- package/dist/internal/index.js.map +1 -1
- package/package.json +2 -2
- package/src/App.test.ts +100 -0
- package/src/App.ts +42 -3
- package/src/Client.test-d.ts +46 -0
- package/src/Scope.test.ts +20 -0
- package/src/Scope.ts +16 -0
- package/src/admin/apps/api-keys.test.ts +75 -0
- package/src/admin/apps/api-keys.ts +64 -0
- package/src/admin/apps/earn-vaults.test.ts +1 -1
- package/src/admin/apps/earn-vaults.ts +1 -1
- package/src/admin/ui/src/lib/api-keys.ts +19 -0
- package/src/admin/ui/src/routes/api-keys.tsx +89 -2
- package/src/admin/ui.generated.ts +189 -151
- package/src/apps/Zones.test.ts +4 -4
- package/src/apps/Zones.ts +4 -4
- package/src/apps/composition.test.ts +1 -1
- package/src/apps/data/App.test.ts +41 -0
- package/src/apps/data/App.ts +18 -18
- package/src/apps/data/ZoneSelection.test.ts +2 -2
- package/src/apps/data/ZoneSelection.ts +8 -8
- package/src/apps/data/routes/activities.test.ts +1526 -12
- package/src/apps/data/routes/activities.ts +1774 -1356
- package/src/apps/data/routes/balances.ts +133 -97
- package/src/apps/data/routes/blocks.test.ts +533 -0
- package/src/apps/data/routes/blocks.ts +35 -16
- package/src/apps/data/routes/earn.test.ts +4 -2
- package/src/apps/data/routes/earn.ts +13 -13
- package/src/apps/data/routes/earn.zone-access.test.ts +6 -6
- package/src/apps/data/routes/receipts.ts +47 -16
- package/src/apps/data/routes/tokens.test.ts +578 -0
- package/src/apps/data/routes/tokens.ts +283 -225
- package/src/apps/data/routes/transactions.ts +68 -29
- package/src/apps/data/routes/transfers.test.ts +421 -0
- package/src/apps/data/routes/transfers.ts +45 -27
- package/src/apps/data/routes/valuation.ts +94 -78
- package/src/apps/data/routes/zones.test.ts +2 -2
- package/src/apps/data/routes/zones.ts +1 -1
- package/src/apps/funding/routes/deposit-addresses.test.ts +25 -3
- package/src/apps/funding/routes/deposit-addresses.ts +17 -12
- package/src/apps/funding/routes/quotes.test.ts +5 -9
- package/src/apps/funding/routes/transfers.test.ts +395 -8
- package/src/apps/funding/routes/transfers.ts +76 -59
- package/src/apps/management/routes/api-keys.test.ts +220 -0
- package/src/apps/management/routes/api-keys.ts +280 -3
- package/src/db/Db.ts +13 -1
- package/src/db/Schema.ts +5 -2
- package/src/db/tables/earnVaults.test.ts +9 -9
- package/src/db/tables/fundingCatalog.test.ts +16 -2
- package/src/db/tables/fundingCatalog.ts +9 -3
- package/src/handlers/internal/utils.test.ts +53 -0
- package/src/handlers/internal/utils.ts +15 -0
- package/src/internal/Auth.ts +12 -4
- package/src/internal/EarnVaults.capabilities.test.ts +2 -2
- package/src/internal/EarnVaults.test.ts +1 -1
- package/src/internal/EarnVaults.ts +3 -3
- package/src/internal/Fetch.test.ts +46 -0
- package/src/internal/Fetch.ts +56 -0
- package/src/internal/Log.test.ts +21 -0
- package/src/internal/Log.ts +20 -5
- package/src/internal/OpenApi.ts +14 -4
- package/src/internal/Response.ts +1 -1
- package/src/internal/Schema.test.ts +2 -0
- package/src/internal/Schema.ts +12 -2
- package/src/internal/Store.test.ts +178 -0
- package/src/internal/Store.ts +104 -18
- package/src/internal/Tidx.test.ts +2 -1
- package/src/internal/Tidx.ts +7 -2
- package/src/internal/Timeout.test.ts +35 -0
- package/src/internal/Timeout.ts +73 -0
- package/src/internal/Timing.ts +16 -0
- package/src/internal/WebhookDestination.ts +3 -0
- package/src/internal/funding/Action.test.ts +41 -1
- package/src/internal/funding/Action.ts +69 -41
- package/src/internal/funding/Catalog.test.ts +34 -0
- package/src/internal/funding/Catalog.ts +28 -11
- package/src/internal/funding/Provider.test.ts +73 -0
- package/src/internal/funding/Provider.ts +82 -28
- package/src/internal/funding/Transfer.ts +133 -110
- package/src/internal/funding/providers/index.test.ts +4 -167
- package/src/internal/funding/providers/relay.test.ts +2 -2
- package/src/internal/funding/providers/stargate.test.ts +163 -0
- package/src/internal/funding/providers/stargate.ts +359 -134
- package/src/internal/index.test.ts +1 -0
- package/src/internal/index.ts +1 -0
|
@@ -71,8 +71,12 @@ describe('publish', () => {
|
|
|
71
71
|
catalog: head,
|
|
72
72
|
chainTokens: catalog.chainTokens,
|
|
73
73
|
chains: catalog.chains,
|
|
74
|
-
//
|
|
75
|
-
routes: catalog.routes.map((route) => ({
|
|
74
|
+
// Optional route JSON persists as null.
|
|
75
|
+
routes: catalog.routes.map((route) => ({
|
|
76
|
+
...route,
|
|
77
|
+
capabilities: null,
|
|
78
|
+
configuration: null,
|
|
79
|
+
})),
|
|
76
80
|
tokens: catalog.tokens,
|
|
77
81
|
})
|
|
78
82
|
})
|
|
@@ -87,6 +91,16 @@ describe('publish', () => {
|
|
|
87
91
|
expect((await FundingCatalog.read(db)).routes[0]?.capabilities).toStrictEqual(capabilities)
|
|
88
92
|
})
|
|
89
93
|
|
|
94
|
+
test('round-trips route configuration', async () => {
|
|
95
|
+
const db = TestApp.database()
|
|
96
|
+
const configuration = { poolAddress: `0x${'11'.repeat(20)}` }
|
|
97
|
+
await FundingCatalog.publish(db, {
|
|
98
|
+
...catalog,
|
|
99
|
+
routes: [{ ...catalog.routes[0]!, configuration }],
|
|
100
|
+
})
|
|
101
|
+
expect((await FundingCatalog.read(db)).routes[0]?.configuration).toStrictEqual(configuration)
|
|
102
|
+
})
|
|
103
|
+
|
|
90
104
|
test('replaces rows and advances the catalog version', async () => {
|
|
91
105
|
const db = TestApp.database()
|
|
92
106
|
const first = await FundingCatalog.publish(db, catalog)
|
|
@@ -28,10 +28,12 @@ export type ChainTokenTable = db_Schema.FundingChainToken
|
|
|
28
28
|
/** Columns of the `funding_routes` table. */
|
|
29
29
|
export type RouteTable = db_Schema.FundingRoute
|
|
30
30
|
|
|
31
|
-
/** A route row whose
|
|
32
|
-
export type RouteRow = Omit<db_Schema.FundingRoute, 'capabilities'> & {
|
|
31
|
+
/** A route row whose optional JSON fields may be absent in validated input. */
|
|
32
|
+
export type RouteRow = Omit<db_Schema.FundingRoute, 'capabilities' | 'configuration'> & {
|
|
33
33
|
/** Executable capability; absent and null both mean indicative-only. */
|
|
34
34
|
capabilities?: db_Schema.FundingRoute['capabilities'] | undefined
|
|
35
|
+
/** Provider-specific route configuration; absent and null are equivalent. */
|
|
36
|
+
configuration?: db_Schema.FundingRoute['configuration'] | undefined
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
/** Stored funding catalog rows and version metadata. */
|
|
@@ -117,7 +119,11 @@ export function publish(db: Db.Db, input: publish.Input): Promise<db_Schema.Fund
|
|
|
117
119
|
await tx.kysely
|
|
118
120
|
.insertInto('funding_routes')
|
|
119
121
|
.values(
|
|
120
|
-
input.routes.map((route) => ({
|
|
122
|
+
input.routes.map((route) => ({
|
|
123
|
+
...route,
|
|
124
|
+
capabilities: route.capabilities ?? null,
|
|
125
|
+
configuration: route.configuration ?? null,
|
|
126
|
+
})),
|
|
121
127
|
)
|
|
122
128
|
.execute()
|
|
123
129
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { RpcRequest } from 'ox'
|
|
2
|
+
|
|
3
|
+
import * as Utils from './utils.js'
|
|
4
|
+
|
|
5
|
+
const request = RpcRequest.from({ id: 1, method: 'eth_sendRawTransaction', params: ['0x'] })
|
|
6
|
+
|
|
7
|
+
describe('rpcErrorJson', () => {
|
|
8
|
+
test('maps upstream expired transactions to a transaction-rejected error', () => {
|
|
9
|
+
const error = {
|
|
10
|
+
cause: {
|
|
11
|
+
code: -32603,
|
|
12
|
+
message:
|
|
13
|
+
'Revm error: transaction expired: current block timestamp 1755616740 >= validBefore 1755616739',
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
expect(Utils.rpcErrorJson(request, error)).toMatchInlineSnapshot(`
|
|
18
|
+
{
|
|
19
|
+
"error": {
|
|
20
|
+
"code": -32003,
|
|
21
|
+
"data": {
|
|
22
|
+
"code": "transaction_expired",
|
|
23
|
+
},
|
|
24
|
+
"message": "Transaction expired.",
|
|
25
|
+
},
|
|
26
|
+
"id": 1,
|
|
27
|
+
"jsonrpc": "2.0",
|
|
28
|
+
}
|
|
29
|
+
`)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('preserves unrelated upstream internal errors', () => {
|
|
33
|
+
const error = {
|
|
34
|
+
code: -32603,
|
|
35
|
+
data: { reason: 'database unavailable' },
|
|
36
|
+
message: 'Revm error: database unavailable',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
expect(Utils.rpcErrorJson(request, error)).toMatchInlineSnapshot(`
|
|
40
|
+
{
|
|
41
|
+
"error": {
|
|
42
|
+
"code": -32603,
|
|
43
|
+
"data": {
|
|
44
|
+
"reason": "database unavailable",
|
|
45
|
+
},
|
|
46
|
+
"message": "Revm error: database unavailable",
|
|
47
|
+
},
|
|
48
|
+
"id": 1,
|
|
49
|
+
"jsonrpc": "2.0",
|
|
50
|
+
}
|
|
51
|
+
`)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -127,6 +127,21 @@ export function rpcErrorJson(request: RpcRequest.RpcRequest, error: unknown) {
|
|
|
127
127
|
{ request },
|
|
128
128
|
)
|
|
129
129
|
|
|
130
|
+
if (
|
|
131
|
+
inner.code === RpcResponse.InternalError.code &&
|
|
132
|
+
/^Revm error: transaction expired(?:\.|: .+)?$/.test(inner.message)
|
|
133
|
+
)
|
|
134
|
+
return RpcResponse.from(
|
|
135
|
+
{
|
|
136
|
+
error: {
|
|
137
|
+
code: RpcResponse.TransactionRejectedError.code,
|
|
138
|
+
data: { code: 'transaction_expired' },
|
|
139
|
+
message: 'Transaction expired.',
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{ request },
|
|
143
|
+
)
|
|
144
|
+
|
|
130
145
|
const { code, data, message } = inner
|
|
131
146
|
return RpcResponse.from(
|
|
132
147
|
{
|
package/src/internal/Auth.ts
CHANGED
|
@@ -676,7 +676,10 @@ export declare namespace middleware {
|
|
|
676
676
|
}
|
|
677
677
|
|
|
678
678
|
/** MPP payment options used by auth middleware. */
|
|
679
|
-
type MppOptions = Omit<
|
|
679
|
+
type MppOptions = Omit<
|
|
680
|
+
Parameters<typeof Mppx.create<readonly [ReturnType<typeof tempo.session>]>>[0],
|
|
681
|
+
'methods'
|
|
682
|
+
> & {
|
|
680
683
|
/** Paid-request quota per access principal. Defaults to 100 per minute. */
|
|
681
684
|
rateLimit?: RateLimit.Limit | undefined
|
|
682
685
|
/** Global Tempo Session options passed to the MPP method instantiator. */
|
|
@@ -1451,8 +1454,8 @@ export function require<environment extends Environment = Environment>(
|
|
|
1451
1454
|
return proceed()
|
|
1452
1455
|
}
|
|
1453
1456
|
|
|
1454
|
-
const
|
|
1455
|
-
if (!
|
|
1457
|
+
const apiKey_resolved = await auth.apiKey?.resolve(c, credential.token)
|
|
1458
|
+
if (!apiKey_resolved) {
|
|
1456
1459
|
// A bearer session token is indistinguishable from an API key at the
|
|
1457
1460
|
// header, so a failed key lookup falls through to the session lane
|
|
1458
1461
|
// before rejecting.
|
|
@@ -1470,6 +1473,10 @@ export function require<environment extends Environment = Environment>(
|
|
|
1470
1473
|
})
|
|
1471
1474
|
}
|
|
1472
1475
|
|
|
1476
|
+
const scopes = Scope.expandAliases(apiKey_resolved.scopes)
|
|
1477
|
+
const apiKey =
|
|
1478
|
+
scopes === apiKey_resolved.scopes ? apiKey_resolved : { ...apiKey_resolved, scopes }
|
|
1479
|
+
|
|
1473
1480
|
// Sandbox keys are testnet-only. A sandbox key that explicitly targets
|
|
1474
1481
|
// mainnet (`?chainId`) is rejected with a clear message rather than
|
|
1475
1482
|
// steered or downgraded to anonymous, so the caller learns their key does
|
|
@@ -1847,7 +1854,8 @@ function getRequestId(c: hono_Context) {
|
|
|
1847
1854
|
return typeof variables['requestId'] === 'string' ? variables['requestId'] : undefined
|
|
1848
1855
|
}
|
|
1849
1856
|
|
|
1850
|
-
|
|
1857
|
+
/** True when the request carries an `Authorization: Payment` MPP credential. */
|
|
1858
|
+
export function hasPaymentCredential(c: hono_Context) {
|
|
1851
1859
|
const authorization = c.req.header('authorization')
|
|
1852
1860
|
if (!authorization) return false
|
|
1853
1861
|
return !!Credential.extractPaymentScheme(authorization)
|
|
@@ -21,8 +21,8 @@ describe('deriveZoneId', () => {
|
|
|
21
21
|
test('derives IDs from mainnet and testnet Zone chain schemes', () => {
|
|
22
22
|
expect([
|
|
23
23
|
EarnVaults.deriveZoneId(421_700_006),
|
|
24
|
-
EarnVaults.deriveZoneId(
|
|
25
|
-
]).toStrictEqual([6,
|
|
24
|
+
EarnVaults.deriveZoneId(1_424_310_003),
|
|
25
|
+
]).toStrictEqual([6, 3])
|
|
26
26
|
})
|
|
27
27
|
|
|
28
28
|
test('rejects a source chain ID', () => {
|
|
@@ -245,7 +245,7 @@ describe('resolveZoneRoutes', () => {
|
|
|
245
245
|
zones: [TestEarn.zone],
|
|
246
246
|
}),
|
|
247
247
|
).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
248
|
-
`[EarnVaults.InvalidRegistryError: Zone
|
|
248
|
+
`[EarnVaults.InvalidRegistryError: Zone 1424310003 is configured more than once.]`,
|
|
249
249
|
)
|
|
250
250
|
await expect(
|
|
251
251
|
EarnVaults.resolveZoneRoutes({
|
|
@@ -167,7 +167,7 @@ export namespace schema {
|
|
|
167
167
|
.strictObject({
|
|
168
168
|
chainId: Schema.ChainId.check(
|
|
169
169
|
z.describe('Private Zone chain id.'),
|
|
170
|
-
z.meta({ examples: [
|
|
170
|
+
z.meta({ examples: [1424310003] }),
|
|
171
171
|
),
|
|
172
172
|
deploymentBlock: z
|
|
173
173
|
.number()
|
|
@@ -175,11 +175,11 @@ export namespace schema {
|
|
|
175
175
|
z.int(),
|
|
176
176
|
z.nonnegative(),
|
|
177
177
|
z.describe('Parent-chain block that deployed the Earn router.'),
|
|
178
|
-
z.meta({ examples: [
|
|
178
|
+
z.meta({ examples: [31730087] }),
|
|
179
179
|
),
|
|
180
180
|
earnRouter: Schema.Address.check(
|
|
181
181
|
z.describe('Parent-chain router for this Zone and Earn vault.'),
|
|
182
|
-
z.meta({ examples: ['
|
|
182
|
+
z.meta({ examples: ['0xebedc74ffeead186acc6cac05eb82fb9e093a7c7'] }),
|
|
183
183
|
),
|
|
184
184
|
})
|
|
185
185
|
.check(z.describe('Curated Earn route through one private Zone.'))
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import * as http from 'node:http'
|
|
2
|
+
|
|
3
|
+
import * as Fetch from './Fetch.js'
|
|
4
|
+
|
|
5
|
+
describe('withTimeout', () => {
|
|
6
|
+
test('aborts a fetch after the configured duration', async () => {
|
|
7
|
+
const server = http.createServer(() => {})
|
|
8
|
+
const url = await listen(server)
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const fetch = Fetch.withTimeout(globalThis.fetch, { timeout: 10 })
|
|
12
|
+
await expect(fetch(url)).rejects.toMatchObject({ name: 'TimeoutError' })
|
|
13
|
+
} finally {
|
|
14
|
+
server.closeAllConnections()
|
|
15
|
+
await close(server)
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe('resolveSignal', () => {
|
|
21
|
+
test('preserves caller and Request signals', () => {
|
|
22
|
+
const signal = AbortSignal.timeout(1_000)
|
|
23
|
+
const request = new Request('https://api.tempo.xyz', { signal })
|
|
24
|
+
|
|
25
|
+
expect(Fetch.resolveSignal('https://api.tempo.xyz', { init: { signal }, timeout: 10 })).toBe(
|
|
26
|
+
signal,
|
|
27
|
+
)
|
|
28
|
+
expect(Fetch.resolveSignal(request, { timeout: 10 })).toBe(request.signal)
|
|
29
|
+
})
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
function close(server: http.Server): Promise<void> {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
server.close((error) => (error ? reject(error) : resolve()))
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function listen(server: http.Server): Promise<string> {
|
|
39
|
+
return new Promise((resolve) => {
|
|
40
|
+
server.listen(0, '127.0.0.1', () => {
|
|
41
|
+
const address = server.address()
|
|
42
|
+
if (!address || typeof address === 'string') throw new Error('Expected a TCP server address.')
|
|
43
|
+
resolve(`http://127.0.0.1:${address.port}`)
|
|
44
|
+
})
|
|
45
|
+
})
|
|
46
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as Timeout from './Timeout.js'
|
|
2
|
+
|
|
3
|
+
/** Installs the default outbound fetch timeout once for this runtime. */
|
|
4
|
+
export function install(): void {
|
|
5
|
+
if (isInstalled(globalThis.fetch)) return
|
|
6
|
+
|
|
7
|
+
const fetch = withTimeout(globalThis.fetch.bind(globalThis), { timeout: Timeout.duration })
|
|
8
|
+
Object.defineProperty(fetch, timeoutMarker, { value: true })
|
|
9
|
+
globalThis.fetch = fetch
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Adds a default maximum duration to fetch calls that do not carry a signal. */
|
|
13
|
+
export function withTimeout(
|
|
14
|
+
fetch: typeof globalThis.fetch,
|
|
15
|
+
options: withTimeout.Options,
|
|
16
|
+
): typeof globalThis.fetch {
|
|
17
|
+
return ((input, init) => {
|
|
18
|
+
const signal = resolveSignal(input, { init, timeout: options.timeout })
|
|
19
|
+
return fetch(input, { ...init, signal })
|
|
20
|
+
}) as typeof globalThis.fetch
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Resolves a caller signal or creates the default fetch deadline signal. */
|
|
24
|
+
export function resolveSignal(
|
|
25
|
+
input: Parameters<typeof globalThis.fetch>[0],
|
|
26
|
+
options: resolveSignal.Options,
|
|
27
|
+
): AbortSignal {
|
|
28
|
+
return (
|
|
29
|
+
options.init?.signal ??
|
|
30
|
+
(input instanceof Request ? input.signal : Timeout.signal({ timeout: options.timeout }))
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export declare namespace resolveSignal {
|
|
35
|
+
/** Options for {@link resolveSignal}. */
|
|
36
|
+
type Options = {
|
|
37
|
+
/** Optional fetch initialization carrying a caller signal. */
|
|
38
|
+
init?: Parameters<typeof globalThis.fetch>[1]
|
|
39
|
+
/** Default maximum duration in milliseconds. */
|
|
40
|
+
timeout: number
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export declare namespace withTimeout {
|
|
45
|
+
/** Options for {@link withTimeout}. */
|
|
46
|
+
type Options = {
|
|
47
|
+
/** Maximum fetch duration in milliseconds. */
|
|
48
|
+
timeout: number
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const timeoutMarker = Symbol.for('tempo.api.fetch.timeout')
|
|
53
|
+
|
|
54
|
+
function isInstalled(fetch: typeof globalThis.fetch): boolean {
|
|
55
|
+
return timeoutMarker in fetch
|
|
56
|
+
}
|
package/src/internal/Log.test.ts
CHANGED
|
@@ -457,6 +457,27 @@ describe('middleware', () => {
|
|
|
457
457
|
`)
|
|
458
458
|
})
|
|
459
459
|
|
|
460
|
+
test('preserves the request error and unfinished stages', async () => {
|
|
461
|
+
const entries: Log.Entry[] = []
|
|
462
|
+
const causes: Error[] = []
|
|
463
|
+
const api = app({
|
|
464
|
+
emit: (entry, cause) => {
|
|
465
|
+
entries.push(entry)
|
|
466
|
+
if (cause) causes.push(cause)
|
|
467
|
+
},
|
|
468
|
+
})
|
|
469
|
+
const cause = new Error('Memoized cache-miss flight timed out after 15000ms.')
|
|
470
|
+
api.get('/activities', (c) => {
|
|
471
|
+
void Timing.time(c, 'transaction_activity_mpp', () => new Promise(() => {}))
|
|
472
|
+
return Response.upstream(c, cause)
|
|
473
|
+
})
|
|
474
|
+
|
|
475
|
+
await api.request('/activities')
|
|
476
|
+
|
|
477
|
+
expect(causes[0]).toBe(cause)
|
|
478
|
+
expect(entries[0]?.pendingTimings?.['transaction_activity_mpp']).toBeGreaterThanOrEqual(0)
|
|
479
|
+
})
|
|
480
|
+
|
|
460
481
|
test('includes bounded funding provider attempts', async () => {
|
|
461
482
|
const entries: Log.Entry[] = []
|
|
462
483
|
const api = app({ emit: (entry) => void entries.push(entry) })
|
package/src/internal/Log.ts
CHANGED
|
@@ -151,6 +151,8 @@ export type Entry = {
|
|
|
151
151
|
path: string
|
|
152
152
|
/** MPP payment reason when the request was paid per call. */
|
|
153
153
|
payment?: Auth.Payment['reason'] | undefined
|
|
154
|
+
/** Operations still running when the response completed, with elapsed milliseconds. */
|
|
155
|
+
pendingTimings?: Record<string, number> | undefined
|
|
154
156
|
/** Bounded outcome for an MPP relay operation. */
|
|
155
157
|
mpp?: {
|
|
156
158
|
/** Resolved Tempo chain selected by the credential. */
|
|
@@ -228,7 +230,7 @@ export type Entry = {
|
|
|
228
230
|
}
|
|
229
231
|
|
|
230
232
|
/** Sink receiving one {@link Entry} per request. */
|
|
231
|
-
export type Emit = (entry: Entry) => Promise<void> | void
|
|
233
|
+
export type Emit = (entry: Entry, cause?: Error) => Promise<void> | void
|
|
232
234
|
|
|
233
235
|
/**
|
|
234
236
|
* Emits one canonical structured log entry per request, after the response is
|
|
@@ -258,7 +260,8 @@ export function middleware<environment extends Environment = Environment>(
|
|
|
258
260
|
// Hono cannot resolve variable types through the generic `environment`,
|
|
259
261
|
// so shared-variable reads are cast from the environment contract above
|
|
260
262
|
// (the same pattern EdgeCache uses for its cross-layer reads).
|
|
261
|
-
const
|
|
263
|
+
const cause = c.error
|
|
264
|
+
const error = cause as (Error & ErrorMetadata) | undefined
|
|
262
265
|
const principal = c.get('principal' as never) as Auth.Principal | undefined
|
|
263
266
|
const errorCaptured = c.get('errorCaptured' as never) as boolean | undefined
|
|
264
267
|
const chainId = c.get('chainId' as never) as number | undefined
|
|
@@ -279,7 +282,6 @@ export function middleware<environment extends Environment = Environment>(
|
|
|
279
282
|
error?.providerFailures ?? (c.get('providerFailures' as never) as Entry['providerFailures'])
|
|
280
283
|
const rpc = c.get('rpcResponse' as never) as Entry['rpc']
|
|
281
284
|
const sponsorship = c.get('sponsorship' as never) as Entry['sponsorship']
|
|
282
|
-
|
|
283
285
|
// The `request` total metric duplicates `duration` (measured here, one
|
|
284
286
|
// layer further out), so only operation timings are kept.
|
|
285
287
|
const metrics = (c.get('serverTiming' as never) as Timing.Metric[] | undefined)?.filter(
|
|
@@ -295,6 +297,17 @@ export function middleware<environment extends Environment = Environment>(
|
|
|
295
297
|
}, new Map<string, number>()),
|
|
296
298
|
)
|
|
297
299
|
: undefined
|
|
300
|
+
const activeTimings = c.get('activeTimings' as never) as Timing.Active[] | undefined
|
|
301
|
+
const pendingTimings = activeTimings?.length
|
|
302
|
+
? Object.fromEntries(
|
|
303
|
+
activeTimings.reduce((result, timing) => {
|
|
304
|
+
const duration = round(performance.now() - timing.start)
|
|
305
|
+
const existing = result.get(timing.name)
|
|
306
|
+
if (existing === undefined || duration > existing) result.set(timing.name, duration)
|
|
307
|
+
return result
|
|
308
|
+
}, new Map<string, number>()),
|
|
309
|
+
)
|
|
310
|
+
: undefined
|
|
298
311
|
|
|
299
312
|
const rateLimit = (() => {
|
|
300
313
|
const limit = c.res.headers.get('RateLimit-Limit')
|
|
@@ -310,7 +323,7 @@ export function middleware<environment extends Environment = Environment>(
|
|
|
310
323
|
}
|
|
311
324
|
})()
|
|
312
325
|
|
|
313
|
-
const
|
|
326
|
+
const entry = {
|
|
314
327
|
...(c.get('edgeCacheStatus' as never) === 'hit' ? { cache: 'hit' as const } : {}),
|
|
315
328
|
...(chainId === undefined ? {} : { chainId }),
|
|
316
329
|
duration: round(duration),
|
|
@@ -333,6 +346,7 @@ export function middleware<environment extends Environment = Environment>(
|
|
|
333
346
|
...(mpp === undefined ? {} : { mpp }),
|
|
334
347
|
path: url.pathname,
|
|
335
348
|
...(principal?.payment === undefined ? {} : { payment: principal.payment.reason }),
|
|
349
|
+
...(pendingTimings === undefined ? {} : { pendingTimings }),
|
|
336
350
|
...(principal === undefined
|
|
337
351
|
? {}
|
|
338
352
|
: {
|
|
@@ -360,7 +374,8 @@ export function middleware<environment extends Environment = Environment>(
|
|
|
360
374
|
...(sponsorship === undefined ? {} : { sponsorship }),
|
|
361
375
|
status,
|
|
362
376
|
...(timings === undefined ? {} : { timings }),
|
|
363
|
-
}
|
|
377
|
+
} satisfies Entry
|
|
378
|
+
const result = fn(entry, cause)
|
|
364
379
|
|
|
365
380
|
if (result instanceof Promise) {
|
|
366
381
|
const promise = result.catch((error) => console.error('request log sink failed', error))
|
package/src/internal/OpenApi.ts
CHANGED
|
@@ -148,6 +148,7 @@ export const errorCodes = {
|
|
|
148
148
|
500: ['internal_error'],
|
|
149
149
|
501: ['billing_unconfigured'],
|
|
150
150
|
502: ['upstream_error'],
|
|
151
|
+
504: ['request_timeout'],
|
|
151
152
|
} as const satisfies Record<number, readonly [string, ...string[]]>
|
|
152
153
|
|
|
153
154
|
/**
|
|
@@ -220,6 +221,7 @@ const standardErrorNames = {
|
|
|
220
221
|
500: 'InternalError',
|
|
221
222
|
501: 'NotImplementedError',
|
|
222
223
|
502: 'UpstreamError',
|
|
224
|
+
504: 'RequestTimeoutError',
|
|
223
225
|
} as const satisfies Record<keyof typeof errorCodes, string>
|
|
224
226
|
|
|
225
227
|
const reservedErrorNames = new Set<string>(Object.values(standardErrorNames))
|
|
@@ -300,12 +302,12 @@ export const paymentChallenge = {
|
|
|
300
302
|
headers: { 'WWW-Authenticate': headerRef('WwwAuthenticate') },
|
|
301
303
|
} as const
|
|
302
304
|
|
|
303
|
-
type ResponseComponentName = 'InternalError' | 'PaymentRequired' | 'RateLimited'
|
|
305
|
+
type ResponseComponentName = 'InternalError' | 'PaymentRequired' | 'RateLimited' | 'RequestTimeout'
|
|
304
306
|
|
|
305
307
|
/**
|
|
306
308
|
* Reusable responses registered under `components.responses` (see `App.create`)
|
|
307
|
-
* and `$ref`-erenced from operations, so the uniform `402`/`429`/`500`
|
|
308
|
-
* are defined once instead of inlined on every operation.
|
|
309
|
+
* and `$ref`-erenced from operations, so the uniform `402`/`429`/`500`/`504`
|
|
310
|
+
* responses are defined once instead of inlined on every operation.
|
|
309
311
|
*/
|
|
310
312
|
export function responseComponents(): ResponsesWithResolver {
|
|
311
313
|
return {
|
|
@@ -320,6 +322,11 @@ export function responseComponents(): ResponsesWithResolver {
|
|
|
320
322
|
'Rate limit exceeded. On endpoints that accept MPP, over-quota unpaid requests return `402 Payment Required` instead.',
|
|
321
323
|
headers: { ...rateLimitHeaders },
|
|
322
324
|
},
|
|
325
|
+
RequestTimeout: {
|
|
326
|
+
content: errorContent(504, errorCodes[504], 'Request timed out'),
|
|
327
|
+
description:
|
|
328
|
+
'Request timed out. The request did not complete within the 60-second deadline; retry it.',
|
|
329
|
+
},
|
|
323
330
|
}
|
|
324
331
|
}
|
|
325
332
|
|
|
@@ -344,6 +351,7 @@ export function standardError(
|
|
|
344
351
|
): ResponsesWithResolver[string] {
|
|
345
352
|
if (status === 429) return responseRef('RateLimited')
|
|
346
353
|
if (status === 500) return responseRef('InternalError')
|
|
354
|
+
if (status === 504) return responseRef('RequestTimeout')
|
|
347
355
|
return { content: errorContent(status, mergeErrorCodes(status, codes), description), description }
|
|
348
356
|
}
|
|
349
357
|
|
|
@@ -352,7 +360,8 @@ export function standardError(
|
|
|
352
360
|
* pre-filled, plus the success response (with rate-limit headers):
|
|
353
361
|
*
|
|
354
362
|
* - `400` invalid request, `401` missing/invalid key, `403` forbidden, `429`
|
|
355
|
-
* rate limited, `500` internal error,
|
|
363
|
+
* rate limited, `500` internal error, `502` upstream failure, and `504`
|
|
364
|
+
* request timeout on every operation;
|
|
356
365
|
* - `404`/`409`/`412`/`413`/`415` only when an `errors` entry is set.
|
|
357
366
|
*
|
|
358
367
|
* `error.code` is enumerated per status from {@link errorCodes}. Operation-level
|
|
@@ -404,6 +413,7 @@ export function responses<schema extends z.ZodMiniType>(
|
|
|
404
413
|
500: responseRef('InternalError'),
|
|
405
414
|
...(errors?.[501] && { 501: error(501, 'Not implemented.') }),
|
|
406
415
|
502: error(502, 'Upstream data failure.'),
|
|
416
|
+
504: responseRef('RequestTimeout'),
|
|
407
417
|
}
|
|
408
418
|
}
|
|
409
419
|
|
package/src/internal/Response.ts
CHANGED
|
@@ -142,7 +142,7 @@ export declare namespace error {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
/** HTTP status code accepted by the public error helper. */
|
|
145
|
-
type Status = 400 | 401 | 403 | 404 | 409 | 412 | 413 | 415 | 429 | 500 | 501 | 502 | 503
|
|
145
|
+
type Status = 400 | 401 | 403 | 404 | 409 | 412 | 413 | 415 | 429 | 500 | 501 | 502 | 503 | 504
|
|
146
146
|
|
|
147
147
|
/** Options for creating a JSON error response. */
|
|
148
148
|
type Options<status extends Status = Status, code extends string = string> = {
|
|
@@ -6,6 +6,8 @@ describe('ChainId', () => {
|
|
|
6
6
|
test('resolves aliases to their chain id', () => {
|
|
7
7
|
expect(Schema.ChainId.parse('mainnet')).toMatchInlineSnapshot(`4217`)
|
|
8
8
|
expect(Schema.ChainId.parse('testnet')).toMatchInlineSnapshot(`42431`)
|
|
9
|
+
expect(Schema.ChainId.parse('1424310001')).toMatchInlineSnapshot(`1424310003`)
|
|
10
|
+
expect(Schema.ChainId.parse(1_424_310_001)).toMatchInlineSnapshot(`1424310003`)
|
|
9
11
|
})
|
|
10
12
|
|
|
11
13
|
test('accepts numeric chain ids as numbers or strings', () => {
|
package/src/internal/Schema.ts
CHANGED
|
@@ -90,12 +90,19 @@ export function tokenAddress(example: core_Hex.Hex) {
|
|
|
90
90
|
)
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
/** Maps
|
|
93
|
+
/** Maps caller-facing chain aliases to their current Tempo chain id. */
|
|
94
94
|
export const chainIdAlias = {
|
|
95
|
+
// TODO: Remove this alias after clients migrate to chain 1424310003.
|
|
96
|
+
1_424_310_001: 1_424_310_003,
|
|
95
97
|
mainnet: 4217,
|
|
96
98
|
testnet: 42431,
|
|
97
99
|
} as const
|
|
98
100
|
|
|
101
|
+
/** Resolves a numeric chain id through the caller-facing alias map. */
|
|
102
|
+
export function resolveChainId(chainId: number): number {
|
|
103
|
+
return chainIdAlias[chainId as keyof typeof chainIdAlias] ?? chainId
|
|
104
|
+
}
|
|
105
|
+
|
|
99
106
|
/**
|
|
100
107
|
* Tempo chain id. Accepts a chain alias (`mainnet`/`testnet`) or any positive
|
|
101
108
|
* integer chain id (a number or numeric string), normalizing all forms to a
|
|
@@ -110,7 +117,10 @@ export const ChainId = z
|
|
|
110
117
|
z.enum(['mainnet', 'testnet']),
|
|
111
118
|
z.transform((alias) => chainIdAlias[alias]),
|
|
112
119
|
),
|
|
113
|
-
z.
|
|
120
|
+
z.pipe(
|
|
121
|
+
z.coerce.number(),
|
|
122
|
+
z.transform((chainId) => resolveChainId(chainId)),
|
|
123
|
+
),
|
|
114
124
|
]),
|
|
115
125
|
z.number().check(z.int(), z.positive()),
|
|
116
126
|
)
|