@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.
- package/dist/routing/carriers/ask-answer.d.ts +2 -0
- package/dist/routing/carriers/pulsex.d.ts +38 -0
- package/dist/routing/carriers/pulsex.js +59 -10
- package/dist/routing/index.d.ts +1 -1
- package/dist/routing/index.js +1 -1
- package/dist/routing/search/graph.d.ts +14 -0
- package/dist/routing/search/score.d.ts +30 -1
- package/dist/routing/search/score.js +30 -3
- package/llms.txt +169 -0
- package/package.json +5 -4
|
@@ -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
|
-
|
|
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
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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
|
-
|
|
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',
|
package/dist/routing/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/routing/index.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
201
|
-
"@gibs/common": "^1.13.
|
|
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"
|