@gibs/bridge-sdk 1.18.0 → 1.20.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 (120) hide show
  1. package/README.md +33 -0
  2. package/dist/abis.js +0 -2
  3. package/dist/bridge-limits.js +0 -38
  4. package/dist/chain-info.js +0 -116
  5. package/dist/chain-names.js +0 -151
  6. package/dist/chains.js +0 -198
  7. package/dist/cjs/abis.js +0 -2
  8. package/dist/cjs/bridge-limits.js +0 -38
  9. package/dist/cjs/chain-info.js +0 -116
  10. package/dist/cjs/chain-names.js +0 -151
  11. package/dist/cjs/chains.js +0 -198
  12. package/dist/cjs/claims.js +0 -60
  13. package/dist/cjs/config.js +1 -208
  14. package/dist/cjs/consolidated-quote.js +2 -464
  15. package/dist/cjs/destination-calldata.js +0 -71
  16. package/dist/cjs/ecosystems.js +0 -183
  17. package/dist/cjs/execution.js +0 -92
  18. package/dist/cjs/fee-type.js +0 -1
  19. package/dist/cjs/hyperliquid-execution.js +0 -98
  20. package/dist/cjs/image-links.js +0 -108
  21. package/dist/cjs/lifi-execution-guard.js +65 -172
  22. package/dist/cjs/near-intents-conformance.js +0 -110
  23. package/dist/cjs/near-intents-route.d.ts +13 -0
  24. package/dist/cjs/near-intents-route.js +7 -140
  25. package/dist/cjs/near-intents.js +0 -562
  26. package/dist/cjs/origin-deposit.js +0 -56
  27. package/dist/cjs/pre-quote.js +0 -80
  28. package/dist/cjs/prices.js +0 -113
  29. package/dist/cjs/providers.js +0 -83
  30. package/dist/cjs/quote-fees.js +0 -80
  31. package/dist/cjs/referral.js +0 -75
  32. package/dist/cjs/relay-currencies.js +0 -31
  33. package/dist/cjs/relay-depository.js +52 -77
  34. package/dist/cjs/relay-quote.js +0 -394
  35. package/dist/cjs/route-steps.js +0 -100
  36. package/dist/cjs/routing/amounts.js +0 -83
  37. package/dist/cjs/routing/candidate-route.d.ts +46 -1
  38. package/dist/cjs/routing/candidate-route.js +12 -40
  39. package/dist/cjs/routing/candidate.js +0 -46
  40. package/dist/cjs/routing/carrier-provider-key.js +0 -27
  41. package/dist/cjs/routing/carriers/omnibridge.js +0 -120
  42. package/dist/cjs/routing/carriers/pulsex-paths.js +0 -57
  43. package/dist/cjs/routing/carriers/pulsex-split.js +0 -92
  44. package/dist/cjs/routing/carriers/pulsex.js +0 -279
  45. package/dist/cjs/routing/carriers/wrap.js +0 -57
  46. package/dist/cjs/routing/composed-route-quality.js +0 -95
  47. package/dist/cjs/routing/entry-chain-anchor.js +0 -27
  48. package/dist/cjs/routing/format.js +0 -50
  49. package/dist/cjs/routing/index.d.ts +1 -1
  50. package/dist/cjs/routing/index.js +3 -30
  51. package/dist/cjs/routing/producers.js +0 -854
  52. package/dist/cjs/routing/providers.js +0 -27
  53. package/dist/cjs/routing/search/compose.js +0 -56
  54. package/dist/cjs/routing/search/graph.js +0 -364
  55. package/dist/cjs/routing/search/plan-route-data.js +5 -510
  56. package/dist/cjs/routing/search/score.js +0 -344
  57. package/dist/cjs/routing/swap-transactions.js +0 -74
  58. package/dist/cjs/routing/usd-value.js +0 -12
  59. package/dist/cjs/settings.js +1 -456
  60. package/dist/cjs/swap-venues.js +0 -17
  61. package/dist/cjs/ton-connect.js +0 -82
  62. package/dist/cjs/ton-execution.js +0 -6
  63. package/dist/cjs/ton.js +0 -42
  64. package/dist/cjs/tron-execution.js +0 -145
  65. package/dist/cjs/types.d.ts +8 -0
  66. package/dist/claims.js +0 -60
  67. package/dist/config.js +1 -211
  68. package/dist/consolidated-quote.js +2 -464
  69. package/dist/destination-calldata.js +0 -71
  70. package/dist/ecosystems.js +0 -183
  71. package/dist/execution.js +0 -92
  72. package/dist/fee-type.js +0 -2
  73. package/dist/hyperliquid-execution.js +0 -98
  74. package/dist/image-links.js +0 -108
  75. package/dist/lifi-execution-guard.js +65 -172
  76. package/dist/near-intents-conformance.js +0 -110
  77. package/dist/near-intents-route.d.ts +13 -0
  78. package/dist/near-intents-route.js +5 -139
  79. package/dist/near-intents.js +0 -562
  80. package/dist/origin-deposit.js +0 -56
  81. package/dist/pre-quote.js +0 -80
  82. package/dist/prices.js +0 -113
  83. package/dist/providers.js +0 -83
  84. package/dist/quote-fees.js +0 -80
  85. package/dist/referral.js +0 -75
  86. package/dist/relay-currencies.js +0 -31
  87. package/dist/relay-depository.js +52 -77
  88. package/dist/relay-quote.js +0 -394
  89. package/dist/route-steps.js +0 -100
  90. package/dist/routing/amounts.js +0 -83
  91. package/dist/routing/candidate-route.d.ts +46 -1
  92. package/dist/routing/candidate-route.js +10 -39
  93. package/dist/routing/candidate.js +0 -46
  94. package/dist/routing/carrier-provider-key.js +0 -27
  95. package/dist/routing/carriers/omnibridge.js +0 -120
  96. package/dist/routing/carriers/pulsex-paths.js +0 -57
  97. package/dist/routing/carriers/pulsex-split.js +0 -92
  98. package/dist/routing/carriers/pulsex.js +0 -279
  99. package/dist/routing/carriers/wrap.js +0 -57
  100. package/dist/routing/composed-route-quality.js +0 -95
  101. package/dist/routing/entry-chain-anchor.js +0 -27
  102. package/dist/routing/format.js +0 -50
  103. package/dist/routing/index.d.ts +1 -1
  104. package/dist/routing/index.js +1 -29
  105. package/dist/routing/producers.js +0 -854
  106. package/dist/routing/providers.js +0 -27
  107. package/dist/routing/search/compose.js +0 -56
  108. package/dist/routing/search/graph.js +0 -364
  109. package/dist/routing/search/plan-route-data.js +6 -511
  110. package/dist/routing/search/score.js +0 -344
  111. package/dist/routing/swap-transactions.js +0 -74
  112. package/dist/routing/usd-value.js +0 -12
  113. package/dist/settings.js +1 -456
  114. package/dist/swap-venues.js +0 -17
  115. package/dist/ton-connect.js +0 -82
  116. package/dist/ton-execution.js +0 -6
  117. package/dist/ton.js +0 -42
  118. package/dist/tron-execution.js +0 -145
  119. package/dist/types.d.ts +8 -0
  120. package/package.json +3 -3
@@ -1,14 +1,5 @@
1
1
  import { chainDisplayName } from './chain-names.js';
2
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
3
  const sumUsdCosts = (costs) => {
13
4
  if (!Array.isArray(costs)) {
14
5
  return 0;
@@ -25,13 +16,6 @@ const asRecord = (value) => {
25
16
  }
26
17
  return value;
27
18
  };
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
19
  const lifiTypeToKind = (type) => {
36
20
  if (type === 'cross')
37
21
  return 'bridge';
@@ -43,12 +27,6 @@ const lifiTypeToKind = (type) => {
43
27
  return 'unwrap';
44
28
  return 'send';
45
29
  };
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
30
  const buildLifiLabel = (kind, toChainId, toolName) => {
53
31
  if (kind === 'bridge')
54
32
  return `Bridge to ${chainDisplayName(toChainId)}`;
@@ -60,15 +38,6 @@ const buildLifiLabel = (kind, toChainId, toolName) => {
60
38
  return `Unwrap to native on ${chainDisplayName(toChainId)}`;
61
39
  return `Transfer on ${chainDisplayName(toChainId)}`;
62
40
  };
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
41
  export const normalizeLifiSteps = (steps) => {
73
42
  if (!Array.isArray(steps) || steps.length === 0) {
74
43
  return [];
@@ -89,12 +58,6 @@ export const normalizeLifiSteps = (steps) => {
89
58
  if (fromChainId === null || toChainId === null) {
90
59
  continue;
91
60
  }
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
61
  const typeKind = lifiTypeToKind(type);
99
62
  const kind = typeKind === 'send' && fromChainId !== toChainId ? 'bridge' : typeKind;
100
63
  const fromTokenRecord = asRecord(action.fromToken);
@@ -114,10 +77,6 @@ export const normalizeLifiSteps = (steps) => {
114
77
  : typeof step.tool === 'string'
115
78
  ? step.tool
116
79
  : '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
80
  const providerLogoUri = typeof toolDetails?.logoURI === 'string' ? toolDetails.logoURI : null;
122
81
  const label = buildLifiLabel(kind, toChainId, toolName);
123
82
  const estimate = asRecord(step.estimate);
@@ -135,15 +94,10 @@ export const normalizeLifiSteps = (steps) => {
135
94
  const router = isHexString(integrationData?.callToAddress)
136
95
  ? integrationData.callToAddress
137
96
  : 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
97
  const protocolFeeUsd = sumUsdCosts(estimate?.feeCosts);
141
98
  const gasCostUsd = sumUsdCosts(estimate?.gasCosts);
142
99
  const stepCostUsd = protocolFeeUsd + gasCostUsd;
143
100
  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
101
  const gasUsd = gasCostUsd > 0 ? gasCostUsd : null;
148
102
  result.push({
149
103
  kind,
@@ -165,12 +119,6 @@ export const normalizeLifiSteps = (steps) => {
165
119
  }
166
120
  return result;
167
121
  };
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
122
  const relayRouteEndpoint = (value) => {
175
123
  const entry = asRecord(value);
176
124
  const currency = asRecord(entry?.currency);
@@ -179,8 +127,6 @@ const relayRouteEndpoint = (value) => {
179
127
  }
180
128
  const chainId = typeof currency.chainId === 'number' ? currency.chainId : null;
181
129
  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
130
  if (chainId === null || address === null) {
185
131
  return null;
186
132
  }
@@ -202,46 +148,12 @@ const relayRouteEndpoint = (value) => {
202
148
  amount,
203
149
  };
204
150
  };
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
151
  const relayFeeUsd = (fees, key) => {
211
152
  const fee = asRecord(fees?.[key]);
212
153
  const amountUsd = typeof fee?.amountUsd === 'string' ? Number(fee.amountUsd) : Number.NaN;
213
154
  return Number.isFinite(amountUsd) ? amountUsd : 0;
214
155
  };
215
- /** True when two endpoints are the same currency on the same chain. */
216
156
  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
157
  export const normalizeRelaySteps = (quote) => {
246
158
  const details = asRecord(asRecord(quote)?.details);
247
159
  const route = asRecord(details?.route);
@@ -256,15 +168,12 @@ export const normalizeRelaySteps = (quote) => {
256
168
  relayRouteEndpoint(destination?.inputCurrency),
257
169
  relayRouteEndpoint(destination?.outputCurrency),
258
170
  ].filter((endpoint) => endpoint !== null);
259
- // Collapse repeats so a leg that converts nothing does not become a step.
260
171
  const path = endpoints.filter((endpoint, index) => index === 0 || !isSameEndpoint(endpoint, endpoints[index - 1]));
261
172
  if (path.length < 2) {
262
173
  return [];
263
174
  }
264
175
  const fees = asRecord(asRecord(quote)?.fees);
265
176
  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
177
  const totalUsd = gasUsd + relayFeeUsd(fees, 'relayer') + relayFeeUsd(fees, 'app');
269
178
  const result = [];
270
179
  for (let index = 1; index < path.length; index += 1) {
@@ -283,20 +192,11 @@ export const normalizeRelaySteps = (quote) => {
283
192
  toToken: to.address,
284
193
  fromTokenSymbol: from.symbol,
285
194
  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
195
  fromTokenLogoUri: from.logoUri,
290
196
  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
197
  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
198
  providerLogoUri: null,
298
199
  estimatedOut: to.amount,
299
- // Route-level costs land on the signed step; later legs run inside it.
300
200
  feeUsd: isFirstStep && totalUsd > 0 ? totalUsd : null,
301
201
  gasUsd: isFirstStep && gasUsd > 0 ? gasUsd : null,
302
202
  });
@@ -1,34 +1,6 @@
1
1
  import { formatUnits, parseUnits } from 'viem';
2
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
3
  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
4
  if (typeof entryChainAmount !== 'bigint' || typeof omnibridgeFeePerEther !== 'bigint') {
33
5
  return entryChainAmount;
34
6
  }
@@ -36,25 +8,6 @@ export const netDeliveredAmount = (entryChainAmount, omnibridgeFeePerEther) => {
36
8
  const net = amountAfterBridgeFee({ amountToBridge: entryChainAmount, bridgeCost: cost });
37
9
  return net ?? entryChainAmount;
38
10
  };
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
11
  export const restateInDecimals = ({ amount, fromDecimals, toDecimals, }) => {
59
12
  if (fromDecimals === toDecimals)
60
13
  return amount;
@@ -62,47 +15,11 @@ export const restateInDecimals = ({ amount, fromDecimals, toDecimals, }) => {
62
15
  return amount * 10n ** BigInt(toDecimals - fromDecimals);
63
16
  return amount / 10n ** BigInt(fromDecimals - toDecimals);
64
17
  };
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
18
  export const valueEquivalentAmount = ({ amount, fromDecimals, fromPrice, toDecimals, toPrice, }) => {
92
19
  if (fromPrice === null || toPrice === null)
93
20
  return null;
94
21
  if (!(fromPrice > 0) || !(toPrice > 0))
95
22
  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
23
  if (fromPrice === toPrice)
107
24
  return restateInDecimals({ amount, fromDecimals, toDecimals });
108
25
  const valueUsd = Number(formatUnits(amount, fromDecimals)) * fromPrice;
@@ -1,3 +1,4 @@
1
+ import { type CandidateExecutionInputs } from '../execution.js';
1
2
  import type { RouteCandidate } from './candidate.js';
2
3
  import type { RouteData } from './route-data.js';
3
4
  /**
@@ -37,7 +38,51 @@ export declare const buildRouteDataForCandidate: (candidate: RouteCandidate) =>
37
38
  * site, so a surface that warns about a long journey and a surface that ranks
38
39
  * routes cannot come to different counts.
39
40
  *
41
+ * THIS COUNT LEAVES OUT APPROVALS. A token that is not the chain's native coin
42
+ * needs an `approve` transaction before its first section when the allowance
43
+ * is too small, and that approval is a signature too. Raw route data does not
44
+ * say who the spender is or what the account has approved already, so this
45
+ * function cannot count it. Use {@link routeSignatureCountForAccount} for the
46
+ * number the wallet will really ask for.
47
+ *
40
48
  * @param route - the raw route data
41
- * @returns the number of signatures, one per section
49
+ * @returns the number of signatures, one per section, with no approvals
42
50
  */
43
51
  export declare const routeSignatureCount: (route: RouteData) => number;
52
+ /** What {@link routeSignatureCountForAccount} reports. */
53
+ export type RouteSignatureCount = {
54
+ /** Signatures for the sections themselves: one per planned section. */
55
+ readonly signatures: number;
56
+ /** Approval transactions the account must sign first, given what it has approved already. */
57
+ readonly approvals: number;
58
+ /** `signatures + approvals`: every wallet prompt the planned sections cause. */
59
+ readonly total: number;
60
+ };
61
+ /**
62
+ * How many wallet signatures a route asks one account for, approvals included.
63
+ *
64
+ * {@link routeSignatureCount} counts sections only, so a USDC route reads "1"
65
+ * and then asks for two prompts: approve, then send. This function plans the
66
+ * sections with {@link planCandidateExecution} and counts what that plan
67
+ * contains, so it uses the same allowance rule and the same spender the
68
+ * execution planner uses. A native input needs no approval. An allowance that
69
+ * already covers the amount needs none. A smaller, non-zero allowance counts
70
+ * the reset and the new approval, the two transactions the planner emits. The
71
+ * spender is whatever the planner picks: the omnibridge mediator, a swap
72
+ * venue's router (including routers passed in `swapVenues`), or the
73
+ * aggregator's approval address.
74
+ *
75
+ * Pass every section of the route to count the whole route. Pass fewer to
76
+ * count only the window you plan now; each section is counted against the
77
+ * allowance on chain today, so a later section that spends a token an earlier
78
+ * section delivers is counted as if the approval is still needed.
79
+ *
80
+ * An aggregator section needs its `aggregator` inputs, a quote and the request
81
+ * it answered, exactly as {@link planCandidateExecution} does. Without them
82
+ * this function throws, because the approval spender comes from that quote.
83
+ *
84
+ * @param inputs - the same inputs {@link planCandidateExecution} takes
85
+ * @returns the signatures, approvals, and their total
86
+ * @throws when a section cannot be planned; the message names the section and the reason
87
+ */
88
+ export declare const routeSignatureCountForAccount: (inputs: CandidateExecutionInputs) => Promise<RouteSignatureCount>;
@@ -1,45 +1,16 @@
1
1
  import { buildNearIntentsTwoStepRouteData } from '../near-intents-route.js';
2
+ import { planCandidateExecution } from '../execution.js';
2
3
  import { buildCandidateRouteData } from './producers.js';
3
- /**
4
- * One door in front of the two base route builders.
5
- *
6
- * WHY THIS EXISTS. A candidate is drawn by one of two builders and the choice
7
- * is not the caller's to make by hand. {@link buildCandidateRouteData} draws a
8
- * candidate as ONE signature, because the aggregator's executor performs the
9
- * omnibridge entry call on arrival. {@link buildNearIntentsTwoStepRouteData}
10
- * draws it as TWO, because the aggregator that carried it cannot execute
11
- * anything on the chain it delivers to, so the reader signs the crossing
12
- * themselves.
13
- *
14
- * Every composer that builds on top of a candidate — the PulseX conversion
15
- * route is the first — has to pick between them, and picking wrong is not a
16
- * cosmetic error. A two-signature candidate run through the one-signature
17
- * builder produces a route that claims the crossing happens inside the
18
- * transaction the reader already signed. It does not. The reader would select
19
- * that route, sign once, and be left holding the anchor token on the entry
20
- * chain with the interface reporting the journey finished.
21
- *
22
- * `candidate.deferredCrossing` is the fact that decides it, and it is the same
23
- * field {@link buildNearIntentsTwoStepRouteData} refuses to build without. So
24
- * the two agree by construction rather than by two callers remembering the
25
- * same rule.
26
- *
27
- * @param candidate - the priced route candidate
28
- * @returns the raw route data, or null when the candidate names a deferred
29
- * crossing but no omnibridge provider to perform it
30
- */
31
4
  export const buildRouteDataForCandidate = (candidate) => candidate.deferredCrossing
32
5
  ? buildNearIntentsTwoStepRouteData(candidate)
33
6
  : buildCandidateRouteData(candidate);
34
- /**
35
- * How many wallet signatures a raw route asks the reader for.
36
- *
37
- * Counts the sections, because one section is one signature — that is what a
38
- * section means. Named rather than left as `sections.length` at each call
39
- * site, so a surface that warns about a long journey and a surface that ranks
40
- * routes cannot come to different counts.
41
- *
42
- * @param route - the raw route data
43
- * @returns the number of signatures, one per section
44
- */
45
7
  export const routeSignatureCount = (route) => route.sections.length;
8
+ export const routeSignatureCountForAccount = async (inputs) => {
9
+ const plan = await planCandidateExecution(inputs);
10
+ if (plan.kind === 'refused') {
11
+ throw new Error(`Cannot count signatures: section ${plan.sectionIndex} cannot be planned (${JSON.stringify(plan.refusal)}).`);
12
+ }
13
+ const approvals = plan.transactions.filter((transaction) => transaction.kind === 'approve').length;
14
+ const signatures = plan.transactions.length - approvals;
15
+ return { signatures, approvals, total: plan.transactions.length };
16
+ };
@@ -1,39 +1,6 @@
1
1
  import { formatUnits } from 'viem';
2
2
  import { netDeliveredAmount } from './amounts.js';
3
3
  import { omnibridgeProviderForEntryChain } from './providers.js';
4
- /**
5
- * Synthesizes a {@link RouteCandidate} for the SAME-CHAIN direct-omnibridge
6
- * conversion case: when the origin chain IS one of the omnibridge entry chains
7
- * (Ethereum chain 1 or Binance Smart Chain chain 56), there is no aggregator leg
8
- * to quote — the user bridges directly from that chain to PulseChain. The
9
- * same-chain skip in {@link useAggregatorRouteCandidate} is correct (nothing to
10
- * quote), but it left the conversion path empty. This helper fills that gap by
11
- * synthesizing a zero-step candidate. The user interface no longer calls this:
12
- * the route engine serves a conversion now. The export stays as published
13
- * interface until a separate decision removes it.
14
- *
15
- * The synthetic quote carries zero steps (no aggregator hops), a zero execution
16
- * duration (the direct crossing needs no quote wait), and a null transaction
17
- * request — this candidate is DISPLAY and RANKING only. The candidate's
18
- * `deliveredAmount` is net of the omnibridge crossing fee, identical to the math
19
- * {@link useAggregatorRouteCandidate} applies.
20
- *
21
- * This is a PURE exported helper (no hooks, no side effects) so it is
22
- * unit-testable in isolation and can be called from two `useMemo` blocks in
23
- * Bridge.tsx without duplicating the object literal.
24
- *
25
- * EXECUTION: when the route built from this candidate is selected, the
26
- * consolidated (aggregator) execute path is deliberately NOT used — there is no
27
- * aggregator leg for it to sign. `executesViaConsolidated`
28
- * (`packages/ui/src/lib/state/bridgeExecution.ts`) recognizes the candidate's
29
- * `entryChainId === originChainId` and routes the selection through the direct
30
- * omnibridge sender instead, which builds its transaction from the live
31
- * `useBridgeSettings` cascade rather than from this synthetic quote.
32
- *
33
- * @param inputs - the entry chain, origin/destination tokens, amount, and fee rate
34
- * @returns the synthesized RouteCandidate, or throws when the entry chain is not
35
- * a valid omnibridge entry point (caller must guard on `omnibridgeProviderForEntryChain`)
36
- */
37
4
  export const buildDirectCrossingCandidate = ({ entryChainId, originChainId, originToken, destinationToken, amountIn, originUsdPrice, destinationUsdPrice, omnibridgeFeePerEther, }) => {
38
5
  const provider = omnibridgeProviderForEntryChain(entryChainId);
39
6
  if (provider === null) {
@@ -49,13 +16,6 @@ export const buildDirectCrossingCandidate = ({ entryChainId, originChainId, orig
49
16
  : null;
50
17
  const quoteId = `direct-omnibridge-${entryChainId}-${originChainId}`;
51
18
  const syntheticQuote = {
52
- // The synthetic quote wraps the omnibridge provider slug. ConsolidatedQuote's
53
- // `provider` field is typed for the aggregator providers ('lifi' | 'relay'),
54
- // but the functions that read it (`resolveProviderDisplayName`,
55
- // `resolveProviderLogoUri`) accept any string at runtime. The cast is safe here
56
- // because no aggregator-specific code path runs on a zero-step candidate —
57
- // this synthetic quote is never itself signed; see the module doc comment
58
- // above for where execution actually happens for the route built from it.
59
19
  provider: provider,
60
20
  quoteId,
61
21
  tool: provider,
@@ -65,9 +25,6 @@ export const buildDirectCrossingCandidate = ({ entryChainId, originChainId, orig
65
25
  approvalAddress: null,
66
26
  executionDurationSeconds: 0,
67
27
  steps: [],
68
- // A direct crossing carries no aggregator, so there is no carrier fee to
69
- // report. The omnibridge's own crossing fee is `omnibridgeFeeAmount`
70
- // beside it, which the route surfaces separately.
71
28
  fees: [],
72
29
  originDeposit: {
73
30
  ecosystem: 'evm',
@@ -88,9 +45,6 @@ export const buildDirectCrossingCandidate = ({ entryChainId, originChainId, orig
88
45
  deliveredAmount: delivered,
89
46
  omnibridgeFeeAmount,
90
47
  destinationUsdPrice,
91
- // A same-chain crossing has no aggregator leg to solve backward, so it spends
92
- // exactly what was typed — requested and spent are the same figure by
93
- // construction, and the route reports nothing unspent.
94
48
  requestedAmount: amountIn,
95
49
  };
96
50
  };
@@ -1,37 +1,10 @@
1
1
  import { Providers } from '../config.js';
2
2
  import { providerRegistry } from '../providers.js';
3
- /**
4
- * The carrier id of the native-coin wrap and unwrap step
5
- * (`routing/carriers/wrap.ts`).
6
- */
7
3
  export const WRAP_CARRIER_ID = 'wrap';
8
- /**
9
- * Every engine carrier id that IS a provider, mapped to its {@link ProviderKey}.
10
- * The omnibridge crossings carry the provider value itself; the aggregators
11
- * carry their registry id; the PulseChain exchange carries its own literal.
12
- */
13
4
  const providerKeyByCarrierId = new Map([
14
5
  ...Object.values(Providers).map((provider) => [provider, provider]),
15
6
  ...providerRegistry.map((descriptor) => [descriptor.id, descriptor.id]),
16
7
  ['PulseX', 'PulseX'],
17
8
  ]);
18
- /**
19
- * The one place an engine carrier id becomes a {@link ProviderKey}.
20
- *
21
- * AN ENGINE CARRIER ID IS NOT A PROVIDER KEY. The engine is a plug-in host: a
22
- * carrier id is any string, and one of them, `'wrap'`, names a step that no
23
- * provider performs. Handing a raw id to the logo, glyph, or name tables drew a
24
- * blank mark and logged an error. Route data resolves the key here, once, and
25
- * every display site reads the resolved key.
26
- *
27
- * @param carrierId - the `carrier` of a planned hop's edge
28
- * @returns the provider key, or null when no registered provider carries that id
29
- */
30
9
  export const carrierProviderKey = (carrierId) => providerKeyByCarrierId.get(carrierId) ?? null;
31
- /**
32
- * The kind of provider-less step a carrier id performs.
33
- *
34
- * @param carrierId - the `carrier` of a planned hop's edge
35
- * @returns the step kind, or null when the carrier is a provider or unknown
36
- */
37
10
  export const carrierStepKind = (carrierId) => carrierId === WRAP_CARRIER_ID ? 'wrap' : null;