@gibs/bridge-sdk 1.13.0 → 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 +19 -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 +69 -21
- package/dist/routing/carriers/pulsex.js +184 -65
- 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 +7 -3
- package/dist/routing/index.js +7 -3
- 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 +29 -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 +33 -1
- package/dist/routing/search/score.js +41 -6
- 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 +234 -0
- 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 +818 -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
package/dist/config.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as _ from 'lodash-es';
|
|
2
1
|
import { getAddress, isAddress, isAddressEqual, keccak256, stringToHex, zeroAddress, } from 'viem';
|
|
3
2
|
/**
|
|
4
3
|
* Add here your chain id as hex, be sure to add your chain in the chainsMetadata object too
|
|
@@ -44,6 +43,30 @@ export const nativeAssetOut = {
|
|
|
44
43
|
[Chains.SEP]: '0x7b79995e5f793A07Bc00c21412e50Ecae098E7f9',
|
|
45
44
|
[Chains.V4PLS]: '0x70499adEBB11Efd915E3b69E700c331778628707',
|
|
46
45
|
};
|
|
46
|
+
/**
|
|
47
|
+
* Four PulseChain mainnet tokens that no other constant in this package
|
|
48
|
+
* names. The PulseX carrier routes swaps through them
|
|
49
|
+
* (`routing/carriers/pulsex-paths.ts`), and the interface builds its
|
|
50
|
+
* PulseChain hub nodes from the two stablecoins.
|
|
51
|
+
*
|
|
52
|
+
* Each address was already in the repository before it moved here: USDC and
|
|
53
|
+
* USDT from the interface's `HUB_STABLECOIN_ADDRESSES`
|
|
54
|
+
* (`packages/ui/src/lib/state/useRouteSearch.ts`), PLSX and HEX from
|
|
55
|
+
* `scripts/route-parity-corpus.ts`. Each one was read on chain again on
|
|
56
|
+
* 2026-10-06 (`symbol()`, `decimals()`): PLSX 18, HEX 8, USDC 6, USDT 6.
|
|
57
|
+
* `getAddress` checksums them at load, so a mistyped character fails the
|
|
58
|
+
* build rather than silently matching nothing.
|
|
59
|
+
*/
|
|
60
|
+
export const pulsechainTokenAddresses = Object.freeze({
|
|
61
|
+
/** PulseX, the exchange's own token. */
|
|
62
|
+
PLSX: getAddress('0x95b303987a60c71504d99aa1b13b4da07b0790ab'),
|
|
63
|
+
/** HEX. */
|
|
64
|
+
HEX: getAddress('0x2b591e99afe9f32eaa6214f7b7629768c40eeb39'),
|
|
65
|
+
/** USD Coin, bridged from Ethereum. */
|
|
66
|
+
USDC: getAddress('0x15d38573d2feeb82e7ad5187ab8c1d52810b1f07'),
|
|
67
|
+
/** Tether USD, bridged from Ethereum. */
|
|
68
|
+
USDT: getAddress('0x0cb6f5a34ad42ec934882a05265a7d5f59b51a2f'),
|
|
69
|
+
});
|
|
47
70
|
/** A map of chains to deprecated native asset out addresses */
|
|
48
71
|
export const deprecatedNativeAssetOut = {
|
|
49
72
|
[Chains.PLS]: new Set(['0x97Ac4a2439A47c07ad535bb1188c989dae755341']),
|
|
@@ -335,12 +358,23 @@ export const inferBridgeKey = ({ currentKey, provider, fromChain, toChain, isPro
|
|
|
335
358
|
* @param isProd - whether this is a production build; testnets are off when true
|
|
336
359
|
* @returns the group keyed by destination chain, or undefined when unknown
|
|
337
360
|
*/
|
|
338
|
-
export const pathwayGroup = (provider, chain, isProd) =>
|
|
339
|
-
|
|
361
|
+
export const pathwayGroup = (provider, chain, isProd) => pathways[provider]?.[chain] || (!isProd ? testnetPathways[provider]?.[chain] : undefined);
|
|
362
|
+
/**
|
|
363
|
+
* Reads one pathway out of a provider, chain and chain map.
|
|
364
|
+
*
|
|
365
|
+
* Every level is optional at runtime: the testnet map leaves whole providers
|
|
366
|
+
* out, and a caller can hold a bridge key built from untrusted input.
|
|
367
|
+
*
|
|
368
|
+
* @param config - the pathway map to read
|
|
369
|
+
* @param bridgeKey - the provider, origin chain and destination chain
|
|
370
|
+
* @returns the pathway, or undefined when any level is missing
|
|
371
|
+
*/
|
|
372
|
+
const lookupPathway = (config, [provider, from, to]) => config[provider]?.[from]?.[to];
|
|
340
373
|
export const pathway = (bridgeKey, isProd, assetInAddress) => {
|
|
341
374
|
if (!bridgeKey)
|
|
342
375
|
return;
|
|
343
|
-
let pathway =
|
|
376
|
+
let pathway = lookupPathway(pathways, bridgeKey) ||
|
|
377
|
+
(!isProd ? lookupPathway(testnetPathways, bridgeKey) : undefined);
|
|
344
378
|
if (assetInAddress) {
|
|
345
379
|
const addr = getAddress(assetInAddress);
|
|
346
380
|
if (pathway?.settingOverrides?.[addr]) {
|
|
@@ -362,7 +396,7 @@ export const defaultAssetIn = ($bridgeKey, isProd) => {
|
|
|
362
396
|
if (!$bridgeKey)
|
|
363
397
|
return null;
|
|
364
398
|
const conf = pathway($bridgeKey, isProd);
|
|
365
|
-
const defaultAssetIn =
|
|
399
|
+
const defaultAssetIn = conf?.bridgedNativeAssetOut;
|
|
366
400
|
return defaultAssetIn;
|
|
367
401
|
};
|
|
368
402
|
/**
|
|
@@ -1,22 +1,26 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Hex } from 'viem';
|
|
2
2
|
import type { DestinationCall } from './destination-calldata.js';
|
|
3
3
|
import type { Ecosystem } from './ecosystems.js';
|
|
4
4
|
import { type EvmDeposit, type OriginDeposit } from './origin-deposit.js';
|
|
5
5
|
import type { ProviderId, ProviderQuoteContext } from './providers.js';
|
|
6
6
|
import { type QuoteFee } from './quote-fees.js';
|
|
7
|
+
import type { ApprovalRequirement } from './settings.js';
|
|
7
8
|
import type { RouteStep, Token } from './types.js';
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
-
* {@link ConsolidatedQuoteRequest.lifiIntegrator} out.
|
|
10
|
+
* Gibs Finance's own LI.FI `integrator` label.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* NOT A DEFAULT. A request that leaves
|
|
13
|
+
* {@link ConsolidatedQuoteRequest.lifiIntegrator} out sends no `integrator`
|
|
14
|
+
* at all. An integrator label is attribution: LI.FI records the traffic under
|
|
15
|
+
* it, and pays any `fee` to the account behind it. A wallet that builds on this
|
|
16
|
+
* package must not attribute its users' routes to Gibs Finance without
|
|
17
|
+
* choosing to. The Gibs interface passes this value explicitly.
|
|
17
18
|
*/
|
|
18
19
|
export declare const lifiIntegrator = "gibs.finance";
|
|
19
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* The referral fee Gibs Finance charges on its own LI.FI routes (0.001 = 0.1%).
|
|
22
|
+
* No builder applies it. A request carries a fee only when it sets `lifiFee`.
|
|
23
|
+
*/
|
|
20
24
|
export declare const lifiReferralFeePercent = 0.001;
|
|
21
25
|
/**
|
|
22
26
|
* LI.FI's HTTP origin when the interface's `PUBLIC_LIFI_API_URL` build flag is
|
|
@@ -235,11 +239,10 @@ export type ConsolidatedQuoteRequest = {
|
|
|
235
239
|
*/
|
|
236
240
|
lifiFee?: number;
|
|
237
241
|
/**
|
|
238
|
-
* The LI.FI `integrator` label. Left out
|
|
239
|
-
*
|
|
242
|
+
* The LI.FI `integrator` label. Left out or `null`, the field is not sent; a
|
|
243
|
+
* string is sent as-is. REQUIRED (as a string)
|
|
240
244
|
* whenever {@link lifiFee} is positive: LI.FI pays a fee to the account behind
|
|
241
|
-
* the integrator label, so a fee
|
|
242
|
-
* Finance, not you.
|
|
245
|
+
* the integrator label, so a fee needs a label that is yours.
|
|
243
246
|
*/
|
|
244
247
|
lifiIntegrator?: string | null;
|
|
245
248
|
/**
|
|
@@ -298,7 +301,7 @@ export type LifiContractCallsRequestBody = {
|
|
|
298
301
|
toToken: Hex;
|
|
299
302
|
toAmount: string;
|
|
300
303
|
toFallbackAddress: Hex;
|
|
301
|
-
/**
|
|
304
|
+
/** Present only when the request names a `lifiIntegrator`. */
|
|
302
305
|
integrator?: string;
|
|
303
306
|
/** Referral fee as a decimal fraction (e.g. 0.001 = 0.1%). Omitted when not charging a fee. */
|
|
304
307
|
fee?: number;
|
|
@@ -313,7 +316,7 @@ export type LifiContractCallsRequestBody = {
|
|
|
313
316
|
* @param request - the consolidated request's attribution fields
|
|
314
317
|
* @returns the fields to merge into the request; empty when none apply
|
|
315
318
|
* @throws when a positive `lifiFee` arrives without an explicit `lifiIntegrator`
|
|
316
|
-
* string —
|
|
319
|
+
* string — LI.FI would have nobody of yours to pay it to
|
|
317
320
|
*/
|
|
318
321
|
export declare const lifiAttribution: (request: Pick<ConsolidatedQuoteRequest, "lifiFee" | "lifiIntegrator">) => {
|
|
319
322
|
integrator?: string;
|
|
@@ -427,6 +430,97 @@ export type ConsolidatedQuote = {
|
|
|
427
430
|
* path must never silently proceed on a Solana or Bitcoin payload.
|
|
428
431
|
*/
|
|
429
432
|
export declare const evmDepositOf: (quote: ConsolidatedQuote) => EvmDeposit;
|
|
433
|
+
/** Why an aggregator's origin deposit is refused before anything is approved or sent. */
|
|
434
|
+
export type AggregatorOriginRefusal = {
|
|
435
|
+
reason: 'not-evm-deposit';
|
|
436
|
+
depositFamily: string;
|
|
437
|
+
} | {
|
|
438
|
+
reason: 'chain-mismatch';
|
|
439
|
+
depositChainId: number;
|
|
440
|
+
requestChainId: number;
|
|
441
|
+
} | {
|
|
442
|
+
reason: 'unverified-provider';
|
|
443
|
+
provider: string;
|
|
444
|
+
} | {
|
|
445
|
+
reason: 'unverified-chain';
|
|
446
|
+
provider: string;
|
|
447
|
+
chainId: number;
|
|
448
|
+
} | {
|
|
449
|
+
reason: 'spender-not-allowed';
|
|
450
|
+
provider: string;
|
|
451
|
+
spender: Hex;
|
|
452
|
+
} | {
|
|
453
|
+
reason: 'deposit-target-not-allowed';
|
|
454
|
+
provider: string;
|
|
455
|
+
target: Hex;
|
|
456
|
+
} | {
|
|
457
|
+
reason: 'deposit-shape';
|
|
458
|
+
provider: string;
|
|
459
|
+
detail: string;
|
|
460
|
+
} | {
|
|
461
|
+
reason: 'amount-mismatch';
|
|
462
|
+
provider: string;
|
|
463
|
+
/** Which figure disagreed: the quote's own, the native value, or the amount inside the calldata. */
|
|
464
|
+
field: 'quote-from-amount' | 'deposit-value' | 'calldata-amount';
|
|
465
|
+
expected: bigint;
|
|
466
|
+
actual: bigint;
|
|
467
|
+
} | {
|
|
468
|
+
reason: 'recipient-mismatch';
|
|
469
|
+
provider: string;
|
|
470
|
+
/** Which address disagreed: the native payee, the transfer payee, or the token a deposit names. */
|
|
471
|
+
field: 'deposit-to' | 'transfer-to' | 'calldata-token';
|
|
472
|
+
expected: string;
|
|
473
|
+
actual: string;
|
|
474
|
+
} | {
|
|
475
|
+
reason: 'lifi-guard';
|
|
476
|
+
detail: string;
|
|
477
|
+
};
|
|
478
|
+
/**
|
|
479
|
+
* What an aggregator's Ethereum origin deposit asks the wallet to send: the
|
|
480
|
+
* approval it needs first, if any, and the deposit itself. Both are checked
|
|
481
|
+
* before they are returned.
|
|
482
|
+
*/
|
|
483
|
+
export type AggregatorOriginCalls = {
|
|
484
|
+
kind: 'calls';
|
|
485
|
+
approval: ApprovalRequirement | null;
|
|
486
|
+
deposit: EvmDeposit;
|
|
487
|
+
} | {
|
|
488
|
+
kind: 'refused';
|
|
489
|
+
refusal: AggregatorOriginRefusal;
|
|
490
|
+
};
|
|
491
|
+
/**
|
|
492
|
+
* Reads the approval and the deposit off an aggregator quote with an Ethereum
|
|
493
|
+
* origin, after checking both against what this code knows about the
|
|
494
|
+
* provider. The interface's aggregator press (`stores/consolidated-execute.ts`)
|
|
495
|
+
* and the SDK's execution planner both read them here, so neither can sign a
|
|
496
|
+
* call the other would refuse.
|
|
497
|
+
*
|
|
498
|
+
* WHAT IS CHECKED, BEFORE ANYTHING IS APPROVED OR SENT:
|
|
499
|
+
* - Every provider: when the request names the amount to send
|
|
500
|
+
* (`request.fromAmount`), the quote's own `fromAmount` must equal it.
|
|
501
|
+
* - LI.FI: `verifyLifiEvmExecution`: the Diamond for the chain, a known
|
|
502
|
+
* facet call, calldata that matches the request, and the decoded amount
|
|
503
|
+
* pulled from the reader equal to the amount to send.
|
|
504
|
+
* - Relay: the deposit target and the spender are Relay's depository for the
|
|
505
|
+
* chain (`relayDepositoryByChainId`). A native deposit carries the amount
|
|
506
|
+
* as `value` through `depositNative`; a token deposit carries no value and
|
|
507
|
+
* is `depositErc20` for the request's token and the amount.
|
|
508
|
+
* - NEAR Intents: no spender, and a plain payment of the amount to send to the
|
|
509
|
+
* quote's own deposit address (`quote.quoteId`), native or `transfer`.
|
|
510
|
+
* - Any other provider is refused. Its spender is unknown.
|
|
511
|
+
*
|
|
512
|
+
* The approval is for exactly the amount to send (the request's, else the
|
|
513
|
+
* quote's when the route is solved backwards and the request names none), on
|
|
514
|
+
* the request's origin chain. The native coin needs none.
|
|
515
|
+
*
|
|
516
|
+
* @param options.quote - the live quote to sign
|
|
517
|
+
* @param options.request - the request it answered
|
|
518
|
+
* @returns the checked approval and deposit, or why they are refused
|
|
519
|
+
*/
|
|
520
|
+
export declare const aggregatorOriginCalls: ({ quote, request, }: {
|
|
521
|
+
quote: ConsolidatedQuote;
|
|
522
|
+
request: ConsolidatedQuoteRequest;
|
|
523
|
+
}) => AggregatorOriginCalls;
|
|
430
524
|
/**
|
|
431
525
|
* Normalizes a quote response from the contract-calls endpoint.
|
|
432
526
|
*
|
|
@@ -1,21 +1,27 @@
|
|
|
1
|
+
import { buildQuery, buildUrl, isHttpUrl } from '@gibs/common/url';
|
|
2
|
+
import { decodeFunctionData, erc20Abi, parseAbi, zeroAddress } from 'viem';
|
|
1
3
|
import { Chains } from './config.js';
|
|
2
|
-
import { LifiExecutionRefusedError, verifyLifiEvmExecution, } from './lifi-execution-guard.js';
|
|
4
|
+
import { LifiExecutionRefusedError, lifiDiamondAddressByChainId, verifyLifiEvmExecution, } from './lifi-execution-guard.js';
|
|
3
5
|
import { depositFamilyOf, isEvmDeposit, } from './origin-deposit.js';
|
|
4
6
|
import { lifiQuoteFees } from './quote-fees.js';
|
|
5
7
|
import { referralIntegrator, resolveAttribution } from './referral.js';
|
|
6
8
|
import { normalizeLifiSteps } from './route-steps.js';
|
|
9
|
+
import { relayDepositoryByChainId } from './relay-depository.js';
|
|
7
10
|
/**
|
|
8
|
-
*
|
|
9
|
-
* {@link ConsolidatedQuoteRequest.lifiIntegrator} out.
|
|
11
|
+
* Gibs Finance's own LI.FI `integrator` label.
|
|
10
12
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* NOT A DEFAULT. A request that leaves
|
|
14
|
+
* {@link ConsolidatedQuoteRequest.lifiIntegrator} out sends no `integrator`
|
|
15
|
+
* at all. An integrator label is attribution: LI.FI records the traffic under
|
|
16
|
+
* it, and pays any `fee` to the account behind it. A wallet that builds on this
|
|
17
|
+
* package must not attribute its users' routes to Gibs Finance without
|
|
18
|
+
* choosing to. The Gibs interface passes this value explicitly.
|
|
16
19
|
*/
|
|
17
20
|
export const lifiIntegrator = referralIntegrator;
|
|
18
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* The referral fee Gibs Finance charges on its own LI.FI routes (0.001 = 0.1%).
|
|
23
|
+
* No builder applies it. A request carries a fee only when it sets `lifiFee`.
|
|
24
|
+
*/
|
|
19
25
|
export const lifiReferralFeePercent = 0.001;
|
|
20
26
|
/**
|
|
21
27
|
* LI.FI's HTTP origin when the interface's `PUBLIC_LIFI_API_URL` build flag is
|
|
@@ -24,15 +30,6 @@ export const lifiReferralFeePercent = 0.001;
|
|
|
24
30
|
* at LI.FI's shared, unauthenticated tier — with no configuration at all.
|
|
25
31
|
*/
|
|
26
32
|
export const lifiDefaultApiOrigin = 'https://li.quest';
|
|
27
|
-
const isAbsoluteHttpUrl = (value) => {
|
|
28
|
-
try {
|
|
29
|
-
const url = new URL(value);
|
|
30
|
-
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
33
|
/**
|
|
37
34
|
* Builds every LI.FI endpoint address from one origin — THE ONE PLACE the
|
|
38
35
|
* `/v1/...` paths are spelled, so a caller pointed at a different origin (the
|
|
@@ -48,7 +45,7 @@ const isAbsoluteHttpUrl = (value) => {
|
|
|
48
45
|
* @returns a frozen object of every endpoint this codebase calls
|
|
49
46
|
*/
|
|
50
47
|
export const lifiEndpoints = (apiOrigin) => {
|
|
51
|
-
if (!
|
|
48
|
+
if (!isHttpUrl(apiOrigin)) {
|
|
52
49
|
throw new Error(`lifiEndpoints: "${apiOrigin}" is not an absolute http(s) origin`);
|
|
53
50
|
}
|
|
54
51
|
const base = `${apiOrigin.replace(/\/+$/, '')}/v1`;
|
|
@@ -56,22 +53,12 @@ export const lifiEndpoints = (apiOrigin) => {
|
|
|
56
53
|
quote: `${base}/quote`,
|
|
57
54
|
contractCallsQuote: `${base}/quote/contractCalls`,
|
|
58
55
|
chains: `${base}/chains`,
|
|
59
|
-
tokens: (chainId) => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const url = new URL(`${base}/status`);
|
|
66
|
-
url.searchParams.set('txHash', txHash);
|
|
67
|
-
if (fromChainId !== undefined) {
|
|
68
|
-
url.searchParams.set('fromChain', fromChainId.toString());
|
|
69
|
-
}
|
|
70
|
-
if (toChainId !== undefined) {
|
|
71
|
-
url.searchParams.set('toChain', toChainId.toString());
|
|
72
|
-
}
|
|
73
|
-
return url.toString();
|
|
74
|
-
},
|
|
56
|
+
tokens: (chainId) => buildUrl(`${base}/tokens`, { chains: chainId.toString() }),
|
|
57
|
+
status: ({ txHash, fromChainId, toChainId }) => buildUrl(`${base}/status`, {
|
|
58
|
+
txHash,
|
|
59
|
+
fromChain: fromChainId?.toString(),
|
|
60
|
+
toChain: toChainId?.toString(),
|
|
61
|
+
}),
|
|
75
62
|
});
|
|
76
63
|
};
|
|
77
64
|
const defaultLifiEndpoints = lifiEndpoints(lifiDefaultApiOrigin);
|
|
@@ -139,16 +126,16 @@ export const isNonEthereumVirtualMachineOrigin = (request) => (request.originVmT
|
|
|
139
126
|
* @param request - the consolidated request's attribution fields
|
|
140
127
|
* @returns the fields to merge into the request; empty when none apply
|
|
141
128
|
* @throws when a positive `lifiFee` arrives without an explicit `lifiIntegrator`
|
|
142
|
-
* string —
|
|
129
|
+
* string — LI.FI would have nobody of yours to pay it to
|
|
143
130
|
*/
|
|
144
131
|
export const lifiAttribution = (request) => {
|
|
145
|
-
const integrator = resolveAttribution(request.lifiIntegrator,
|
|
132
|
+
const integrator = resolveAttribution(request.lifiIntegrator, undefined);
|
|
146
133
|
const chargesFee = request.lifiFee !== undefined && request.lifiFee > 0;
|
|
147
134
|
if (!chargesFee) {
|
|
148
135
|
return integrator === undefined ? {} : { integrator };
|
|
149
136
|
}
|
|
150
137
|
if (typeof request.lifiIntegrator !== 'string') {
|
|
151
|
-
throw new Error('lifiFee needs an explicit lifiIntegrator: LI.FI pays the fee to the
|
|
138
|
+
throw new Error('lifiFee needs an explicit lifiIntegrator: LI.FI pays the fee to the account behind the integrator label');
|
|
152
139
|
}
|
|
153
140
|
return { integrator: request.lifiIntegrator, fee: request.lifiFee };
|
|
154
141
|
};
|
|
@@ -200,7 +187,10 @@ export const buildLifiQuoteRequest = (request) => {
|
|
|
200
187
|
if ((request.originVmType ?? 'evm') !== 'evm') {
|
|
201
188
|
return null;
|
|
202
189
|
}
|
|
203
|
-
|
|
190
|
+
// Same attribution as the contract-calls builder, so a fee the reader agreed
|
|
191
|
+
// to is charged on both endpoints, not only on one.
|
|
192
|
+
const { integrator, fee } = lifiAttribution(request);
|
|
193
|
+
return buildQuery({
|
|
204
194
|
fromChain: request.fromChainId.toString(),
|
|
205
195
|
toChain: toChainId.toString(),
|
|
206
196
|
fromToken: request.fromTokenAddress,
|
|
@@ -211,20 +201,11 @@ export const buildLifiQuoteRequest = (request) => {
|
|
|
211
201
|
// same account for an ordinary transfer and are NOT when a reader has named
|
|
212
202
|
// a delivery address, which is exactly the case a bridge has to get right.
|
|
213
203
|
toAddress: request.recipient,
|
|
204
|
+
// Each optional parameter is left out when it is undefined.
|
|
205
|
+
integrator,
|
|
206
|
+
fee: fee?.toString(),
|
|
207
|
+
slippage: request.slippage?.toString(),
|
|
214
208
|
});
|
|
215
|
-
// Same attribution as the contract-calls builder, so a fee the reader agreed
|
|
216
|
-
// to is charged on both endpoints, not only on one.
|
|
217
|
-
const { integrator, fee } = lifiAttribution(request);
|
|
218
|
-
if (integrator !== undefined) {
|
|
219
|
-
params.set('integrator', integrator);
|
|
220
|
-
}
|
|
221
|
-
if (fee !== undefined) {
|
|
222
|
-
params.set('fee', fee.toString());
|
|
223
|
-
}
|
|
224
|
-
if (request.slippage !== undefined) {
|
|
225
|
-
params.set('slippage', request.slippage.toString());
|
|
226
|
-
}
|
|
227
|
-
return params.toString();
|
|
228
209
|
};
|
|
229
210
|
export const buildLifiContractCallsRequest = (request) => {
|
|
230
211
|
const { destinationCall, toAmount } = request;
|
|
@@ -286,6 +267,282 @@ export const evmDepositOf = (quote) => {
|
|
|
286
267
|
}
|
|
287
268
|
return quote.originDeposit;
|
|
288
269
|
};
|
|
270
|
+
const refuseOrigin = (refusal) => ({ kind: 'refused', refusal });
|
|
271
|
+
const sameHexAddress = (a, b) => a.toLowerCase() === b.toLowerCase();
|
|
272
|
+
/**
|
|
273
|
+
* The amount of the origin token the reader sends, in base units.
|
|
274
|
+
*
|
|
275
|
+
* THE REQUEST IS THE AUTHORITY. A route with no omnibridge entry call is
|
|
276
|
+
* solved forwards: the reader names the input and the request carries it as
|
|
277
|
+
* `fromAmount`. {@link aggregatorOriginCalls} refuses a quote whose own
|
|
278
|
+
* `fromAmount` differs from it.
|
|
279
|
+
*
|
|
280
|
+
* A route WITH an entry call is solved backwards from an exact output, so the
|
|
281
|
+
* request names no input at all and the provider works one out. For that
|
|
282
|
+
* route the quote's `fromAmount` is the only figure there is. The deposit, the
|
|
283
|
+
* approval and the quote must still agree with each other, but no figure of
|
|
284
|
+
* the reader's exists to compare them with.
|
|
285
|
+
*/
|
|
286
|
+
const amountToSendOf = ({ quote, request }) => request.fromAmount ?? quote.fromAmount;
|
|
287
|
+
const refuseAmount = ({ provider, field, expected, actual, }) => refuseVerdict({ reason: 'amount-mismatch', provider, field, expected, actual });
|
|
288
|
+
const refuseRecipient = ({ provider, field, expected, actual, }) => refuseVerdict({ reason: 'recipient-mismatch', provider, field, expected, actual });
|
|
289
|
+
const refuseVerdict = (refusal) => ({ kind: 'refused', refusal });
|
|
290
|
+
/**
|
|
291
|
+
* The spender a provider allows, given the one contract it trusts on this
|
|
292
|
+
* chain: no approval when the quote asks for none, the trusted contract when
|
|
293
|
+
* the quote names it, and a refusal for any other address.
|
|
294
|
+
*/
|
|
295
|
+
const spenderVerdict = ({ provider, requested, trusted, }) => {
|
|
296
|
+
if (requested === null)
|
|
297
|
+
return { kind: 'verified', spender: null };
|
|
298
|
+
if (trusted === null || !sameHexAddress(requested, trusted)) {
|
|
299
|
+
return refuseVerdict({ reason: 'spender-not-allowed', provider, spender: requested });
|
|
300
|
+
}
|
|
301
|
+
// The one place a spender is branded verified: the trusted table entry.
|
|
302
|
+
return { kind: 'verified', spender: trusted };
|
|
303
|
+
};
|
|
304
|
+
/**
|
|
305
|
+
* LI.FI: the deposit and the spender must be LI.FI's Diamond, and the calldata
|
|
306
|
+
* must say what was asked: the receiver, the asset, and (through
|
|
307
|
+
* `originAmount`) the exact amount pulled from the reader.
|
|
308
|
+
*/
|
|
309
|
+
const lifiOriginVerdict = ({ request, quote, deposit, amountToSend }) => {
|
|
310
|
+
const diamond = lifiDiamondAddressByChainId.get(deposit.chainId);
|
|
311
|
+
if (diamond === undefined)
|
|
312
|
+
return refuseVerdict({ reason: 'unverified-chain', provider: 'lifi', chainId: deposit.chainId });
|
|
313
|
+
try {
|
|
314
|
+
verifyLifiEvmExecution(deposit, quote.approvalAddress, {
|
|
315
|
+
...lifiExecutionExpectation(request),
|
|
316
|
+
originAmount: amountToSend,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
if (error instanceof LifiExecutionRefusedError)
|
|
321
|
+
return refuseVerdict({ reason: 'lifi-guard', detail: error.refusal.detail });
|
|
322
|
+
throw error;
|
|
323
|
+
}
|
|
324
|
+
return spenderVerdict({ provider: 'lifi', requested: quote.approvalAddress, trusted: diamond });
|
|
325
|
+
};
|
|
326
|
+
/**
|
|
327
|
+
* The two deposit functions Relay's depository exposes, as observed in live
|
|
328
|
+
* `POST /quote/v2` answers on 2026-10-07 (Ethereum to Base):
|
|
329
|
+
*
|
|
330
|
+
* - `depositNative(address depositor, bytes32 id)` (selector `0x49290c1c`):
|
|
331
|
+
* the amount is `msg.value`; the calldata carries NO amount.
|
|
332
|
+
* - `depositErc20(address depositor, address token, uint256 amount, bytes32 id)`
|
|
333
|
+
* (selector `0xe8017952`): the calldata carries the token and the amount.
|
|
334
|
+
*
|
|
335
|
+
* Any other selector is refused rather than guessed at.
|
|
336
|
+
*/
|
|
337
|
+
const relayDepositoryAbi = parseAbi([
|
|
338
|
+
'function depositNative(address depositor, bytes32 id) payable',
|
|
339
|
+
'function depositErc20(address depositor, address token, uint256 amount, bytes32 id)',
|
|
340
|
+
]);
|
|
341
|
+
const decodeRelayDeposit = (data) => {
|
|
342
|
+
try {
|
|
343
|
+
return decodeFunctionData({ abi: relayDepositoryAbi, data });
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
/** The native half of Relay's deposit: `depositNative`, with the amount as `msg.value`. */
|
|
350
|
+
const relayNativeVerdict = ({ deposit, amountToSend }) => {
|
|
351
|
+
const provider = 'relay';
|
|
352
|
+
if (deposit.value !== amountToSend) {
|
|
353
|
+
return refuseAmount({ provider, field: 'deposit-value', expected: amountToSend, actual: deposit.value });
|
|
354
|
+
}
|
|
355
|
+
const decoded = decodeRelayDeposit(deposit.data);
|
|
356
|
+
if (decoded === null || decoded.functionName !== 'depositNative') {
|
|
357
|
+
return refuseVerdict({ reason: 'deposit-shape', provider, detail: 'a native deposit is not depositNative' });
|
|
358
|
+
}
|
|
359
|
+
return null;
|
|
360
|
+
};
|
|
361
|
+
/** The token half of Relay's deposit: no value, and `depositErc20` naming the request's token and amount. */
|
|
362
|
+
const relayTokenVerdict = ({ request, deposit, amountToSend }) => {
|
|
363
|
+
const provider = 'relay';
|
|
364
|
+
if (deposit.value !== 0n) {
|
|
365
|
+
return refuseAmount({ provider, field: 'deposit-value', expected: 0n, actual: deposit.value });
|
|
366
|
+
}
|
|
367
|
+
const decoded = decodeRelayDeposit(deposit.data);
|
|
368
|
+
if (decoded === null || decoded.functionName !== 'depositErc20') {
|
|
369
|
+
return refuseVerdict({ reason: 'deposit-shape', provider, detail: 'a token deposit is not depositErc20' });
|
|
370
|
+
}
|
|
371
|
+
const [, token, amount] = decoded.args;
|
|
372
|
+
if (!sameHexAddress(token, request.fromTokenAddress)) {
|
|
373
|
+
return refuseRecipient({ provider, field: 'calldata-token', expected: request.fromTokenAddress, actual: token });
|
|
374
|
+
}
|
|
375
|
+
if (amount !== amountToSend) {
|
|
376
|
+
return refuseAmount({ provider, field: 'calldata-amount', expected: amountToSend, actual: amount });
|
|
377
|
+
}
|
|
378
|
+
return null;
|
|
379
|
+
};
|
|
380
|
+
/**
|
|
381
|
+
* Relay: the deposit and the spender must be Relay's depository on the
|
|
382
|
+
* deposit's chain, and the deposit must move exactly the amount to send.
|
|
383
|
+
*
|
|
384
|
+
* - A native origin: `value` equals the amount, and the calldata is
|
|
385
|
+
* `depositNative`, which carries no amount of its own.
|
|
386
|
+
* - A token origin: `value` is 0, and the calldata is `depositErc20` for the
|
|
387
|
+
* request's token and the amount, decoded with {@link relayDepositoryAbi}.
|
|
388
|
+
*
|
|
389
|
+
* The depositor and the order identifier inside the calldata are NOT checked.
|
|
390
|
+
* The identifier is Relay's own, and nothing in the request predicts it.
|
|
391
|
+
*/
|
|
392
|
+
const relayOriginVerdict = (inputs) => {
|
|
393
|
+
const { quote, request, deposit } = inputs;
|
|
394
|
+
const depository = relayDepositoryByChainId.get(deposit.chainId);
|
|
395
|
+
if (depository === undefined) {
|
|
396
|
+
return refuseVerdict({ reason: 'unverified-chain', provider: 'relay', chainId: deposit.chainId });
|
|
397
|
+
}
|
|
398
|
+
if (!sameHexAddress(deposit.to, depository)) {
|
|
399
|
+
return refuseVerdict({ reason: 'deposit-target-not-allowed', provider: 'relay', target: deposit.to });
|
|
400
|
+
}
|
|
401
|
+
const spender = spenderVerdict({ provider: 'relay', requested: quote.approvalAddress, trusted: depository });
|
|
402
|
+
if (spender.kind === 'refused')
|
|
403
|
+
return spender;
|
|
404
|
+
const refusal = request.fromTokenAddress === zeroAddress ? relayNativeVerdict(inputs) : relayTokenVerdict(inputs);
|
|
405
|
+
return refusal ?? spender;
|
|
406
|
+
};
|
|
407
|
+
const decodeTransfer = (data) => {
|
|
408
|
+
try {
|
|
409
|
+
return decodeFunctionData({ abi: erc20Abi, data });
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
/**
|
|
416
|
+
* NEAR Intents: no contract pulls anything. The deposit is a plain payment to
|
|
417
|
+
* the quote's own deposit address, so the check is on its SHAPE and on the two
|
|
418
|
+
* figures that matter: who is paid and how much.
|
|
419
|
+
*
|
|
420
|
+
* - No approval.
|
|
421
|
+
* - A native origin: no calldata, `to` is the quote's deposit address
|
|
422
|
+
* (`quote.quoteId`, which `parseNearIntentsQuote` sets to it), and `value`
|
|
423
|
+
* is the amount to send.
|
|
424
|
+
* - A token origin: no value, `to` is the origin token, and the calldata is the
|
|
425
|
+
* token's own `transfer(depositAddress, amount)` for the same two figures.
|
|
426
|
+
*
|
|
427
|
+
* The deposit address itself is per quote and cannot be checked against a
|
|
428
|
+
* fixed table; the checks bind the payment to the address the quote names.
|
|
429
|
+
*/
|
|
430
|
+
const nearIntentsOriginVerdict = ({ quote, request, deposit, amountToSend }) => {
|
|
431
|
+
const provider = 'near-intents';
|
|
432
|
+
// NEAR Intents trusts no spender at all.
|
|
433
|
+
const spender = spenderVerdict({ provider, requested: quote.approvalAddress, trusted: null });
|
|
434
|
+
if (spender.kind === 'refused')
|
|
435
|
+
return spender;
|
|
436
|
+
if (request.fromTokenAddress === zeroAddress) {
|
|
437
|
+
if (deposit.data !== '0x')
|
|
438
|
+
return refuseVerdict({ reason: 'deposit-shape', provider, detail: 'a native deposit carries calldata' });
|
|
439
|
+
if (!sameHexAddress(deposit.to, quote.quoteId)) {
|
|
440
|
+
return refuseRecipient({ provider, field: 'deposit-to', expected: quote.quoteId, actual: deposit.to });
|
|
441
|
+
}
|
|
442
|
+
if (deposit.value !== amountToSend) {
|
|
443
|
+
return refuseAmount({ provider, field: 'deposit-value', expected: amountToSend, actual: deposit.value });
|
|
444
|
+
}
|
|
445
|
+
return spender;
|
|
446
|
+
}
|
|
447
|
+
if (!sameHexAddress(deposit.to, request.fromTokenAddress)) {
|
|
448
|
+
return refuseVerdict({ reason: 'deposit-target-not-allowed', provider, target: deposit.to });
|
|
449
|
+
}
|
|
450
|
+
if (deposit.value !== 0n)
|
|
451
|
+
return refuseAmount({ provider, field: 'deposit-value', expected: 0n, actual: deposit.value });
|
|
452
|
+
const decoded = decodeTransfer(deposit.data);
|
|
453
|
+
if (decoded === null || decoded.functionName !== 'transfer') {
|
|
454
|
+
return refuseVerdict({ reason: 'deposit-shape', provider, detail: 'a token deposit is not a plain transfer' });
|
|
455
|
+
}
|
|
456
|
+
const [payee, amount] = decoded.args;
|
|
457
|
+
if (!sameHexAddress(payee, quote.quoteId)) {
|
|
458
|
+
return refuseRecipient({ provider, field: 'transfer-to', expected: quote.quoteId, actual: payee });
|
|
459
|
+
}
|
|
460
|
+
if (amount !== amountToSend) {
|
|
461
|
+
return refuseAmount({ provider, field: 'calldata-amount', expected: amountToSend, actual: amount });
|
|
462
|
+
}
|
|
463
|
+
return spender;
|
|
464
|
+
};
|
|
465
|
+
const originVerdictByProvider = {
|
|
466
|
+
lifi: lifiOriginVerdict,
|
|
467
|
+
relay: relayOriginVerdict,
|
|
468
|
+
'near-intents': nearIntentsOriginVerdict,
|
|
469
|
+
};
|
|
470
|
+
/**
|
|
471
|
+
* Reads the approval and the deposit off an aggregator quote with an Ethereum
|
|
472
|
+
* origin, after checking both against what this code knows about the
|
|
473
|
+
* provider. The interface's aggregator press (`stores/consolidated-execute.ts`)
|
|
474
|
+
* and the SDK's execution planner both read them here, so neither can sign a
|
|
475
|
+
* call the other would refuse.
|
|
476
|
+
*
|
|
477
|
+
* WHAT IS CHECKED, BEFORE ANYTHING IS APPROVED OR SENT:
|
|
478
|
+
* - Every provider: when the request names the amount to send
|
|
479
|
+
* (`request.fromAmount`), the quote's own `fromAmount` must equal it.
|
|
480
|
+
* - LI.FI: `verifyLifiEvmExecution`: the Diamond for the chain, a known
|
|
481
|
+
* facet call, calldata that matches the request, and the decoded amount
|
|
482
|
+
* pulled from the reader equal to the amount to send.
|
|
483
|
+
* - Relay: the deposit target and the spender are Relay's depository for the
|
|
484
|
+
* chain (`relayDepositoryByChainId`). A native deposit carries the amount
|
|
485
|
+
* as `value` through `depositNative`; a token deposit carries no value and
|
|
486
|
+
* is `depositErc20` for the request's token and the amount.
|
|
487
|
+
* - NEAR Intents: no spender, and a plain payment of the amount to send to the
|
|
488
|
+
* quote's own deposit address (`quote.quoteId`), native or `transfer`.
|
|
489
|
+
* - Any other provider is refused. Its spender is unknown.
|
|
490
|
+
*
|
|
491
|
+
* The approval is for exactly the amount to send (the request's, else the
|
|
492
|
+
* quote's when the route is solved backwards and the request names none), on
|
|
493
|
+
* the request's origin chain. The native coin needs none.
|
|
494
|
+
*
|
|
495
|
+
* @param options.quote - the live quote to sign
|
|
496
|
+
* @param options.request - the request it answered
|
|
497
|
+
* @returns the checked approval and deposit, or why they are refused
|
|
498
|
+
*/
|
|
499
|
+
export const aggregatorOriginCalls = ({ quote, request, }) => {
|
|
500
|
+
const { originDeposit } = quote;
|
|
501
|
+
if (!isEvmDeposit(originDeposit)) {
|
|
502
|
+
return refuseOrigin({ reason: 'not-evm-deposit', depositFamily: depositFamilyOf(originDeposit) });
|
|
503
|
+
}
|
|
504
|
+
if (originDeposit.chainId !== request.fromChainId) {
|
|
505
|
+
return refuseOrigin({
|
|
506
|
+
reason: 'chain-mismatch',
|
|
507
|
+
depositChainId: originDeposit.chainId,
|
|
508
|
+
requestChainId: request.fromChainId,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
const check = originVerdictByProvider[quote.provider];
|
|
512
|
+
if (check === undefined)
|
|
513
|
+
return refuseOrigin({ reason: 'unverified-provider', provider: quote.provider });
|
|
514
|
+
const amountToSend = amountToSendOf({ quote, request });
|
|
515
|
+
if (amountToSend !== quote.fromAmount) {
|
|
516
|
+
return refuseOrigin({
|
|
517
|
+
reason: 'amount-mismatch',
|
|
518
|
+
provider: quote.provider,
|
|
519
|
+
field: 'quote-from-amount',
|
|
520
|
+
expected: amountToSend,
|
|
521
|
+
actual: quote.fromAmount,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
const verdict = check({ quote, request, deposit: originDeposit, amountToSend });
|
|
525
|
+
if (verdict.kind === 'refused')
|
|
526
|
+
return refuseOrigin(verdict.refusal);
|
|
527
|
+
// THE SPENDER COMES FROM THE VERDICT, never from `quote.approvalAddress`:
|
|
528
|
+
// only an address the provider check took from its own table can be
|
|
529
|
+
// approved, so no provider path can forget the check.
|
|
530
|
+
if (request.fromTokenAddress === zeroAddress || verdict.spender === null) {
|
|
531
|
+
return { kind: 'calls', approval: null, deposit: originDeposit };
|
|
532
|
+
}
|
|
533
|
+
return {
|
|
534
|
+
kind: 'calls',
|
|
535
|
+
approval: {
|
|
536
|
+
chainId: request.fromChainId,
|
|
537
|
+
// An Ethereum origin; the request widens the address to `string` only
|
|
538
|
+
// for non-Ethereum origins, which were refused above.
|
|
539
|
+
token: request.fromTokenAddress,
|
|
540
|
+
spender: verdict.spender,
|
|
541
|
+
minimum: amountToSend,
|
|
542
|
+
},
|
|
543
|
+
deposit: originDeposit,
|
|
544
|
+
};
|
|
545
|
+
};
|
|
289
546
|
const isHexString = (value) => typeof value === 'string' && value.startsWith('0x');
|
|
290
547
|
/**
|
|
291
548
|
* Parses the ecosystem-independent part of a LI.FI quote.
|
|
@@ -382,6 +639,7 @@ const lifiExecutionExpectation = (request) => {
|
|
|
382
639
|
sendingAssetId: request.fromTokenAddress,
|
|
383
640
|
hasDestinationCall,
|
|
384
641
|
minimumAmount: hasDestinationCall ? request.toAmount : null,
|
|
642
|
+
originAmount: request.fromAmount ?? null,
|
|
385
643
|
};
|
|
386
644
|
};
|
|
387
645
|
/**
|