@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.
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 +19 -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 +69 -21
  162. package/dist/routing/carriers/pulsex.js +184 -65
  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 +7 -3
  168. package/dist/routing/index.js +7 -3
  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 +29 -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 +33 -1
  176. package/dist/routing/search/score.js +41 -6
  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 +234 -0
  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 +818 -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
@@ -0,0 +1,210 @@
1
+ import { type Hex } from 'viem';
2
+ /**
3
+ * Verifies a LI.FI EVM execution transaction before a wallet ever signs it.
4
+ *
5
+ * WHY THIS EXISTS. `lifiApi.quote` and `lifiApi.contractCallsQuote` are fetched
6
+ * through a configurable base URL — today `lifi.gibs.finance`, a server-side
7
+ * proxy; the interface may point at a different host in the future. Whichever
8
+ * host answers, this codebase used to sign whatever `transactionRequest` came
9
+ * back after checking only that `to` and `data` were hexadecimal strings (see
10
+ * `lifiEvmDeposit` in `consolidated-quote.ts`). A COMPROMISED PROXY could
11
+ * return ANY `to`/`data`/`value` and a connected wallet would sign it,
12
+ * moving the reader's funds wherever the proxy chose.
13
+ *
14
+ * WHAT THIS MODULE PROVES INSTEAD, before signing:
15
+ *
16
+ * 1. `to` (and, when present, `estimate.approvalAddress`) is LI.FI's own
17
+ * Diamond entryway contract for the chain the transaction runs on — a
18
+ * fixed table, not something the response gets to assert about itself.
19
+ * See {@link lifiDiamondAddressByChainId}.
20
+ * 2. The calldata's selector is one of a small, explicitly verified set of
21
+ * LI.FI facet functions whose ABI this module carries in full. An unknown
22
+ * selector is refused rather than guessed at.
23
+ * 3. The decoded `BridgeData` and leading `SwapData` entry agree with what
24
+ * THIS APPLICATION asked for — not with other fields of the same,
25
+ * potentially compromised, response. See {@link LifiExecutionExpectation}.
26
+ * 4. `transactionRequest.value` equals exactly the native currency the
27
+ * decoded calldata implies (the amount being deposited, when that amount
28
+ * is native, plus any protocol messaging fee the tool's own data
29
+ * declares) — never more, never less.
30
+ *
31
+ * WHERE THE FIELD SEMANTICS CAME FROM. Every fact this module relies on —
32
+ * which struct field means what, and how the three verified facet functions
33
+ * actually populate `BridgeData` and `SwapData` — was read from REAL
34
+ * responses captured live from `https://li.quest`, decoded with this exact
35
+ * machinery, and cross-checked against the deployed contracts' Solidity
36
+ * source (`lifinance/contracts`, commit `025989955bb373739737ee78ddff01620638ae3b`,
37
+ * 2026-09-23). Two assumptions a first reading of LI.FI's own field
38
+ * descriptions would suggest turned out to be wrong, and are recorded here so
39
+ * nobody reintroduces them:
40
+ *
41
+ * - `BridgeData.receiver` is NOT "the final recipient" in general. For the
42
+ * contract-calls endpoint (`hasDestinationCall: true`) it decoded to the
43
+ * REQUEST'S `toFallbackAddress` — the address that receives the bridged
44
+ * asset if the destination call reverts — not the destination call's own
45
+ * contract or the reader's recipient. Only for the plain endpoint
46
+ * (`hasDestinationCall: false`) does it equal the recipient (`toAddress`).
47
+ * Verified against three independently captured quotes; see the fixtures
48
+ * this module's test file reads.
49
+ * - `BridgeData.minAmount` is NOT the destination-side guaranteed amount for
50
+ * any of the three verified tools. It is the amount of `sendingAssetId`
51
+ * pulled from the depositor on the ORIGIN chain (`LibAsset.depositAsset`
52
+ * in every facet's Solidity), reassigned at runtime to the real swap
53
+ * output when `hasSourceSwaps` is true — a fact this module's calldata
54
+ * never carries. The genuine destination-side guarantee lives in each
55
+ * tool's own trailing struct (`AcrossV4Data.outputAmount`,
56
+ * `StargateData.sendParams.minAmountLD`) and is read from there instead.
57
+ * LayerSwap's own struct carries no on-chain amount field at all — its
58
+ * guarantee is an off-chain, backend-signed attestation — so this module
59
+ * cannot verify a destination minimum for it and refuses to try; see
60
+ * {@link DecodedLifiFacetCall.destinationMinimum}.
61
+ *
62
+ * SCOPE, DELIBERATELY NARROW. Only the exact facet-function shapes seen in a
63
+ * REAL captured LI.FI response are decoded; every other selector refuses.
64
+ * Adding a new tool means capturing a fixture for it first — see
65
+ * `packages/bridge-sdk/src/__fixtures__/lifi/README.md` — and extending
66
+ * {@link lifiFacetDecoders} from that fixture, never from documentation
67
+ * alone (per the hand-built-quotes-hide-parser-gaps lesson: a decoder written
68
+ * from a guess passes its own hand-built test and nothing else).
69
+ */
70
+ /**
71
+ * Every reason this module can refuse a LI.FI execution transaction.
72
+ *
73
+ * Each maps to one property of the response this module distrusts by
74
+ * default: `unknown-chain` and `router-mismatch` distrust `to`;
75
+ * `approval-address-mismatch` distrusts `estimate.approvalAddress`;
76
+ * `unknown-selector` and `calldata-undecodable` distrust `data`'s shape;
77
+ * `receiver-mismatch`, `destination-chain-mismatch`, `sending-asset-mismatch`,
78
+ * `destination-call-flag-mismatch` and `minimum-amount-below-requested`
79
+ * distrust `data`'s DECODED fields against what this application asked for;
80
+ * `origin-amount-mismatch` distrusts the decoded amount pulled from the
81
+ * depositor; `native-value-mismatch` distrusts `value`. `missing-request-context` is
82
+ * different in kind: it means the CALLER never supplied what this
83
+ * application expected, so none of the above could even be checked — see
84
+ * `parseLifiContractCallsQuote` and `parseLifiQuote` in `consolidated-quote.ts`.
85
+ */
86
+ export type LifiExecutionRefusalReason = 'unknown-chain' | 'router-mismatch' | 'approval-address-mismatch' | 'unknown-selector' | 'calldata-undecodable' | 'receiver-mismatch' | 'destination-chain-mismatch' | 'sending-asset-mismatch' | 'destination-call-flag-mismatch' | 'minimum-amount-below-requested' | 'origin-amount-mismatch' | 'native-value-mismatch' | 'missing-request-context';
87
+ /** Why a LI.FI execution transaction was refused before signing. */
88
+ export type LifiExecutionRefusal = {
89
+ readonly reason: LifiExecutionRefusalReason;
90
+ /** A short, specific explanation naming the values that disagreed. */
91
+ readonly detail: string;
92
+ };
93
+ /**
94
+ * Thrown by {@link verifyLifiEvmExecution} instead of returning a deposit a
95
+ * wallet could sign.
96
+ *
97
+ * NAMED AND TYPED SO A REFUSAL IS NEVER A SILENT DROP. Every caller that
98
+ * catches this can read `refusal.reason` and show, or log, exactly which
99
+ * check failed — the same discipline `@gibs/quotes`'s `ProviderQuoteError`
100
+ * applies to a provider declining a quote, applied here to a provider's
101
+ * ANSWER disagreeing with itself.
102
+ */
103
+ export declare class LifiExecutionRefusedError extends Error {
104
+ readonly refusal: LifiExecutionRefusal;
105
+ constructor(refusal: LifiExecutionRefusal);
106
+ }
107
+ /**
108
+ * LI.FI's Diamond entryway contract address, by chain id.
109
+ *
110
+ * SOURCE. `lifinance/contracts` (https://github.com/lifinance/contracts),
111
+ * commit `025989955bb373739737ee78ddff01620638ae3b` (2026-09-23): the
112
+ * `LiFiDiamond` address in `deployments/<network>.json`, matched to its
113
+ * chain id in `config/networks.json`, for every network whose `status` is
114
+ * `"active"` and `"type"` is `"mainnet"`. LI.FI's own documentation
115
+ * (https://docs.li.fi/introduction/lifi-architecture/smart-contract-addresses)
116
+ * states the address is `0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE` "on most
117
+ * supported networks" but differs on some — confirmed here directly against
118
+ * the deployment records rather than assumed: 39 of the 65 active mainnet
119
+ * networks use a DIFFERENT address, almost all of them newer or lower-volume
120
+ * chains (0G, Abstract, ApeChain, Arbitrum Nova, Berachain, Flare, zkSync
121
+ * Era, and more).
122
+ *
123
+ * DELIBERATELY A FIXED, BAKED-IN TABLE, NEVER A LIVE LOOKUP. LI.FI's own
124
+ * `/v1/chains` endpoint carries a `diamondAddress` per chain, and this
125
+ * codebase already fetches it for other purposes — but that endpoint is
126
+ * reached through the SAME configurable, potentially-compromised host this
127
+ * module exists to distrust. Trusting it here would make the whole guard
128
+ * circular: a compromised proxy could simply report its own malicious
129
+ * address as the "correct" one. This table is real code, reviewed and
130
+ * shipped with the application, and can only change through a new release.
131
+ *
132
+ * UNKNOWN CHAIN, UNKNOWN ADDRESS. A chain id absent from this table refuses
133
+ * rather than falling back to the common address — LI.FI adds networks
134
+ * continuously, and assuming a newly-added chain shares the common address
135
+ * would be exactly the kind of guess this module exists to avoid making.
136
+ */
137
+ export declare const lifiDiamondAddressByChainId: ReadonlyMap<number, Hex>;
138
+ /** LI.FI's `ILiFi.BridgeData` struct — identical across every facet function. */
139
+ export type LifiBridgeData = {
140
+ readonly transactionId: Hex;
141
+ readonly bridge: string;
142
+ readonly integrator: string;
143
+ readonly referrer: Hex;
144
+ readonly sendingAssetId: Hex;
145
+ readonly receiver: Hex;
146
+ readonly minAmount: bigint;
147
+ readonly destinationChainId: bigint;
148
+ readonly hasSourceSwaps: boolean;
149
+ readonly hasDestinationCall: boolean;
150
+ };
151
+ /**
152
+ * What THIS APPLICATION asked LI.FI for, independent of anything the
153
+ * response itself claims — the only thing a decoded transaction is allowed
154
+ * to be checked against.
155
+ */
156
+ export type LifiExecutionExpectation = {
157
+ /**
158
+ * Where the bridged asset lands if the route needs no destination call
159
+ * (the plain endpoint's recipient), or where it lands if the destination
160
+ * call reverts (the contract-calls endpoint's fallback address) — see the
161
+ * `BridgeData.receiver` note at the top of this file for why these two are
162
+ * NOT the same field despite the shared name.
163
+ */
164
+ readonly receiver: Hex;
165
+ /** The chain the entry call (or the plain delivery) must land on. */
166
+ readonly destinationChainId: number;
167
+ /** The token this application is paying with on the origin chain. */
168
+ readonly sendingAssetId: Hex;
169
+ /** Whether this application attached an omnibridge entry call. */
170
+ readonly hasDestinationCall: boolean;
171
+ /**
172
+ * The guaranteed minimum this application asked LI.FI to deliver, or
173
+ * `null` for the plain (forward-solved) endpoint, which names no such
174
+ * minimum — the delivered amount is read back from the response instead
175
+ * of constrained by the request. See `ConsolidatedQuote.toAmount`.
176
+ */
177
+ readonly minimumAmount: bigint | null;
178
+ /**
179
+ * The exact amount of the origin token this application sends, in base units,
180
+ * or `null` when the request names none.
181
+ *
182
+ * Compared with the decoded `swapData[0].fromAmount`, the amount pulled from
183
+ * the depositor. Without it a response could keep every other field honest and
184
+ * still pull more than the reader chose. The request, never the response,
185
+ * supplies this figure.
186
+ */
187
+ readonly originAmount: bigint | null;
188
+ };
189
+ /** The transaction fields {@link verifyLifiEvmExecution} reads off the raw response. */
190
+ export type LifiExecutionTransactionRequest = {
191
+ readonly to: Hex;
192
+ readonly data: Hex;
193
+ readonly value: bigint;
194
+ readonly chainId: number;
195
+ };
196
+ /**
197
+ * Verifies a LI.FI EVM execution transaction against what this application
198
+ * asked for, throwing {@link LifiExecutionRefusedError} on any disagreement.
199
+ * Returns normally — carrying nothing, since the caller already holds the
200
+ * transaction it validated — when every check passes.
201
+ *
202
+ * See this module's top-of-file doc comment for what is checked and why.
203
+ *
204
+ * @param transactionRequest - the parsed `to`/`data`/`value`/`chainId` this
205
+ * application is about to hand to a wallet
206
+ * @param approvalAddress - `estimate.approvalAddress` from the same
207
+ * response, or `null` when the route needs no token approval
208
+ * @param expectation - what this application actually asked for
209
+ */
210
+ export declare const verifyLifiEvmExecution: (transactionRequest: LifiExecutionTransactionRequest, approvalAddress: Hex | null, expectation: LifiExecutionExpectation) => void;
@@ -0,0 +1,409 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.verifyLifiEvmExecution = exports.lifiDiamondAddressByChainId = exports.LifiExecutionRefusedError = void 0;
4
+ const viem_1 = require("viem");
5
+ /**
6
+ * Thrown by {@link verifyLifiEvmExecution} instead of returning a deposit a
7
+ * wallet could sign.
8
+ *
9
+ * NAMED AND TYPED SO A REFUSAL IS NEVER A SILENT DROP. Every caller that
10
+ * catches this can read `refusal.reason` and show, or log, exactly which
11
+ * check failed — the same discipline `@gibs/quotes`'s `ProviderQuoteError`
12
+ * applies to a provider declining a quote, applied here to a provider's
13
+ * ANSWER disagreeing with itself.
14
+ */
15
+ class LifiExecutionRefusedError extends Error {
16
+ refusal;
17
+ constructor(refusal) {
18
+ super(`LI.FI execution transaction refused: ${refusal.detail}`);
19
+ this.refusal = refusal;
20
+ this.name = 'LifiExecutionRefusedError';
21
+ }
22
+ }
23
+ exports.LifiExecutionRefusedError = LifiExecutionRefusedError;
24
+ const refuse = (reason, detail) => {
25
+ throw new LifiExecutionRefusedError({ reason, detail });
26
+ };
27
+ /**
28
+ * LI.FI's Diamond entryway contract address, by chain id.
29
+ *
30
+ * SOURCE. `lifinance/contracts` (https://github.com/lifinance/contracts),
31
+ * commit `025989955bb373739737ee78ddff01620638ae3b` (2026-09-23): the
32
+ * `LiFiDiamond` address in `deployments/<network>.json`, matched to its
33
+ * chain id in `config/networks.json`, for every network whose `status` is
34
+ * `"active"` and `"type"` is `"mainnet"`. LI.FI's own documentation
35
+ * (https://docs.li.fi/introduction/lifi-architecture/smart-contract-addresses)
36
+ * states the address is `0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE` "on most
37
+ * supported networks" but differs on some — confirmed here directly against
38
+ * the deployment records rather than assumed: 39 of the 65 active mainnet
39
+ * networks use a DIFFERENT address, almost all of them newer or lower-volume
40
+ * chains (0G, Abstract, ApeChain, Arbitrum Nova, Berachain, Flare, zkSync
41
+ * Era, and more).
42
+ *
43
+ * DELIBERATELY A FIXED, BAKED-IN TABLE, NEVER A LIVE LOOKUP. LI.FI's own
44
+ * `/v1/chains` endpoint carries a `diamondAddress` per chain, and this
45
+ * codebase already fetches it for other purposes — but that endpoint is
46
+ * reached through the SAME configurable, potentially-compromised host this
47
+ * module exists to distrust. Trusting it here would make the whole guard
48
+ * circular: a compromised proxy could simply report its own malicious
49
+ * address as the "correct" one. This table is real code, reviewed and
50
+ * shipped with the application, and can only change through a new release.
51
+ *
52
+ * UNKNOWN CHAIN, UNKNOWN ADDRESS. A chain id absent from this table refuses
53
+ * rather than falling back to the common address — LI.FI adds networks
54
+ * continuously, and assuming a newly-added chain shares the common address
55
+ * would be exactly the kind of guess this module exists to avoid making.
56
+ */
57
+ exports.lifiDiamondAddressByChainId = new Map([
58
+ [1, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // mainnet
59
+ [10, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // optimism
60
+ [14, '0x198FC70Dfe05E755C81e54bd67Bff3F729344B9b'], // flare
61
+ [25, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // cronos
62
+ [30, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // rootstock
63
+ [40, '0xa7cd44521903C65915648385E3FE881E49171582'], // telos
64
+ [50, '0x055d4612Ec74aD799C6cB4dF72C0Ab8dbDBCBAfa'], // xdc
65
+ [56, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // bsc
66
+ [88, '0x198FC70Dfe05E755C81e54bd67Bff3F729344B9b'], // viction
67
+ [100, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // gnosis
68
+ [122, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // fuse
69
+ [130, '0x864b314D4C5a0399368609581d3E8933a63b9232'], // unichain
70
+ [137, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // polygon
71
+ [143, '0x026F252016A7C47CDEf1F05a3Fc9E20C92a49C37'], // monad
72
+ [146, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // sonic
73
+ [196, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // xlayer
74
+ [204, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // opbnb
75
+ [232, '0xF3B20515d9B193531c48E47c18aF16d1e5d28f9a'], // lens
76
+ [252, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // fraxtal
77
+ [288, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // boba
78
+ [324, '0x341e94069f53234fE6DabeF707aD424830525715'], // zksync
79
+ [480, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // worldchain
80
+ [747, '0x026F252016A7C47CDEf1F05a3Fc9E20C92a49C37'], // flow
81
+ [988, '0x026F252016A7C47CDEf1F05a3Fc9E20C92a49C37'], // stable
82
+ [1088, '0x24ca98fB6972F5eE05f0dB00595c7f68D9FaFd68'], // metis
83
+ [1135, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // lisk
84
+ [1329, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // sei
85
+ [1480, '0x198FC70Dfe05E755C81e54bd67Bff3F729344B9b'], // vana
86
+ [1625, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // gravity
87
+ [1672, '0xFf70F4A1d11995621854F3692acF286d8aCd04b2'], // pharos
88
+ [1776, '0xeCeC3970Ca674278DA8D9B1c484ACaF6B20181F5'], // injective
89
+ [1868, '0x864b314D4C5a0399368609581d3E8933a63b9232'], // soneium
90
+ [2020, '0x452Cf1B8597E6319Cd21abd847312bF17E26d8d1'], // ronin
91
+ [2741, '0x4f8C9056bb8A3616693a76922FA35d53C056E5b3'], // abstract
92
+ [2818, '0xF7aB42D00D2399f8A4Fba2F15466Be40709fe307'], // morph
93
+ [4217, '0x2cAcAE8e22418E65dcf7651c67aEbe6288EB8243'], // tempo
94
+ [4326, '0x026F252016A7C47CDEf1F05a3Fc9E20C92a49C37'], // megaeth
95
+ [4663, '0xB477751B76CF82d00a686A1232f5fCD772414Af3'], // robinhood
96
+ [5000, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // mantle
97
+ [5031, '0x0bc413CAe89C5b3Ba3AA87FB98dCA23425E1828f'], // somnia
98
+ [5042, '0xA4072583658Fae592A3506A42431cb6316a8d40b'], // arc
99
+ [5734951, '0x026F252016A7C47CDEf1F05a3Fc9E20C92a49C37'], // jovay
100
+ [6900, '0x926c22Dc753E543Cf9CaB75CeEB46ecf865d006a'], // nibiru
101
+ [8217, '0x1255d17c1BC2f764d087536410879F2d0D8772fD'], // kaia
102
+ [8453, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // base
103
+ [9745, '0x026F252016A7C47CDEf1F05a3Fc9E20C92a49C37'], // plasma
104
+ [13371, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // immutablezkevm
105
+ [16661, '0x213A83c67E1Fc0334eF684571ABCB820708A6536'], // 0g
106
+ [33139, '0x2dea447e7dc6cd2f10b31bF10dCB30F87E838417'], // apechain
107
+ [34443, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // mode
108
+ [42161, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // arbitrum
109
+ [42170, '0xFf70F4A1d11995621854F3692acF286d8aCd04b2'], // arbitrumnova
110
+ [42220, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // celo
111
+ [42793, '0x977474593c982cFa8b197cAE302e6d01f789435b'], // etherlink
112
+ [43111, '0x026F252016A7C47CDEf1F05a3Fc9E20C92a49C37'], // hemi
113
+ [43114, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // avalanche
114
+ [57073, '0x864b314D4C5a0399368609581d3E8933a63b9232'], // ink
115
+ [59144, '0xDE1E598b81620773454588B85D6b5D4eEC32573e'], // linea
116
+ [60808, '0x452Cf1B8597E6319Cd21abd847312bF17E26d8d1'], // bob
117
+ [80094, '0xf909c4Ae16622898b885B89d7F839E0244851c66'], // berachain
118
+ [81457, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // blast
119
+ [98866, '0x6f5C8Bb0C5Fe4ECeAC40EE1C238EaB6bbb29761c'], // plume
120
+ [534352, '0x1231DEB6f5749EF6cE6943a275A1D3E7486F4EaE'], // scroll
121
+ [747474, '0xC59fe32C9549e3E8B5dCcdAbC45BD287Bd5bA2bc'], // katana
122
+ [999, '0x0a0758d937d1059c356D4714e57F5df0239bce1A'], // hyperevm
123
+ ]);
124
+ const bridgeDataAbiType = {
125
+ type: 'tuple',
126
+ components: [
127
+ { name: 'transactionId', type: 'bytes32' },
128
+ { name: 'bridge', type: 'string' },
129
+ { name: 'integrator', type: 'string' },
130
+ { name: 'referrer', type: 'address' },
131
+ { name: 'sendingAssetId', type: 'address' },
132
+ { name: 'receiver', type: 'address' },
133
+ { name: 'minAmount', type: 'uint256' },
134
+ { name: 'destinationChainId', type: 'uint256' },
135
+ { name: 'hasSourceSwaps', type: 'bool' },
136
+ { name: 'hasDestinationCall', type: 'bool' },
137
+ ],
138
+ };
139
+ const swapDataArrayAbiType = {
140
+ type: 'tuple[]',
141
+ components: [
142
+ { name: 'callTo', type: 'address' },
143
+ { name: 'approveTo', type: 'address' },
144
+ { name: 'sendingAssetId', type: 'address' },
145
+ { name: 'receivingAssetId', type: 'address' },
146
+ { name: 'fromAmount', type: 'uint256' },
147
+ { name: 'callData', type: 'bytes' },
148
+ { name: 'requiresDeposit', type: 'bool' },
149
+ ],
150
+ };
151
+ /** Decodes `BridgeData` and the leading `SwapData` entry, shared by every verified decoder. */
152
+ const decodeBridgeDataAndLeadingSwap = (encodedTail) => {
153
+ const [bridgeData, swapData] = (0, viem_1.decodeAbiParameters)([bridgeDataAbiType, swapDataArrayAbiType], encodedTail);
154
+ // EVERY VERIFIED SELECTOR IS A `swapAndStartBridgeTokensViaX` FUNCTION,
155
+ // whose `containsSourceSwaps` modifier requires a non-empty `SwapData[]`
156
+ // on a genuine call. An empty array here — whether from `hasSourceSwaps`
157
+ // being forged false or the array itself being emptied — means this
158
+ // module cannot identify what was actually pulled from the depositor, so
159
+ // it refuses rather than reading past the end of the array.
160
+ const leadingSwap = swapData[0];
161
+ if (!bridgeData.hasSourceSwaps || leadingSwap === undefined) {
162
+ return null;
163
+ }
164
+ return { bridgeData, leadingSwap };
165
+ };
166
+ /**
167
+ * `swapAndStartBridgeTokensViaAcrossV4((BridgeData),(SwapData[]),(AcrossV4Data))`.
168
+ *
169
+ * Selector `0x1794958f`, verified against
170
+ * `packages/bridge-sdk/src/__fixtures__/lifi/contract-quote-success-eth-to-base-usdc.json`
171
+ * (a real `GET /v1/quote` response) — signature and struct layout read from
172
+ * `lifinance/contracts/src/Facets/AcrossFacetV4.sol`. `AcrossV4Data` carries
173
+ * no native-fee field, so {@link DecodedLifiFacetCall.extraNativeFee} is
174
+ * always `0n`. `outputAmount` is Across's own destination-side guarantee —
175
+ * confirmed equal to the SAME response's `estimate.toAmountMin`.
176
+ */
177
+ const decodeAcrossV4SwapAndStart = (data) => {
178
+ const acrossV4DataAbiType = {
179
+ type: 'tuple',
180
+ components: [
181
+ { name: 'receiverAddress', type: 'bytes32' },
182
+ { name: 'refundAddress', type: 'bytes32' },
183
+ { name: 'sendingAssetId', type: 'bytes32' },
184
+ { name: 'receivingAssetId', type: 'bytes32' },
185
+ { name: 'outputAmount', type: 'uint256' },
186
+ { name: 'outputAmountMultiplier', type: 'uint128' },
187
+ { name: 'exclusiveRelayer', type: 'bytes32' },
188
+ { name: 'quoteTimestamp', type: 'uint32' },
189
+ { name: 'fillDeadline', type: 'uint32' },
190
+ { name: 'exclusivityParameter', type: 'uint32' },
191
+ { name: 'message', type: 'bytes' },
192
+ ],
193
+ };
194
+ const encodedTail = `0x${data.slice(10)}`;
195
+ const decodedHead = decodeBridgeDataAndLeadingSwap(encodedTail);
196
+ if (decodedHead === null) {
197
+ return null;
198
+ }
199
+ const [, , acrossData] = (0, viem_1.decodeAbiParameters)([bridgeDataAbiType, swapDataArrayAbiType, acrossV4DataAbiType], encodedTail);
200
+ return {
201
+ toolLabel: 'across',
202
+ bridgeData: decodedHead.bridgeData,
203
+ originSendingAssetId: decodedHead.leadingSwap.sendingAssetId,
204
+ originAmount: decodedHead.leadingSwap.fromAmount,
205
+ extraNativeFee: 0n,
206
+ destinationMinimum: acrossData.outputAmount,
207
+ };
208
+ };
209
+ /**
210
+ * `swapAndStartBridgeTokensViaLayerSwap((BridgeData),(SwapData[]),(LayerSwapData))`.
211
+ *
212
+ * Selector `0x4c279d6b`, verified against
213
+ * `packages/bridge-sdk/src/__fixtures__/lifi/quote-plain-base-to-polygon.json`
214
+ * (a real `GET /v1/quote` response) — signature and struct layout read from
215
+ * `lifinance/contracts/src/Facets/LayerSwapFacet.sol`. `LayerSwapData`
216
+ * carries no native-fee field ({@link DecodedLifiFacetCall.extraNativeFee}
217
+ * is always `0n`) and, unlike Across or Stargate, NO destination-amount
218
+ * field at all — LayerSwap's delivered amount is attested off-chain by an
219
+ * EIP-712 signature over `requestId`, not provable from this calldata, so
220
+ * {@link DecodedLifiFacetCall.destinationMinimum} is always `null`.
221
+ */
222
+ const decodeLayerSwapSwapAndStart = (data) => {
223
+ const layerSwapDataAbiType = {
224
+ type: 'tuple',
225
+ components: [
226
+ { name: 'requestId', type: 'bytes32' },
227
+ { name: 'depositoryReceiver', type: 'address' },
228
+ { name: 'refundRecipient', type: 'address' },
229
+ { name: 'nonEVMReceiver', type: 'bytes32' },
230
+ { name: 'signature', type: 'bytes' },
231
+ { name: 'deadline', type: 'uint256' },
232
+ ],
233
+ };
234
+ const encodedTail = `0x${data.slice(10)}`;
235
+ const decodedHead = decodeBridgeDataAndLeadingSwap(encodedTail);
236
+ if (decodedHead === null) {
237
+ return null;
238
+ }
239
+ return {
240
+ toolLabel: 'layerswap',
241
+ bridgeData: decodedHead.bridgeData,
242
+ originSendingAssetId: decodedHead.leadingSwap.sendingAssetId,
243
+ originAmount: decodedHead.leadingSwap.fromAmount,
244
+ extraNativeFee: 0n,
245
+ destinationMinimum: null,
246
+ };
247
+ };
248
+ /**
249
+ * `swapAndStartBridgeTokensViaStargate((BridgeData),(SwapData[]),(StargateData))`.
250
+ *
251
+ * Selector `0xa6010a66`, verified against two live captures of
252
+ * `POST /v1/quote/contractCalls`
253
+ * (`packages/bridge-sdk/src/__fixtures__/lifi/contract-calls-native-base-to-eth-mediator.json`
254
+ * and its `-erc20-` sibling) — signature and struct layout read from
255
+ * `lifinance/contracts/src/Facets/StargateFacetV2.sol` and
256
+ * `src/Interfaces/IStargate.sol`. `StargateData.fee.nativeFee` is a
257
+ * LayerZero messaging fee that rides in `msg.value` ON TOP of any deposited
258
+ * native amount — confirmed by both fixtures: `transactionRequest.value`
259
+ * equalled `originAmount + fee.nativeFee` exactly (origin ETH) and
260
+ * `fee.nativeFee` alone when the origin amount was an ERC20 (USD Coin, no
261
+ * native component). `sendParams.minAmountLD` is Stargate's own
262
+ * destination-side guarantee.
263
+ */
264
+ const decodeStargateSwapAndStart = (data) => {
265
+ const stargateDataAbiType = {
266
+ type: 'tuple',
267
+ components: [
268
+ { name: 'assetId', type: 'uint16' },
269
+ {
270
+ name: 'sendParams',
271
+ type: 'tuple',
272
+ components: [
273
+ { name: 'dstEid', type: 'uint32' },
274
+ { name: 'to', type: 'bytes32' },
275
+ { name: 'amountLD', type: 'uint256' },
276
+ { name: 'minAmountLD', type: 'uint256' },
277
+ { name: 'extraOptions', type: 'bytes' },
278
+ { name: 'composeMsg', type: 'bytes' },
279
+ { name: 'oftCmd', type: 'bytes' },
280
+ ],
281
+ },
282
+ {
283
+ name: 'fee',
284
+ type: 'tuple',
285
+ components: [
286
+ { name: 'nativeFee', type: 'uint256' },
287
+ { name: 'lzTokenFee', type: 'uint256' },
288
+ ],
289
+ },
290
+ { name: 'refundAddress', type: 'address' },
291
+ ],
292
+ };
293
+ const encodedTail = `0x${data.slice(10)}`;
294
+ const decodedHead = decodeBridgeDataAndLeadingSwap(encodedTail);
295
+ if (decodedHead === null) {
296
+ return null;
297
+ }
298
+ const [, , stargateData] = (0, viem_1.decodeAbiParameters)([bridgeDataAbiType, swapDataArrayAbiType, stargateDataAbiType], encodedTail);
299
+ return {
300
+ toolLabel: 'stargateV2',
301
+ bridgeData: decodedHead.bridgeData,
302
+ originSendingAssetId: decodedHead.leadingSwap.sendingAssetId,
303
+ originAmount: decodedHead.leadingSwap.fromAmount,
304
+ extraNativeFee: stargateData.fee.nativeFee,
305
+ destinationMinimum: stargateData.sendParams.minAmountLD,
306
+ };
307
+ };
308
+ /**
309
+ * Every LI.FI facet function this module can decode, keyed by its four-byte
310
+ * selector. AN UNKNOWN SELECTOR IS NEVER DECODED — {@link verifyLifiEvmExecution}
311
+ * refuses it outright. Extending this table means capturing a real fixture
312
+ * for the new tool first; see this module's top-of-file doc comment.
313
+ */
314
+ const lifiFacetDecoders = {
315
+ '0x1794958f': decodeAcrossV4SwapAndStart,
316
+ '0x4c279d6b': decodeLayerSwapSwapAndStart,
317
+ '0xa6010a66': decodeStargateSwapAndStart,
318
+ };
319
+ const sameAddress = (a, b) => a.toLowerCase() === b.toLowerCase();
320
+ /**
321
+ * Verifies a LI.FI EVM execution transaction against what this application
322
+ * asked for, throwing {@link LifiExecutionRefusedError} on any disagreement.
323
+ * Returns normally — carrying nothing, since the caller already holds the
324
+ * transaction it validated — when every check passes.
325
+ *
326
+ * See this module's top-of-file doc comment for what is checked and why.
327
+ *
328
+ * @param transactionRequest - the parsed `to`/`data`/`value`/`chainId` this
329
+ * application is about to hand to a wallet
330
+ * @param approvalAddress - `estimate.approvalAddress` from the same
331
+ * response, or `null` when the route needs no token approval
332
+ * @param expectation - what this application actually asked for
333
+ */
334
+ const verifyLifiEvmExecution = (transactionRequest, approvalAddress, expectation) => {
335
+ const router = exports.lifiDiamondAddressByChainId.get(transactionRequest.chainId);
336
+ if (router === undefined) {
337
+ refuse('unknown-chain', `no known LI.FI Diamond address for chain ${transactionRequest.chainId}`);
338
+ return;
339
+ }
340
+ if (!sameAddress(transactionRequest.to, router)) {
341
+ refuse('router-mismatch', `transactionRequest.to (${transactionRequest.to}) is not LI.FI's Diamond on chain ${transactionRequest.chainId} (${router})`);
342
+ return;
343
+ }
344
+ if (approvalAddress !== null && !sameAddress(approvalAddress, router)) {
345
+ refuse('approval-address-mismatch', `estimate.approvalAddress (${approvalAddress}) is not LI.FI's Diamond on chain ${transactionRequest.chainId} (${router})`);
346
+ return;
347
+ }
348
+ if (transactionRequest.data.length < 10) {
349
+ refuse('calldata-undecodable', 'transactionRequest.data is shorter than a function selector');
350
+ return;
351
+ }
352
+ const selector = transactionRequest.data.slice(0, 10).toLowerCase();
353
+ const decoder = lifiFacetDecoders[selector];
354
+ if (decoder === undefined) {
355
+ refuse('unknown-selector', `transactionRequest.data's selector (${selector}) is not a verified LI.FI facet function`);
356
+ return;
357
+ }
358
+ let decoded;
359
+ try {
360
+ decoded = decoder(transactionRequest.data);
361
+ }
362
+ catch (error) {
363
+ refuse('calldata-undecodable', `transactionRequest.data did not decode as ${selector}'s known parameters: ${error instanceof Error ? error.message : String(error)}`);
364
+ return;
365
+ }
366
+ if (decoded === null) {
367
+ refuse('calldata-undecodable', `${selector} decoded with no usable source-swap entry`);
368
+ return;
369
+ }
370
+ if (!sameAddress(decoded.bridgeData.receiver, expectation.receiver)) {
371
+ refuse('receiver-mismatch', `decoded BridgeData.receiver (${decoded.bridgeData.receiver}) does not match the expected receiver (${expectation.receiver})`);
372
+ return;
373
+ }
374
+ if (decoded.bridgeData.destinationChainId !== BigInt(expectation.destinationChainId)) {
375
+ refuse('destination-chain-mismatch', `decoded BridgeData.destinationChainId (${decoded.bridgeData.destinationChainId}) does not match the requested destination chain (${expectation.destinationChainId})`);
376
+ return;
377
+ }
378
+ if (!sameAddress(decoded.originSendingAssetId, expectation.sendingAssetId)) {
379
+ refuse('sending-asset-mismatch', `decoded origin sending asset (${decoded.originSendingAssetId}) does not match the requested origin token (${expectation.sendingAssetId})`);
380
+ return;
381
+ }
382
+ if (decoded.bridgeData.hasDestinationCall !== expectation.hasDestinationCall) {
383
+ refuse('destination-call-flag-mismatch', `decoded BridgeData.hasDestinationCall (${decoded.bridgeData.hasDestinationCall}) does not match whether this request attached a destination call (${expectation.hasDestinationCall})`);
384
+ return;
385
+ }
386
+ if (expectation.minimumAmount !== null) {
387
+ if (decoded.destinationMinimum === null) {
388
+ refuse('minimum-amount-below-requested', `${decoded.toolLabel} carries no on-chain destination-amount field to verify against the requested minimum (${expectation.minimumAmount})`);
389
+ return;
390
+ }
391
+ if (decoded.destinationMinimum < expectation.minimumAmount) {
392
+ refuse('minimum-amount-below-requested', `decoded destination minimum (${decoded.destinationMinimum}) is below the requested minimum (${expectation.minimumAmount})`);
393
+ return;
394
+ }
395
+ }
396
+ if (expectation.originAmount !== null && decoded.originAmount !== expectation.originAmount) {
397
+ refuse('origin-amount-mismatch', `decoded origin amount (${decoded.originAmount}) does not equal the amount to send (${expectation.originAmount})`);
398
+ return;
399
+ }
400
+ const nativeOriginComponent = sameAddress(decoded.originSendingAssetId, viem_1.zeroAddress)
401
+ ? decoded.originAmount
402
+ : 0n;
403
+ const expectedValue = nativeOriginComponent + decoded.extraNativeFee;
404
+ if (transactionRequest.value !== expectedValue) {
405
+ refuse('native-value-mismatch', `transactionRequest.value (${transactionRequest.value}) does not equal the native amount the decoded calldata implies (${expectedValue})`);
406
+ return;
407
+ }
408
+ };
409
+ exports.verifyLifiEvmExecution = verifyLifiEvmExecution;