@gibs/bridge-sdk 1.17.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +52 -1
  2. package/dist/cjs/config.d.ts +18 -3
  3. package/dist/cjs/config.js +49 -17
  4. package/dist/cjs/execution.d.ts +17 -4
  5. package/dist/cjs/execution.js +40 -13
  6. package/dist/cjs/index.d.ts +1 -0
  7. package/dist/cjs/index.js +1 -0
  8. package/dist/cjs/prices.d.ts +10 -0
  9. package/dist/cjs/prices.js +54 -2
  10. package/dist/cjs/routing/carrier-provider-key.d.ts +31 -0
  11. package/dist/cjs/routing/carrier-provider-key.js +42 -0
  12. package/dist/cjs/routing/carriers/pulsex.d.ts +50 -25
  13. package/dist/cjs/routing/carriers/pulsex.js +135 -85
  14. package/dist/cjs/routing/index.d.ts +6 -2
  15. package/dist/cjs/routing/index.js +9 -1
  16. package/dist/cjs/routing/route-data.d.ts +47 -5
  17. package/dist/cjs/routing/search/plan-route-data.d.ts +17 -1
  18. package/dist/cjs/routing/search/plan-route-data.js +30 -7
  19. package/dist/cjs/routing/swap-transactions.d.ts +7 -3
  20. package/dist/cjs/routing/swap-transactions.js +6 -4
  21. package/dist/cjs/swap-venues.d.ts +43 -0
  22. package/dist/cjs/swap-venues.js +29 -0
  23. package/dist/config.d.ts +18 -3
  24. package/dist/config.js +48 -16
  25. package/dist/execution.d.ts +17 -4
  26. package/dist/execution.js +41 -14
  27. package/dist/index.d.ts +1 -0
  28. package/dist/index.js +1 -0
  29. package/dist/prices.d.ts +10 -0
  30. package/dist/prices.js +52 -1
  31. package/dist/routing/carrier-provider-key.d.ts +31 -0
  32. package/dist/routing/carrier-provider-key.js +37 -0
  33. package/dist/routing/carriers/pulsex.d.ts +50 -25
  34. package/dist/routing/carriers/pulsex.js +133 -84
  35. package/dist/routing/index.d.ts +6 -2
  36. package/dist/routing/index.js +3 -1
  37. package/dist/routing/route-data.d.ts +47 -5
  38. package/dist/routing/search/plan-route-data.d.ts +17 -1
  39. package/dist/routing/search/plan-route-data.js +30 -7
  40. package/dist/routing/swap-transactions.d.ts +7 -3
  41. package/dist/routing/swap-transactions.js +7 -5
  42. package/dist/swap-venues.d.ts +43 -0
  43. package/dist/swap-venues.js +24 -0
  44. package/package.json +3 -3
@@ -1,6 +1,7 @@
1
1
  import type { Hex } from 'viem';
2
2
  import type { QuoteFee } from '../quote-fees.js';
3
3
  import type { ProviderKey } from '../providers.js';
4
+ import type { CarrierStepKind } from './carrier-provider-key.js';
4
5
  /**
5
6
  * The executing engine family a route runs through.
6
7
  *
@@ -91,7 +92,12 @@ export type RouteHopData = {
91
92
  /** Display name for the hop's provider, e.g. "LI.FI", "PulseChain Bridge". */
92
93
  providerName: string;
93
94
  /** Raw provider key for logo resolution when no `providerLogoUri` is supplied. */
94
- providerKey?: string | null;
95
+ providerKey?: ProviderKey | null;
96
+ /**
97
+ * What the hop does when no provider performs it (a native wrap). Such a hop
98
+ * has a null `providerKey`; the adapter draws its mark from this kind.
99
+ */
100
+ stepKind?: CarrierStepKind | null;
95
101
  /** Quote-supplied provider logo url (aggregator legs); preferred when present. */
96
102
  providerLogoUri?: string | null;
97
103
  /** Human-readable hop title, e.g. "Bridge to PulseChain". */
@@ -110,11 +116,27 @@ export type RouteHopData = {
110
116
  * completes. Null/undefined when the provider named no output token.
111
117
  */
112
118
  toToken?: RouteTokenRef | null;
113
- /** Per-hop fee in United States dollars as a NUMBER, or null when unknown. */
119
+ /** Per-hop fee in United States dollars as a NUMBER, or null when unknown. A search route's figure INCLUDES the signature's network gas (carried on the section's first hop). */
114
120
  feeUsdValue?: number | null;
121
+ /** The part of {@link feeUsdValue} that is not network gas: what the hop costs in value. Absent when the producer states no gas split. */
122
+ feeExcludingGasUsdValue?: number | null;
123
+ /** The network gas of the signature this hop opens, in dollars; null or absent on every hop that opens none, and when gas is unknown. */
124
+ gasUsdValue?: number | null;
115
125
  /** True when this hop runs automatically inside the section's single signature. */
116
126
  automatic?: boolean;
117
127
  };
128
+ /**
129
+ * The network gas a route needs on one signing chain, summed across the
130
+ * signatures it signs there.
131
+ */
132
+ export type RouteGasByChain = {
133
+ /** The chain the signatures broadcast on. */
134
+ chainId: number;
135
+ /** The native coin they burn, in base units (wei), or null when the chain's gas price is unknown. */
136
+ amountWei: bigint | null;
137
+ /** The same gas in dollars, or null when the amount or the native price is unknown. */
138
+ usdValue: number | null;
139
+ };
118
140
  /**
119
141
  * What a signature section's funds LAND IN when that step completes — the stop
120
142
  * the money actually comes to rest at, named in tokens rather than only drawn as
@@ -181,7 +203,9 @@ export type RouteSectionData = {
181
203
  /** Short label naming what this signature does, e.g. "Bridge to PulseChain". */
182
204
  label: string;
183
205
  /** Raw provider key for the service executing this signature, or null. */
184
- providerKey?: string | null;
206
+ providerKey?: ProviderKey | null;
207
+ /** What the signature does when no provider performs it (a native wrap); see {@link RouteHopData.stepKind}. */
208
+ stepKind?: CarrierStepKind | null;
185
209
  /**
186
210
  * The token-icon flow this signature walks, in raw references: the source, then
187
211
  * every INTERMEDIATE stop the funds pass through, then the destination. A
@@ -202,8 +226,12 @@ export type RouteSectionData = {
202
226
  * a cost.
203
227
  */
204
228
  unspent?: RouteUnspentData | null;
205
- /** Section total fee in United States dollars as a NUMBER, or null. */
229
+ /** Section total fee in United States dollars as a NUMBER, or null. A search route's figure INCLUDES this signature's network gas. */
206
230
  feeUsdValue?: number | null;
231
+ /** The part of {@link feeUsdValue} that is not network gas. Absent when the producer states no gas split. */
232
+ feeExcludingGasUsdValue?: number | null;
233
+ /** This signature's network gas in dollars, or null/absent when unknown. */
234
+ gasUsdValue?: number | null;
207
235
  /** Numeric inputs for an optional fee breakdown line (conversion mode). */
208
236
  feePercent?: number | null;
209
237
  /** Price-impact percentage for an optional fee breakdown line (conversion mode). */
@@ -316,8 +344,22 @@ export type RouteData = {
316
344
  sections: RouteSectionData[];
317
345
  /** What the route delivers — used for ranking and the output row. */
318
346
  output: RouteOutputData;
319
- /** Aggregate all-in cost in United States dollars as a NUMBER, or null. */
347
+ /**
348
+ * Aggregate ALL-IN cost in United States dollars as a NUMBER, or null:
349
+ * value lost plus network gas. The ranking reads this figure. For the
350
+ * figure without gas, read {@link costExcludingGasUsdValue}.
351
+ */
320
352
  totalCostUsdValue?: number | null;
353
+ /** {@link totalCostUsdValue} without network gas: the value the reader gives up. Absent when the producer states no gas split. */
354
+ costExcludingGasUsdValue?: number | null;
355
+ /**
356
+ * The network gas the reader pays across every signature, in dollars, or
357
+ * null/absent when no signing chain has a gas reading. Never zero for
358
+ * "unknown".
359
+ */
360
+ gasCostUsdValue?: number | null;
361
+ /** The gas by signing chain, in signing order: what to hold, and in which coin. */
362
+ gasByChain?: RouteGasByChain[];
321
363
  /** The user's net United States dollar outcome; the figure routes are ranked on. */
322
364
  netValueUsd?: number | null;
323
365
  /** Estimated end-to-end duration in minutes, or null when unknown. */
@@ -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");
@@ -265,6 +266,8 @@ const hopProviderName = (carrier) => {
265
266
  return (0, producers_js_1.providerDisplayName)(carrier);
266
267
  if (carrier === 'PulseX')
267
268
  return 'PulseX';
269
+ if ((0, carrier_provider_key_js_1.carrierStepKind)(carrier) === 'wrap')
270
+ return 'Wrap';
268
271
  // TOTAL OVER EVERY CARRIER. The engine plans hops for carriers the provider
269
272
  // registry does not hold (a local wrap, a host's own plug-in). Casting such
270
273
  // an id to a provider id and asking the throwing lookup crashed the whole
@@ -386,11 +389,13 @@ const hopIsAutomatic = (hopIndex, plan) => {
386
389
  * @param plan - the plan the hop belongs to
387
390
  * @param resolveToken - resolves a graph node to its full display token
388
391
  * @param feeUsdValue - the hop's own share of its section's cost, or null
392
+ * @param gas - the hop's gas split, or null when the producer states none
389
393
  * @returns the raw hop data
390
394
  */
391
- const toRouteHopData = (hop, hopIndex, plan, resolveToken, feeUsdValue) => ({
395
+ const toRouteHopData = (hop, hopIndex, plan, resolveToken, feeUsdValue, gas) => ({
392
396
  providerName: hopProviderName(hop.edge.carrier),
393
- providerKey: hop.edge.carrier,
397
+ providerKey: (0, carrier_provider_key_js_1.carrierProviderKey)(hop.edge.carrier),
398
+ stepKind: (0, carrier_provider_key_js_1.carrierStepKind)(hop.edge.carrier),
394
399
  title: hopTitle(hop, hopIndex, plan, resolveToken),
395
400
  conversion: hopConversion(hop, hopIndex, plan, resolveToken),
396
401
  fromToken: toRouteTokenRef(hop.edge.from, resolveToken),
@@ -398,6 +403,7 @@ const toRouteHopData = (hop, hopIndex, plan, resolveToken, feeUsdValue) => ({
398
403
  // The per-hop dollar figure is a caller-supplied input (see this module's
399
404
  // head doc); the caller passes it in, never derived here.
400
405
  feeUsdValue,
406
+ ...(gas === null ? {} : gas),
401
407
  automatic: hopIsAutomatic(hopIndex, plan),
402
408
  });
403
409
  /**
@@ -542,8 +548,6 @@ exports.derivePlanKind = derivePlanKind;
542
548
  * @param carrier - the plan's headline carrier
543
549
  * @returns the same value, typed as `ProviderKey`
544
550
  */
545
- /** The carrier id of the native-coin wrap step (`routing/carriers/wrap.ts`). */
546
- const WRAP_CARRIER = 'wrap';
547
551
  const toHeadlineProviderKey = (carrier) => {
548
552
  if (isBridgeCarrier(carrier))
549
553
  return carrier;
@@ -576,7 +580,7 @@ exports.deriveHeadlineProviderKeyFromPlan = deriveHeadlineProviderKeyFromPlan;
576
580
  * @param plan - the plan to read
577
581
  * @returns the carrier to headline
578
582
  */
579
- const firstHeadlineCarrier = (plan) => plan.hops.find((hop) => hop.edge.carrier !== WRAP_CARRIER)?.edge.carrier ?? 'PulseX';
583
+ const firstHeadlineCarrier = (plan) => plan.hops.find((hop) => hop.edge.carrier !== carrier_provider_key_js_1.WRAP_CARRIER_ID)?.edge.carrier ?? 'PulseX';
580
584
  /**
581
585
  * The anchor a two-signature plan leaves the reader holding between its
582
586
  * first and second groups — `RouteCandidate.plan`'s own TSDoc: "the anchor
@@ -696,10 +700,13 @@ const buildRouteDataFromPlan = (inputs) => {
696
700
  const landing = resolveNodeWithAssertedDecimals(lastHop.edge.to, resolveToken);
697
701
  const landingIsUnpriced = hopOutcomeIsUnpriced(lastHop);
698
702
  const hopFeeUsdValues = groupHopFeeUsdValues(pricing, group, groupIndex);
703
+ const statesGasSplit = pricing.feeExcludingGasUsdValue !== undefined;
704
+ const hopFeeExcludingGasUsdValues = pricing.hopFeeExcludingGasUsdValues;
699
705
  return {
700
706
  id: `${routeId}-signature-${groupIndex + 1}`,
701
707
  label: sectionLabel(group, groupIndex, plan),
702
- providerKey: hops[0].edge.carrier,
708
+ providerKey: (0, carrier_provider_key_js_1.carrierProviderKey)(hops[0].edge.carrier),
709
+ stepKind: (0, carrier_provider_key_js_1.carrierStepKind)(hops[0].edge.carrier),
703
710
  tokenFlow: buildGroupTokenFlow(hops, resolveToken),
704
711
  landing: {
705
712
  amount: sectionLandingAmount(pricing, lastHop),
@@ -709,10 +716,19 @@ const buildRouteDataFromPlan = (inputs) => {
709
716
  estimateBasis: hopEstimateBasis(lastHop),
710
717
  },
711
718
  feeUsdValue: pricing.feeUsdValue,
719
+ ...(statesGasSplit
720
+ ? { feeExcludingGasUsdValue: pricing.feeExcludingGasUsdValue, gasUsdValue: pricing.gasUsdValue ?? null }
721
+ : {}),
712
722
  fees: pricing.fees ? [...pricing.fees] : undefined,
713
723
  etaMinutes: pricing.etaMinutes,
714
724
  etaSeconds: pricing.etaSeconds,
715
- hops: group.map((hopIndex, positionInGroup) => toRouteHopData(plan.hops[hopIndex], hopIndex, plan, resolveToken, hopFeeUsdValues[positionInGroup] ?? null)),
725
+ hops: group.map((hopIndex, positionInGroup) => toRouteHopData(plan.hops[hopIndex], hopIndex, plan, resolveToken, hopFeeUsdValues[positionInGroup] ?? null, statesGasSplit
726
+ ? {
727
+ feeExcludingGasUsdValue: hopFeeExcludingGasUsdValues?.[positionInGroup] ??
728
+ (group.length === 1 ? (pricing.feeExcludingGasUsdValue ?? null) : null),
729
+ gasUsdValue: positionInGroup === 0 ? (pricing.gasUsdValue ?? null) : null,
730
+ }
731
+ : null)),
716
732
  deferred: groupIndex > 0 ? true : undefined,
717
733
  deferredNote: pricing.deferredNote,
718
734
  };
@@ -731,6 +747,13 @@ const buildRouteDataFromPlan = (inputs) => {
731
747
  estimateBasis: lastSection.landing?.estimateBasis ?? null,
732
748
  },
733
749
  totalCostUsdValue,
750
+ ...(inputs.costExcludingGasUsdValue === undefined
751
+ ? {}
752
+ : {
753
+ costExcludingGasUsdValue: inputs.costExcludingGasUsdValue,
754
+ gasCostUsdValue: inputs.gasCostUsdValue ?? null,
755
+ gasByChain: [...(inputs.gasByChain ?? [])],
756
+ }),
734
757
  netValueUsd,
735
758
  etaMinutes,
736
759
  // 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;
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',
@@ -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>;