@gibs/quotes 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +45 -0
  3. package/dist/cache.d.ts +73 -0
  4. package/dist/cache.js +137 -0
  5. package/dist/carriers/calibration.d.ts +115 -0
  6. package/dist/carriers/calibration.js +83 -0
  7. package/dist/carriers/lifi.d.ts +63 -0
  8. package/dist/carriers/lifi.js +217 -0
  9. package/dist/carriers/near-intents.d.ts +91 -0
  10. package/dist/carriers/near-intents.js +249 -0
  11. package/dist/carriers/price-catalogue.d.ts +186 -0
  12. package/dist/carriers/price-catalogue.js +191 -0
  13. package/dist/carriers/relay.d.ts +97 -0
  14. package/dist/carriers/relay.js +242 -0
  15. package/dist/chain-lists.d.ts +144 -0
  16. package/dist/chain-lists.js +169 -0
  17. package/dist/client.d.ts +102 -0
  18. package/dist/client.js +402 -0
  19. package/dist/index.d.ts +19 -0
  20. package/dist/index.js +18 -0
  21. package/dist/quote-guards.d.ts +128 -0
  22. package/dist/quote-guards.js +240 -0
  23. package/dist/quote-refusals.d.ts +180 -0
  24. package/dist/quote-refusals.js +284 -0
  25. package/dist/quote-service-contract.d.ts +2240 -0
  26. package/dist/quote-service-contract.js +247 -0
  27. package/dist/search/capabilities.d.ts +68 -0
  28. package/dist/search/capabilities.js +28 -0
  29. package/dist/search/enumerate.d.ts +162 -0
  30. package/dist/search/enumerate.js +313 -0
  31. package/dist/search/index.d.ts +17 -0
  32. package/dist/search/index.js +17 -0
  33. package/dist/search/serve.d.ts +178 -0
  34. package/dist/search/serve.js +142 -0
  35. package/dist/search/waves.d.ts +481 -0
  36. package/dist/search/waves.js +939 -0
  37. package/dist/single-flight.d.ts +90 -0
  38. package/dist/single-flight.js +142 -0
  39. package/dist/types.d.ts +502 -0
  40. package/dist/types.js +1 -0
  41. package/dist/upstream.d.ts +126 -0
  42. package/dist/upstream.js +343 -0
  43. package/llms.txt +217 -0
  44. package/package.json +142 -0
@@ -0,0 +1,186 @@
1
+ import { type NearIntentsAsset } from '@gibs/bridge-sdk/near-intents';
2
+ import type { CarrierAssetNode } from '../types.js';
3
+ /**
4
+ * Each built-in carrier's own token price catalogue — LI.FI's `priceUSD` on
5
+ * `GET /v1/tokens`, Relay's `GET /currencies/token/price`, and NEAR Intents'
6
+ * `price` on `GET /v0/tokens`.
7
+ *
8
+ * WHY THIS EXISTS. The owner's own model for `estimate()`: "get their price
9
+ * for each token and their fee... then you can use some smart logic to find
10
+ * the best routes." A provider-model estimate needs a price for BOTH ends of
11
+ * an edge before it can say anything — see `types.ts`'s `EstimatedHopResult`
12
+ * doc. `Carrier.estimate` is synchronous (never a network call — see
13
+ * `types.ts`), so each catalogue here is populated ahead of time (through
14
+ * `Carrier.prepare`, for LI.FI and Relay) and read back with a plain,
15
+ * synchronous lookup.
16
+ *
17
+ * MISSING PRICE MEANS NO ESTIMATE, NEVER A GUESS. Every catalogue's
18
+ * `priceUsd` returns `null` for a token it cannot price, and every built-in
19
+ * carrier's own `estimate()` returns `null` in turn the moment either side
20
+ * of an edge prices to `null` — see `lifi.ts`, `relay.ts`, `near-intents.ts`.
21
+ *
22
+ * SOURCES, READ LIVE — never guessed:
23
+ * - LI.FI `GET /v1/tokens`: https://docs.li.fi/api-reference/fetch-all-known-tokens
24
+ * — each token object carries an optional `priceUSD` string. This module
25
+ * reuses `@gibs/bridge-sdk/consolidated-quote`'s own `lifiTokensUrl` and
26
+ * `parseLifiTokens` (already written for the token-picker's own price
27
+ * column) rather than a second parser for the same response shape.
28
+ * - Relay `GET /currencies/token/price?address=&chainId=`:
29
+ * https://docs.relay.link/references/api/get-token-price — `{ price: number }`,
30
+ * ONE token per call. Relay publishes no BULK priced catalogue: its bulk
31
+ * listing, `POST /currencies/v2`, carries `chainId`/`address`/`symbol`/`name`/
32
+ * `decimals`/`vmType`/`metadata` and NO price field at all (verified against
33
+ * `docs.relay.link/references/api/get-currencies-v2` and this repository's
34
+ * own recorded capture, `packages/bridge-sdk/src/__fixtures__/relay/currencies-solana.json`,
35
+ * 2026-09-24). So this module fetches Relay's price per token, once each,
36
+ * and caches every one it learns rather than re-asking.
37
+ * - NEAR Intents `GET /v0/tokens`: every asset already carries its own
38
+ * `price` (a number) and `priceUpdatedAt` (an ISO instant) — verified
39
+ * against the checked-in capture, `packages/bridge-sdk/src/__fixtures__/near-intents/tokens.json`
40
+ * ("price": 3.11 on wrapped NEAR, 2026-09-17). `@gibs/bridge-sdk/near-intents`'s
41
+ * `NearIntentsAsset` and `parseNearIntentsTokens` now keep both fields
42
+ * (previously dropped — see that module's own change note), so
43
+ * {@link nearIntentsPriceCatalogueFromAssets} reads them from the SAME
44
+ * asset list `chain-lists.ts`'s `nearIntentsChainListSource` already
45
+ * fetches on its own refresh cadence — never a second fetch under a
46
+ * second schedule, mirroring how `carriers/near-intents.ts` already reads
47
+ * that list for its own request-building.
48
+ */
49
+ /** One priced token, as a provider's own catalogue reports it. */
50
+ export type CataloguedPrice = {
51
+ /** The token's own price, in United States dollars per whole (decimal-adjusted) unit. */
52
+ readonly priceUsd: number;
53
+ /** When this price was learned, in epoch milliseconds — the catalogue's own clock, not necessarily the provider's. Null when the provider names no timestamp of its own (LI.FI, Relay). */
54
+ readonly updatedAtMs: number | null;
55
+ };
56
+ /** Looks up one token's own most recent price, synchronously, from whatever a catalogue has already loaded. Never triggers a fetch. */
57
+ export type PriceCatalogue = {
58
+ /**
59
+ * @param node - the token to price
60
+ * @returns its United States dollar price, or null when this catalogue has none
61
+ */
62
+ readonly priceUsd: (node: CarrierAssetNode) => number | null;
63
+ };
64
+ /**
65
+ * The provider-model math every built-in carrier's `estimate()` shares — see
66
+ * `types.ts`'s `EstimatedHopResult` doc: "a price ratio times (one minus the
67
+ * carrier's typical fee) minus a fixed cost, calibrated against observed
68
+ * history." Only the FEE INPUTS differ between LI.FI, Relay and NEAR
69
+ * Intents; the arithmetic that turns a price pair and a fee model into a
70
+ * delivered amount is one function, not three copies of it.
71
+ *
72
+ * Works in floating-point whole-token units (`Number`, via `formatUnits`),
73
+ * matching this codebase's existing price-conversion style
74
+ * (`@gibs/bridge-sdk/prices.ts`'s `computeUsdPriceFromAmountsOut`) — an
75
+ * ESTIMATE, not a settlement figure, so the precision a `bigint`
76
+ * fixed-point scheme would add is not worth the extra machinery.
77
+ *
78
+ * @param options.amountIn - the input amount, in `from`'s base units
79
+ * @param options.fromDecimals - `from`'s decimal places
80
+ * @param options.toDecimals - `to`'s decimal places
81
+ * @param options.priceFromUsd - `from`'s own United States dollar price, per whole token
82
+ * @param options.priceToUsd - `to`'s own United States dollar price, per whole token
83
+ * @param options.percentageFee - every proportional charge this provider is known to apply, summed, as a fraction (0.001 = 0.1%)
84
+ * @param options.fixedCostUsd - every flat, United States dollar-denominated charge this provider is known to apply, summed
85
+ * @param options.calibrationFactor - {@link @gibs/quotes/carriers/calibration!CalibrationStore.factor}'s own answer for this provider and chain pair — 1 when uncalibrated
86
+ * @returns the delivered amount, in `to`'s base units; 0 when the inputs would price below zero
87
+ */
88
+ export declare const estimateDeliveredAmount: ({ amountIn, fromDecimals, toDecimals, priceFromUsd, priceToUsd, percentageFee, fixedCostUsd, calibrationFactor, }: {
89
+ readonly amountIn: bigint;
90
+ readonly fromDecimals: number;
91
+ readonly toDecimals: number;
92
+ readonly priceFromUsd: number;
93
+ readonly priceToUsd: number;
94
+ readonly percentageFee: number;
95
+ readonly fixedCostUsd: number;
96
+ readonly calibrationFactor: number;
97
+ }) => bigint;
98
+ /** What {@link createLifiPriceCatalogue} needs. */
99
+ export type LifiPriceCatalogueOptions = {
100
+ /** Overrides LI.FI's own API root, mirroring `LifiProviderConfig.baseUrl`. */
101
+ readonly baseUrl?: string;
102
+ /** The `fetch` this catalogue's own requests go through. Defaults to the runtime global. */
103
+ readonly fetchImpl?: typeof fetch;
104
+ /** The clock a recorded price's `updatedAtMs` reads. Defaults to `Date.now`. */
105
+ readonly now?: () => number;
106
+ };
107
+ /** A LI.FI {@link PriceCatalogue}, with the async load step `Carrier.prepare` drives. */
108
+ export type LifiPriceCatalogue = PriceCatalogue & {
109
+ /**
110
+ * Fetches `GET /v1/tokens` for every chain id among `nodes` not already
111
+ * loaded, merging results into the cache. A chain already loaded is
112
+ * skipped — this catalogue fetches each chain's token list AT MOST ONCE
113
+ * over its own lifetime, per this module's head note.
114
+ *
115
+ * THIS METHOD TRUSTS ITS CALLER TO HAVE ALREADY FILTERED `nodes` TO CHAINS
116
+ * THE PROVIDER ITSELF LISTS. It has no `QuoteClientConfig.neverSendChainIds`
117
+ * of its own to consult (this package's carriers, not this catalogue,
118
+ * hold that guard) — `carriers/lifi.ts`'s own `prepare` is where a chain a
119
+ * host never wants named (PulseChain, for this repository's own host) is
120
+ * filtered out before this method is ever called.
121
+ *
122
+ * @param nodes - every node a search might branch through, already filtered to chains this provider lists — only their `ecosystem === 'evm'` chain ids matter, since LI.FI prices Ethereum-Virtual-Machine chains only
123
+ */
124
+ readonly ensureLoaded: (nodes: readonly CarrierAssetNode[]) => Promise<void>;
125
+ };
126
+ /**
127
+ * Builds LI.FI's own {@link LifiPriceCatalogue}.
128
+ *
129
+ * @param options - see {@link LifiPriceCatalogueOptions}
130
+ * @returns the catalogue
131
+ */
132
+ export declare const createLifiPriceCatalogue: (options?: LifiPriceCatalogueOptions) => LifiPriceCatalogue;
133
+ /** What {@link createRelayPriceCatalogue} needs. */
134
+ export type RelayPriceCatalogueOptions = {
135
+ /** Overrides Relay's own API root, mirroring `RelayProviderConfig.baseUrl`. */
136
+ readonly baseUrl?: string;
137
+ /** Forwarded as `x-api-key`, when set — see `docs.relay.link/references/api/get-token-price`'s optional auth header. */
138
+ readonly apiKey?: string;
139
+ /** The `fetch` this catalogue's own requests go through. Defaults to the runtime global. */
140
+ readonly fetchImpl?: typeof fetch;
141
+ /** The clock a recorded price's `updatedAtMs` reads. Defaults to `Date.now`. */
142
+ readonly now?: () => number;
143
+ };
144
+ /** A Relay {@link PriceCatalogue}, with the async load step `Carrier.prepare` drives. */
145
+ export type RelayPriceCatalogue = PriceCatalogue & {
146
+ /**
147
+ * Fetches `GET /currencies/token/price` for every node not already
148
+ * attempted, in parallel. A token already attempted — priced or not — is
149
+ * skipped, per this module's head note: Relay has no bulk price endpoint,
150
+ * so each token is asked for AT MOST ONCE over this catalogue's lifetime.
151
+ *
152
+ * THIS METHOD TRUSTS ITS CALLER TO HAVE ALREADY FILTERED `nodes` TO CHAINS
153
+ * RELAY ITSELF LISTS — see `LifiPriceCatalogue.ensureLoaded`'s identical
154
+ * note; `carriers/relay.ts`'s own `prepare` is where that filtering happens.
155
+ *
156
+ * @param nodes - every node a search might branch through, already filtered to chains this provider lists
157
+ */
158
+ readonly ensureLoaded: (nodes: readonly CarrierAssetNode[]) => Promise<void>;
159
+ };
160
+ /**
161
+ * Builds Relay's own {@link RelayPriceCatalogue}.
162
+ *
163
+ * @param options - see {@link RelayPriceCatalogueOptions}
164
+ * @returns the catalogue
165
+ */
166
+ export declare const createRelayPriceCatalogue: (options?: RelayPriceCatalogueOptions) => RelayPriceCatalogue;
167
+ /**
168
+ * Builds a NEAR Intents {@link PriceCatalogue} from an already-loaded asset
169
+ * list — the SAME list `chain-lists.ts`'s `nearIntentsChainListSource`
170
+ * fetches and `carriers/near-intents.ts` already reads for its own request
171
+ * building (`chainList.capability('near-intents')`). No `ensureLoaded` and
172
+ * no fetch of its own: this catalogue is a pure, synchronous view over data
173
+ * a caller already has, refreshed on that source's own cadence — see this
174
+ * module's head note.
175
+ *
176
+ * READS THROUGH `findNearIntentsAsset`, NOT A SEPARATE ADDRESS MAP. That
177
+ * function already resolves a `CarrierAssetNode`'s chain id and address to
178
+ * the service's own asset record — including the native-asset and
179
+ * ecosystem-scoping rules `@gibs/bridge-sdk/near-intents`'s own doc lays
180
+ * out — so this module reuses that lookup rather than rebuilding an
181
+ * equivalent one that could silently disagree with it.
182
+ *
183
+ * @param assets - the parsed `GET /v0/tokens` asset list
184
+ * @returns the catalogue
185
+ */
186
+ export declare const nearIntentsPriceCatalogueFromAssets: (assets: readonly NearIntentsAsset[]) => PriceCatalogue;
@@ -0,0 +1,191 @@
1
+ import { formatUnits, parseUnits } from 'viem';
2
+ import { lifiEndpoints, lifiTokensUrl, parseLifiTokens } from '@gibs/bridge-sdk/consolidated-quote';
3
+ import { assetNodeKey } from '@gibs/bridge-sdk/routing';
4
+ import { findNearIntentsAsset } from '@gibs/bridge-sdk/near-intents';
5
+ // ---------------------------------------------------------------------------
6
+ // estimateDeliveredAmount — the arithmetic every built-in carrier shares
7
+ // ---------------------------------------------------------------------------
8
+ /**
9
+ * The provider-model math every built-in carrier's `estimate()` shares — see
10
+ * `types.ts`'s `EstimatedHopResult` doc: "a price ratio times (one minus the
11
+ * carrier's typical fee) minus a fixed cost, calibrated against observed
12
+ * history." Only the FEE INPUTS differ between LI.FI, Relay and NEAR
13
+ * Intents; the arithmetic that turns a price pair and a fee model into a
14
+ * delivered amount is one function, not three copies of it.
15
+ *
16
+ * Works in floating-point whole-token units (`Number`, via `formatUnits`),
17
+ * matching this codebase's existing price-conversion style
18
+ * (`@gibs/bridge-sdk/prices.ts`'s `computeUsdPriceFromAmountsOut`) — an
19
+ * ESTIMATE, not a settlement figure, so the precision a `bigint`
20
+ * fixed-point scheme would add is not worth the extra machinery.
21
+ *
22
+ * @param options.amountIn - the input amount, in `from`'s base units
23
+ * @param options.fromDecimals - `from`'s decimal places
24
+ * @param options.toDecimals - `to`'s decimal places
25
+ * @param options.priceFromUsd - `from`'s own United States dollar price, per whole token
26
+ * @param options.priceToUsd - `to`'s own United States dollar price, per whole token
27
+ * @param options.percentageFee - every proportional charge this provider is known to apply, summed, as a fraction (0.001 = 0.1%)
28
+ * @param options.fixedCostUsd - every flat, United States dollar-denominated charge this provider is known to apply, summed
29
+ * @param options.calibrationFactor - {@link @gibs/quotes/carriers/calibration!CalibrationStore.factor}'s own answer for this provider and chain pair — 1 when uncalibrated
30
+ * @returns the delivered amount, in `to`'s base units; 0 when the inputs would price below zero
31
+ */
32
+ export const estimateDeliveredAmount = ({ amountIn, fromDecimals, toDecimals, priceFromUsd, priceToUsd, percentageFee, fixedCostUsd, calibrationFactor, }) => {
33
+ const amountInWhole = Number(formatUnits(amountIn, fromDecimals));
34
+ const priceRatio = priceFromUsd / priceToUsd;
35
+ const grossOutWhole = amountInWhole * priceRatio * (1 - percentageFee);
36
+ const fixedCostWhole = fixedCostUsd / priceToUsd;
37
+ const netOutWhole = (grossOutWhole - fixedCostWhole) * calibrationFactor;
38
+ if (!Number.isFinite(netOutWhole) || netOutWhole <= 0) {
39
+ return 0n;
40
+ }
41
+ return parseUnits(netOutWhole.toFixed(toDecimals), toDecimals);
42
+ };
43
+ /**
44
+ * Builds LI.FI's own {@link LifiPriceCatalogue}.
45
+ *
46
+ * @param options - see {@link LifiPriceCatalogueOptions}
47
+ * @returns the catalogue
48
+ */
49
+ export const createLifiPriceCatalogue = (options = {}) => {
50
+ const fetchImpl = options.fetchImpl ?? fetch;
51
+ const now = options.now ?? Date.now;
52
+ const prices = new Map();
53
+ const loadedChainIds = new Set();
54
+ let inFlight = null;
55
+ const loadChain = async (chainId) => {
56
+ const url = options.baseUrl === undefined ? lifiTokensUrl(chainId) : lifiEndpoints(options.baseUrl).tokens(chainId);
57
+ let response;
58
+ try {
59
+ response = await fetchImpl(url);
60
+ }
61
+ catch {
62
+ // Stays unloaded; a later `ensureLoaded` call over the same chain retries.
63
+ return;
64
+ }
65
+ if (!response.ok) {
66
+ return;
67
+ }
68
+ const payload = await response.json().catch(() => null);
69
+ const tokens = parseLifiTokens(payload, chainId);
70
+ if (tokens === null) {
71
+ return;
72
+ }
73
+ for (const token of tokens) {
74
+ if (token.priceUSD === null) {
75
+ continue;
76
+ }
77
+ const price = Number(token.priceUSD);
78
+ if (!Number.isFinite(price) || price <= 0) {
79
+ continue;
80
+ }
81
+ const key = assetNodeKey({ chainId, address: token.address, ecosystem: 'evm', decimals: token.decimals });
82
+ prices.set(key, { priceUsd: price, updatedAtMs: now() });
83
+ }
84
+ loadedChainIds.add(chainId);
85
+ };
86
+ const ensureLoaded = async (nodes) => {
87
+ const missing = Array.from(new Set(nodes.filter((node) => node.ecosystem === 'evm').map((node) => node.chainId))).filter((chainId) => !loadedChainIds.has(chainId));
88
+ if (missing.length === 0) {
89
+ return;
90
+ }
91
+ if (inFlight !== null) {
92
+ await inFlight;
93
+ return ensureLoaded(nodes);
94
+ }
95
+ const task = Promise.all(missing.map(loadChain)).then(() => undefined);
96
+ inFlight = task;
97
+ try {
98
+ await task;
99
+ }
100
+ finally {
101
+ inFlight = null;
102
+ }
103
+ };
104
+ return {
105
+ priceUsd: (node) => prices.get(assetNodeKey(node))?.priceUsd ?? null,
106
+ ensureLoaded,
107
+ };
108
+ };
109
+ /**
110
+ * Builds Relay's own {@link RelayPriceCatalogue}.
111
+ *
112
+ * @param options - see {@link RelayPriceCatalogueOptions}
113
+ * @returns the catalogue
114
+ */
115
+ export const createRelayPriceCatalogue = (options = {}) => {
116
+ const fetchImpl = options.fetchImpl ?? fetch;
117
+ const now = options.now ?? Date.now;
118
+ const prices = new Map();
119
+ const attempted = new Set();
120
+ const loadToken = async (node) => {
121
+ const key = assetNodeKey(node);
122
+ if (attempted.has(key)) {
123
+ return;
124
+ }
125
+ attempted.add(key);
126
+ const base = options.baseUrl === undefined ? 'https://api.relay.link' : options.baseUrl.replace(/\/+$/, '');
127
+ const url = new URL(`${base}/currencies/token/price`);
128
+ url.searchParams.set('address', node.address);
129
+ url.searchParams.set('chainId', node.chainId.toString());
130
+ let response;
131
+ try {
132
+ response = await fetchImpl(url.toString(), {
133
+ headers: options.apiKey === undefined ? {} : { 'x-api-key': options.apiKey },
134
+ });
135
+ }
136
+ catch {
137
+ return;
138
+ }
139
+ if (!response.ok) {
140
+ return;
141
+ }
142
+ const body = await response.json().catch(() => null);
143
+ const price = typeof body === 'object' && body !== null ? body.price : undefined;
144
+ if (typeof price !== 'number' || !Number.isFinite(price) || price <= 0) {
145
+ return;
146
+ }
147
+ prices.set(key, { priceUsd: price, updatedAtMs: now() });
148
+ };
149
+ return {
150
+ priceUsd: (node) => prices.get(assetNodeKey(node))?.priceUsd ?? null,
151
+ ensureLoaded: async (nodes) => {
152
+ await Promise.all(nodes.map(loadToken));
153
+ },
154
+ };
155
+ };
156
+ // ---------------------------------------------------------------------------
157
+ // NEAR Intents — derived from the already-loaded asset list, no fetch here
158
+ // ---------------------------------------------------------------------------
159
+ /**
160
+ * Builds a NEAR Intents {@link PriceCatalogue} from an already-loaded asset
161
+ * list — the SAME list `chain-lists.ts`'s `nearIntentsChainListSource`
162
+ * fetches and `carriers/near-intents.ts` already reads for its own request
163
+ * building (`chainList.capability('near-intents')`). No `ensureLoaded` and
164
+ * no fetch of its own: this catalogue is a pure, synchronous view over data
165
+ * a caller already has, refreshed on that source's own cadence — see this
166
+ * module's head note.
167
+ *
168
+ * READS THROUGH `findNearIntentsAsset`, NOT A SEPARATE ADDRESS MAP. That
169
+ * function already resolves a `CarrierAssetNode`'s chain id and address to
170
+ * the service's own asset record — including the native-asset and
171
+ * ecosystem-scoping rules `@gibs/bridge-sdk/near-intents`'s own doc lays
172
+ * out — so this module reuses that lookup rather than rebuilding an
173
+ * equivalent one that could silently disagree with it.
174
+ *
175
+ * @param assets - the parsed `GET /v0/tokens` asset list
176
+ * @returns the catalogue
177
+ */
178
+ export const nearIntentsPriceCatalogueFromAssets = (assets) => ({
179
+ priceUsd: (node) => {
180
+ const asset = findNearIntentsAsset({
181
+ assets,
182
+ chainId: node.chainId,
183
+ tokenAddress: node.address,
184
+ ecosystem: node.ecosystem,
185
+ });
186
+ if (asset === null || asset.price === null) {
187
+ return null;
188
+ }
189
+ return Number.isFinite(asset.price) && asset.price > 0 ? asset.price : null;
190
+ },
191
+ });
@@ -0,0 +1,97 @@
1
+ import { type Ecosystem } from '@gibs/bridge-sdk/ecosystems';
2
+ import type { ChainListRegistry } from '../chain-lists.js';
3
+ import { type CalibrationStore } from './calibration.js';
4
+ import { type RelayPriceCatalogue } from './price-catalogue.js';
5
+ import type { CarrierHopEdge, ProviderAsker, RelayProviderConfig } from '../types.js';
6
+ /**
7
+ * Relay as a built-in {@link ProviderAsker}: a plain, forward-solving
8
+ * `POST /quote/v2` pre-quote, ported from
9
+ * `packages/ui/src/lib/state/route-search-quotes.ts`'s `askRelayEdge` —
10
+ * every `gibs.finance` fact replaced by a value read from this carrier's own
11
+ * {@link CreateRelayCarrierOptions}. See `carriers/lifi.ts`'s head note for
12
+ * why every request here should go through `UpstreamFunnel.guardedFetch`
13
+ * bound to `'relay'`.
14
+ *
15
+ * `POST /quote/v2` NOW REQUIRES `x-api-key` (verified live 2026-09-23; it did
16
+ * not before). `relayRequestHeaders` already sends the header only when
17
+ * `config.apiKey` is set — this carrier forwards whatever the host
18
+ * configured and never invents a key. WITH NO KEY CONFIGURED, this carrier
19
+ * still asks (a host may be mid-migration, or deliberately keyless), and the
20
+ * refusal Relay's own response carries is surfaced through
21
+ * `readRelayRefusal` exactly as recorded — never replaced with a
22
+ * locally-invented "missing key" refusal this module has not observed Relay
23
+ * actually send.
24
+ */
25
+ /** What {@link createRelayCarrier} needs beyond the edge and amount it is asked about. */
26
+ export type CreateRelayCarrierOptions = {
27
+ /** Relay's own configuration — the host's api key, referrer, and base url override. */
28
+ readonly config?: RelayProviderConfig;
29
+ /** A server-held address, per ecosystem — see `carriers/lifi.ts`'s identical field for the full contract. */
30
+ readonly probeAddresses?: Readonly<Partial<Record<Ecosystem, string>>>;
31
+ /** The chain-list registry `chain-lists.ts` builds — read under the `'relay'` key. */
32
+ readonly chainList: Pick<ChainListRegistry, 'chainIds'>;
33
+ /** The `fetch` this carrier's own request goes through — see `carriers/lifi.ts`'s identical field. */
34
+ readonly fetchImpl?: typeof fetch;
35
+ /**
36
+ * This carrier's own {@link RelayPriceCatalogue}, backing {@link Carrier.estimate}.
37
+ * Defaults to `createRelayPriceCatalogue({ baseUrl: config?.baseUrl, apiKey: config?.apiKey, fetchImpl })`.
38
+ */
39
+ readonly priceCatalogue?: RelayPriceCatalogue;
40
+ /**
41
+ * This carrier's own {@link CalibrationStore}, correcting {@link Carrier.estimate}
42
+ * against confirmed `ask()` results. Defaults to an in-memory
43
+ * `createCalibrationStore()` — see `carriers/lifi.ts`'s identical field.
44
+ */
45
+ readonly calibrationStore?: CalibrationStore;
46
+ /**
47
+ * The host's own application fee on Relay routes, in basis points — Relay's
48
+ * `appFees` mechanism (`docs.relay.link/features/app-fees`), configured
49
+ * per host rather than assumed. Undefined or non-positive means no app fee
50
+ * is modeled — this package invents no gibs figure (compare
51
+ * `@gibs/bridge-sdk/referral.ts`'s `relayReferralFeeBasisPoints`, which is
52
+ * gibs's OWN wiring, kept out of this host-agnostic package).
53
+ */
54
+ readonly appFeeBasisPoints?: number;
55
+ };
56
+ /**
57
+ * Which of Relay's published platform-fee tiers an edge most likely falls
58
+ * under — read from `docs.relay.link/how-it-works/fees` and
59
+ * `docs.relay.link/references/api/api_core_concepts/fees.md` (both fetched
60
+ * 2026-09-24): Token Bridge & same-chain Wrap/Unwrap 0.00%, Stablecoin Swap
61
+ * 0.01%, Major Swap (ETH, BTC, SOL, POL, BNB, PLUME, and any of those paired
62
+ * with a stablecoin) 0.06%, Minor Swap (everything else) 0.15%.
63
+ *
64
+ * WHY THIS IS A HEURISTIC, NOT A LOOKUP. {@link CarrierAssetNode} carries no
65
+ * token symbol (see `@gibs/bridge-sdk/routing`'s `AssetNode` — only chain
66
+ * id, address, ecosystem, decimals), and this host-agnostic package keeps no
67
+ * stablecoin/major-asset registry of its own to classify one. So this
68
+ * function only ever answers the two tiers it CAN determine from the edge's
69
+ * own shape, honestly:
70
+ *
71
+ * - `'bridge'` — the literal SAME address crosses chains (a real, checkable
72
+ * fact: some tokens, most commonly stablecoins deployed through a
73
+ * deterministic factory, share one address across chains).
74
+ * - `'major'` — either side is that chain's OWN native asset. Every chain
75
+ * this package's built-in carriers reach is presently an Ethereum-Virtual-
76
+ * Machine chain whose native coin is ether or a major chain's own gas
77
+ * token (BNB, and so on) — Relay's own "major" list — so a native-asset
78
+ * edge is major far more often than not.
79
+ * - `'minor'` — the default for everything else, INCLUDING a stablecoin pair
80
+ * this function cannot tell apart from an ordinary token pair without a
81
+ * symbol. That misclassification prices 0.15% instead of the true 0.01%,
82
+ * a difference `calibrationStore` (keyed per chain pair — see
83
+ * `carriers/lifi.ts`'s identical note) absorbs quickly for any pair quoted
84
+ * more than a few times, because the SAME pair keeps landing in the same
85
+ * true tier.
86
+ *
87
+ * @param edge - the candidate hop
88
+ * @returns the tier
89
+ */
90
+ export declare const classifyRelaySwapKind: (edge: CarrierHopEdge) => "bridge" | "major" | "minor";
91
+ /**
92
+ * Builds Relay as a {@link ProviderAsker}.
93
+ *
94
+ * @param options - see {@link CreateRelayCarrierOptions}
95
+ * @returns the carrier
96
+ */
97
+ export declare const createRelayCarrier: (options: CreateRelayCarrierOptions) => ProviderAsker;