@gibs/bridge-sdk 1.13.1 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +244 -18
- package/abis/package.json +6 -0
- package/bridge-limits/package.json +6 -0
- package/chain-info/package.json +6 -0
- package/chain-names/package.json +6 -0
- package/chains/package.json +6 -0
- package/claims/package.json +6 -0
- package/config/package.json +6 -0
- package/consolidated-quote/package.json +6 -0
- package/destination-calldata/package.json +6 -0
- package/dist/chain-info.d.ts +1 -1
- package/dist/chain-info.js +11 -12
- package/dist/chains.d.ts +25 -0
- package/dist/chains.js +55 -20
- package/dist/cjs/abis.d.ts +1 -0
- package/dist/cjs/abis.js +19 -0
- package/dist/cjs/bridge-limits.d.ts +103 -0
- package/dist/cjs/bridge-limits.js +89 -0
- package/dist/cjs/chain-info.d.ts +169 -0
- package/dist/cjs/chain-info.js +452 -0
- package/dist/cjs/chain-names.d.ts +160 -0
- package/dist/cjs/chain-names.js +207 -0
- package/dist/cjs/chains.d.ts +90 -0
- package/dist/cjs/chains.js +357 -0
- package/dist/cjs/claims.d.ts +198 -0
- package/dist/cjs/claims.js +182 -0
- package/dist/cjs/config.d.ts +240 -0
- package/dist/cjs/config.js +537 -0
- package/dist/cjs/consolidated-quote.d.ts +648 -0
- package/dist/cjs/consolidated-quote.js +1048 -0
- package/dist/cjs/destination-calldata.d.ts +118 -0
- package/dist/cjs/destination-calldata.js +156 -0
- package/dist/cjs/ecosystems.d.ts +217 -0
- package/dist/cjs/ecosystems.js +298 -0
- package/dist/cjs/execution.d.ts +346 -0
- package/dist/cjs/execution.js +468 -0
- package/dist/cjs/fee-type.d.ts +10 -0
- package/dist/cjs/fee-type.js +10 -0
- package/dist/cjs/hyperliquid-execution.d.ts +152 -0
- package/dist/cjs/hyperliquid-execution.js +175 -0
- package/dist/cjs/image-links.d.ts +117 -0
- package/dist/cjs/image-links.js +153 -0
- package/dist/cjs/index.d.ts +23 -0
- package/dist/cjs/index.js +39 -0
- package/dist/cjs/lifi-execution-guard.d.ts +210 -0
- package/dist/cjs/lifi-execution-guard.js +409 -0
- package/dist/cjs/near-intents-conformance.d.ts +85 -0
- package/dist/cjs/near-intents-conformance.js +337 -0
- package/dist/cjs/near-intents-route.d.ts +47 -0
- package/dist/cjs/near-intents-route.js +303 -0
- package/dist/cjs/near-intents.d.ts +524 -0
- package/dist/cjs/near-intents.js +1071 -0
- package/dist/cjs/origin-deposit.d.ts +326 -0
- package/dist/cjs/origin-deposit.js +82 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/pre-quote.d.ts +198 -0
- package/dist/cjs/pre-quote.js +180 -0
- package/dist/cjs/prices.d.ts +107 -0
- package/dist/cjs/prices.js +172 -0
- package/dist/cjs/providers.d.ts +238 -0
- package/dist/cjs/providers.js +171 -0
- package/dist/cjs/quote-fees.d.ts +102 -0
- package/dist/cjs/quote-fees.js +201 -0
- package/dist/cjs/referral.d.ts +85 -0
- package/dist/cjs/referral.js +85 -0
- package/dist/cjs/relay-currencies.d.ts +47 -0
- package/dist/cjs/relay-currencies.js +95 -0
- package/dist/cjs/relay-depository.d.ts +2 -0
- package/dist/cjs/relay-depository.js +85 -0
- package/dist/cjs/relay-quote.d.ts +301 -0
- package/dist/cjs/relay-quote.js +996 -0
- package/dist/cjs/route-steps.d.ts +138 -0
- package/dist/cjs/route-steps.js +310 -0
- package/dist/cjs/routing/amounts.d.ts +80 -0
- package/dist/cjs/routing/amounts.js +126 -0
- package/dist/cjs/routing/candidate-route.d.ts +43 -0
- package/dist/cjs/routing/candidate-route.js +50 -0
- package/dist/cjs/routing/candidate.d.ts +237 -0
- package/dist/cjs/routing/candidate.js +100 -0
- package/dist/cjs/routing/carriers/ask-answer.d.ts +77 -0
- package/dist/cjs/routing/carriers/ask-answer.js +2 -0
- package/dist/cjs/routing/carriers/omnibridge.d.ts +137 -0
- package/dist/cjs/routing/carriers/omnibridge.js +459 -0
- package/dist/cjs/routing/carriers/pulsex-paths.d.ts +80 -0
- package/dist/cjs/routing/carriers/pulsex-paths.js +97 -0
- package/dist/cjs/routing/carriers/pulsex-split.d.ts +175 -0
- package/dist/cjs/routing/carriers/pulsex-split.js +193 -0
- package/dist/cjs/routing/carriers/pulsex.d.ts +159 -0
- package/dist/cjs/routing/carriers/pulsex.js +565 -0
- package/dist/cjs/routing/carriers/wrap.d.ts +25 -0
- package/dist/cjs/routing/carriers/wrap.js +119 -0
- package/dist/cjs/routing/composed-route-quality.d.ts +125 -0
- package/dist/cjs/routing/composed-route-quality.js +144 -0
- package/dist/cjs/routing/entry-chain-anchor.d.ts +88 -0
- package/dist/cjs/routing/entry-chain-anchor.js +59 -0
- package/dist/cjs/routing/format.d.ts +31 -0
- package/dist/cjs/routing/format.js +64 -0
- package/dist/cjs/routing/index.d.ts +38 -0
- package/dist/cjs/routing/index.js +134 -0
- package/dist/cjs/routing/producers.d.ts +770 -0
- package/dist/cjs/routing/producers.js +1617 -0
- package/dist/cjs/routing/providers.d.ts +31 -0
- package/dist/cjs/routing/providers.js +48 -0
- package/dist/cjs/routing/route-data.d.ts +355 -0
- package/dist/cjs/routing/route-data.js +2 -0
- package/dist/cjs/routing/search/compose.d.ts +168 -0
- package/dist/cjs/routing/search/compose.js +104 -0
- package/dist/cjs/routing/search/graph.d.ts +668 -0
- package/dist/cjs/routing/search/graph.js +461 -0
- package/dist/cjs/routing/search/plan-route-data.d.ts +269 -0
- package/dist/cjs/routing/search/plan-route-data.js +848 -0
- package/dist/cjs/routing/search/score.d.ts +356 -0
- package/dist/cjs/routing/search/score.js +445 -0
- package/dist/cjs/routing/swap-transactions.d.ts +91 -0
- package/dist/cjs/routing/swap-transactions.js +131 -0
- package/dist/cjs/routing/usd-value.d.ts +17 -0
- package/dist/cjs/routing/usd-value.js +29 -0
- package/dist/cjs/settings.d.ts +785 -0
- package/dist/cjs/settings.js +1074 -0
- package/dist/cjs/ton-connect.d.ts +98 -0
- package/dist/cjs/ton-connect.js +206 -0
- package/dist/cjs/ton-execution.d.ts +7 -0
- package/dist/cjs/ton-execution.js +23 -0
- package/dist/cjs/ton.d.ts +37 -0
- package/dist/cjs/ton.js +83 -0
- package/dist/cjs/tron-execution.d.ts +146 -0
- package/dist/cjs/tron-execution.js +248 -0
- package/dist/cjs/types.d.ts +51 -0
- package/dist/cjs/types.js +2 -0
- package/dist/claims.d.ts +198 -0
- package/dist/claims.js +140 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.js +39 -5
- package/dist/consolidated-quote.d.ts +109 -15
- package/dist/consolidated-quote.js +310 -52
- package/dist/execution.d.ts +346 -0
- package/dist/execution.js +428 -0
- package/dist/hyperliquid-execution.js +2 -3
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/lifi-execution-guard.d.ts +13 -2
- package/dist/lifi-execution-guard.js +4 -0
- package/dist/near-intents.js +2 -3
- package/dist/pre-quote.d.ts +6 -6
- package/dist/pre-quote.js +10 -14
- package/dist/providers.d.ts +17 -0
- package/dist/providers.js +6 -0
- package/dist/referral.d.ts +12 -4
- package/dist/referral.js +12 -4
- package/dist/relay-depository.d.ts +2 -0
- package/dist/relay-depository.js +82 -0
- package/dist/relay-quote.js +2 -3
- package/dist/routing/candidate.d.ts +3 -3
- package/dist/routing/candidate.js +3 -3
- package/dist/routing/carriers/ask-answer.d.ts +17 -0
- package/dist/routing/carriers/omnibridge.js +23 -21
- package/dist/routing/carriers/pulsex-paths.d.ts +80 -0
- package/dist/routing/carriers/pulsex-paths.js +92 -0
- package/dist/routing/carriers/pulsex-split.d.ts +175 -0
- package/dist/routing/carriers/pulsex-split.js +185 -0
- package/dist/routing/carriers/pulsex.d.ts +31 -21
- package/dist/routing/carriers/pulsex.js +141 -71
- package/dist/routing/composed-route-quality.d.ts +28 -6
- package/dist/routing/composed-route-quality.js +36 -5
- package/dist/routing/entry-chain-anchor.d.ts +1 -1
- package/dist/routing/entry-chain-anchor.js +1 -1
- package/dist/routing/index.d.ts +6 -2
- package/dist/routing/index.js +6 -2
- package/dist/routing/producers.js +1 -1
- package/dist/routing/providers.d.ts +5 -0
- package/dist/routing/providers.js +5 -0
- package/dist/routing/search/graph.d.ts +15 -0
- package/dist/routing/search/plan-route-data.d.ts +25 -0
- package/dist/routing/search/plan-route-data.js +43 -7
- package/dist/routing/search/score.d.ts +3 -0
- package/dist/routing/search/score.js +11 -3
- package/dist/routing/swap-transactions.d.ts +91 -0
- package/dist/routing/swap-transactions.js +125 -0
- package/dist/settings.d.ts +94 -1
- package/dist/settings.js +95 -3
- package/dist/ton-connect.d.ts +98 -0
- package/dist/ton-connect.js +200 -0
- package/dist/ton-execution.d.ts +4 -114
- package/dist/ton-execution.js +4 -244
- package/dist/ton.d.ts +37 -0
- package/dist/ton.js +65 -0
- package/docs/examples/example-a-ether-to-pulse.ts +300 -0
- package/docs/examples/example-b-token-pulsechain-to-ethereum.ts +270 -0
- package/docs/examples/shared.ts +261 -0
- package/docs/examples/tsconfig.json +18 -0
- package/docs/integration-guide.md +617 -0
- package/ecosystems/package.json +6 -0
- package/execution/package.json +6 -0
- package/fee-type/package.json +6 -0
- package/hyperliquid-execution/package.json +6 -0
- package/image-links/package.json +6 -0
- package/lifi-execution-guard/package.json +6 -0
- package/llms.txt +75 -10
- package/near-intents/package.json +6 -0
- package/near-intents-route/package.json +6 -0
- package/origin-deposit/package.json +6 -0
- package/package.json +817 -10
- package/pre-quote/package.json +6 -0
- package/prices/package.json +6 -0
- package/providers/package.json +6 -0
- package/quote-fees/package.json +6 -0
- package/referral/package.json +6 -0
- package/relay-currencies/package.json +6 -0
- package/relay-quote/package.json +6 -0
- package/route-steps/package.json +6 -0
- package/routing/package.json +6 -0
- package/settings/package.json +6 -0
- package/ton/package.json +6 -0
- package/ton-connect/package.json +6 -0
- package/ton-execution/package.json +6 -0
- package/tron-execution/package.json +6 -0
- package/types/package.json +6 -0
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createWrapCarrier = void 0;
|
|
4
|
+
const viem_1 = require("viem");
|
|
5
|
+
const config_js_1 = require("../../config.js");
|
|
6
|
+
const graph_js_1 = require("../search/graph.js");
|
|
7
|
+
/**
|
|
8
|
+
* Native-coin wrap/unwrap (PLS <-> WPLS on PulseChain, and the same pair on
|
|
9
|
+
* every other Ethereum-Virtual-Machine chain the search graph touches — ETH
|
|
10
|
+
* <-> WETH, BNB <-> WBNB) as a plug-in `Carrier` for `@gibs/quotes`'s
|
|
11
|
+
* route-search engine. GIBS-SPECIFIC in the sense `omnibridge.ts`/`pulsex.ts`
|
|
12
|
+
* are (see `omnibridge.ts`'s head doc for why this returns a plain object
|
|
13
|
+
* shaped like `Carrier` rather than importing that type) — though the
|
|
14
|
+
* carrier itself names no chain: it derives every wrap pair from
|
|
15
|
+
* `config.ts`'s own `nativeAssetOut` map, so a chain added there gains a
|
|
16
|
+
* wrap edge with no change here.
|
|
17
|
+
*
|
|
18
|
+
* WHY THIS IS A SEPARATE CARRIER FROM PulseX. `pulsex.ts`'s own
|
|
19
|
+
* `readPulseXAmountOut` (via `pulseXSwapPathForEdge` in
|
|
20
|
+
* `packages/ui/src/lib/state/useRouteSearch.ts`) quotes native Pulse AS
|
|
21
|
+
* Wrapped Pulse — both map to the same `quotePathAddress` — so a PLS<->WPLS
|
|
22
|
+
* pair collapses to "no swap to quote" and PulseX has never carried it.
|
|
23
|
+
* There is no PulseX POOL for native-versus-wrapped; it is a 1:1 contract
|
|
24
|
+
* call (`deposit()`/`withdraw(uint256)`), not a trade, so it needed its own
|
|
25
|
+
* carrier rather than a PulseX special case.
|
|
26
|
+
*
|
|
27
|
+
* SENDER: `'swap'`, THE SAME NAME `pulsex.ts` USES — the reader's own
|
|
28
|
+
* connected wallet signs a `deposit()`/`withdraw(uint256)` call on the
|
|
29
|
+
* wrapped token's own contract (WETH9-style; confirmed present on WPLS by
|
|
30
|
+
* bytecode selector 2026-09-24), exactly the wallet a same-chain PulseX swap
|
|
31
|
+
* already uses. WHETHER `settings.ts`'s EXISTING swap-transaction builder
|
|
32
|
+
* (`pulseXSwapTransactionInputs`) CAN BUILD THIS CALL: NO, not without a
|
|
33
|
+
* change there — that builder only ever targets a PulseX ROUTER address and
|
|
34
|
+
* encodes `swapExact*` calldata; a wrap needs `to` = the wrapped token
|
|
35
|
+
* itself and `data` = plain `deposit()`/`withdraw(amount)` calldata, which
|
|
36
|
+
* is a different (and simpler) shape. Sharing the `'swap'` sender name says
|
|
37
|
+
* "the same wallet, the same signing step in the flow" — it does not claim
|
|
38
|
+
* the existing builder already emits this calldata. Building that call is
|
|
39
|
+
* `settings.ts`'s job, out of this carrier's scope; this module answers only
|
|
40
|
+
* the route-search question ("what does this edge deliver, exactly").
|
|
41
|
+
*/
|
|
42
|
+
/**
|
|
43
|
+
* Whether this carrier can wrap/unwrap directly between `from` and `to`:
|
|
44
|
+
* same Ethereum-Virtual-Machine chain, and the two addresses are EXACTLY the
|
|
45
|
+
* zero-address/wrapped-native pair `config.ts`'s `nativeAssetOut` names for
|
|
46
|
+
* that chain — nothing else, so a plain token pair that merely happens to
|
|
47
|
+
* share a chain is never mistaken for a wrap.
|
|
48
|
+
*/
|
|
49
|
+
const wrapCarries = (from, to) => {
|
|
50
|
+
if (from.ecosystem !== 'evm' || to.ecosystem !== 'evm')
|
|
51
|
+
return false;
|
|
52
|
+
if (from.chainId !== to.chainId)
|
|
53
|
+
return false;
|
|
54
|
+
const wrappedNativeAddress = config_js_1.nativeAssetOut[(0, config_js_1.toChain)(from.chainId)];
|
|
55
|
+
if (wrappedNativeAddress === undefined)
|
|
56
|
+
return false;
|
|
57
|
+
const wrapped = (0, viem_1.getAddress)(wrappedNativeAddress).toLowerCase();
|
|
58
|
+
const fromAddress = from.address.toLowerCase();
|
|
59
|
+
const toAddress = to.address.toLowerCase();
|
|
60
|
+
const fromIsNative = fromAddress === viem_1.zeroAddress;
|
|
61
|
+
const toIsNative = toAddress === viem_1.zeroAddress;
|
|
62
|
+
if (fromIsNative)
|
|
63
|
+
return toAddress === wrapped;
|
|
64
|
+
if (toIsNative)
|
|
65
|
+
return fromAddress === wrapped;
|
|
66
|
+
return false;
|
|
67
|
+
};
|
|
68
|
+
/** Every wrap/unwrap edge is worth exactly `amountIn`, in `edge.to`'s base units — native and wrapped share the same decimals on every chain this carrier has ever seen (18, both sides, everywhere `nativeAssetOut` names). */
|
|
69
|
+
const wrapExact = (edge, amountIn) => {
|
|
70
|
+
if (!wrapCarries(edge.from, edge.to))
|
|
71
|
+
return null;
|
|
72
|
+
return { outcome: 'delivered', deliveredAmount: amountIn };
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Builds the native wrap/unwrap carrier as a plug-in.
|
|
76
|
+
*
|
|
77
|
+
* NO OPTIONS, NO `prepare`. There is no local read to batch — a wrap has no
|
|
78
|
+
* minimum, no maximum, no fee rate and no reserve to be wrong about; it is a
|
|
79
|
+
* pure 1:1 identity over every chain `config.ts` names a wrapped-native
|
|
80
|
+
* token for. `exact` alone is total over every edge this carrier offers.
|
|
81
|
+
*
|
|
82
|
+
* @returns the carrier — `id: 'wrap'`
|
|
83
|
+
*/
|
|
84
|
+
const createWrapCarrier = () => {
|
|
85
|
+
const edgesFrom = (from, to) => {
|
|
86
|
+
if ((0, graph_js_1.assetNodeKey)(from) === (0, graph_js_1.assetNodeKey)(to))
|
|
87
|
+
return [];
|
|
88
|
+
if (!wrapCarries(from, to))
|
|
89
|
+
return [];
|
|
90
|
+
return [{ carrier: 'wrap', from, to, fusesNext: false, origin: 'wallet', floors: [] }];
|
|
91
|
+
};
|
|
92
|
+
const ask = async ({ edge, inputAmount }) => {
|
|
93
|
+
const result = wrapExact(edge, inputAmount);
|
|
94
|
+
if (result === null || result.outcome === 'refused') {
|
|
95
|
+
return { ok: false, refusal: { kind: 'cannot-carry', reason: 'not a native/wrapped pair', minimumAmount: null } };
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
ok: true,
|
|
99
|
+
deliveredAmount: result.deliveredAmount,
|
|
100
|
+
// 1:1, always — there is no slippage to guarantee against.
|
|
101
|
+
minimumDeliveredAmount: result.deliveredAmount,
|
|
102
|
+
claimedDurationSeconds: 0,
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
id: 'wrap',
|
|
107
|
+
edgesFrom,
|
|
108
|
+
ask,
|
|
109
|
+
exact: wrapExact,
|
|
110
|
+
isRateLimited: false,
|
|
111
|
+
// A `deposit()`/`withdraw()` call requires the reader's own wallet
|
|
112
|
+
// signature — never a by-hand deposit.
|
|
113
|
+
depositMode: 'wallet-signed',
|
|
114
|
+
sender: 'swap',
|
|
115
|
+
// A same-chain call settles with the transaction itself.
|
|
116
|
+
settlementSeconds: 0,
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
exports.createWrapCarrier = createWrapCarrier;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a composed multi-signature route is good enough to offer at all.
|
|
3
|
+
*
|
|
4
|
+
* WHY A ROUTE NEEDS A GATE THAT A SINGLE CROSSING DOES NOT. A one-signature
|
|
5
|
+
* route that turns out badly costs the reader one transaction and they still
|
|
6
|
+
* hold something they chose. A composed route — an aggregator leg, then an
|
|
7
|
+
* omnibridge crossing, then a swap — strands them between signatures: after
|
|
8
|
+
* the first they hold an anchor token on a chain they did not ask for, and the
|
|
9
|
+
* only way out is to pay again. So a cheap first leg followed by a bad swap is
|
|
10
|
+
* worse than the route it replaced, and the difference has to be found BEFORE
|
|
11
|
+
* the route is listed rather than after the reader has signed.
|
|
12
|
+
*
|
|
13
|
+
* NOTHING HERE COSTS A REQUEST. Both figures the verdict reads are already in
|
|
14
|
+
* hand when a composer runs: the swap's price impact comes from the on-chain
|
|
15
|
+
* `getAmountsOut` pair the estimate already fetched, and the two dollar values
|
|
16
|
+
* come from the route's own basis. The gate is free, which is why it can run
|
|
17
|
+
* on every route rather than only on suspicious ones.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* The price impact above which a composed route is withdrawn, in percent.
|
|
21
|
+
*
|
|
22
|
+
* MEASURED RATHER THAN CHOSEN. Across the anchor and size combinations swept
|
|
23
|
+
* on 2026-09-21 — bridged wrapped ether, USD Coin, DAI and USDT into wrapped
|
|
24
|
+
* PulseChain coin, at roughly forty and one hundred and forty United States
|
|
25
|
+
* dollars — the impact ran from 0.008 percent to 0.037 percent, with bridged
|
|
26
|
+
* USDT the one outlier at 0.34 to 0.37 percent. Three percent is two orders of
|
|
27
|
+
* magnitude above the normal case and an order above the worst observed, so it
|
|
28
|
+
* cannot fire on an ordinary route; and it is still far below the point where
|
|
29
|
+
* a reader is quietly losing real money on a leg they cannot undo.
|
|
30
|
+
*
|
|
31
|
+
* See `docs/near-intents-conversion-composition.md` for the full sweep.
|
|
32
|
+
*/
|
|
33
|
+
export declare const COMPOSED_ROUTE_MAX_PRICE_IMPACT_PERCENT = 3;
|
|
34
|
+
/**
|
|
35
|
+
* The end-to-end value loss above which a composed route is withdrawn, in
|
|
36
|
+
* percent of what the reader pays.
|
|
37
|
+
*
|
|
38
|
+
* A SECOND TEST BECAUSE THE FIRST CAN READ FINE ON A BROKEN POOL. Price impact
|
|
39
|
+
* is measured against a marginal reference quote taken from the same pool, so
|
|
40
|
+
* a pool that is uniformly mispriced — a stale pair, a pair holding almost
|
|
41
|
+
* nothing — reports a small impact while paying out far less than the tokens
|
|
42
|
+
* going in are worth. Only comparing the two ends of the journey catches that.
|
|
43
|
+
*
|
|
44
|
+
* Measured whole-journey loss on the same sweep ran from 0.81 to 1.36 percent,
|
|
45
|
+
* of which the swap fee alone is 0.29. Five percent leaves every observed
|
|
46
|
+
* route untouched and still stops a route that is quietly eating a twentieth
|
|
47
|
+
* of the reader's money.
|
|
48
|
+
*/
|
|
49
|
+
export declare const COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT = 5;
|
|
50
|
+
/**
|
|
51
|
+
* The end-to-end value GAIN above which a composed route is withdrawn, in
|
|
52
|
+
* percent of what the reader pays.
|
|
53
|
+
*
|
|
54
|
+
* A ROUTE CANNOT MAKE MONEY. Every leg charges a fee, so a route that
|
|
55
|
+
* delivers more value than it takes says one of its figures is wrong. Only a
|
|
56
|
+
* disagreement between the two price feeds can make a gain look real, and the
|
|
57
|
+
* value-loss ceiling already trusts those feeds to within the same five
|
|
58
|
+
* percent. So the bound is symmetric with
|
|
59
|
+
* {@link COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT}.
|
|
60
|
+
*
|
|
61
|
+
* THE DEFECT THIS STOPS. On 2026-10-03 the engine served 0.0002 BNB to HEX at
|
|
62
|
+
* 495,256 HEX, about seven thousand times the rate of the next tier. The
|
|
63
|
+
* value-loss test read that as a loss of minus seven hundred thousand percent
|
|
64
|
+
* and passed it. A PulseX hop rescaled from an upstream live answer keeps the
|
|
65
|
+
* price impact measured at its original, smaller input
|
|
66
|
+
* (`recomposePath` in `@gibs/quotes`'s `search/waves.ts`), so the impact test
|
|
67
|
+
* cannot see an inflated amount either. Only the two ends of the journey can.
|
|
68
|
+
*/
|
|
69
|
+
export declare const COMPOSED_ROUTE_MAX_VALUE_GAIN_PERCENT = 5;
|
|
70
|
+
/**
|
|
71
|
+
* The verdict on one composed route.
|
|
72
|
+
*
|
|
73
|
+
* THREE STATES RATHER THAN A BOOLEAN, because "we have not priced this yet"
|
|
74
|
+
* and "we priced it and it is bad" are different facts and a caller must be
|
|
75
|
+
* able to act differently on them. Collapsing them would either hide a route
|
|
76
|
+
* that is merely still loading or offer one nobody has checked.
|
|
77
|
+
*/
|
|
78
|
+
export type ComposedRouteVerdict = {
|
|
79
|
+
readonly state: 'acceptable';
|
|
80
|
+
} | {
|
|
81
|
+
readonly state: 'unknown';
|
|
82
|
+
readonly missing: 'price-impact' | 'route-value';
|
|
83
|
+
} | {
|
|
84
|
+
readonly state: 'rejected';
|
|
85
|
+
readonly reason: 'price-impact' | 'value-loss' | 'value-gain';
|
|
86
|
+
/** The measured figure, in percent, that failed its ceiling. */
|
|
87
|
+
readonly measuredPercent: number;
|
|
88
|
+
/** The ceiling it failed. */
|
|
89
|
+
readonly ceilingPercent: number;
|
|
90
|
+
};
|
|
91
|
+
/**
|
|
92
|
+
* Judges a composed route against its three ceilings.
|
|
93
|
+
*
|
|
94
|
+
* Checks price impact first, then value loss, then value gain. Impact names
|
|
95
|
+
* the specific leg at fault, while the two value tests only say the journey as
|
|
96
|
+
* a whole went wrong, so impact is the more useful answer when several fail.
|
|
97
|
+
* In the measured percent of a `'value-gain'` rejection, the gain is a
|
|
98
|
+
* positive number.
|
|
99
|
+
*
|
|
100
|
+
* @param options.priceImpactPercent - the swap leg's price impact, from the
|
|
101
|
+
* estimate's marginal reference quote; null when not yet quoted
|
|
102
|
+
* @param options.paidUsdValue - what the reader spends at the origin, in
|
|
103
|
+
* United States dollars; null when unpriced
|
|
104
|
+
* @param options.deliveredUsdValue - what the route delivers, valued on the
|
|
105
|
+
* SAME basis as `paidUsdValue`; null when unpriced
|
|
106
|
+
* @returns the verdict
|
|
107
|
+
*/
|
|
108
|
+
export declare const assessComposedRoute: ({ priceImpactPercent, paidUsdValue, deliveredUsdValue, }: {
|
|
109
|
+
priceImpactPercent: number | null | undefined;
|
|
110
|
+
paidUsdValue: number | null | undefined;
|
|
111
|
+
deliveredUsdValue: number | null | undefined;
|
|
112
|
+
}) => ComposedRouteVerdict;
|
|
113
|
+
/**
|
|
114
|
+
* Whether a composed route may be listed on the strip.
|
|
115
|
+
*
|
|
116
|
+
* ONLY AN EXPLICIT PASS LISTS THE ROUTE. An unknown verdict withholds it, the
|
|
117
|
+
* same as a rejection, because a three-signature route nobody has checked is
|
|
118
|
+
* the case this gate exists for. The two remain distinct in
|
|
119
|
+
* {@link ComposedRouteVerdict} so a surface can tell the reader "still
|
|
120
|
+
* pricing" rather than "no route", which a boolean alone could not.
|
|
121
|
+
*
|
|
122
|
+
* @param verdict - the verdict from {@link assessComposedRoute}
|
|
123
|
+
* @returns true only when the route passed every ceiling
|
|
124
|
+
*/
|
|
125
|
+
export declare const composedRouteMayBeOffered: (verdict: ComposedRouteVerdict) => boolean;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.composedRouteMayBeOffered = exports.assessComposedRoute = exports.COMPOSED_ROUTE_MAX_VALUE_GAIN_PERCENT = exports.COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT = exports.COMPOSED_ROUTE_MAX_PRICE_IMPACT_PERCENT = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Whether a composed multi-signature route is good enough to offer at all.
|
|
6
|
+
*
|
|
7
|
+
* WHY A ROUTE NEEDS A GATE THAT A SINGLE CROSSING DOES NOT. A one-signature
|
|
8
|
+
* route that turns out badly costs the reader one transaction and they still
|
|
9
|
+
* hold something they chose. A composed route — an aggregator leg, then an
|
|
10
|
+
* omnibridge crossing, then a swap — strands them between signatures: after
|
|
11
|
+
* the first they hold an anchor token on a chain they did not ask for, and the
|
|
12
|
+
* only way out is to pay again. So a cheap first leg followed by a bad swap is
|
|
13
|
+
* worse than the route it replaced, and the difference has to be found BEFORE
|
|
14
|
+
* the route is listed rather than after the reader has signed.
|
|
15
|
+
*
|
|
16
|
+
* NOTHING HERE COSTS A REQUEST. Both figures the verdict reads are already in
|
|
17
|
+
* hand when a composer runs: the swap's price impact comes from the on-chain
|
|
18
|
+
* `getAmountsOut` pair the estimate already fetched, and the two dollar values
|
|
19
|
+
* come from the route's own basis. The gate is free, which is why it can run
|
|
20
|
+
* on every route rather than only on suspicious ones.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* The price impact above which a composed route is withdrawn, in percent.
|
|
24
|
+
*
|
|
25
|
+
* MEASURED RATHER THAN CHOSEN. Across the anchor and size combinations swept
|
|
26
|
+
* on 2026-09-21 — bridged wrapped ether, USD Coin, DAI and USDT into wrapped
|
|
27
|
+
* PulseChain coin, at roughly forty and one hundred and forty United States
|
|
28
|
+
* dollars — the impact ran from 0.008 percent to 0.037 percent, with bridged
|
|
29
|
+
* USDT the one outlier at 0.34 to 0.37 percent. Three percent is two orders of
|
|
30
|
+
* magnitude above the normal case and an order above the worst observed, so it
|
|
31
|
+
* cannot fire on an ordinary route; and it is still far below the point where
|
|
32
|
+
* a reader is quietly losing real money on a leg they cannot undo.
|
|
33
|
+
*
|
|
34
|
+
* See `docs/near-intents-conversion-composition.md` for the full sweep.
|
|
35
|
+
*/
|
|
36
|
+
exports.COMPOSED_ROUTE_MAX_PRICE_IMPACT_PERCENT = 3;
|
|
37
|
+
/**
|
|
38
|
+
* The end-to-end value loss above which a composed route is withdrawn, in
|
|
39
|
+
* percent of what the reader pays.
|
|
40
|
+
*
|
|
41
|
+
* A SECOND TEST BECAUSE THE FIRST CAN READ FINE ON A BROKEN POOL. Price impact
|
|
42
|
+
* is measured against a marginal reference quote taken from the same pool, so
|
|
43
|
+
* a pool that is uniformly mispriced — a stale pair, a pair holding almost
|
|
44
|
+
* nothing — reports a small impact while paying out far less than the tokens
|
|
45
|
+
* going in are worth. Only comparing the two ends of the journey catches that.
|
|
46
|
+
*
|
|
47
|
+
* Measured whole-journey loss on the same sweep ran from 0.81 to 1.36 percent,
|
|
48
|
+
* of which the swap fee alone is 0.29. Five percent leaves every observed
|
|
49
|
+
* route untouched and still stops a route that is quietly eating a twentieth
|
|
50
|
+
* of the reader's money.
|
|
51
|
+
*/
|
|
52
|
+
exports.COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT = 5;
|
|
53
|
+
/**
|
|
54
|
+
* The end-to-end value GAIN above which a composed route is withdrawn, in
|
|
55
|
+
* percent of what the reader pays.
|
|
56
|
+
*
|
|
57
|
+
* A ROUTE CANNOT MAKE MONEY. Every leg charges a fee, so a route that
|
|
58
|
+
* delivers more value than it takes says one of its figures is wrong. Only a
|
|
59
|
+
* disagreement between the two price feeds can make a gain look real, and the
|
|
60
|
+
* value-loss ceiling already trusts those feeds to within the same five
|
|
61
|
+
* percent. So the bound is symmetric with
|
|
62
|
+
* {@link COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT}.
|
|
63
|
+
*
|
|
64
|
+
* THE DEFECT THIS STOPS. On 2026-10-03 the engine served 0.0002 BNB to HEX at
|
|
65
|
+
* 495,256 HEX, about seven thousand times the rate of the next tier. The
|
|
66
|
+
* value-loss test read that as a loss of minus seven hundred thousand percent
|
|
67
|
+
* and passed it. A PulseX hop rescaled from an upstream live answer keeps the
|
|
68
|
+
* price impact measured at its original, smaller input
|
|
69
|
+
* (`recomposePath` in `@gibs/quotes`'s `search/waves.ts`), so the impact test
|
|
70
|
+
* cannot see an inflated amount either. Only the two ends of the journey can.
|
|
71
|
+
*/
|
|
72
|
+
exports.COMPOSED_ROUTE_MAX_VALUE_GAIN_PERCENT = 5;
|
|
73
|
+
/**
|
|
74
|
+
* Judges a composed route against its three ceilings.
|
|
75
|
+
*
|
|
76
|
+
* Checks price impact first, then value loss, then value gain. Impact names
|
|
77
|
+
* the specific leg at fault, while the two value tests only say the journey as
|
|
78
|
+
* a whole went wrong, so impact is the more useful answer when several fail.
|
|
79
|
+
* In the measured percent of a `'value-gain'` rejection, the gain is a
|
|
80
|
+
* positive number.
|
|
81
|
+
*
|
|
82
|
+
* @param options.priceImpactPercent - the swap leg's price impact, from the
|
|
83
|
+
* estimate's marginal reference quote; null when not yet quoted
|
|
84
|
+
* @param options.paidUsdValue - what the reader spends at the origin, in
|
|
85
|
+
* United States dollars; null when unpriced
|
|
86
|
+
* @param options.deliveredUsdValue - what the route delivers, valued on the
|
|
87
|
+
* SAME basis as `paidUsdValue`; null when unpriced
|
|
88
|
+
* @returns the verdict
|
|
89
|
+
*/
|
|
90
|
+
const assessComposedRoute = ({ priceImpactPercent, paidUsdValue, deliveredUsdValue, }) => {
|
|
91
|
+
if (typeof priceImpactPercent !== 'number' || !Number.isFinite(priceImpactPercent)) {
|
|
92
|
+
return { state: 'unknown', missing: 'price-impact' };
|
|
93
|
+
}
|
|
94
|
+
if (priceImpactPercent > exports.COMPOSED_ROUTE_MAX_PRICE_IMPACT_PERCENT) {
|
|
95
|
+
return {
|
|
96
|
+
state: 'rejected',
|
|
97
|
+
reason: 'price-impact',
|
|
98
|
+
measuredPercent: priceImpactPercent,
|
|
99
|
+
ceilingPercent: exports.COMPOSED_ROUTE_MAX_PRICE_IMPACT_PERCENT,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const bothValuesKnown = typeof paidUsdValue === 'number' &&
|
|
103
|
+
Number.isFinite(paidUsdValue) &&
|
|
104
|
+
paidUsdValue > 0 &&
|
|
105
|
+
typeof deliveredUsdValue === 'number' &&
|
|
106
|
+
Number.isFinite(deliveredUsdValue);
|
|
107
|
+
if (!bothValuesKnown) {
|
|
108
|
+
return { state: 'unknown', missing: 'route-value' };
|
|
109
|
+
}
|
|
110
|
+
const lossPercent = ((paidUsdValue - deliveredUsdValue) / paidUsdValue) * 100;
|
|
111
|
+
if (lossPercent > exports.COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT) {
|
|
112
|
+
return {
|
|
113
|
+
state: 'rejected',
|
|
114
|
+
reason: 'value-loss',
|
|
115
|
+
measuredPercent: lossPercent,
|
|
116
|
+
ceilingPercent: exports.COMPOSED_ROUTE_MAX_VALUE_LOSS_PERCENT,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const gainPercent = -lossPercent;
|
|
120
|
+
if (gainPercent > exports.COMPOSED_ROUTE_MAX_VALUE_GAIN_PERCENT) {
|
|
121
|
+
return {
|
|
122
|
+
state: 'rejected',
|
|
123
|
+
reason: 'value-gain',
|
|
124
|
+
measuredPercent: gainPercent,
|
|
125
|
+
ceilingPercent: exports.COMPOSED_ROUTE_MAX_VALUE_GAIN_PERCENT,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return { state: 'acceptable' };
|
|
129
|
+
};
|
|
130
|
+
exports.assessComposedRoute = assessComposedRoute;
|
|
131
|
+
/**
|
|
132
|
+
* Whether a composed route may be listed on the strip.
|
|
133
|
+
*
|
|
134
|
+
* ONLY AN EXPLICIT PASS LISTS THE ROUTE. An unknown verdict withholds it, the
|
|
135
|
+
* same as a rejection, because a three-signature route nobody has checked is
|
|
136
|
+
* the case this gate exists for. The two remain distinct in
|
|
137
|
+
* {@link ComposedRouteVerdict} so a surface can tell the reader "still
|
|
138
|
+
* pricing" rather than "no route", which a boolean alone could not.
|
|
139
|
+
*
|
|
140
|
+
* @param verdict - the verdict from {@link assessComposedRoute}
|
|
141
|
+
* @returns true only when the route passed every ceiling
|
|
142
|
+
*/
|
|
143
|
+
const composedRouteMayBeOffered = (verdict) => verdict.state === 'acceptable';
|
|
144
|
+
exports.composedRouteMayBeOffered = composedRouteMayBeOffered;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Token } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Which anchor tokens a non-executing aggregator may deliver on an omnibridge
|
|
4
|
+
* entry chain, and what the reader's own crossing signature then does with
|
|
5
|
+
* each one.
|
|
6
|
+
*
|
|
7
|
+
* WHY THE ANCHOR HAS TO BE A CHOICE RATHER THAN A DERIVATION. A two-signature
|
|
8
|
+
* route quotes towards one entry-chain token, taken straight from the
|
|
9
|
+
* destination pair. For a native-PulseChain destination that token is
|
|
10
|
+
* WRAPPED ether, because the omnibridge home list holds no entry for the
|
|
11
|
+
* native coin at all — measured on 2026-09-21, 657 Ethereum-side entries and
|
|
12
|
+
* not one at the zero address — so the pair a native destination resolves to
|
|
13
|
+
* is `WETH ↔ WETH from Ethereum`.
|
|
14
|
+
*
|
|
15
|
+
* NEAR Intents has NO LIQUIDITY IN WRAPPED ETHER ON ETHEREUM. Measured at
|
|
16
|
+
* 0.015, 0.05, 0.2 and 0.5 ether of input, every request answered
|
|
17
|
+
* `No liquidity available`. It carries the NATIVE coin on the same chain with
|
|
18
|
+
* plenty. So the one derived anchor is the one anchor the service cannot
|
|
19
|
+
* price, the quote is refused, and the route withdraws in silence — which is
|
|
20
|
+
* the correct response to a refusal and is also what hid this from us.
|
|
21
|
+
*
|
|
22
|
+
* WRAPPED NATIVE AND THE NATIVE COIN LAND IN THE SAME PLACE, which is what
|
|
23
|
+
* makes the substitution safe rather than merely convenient. The mediator's
|
|
24
|
+
* native router wraps on the way in: `wrapAndRelayTokens` takes the coin as
|
|
25
|
+
* transaction value and the crossing mints the SAME PulseChain token the
|
|
26
|
+
* wrapped entry would have minted. See `settings.ts`, the
|
|
27
|
+
* `assetIn.address === zeroAddress` branch. So swapping one anchor for the
|
|
28
|
+
* other changes what the aggregator delivers and changes nothing downstream.
|
|
29
|
+
*
|
|
30
|
+
* IT IS ALSO CHEAPER AND SHORTER, on both legs. Measured from Base native
|
|
31
|
+
* ether on 2026-09-21, one request batch per size:
|
|
32
|
+
*
|
|
33
|
+
* | anchor on Ethereum | 0.015 ether | 0.05 ether | 0.2 ether |
|
|
34
|
+
* | ------------------ | ----------- | ---------- | --------- |
|
|
35
|
+
* | native ether | 0.494% | 0.330% | 0.278% |
|
|
36
|
+
* | USD Coin | 1.037% | 0.651% | 0.509% |
|
|
37
|
+
* | Tether | 1.052% | 0.665% | 0.506% |
|
|
38
|
+
* | wrapped ether | refused | refused | refused |
|
|
39
|
+
*
|
|
40
|
+
* And the native coin needs no allowance, so its crossing is ONE signature
|
|
41
|
+
* where an entry-chain token's is an approval followed by a relay.
|
|
42
|
+
*/
|
|
43
|
+
/** What the reader's crossing signature calls with a given anchor. */
|
|
44
|
+
export type EntryChainCrossingCall =
|
|
45
|
+
/** Native coin sent as transaction value; the router wraps and relays it. */
|
|
46
|
+
'wrapAndRelayTokens'
|
|
47
|
+
/** An entry-chain token handed to the mediator, which needs an allowance first. */
|
|
48
|
+
| 'relayTokens';
|
|
49
|
+
/** One anchor the aggregator may be asked to deliver, and its consequences. */
|
|
50
|
+
export type EntryChainAnchor = {
|
|
51
|
+
/** The token the aggregator delivers into the reader's own wallet. */
|
|
52
|
+
readonly token: Token;
|
|
53
|
+
/** What the reader's crossing signature calls with it. */
|
|
54
|
+
readonly crossingCall: EntryChainCrossingCall;
|
|
55
|
+
/**
|
|
56
|
+
* Whether the crossing needs a separate approval transaction first, which
|
|
57
|
+
* is an EXTRA signature on a route that already asks for several.
|
|
58
|
+
*/
|
|
59
|
+
readonly requiresApproval: boolean;
|
|
60
|
+
/** The PulseChain token the crossing mints. Identical across every anchor here. */
|
|
61
|
+
readonly landsAs: Token;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Builds the ordered anchor list for one destination pair, cheapest and
|
|
65
|
+
* shortest first.
|
|
66
|
+
*
|
|
67
|
+
* The list is ordered, not ranked: a caller asks the aggregator for the first
|
|
68
|
+
* entry and moves to the next only on a refusal. That keeps the cost at ONE
|
|
69
|
+
* vendor request in the ordinary case, which matters against a limit of
|
|
70
|
+
* twelve hundred requests per sixty seconds.
|
|
71
|
+
*
|
|
72
|
+
* The substitution is narrow ON PURPOSE. Only the chain's wrapped native
|
|
73
|
+
* asset gains a second anchor, because only the native coin mints the same
|
|
74
|
+
* PulseChain token. A USD Coin pair does not gain an ether anchor here —
|
|
75
|
+
* ether would land as bridged ether, a different token, needing a different
|
|
76
|
+
* swap path. That is a different DESTINATION PAIR, and choosing between pairs
|
|
77
|
+
* is made by the route engine, which holds every destination pair.
|
|
78
|
+
*
|
|
79
|
+
* @param options.entryChainToken - the destination pair's entry-chain side
|
|
80
|
+
* @param options.pulsechainToken - the destination pair's PulseChain side
|
|
81
|
+
* @param options.entryChainId - the omnibridge entry chain's decimal id
|
|
82
|
+
* @returns the anchors to try, in order; never empty
|
|
83
|
+
*/
|
|
84
|
+
export declare const entryChainAnchorsForPair: ({ entryChainToken, pulsechainToken, entryChainId, }: {
|
|
85
|
+
entryChainToken: Token;
|
|
86
|
+
pulsechainToken: Token;
|
|
87
|
+
entryChainId: number;
|
|
88
|
+
}) => readonly EntryChainAnchor[];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.entryChainAnchorsForPair = void 0;
|
|
4
|
+
const viem_1 = require("viem");
|
|
5
|
+
const config_js_1 = require("../config.js");
|
|
6
|
+
const ecosystems_js_1 = require("../ecosystems.js");
|
|
7
|
+
/**
|
|
8
|
+
* Builds the ordered anchor list for one destination pair, cheapest and
|
|
9
|
+
* shortest first.
|
|
10
|
+
*
|
|
11
|
+
* The list is ordered, not ranked: a caller asks the aggregator for the first
|
|
12
|
+
* entry and moves to the next only on a refusal. That keeps the cost at ONE
|
|
13
|
+
* vendor request in the ordinary case, which matters against a limit of
|
|
14
|
+
* twelve hundred requests per sixty seconds.
|
|
15
|
+
*
|
|
16
|
+
* The substitution is narrow ON PURPOSE. Only the chain's wrapped native
|
|
17
|
+
* asset gains a second anchor, because only the native coin mints the same
|
|
18
|
+
* PulseChain token. A USD Coin pair does not gain an ether anchor here —
|
|
19
|
+
* ether would land as bridged ether, a different token, needing a different
|
|
20
|
+
* swap path. That is a different DESTINATION PAIR, and choosing between pairs
|
|
21
|
+
* is made by the route engine, which holds every destination pair.
|
|
22
|
+
*
|
|
23
|
+
* @param options.entryChainToken - the destination pair's entry-chain side
|
|
24
|
+
* @param options.pulsechainToken - the destination pair's PulseChain side
|
|
25
|
+
* @param options.entryChainId - the omnibridge entry chain's decimal id
|
|
26
|
+
* @returns the anchors to try, in order; never empty
|
|
27
|
+
*/
|
|
28
|
+
const entryChainAnchorsForPair = ({ entryChainToken, pulsechainToken, entryChainId, }) => {
|
|
29
|
+
const pairAnchor = {
|
|
30
|
+
token: entryChainToken,
|
|
31
|
+
crossingCall: 'relayTokens',
|
|
32
|
+
requiresApproval: true,
|
|
33
|
+
landsAs: pulsechainToken,
|
|
34
|
+
};
|
|
35
|
+
const entryChain = (0, config_js_1.toChain)(entryChainId);
|
|
36
|
+
// `nativeAssetOut` is typed as a total record and is not one — `toChain` is a
|
|
37
|
+
// cast, so an unmapped chain id yields a key that indexes to undefined. The
|
|
38
|
+
// runtime check outlives the static type on purpose.
|
|
39
|
+
const wrappedNativeAddress = config_js_1.nativeAssetOut[entryChain];
|
|
40
|
+
if (wrappedNativeAddress === undefined ||
|
|
41
|
+
!(0, ecosystems_js_1.sameAddress)(entryChainToken.address, wrappedNativeAddress, 'evm')) {
|
|
42
|
+
return [pairAnchor];
|
|
43
|
+
}
|
|
44
|
+
const nativeAnchor = {
|
|
45
|
+
token: {
|
|
46
|
+
// Decimals and name come from the WRAPPED token rather than from a
|
|
47
|
+
// constant, because the wrapped asset mirrors its coin exactly and a
|
|
48
|
+
// hard-coded eighteen would be a second source of the same fact.
|
|
49
|
+
...entryChainToken,
|
|
50
|
+
address: viem_1.zeroAddress,
|
|
51
|
+
symbol: config_js_1.nativeTokenSymbol[entryChain] ?? entryChainToken.symbol,
|
|
52
|
+
},
|
|
53
|
+
crossingCall: 'wrapAndRelayTokens',
|
|
54
|
+
requiresApproval: false,
|
|
55
|
+
landsAs: pulsechainToken,
|
|
56
|
+
};
|
|
57
|
+
return [nativeAnchor, pairAnchor];
|
|
58
|
+
};
|
|
59
|
+
exports.entryChainAnchorsForPair = entryChainAnchorsForPair;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats a United States dollar number to a cent-precise string ("1,234.50",
|
|
3
|
+
* or "1.234,50" in German). No leading currency symbol — callers prepend it
|
|
4
|
+
* where the design needs one. The single source of truth shared by the raw
|
|
5
|
+
* producers (cent-rounding) and the display adapter (final formatting).
|
|
6
|
+
*
|
|
7
|
+
* `locale` defaults to `'en-US'` so every caller that does not read the
|
|
8
|
+
* reader's own language — the raw producers, and every existing test —
|
|
9
|
+
* keeps today's exact string. THE DISPLAY ADAPTER (`toRouteView.ts`) is the
|
|
10
|
+
* one caller that should pass the reader's own Lingui locale (`i18n.locale`):
|
|
11
|
+
* without it, the route strip's dollar figures stayed English-formatted
|
|
12
|
+
* inside every other translated screen, the one number on the page that
|
|
13
|
+
* never adopted the reader's language. The digit count never moves — only
|
|
14
|
+
* `toLocaleString`'s grouping and decimal separators do, per
|
|
15
|
+
* `minimumFractionDigits`/`maximumFractionDigits` staying pinned at 2.
|
|
16
|
+
*
|
|
17
|
+
* @param value - the United States dollar amount as a number
|
|
18
|
+
* @param locale - a BCP 47 locale tag to format with; defaults to `'en-US'`
|
|
19
|
+
* @returns the formatted two-decimal string, without a leading `$`
|
|
20
|
+
*/
|
|
21
|
+
export declare const formatUsd: (value: number, locale?: string) => string;
|
|
22
|
+
/**
|
|
23
|
+
* Nullable {@link formatUsd}: null/undefined pass through as null so an unpriced
|
|
24
|
+
* value renders as absent rather than "0.00".
|
|
25
|
+
*
|
|
26
|
+
* @param value - the United States dollar amount, or null/undefined when unknown
|
|
27
|
+
* @param locale - a BCP 47 locale tag to format with; defaults to `'en-US'`
|
|
28
|
+
* @returns the formatted string, or null when the value is absent
|
|
29
|
+
*/
|
|
30
|
+
export declare const formatUsdNullable: (value: number | null | undefined, locale?: string) => string | null;
|
|
31
|
+
export declare const roundUsdToCents: (value: number) => number;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.roundUsdToCents = exports.formatUsdNullable = exports.formatUsd = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Formats a United States dollar number to a cent-precise string ("1,234.50",
|
|
6
|
+
* or "1.234,50" in German). No leading currency symbol — callers prepend it
|
|
7
|
+
* where the design needs one. The single source of truth shared by the raw
|
|
8
|
+
* producers (cent-rounding) and the display adapter (final formatting).
|
|
9
|
+
*
|
|
10
|
+
* `locale` defaults to `'en-US'` so every caller that does not read the
|
|
11
|
+
* reader's own language — the raw producers, and every existing test —
|
|
12
|
+
* keeps today's exact string. THE DISPLAY ADAPTER (`toRouteView.ts`) is the
|
|
13
|
+
* one caller that should pass the reader's own Lingui locale (`i18n.locale`):
|
|
14
|
+
* without it, the route strip's dollar figures stayed English-formatted
|
|
15
|
+
* inside every other translated screen, the one number on the page that
|
|
16
|
+
* never adopted the reader's language. The digit count never moves — only
|
|
17
|
+
* `toLocaleString`'s grouping and decimal separators do, per
|
|
18
|
+
* `minimumFractionDigits`/`maximumFractionDigits` staying pinned at 2.
|
|
19
|
+
*
|
|
20
|
+
* @param value - the United States dollar amount as a number
|
|
21
|
+
* @param locale - a BCP 47 locale tag to format with; defaults to `'en-US'`
|
|
22
|
+
* @returns the formatted two-decimal string, without a leading `$`
|
|
23
|
+
*/
|
|
24
|
+
const formatUsd = (value, locale = 'en-US') => value.toLocaleString(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
|
25
|
+
exports.formatUsd = formatUsd;
|
|
26
|
+
/**
|
|
27
|
+
* Nullable {@link formatUsd}: null/undefined pass through as null so an unpriced
|
|
28
|
+
* value renders as absent rather than "0.00".
|
|
29
|
+
*
|
|
30
|
+
* @param value - the United States dollar amount, or null/undefined when unknown
|
|
31
|
+
* @param locale - a BCP 47 locale tag to format with; defaults to `'en-US'`
|
|
32
|
+
* @returns the formatted string, or null when the value is absent
|
|
33
|
+
*/
|
|
34
|
+
const formatUsdNullable = (value, locale) => value === null || value === undefined ? null : (0, exports.formatUsd)(value, locale);
|
|
35
|
+
exports.formatUsdNullable = formatUsdNullable;
|
|
36
|
+
/**
|
|
37
|
+
* Rounds a United States dollar amount to whole cents.
|
|
38
|
+
*
|
|
39
|
+
* This replaces cent-rounding that went through the DISPLAY formatter and parsed
|
|
40
|
+
* the result back — `Number(formatUsd(value).replace(/,/g, ''))`. That works
|
|
41
|
+
* only while the display is pinned to a locale which groups with commas and
|
|
42
|
+
* decimalises with a dot. The moment money follows the reader's language, a
|
|
43
|
+
* German build formats 1234.56 as "1.234,56", and stripping the commas from THAT
|
|
44
|
+
* yields 1.23456: a fee understated by a factor of a thousand, silently, with no
|
|
45
|
+
* NaN anywhere to make it visible.
|
|
46
|
+
*
|
|
47
|
+
* It keeps its OWN formatter rather than borrowing the display one, pinned to a
|
|
48
|
+
* fixed locale and with grouping switched off, so the string it parses has no
|
|
49
|
+
* separators to strip and no locale to disagree with. That also makes it exact
|
|
50
|
+
* rather than merely close: `Math.round(value * 100) / 100` looks equivalent and
|
|
51
|
+
* is not, because it rounds the binary double while the formatter rounds the
|
|
52
|
+
* decimal the reader typed — they disagree on 1.005, giving 1.00 against 1.01.
|
|
53
|
+
* Matching the old behaviour to the cent is the point, so the old engine stays.
|
|
54
|
+
*
|
|
55
|
+
* @param value - the United States dollar amount
|
|
56
|
+
* @returns the amount rounded to two decimal places
|
|
57
|
+
*/
|
|
58
|
+
const centRounder = new Intl.NumberFormat('en-US', {
|
|
59
|
+
minimumFractionDigits: 2,
|
|
60
|
+
maximumFractionDigits: 2,
|
|
61
|
+
useGrouping: false,
|
|
62
|
+
});
|
|
63
|
+
const roundUsdToCents = (value) => Number(centRounder.format(value));
|
|
64
|
+
exports.roundUsdToCents = roundUsdToCents;
|