@gibs/bridge-sdk 1.13.1 → 1.18.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 (228) hide show
  1. package/README.md +295 -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 +255 -0
  28. package/dist/cjs/config.js +569 -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 +359 -0
  36. package/dist/cjs/execution.js +495 -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 +24 -0
  44. package/dist/cjs/index.js +40 -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 +117 -0
  59. package/dist/cjs/prices.js +224 -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/carrier-provider-key.d.ts +31 -0
  81. package/dist/cjs/routing/carrier-provider-key.js +42 -0
  82. package/dist/cjs/routing/carriers/ask-answer.d.ts +77 -0
  83. package/dist/cjs/routing/carriers/ask-answer.js +2 -0
  84. package/dist/cjs/routing/carriers/omnibridge.d.ts +137 -0
  85. package/dist/cjs/routing/carriers/omnibridge.js +459 -0
  86. package/dist/cjs/routing/carriers/pulsex-paths.d.ts +80 -0
  87. package/dist/cjs/routing/carriers/pulsex-paths.js +97 -0
  88. package/dist/cjs/routing/carriers/pulsex-split.d.ts +175 -0
  89. package/dist/cjs/routing/carriers/pulsex-split.js +193 -0
  90. package/dist/cjs/routing/carriers/pulsex.d.ts +184 -0
  91. package/dist/cjs/routing/carriers/pulsex.js +615 -0
  92. package/dist/cjs/routing/carriers/wrap.d.ts +25 -0
  93. package/dist/cjs/routing/carriers/wrap.js +119 -0
  94. package/dist/cjs/routing/composed-route-quality.d.ts +125 -0
  95. package/dist/cjs/routing/composed-route-quality.js +144 -0
  96. package/dist/cjs/routing/entry-chain-anchor.d.ts +88 -0
  97. package/dist/cjs/routing/entry-chain-anchor.js +59 -0
  98. package/dist/cjs/routing/format.d.ts +31 -0
  99. package/dist/cjs/routing/format.js +64 -0
  100. package/dist/cjs/routing/index.d.ts +42 -0
  101. package/dist/cjs/routing/index.js +142 -0
  102. package/dist/cjs/routing/producers.d.ts +770 -0
  103. package/dist/cjs/routing/producers.js +1617 -0
  104. package/dist/cjs/routing/providers.d.ts +31 -0
  105. package/dist/cjs/routing/providers.js +48 -0
  106. package/dist/cjs/routing/route-data.d.ts +397 -0
  107. package/dist/cjs/routing/route-data.js +2 -0
  108. package/dist/cjs/routing/search/compose.d.ts +168 -0
  109. package/dist/cjs/routing/search/compose.js +104 -0
  110. package/dist/cjs/routing/search/graph.d.ts +668 -0
  111. package/dist/cjs/routing/search/graph.js +461 -0
  112. package/dist/cjs/routing/search/plan-route-data.d.ts +285 -0
  113. package/dist/cjs/routing/search/plan-route-data.js +871 -0
  114. package/dist/cjs/routing/search/score.d.ts +356 -0
  115. package/dist/cjs/routing/search/score.js +445 -0
  116. package/dist/cjs/routing/swap-transactions.d.ts +95 -0
  117. package/dist/cjs/routing/swap-transactions.js +133 -0
  118. package/dist/cjs/routing/usd-value.d.ts +17 -0
  119. package/dist/cjs/routing/usd-value.js +29 -0
  120. package/dist/cjs/settings.d.ts +785 -0
  121. package/dist/cjs/settings.js +1074 -0
  122. package/dist/cjs/swap-venues.d.ts +43 -0
  123. package/dist/cjs/swap-venues.js +29 -0
  124. package/dist/cjs/ton-connect.d.ts +98 -0
  125. package/dist/cjs/ton-connect.js +206 -0
  126. package/dist/cjs/ton-execution.d.ts +7 -0
  127. package/dist/cjs/ton-execution.js +23 -0
  128. package/dist/cjs/ton.d.ts +37 -0
  129. package/dist/cjs/ton.js +83 -0
  130. package/dist/cjs/tron-execution.d.ts +146 -0
  131. package/dist/cjs/tron-execution.js +248 -0
  132. package/dist/cjs/types.d.ts +51 -0
  133. package/dist/cjs/types.js +2 -0
  134. package/dist/claims.d.ts +198 -0
  135. package/dist/claims.js +140 -0
  136. package/dist/config.d.ts +42 -3
  137. package/dist/config.js +87 -21
  138. package/dist/consolidated-quote.d.ts +109 -15
  139. package/dist/consolidated-quote.js +310 -52
  140. package/dist/execution.d.ts +359 -0
  141. package/dist/execution.js +455 -0
  142. package/dist/hyperliquid-execution.js +2 -3
  143. package/dist/index.d.ts +4 -0
  144. package/dist/index.js +4 -0
  145. package/dist/lifi-execution-guard.d.ts +13 -2
  146. package/dist/lifi-execution-guard.js +4 -0
  147. package/dist/near-intents.js +2 -3
  148. package/dist/pre-quote.d.ts +6 -6
  149. package/dist/pre-quote.js +10 -14
  150. package/dist/prices.d.ts +10 -0
  151. package/dist/prices.js +52 -1
  152. package/dist/providers.d.ts +17 -0
  153. package/dist/providers.js +6 -0
  154. package/dist/referral.d.ts +12 -4
  155. package/dist/referral.js +12 -4
  156. package/dist/relay-depository.d.ts +2 -0
  157. package/dist/relay-depository.js +82 -0
  158. package/dist/relay-quote.js +2 -3
  159. package/dist/routing/candidate.d.ts +3 -3
  160. package/dist/routing/candidate.js +3 -3
  161. package/dist/routing/carrier-provider-key.d.ts +31 -0
  162. package/dist/routing/carrier-provider-key.js +37 -0
  163. package/dist/routing/carriers/ask-answer.d.ts +17 -0
  164. package/dist/routing/carriers/omnibridge.js +23 -21
  165. package/dist/routing/carriers/pulsex-paths.d.ts +80 -0
  166. package/dist/routing/carriers/pulsex-paths.js +92 -0
  167. package/dist/routing/carriers/pulsex-split.d.ts +175 -0
  168. package/dist/routing/carriers/pulsex-split.js +185 -0
  169. package/dist/routing/carriers/pulsex.d.ts +81 -46
  170. package/dist/routing/carriers/pulsex.js +267 -148
  171. package/dist/routing/composed-route-quality.d.ts +28 -6
  172. package/dist/routing/composed-route-quality.js +36 -5
  173. package/dist/routing/entry-chain-anchor.d.ts +1 -1
  174. package/dist/routing/entry-chain-anchor.js +1 -1
  175. package/dist/routing/index.d.ts +11 -3
  176. package/dist/routing/index.js +8 -2
  177. package/dist/routing/producers.js +1 -1
  178. package/dist/routing/providers.d.ts +5 -0
  179. package/dist/routing/providers.js +5 -0
  180. package/dist/routing/route-data.d.ts +47 -5
  181. package/dist/routing/search/graph.d.ts +15 -0
  182. package/dist/routing/search/plan-route-data.d.ts +42 -1
  183. package/dist/routing/search/plan-route-data.js +68 -9
  184. package/dist/routing/search/score.d.ts +3 -0
  185. package/dist/routing/search/score.js +11 -3
  186. package/dist/routing/swap-transactions.d.ts +95 -0
  187. package/dist/routing/swap-transactions.js +127 -0
  188. package/dist/settings.d.ts +94 -1
  189. package/dist/settings.js +95 -3
  190. package/dist/swap-venues.d.ts +43 -0
  191. package/dist/swap-venues.js +24 -0
  192. package/dist/ton-connect.d.ts +98 -0
  193. package/dist/ton-connect.js +200 -0
  194. package/dist/ton-execution.d.ts +4 -114
  195. package/dist/ton-execution.js +4 -244
  196. package/dist/ton.d.ts +37 -0
  197. package/dist/ton.js +65 -0
  198. package/docs/examples/example-a-ether-to-pulse.ts +300 -0
  199. package/docs/examples/example-b-token-pulsechain-to-ethereum.ts +270 -0
  200. package/docs/examples/shared.ts +261 -0
  201. package/docs/examples/tsconfig.json +18 -0
  202. package/docs/integration-guide.md +617 -0
  203. package/ecosystems/package.json +6 -0
  204. package/execution/package.json +6 -0
  205. package/fee-type/package.json +6 -0
  206. package/hyperliquid-execution/package.json +6 -0
  207. package/image-links/package.json +6 -0
  208. package/lifi-execution-guard/package.json +6 -0
  209. package/llms.txt +75 -10
  210. package/near-intents/package.json +6 -0
  211. package/near-intents-route/package.json +6 -0
  212. package/origin-deposit/package.json +6 -0
  213. package/package.json +817 -10
  214. package/pre-quote/package.json +6 -0
  215. package/prices/package.json +6 -0
  216. package/providers/package.json +6 -0
  217. package/quote-fees/package.json +6 -0
  218. package/referral/package.json +6 -0
  219. package/relay-currencies/package.json +6 -0
  220. package/relay-quote/package.json +6 -0
  221. package/route-steps/package.json +6 -0
  222. package/routing/package.json +6 -0
  223. package/settings/package.json +6 -0
  224. package/ton/package.json +6 -0
  225. package/ton-connect/package.json +6 -0
  226. package/ton-execution/package.json +6 -0
  227. package/tron-execution/package.json +6 -0
  228. package/types/package.json +6 -0
@@ -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
  /**