@gibs/bridge-sdk 1.17.0 → 1.19.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 (56) hide show
  1. package/README.md +85 -1
  2. package/dist/cjs/config.d.ts +18 -3
  3. package/dist/cjs/config.js +49 -17
  4. package/dist/cjs/consolidated-quote.js +3 -0
  5. package/dist/cjs/execution.d.ts +17 -4
  6. package/dist/cjs/execution.js +40 -13
  7. package/dist/cjs/index.d.ts +1 -0
  8. package/dist/cjs/index.js +1 -0
  9. package/dist/cjs/near-intents-route.d.ts +13 -0
  10. package/dist/cjs/near-intents-route.js +19 -3
  11. package/dist/cjs/prices.d.ts +10 -0
  12. package/dist/cjs/prices.js +54 -2
  13. package/dist/cjs/routing/candidate-route.d.ts +46 -1
  14. package/dist/cjs/routing/candidate-route.js +47 -2
  15. package/dist/cjs/routing/carrier-provider-key.d.ts +31 -0
  16. package/dist/cjs/routing/carrier-provider-key.js +42 -0
  17. package/dist/cjs/routing/carriers/pulsex.d.ts +50 -25
  18. package/dist/cjs/routing/carriers/pulsex.js +135 -85
  19. package/dist/cjs/routing/index.d.ts +7 -3
  20. package/dist/cjs/routing/index.js +11 -2
  21. package/dist/cjs/routing/route-data.d.ts +47 -5
  22. package/dist/cjs/routing/search/plan-route-data.d.ts +17 -1
  23. package/dist/cjs/routing/search/plan-route-data.js +39 -13
  24. package/dist/cjs/routing/swap-transactions.d.ts +7 -3
  25. package/dist/cjs/routing/swap-transactions.js +6 -4
  26. package/dist/cjs/swap-venues.d.ts +43 -0
  27. package/dist/cjs/swap-venues.js +29 -0
  28. package/dist/cjs/types.d.ts +8 -0
  29. package/dist/config.d.ts +18 -3
  30. package/dist/config.js +48 -16
  31. package/dist/consolidated-quote.js +3 -0
  32. package/dist/execution.d.ts +17 -4
  33. package/dist/execution.js +41 -14
  34. package/dist/index.d.ts +1 -0
  35. package/dist/index.js +1 -0
  36. package/dist/near-intents-route.d.ts +13 -0
  37. package/dist/near-intents-route.js +17 -2
  38. package/dist/prices.d.ts +10 -0
  39. package/dist/prices.js +52 -1
  40. package/dist/routing/candidate-route.d.ts +46 -1
  41. package/dist/routing/candidate-route.js +45 -1
  42. package/dist/routing/carrier-provider-key.d.ts +31 -0
  43. package/dist/routing/carrier-provider-key.js +37 -0
  44. package/dist/routing/carriers/pulsex.d.ts +50 -25
  45. package/dist/routing/carriers/pulsex.js +133 -84
  46. package/dist/routing/index.d.ts +7 -3
  47. package/dist/routing/index.js +4 -2
  48. package/dist/routing/route-data.d.ts +47 -5
  49. package/dist/routing/search/plan-route-data.d.ts +17 -1
  50. package/dist/routing/search/plan-route-data.js +40 -14
  51. package/dist/routing/swap-transactions.d.ts +7 -3
  52. package/dist/routing/swap-transactions.js +7 -5
  53. package/dist/swap-venues.d.ts +43 -0
  54. package/dist/swap-venues.js +24 -0
  55. package/dist/types.d.ts +8 -0
  56. package/package.json +3 -3
@@ -3,7 +3,7 @@ import type { Provider } from '../../config.js';
3
3
  import type { QuoteFee } from '../../quote-fees.js';
4
4
  import type { Token } from '../../types.js';
5
5
  import type { DeferredCrossing, RouteCandidate } from '../candidate.js';
6
- import type { RouteData, RouteKind } from '../route-data.js';
6
+ import type { RouteData, RouteGasByChain, RouteKind } from '../route-data.js';
7
7
  import type { RouteScoreInputs } from './score.js';
8
8
  import { type AssetNode, type RoutePlan } from './graph.js';
9
9
  /**
@@ -141,6 +141,16 @@ export type PlanSectionPricing = {
141
141
  * caller passes shares that add up to `feeUsdValue`, or nothing.
142
142
  */
143
143
  hopFeeUsdValues?: readonly (number | null)[];
144
+ /**
145
+ * The gas split of `feeUsdValue`, from a producer that prices network gas
146
+ * apart: `feeExcludingGasUsdValue` is the cost without gas, `gasUsdValue` the
147
+ * signature's gas (null when unknown). `hopFeeExcludingGasUsdValues` splits
148
+ * the first the way `hopFeeUsdValues` splits the all-in figure. The gas sits
149
+ * on the section's first hop.
150
+ */
151
+ feeExcludingGasUsdValue?: number | null;
152
+ gasUsdValue?: number | null;
153
+ hopFeeExcludingGasUsdValues?: readonly (number | null)[];
144
154
  /** The itemized charges behind `feeUsdValue`, passed straight through to `RouteSectionData.fees`. */
145
155
  fees?: readonly QuoteFee[];
146
156
  /** Estimated duration for this signature in minutes, or null/undefined to omit. */
@@ -175,6 +185,12 @@ export type PlanRouteDataInputs = {
175
185
  sections: readonly PlanSectionPricing[];
176
186
  /** The route's aggregate all-in cost in United States dollars, or null/undefined. */
177
187
  totalCostUsdValue?: number | null;
188
+ /** The all-in cost without network gas, or null/undefined. */
189
+ costExcludingGasUsdValue?: number | null;
190
+ /** The route's network gas in dollars, or null/undefined when unknown. */
191
+ gasCostUsdValue?: number | null;
192
+ /** The gas by signing chain, in signing order. */
193
+ gasByChain?: readonly RouteGasByChain[];
178
194
  /** The route's net United States dollar outcome — the figure routes are ranked on. */
179
195
  netValueUsd?: number | null;
180
196
  /** Estimated end-to-end duration in minutes, or null/undefined. */
@@ -37,6 +37,7 @@ exports.buildRouteScoreInputsFromPlan = exports.buildRouteCandidateFromPlan = ex
37
37
  const imageLinks = __importStar(require("../../image-links.js"));
38
38
  const chain_names_js_1 = require("../../chain-names.js");
39
39
  const providers_js_1 = require("../../providers.js");
40
+ const carrier_provider_key_js_1 = require("../carrier-provider-key.js");
40
41
  const near_intents_route_js_1 = require("../../near-intents-route.js");
41
42
  const producers_js_1 = require("../producers.js");
42
43
  const graph_js_1 = require("./graph.js");
@@ -214,6 +215,8 @@ const groupEndsWithBridgeHop = (hops) => isBridgeCarrier(hops[hops.length - 1].e
214
215
  * section, in BOTH its shapes (the entry-chain swap and the onward
215
216
  * aggregator hop) — the section label names where the journey finishes
216
217
  * regardless of which service carries the last leg.
218
+ * - **"Swap on {X}"** — a group of the "Send to" shape whose first hop starts
219
+ * on the chain its last hop ends on: nothing is sent anywhere.
217
220
  * - **"Send to {X}"** — every other case: a non-final, non-bridge-ending
218
221
  * group. Matches `buildNearIntentsTwoStepRouteData`'s first section.
219
222
  *
@@ -235,7 +238,7 @@ const sectionLabel = (group, groupIndex, plan) => {
235
238
  if (isFinalGroup) {
236
239
  return `Complete at ${(0, chain_names_js_1.chainDisplayName)(destinationChainId)}`;
237
240
  }
238
- return `Send to ${(0, chain_names_js_1.chainDisplayName)(destinationChainId)}`;
241
+ return (0, near_intents_route_js_1.sendOrSwapLabel)(hops[0].edge.from.chainId, destinationChainId);
239
242
  };
240
243
  // ---------------------------------------------------------------------------
241
244
  // Hop-level rendering
@@ -265,6 +268,8 @@ const hopProviderName = (carrier) => {
265
268
  return (0, producers_js_1.providerDisplayName)(carrier);
266
269
  if (carrier === 'PulseX')
267
270
  return 'PulseX';
271
+ if ((0, carrier_provider_key_js_1.carrierStepKind)(carrier) === 'wrap')
272
+ return 'Wrap';
268
273
  // TOTAL OVER EVERY CARRIER. The engine plans hops for carriers the provider
269
274
  // registry does not hold (a local wrap, a host's own plug-in). Casting such
270
275
  // an id to a provider id and asking the throwing lookup crashed the whole
@@ -288,7 +293,8 @@ const hopProviderName = (carrier) => {
288
293
  * `"Bridge to {toChainName}"` (matches `buildOutboundRouteData`'s onward
289
294
  * hop, which names the chain it finishes on even though an aggregator,
290
295
  * not the omnibridge, carries it);
291
- * - any other aggregator hop (something follows it) → `"Send to {toChainName}"`
296
+ * - any other aggregator hop (something follows it) → `"Send to {toChainName}"`,
297
+ * or `"Swap on {chainName}"` when the hop starts and ends on one chain
292
298
  * (matches `buildNearIntentsTwoStepRouteData`'s aggregator hop).
293
299
  *
294
300
  * @param hop - the hop to title
@@ -307,10 +313,10 @@ const hopTitle = (hop, hopIndex, plan, resolveToken) => {
307
313
  if (carrier === 'PulseX') {
308
314
  return `Swap to ${resolveToken(hop.edge.to).symbol}`;
309
315
  }
310
- const destinationChainName = (0, chain_names_js_1.chainDisplayName)(hop.edge.to.chainId);
311
- return isFinalHopOfPlan(hopIndex, plan)
312
- ? `Bridge to ${destinationChainName}`
313
- : `Send to ${destinationChainName}`;
316
+ if (isFinalHopOfPlan(hopIndex, plan)) {
317
+ return `Bridge to ${(0, chain_names_js_1.chainDisplayName)(hop.edge.to.chainId)}`;
318
+ }
319
+ return (0, near_intents_route_js_1.sendOrSwapLabel)(hop.edge.from.chainId, hop.edge.to.chainId);
314
320
  };
315
321
  /**
316
322
  * A hop's conversion subtitle.
@@ -386,11 +392,13 @@ const hopIsAutomatic = (hopIndex, plan) => {
386
392
  * @param plan - the plan the hop belongs to
387
393
  * @param resolveToken - resolves a graph node to its full display token
388
394
  * @param feeUsdValue - the hop's own share of its section's cost, or null
395
+ * @param gas - the hop's gas split, or null when the producer states none
389
396
  * @returns the raw hop data
390
397
  */
391
- const toRouteHopData = (hop, hopIndex, plan, resolveToken, feeUsdValue) => ({
398
+ const toRouteHopData = (hop, hopIndex, plan, resolveToken, feeUsdValue, gas) => ({
392
399
  providerName: hopProviderName(hop.edge.carrier),
393
- providerKey: hop.edge.carrier,
400
+ providerKey: (0, carrier_provider_key_js_1.carrierProviderKey)(hop.edge.carrier),
401
+ stepKind: (0, carrier_provider_key_js_1.carrierStepKind)(hop.edge.carrier),
394
402
  title: hopTitle(hop, hopIndex, plan, resolveToken),
395
403
  conversion: hopConversion(hop, hopIndex, plan, resolveToken),
396
404
  fromToken: toRouteTokenRef(hop.edge.from, resolveToken),
@@ -398,6 +406,7 @@ const toRouteHopData = (hop, hopIndex, plan, resolveToken, feeUsdValue) => ({
398
406
  // The per-hop dollar figure is a caller-supplied input (see this module's
399
407
  // head doc); the caller passes it in, never derived here.
400
408
  feeUsdValue,
409
+ ...(gas === null ? {} : gas),
401
410
  automatic: hopIsAutomatic(hopIndex, plan),
402
411
  });
403
412
  /**
@@ -542,8 +551,6 @@ exports.derivePlanKind = derivePlanKind;
542
551
  * @param carrier - the plan's headline carrier
543
552
  * @returns the same value, typed as `ProviderKey`
544
553
  */
545
- /** The carrier id of the native-coin wrap step (`routing/carriers/wrap.ts`). */
546
- const WRAP_CARRIER = 'wrap';
547
554
  const toHeadlineProviderKey = (carrier) => {
548
555
  if (isBridgeCarrier(carrier))
549
556
  return carrier;
@@ -576,7 +583,7 @@ exports.deriveHeadlineProviderKeyFromPlan = deriveHeadlineProviderKeyFromPlan;
576
583
  * @param plan - the plan to read
577
584
  * @returns the carrier to headline
578
585
  */
579
- const firstHeadlineCarrier = (plan) => plan.hops.find((hop) => hop.edge.carrier !== WRAP_CARRIER)?.edge.carrier ?? 'PulseX';
586
+ const firstHeadlineCarrier = (plan) => plan.hops.find((hop) => hop.edge.carrier !== carrier_provider_key_js_1.WRAP_CARRIER_ID)?.edge.carrier ?? 'PulseX';
580
587
  /**
581
588
  * The anchor a two-signature plan leaves the reader holding between its
582
589
  * first and second groups — `RouteCandidate.plan`'s own TSDoc: "the anchor
@@ -696,10 +703,13 @@ const buildRouteDataFromPlan = (inputs) => {
696
703
  const landing = resolveNodeWithAssertedDecimals(lastHop.edge.to, resolveToken);
697
704
  const landingIsUnpriced = hopOutcomeIsUnpriced(lastHop);
698
705
  const hopFeeUsdValues = groupHopFeeUsdValues(pricing, group, groupIndex);
706
+ const statesGasSplit = pricing.feeExcludingGasUsdValue !== undefined;
707
+ const hopFeeExcludingGasUsdValues = pricing.hopFeeExcludingGasUsdValues;
699
708
  return {
700
709
  id: `${routeId}-signature-${groupIndex + 1}`,
701
710
  label: sectionLabel(group, groupIndex, plan),
702
- providerKey: hops[0].edge.carrier,
711
+ providerKey: (0, carrier_provider_key_js_1.carrierProviderKey)(hops[0].edge.carrier),
712
+ stepKind: (0, carrier_provider_key_js_1.carrierStepKind)(hops[0].edge.carrier),
703
713
  tokenFlow: buildGroupTokenFlow(hops, resolveToken),
704
714
  landing: {
705
715
  amount: sectionLandingAmount(pricing, lastHop),
@@ -709,10 +719,19 @@ const buildRouteDataFromPlan = (inputs) => {
709
719
  estimateBasis: hopEstimateBasis(lastHop),
710
720
  },
711
721
  feeUsdValue: pricing.feeUsdValue,
722
+ ...(statesGasSplit
723
+ ? { feeExcludingGasUsdValue: pricing.feeExcludingGasUsdValue, gasUsdValue: pricing.gasUsdValue ?? null }
724
+ : {}),
712
725
  fees: pricing.fees ? [...pricing.fees] : undefined,
713
726
  etaMinutes: pricing.etaMinutes,
714
727
  etaSeconds: pricing.etaSeconds,
715
- hops: group.map((hopIndex, positionInGroup) => toRouteHopData(plan.hops[hopIndex], hopIndex, plan, resolveToken, hopFeeUsdValues[positionInGroup] ?? null)),
728
+ hops: group.map((hopIndex, positionInGroup) => toRouteHopData(plan.hops[hopIndex], hopIndex, plan, resolveToken, hopFeeUsdValues[positionInGroup] ?? null, statesGasSplit
729
+ ? {
730
+ feeExcludingGasUsdValue: hopFeeExcludingGasUsdValues?.[positionInGroup] ??
731
+ (group.length === 1 ? (pricing.feeExcludingGasUsdValue ?? null) : null),
732
+ gasUsdValue: positionInGroup === 0 ? (pricing.gasUsdValue ?? null) : null,
733
+ }
734
+ : null)),
716
735
  deferred: groupIndex > 0 ? true : undefined,
717
736
  deferredNote: pricing.deferredNote,
718
737
  };
@@ -731,6 +750,13 @@ const buildRouteDataFromPlan = (inputs) => {
731
750
  estimateBasis: lastSection.landing?.estimateBasis ?? null,
732
751
  },
733
752
  totalCostUsdValue,
753
+ ...(inputs.costExcludingGasUsdValue === undefined
754
+ ? {}
755
+ : {
756
+ costExcludingGasUsdValue: inputs.costExcludingGasUsdValue,
757
+ gasCostUsdValue: inputs.gasCostUsdValue ?? null,
758
+ gasByChain: [...(inputs.gasByChain ?? [])],
759
+ }),
734
760
  netValueUsd,
735
761
  etaMinutes,
736
762
  // The single legacy `viaChainId` a not-yet-migrated consumer may still
@@ -1,5 +1,6 @@
1
1
  import { type Hex } from 'viem';
2
2
  import { type SignedLeg } from '../settings.js';
3
+ import type { SwapVenues } from '../swap-venues.js';
3
4
  import type { HopEdge, PlannedHop } from './search/graph.js';
4
5
  /**
5
6
  * The same-chain, one-signature transaction shape every sender in this file
@@ -27,7 +28,7 @@ export type SwapCallInputs = {
27
28
  * looking at) and the native-in/native-out/token-token branching.
28
29
  *
29
30
  * SIGNS ON THE EDGE'S OWN ROUTER. A PulseX edge names the deployment it was
30
- * quoted on (`pulseXRouterForEdge`: V1 or V2, each with its own pools). The
31
+ * quoted on (`venueForEdge`: V1 or V2, each with its own pools). The
31
32
  * caller's `routerAddress` must be that same router — it is also the router
32
33
  * the caller approved to spend the input token — or nothing is built. A V2
33
34
  * quote signed on V1 would trade against a different pool than the one the
@@ -35,8 +36,9 @@ export type SwapCallInputs = {
35
36
  *
36
37
  * @param options - the hop to swap, the amount and slippage-adjusted
37
38
  * minimum, who receives the output, the signing deadline, what the caller
38
- * believes this signature does (`signedLeg`), the chain it executes on, and
39
- * the resolved router address
39
+ * believes this signature does (`signedLeg`), the chain it executes on, the
40
+ * resolved router address, and the swap venues the router must belong to
41
+ * (the defaults when omitted)
40
42
  * @returns the swap transaction inputs, or null when the hop records no
41
43
  * PulseX swap path, names no known router, `routerAddress` is not the
42
44
  * edge's own router, the leg on screen disagrees, or any of
@@ -51,6 +53,8 @@ export declare const buildPulseXSwapTransaction: (options: {
51
53
  readonly signedLeg: SignedLeg | null;
52
54
  readonly chainId: number | null;
53
55
  readonly routerAddress: Hex | null;
56
+ /** The venues the edge's router must belong to. Omitted means the defaults. */
57
+ readonly venues?: SwapVenues;
54
58
  }) => SwapCallInputs | null;
55
59
  /**
56
60
  * Builds a native-coin wrap/unwrap transaction for one route-search graph
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.swapMinimumOut = exports.buildWrapTransaction = exports.buildPulseXSwapTransaction = void 0;
4
4
  const viem_1 = require("viem");
5
5
  const settings_js_1 = require("../settings.js");
6
+ const config_js_1 = require("../config.js");
6
7
  const pulsex_js_1 = require("./carriers/pulsex.js");
7
8
  /**
8
9
  * The minimal WETH9-style wrap/unwrap interface: `deposit()` mints the
@@ -32,7 +33,7 @@ const wrapAbi = (0, viem_1.parseAbi)(['function deposit() payable', 'function wi
32
33
  * looking at) and the native-in/native-out/token-token branching.
33
34
  *
34
35
  * SIGNS ON THE EDGE'S OWN ROUTER. A PulseX edge names the deployment it was
35
- * quoted on (`pulseXRouterForEdge`: V1 or V2, each with its own pools). The
36
+ * quoted on (`venueForEdge`: V1 or V2, each with its own pools). The
36
37
  * caller's `routerAddress` must be that same router — it is also the router
37
38
  * the caller approved to spend the input token — or nothing is built. A V2
38
39
  * quote signed on V1 would trade against a different pool than the one the
@@ -40,8 +41,9 @@ const wrapAbi = (0, viem_1.parseAbi)(['function deposit() payable', 'function wi
40
41
  *
41
42
  * @param options - the hop to swap, the amount and slippage-adjusted
42
43
  * minimum, who receives the output, the signing deadline, what the caller
43
- * believes this signature does (`signedLeg`), the chain it executes on, and
44
- * the resolved router address
44
+ * believes this signature does (`signedLeg`), the chain it executes on, the
45
+ * resolved router address, and the swap venues the router must belong to
46
+ * (the defaults when omitted)
45
47
  * @returns the swap transaction inputs, or null when the hop records no
46
48
  * PulseX swap path, names no known router, `routerAddress` is not the
47
49
  * edge's own router, the leg on screen disagrees, or any of
@@ -54,7 +56,7 @@ const buildPulseXSwapTransaction = (options) => {
54
56
  if (swapPath === null) {
55
57
  return null;
56
58
  }
57
- const quotedRouter = (0, pulsex_js_1.pulseXRouterForEdge)(edge);
59
+ const quotedRouter = (0, pulsex_js_1.venueForEdge)(options.venues ?? config_js_1.defaultSwapVenues, edge)?.router ?? null;
58
60
  if (quotedRouter === null || routerAddress === null || !(0, viem_1.isAddressEqual)(quotedRouter, routerAddress)) {
59
61
  return null;
60
62
  }
@@ -0,0 +1,43 @@
1
+ import type { Hex } from 'viem';
2
+ /**
3
+ * A place a same-chain token swap can execute. Today only Uniswap-V2-shaped
4
+ * routers exist: `getAmountsOut`, `swapExactTokensForTokens`,
5
+ * `swapExactETHForTokens`, `swapExactTokensForETH`, a factory with
6
+ * `getPair`, and pairs with `getReserves`. The `kind` field leaves room for
7
+ * other shapes (for example `'uniswap-v3'`) without breaking callers.
8
+ */
9
+ export type SwapVenue = {
10
+ /** The router shape. Only `'uniswap-v2'` is supported. */
11
+ readonly kind: 'uniswap-v2';
12
+ /** The numeric chain id the router lives on. */
13
+ readonly chainId: number;
14
+ /** The router a swap is signed on. It is also the token approval spender. */
15
+ readonly router: Hex;
16
+ /** The factory whose pools the router trades against. */
17
+ readonly factory: Hex;
18
+ /** The swap fee in basis points: 29 for PulseX, 30 for Uniswap V2 forks. */
19
+ readonly feeBasisPoints: number;
20
+ /** A name for logs and displays. */
21
+ readonly label?: string;
22
+ };
23
+ /** The venues on each chain, keyed by numeric chain id. The first venue of a chain is its default. */
24
+ export type SwapVenues = Readonly<Record<number, readonly SwapVenue[]>>;
25
+ /**
26
+ * Merges caller venues over {@link defaultSwapVenues}. A chain present in
27
+ * `overrides` replaces the default list for that chain. A chain absent from
28
+ * `overrides` keeps its default list. An empty list switches swaps off on
29
+ * that chain. Neither input is changed; the result is frozen.
30
+ *
31
+ * @param overrides - the caller's venues, or undefined for the defaults
32
+ * @returns the venues to use
33
+ */
34
+ export declare const resolveSwapVenues: (overrides?: SwapVenues) => SwapVenues;
35
+ /**
36
+ * Finds the venue on a chain that has the given router.
37
+ *
38
+ * @param venues - the venues to search
39
+ * @param chainId - the numeric chain id
40
+ * @param router - the router address, in any letter case
41
+ * @returns the venue, or null when the chain has no venue with that router
42
+ */
43
+ export declare const findSwapVenue: (venues: SwapVenues, chainId: number, router: Hex) => SwapVenue | null;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findSwapVenue = exports.resolveSwapVenues = void 0;
4
+ const config_js_1 = require("./config.js");
5
+ /**
6
+ * Merges caller venues over {@link defaultSwapVenues}. A chain present in
7
+ * `overrides` replaces the default list for that chain. A chain absent from
8
+ * `overrides` keeps its default list. An empty list switches swaps off on
9
+ * that chain. Neither input is changed; the result is frozen.
10
+ *
11
+ * @param overrides - the caller's venues, or undefined for the defaults
12
+ * @returns the venues to use
13
+ */
14
+ const resolveSwapVenues = (overrides) => {
15
+ if (overrides === undefined)
16
+ return config_js_1.defaultSwapVenues;
17
+ return Object.freeze({ ...config_js_1.defaultSwapVenues, ...overrides });
18
+ };
19
+ exports.resolveSwapVenues = resolveSwapVenues;
20
+ /**
21
+ * Finds the venue on a chain that has the given router.
22
+ *
23
+ * @param venues - the venues to search
24
+ * @param chainId - the numeric chain id
25
+ * @param router - the router address, in any letter case
26
+ * @returns the venue, or null when the chain has no venue with that router
27
+ */
28
+ const findSwapVenue = (venues, chainId, router) => venues[chainId]?.find((venue) => venue.router.toLowerCase() === router.toLowerCase()) ?? null;
29
+ exports.findSwapVenue = findSwapVenue;
@@ -30,6 +30,14 @@ export type Token = TokenMetadata & {
30
30
  * that carry no price (e.g. the dapp image lists).
31
31
  */
32
32
  priceUSD?: string | null;
33
+ /**
34
+ * LI.FI's `coinKey`: a short key naming the asset across chains (for example
35
+ * "USDC"). Present only on tokens parsed from a LI.FI list that carries one.
36
+ * LI.FI repeats some keys within one chain, so a key alone is not an identity.
37
+ */
38
+ coinKey?: string;
39
+ /** LI.FI's `verificationStatus` (for example "verified"). Present only on tokens parsed from a LI.FI list that carries one. */
40
+ verificationStatus?: string;
33
41
  };
34
42
  /** The token out for a given asset */
35
43
  export type TokenOut = Omit<Token, 'address'> & {
package/dist/config.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type Hex } from 'viem';
2
+ import type { SwapVenues } from './swap-venues.js';
2
3
  import type { BridgeKey, Token, TokenOut } from './types.js';
3
4
  /**
4
5
  * Add here your chain id as hex, be sure to add your chain in the chainsMetadata object too
@@ -59,8 +60,22 @@ export declare const pulsechainTokenAddresses: Readonly<{
59
60
  }>;
60
61
  /** A map of chains to deprecated native asset out addresses */
61
62
  export declare const deprecatedNativeAssetOut: Partial<Record<Chains, Set<Hex>>>;
62
- /** A map of chains to uni v2 router addresses */
63
- export declare const uniV2Routers: Record<Chains, Hex[]>;
63
+ /**
64
+ * The swap venues the package ships with: PulseX V1 then V2 on PulseChain.
65
+ * Frozen. A caller who wants other routers passes its own
66
+ * {@link SwapVenues} (see `resolveSwapVenues`); this table is never mutated.
67
+ *
68
+ * Each factory is the router's own `router.factory()` read on chain — see
69
+ * {@link uniV2Factories}'s doc. The fee is 29 basis points for both
70
+ * deployments (measured, same doc).
71
+ */
72
+ export declare const defaultSwapVenues: SwapVenues;
73
+ /**
74
+ * A map of chains to uni v2 router addresses. Frozen. PulseChain's entries
75
+ * derive from {@link defaultSwapVenues}, so the two cannot drift. Kept for
76
+ * compatibility; to swap on other routers pass `swapVenues` instead.
77
+ */
78
+ export declare const uniV2Routers: Readonly<Record<Chains, readonly Hex[]>>;
64
79
  /**
65
80
  * Each {@link uniV2Routers} entry's OWN factory address, same index, same
66
81
  * chain — `router.factory()` read live from `one.valve.city` on 2026-09-24.
@@ -85,7 +100,7 @@ export declare const uniV2Routers: Record<Chains, Hex[]>;
85
100
  * salt collision) and worth this note so a future reader does not assume a
86
101
  * transcription error and "fix" it back to something wrong.
87
102
  */
88
- export declare const uniV2Factories: Record<Chains, Hex[]>;
103
+ export declare const uniV2Factories: Readonly<Record<Chains, readonly Hex[]>>;
89
104
  /** A map of chains to native token symbols */
90
105
  export declare const nativeTokenSymbol: Record<Chains, string>;
91
106
  /** A map of chains to native token names */
package/dist/config.js CHANGED
@@ -71,12 +71,13 @@ export const pulsechainTokenAddresses = Object.freeze({
71
71
  export const deprecatedNativeAssetOut = {
72
72
  [Chains.PLS]: new Set(['0x97Ac4a2439A47c07ad535bb1188c989dae755341']),
73
73
  };
74
- /** A map of chains to uni v2 router addresses */
75
- export const uniV2Routers = {
76
- [Chains.PLS]: [
77
- '0x98bf93ebf5c380C0e6Ae8e192A7e2AE08edAcc02',
78
- '0x165C3410fC91EF562C50559f7d2289fEbed552d9',
79
- ],
74
+ /**
75
+ * Routers on chains where this package knows the router but has not verified
76
+ * a factory, so no {@link SwapVenue} can be built for them. They stay in
77
+ * {@link uniV2Routers} for the native-price reads (`chain-info.ts`,
78
+ * `readAmountOut`) that need only a router.
79
+ */
80
+ const routersWithoutVenue = {
80
81
  [Chains.ETH]: ['0x7a250d5630b4cf539739df2c5dacb4c659f2488d'],
81
82
  [Chains.BNB]: ['0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F'],
82
83
  [Chains.SEP]: ['0xeE567Fe1712Faf6149d80dA1E6934E354124CfE3'],
@@ -85,6 +86,43 @@ export const uniV2Routers = {
85
86
  '0x636f6407B90661b73b1C0F7e24F4C79f624d0738',
86
87
  ],
87
88
  };
89
+ /**
90
+ * The swap venues the package ships with: PulseX V1 then V2 on PulseChain.
91
+ * Frozen. A caller who wants other routers passes its own
92
+ * {@link SwapVenues} (see `resolveSwapVenues`); this table is never mutated.
93
+ *
94
+ * Each factory is the router's own `router.factory()` read on chain — see
95
+ * {@link uniV2Factories}'s doc. The fee is 29 basis points for both
96
+ * deployments (measured, same doc).
97
+ */
98
+ export const defaultSwapVenues = Object.freeze({
99
+ [Number(Chains.PLS)]: Object.freeze([
100
+ Object.freeze({
101
+ kind: 'uniswap-v2',
102
+ chainId: Number(Chains.PLS),
103
+ router: '0x98bf93ebf5c380C0e6Ae8e192A7e2AE08edAcc02',
104
+ factory: '0x1715a3E4A142d8b698131108995174F37aEBA10D',
105
+ feeBasisPoints: 29,
106
+ label: 'PulseX V1',
107
+ }),
108
+ Object.freeze({
109
+ kind: 'uniswap-v2',
110
+ chainId: Number(Chains.PLS),
111
+ router: '0x165C3410fC91EF562C50559f7d2289fEbed552d9',
112
+ factory: '0x29eA7545DEf87022BAdc76323F373EA1e707C523',
113
+ feeBasisPoints: 29,
114
+ label: 'PulseX V2',
115
+ }),
116
+ ]),
117
+ });
118
+ /** One chain's routers: its default venues' routers, else the router-only entries. */
119
+ const defaultRoutersFor = (chain) => defaultSwapVenues[Number(chain)]?.map((venue) => venue.router) ?? routersWithoutVenue[chain] ?? [];
120
+ /**
121
+ * A map of chains to uni v2 router addresses. Frozen. PulseChain's entries
122
+ * derive from {@link defaultSwapVenues}, so the two cannot drift. Kept for
123
+ * compatibility; to swap on other routers pass `swapVenues` instead.
124
+ */
125
+ export const uniV2Routers = Object.freeze(Object.fromEntries(Object.values(Chains).map((chain) => [chain, Object.freeze([...defaultRoutersFor(chain)])])));
88
126
  /**
89
127
  * Each {@link uniV2Routers} entry's OWN factory address, same index, same
90
128
  * chain — `router.factory()` read live from `one.valve.city` on 2026-09-24.
@@ -109,16 +147,10 @@ export const uniV2Routers = {
109
147
  * salt collision) and worth this note so a future reader does not assume a
110
148
  * transcription error and "fix" it back to something wrong.
111
149
  */
112
- export const uniV2Factories = {
113
- [Chains.PLS]: [
114
- '0x1715a3E4A142d8b698131108995174F37aEBA10D',
115
- '0x29eA7545DEf87022BAdc76323F373EA1e707C523',
116
- ],
117
- [Chains.ETH]: [],
118
- [Chains.BNB]: [],
119
- [Chains.SEP]: [],
120
- [Chains.V4PLS]: [],
121
- };
150
+ export const uniV2Factories = Object.freeze(Object.fromEntries(Object.values(Chains).map((chain) => [
151
+ chain,
152
+ Object.freeze((defaultSwapVenues[Number(chain)] ?? []).map((venue) => venue.factory)),
153
+ ])));
122
154
  /** A map of chains to native token symbols */
123
155
  export const nativeTokenSymbol = {
124
156
  [Chains.PLS]: 'PLS',
@@ -1025,6 +1025,9 @@ export const parseLifiTokens = (payload, chainId) => {
1025
1025
  // Preserve the inline spot price the LI.FI list carries — it is the USD
1026
1026
  // price source for aggregator chains (the dapp image lists carry none).
1027
1027
  priceUSD: typeof entry.priceUSD === 'string' ? entry.priceUSD : null,
1028
+ // Additive: kept only when LI.FI sends them, so a token without them has the same shape as before.
1029
+ ...(typeof entry.coinKey === 'string' ? { coinKey: entry.coinKey } : {}),
1030
+ ...(typeof entry.verificationStatus === 'string' ? { verificationStatus: entry.verificationStatus } : {}),
1028
1031
  });
1029
1032
  }
1030
1033
  return parsed;
@@ -1,5 +1,6 @@
1
1
  import { type Hex, type PublicClient } from 'viem';
2
2
  import type { TokenBridgeInfo } from './chain-info.js';
3
+ import { type SwapVenues } from './swap-venues.js';
3
4
  import { type AggregatorOriginRefusal, type ConsolidatedQuote, type ConsolidatedQuoteRequest } from './consolidated-quote.js';
4
5
  import type { FeeType } from './fee-type.js';
5
6
  import type { RouteCandidate } from './routing/candidate.js';
@@ -20,7 +21,7 @@ import type { BridgeKey, Token } from './types.js';
20
21
  * EVERY SPENDER IS ONE THIS CODE ALREADY KNOWS. Approvals are for the exact
21
22
  * amount spent, never unlimited, and go only to:
22
23
  * - the omnibridge pathway's `from` mediator, read from `config.ts`;
23
- * - the PulseX router the edge was quoted on, read from `config.ts`;
24
+ * - the swap router the edge was quoted on, found in the swap venues (the defaults in `config.ts` unless the caller passes its own);
24
25
  * - an aggregator spender that `aggregatorOriginCalls` checked against a
25
26
  * fixed table for its provider and chain (LI.FI's Diamond, Relay's
26
27
  * depository). A quote cannot name its own spender.
@@ -212,12 +213,15 @@ export type ExecutionPlan = {
212
213
  * @param options.account - the account that signs every transaction
213
214
  * @param options.legs - the legs, in the order the wallet signs them
214
215
  * @param options.readAllowance - reads an allowance on chain; see {@link AllowanceReader}
216
+ * @param options.swapVenues - the routers swaps may sign on, merged over the defaults; a swap whose router is in none of them throws
215
217
  * @returns the ordered transactions, or the first leg that cannot be planned
218
+ * @throws when a swap leg's router is not in the swap venues
216
219
  */
217
- export declare const planExecution: ({ account, legs, readAllowance, }: {
220
+ export declare const planExecution: ({ account, legs, readAllowance, swapVenues, }: {
218
221
  readonly account: Hex;
219
222
  readonly legs: readonly ExecutionLeg[];
220
223
  readonly readAllowance: AllowanceReader;
224
+ readonly swapVenues?: SwapVenues;
221
225
  }) => Promise<ExecutionPlan>;
222
226
  /**
223
227
  * What the caller supplies for one signature section of a candidate. Each
@@ -270,6 +274,12 @@ export type CandidateExecutionInputs = {
270
274
  readonly readAllowance: AllowanceReader;
271
275
  /** The resolver the candidate was built with; turns a plan's graph node into its token. */
272
276
  readonly resolveToken: ResolveNodeToken;
277
+ /**
278
+ * The routers swaps may sign on, merged over the defaults: a chain named
279
+ * here replaces that chain's default list. Pass the SAME venues the route
280
+ * search used. A swap whose router is in none of them throws.
281
+ */
282
+ readonly swapVenues?: SwapVenues;
273
283
  /**
274
284
  * The index of the section that `sections[0]` plans. It defaults to 0.
275
285
  * Pass it to plan a later section after the earlier ones landed, with the
@@ -337,10 +347,13 @@ export declare const planCandidateExecution: (inputs: CandidateExecutionInputs)
337
347
  * @param options.hop - the swap hop; its recorded path and edge name what to read
338
348
  * @param options.amountIn - the amount the swap spends, in base units
339
349
  * @param options.client - a client that can call `readContract`
340
- * @returns the amount the last token of the path delivers, or null when the hop records no path, names no known router, the amount is not positive, or the read fails or returns nothing
350
+ * @param options.swapVenues - the routers swaps may sign on, merged over the defaults
351
+ * @returns the amount the last token of the path delivers, or null when the hop records no path, the amount is not positive, or the read fails or returns nothing
352
+ * @throws when the hop records a path but its router is not in the swap venues
341
353
  */
342
- export declare const readPulseXSectionQuote: ({ hop, amountIn, client, }: {
354
+ export declare const readPulseXSectionQuote: ({ hop, amountIn, client, swapVenues, }: {
343
355
  readonly hop: Pick<PlannedHop, "edge" | "outcome">;
344
356
  readonly amountIn: bigint;
345
357
  readonly client: Pick<PublicClient, "readContract">;
358
+ readonly swapVenues?: SwapVenues;
346
359
  }) => Promise<bigint | null>;