tapimo 0.4.1 → 0.4.3
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 +1 -1
- package/dist/ApiKey.d.ts +1 -9
- package/dist/ApiKey.d.ts.map +1 -1
- package/dist/ApiKey.js +2 -3
- package/dist/ApiKey.js.map +1 -1
- package/dist/ApiKeys.d.ts +53 -27
- package/dist/ApiKeys.d.ts.map +1 -1
- package/dist/ApiKeys.js +32 -17
- package/dist/ApiKeys.js.map +1 -1
- package/dist/App.d.ts +2451 -1533
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +23 -9
- package/dist/App.js.map +1 -1
- package/dist/Assets.d.ts +10 -0
- package/dist/Assets.d.ts.map +1 -1
- package/dist/Assets.js +4 -0
- package/dist/Assets.js.map +1 -1
- package/dist/Auth.d.ts +2 -1
- package/dist/Auth.d.ts.map +1 -1
- package/dist/Auth.js +2 -1
- package/dist/Auth.js.map +1 -1
- package/dist/Scope.d.ts +40 -15
- package/dist/Scope.d.ts.map +1 -1
- package/dist/Scope.js +44 -13
- package/dist/Scope.js.map +1 -1
- package/dist/admin/App.d.ts +560 -9
- package/dist/admin/App.d.ts.map +1 -1
- package/dist/admin/App.js +38 -6
- package/dist/admin/App.js.map +1 -1
- package/dist/admin/apps/api-keys.d.ts +10 -24
- package/dist/admin/apps/api-keys.d.ts.map +1 -1
- package/dist/admin/apps/api-keys.js +10 -3
- package/dist/admin/apps/api-keys.js.map +1 -1
- package/dist/admin/apps/organizations.d.ts +833 -0
- package/dist/admin/apps/organizations.d.ts.map +1 -0
- package/dist/admin/apps/organizations.js +733 -0
- package/dist/admin/apps/organizations.js.map +1 -0
- package/dist/admin/apps/scopes.d.ts +8 -0
- package/dist/admin/apps/scopes.d.ts.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 +53 -13
- package/dist/admin/ui.generated.js.map +1 -1
- package/dist/analytics/Analytics.d.ts +10 -0
- package/dist/analytics/Analytics.d.ts.map +1 -1
- package/dist/analytics/Analytics.js +8 -0
- package/dist/analytics/Analytics.js.map +1 -1
- package/dist/analytics/tables/requestEvents.d.ts +9 -0
- package/dist/analytics/tables/requestEvents.d.ts.map +1 -1
- package/dist/analytics/tables/requestEvents.js +14 -0
- package/dist/analytics/tables/requestEvents.js.map +1 -1
- package/dist/apps/data/App.d.ts +1354 -984
- package/dist/apps/data/App.d.ts.map +1 -1
- package/dist/apps/data/FundingRouteProviders.d.ts +94 -5
- package/dist/apps/data/FundingRouteProviders.d.ts.map +1 -1
- package/dist/apps/data/FundingRouteProviders.js +196 -5
- package/dist/apps/data/FundingRouteProviders.js.map +1 -1
- package/dist/apps/data/routes/activities.d.ts +38 -38
- package/dist/apps/data/routes/addresses.d.ts +24 -24
- package/dist/apps/data/routes/blocks.d.ts +39 -39
- package/dist/apps/data/routes/exchanges.d.ts +453 -453
- package/dist/apps/data/routes/fee-amm.d.ts +101 -101
- package/dist/apps/data/routes/funding.d.ts +730 -138
- package/dist/apps/data/routes/funding.d.ts.map +1 -1
- package/dist/apps/data/routes/funding.js +234 -0
- 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/transactions.d.ts +281 -281
- package/dist/apps/data/routes/transfers.d.ts +58 -58
- package/dist/apps/data/routes/verified-tokens.d.ts +81 -81
- package/dist/apps/management/App.d.ts +1156 -615
- package/dist/apps/management/App.d.ts.map +1 -1
- package/dist/apps/management/App.js +2 -2
- package/dist/apps/management/Billing.d.ts +3 -0
- package/dist/apps/management/Billing.d.ts.map +1 -1
- package/dist/apps/management/Billing.js +7 -2
- package/dist/apps/management/Billing.js.map +1 -1
- package/dist/apps/management/routes/api-keys.d.ts +106 -90
- package/dist/apps/management/routes/api-keys.d.ts.map +1 -1
- package/dist/apps/management/routes/api-keys.js +41 -30
- package/dist/apps/management/routes/api-keys.js.map +1 -1
- package/dist/apps/management/routes/billing.d.ts +278 -146
- package/dist/apps/management/routes/billing.d.ts.map +1 -1
- package/dist/apps/management/routes/billing.js +14 -14
- package/dist/apps/management/routes/billing.js.map +1 -1
- package/dist/apps/management/routes/invitations.d.ts +93 -20
- package/dist/apps/management/routes/invitations.d.ts.map +1 -1
- package/dist/apps/management/routes/invitations.js +12 -15
- package/dist/apps/management/routes/invitations.js.map +1 -1
- package/dist/apps/management/routes/me.d.ts +6 -6
- package/dist/apps/management/routes/members.d.ts +89 -18
- package/dist/apps/management/routes/members.d.ts.map +1 -1
- package/dist/apps/management/routes/members.js +10 -10
- package/dist/apps/management/routes/members.js.map +1 -1
- package/dist/apps/management/routes/orgs.d.ts +130 -70
- package/dist/apps/management/routes/orgs.d.ts.map +1 -1
- package/dist/apps/management/routes/orgs.js +19 -19
- package/dist/apps/management/routes/orgs.js.map +1 -1
- package/dist/apps/management/routes/projects.d.ts +137 -48
- package/dist/apps/management/routes/projects.d.ts.map +1 -1
- package/dist/apps/management/routes/projects.js +12 -13
- package/dist/apps/management/routes/projects.js.map +1 -1
- package/dist/apps/management/routes/scopes.d.ts +4 -18
- package/dist/apps/management/routes/scopes.d.ts.map +1 -1
- package/dist/apps/management/routes/scopes.js +6 -7
- package/dist/apps/management/routes/scopes.js.map +1 -1
- package/dist/apps/management/routes/usage.d.ts +63 -19
- package/dist/apps/management/routes/usage.d.ts.map +1 -1
- package/dist/apps/management/routes/usage.js +9 -10
- package/dist/apps/management/routes/usage.js.map +1 -1
- package/dist/apps/mcp.d.ts.map +1 -1
- package/dist/apps/mcp.js +35 -3
- package/dist/apps/mcp.js.map +1 -1
- package/dist/apps/relay/App.d.ts +26 -4
- package/dist/apps/relay/App.d.ts.map +1 -1
- package/dist/apps/relay/App.js +17 -14
- package/dist/apps/relay/App.js.map +1 -1
- package/dist/db/Db.d.ts +5 -3
- package/dist/db/Db.d.ts.map +1 -1
- package/dist/db/Db.js +52 -0
- package/dist/db/Db.js.map +1 -1
- package/dist/db/Schema.d.ts +13 -0
- package/dist/db/Schema.d.ts.map +1 -1
- package/dist/db/Schema.js +16 -1
- package/dist/db/Schema.js.map +1 -1
- package/dist/db/tables/adminAuditLogs.d.ts +27 -0
- package/dist/db/tables/adminAuditLogs.d.ts.map +1 -0
- package/dist/db/tables/adminAuditLogs.js +19 -0
- package/dist/db/tables/adminAuditLogs.js.map +1 -0
- package/dist/db/tables/invitations.d.ts +1 -1
- package/dist/db/tables/invitations.d.ts.map +1 -1
- package/dist/db/tables/memberships.d.ts +32 -4
- package/dist/db/tables/memberships.d.ts.map +1 -1
- package/dist/db/tables/memberships.js +31 -0
- package/dist/db/tables/memberships.js.map +1 -1
- package/dist/db/tables/organizations.d.ts +43 -1
- package/dist/db/tables/organizations.d.ts.map +1 -1
- package/dist/db/tables/organizations.js +103 -0
- package/dist/db/tables/organizations.js.map +1 -1
- package/dist/db/tables/projects.d.ts +25 -0
- package/dist/db/tables/projects.d.ts.map +1 -1
- package/dist/db/tables/projects.js +22 -0
- package/dist/db/tables/projects.js.map +1 -1
- package/dist/db/tables/webhookDeliveries.d.ts +26 -0
- package/dist/db/tables/webhookDeliveries.d.ts.map +1 -1
- package/dist/db/tables/webhookDeliveries.js +37 -0
- package/dist/db/tables/webhookDeliveries.js.map +1 -1
- package/dist/internal/Auth.d.ts +13 -18
- package/dist/internal/Auth.d.ts.map +1 -1
- package/dist/internal/Auth.js +78 -58
- package/dist/internal/Auth.js.map +1 -1
- package/dist/internal/Cursor.d.ts +1 -1
- package/dist/internal/Cursor.d.ts.map +1 -1
- package/dist/internal/Cursor.js +4 -0
- package/dist/internal/Cursor.js.map +1 -1
- package/dist/internal/EdgeCache.d.ts.map +1 -1
- package/dist/internal/EdgeCache.js +2 -0
- package/dist/internal/EdgeCache.js.map +1 -1
- package/dist/internal/FundingRoutes.d.ts +170 -0
- package/dist/internal/FundingRoutes.d.ts.map +1 -1
- package/dist/internal/FundingRoutes.js +68 -6
- package/dist/internal/FundingRoutes.js.map +1 -1
- package/dist/internal/Log.d.ts +16 -1
- package/dist/internal/Log.d.ts.map +1 -1
- package/dist/internal/Log.js +5 -3
- package/dist/internal/Log.js.map +1 -1
- package/package.json +2 -2
- package/src/ApiKey.ts +2 -3
- package/src/ApiKeys.ts +95 -17
- package/src/App.test.ts +44 -2
- package/src/App.ts +29 -9
- package/src/Assets.ts +8 -0
- package/src/Auth.test-d.ts +36 -0
- package/src/Auth.ts +20 -1
- package/src/Client.test-d.ts +64 -17
- package/src/Scope.test.ts +34 -0
- package/src/Scope.ts +53 -17
- package/src/admin/App.test.ts +33 -3
- package/src/admin/App.ts +49 -14
- package/src/admin/apps/api-keys.ts +10 -3
- package/src/admin/apps/organizations.test.ts +455 -0
- package/src/admin/apps/organizations.ts +953 -0
- package/src/admin/apps/scopes.test.ts +10 -0
- package/src/admin/ui/plugin.ts +1 -1
- package/src/admin/ui/src/components/MobileProfile.tsx +2 -0
- package/src/admin/ui/src/components/ThemeToggle.tsx +20 -0
- package/src/admin/ui/src/lib/api-keys.ts +36 -19
- package/src/admin/ui/src/lib/config.ts +2 -3
- package/src/admin/ui/src/lib/organizations.test.ts +89 -0
- package/src/admin/ui/src/lib/organizations.ts +262 -0
- package/src/admin/ui/src/lib/query-client.ts +3 -1
- package/src/admin/ui/src/lib/theme.ts +57 -0
- package/src/admin/ui/src/routeTree.gen.ts +47 -4
- package/src/admin/ui/src/routes/__root.tsx +18 -3
- package/src/admin/ui/src/routes/api-keys.tsx +52 -7
- package/src/admin/ui/src/routes/index.tsx +1 -1
- package/src/admin/ui/src/routes/organizations.tsx +158 -0
- package/src/admin/ui/src/routes/organizations_.$orgId.tsx +710 -0
- package/src/admin/ui/src/start.ts +6 -0
- package/src/admin/ui.generated.ts +63 -13
- package/src/analytics/Analytics.ts +25 -0
- package/src/analytics/tables/requestEvents.ts +33 -0
- package/src/apps/composition.test.ts +82 -2
- package/src/apps/data/FundingRouteProviders.test.ts +204 -0
- package/src/apps/data/FundingRouteProviders.ts +294 -9
- package/src/apps/data/routes/funding.test.ts +90 -23
- package/src/apps/data/routes/funding.ts +315 -0
- package/src/apps/management/App.ts +2 -2
- package/src/apps/management/Billing.ts +10 -2
- package/src/apps/management/routes/api-keys.test.ts +137 -1
- package/src/apps/management/routes/api-keys.ts +63 -38
- package/src/apps/management/routes/billing.ts +14 -14
- package/src/apps/management/routes/invitations.test.ts +102 -1
- package/src/apps/management/routes/invitations.ts +13 -15
- package/src/apps/management/routes/members.ts +10 -10
- package/src/apps/management/routes/orgs.test.ts +78 -16
- package/src/apps/management/routes/orgs.ts +21 -20
- package/src/apps/management/routes/projects.ts +12 -13
- package/src/apps/management/routes/scopes.test.ts +16 -0
- package/src/apps/management/routes/scopes.ts +6 -7
- package/src/apps/management/routes/usage.ts +9 -14
- package/src/apps/mcp.test.ts +25 -1
- package/src/apps/mcp.ts +43 -3
- package/src/apps/relay/App.test.ts +23 -18
- package/src/apps/relay/App.ts +35 -14
- package/src/db/Db.ts +59 -3
- package/src/db/Schema.ts +20 -1
- package/src/db/tables/adminAuditLogs.ts +47 -0
- package/src/db/tables/invitations.ts +1 -1
- package/src/db/tables/memberships.ts +59 -3
- package/src/db/tables/organizations.ts +148 -1
- package/src/db/tables/projects.ts +47 -0
- package/src/db/tables/webhookDeliveries.ts +83 -0
- package/src/internal/Auth.test.ts +174 -7
- package/src/internal/Auth.ts +90 -62
- package/src/internal/Cursor.test.ts +10 -0
- package/src/internal/Cursor.ts +5 -1
- package/src/internal/EdgeCache.test.ts +11 -0
- package/src/internal/EdgeCache.ts +2 -0
- package/src/internal/FundingRoutes.test.ts +26 -0
- package/src/internal/FundingRoutes.ts +187 -18
- package/src/internal/Log.test.ts +41 -3
- package/src/internal/Log.ts +23 -4
|
@@ -1,10 +1,48 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as z from 'zod/mini'
|
|
2
|
+
|
|
3
|
+
import * as FundingRoutes from '../../internal/FundingRoutes.js'
|
|
2
4
|
|
|
3
5
|
type Fetch = typeof globalThis.fetch
|
|
4
6
|
|
|
7
|
+
const defaultFetch: Fetch = (input, init) => globalThis.fetch(input, init)
|
|
8
|
+
|
|
5
9
|
const defaultAcrossBaseUrl = 'https://app.across.to/api'
|
|
6
10
|
const defaultRelayBaseUrl = 'https://api.relay.link'
|
|
7
11
|
|
|
12
|
+
namespace schema {
|
|
13
|
+
const DecimalString = z.string().check(z.regex(/^\d+(\.\d+)?$/))
|
|
14
|
+
const IntegerString = z.string().check(z.regex(/^\d+$/))
|
|
15
|
+
|
|
16
|
+
const AcrossToken = z.object({
|
|
17
|
+
address: z.string(),
|
|
18
|
+
chainId: z.number().check(z.int(), z.nonnegative()),
|
|
19
|
+
decimals: z.number().check(z.int(), z.nonnegative()),
|
|
20
|
+
symbol: z.string(),
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const AcrossFee = z.object({
|
|
24
|
+
amount: IntegerString,
|
|
25
|
+
amountUsd: DecimalString,
|
|
26
|
+
pct: IntegerString,
|
|
27
|
+
token: AcrossToken,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export const AcrossQuote = z.object({
|
|
31
|
+
amountType: z.literal('exactInput'),
|
|
32
|
+
expectedFillTime: z.number().check(z.int(), z.nonnegative()),
|
|
33
|
+
expectedOutputAmount: IntegerString,
|
|
34
|
+
fees: z.object({
|
|
35
|
+
total: AcrossFee,
|
|
36
|
+
totalMax: AcrossFee,
|
|
37
|
+
}),
|
|
38
|
+
inputAmount: IntegerString,
|
|
39
|
+
inputToken: AcrossToken,
|
|
40
|
+
minOutputAmount: IntegerString,
|
|
41
|
+
outputToken: AcrossToken,
|
|
42
|
+
quoteExpiryTimestamp: z.number().check(z.int(), z.nonnegative()),
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
8
46
|
/** Creates the live funding route adapters configured for this deployment. */
|
|
9
47
|
export function createAdapters(options: createAdapters.Options = {}) {
|
|
10
48
|
return {
|
|
@@ -16,17 +54,17 @@ export function createAdapters(options: createAdapters.Options = {}) {
|
|
|
16
54
|
export declare namespace createAdapters {
|
|
17
55
|
/** Provider adapter factory options. Omit a provider to keep its routes static. */
|
|
18
56
|
type Options = {
|
|
19
|
-
/** Across
|
|
57
|
+
/** Across discovery adapter options. */
|
|
20
58
|
across?: across.Options | false | undefined
|
|
21
59
|
/** Relay quote adapter options. */
|
|
22
60
|
relay?: relay.Options | false | undefined
|
|
23
61
|
}
|
|
24
62
|
}
|
|
25
63
|
|
|
26
|
-
/** Creates an Across
|
|
64
|
+
/** Creates an Across discovery adapter backed by `/suggested-fees`. */
|
|
27
65
|
export function across(options: across.Options): FundingRoutes.ProviderAdapter {
|
|
28
66
|
const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultAcrossBaseUrl)
|
|
29
|
-
const fetcher = options.fetch ??
|
|
67
|
+
const fetcher = options.fetch ?? defaultFetch
|
|
30
68
|
const now = options.now ?? (() => new Date())
|
|
31
69
|
|
|
32
70
|
return {
|
|
@@ -65,17 +103,141 @@ export function across(options: across.Options): FundingRoutes.ProviderAdapter {
|
|
|
65
103
|
}
|
|
66
104
|
}
|
|
67
105
|
|
|
68
|
-
|
|
69
|
-
|
|
106
|
+
/** Builds the authenticated Across `/swap/approval` request for an exact-input quote. */
|
|
107
|
+
export function createAcrossQuoteRequest(options: createAcrossQuoteRequest.Options) {
|
|
108
|
+
assertAcrossIntegratorId(options.integratorId)
|
|
109
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultAcrossBaseUrl)
|
|
110
|
+
const sourceChainId = providerChainId(options.route.sourceChain, 'across')
|
|
111
|
+
const destinationChainId = eip155ChainId(options.route.destinationChain)
|
|
112
|
+
const url = new URL(`${baseUrl}/swap/approval`)
|
|
113
|
+
url.search = new URLSearchParams({
|
|
114
|
+
amount: options.route.sourceAmount.amount,
|
|
115
|
+
depositor: options.depositor,
|
|
116
|
+
destinationChainId,
|
|
117
|
+
inputToken: options.route.sourceToken.address,
|
|
118
|
+
integratorId: options.integratorId,
|
|
119
|
+
originChainId: sourceChainId,
|
|
120
|
+
outputToken: options.route.destinationToken.address,
|
|
121
|
+
skipOriginTxEstimation: 'true',
|
|
122
|
+
strictTradeType: 'true',
|
|
123
|
+
tradeType: 'exactInput',
|
|
124
|
+
}).toString()
|
|
125
|
+
if (options.recipient !== undefined) url.searchParams.set('recipient', options.recipient)
|
|
126
|
+
return { headers: { Authorization: `Bearer ${options.apiKey}` }, url }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export declare namespace createAcrossQuoteRequest {
|
|
130
|
+
/** Inputs required to construct an Across exact-input quote request. */
|
|
131
|
+
type Options = {
|
|
132
|
+
/** Across API key sent as a bearer token. */
|
|
133
|
+
apiKey: string
|
|
134
|
+
/** Optional Across API base URL. */
|
|
135
|
+
baseUrl?: string | undefined
|
|
136
|
+
/** Address that would submit the source transaction. */
|
|
137
|
+
depositor: string
|
|
138
|
+
/** Across integrator id sent as the required query parameter. */
|
|
139
|
+
integratorId: string
|
|
140
|
+
/** Optional destination recipient. */
|
|
141
|
+
recipient?: string | undefined
|
|
142
|
+
/** Curated route selected for the quote. */
|
|
143
|
+
route: FundingRoutes.RouteCandidate
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Creates an explicit Across quote adapter backed by `/swap/approval`. */
|
|
148
|
+
export function acrossQuote(options: acrossQuote.Options): FundingRoutes.ProviderQuote {
|
|
149
|
+
assertAcrossIntegratorId(options.integratorId)
|
|
150
|
+
|
|
151
|
+
return async ({ depositor, recipient, route }, signal) => {
|
|
152
|
+
const request = createAcrossQuoteRequest({
|
|
153
|
+
apiKey: options.apiKey,
|
|
154
|
+
baseUrl: options.baseUrl,
|
|
155
|
+
depositor,
|
|
156
|
+
integratorId: options.integratorId,
|
|
157
|
+
recipient,
|
|
158
|
+
route,
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const body = await requestJson(request.url, {
|
|
163
|
+
fetcher: defaultFetch,
|
|
164
|
+
headers: request.headers,
|
|
165
|
+
signal,
|
|
166
|
+
})
|
|
167
|
+
return parseAcrossQuote({ body, now: new Date(), route })
|
|
168
|
+
} catch (cause) {
|
|
169
|
+
if (!(cause instanceof ProviderResponseError)) throw cause
|
|
170
|
+
throw mapAcrossQuoteError({ body: cause.body, status: cause.status })
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export declare namespace acrossQuote {
|
|
176
|
+
/** Across Swap API adapter options. */
|
|
70
177
|
type Options = {
|
|
71
178
|
/** Across API key sent as a bearer token. */
|
|
72
179
|
apiKey: string
|
|
180
|
+
/** Optional Across API base URL. */
|
|
181
|
+
baseUrl?: string | undefined
|
|
73
182
|
/** Across integrator id sent as the required query parameter. */
|
|
74
183
|
integratorId: string
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Returns bounded Across quote failure details suitable for structured request logs. */
|
|
188
|
+
export function acrossQuoteFailure(options: acrossQuoteFailure.Options): acrossQuoteFailure.Result {
|
|
189
|
+
const { cause } = options
|
|
190
|
+
const base = { id: 'across', operation: 'swap/approval' } as const
|
|
191
|
+
if (cause instanceof ProviderResponseError) {
|
|
192
|
+
const code = providerErrorCode(cause.body)
|
|
193
|
+
return {
|
|
194
|
+
...(code === undefined ? {} : { code }),
|
|
195
|
+
failure: 'http' as const,
|
|
196
|
+
...base,
|
|
197
|
+
status: cause.status,
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (cause instanceof ProviderPayloadError) return { failure: 'payload', ...base }
|
|
201
|
+
if (cause instanceof TypeError) return { failure: 'network', ...base }
|
|
202
|
+
if (cause instanceof Error) return { failure: 'unknown', ...base }
|
|
203
|
+
return undefined
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export declare namespace acrossQuoteFailure {
|
|
207
|
+
/** Inputs used to classify an Across quote failure. */
|
|
208
|
+
type Options = {
|
|
209
|
+
/** Error thrown by the Across quote boundary. */
|
|
210
|
+
cause: unknown
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Bounded diagnostic fields, or undefined when the thrown value is not an error. */
|
|
214
|
+
type Result =
|
|
215
|
+
| {
|
|
216
|
+
/** Stable provider error code, when one is safe to retain. */
|
|
217
|
+
code?: string | undefined
|
|
218
|
+
/** Failure boundary that rejected the provider request. */
|
|
219
|
+
failure: 'http' | 'network' | 'payload' | 'unknown'
|
|
220
|
+
/** Provider identifier. */
|
|
221
|
+
id: 'across'
|
|
222
|
+
/** Across operation identifier. */
|
|
223
|
+
operation: 'swap/approval'
|
|
224
|
+
/** Upstream HTTP status, when a response was received. */
|
|
225
|
+
status?: number | undefined
|
|
226
|
+
}
|
|
227
|
+
| undefined
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export declare namespace across {
|
|
231
|
+
/** Across API adapter options. */
|
|
232
|
+
type Options = {
|
|
75
233
|
/** Override for tests or non-production Across environments. */
|
|
76
234
|
baseUrl?: string | undefined
|
|
77
235
|
/** Fetch implementation override for tests. */
|
|
78
236
|
fetch?: Fetch | undefined
|
|
237
|
+
/** Across API key sent as a bearer token. */
|
|
238
|
+
apiKey: string
|
|
239
|
+
/** Across integrator id sent as the required query parameter. */
|
|
240
|
+
integratorId: string
|
|
79
241
|
/** Clock override for deterministic tests. */
|
|
80
242
|
now?: (() => Date) | undefined
|
|
81
243
|
}
|
|
@@ -84,7 +246,7 @@ export declare namespace across {
|
|
|
84
246
|
/** Creates a Relay quote adapter backed by `/chains/liquidity` and `/quote/v2`. */
|
|
85
247
|
export function relay(options: relay.Options): FundingRoutes.ProviderAdapter {
|
|
86
248
|
const baseUrl = normalizeBaseUrl(options.baseUrl ?? defaultRelayBaseUrl)
|
|
87
|
-
const fetcher = options.fetch ??
|
|
249
|
+
const fetcher = options.fetch ?? defaultFetch
|
|
88
250
|
const now = options.now ?? (() => new Date())
|
|
89
251
|
|
|
90
252
|
return {
|
|
@@ -152,6 +314,85 @@ export declare namespace relay {
|
|
|
152
314
|
}
|
|
153
315
|
}
|
|
154
316
|
|
|
317
|
+
/** Maps an Across HTTP error into the stable funding-provider error contract. */
|
|
318
|
+
export function mapAcrossQuoteError(options: mapAcrossQuoteError.Options) {
|
|
319
|
+
if (options.status === 429) return new FundingRoutes.ProviderRateLimitError()
|
|
320
|
+
const cause = new ProviderResponseError(options.status, options.body)
|
|
321
|
+
const unavailable = providerUnavailable(cause, [
|
|
322
|
+
'AMOUNT_TOO_HIGH',
|
|
323
|
+
'AMOUNT_TOO_LOW',
|
|
324
|
+
'INSUFFICIENT_LIQUIDITY',
|
|
325
|
+
'NO_QUOTES',
|
|
326
|
+
'NO_ROUTE',
|
|
327
|
+
'UNSUPPORTED_ROUTE',
|
|
328
|
+
])
|
|
329
|
+
if (unavailable) return new FundingRoutes.ProviderQuoteUnavailableError()
|
|
330
|
+
return cause
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export declare namespace mapAcrossQuoteError {
|
|
334
|
+
/** Across HTTP error response to classify. */
|
|
335
|
+
type Options = {
|
|
336
|
+
/** Parsed response body returned by Across. */
|
|
337
|
+
body: unknown
|
|
338
|
+
/** Across HTTP response status. */
|
|
339
|
+
status: number
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Validates and normalizes an Across quote payload against its curated route. */
|
|
344
|
+
export function parseAcrossQuote(
|
|
345
|
+
options: parseAcrossQuote.Options,
|
|
346
|
+
): FundingRoutes.ProviderQuoteResult {
|
|
347
|
+
const parsed = schema.AcrossQuote.safeParse(options.body)
|
|
348
|
+
if (!parsed.success) throw new ProviderPayloadError()
|
|
349
|
+
const response = parsed.data
|
|
350
|
+
const total = response.fees.total
|
|
351
|
+
const totalMax = response.fees.totalMax
|
|
352
|
+
if (
|
|
353
|
+
response.inputAmount !== options.route.sourceAmount.amount ||
|
|
354
|
+
response.inputToken.address.toLowerCase() !== options.route.sourceToken.address.toLowerCase() ||
|
|
355
|
+
response.inputToken.chainId !== Number(eip155ChainId(options.route.sourceChain)) ||
|
|
356
|
+
response.inputToken.decimals !== options.route.sourceToken.decimals ||
|
|
357
|
+
response.outputToken.address.toLowerCase() !==
|
|
358
|
+
options.route.destinationToken.address.toLowerCase() ||
|
|
359
|
+
response.outputToken.chainId !== Number(eip155ChainId(options.route.destinationChain)) ||
|
|
360
|
+
response.outputToken.decimals !== options.route.destinationToken.decimals ||
|
|
361
|
+
BigInt(response.expectedOutputAmount) <= 0n ||
|
|
362
|
+
BigInt(response.minOutputAmount) <= 0n ||
|
|
363
|
+
BigInt(response.minOutputAmount) > BigInt(response.expectedOutputAmount) ||
|
|
364
|
+
response.quoteExpiryTimestamp * 1_000 <= options.now.getTime() ||
|
|
365
|
+
!matchesFeeToken(total.token, options.route) ||
|
|
366
|
+
!matchesFeeToken(totalMax.token, options.route) ||
|
|
367
|
+
BigInt(totalMax.amount) < BigInt(total.amount) ||
|
|
368
|
+
BigInt(totalMax.pct) < BigInt(total.pct)
|
|
369
|
+
)
|
|
370
|
+
throw new ProviderPayloadError()
|
|
371
|
+
return {
|
|
372
|
+
estimatedSeconds: response.expectedFillTime,
|
|
373
|
+
expiresAt: isoFromUnixSeconds(String(response.quoteExpiryTimestamp)),
|
|
374
|
+
fees: {
|
|
375
|
+
total: formatAcrossFee(response.fees.total),
|
|
376
|
+
totalMax: formatAcrossFee(response.fees.totalMax),
|
|
377
|
+
},
|
|
378
|
+
minimumOutputAmount: response.minOutputAmount,
|
|
379
|
+
outputAmount: response.expectedOutputAmount,
|
|
380
|
+
sampledAt: options.now.toISOString(),
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export declare namespace parseAcrossQuote {
|
|
385
|
+
/** Inputs required to validate and normalize an Across quote payload. */
|
|
386
|
+
type Options = {
|
|
387
|
+
/** Untrusted JSON body returned by Across. */
|
|
388
|
+
body: unknown
|
|
389
|
+
/** Timestamp when Tempo sampled the response. */
|
|
390
|
+
now: Date
|
|
391
|
+
/** Curated route the response must match. */
|
|
392
|
+
route: FundingRoutes.RouteCandidate
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
155
396
|
function formatAcrossResult(
|
|
156
397
|
route: FundingRoutes.RouteCandidate,
|
|
157
398
|
body: unknown,
|
|
@@ -191,6 +432,22 @@ function formatAcrossResult(
|
|
|
191
432
|
}
|
|
192
433
|
}
|
|
193
434
|
|
|
435
|
+
function formatAcrossFee(
|
|
436
|
+
fee: z.output<typeof schema.AcrossQuote>['fees']['total'],
|
|
437
|
+
): FundingRoutes.ProviderQuoteFee {
|
|
438
|
+
return {
|
|
439
|
+
amount: fee.amount,
|
|
440
|
+
amountUsd: fee.amountUsd,
|
|
441
|
+
bps: scaledPctToBps(fee.pct),
|
|
442
|
+
token: {
|
|
443
|
+
address: fee.token.address,
|
|
444
|
+
chainId: `eip155:${fee.token.chainId}`,
|
|
445
|
+
decimals: fee.token.decimals,
|
|
446
|
+
symbol: fee.token.symbol,
|
|
447
|
+
},
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
194
451
|
function formatRelayResult(
|
|
195
452
|
route: FundingRoutes.RouteCandidate,
|
|
196
453
|
quote: unknown,
|
|
@@ -333,12 +590,17 @@ function relayHeaders(apiKey: string | undefined): Record<string, string> | unde
|
|
|
333
590
|
function providerUnavailable(cause: unknown, codes: readonly string[]) {
|
|
334
591
|
if (!(cause instanceof ProviderResponseError) || cause.status < 400 || cause.status >= 500)
|
|
335
592
|
return undefined
|
|
336
|
-
const
|
|
337
|
-
const code = stringValue(body['code']) ?? stringValue(body['errorCode'])
|
|
593
|
+
const code = providerErrorCode(cause.body)
|
|
338
594
|
if (code && codes.includes(code)) return code
|
|
339
595
|
return undefined
|
|
340
596
|
}
|
|
341
597
|
|
|
598
|
+
function providerErrorCode(input: unknown) {
|
|
599
|
+
const body = record(input)
|
|
600
|
+
const code = stringValue(body['code']) ?? stringValue(body['errorCode'])
|
|
601
|
+
return code && /^[A-Za-z0-9_.:-]{1,64}$/.test(code) ? code : undefined
|
|
602
|
+
}
|
|
603
|
+
|
|
342
604
|
class ProviderResponseError extends Error {
|
|
343
605
|
override name = 'FundingRouteProviders.ProviderResponseError'
|
|
344
606
|
|
|
@@ -350,6 +612,14 @@ class ProviderResponseError extends Error {
|
|
|
350
612
|
}
|
|
351
613
|
}
|
|
352
614
|
|
|
615
|
+
class ProviderPayloadError extends Error {
|
|
616
|
+
override name = 'FundingRouteProviders.ProviderPayloadError'
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
class ProviderConfigurationError extends Error {
|
|
620
|
+
override name = 'FundingRouteProviders.ProviderConfigurationError'
|
|
621
|
+
}
|
|
622
|
+
|
|
353
623
|
function providerChainId(
|
|
354
624
|
routeChain: FundingRoutes.RouteCandidate['sourceChain'],
|
|
355
625
|
provider: string,
|
|
@@ -371,6 +641,21 @@ class UnsupportedProviderRouteError extends Error {
|
|
|
371
641
|
override name = 'FundingRouteProviders.UnsupportedProviderRouteError'
|
|
372
642
|
}
|
|
373
643
|
|
|
644
|
+
function assertAcrossIntegratorId(integratorId: string) {
|
|
645
|
+
if (!/^0x[\da-fA-F]{4}$/.test(integratorId)) throw new ProviderConfigurationError()
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function matchesFeeToken(
|
|
649
|
+
token: z.output<typeof schema.AcrossQuote>['fees']['total']['token'],
|
|
650
|
+
route: FundingRoutes.RouteCandidate,
|
|
651
|
+
) {
|
|
652
|
+
return (
|
|
653
|
+
token.address.toLowerCase() === route.sourceToken.address.toLowerCase() &&
|
|
654
|
+
token.chainId === Number(eip155ChainId(route.sourceChain)) &&
|
|
655
|
+
token.decimals === route.sourceToken.decimals
|
|
656
|
+
)
|
|
657
|
+
}
|
|
658
|
+
|
|
374
659
|
function normalizeBaseUrl(input: string) {
|
|
375
660
|
return input.replace(/\/+$/, '')
|
|
376
661
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Schema } from 'tapimo'
|
|
2
2
|
|
|
3
3
|
import * as TestApp from '../../../../test/App.js'
|
|
4
|
+
import type * as Log from '../../../internal/Log.js'
|
|
5
|
+
import * as FundingRouteProviders from '../FundingRouteProviders.js'
|
|
4
6
|
import * as Funding from './funding.js'
|
|
5
7
|
|
|
6
8
|
describe('GET /funding/routes', () => {
|
|
@@ -233,6 +235,84 @@ describe('GET /funding/routes', () => {
|
|
|
233
235
|
})
|
|
234
236
|
})
|
|
235
237
|
|
|
238
|
+
describe('POST /funding/routes/quote', () => {
|
|
239
|
+
test('rejects invalid depositor addresses', async () => {
|
|
240
|
+
const response = await TestApp.create().request('/v1/funding/routes/quote', {
|
|
241
|
+
body: JSON.stringify({
|
|
242
|
+
depositor: 'invalid',
|
|
243
|
+
provider: 'across',
|
|
244
|
+
sourceAmount: '1000000',
|
|
245
|
+
sourceChain: 'base',
|
|
246
|
+
sourceToken: 'USDC',
|
|
247
|
+
}),
|
|
248
|
+
headers: { ...TestApp.auth.headers, 'content-type': 'application/json' },
|
|
249
|
+
method: 'POST',
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
expect(response.status).toBe(400)
|
|
253
|
+
expect(response.headers.get('cache-control')).toBe('no-store')
|
|
254
|
+
expect(await response.json()).toMatchObject({ error: { code: 'body_invalid' } })
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
test('returns a stable error when Across quoting is not configured', async () => {
|
|
258
|
+
const response = await TestApp.create().request('/v1/funding/routes/quote', {
|
|
259
|
+
body: JSON.stringify(quoteRequest()),
|
|
260
|
+
headers: { ...TestApp.auth.headers, 'content-type': 'application/json' },
|
|
261
|
+
method: 'POST',
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
expect(response.status).toBe(501)
|
|
265
|
+
expect(response.headers.get('cache-control')).toBe('no-store')
|
|
266
|
+
expect(await response.json()).toMatchObject({ error: { code: 'provider_unconfigured' } })
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
test('records bounded Across failure details in the request log', async () => {
|
|
270
|
+
const entries: Log.Entry[] = []
|
|
271
|
+
const response = await TestApp.create({
|
|
272
|
+
fundingRoutes: {
|
|
273
|
+
async acrossQuote() {
|
|
274
|
+
throw FundingRouteProviders.mapAcrossQuoteError({
|
|
275
|
+
body: { code: 'AUTH_FAILED', detail: 'not logged' },
|
|
276
|
+
status: 401,
|
|
277
|
+
})
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
logger: (entry) => void entries.push(entry),
|
|
281
|
+
}).request('/v1/funding/routes/quote', {
|
|
282
|
+
body: JSON.stringify(quoteRequest()),
|
|
283
|
+
headers: { ...TestApp.auth.headers, 'content-type': 'application/json' },
|
|
284
|
+
method: 'POST',
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
expect(response.status).toBe(502)
|
|
288
|
+
expect(entries[0]).toMatchObject({
|
|
289
|
+
errorCode: 'provider_error',
|
|
290
|
+
provider: {
|
|
291
|
+
code: 'AUTH_FAILED',
|
|
292
|
+
failure: 'http',
|
|
293
|
+
id: 'across',
|
|
294
|
+
operation: 'swap/approval',
|
|
295
|
+
status: 401,
|
|
296
|
+
},
|
|
297
|
+
})
|
|
298
|
+
expect(JSON.stringify(entries[0])).not.toContain('not logged')
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
test('publishes the normalized quote contract in OpenAPI', async () => {
|
|
302
|
+
const response = await TestApp.create().request('/openapi.json')
|
|
303
|
+
const document = (await response.json()) as {
|
|
304
|
+
paths: Record<string, { post?: { operationId?: string; responses?: object } }>
|
|
305
|
+
}
|
|
306
|
+
const operation = document.paths['/v1/funding/routes/quote']?.post
|
|
307
|
+
|
|
308
|
+
expect(operation).toMatchObject({
|
|
309
|
+
operationId: 'quoteFundingRoute',
|
|
310
|
+
responses: { 200: expect.any(Object), 400: expect.any(Object), 502: expect.any(Object) },
|
|
311
|
+
})
|
|
312
|
+
expect(JSON.stringify(operation)).not.toMatch(/approvalTxns|checks|swapTx/)
|
|
313
|
+
})
|
|
314
|
+
})
|
|
315
|
+
|
|
236
316
|
describe('GET /funding/chains', () => {
|
|
237
317
|
test('lists source chain and stablecoin inventory', async () => {
|
|
238
318
|
const response = await TestApp.client().v1.funding.chains.$get(undefined, TestApp.auth)
|
|
@@ -252,6 +332,16 @@ describe('GET /funding/chains', () => {
|
|
|
252
332
|
})
|
|
253
333
|
})
|
|
254
334
|
|
|
335
|
+
function quoteRequest() {
|
|
336
|
+
return {
|
|
337
|
+
depositor: '0x1111111111111111111111111111111111111111',
|
|
338
|
+
provider: 'across',
|
|
339
|
+
sourceAmount: '1000000',
|
|
340
|
+
sourceChain: 'base',
|
|
341
|
+
sourceToken: 'USDC',
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
255
345
|
describe('GET /providers', () => {
|
|
256
346
|
test('lists providers filtered by capability', async () => {
|
|
257
347
|
const response = await TestApp.client().v1.providers.$get(
|
|
@@ -280,29 +370,6 @@ describe('GET /providers', () => {
|
|
|
280
370
|
])
|
|
281
371
|
})
|
|
282
372
|
|
|
283
|
-
test('marks configured live provider adapters', async () => {
|
|
284
|
-
const app = TestApp.create({
|
|
285
|
-
fundingRoutes: {
|
|
286
|
-
adapters: {
|
|
287
|
-
across: {
|
|
288
|
-
async probeQuote() {
|
|
289
|
-
throw new Error('not used')
|
|
290
|
-
},
|
|
291
|
-
},
|
|
292
|
-
},
|
|
293
|
-
},
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
const response = await app.request('/v1/providers?capability=fundingRoutes', {
|
|
297
|
-
headers: TestApp.auth.headers,
|
|
298
|
-
})
|
|
299
|
-
expect(response.status).toBe(200)
|
|
300
|
-
const body = await TestApp.json(response, Funding.schema.listProviders.Response)
|
|
301
|
-
|
|
302
|
-
expect(body.data.find((provider) => provider.id === 'across')?.status).toBe('configured')
|
|
303
|
-
expect(body.data.find((provider) => provider.id === 'relay')?.status).toBe('inventoryOnly')
|
|
304
|
-
})
|
|
305
|
-
|
|
306
373
|
test('rejects unsupported provider capabilities', async () => {
|
|
307
374
|
const response = await TestApp.create().request('/v1/providers?capability=settlement', {
|
|
308
375
|
headers: TestApp.auth.headers,
|