@gibs/bridge-sdk 1.12.0 → 1.13.1

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 (118) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +234 -432
  3. package/dist/abis.d.ts +1 -0
  4. package/dist/bridge-limits.d.ts +103 -0
  5. package/dist/bridge-limits.js +84 -0
  6. package/dist/chain-info.d.ts +169 -0
  7. package/dist/chain-info.js +410 -0
  8. package/dist/chain-names.d.ts +160 -0
  9. package/dist/chain-names.js +200 -0
  10. package/dist/chains.d.ts +65 -0
  11. package/dist/chains.js +284 -0
  12. package/dist/config.d.ts +216 -0
  13. package/dist/config.js +493 -0
  14. package/dist/consolidated-quote.d.ts +554 -0
  15. package/dist/consolidated-quote.js +773 -0
  16. package/dist/destination-calldata.d.ts +118 -0
  17. package/dist/destination-calldata.js +152 -0
  18. package/dist/ecosystems.d.ts +217 -0
  19. package/dist/ecosystems.js +288 -0
  20. package/dist/fee-type.d.ts +10 -0
  21. package/dist/fee-type.js +9 -0
  22. package/dist/hyperliquid-execution.d.ts +152 -0
  23. package/dist/hyperliquid-execution.js +168 -0
  24. package/dist/image-links.d.ts +117 -0
  25. package/dist/image-links.js +142 -0
  26. package/dist/index.d.ts +20 -0
  27. package/dist/index.js +20 -0
  28. package/dist/lifi-execution-guard.d.ts +199 -0
  29. package/dist/lifi-execution-guard.js +400 -0
  30. package/dist/near-intents-conformance.d.ts +85 -0
  31. package/dist/near-intents-conformance.js +329 -0
  32. package/dist/near-intents-route.d.ts +47 -0
  33. package/dist/near-intents-route.js +265 -0
  34. package/dist/near-intents.d.ts +524 -0
  35. package/dist/near-intents.js +1055 -0
  36. package/dist/origin-deposit.d.ts +326 -0
  37. package/dist/origin-deposit.js +68 -0
  38. package/dist/pre-quote.d.ts +198 -0
  39. package/dist/pre-quote.js +175 -0
  40. package/dist/prices.d.ts +107 -0
  41. package/dist/prices.js +162 -0
  42. package/dist/providers.d.ts +221 -0
  43. package/dist/providers.js +157 -0
  44. package/dist/quote-fees.d.ts +102 -0
  45. package/dist/quote-fees.js +195 -0
  46. package/dist/referral.d.ts +77 -0
  47. package/dist/referral.js +72 -0
  48. package/dist/relay-currencies.d.ts +47 -0
  49. package/dist/relay-currencies.js +89 -0
  50. package/dist/relay-quote.d.ts +301 -0
  51. package/dist/relay-quote.js +983 -0
  52. package/dist/route-steps.d.ts +138 -0
  53. package/dist/route-steps.js +305 -0
  54. package/dist/routing/amounts.d.ts +80 -0
  55. package/dist/routing/amounts.js +120 -0
  56. package/dist/routing/candidate-route.d.ts +43 -0
  57. package/dist/routing/candidate-route.js +45 -0
  58. package/dist/routing/candidate.d.ts +237 -0
  59. package/dist/routing/candidate.js +96 -0
  60. package/dist/routing/carriers/ask-answer.d.ts +60 -0
  61. package/dist/routing/carriers/ask-answer.js +1 -0
  62. package/dist/routing/carriers/omnibridge.d.ts +137 -0
  63. package/dist/routing/carriers/omnibridge.js +419 -0
  64. package/dist/routing/carriers/pulsex.d.ts +149 -0
  65. package/dist/routing/carriers/pulsex.js +453 -0
  66. package/dist/routing/carriers/wrap.d.ts +25 -0
  67. package/dist/routing/carriers/wrap.js +115 -0
  68. package/dist/routing/composed-route-quality.d.ts +103 -0
  69. package/dist/routing/composed-route-quality.js +108 -0
  70. package/dist/routing/entry-chain-anchor.d.ts +88 -0
  71. package/dist/routing/entry-chain-anchor.js +55 -0
  72. package/dist/routing/format.d.ts +31 -0
  73. package/dist/routing/format.js +58 -0
  74. package/dist/routing/index.d.ts +34 -0
  75. package/dist/routing/index.js +42 -0
  76. package/dist/routing/producers.d.ts +770 -0
  77. package/dist/routing/producers.js +1564 -0
  78. package/dist/routing/providers.d.ts +26 -0
  79. package/dist/routing/providers.js +38 -0
  80. package/dist/routing/route-data.d.ts +355 -0
  81. package/dist/routing/route-data.js +1 -0
  82. package/dist/routing/search/compose.d.ts +168 -0
  83. package/dist/routing/search/compose.js +98 -0
  84. package/dist/routing/search/graph.d.ts +653 -0
  85. package/dist/routing/search/graph.js +445 -0
  86. package/dist/routing/search/plan-route-data.d.ts +244 -0
  87. package/dist/routing/search/plan-route-data.js +767 -0
  88. package/dist/routing/search/score.d.ts +353 -0
  89. package/dist/routing/search/score.js +427 -0
  90. package/dist/routing/usd-value.d.ts +17 -0
  91. package/dist/routing/usd-value.js +25 -0
  92. package/dist/settings.d.ts +692 -0
  93. package/dist/settings.js +909 -0
  94. package/dist/ton-execution.d.ts +117 -0
  95. package/dist/ton-execution.js +247 -0
  96. package/dist/tron-execution.d.ts +146 -0
  97. package/dist/tron-execution.js +239 -0
  98. package/dist/types.d.ts +51 -0
  99. package/dist/types.js +1 -0
  100. package/llms.txt +169 -0
  101. package/package.json +204 -23
  102. package/src/chain-info.test.ts +0 -968
  103. package/src/chain-info.ts +0 -498
  104. package/src/chains.test.ts +0 -123
  105. package/src/chains.ts +0 -88
  106. package/src/config.test.ts +0 -679
  107. package/src/config.ts +0 -500
  108. package/src/fee-type.test.ts +0 -45
  109. package/src/fee-type.ts +0 -14
  110. package/src/image-links.test.ts +0 -160
  111. package/src/image-links.ts +0 -65
  112. package/src/index.ts +0 -8
  113. package/src/settings.test.ts +0 -2754
  114. package/src/settings.ts +0 -875
  115. package/src/types.ts +0 -48
  116. package/tsconfig.json +0 -17
  117. package/vitest.config.ts +0 -13
  118. /package/{src/abis.ts → dist/abis.js} +0 -0
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Whether a composed multi-signature route is good enough to offer at all.
3
+ *
4
+ * WHY A ROUTE NEEDS A GATE THAT A SINGLE CROSSING DOES NOT. A one-signature
5
+ * route that turns out badly costs the reader one transaction and they still
6
+ * hold something they chose. A composed route — an aggregator leg, then an
7
+ * omnibridge crossing, then a swap — strands them between signatures: after
8
+ * the first they hold an anchor token on a chain they did not ask for, and the
9
+ * only way out is to pay again. So a cheap first leg followed by a bad swap is
10
+ * worse than the route it replaced, and the difference has to be found BEFORE
11
+ * the route is listed rather than after the reader has signed.
12
+ *
13
+ * NOTHING HERE COSTS A REQUEST. Both figures the verdict reads are already in
14
+ * hand when a composer runs: the swap's price impact comes from the on-chain
15
+ * `getAmountsOut` pair the estimate already fetched, and the two dollar values
16
+ * come from the route's own basis. The gate is free, which is why it can run
17
+ * on every route rather than only on suspicious ones.
18
+ */
19
+ /**
20
+ * The price impact above which a composed route is withdrawn, in percent.
21
+ *
22
+ * MEASURED RATHER THAN CHOSEN. Across the anchor and size combinations swept
23
+ * on 2026-09-21 — bridged wrapped ether, USD Coin, DAI and USDT into wrapped
24
+ * PulseChain coin, at roughly forty and one hundred and forty United States
25
+ * dollars — the impact ran from 0.008 percent to 0.037 percent, with bridged
26
+ * USDT the one outlier at 0.34 to 0.37 percent. Three percent is two orders of
27
+ * magnitude above the normal case and an order above the worst observed, so it
28
+ * cannot fire on an ordinary route; and it is still far below the point where
29
+ * a reader is quietly losing real money on a leg they cannot undo.
30
+ *
31
+ * See `docs/near-intents-conversion-composition.md` for the full sweep.
32
+ */
33
+ export declare const COMPOSED_ROUTE_MAX_PRICE_IMPACT_PERCENT = 3;
34
+ /**
35
+ * The end-to-end value loss above which a composed route is withdrawn, in
36
+ * percent of what the reader pays.
37
+ *
38
+ * A SECOND TEST BECAUSE THE FIRST CAN READ FINE ON A BROKEN POOL. Price impact
39
+ * is measured against a marginal reference quote taken from the same pool, so
40
+ * a pool that is uniformly mispriced — a stale pair, a pair holding almost
41
+ * nothing — reports a small impact while paying out far less than the tokens
42
+ * going in are worth. Only comparing the two ends of the journey catches that.
43
+ *
44
+ * Measured whole-journey loss on the same sweep ran from 0.81 to 1.36 percent,
45
+ * of which the swap fee alone is 0.29. Five percent leaves every observed
46
+ * route untouched and still stops a route that is quietly eating a twentieth
47
+ * of the reader's money.
48
+ */
49
+ export declare const COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT = 5;
50
+ /**
51
+ * The verdict on one composed route.
52
+ *
53
+ * THREE STATES RATHER THAN A BOOLEAN, because "we have not priced this yet"
54
+ * and "we priced it and it is bad" are different facts and a caller must be
55
+ * able to act differently on them. Collapsing them would either hide a route
56
+ * that is merely still loading or offer one nobody has checked.
57
+ */
58
+ export type ComposedRouteVerdict = {
59
+ readonly state: 'acceptable';
60
+ } | {
61
+ readonly state: 'unknown';
62
+ readonly missing: 'price-impact' | 'route-value';
63
+ } | {
64
+ readonly state: 'rejected';
65
+ readonly reason: 'price-impact' | 'value-loss';
66
+ /** The measured figure, in percent, that failed its ceiling. */
67
+ readonly measuredPercent: number;
68
+ /** The ceiling it failed. */
69
+ readonly ceilingPercent: number;
70
+ };
71
+ /**
72
+ * Judges a composed route against both ceilings.
73
+ *
74
+ * Checks price impact first and value loss second, because impact names the
75
+ * specific leg at fault while value loss only says the journey as a whole went
76
+ * wrong — so reporting impact when both fail is the more useful answer.
77
+ *
78
+ * @param options.priceImpactPercent - the swap leg's price impact, from the
79
+ * estimate's marginal reference quote; null when not yet quoted
80
+ * @param options.paidUsdValue - what the reader spends at the origin, in
81
+ * United States dollars; null when unpriced
82
+ * @param options.deliveredUsdValue - what the route delivers, valued on the
83
+ * SAME basis as `paidUsdValue`; null when unpriced
84
+ * @returns the verdict
85
+ */
86
+ export declare const assessComposedRoute: ({ priceImpactPercent, paidUsdValue, deliveredUsdValue, }: {
87
+ priceImpactPercent: number | null | undefined;
88
+ paidUsdValue: number | null | undefined;
89
+ deliveredUsdValue: number | null | undefined;
90
+ }) => ComposedRouteVerdict;
91
+ /**
92
+ * Whether a composed route may be listed on the strip.
93
+ *
94
+ * ONLY AN EXPLICIT PASS LISTS THE ROUTE. An unknown verdict withholds it, the
95
+ * same as a rejection, because a three-signature route nobody has checked is
96
+ * the case this gate exists for. The two remain distinct in
97
+ * {@link ComposedRouteVerdict} so a surface can tell the reader "still
98
+ * pricing" rather than "no route", which a boolean alone could not.
99
+ *
100
+ * @param verdict - the verdict from {@link assessComposedRoute}
101
+ * @returns true only when the route passed both ceilings
102
+ */
103
+ export declare const composedRouteMayBeOffered: (verdict: ComposedRouteVerdict) => boolean;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Whether a composed multi-signature route is good enough to offer at all.
3
+ *
4
+ * WHY A ROUTE NEEDS A GATE THAT A SINGLE CROSSING DOES NOT. A one-signature
5
+ * route that turns out badly costs the reader one transaction and they still
6
+ * hold something they chose. A composed route — an aggregator leg, then an
7
+ * omnibridge crossing, then a swap — strands them between signatures: after
8
+ * the first they hold an anchor token on a chain they did not ask for, and the
9
+ * only way out is to pay again. So a cheap first leg followed by a bad swap is
10
+ * worse than the route it replaced, and the difference has to be found BEFORE
11
+ * the route is listed rather than after the reader has signed.
12
+ *
13
+ * NOTHING HERE COSTS A REQUEST. Both figures the verdict reads are already in
14
+ * hand when a composer runs: the swap's price impact comes from the on-chain
15
+ * `getAmountsOut` pair the estimate already fetched, and the two dollar values
16
+ * come from the route's own basis. The gate is free, which is why it can run
17
+ * on every route rather than only on suspicious ones.
18
+ */
19
+ /**
20
+ * The price impact above which a composed route is withdrawn, in percent.
21
+ *
22
+ * MEASURED RATHER THAN CHOSEN. Across the anchor and size combinations swept
23
+ * on 2026-09-21 — bridged wrapped ether, USD Coin, DAI and USDT into wrapped
24
+ * PulseChain coin, at roughly forty and one hundred and forty United States
25
+ * dollars — the impact ran from 0.008 percent to 0.037 percent, with bridged
26
+ * USDT the one outlier at 0.34 to 0.37 percent. Three percent is two orders of
27
+ * magnitude above the normal case and an order above the worst observed, so it
28
+ * cannot fire on an ordinary route; and it is still far below the point where
29
+ * a reader is quietly losing real money on a leg they cannot undo.
30
+ *
31
+ * See `docs/near-intents-conversion-composition.md` for the full sweep.
32
+ */
33
+ export const COMPOSED_ROUTE_MAX_PRICE_IMPACT_PERCENT = 3;
34
+ /**
35
+ * The end-to-end value loss above which a composed route is withdrawn, in
36
+ * percent of what the reader pays.
37
+ *
38
+ * A SECOND TEST BECAUSE THE FIRST CAN READ FINE ON A BROKEN POOL. Price impact
39
+ * is measured against a marginal reference quote taken from the same pool, so
40
+ * a pool that is uniformly mispriced — a stale pair, a pair holding almost
41
+ * nothing — reports a small impact while paying out far less than the tokens
42
+ * going in are worth. Only comparing the two ends of the journey catches that.
43
+ *
44
+ * Measured whole-journey loss on the same sweep ran from 0.81 to 1.36 percent,
45
+ * of which the swap fee alone is 0.29. Five percent leaves every observed
46
+ * route untouched and still stops a route that is quietly eating a twentieth
47
+ * of the reader's money.
48
+ */
49
+ export const COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT = 5;
50
+ /**
51
+ * Judges a composed route against both ceilings.
52
+ *
53
+ * Checks price impact first and value loss second, because impact names the
54
+ * specific leg at fault while value loss only says the journey as a whole went
55
+ * wrong — so reporting impact when both fail is the more useful answer.
56
+ *
57
+ * @param options.priceImpactPercent - the swap leg's price impact, from the
58
+ * estimate's marginal reference quote; null when not yet quoted
59
+ * @param options.paidUsdValue - what the reader spends at the origin, in
60
+ * United States dollars; null when unpriced
61
+ * @param options.deliveredUsdValue - what the route delivers, valued on the
62
+ * SAME basis as `paidUsdValue`; null when unpriced
63
+ * @returns the verdict
64
+ */
65
+ export const assessComposedRoute = ({ priceImpactPercent, paidUsdValue, deliveredUsdValue, }) => {
66
+ if (typeof priceImpactPercent !== 'number' || !Number.isFinite(priceImpactPercent)) {
67
+ return { state: 'unknown', missing: 'price-impact' };
68
+ }
69
+ if (priceImpactPercent > COMPOSED_ROUTE_MAX_PRICE_IMPACT_PERCENT) {
70
+ return {
71
+ state: 'rejected',
72
+ reason: 'price-impact',
73
+ measuredPercent: priceImpactPercent,
74
+ ceilingPercent: COMPOSED_ROUTE_MAX_PRICE_IMPACT_PERCENT,
75
+ };
76
+ }
77
+ const bothValuesKnown = typeof paidUsdValue === 'number' &&
78
+ Number.isFinite(paidUsdValue) &&
79
+ paidUsdValue > 0 &&
80
+ typeof deliveredUsdValue === 'number' &&
81
+ Number.isFinite(deliveredUsdValue);
82
+ if (!bothValuesKnown) {
83
+ return { state: 'unknown', missing: 'route-value' };
84
+ }
85
+ const lossPercent = ((paidUsdValue - deliveredUsdValue) / paidUsdValue) * 100;
86
+ if (lossPercent > COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT) {
87
+ return {
88
+ state: 'rejected',
89
+ reason: 'value-loss',
90
+ measuredPercent: lossPercent,
91
+ ceilingPercent: COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT,
92
+ };
93
+ }
94
+ return { state: 'acceptable' };
95
+ };
96
+ /**
97
+ * Whether a composed route may be listed on the strip.
98
+ *
99
+ * ONLY AN EXPLICIT PASS LISTS THE ROUTE. An unknown verdict withholds it, the
100
+ * same as a rejection, because a three-signature route nobody has checked is
101
+ * the case this gate exists for. The two remain distinct in
102
+ * {@link ComposedRouteVerdict} so a surface can tell the reader "still
103
+ * pricing" rather than "no route", which a boolean alone could not.
104
+ *
105
+ * @param verdict - the verdict from {@link assessComposedRoute}
106
+ * @returns true only when the route passed both ceilings
107
+ */
108
+ export const composedRouteMayBeOffered = (verdict) => verdict.state === 'acceptable';
@@ -0,0 +1,88 @@
1
+ import type { Token } from '../types.js';
2
+ /**
3
+ * Which anchor tokens a non-executing aggregator may deliver on an omnibridge
4
+ * entry chain, and what the reader's own crossing signature then does with
5
+ * each one.
6
+ *
7
+ * WHY THE ANCHOR HAS TO BE A CHOICE RATHER THAN A DERIVATION. A two-signature
8
+ * route quotes towards one entry-chain token, taken straight from the
9
+ * destination pair. For a native-PulseChain destination that token is
10
+ * WRAPPED ether, because the omnibridge home list holds no entry for the
11
+ * native coin at all — measured on 2026-09-21, 657 Ethereum-side entries and
12
+ * not one at the zero address — so the pair a native destination resolves to
13
+ * is `WETH ↔ WETH from Ethereum`.
14
+ *
15
+ * NEAR Intents has NO LIQUIDITY IN WRAPPED ETHER ON ETHEREUM. Measured at
16
+ * 0.015, 0.05, 0.2 and 0.5 ether of input, every request answered
17
+ * `No liquidity available`. It carries the NATIVE coin on the same chain with
18
+ * plenty. So the one derived anchor is the one anchor the service cannot
19
+ * price, the quote is refused, and the route withdraws in silence — which is
20
+ * the correct response to a refusal and is also what hid this from us.
21
+ *
22
+ * WRAPPED NATIVE AND THE NATIVE COIN LAND IN THE SAME PLACE, which is what
23
+ * makes the substitution safe rather than merely convenient. The mediator's
24
+ * native router wraps on the way in: `wrapAndRelayTokens` takes the coin as
25
+ * transaction value and the crossing mints the SAME PulseChain token the
26
+ * wrapped entry would have minted. See `settings.ts`, the
27
+ * `assetIn.address === zeroAddress` branch. So swapping one anchor for the
28
+ * other changes what the aggregator delivers and changes nothing downstream.
29
+ *
30
+ * IT IS ALSO CHEAPER AND SHORTER, on both legs. Measured from Base native
31
+ * ether on 2026-09-21, one request batch per size:
32
+ *
33
+ * | anchor on Ethereum | 0.015 ether | 0.05 ether | 0.2 ether |
34
+ * | ------------------ | ----------- | ---------- | --------- |
35
+ * | native ether | 0.494% | 0.330% | 0.278% |
36
+ * | USD Coin | 1.037% | 0.651% | 0.509% |
37
+ * | Tether | 1.052% | 0.665% | 0.506% |
38
+ * | wrapped ether | refused | refused | refused |
39
+ *
40
+ * And the native coin needs no allowance, so its crossing is ONE signature
41
+ * where an entry-chain token's is an approval followed by a relay.
42
+ */
43
+ /** What the reader's crossing signature calls with a given anchor. */
44
+ export type EntryChainCrossingCall =
45
+ /** Native coin sent as transaction value; the router wraps and relays it. */
46
+ 'wrapAndRelayTokens'
47
+ /** An entry-chain token handed to the mediator, which needs an allowance first. */
48
+ | 'relayTokens';
49
+ /** One anchor the aggregator may be asked to deliver, and its consequences. */
50
+ export type EntryChainAnchor = {
51
+ /** The token the aggregator delivers into the reader's own wallet. */
52
+ readonly token: Token;
53
+ /** What the reader's crossing signature calls with it. */
54
+ readonly crossingCall: EntryChainCrossingCall;
55
+ /**
56
+ * Whether the crossing needs a separate approval transaction first, which
57
+ * is an EXTRA signature on a route that already asks for several.
58
+ */
59
+ readonly requiresApproval: boolean;
60
+ /** The PulseChain token the crossing mints. Identical across every anchor here. */
61
+ readonly landsAs: Token;
62
+ };
63
+ /**
64
+ * Builds the ordered anchor list for one destination pair, cheapest and
65
+ * shortest first.
66
+ *
67
+ * The list is ordered, not ranked: a caller asks the aggregator for the first
68
+ * entry and moves to the next only on a refusal. That keeps the cost at ONE
69
+ * vendor request in the ordinary case, which matters against a limit of
70
+ * twelve hundred requests per sixty seconds.
71
+ *
72
+ * The substitution is narrow ON PURPOSE. Only the chain's wrapped native
73
+ * asset gains a second anchor, because only the native coin mints the same
74
+ * PulseChain token. A USD Coin pair does not gain an ether anchor here —
75
+ * ether would land as bridged ether, a different token, needing a different
76
+ * swap path. That is a different DESTINATION PAIR, and choosing between pairs
77
+ * already has a home in `selectAnchorDestinationPair`.
78
+ *
79
+ * @param options.entryChainToken - the destination pair's entry-chain side
80
+ * @param options.pulsechainToken - the destination pair's PulseChain side
81
+ * @param options.entryChainId - the omnibridge entry chain's decimal id
82
+ * @returns the anchors to try, in order; never empty
83
+ */
84
+ export declare const entryChainAnchorsForPair: ({ entryChainToken, pulsechainToken, entryChainId, }: {
85
+ entryChainToken: Token;
86
+ pulsechainToken: Token;
87
+ entryChainId: number;
88
+ }) => readonly EntryChainAnchor[];
@@ -0,0 +1,55 @@
1
+ import { zeroAddress } from 'viem';
2
+ import { nativeAssetOut, nativeTokenSymbol, toChain } from '../config.js';
3
+ import { sameAddress } from '../ecosystems.js';
4
+ /**
5
+ * Builds the ordered anchor list for one destination pair, cheapest and
6
+ * shortest first.
7
+ *
8
+ * The list is ordered, not ranked: a caller asks the aggregator for the first
9
+ * entry and moves to the next only on a refusal. That keeps the cost at ONE
10
+ * vendor request in the ordinary case, which matters against a limit of
11
+ * twelve hundred requests per sixty seconds.
12
+ *
13
+ * The substitution is narrow ON PURPOSE. Only the chain's wrapped native
14
+ * asset gains a second anchor, because only the native coin mints the same
15
+ * PulseChain token. A USD Coin pair does not gain an ether anchor here —
16
+ * ether would land as bridged ether, a different token, needing a different
17
+ * swap path. That is a different DESTINATION PAIR, and choosing between pairs
18
+ * already has a home in `selectAnchorDestinationPair`.
19
+ *
20
+ * @param options.entryChainToken - the destination pair's entry-chain side
21
+ * @param options.pulsechainToken - the destination pair's PulseChain side
22
+ * @param options.entryChainId - the omnibridge entry chain's decimal id
23
+ * @returns the anchors to try, in order; never empty
24
+ */
25
+ export const entryChainAnchorsForPair = ({ entryChainToken, pulsechainToken, entryChainId, }) => {
26
+ const pairAnchor = {
27
+ token: entryChainToken,
28
+ crossingCall: 'relayTokens',
29
+ requiresApproval: true,
30
+ landsAs: pulsechainToken,
31
+ };
32
+ const entryChain = toChain(entryChainId);
33
+ // `nativeAssetOut` is typed as a total record and is not one — `toChain` is a
34
+ // cast, so an unmapped chain id yields a key that indexes to undefined. The
35
+ // runtime check outlives the static type on purpose.
36
+ const wrappedNativeAddress = nativeAssetOut[entryChain];
37
+ if (wrappedNativeAddress === undefined ||
38
+ !sameAddress(entryChainToken.address, wrappedNativeAddress, 'evm')) {
39
+ return [pairAnchor];
40
+ }
41
+ const nativeAnchor = {
42
+ token: {
43
+ // Decimals and name come from the WRAPPED token rather than from a
44
+ // constant, because the wrapped asset mirrors its coin exactly and a
45
+ // hard-coded eighteen would be a second source of the same fact.
46
+ ...entryChainToken,
47
+ address: zeroAddress,
48
+ symbol: nativeTokenSymbol[entryChain] ?? entryChainToken.symbol,
49
+ },
50
+ crossingCall: 'wrapAndRelayTokens',
51
+ requiresApproval: false,
52
+ landsAs: pulsechainToken,
53
+ };
54
+ return [nativeAnchor, pairAnchor];
55
+ };
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Formats a United States dollar number to a cent-precise string ("1,234.50",
3
+ * or "1.234,50" in German). No leading currency symbol — callers prepend it
4
+ * where the design needs one. The single source of truth shared by the raw
5
+ * producers (cent-rounding) and the display adapter (final formatting).
6
+ *
7
+ * `locale` defaults to `'en-US'` so every caller that does not read the
8
+ * reader's own language — the raw producers, and every existing test —
9
+ * keeps today's exact string. THE DISPLAY ADAPTER (`toRouteView.ts`) is the
10
+ * one caller that should pass the reader's own Lingui locale (`i18n.locale`):
11
+ * without it, the route strip's dollar figures stayed English-formatted
12
+ * inside every other translated screen, the one number on the page that
13
+ * never adopted the reader's language. The digit count never moves — only
14
+ * `toLocaleString`'s grouping and decimal separators do, per
15
+ * `minimumFractionDigits`/`maximumFractionDigits` staying pinned at 2.
16
+ *
17
+ * @param value - the United States dollar amount as a number
18
+ * @param locale - a BCP 47 locale tag to format with; defaults to `'en-US'`
19
+ * @returns the formatted two-decimal string, without a leading `$`
20
+ */
21
+ export declare const formatUsd: (value: number, locale?: string) => string;
22
+ /**
23
+ * Nullable {@link formatUsd}: null/undefined pass through as null so an unpriced
24
+ * value renders as absent rather than "0.00".
25
+ *
26
+ * @param value - the United States dollar amount, or null/undefined when unknown
27
+ * @param locale - a BCP 47 locale tag to format with; defaults to `'en-US'`
28
+ * @returns the formatted string, or null when the value is absent
29
+ */
30
+ export declare const formatUsdNullable: (value: number | null | undefined, locale?: string) => string | null;
31
+ export declare const roundUsdToCents: (value: number) => number;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Formats a United States dollar number to a cent-precise string ("1,234.50",
3
+ * or "1.234,50" in German). No leading currency symbol — callers prepend it
4
+ * where the design needs one. The single source of truth shared by the raw
5
+ * producers (cent-rounding) and the display adapter (final formatting).
6
+ *
7
+ * `locale` defaults to `'en-US'` so every caller that does not read the
8
+ * reader's own language — the raw producers, and every existing test —
9
+ * keeps today's exact string. THE DISPLAY ADAPTER (`toRouteView.ts`) is the
10
+ * one caller that should pass the reader's own Lingui locale (`i18n.locale`):
11
+ * without it, the route strip's dollar figures stayed English-formatted
12
+ * inside every other translated screen, the one number on the page that
13
+ * never adopted the reader's language. The digit count never moves — only
14
+ * `toLocaleString`'s grouping and decimal separators do, per
15
+ * `minimumFractionDigits`/`maximumFractionDigits` staying pinned at 2.
16
+ *
17
+ * @param value - the United States dollar amount as a number
18
+ * @param locale - a BCP 47 locale tag to format with; defaults to `'en-US'`
19
+ * @returns the formatted two-decimal string, without a leading `$`
20
+ */
21
+ export const formatUsd = (value, locale = 'en-US') => value.toLocaleString(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
22
+ /**
23
+ * Nullable {@link formatUsd}: null/undefined pass through as null so an unpriced
24
+ * value renders as absent rather than "0.00".
25
+ *
26
+ * @param value - the United States dollar amount, or null/undefined when unknown
27
+ * @param locale - a BCP 47 locale tag to format with; defaults to `'en-US'`
28
+ * @returns the formatted string, or null when the value is absent
29
+ */
30
+ export const formatUsdNullable = (value, locale) => value === null || value === undefined ? null : formatUsd(value, locale);
31
+ /**
32
+ * Rounds a United States dollar amount to whole cents.
33
+ *
34
+ * This replaces cent-rounding that went through the DISPLAY formatter and parsed
35
+ * the result back — `Number(formatUsd(value).replace(/,/g, ''))`. That works
36
+ * only while the display is pinned to a locale which groups with commas and
37
+ * decimalises with a dot. The moment money follows the reader's language, a
38
+ * German build formats 1234.56 as "1.234,56", and stripping the commas from THAT
39
+ * yields 1.23456: a fee understated by a factor of a thousand, silently, with no
40
+ * NaN anywhere to make it visible.
41
+ *
42
+ * It keeps its OWN formatter rather than borrowing the display one, pinned to a
43
+ * fixed locale and with grouping switched off, so the string it parses has no
44
+ * separators to strip and no locale to disagree with. That also makes it exact
45
+ * rather than merely close: `Math.round(value * 100) / 100` looks equivalent and
46
+ * is not, because it rounds the binary double while the formatter rounds the
47
+ * decimal the reader typed — they disagree on 1.005, giving 1.00 against 1.01.
48
+ * Matching the old behaviour to the cent is the point, so the old engine stays.
49
+ *
50
+ * @param value - the United States dollar amount
51
+ * @returns the amount rounded to two decimal places
52
+ */
53
+ const centRounder = new Intl.NumberFormat('en-US', {
54
+ minimumFractionDigits: 2,
55
+ maximumFractionDigits: 2,
56
+ useGrouping: false,
57
+ });
58
+ export const roundUsdToCents = (value) => Number(centRounder.format(value));
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Framework-agnostic routing core for the Gibs bridge. Holds the raw numeric
3
+ * primitives, the candidate shape, and candidate construction — none of which
4
+ * depend on React or any display formatting. Display-coupled route builders
5
+ * remain in the user interface until the raw-versus-view RouteOption split.
6
+ */
7
+ export { netDeliveredAmount, restateInDecimals, valueEquivalentAmount } from './amounts.js';
8
+ export { omnibridgeProviderForEntryChain, pulseXConversionSwapPath } from './providers.js';
9
+ export { buildDirectCrossingCandidate } from './candidate.js';
10
+ export type { RouteCandidate, DeferredCrossing, DirectCrossingCandidateInputs } from './candidate.js';
11
+ export { buildRouteDataForCandidate, routeSignatureCount } from './candidate-route.js';
12
+ export { COMPOSED_ROUTE_MAX_PRICE_IMPACT_PERCENT, COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT, assessComposedRoute, composedRouteMayBeOffered, } from './composed-route-quality.js';
13
+ export type { ComposedRouteVerdict } from './composed-route-quality.js';
14
+ export { entryChainAnchorsForPair } from './entry-chain-anchor.js';
15
+ export type { EntryChainAnchor, EntryChainCrossingCall } from './entry-chain-anchor.js';
16
+ export type { EstimateBasis, RouteData, RouteHopData, RouteKind, RouteOutputData, RouteSectionData, RouteTokenRef, } from './route-data.js';
17
+ export { usdValueOfAmount } from './usd-value.js';
18
+ export { formatUsd, formatUsdNullable } from './format.js';
19
+ export { BRIDGE_SETTLEMENT_ETA_MINUTES, PULSEX_MARGINAL_REFERENCE_DIVISOR, pulsechainChainId, addUsdValues, providerDisplayName, resolveEntryChainName, buildCandidateRouteData, buildConversionRouteData, buildDirectRouteData, buildPulseXSwapEstimateData, buildPulseXSwapSectionData, buildOutboundRouteData, buildSameChainSwapRouteData, candidateDeliveredUsd, candidateNetValueUsd, candidateOutsideFeesUsd, candidatePaidUsd, } from './producers.js';
20
+ export type { ConversionRouteComposition, PulseXSwapEstimateData, PulseXSwapSectionDataRoots, OutboundRouteRoots, OutboundOnwardHop, DirectRouteRoots, CrossingFeeRoots, SameChainSwapRouteRoots, } from './producers.js';
21
+ export { RANKABLE_ESTIMATE_BASES, assetNodeKey, combineComposedPathBasis, compareRoutePlansByDeliveredAmount, groupHopsBySignature, hopComposedBasis, hopEdgeKey, isConfirmedBasis, isRankableBasis, isRateLimitedCarrier, planComposedBasis, planIsConfirmed, planIsRankable, routePlanPathKey, } from './search/graph.js';
22
+ export type { AssetNode, CarrierId, ComposedPathBasis, HopCarrier, HopEdge, HopEdgeOrigin, HopFloor, HopOutcome, HopRequest, PlannedHop, RoutePlan, } from './search/graph.js';
23
+ export { composableHopBasis, composeRouteDelivery, hopOutputAmount, } from './search/compose.js';
24
+ export type { ComposableHop, ComposableHopFigure, ComposeRefusalReason, ComposedRouteDelivery, ExchangeRate, } from './search/compose.js';
25
+ export { GAS_TOPUP_PENALTY_FLOOR_USD, GAS_TOPUP_PENALTY_PPM, SIGNATURE_PENALTY_FLOOR_USD, SIGNATURE_PENALTY_PPM, compareRoutePlansByGasAdjustedDeliveredAmount, compareRoutePlansByScoreAdjustedDeliveredAmount, gasAdjustedDeliveredAmount, scoreRoutePlan, sectionIsFundedByLanding, waitPenaltyPpm, zeroGasCostFor, } from './search/score.js';
26
+ export type { GasCostFor, GasCostQuote, RouteScoreInputs } from './search/score.js';
27
+ export { buildRouteCandidateFromPlan, buildRouteDataFromPlan, buildRouteScoreInputsFromPlan, deriveDeferredCrossingFromPlan, deriveEntryChainIdFromPlan, deriveOmnibridgeProviderFromPlan, deriveViaChainIdsFromPlan, derivePlanKind, } from './search/plan-route-data.js';
28
+ export type { PlanCandidateInputs, PlanRouteDataInputs, PlanSectionPricing, PlanValuation, ResolveNodeToken, } from './search/plan-route-data.js';
29
+ export { createOmnibridgeCarrier, withOmnibridgeEntryFusing, } from './carriers/omnibridge.js';
30
+ export type { BridgeablePair, CreateOmnibridgeCarrierOptions, } from './carriers/omnibridge.js';
31
+ export { createPulseXCarrier, pulseXPoolPath, pulseXRouterForEdge, pulseXVenues } from './carriers/pulsex.js';
32
+ export type { CreatePulseXCarrierOptions, PulseXVenue } from './carriers/pulsex.js';
33
+ export { createWrapCarrier } from './carriers/wrap.js';
34
+ export type { CarrierAskAnswer, CarrierExactResult } from './carriers/ask-answer.js';
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Framework-agnostic routing core for the Gibs bridge. Holds the raw numeric
3
+ * primitives, the candidate shape, and candidate construction — none of which
4
+ * depend on React or any display formatting. Display-coupled route builders
5
+ * remain in the user interface until the raw-versus-view RouteOption split.
6
+ */
7
+ export { netDeliveredAmount, restateInDecimals, valueEquivalentAmount } from './amounts.js';
8
+ export { omnibridgeProviderForEntryChain, pulseXConversionSwapPath } from './providers.js';
9
+ export { buildDirectCrossingCandidate } from './candidate.js';
10
+ export { buildRouteDataForCandidate, routeSignatureCount } from './candidate-route.js';
11
+ export { COMPOSED_ROUTE_MAX_PRICE_IMPACT_PERCENT, COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT, assessComposedRoute, composedRouteMayBeOffered, } from './composed-route-quality.js';
12
+ export { entryChainAnchorsForPair } from './entry-chain-anchor.js';
13
+ export { usdValueOfAmount } from './usd-value.js';
14
+ export { formatUsd, formatUsdNullable } from './format.js';
15
+ export { BRIDGE_SETTLEMENT_ETA_MINUTES, PULSEX_MARGINAL_REFERENCE_DIVISOR, pulsechainChainId, addUsdValues, providerDisplayName, resolveEntryChainName, buildCandidateRouteData, buildConversionRouteData, buildDirectRouteData, buildPulseXSwapEstimateData, buildPulseXSwapSectionData, buildOutboundRouteData, buildSameChainSwapRouteData, candidateDeliveredUsd, candidateNetValueUsd, candidateOutsideFeesUsd, candidatePaidUsd, } from './producers.js';
16
+ // The route-search engine's pure data model: node/edge shapes, path keying,
17
+ // signature grouping, and the basis/rankability rules. The SEARCH ORCHESTRATION
18
+ // built on top of this (`capabilities.ts`, `enumerate.ts`, `waves.ts`) now
19
+ // lives in `@gibs/quotes`'s `search/` directory, carrier-agnostic — see
20
+ // `docs/quote-service.md`'s "Carriers" section. This module stays here
21
+ // because `search/plan-route-data.ts` (gibs's presentation layer, below)
22
+ // calls its functions directly and `bridge-sdk` cannot depend on `@gibs/quotes`
23
+ // (which already depends on `bridge-sdk`) without a circular package
24
+ // dependency; `@gibs/quotes`'s `types.ts` re-exports the same declarations
25
+ // under its `Carrier*` names.
26
+ export { RANKABLE_ESTIMATE_BASES, assetNodeKey, combineComposedPathBasis, compareRoutePlansByDeliveredAmount, groupHopsBySignature, hopComposedBasis, hopEdgeKey, isConfirmedBasis, isRankableBasis, isRateLimitedCarrier, planComposedBasis, planIsConfirmed, planIsRankable, routePlanPathKey, } from './search/graph.js';
27
+ export { composableHopBasis, composeRouteDelivery, hopOutputAmount, } from './search/compose.js';
28
+ export { GAS_TOPUP_PENALTY_FLOOR_USD, GAS_TOPUP_PENALTY_PPM, SIGNATURE_PENALTY_FLOOR_USD, SIGNATURE_PENALTY_PPM, compareRoutePlansByGasAdjustedDeliveredAmount, compareRoutePlansByScoreAdjustedDeliveredAmount, gasAdjustedDeliveredAmount, scoreRoutePlan, sectionIsFundedByLanding, waitPenaltyPpm, zeroGasCostFor, } from './search/score.js';
29
+ // General route search — Phase 1: plan-route-data.ts. Turns a priced
30
+ // RoutePlan into the same RouteData/RouteCandidate/RouteScoreInputs shapes
31
+ // today's four hand-written producers emit — see that module's head doc.
32
+ export { buildRouteCandidateFromPlan, buildRouteDataFromPlan, buildRouteScoreInputsFromPlan, deriveDeferredCrossingFromPlan, deriveEntryChainIdFromPlan, deriveOmnibridgeProviderFromPlan, deriveViaChainIdsFromPlan, derivePlanKind, } from './search/plan-route-data.js';
33
+ // The route-search maximization ENGINE — carrier registration
34
+ // (`buildCapabilityIndex`), the depth-first search (`enumerateRoutes`) and
35
+ // the wave-based quoting orchestrator (`searchRouteWaves`) — moved to
36
+ // `@gibs/quotes`'s `search/` directory (`docs/quote-service.md`'s
37
+ // "Carriers" section). Import `buildCapabilityIndex`, `enumerateRoutes`,
38
+ // `searchRouteWaves` and their types from `@gibs/quotes/search`. Gibs's own
39
+ // carriers (the omnibridge crossing, PulseX) live in `./carriers/`, below.
40
+ export { createOmnibridgeCarrier, withOmnibridgeEntryFusing, } from './carriers/omnibridge.js';
41
+ export { createPulseXCarrier, pulseXPoolPath, pulseXRouterForEdge, pulseXVenues } from './carriers/pulsex.js';
42
+ export { createWrapCarrier } from './carriers/wrap.js';