@gibs/bridge-sdk 1.12.0 → 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 (117) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +234 -432
  3. package/dist/abis.d.ts +1 -0
  4. package/dist/bridge-limits.d.ts +103 -0
  5. package/dist/bridge-limits.js +84 -0
  6. package/dist/chain-info.d.ts +169 -0
  7. package/dist/chain-info.js +410 -0
  8. package/dist/chain-names.d.ts +160 -0
  9. package/dist/chain-names.js +200 -0
  10. package/dist/chains.d.ts +65 -0
  11. package/dist/chains.js +284 -0
  12. package/dist/config.d.ts +216 -0
  13. package/dist/config.js +493 -0
  14. package/dist/consolidated-quote.d.ts +554 -0
  15. package/dist/consolidated-quote.js +773 -0
  16. package/dist/destination-calldata.d.ts +118 -0
  17. package/dist/destination-calldata.js +152 -0
  18. package/dist/ecosystems.d.ts +217 -0
  19. package/dist/ecosystems.js +288 -0
  20. package/dist/fee-type.d.ts +10 -0
  21. package/dist/fee-type.js +9 -0
  22. package/dist/hyperliquid-execution.d.ts +152 -0
  23. package/dist/hyperliquid-execution.js +168 -0
  24. package/dist/image-links.d.ts +117 -0
  25. package/dist/image-links.js +142 -0
  26. package/dist/index.d.ts +20 -0
  27. package/dist/index.js +20 -0
  28. package/dist/lifi-execution-guard.d.ts +199 -0
  29. package/dist/lifi-execution-guard.js +400 -0
  30. package/dist/near-intents-conformance.d.ts +85 -0
  31. package/dist/near-intents-conformance.js +329 -0
  32. package/dist/near-intents-route.d.ts +47 -0
  33. package/dist/near-intents-route.js +265 -0
  34. package/dist/near-intents.d.ts +524 -0
  35. package/dist/near-intents.js +1055 -0
  36. package/dist/origin-deposit.d.ts +326 -0
  37. package/dist/origin-deposit.js +68 -0
  38. package/dist/pre-quote.d.ts +198 -0
  39. package/dist/pre-quote.js +175 -0
  40. package/dist/prices.d.ts +107 -0
  41. package/dist/prices.js +162 -0
  42. package/dist/providers.d.ts +221 -0
  43. package/dist/providers.js +157 -0
  44. package/dist/quote-fees.d.ts +102 -0
  45. package/dist/quote-fees.js +195 -0
  46. package/dist/referral.d.ts +77 -0
  47. package/dist/referral.js +72 -0
  48. package/dist/relay-currencies.d.ts +47 -0
  49. package/dist/relay-currencies.js +89 -0
  50. package/dist/relay-quote.d.ts +301 -0
  51. package/dist/relay-quote.js +983 -0
  52. package/dist/route-steps.d.ts +138 -0
  53. package/dist/route-steps.js +305 -0
  54. package/dist/routing/amounts.d.ts +80 -0
  55. package/dist/routing/amounts.js +120 -0
  56. package/dist/routing/candidate-route.d.ts +43 -0
  57. package/dist/routing/candidate-route.js +45 -0
  58. package/dist/routing/candidate.d.ts +237 -0
  59. package/dist/routing/candidate.js +96 -0
  60. package/dist/routing/carriers/ask-answer.d.ts +58 -0
  61. package/dist/routing/carriers/ask-answer.js +1 -0
  62. package/dist/routing/carriers/omnibridge.d.ts +137 -0
  63. package/dist/routing/carriers/omnibridge.js +419 -0
  64. package/dist/routing/carriers/pulsex.d.ts +111 -0
  65. package/dist/routing/carriers/pulsex.js +404 -0
  66. package/dist/routing/carriers/wrap.d.ts +25 -0
  67. package/dist/routing/carriers/wrap.js +115 -0
  68. package/dist/routing/composed-route-quality.d.ts +103 -0
  69. package/dist/routing/composed-route-quality.js +108 -0
  70. package/dist/routing/entry-chain-anchor.d.ts +88 -0
  71. package/dist/routing/entry-chain-anchor.js +55 -0
  72. package/dist/routing/format.d.ts +31 -0
  73. package/dist/routing/format.js +58 -0
  74. package/dist/routing/index.d.ts +34 -0
  75. package/dist/routing/index.js +42 -0
  76. package/dist/routing/producers.d.ts +770 -0
  77. package/dist/routing/producers.js +1564 -0
  78. package/dist/routing/providers.d.ts +26 -0
  79. package/dist/routing/providers.js +38 -0
  80. package/dist/routing/route-data.d.ts +355 -0
  81. package/dist/routing/route-data.js +1 -0
  82. package/dist/routing/search/compose.d.ts +168 -0
  83. package/dist/routing/search/compose.js +98 -0
  84. package/dist/routing/search/graph.d.ts +639 -0
  85. package/dist/routing/search/graph.js +445 -0
  86. package/dist/routing/search/plan-route-data.d.ts +244 -0
  87. package/dist/routing/search/plan-route-data.js +767 -0
  88. package/dist/routing/search/score.d.ts +324 -0
  89. package/dist/routing/search/score.js +400 -0
  90. package/dist/routing/usd-value.d.ts +17 -0
  91. package/dist/routing/usd-value.js +25 -0
  92. package/dist/settings.d.ts +692 -0
  93. package/dist/settings.js +909 -0
  94. package/dist/ton-execution.d.ts +117 -0
  95. package/dist/ton-execution.js +247 -0
  96. package/dist/tron-execution.d.ts +146 -0
  97. package/dist/tron-execution.js +239 -0
  98. package/dist/types.d.ts +51 -0
  99. package/dist/types.js +1 -0
  100. package/package.json +203 -23
  101. package/src/chain-info.test.ts +0 -968
  102. package/src/chain-info.ts +0 -498
  103. package/src/chains.test.ts +0 -123
  104. package/src/chains.ts +0 -88
  105. package/src/config.test.ts +0 -679
  106. package/src/config.ts +0 -500
  107. package/src/fee-type.test.ts +0 -45
  108. package/src/fee-type.ts +0 -14
  109. package/src/image-links.test.ts +0 -160
  110. package/src/image-links.ts +0 -65
  111. package/src/index.ts +0 -8
  112. package/src/settings.test.ts +0 -2754
  113. package/src/settings.ts +0 -875
  114. package/src/types.ts +0 -48
  115. package/tsconfig.json +0 -17
  116. package/vitest.config.ts +0 -13
  117. /package/{src/abis.ts → dist/abis.js} +0 -0
@@ -0,0 +1,175 @@
1
+ import { isNonEthereumVirtualMachineOrigin, lifiIntegrator, } from './consolidated-quote.js';
2
+ import { resolveAttribution } from './referral.js';
3
+ import { relayReferrerField, slippageToBasisPoints, } from './relay-quote.js';
4
+ /**
5
+ * Plain LI.FI swap quote endpoint (forward-solving), re-exported from
6
+ * `consolidated-quote.js` rather than redeclared here — both modules must
7
+ * agree on the same address, and a second literal would be free to drift from
8
+ * it.
9
+ */
10
+ export { lifiQuoteUrl, lifiRequestHeaders } from './consolidated-quote.js';
11
+ /**
12
+ * Builds the Relay EXACT_INPUT swap pre-quote body for a fixed input. NO `txs`
13
+ * are attached: Relay solves FORWARD and reports the delivered anchor amount.
14
+ *
15
+ * `recipient` and `refundTo` name TWO DIFFERENT chains and must never be
16
+ * conflated. `refundTo` is always the user's OWN origin-chain address — Relay
17
+ * refunds a failed (or, here, never-run) swap on the origin chain, and an
18
+ * Ethereum fallback address could not receive one there. `recipient` names
19
+ * where the swap lands on the ENTRY chain (`toChainId`), which is always
20
+ * Ethereum Virtual Machine — for an Ethereum origin the user's own address
21
+ * already satisfies that and is the default; for a non-Ethereum origin
22
+ * (Solana base58, Bitcoin bech32, ...) it does NOT, and Relay's `/quote/v2`
23
+ * validates the recipient's FORMAT against `toChainId` even though this
24
+ * pre-quote never executes. Sending the origin account there — as an earlier
25
+ * version of this builder did, reasoning that an unexecuted quote's recipient
26
+ * is "informational" — gets Relay's `400 INVALID_ADDRESS` on every
27
+ * non-Ethereum-origin pre-quote, which starves `fetchBestEntryAnchorPreQuote`
28
+ * of its only capable provider and surfaces as "No route available" for the
29
+ * whole non-Ethereum-origin flow. `request.recipient` (an explicit
30
+ * Ethereum-format address the caller supplies) is therefore REQUIRED for a
31
+ * non-Ethereum origin; without it this returns null.
32
+ *
33
+ * @param request - the provider-agnostic pre-quote request
34
+ * @returns the request body, or null when the input amount is non-positive or
35
+ * a non-Ethereum origin has no explicit Ethereum-format `recipient`
36
+ */
37
+ export const buildRelayPreQuoteRequest = (request) => {
38
+ if (request.amount <= 0n) {
39
+ return null;
40
+ }
41
+ const nonEthereumOrigin = isNonEthereumVirtualMachineOrigin(request);
42
+ // A non-Ethereum origin's own address cannot stand in for an entry-chain
43
+ // recipient; it must be supplied explicitly. An Ethereum origin's own
44
+ // address already IS a valid entry-chain address, so it remains the default.
45
+ const recipient = nonEthereumOrigin ? request.recipient : (request.recipient ?? request.fromAddress);
46
+ if (recipient === undefined) {
47
+ return null;
48
+ }
49
+ return {
50
+ user: request.fromAddress,
51
+ originChainId: request.fromChainId,
52
+ originCurrency: request.fromTokenAddress,
53
+ destinationChainId: request.toChainId,
54
+ destinationCurrency: request.toTokenAddress,
55
+ amount: request.amount.toString(),
56
+ tradeType: 'EXACT_INPUT',
57
+ recipient,
58
+ // Relay always refunds on the ORIGIN chain, so the origin's own address
59
+ // is the correct refund target regardless of ecosystem — unlike
60
+ // `recipient` above, this one is deliberately NOT Ethereum-format for a
61
+ // non-Ethereum origin.
62
+ refundTo: request.fromAddress,
63
+ ...relayReferrerField(request),
64
+ ...(request.slippage === undefined
65
+ ? {}
66
+ : { slippageTolerance: slippageToBasisPoints(request.slippage) }),
67
+ };
68
+ };
69
+ /**
70
+ * Builds the plain LI.FI swap pre-quote as a query string for `GET /v1/quote`.
71
+ * LI.FI's plain `/quote` is forward-solving by construction (`fromAmount` in,
72
+ * `estimate.toAmount` out), so no trade-type flag is needed.
73
+ *
74
+ * Returns null for a non-Ethereum origin (LI.FI cannot bridge across
75
+ * virtual-machine ecosystems) and for a non-positive input amount.
76
+ *
77
+ * @param request - the provider-agnostic pre-quote request
78
+ * @returns the URL query string (without a leading `?`), or null
79
+ */
80
+ export const buildLifiPreQuoteRequest = (request) => {
81
+ if (request.amount <= 0n) {
82
+ return null;
83
+ }
84
+ if (isNonEthereumVirtualMachineOrigin(request)) {
85
+ return null;
86
+ }
87
+ const params = new URLSearchParams({
88
+ fromChain: request.fromChainId.toString(),
89
+ toChain: request.toChainId.toString(),
90
+ fromToken: request.fromTokenAddress,
91
+ toToken: request.toTokenAddress,
92
+ fromAmount: request.amount.toString(),
93
+ fromAddress: request.fromAddress,
94
+ });
95
+ // `null` means "no integrator of this caller's own" and OMITS the query
96
+ // parameter (LI.FI's own schema marks it optional); `undefined` means
97
+ // "this repository's own default"; a string is sent as-is. See
98
+ // `PreQuoteRequest.lifiIntegrator`'s doc.
99
+ const integrator = resolveAttribution(request.lifiIntegrator, lifiIntegrator);
100
+ if (integrator !== undefined) {
101
+ params.set('integrator', integrator);
102
+ }
103
+ if (request.slippage !== undefined) {
104
+ params.set('slippage', request.slippage.toString());
105
+ }
106
+ return params.toString();
107
+ };
108
+ const asRecord = (value) => {
109
+ if (typeof value !== 'object' || value === null) {
110
+ return null;
111
+ }
112
+ return value;
113
+ };
114
+ /**
115
+ * Parses a decimal-string amount into a bigint.
116
+ *
117
+ * @returns the bigint value, or null when the input is not a parseable amount
118
+ */
119
+ const parseAmount = (value) => {
120
+ if (typeof value !== 'string' || value === '') {
121
+ return null;
122
+ }
123
+ try {
124
+ return BigInt(value);
125
+ }
126
+ catch {
127
+ return null;
128
+ }
129
+ };
130
+ /**
131
+ * Assembles the expected/minimum pair, defaulting the minimum to the expected
132
+ * amount when the provider omits a guaranteed floor. Shared by both parsers so
133
+ * the fallback rule lives in exactly one place.
134
+ *
135
+ * @returns the result, or null when the expected amount is absent
136
+ */
137
+ const toPreQuoteResult = (expected, minimum) => {
138
+ const expectedAmount = parseAmount(expected);
139
+ if (expectedAmount === null) {
140
+ return null;
141
+ }
142
+ const minimumAmount = parseAmount(minimum);
143
+ return {
144
+ expectedAmount,
145
+ minimumAmount: minimumAmount ?? expectedAmount,
146
+ };
147
+ };
148
+ /**
149
+ * Parses a Relay EXACT_INPUT pre-quote response. The delivered anchor figures
150
+ * live under `details.currencyOut`: `amount` (expected) and `minimumAmount`
151
+ * (guaranteed slippage floor). Verified live against `api.relay.link/quote/v2`.
152
+ *
153
+ * @returns the delivered amounts, or null when the payload shape is unexpected
154
+ */
155
+ export const parseRelayPreQuote = (payload) => {
156
+ const currencyOut = asRecord(asRecord(asRecord(payload)?.details)?.currencyOut);
157
+ if (!currencyOut) {
158
+ return null;
159
+ }
160
+ return toPreQuoteResult(currencyOut.amount, currencyOut.minimumAmount);
161
+ };
162
+ /**
163
+ * Parses a plain LI.FI `/quote` response. The delivered anchor figures live
164
+ * under `estimate`: `toAmount` (expected) and `toAmountMin` (guaranteed minimum
165
+ * after slippage) — the same estimate shape the route-step normalizer reads.
166
+ *
167
+ * @returns the delivered amounts, or null when the payload shape is unexpected
168
+ */
169
+ export const parseLifiPreQuote = (payload) => {
170
+ const estimate = asRecord(asRecord(payload)?.estimate);
171
+ if (!estimate) {
172
+ return null;
173
+ }
174
+ return toPreQuoteResult(estimate.toAmount, estimate.toAmountMin);
175
+ };
@@ -0,0 +1,107 @@
1
+ import { type Hex } from 'viem';
2
+ import type { Token } from './types.js';
3
+ /**
4
+ * PulseChain mainnet numeric chain id (`Chains.PLS` = `0x171`). Tokens on this
5
+ * chain (and its testnet sibling below) are priced on-chain via PulseX rather
6
+ * than from an off-chain price feed.
7
+ */
8
+ export declare const PULSECHAIN_CHAIN_ID: number;
9
+ /**
10
+ * PulseChain V4 testnet numeric chain id (`Chains.V4PLS` = `0x3af`). Shares the
11
+ * PulseX on-chain pricing path with mainnet.
12
+ */
13
+ export declare const PULSECHAIN_TESTNET_CHAIN_ID: number;
14
+ /**
15
+ * Bridged DAI on PulseChain (18 decimals) — the USD anchor stablecoin for
16
+ * on-chain PulseX pricing. One token's `getAmountsOut` ending in this address
17
+ * yields that token's USD price directly (DAI is treated as exactly $1).
18
+ *
19
+ * Kept as an exported constant so the anchor can be swapped without touching the
20
+ * pricing math. Mirrors `DAI_ADDRESS` in `packages/tx-report/src/server/prices.ts`.
21
+ */
22
+ export declare const PULSECHAIN_USD_ANCHOR_ADDRESS: Hex;
23
+ /** Decimals of the {@link PULSECHAIN_USD_ANCHOR_ADDRESS} stablecoin. */
24
+ export declare const PULSECHAIN_USD_ANCHOR_DECIMALS = 18;
25
+ /** Wrapped PLS on PulseChain — the routing intermediary for PulseX paths. */
26
+ export declare const WPLS_ADDRESS: `0x${string}`;
27
+ /** Lowercases an address for case-insensitive map keys and comparisons. */
28
+ export declare const lowercaseAddress: (address: string) => string;
29
+ /**
30
+ * Map key for a foreign (LI.FI-priced) token: `${chainId}-${lowercased address}`.
31
+ * Matches the key the UI builds when assembling the LI.FI price map.
32
+ */
33
+ export declare const lifiPriceKey: (chainId: number, address: string) => string;
34
+ /** True when a chain id is priced on-chain via PulseX (mainnet or V4 testnet). */
35
+ export declare const isPulsechain: (chainId: number) => boolean;
36
+ /**
37
+ * The PulseChain-origin address a bridged token backs out to, when the token's
38
+ * `extensions.bridgeInfo` records a PulseChain counterpart. Bridged tokens are
39
+ * 1:1 with their origin, so the origin's PulseX price is the token's USD value.
40
+ *
41
+ * @param token - the token whose PulseChain-origin link to resolve
42
+ * @returns the PulseChain origin address, or null when the token has no such link
43
+ */
44
+ export declare const pulsechainOriginAddress: (token: Token) => Hex | null;
45
+ /**
46
+ * The set of PulseX swap paths to price one whole unit of `token` into the USD
47
+ * anchor stablecoin. Edge cases collapse to shorter paths:
48
+ *
49
+ * - `token` is the stablecoin itself -> no swap needed (priced as exactly 1).
50
+ * - `token` is WPLS (or the native zero-address) -> `[WPLS, stable]` only.
51
+ * - otherwise -> both `[token, WPLS, stable]` and `[token, stable]`.
52
+ *
53
+ * Returned addresses are always concrete (the native sentinel is rewritten to
54
+ * WPLS) so they can be fed straight into `getAmountsOut`.
55
+ *
56
+ * @param options.token - the token (only its address is read) to build paths for
57
+ * @param options.wpls - the wrapped-native intermediary (defaults to {@link WPLS_ADDRESS})
58
+ * @param options.stable - the USD anchor (defaults to {@link PULSECHAIN_USD_ANCHOR_ADDRESS})
59
+ * @returns `{ paths }` — the candidate paths; empty when the token is the stablecoin
60
+ */
61
+ export declare const pulsexPricePaths: ({ token, wpls, stable, }: {
62
+ token: Pick<Token, "address">;
63
+ wpls?: Hex;
64
+ stable?: Hex;
65
+ }) => {
66
+ paths: Hex[][];
67
+ };
68
+ /**
69
+ * A single `getAmountsOut` outcome: the decoded `bigint[]` on success, or raw
70
+ * `Hex` return data when the (allowFailure) call reverted. Mirrors the
71
+ * `multicallRead` return shape so results can be passed in untransformed.
72
+ */
73
+ export type AmountsOutResult = bigint[] | Hex;
74
+ /**
75
+ * Reduces every `getAmountsOut` result (across all paths and all routers for one
76
+ * token) to a single USD price by taking the MAX final output, then scaling out
77
+ * of the stablecoin's decimals. Failed/empty results are dropped; when none
78
+ * succeed the price is null (no liquidity).
79
+ *
80
+ * @param options.results - the `getAmountsOut` outcomes for the token's paths/routers
81
+ * @param options.stableDecimals - decimals of the anchor (defaults to {@link PULSECHAIN_USD_ANCHOR_DECIMALS})
82
+ * @returns the USD price as a number, or null when no path produced a value
83
+ */
84
+ export declare const computeUsdPriceFromAmountsOut: ({ results, stableDecimals, }: {
85
+ results: readonly AmountsOutResult[];
86
+ stableDecimals?: number;
87
+ }) => number | null;
88
+ /**
89
+ * Resolves a token's USD price using pre-fetched price maps, following the
90
+ * co-designed precedence:
91
+ *
92
+ * 1. PulseChain (mainnet or V4 testnet): the token's own PulseX price.
93
+ * 2. Bridged PulseChain-origin token: the origin's PulseX price (1:1 value).
94
+ * 3. Genuinely foreign token: the LI.FI inline price.
95
+ *
96
+ * The stablecoin anchor itself resolves to exactly 1. All address lookups are
97
+ * case-insensitive (maps are keyed by lowercased address).
98
+ *
99
+ * @param token - the token to price
100
+ * @param options.pulsexPrices - lowercased PulseChain address -> USD price
101
+ * @param options.lifiPrices - `${chainId}-${lowercased address}` -> USD price
102
+ * @returns the USD price as a number, or null when no source can price it
103
+ */
104
+ export declare const resolveTokenPriceUsd: (token: Token, { pulsexPrices, lifiPrices, }: {
105
+ pulsexPrices: Map<string, number>;
106
+ lifiPrices: Map<string, number>;
107
+ }) => number | null;
package/dist/prices.js ADDED
@@ -0,0 +1,162 @@
1
+ import { formatUnits } from 'viem';
2
+ import { Chains, nativeAssetOut } from './config.js';
3
+ /**
4
+ * PulseChain mainnet numeric chain id (`Chains.PLS` = `0x171`). Tokens on this
5
+ * chain (and its testnet sibling below) are priced on-chain via PulseX rather
6
+ * than from an off-chain price feed.
7
+ */
8
+ export const PULSECHAIN_CHAIN_ID = Number(Chains.PLS);
9
+ /**
10
+ * PulseChain V4 testnet numeric chain id (`Chains.V4PLS` = `0x3af`). Shares the
11
+ * PulseX on-chain pricing path with mainnet.
12
+ */
13
+ export const PULSECHAIN_TESTNET_CHAIN_ID = Number(Chains.V4PLS);
14
+ /**
15
+ * Bridged DAI on PulseChain (18 decimals) — the USD anchor stablecoin for
16
+ * on-chain PulseX pricing. One token's `getAmountsOut` ending in this address
17
+ * yields that token's USD price directly (DAI is treated as exactly $1).
18
+ *
19
+ * Kept as an exported constant so the anchor can be swapped without touching the
20
+ * pricing math. Mirrors `DAI_ADDRESS` in `packages/tx-report/src/server/prices.ts`.
21
+ */
22
+ export const PULSECHAIN_USD_ANCHOR_ADDRESS = '0xefd766ccb38eaf1dfd701853bfce31359239f305';
23
+ /** Decimals of the {@link PULSECHAIN_USD_ANCHOR_ADDRESS} stablecoin. */
24
+ export const PULSECHAIN_USD_ANCHOR_DECIMALS = 18;
25
+ /** Wrapped PLS on PulseChain — the routing intermediary for PulseX paths. */
26
+ export const WPLS_ADDRESS = nativeAssetOut[Chains.PLS];
27
+ /** The native (gas) token sentinel address used across the dapp. */
28
+ const NATIVE_TOKEN_ADDRESS = '0x0000000000000000000000000000000000000000';
29
+ /** Lowercases an address for case-insensitive map keys and comparisons. */
30
+ export const lowercaseAddress = (address) => address.toLowerCase();
31
+ /** True when two addresses are equal ignoring case (and 0x-checksum casing). */
32
+ const sameAddress = (left, right) => lowercaseAddress(left) === lowercaseAddress(right);
33
+ /**
34
+ * Map key for a foreign (LI.FI-priced) token: `${chainId}-${lowercased address}`.
35
+ * Matches the key the UI builds when assembling the LI.FI price map.
36
+ */
37
+ export const lifiPriceKey = (chainId, address) => `${chainId}-${lowercaseAddress(address)}`;
38
+ /** True when a chain id is priced on-chain via PulseX (mainnet or V4 testnet). */
39
+ export const isPulsechain = (chainId) => chainId === PULSECHAIN_CHAIN_ID || chainId === PULSECHAIN_TESTNET_CHAIN_ID;
40
+ /**
41
+ * The PulseChain-origin address a bridged token backs out to, when the token's
42
+ * `extensions.bridgeInfo` records a PulseChain counterpart. Bridged tokens are
43
+ * 1:1 with their origin, so the origin's PulseX price is the token's USD value.
44
+ *
45
+ * @param token - the token whose PulseChain-origin link to resolve
46
+ * @returns the PulseChain origin address, or null when the token has no such link
47
+ */
48
+ export const pulsechainOriginAddress = (token) => {
49
+ const link = token.extensions?.bridgeInfo?.[PULSECHAIN_CHAIN_ID];
50
+ if (!link) {
51
+ return null;
52
+ }
53
+ return link.tokenAddress;
54
+ };
55
+ /**
56
+ * The set of PulseX swap paths to price one whole unit of `token` into the USD
57
+ * anchor stablecoin. Edge cases collapse to shorter paths:
58
+ *
59
+ * - `token` is the stablecoin itself -> no swap needed (priced as exactly 1).
60
+ * - `token` is WPLS (or the native zero-address) -> `[WPLS, stable]` only.
61
+ * - otherwise -> both `[token, WPLS, stable]` and `[token, stable]`.
62
+ *
63
+ * Returned addresses are always concrete (the native sentinel is rewritten to
64
+ * WPLS) so they can be fed straight into `getAmountsOut`.
65
+ *
66
+ * @param options.token - the token (only its address is read) to build paths for
67
+ * @param options.wpls - the wrapped-native intermediary (defaults to {@link WPLS_ADDRESS})
68
+ * @param options.stable - the USD anchor (defaults to {@link PULSECHAIN_USD_ANCHOR_ADDRESS})
69
+ * @returns `{ paths }` — the candidate paths; empty when the token is the stablecoin
70
+ */
71
+ export const pulsexPricePaths = ({ token, wpls = WPLS_ADDRESS, stable = PULSECHAIN_USD_ANCHOR_ADDRESS, }) => {
72
+ const address = sameAddress(token.address, NATIVE_TOKEN_ADDRESS) ? wpls : token.address;
73
+ if (sameAddress(address, stable)) {
74
+ return { paths: [] };
75
+ }
76
+ if (sameAddress(address, wpls)) {
77
+ return { paths: [[wpls, stable]] };
78
+ }
79
+ return {
80
+ paths: [
81
+ [address, wpls, stable],
82
+ [address, stable],
83
+ ],
84
+ };
85
+ };
86
+ /**
87
+ * The final output amount of one `getAmountsOut` result, or null when the call
88
+ * failed (Hex) or returned an empty array. The last element of a `bigint[]`
89
+ * `getAmountsOut` is the output-token amount.
90
+ */
91
+ const finalOutput = (result) => {
92
+ if (typeof result === 'string') {
93
+ return null;
94
+ }
95
+ if (result.length === 0) {
96
+ return null;
97
+ }
98
+ return result[result.length - 1];
99
+ };
100
+ /**
101
+ * Reduces every `getAmountsOut` result (across all paths and all routers for one
102
+ * token) to a single USD price by taking the MAX final output, then scaling out
103
+ * of the stablecoin's decimals. Failed/empty results are dropped; when none
104
+ * succeed the price is null (no liquidity).
105
+ *
106
+ * @param options.results - the `getAmountsOut` outcomes for the token's paths/routers
107
+ * @param options.stableDecimals - decimals of the anchor (defaults to {@link PULSECHAIN_USD_ANCHOR_DECIMALS})
108
+ * @returns the USD price as a number, or null when no path produced a value
109
+ */
110
+ export const computeUsdPriceFromAmountsOut = ({ results, stableDecimals = PULSECHAIN_USD_ANCHOR_DECIMALS, }) => {
111
+ const outputs = results.flatMap((result) => {
112
+ const output = finalOutput(result);
113
+ return output === null ? [] : [output];
114
+ });
115
+ if (outputs.length === 0) {
116
+ return null;
117
+ }
118
+ const maxOutput = outputs.reduce((max, current) => (current > max ? current : max), 0n);
119
+ if (maxOutput === 0n) {
120
+ return null;
121
+ }
122
+ return Number(formatUnits(maxOutput, stableDecimals));
123
+ };
124
+ /**
125
+ * Resolves a token's USD price using pre-fetched price maps, following the
126
+ * co-designed precedence:
127
+ *
128
+ * 1. PulseChain (mainnet or V4 testnet): the token's own PulseX price.
129
+ * 2. Bridged PulseChain-origin token: the origin's PulseX price (1:1 value).
130
+ * 3. Genuinely foreign token: the LI.FI inline price.
131
+ *
132
+ * The stablecoin anchor itself resolves to exactly 1. All address lookups are
133
+ * case-insensitive (maps are keyed by lowercased address).
134
+ *
135
+ * @param token - the token to price
136
+ * @param options.pulsexPrices - lowercased PulseChain address -> USD price
137
+ * @param options.lifiPrices - `${chainId}-${lowercased address}` -> USD price
138
+ * @returns the USD price as a number, or null when no source can price it
139
+ */
140
+ export const resolveTokenPriceUsd = (token, { pulsexPrices, lifiPrices, }) => {
141
+ if (isPulsechain(token.chainId)) {
142
+ if (sameAddress(token.address, PULSECHAIN_USD_ANCHOR_ADDRESS)) {
143
+ return 1;
144
+ }
145
+ return pulsexPrices.get(lowercaseAddress(token.address)) ?? null;
146
+ }
147
+ const origin = pulsechainOriginAddress(token);
148
+ if (origin) {
149
+ if (sameAddress(origin, PULSECHAIN_USD_ANCHOR_ADDRESS)) {
150
+ return 1;
151
+ }
152
+ // Prefer the bridged origin's on-chain PulseX price. Fall back to the token's
153
+ // OWN home-chain LI.FI inline price when PulseX cannot price it — a bridged
154
+ // major (e.g. wrapped ether / wrapped binance coin) whose PulseChain pool path
155
+ // returns nothing would otherwise resolve to null despite a well-known market
156
+ // price on its home chain.
157
+ return (pulsexPrices.get(lowercaseAddress(origin)) ??
158
+ lifiPrices.get(lifiPriceKey(token.chainId, token.address)) ??
159
+ null);
160
+ }
161
+ return lifiPrices.get(lifiPriceKey(token.chainId, token.address)) ?? null;
162
+ };
@@ -0,0 +1,221 @@
1
+ import { type ConsolidatedQuote, type ConsolidatedQuoteRequest } from './consolidated-quote.js';
2
+ import { type Provider } from './config.js';
3
+ import type { Ecosystem } from './ecosystems.js';
4
+ import { type PreQuoteRequest, type PreQuoteResult } from './pre-quote.js';
5
+ import { type NearIntentsAsset } from './near-intents.js';
6
+ import type { QuoteFeeToken } from './quote-fees.js';
7
+ /**
8
+ * A consolidated-route aggregator. Like {@link Ecosystem}, this is the `id` of a
9
+ * descriptor in {@link providerRegistry}, so adding a provider is one descriptor
10
+ * rather than an edit spread across a union, a capability conditional, and two
11
+ * interface-side arrays.
12
+ */
13
+ export type ProviderId = 'lifi' | 'relay' | 'near-intents';
14
+ /**
15
+ * Every source a hop or a lookup table can attribute to a name, a logo or a
16
+ * display colour: a consolidated-route aggregator ({@link ProviderId}), an
17
+ * omnibridge crossing ({@link Provider}, from `config.ts` — `'pulsechain'` or
18
+ * `'tokensex'`), or the on-chain PulseChain DEX swap leg, which belongs to
19
+ * neither registry and is named here as its own literal.
20
+ *
21
+ * THE ONE UNION FOR "NAME A CARRIER." Every table a hop's carrier is looked up
22
+ * in — a logo url, a glyph, a display name — must be typed
23
+ * `Record<ProviderKey, X>`, never `Record<string, X>`: a table keyed on
24
+ * `string` accepts a carrier with no entry silently, which is how NEAR
25
+ * Intents shipped with no logo and how a lower-cased `'pulsex'` drew the
26
+ * initials tile "PU" instead of the PulseX mark. Typed on this union, a
27
+ * carrier added to any of its three sources fails to compile until it also
28
+ * has a name and a mark.
29
+ *
30
+ * `'PulseX'` IS SPELLED WITH THAT EXACT CASING, matching `providerKey:
31
+ * 'PulseX'` in `routing/producers.ts` and every lookup table keyed on it.
32
+ * `routing/search/graph.ts`'s `HopEdge.carrier` is a plain `CarrierId`
33
+ * string, not this union — the route-search engine is a plug-in host that
34
+ * does not close its carrier vocabulary over gibs's own three; every member
35
+ * of THIS union is still a valid `CarrierId`, just not the only one.
36
+ */
37
+ export type ProviderKey = ProviderId | Provider | 'PulseX';
38
+ /**
39
+ * What a provider may need to form a request or read an answer, beyond the
40
+ * route request itself.
41
+ *
42
+ * EVERY FIELD IS OPTIONAL AND MOST PROVIDERS IGNORE ALL OF THEM. LI.FI and Relay
43
+ * name assets by chain and address, and hand back a signable transaction, so
44
+ * their builders and parsers are total over the request alone — and they stay
45
+ * referenced here unchanged, because a function of one argument satisfies a type
46
+ * of two. NEAR Intents needs more in both directions: it names assets by its own
47
+ * identifiers, which can only be looked up in its published list, and it answers
48
+ * with a deposit ADDRESS rather than a transaction, so the chain and token the
49
+ * transfer must be composed from exist only on the request.
50
+ *
51
+ * Kept as one optional record rather than a per-provider generic parameter
52
+ * because the registry's whole purpose is that callers iterate descriptors
53
+ * without knowing which provider they hold.
54
+ */
55
+ export type ProviderQuoteContext = {
56
+ /**
57
+ * The NEAR Intents asset list. Its identifiers cannot be constructed from a
58
+ * chain and an address — see `findNearIntentsAsset`.
59
+ */
60
+ nearIntentsAssets?: readonly NearIntentsAsset[];
61
+ /** The clock, for a provider whose request states a deadline. */
62
+ now?: Date;
63
+ /** Distribution-channel tag, for a provider that accepts one. */
64
+ referral?: string;
65
+ /**
66
+ * The request being answered. Needed only by a parser whose response does not
67
+ * carry enough to build the origin deposit on its own.
68
+ */
69
+ request?: ConsolidatedQuoteRequest;
70
+ /** The paid token, for normalizing a charge denominated in it. */
71
+ originToken?: QuoteFeeToken;
72
+ /** The delivered token, for the same reason. */
73
+ destinationToken?: QuoteFeeToken;
74
+ };
75
+ /**
76
+ * A self-describing provider: which source ecosystems it can carry, and the
77
+ * existing request-builders and response-parsers for each of the two quoting
78
+ * phases. The builders and parsers are REFERENCED, never re-implemented — they
79
+ * already exist and are tested. Endpoint URLs are intentionally absent: the
80
+ * transport wrappers that own the endpoint, abort signal, and headers live in
81
+ * the interface package.
82
+ */
83
+ export type ProviderDescriptor = {
84
+ id: ProviderId;
85
+ label: string;
86
+ /** The source ecosystems this provider can originate a route from. */
87
+ supportedSourceEcosystems: readonly Ecosystem[];
88
+ /**
89
+ * Whether this provider's executor performs a call on the destination chain
90
+ * when the funds arrive.
91
+ *
92
+ * THE FIELD THAT DECIDES WHAT SHAPE OF ROUTE A PROVIDER CAN CARRY, and the
93
+ * owner's standing criterion expressed as data rather than as a rule somebody
94
+ * has to remember.
95
+ *
96
+ * True — hand it the omnibridge entry call and the crossing is one signature.
97
+ * False — it delivers tokens to an address and stops, so a crossing through it
98
+ * takes TWO signatures: the provider's leg, then the reader signing the entry
99
+ * call themselves once the funds land.
100
+ *
101
+ * It also gates the entry-anchor pre-quote, and that is the part that bites.
102
+ * That phase takes the LARGEST delivered amount across providers and feeds it
103
+ * to the execution quote as a fixed exact-output target. A provider that
104
+ * cannot execute can still deliver more — so letting one bid there raises a
105
+ * target only it could reach and that nobody can execute, turning a working
106
+ * route into no route at all. See `selectPreQuoteProviderIds`.
107
+ */
108
+ executesDestinationCalls: boolean;
109
+ /**
110
+ * The origin chains this provider can perform a destination call FROM, or
111
+ * `'any'` when every origin it carries can. Read only when
112
+ * {@link executesDestinationCalls} is true.
113
+ *
114
+ * LI.FI IS ETHEREUM ONLY — the owner's rule: its contract-calls endpoint
115
+ * is what attaches the omnibridge entry call, and from any other origin a
116
+ * LI.FI leg is an ordinary delivery to the reader's own wallet, followed by
117
+ * a crossing the reader signs. The route search reads this list, so it never
118
+ * proposes a fused LI.FI hop it could not have signed.
119
+ */
120
+ destinationCallOriginChainIds: readonly number[] | 'any';
121
+ /**
122
+ * Whether the destination call this provider performs may carry native
123
+ * value — the omnibridge's native entry (`wrapAndRelayTokens`) is a payable
124
+ * call, where every other entry moves a token.
125
+ *
126
+ * Relay's is verified: a dry `quote/v2` for Base USD Coin into Ethereum
127
+ * ether with a value-bearing `wrapAndRelayTokens` call answered 200 at 0.05
128
+ * ether and refused with `DESTINATION_TX_FAILED` at 0.01 (under the
129
+ * mediator's floor) — so Relay simulates the call with its value attached
130
+ * (measured 2026-09-27). LI.FI's is not verified, and its request builder
131
+ * refuses such a call; this field and that builder must agree (see
132
+ * `providers.test.ts`).
133
+ */
134
+ destinationCallCarriesNativeValue: boolean;
135
+ /**
136
+ * Phase one: the plain forward-solving origin-to-anchor swap pre-quote.
137
+ *
138
+ * The `context` argument is ignored by every provider that does not need it,
139
+ * and a builder written to take only the request still satisfies this type.
140
+ */
141
+ preQuote: {
142
+ method: 'GET' | 'POST';
143
+ build: (request: PreQuoteRequest, context?: ProviderQuoteContext) => string | object | null;
144
+ parse: (payload: unknown, context?: ProviderQuoteContext) => PreQuoteResult | null;
145
+ };
146
+ /** Phase two: the backward-solving execution quote with destination calls. */
147
+ executionQuote: {
148
+ method: 'GET' | 'POST';
149
+ build: (request: ConsolidatedQuoteRequest, context?: ProviderQuoteContext) => object | null;
150
+ parse: (payload: unknown, context?: ProviderQuoteContext) => ConsolidatedQuote | null;
151
+ };
152
+ };
153
+ /**
154
+ * Every integrated provider. LI.FI is first: the interface preserves this order
155
+ * in its active lists, and LI.FI-first is the existing tie-break that prefers
156
+ * the verified provider on an equal-cost quote. NEAR Intents is last, so it
157
+ * wins a tie against neither — appropriate for the one provider whose route
158
+ * costs the reader a second signature.
159
+ */
160
+ export declare const providerRegistry: readonly ProviderDescriptor[];
161
+ /**
162
+ * Looks up a provider descriptor for ANY carrier id, answering null for an id
163
+ * the registry does not hold. The route-search engine carries hops for
164
+ * carriers that are not providers at all (a local wrap, a pool swap, a
165
+ * host's own plug-in), so a caller holding an open carrier id must ask this,
166
+ * never cast the id to {@link ProviderId} and call {@link providerById}, which
167
+ * throws for such an id.
168
+ *
169
+ * @param id - any carrier id
170
+ * @returns the descriptor, or null when no provider is registered under `id`
171
+ */
172
+ export declare const findProviderById: (id: string) => ProviderDescriptor | null;
173
+ /**
174
+ * Looks up a provider descriptor. Total over the `ProviderId` union; throws only
175
+ * if the registry is left incomplete.
176
+ *
177
+ * @param id - the provider key
178
+ * @returns the descriptor whose `id` equals `id`
179
+ */
180
+ export declare const providerById: (id: ProviderId) => ProviderDescriptor;
181
+ /**
182
+ * The providers that can originate a route from `ecosystem`, in registry order.
183
+ * This is the data that used to be the hand-written capability conditional.
184
+ *
185
+ * @param ecosystem - the source ecosystem
186
+ * @returns the descriptors whose `supportedSourceEcosystems` include `ecosystem`
187
+ */
188
+ export declare const providersForOrigin: (ecosystem: Ecosystem) => readonly ProviderDescriptor[];
189
+ /**
190
+ * The providers that may bid in the entry-anchor pre-quote for an origin.
191
+ *
192
+ * A STRICTER LIST THAN {@link providersForOrigin}, AND THE DIFFERENCE IS NOT
193
+ * COSMETIC. The pre-quote exists for one purpose: to learn, for the reader's
194
+ * fixed input, how much anchor an entry really delivers, so that figure can
195
+ * become the exact-output target of the execution quote. The winner is whoever
196
+ * delivers MOST.
197
+ *
198
+ * A provider that cannot execute the entry call can still win that comparison —
199
+ * it has one less thing to pay for. Its number then becomes a target handed to
200
+ * the providers that must actually fill it, and they cannot, so the whole entry
201
+ * produces no route. The failure is silent and looks like an absent quote.
202
+ *
203
+ * So the pre-quote is restricted to providers that execute. This is not a
204
+ * judgement about quality; it is that a pre-quote and an execution quote have
205
+ * to be answerable by the same provider for the handoff between them to mean
206
+ * anything.
207
+ *
208
+ * @param ecosystem - the source ecosystem
209
+ * @returns the descriptors that may pre-quote this origin, in registry order
210
+ */
211
+ export declare const preQuoteProvidersForOrigin: (ecosystem: Ecosystem) => readonly ProviderDescriptor[];
212
+ /**
213
+ * Whether a provider can quote a request's origin ecosystem, derived from the
214
+ * registry. Equivalent to `providersForOrigin(...).some(...)`, kept as a named
215
+ * predicate for the call sites that ask about one provider at a time.
216
+ *
217
+ * @param provider - the aggregator under consideration
218
+ * @param request - the request whose origin is being checked
219
+ * @returns true when the provider supports the request's origin ecosystem
220
+ */
221
+ export declare const providerSupportsOrigin: (provider: ProviderId, request: Pick<ConsolidatedQuoteRequest, "originVmType">) => boolean;