@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
@@ -1,7 +1,8 @@
1
1
  import * as imageLinks from '../../image-links.js';
2
2
  import { chainDisplayName } from '../../chain-names.js';
3
3
  import { findProviderById } from '../../providers.js';
4
- import { describeDeferredCrossing } from '../../near-intents-route.js';
4
+ import { WRAP_CARRIER_ID, carrierProviderKey, carrierStepKind } from '../carrier-provider-key.js';
5
+ import { describeDeferredCrossing, sendOrSwapLabel } from '../../near-intents-route.js';
5
6
  import { candidateDeliveredUsd, candidateOutsideFeesUsd, candidatePaidUsd, pulsechainChainId, providerDisplayName, } from '../producers.js';
6
7
  import { assetNodeKey, planComposedBasis, } from './graph.js';
7
8
  /**
@@ -178,6 +179,8 @@ const groupEndsWithBridgeHop = (hops) => isBridgeCarrier(hops[hops.length - 1].e
178
179
  * section, in BOTH its shapes (the entry-chain swap and the onward
179
180
  * aggregator hop) — the section label names where the journey finishes
180
181
  * regardless of which service carries the last leg.
182
+ * - **"Swap on {X}"** — a group of the "Send to" shape whose first hop starts
183
+ * on the chain its last hop ends on: nothing is sent anywhere.
181
184
  * - **"Send to {X}"** — every other case: a non-final, non-bridge-ending
182
185
  * group. Matches `buildNearIntentsTwoStepRouteData`'s first section.
183
186
  *
@@ -199,7 +202,7 @@ const sectionLabel = (group, groupIndex, plan) => {
199
202
  if (isFinalGroup) {
200
203
  return `Complete at ${chainDisplayName(destinationChainId)}`;
201
204
  }
202
- return `Send to ${chainDisplayName(destinationChainId)}`;
205
+ return sendOrSwapLabel(hops[0].edge.from.chainId, destinationChainId);
203
206
  };
204
207
  // ---------------------------------------------------------------------------
205
208
  // Hop-level rendering
@@ -229,6 +232,8 @@ const hopProviderName = (carrier) => {
229
232
  return providerDisplayName(carrier);
230
233
  if (carrier === 'PulseX')
231
234
  return 'PulseX';
235
+ if (carrierStepKind(carrier) === 'wrap')
236
+ return 'Wrap';
232
237
  // TOTAL OVER EVERY CARRIER. The engine plans hops for carriers the provider
233
238
  // registry does not hold (a local wrap, a host's own plug-in). Casting such
234
239
  // an id to a provider id and asking the throwing lookup crashed the whole
@@ -252,7 +257,8 @@ const hopProviderName = (carrier) => {
252
257
  * `"Bridge to {toChainName}"` (matches `buildOutboundRouteData`'s onward
253
258
  * hop, which names the chain it finishes on even though an aggregator,
254
259
  * not the omnibridge, carries it);
255
- * - any other aggregator hop (something follows it) → `"Send to {toChainName}"`
260
+ * - any other aggregator hop (something follows it) → `"Send to {toChainName}"`,
261
+ * or `"Swap on {chainName}"` when the hop starts and ends on one chain
256
262
  * (matches `buildNearIntentsTwoStepRouteData`'s aggregator hop).
257
263
  *
258
264
  * @param hop - the hop to title
@@ -271,10 +277,10 @@ const hopTitle = (hop, hopIndex, plan, resolveToken) => {
271
277
  if (carrier === 'PulseX') {
272
278
  return `Swap to ${resolveToken(hop.edge.to).symbol}`;
273
279
  }
274
- const destinationChainName = chainDisplayName(hop.edge.to.chainId);
275
- return isFinalHopOfPlan(hopIndex, plan)
276
- ? `Bridge to ${destinationChainName}`
277
- : `Send to ${destinationChainName}`;
280
+ if (isFinalHopOfPlan(hopIndex, plan)) {
281
+ return `Bridge to ${chainDisplayName(hop.edge.to.chainId)}`;
282
+ }
283
+ return sendOrSwapLabel(hop.edge.from.chainId, hop.edge.to.chainId);
278
284
  };
279
285
  /**
280
286
  * A hop's conversion subtitle.
@@ -350,11 +356,13 @@ const hopIsAutomatic = (hopIndex, plan) => {
350
356
  * @param plan - the plan the hop belongs to
351
357
  * @param resolveToken - resolves a graph node to its full display token
352
358
  * @param feeUsdValue - the hop's own share of its section's cost, or null
359
+ * @param gas - the hop's gas split, or null when the producer states none
353
360
  * @returns the raw hop data
354
361
  */
355
- const toRouteHopData = (hop, hopIndex, plan, resolveToken, feeUsdValue) => ({
362
+ const toRouteHopData = (hop, hopIndex, plan, resolveToken, feeUsdValue, gas) => ({
356
363
  providerName: hopProviderName(hop.edge.carrier),
357
- providerKey: hop.edge.carrier,
364
+ providerKey: carrierProviderKey(hop.edge.carrier),
365
+ stepKind: carrierStepKind(hop.edge.carrier),
358
366
  title: hopTitle(hop, hopIndex, plan, resolveToken),
359
367
  conversion: hopConversion(hop, hopIndex, plan, resolveToken),
360
368
  fromToken: toRouteTokenRef(hop.edge.from, resolveToken),
@@ -362,6 +370,7 @@ const toRouteHopData = (hop, hopIndex, plan, resolveToken, feeUsdValue) => ({
362
370
  // The per-hop dollar figure is a caller-supplied input (see this module's
363
371
  // head doc); the caller passes it in, never derived here.
364
372
  feeUsdValue,
373
+ ...(gas === null ? {} : gas),
365
374
  automatic: hopIsAutomatic(hopIndex, plan),
366
375
  });
367
376
  /**
@@ -502,8 +511,6 @@ export const derivePlanKind = (plan) => {
502
511
  * @param carrier - the plan's headline carrier
503
512
  * @returns the same value, typed as `ProviderKey`
504
513
  */
505
- /** The carrier id of the native-coin wrap step (`routing/carriers/wrap.ts`). */
506
- const WRAP_CARRIER = 'wrap';
507
514
  const toHeadlineProviderKey = (carrier) => {
508
515
  if (isBridgeCarrier(carrier))
509
516
  return carrier;
@@ -535,7 +542,7 @@ export const deriveHeadlineProviderKeyFromPlan = (plan) => toHeadlineProviderKey
535
542
  * @param plan - the plan to read
536
543
  * @returns the carrier to headline
537
544
  */
538
- const firstHeadlineCarrier = (plan) => plan.hops.find((hop) => hop.edge.carrier !== WRAP_CARRIER)?.edge.carrier ?? 'PulseX';
545
+ const firstHeadlineCarrier = (plan) => plan.hops.find((hop) => hop.edge.carrier !== WRAP_CARRIER_ID)?.edge.carrier ?? 'PulseX';
539
546
  /**
540
547
  * The anchor a two-signature plan leaves the reader holding between its
541
548
  * first and second groups — `RouteCandidate.plan`'s own TSDoc: "the anchor
@@ -654,10 +661,13 @@ export const buildRouteDataFromPlan = (inputs) => {
654
661
  const landing = resolveNodeWithAssertedDecimals(lastHop.edge.to, resolveToken);
655
662
  const landingIsUnpriced = hopOutcomeIsUnpriced(lastHop);
656
663
  const hopFeeUsdValues = groupHopFeeUsdValues(pricing, group, groupIndex);
664
+ const statesGasSplit = pricing.feeExcludingGasUsdValue !== undefined;
665
+ const hopFeeExcludingGasUsdValues = pricing.hopFeeExcludingGasUsdValues;
657
666
  return {
658
667
  id: `${routeId}-signature-${groupIndex + 1}`,
659
668
  label: sectionLabel(group, groupIndex, plan),
660
- providerKey: hops[0].edge.carrier,
669
+ providerKey: carrierProviderKey(hops[0].edge.carrier),
670
+ stepKind: carrierStepKind(hops[0].edge.carrier),
661
671
  tokenFlow: buildGroupTokenFlow(hops, resolveToken),
662
672
  landing: {
663
673
  amount: sectionLandingAmount(pricing, lastHop),
@@ -667,10 +677,19 @@ export const buildRouteDataFromPlan = (inputs) => {
667
677
  estimateBasis: hopEstimateBasis(lastHop),
668
678
  },
669
679
  feeUsdValue: pricing.feeUsdValue,
680
+ ...(statesGasSplit
681
+ ? { feeExcludingGasUsdValue: pricing.feeExcludingGasUsdValue, gasUsdValue: pricing.gasUsdValue ?? null }
682
+ : {}),
670
683
  fees: pricing.fees ? [...pricing.fees] : undefined,
671
684
  etaMinutes: pricing.etaMinutes,
672
685
  etaSeconds: pricing.etaSeconds,
673
- hops: group.map((hopIndex, positionInGroup) => toRouteHopData(plan.hops[hopIndex], hopIndex, plan, resolveToken, hopFeeUsdValues[positionInGroup] ?? null)),
686
+ hops: group.map((hopIndex, positionInGroup) => toRouteHopData(plan.hops[hopIndex], hopIndex, plan, resolveToken, hopFeeUsdValues[positionInGroup] ?? null, statesGasSplit
687
+ ? {
688
+ feeExcludingGasUsdValue: hopFeeExcludingGasUsdValues?.[positionInGroup] ??
689
+ (group.length === 1 ? (pricing.feeExcludingGasUsdValue ?? null) : null),
690
+ gasUsdValue: positionInGroup === 0 ? (pricing.gasUsdValue ?? null) : null,
691
+ }
692
+ : null)),
674
693
  deferred: groupIndex > 0 ? true : undefined,
675
694
  deferredNote: pricing.deferredNote,
676
695
  };
@@ -689,6 +708,13 @@ export const buildRouteDataFromPlan = (inputs) => {
689
708
  estimateBasis: lastSection.landing?.estimateBasis ?? null,
690
709
  },
691
710
  totalCostUsdValue,
711
+ ...(inputs.costExcludingGasUsdValue === undefined
712
+ ? {}
713
+ : {
714
+ costExcludingGasUsdValue: inputs.costExcludingGasUsdValue,
715
+ gasCostUsdValue: inputs.gasCostUsdValue ?? null,
716
+ gasByChain: [...(inputs.gasByChain ?? [])],
717
+ }),
692
718
  netValueUsd,
693
719
  etaMinutes,
694
720
  // 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
@@ -1,6 +1,7 @@
1
1
  import { encodeFunctionData, isAddressEqual, parseAbi, zeroAddress } from 'viem';
2
2
  import { pulseXSwapTransactionInputs } from '../settings.js';
3
- import { pulseXRouterForEdge, pulseXSwapPathForHop } from './carriers/pulsex.js';
3
+ import { defaultSwapVenues } from '../config.js';
4
+ import { pulseXSwapPathForHop, venueForEdge } from './carriers/pulsex.js';
4
5
  /**
5
6
  * The minimal WETH9-style wrap/unwrap interface: `deposit()` mints the
6
7
  * wrapped token for the native coin sent with the call, `withdraw(uint256)`
@@ -29,7 +30,7 @@ const wrapAbi = parseAbi(['function deposit() payable', 'function withdraw(uint2
29
30
  * looking at) and the native-in/native-out/token-token branching.
30
31
  *
31
32
  * SIGNS ON THE EDGE'S OWN ROUTER. A PulseX edge names the deployment it was
32
- * quoted on (`pulseXRouterForEdge`: V1 or V2, each with its own pools). The
33
+ * quoted on (`venueForEdge`: V1 or V2, each with its own pools). The
33
34
  * caller's `routerAddress` must be that same router — it is also the router
34
35
  * the caller approved to spend the input token — or nothing is built. A V2
35
36
  * quote signed on V1 would trade against a different pool than the one the
@@ -37,8 +38,9 @@ const wrapAbi = parseAbi(['function deposit() payable', 'function withdraw(uint2
37
38
  *
38
39
  * @param options - the hop to swap, the amount and slippage-adjusted
39
40
  * minimum, who receives the output, the signing deadline, what the caller
40
- * believes this signature does (`signedLeg`), the chain it executes on, and
41
- * the resolved router address
41
+ * believes this signature does (`signedLeg`), the chain it executes on, the
42
+ * resolved router address, and the swap venues the router must belong to
43
+ * (the defaults when omitted)
42
44
  * @returns the swap transaction inputs, or null when the hop records no
43
45
  * PulseX swap path, names no known router, `routerAddress` is not the
44
46
  * edge's own router, the leg on screen disagrees, or any of
@@ -51,7 +53,7 @@ export const buildPulseXSwapTransaction = (options) => {
51
53
  if (swapPath === null) {
52
54
  return null;
53
55
  }
54
- const quotedRouter = pulseXRouterForEdge(edge);
56
+ const quotedRouter = venueForEdge(options.venues ?? defaultSwapVenues, edge)?.router ?? null;
55
57
  if (quotedRouter === null || routerAddress === null || !isAddressEqual(quotedRouter, routerAddress)) {
56
58
  return null;
57
59
  }
@@ -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,24 @@
1
+ import { defaultSwapVenues } from './config.js';
2
+ /**
3
+ * Merges caller venues over {@link defaultSwapVenues}. A chain present in
4
+ * `overrides` replaces the default list for that chain. A chain absent from
5
+ * `overrides` keeps its default list. An empty list switches swaps off on
6
+ * that chain. Neither input is changed; the result is frozen.
7
+ *
8
+ * @param overrides - the caller's venues, or undefined for the defaults
9
+ * @returns the venues to use
10
+ */
11
+ export const resolveSwapVenues = (overrides) => {
12
+ if (overrides === undefined)
13
+ return defaultSwapVenues;
14
+ return Object.freeze({ ...defaultSwapVenues, ...overrides });
15
+ };
16
+ /**
17
+ * Finds the venue on a chain that has the given router.
18
+ *
19
+ * @param venues - the venues to search
20
+ * @param chainId - the numeric chain id
21
+ * @param router - the router address, in any letter case
22
+ * @returns the venue, or null when the chain has no venue with that router
23
+ */
24
+ export const findSwapVenue = (venues, chainId, router) => venues[chainId]?.find((venue) => venue.router.toLowerCase() === router.toLowerCase()) ?? null;
package/dist/types.d.ts CHANGED
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gibs/bridge-sdk",
3
- "version": "1.17.0",
3
+ "version": "1.19.0",
4
4
  "description": "Build and price cross-chain transfers between PulseChain, Ethereum, and Binance Smart Chain.",
5
5
  "keywords": [
6
6
  "ethereum",
@@ -1007,8 +1007,8 @@
1007
1007
  "test:live:near-intents": "../../scripts/near-intents-live.sh"
1008
1008
  },
1009
1009
  "dependencies": {
1010
- "@gibs/abis": "^1.17.0",
1011
- "@gibs/common": "^1.17.0",
1010
+ "@gibs/abis": "^1.19.0",
1011
+ "@gibs/common": "^1.19.0",
1012
1012
  "@ton/core": "^0.63.1",
1013
1013
  "@ton/crypto": "^3.3.0"
1014
1014
  },