@gibs/bridge-sdk 1.12.0 → 1.13.1

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 (118) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +234 -432
  3. package/dist/abis.d.ts +1 -0
  4. package/dist/bridge-limits.d.ts +103 -0
  5. package/dist/bridge-limits.js +84 -0
  6. package/dist/chain-info.d.ts +169 -0
  7. package/dist/chain-info.js +410 -0
  8. package/dist/chain-names.d.ts +160 -0
  9. package/dist/chain-names.js +200 -0
  10. package/dist/chains.d.ts +65 -0
  11. package/dist/chains.js +284 -0
  12. package/dist/config.d.ts +216 -0
  13. package/dist/config.js +493 -0
  14. package/dist/consolidated-quote.d.ts +554 -0
  15. package/dist/consolidated-quote.js +773 -0
  16. package/dist/destination-calldata.d.ts +118 -0
  17. package/dist/destination-calldata.js +152 -0
  18. package/dist/ecosystems.d.ts +217 -0
  19. package/dist/ecosystems.js +288 -0
  20. package/dist/fee-type.d.ts +10 -0
  21. package/dist/fee-type.js +9 -0
  22. package/dist/hyperliquid-execution.d.ts +152 -0
  23. package/dist/hyperliquid-execution.js +168 -0
  24. package/dist/image-links.d.ts +117 -0
  25. package/dist/image-links.js +142 -0
  26. package/dist/index.d.ts +20 -0
  27. package/dist/index.js +20 -0
  28. package/dist/lifi-execution-guard.d.ts +199 -0
  29. package/dist/lifi-execution-guard.js +400 -0
  30. package/dist/near-intents-conformance.d.ts +85 -0
  31. package/dist/near-intents-conformance.js +329 -0
  32. package/dist/near-intents-route.d.ts +47 -0
  33. package/dist/near-intents-route.js +265 -0
  34. package/dist/near-intents.d.ts +524 -0
  35. package/dist/near-intents.js +1055 -0
  36. package/dist/origin-deposit.d.ts +326 -0
  37. package/dist/origin-deposit.js +68 -0
  38. package/dist/pre-quote.d.ts +198 -0
  39. package/dist/pre-quote.js +175 -0
  40. package/dist/prices.d.ts +107 -0
  41. package/dist/prices.js +162 -0
  42. package/dist/providers.d.ts +221 -0
  43. package/dist/providers.js +157 -0
  44. package/dist/quote-fees.d.ts +102 -0
  45. package/dist/quote-fees.js +195 -0
  46. package/dist/referral.d.ts +77 -0
  47. package/dist/referral.js +72 -0
  48. package/dist/relay-currencies.d.ts +47 -0
  49. package/dist/relay-currencies.js +89 -0
  50. package/dist/relay-quote.d.ts +301 -0
  51. package/dist/relay-quote.js +983 -0
  52. package/dist/route-steps.d.ts +138 -0
  53. package/dist/route-steps.js +305 -0
  54. package/dist/routing/amounts.d.ts +80 -0
  55. package/dist/routing/amounts.js +120 -0
  56. package/dist/routing/candidate-route.d.ts +43 -0
  57. package/dist/routing/candidate-route.js +45 -0
  58. package/dist/routing/candidate.d.ts +237 -0
  59. package/dist/routing/candidate.js +96 -0
  60. package/dist/routing/carriers/ask-answer.d.ts +60 -0
  61. package/dist/routing/carriers/ask-answer.js +1 -0
  62. package/dist/routing/carriers/omnibridge.d.ts +137 -0
  63. package/dist/routing/carriers/omnibridge.js +419 -0
  64. package/dist/routing/carriers/pulsex.d.ts +149 -0
  65. package/dist/routing/carriers/pulsex.js +453 -0
  66. package/dist/routing/carriers/wrap.d.ts +25 -0
  67. package/dist/routing/carriers/wrap.js +115 -0
  68. package/dist/routing/composed-route-quality.d.ts +103 -0
  69. package/dist/routing/composed-route-quality.js +108 -0
  70. package/dist/routing/entry-chain-anchor.d.ts +88 -0
  71. package/dist/routing/entry-chain-anchor.js +55 -0
  72. package/dist/routing/format.d.ts +31 -0
  73. package/dist/routing/format.js +58 -0
  74. package/dist/routing/index.d.ts +34 -0
  75. package/dist/routing/index.js +42 -0
  76. package/dist/routing/producers.d.ts +770 -0
  77. package/dist/routing/producers.js +1564 -0
  78. package/dist/routing/providers.d.ts +26 -0
  79. package/dist/routing/providers.js +38 -0
  80. package/dist/routing/route-data.d.ts +355 -0
  81. package/dist/routing/route-data.js +1 -0
  82. package/dist/routing/search/compose.d.ts +168 -0
  83. package/dist/routing/search/compose.js +98 -0
  84. package/dist/routing/search/graph.d.ts +653 -0
  85. package/dist/routing/search/graph.js +445 -0
  86. package/dist/routing/search/plan-route-data.d.ts +244 -0
  87. package/dist/routing/search/plan-route-data.js +767 -0
  88. package/dist/routing/search/score.d.ts +353 -0
  89. package/dist/routing/search/score.js +427 -0
  90. package/dist/routing/usd-value.d.ts +17 -0
  91. package/dist/routing/usd-value.js +25 -0
  92. package/dist/settings.d.ts +692 -0
  93. package/dist/settings.js +909 -0
  94. package/dist/ton-execution.d.ts +117 -0
  95. package/dist/ton-execution.js +247 -0
  96. package/dist/tron-execution.d.ts +146 -0
  97. package/dist/tron-execution.js +239 -0
  98. package/dist/types.d.ts +51 -0
  99. package/dist/types.js +1 -0
  100. package/llms.txt +169 -0
  101. package/package.json +204 -23
  102. package/src/chain-info.test.ts +0 -968
  103. package/src/chain-info.ts +0 -498
  104. package/src/chains.test.ts +0 -123
  105. package/src/chains.ts +0 -88
  106. package/src/config.test.ts +0 -679
  107. package/src/config.ts +0 -500
  108. package/src/fee-type.test.ts +0 -45
  109. package/src/fee-type.ts +0 -14
  110. package/src/image-links.test.ts +0 -160
  111. package/src/image-links.ts +0 -65
  112. package/src/index.ts +0 -8
  113. package/src/settings.test.ts +0 -2754
  114. package/src/settings.ts +0 -875
  115. package/src/types.ts +0 -48
  116. package/tsconfig.json +0 -17
  117. package/vitest.config.ts +0 -13
  118. /package/{src/abis.ts → dist/abis.js} +0 -0
@@ -0,0 +1,909 @@
1
+ import { concatHex, encodeAbiParameters, encodeFunctionData, getAddress, isAddress, isAddressEqual, zeroAddress, } from 'viem';
2
+ import * as abis from './abis.js';
3
+ import { isNative, nativeAssetOut, pathway } from './config.js';
4
+ import { FeeType } from './fee-type.js';
5
+ export const oneEther = 10n ** 18n;
6
+ export const basisPoints = 10000n;
7
+ /**
8
+ * Returns the bridge pathway (configuration) for a given bridge key, asset in, and whether to use the production pathway
9
+ * @param bridgeKey - the bridge key to use
10
+ * @param isProd - whether to use the production pathway
11
+ * @param assetIn - the asset in to bridge
12
+ * @returns the bridge pathway
13
+ */
14
+ export const bridgePathway = ({ bridgeKey, isProd, assetIn, }) => pathway(bridgeKey, isProd, assetIn?.address) ?? null;
15
+ /**
16
+ * Retrieves the amount to bridge from the amount and asset in. If either of them is nil, returns 0n
17
+ * @param amountIn - the amount to bridge
18
+ * @param assetIn - the asset in to bridge
19
+ * @returns the amount to bridge
20
+ */
21
+ export const amountToBridge = ({ amountIn, assetIn, }) => {
22
+ if (!amountIn || amountIn === 0n || !assetIn) {
23
+ return 0n;
24
+ }
25
+ return amountIn;
26
+ };
27
+ /**
28
+ * Calculates the bridge cost from the amount to bridge and bridge fee
29
+ * @param amountToBridge - the amount to bridge
30
+ * @param bridgeFee - the bridge fee
31
+ * @returns the bridge cost
32
+ */
33
+ export const bridgeCost = ({ amountToBridge, bridgeFee, }) => {
34
+ if (bridgeFee === null)
35
+ return null;
36
+ return (amountToBridge * bridgeFee) / oneEther;
37
+ };
38
+ /**
39
+ * Calculates the amount after bridge fee from the amount to bridge and bridge cost
40
+ * @param amountToBridge - the amount to bridge
41
+ * @param bridgeCost - the bridge cost
42
+ * @returns the amount after bridge fee
43
+ */
44
+ export const amountAfterBridgeFee = ({ amountToBridge, bridgeCost, }) => {
45
+ if (bridgeCost === null)
46
+ return null;
47
+ const afterFee = amountToBridge - bridgeCost;
48
+ if (afterFee < 0n)
49
+ return 0n;
50
+ return afterFee;
51
+ };
52
+ /**
53
+ * Calculates the estimated amount out from the amount after bridge fee and fee
54
+ * @param amountAfterBridgeFee - the amount after bridge fee
55
+ * @param fee - the fee
56
+ * @returns the estimated amount out
57
+ */
58
+ export const estimatedAmountOut = ({ amountAfterBridgeFee, fee, }) => {
59
+ if (typeof fee !== 'bigint')
60
+ return null;
61
+ if (amountAfterBridgeFee === null)
62
+ return null;
63
+ if (fee > amountAfterBridgeFee)
64
+ return 0n;
65
+ return amountAfterBridgeFee - fee;
66
+ };
67
+ /**
68
+ * Calculates the estimated fee from the amount after bridge fee, fee, percent fee, fee type, limit, and whether to deliver the costs
69
+ * @param amountAfterBridgeFee - the amount after bridge fee
70
+ * @param fee - the fee
71
+ * @param percentFee - the percent fee
72
+ * @param feeType - the fee type
73
+ * @param limit - the limit
74
+ * @param costsToDeliver - whether to deliver the costs
75
+ * @returns the estimated fee
76
+ */
77
+ export const estimatedFee = ({ amountAfterBridgeFee, fee, percentFee, feeType, limit, costsToDeliver, }) => {
78
+ const fixedFee = amountAfterBridgeFee ? (amountAfterBridgeFee * percentFee) / oneEther : null;
79
+ if (!costsToDeliver) {
80
+ return 0n;
81
+ }
82
+ if (feeType === FeeType.FIXED) {
83
+ return fee;
84
+ }
85
+ else if (feeType === FeeType.PERCENT) {
86
+ if (amountAfterBridgeFee === null)
87
+ return null;
88
+ return fixedFee;
89
+ }
90
+ else if (feeType === FeeType.GAS_TIP) {
91
+ return limit;
92
+ }
93
+ return null;
94
+ };
95
+ /**
96
+ * Calculates the estimated native network cost from the estimated gas and latest base fee per gas
97
+ * @param estimatedGas - the estimated gas
98
+ * @param latestBaseFeePerGas - the latest base fee per gas
99
+ * @param requiresDelivery - whether to deliver the costs
100
+ * @returns the estimated native network cost
101
+ */
102
+ export const estimatedNativeNetworkCost = ({ estimatedGas, latestBaseFeePerGas, requiresDelivery, }) => {
103
+ if (!estimatedGas || !latestBaseFeePerGas || !requiresDelivery) {
104
+ return null;
105
+ }
106
+ return BigInt(estimatedGas * latestBaseFeePerGas);
107
+ };
108
+ /**
109
+ * Calculates the estimated token network cost from the estimated native
110
+ * network cost, price corrective, decimals, and one token int
111
+ * @param estimatedNativeNetworkCost - the estimated native network cost
112
+ * @param priceCorrective - the price corrective
113
+ * @param decimals - the decimals
114
+ * @param oneTokenInt - the one token int
115
+ * @returns the estimated token network cost
116
+ */
117
+ export const estimatedTokenNetworkCost = ({ estimatedNativeNetworkCost, priceCorrective, decimals, oneTokenInt, }) => {
118
+ if (!priceCorrective || !oneTokenInt || !estimatedNativeNetworkCost || !decimals) {
119
+ return null;
120
+ }
121
+ const tokenCost = (estimatedNativeNetworkCost * oneTokenInt) / priceCorrective;
122
+ return tokenCost;
123
+ };
124
+ /**
125
+ * Calculates one token of the asset in as an int
126
+ * @param assetIn - the asset in to bridge
127
+ * @returns the one token int
128
+ */
129
+ export const oneTokenInt = ({ assetIn }) => {
130
+ if (!assetIn)
131
+ return 1n;
132
+ return 10n ** BigInt(assetIn.decimals);
133
+ };
134
+ /**
135
+ * Returns the limit as a bigint. Nil uses 0n
136
+ * @param lim - the limit
137
+ * @returns the limit
138
+ */
139
+ export const limit = (lim) => {
140
+ if (!lim) {
141
+ return 0n;
142
+ }
143
+ return lim;
144
+ };
145
+ /**
146
+ * Returns the network switch asset out address from the asset out, unwrap, and to chain
147
+ * @param assetOut - the asset out
148
+ * @param unwrap - whether to unwrap
149
+ * @param toChain - the chain to switch to
150
+ * @returns the network switch asset out address
151
+ */
152
+ export const networkSwitchAssetOutAddress = ({ assetOut, unwrap, toChain, }) => {
153
+ if (!assetOut)
154
+ return null;
155
+ if (!unwrap)
156
+ return assetOut.address;
157
+ const wrappedNative = nativeAssetOut[toChain];
158
+ // Addresses arrive in whatever case their source used (a checksummed literal
159
+ // here, a lower-cased list entry there), so equality must ignore case.
160
+ const isWrappedNative = !!wrappedNative && isAddress(assetOut.address, { strict: false }) && isAddressEqual(wrappedNative, assetOut.address);
161
+ return isWrappedNative ? zeroAddress : assetOut.address;
162
+ };
163
+ /**
164
+ * Calculates the desired excess compensation basis points from the path, asset in, asset out, bridge key, fee type, list, and asset out address
165
+ * @param path - the path
166
+ * @param assetIn - the asset in to bridge
167
+ * @param assetOut - the asset out
168
+ * @param bridgeKey - the bridge key
169
+ * @param feeType - the fee type
170
+ * @param list - the list
171
+ * @param assetOutAddress - the asset out address
172
+ * @returns the desired excess compensation basis points
173
+ */
174
+ export const desiredExcessCompensationBasisPoints = ({ path, assetIn, assetOut, bridgeKey, feeType, list, assetOutAddress, }) => {
175
+ if (!path?.requiresDelivery) {
176
+ return 0n;
177
+ }
178
+ return !assetIn
179
+ ? 0n
180
+ : feeType === FeeType.PERCENT
181
+ ? 1000n
182
+ : isNative(assetIn, bridgeKey) || isNative(assetOut, bridgeKey)
183
+ ? 1000n
184
+ : list.has(getAddress(assetIn?.address ?? '')) ||
185
+ list.has(assetOutAddress ? getAddress(assetOutAddress) : '0x')
186
+ ? 5000n
187
+ : 10000n;
188
+ };
189
+ /**
190
+ * Works out the Gibs delivery fee for a transfer, as a {@link DeliveryFee}.
191
+ *
192
+ * Each mode reads the SAME figure the delivered estimate subtracts
193
+ * ({@link estimatedFee}), so a route's cost and its delivered value reconcile:
194
+ *
195
+ * - percent: the arriving value times the rate. The router computes
196
+ * `value * multiplier / 1 ether`.
197
+ * - fixed: the fixed amount. The router takes `limit` as the fee.
198
+ * - gas: the limit. The router takes the gas cost, capped at the limit, so the
199
+ * limit is the most the reader pays and the figure the delivered estimate
200
+ * already assumes.
201
+ *
202
+ * `percentFee` must be null when the rate is not known. Passing a zero stand-in
203
+ * would state a free delivery.
204
+ *
205
+ * @param options.shouldDeliver - whether the reader asked for delivery
206
+ * @param options.routeOffersDelivery - whether this pathway delivers for a fee at all
207
+ * @param options.feeType - the delivery fee mode
208
+ * @param options.amountAfterBridgeFee - the value that reaches the destination router, or null
209
+ * @param options.percentFee - the percent rate as a fraction of one ether, or null when unknown
210
+ * @param options.fixedFee - the fixed fee in base units, or null when unknown
211
+ * @param options.limit - the gas mode cap in base units, or null when unknown
212
+ * @returns the delivery fee
213
+ */
214
+ export const deliveryFee = ({ shouldDeliver, routeOffersDelivery, feeType, amountAfterBridgeFee, percentFee, fixedFee, limit, }) => {
215
+ if (!shouldDeliver || !routeOffersDelivery) {
216
+ return { status: 'not-requested' };
217
+ }
218
+ if (feeType === FeeType.FIXED) {
219
+ return { status: 'requested', amount: fixedFee, rate: null };
220
+ }
221
+ if (feeType === FeeType.GAS_TIP) {
222
+ return { status: 'requested', amount: limit, rate: null };
223
+ }
224
+ if (amountAfterBridgeFee === null || percentFee === null) {
225
+ return { status: 'requested', amount: null, rate: percentFee };
226
+ }
227
+ return {
228
+ status: 'requested',
229
+ amount: (amountAfterBridgeFee * percentFee) / oneEther,
230
+ rate: percentFee,
231
+ };
232
+ };
233
+ /**
234
+ * Calculates the reasonable fixed fee from the estimated token network cost and desired excess compensation basis points
235
+ * @param estimatedTokenNetworkCost - the estimated token network cost
236
+ * @param desiredExcessCompensationBasisPoints - the desired excess compensation basis points
237
+ * @returns the reasonable fixed fee
238
+ */
239
+ export const reasonableFixedFee = ({ estimatedTokenNetworkCost, desiredExcessCompensationBasisPoints, }) => {
240
+ if (!estimatedTokenNetworkCost) {
241
+ return null;
242
+ }
243
+ return ((estimatedTokenNetworkCost * (25000n + desiredExcessCompensationBasisPoints)) / basisPoints);
244
+ };
245
+ /**
246
+ * Calculates the reasonable percent on gas limit from the gas tip fee and estimated token network cost
247
+ * @param gasTipFee - the gas tip fee
248
+ * @param estimatedTokenNetworkCost - the estimated token network cost
249
+ * @returns the reasonable percent on gas limit
250
+ */
251
+ export const reasonablePercentOnGasLimit = ({ gasTipFee, estimatedTokenNetworkCost, }) => {
252
+ return ((25000n * ((oneEther + (gasTipFee ?? 0n)) * (estimatedTokenNetworkCost ?? 0n))) /
253
+ (oneEther * basisPoints));
254
+ };
255
+ /**
256
+ * Calculates the reasonable percent fee from the reasonable fixed fee and amount after bridge fee
257
+ * @param reasonableFixedFee - the reasonable fixed fee
258
+ * @param amountAfterBridgeFee - the amount after bridge fee
259
+ * @returns the reasonable percent fee
260
+ */
261
+ export const reasonablePercentFee = ({ reasonableFixedFee, amountAfterBridgeFee, }) => {
262
+ if (!reasonableFixedFee) {
263
+ return null;
264
+ }
265
+ if (!amountAfterBridgeFee) {
266
+ return null;
267
+ }
268
+ const highFee = amountAfterBridgeFee / 10n;
269
+ const basisFeeTruncator = 10n ** 14n;
270
+ const max = 1000n * basisFeeTruncator;
271
+ const min = 5n * basisFeeTruncator;
272
+ if (highFee < reasonableFixedFee) {
273
+ return max; // 10%
274
+ }
275
+ const highResPercent = (reasonableFixedFee * oneEther) / amountAfterBridgeFee;
276
+ if (highResPercent < min) {
277
+ return min;
278
+ }
279
+ else if (highResPercent > max) {
280
+ return max;
281
+ }
282
+ const basisPoint = highResPercent / basisFeeTruncator;
283
+ return basisPoint * basisFeeTruncator;
284
+ };
285
+ /**
286
+ * Returns whether the asset in address is interacting with a bridge token
287
+ * @param assetLink - the asset link
288
+ * @param assetInAddress - the asset in address
289
+ * @returns whether the asset in address is interacting with a bridge token
290
+ */
291
+ export const interactingWithBridgeToken = ({ assetLink, assetInAddress, }) => {
292
+ const { toForeign, toHome } = assetLink || {};
293
+ const { foreign } = toForeign || {};
294
+ const { home } = toHome || {};
295
+ if (!assetInAddress)
296
+ return false;
297
+ return !!((foreign && getAddress(foreign) === getAddress(assetInAddress)) ||
298
+ (home && getAddress(home) === getAddress(assetInAddress)));
299
+ };
300
+ /**
301
+ * Calculates the fee director struct encoded from the recipient, bridge pathway, fee type, price corrective, gas tip fee, percent fee, fee type settings, limit, and asset out
302
+ * @param recipient - the recipient
303
+ * @param bridgePathway - the bridge pathway
304
+ * @param feeType - the fee type
305
+ * @param priceCorrective - the price corrective
306
+ * @param gasTipFee - the gas tip fee
307
+ * @param percentFee - the percent fee
308
+ * @param feeTypeSettings - the fee type settings
309
+ * @param limit - the limit
310
+ * @param assetOut - the asset out
311
+ * @returns the fee director struct encoded
312
+ */
313
+ export const feeDirectorStructEncoded = ({ recipient, bridgePathway, feeType, priceCorrective, gasTipFee, percentFee, feeTypeSettings, limit, assetOut, }) => {
314
+ if (!assetOut || !bridgePathway) {
315
+ return null;
316
+ }
317
+ if (!bridgePathway.requiresDelivery) {
318
+ return null;
319
+ }
320
+ if (!priceCorrective) {
321
+ return null;
322
+ }
323
+ let multiplier = 0n;
324
+ if (feeType === FeeType.GAS_TIP && priceCorrective > 0n) {
325
+ multiplier =
326
+ ((oneEther + (gasTipFee ?? 0n)) * 10n ** BigInt(assetOut.decimals)) / priceCorrective;
327
+ }
328
+ else if (feeType === FeeType.PERCENT) {
329
+ multiplier = percentFee ?? 0n;
330
+ }
331
+ if (!recipient || !isAddress(recipient)) {
332
+ return null;
333
+ }
334
+ return encodeAbiParameters(abis.feeDeliveryStruct, [
335
+ [recipient, feeTypeSettings, limit, multiplier],
336
+ ]);
337
+ };
338
+ /**
339
+ * Calculates the destination data param from the recipient, bridge pathway, asset in, destination router, fee director struct encoded, asset link, should deliver, and unwrap
340
+ * @param recipient - the recipient
341
+ * @param bridgePathway - the bridge pathway
342
+ * @param assetIn - the asset in to bridge
343
+ * @param destinationRouter - the destination router
344
+ * @param feeDirectorStructEncoded - the fee director struct encoded
345
+ * @param assetLink - the asset link
346
+ * @param shouldDeliver - whether to deliver the costs
347
+ * @param unwrap - whether to unwrap
348
+ * @returns the destination data param
349
+ */
350
+ export const destinationDataParam = ({ recipient, bridgePathway, assetIn, destinationRouter, feeDirectorStructEncoded, assetLink, shouldDeliver, unwrap, }) => {
351
+ // const recipient = input.recipient.value
352
+ if (!recipient) {
353
+ return null;
354
+ }
355
+ if (!bridgePathway || !assetIn) {
356
+ return null;
357
+ }
358
+ if (!destinationRouter) {
359
+ return null;
360
+ }
361
+ if (bridgePathway.requiresDelivery && !feeDirectorStructEncoded) {
362
+ return null;
363
+ }
364
+ if (!assetLink) {
365
+ return null;
366
+ }
367
+ if (bridgePathway.requiresDelivery) {
368
+ // going from home to foreign
369
+ if (!shouldDeliver) {
370
+ // act like a regular bridge
371
+ return recipient;
372
+ }
373
+ // going from foreign to home
374
+ if (feeDirectorStructEncoded) {
375
+ return concatHex([destinationRouter, feeDirectorStructEncoded]);
376
+ }
377
+ return recipient;
378
+ }
379
+ if (!unwrap) {
380
+ return recipient;
381
+ }
382
+ // unwrap wpls->pls
383
+ return concatHex([destinationRouter, recipient]);
384
+ };
385
+ /**
386
+ * Converts a desired excess compensation basis point value into a percentage as an integer, with the denominator assumed to be 1e18
387
+ * @param desiredExcessCompensationBasisPoints - the desired excess compensation basis points
388
+ * @returns the reasonable percent on top of gas fee
389
+ */
390
+ export const reasonablePercentOnTopOfGasFee = ({ desiredExcessCompensationBasisPoints, }) => {
391
+ return desiredExcessCompensationBasisPoints * (oneEther / basisPoints);
392
+ };
393
+ /**
394
+ * Calculates the desired compensation ratio from the desired excess compensation basis points
395
+ * @param desiredExcessCompensationBasisPoints - the desired excess compensation basis points
396
+ * @returns the desired compensation ratio
397
+ */
398
+ export const desiredCompensationRatio = ({ desiredExcessCompensationBasisPoints, }) => {
399
+ return oneEther + (desiredExcessCompensationBasisPoints * oneEther) / basisPoints;
400
+ };
401
+ /**
402
+ * Calculates the available compensation maximum from the amount after bridge fee, percent fee, fee type, fixed fee, and limit
403
+ * @param amountAfterBridgeFee - the amount after bridge fee
404
+ * @param percentFee - the percent fee
405
+ * @param feeType - the fee type
406
+ * @param fixedFee - the fixed fee
407
+ * @param limit - the limit
408
+ * @returns the available compensation maximum
409
+ */
410
+ export const availableCompensationMaximum = ({ amountAfterBridgeFee, percentFee, feeType, fixedFee, limit, }) => {
411
+ if (feeType === FeeType.PERCENT) {
412
+ if (amountAfterBridgeFee === null)
413
+ return null;
414
+ return (amountAfterBridgeFee * (percentFee ?? 0n)) / oneEther;
415
+ }
416
+ else if (feeType === FeeType.GAS_TIP) {
417
+ return limit;
418
+ }
419
+ else if (feeType === FeeType.FIXED) {
420
+ return fixedFee ?? 0n;
421
+ }
422
+ return 0n;
423
+ };
424
+ /**
425
+ * Determines if the bridge is undercompensated based on network costs vs compensation limit using desired compensation rate
426
+ * @param estimatedTokenNetworkCost - the estimated token network cost
427
+ * @param feeType - the fee type
428
+ * @param limit - the maximum compensation limit
429
+ * @param amountAfterBridgeFee - the amount after bridge fee
430
+ * @param desiredExcessCompensationBasisPoints - the desired excess compensation basis points
431
+ * @returns whether the bridge is undercompensated
432
+ */
433
+ export const isUndercompensated = ({ estimatedTokenNetworkCost, feeType, limit, amountAfterBridgeFee, desiredExcessCompensationBasisPoints, }) => {
434
+ // No undercompensation if no amount to bridge
435
+ if (!amountAfterBridgeFee || amountAfterBridgeFee <= 0n) {
436
+ return false;
437
+ }
438
+ // GAS_TIP fee type doesn't have undercompensation issues
439
+ if (feeType === FeeType.GAS_TIP) {
440
+ return false;
441
+ }
442
+ // No network cost estimate means we can't determine undercompensation
443
+ if (!estimatedTokenNetworkCost) {
444
+ return false;
445
+ }
446
+ // Calculate desired compensation using the actual desired compensation ratio
447
+ const desiredCompensationRatio = oneEther + (desiredExcessCompensationBasisPoints * oneEther) / basisPoints;
448
+ const desiredCompensationForNetworkCost = (estimatedTokenNetworkCost * desiredCompensationRatio) / oneEther;
449
+ // Undercompensated if desired compensation exceeds the limit
450
+ return desiredCompensationForNetworkCost > limit;
451
+ };
452
+ /**
453
+ * Why a bridge key must not be built into a transaction for the pair on screen,
454
+ * or null when the two agree.
455
+ *
456
+ * WHY THIS EXISTS, in the reader's words: "i just tried to swap from weth from
457
+ * ethereum on pls, to pls, and the calldata that i must have signed was to
458
+ * bridge back from pls to eth". A selection of PulseChain on BOTH sides is a
459
+ * swap; it has no pathway, so the bridge key kept whatever crossing it last
460
+ * held — or the address bar was repaired to a crossing — and the settings
461
+ * cascade went on building a real, signable transfer of the reader's bridged
462
+ * wrapped ether to Ethereum while the screen still named PulseChain.
463
+ *
464
+ * Three refusals, and each one is a separate way the two can part company:
465
+ *
466
+ * - A SAME-CHAIN SELECTION IS NEVER A CROSSING. Origin and destination on one
467
+ * chain describes a swap. No omnibridge pathway can serve it, so any pathway
468
+ * reached for here belongs to some other journey.
469
+ * - THE PATHWAY MUST LEAVE THE CHAIN THE READER'S FUNDS ARE ON. A key whose
470
+ * source chain differs spends a token the reader is not looking at.
471
+ * - THE PATHWAY MUST LAND WHERE THE READER ASKED. This is the reported defect:
472
+ * the destination silently became Ethereum.
473
+ *
474
+ * `signedCrossing` is `null` for a call no reader composed — the aggregator's
475
+ * executor call built by `buildDestinationCalldata`, where the chain pair IS
476
+ * the pathway and there is no screen to disagree with. Nullable rather than
477
+ * optional so every caller has to decide, and the decision is visible at the
478
+ * call site.
479
+ *
480
+ * @param bridgeKey - the key the pathway was resolved from
481
+ * @param signedCrossing - the pair on screen, or null when no surface composed it
482
+ * @returns the reason to refuse, or null when the two agree
483
+ */
484
+ export const crossingRefusal = ({ bridgeKey, signedCrossing, }) => {
485
+ // `undefined` is treated as `null` here, and only here. The field is REQUIRED
486
+ // by the type, so no compiled caller can omit it — but a test double that
487
+ // returns a partial object can, and a crash inside the settings cascade blanks
488
+ // the page rather than refusing a transaction.
489
+ if (signedCrossing === null || signedCrossing === undefined) {
490
+ return null;
491
+ }
492
+ const { originChainId, destinationChainId } = signedCrossing;
493
+ if (originChainId === destinationChainId) {
494
+ return `Refusing to build a crossing for a selection that stays on chain ${originChainId}. Origin and destination are the same chain, which is a swap, not a bridge.`;
495
+ }
496
+ if (!bridgeKey) {
497
+ return `Refusing to build a crossing from chain ${originChainId} to chain ${destinationChainId}: no bridge key was resolved for it.`;
498
+ }
499
+ const pathwayOriginChainId = Number(bridgeKey[1]);
500
+ const pathwayDestinationChainId = Number(bridgeKey[2]);
501
+ if (pathwayOriginChainId !== originChainId) {
502
+ return `Refusing to build a crossing that leaves chain ${pathwayOriginChainId} while the selection starts on chain ${originChainId}.`;
503
+ }
504
+ if (pathwayDestinationChainId !== destinationChainId) {
505
+ return `Refusing to build a crossing that delivers on chain ${pathwayDestinationChainId} while the selection asks for chain ${destinationChainId}.`;
506
+ }
507
+ return null;
508
+ };
509
+ /**
510
+ * Throws when the bridge key and the pair on screen describe different journeys.
511
+ *
512
+ * The LOUD half of {@link crossingRefusal}. {@link transactionInputs} runs
513
+ * during render, where a throw would blank the application, so it refuses
514
+ * quietly by returning null and the button it feeds stays inert. This is for
515
+ * the broadcast funnel, where a disagreement means something reconstructed the
516
+ * call another way and the reader is one signature from a transfer they never
517
+ * asked for — there, silence is the worse failure.
518
+ *
519
+ * @param bridgeKey - the key the pathway was resolved from
520
+ * @param signedCrossing - the pair on screen, or null when no surface composed it
521
+ * @throws when the two describe different journeys
522
+ */
523
+ export const assertCrossingAgrees = ({ bridgeKey, signedCrossing, }) => {
524
+ const refusal = crossingRefusal({ bridgeKey, signedCrossing });
525
+ if (refusal !== null) {
526
+ throw new Error(refusal);
527
+ }
528
+ };
529
+ /**
530
+ * Why a same-chain PulseX swap transaction must not be built for the leg on
531
+ * screen, or null when they agree. The swap-side sibling of
532
+ * {@link crossingRefusal}.
533
+ *
534
+ * Two refusals, each a separate way the two can part company:
535
+ *
536
+ * - THE LEG ON SCREEN NAMES A CROSSING. A caller building a swap transaction
537
+ * while the reader is looking at a two-chain crossing would sign the wrong
538
+ * kind of thing for what is on screen.
539
+ * - THE SWAP DOES NOT EXECUTE ON THE CHAIN THE READER IS LOOKING AT. A stale
540
+ * candidate built for a chain the address bar has since left.
541
+ *
542
+ * @param signedLeg - what the surface believes this signature does, or null
543
+ * when no surface composed it
544
+ * @param chainId - the chain the candidate swap transaction actually executes on
545
+ * @returns the reason to refuse, or null when the two agree
546
+ */
547
+ export const swapRefusal = ({ signedLeg, chainId, }) => {
548
+ // `undefined` treated as `null`, matching `crossingRefusal`'s own guard: the
549
+ // field is required by the type, so only a test double or a crash inside the
550
+ // cascade could omit it.
551
+ if (signedLeg === null || signedLeg === undefined) {
552
+ return null;
553
+ }
554
+ if (signedLeg.kind !== 'swap') {
555
+ return `Refusing to build a same-chain swap for a selection that names a ${signedLeg.kind} (chain ${signedLeg.originChainId} to chain ${signedLeg.destinationChainId}).`;
556
+ }
557
+ if (chainId === null) {
558
+ return `Refusing to build a same-chain swap: no chain was resolved for it.`;
559
+ }
560
+ if (signedLeg.chainId !== chainId) {
561
+ return `Refusing to build a swap on chain ${chainId} while the selection names chain ${signedLeg.chainId}.`;
562
+ }
563
+ return null;
564
+ };
565
+ /**
566
+ * Throws when the leg on screen and the candidate swap transaction describe
567
+ * different journeys. The swap-side sibling of {@link assertCrossingAgrees} —
568
+ * loud where {@link swapRefusal} is quiet, for the same reason: this runs one
569
+ * step before the wallet, where silence is what lets a reader sign.
570
+ *
571
+ * @param signedLeg - what the surface believes this signature does, or null
572
+ * @param chainId - the chain the candidate swap transaction actually executes on
573
+ * @throws when the two describe different journeys
574
+ */
575
+ export const assertSwapAgrees = ({ signedLeg, chainId, }) => {
576
+ const refusal = swapRefusal({ signedLeg, chainId });
577
+ if (refusal !== null) {
578
+ throw new Error(refusal);
579
+ }
580
+ };
581
+ /**
582
+ * Why a candidate must not be signed for the leg and section a caller is
583
+ * about to build calldata for, or null when they agree. The candidate-pick
584
+ * sibling of {@link crossingRefusal}/{@link swapRefusal}.
585
+ *
586
+ * UNLIKE THOSE TWO, A MISSING PICK IS ITSELF A REFUSAL. `crossingRefusal` and
587
+ * `swapRefusal` let a null leg through because some calls compose no screen at
588
+ * all (the aggregator's own executor call). A candidate-signing sender always
589
+ * has a screen — the route the reader picked — so a candidate id with no
590
+ * recorded leg is exactly the gap this type exists to close: `candidateId`
591
+ * alone used to be treated as sufficient identity, and this function is what
592
+ * stops it from being treated that way again.
593
+ *
594
+ * @param pick - what the route recorded it picked, or null/undefined when the
595
+ * producer that built the route never set one
596
+ * @param leg - the leg the caller is about to build calldata for
597
+ * @param sectionIndex - the route section the caller is about to build
598
+ * calldata for
599
+ * @returns the reason to refuse, or null when they agree
600
+ */
601
+ export const candidatePickRefusal = ({ pick, leg, sectionIndex, }) => {
602
+ if (pick === null || pick === undefined) {
603
+ return `Refusing to sign a ${leg} at section ${sectionIndex}: the route named no candidate to sign.`;
604
+ }
605
+ if (pick.leg !== leg) {
606
+ return `Refusing to sign candidate ${pick.candidateId}'s ${pick.leg} leg as a ${leg}: the route picked a different kind of leg.`;
607
+ }
608
+ if (pick.sectionIndex !== sectionIndex) {
609
+ return `Refusing to sign candidate ${pick.candidateId} for section ${sectionIndex}: the route picked it for section ${pick.sectionIndex}.`;
610
+ }
611
+ return null;
612
+ };
613
+ /**
614
+ * Throws when a candidate pick and the leg/section a caller is about to build
615
+ * calldata for describe different signatures. The candidate-pick sibling of
616
+ * {@link assertCrossingAgrees}/{@link assertSwapAgrees} — loud where {@link
617
+ * candidatePickRefusal} is quiet, for the same reason: this runs one step
618
+ * before the wallet, where silence is what lets a reader sign the wrong thing.
619
+ *
620
+ * @param pick - what the route recorded it picked, or null
621
+ * @param leg - the leg the caller is about to build calldata for
622
+ * @param sectionIndex - the route section the caller is about to build
623
+ * calldata for
624
+ * @throws when the pick and the leg/section being built describe different
625
+ * signatures
626
+ */
627
+ export const assertCandidatePickAgrees = ({ pick, leg, sectionIndex, }) => {
628
+ const refusal = candidatePickRefusal({ pick, leg, sectionIndex });
629
+ if (refusal !== null) {
630
+ throw new Error(refusal);
631
+ }
632
+ };
633
+ /**
634
+ * Calculates the same-chain PulseX swap transaction inputs, or null when the
635
+ * inputs cannot form a call — including when the leg on screen and the swap's
636
+ * own chain disagree. The swap-side sibling of {@link transactionInputs}.
637
+ *
638
+ * SAME SHAPE AS `transactionInputs`, deliberately: `{ to, value, data } | null`,
639
+ * so a caller can dispatch either builder's result through one `sendTransaction`
640
+ * without a second shape to handle.
641
+ *
642
+ * WRAPPING AND UNWRAPPING THE NATIVE COIN USE THE ROUTER'S OWN NATIVE-COIN
643
+ * FUNCTIONS, not a separate deposit/withdraw step — one router contract wraps
644
+ * on the way in (`swapExactETHForTokens`) and unwraps on the way out
645
+ * (`swapExactTokensForETH`), the same contract `swapExactTokensForTokens`
646
+ * already targets when neither side is native.
647
+ *
648
+ * @param signedLeg - what the surface believes this signature does, or null
649
+ * @param chainId - the chain the swap actually executes on
650
+ * @param routerAddress - the PulseX router this swap is sent to, or null when
651
+ * none is deployed for this chain
652
+ * @param fromTokenAddress - the input token address, or the zero address for
653
+ * the chain's native coin
654
+ * @param toTokenAddress - the output token address, or the zero address for
655
+ * the chain's native coin
656
+ * @param swapPath - the ordered swap path (see `pulseXConversionSwapPath`),
657
+ * already expressed in the router's own token addresses — the wrapped-native
658
+ * token stands in for either native end
659
+ * @param amountIn - the amount to swap, in the input token's base units
660
+ * @param minAmountOut - the minimum acceptable output after slippage, in the
661
+ * output token's base units
662
+ * @param recipient - the address the swap delivers to
663
+ * @param deadline - the unix-seconds deadline the router enforces
664
+ * @returns the transaction inputs, or null when the inputs cannot form a call
665
+ */
666
+ export const pulseXSwapTransactionInputs = ({ signedLeg, chainId, routerAddress, fromTokenAddress, toTokenAddress, swapPath, amountIn, minAmountOut, recipient, deadline, }) => {
667
+ // FIRST, BEFORE ANY ENCODING — see `transactionInputs`'s identical note.
668
+ if (swapRefusal({ signedLeg, chainId }) !== null) {
669
+ return null;
670
+ }
671
+ if (!routerAddress) {
672
+ return null;
673
+ }
674
+ if (!recipient || !isAddress(recipient) || recipient === zeroAddress) {
675
+ return null;
676
+ }
677
+ if (swapPath.length < 2) {
678
+ return null;
679
+ }
680
+ if (amountIn <= 0n) {
681
+ return null;
682
+ }
683
+ const fromIsNative = fromTokenAddress === zeroAddress;
684
+ const toIsNative = toTokenAddress === zeroAddress;
685
+ // Native-to-native names no real swap — there is nothing for the router to do.
686
+ if (fromIsNative && toIsNative) {
687
+ return null;
688
+ }
689
+ if (fromIsNative) {
690
+ return {
691
+ to: routerAddress,
692
+ value: amountIn,
693
+ data: encodeFunctionData({
694
+ abi: abis.univ2Router,
695
+ functionName: 'swapExactETHForTokens',
696
+ args: [minAmountOut, swapPath, recipient, deadline],
697
+ }),
698
+ };
699
+ }
700
+ if (toIsNative) {
701
+ return {
702
+ to: routerAddress,
703
+ value: 0n,
704
+ data: encodeFunctionData({
705
+ abi: abis.univ2Router,
706
+ functionName: 'swapExactTokensForETH',
707
+ args: [amountIn, minAmountOut, swapPath, recipient, deadline],
708
+ }),
709
+ };
710
+ }
711
+ return {
712
+ to: routerAddress,
713
+ value: 0n,
714
+ data: encodeFunctionData({
715
+ abi: abis.univ2Router,
716
+ functionName: 'swapExactTokensForTokens',
717
+ args: [amountIn, minAmountOut, swapPath, recipient, deadline],
718
+ }),
719
+ };
720
+ };
721
+ /**
722
+ * Calculates the transaction inputs from the bridge pathway, asset in, recipient, account, asset link, destination data param, amount to bridge, fee director struct encoded, and should deliver
723
+ * @param bridgeKey - the key the pathway was resolved from, for {@link crossingRefusal}
724
+ * @param signedCrossing - the chain pair on screen, or null when no surface composed it
725
+ * @param bridgePathway - the bridge pathway to use
726
+ * @param assetIn - the asset in to bridge
727
+ * @param recipient - the recipient of the bridge
728
+ * @param account - the account to bridge from
729
+ * @param assetLink - the asset link to use
730
+ * @param destinationDataParam - the destination data param to use
731
+ * @param amountToBridge - the amount to bridge
732
+ * @param feeDirectorStructEncoded - the fee director struct encoded to use
733
+ * @param shouldDeliver - whether to deliver the tokens to the recipient
734
+ * @returns the transaction inputs, or null when the inputs cannot form a call —
735
+ * including when the pathway crosses somewhere other than the pair on screen
736
+ */
737
+ export const transactionInputs = ({ bridgeKey, signedCrossing, bridgePathway, assetIn, recipient, account, assetLink, destinationDataParam, amountToBridge, feeDirectorStructEncoded, shouldDeliver, }) => {
738
+ // FIRST, BEFORE ANY ENCODING. Every branch below writes a real mediator call;
739
+ // by the time one is chosen the disagreement is already encoded.
740
+ if (crossingRefusal({ bridgeKey, signedCrossing }) !== null) {
741
+ return null;
742
+ }
743
+ if (!bridgePathway) {
744
+ return null;
745
+ }
746
+ // check that recipient is valid
747
+ if (!recipient || !isAddress(recipient) || recipient === zeroAddress) {
748
+ return null;
749
+ }
750
+ // check that wallet account is valid
751
+ if (!account || !isAddress(account) || account === zeroAddress) {
752
+ return null;
753
+ }
754
+ // check that asset in is valid
755
+ if (!assetIn) {
756
+ return null;
757
+ }
758
+ // check that asset link is valid
759
+ if (!assetLink) {
760
+ return null;
761
+ }
762
+ let value = 0n;
763
+ // we are moving "from" this side, so we need to call a function on the "from" address
764
+ // only relevant for the relayTokens(AndCall) pathway outside of native tokens
765
+ let toAddress = bridgePathway.from;
766
+ let data = '0x';
767
+ const inputIsBridgeToken = interactingWithBridgeToken({
768
+ assetLink,
769
+ assetInAddress: assetIn.address,
770
+ });
771
+ if (inputIsBridgeToken) {
772
+ // when interacting with a bridged token, we need to call the token address directly
773
+ toAddress = assetIn.address;
774
+ value = 0n;
775
+ // if we want delivery of the tokens (going from home to foreign) then we need to have the foreign data param
776
+ // and it needs to start with the destination router
777
+ if (!destinationDataParam) {
778
+ return null;
779
+ }
780
+ data = bridgePathway.usesExtraParam
781
+ ? encodeFunctionData({
782
+ abi: abis.erc677ExtraInput,
783
+ functionName: 'transferAndCall',
784
+ args: [bridgePathway.from, amountToBridge, destinationDataParam, account],
785
+ })
786
+ : encodeFunctionData({
787
+ abi: abis.erc677,
788
+ functionName: 'transferAndCall',
789
+ args: [bridgePathway.from, amountToBridge, destinationDataParam],
790
+ });
791
+ }
792
+ else if (assetIn.address === zeroAddress) {
793
+ value = amountToBridge;
794
+ toAddress = bridgePathway.nativeRouter;
795
+ if (bridgePathway.feeManager === 'from' && shouldDeliver) {
796
+ // transferring native to foreign
797
+ if (!feeDirectorStructEncoded)
798
+ return null;
799
+ if (!bridgePathway.destinationRouter)
800
+ return null;
801
+ data = bridgePathway.usesExtraParam
802
+ ? encodeFunctionData({
803
+ abi: abis.nativeRouterExtraInput,
804
+ functionName: 'relayTokensAndCall',
805
+ args: [bridgePathway.destinationRouter, feeDirectorStructEncoded, account],
806
+ })
807
+ : encodeFunctionData({
808
+ abi: abis.nativeRouter,
809
+ functionName: 'relayTokensAndCall',
810
+ args: [bridgePathway.destinationRouter, feeDirectorStructEncoded],
811
+ });
812
+ }
813
+ else {
814
+ // delivery always occurs when moving from foreign to home
815
+ data = bridgePathway.usesExtraParam
816
+ ? encodeFunctionData({
817
+ abi: abis.nativeRouterExtraInput,
818
+ functionName: 'wrapAndRelayTokens',
819
+ args: [recipient, account],
820
+ })
821
+ : encodeFunctionData({
822
+ abi: abis.nativeRouter,
823
+ functionName: 'wrapAndRelayTokens',
824
+ args: [recipient],
825
+ });
826
+ }
827
+ }
828
+ else {
829
+ // tokens native to this side, entering the bridge
830
+ if (shouldDeliver && bridgePathway.requiresDelivery) {
831
+ if (!feeDirectorStructEncoded)
832
+ return null;
833
+ if (!bridgePathway.destinationRouter)
834
+ return null;
835
+ data = bridgePathway.usesExtraParam
836
+ ? encodeFunctionData({
837
+ abi: abis.inputBridgeExtraInput,
838
+ functionName: 'relayTokensAndCall',
839
+ args: [
840
+ assetIn.address,
841
+ bridgePathway.destinationRouter,
842
+ amountToBridge,
843
+ feeDirectorStructEncoded,
844
+ account,
845
+ ],
846
+ })
847
+ : encodeFunctionData({
848
+ abi: abis.inputBridge,
849
+ functionName: 'relayTokensAndCall',
850
+ args: [
851
+ assetIn.address,
852
+ bridgePathway.destinationRouter,
853
+ amountToBridge,
854
+ feeDirectorStructEncoded,
855
+ ],
856
+ });
857
+ }
858
+ else {
859
+ data = bridgePathway.usesExtraParam
860
+ ? encodeFunctionData({
861
+ abi: abis.inputBridgeExtraInput,
862
+ functionName: 'relayTokens',
863
+ args: [assetIn.address, recipient, amountToBridge, account],
864
+ })
865
+ : encodeFunctionData({
866
+ abi: abis.inputBridge,
867
+ functionName: 'relayTokens',
868
+ args: [assetIn.address, recipient, amountToBridge],
869
+ });
870
+ }
871
+ }
872
+ return {
873
+ to: toAddress,
874
+ value,
875
+ data,
876
+ };
877
+ };
878
+ /**
879
+ * Calculates the asset out key from the bridge key path, asset in address, and unwrap
880
+ * @param bridgeKeyPath - the bridge key path
881
+ * @param assetInAddress - the asset in address
882
+ * @param unwrap - whether to unwrap
883
+ * @returns the asset out key
884
+ */
885
+ export const assetOutKey = ({ bridgeKeyPath, assetInAddress, unwrap, }) => {
886
+ if (!bridgeKeyPath || !assetInAddress)
887
+ return null;
888
+ return `${bridgeKeyPath}/${unwrap ? 'native' : 'erc20'}/${assetInAddress.toLowerCase()}`;
889
+ };
890
+ /**
891
+ * Returns the asset out from the asset in address, assets out, bridge key path, and unwrap
892
+ * @param assetInAddress - the asset in address
893
+ * @param assetsOut - the assets out
894
+ * @param bridgeKeyPath - the bridge key path
895
+ * @param unwrap - whether to unwrap
896
+ * @returns the asset out
897
+ */
898
+ export const assetOut = ({ assetInAddress, assetsOut, bridgeKeyPath, unwrap, }) => {
899
+ if (!assetInAddress)
900
+ return null;
901
+ const key = assetOutKey({
902
+ bridgeKeyPath,
903
+ assetInAddress,
904
+ unwrap,
905
+ });
906
+ if (!key)
907
+ return null;
908
+ return assetsOut.get(key) ?? null;
909
+ };