@gibs/bridge-sdk 1.13.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.
@@ -46,6 +46,8 @@ export type CarrierExactResult = {
46
46
  readonly outcome: 'delivered';
47
47
  /** What this edge delivers for exactly the requested input amount, in `edge.to`'s base units. */
48
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;
49
51
  } | {
50
52
  readonly outcome: 'refused';
51
53
  readonly refusal: {
@@ -55,6 +55,44 @@ export declare const pulseXRouterForEdge: (edge: Pick<HopEdge, "carrier" | "venu
55
55
  * @returns the swap path, or null when there is nothing for PulseX to swap
56
56
  */
57
57
  export declare const pulseXPoolPath: (from: AssetNode, to: AssetNode) => readonly Hex[] | null;
58
+ /** One pool a swap path crosses, read in the direction of travel. */
59
+ export type PulseXPathStep = {
60
+ /** The pool's reserve of the token flowing in. */
61
+ readonly reserveIn: bigint;
62
+ /** The pool's reserve of the token flowing out. */
63
+ readonly reserveOut: bigint;
64
+ };
65
+ /**
66
+ * What `amountIn` delivers after crossing every pool of a path in order,
67
+ * chaining {@link constantProductAmountOut}.
68
+ *
69
+ * @param amountIn - the amount entering the first pool
70
+ * @param steps - the pools in order of travel
71
+ * @returns the amount leaving the last pool; 0 when any pool cannot pay out
72
+ */
73
+ export declare const amountOutAlongPath: (amountIn: bigint, steps: readonly PulseXPathStep[]) => bigint;
74
+ /**
75
+ * How far a trade's executed rate falls below the marginal (near-spot) rate,
76
+ * in percent. The marginal rate is a tiny reference trade through the same
77
+ * pools (`amountIn / PULSEX_MARGINAL_REFERENCE_DIVISOR`). The swap fee rides
78
+ * in both rates, so it cancels and the gap is price impact alone. A path
79
+ * through several pools composes by itself: the reference trade crosses the
80
+ * same pools in the same order.
81
+ *
82
+ * impact = (1 - (amountOut / amountIn) / (referenceAmountOut / referenceAmountIn)) * 100
83
+ *
84
+ * @param options.amountIn - the full trade's input
85
+ * @param options.amountOut - what the full trade delivers
86
+ * @param options.referenceAmountIn - the reference trade's input
87
+ * @param options.referenceAmountOut - what the reference trade delivers
88
+ * @returns the impact in percent, never below 0; null when a reference figure is zero (impact cannot be measured)
89
+ */
90
+ export declare const priceImpactAgainstMarginal: (options: {
91
+ readonly amountIn: bigint;
92
+ readonly amountOut: bigint;
93
+ readonly referenceAmountIn: bigint;
94
+ readonly referenceAmountOut: bigint;
95
+ }) => number | null;
58
96
  /** What {@link createPulseXCarrier} needs. */
59
97
  export type CreatePulseXCarrierOptions = {
60
98
  /**
@@ -5,7 +5,7 @@ import { chainsMetadata } from '../../chains.js';
5
5
  import { Chains, nativeAssetOut, toChain, uniV2Factories, uniV2Routers } from '../../config.js';
6
6
  import { sameAddress } from '../../ecosystems.js';
7
7
  import { pulseXConversionSwapPath } from '../providers.js';
8
- import { pulsechainChainId } from '../producers.js';
8
+ import { PULSEX_MARGINAL_REFERENCE_DIVISOR, pulsechainChainId } from '../producers.js';
9
9
  import { assetNodeKey } from '../search/graph.js';
10
10
  /**
11
11
  * PulseX (the PulseChain-native decentralized exchange, used here for a
@@ -172,6 +172,44 @@ const constantProductAmountOut = (amountIn, reserveIn, reserveOut) => {
172
172
  const denominator = reserveIn * PULSEX_FEE_DENOMINATOR + amountInWithFee;
173
173
  return numerator / denominator;
174
174
  };
175
+ /**
176
+ * What `amountIn` delivers after crossing every pool of a path in order,
177
+ * chaining {@link constantProductAmountOut}.
178
+ *
179
+ * @param amountIn - the amount entering the first pool
180
+ * @param steps - the pools in order of travel
181
+ * @returns the amount leaving the last pool; 0 when any pool cannot pay out
182
+ */
183
+ export const amountOutAlongPath = (amountIn, steps) => steps.reduce((runningAmount, step) => constantProductAmountOut(runningAmount, step.reserveIn, step.reserveOut), amountIn);
184
+ /**
185
+ * How far a trade's executed rate falls below the marginal (near-spot) rate,
186
+ * in percent. The marginal rate is a tiny reference trade through the same
187
+ * pools (`amountIn / PULSEX_MARGINAL_REFERENCE_DIVISOR`). The swap fee rides
188
+ * in both rates, so it cancels and the gap is price impact alone. A path
189
+ * through several pools composes by itself: the reference trade crosses the
190
+ * same pools in the same order.
191
+ *
192
+ * impact = (1 - (amountOut / amountIn) / (referenceAmountOut / referenceAmountIn)) * 100
193
+ *
194
+ * @param options.amountIn - the full trade's input
195
+ * @param options.amountOut - what the full trade delivers
196
+ * @param options.referenceAmountIn - the reference trade's input
197
+ * @param options.referenceAmountOut - what the reference trade delivers
198
+ * @returns the impact in percent, never below 0; null when a reference figure is zero (impact cannot be measured)
199
+ */
200
+ export const priceImpactAgainstMarginal = (options) => {
201
+ const { amountIn, amountOut, referenceAmountIn, referenceAmountOut } = options;
202
+ if (amountIn <= 0n || referenceAmountIn <= 0n || referenceAmountOut <= 0n)
203
+ return null;
204
+ // The output the trade would deliver at the marginal rate with no impact.
205
+ const idealAmountOut = (referenceAmountOut * amountIn) / referenceAmountIn;
206
+ if (idealAmountOut <= 0n)
207
+ return null;
208
+ if (amountOut >= idealAmountOut)
209
+ return 0;
210
+ const MILLIONTHS = 1000000n;
211
+ return Number(((idealAmountOut - amountOut) * MILLIONTHS * 100n) / idealAmountOut) / Number(MILLIONTHS);
212
+ };
175
213
  /** The stable key one pool is stored and looked up under — the two token addresses, lower-cased and sorted, so either query order finds the same entry. */
176
214
  const poolKey = (tokenA, tokenB) => {
177
215
  const [lower, higher] = [tokenA.toLowerCase(), tokenB.toLowerCase()].sort();
@@ -365,7 +403,7 @@ export const createPulseXCarrier = (options) => {
365
403
  const hops = hopsAlongPath(path);
366
404
  if (hops.length === 0)
367
405
  return null;
368
- let runningAmount = amountIn;
406
+ const steps = [];
369
407
  for (const [tokenIn, tokenOut] of hops) {
370
408
  const reserves = reservesByPoolKey.get(venuePoolKey(venue, tokenIn, tokenOut));
371
409
  // `prepare` never read this pool — a structural non-answer, not a
@@ -377,15 +415,26 @@ export const createPulseXCarrier = (options) => {
377
415
  if (reserves === undefined)
378
416
  return null;
379
417
  const tokenInIsLower = tokenIn.toLowerCase() < tokenOut.toLowerCase();
380
- const reserveIn = tokenInIsLower ? reserves.reserveOfLowerAddress : reserves.reserveOfHigherAddress;
381
- const reserveOut = tokenInIsLower ? reserves.reserveOfHigherAddress : reserves.reserveOfLowerAddress;
382
- const hopAmountOut = constantProductAmountOut(runningAmount, reserveIn, reserveOut);
383
- if (hopAmountOut <= 0n) {
384
- return { outcome: 'refused', refusal: { kind: 'no-quote', reason: 'no liquidity on this PulseX path', minimumAmount: null } };
385
- }
386
- runningAmount = hopAmountOut;
418
+ steps.push({
419
+ reserveIn: tokenInIsLower ? reserves.reserveOfLowerAddress : reserves.reserveOfHigherAddress,
420
+ reserveOut: tokenInIsLower ? reserves.reserveOfHigherAddress : reserves.reserveOfLowerAddress,
421
+ });
422
+ }
423
+ const deliveredAmount = amountOutAlongPath(amountIn, steps);
424
+ if (deliveredAmount <= 0n) {
425
+ return { outcome: 'refused', refusal: { kind: 'no-quote', reason: 'no liquidity on this PulseX path', minimumAmount: null } };
387
426
  }
388
- return { outcome: 'delivered', deliveredAmount: runningAmount };
427
+ const priceImpactPercent = priceImpactAgainstMarginal({
428
+ amountIn,
429
+ amountOut: deliveredAmount,
430
+ referenceAmountIn: amountIn / PULSEX_MARGINAL_REFERENCE_DIVISOR,
431
+ referenceAmountOut: amountOutAlongPath(amountIn / PULSEX_MARGINAL_REFERENCE_DIVISOR, steps),
432
+ });
433
+ // Left off (not zero) when the reference trade rounds away: an unknown
434
+ // impact must stay unknown so the quality gate withholds the plan.
435
+ return priceImpactPercent === null
436
+ ? { outcome: 'delivered', deliveredAmount }
437
+ : { outcome: 'delivered', deliveredAmount, priceImpactPercent };
389
438
  };
390
439
  return {
391
440
  id: 'PulseX',
@@ -22,7 +22,7 @@ export { RANKABLE_ESTIMATE_BASES, assetNodeKey, combineComposedPathBasis, compar
22
22
  export type { AssetNode, CarrierId, ComposedPathBasis, HopCarrier, HopEdge, HopEdgeOrigin, HopFloor, HopOutcome, HopRequest, PlannedHop, RoutePlan, } from './search/graph.js';
23
23
  export { composableHopBasis, composeRouteDelivery, hopOutputAmount, } from './search/compose.js';
24
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, waitPenaltyPpm, zeroGasCostFor, } from './search/score.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
26
  export type { GasCostFor, GasCostQuote, RouteScoreInputs } from './search/score.js';
27
27
  export { buildRouteCandidateFromPlan, buildRouteDataFromPlan, buildRouteScoreInputsFromPlan, deriveDeferredCrossingFromPlan, deriveEntryChainIdFromPlan, deriveOmnibridgeProviderFromPlan, deriveViaChainIdsFromPlan, derivePlanKind, } from './search/plan-route-data.js';
28
28
  export type { PlanCandidateInputs, PlanRouteDataInputs, PlanSectionPricing, PlanValuation, ResolveNodeToken, } from './search/plan-route-data.js';
@@ -25,7 +25,7 @@ export { BRIDGE_SETTLEMENT_ETA_MINUTES, PULSEX_MARGINAL_REFERENCE_DIVISOR, pulse
25
25
  // under its `Carrier*` names.
26
26
  export { RANKABLE_ESTIMATE_BASES, assetNodeKey, combineComposedPathBasis, compareRoutePlansByDeliveredAmount, groupHopsBySignature, hopComposedBasis, hopEdgeKey, isConfirmedBasis, isRankableBasis, isRateLimitedCarrier, planComposedBasis, planIsConfirmed, planIsRankable, routePlanPathKey, } from './search/graph.js';
27
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, waitPenaltyPpm, zeroGasCostFor, } from './search/score.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
29
  // General route search — Phase 1: plan-route-data.ts. Turns a priced
30
30
  // RoutePlan into the same RouteData/RouteCandidate/RouteScoreInputs shapes
31
31
  // today's four hand-written producers emit — see that module's head doc.
@@ -318,6 +318,12 @@ export type HopOutcome = {
318
318
  * build exactly the request the route was priced for.
319
319
  */
320
320
  readonly minimumDeliveredAmount?: bigint;
321
+ /**
322
+ * How far the hop's executed rate falls below its marginal rate, in
323
+ * percent — present only when the carrier measured it from its own
324
+ * pool reserves (a PulseX swap). Absent means "not measured", never zero.
325
+ */
326
+ readonly priceImpactPercent?: number;
321
327
  } | {
322
328
  readonly kind: 'estimate';
323
329
  /** The composed/probed figure, in the hop's `to` node's base units. */
@@ -353,6 +359,14 @@ export type HopOutcome = {
353
359
  * a plan on any more.
354
360
  */
355
361
  readonly refusalReason?: string | null;
362
+ /**
363
+ * The price impact measured when the carrier answered this hop exactly,
364
+ * carried onto the rescaled estimate when an upstream amount moved and
365
+ * the exact figure no longer applies verbatim. Impact grows roughly in
366
+ * proportion to the trade, so the carried figure stays close for the
367
+ * small moves a live upstream quote makes. Absent means "not measured".
368
+ */
369
+ readonly priceImpactPercent?: number;
356
370
  };
357
371
  /**
358
372
  * Whether every hop of `hops` carries a figure a ranking may trust: a live
@@ -244,8 +244,20 @@ export type GasCostQuote = {
244
244
  * the user needs gas, then they should get it" — a gasless intermediate
245
245
  * chain is modelled as ONE MORE CHARGE against the route, exactly like the
246
246
  * gas itself, never as a refusal or an exclusion from the ranking.
247
+ *
248
+ * A SECTION THE PREVIOUS SECTION FUNDS IS NEVER CHARGED THIS. When the
249
+ * hop landing on this chain delivers native coin, the reader arrives
250
+ * holding gas — {@link gasAdjustedDeliveredAmount} drops this figure for
251
+ * that section (see {@link sectionIsFundedByLanding}).
247
252
  */
248
253
  readonly refuelAmount: bigint;
254
+ /**
255
+ * The native coin this signature burns, in the signing chain's own native
256
+ * base units (wei on an Ethereum-Virtual-Machine chain) — the amount a gas
257
+ * top-up has to land at minimum. Optional: a caller with no gas-price read
258
+ * omits it, and a served top-up then reports its native amount as unknown.
259
+ */
260
+ readonly nativeGasAmount?: bigint;
249
261
  };
250
262
  /**
251
263
  * Supplies the REAL cost of signing one step, for the chain it broadcasts on
@@ -285,12 +297,29 @@ export type GasCostFor = (chainId: number, sender: HopCarrier) => GasCostQuote;
285
297
  * has something total to call when nobody has.
286
298
  */
287
299
  export declare const zeroGasCostFor: GasCostFor;
300
+ /**
301
+ * Whether the section at `sectionIndex` starts with native coin already in
302
+ * the reader's hand: the hop that ends the PREVIOUS section delivers the
303
+ * chain's native coin, so the reader arrives holding gas for this section's
304
+ * own signature and needs no top-up there, whatever their balance was.
305
+ *
306
+ * Always false for the first section — nothing lands before it, so its gas
307
+ * can only come from the reader's own balance.
308
+ *
309
+ * @param plan - the plan the section belongs to
310
+ * @param sectionIndex - 0-based index into `plan.signatures`
311
+ * @returns true when the previous section lands native coin on this
312
+ * section's chain
313
+ */
314
+ export declare const sectionIsFundedByLanding: (plan: RoutePlan, sectionIndex: number) => boolean;
288
315
  /**
289
316
  * `plan`'s last-hop `deliveredAmount`, minus the REAL cost of every
290
317
  * signature it needs — `gasCostFor` charged ONCE per signature group
291
318
  * (`plan.signatures`, built by `groupHopsBySignature`), on the chain and
292
319
  * carrier of that group's FIRST hop, which is the hop the reader actually
293
- * signs (or deposits by hand) for that group. Floored at zero: a plan whose
320
+ * signs (or deposits by hand) for that group. A group's refuel cost is
321
+ * dropped when the previous group lands native coin
322
+ * ({@link sectionIsFundedByLanding}). Floored at zero: a plan whose
294
323
  * gas costs exceed its own delivered amount is worth nothing, never negative.
295
324
  *
296
325
  * @param plan - the plan to charge
@@ -1,3 +1,4 @@
1
+ import { isNativeAsset } from '../../ecosystems.js';
1
2
  import { compareRoutePlansByDeliveredAmount, planIsRankable, isRankableBasis, } from './graph.js';
2
3
  /**
3
4
  * PHASE 0 — FOUNDATION ONLY. Today this module holds exactly one function,
@@ -337,12 +338,37 @@ export const scoreRoutePlan = (inputs) => {
337
338
  * has something total to call when nobody has.
338
339
  */
339
340
  export const zeroGasCostFor = () => ({ gasAmount: 0n, refuelAmount: 0n });
341
+ /**
342
+ * Whether the section at `sectionIndex` starts with native coin already in
343
+ * the reader's hand: the hop that ends the PREVIOUS section delivers the
344
+ * chain's native coin, so the reader arrives holding gas for this section's
345
+ * own signature and needs no top-up there, whatever their balance was.
346
+ *
347
+ * Always false for the first section — nothing lands before it, so its gas
348
+ * can only come from the reader's own balance.
349
+ *
350
+ * @param plan - the plan the section belongs to
351
+ * @param sectionIndex - 0-based index into `plan.signatures`
352
+ * @returns true when the previous section lands native coin on this
353
+ * section's chain
354
+ */
355
+ export const sectionIsFundedByLanding = (plan, sectionIndex) => {
356
+ if (sectionIndex < 1)
357
+ return false;
358
+ const previousGroup = plan.signatures[sectionIndex - 1];
359
+ const landingHop = plan.hops[previousGroup?.[previousGroup.length - 1] ?? -1];
360
+ if (landingHop === undefined)
361
+ return false;
362
+ return isNativeAsset(landingHop.edge.to.address, landingHop.edge.to.ecosystem);
363
+ };
340
364
  /**
341
365
  * `plan`'s last-hop `deliveredAmount`, minus the REAL cost of every
342
366
  * signature it needs — `gasCostFor` charged ONCE per signature group
343
367
  * (`plan.signatures`, built by `groupHopsBySignature`), on the chain and
344
368
  * carrier of that group's FIRST hop, which is the hop the reader actually
345
- * signs (or deposits by hand) for that group. Floored at zero: a plan whose
369
+ * signs (or deposits by hand) for that group. A group's refuel cost is
370
+ * dropped when the previous group lands native coin
371
+ * ({@link sectionIsFundedByLanding}). Floored at zero: a plan whose
346
372
  * gas costs exceed its own delivered amount is worth nothing, never negative.
347
373
  *
348
374
  * @param plan - the plan to charge
@@ -351,13 +377,14 @@ export const zeroGasCostFor = () => ({ gasAmount: 0n, refuelAmount: 0n });
351
377
  */
352
378
  export const gasAdjustedDeliveredAmount = (plan, gasCostFor) => {
353
379
  const deliveredAmount = plan.hops[plan.hops.length - 1]?.outcome.deliveredAmount ?? 0n;
354
- const totalGasCost = plan.signatures.reduce((total, group) => {
380
+ const totalGasCost = plan.signatures.reduce((total, group, sectionIndex) => {
355
381
  const firstHopIndex = group[0];
356
382
  const firstHop = firstHopIndex === undefined ? undefined : plan.hops[firstHopIndex];
357
383
  if (firstHop === undefined)
358
384
  return total;
359
385
  const quote = gasCostFor(firstHop.edge.from.chainId, firstHop.edge.carrier);
360
- return total + quote.gasAmount + quote.refuelAmount;
386
+ const refuelAmount = sectionIsFundedByLanding(plan, sectionIndex) ? 0n : quote.refuelAmount;
387
+ return total + quote.gasAmount + refuelAmount;
361
388
  }, 0n);
362
389
  const netAmount = deliveredAmount - totalGasCost;
363
390
  return netAmount < 0n ? 0n : netAmount;
package/llms.txt ADDED
@@ -0,0 +1,169 @@
1
+ # @gibs/bridge-sdk
2
+
3
+ > A TypeScript library that prices and builds transfers across the Gibs
4
+ > Finance omnibridges (PulseChain with Ethereum, PulseChain with BNB Smart
5
+ > Chain). It also builds the requests for the aggregator routes (LI.FI,
6
+ > Relay, NEAR Intents) that reach those bridges from other chains. It does
7
+ > not sign or send anything. You bring a viem client and a wallet.
8
+
9
+ ## Install
10
+
11
+ - Run `npm install @gibs/bridge-sdk viem`.
12
+ - The package is ESM only. Use `import`, not `require`.
13
+ - It needs Node 20 or later (`engines.node` is `>=20`).
14
+ - viem is a peer dependency. The range is `>=2.37.0 <3`.
15
+ - Version note: 1.12.0 and older on npm are broken. 1.13.0 is the first usable version.
16
+ - In a browser, `@gibs/bridge-sdk/ton-execution` needs a global `Buffer`. No other entry point does.
17
+
18
+ ## Entry points
19
+
20
+ Import from the narrowest subpath. The root `@gibs/bridge-sdk` exports everything.
21
+
22
+ - `@gibs/bridge-sdk/config`: chains, providers, pathways, native-asset tables.
23
+ - `@gibs/bridge-sdk/types`: shared types such as `BridgeKey` and `Token`.
24
+ - `@gibs/bridge-sdk/chains`: viem chain objects with logos and RPC URLs.
25
+ - `@gibs/bridge-sdk/chain-info`: on-chain reads (token mappings, fees, minimum amounts).
26
+ - `@gibs/bridge-sdk/settings`: fee and amount arithmetic.
27
+ - `@gibs/bridge-sdk/consolidated-quote`: LI.FI request builders and parsers.
28
+ - `@gibs/bridge-sdk/relay-quote`: Relay request builders and parsers.
29
+ - `@gibs/bridge-sdk/near-intents`: NEAR Intents requests and parsers.
30
+ - `@gibs/bridge-sdk/pre-quote`: price checks before a binding quote.
31
+ - `@gibs/bridge-sdk/routing`: route search across carriers.
32
+ - `@gibs/bridge-sdk/image-links`: image URLs from gib.show.
33
+ - `@gibs/bridge-sdk/abis`: contract ABIs.
34
+
35
+ ## Chains and pathways
36
+
37
+ A chain is a hex id from the `Chains` enum. A route is a `BridgeKey`:
38
+ `[provider, fromChain, toChain]`. `pathway` returns the contract addresses
39
+ for a key, or `undefined` when no pathway exists.
40
+
41
+ ```ts
42
+ import { Chains, Providers, pathway } from '@gibs/bridge-sdk/config'
43
+ import type { BridgeKey } from '@gibs/bridge-sdk/types'
44
+
45
+ const bridgeKey: BridgeKey = [Providers.PULSECHAIN, Chains.ETH, Chains.PLS]
46
+ const route = pathway(bridgeKey, true) // true: mainnet only
47
+ if (route) {
48
+ route.from // the mediator on Ethereum, where funds enter
49
+ route.feeManager // 'to': the PulseChain mediator owns the fee manager
50
+ }
51
+ ```
52
+
53
+ ## Fees: direction and units
54
+
55
+ These rules must hold in every integration.
56
+
57
+ - PulseChain is the home side of both omnibridges.
58
+ - Foreign to home (`feeF2H`) means entering PulseChain.
59
+ - Home to foreign (`feeH2F`) means leaving PulseChain.
60
+ - Use the fee for your direction. Do not use one fee for both.
61
+ - Bridge fees are a rate per `1e18`. `1e18` is one hundred percent.
62
+ - Aggregator referral fees are basis points. `10_000n` is one hundred percent.
63
+ - LI.FI takes its fee as a decimal fraction (`0.001` is a tenth of a percent).
64
+ - Fees are on-chain settings. Read them. Do not hard-code them.
65
+
66
+ ```ts
67
+ import { loadBridgeFees } from '@gibs/bridge-sdk/chain-info'
68
+ import { Chains, pathway, Providers } from '@gibs/bridge-sdk/config'
69
+ import { amountAfterBridgeFee, bridgeCost } from '@gibs/bridge-sdk/settings'
70
+ import type { PublicClient } from 'viem'
71
+
72
+ declare const ethereumClient: PublicClient
73
+ declare const pulsechainClient: PublicClient
74
+
75
+ const route = pathway([Providers.PULSECHAIN, Chains.ETH, Chains.PLS], true)
76
+ if (route) {
77
+ const { feeF2H } = await loadBridgeFees({
78
+ pathway: route,
79
+ fromChainClient: ethereumClient,
80
+ toChainClient: pulsechainClient,
81
+ })
82
+ const amountToBridge = 1_000_000n
83
+ const cost = bridgeCost({ amountToBridge, bridgeFee: feeF2H })
84
+ const received = amountAfterBridgeFee({ amountToBridge, bridgeCost: cost })
85
+ }
86
+ ```
87
+
88
+ `loadBridgeFees` reads the default rate. It does not read per-token rates.
89
+
90
+ ## Tokens that are not bridged yet
91
+
92
+ `tokenBridgeInfo` asks both mediators how a token maps across the bridge.
93
+ A `null` `assetOutAddress` is a normal answer. The token gets an address on
94
+ the far side when its first transfer arrives. Always check for `null`.
95
+
96
+ ```ts
97
+ import { tokenBridgeInfo } from '@gibs/bridge-sdk/chain-info'
98
+ import { Chains, Providers } from '@gibs/bridge-sdk/config'
99
+ import type { Token } from '@gibs/bridge-sdk/types'
100
+ import type { PublicClient } from 'viem'
101
+
102
+ declare const assetIn: Token
103
+ declare const ethereumClient: PublicClient
104
+ declare const pulsechainClient: PublicClient
105
+
106
+ const info = await tokenBridgeInfo({
107
+ bridgeKey: [Providers.PULSECHAIN, Chains.ETH, Chains.PLS],
108
+ assetIn,
109
+ isProd: true,
110
+ fromChainClient: ethereumClient,
111
+ toChainClient: pulsechainClient,
112
+ })
113
+ if (info && info.assetOutAddress === null) {
114
+ // Not bridged yet. Do not treat this as an error.
115
+ }
116
+ ```
117
+
118
+ ## Aggregator identity: pass your own
119
+
120
+ The package ships no API key. Pass your own.
121
+
122
+ - Relay: pass your own `apiKey` to `relayRequestHeaders`. Name your own `relayReferrer`. The package sends no referrer by default, because Relay rejects a referrer without a key.
123
+ - LI.FI: set your own `lifiIntegrator`. A positive `lifiFee` without your own integrator throws.
124
+ - NEAR Intents: no key is needed. Pass your own `userAgent` if you want one.
125
+
126
+ ```ts
127
+ import {
128
+ buildLifiContractCallsRequest,
129
+ type ConsolidatedQuoteRequest,
130
+ } from '@gibs/bridge-sdk/consolidated-quote'
131
+ import { nearIntentsRequestHeaders } from '@gibs/bridge-sdk/near-intents'
132
+ import { buildRelayQuoteRequest, relayQuoteUrl, relayRequestHeaders } from '@gibs/bridge-sdk/relay-quote'
133
+
134
+ declare const baseRequest: ConsolidatedQuoteRequest
135
+ declare const myRelayApiKey: string
136
+
137
+ const request: ConsolidatedQuoteRequest = {
138
+ ...baseRequest,
139
+ lifiIntegrator: 'your-app.example',
140
+ lifiFee: 0.001,
141
+ relayReferrer: 'your-app.example',
142
+ }
143
+
144
+ const lifiBody = buildLifiContractCallsRequest(request)
145
+ const relayBody = buildRelayQuoteRequest(request)
146
+ if (relayBody) {
147
+ await fetch(relayQuoteUrl, {
148
+ method: 'POST',
149
+ body: JSON.stringify(relayBody),
150
+ headers: relayRequestHeaders(myRelayApiKey),
151
+ })
152
+ }
153
+
154
+ const nearHeaders = nearIntentsRequestHeaders(undefined, { userAgent: 'your-app/1.0' })
155
+ ```
156
+
157
+ ## Nulls and throws
158
+
159
+ - A question the package cannot answer returns `null`, `undefined` or `false`.
160
+ - A caller mistake or a failed chain read throws.
161
+ - Every request builder returns `null` for a request its provider cannot express.
162
+ - Every response parser returns `null` for an unexpected response shape.
163
+ - Check each return value before you use it.
164
+
165
+ ## Links
166
+
167
+ - [README](https://github.com/gibsfinance/bridge/blob/staging/packages/bridge-sdk/README.md): the full guide.
168
+ - [Gibs Finance llms.txt](https://next.gibs.finance/llms.txt): the whole platform.
169
+ - [@gibs/quotes](https://github.com/gibsfinance/bridge/blob/staging/packages/quotes/llms.txt): live multi-provider quotes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gibs/bridge-sdk",
3
- "version": "1.13.0",
3
+ "version": "1.13.1",
4
4
  "description": "Build and price cross-chain transfers between PulseChain, Ethereum, and Binance Smart Chain.",
5
5
  "keywords": [
6
6
  "ethereum",
@@ -185,7 +185,8 @@
185
185
  "!dist/tsconfig.tsbuildinfo",
186
186
  "!dist/**/*.test-d.*",
187
187
  "!dist/test-utils",
188
- "LICENSE"
188
+ "LICENSE",
189
+ "llms.txt"
189
190
  ],
190
191
  "scripts": {
191
192
  "prepublishOnly": "npx tsc",
@@ -197,8 +198,8 @@
197
198
  "test:live:near-intents": "../../scripts/near-intents-live.sh"
198
199
  },
199
200
  "dependencies": {
200
- "@gibs/abis": "^1.13.0",
201
- "@gibs/common": "^1.13.0",
201
+ "@gibs/abis": "^1.13.1",
202
+ "@gibs/common": "^1.13.1",
202
203
  "@ton/core": "^0.63.1",
203
204
  "@ton/crypto": "^3.3.0",
204
205
  "lodash-es": "^4.17.21"