@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,138 @@
1
+ import type { Hex } from 'viem';
2
+ /**
3
+ * The semantic kind of a single route step. These map to the provider's
4
+ * action type as faithfully as the response data allows.
5
+ *
6
+ * - `bridge` — cross-chain transfer (different `fromChainId` and `toChainId`)
7
+ * - `swap` — same-chain token exchange via a decentralized exchange
8
+ * - `wrap` — native asset → wrapped native (e.g. ETH → WETH)
9
+ * - `unwrap` — wrapped native → native asset
10
+ * - `send` — plain transfer with no token conversion
11
+ */
12
+ export type RouteStepKind = 'bridge' | 'swap' | 'wrap' | 'unwrap' | 'send';
13
+ /**
14
+ * A normalized, provider-agnostic description of one leg in a bridge route.
15
+ * The UI renders these in order to show the user what will happen to their
16
+ * funds at each stage of the journey.
17
+ */
18
+ export type RouteStep = {
19
+ /** Semantic category of this step. */
20
+ kind: RouteStepKind;
21
+ /**
22
+ * Human-readable, fully-spelled-out label for this step (no acronyms).
23
+ * Example: "Bridge to Ethereum" or "Swap on Uniswap V3".
24
+ */
25
+ label: string;
26
+ /** Chain the step starts on, as a decimal chain id. */
27
+ fromChainId: number;
28
+ /** Chain the step lands on, as a decimal chain id. */
29
+ toChainId: number;
30
+ /**
31
+ * Token address the step consumes.
32
+ *
33
+ * Typed as `string` rather than `Hex` because a leg can start on a
34
+ * non-Ethereum chain, where the token identifier is not `0x`-prefixed — a
35
+ * Bitcoin address, a Solana or Tron base58 address, a The Open Network
36
+ * user-friendly address. Treat it as an opaque identifier for display and
37
+ * prefer {@link fromTokenSymbol} when showing it.
38
+ */
39
+ fromToken: string;
40
+ /** Token address the step produces. `string` for the same reason as {@link fromToken}. */
41
+ toToken: string;
42
+ /**
43
+ * Ticker symbol of the consumed token (e.g. "USDC"), when the provider
44
+ * supplies it. Null when the response omitted the token's symbol — callers
45
+ * showing a conversion should fall back to a shortened address rather than the
46
+ * full address, which is unreadable.
47
+ */
48
+ fromTokenSymbol: string | null;
49
+ /** Ticker symbol of the produced token, or null when the provider omits it. */
50
+ toTokenSymbol: string | null;
51
+ /**
52
+ * Logo image url for the CONSUMED token, when the provider supplies one. This is
53
+ * the token's own image, not the service provider's ({@link providerLogoUri}).
54
+ * Carried because a step can start on a non-Ethereum chain, where the image
55
+ * service has no entry keyed by a checksummed hex address and the provider's url
56
+ * is the only image available. Null when the provider omitted it.
57
+ */
58
+ fromTokenLogoUri: string | null;
59
+ /** Logo image url for the PRODUCED token. Null for the same reasons as {@link fromTokenLogoUri}. */
60
+ toTokenLogoUri: string | null;
61
+ /**
62
+ * The router or contract this step calls, when the provider supplies it.
63
+ * Null when the provider does not expose a contract address for this leg.
64
+ */
65
+ router: Hex | null;
66
+ /**
67
+ * Logo image url for the service provider that performs this step (for a
68
+ * LI.FI route this is the per-step tool: the decentralized exchange or bridge
69
+ * the aggregator routes through, for example Uniswap or Stargate). Taken from
70
+ * the provider's per-step tool details when present. Null when the provider
71
+ * supplied no logo url for the step, in which case the consuming interface
72
+ * falls back to a provider-initials tile rather than a broken image.
73
+ */
74
+ providerLogoUri: string | null;
75
+ /**
76
+ * Estimated output of this step in base units, when the provider supplies it.
77
+ * Null when the provider does not report an estimated output for this leg.
78
+ */
79
+ estimatedOut: bigint | null;
80
+ /**
81
+ * Estimated United States dollar cost of THIS step — the sum of the provider's
82
+ * per-step protocol fees (`feeCosts`) and gas costs (`gasCosts`) — so the UI can
83
+ * show a fee on every hop rather than only a single route total. Null when the
84
+ * provider reported no per-step costs (the UI then shows no fee for the hop).
85
+ */
86
+ feeUsd: number | null;
87
+ /**
88
+ * Estimated United States dollar GAS cost of THIS step — the provider's
89
+ * `gasCosts` ALONE, with the protocol-fee portion ({@link feeUsd}'s `feeCosts`
90
+ * component) excluded. Surfaced separately from {@link feeUsd} because the two
91
+ * costs enter route comparison differently: protocol fees and slippage are
92
+ * already embedded in the quote's `fromAmount` (the backward-quoted origin
93
+ * token), whereas gas is paid ON TOP in the network's native token. Route
94
+ * ranking therefore charges only this gas figure on top of the paid-versus-
95
+ * delivered spread, so it must not double-count the protocol fees. Null when the
96
+ * provider reported no gas costs for the leg.
97
+ */
98
+ gasUsd: number | null;
99
+ };
100
+ /**
101
+ * Normalizes a LI.FI `includedSteps` array into the provider-agnostic
102
+ * `RouteStep[]` shape. Steps with missing or non-hex token addresses are
103
+ * silently dropped rather than causing the whole normalization to fail.
104
+ *
105
+ * @param steps - the raw `includedSteps` value from a LI.FI quote response,
106
+ * or `undefined` when the field was absent (trimmed responses)
107
+ * @returns ordered list of normalized steps; empty when no usable legs are present
108
+ */
109
+ export declare const normalizeLifiSteps: (steps: unknown) => RouteStep[];
110
+ /**
111
+ * Normalizes a Relay quote into the provider-agnostic `RouteStep[]` shape.
112
+ *
113
+ * Relay's `steps` array holds only OPERATIONAL steps (`approve` and `deposit`) —
114
+ * what the user signs, not where the funds go. The route itself lives in
115
+ * `details.route`, which describes the journey as an origin leg and a
116
+ * destination leg, each with an input and an output currency. Reading those four
117
+ * endpoints in order and emitting a step between each consecutive pair recovers
118
+ * the real path, so a Bitcoin origin renders as "Bitcoin → United States Dollar
119
+ * Coin on Ethereum" rather than as nothing at all.
120
+ *
121
+ * Consecutive endpoints that name the same currency on the same chain are
122
+ * collapsed, because Relay repeats an endpoint when a leg performs no
123
+ * conversion. A step's kind follows from its chains: crossing chains is a
124
+ * `bridge`, staying put is a `swap`. Relay reports no wrap/unwrap distinction,
125
+ * so none is invented.
126
+ *
127
+ * Costs are route-level in Relay's response, not per leg, so they are attached
128
+ * to the FIRST step — the one the user actually signs and pays at. `gasUsd`
129
+ * carries `fees.gas` alone (the origin transaction's gas, paid on top in the
130
+ * network's native token), while `feeUsd` adds the relayer and application fees
131
+ * on top of it, matching how the LI.FI normalizer splits the two. The relayer
132
+ * fee's `relayerGas`/`relayerService` components are deliberately NOT summed:
133
+ * they add up to `relayer` exactly, so counting them would double the fee.
134
+ *
135
+ * @param quote - a raw Relay quote response body
136
+ * @returns ordered list of normalized steps; empty when no route is present
137
+ */
138
+ export declare const normalizeRelaySteps: (quote: unknown) => RouteStep[];
@@ -0,0 +1,305 @@
1
+ import { chainDisplayName } from './chain-names.js';
2
+ const isHexString = (value) => typeof value === 'string' && value.startsWith('0x');
3
+ /**
4
+ * Sums the `amountUSD` field across a LI.FI cost array (`feeCosts` or
5
+ * `gasCosts`), tolerating missing/malformed entries by skipping them. LI.FI
6
+ * reports each cost's dollar value as a numeric string; entries without a finite
7
+ * `amountUSD` contribute nothing rather than poisoning the sum with `NaN`.
8
+ *
9
+ * @param costs - a raw `feeCosts`/`gasCosts` value from a step estimate
10
+ * @returns the total United States dollar cost; zero when none are present
11
+ */
12
+ const sumUsdCosts = (costs) => {
13
+ if (!Array.isArray(costs)) {
14
+ return 0;
15
+ }
16
+ return costs.reduce((total, raw) => {
17
+ const cost = asRecord(raw);
18
+ const amountUsd = typeof cost?.amountUSD === 'string' ? Number(cost.amountUSD) : Number.NaN;
19
+ return Number.isFinite(amountUsd) ? total + amountUsd : total;
20
+ }, 0);
21
+ };
22
+ const asRecord = (value) => {
23
+ if (typeof value !== 'object' || value === null) {
24
+ return null;
25
+ }
26
+ return value;
27
+ };
28
+ /**
29
+ * Maps a LI.FI step type string to the normalized `RouteStepKind`.
30
+ *
31
+ * LI.FI reports `"cross"` for bridge legs and `"swap"` for same-chain
32
+ * exchanges. `"wrap"` and `"unwrap"` map directly. Unknown types fall back to
33
+ * `"send"` rather than failing the whole normalization.
34
+ */
35
+ const lifiTypeToKind = (type) => {
36
+ if (type === 'cross')
37
+ return 'bridge';
38
+ if (type === 'swap')
39
+ return 'swap';
40
+ if (type === 'wrap')
41
+ return 'wrap';
42
+ if (type === 'unwrap')
43
+ return 'unwrap';
44
+ return 'send';
45
+ };
46
+ /**
47
+ * Builds a human-readable label for a LI.FI step.
48
+ *
49
+ * Labels are fully spelled out (no acronyms) so they can be displayed
50
+ * directly in the UI without any post-processing.
51
+ */
52
+ const buildLifiLabel = (kind, toChainId, toolName) => {
53
+ if (kind === 'bridge')
54
+ return `Bridge to ${chainDisplayName(toChainId)}`;
55
+ if (kind === 'swap')
56
+ return `Swap on ${toolName}`;
57
+ if (kind === 'wrap')
58
+ return `Wrap to native token on ${chainDisplayName(toChainId)}`;
59
+ if (kind === 'unwrap')
60
+ return `Unwrap to native on ${chainDisplayName(toChainId)}`;
61
+ return `Transfer on ${chainDisplayName(toChainId)}`;
62
+ };
63
+ /**
64
+ * Normalizes a LI.FI `includedSteps` array into the provider-agnostic
65
+ * `RouteStep[]` shape. Steps with missing or non-hex token addresses are
66
+ * silently dropped rather than causing the whole normalization to fail.
67
+ *
68
+ * @param steps - the raw `includedSteps` value from a LI.FI quote response,
69
+ * or `undefined` when the field was absent (trimmed responses)
70
+ * @returns ordered list of normalized steps; empty when no usable legs are present
71
+ */
72
+ export const normalizeLifiSteps = (steps) => {
73
+ if (!Array.isArray(steps) || steps.length === 0) {
74
+ return [];
75
+ }
76
+ const result = [];
77
+ for (const raw of steps) {
78
+ const step = asRecord(raw);
79
+ if (!step) {
80
+ continue;
81
+ }
82
+ const type = typeof step.type === 'string' ? step.type : 'send';
83
+ const action = asRecord(step.action);
84
+ if (!action) {
85
+ continue;
86
+ }
87
+ const fromChainId = typeof action.fromChainId === 'number' ? action.fromChainId : null;
88
+ const toChainId = typeof action.toChainId === 'number' ? action.toChainId : null;
89
+ if (fromChainId === null || toChainId === null) {
90
+ continue;
91
+ }
92
+ // A step that changes chains IS a bridge, whatever the provider called its
93
+ // type. LI.FI labels most cross-chain legs `"cross"`, but some (e.g. an Across
94
+ // leg, or a `"protocol"`/`"lifi"`-typed leg) would otherwise fall through to
95
+ // the generic `"send"` kind and read as "Transfer on Ethereum" — misleading,
96
+ // since the funds are actually being BRIDGED there. Deciding from the chains
97
+ // rather than the type string keeps the label honest.
98
+ const typeKind = lifiTypeToKind(type);
99
+ const kind = typeKind === 'send' && fromChainId !== toChainId ? 'bridge' : typeKind;
100
+ const fromTokenRecord = asRecord(action.fromToken);
101
+ const toTokenRecord = asRecord(action.toToken);
102
+ const fromToken = isHexString(fromTokenRecord?.address) ? fromTokenRecord.address : null;
103
+ const toToken = isHexString(toTokenRecord?.address) ? toTokenRecord.address : null;
104
+ if (!fromToken || !toToken) {
105
+ continue;
106
+ }
107
+ const fromTokenSymbol = typeof fromTokenRecord?.symbol === 'string' ? fromTokenRecord.symbol : null;
108
+ const toTokenSymbol = typeof toTokenRecord?.symbol === 'string' ? toTokenRecord.symbol : null;
109
+ const fromTokenLogoUri = typeof fromTokenRecord?.logoURI === 'string' ? fromTokenRecord.logoURI : null;
110
+ const toTokenLogoUri = typeof toTokenRecord?.logoURI === 'string' ? toTokenRecord.logoURI : null;
111
+ const toolDetails = asRecord(step.toolDetails);
112
+ const toolName = typeof toolDetails?.name === 'string'
113
+ ? toolDetails.name
114
+ : typeof step.tool === 'string'
115
+ ? step.tool
116
+ : 'unknown';
117
+ // The per-step tool's logo, when the provider supplies one. This is the best
118
+ // source for a real service-provider image on the consuming interface (for
119
+ // example the Uniswap or Stargate logo for the leg the aggregator routes
120
+ // through). Null when absent, so the interface falls back to an initials tile.
121
+ const providerLogoUri = typeof toolDetails?.logoURI === 'string' ? toolDetails.logoURI : null;
122
+ const label = buildLifiLabel(kind, toChainId, toolName);
123
+ const estimate = asRecord(step.estimate);
124
+ const estimatedOut = typeof estimate?.toAmount === 'string' && estimate.toAmount !== ''
125
+ ? (() => {
126
+ try {
127
+ return BigInt(estimate.toAmount);
128
+ }
129
+ catch {
130
+ return null;
131
+ }
132
+ })()
133
+ : null;
134
+ const integrationData = asRecord(step.integrationData);
135
+ const router = isHexString(integrationData?.callToAddress)
136
+ ? integrationData.callToAddress
137
+ : null;
138
+ // Per-step cost = protocol fees + gas, both reported in United States dollars.
139
+ // Null when the provider reported neither, so the UI omits the hop's fee.
140
+ const protocolFeeUsd = sumUsdCosts(estimate?.feeCosts);
141
+ const gasCostUsd = sumUsdCosts(estimate?.gasCosts);
142
+ const stepCostUsd = protocolFeeUsd + gasCostUsd;
143
+ const feeUsd = stepCostUsd > 0 ? stepCostUsd : null;
144
+ // Gas alone, for route ranking (which charges gas on top of the paid-versus-
145
+ // delivered spread and must not double-count the protocol fees already baked
146
+ // into the quote's `fromAmount`). Null when no gas was reported.
147
+ const gasUsd = gasCostUsd > 0 ? gasCostUsd : null;
148
+ result.push({
149
+ kind,
150
+ label,
151
+ fromChainId,
152
+ toChainId,
153
+ fromToken,
154
+ toToken,
155
+ fromTokenSymbol,
156
+ toTokenSymbol,
157
+ fromTokenLogoUri,
158
+ toTokenLogoUri,
159
+ router,
160
+ estimatedOut,
161
+ feeUsd,
162
+ gasUsd,
163
+ providerLogoUri,
164
+ });
165
+ }
166
+ return result;
167
+ };
168
+ /**
169
+ * Parses one `{ currency, amount }` entry from Relay's `details.route`.
170
+ *
171
+ * @param value - a raw `inputCurrency`/`outputCurrency` value
172
+ * @returns the endpoint, or null when the chain id or address is unusable
173
+ */
174
+ const relayRouteEndpoint = (value) => {
175
+ const entry = asRecord(value);
176
+ const currency = asRecord(entry?.currency);
177
+ if (!currency) {
178
+ return null;
179
+ }
180
+ const chainId = typeof currency.chainId === 'number' ? currency.chainId : null;
181
+ const address = typeof currency.address === 'string' ? currency.address : null;
182
+ // Without a chain and an identifier there is no leg to describe. Both are
183
+ // always present in Relay's verified shape; bail rather than invent one.
184
+ if (chainId === null || address === null) {
185
+ return null;
186
+ }
187
+ const metadata = asRecord(currency.metadata);
188
+ let amount = null;
189
+ if (typeof entry?.amount === 'string' && entry.amount !== '') {
190
+ try {
191
+ amount = BigInt(entry.amount);
192
+ }
193
+ catch {
194
+ amount = null;
195
+ }
196
+ }
197
+ return {
198
+ chainId,
199
+ address,
200
+ symbol: typeof currency.symbol === 'string' ? currency.symbol : null,
201
+ logoUri: typeof metadata?.logoURI === 'string' ? metadata.logoURI : null,
202
+ amount,
203
+ };
204
+ };
205
+ /**
206
+ * Reads a `{ amountUsd }` entry from Relay's `fees` object as a number.
207
+ * Relay reports dollar values as numeric strings; anything non-finite
208
+ * contributes nothing rather than poisoning the sum with `NaN`.
209
+ */
210
+ const relayFeeUsd = (fees, key) => {
211
+ const fee = asRecord(fees?.[key]);
212
+ const amountUsd = typeof fee?.amountUsd === 'string' ? Number(fee.amountUsd) : Number.NaN;
213
+ return Number.isFinite(amountUsd) ? amountUsd : 0;
214
+ };
215
+ /** True when two endpoints are the same currency on the same chain. */
216
+ const isSameEndpoint = (left, right) => left.chainId === right.chainId && left.address === right.address;
217
+ /**
218
+ * Normalizes a Relay quote into the provider-agnostic `RouteStep[]` shape.
219
+ *
220
+ * Relay's `steps` array holds only OPERATIONAL steps (`approve` and `deposit`) —
221
+ * what the user signs, not where the funds go. The route itself lives in
222
+ * `details.route`, which describes the journey as an origin leg and a
223
+ * destination leg, each with an input and an output currency. Reading those four
224
+ * endpoints in order and emitting a step between each consecutive pair recovers
225
+ * the real path, so a Bitcoin origin renders as "Bitcoin → United States Dollar
226
+ * Coin on Ethereum" rather than as nothing at all.
227
+ *
228
+ * Consecutive endpoints that name the same currency on the same chain are
229
+ * collapsed, because Relay repeats an endpoint when a leg performs no
230
+ * conversion. A step's kind follows from its chains: crossing chains is a
231
+ * `bridge`, staying put is a `swap`. Relay reports no wrap/unwrap distinction,
232
+ * so none is invented.
233
+ *
234
+ * Costs are route-level in Relay's response, not per leg, so they are attached
235
+ * to the FIRST step — the one the user actually signs and pays at. `gasUsd`
236
+ * carries `fees.gas` alone (the origin transaction's gas, paid on top in the
237
+ * network's native token), while `feeUsd` adds the relayer and application fees
238
+ * on top of it, matching how the LI.FI normalizer splits the two. The relayer
239
+ * fee's `relayerGas`/`relayerService` components are deliberately NOT summed:
240
+ * they add up to `relayer` exactly, so counting them would double the fee.
241
+ *
242
+ * @param quote - a raw Relay quote response body
243
+ * @returns ordered list of normalized steps; empty when no route is present
244
+ */
245
+ export const normalizeRelaySteps = (quote) => {
246
+ const details = asRecord(asRecord(quote)?.details);
247
+ const route = asRecord(details?.route);
248
+ if (!route) {
249
+ return [];
250
+ }
251
+ const origin = asRecord(route.origin);
252
+ const destination = asRecord(route.destination);
253
+ const endpoints = [
254
+ relayRouteEndpoint(origin?.inputCurrency),
255
+ relayRouteEndpoint(origin?.outputCurrency),
256
+ relayRouteEndpoint(destination?.inputCurrency),
257
+ relayRouteEndpoint(destination?.outputCurrency),
258
+ ].filter((endpoint) => endpoint !== null);
259
+ // Collapse repeats so a leg that converts nothing does not become a step.
260
+ const path = endpoints.filter((endpoint, index) => index === 0 || !isSameEndpoint(endpoint, endpoints[index - 1]));
261
+ if (path.length < 2) {
262
+ return [];
263
+ }
264
+ const fees = asRecord(asRecord(quote)?.fees);
265
+ const gasUsd = relayFeeUsd(fees, 'gas');
266
+ // `relayer` is the full relayer charge; its `relayerGas` and `relayerService`
267
+ // components sum to it exactly, so only the total is counted here.
268
+ const totalUsd = gasUsd + relayFeeUsd(fees, 'relayer') + relayFeeUsd(fees, 'app');
269
+ const result = [];
270
+ for (let index = 1; index < path.length; index += 1) {
271
+ const from = path[index - 1];
272
+ const to = path[index];
273
+ const kind = from.chainId === to.chainId ? 'swap' : 'bridge';
274
+ const isFirstStep = index === 1;
275
+ result.push({
276
+ kind,
277
+ label: kind === 'bridge'
278
+ ? `Bridge to ${chainDisplayName(to.chainId)}`
279
+ : `Swap on ${chainDisplayName(to.chainId)}`,
280
+ fromChainId: from.chainId,
281
+ toChainId: to.chainId,
282
+ fromToken: from.address,
283
+ toToken: to.address,
284
+ fromTokenSymbol: from.symbol,
285
+ toTokenSymbol: to.symbol,
286
+ // Relay's currency metadata carries the TOKEN's own image, which is the only
287
+ // one available for a non-Ethereum endpoint (Bitcoin, Solana, Tron, The Open
288
+ // Network) — the image service cannot key those by a hex address.
289
+ fromTokenLogoUri: from.logoUri,
290
+ toTokenLogoUri: to.logoUri,
291
+ // Relay's `router` field on a route leg names the service ("relay"), not a
292
+ // contract address, so there is no router address to surface for the leg.
293
+ router: null,
294
+ // Relay supplies a logo for the currency, not for a per-leg tool, so the
295
+ // consuming interface falls back to the provider tile. Deliberately null
296
+ // rather than passing a token image off as a service-provider image.
297
+ providerLogoUri: null,
298
+ estimatedOut: to.amount,
299
+ // Route-level costs land on the signed step; later legs run inside it.
300
+ feeUsd: isFirstStep && totalUsd > 0 ? totalUsd : null,
301
+ gasUsd: isFirstStep && gasUsd > 0 ? gasUsd : null,
302
+ });
303
+ }
304
+ return result;
305
+ };
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The amount that actually LANDS on PulseChain for an aggregator candidate, net
3
+ * of the omnibridge crossing fee.
4
+ *
5
+ * The aggregator delivers `entryChainAmount` (the consolidated request's
6
+ * `toAmount`) onto the omnibridge entry chain, and the omnibridge entry call
7
+ * relays exactly that — but the omnibridge takes its foreign→home crossing fee on
8
+ * the way to PulseChain, so the user receives LESS than `entryChainAmount`. This
9
+ * nets that crossing fee out using the SDK's OWN fee primitives ({@link bridgeCost}
10
+ * and {@link amountAfterBridgeFee}) — the identical math the direct engine applies
11
+ * — so the displayed received amount is honest rather than overstated.
12
+ *
13
+ * The multiplexed (aggregator) swap fees are NOT subtracted here: the aggregator
14
+ * already quotes the source-chain `fromAmount` it needs to GUARANTEE
15
+ * `entryChainAmount` arrives, so those fees are already reflected in `fromAmount`.
16
+ * Only the omnibridge crossing fee remains to net out, which is this function's
17
+ * sole job.
18
+ *
19
+ * @param entryChainAmount - the amount arriving on the entry chain (request `toAmount`)
20
+ * @param omnibridgeFeePerEther - the omnibridge foreign→home fee rate (per 1e18), or null
21
+ * @returns the net amount delivered on PulseChain; equals `entryChainAmount` when the fee is unknown
22
+ */
23
+ export declare const netDeliveredAmount: (entryChainAmount: bigint, omnibridgeFeePerEther: bigint | null) => bigint;
24
+ /**
25
+ * Restates an amount in a different token's decimals, preserving the QUANTITY.
26
+ *
27
+ * This is the honest degraded answer when {@link valueEquivalentAmount} cannot
28
+ * price a conversion. Losing the price must not also lose the decimals: the two
29
+ * omnibridge anchors hold the same asset bridged two ways, and the only
30
+ * difference between them is where the decimal point sits. Returning an
31
+ * unadjusted amount across that gap is wrong by a factor of 10^12, which is the
32
+ * exact error the value conversion exists to prevent.
33
+ *
34
+ * Scaling DOWN truncates, and must — a six-decimal token cannot hold the twelve
35
+ * extra digits an eighteen-decimal one carries. The lost remainder is smaller
36
+ * than one base unit of the target.
37
+ *
38
+ * @param params.amount - the amount, in `fromDecimals` base units
39
+ * @param params.fromDecimals - the decimals the amount is currently expressed in
40
+ * @param params.toDecimals - the decimals to restate it in
41
+ * @returns the same quantity, in `toDecimals` base units
42
+ */
43
+ export declare const restateInDecimals: ({ amount, fromDecimals, toDecimals, }: {
44
+ amount: bigint;
45
+ fromDecimals: number;
46
+ toDecimals: number;
47
+ }) => bigint;
48
+ /**
49
+ * Converts `amount` of a source token into the base-unit amount of a TARGET token
50
+ * carrying the SAME United States dollar value:
51
+ *
52
+ * valueUsd = amount / 10^fromDecimals × fromPrice
53
+ * targetAmount = valueUsd / toPrice × 10^toDecimals
54
+ *
55
+ * This is what lets the two omnibridge entry points be quoted for the SAME VALUE
56
+ * even when their anchor tokens differ in decimals — a PulseChain-bridged
57
+ * stablecoin is 6 decimals when bridged from Ethereum but 18 from Binance Smart
58
+ * Chain. Feeding one raw integer to both targeted quantities ~10^12 apart, so the
59
+ * routes were not comparable and the higher-decimal entry won on a bigger NUMBER
60
+ * rather than more value. Targeting equal value in each anchor's own decimals
61
+ * restores a fair, decimals-correct comparison.
62
+ *
63
+ * Returns null when either price is missing or non-positive, the scaled value is
64
+ * not finite, or the target cannot be parsed — the caller then has no
65
+ * value-equivalent target and must fall back rather than quote a bogus amount.
66
+ *
67
+ * @param params.amount - the source amount, in `fromDecimals` base units
68
+ * @param params.fromDecimals - the source token's decimals
69
+ * @param params.fromPrice - the source token's per-token United States dollar price, or null
70
+ * @param params.toDecimals - the target token's decimals
71
+ * @param params.toPrice - the target token's per-token United States dollar price, or null
72
+ * @returns the value-equivalent amount in the target token's base units, or null
73
+ */
74
+ export declare const valueEquivalentAmount: ({ amount, fromDecimals, fromPrice, toDecimals, toPrice, }: {
75
+ amount: bigint;
76
+ fromDecimals: number;
77
+ fromPrice: number | null;
78
+ toDecimals: number;
79
+ toPrice: number | null;
80
+ }) => bigint | null;
@@ -0,0 +1,120 @@
1
+ import { formatUnits, parseUnits } from 'viem';
2
+ import { amountAfterBridgeFee, bridgeCost } from '../settings.js';
3
+ /**
4
+ * The amount that actually LANDS on PulseChain for an aggregator candidate, net
5
+ * of the omnibridge crossing fee.
6
+ *
7
+ * The aggregator delivers `entryChainAmount` (the consolidated request's
8
+ * `toAmount`) onto the omnibridge entry chain, and the omnibridge entry call
9
+ * relays exactly that — but the omnibridge takes its foreign→home crossing fee on
10
+ * the way to PulseChain, so the user receives LESS than `entryChainAmount`. This
11
+ * nets that crossing fee out using the SDK's OWN fee primitives ({@link bridgeCost}
12
+ * and {@link amountAfterBridgeFee}) — the identical math the direct engine applies
13
+ * — so the displayed received amount is honest rather than overstated.
14
+ *
15
+ * The multiplexed (aggregator) swap fees are NOT subtracted here: the aggregator
16
+ * already quotes the source-chain `fromAmount` it needs to GUARANTEE
17
+ * `entryChainAmount` arrives, so those fees are already reflected in `fromAmount`.
18
+ * Only the omnibridge crossing fee remains to net out, which is this function's
19
+ * sole job.
20
+ *
21
+ * @param entryChainAmount - the amount arriving on the entry chain (request `toAmount`)
22
+ * @param omnibridgeFeePerEther - the omnibridge foreign→home fee rate (per 1e18), or null
23
+ * @returns the net amount delivered on PulseChain; equals `entryChainAmount` when the fee is unknown
24
+ */
25
+ export const netDeliveredAmount = (entryChainAmount, omnibridgeFeePerEther) => {
26
+ // Defensive: the omnibridge percentage math runs ONLY when both operands are
27
+ // true bigints. A non-bigint fee (a value rehydrated from storage as a string,
28
+ // the project's documented BigInt-serialization gotcha) would throw inside
29
+ // `bridgeCost`'s mixed-type multiplication, so fall back to the un-netted
30
+ // entry-chain amount rather than crash the render. The `typeof` guards intentionally
31
+ // outlive the static `bigint` types because the runtime value can violate them.
32
+ if (typeof entryChainAmount !== 'bigint' || typeof omnibridgeFeePerEther !== 'bigint') {
33
+ return entryChainAmount;
34
+ }
35
+ const cost = bridgeCost({ amountToBridge: entryChainAmount, bridgeFee: omnibridgeFeePerEther });
36
+ const net = amountAfterBridgeFee({ amountToBridge: entryChainAmount, bridgeCost: cost });
37
+ return net ?? entryChainAmount;
38
+ };
39
+ /**
40
+ * Restates an amount in a different token's decimals, preserving the QUANTITY.
41
+ *
42
+ * This is the honest degraded answer when {@link valueEquivalentAmount} cannot
43
+ * price a conversion. Losing the price must not also lose the decimals: the two
44
+ * omnibridge anchors hold the same asset bridged two ways, and the only
45
+ * difference between them is where the decimal point sits. Returning an
46
+ * unadjusted amount across that gap is wrong by a factor of 10^12, which is the
47
+ * exact error the value conversion exists to prevent.
48
+ *
49
+ * Scaling DOWN truncates, and must — a six-decimal token cannot hold the twelve
50
+ * extra digits an eighteen-decimal one carries. The lost remainder is smaller
51
+ * than one base unit of the target.
52
+ *
53
+ * @param params.amount - the amount, in `fromDecimals` base units
54
+ * @param params.fromDecimals - the decimals the amount is currently expressed in
55
+ * @param params.toDecimals - the decimals to restate it in
56
+ * @returns the same quantity, in `toDecimals` base units
57
+ */
58
+ export const restateInDecimals = ({ amount, fromDecimals, toDecimals, }) => {
59
+ if (fromDecimals === toDecimals)
60
+ return amount;
61
+ if (toDecimals > fromDecimals)
62
+ return amount * 10n ** BigInt(toDecimals - fromDecimals);
63
+ return amount / 10n ** BigInt(fromDecimals - toDecimals);
64
+ };
65
+ /**
66
+ * Converts `amount` of a source token into the base-unit amount of a TARGET token
67
+ * carrying the SAME United States dollar value:
68
+ *
69
+ * valueUsd = amount / 10^fromDecimals × fromPrice
70
+ * targetAmount = valueUsd / toPrice × 10^toDecimals
71
+ *
72
+ * This is what lets the two omnibridge entry points be quoted for the SAME VALUE
73
+ * even when their anchor tokens differ in decimals — a PulseChain-bridged
74
+ * stablecoin is 6 decimals when bridged from Ethereum but 18 from Binance Smart
75
+ * Chain. Feeding one raw integer to both targeted quantities ~10^12 apart, so the
76
+ * routes were not comparable and the higher-decimal entry won on a bigger NUMBER
77
+ * rather than more value. Targeting equal value in each anchor's own decimals
78
+ * restores a fair, decimals-correct comparison.
79
+ *
80
+ * Returns null when either price is missing or non-positive, the scaled value is
81
+ * not finite, or the target cannot be parsed — the caller then has no
82
+ * value-equivalent target and must fall back rather than quote a bogus amount.
83
+ *
84
+ * @param params.amount - the source amount, in `fromDecimals` base units
85
+ * @param params.fromDecimals - the source token's decimals
86
+ * @param params.fromPrice - the source token's per-token United States dollar price, or null
87
+ * @param params.toDecimals - the target token's decimals
88
+ * @param params.toPrice - the target token's per-token United States dollar price, or null
89
+ * @returns the value-equivalent amount in the target token's base units, or null
90
+ */
91
+ export const valueEquivalentAmount = ({ amount, fromDecimals, fromPrice, toDecimals, toPrice, }) => {
92
+ if (fromPrice === null || toPrice === null)
93
+ return null;
94
+ if (!(fromPrice > 0) || !(toPrice > 0))
95
+ return null;
96
+ // Equal prices make this a pure change of decimals, and integer rescaling is
97
+ // EXACT where the double round trip is not. `39.3` is not representable as a
98
+ // double, so `toFixed(18)` writes 39.299999999999997158 and the conversion
99
+ // comes back 2842 base units short of the 39.3 that went in. At the same
100
+ // decimals it lands one unit out (0.008802 returns as 8802000000000001).
101
+ //
102
+ // Both are negligible in value and neither is a reason to convert at all: with
103
+ // one price on both sides there is no value question to answer, only a decimal
104
+ // point to move. Moving it in integers keeps the same input producing the same
105
+ // target.
106
+ if (fromPrice === toPrice)
107
+ return restateInDecimals({ amount, fromDecimals, toDecimals });
108
+ const valueUsd = Number(formatUnits(amount, fromDecimals)) * fromPrice;
109
+ if (!Number.isFinite(valueUsd))
110
+ return null;
111
+ const targetWhole = valueUsd / toPrice;
112
+ if (!Number.isFinite(targetWhole) || targetWhole < 0)
113
+ return null;
114
+ try {
115
+ return parseUnits(targetWhole.toFixed(toDecimals), toDecimals);
116
+ }
117
+ catch {
118
+ return null;
119
+ }
120
+ };