tapimo 0.7.2 → 0.8.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/README.md +10 -7
- package/dist/ApiKey.d.ts +3 -0
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +10 -0
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +7 -3
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +9 -2
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +20434 -19484
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +62 -4
- package/dist/App.js.map +1 -1
- package/dist/admin/App.d.ts +84 -74
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +14 -1
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +6 -2
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +2 -0
- package/dist/admin/apps/organizations.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.d.ts +168 -168
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.js +67 -67
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts +2 -0
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +3 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/apps/data/App.d.ts +10305 -12785
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +4 -8
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +24 -24
- package/dist/apps/data/routes/addresses.d.ts +288 -288
- package/dist/apps/data/routes/balances.d.ts +6 -6
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/coingecko.d.ts +170 -170
- package/dist/apps/data/routes/earn.d.ts +436 -6
- package/dist/apps/data/routes/earn.d.ts.map +1 -1
- package/dist/apps/data/routes/earn.js +631 -4
- package/dist/apps/data/routes/earn.js.map +1 -1
- package/dist/apps/data/routes/exchanges.d.ts +10 -10
- package/dist/apps/data/routes/fee-amm.d.ts +2 -2
- package/dist/apps/data/routes/indexer.d.ts +6 -6
- package/dist/apps/data/routes/indexer.js +4 -1
- package/dist/apps/data/routes/indexer.js.map +1 -1
- package/dist/apps/data/routes/receipts.d.ts +235 -235
- package/dist/apps/data/routes/rpc.d.ts +1 -1
- package/dist/apps/data/routes/rpc.js +4 -1
- package/dist/apps/data/routes/rpc.js.map +1 -1
- package/dist/apps/data/routes/tokenlist.d.ts +36 -36
- package/dist/apps/data/routes/tokens.d.ts +6 -6
- package/dist/apps/data/routes/transactions.d.ts +2 -2
- package/dist/apps/data/routes/transfers.d.ts +1 -1
- package/dist/apps/data/routes/valuation.d.ts +1 -1
- package/dist/apps/data/routes/verified-tokens.d.ts +36 -36
- package/dist/apps/data/routes/webhooks.d.ts +9 -9
- package/dist/apps/data/routes/zones.d.ts +14 -14
- package/dist/apps/funding/Access.d.ts +16 -0
- package/dist/apps/funding/Access.d.ts.map +1 -0
- package/dist/apps/funding/Access.js +22 -0
- package/dist/apps/funding/Access.js.map +1 -0
- package/dist/apps/{data/routes/funding.d.ts → funding/App.d.ts} +1717 -3035
- package/dist/apps/funding/App.d.ts.map +1 -0
- package/dist/apps/funding/App.js +46 -0
- package/dist/apps/funding/App.js.map +1 -0
- package/dist/apps/funding/Inventory.d.ts +5 -0
- package/dist/apps/funding/Inventory.d.ts.map +1 -0
- package/dist/apps/funding/Inventory.js +25 -0
- package/dist/apps/funding/Inventory.js.map +1 -0
- package/dist/apps/funding/ProviderAttempts.d.ts +5 -0
- package/dist/apps/funding/ProviderAttempts.d.ts.map +1 -0
- package/dist/apps/funding/ProviderAttempts.js +24 -0
- package/dist/apps/funding/ProviderAttempts.js.map +1 -0
- package/dist/apps/funding/index.d.ts +2 -0
- package/dist/apps/funding/index.d.ts.map +1 -0
- package/dist/apps/funding/index.js +2 -0
- package/dist/apps/funding/index.js.map +1 -0
- package/dist/apps/funding/routes/chains.d.ts +169 -0
- package/dist/apps/funding/routes/chains.d.ts.map +1 -0
- package/dist/apps/funding/routes/chains.js +80 -0
- package/dist/apps/funding/routes/chains.js.map +1 -0
- package/dist/apps/funding/routes/deposit-addresses.d.ts +2388 -0
- package/dist/apps/funding/routes/deposit-addresses.d.ts.map +1 -0
- package/dist/apps/funding/routes/deposit-addresses.js +860 -0
- package/dist/apps/funding/routes/deposit-addresses.js.map +1 -0
- package/dist/apps/funding/routes/providers.d.ts +91 -0
- package/dist/apps/funding/routes/providers.d.ts.map +1 -0
- package/dist/apps/funding/routes/providers.js +56 -0
- package/dist/apps/funding/routes/providers.js.map +1 -0
- package/dist/apps/funding/routes/quotes.d.ts +426 -0
- package/dist/apps/funding/routes/quotes.d.ts.map +1 -0
- package/dist/apps/funding/routes/quotes.js +188 -0
- package/dist/apps/funding/routes/quotes.js.map +1 -0
- package/dist/apps/funding/routes/transfers.d.ts +1999 -0
- package/dist/apps/funding/routes/transfers.d.ts.map +1 -0
- package/dist/apps/funding/routes/transfers.js +614 -0
- package/dist/apps/funding/routes/transfers.js.map +1 -0
- 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 +430 -165
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +298 -21
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +75 -1
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +6 -6
- package/dist/apps/management/routes/faucet.d.ts +35 -35
- package/dist/apps/management/routes/invitations.d.ts +48 -48
- package/dist/apps/management/routes/invite-links.d.ts +38 -38
- package/dist/apps/management/routes/members.d.ts +69 -69
- package/dist/apps/management/routes/orgs.d.ts +37 -37
- package/dist/apps/management/routes/projects.d.ts +25 -25
- package/dist/apps/management/routes/scopes.d.ts +1 -1
- package/dist/apps/management/routes/usage.d.ts +72 -72
- package/dist/apps/management/routes/verified-token-requests.d.ts +18 -18
- package/dist/apps/management/routes/webhooks.d.ts +128 -128
- package/dist/apps/mcp.d.ts +2 -3
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +16 -14
- package/dist/apps/mcp.js.map +1 -1
- package/dist/apps/metadata.d.ts +4 -0
- package/dist/apps/metadata.d.ts.map +1 -1
- package/dist/apps/metadata.js +10 -0
- package/dist/apps/metadata.js.map +1 -1
- package/dist/apps/mpp/App.d.ts +2 -2
- package/dist/cloudflare.d.ts +50 -0
- package/dist/cloudflare.d.ts.map +1 -1
- package/dist/cloudflare.js +178 -0
- package/dist/cloudflare.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 +37 -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 +20 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/fundingDepositAddresses.d.ts +13 -0
- package/dist/db/tables/fundingDepositAddresses.d.ts.map +1 -1
- package/dist/db/tables/fundingDepositAddresses.js +12 -0
- package/dist/db/tables/fundingDepositAddresses.js.map +1 -1
- package/dist/db/tables/fundingDepositRequestObservations.d.ts +45 -0
- package/dist/db/tables/fundingDepositRequestObservations.d.ts.map +1 -0
- package/dist/db/tables/fundingDepositRequestObservations.js +46 -0
- package/dist/db/tables/fundingDepositRequestObservations.js.map +1 -0
- package/dist/db/tables/fundingDeposits.d.ts +57 -1
- package/dist/db/tables/fundingDeposits.d.ts.map +1 -1
- package/dist/db/tables/fundingDeposits.js +82 -0
- package/dist/db/tables/fundingDeposits.js.map +1 -1
- package/dist/internal/Auth.d.ts +11 -11
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +41 -15
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cache.js +1 -1
- package/dist/internal/Cache.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts +2 -2
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +10 -4
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/IpAllowlist.d.ts +15 -0
- package/dist/internal/IpAllowlist.d.ts.map +1 -0
- package/dist/internal/IpAllowlist.js +27 -0
- package/dist/internal/IpAllowlist.js.map +1 -0
- package/dist/internal/OpenApi.d.ts +1 -1
- package/dist/internal/OpenApi.js +1 -1
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/Tidx.d.ts +6 -0
- package/dist/internal/Tidx.d.ts.map +1 -1
- package/dist/internal/Tidx.js +13 -3
- package/dist/internal/Tidx.js.map +1 -1
- package/dist/internal/Viem.d.ts +8 -0
- package/dist/internal/Viem.d.ts.map +1 -1
- package/dist/internal/Viem.js +12 -5
- package/dist/internal/Viem.js.map +1 -1
- package/dist/internal/funding/Deposit.d.ts +42 -0
- package/dist/internal/funding/Deposit.d.ts.map +1 -1
- package/dist/internal/funding/Deposit.js +37 -5
- package/dist/internal/funding/Deposit.js.map +1 -1
- package/dist/internal/funding/Evidence.d.ts +1 -5
- package/dist/internal/funding/Evidence.d.ts.map +1 -1
- package/dist/internal/funding/Evidence.js +4 -3
- package/dist/internal/funding/Evidence.js.map +1 -1
- package/dist/internal/funding/Provider.d.ts +102 -0
- package/dist/internal/funding/Provider.d.ts.map +1 -1
- package/dist/internal/funding/Provider.js +5 -0
- package/dist/internal/funding/Provider.js.map +1 -1
- package/dist/internal/funding/Reconciliation.d.ts +16 -2
- package/dist/internal/funding/Reconciliation.d.ts.map +1 -1
- package/dist/internal/funding/Reconciliation.js +257 -48
- package/dist/internal/funding/Reconciliation.js.map +1 -1
- package/dist/internal/funding/SourceObservation.d.ts +108 -0
- package/dist/internal/funding/SourceObservation.d.ts.map +1 -0
- package/dist/internal/funding/SourceObservation.js +143 -0
- package/dist/internal/funding/SourceObservation.js.map +1 -0
- package/dist/internal/funding/Subsidy.d.ts +2 -2
- package/dist/internal/funding/Subsidy.d.ts.map +1 -1
- package/dist/internal/funding/Subsidy.js +1 -1
- package/dist/internal/funding/Subsidy.js.map +1 -1
- package/dist/internal/funding/index.d.ts +1 -0
- package/dist/internal/funding/index.d.ts.map +1 -1
- package/dist/internal/funding/index.js +1 -0
- package/dist/internal/funding/index.js.map +1 -1
- package/dist/internal/funding/providers/relay.d.ts +43 -1
- package/dist/internal/funding/providers/relay.d.ts.map +1 -1
- package/dist/internal/funding/providers/relay.js +434 -27
- package/dist/internal/funding/providers/relay.js.map +1 -1
- package/package.json +3 -3
- package/src/ApiKey.test.ts +35 -0
- package/src/ApiKey.ts +27 -0
- package/src/ApiKeys.test.ts +29 -0
- package/src/ApiKeys.ts +11 -3
- package/src/App.test.ts +23 -5
- package/src/App.ts +76 -5
- package/src/Client.test-d.ts +171 -19
- package/src/admin/apps/api-keys.test.ts +25 -1
- package/src/admin/apps/api-keys.ts +6 -2
- package/src/admin/ui.generated.ts +67 -67
- package/src/analytics/Analytics.ts +4 -0
- package/src/apps/composition.test.ts +100 -2
- package/src/apps/data/App.test.ts +0 -12
- package/src/apps/data/App.ts +4 -8
- package/src/apps/data/routes/earn.test.ts +283 -0
- package/src/apps/data/routes/earn.ts +800 -3
- package/src/apps/data/routes/indexer.ts +2 -1
- package/src/apps/data/routes/rpc.ts +2 -0
- package/src/apps/data/routes/tokens.test.ts +6 -8
- package/src/apps/funding/Access.ts +26 -0
- package/src/apps/funding/App.test.ts +78 -0
- package/src/apps/funding/App.ts +63 -0
- package/src/apps/funding/Inventory.ts +30 -0
- package/src/apps/funding/ProviderAttempts.ts +26 -0
- package/src/apps/funding/index.ts +1 -0
- package/src/apps/funding/routes/chains.test.ts +78 -0
- package/src/apps/funding/routes/chains.ts +101 -0
- package/src/apps/funding/routes/deposit-addresses.test.ts +1628 -0
- package/src/apps/funding/routes/deposit-addresses.ts +1015 -0
- package/src/apps/funding/routes/providers.test.ts +165 -0
- package/src/apps/funding/routes/providers.ts +73 -0
- package/src/apps/funding/routes/quotes.test.ts +511 -0
- package/src/apps/funding/routes/quotes.ts +205 -0
- package/src/apps/funding/routes/transfers.test.ts +1255 -0
- package/src/apps/funding/routes/transfers.ts +704 -0
- package/src/apps/index.ts +1 -0
- package/src/apps/management/routes/api-keys.test.ts +98 -0
- package/src/apps/management/routes/api-keys.ts +96 -1
- package/src/apps/mcp.test.ts +17 -2
- package/src/apps/mcp.ts +18 -13
- package/src/apps/metadata.ts +12 -0
- package/src/apps/mpp/internal/Policy.test.ts +7 -1
- package/src/cloudflare.ts +279 -0
- package/src/db/Db.ts +62 -0
- package/src/db/Schema.ts +24 -0
- package/src/db/tables/fundingDepositAddresses.ts +28 -0
- package/src/db/tables/fundingDepositRequestObservations.test.ts +106 -0
- package/src/db/tables/fundingDepositRequestObservations.ts +99 -0
- package/src/db/tables/fundingDeposits.test.ts +138 -0
- package/src/db/tables/fundingDeposits.ts +145 -1
- package/src/internal/Auth.test.ts +177 -5
- package/src/internal/Auth.ts +48 -18
- package/src/internal/Cache.test.ts +37 -3
- package/src/internal/Cache.ts +1 -1
- package/src/internal/EdgeCache.test.ts +7 -7
- package/src/internal/EdgeCache.ts +13 -4
- package/src/internal/IpAllowlist.test.ts +52 -0
- package/src/internal/IpAllowlist.ts +39 -0
- package/src/internal/Log.test.ts +7 -1
- package/src/internal/OpenApi.ts +1 -1
- package/src/internal/Tidx.test.ts +28 -0
- package/src/internal/Tidx.ts +19 -3
- package/src/internal/Viem.test.ts +28 -0
- package/src/internal/Viem.ts +21 -5
- package/src/internal/funding/Deposit.ts +74 -5
- package/src/internal/funding/Evidence.test.ts +2 -2
- package/src/internal/funding/Evidence.ts +4 -9
- package/src/internal/funding/Provider.ts +115 -0
- package/src/internal/funding/Reconciliation.local.test.ts +7 -0
- package/src/internal/funding/Reconciliation.test.ts +1052 -35
- package/src/internal/funding/Reconciliation.ts +328 -56
- package/src/internal/funding/SourceObservation.test.ts +301 -0
- package/src/internal/funding/SourceObservation.ts +267 -0
- package/src/internal/funding/Subsidy.ts +2 -2
- package/src/internal/funding/index.test.ts +1 -0
- package/src/internal/funding/index.ts +1 -0
- package/src/internal/funding/providers/relay.test.ts +94 -4
- package/src/internal/funding/providers/relay.ts +528 -27
- package/dist/apps/data/routes/funding.d.ts.map +0 -1
- package/dist/apps/data/routes/funding.js +0 -1557
- package/dist/apps/data/routes/funding.js.map +0 -1
- package/src/apps/data/routes/funding.test.ts +0 -2994
- package/src/apps/data/routes/funding.ts +0 -1821
|
@@ -52,7 +52,7 @@ describe('response', () => {
|
|
|
52
52
|
expect(etag(first)).toMatchInlineSnapshot(`"<etag>"`)
|
|
53
53
|
expect(first.headers.get('ratelimit-remaining')).toMatchInlineSnapshot(`"9"`)
|
|
54
54
|
expect(first.headers.get('vary')).toMatchInlineSnapshot(
|
|
55
|
-
`"Accept-Encoding, Authorization, Tempo-API-Key"`,
|
|
55
|
+
`"Accept-Encoding, Authorization, Tempo-API-Key, X-API-Key"`,
|
|
56
56
|
)
|
|
57
57
|
|
|
58
58
|
expect(await second.json()).toMatchInlineSnapshot(`
|
|
@@ -66,7 +66,7 @@ describe('response', () => {
|
|
|
66
66
|
expect(second.headers.get('etag') === first.headers.get('etag')).toMatchInlineSnapshot(`true`)
|
|
67
67
|
expect(second.headers.get('ratelimit-remaining')).toMatchInlineSnapshot(`"8"`)
|
|
68
68
|
expect(second.headers.get('vary')).toMatchInlineSnapshot(
|
|
69
|
-
`"Accept-Encoding, Authorization, Tempo-API-Key"`,
|
|
69
|
+
`"Accept-Encoding, Authorization, Tempo-API-Key, X-API-Key"`,
|
|
70
70
|
)
|
|
71
71
|
|
|
72
72
|
expect(third.status).toMatchInlineSnapshot(`304`)
|
|
@@ -77,11 +77,45 @@ describe('response', () => {
|
|
|
77
77
|
expect(third.headers.get('etag') === first.headers.get('etag')).toMatchInlineSnapshot(`true`)
|
|
78
78
|
expect(third.headers.get('ratelimit-remaining')).toMatchInlineSnapshot(`"8"`)
|
|
79
79
|
expect(third.headers.get('vary')).toMatchInlineSnapshot(
|
|
80
|
-
`"Accept-Encoding, Authorization, Tempo-API-Key"`,
|
|
80
|
+
`"Accept-Encoding, Authorization, Tempo-API-Key, X-API-Key"`,
|
|
81
81
|
)
|
|
82
82
|
})
|
|
83
83
|
})
|
|
84
84
|
|
|
85
|
+
test('varies cached responses by the legacy API-key header', async () => {
|
|
86
|
+
await withCaches(async () => {
|
|
87
|
+
let calls = 0
|
|
88
|
+
const app = new Hono()
|
|
89
|
+
app.get('/', Cache.response({ key: (c) => c.req.url, name: 'test' }), (c) =>
|
|
90
|
+
c.json({ calls: ++calls, token: c.req.header('x-api-key') }),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
const first = await app.request('/', { headers: { 'x-api-key': 'one' } })
|
|
94
|
+
const second = await app.request('/', { headers: { 'x-api-key': 'two' } })
|
|
95
|
+
const hit = await app.request('/', { headers: { 'x-api-key': 'one' } })
|
|
96
|
+
|
|
97
|
+
expect(calls).toMatchInlineSnapshot(`2`)
|
|
98
|
+
expect(await first.json()).toMatchInlineSnapshot(`
|
|
99
|
+
{
|
|
100
|
+
"calls": 1,
|
|
101
|
+
"token": "one",
|
|
102
|
+
}
|
|
103
|
+
`)
|
|
104
|
+
expect(await second.json()).toMatchInlineSnapshot(`
|
|
105
|
+
{
|
|
106
|
+
"calls": 2,
|
|
107
|
+
"token": "two",
|
|
108
|
+
}
|
|
109
|
+
`)
|
|
110
|
+
expect(await hit.json()).toMatchInlineSnapshot(`
|
|
111
|
+
{
|
|
112
|
+
"calls": 1,
|
|
113
|
+
"token": "one",
|
|
114
|
+
}
|
|
115
|
+
`)
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
85
119
|
test('stores entries with the shared policy form, not the client form', async () => {
|
|
86
120
|
await withCaches(async (stores) => {
|
|
87
121
|
const app = new Hono()
|
package/src/internal/Cache.ts
CHANGED
|
@@ -57,7 +57,7 @@ export function response<environment extends Env>(
|
|
|
57
57
|
options: response.Options<environment>,
|
|
58
58
|
): MiddlewareHandler<environment> {
|
|
59
59
|
const defaultCacheControl = options.cacheControl ?? policies.default
|
|
60
|
-
const vary = options.vary ?? ['Accept-Encoding', 'Authorization', 'Tempo-API-Key']
|
|
60
|
+
const vary = options.vary ?? ['Accept-Encoding', 'Authorization', 'Tempo-API-Key', 'X-API-Key']
|
|
61
61
|
// hono/cache suffixes its key with `::<directive>=<value>` for every
|
|
62
62
|
// configured vary directive (trimmed, lowercased, deduped). Mirror that
|
|
63
63
|
// normalization so the `no-cache` purge below deletes exactly the entry the
|
|
@@ -37,18 +37,18 @@ describe('middleware', () => {
|
|
|
37
37
|
expect(second.headers.get('vary')).toMatchInlineSnapshot(`"Accept-Encoding"`)
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
-
test('
|
|
40
|
+
test('bypasses the cache for credential headers', async () => {
|
|
41
41
|
const { app, store, downstreamCalls } = createApp()
|
|
42
42
|
|
|
43
|
-
// An authenticated request warms the cache; subsequent requests — whether
|
|
44
|
-
// authenticated (with any key) or anonymous — are all served from the one
|
|
45
|
-
// shared entry without re-running downstream auth/rate-limiting.
|
|
46
43
|
await app.request('/cached', { headers: { authorization: 'Bearer one' } })
|
|
44
|
+
await app.request('/cached', { headers: { authorization: 'Basic malformed' } })
|
|
45
|
+
await app.request('/cached', { headers: { authorization: 'Payment paid' } })
|
|
47
46
|
await app.request('/cached', { headers: { 'tempo-api-key': 'two' } })
|
|
48
|
-
await app.request('/cached')
|
|
47
|
+
await app.request('/cached', { headers: { 'x-api-key': 'three' } })
|
|
49
48
|
|
|
50
|
-
expect(downstreamCalls()).toMatchInlineSnapshot(`
|
|
51
|
-
expect(store.
|
|
49
|
+
expect(downstreamCalls()).toMatchInlineSnapshot(`5`)
|
|
50
|
+
expect(store.gets).toMatchInlineSnapshot(`0`)
|
|
51
|
+
expect(store.size).toMatchInlineSnapshot(`0`)
|
|
52
52
|
})
|
|
53
53
|
|
|
54
54
|
test('bypasses the cache for query API key credentials', async () => {
|
|
@@ -48,8 +48,8 @@ const volatileHeaders = [
|
|
|
48
48
|
/**
|
|
49
49
|
* Serves GET responses from the edge cache store BEFORE auth and rate-limiting
|
|
50
50
|
* run, so cache hits skip the per-request store round-trips that dominate
|
|
51
|
-
* latency
|
|
52
|
-
* auth middleware.
|
|
51
|
+
* latency. Credential-bearing requests bypass this layer so per-key policy
|
|
52
|
+
* enforcement always runs. Place this ahead of the auth middleware.
|
|
53
53
|
*
|
|
54
54
|
* Only 2xx responses from routes that opted into caching (via `Cache.response`,
|
|
55
55
|
* which publishes the `edgeCache` policy) are stored, and the policy is only
|
|
@@ -84,8 +84,8 @@ export function middleware<environment extends Env>(
|
|
|
84
84
|
store !== undefined &&
|
|
85
85
|
c.req.method === 'GET' &&
|
|
86
86
|
!c.req.raw.headers.has('range') &&
|
|
87
|
-
//
|
|
88
|
-
!
|
|
87
|
+
// Credential-bearing requests must reach auth for per-key policy enforcement.
|
|
88
|
+
!hasCredential(c.req.raw) &&
|
|
89
89
|
isCacheableRoute(c)
|
|
90
90
|
const key = eligible
|
|
91
91
|
? await routeCacheKey(c).catch(() => {
|
|
@@ -144,6 +144,15 @@ export function middleware<environment extends Env>(
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
function hasCredential(request: Request): boolean {
|
|
148
|
+
return (
|
|
149
|
+
request.headers.has('tempo-api-key') ||
|
|
150
|
+
request.headers.has('x-api-key') ||
|
|
151
|
+
request.headers.has('authorization') ||
|
|
152
|
+
new URL(request.url).searchParams.has('key')
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
147
156
|
// Marks handlers whose route publishes an edge-cache policy; the middleware
|
|
148
157
|
// pays the store lookup only when a matched handler carries it.
|
|
149
158
|
const cacheableSymbol = Symbol('tempoApi.edgeCacheable')
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
|
|
3
|
+
import * as IpAllowlist from './IpAllowlist.js'
|
|
4
|
+
|
|
5
|
+
describe('isRule', () => {
|
|
6
|
+
test.each([
|
|
7
|
+
['203.0.113.7', true],
|
|
8
|
+
['0.0.0.0/0', true],
|
|
9
|
+
['203.0.113.0/24', true],
|
|
10
|
+
['203.0.113.7/32', true],
|
|
11
|
+
['2001:db8::7', true],
|
|
12
|
+
['::/0', true],
|
|
13
|
+
['2001:db8::/32', true],
|
|
14
|
+
['2001:db8::7/128', true],
|
|
15
|
+
['::ffff:192.0.2.1', true],
|
|
16
|
+
['203.0.113.0/33', false],
|
|
17
|
+
['2001:db8::/129', false],
|
|
18
|
+
['203.0.113.0/-1', false],
|
|
19
|
+
['203.0.113.999', false],
|
|
20
|
+
['2001:db8::1%eth0', false],
|
|
21
|
+
['203.0.113.0/24/1', false],
|
|
22
|
+
[' 203.0.113.7', false],
|
|
23
|
+
['203.0.113.7 ', false],
|
|
24
|
+
['*', false],
|
|
25
|
+
['', false],
|
|
26
|
+
])('%s -> %s', (rule, expected) => {
|
|
27
|
+
expect(IpAllowlist.isRule(rule)).toBe(expected)
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe('allows', () => {
|
|
32
|
+
test.each([
|
|
33
|
+
['203.0.113.7', ['203.0.113.7'], true],
|
|
34
|
+
['203.0.113.7', ['0.0.0.0/0'], true],
|
|
35
|
+
['203.0.113.7', ['203.0.113.0/24'], true],
|
|
36
|
+
['203.0.113.0', ['203.0.113.0/24'], true],
|
|
37
|
+
['203.0.113.255', ['203.0.113.0/24'], true],
|
|
38
|
+
['203.0.114.7', ['203.0.113.0/24'], false],
|
|
39
|
+
['2001:db8:1::7', ['2001:db8::/32'], true],
|
|
40
|
+
['2001:db8::7', ['2001:db8::7/128'], true],
|
|
41
|
+
['2001:db8::8', ['2001:db8::7/128'], false],
|
|
42
|
+
['2001:db9::7', ['2001:db8::/32'], false],
|
|
43
|
+
['203.0.113.7', ['2001:db8::/32'], false],
|
|
44
|
+
['::ffff:192.0.2.1', ['::ffff:192.0.2.1'], true],
|
|
45
|
+
['not-an-ip', ['203.0.113.0/24'], false],
|
|
46
|
+
])('%s in %j -> %s', async (address, rules, expected) => {
|
|
47
|
+
const app = new Hono().get('/', async (c) =>
|
|
48
|
+
c.json({ allowed: await IpAllowlist.allows(c, { address, rules }) }),
|
|
49
|
+
)
|
|
50
|
+
expect(await (await app.request('/')).json()).toEqual({ allowed: expected })
|
|
51
|
+
})
|
|
52
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Context } from 'hono'
|
|
2
|
+
import { ipRestriction } from 'hono/ip-restriction'
|
|
3
|
+
|
|
4
|
+
/** Returns whether a value is an IPv4/IPv6 address or CIDR range. */
|
|
5
|
+
export function isRule(value: string): boolean {
|
|
6
|
+
if (value === '*' || value.trim() !== value || value.split('/').length > 2) return false
|
|
7
|
+
try {
|
|
8
|
+
ipRestriction(() => '', { allowList: [value] })
|
|
9
|
+
return true
|
|
10
|
+
} catch {
|
|
11
|
+
return false
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Returns whether the client address matches at least one allowlist rule. */
|
|
16
|
+
export async function allows(c: Context, options: allows.Options): Promise<boolean> {
|
|
17
|
+
const { address, rules } = options
|
|
18
|
+
if (!address) return false
|
|
19
|
+
try {
|
|
20
|
+
const denied = await ipRestriction(
|
|
21
|
+
() => address,
|
|
22
|
+
{ allowList: [...rules] },
|
|
23
|
+
() => new Response(null, { status: 403 }),
|
|
24
|
+
)(c, async () => {})
|
|
25
|
+
return denied === undefined
|
|
26
|
+
} catch {
|
|
27
|
+
return false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export declare namespace allows {
|
|
32
|
+
/** Inputs used to check a client address. */
|
|
33
|
+
type Options = {
|
|
34
|
+
/** Trusted client IP address. */
|
|
35
|
+
address?: string | undefined
|
|
36
|
+
/** Exact IP and CIDR rules to match. */
|
|
37
|
+
rules: readonly string[]
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/internal/Log.test.ts
CHANGED
|
@@ -102,7 +102,13 @@ describe('middleware', () => {
|
|
|
102
102
|
const api = app({ emit: (entry) => void entries.push(entry) })
|
|
103
103
|
api.use('*', async (c, next) => {
|
|
104
104
|
c.set('principal', {
|
|
105
|
-
apiKey: {
|
|
105
|
+
apiKey: {
|
|
106
|
+
allowedIps: [],
|
|
107
|
+
environment: 'production',
|
|
108
|
+
id: 'key_1',
|
|
109
|
+
orgId: 'org_1',
|
|
110
|
+
scopes: ['data:read'],
|
|
111
|
+
},
|
|
106
112
|
environment: 'production',
|
|
107
113
|
id: 'key_1',
|
|
108
114
|
orgId: 'org_1',
|
package/src/internal/OpenApi.ts
CHANGED
|
@@ -80,7 +80,7 @@ export const errorCodes = {
|
|
|
80
80
|
'api_key_malformed',
|
|
81
81
|
],
|
|
82
82
|
401: ['api_key_missing', 'api_key_invalid', 'unauthorized'],
|
|
83
|
-
403: ['api_key_forbidden', 'limit_exceeded'],
|
|
83
|
+
403: ['api_key_forbidden', 'api_key_ip_forbidden', 'limit_exceeded'],
|
|
84
84
|
404: [
|
|
85
85
|
'not_found',
|
|
86
86
|
'token_not_found',
|
|
@@ -460,6 +460,34 @@ describe('getClient', () => {
|
|
|
460
460
|
fetch.mockRestore()
|
|
461
461
|
}
|
|
462
462
|
})
|
|
463
|
+
|
|
464
|
+
test('sends Zone headers', async () => {
|
|
465
|
+
const fetch = vi.spyOn(globalThis, 'fetch').mockResolvedValue(
|
|
466
|
+
new Response(JSON.stringify({ columns: [], ok: true, row_count: 0, rows: [] }), {
|
|
467
|
+
headers: { 'content-type': 'application/json' },
|
|
468
|
+
}),
|
|
469
|
+
)
|
|
470
|
+
try {
|
|
471
|
+
const client = Tidx.getClient({
|
|
472
|
+
chainId: Zones.zoneE.id,
|
|
473
|
+
tidx: {
|
|
474
|
+
baseUrl: 'https://internal.indexer',
|
|
475
|
+
zoneHeaders: {
|
|
476
|
+
'CF-Access-Client-Id': 'client-id',
|
|
477
|
+
'CF-Access-Client-Secret': 'client-secret',
|
|
478
|
+
},
|
|
479
|
+
},
|
|
480
|
+
zone: Zones.zoneE,
|
|
481
|
+
})
|
|
482
|
+
await client.fetch({ query: 'select 1' })
|
|
483
|
+
|
|
484
|
+
const upstream = fetch.mock.calls[0]![0] as Request
|
|
485
|
+
expect(upstream.headers.get('CF-Access-Client-Id')).toBe('client-id')
|
|
486
|
+
expect(upstream.headers.get('CF-Access-Client-Secret')).toBe('client-secret')
|
|
487
|
+
} finally {
|
|
488
|
+
fetch.mockRestore()
|
|
489
|
+
}
|
|
490
|
+
})
|
|
463
491
|
})
|
|
464
492
|
|
|
465
493
|
describe('createGetClient', () => {
|
package/src/internal/Tidx.ts
CHANGED
|
@@ -41,6 +41,7 @@ export function resolve(
|
|
|
41
41
|
const config = typeof tidx === 'function' ? tidx(context) : tidx
|
|
42
42
|
return {
|
|
43
43
|
...Auth.resolveCredential({ chainId: context.chainId, value: config?.auth }),
|
|
44
|
+
...(context.zone && config?.zoneHeaders ? { zoneHeaders: config.zoneHeaders } : {}),
|
|
44
45
|
baseUrl:
|
|
45
46
|
Viem.resolveUrl(config?.baseUrl, context.chainId) ??
|
|
46
47
|
context.zone?.rpcUrls['tidx']?.http[0] ??
|
|
@@ -61,18 +62,25 @@ export declare namespace resolve {
|
|
|
61
62
|
basicAuth?: string | undefined
|
|
62
63
|
/** Bearer auth credential, or undefined to send no Bearer auth. */
|
|
63
64
|
bearerAuth?: string | undefined
|
|
65
|
+
/** Headers sent to Zone indexer upstreams. */
|
|
66
|
+
zoneHeaders?: Viem.ZoneHeaders | undefined
|
|
64
67
|
}
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
export function getClient(options: getClient.Options): Client {
|
|
68
71
|
const { chainId = Viem.defaultChainId, principal = null, tidx, zone } = options
|
|
69
|
-
const { baseUrl, basicAuth, bearerAuth } = resolve(tidx, {
|
|
72
|
+
const { baseUrl, basicAuth, bearerAuth, zoneHeaders } = resolve(tidx, {
|
|
73
|
+
chainId,
|
|
74
|
+
principal,
|
|
75
|
+
zone,
|
|
76
|
+
})
|
|
70
77
|
return withTransientRetry(
|
|
71
78
|
Tidx.create({
|
|
72
79
|
baseUrl,
|
|
73
80
|
basicAuth,
|
|
74
81
|
bearerAuth,
|
|
75
82
|
chainId,
|
|
83
|
+
headers: zoneHeaders,
|
|
76
84
|
}),
|
|
77
85
|
)
|
|
78
86
|
}
|
|
@@ -96,8 +104,12 @@ export function createGetClient(options: createGetClient.Options = {}): GetClien
|
|
|
96
104
|
const { principal = null, zone } = options
|
|
97
105
|
// Resolve to derive the cache key; `getClient` re-resolves to build the
|
|
98
106
|
// client on a miss. `resolve` is pure and cheap, so the extra call is free.
|
|
99
|
-
const { baseUrl, basicAuth, bearerAuth } = resolve(tidx, {
|
|
100
|
-
|
|
107
|
+
const { baseUrl, basicAuth, bearerAuth, zoneHeaders } = resolve(tidx, {
|
|
108
|
+
chainId,
|
|
109
|
+
principal,
|
|
110
|
+
zone,
|
|
111
|
+
})
|
|
112
|
+
const cacheKey = `${baseUrl}|${basicAuth ?? ''}|${bearerAuth ?? ''}|${JSON.stringify(zoneHeaders)}|${chainId}`
|
|
101
113
|
const existing = clients.get(cacheKey)
|
|
102
114
|
if (existing) return existing
|
|
103
115
|
|
|
@@ -312,12 +324,16 @@ export declare namespace getClient {
|
|
|
312
324
|
auth?: Auth.ChainCredential | undefined
|
|
313
325
|
/** Resolves the TIDX base URL for a Tempo chain id, or a static base URL. */
|
|
314
326
|
baseUrl?: UrlResolver | undefined
|
|
327
|
+
/** Headers sent to Zone indexer upstreams. */
|
|
328
|
+
zoneHeaders?: Viem.ZoneHeaders | undefined
|
|
315
329
|
}
|
|
316
330
|
| ((context: Viem.resolveRpc.Context) => {
|
|
317
331
|
/** Shared or per-chain TIDX credentials, or undefined for none. */
|
|
318
332
|
auth?: Auth.ChainCredential | undefined
|
|
319
333
|
/** TIDX base URL to use; falls back to the built-in public host when omitted. */
|
|
320
334
|
baseUrl?: string | undefined
|
|
335
|
+
/** Headers sent to Zone indexer upstreams. */
|
|
336
|
+
zoneHeaders?: Viem.ZoneHeaders | undefined
|
|
321
337
|
})
|
|
322
338
|
}
|
|
323
339
|
|
|
@@ -265,6 +265,34 @@ describe('getClient', () => {
|
|
|
265
265
|
fetch.mockRestore()
|
|
266
266
|
}
|
|
267
267
|
})
|
|
268
|
+
|
|
269
|
+
test('sends Zone headers to both internal and public Zone URLs', async () => {
|
|
270
|
+
const fetch = vi
|
|
271
|
+
.spyOn(globalThis, 'fetch')
|
|
272
|
+
.mockResolvedValue(Response.json({ id: 1, jsonrpc: '2.0', result: '0x1079' }))
|
|
273
|
+
try {
|
|
274
|
+
const client = Viem.getClient({
|
|
275
|
+
chainId: zone.id,
|
|
276
|
+
rpc: {
|
|
277
|
+
publicZoneUrl: 'https://public.zone.rpc.test',
|
|
278
|
+
zoneHeaders: {
|
|
279
|
+
'CF-Access-Client-Id': 'client-id',
|
|
280
|
+
'CF-Access-Client-Secret': 'client-secret',
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
zone,
|
|
284
|
+
zoneToken: 'zone-token',
|
|
285
|
+
})
|
|
286
|
+
await client.getChainId()
|
|
287
|
+
|
|
288
|
+
const [input, init] = fetch.mock.calls[0]!
|
|
289
|
+
const upstream = new Request(input, init)
|
|
290
|
+
expect(upstream.headers.get('CF-Access-Client-Id')).toBe('client-id')
|
|
291
|
+
expect(upstream.headers.get('CF-Access-Client-Secret')).toBe('client-secret')
|
|
292
|
+
} finally {
|
|
293
|
+
fetch.mockRestore()
|
|
294
|
+
}
|
|
295
|
+
})
|
|
268
296
|
})
|
|
269
297
|
|
|
270
298
|
describe('createGetClient', () => {
|
package/src/internal/Viem.ts
CHANGED
|
@@ -47,6 +47,9 @@ export type UrlResolver =
|
|
|
47
47
|
| Record<number, string>
|
|
48
48
|
| ((chainId: ChainId) => string | undefined)
|
|
49
49
|
|
|
50
|
+
/** Headers required when connecting to a Zone upstream. */
|
|
51
|
+
export type ZoneHeaders = Record<string, string>
|
|
52
|
+
|
|
50
53
|
/** Returns chain ids declared by a static chain-id-to-URL map. */
|
|
51
54
|
export function configuredChainIds(value: UrlResolver | undefined): number[] {
|
|
52
55
|
const urls = parseUrls(value)
|
|
@@ -83,6 +86,7 @@ export function resolveRpc(
|
|
|
83
86
|
return {
|
|
84
87
|
...auth,
|
|
85
88
|
...(publicZoneUrl ? { publicZoneUrl } : {}),
|
|
89
|
+
...(context.zone && config?.zoneHeaders ? { zoneHeaders: config.zoneHeaders } : {}),
|
|
86
90
|
url:
|
|
87
91
|
resolveUrl(config?.url, context.chainId) ??
|
|
88
92
|
context.zone?.rpcUrls['internal']?.http[0] ??
|
|
@@ -114,6 +118,8 @@ export declare namespace resolveRpc {
|
|
|
114
118
|
bearerAuth?: string | undefined
|
|
115
119
|
/** Public Zone RPC URL that accepts a caller-provided authorization token. */
|
|
116
120
|
publicZoneUrl?: string | undefined
|
|
121
|
+
/** Headers sent to Zone RPC upstreams. */
|
|
122
|
+
zoneHeaders?: ZoneHeaders | undefined
|
|
117
123
|
/**
|
|
118
124
|
* Resolved RPC URL, or `undefined` when neither a configured `rpc` nor the
|
|
119
125
|
* built-in {@link url} map covers the chain (e.g. an unconfigured localnet).
|
|
@@ -125,7 +131,7 @@ export declare namespace resolveRpc {
|
|
|
125
131
|
|
|
126
132
|
export function getClient(options: getClient.Options): getClient.ReturnType {
|
|
127
133
|
const { chainId = defaultChainId, principal = null, rpc, zone, zoneToken } = options
|
|
128
|
-
const { basicAuth, bearerAuth, publicZoneUrl, url } = resolveRpc(rpc, {
|
|
134
|
+
const { basicAuth, bearerAuth, publicZoneUrl, url, zoneHeaders } = resolveRpc(rpc, {
|
|
129
135
|
chainId,
|
|
130
136
|
principal,
|
|
131
137
|
zone,
|
|
@@ -141,9 +147,10 @@ export function getClient(options: getClient.Options): getClient.ReturnType {
|
|
|
141
147
|
? `Bearer ${bearerAuth}`
|
|
142
148
|
: undefined
|
|
143
149
|
const fetchOptions =
|
|
144
|
-
authorization || zoneToken
|
|
150
|
+
authorization || zoneToken || zoneHeaders
|
|
145
151
|
? {
|
|
146
152
|
headers: {
|
|
153
|
+
...(zoneHeaders ? Object.fromEntries(new Headers(zoneHeaders)) : {}),
|
|
147
154
|
...(authorization ? { Authorization: authorization } : {}),
|
|
148
155
|
...(zoneToken ? { [ZoneRpcAuthentication.headerName]: zoneToken } : {}),
|
|
149
156
|
},
|
|
@@ -171,18 +178,23 @@ export function createGetClient(options: createGetClient.Options = {}): createGe
|
|
|
171
178
|
const zones = new Map((options.zones ?? []).map((zone) => [zone.id, zone]))
|
|
172
179
|
|
|
173
180
|
return (chainId = defaultChainId_option, principal = null) => {
|
|
174
|
-
const { basicAuth, bearerAuth, url } = resolveRpc(rpc, {
|
|
181
|
+
const { basicAuth, bearerAuth, url, zoneHeaders } = resolveRpc(rpc, {
|
|
175
182
|
chainId,
|
|
176
183
|
principal,
|
|
177
184
|
zone: zones.get(chainId),
|
|
178
185
|
})
|
|
179
|
-
const cacheKey = JSON.stringify([url, basicAuth, bearerAuth, chainId])
|
|
186
|
+
const cacheKey = JSON.stringify([url, basicAuth, bearerAuth, zoneHeaders, chainId])
|
|
180
187
|
const existing = clients.get(cacheKey)
|
|
181
188
|
if (existing) return existing
|
|
182
189
|
|
|
183
190
|
const client = getClient({
|
|
184
191
|
chainId,
|
|
185
|
-
rpc: {
|
|
192
|
+
rpc: {
|
|
193
|
+
auth: basicAuth ?? bearerAuth,
|
|
194
|
+
url: () => url,
|
|
195
|
+
...(zoneHeaders ? { zoneHeaders } : {}),
|
|
196
|
+
},
|
|
197
|
+
zone: zones.get(chainId),
|
|
186
198
|
})
|
|
187
199
|
clients.set(cacheKey, client)
|
|
188
200
|
return client
|
|
@@ -240,6 +252,8 @@ export declare namespace getClient {
|
|
|
240
252
|
auth?: Auth.ChainCredential | undefined
|
|
241
253
|
/** Resolves the public Zone RPC URL used with caller-provided authorization. */
|
|
242
254
|
publicZoneUrl?: UrlResolver | undefined
|
|
255
|
+
/** Headers sent to Zone RPC upstreams. */
|
|
256
|
+
zoneHeaders?: ZoneHeaders | undefined
|
|
243
257
|
/** Resolves the RPC URL for a Tempo chain id. */
|
|
244
258
|
url?: UrlResolver | undefined
|
|
245
259
|
}
|
|
@@ -248,6 +262,8 @@ export declare namespace getClient {
|
|
|
248
262
|
auth?: Auth.ChainCredential | undefined
|
|
249
263
|
/** Public Zone RPC URL used with caller-provided authorization. */
|
|
250
264
|
publicZoneUrl?: string | undefined
|
|
265
|
+
/** Headers sent to Zone RPC upstreams. */
|
|
266
|
+
zoneHeaders?: ZoneHeaders | undefined
|
|
251
267
|
/** RPC URL to use; falls back to the built-in public host when omitted. */
|
|
252
268
|
url?: string | undefined
|
|
253
269
|
})
|
|
@@ -7,6 +7,12 @@ import * as Id from '../Id.js'
|
|
|
7
7
|
import * as Schema from '../Schema.js'
|
|
8
8
|
import * as Provider from './Provider.js'
|
|
9
9
|
|
|
10
|
+
/** Mechanisms that can first detect a funding deposit. */
|
|
11
|
+
export const detectionTriggers = ['chain', 'manual', 'poll', 'webhook'] as const
|
|
12
|
+
|
|
13
|
+
/** Mechanism that first detected a funding deposit. */
|
|
14
|
+
export type DetectionTrigger = (typeof detectionTriggers)[number]
|
|
15
|
+
|
|
10
16
|
/**
|
|
11
17
|
* Public funding deposit statuses:
|
|
12
18
|
*
|
|
@@ -95,6 +101,11 @@ export namespace schema {
|
|
|
95
101
|
.readonly(z.array(z.string()))
|
|
96
102
|
.check(z.describe(description), z.meta({ examples: [[`0x${'aa'.repeat(32)}`]] }))
|
|
97
103
|
|
|
104
|
+
/** Mechanism that first detected a funding deposit. */
|
|
105
|
+
export const DetectionTrigger = z
|
|
106
|
+
.enum(detectionTriggers)
|
|
107
|
+
.check(z.describe('How the deposit was first detected.'), z.meta({ examples: ['webhook'] }))
|
|
108
|
+
|
|
98
109
|
/** Funding deposit lifecycle status. */
|
|
99
110
|
export const Status = z
|
|
100
111
|
.enum(statuses)
|
|
@@ -187,6 +198,7 @@ export namespace schema {
|
|
|
187
198
|
z.meta({ examples: ['2026-08-04T00:01:00.000Z'] }),
|
|
188
199
|
),
|
|
189
200
|
depositAddressId: Snapshot.shape.depositAddressId,
|
|
201
|
+
detectionTrigger: z.optional(DetectionTrigger),
|
|
190
202
|
destinationAmount: Snapshot.shape.destinationAmount,
|
|
191
203
|
destinationAmountRequired: Snapshot.shape.destinationAmountRequired,
|
|
192
204
|
destinationChain: Snapshot.shape.destinationChain,
|
|
@@ -238,6 +250,46 @@ export type Public = z.output<typeof schema.FundingDeposit>
|
|
|
238
250
|
|
|
239
251
|
/** Creates a durable record for one provider-observed source transaction. */
|
|
240
252
|
export async function create(db: Db.Db, input: create.Input): Promise<FundingDeposits.Record> {
|
|
253
|
+
return FundingDeposits.insert(db, await record(db, input))
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Creates or returns the deposit for one verified chain transfer. */
|
|
257
|
+
export async function createFromSource(
|
|
258
|
+
db: Db.Db,
|
|
259
|
+
input: createFromSource.Input,
|
|
260
|
+
): Promise<FundingDeposits.insertOrGetSource.Result> {
|
|
261
|
+
const stored = await record(db, {
|
|
262
|
+
detectionTrigger: 'chain',
|
|
263
|
+
now: input.now,
|
|
264
|
+
providerRequestId: null,
|
|
265
|
+
snapshot: input.snapshot,
|
|
266
|
+
sourceTransactionHash: input.sourceTransactionHash,
|
|
267
|
+
sourceTransferIndex: input.sourceTransferIndex,
|
|
268
|
+
})
|
|
269
|
+
return FundingDeposits.insertOrGetSource(db, {
|
|
270
|
+
...stored,
|
|
271
|
+
sourceTransferIndex: input.sourceTransferIndex,
|
|
272
|
+
})
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export declare namespace createFromSource {
|
|
276
|
+
/** Verified source transfer used to create a chain-detected deposit. */
|
|
277
|
+
type Input = {
|
|
278
|
+
/** Detection time override for deterministic tests. */
|
|
279
|
+
now?: Date | undefined
|
|
280
|
+
/** Public route and verified source amount. */
|
|
281
|
+
snapshot: Snapshot
|
|
282
|
+
/** Source transaction containing the deposited transfer. */
|
|
283
|
+
sourceTransactionHash: string
|
|
284
|
+
/** Verified transfer position within the source transaction. */
|
|
285
|
+
sourceTransferIndex: number
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function record(
|
|
290
|
+
db: Db.Db,
|
|
291
|
+
input: Omit<create.Input, 'providerRequestId'> & { providerRequestId: string | null },
|
|
292
|
+
): Promise<FundingDeposits.Record> {
|
|
241
293
|
const now = input.now ?? new Date()
|
|
242
294
|
const depositAddressId = schema.Snapshot.shape.depositAddressId.parse(
|
|
243
295
|
input.snapshot.depositAddressId,
|
|
@@ -256,18 +308,23 @@ export async function create(db: Db.Db, input: create.Input): Promise<FundingDep
|
|
|
256
308
|
sourceToken: address.snapshot.sourceToken,
|
|
257
309
|
sourceTransactionHashes: [input.sourceTransactionHash],
|
|
258
310
|
})
|
|
259
|
-
return
|
|
311
|
+
return {
|
|
260
312
|
createdAt: now.toISOString(),
|
|
261
313
|
depositAddressId: snapshot.depositAddressId,
|
|
314
|
+
detectionTrigger: input.detectionTrigger ?? null,
|
|
262
315
|
environment: address.environment,
|
|
263
316
|
id: input.id ?? generateId(now),
|
|
264
317
|
orgId: address.orgId,
|
|
318
|
+
pollObservedAt: input.pollObservedAt ?? null,
|
|
265
319
|
projectId: address.projectId,
|
|
266
320
|
providerOutputAmount: input.providerOutputAmount ?? null,
|
|
267
321
|
providerOutputToken: address.providerOutputToken,
|
|
268
322
|
providerRequestId: input.providerRequestId,
|
|
269
323
|
providerRequestIds: [
|
|
270
|
-
...new Set([
|
|
324
|
+
...new Set([
|
|
325
|
+
...(input.providerRequestId === null ? [] : [input.providerRequestId]),
|
|
326
|
+
...(input.providerRequestIds ?? []),
|
|
327
|
+
]),
|
|
271
328
|
],
|
|
272
329
|
providerState: input.providerState ?? null,
|
|
273
330
|
providerTransactionHashes: input.providerTransactionHashes ?? [],
|
|
@@ -285,16 +342,21 @@ export async function create(db: Db.Db, input: create.Input): Promise<FundingDep
|
|
|
285
342
|
tempoGasPaid: input.tempoGasPaid ?? null,
|
|
286
343
|
updatedAt: now.toISOString(),
|
|
287
344
|
version: 1,
|
|
288
|
-
|
|
345
|
+
webhookReceivedAt: input.webhookReceivedAt ?? null,
|
|
346
|
+
}
|
|
289
347
|
}
|
|
290
348
|
|
|
291
349
|
export declare namespace create {
|
|
292
350
|
/** Fields required to persist a provider-observed deposit. */
|
|
293
351
|
type Input = {
|
|
352
|
+
/** Mechanism that first detected the deposit. */
|
|
353
|
+
detectionTrigger?: DetectionTrigger | undefined
|
|
294
354
|
/** Deposit id override for deterministic tests. */
|
|
295
355
|
id?: string | undefined
|
|
296
356
|
/** Detection time override for deterministic tests. */
|
|
297
357
|
now?: Date | undefined
|
|
358
|
+
/** When polling first observed the provider request. */
|
|
359
|
+
pollObservedAt?: string | undefined
|
|
298
360
|
/** Provider output amount confirmed on Tempo. */
|
|
299
361
|
providerOutputAmount?: z.output<typeof Schema.TokenAmount> | undefined
|
|
300
362
|
/** Provider request that reported the source transaction. */
|
|
@@ -323,6 +385,8 @@ export declare namespace create {
|
|
|
323
385
|
subsidyAmount?: z.output<typeof Schema.TokenAmount> | undefined
|
|
324
386
|
/** Tempo gas paid for settlement, in base units. */
|
|
325
387
|
tempoGasPaid?: string | undefined
|
|
388
|
+
/** When Tempo first received an authenticated provider webhook. */
|
|
389
|
+
webhookReceivedAt?: string | undefined
|
|
326
390
|
}
|
|
327
391
|
}
|
|
328
392
|
|
|
@@ -362,12 +426,12 @@ export async function transition(
|
|
|
362
426
|
expectedVersion: options.expectedVersion,
|
|
363
427
|
id: options.id,
|
|
364
428
|
providerOutputAmount: options.providerOutputAmount ?? current.providerOutputAmount,
|
|
365
|
-
providerRequestId: current.providerRequestId,
|
|
429
|
+
providerRequestId: current.providerRequestId ?? options.providerRequestId ?? null,
|
|
366
430
|
providerRequestIds: options.providerRequestIds ?? current.providerRequestIds,
|
|
367
431
|
providerState: options.providerState ?? current.providerState,
|
|
368
432
|
providerTransactionHashes:
|
|
369
433
|
options.providerTransactionHashes ?? current.providerTransactionHashes,
|
|
370
|
-
providerTransferIndex: current.providerTransferIndex,
|
|
434
|
+
providerTransferIndex: options.providerTransferIndex ?? current.providerTransferIndex,
|
|
371
435
|
retryState: options.retryState ?? current.retryState,
|
|
372
436
|
settlementTransaction: options.settlementTransaction ?? current.settlementTransaction,
|
|
373
437
|
settlementTransactionHash:
|
|
@@ -395,12 +459,16 @@ export declare namespace transition {
|
|
|
395
459
|
id: string
|
|
396
460
|
/** Provider output amount confirmed on Tempo. */
|
|
397
461
|
providerOutputAmount?: z.output<typeof Schema.TokenAmount> | undefined
|
|
462
|
+
/** Provider request attached after chain-first detection. */
|
|
463
|
+
providerRequestId?: string | undefined
|
|
398
464
|
/** Private provider request identifiers. */
|
|
399
465
|
providerRequestIds?: readonly string[] | undefined
|
|
400
466
|
/** Bounded private provider state. */
|
|
401
467
|
providerState?: PrivateState | undefined
|
|
402
468
|
/** Provider-leg transaction references. */
|
|
403
469
|
providerTransactionHashes?: readonly string[] | undefined
|
|
470
|
+
/** Deposit position within the attached provider request. */
|
|
471
|
+
providerTransferIndex?: number | undefined
|
|
404
472
|
/** Bounded private retry state. */
|
|
405
473
|
retryState?: PrivateState | undefined
|
|
406
474
|
/** Persisted settlement transaction bytes for exactly-once rebroadcast. */
|
|
@@ -434,6 +502,7 @@ export function toPublic(record: FundingDeposits.Record): Public {
|
|
|
434
502
|
return schema.FundingDeposit.parse({
|
|
435
503
|
...record.snapshot,
|
|
436
504
|
createdAt: record.createdAt,
|
|
505
|
+
...(record.detectionTrigger === null ? {} : { detectionTrigger: record.detectionTrigger }),
|
|
437
506
|
id: record.id,
|
|
438
507
|
...(record.sourceTransferIndex === null
|
|
439
508
|
? {}
|