@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,43 @@
1
+ import type { RouteCandidate } from './candidate.js';
2
+ import type { RouteData } from './route-data.js';
3
+ /**
4
+ * One door in front of the two base route builders.
5
+ *
6
+ * WHY THIS EXISTS. A candidate is drawn by one of two builders and the choice
7
+ * is not the caller's to make by hand. {@link buildCandidateRouteData} draws a
8
+ * candidate as ONE signature, because the aggregator's executor performs the
9
+ * omnibridge entry call on arrival. {@link buildNearIntentsTwoStepRouteData}
10
+ * draws it as TWO, because the aggregator that carried it cannot execute
11
+ * anything on the chain it delivers to, so the reader signs the crossing
12
+ * themselves.
13
+ *
14
+ * Every composer that builds on top of a candidate — the PulseX conversion
15
+ * route is the first — has to pick between them, and picking wrong is not a
16
+ * cosmetic error. A two-signature candidate run through the one-signature
17
+ * builder produces a route that claims the crossing happens inside the
18
+ * transaction the reader already signed. It does not. The reader would select
19
+ * that route, sign once, and be left holding the anchor token on the entry
20
+ * chain with the interface reporting the journey finished.
21
+ *
22
+ * `candidate.deferredCrossing` is the fact that decides it, and it is the same
23
+ * field {@link buildNearIntentsTwoStepRouteData} refuses to build without. So
24
+ * the two agree by construction rather than by two callers remembering the
25
+ * same rule.
26
+ *
27
+ * @param candidate - the priced route candidate
28
+ * @returns the raw route data, or null when the candidate names a deferred
29
+ * crossing but no omnibridge provider to perform it
30
+ */
31
+ export declare const buildRouteDataForCandidate: (candidate: RouteCandidate) => RouteData | null;
32
+ /**
33
+ * How many wallet signatures a raw route asks the reader for.
34
+ *
35
+ * Counts the sections, because one section is one signature — that is what a
36
+ * section means. Named rather than left as `sections.length` at each call
37
+ * site, so a surface that warns about a long journey and a surface that ranks
38
+ * routes cannot come to different counts.
39
+ *
40
+ * @param route - the raw route data
41
+ * @returns the number of signatures, one per section
42
+ */
43
+ export declare const routeSignatureCount: (route: RouteData) => number;
@@ -0,0 +1,45 @@
1
+ import { buildNearIntentsTwoStepRouteData } from '../near-intents-route.js';
2
+ import { buildCandidateRouteData } from './producers.js';
3
+ /**
4
+ * One door in front of the two base route builders.
5
+ *
6
+ * WHY THIS EXISTS. A candidate is drawn by one of two builders and the choice
7
+ * is not the caller's to make by hand. {@link buildCandidateRouteData} draws a
8
+ * candidate as ONE signature, because the aggregator's executor performs the
9
+ * omnibridge entry call on arrival. {@link buildNearIntentsTwoStepRouteData}
10
+ * draws it as TWO, because the aggregator that carried it cannot execute
11
+ * anything on the chain it delivers to, so the reader signs the crossing
12
+ * themselves.
13
+ *
14
+ * Every composer that builds on top of a candidate — the PulseX conversion
15
+ * route is the first — has to pick between them, and picking wrong is not a
16
+ * cosmetic error. A two-signature candidate run through the one-signature
17
+ * builder produces a route that claims the crossing happens inside the
18
+ * transaction the reader already signed. It does not. The reader would select
19
+ * that route, sign once, and be left holding the anchor token on the entry
20
+ * chain with the interface reporting the journey finished.
21
+ *
22
+ * `candidate.deferredCrossing` is the fact that decides it, and it is the same
23
+ * field {@link buildNearIntentsTwoStepRouteData} refuses to build without. So
24
+ * the two agree by construction rather than by two callers remembering the
25
+ * same rule.
26
+ *
27
+ * @param candidate - the priced route candidate
28
+ * @returns the raw route data, or null when the candidate names a deferred
29
+ * crossing but no omnibridge provider to perform it
30
+ */
31
+ export const buildRouteDataForCandidate = (candidate) => candidate.deferredCrossing
32
+ ? buildNearIntentsTwoStepRouteData(candidate)
33
+ : buildCandidateRouteData(candidate);
34
+ /**
35
+ * How many wallet signatures a raw route asks the reader for.
36
+ *
37
+ * Counts the sections, because one section is one signature — that is what a
38
+ * section means. Named rather than left as `sections.length` at each call
39
+ * site, so a surface that warns about a long journey and a surface that ranks
40
+ * routes cannot come to different counts.
41
+ *
42
+ * @param route - the raw route data
43
+ * @returns the number of signatures, one per section
44
+ */
45
+ export const routeSignatureCount = (route) => route.sections.length;
@@ -0,0 +1,237 @@
1
+ import type { ConsolidatedQuote, ConsolidatedQuoteRequest } from '../consolidated-quote.js';
2
+ import type { Provider } from '../config.js';
3
+ import type { Token } from '../types.js';
4
+ import type { RoutePlan } from './search/graph.js';
5
+ /**
6
+ * One priced route candidate: the consolidated (aggregator) quote for the leg
7
+ * from the source chain to an omnibridge entry point, paired with the context
8
+ * needed to describe the omnibridge leg that carries it the rest of the way to
9
+ * PulseChain. The hook builds one of these per entry point it can quote.
10
+ */
11
+ export type RouteCandidate = {
12
+ /** Stable identity for keys and selection, unique within one strip render. */
13
+ id: string;
14
+ /** The aggregator quote for the source → entry-point leg. */
15
+ quote: ConsolidatedQuote;
16
+ /**
17
+ * The bridge provider that crosses from the entry point to PulseChain.
18
+ *
19
+ * NULL ON A ROUTE THAT CROSSES NO BRIDGE. A candidate whose destination the
20
+ * omnibridge does not reach is carried entirely by the aggregator: there is
21
+ * no second leg, so there is no bridge to name. Null rather than a stand-in
22
+ * string, because every reader of this field is asking which bridge performs
23
+ * that leg, and naming one that is not involved is worse than saying nothing.
24
+ *
25
+ * Typed `Provider | null`, not `string | null` — the source is
26
+ * `omnibridgeProviderForEntryChain`, which already returns `Provider | null`.
27
+ */
28
+ omnibridgeProvider: Provider | null;
29
+ /** The chain the omnibridge leg starts on (the entry point: Ethereum or BSC). */
30
+ entryChainId: number;
31
+ /**
32
+ * The source token the user pays with. Its symbol and resolvable image render
33
+ * the SOURCE node of the route's collapsed token flow — without it the source
34
+ * node would have no symbol (the normalized quote step carries only a raw token
35
+ * address) and no logo.
36
+ */
37
+ originToken: Token;
38
+ /** The token the user receives on PulseChain (the route's delivered token). */
39
+ destinationToken: Token;
40
+ /**
41
+ * The amount delivered on PulseChain after the omnibridge crossing fee, in base
42
+ * units. The aggregator delivers the request's `toAmount` onto the entry chain;
43
+ * the omnibridge takes its crossing fee before it lands, so this is the NET the
44
+ * user receives — and the directly comparable "amount delivered" the routes
45
+ * rank on (cheapest total wins).
46
+ */
47
+ deliveredAmount: bigint;
48
+ /**
49
+ * The omnibridge crossing fee in delivered-token base units — the amount the
50
+ * omnibridge takes between the entry chain and PulseChain (`toAmount` minus
51
+ * {@link deliveredAmount}). Carried so the route's detail view can show the
52
+ * BSC→PulseChain / Ethereum→PulseChain crossing fee as its OWN line, computed
53
+ * from the known on-chain fee rate rather than inferred from a price spread.
54
+ * Zero when the fee rate has not loaded.
55
+ */
56
+ omnibridgeFeeAmount: bigint;
57
+ /** Per-PulseChain-token United States dollar price, or null when unpriced. */
58
+ destinationUsdPrice: number | null;
59
+ /**
60
+ * The amount the user actually TYPED, in origin token base units — as distinct
61
+ * from `quote.fromAmount`, which is what the quote turned out to need.
62
+ *
63
+ * The two differ because the quote is solved BACKWARD to a delivery target, and
64
+ * that target is the provider's guaranteed floor rather than its expectation.
65
+ * Backward-solving to the floor consumes less than was typed, and the remainder
66
+ * stays in the user's wallet. Carried here so the route can NAME that remainder
67
+ * instead of letting it show up as an unexplained gap between the amount the
68
+ * form prices and the value the route reports delivering.
69
+ *
70
+ * Null or absent when the caller does not know it; the route then says nothing
71
+ * about unspent tokens rather than guessing at a figure.
72
+ */
73
+ requestedAmount?: bigint | null;
74
+ /**
75
+ * The crossing the reader has to come back and sign for themselves.
76
+ *
77
+ * ABSENT ON EVERY ROUTE THAT TAKES ONE SIGNATURE, which is nearly all of
78
+ * them. An aggregator that can execute a call on the chain it delivers to is
79
+ * handed the omnibridge entry call, performs it on arrival, and the whole
80
+ * journey is one signature — so there is no second crossing to describe.
81
+ *
82
+ * Present when the aggregator CANNOT execute on arrival. It then delivers the
83
+ * anchor token into the reader's own wallet on the entry chain and stops, and
84
+ * the omnibridge crossing becomes a second signature the reader makes later.
85
+ * The route must say so on its face rather than in a tooltip, which is what
86
+ * this field drives: see `buildNearIntentsTwoStepRouteData`.
87
+ *
88
+ * It carries the ANCHOR — what lands on the entry chain — because nothing
89
+ * else on this candidate does. `destinationToken` is the far side of the
90
+ * crossing and `originToken` the near side of the swap; the token in the
91
+ * middle, which is the thing the reader is left holding between the two
92
+ * signatures, appears nowhere else.
93
+ */
94
+ deferredCrossing?: DeferredCrossing | null;
95
+ /**
96
+ * The consolidated quote REQUEST this candidate's `quote` actually answers,
97
+ * when that differs from the entry's shared one-signature `request`.
98
+ *
99
+ * ABSENT ON EVERY ONE-SIGNATURE CANDIDATE. Every candidate quoted through the
100
+ * competed field (`quotedCandidates` in `useRouteOptions.ts`) answers that
101
+ * entry's own `request` — the omnibridge entry call aimed at PulseChain — so
102
+ * the caller already has the right request and this field would only repeat
103
+ * it.
104
+ *
105
+ * PRESENT ON A TWO-STEP CANDIDATE (one with {@link deferredCrossing} set).
106
+ * That candidate's `quote` answers a DIFFERENT question — `twoStepRequest`
107
+ * in `useRouteOptions.ts`, which targets the ENTRY chain and the reader's
108
+ * own wallet, not PulseChain. A caller that re-quotes or records against the
109
+ * entry's shared `request` instead would ask a live provider to reprice a
110
+ * PulseChain delivery this candidate never offered, and would write a resume
111
+ * record naming the wrong destination token and chain. Every caller that
112
+ * commits or records a SELECTED candidate must read `quoteRequest` first and
113
+ * fall back to the entry's `request` only when this is absent.
114
+ */
115
+ quoteRequest?: ConsolidatedQuoteRequest;
116
+ /**
117
+ * The general-search plan this candidate was built from, when it came from
118
+ * one — see the route-search design, section 1.
119
+ *
120
+ * ABSENT ON EVERY CANDIDATE PHASE 0 PRODUCES. Nothing builds a
121
+ * {@link RoutePlan} yet; the field exists so the shape it will fill is
122
+ * fixed before Phase 1's `enumerate.ts` starts producing plans. This is
123
+ * ALSO WHY IT IS OPTIONAL RATHER THAN REQUIRED: today's producers
124
+ * (`useAggregatorRouteCandidate`, `buildDirectCrossingCandidate`,
125
+ * `buildNearIntentsTwoStepRouteData`'s caller, and every stored `History`
126
+ * record replayed through `storedShapes`) build a `RouteCandidate` with no
127
+ * plan at all, and a required field would break every one of them on this
128
+ * phase landing alone.
129
+ *
130
+ * ONCE A CANDIDATE CARRIES A PLAN, three fields above stop being
131
+ * independently set and are instead DERIVED from it, so there is exactly
132
+ * one source of truth for what the candidate's route actually does:
133
+ * - {@link deferredCrossing} — present when `plan.signatures` has more than
134
+ * one group, taken from the first hop of the second group (the anchor
135
+ * the reader is left holding between signatures).
136
+ * - {@link entryChainId} — the `to` node's `chainId` of the plan's last
137
+ * hop whose carrier is `'pulsechain'` or `'tokensex'` (its bridge hop).
138
+ * - {@link omnibridgeProvider} — that same hop's `carrier`, or null when
139
+ * the plan crosses no bridge hop at all (a route the omnibridge does not
140
+ * reach, carried entirely by aggregators).
141
+ *
142
+ * A candidate built WITHOUT a plan keeps setting these three fields by
143
+ * hand, exactly as it does today; nothing about their existing meaning
144
+ * changes until a caller actually starts constructing plans.
145
+ */
146
+ plan?: RoutePlan;
147
+ };
148
+ /**
149
+ * The anchor a two-signature route leaves the reader holding between its legs.
150
+ *
151
+ * Named for the crossing that is deferred, not for the leg that is signed now,
152
+ * because the deferral is the fact a reader needs and the anchor is only how it
153
+ * is described.
154
+ */
155
+ export type DeferredCrossing = {
156
+ /** The token that lands on the entry chain, in the reader's own wallet. */
157
+ anchorToken: Token;
158
+ /** How much of it lands, in that token's base units. */
159
+ anchorAmount: bigint;
160
+ /**
161
+ * What the aggregator says that landing is worth, in United States dollars.
162
+ *
163
+ * The aggregator's OWN figure for the delivered side, paired with its own
164
+ * figure for the paid side. Two price feeds disagree by more than a small
165
+ * route's whole fee, so a section's cost is only a cost when both ends come
166
+ * from one answer.
167
+ */
168
+ anchorUsdValue: string | null;
169
+ };
170
+ /**
171
+ * The inputs for {@link buildDirectCrossingCandidate}: the entry chain that equals
172
+ * the origin chain, the origin token paid on that chain, the anchor token delivered
173
+ * on PulseChain, the raw amount in, the origin token's United States dollar price,
174
+ * and the omnibridge crossing fee rate.
175
+ */
176
+ export type DirectCrossingCandidateInputs = {
177
+ /** The omnibridge entry chain — MUST equal the origin chain for same-chain crossing. */
178
+ entryChainId: number;
179
+ /** The source chain the user's funds start on (equals entryChainId for this path). */
180
+ originChainId: number;
181
+ /** The source token the user pays with on the origin/entry chain. */
182
+ originToken: Token;
183
+ /**
184
+ * The anchor token delivered on PulseChain — the
185
+ * `effectiveEthereumDestination.pulsechainToken` or its Binance Smart Chain
186
+ * counterpart.
187
+ */
188
+ destinationToken: Token;
189
+ /** The raw amount the user is sending, in origin token base units. */
190
+ amountIn: bigint;
191
+ /** Per-origin-token United States dollar price, or null when unpriced. */
192
+ originUsdPrice: number | null;
193
+ /**
194
+ * Per-destination-token United States dollar price on PulseChain, or null when
195
+ * unpriced. Used to value the crossing fee and the delivered amount for ranking.
196
+ */
197
+ destinationUsdPrice: number | null;
198
+ /**
199
+ * The omnibridge foreign→home crossing fee rate (per 1e18) for this entry
200
+ * chain, or null when the fee config has not loaded.
201
+ */
202
+ omnibridgeFeePerEther: bigint | null;
203
+ };
204
+ /**
205
+ * Synthesizes a {@link RouteCandidate} for the SAME-CHAIN direct-omnibridge
206
+ * conversion case: when the origin chain IS one of the omnibridge entry chains
207
+ * (Ethereum chain 1 or Binance Smart Chain chain 56), there is no aggregator leg
208
+ * to quote — the user bridges directly from that chain to PulseChain. The
209
+ * same-chain skip in {@link useAggregatorRouteCandidate} is correct (nothing to
210
+ * quote), but it left the conversion path empty. This helper fills that gap by
211
+ * synthesizing a zero-step candidate that `usePulseXConversionRoutes` (the user
212
+ * interface's plural composer) can consume unchanged, producing the correct
213
+ * same-chain conversion route so the ranker can pick the cheapest option.
214
+ *
215
+ * The synthetic quote carries zero steps (no aggregator hops), a zero execution
216
+ * duration (the direct crossing needs no quote wait), and a null transaction
217
+ * request — this candidate is DISPLAY and RANKING only. The candidate's
218
+ * `deliveredAmount` is net of the omnibridge crossing fee, identical to the math
219
+ * {@link useAggregatorRouteCandidate} applies.
220
+ *
221
+ * This is a PURE exported helper (no hooks, no side effects) so it is
222
+ * unit-testable in isolation and can be called from two `useMemo` blocks in
223
+ * Bridge.tsx without duplicating the object literal.
224
+ *
225
+ * EXECUTION: when the route built from this candidate is selected, the
226
+ * consolidated (aggregator) execute path is deliberately NOT used — there is no
227
+ * aggregator leg for it to sign. `executesViaConsolidated`
228
+ * (`packages/ui/src/lib/state/bridgeExecution.ts`) recognizes the candidate's
229
+ * `entryChainId === originChainId` and routes the selection through the direct
230
+ * omnibridge sender instead, which builds its transaction from the live
231
+ * `useBridgeSettings` cascade rather than from this synthetic quote.
232
+ *
233
+ * @param inputs - the entry chain, origin/destination tokens, amount, and fee rate
234
+ * @returns the synthesized RouteCandidate, or throws when the entry chain is not
235
+ * a valid omnibridge entry point (caller must guard on `omnibridgeProviderForEntryChain`)
236
+ */
237
+ export declare const buildDirectCrossingCandidate: ({ entryChainId, originChainId, originToken, destinationToken, amountIn, originUsdPrice, destinationUsdPrice, omnibridgeFeePerEther, }: DirectCrossingCandidateInputs) => RouteCandidate;
@@ -0,0 +1,96 @@
1
+ import { formatUnits } from 'viem';
2
+ import { netDeliveredAmount } from './amounts.js';
3
+ import { omnibridgeProviderForEntryChain } from './providers.js';
4
+ /**
5
+ * Synthesizes a {@link RouteCandidate} for the SAME-CHAIN direct-omnibridge
6
+ * conversion case: when the origin chain IS one of the omnibridge entry chains
7
+ * (Ethereum chain 1 or Binance Smart Chain chain 56), there is no aggregator leg
8
+ * to quote — the user bridges directly from that chain to PulseChain. The
9
+ * same-chain skip in {@link useAggregatorRouteCandidate} is correct (nothing to
10
+ * quote), but it left the conversion path empty. This helper fills that gap by
11
+ * synthesizing a zero-step candidate that `usePulseXConversionRoutes` (the user
12
+ * interface's plural composer) can consume unchanged, producing the correct
13
+ * same-chain conversion route so the ranker can pick the cheapest option.
14
+ *
15
+ * The synthetic quote carries zero steps (no aggregator hops), a zero execution
16
+ * duration (the direct crossing needs no quote wait), and a null transaction
17
+ * request — this candidate is DISPLAY and RANKING only. The candidate's
18
+ * `deliveredAmount` is net of the omnibridge crossing fee, identical to the math
19
+ * {@link useAggregatorRouteCandidate} applies.
20
+ *
21
+ * This is a PURE exported helper (no hooks, no side effects) so it is
22
+ * unit-testable in isolation and can be called from two `useMemo` blocks in
23
+ * Bridge.tsx without duplicating the object literal.
24
+ *
25
+ * EXECUTION: when the route built from this candidate is selected, the
26
+ * consolidated (aggregator) execute path is deliberately NOT used — there is no
27
+ * aggregator leg for it to sign. `executesViaConsolidated`
28
+ * (`packages/ui/src/lib/state/bridgeExecution.ts`) recognizes the candidate's
29
+ * `entryChainId === originChainId` and routes the selection through the direct
30
+ * omnibridge sender instead, which builds its transaction from the live
31
+ * `useBridgeSettings` cascade rather than from this synthetic quote.
32
+ *
33
+ * @param inputs - the entry chain, origin/destination tokens, amount, and fee rate
34
+ * @returns the synthesized RouteCandidate, or throws when the entry chain is not
35
+ * a valid omnibridge entry point (caller must guard on `omnibridgeProviderForEntryChain`)
36
+ */
37
+ export const buildDirectCrossingCandidate = ({ entryChainId, originChainId, originToken, destinationToken, amountIn, originUsdPrice, destinationUsdPrice, omnibridgeFeePerEther, }) => {
38
+ const provider = omnibridgeProviderForEntryChain(entryChainId);
39
+ if (provider === null) {
40
+ throw new Error(`buildDirectCrossingCandidate: chain ${entryChainId} is not an omnibridge entry point`);
41
+ }
42
+ if (originChainId !== entryChainId) {
43
+ throw new Error(`buildDirectCrossingCandidate: originChainId must equal entryChainId (same-chain crossing only)`);
44
+ }
45
+ const delivered = netDeliveredAmount(amountIn, omnibridgeFeePerEther);
46
+ const omnibridgeFeeAmount = amountIn > delivered ? amountIn - delivered : 0n;
47
+ const fromAmountUsd = originUsdPrice !== null && amountIn > 0n
48
+ ? String(Number(formatUnits(amountIn, originToken.decimals)) * originUsdPrice)
49
+ : null;
50
+ const quoteId = `direct-omnibridge-${entryChainId}-${originChainId}`;
51
+ const syntheticQuote = {
52
+ // The synthetic quote wraps the omnibridge provider slug. ConsolidatedQuote's
53
+ // `provider` field is typed for the aggregator providers ('lifi' | 'relay'),
54
+ // but the functions that read it (`resolveProviderDisplayName`,
55
+ // `resolveProviderLogoUri`) accept any string at runtime. The cast is safe here
56
+ // because no aggregator-specific code path runs on a zero-step candidate —
57
+ // this synthetic quote is never itself signed; see the module doc comment
58
+ // above for where execution actually happens for the route built from it.
59
+ provider: provider,
60
+ quoteId,
61
+ tool: provider,
62
+ toolName: provider,
63
+ fromAmount: amountIn,
64
+ fromAmountUsd,
65
+ approvalAddress: null,
66
+ executionDurationSeconds: 0,
67
+ steps: [],
68
+ // A direct crossing carries no aggregator, so there is no carrier fee to
69
+ // report. The omnibridge's own crossing fee is `omnibridgeFeeAmount`
70
+ // beside it, which the route surfaces separately.
71
+ fees: [],
72
+ originDeposit: {
73
+ ecosystem: 'evm',
74
+ to: '0x0000000000000000000000000000000000000000',
75
+ data: '0x',
76
+ value: 0n,
77
+ chainId: originChainId,
78
+ gasLimit: null,
79
+ },
80
+ };
81
+ return {
82
+ id: quoteId,
83
+ quote: syntheticQuote,
84
+ omnibridgeProvider: provider,
85
+ entryChainId,
86
+ originToken,
87
+ destinationToken,
88
+ deliveredAmount: delivered,
89
+ omnibridgeFeeAmount,
90
+ destinationUsdPrice,
91
+ // A same-chain crossing has no aggregator leg to solve backward, so it spends
92
+ // exactly what was typed — requested and spent are the same figure by
93
+ // construction, and the route reports nothing unspent.
94
+ requestedAmount: amountIn,
95
+ };
96
+ };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * What answering one carrier's ask produces: a deliverable amount, or a
3
+ * refusal. Declared LOCALLY, rather than imported, so gibs's own carriers
4
+ * (`omnibridge.ts`, `pulsex.ts`) can pin their `ask` function's return type
5
+ * without `bridge-sdk` depending on `@gibs/quotes` — see
6
+ * `@gibs/quotes/types`'s own doc on why that dependency would be circular.
7
+ *
8
+ * STRUCTURALLY IDENTICAL to `@gibs/quotes`'s `CarrierAskAnswer` (itself an
9
+ * alias of `search/waves.ts`'s `AskEdgeAnswer`) — every field name and type
10
+ * matches exactly, which is what lets an object shaped like this satisfy
11
+ * `Carrier.ask`'s return type wherever a caller that imports both packages
12
+ * (`packages/ui`) assigns one of these carriers into a `Carrier[]` list. This
13
+ * file is the ONE place that shape is spelled out in `bridge-sdk`; every
14
+ * carrier factory here imports this type rather than re-declaring it.
15
+ */
16
+ export type CarrierAskAnswer = {
17
+ readonly ok: true;
18
+ /** What the edge delivers for the asked input, in `edge.to`'s base units. */
19
+ readonly deliveredAmount: bigint;
20
+ /** The GUARANTEED delivered amount after slippage, when known; null when not. */
21
+ readonly minimumDeliveredAmount: bigint | null;
22
+ /** The carrier's own claimed execution duration in seconds, or null. */
23
+ readonly claimedDurationSeconds: number | null;
24
+ } | {
25
+ readonly ok: false;
26
+ readonly refusal: {
27
+ readonly kind: 'no-quote' | 'could-not-ask' | 'cannot-carry' | 'below-minimum' | 'above-available';
28
+ /** The carrier's own words for why, or null when none was given. */
29
+ readonly reason: string | null;
30
+ /** A minimum amount this refusal names, in `edge.from`'s base units, or null. */
31
+ readonly minimumAmount: bigint | null;
32
+ };
33
+ };
34
+ /**
35
+ * What answering one carrier's SYNCHRONOUS, EXACT read produces for one edge
36
+ * at one input amount: a delivered amount, or a refusal. Declared LOCALLY for
37
+ * the same reason {@link CarrierAskAnswer} is — see that type's own doc.
38
+ *
39
+ * STRUCTURALLY IDENTICAL to `@gibs/quotes`'s `ExactHopResult`. `'refused'` is
40
+ * a REAL answer (the carrier's own local read says this amount cannot
41
+ * cross), distinct from the bare `null` a carrier's `exact` method may also
42
+ * return for an edge it has nothing to say about (its `prepare` was never
43
+ * called, or this edge fell outside what it read).
44
+ */
45
+ export type CarrierExactResult = {
46
+ readonly outcome: 'delivered';
47
+ /** What this edge delivers for exactly the requested input amount, in `edge.to`'s base units. */
48
+ readonly deliveredAmount: bigint;
49
+ /** How far this edge's executed rate falls below its marginal rate, in percent; absent when the carrier cannot measure it. */
50
+ readonly priceImpactPercent?: number;
51
+ } | {
52
+ readonly outcome: 'refused';
53
+ readonly refusal: {
54
+ readonly kind: 'no-quote' | 'could-not-ask' | 'cannot-carry' | 'below-minimum' | 'above-available';
55
+ /** The carrier's own words for why, or null when none was given. */
56
+ readonly reason: string | null;
57
+ /** A minimum amount this refusal names, in `edge.from`'s base units, or null. */
58
+ readonly minimumAmount: bigint | null;
59
+ };
60
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,137 @@
1
+ import type { PublicClient } from 'viem';
2
+ import { type EntryLimits } from '../../bridge-limits.js';
3
+ import type { Token } from '../../types.js';
4
+ import { type AssetNode, type HopEdge } from '../search/graph.js';
5
+ import type { CarrierAskAnswer, CarrierExactResult } from './ask-answer.js';
6
+ /**
7
+ * The omnibridge crossing (`'pulsechain'` — the Ethereum entry — and
8
+ * `'tokensex'` — the Binance Smart Chain entry) as a plug-in `Carrier` for
9
+ * `@gibs/quotes`'s route-search engine. GIBS-SPECIFIC — see
10
+ * `docs/quote-service.md`'s "Carriers" section: "never built in ... the
11
+ * omnibridge crossing." Registered from `packages/ui/src/lib/state/useRouteSearch.ts`
12
+ * / `route-search-quotes.ts`.
13
+ *
14
+ * This module returns a plain object shaped like `@gibs/quotes`'s `Carrier`
15
+ * type WITHOUT importing it — `bridge-sdk` cannot depend on `@gibs/quotes`
16
+ * (see `@gibs/quotes/types`'s own doc on the circular-dependency reason).
17
+ * `HopEdge`/`AssetNode`/`AskEdgeAnswer` here are the SAME declarations that
18
+ * package's `CarrierHopEdge`/`CarrierAssetNode`/`CarrierAskAnswer` alias, so
19
+ * the object is structurally identical to a `Carrier` wherever a caller that
20
+ * imports both packages assigns it into one.
21
+ */
22
+ /**
23
+ * One token the omnibridge bridges between its entry chain (Ethereum or
24
+ * Binance Smart Chain today; a testnet entry chain under `testnetPathways`)
25
+ * and PulseChain.
26
+ *
27
+ * NAMED `entryChainToken`, NOT `ethereumToken`. The UI's own
28
+ * `ConsolidatedDestinationPair` (`packages/ui/src/lib/stores/consolidated-tokens.ts`)
29
+ * keeps the older name for its Ethereum-only history; this module reuses its
30
+ * shape, not its name, because a Binance Smart Chain pair routed through the
31
+ * same field would otherwise carry an Ethereum-shaped name for a token that
32
+ * never touches Ethereum.
33
+ */
34
+ export type BridgeablePair = {
35
+ /** The token on the omnibridge's entry chain. */
36
+ readonly entryChainToken: Token;
37
+ /** That token's PulseChain-side representation. */
38
+ readonly pulsechainToken: Token;
39
+ /** The entry chain's decimal chain id — the chain `entryChainToken` lives on. */
40
+ readonly entryChainId: number;
41
+ };
42
+ /** What {@link createOmnibridgeCarrier} needs. */
43
+ export type CreateOmnibridgeCarrierOptions = {
44
+ /** Every token pair the omnibridge bridges, entry chain by entry chain. */
45
+ readonly bridgeablePairs: readonly BridgeablePair[];
46
+ /** Whether this is a production build — see `pathway()`'s own `isProd`. */
47
+ readonly isProductionNetwork: boolean;
48
+ /** Reads the entry mediator's bounds for one bridge-crossing edge — a free, local read. */
49
+ readonly readEntryLimits: (edge: HopEdge) => Promise<EntryLimits | null>;
50
+ /** Reads the omnibridge's own fee rate (per ether) for one bridge-crossing edge, in whichever direction it runs. */
51
+ readonly readFeePerEther: (edge: HopEdge) => Promise<bigint | null>;
52
+ /**
53
+ * Returns a client for a chain id, for {@link Carrier.prepare}'s own
54
+ * batched multicall reads — never called by `ask`, which keeps using
55
+ * {@link readEntryLimits}/{@link readFeePerEther} unchanged.
56
+ *
57
+ * OPTIONAL, ON PURPOSE. A host that has not wired a client lookup yet gets
58
+ * the carrier's original behavior: `prepare` is a no-op and `exact` always
59
+ * returns `null`, so `enumerate.ts` falls through to `estimate`/the
60
+ * price-feed default exactly as it did before either method existed.
61
+ *
62
+ * @param chainId - the chain a batched read needs a client for
63
+ * @returns a client, or null when this host has none for that chain
64
+ */
65
+ readonly clientForChain?: (chainId: number) => PublicClient | null;
66
+ };
67
+ /**
68
+ * Builds the omnibridge crossing as a plug-in carrier.
69
+ *
70
+ * @param options - see {@link CreateOmnibridgeCarrierOptions}
71
+ * @returns the carrier — `id: 'omnibridge'`; every EDGE it offers carries the
72
+ * real `'pulsechain'`/`'tokensex'` provider id, since one registration can
73
+ * speak for either entry chain depending on the pair
74
+ */
75
+ export declare const createOmnibridgeCarrier: (options: CreateOmnibridgeCarrierOptions) => {
76
+ id: string;
77
+ edgesFrom: (from: AssetNode, to: AssetNode) => readonly HopEdge[];
78
+ ask: ({ edge, inputAmount }: {
79
+ edge: HopEdge;
80
+ inputAmount: bigint;
81
+ }) => Promise<CarrierAskAnswer>;
82
+ prepare: (nodes: readonly AssetNode[]) => Promise<void>;
83
+ exact: (edge: HopEdge, amountIn: bigint) => CarrierExactResult | null;
84
+ isRateLimited: boolean;
85
+ depositMode: "wallet-signed";
86
+ sender: "omnibridge";
87
+ settlementSeconds: number;
88
+ };
89
+ /** What {@link withOmnibridgeEntryFusing}'s `fusesWithNext` is asked — structurally `@gibs/quotes`'s `CarrierFusesWithNextOptions`. */
90
+ export type OmnibridgeFuseQuestion = {
91
+ /** What the acting (provider) hop spends. */
92
+ readonly hopFrom: AssetNode;
93
+ /** The carrier of the hop that follows. */
94
+ readonly nextHopCarrier: string;
95
+ /** What the following hop spends — where the provider hop lands. */
96
+ readonly nextHopFrom: AssetNode;
97
+ /** Where the following hop delivers. */
98
+ readonly nextHopDestinationChainId: number;
99
+ };
100
+ /**
101
+ * Wraps ANY carrier-shaped object (built-in or a host's own) with the
102
+ * omnibridge's OWN fuse rule: whether THIS carrier's execution, landing on
103
+ * arrival, also performs the omnibridge's entry call — so the two hops
104
+ * together cost the reader one signature instead of two.
105
+ *
106
+ * EVERY CONDITION IS A FACT THE PROVIDER'S OWN DESCRIPTOR STATES
107
+ * (`../../providers.ts`), never a name compared here:
108
+ *
109
+ * - it executes destination calls at all (`executesDestinationCalls` — true
110
+ * for LI.FI and Relay, false for NEAR Intents, which delivers to the
111
+ * reader's own wallet and stops);
112
+ * - it can do so FROM this hop's origin chain (`destinationCallOriginChainIds`
113
+ * — LI.FI's contract-calls endpoint is Ethereum only, so from Base or
114
+ * Arbitrum a LI.FI hop is an unfused delivery followed by a crossing the
115
+ * reader signs);
116
+ * - when the entry is the omnibridge's NATIVE one (the provider lands a
117
+ * native coin, and `wrapAndRelayTokens` is payable), it can attach value to
118
+ * the call (`destinationCallCarriesNativeValue`);
119
+ * - and the next hop really IS the omnibridge entry, landing on PulseChain
120
+ * (`relayTokens`/`wrapAndRelayTokens` both MINT there; any other
121
+ * destination is the omnibridge's OUTBOUND leg, which no arrival can make).
122
+ *
123
+ * THIS IS THE ONE PLACE `'pulsechain'`/`'tokensex'`/PulseChain'S CHAIN ID
124
+ * APPEAR IN A `fusesWithNext` CALLBACK — gibs's own registration
125
+ * (`packages/ui/src/lib/state/useRouteSearch.ts`) wraps `createLifiCarrier`/
126
+ * `createRelayCarrier` with this before registering them, so the generic
127
+ * search engine (`@gibs/quotes`'s `capabilities.ts`) never has to know what
128
+ * "the bridge" means.
129
+ *
130
+ * @param carrier - the carrier to wrap; its own shape is preserved, only `fusesWithNext` is added/replaced
131
+ * @returns the same carrier, with a `fusesWithNext` that recognizes the omnibridge entry call
132
+ */
133
+ export declare const withOmnibridgeEntryFusing: <T extends {
134
+ readonly id: string;
135
+ }>(carrier: T) => T & {
136
+ readonly fusesWithNext: (nextHop: OmnibridgeFuseQuestion) => boolean;
137
+ };