@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.
Files changed (217) hide show
  1. package/README.md +244 -18
  2. package/abis/package.json +6 -0
  3. package/bridge-limits/package.json +6 -0
  4. package/chain-info/package.json +6 -0
  5. package/chain-names/package.json +6 -0
  6. package/chains/package.json +6 -0
  7. package/claims/package.json +6 -0
  8. package/config/package.json +6 -0
  9. package/consolidated-quote/package.json +6 -0
  10. package/destination-calldata/package.json +6 -0
  11. package/dist/chain-info.d.ts +1 -1
  12. package/dist/chain-info.js +11 -12
  13. package/dist/chains.d.ts +25 -0
  14. package/dist/chains.js +55 -20
  15. package/dist/cjs/abis.d.ts +1 -0
  16. package/dist/cjs/abis.js +19 -0
  17. package/dist/cjs/bridge-limits.d.ts +103 -0
  18. package/dist/cjs/bridge-limits.js +89 -0
  19. package/dist/cjs/chain-info.d.ts +169 -0
  20. package/dist/cjs/chain-info.js +452 -0
  21. package/dist/cjs/chain-names.d.ts +160 -0
  22. package/dist/cjs/chain-names.js +207 -0
  23. package/dist/cjs/chains.d.ts +90 -0
  24. package/dist/cjs/chains.js +357 -0
  25. package/dist/cjs/claims.d.ts +198 -0
  26. package/dist/cjs/claims.js +182 -0
  27. package/dist/cjs/config.d.ts +240 -0
  28. package/dist/cjs/config.js +537 -0
  29. package/dist/cjs/consolidated-quote.d.ts +648 -0
  30. package/dist/cjs/consolidated-quote.js +1048 -0
  31. package/dist/cjs/destination-calldata.d.ts +118 -0
  32. package/dist/cjs/destination-calldata.js +156 -0
  33. package/dist/cjs/ecosystems.d.ts +217 -0
  34. package/dist/cjs/ecosystems.js +298 -0
  35. package/dist/cjs/execution.d.ts +346 -0
  36. package/dist/cjs/execution.js +468 -0
  37. package/dist/cjs/fee-type.d.ts +10 -0
  38. package/dist/cjs/fee-type.js +10 -0
  39. package/dist/cjs/hyperliquid-execution.d.ts +152 -0
  40. package/dist/cjs/hyperliquid-execution.js +175 -0
  41. package/dist/cjs/image-links.d.ts +117 -0
  42. package/dist/cjs/image-links.js +153 -0
  43. package/dist/cjs/index.d.ts +23 -0
  44. package/dist/cjs/index.js +39 -0
  45. package/dist/cjs/lifi-execution-guard.d.ts +210 -0
  46. package/dist/cjs/lifi-execution-guard.js +409 -0
  47. package/dist/cjs/near-intents-conformance.d.ts +85 -0
  48. package/dist/cjs/near-intents-conformance.js +337 -0
  49. package/dist/cjs/near-intents-route.d.ts +47 -0
  50. package/dist/cjs/near-intents-route.js +303 -0
  51. package/dist/cjs/near-intents.d.ts +524 -0
  52. package/dist/cjs/near-intents.js +1071 -0
  53. package/dist/cjs/origin-deposit.d.ts +326 -0
  54. package/dist/cjs/origin-deposit.js +82 -0
  55. package/dist/cjs/package.json +3 -0
  56. package/dist/cjs/pre-quote.d.ts +198 -0
  57. package/dist/cjs/pre-quote.js +180 -0
  58. package/dist/cjs/prices.d.ts +107 -0
  59. package/dist/cjs/prices.js +172 -0
  60. package/dist/cjs/providers.d.ts +238 -0
  61. package/dist/cjs/providers.js +171 -0
  62. package/dist/cjs/quote-fees.d.ts +102 -0
  63. package/dist/cjs/quote-fees.js +201 -0
  64. package/dist/cjs/referral.d.ts +85 -0
  65. package/dist/cjs/referral.js +85 -0
  66. package/dist/cjs/relay-currencies.d.ts +47 -0
  67. package/dist/cjs/relay-currencies.js +95 -0
  68. package/dist/cjs/relay-depository.d.ts +2 -0
  69. package/dist/cjs/relay-depository.js +85 -0
  70. package/dist/cjs/relay-quote.d.ts +301 -0
  71. package/dist/cjs/relay-quote.js +996 -0
  72. package/dist/cjs/route-steps.d.ts +138 -0
  73. package/dist/cjs/route-steps.js +310 -0
  74. package/dist/cjs/routing/amounts.d.ts +80 -0
  75. package/dist/cjs/routing/amounts.js +126 -0
  76. package/dist/cjs/routing/candidate-route.d.ts +43 -0
  77. package/dist/cjs/routing/candidate-route.js +50 -0
  78. package/dist/cjs/routing/candidate.d.ts +237 -0
  79. package/dist/cjs/routing/candidate.js +100 -0
  80. package/dist/cjs/routing/carriers/ask-answer.d.ts +77 -0
  81. package/dist/cjs/routing/carriers/ask-answer.js +2 -0
  82. package/dist/cjs/routing/carriers/omnibridge.d.ts +137 -0
  83. package/dist/cjs/routing/carriers/omnibridge.js +459 -0
  84. package/dist/cjs/routing/carriers/pulsex-paths.d.ts +80 -0
  85. package/dist/cjs/routing/carriers/pulsex-paths.js +97 -0
  86. package/dist/cjs/routing/carriers/pulsex-split.d.ts +175 -0
  87. package/dist/cjs/routing/carriers/pulsex-split.js +193 -0
  88. package/dist/cjs/routing/carriers/pulsex.d.ts +159 -0
  89. package/dist/cjs/routing/carriers/pulsex.js +565 -0
  90. package/dist/cjs/routing/carriers/wrap.d.ts +25 -0
  91. package/dist/cjs/routing/carriers/wrap.js +119 -0
  92. package/dist/cjs/routing/composed-route-quality.d.ts +125 -0
  93. package/dist/cjs/routing/composed-route-quality.js +144 -0
  94. package/dist/cjs/routing/entry-chain-anchor.d.ts +88 -0
  95. package/dist/cjs/routing/entry-chain-anchor.js +59 -0
  96. package/dist/cjs/routing/format.d.ts +31 -0
  97. package/dist/cjs/routing/format.js +64 -0
  98. package/dist/cjs/routing/index.d.ts +38 -0
  99. package/dist/cjs/routing/index.js +134 -0
  100. package/dist/cjs/routing/producers.d.ts +770 -0
  101. package/dist/cjs/routing/producers.js +1617 -0
  102. package/dist/cjs/routing/providers.d.ts +31 -0
  103. package/dist/cjs/routing/providers.js +48 -0
  104. package/dist/cjs/routing/route-data.d.ts +355 -0
  105. package/dist/cjs/routing/route-data.js +2 -0
  106. package/dist/cjs/routing/search/compose.d.ts +168 -0
  107. package/dist/cjs/routing/search/compose.js +104 -0
  108. package/dist/cjs/routing/search/graph.d.ts +668 -0
  109. package/dist/cjs/routing/search/graph.js +461 -0
  110. package/dist/cjs/routing/search/plan-route-data.d.ts +269 -0
  111. package/dist/cjs/routing/search/plan-route-data.js +848 -0
  112. package/dist/cjs/routing/search/score.d.ts +356 -0
  113. package/dist/cjs/routing/search/score.js +445 -0
  114. package/dist/cjs/routing/swap-transactions.d.ts +91 -0
  115. package/dist/cjs/routing/swap-transactions.js +131 -0
  116. package/dist/cjs/routing/usd-value.d.ts +17 -0
  117. package/dist/cjs/routing/usd-value.js +29 -0
  118. package/dist/cjs/settings.d.ts +785 -0
  119. package/dist/cjs/settings.js +1074 -0
  120. package/dist/cjs/ton-connect.d.ts +98 -0
  121. package/dist/cjs/ton-connect.js +206 -0
  122. package/dist/cjs/ton-execution.d.ts +7 -0
  123. package/dist/cjs/ton-execution.js +23 -0
  124. package/dist/cjs/ton.d.ts +37 -0
  125. package/dist/cjs/ton.js +83 -0
  126. package/dist/cjs/tron-execution.d.ts +146 -0
  127. package/dist/cjs/tron-execution.js +248 -0
  128. package/dist/cjs/types.d.ts +51 -0
  129. package/dist/cjs/types.js +2 -0
  130. package/dist/claims.d.ts +198 -0
  131. package/dist/claims.js +140 -0
  132. package/dist/config.d.ts +24 -0
  133. package/dist/config.js +39 -5
  134. package/dist/consolidated-quote.d.ts +109 -15
  135. package/dist/consolidated-quote.js +310 -52
  136. package/dist/execution.d.ts +346 -0
  137. package/dist/execution.js +428 -0
  138. package/dist/hyperliquid-execution.js +2 -3
  139. package/dist/index.d.ts +3 -0
  140. package/dist/index.js +3 -0
  141. package/dist/lifi-execution-guard.d.ts +13 -2
  142. package/dist/lifi-execution-guard.js +4 -0
  143. package/dist/near-intents.js +2 -3
  144. package/dist/pre-quote.d.ts +6 -6
  145. package/dist/pre-quote.js +10 -14
  146. package/dist/providers.d.ts +17 -0
  147. package/dist/providers.js +6 -0
  148. package/dist/referral.d.ts +12 -4
  149. package/dist/referral.js +12 -4
  150. package/dist/relay-depository.d.ts +2 -0
  151. package/dist/relay-depository.js +82 -0
  152. package/dist/relay-quote.js +2 -3
  153. package/dist/routing/candidate.d.ts +3 -3
  154. package/dist/routing/candidate.js +3 -3
  155. package/dist/routing/carriers/ask-answer.d.ts +17 -0
  156. package/dist/routing/carriers/omnibridge.js +23 -21
  157. package/dist/routing/carriers/pulsex-paths.d.ts +80 -0
  158. package/dist/routing/carriers/pulsex-paths.js +92 -0
  159. package/dist/routing/carriers/pulsex-split.d.ts +175 -0
  160. package/dist/routing/carriers/pulsex-split.js +185 -0
  161. package/dist/routing/carriers/pulsex.d.ts +31 -21
  162. package/dist/routing/carriers/pulsex.js +141 -71
  163. package/dist/routing/composed-route-quality.d.ts +28 -6
  164. package/dist/routing/composed-route-quality.js +36 -5
  165. package/dist/routing/entry-chain-anchor.d.ts +1 -1
  166. package/dist/routing/entry-chain-anchor.js +1 -1
  167. package/dist/routing/index.d.ts +6 -2
  168. package/dist/routing/index.js +6 -2
  169. package/dist/routing/producers.js +1 -1
  170. package/dist/routing/providers.d.ts +5 -0
  171. package/dist/routing/providers.js +5 -0
  172. package/dist/routing/search/graph.d.ts +15 -0
  173. package/dist/routing/search/plan-route-data.d.ts +25 -0
  174. package/dist/routing/search/plan-route-data.js +43 -7
  175. package/dist/routing/search/score.d.ts +3 -0
  176. package/dist/routing/search/score.js +11 -3
  177. package/dist/routing/swap-transactions.d.ts +91 -0
  178. package/dist/routing/swap-transactions.js +125 -0
  179. package/dist/settings.d.ts +94 -1
  180. package/dist/settings.js +95 -3
  181. package/dist/ton-connect.d.ts +98 -0
  182. package/dist/ton-connect.js +200 -0
  183. package/dist/ton-execution.d.ts +4 -114
  184. package/dist/ton-execution.js +4 -244
  185. package/dist/ton.d.ts +37 -0
  186. package/dist/ton.js +65 -0
  187. package/docs/examples/example-a-ether-to-pulse.ts +300 -0
  188. package/docs/examples/example-b-token-pulsechain-to-ethereum.ts +270 -0
  189. package/docs/examples/shared.ts +261 -0
  190. package/docs/examples/tsconfig.json +18 -0
  191. package/docs/integration-guide.md +617 -0
  192. package/ecosystems/package.json +6 -0
  193. package/execution/package.json +6 -0
  194. package/fee-type/package.json +6 -0
  195. package/hyperliquid-execution/package.json +6 -0
  196. package/image-links/package.json +6 -0
  197. package/lifi-execution-guard/package.json +6 -0
  198. package/llms.txt +75 -10
  199. package/near-intents/package.json +6 -0
  200. package/near-intents-route/package.json +6 -0
  201. package/origin-deposit/package.json +6 -0
  202. package/package.json +817 -10
  203. package/pre-quote/package.json +6 -0
  204. package/prices/package.json +6 -0
  205. package/providers/package.json +6 -0
  206. package/quote-fees/package.json +6 -0
  207. package/referral/package.json +6 -0
  208. package/relay-currencies/package.json +6 -0
  209. package/relay-quote/package.json +6 -0
  210. package/route-steps/package.json +6 -0
  211. package/routing/package.json +6 -0
  212. package/settings/package.json +6 -0
  213. package/ton/package.json +6 -0
  214. package/ton-connect/package.json +6 -0
  215. package/ton-execution/package.json +6 -0
  216. package/tron-execution/package.json +6 -0
  217. 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) => _.get(pathways, [provider, chain]) ||
339
- (!isProd ? _.get(testnetPathways, [provider, chain]) : undefined);
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 = _.get(pathways, bridgeKey) || (!isProd ? _.get(testnetPathways, bridgeKey) : undefined);
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 = _.get(conf, ['bridgedNativeAssetOut']);
399
+ const defaultAssetIn = conf?.bridgedNativeAssetOut;
366
400
  return defaultAssetIn;
367
401
  };
368
402
  /**
@@ -1,22 +1,26 @@
1
- import type { Hex } from 'viem';
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
- * The LI.FI `integrator` sent when a request leaves
10
- * {@link ConsolidatedQuoteRequest.lifiIntegrator} out.
10
+ * Gibs Finance's own LI.FI `integrator` label.
11
11
  *
12
- * KEPT AS A DEFAULT BECAUSE IT COSTS THE CALLER NOTHING. LI.FI's `integrator`
13
- * is an optional label: it changes neither the quote nor the rate limit, and it
14
- * moves no money unless a `fee` rides with it. A fee is the one case where the
15
- * label decides who gets paid, so a request that charges a fee must name its
16
- * own integrator — see {@link lifiAttribution}.
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
- /** Referral fee charged by this integrator on LI.FI routes (0.001 = 0.1%). */
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, {@link lifiIntegrator} is sent;
239
- * `null` omits the field; a string is sent as-is. REQUIRED (as a string)
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 under the default label would pay Gibs
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
- /** Omitted when the request set `lifiIntegrator: null`. */
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 — that fee would otherwise be paid to the default integrator
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
- * The LI.FI `integrator` sent when a request leaves
9
- * {@link ConsolidatedQuoteRequest.lifiIntegrator} out.
11
+ * Gibs Finance's own LI.FI `integrator` label.
10
12
  *
11
- * KEPT AS A DEFAULT BECAUSE IT COSTS THE CALLER NOTHING. LI.FI's `integrator`
12
- * is an optional label: it changes neither the quote nor the rate limit, and it
13
- * moves no money unless a `fee` rides with it. A fee is the one case where the
14
- * label decides who gets paid, so a request that charges a fee must name its
15
- * own integrator — see {@link lifiAttribution}.
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
- /** Referral fee charged by this integrator on LI.FI routes (0.001 = 0.1%). */
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 (!isAbsoluteHttpUrl(apiOrigin)) {
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
- const url = new URL(`${base}/tokens`);
61
- url.searchParams.set('chains', chainId.toString());
62
- return url.toString();
63
- },
64
- status: ({ txHash, fromChainId, toChainId }) => {
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 — that fee would otherwise be paid to the default integrator
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, 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 integrator label, and the default label is not yours');
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
- const params = new URLSearchParams({
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
  /**