tapimo 0.5.2 → 0.6.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 +13987 -5755
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js.map +1 -1
- package/dist/admin/App.d.ts +910 -412
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +4 -0
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/early-access.d.ts +148 -0
- package/dist/admin/apps/early-access.d.ts.map +1 -0
- package/dist/admin/apps/early-access.js +133 -0
- package/dist/admin/apps/early-access.js.map +1 -0
- package/dist/admin/apps/verified-tokens.d.ts +643 -118
- package/dist/admin/apps/verified-tokens.d.ts.map +1 -1
- package/dist/admin/apps/verified-tokens.js +214 -1
- package/dist/admin/apps/verified-tokens.js.map +1 -1
- package/dist/admin/ui.generated.d.ts +1 -1
- package/dist/admin/ui.generated.d.ts.map +1 -1
- package/dist/admin/ui.generated.js +45 -33
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/apps/data/App.d.ts +3733 -2428
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/App.js +11 -6
- package/dist/apps/data/App.js.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts +34 -1
- package/dist/apps/data/FundingQuoteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingQuoteProviders.js +193 -2
- package/dist/apps/data/FundingQuoteProviders.js.map +1 -1
- package/dist/apps/data/FxOracle.d.ts +71 -0
- package/dist/apps/data/FxOracle.d.ts.map +1 -0
- package/dist/apps/data/FxOracle.js +97 -0
- package/dist/apps/data/FxOracle.js.map +1 -0
- package/dist/apps/data/index.d.ts +1 -0
- package/dist/apps/data/index.d.ts.map +1 -1
- package/dist/apps/data/index.js +1 -0
- package/dist/apps/data/index.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +3575 -1358
- package/dist/apps/data/routes/activities.d.ts.map +1 -1
- package/dist/apps/data/routes/activities.js +230 -88
- package/dist/apps/data/routes/activities.js.map +1 -1
- package/dist/apps/data/routes/addresses.d.ts +963 -342
- package/dist/apps/data/routes/addresses.d.ts.map +1 -1
- package/dist/apps/data/routes/addresses.js +11 -9
- package/dist/apps/data/routes/addresses.js.map +1 -1
- package/dist/apps/data/routes/balances.d.ts +108 -57
- package/dist/apps/data/routes/balances.d.ts.map +1 -1
- package/dist/apps/data/routes/balances.js +88 -45
- package/dist/apps/data/routes/balances.js.map +1 -1
- package/dist/apps/data/routes/coingecko.d.ts +169 -169
- package/dist/apps/data/routes/exchanges.d.ts +952 -201
- package/dist/apps/data/routes/exchanges.d.ts.map +1 -1
- package/dist/apps/data/routes/exchanges.js +518 -76
- package/dist/apps/data/routes/exchanges.js.map +1 -1
- package/dist/apps/data/routes/fee-amm.d.ts +24 -24
- package/dist/apps/data/routes/funding.d.ts +8 -2
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +32 -6
- 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 +392 -315
- package/dist/apps/data/routes/receipts.d.ts.map +1 -1
- package/dist/apps/data/routes/receipts.js +152 -77
- package/dist/apps/data/routes/receipts.js.map +1 -1
- package/dist/apps/data/routes/tokens.d.ts +166 -173
- package/dist/apps/data/routes/tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/tokens.js +14 -12
- package/dist/apps/data/routes/tokens.js.map +1 -1
- package/dist/apps/data/routes/transactions.d.ts +277 -536
- package/dist/apps/data/routes/transactions.d.ts.map +1 -1
- package/dist/apps/data/routes/transactions.js +96 -77
- package/dist/apps/data/routes/transactions.js.map +1 -1
- package/dist/apps/data/routes/transfers.d.ts +82 -17
- package/dist/apps/data/routes/transfers.d.ts.map +1 -1
- package/dist/apps/data/routes/transfers.js +70 -13
- package/dist/apps/data/routes/transfers.js.map +1 -1
- package/dist/apps/data/routes/valuation.d.ts +307 -0
- package/dist/apps/data/routes/valuation.d.ts.map +1 -0
- package/dist/apps/data/routes/valuation.js +292 -0
- package/dist/apps/data/routes/valuation.js.map +1 -0
- package/dist/apps/data/routes/verified-tokens.d.ts.map +1 -1
- package/dist/apps/data/routes/verified-tokens.js +7 -5
- package/dist/apps/data/routes/verified-tokens.js.map +1 -1
- package/dist/apps/data/routes/webhooks.d.ts +459 -479
- package/dist/apps/data/routes/webhooks.d.ts.map +1 -1
- package/dist/apps/data/routes/webhooks.js +124 -25
- package/dist/apps/data/routes/webhooks.js.map +1 -1
- package/dist/apps/index.d.ts +1 -1
- package/dist/apps/index.d.ts.map +1 -1
- package/dist/apps/index.js +1 -1
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/management/App.d.ts +8606 -1681
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +12 -1
- package/dist/apps/management/App.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +23 -0
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +5 -3
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +88 -48
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +10 -2
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/faucet.d.ts +218 -0
- package/dist/apps/management/routes/faucet.d.ts.map +1 -0
- package/dist/apps/management/routes/faucet.js +139 -0
- package/dist/apps/management/routes/faucet.js.map +1 -0
- package/dist/apps/management/routes/invitations.d.ts +121 -121
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +3 -0
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/invite-links.d.ts +197 -168
- package/dist/apps/management/routes/invite-links.d.ts.map +1 -1
- package/dist/apps/management/routes/invite-links.js +26 -1
- package/dist/apps/management/routes/invite-links.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +2 -0
- package/dist/apps/management/routes/me.d.ts.map +1 -1
- package/dist/apps/management/routes/me.js +4 -0
- package/dist/apps/management/routes/me.js.map +1 -1
- package/dist/apps/management/routes/members.d.ts +49 -49
- package/dist/apps/management/routes/orgs.d.ts +91 -91
- package/dist/apps/management/routes/projects.d.ts +115 -115
- package/dist/apps/management/routes/usage.d.ts +35 -32
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +4 -0
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/management/routes/verified-token-requests.d.ts +697 -0
- package/dist/apps/management/routes/verified-token-requests.d.ts.map +1 -0
- package/dist/apps/management/routes/verified-token-requests.js +271 -0
- package/dist/apps/management/routes/verified-token-requests.js.map +1 -0
- package/dist/apps/management/routes/webhooks.d.ts +6977 -0
- package/dist/apps/management/routes/webhooks.d.ts.map +1 -0
- package/dist/apps/management/routes/webhooks.js +573 -0
- package/dist/apps/management/routes/webhooks.js.map +1 -0
- package/dist/apps/mcp.d.ts +9 -2
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +9 -4
- package/dist/apps/mcp.js.map +1 -1
- package/dist/cli/Cli.d.ts.map +1 -1
- package/dist/cli/Cli.js +7 -2
- package/dist/cli/Cli.js.map +1 -1
- package/dist/db/Db.d.ts +4 -0
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +83 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +41 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +45 -0
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/earlyAccess.d.ts +50 -0
- package/dist/db/tables/earlyAccess.d.ts.map +1 -0
- package/dist/db/tables/earlyAccess.js +63 -0
- package/dist/db/tables/earlyAccess.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +12 -1
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/invitations.js +23 -0
- package/dist/db/tables/invitations.js.map +1 -1
- package/dist/db/tables/inviteLinks.d.ts +10 -2
- package/dist/db/tables/inviteLinks.d.ts.map +1 -1
- package/dist/db/tables/inviteLinks.js +10 -1
- package/dist/db/tables/inviteLinks.js.map +1 -1
- package/dist/db/tables/sponsoredTransactions.d.ts +2 -0
- package/dist/db/tables/sponsoredTransactions.d.ts.map +1 -1
- package/dist/db/tables/sponsoredTransactions.js +2 -0
- package/dist/db/tables/sponsoredTransactions.js.map +1 -1
- package/dist/db/tables/verifiedTokenRequests.d.ts +94 -0
- package/dist/db/tables/verifiedTokenRequests.d.ts.map +1 -0
- package/dist/db/tables/verifiedTokenRequests.js +120 -0
- package/dist/db/tables/verifiedTokenRequests.js.map +1 -0
- package/dist/db/tables/webhookSubscriptions.d.ts +14 -0
- package/dist/db/tables/webhookSubscriptions.d.ts.map +1 -1
- package/dist/db/tables/webhookSubscriptions.js +16 -0
- package/dist/db/tables/webhookSubscriptions.js.map +1 -1
- package/dist/internal/Auth.d.ts +43 -2
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +60 -45
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/FundingQuotes.d.ts +69 -15
- package/dist/internal/FundingQuotes.d.ts.map +1 -1
- package/dist/internal/FundingQuotes.js +74 -25
- package/dist/internal/FundingQuotes.js.map +1 -1
- package/dist/internal/Log.d.ts +19 -1
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +2 -0
- package/dist/internal/Log.js.map +1 -1
- package/dist/internal/MetricSink.d.ts.map +1 -1
- package/dist/internal/MetricSink.js +9 -0
- package/dist/internal/MetricSink.js.map +1 -1
- package/dist/internal/OpenApi.d.ts +2 -2
- package/dist/internal/OpenApi.d.ts.map +1 -1
- package/dist/internal/OpenApi.js +3 -0
- package/dist/internal/OpenApi.js.map +1 -1
- package/dist/internal/Schema.d.ts +14 -5
- package/dist/internal/Schema.d.ts.map +1 -1
- package/dist/internal/Schema.js +23 -5
- package/dist/internal/Schema.js.map +1 -1
- package/dist/internal/Value.d.ts +18 -0
- package/dist/internal/Value.d.ts.map +1 -1
- package/dist/internal/Value.js +11 -1
- package/dist/internal/Value.js.map +1 -1
- package/dist/internal/VerifiedTokens.d.ts.map +1 -1
- package/dist/internal/VerifiedTokens.js +10 -1
- package/dist/internal/VerifiedTokens.js.map +1 -1
- package/dist/internal/Viem.d.ts +1 -1
- package/dist/internal/Webhooks.d.ts +24 -3
- package/dist/internal/Webhooks.d.ts.map +1 -1
- package/dist/internal/Webhooks.js +34 -8
- package/dist/internal/Webhooks.js.map +1 -1
- package/package.json +2 -2
- package/src/App.ts +9 -1
- package/src/Client.test-d.ts +638 -6
- package/src/admin/App.ts +4 -0
- package/src/admin/apps/early-access.test.ts +125 -0
- package/src/admin/apps/early-access.ts +175 -0
- package/src/admin/apps/verified-tokens.test.ts +201 -0
- package/src/admin/apps/verified-tokens.ts +532 -286
- package/src/admin/ui/plugin.ts +2 -1
- package/src/admin/ui/src/lib/early-access.ts +63 -0
- package/src/admin/ui/src/lib/verified-tokens.ts +79 -0
- package/src/admin/ui/src/routeTree.gen.ts +33 -2
- package/src/admin/ui/src/routes/__root.tsx +2 -1
- package/src/admin/ui/src/routes/early-access.tsx +157 -0
- package/src/admin/ui/src/routes/verified-tokens.tsx +230 -19
- package/src/admin/ui.generated.ts +48 -33
- package/src/apps/data/App.ts +22 -14
- package/src/apps/data/FundingQuoteProviders.test.ts +298 -0
- package/src/apps/data/FundingQuoteProviders.ts +257 -4
- package/src/apps/data/FxOracle.test.ts +118 -0
- package/src/apps/data/FxOracle.ts +153 -0
- package/src/apps/data/index.ts +1 -0
- package/src/apps/data/routes/activities.test.ts +173 -3
- package/src/apps/data/routes/activities.ts +403 -132
- package/src/apps/data/routes/addresses.ts +19 -9
- package/src/apps/data/routes/balances.test.ts +144 -11
- package/src/apps/data/routes/balances.ts +140 -51
- package/src/apps/data/routes/exchanges.test.ts +540 -9
- package/src/apps/data/routes/exchanges.ts +690 -103
- package/src/apps/data/routes/funding.test.ts +84 -0
- package/src/apps/data/routes/funding.ts +38 -6
- package/src/apps/data/routes/receipts.test.ts +125 -23
- package/src/apps/data/routes/receipts.ts +205 -100
- package/src/apps/data/routes/tokens.test.ts +43 -0
- package/src/apps/data/routes/tokens.ts +27 -12
- package/src/apps/data/routes/transactions.test.ts +72 -19
- package/src/apps/data/routes/transactions.ts +162 -92
- package/src/apps/data/routes/transfers.test.ts +146 -4
- package/src/apps/data/routes/transfers.ts +91 -13
- package/src/apps/data/routes/valuation.test.ts +422 -0
- package/src/apps/data/routes/valuation.ts +423 -0
- package/src/apps/data/routes/verified-tokens.ts +14 -2
- package/src/apps/data/routes/webhooks.test.ts +56 -8
- package/src/apps/data/routes/webhooks.ts +144 -27
- package/src/apps/index.ts +1 -1
- package/src/apps/management/App.ts +13 -0
- package/src/apps/management/routes/api-keys.test.ts +87 -0
- package/src/apps/management/routes/api-keys.ts +4 -3
- package/src/apps/management/routes/billing.test.ts +112 -1
- package/src/apps/management/routes/billing.ts +7 -2
- package/src/apps/management/routes/faucet.test.ts +79 -0
- package/src/apps/management/routes/faucet.ts +185 -0
- package/src/apps/management/routes/invitations.test.ts +172 -0
- package/src/apps/management/routes/invitations.ts +3 -0
- package/src/apps/management/routes/invite-links.test.ts +117 -0
- package/src/apps/management/routes/invite-links.ts +43 -1
- package/src/apps/management/routes/me.test.ts +140 -88
- package/src/apps/management/routes/me.ts +6 -0
- package/src/apps/management/routes/usage.test.ts +6 -0
- package/src/apps/management/routes/usage.ts +7 -0
- package/src/apps/management/routes/verified-token-requests.test.ts +377 -0
- package/src/apps/management/routes/verified-token-requests.ts +397 -0
- package/src/apps/management/routes/webhooks.test.ts +426 -0
- package/src/apps/management/routes/webhooks.ts +672 -0
- package/src/apps/mcp.test.ts +13 -0
- package/src/apps/mcp.ts +18 -5
- package/src/cli/Cli.test.ts +36 -0
- package/src/cli/Cli.ts +7 -2
- package/src/db/Db.test.ts +19 -0
- package/src/db/Db.ts +95 -0
- package/src/db/Schema.ts +53 -0
- package/src/db/tables/earlyAccess.ts +86 -0
- package/src/db/tables/invitations.ts +26 -1
- package/src/db/tables/inviteLinks.ts +22 -3
- package/src/db/tables/sponsoredTransactions.ts +4 -0
- package/src/db/tables/verifiedTokenRequests.test.ts +200 -0
- package/src/db/tables/verifiedTokenRequests.ts +182 -0
- package/src/db/tables/webhookSubscriptions.ts +31 -0
- package/src/internal/Auth.ts +73 -45
- package/src/internal/FundingQuotes.test.ts +143 -1
- package/src/internal/FundingQuotes.ts +95 -28
- package/src/internal/Log.test-d.ts +21 -0
- package/src/internal/Log.test.ts +50 -0
- package/src/internal/Log.ts +24 -1
- package/src/internal/MetricSink.test.ts +160 -0
- package/src/internal/MetricSink.ts +10 -0
- package/src/internal/OpenApi.ts +3 -0
- package/src/internal/Schema.ts +50 -5
- package/src/internal/Value.test.ts +20 -0
- package/src/internal/Value.ts +24 -1
- package/src/internal/VerifiedTokens.ts +9 -1
- package/src/internal/Webhooks.test.ts +140 -0
- package/src/internal/Webhooks.ts +53 -9
|
@@ -10,6 +10,7 @@ const baseUsdcRequest = {
|
|
|
10
10
|
|
|
11
11
|
describe('listQuotes', () => {
|
|
12
12
|
test('returns normalized successful provider quotes', async () => {
|
|
13
|
+
const attempts: FundingQuotes.ProviderAttempt[] = []
|
|
13
14
|
let seen: FundingQuotes.QuoteProbeInput | undefined
|
|
14
15
|
const quotes = await FundingQuotes.listQuotes({
|
|
15
16
|
...baseUsdcRequest,
|
|
@@ -33,6 +34,7 @@ describe('listQuotes', () => {
|
|
|
33
34
|
},
|
|
34
35
|
},
|
|
35
36
|
},
|
|
37
|
+
onAttempt: (attempt) => attempts.push(attempt),
|
|
36
38
|
})
|
|
37
39
|
|
|
38
40
|
expect(seen?.candidate.id).toBe('base-usdc-tempo-usdce-relay')
|
|
@@ -50,13 +52,23 @@ describe('listQuotes', () => {
|
|
|
50
52
|
outputAmountFormatted: '0.99',
|
|
51
53
|
},
|
|
52
54
|
})
|
|
55
|
+
expect(attempts.map(stableAttempt)).toEqual([{ outcome: 'available', provider: 'relay' }])
|
|
56
|
+
expect(attempts[0]?.durationMs).toEqual(expect.any(Number))
|
|
53
57
|
})
|
|
54
58
|
|
|
55
59
|
test('returns no static candidates when no adapter is configured', async () => {
|
|
56
|
-
|
|
60
|
+
const attempts: FundingQuotes.ProviderAttempt[] = []
|
|
61
|
+
await expect(
|
|
62
|
+
FundingQuotes.listQuotes({
|
|
63
|
+
...baseUsdcRequest,
|
|
64
|
+
onAttempt: (attempt) => attempts.push(attempt),
|
|
65
|
+
}),
|
|
66
|
+
).resolves.toEqual([])
|
|
67
|
+
expect(attempts).toEqual([])
|
|
57
68
|
})
|
|
58
69
|
|
|
59
70
|
test('omits provider-declared unavailable quotes', async () => {
|
|
71
|
+
const attempts: FundingQuotes.ProviderAttempt[] = []
|
|
60
72
|
const quotes = FundingQuotes.listQuotes({
|
|
61
73
|
...baseUsdcRequest,
|
|
62
74
|
adapters: {
|
|
@@ -66,12 +78,15 @@ describe('listQuotes', () => {
|
|
|
66
78
|
},
|
|
67
79
|
},
|
|
68
80
|
},
|
|
81
|
+
onAttempt: (attempt) => attempts.push(attempt),
|
|
69
82
|
})
|
|
70
83
|
|
|
71
84
|
await expect(quotes).resolves.toEqual([])
|
|
85
|
+
expect(attempts.map(stableAttempt)).toEqual([{ outcome: 'unavailable', provider: 'relay' }])
|
|
72
86
|
})
|
|
73
87
|
|
|
74
88
|
test('rejects provider failures when no quote succeeds', async () => {
|
|
89
|
+
const attempts: FundingQuotes.ProviderAttempt[] = []
|
|
75
90
|
const quotes = FundingQuotes.listQuotes({
|
|
76
91
|
...baseUsdcRequest,
|
|
77
92
|
adapters: {
|
|
@@ -81,6 +96,7 @@ describe('listQuotes', () => {
|
|
|
81
96
|
},
|
|
82
97
|
},
|
|
83
98
|
},
|
|
99
|
+
onAttempt: (attempt) => attempts.push(attempt),
|
|
84
100
|
})
|
|
85
101
|
|
|
86
102
|
const cause = await quotes.catch((error: unknown) => error)
|
|
@@ -88,6 +104,31 @@ describe('listQuotes', () => {
|
|
|
88
104
|
failures: [{ cause: expect.any(Error), provider: 'relay' }],
|
|
89
105
|
name: 'FundingQuotes.ProviderQuoteError',
|
|
90
106
|
})
|
|
107
|
+
expect(attempts.map(stableAttempt)).toEqual([
|
|
108
|
+
{ cause: expect.any(Error), outcome: 'failed', provider: 'relay' },
|
|
109
|
+
])
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('records thin quotes separately from available quotes', async () => {
|
|
113
|
+
const attempts: FundingQuotes.ProviderAttempt[] = []
|
|
114
|
+
const quotes = await FundingQuotes.listQuotes({
|
|
115
|
+
...baseUsdcRequest,
|
|
116
|
+
adapters: {
|
|
117
|
+
relay: {
|
|
118
|
+
async probeQuote() {
|
|
119
|
+
return {
|
|
120
|
+
outputAmount: '990000',
|
|
121
|
+
sampledAt: '2026-01-01T00:00:00Z',
|
|
122
|
+
status: 'thin',
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
onAttempt: (attempt) => attempts.push(attempt),
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
expect(quotes).toHaveLength(1)
|
|
131
|
+
expect(attempts.map(stableAttempt)).toEqual([{ outcome: 'thin', provider: 'relay' }])
|
|
91
132
|
})
|
|
92
133
|
|
|
93
134
|
test('rejects successful provider responses without output amounts', async () => {
|
|
@@ -108,6 +149,7 @@ describe('listQuotes', () => {
|
|
|
108
149
|
})
|
|
109
150
|
|
|
110
151
|
test('aborts providers that exceed the quote deadline', async () => {
|
|
152
|
+
const attempts: FundingQuotes.ProviderAttempt[] = []
|
|
111
153
|
let aborted = false
|
|
112
154
|
const quotes = FundingQuotes.listQuotes({
|
|
113
155
|
...baseUsdcRequest,
|
|
@@ -122,12 +164,20 @@ describe('listQuotes', () => {
|
|
|
122
164
|
},
|
|
123
165
|
},
|
|
124
166
|
},
|
|
167
|
+
onAttempt: (attempt) => attempts.push(attempt),
|
|
125
168
|
})
|
|
126
169
|
|
|
127
170
|
await expect(quotes).rejects.toThrowErrorMatchingInlineSnapshot(
|
|
128
171
|
`[FundingQuotes.ProviderQuoteError]`,
|
|
129
172
|
)
|
|
130
173
|
expect(aborted).toBe(true)
|
|
174
|
+
expect(attempts.map(stableAttempt)).toEqual([
|
|
175
|
+
{
|
|
176
|
+
cause: expect.any(FundingQuotes.ProviderAdapterTimeoutError),
|
|
177
|
+
outcome: 'failed',
|
|
178
|
+
provider: 'relay',
|
|
179
|
+
},
|
|
180
|
+
])
|
|
131
181
|
})
|
|
132
182
|
|
|
133
183
|
test('passes cache policy through to providers', async () => {
|
|
@@ -149,6 +199,7 @@ describe('listQuotes', () => {
|
|
|
149
199
|
})
|
|
150
200
|
|
|
151
201
|
test('returns successful quotes when another provider fails', async () => {
|
|
202
|
+
const attempts: FundingQuotes.ProviderAttempt[] = []
|
|
152
203
|
const quotes = await FundingQuotes.listQuotes({
|
|
153
204
|
...baseUsdcRequest,
|
|
154
205
|
adapters: {
|
|
@@ -167,14 +218,63 @@ describe('listQuotes', () => {
|
|
|
167
218
|
},
|
|
168
219
|
},
|
|
169
220
|
},
|
|
221
|
+
onAttempt: (attempt) => attempts.push(attempt),
|
|
170
222
|
provider: undefined,
|
|
171
223
|
})
|
|
172
224
|
|
|
173
225
|
expect(quotes).toHaveLength(1)
|
|
174
226
|
expect(quotes[0]?.provider.id).toBe('relay')
|
|
227
|
+
expect(attempts.map(stableAttempt)).toEqual([
|
|
228
|
+
{ cause: expect.any(Error), outcome: 'failed', provider: 'across' },
|
|
229
|
+
{ outcome: 'available', provider: 'relay' },
|
|
230
|
+
])
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
test('does not let attempt observers change quote outcomes', async () => {
|
|
234
|
+
const adapters = {
|
|
235
|
+
relay: {
|
|
236
|
+
async probeQuote() {
|
|
237
|
+
return {
|
|
238
|
+
outputAmount: '990000',
|
|
239
|
+
sampledAt: '2026-01-01T00:00:00Z',
|
|
240
|
+
status: 'available' as const,
|
|
241
|
+
}
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
}
|
|
245
|
+
await expect(
|
|
246
|
+
FundingQuotes.listQuotes({
|
|
247
|
+
...baseUsdcRequest,
|
|
248
|
+
adapters,
|
|
249
|
+
onAttempt() {
|
|
250
|
+
throw new Error('observer failed')
|
|
251
|
+
},
|
|
252
|
+
}),
|
|
253
|
+
).resolves.toHaveLength(1)
|
|
254
|
+
|
|
255
|
+
await expect(
|
|
256
|
+
FundingQuotes.listQuotes({
|
|
257
|
+
...baseUsdcRequest,
|
|
258
|
+
adapters: {
|
|
259
|
+
relay: {
|
|
260
|
+
async probeQuote() {
|
|
261
|
+
throw new Error('provider failed')
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
onAttempt() {
|
|
266
|
+
throw new Error('observer failed')
|
|
267
|
+
},
|
|
268
|
+
}),
|
|
269
|
+
).rejects.toThrowErrorMatchingInlineSnapshot(`[FundingQuotes.ProviderQuoteError]`)
|
|
175
270
|
})
|
|
176
271
|
})
|
|
177
272
|
|
|
273
|
+
function stableAttempt(attempt: FundingQuotes.ProviderAttempt) {
|
|
274
|
+
const { durationMs: _durationMs, ...rest } = attempt
|
|
275
|
+
return rest
|
|
276
|
+
}
|
|
277
|
+
|
|
178
278
|
describe('listChains', () => {
|
|
179
279
|
test('returns only chain and token inputs with quote inventory', () => {
|
|
180
280
|
expect(FundingQuotes.listChains().map((chain) => chain.id)).toEqual([
|
|
@@ -261,6 +361,41 @@ describe('listQuoteCandidates', () => {
|
|
|
261
361
|
'ethereum-usdt-tempo-usdt0-squid',
|
|
262
362
|
])
|
|
263
363
|
})
|
|
364
|
+
|
|
365
|
+
test('selects proven Symbiosis stablecoin corridors', () => {
|
|
366
|
+
const candidates = [
|
|
367
|
+
FundingQuotes.listQuoteCandidates({
|
|
368
|
+
destinationToken: 'USDC.e',
|
|
369
|
+
provider: 'symbiosis',
|
|
370
|
+
sourceAmount: '1000000',
|
|
371
|
+
sourceAmountUnits: 'baseUnits',
|
|
372
|
+
sourceChain: 'base',
|
|
373
|
+
sourceToken: 'USDC',
|
|
374
|
+
})[0],
|
|
375
|
+
FundingQuotes.listQuoteCandidates({
|
|
376
|
+
destinationToken: 'USDT0',
|
|
377
|
+
provider: 'symbiosis',
|
|
378
|
+
sourceAmount: '1000000',
|
|
379
|
+
sourceAmountUnits: 'baseUnits',
|
|
380
|
+
sourceChain: 'ethereum',
|
|
381
|
+
sourceToken: 'USDT',
|
|
382
|
+
})[0],
|
|
383
|
+
FundingQuotes.listQuoteCandidates({
|
|
384
|
+
destinationToken: 'USDT0',
|
|
385
|
+
provider: 'symbiosis',
|
|
386
|
+
sourceAmount: '1000000',
|
|
387
|
+
sourceAmountUnits: 'baseUnits',
|
|
388
|
+
sourceChain: 'tron',
|
|
389
|
+
sourceToken: 'USDT',
|
|
390
|
+
})[0],
|
|
391
|
+
]
|
|
392
|
+
|
|
393
|
+
expect(candidates.map((candidate) => candidate?.id)).toEqual([
|
|
394
|
+
'base-usdc-tempo-usdce-symbiosis',
|
|
395
|
+
'ethereum-usdt-tempo-usdt0-symbiosis',
|
|
396
|
+
'tron-usdt-tempo-usdt0-symbiosis',
|
|
397
|
+
])
|
|
398
|
+
})
|
|
264
399
|
})
|
|
265
400
|
|
|
266
401
|
describe('listProviders', () => {
|
|
@@ -295,6 +430,13 @@ describe('listProviders', () => {
|
|
|
295
430
|
"requiresApiKey": true,
|
|
296
431
|
"status": "unconfigured",
|
|
297
432
|
},
|
|
433
|
+
{
|
|
434
|
+
"id": "symbiosis",
|
|
435
|
+
"kind": "crossChainDex",
|
|
436
|
+
"name": "Symbiosis",
|
|
437
|
+
"requiresApiKey": false,
|
|
438
|
+
"status": "unconfigured",
|
|
439
|
+
},
|
|
298
440
|
]
|
|
299
441
|
`)
|
|
300
442
|
})
|
|
@@ -20,6 +20,7 @@ const sourceChains = [
|
|
|
20
20
|
providerIds: {
|
|
21
21
|
across: '1',
|
|
22
22
|
relay: '1',
|
|
23
|
+
symbiosis: '1',
|
|
23
24
|
},
|
|
24
25
|
slug: 'ethereum',
|
|
25
26
|
tokens: [
|
|
@@ -75,6 +76,7 @@ const sourceChains = [
|
|
|
75
76
|
across: '8453',
|
|
76
77
|
relay: '8453',
|
|
77
78
|
rhino: 'BASE',
|
|
79
|
+
symbiosis: '8453',
|
|
78
80
|
},
|
|
79
81
|
slug: 'base',
|
|
80
82
|
tokens: [
|
|
@@ -113,6 +115,7 @@ const sourceChains = [
|
|
|
113
115
|
providerIds: {
|
|
114
116
|
across: '56',
|
|
115
117
|
relay: '56',
|
|
118
|
+
symbiosis: '56',
|
|
116
119
|
},
|
|
117
120
|
slug: 'bnb',
|
|
118
121
|
tokens: [
|
|
@@ -163,6 +166,7 @@ const sourceChains = [
|
|
|
163
166
|
name: 'Tron',
|
|
164
167
|
providerIds: {
|
|
165
168
|
rhino: 'TRON',
|
|
169
|
+
symbiosis: '728126428',
|
|
166
170
|
},
|
|
167
171
|
slug: 'tron',
|
|
168
172
|
tokens: [
|
|
@@ -252,6 +256,13 @@ const providers = [
|
|
|
252
256
|
requiresApiKey: true,
|
|
253
257
|
status: 'unconfigured',
|
|
254
258
|
},
|
|
259
|
+
{
|
|
260
|
+
id: 'symbiosis',
|
|
261
|
+
kind: 'crossChainDex',
|
|
262
|
+
name: 'Symbiosis',
|
|
263
|
+
requiresApiKey: false,
|
|
264
|
+
status: 'unconfigured',
|
|
265
|
+
},
|
|
255
266
|
] as const
|
|
256
267
|
|
|
257
268
|
type TokenInput = (typeof sourceChains)[number]['tokens'][number]
|
|
@@ -363,6 +374,27 @@ export type ProviderAdapter = {
|
|
|
363
374
|
/** Provider adapter registry, keyed by provider id. */
|
|
364
375
|
export type ProviderAdapters = Partial<Record<ProviderId, ProviderAdapter>>
|
|
365
376
|
|
|
377
|
+
/** One configured provider attempt made while resolving a funding quote request. */
|
|
378
|
+
export type ProviderAttempt =
|
|
379
|
+
| {
|
|
380
|
+
/** Wall-clock duration of the provider attempt in milliseconds. */
|
|
381
|
+
durationMs: number
|
|
382
|
+
/** Successful or expected-unavailable provider outcome. */
|
|
383
|
+
outcome: 'available' | 'thin' | 'unavailable'
|
|
384
|
+
/** Provider that handled the quote candidate. */
|
|
385
|
+
provider: ProviderId
|
|
386
|
+
}
|
|
387
|
+
| {
|
|
388
|
+
/** Error thrown by the provider boundary. */
|
|
389
|
+
cause: unknown
|
|
390
|
+
/** Wall-clock duration of the provider attempt in milliseconds. */
|
|
391
|
+
durationMs: number
|
|
392
|
+
/** Failed provider outcome. */
|
|
393
|
+
outcome: 'failed'
|
|
394
|
+
/** Provider that handled the quote candidate. */
|
|
395
|
+
provider: ProviderId
|
|
396
|
+
}
|
|
397
|
+
|
|
366
398
|
/** A provider adapter failure retained for bounded request diagnostics. */
|
|
367
399
|
export type ProviderFailure = {
|
|
368
400
|
/** Error thrown by the provider boundary. */
|
|
@@ -377,8 +409,10 @@ const inventory: readonly InventoryEntry[] = [
|
|
|
377
409
|
{ destinationToken: 'usdce', provider: 'relay', sourceChains: ['ethereum', 'base', 'bnb'], sourceTokens: ['USDC', 'USDT'] }, // prettier-ignore
|
|
378
410
|
{ destinationToken: 'usdce', provider: 'rhino', sourceChains: ['base', 'solana'], sourceTokens: ['USDC'] }, // prettier-ignore
|
|
379
411
|
{ destinationToken: 'usdce', provider: 'squid', sourceChains: ['ethereum', 'base', 'bnb'], sourceTokens: ['USDC'] }, // prettier-ignore
|
|
412
|
+
{ destinationToken: 'usdce', provider: 'symbiosis', sourceChains: ['ethereum', 'base', 'bnb'], sourceTokens: ['USDC'] }, // prettier-ignore
|
|
380
413
|
{ destinationToken: 'usdt0', provider: 'rhino', sourceChains: ['tron'], sourceTokens: ['USDT'] }, // prettier-ignore
|
|
381
414
|
{ destinationToken: 'usdt0', provider: 'squid', sourceChains: ['ethereum', 'base', 'bnb'], sourceTokens: ['USDT'] }, // prettier-ignore
|
|
415
|
+
{ destinationToken: 'usdt0', provider: 'symbiosis', sourceChains: ['ethereum', 'base', 'bnb', 'tron'], sourceTokens: ['USDT'] }, // prettier-ignore
|
|
382
416
|
]
|
|
383
417
|
|
|
384
418
|
type InventoryEntry = {
|
|
@@ -428,30 +462,61 @@ export declare namespace listProviders {
|
|
|
428
462
|
|
|
429
463
|
/** Returns successful live funding quotes from configured providers. */
|
|
430
464
|
export async function listQuotes(options: listQuotes.Options) {
|
|
431
|
-
const
|
|
465
|
+
const adapters = options.adapters ?? {}
|
|
466
|
+
const candidates = listQuoteCandidates(options).flatMap((candidate) => {
|
|
467
|
+
const adapter = adapters[candidate.provider.id as ProviderId]
|
|
468
|
+
return adapter ? [{ adapter, candidate }] : []
|
|
469
|
+
})
|
|
470
|
+
const attempts = new Map<number, ProviderAttempt>()
|
|
432
471
|
const results = await Promise.allSettled(
|
|
433
|
-
candidates.map((candidate) =>
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
472
|
+
candidates.map(async ({ adapter, candidate }, index) => {
|
|
473
|
+
const start = performance.now()
|
|
474
|
+
try {
|
|
475
|
+
const result = await quoteCandidate(candidate, {
|
|
476
|
+
adapter,
|
|
477
|
+
cache: options.cache ?? { bypass: false },
|
|
478
|
+
request: {
|
|
479
|
+
destinationToken: options.destinationToken,
|
|
480
|
+
provider: options.provider,
|
|
481
|
+
sourceAmount: options.sourceAmount,
|
|
482
|
+
sourceAmountUnits: options.sourceAmountUnits,
|
|
483
|
+
sourceChain: options.sourceChain,
|
|
484
|
+
sourceToken: options.sourceToken,
|
|
485
|
+
},
|
|
486
|
+
timeoutMs: options.adapterTimeoutMs ?? defaultAdapterTimeoutMs,
|
|
487
|
+
})
|
|
488
|
+
attempts.set(index, {
|
|
489
|
+
durationMs: performance.now() - start,
|
|
490
|
+
outcome: result.outcome,
|
|
491
|
+
provider: candidate.provider.id,
|
|
492
|
+
})
|
|
493
|
+
return 'quote' in result ? result.quote : undefined
|
|
494
|
+
} catch (cause) {
|
|
495
|
+
attempts.set(index, {
|
|
496
|
+
cause,
|
|
497
|
+
durationMs: performance.now() - start,
|
|
498
|
+
outcome: 'failed',
|
|
499
|
+
provider: candidate.provider.id,
|
|
500
|
+
})
|
|
501
|
+
throw cause
|
|
502
|
+
}
|
|
503
|
+
}),
|
|
448
504
|
)
|
|
505
|
+
for (const index of candidates.keys()) {
|
|
506
|
+
const attempt = attempts.get(index)
|
|
507
|
+
if (!attempt) continue
|
|
508
|
+
try {
|
|
509
|
+
options.onAttempt?.(attempt)
|
|
510
|
+
} catch {
|
|
511
|
+
// Observability hooks never alter funding quote behavior.
|
|
512
|
+
}
|
|
513
|
+
}
|
|
449
514
|
const quotes = results.flatMap((result) =>
|
|
450
515
|
result.status === 'fulfilled' && result.value ? [result.value] : [],
|
|
451
516
|
)
|
|
452
517
|
const failures = results.flatMap((result, index) =>
|
|
453
518
|
result.status === 'rejected'
|
|
454
|
-
? [{ cause: result.reason, provider: candidates[index]!.provider.id }]
|
|
519
|
+
? [{ cause: result.reason, provider: candidates[index]!.candidate.provider.id }]
|
|
455
520
|
: [],
|
|
456
521
|
)
|
|
457
522
|
if (quotes.length === 0 && failures.length > 0) throw new ProviderQuoteError(failures)
|
|
@@ -519,6 +584,8 @@ export declare namespace listQuotes {
|
|
|
519
584
|
adapterTimeoutMs?: number | undefined
|
|
520
585
|
/** Cache policy for provider-side discovery and quote lookups. */
|
|
521
586
|
cache?: CachePolicy | undefined
|
|
587
|
+
/** Receives one bounded observation for every configured provider attempt. */
|
|
588
|
+
onAttempt?: ((attempt: ProviderAttempt) => void) | undefined
|
|
522
589
|
}
|
|
523
590
|
}
|
|
524
591
|
|
|
@@ -544,18 +611,15 @@ function decimalFromBaseUnits(baseUnits: string, decimals: number) {
|
|
|
544
611
|
async function quoteCandidate(
|
|
545
612
|
candidate: QuoteCandidate,
|
|
546
613
|
options: {
|
|
547
|
-
|
|
614
|
+
adapter: ProviderAdapter
|
|
548
615
|
cache: CachePolicy
|
|
549
616
|
request: QuoteRequest
|
|
550
617
|
timeoutMs: number
|
|
551
618
|
},
|
|
552
619
|
) {
|
|
553
|
-
const adapter = options.adapters[candidate.provider.id as ProviderId]
|
|
554
|
-
if (!adapter) return undefined
|
|
555
|
-
|
|
556
620
|
const quote = await runWithTimeout(
|
|
557
621
|
(signal) =>
|
|
558
|
-
adapter.probeQuote(
|
|
622
|
+
options.adapter.probeQuote(
|
|
559
623
|
{
|
|
560
624
|
cache: options.cache,
|
|
561
625
|
destinationChain: candidate.destinationChain,
|
|
@@ -570,18 +634,21 @@ async function quoteCandidate(
|
|
|
570
634
|
),
|
|
571
635
|
options.timeoutMs,
|
|
572
636
|
)
|
|
573
|
-
if (quote.status === 'unavailable') return
|
|
637
|
+
if (quote.status === 'unavailable') return { outcome: quote.status }
|
|
574
638
|
if (
|
|
575
639
|
(quote.status !== 'available' && quote.status !== 'thin') ||
|
|
576
640
|
quote.outputAmount === undefined ||
|
|
577
641
|
quote.outputAmount === null
|
|
578
642
|
)
|
|
579
643
|
throw new InvalidProviderQuoteError()
|
|
580
|
-
return
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
644
|
+
return {
|
|
645
|
+
outcome: quote.status,
|
|
646
|
+
quote: formatFundingQuote(candidate, {
|
|
647
|
+
...quote,
|
|
648
|
+
outputAmount: quote.outputAmount,
|
|
649
|
+
status: quote.status,
|
|
650
|
+
}),
|
|
651
|
+
}
|
|
585
652
|
}
|
|
586
653
|
|
|
587
654
|
function formatChain(chain: SourceChainInput) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type * as FundingQuotes from './FundingQuotes.js'
|
|
2
|
+
import type * as Log from './Log.js'
|
|
3
|
+
|
|
4
|
+
test('bounds funding provider metric identifiers', () => {
|
|
5
|
+
const provider = null as never as FundingQuotes.ProviderId
|
|
6
|
+
const id: Log.FundingProviderAttempt['id'] = provider
|
|
7
|
+
const provider_roundTrip: FundingQuotes.ProviderId = id
|
|
8
|
+
void provider_roundTrip
|
|
9
|
+
|
|
10
|
+
const attempt = {
|
|
11
|
+
durationMs: 1,
|
|
12
|
+
id: 'relay',
|
|
13
|
+
operation: 'quote/v2',
|
|
14
|
+
outcome: 'available',
|
|
15
|
+
} satisfies Log.FundingProviderAttempt
|
|
16
|
+
void attempt
|
|
17
|
+
|
|
18
|
+
// @ts-expect-error arbitrary provider names would create unbounded metric tags
|
|
19
|
+
const invalid: Log.FundingProviderAttempt['id'] = 'user-controlled-provider'
|
|
20
|
+
void invalid
|
|
21
|
+
})
|
package/src/internal/Log.test.ts
CHANGED
|
@@ -177,6 +177,56 @@ describe('middleware', () => {
|
|
|
177
177
|
`)
|
|
178
178
|
})
|
|
179
179
|
|
|
180
|
+
test('includes bounded funding provider attempts', async () => {
|
|
181
|
+
const entries: Log.Entry[] = []
|
|
182
|
+
const api = app({ emit: (entry) => void entries.push(entry) })
|
|
183
|
+
api.get('/funding', (c) => {
|
|
184
|
+
c.set('fundingProviderAttempts', [
|
|
185
|
+
{
|
|
186
|
+
durationMs: 12.5,
|
|
187
|
+
id: 'relay',
|
|
188
|
+
operation: 'quote/v2',
|
|
189
|
+
outcome: 'available',
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
durationMs: 25,
|
|
193
|
+
failure: 'timeout',
|
|
194
|
+
id: 'across',
|
|
195
|
+
operation: 'swap/approval',
|
|
196
|
+
outcome: 'failed',
|
|
197
|
+
},
|
|
198
|
+
])
|
|
199
|
+
return c.json({ ok: true }, 200)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
await api.request('/funding')
|
|
203
|
+
|
|
204
|
+
expect(stable(entries[0]!)).toMatchInlineSnapshot(`
|
|
205
|
+
{
|
|
206
|
+
"fundingProviderAttempts": [
|
|
207
|
+
{
|
|
208
|
+
"durationMs": 12.5,
|
|
209
|
+
"id": "relay",
|
|
210
|
+
"operation": "quote/v2",
|
|
211
|
+
"outcome": "available",
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"durationMs": 25,
|
|
215
|
+
"failure": "timeout",
|
|
216
|
+
"id": "across",
|
|
217
|
+
"operation": "swap/approval",
|
|
218
|
+
"outcome": "failed",
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
"level": "info",
|
|
222
|
+
"method": "GET",
|
|
223
|
+
"path": "/funding",
|
|
224
|
+
"route": "/funding",
|
|
225
|
+
"status": 200,
|
|
226
|
+
}
|
|
227
|
+
`)
|
|
228
|
+
})
|
|
229
|
+
|
|
180
230
|
test('logs thrown errors at error level', async () => {
|
|
181
231
|
const entries: Log.Entry[] = []
|
|
182
232
|
const api = app({ emit: (entry) => void entries.push(entry) })
|
package/src/internal/Log.ts
CHANGED
|
@@ -4,12 +4,15 @@ import type { RequestIdVariables } from 'hono/request-id'
|
|
|
4
4
|
import * as ApiKey from '../ApiKey.js'
|
|
5
5
|
import type * as Auth from './Auth.js'
|
|
6
6
|
import type * as EdgeCache from './EdgeCache.js'
|
|
7
|
+
import type * as FundingQuotes from './FundingQuotes.js'
|
|
7
8
|
import type * as Timing from './Timing.js'
|
|
8
9
|
|
|
9
10
|
/** Hono variables read by the request logger and written by other layers. */
|
|
10
11
|
export type Variables = {
|
|
11
12
|
/** Stable error code recorded by `Response.error` for the request log entry. */
|
|
12
13
|
errorCode?: string | undefined
|
|
14
|
+
/** Bounded outcomes for funding providers attempted by the request. */
|
|
15
|
+
fundingProviderAttempts?: Entry['fundingProviderAttempts'] | undefined
|
|
13
16
|
/** Redacted provider failure details recorded by an upstream adapter. */
|
|
14
17
|
providerFailure?: Entry['provider'] | undefined
|
|
15
18
|
/** Redacted provider failures when multiple upstream adapters fail. */
|
|
@@ -35,7 +38,7 @@ export type ProviderFailure = {
|
|
|
35
38
|
/** Stable provider error code, when one is safe to retain. */
|
|
36
39
|
code?: string | undefined
|
|
37
40
|
/** Failure boundary that rejected the provider request. */
|
|
38
|
-
failure: 'http' | 'network' | 'payload' | 'timeout' | 'unknown'
|
|
41
|
+
failure: 'http' | 'network' | 'payload' | 'rate_limit' | 'timeout' | 'unknown'
|
|
39
42
|
/** Provider identifier. */
|
|
40
43
|
id: string
|
|
41
44
|
/** Provider operation identifier. */
|
|
@@ -44,6 +47,20 @@ export type ProviderFailure = {
|
|
|
44
47
|
status?: number | undefined
|
|
45
48
|
}
|
|
46
49
|
|
|
50
|
+
/** Bounded operational result for one funding quote provider attempt. */
|
|
51
|
+
export type FundingProviderAttempt = {
|
|
52
|
+
/** Wall-clock duration of the provider attempt in milliseconds. */
|
|
53
|
+
durationMs: number
|
|
54
|
+
/** Failure class, present only when the provider attempt failed. */
|
|
55
|
+
failure?: ProviderFailure['failure'] | undefined
|
|
56
|
+
/** Provider identifier. */
|
|
57
|
+
id: FundingQuotes.ProviderId
|
|
58
|
+
/** Stable upstream operation identifier. */
|
|
59
|
+
operation: string
|
|
60
|
+
/** Provider attempt outcome. */
|
|
61
|
+
outcome: 'available' | 'failed' | 'thin' | 'unavailable'
|
|
62
|
+
}
|
|
63
|
+
|
|
47
64
|
/** One canonical request log entry. */
|
|
48
65
|
export type Entry = {
|
|
49
66
|
/** `'hit'` when the edge response cache served the request without reaching the origin handlers. */
|
|
@@ -52,6 +69,8 @@ export type Entry = {
|
|
|
52
69
|
duration: number
|
|
53
70
|
/** Stable error code when the response is a JSON error envelope (e.g. `not_found`, `api_key_invalid`). */
|
|
54
71
|
errorCode?: string | undefined
|
|
72
|
+
/** Bounded funding provider attempts made while serving the request. */
|
|
73
|
+
fundingProviderAttempts?: readonly FundingProviderAttempt[] | undefined
|
|
55
74
|
/** Log level derived from the response status: 5xx `error`, 4xx `warn`, else `info`. */
|
|
56
75
|
level: Level
|
|
57
76
|
/** Request method. */
|
|
@@ -140,6 +159,9 @@ export function middleware<environment extends Environment = Environment>(
|
|
|
140
159
|
// (the same pattern EdgeCache uses for its cross-layer reads).
|
|
141
160
|
const principal = c.get('principal' as never) as Auth.Principal | undefined
|
|
142
161
|
const errorCode = c.get('errorCode' as never) as string | undefined
|
|
162
|
+
const fundingProviderAttempts = c.get('fundingProviderAttempts' as never) as
|
|
163
|
+
| Entry['fundingProviderAttempts']
|
|
164
|
+
| undefined
|
|
143
165
|
const provider = c.get('providerFailure' as never) as Entry['provider']
|
|
144
166
|
const providerFailures = c.get('providerFailures' as never) as Entry['providerFailures']
|
|
145
167
|
|
|
@@ -170,6 +192,7 @@ export function middleware<environment extends Environment = Environment>(
|
|
|
170
192
|
...(c.get('edgeCacheStatus' as never) === 'hit' ? { cache: 'hit' as const } : {}),
|
|
171
193
|
duration: round(duration),
|
|
172
194
|
...(errorCode === undefined ? {} : { errorCode }),
|
|
195
|
+
...(fundingProviderAttempts === undefined ? {} : { fundingProviderAttempts }),
|
|
173
196
|
level: status >= 500 ? 'error' : status >= 400 ? 'warn' : 'info',
|
|
174
197
|
method: c.req.method,
|
|
175
198
|
path: url.pathname,
|