@gibs/bridge-sdk 1.10.1 → 1.13.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 (122) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +234 -432
  3. package/dist/abis.d.ts +1 -0
  4. package/dist/abis.js +3 -0
  5. package/dist/bridge-limits.d.ts +103 -0
  6. package/dist/bridge-limits.js +84 -0
  7. package/dist/{src/chain-info.d.ts → chain-info.d.ts} +66 -27
  8. package/dist/{src/chain-info.js → chain-info.js} +106 -46
  9. package/dist/chain-names.d.ts +160 -0
  10. package/dist/chain-names.js +200 -0
  11. package/dist/chains.d.ts +65 -0
  12. package/dist/chains.js +284 -0
  13. package/dist/config.d.ts +216 -0
  14. package/dist/{src/config.js → config.js} +151 -44
  15. package/dist/consolidated-quote.d.ts +554 -0
  16. package/dist/consolidated-quote.js +773 -0
  17. package/dist/destination-calldata.d.ts +118 -0
  18. package/dist/destination-calldata.js +152 -0
  19. package/dist/ecosystems.d.ts +217 -0
  20. package/dist/ecosystems.js +288 -0
  21. package/dist/hyperliquid-execution.d.ts +152 -0
  22. package/dist/hyperliquid-execution.js +168 -0
  23. package/dist/image-links.d.ts +117 -0
  24. package/dist/image-links.js +142 -0
  25. package/dist/index.d.ts +20 -0
  26. package/dist/index.js +20 -0
  27. package/dist/lifi-execution-guard.d.ts +199 -0
  28. package/dist/lifi-execution-guard.js +400 -0
  29. package/dist/near-intents-conformance.d.ts +85 -0
  30. package/dist/near-intents-conformance.js +329 -0
  31. package/dist/near-intents-route.d.ts +47 -0
  32. package/dist/near-intents-route.js +265 -0
  33. package/dist/near-intents.d.ts +524 -0
  34. package/dist/near-intents.js +1055 -0
  35. package/dist/origin-deposit.d.ts +326 -0
  36. package/dist/origin-deposit.js +68 -0
  37. package/dist/pre-quote.d.ts +198 -0
  38. package/dist/pre-quote.js +175 -0
  39. package/dist/prices.d.ts +107 -0
  40. package/dist/prices.js +162 -0
  41. package/dist/providers.d.ts +221 -0
  42. package/dist/providers.js +157 -0
  43. package/dist/quote-fees.d.ts +102 -0
  44. package/dist/quote-fees.js +195 -0
  45. package/dist/referral.d.ts +77 -0
  46. package/dist/referral.js +72 -0
  47. package/dist/relay-currencies.d.ts +47 -0
  48. package/dist/relay-currencies.js +89 -0
  49. package/dist/relay-quote.d.ts +301 -0
  50. package/dist/relay-quote.js +983 -0
  51. package/dist/route-steps.d.ts +138 -0
  52. package/dist/route-steps.js +305 -0
  53. package/dist/routing/amounts.d.ts +80 -0
  54. package/dist/routing/amounts.js +120 -0
  55. package/dist/routing/candidate-route.d.ts +43 -0
  56. package/dist/routing/candidate-route.js +45 -0
  57. package/dist/routing/candidate.d.ts +237 -0
  58. package/dist/routing/candidate.js +96 -0
  59. package/dist/routing/carriers/ask-answer.d.ts +58 -0
  60. package/dist/routing/carriers/omnibridge.d.ts +137 -0
  61. package/dist/routing/carriers/omnibridge.js +419 -0
  62. package/dist/routing/carriers/pulsex.d.ts +111 -0
  63. package/dist/routing/carriers/pulsex.js +404 -0
  64. package/dist/routing/carriers/wrap.d.ts +25 -0
  65. package/dist/routing/carriers/wrap.js +115 -0
  66. package/dist/routing/composed-route-quality.d.ts +103 -0
  67. package/dist/routing/composed-route-quality.js +108 -0
  68. package/dist/routing/entry-chain-anchor.d.ts +88 -0
  69. package/dist/routing/entry-chain-anchor.js +55 -0
  70. package/dist/routing/format.d.ts +31 -0
  71. package/dist/routing/format.js +58 -0
  72. package/dist/routing/index.d.ts +34 -0
  73. package/dist/routing/index.js +42 -0
  74. package/dist/routing/producers.d.ts +770 -0
  75. package/dist/routing/producers.js +1564 -0
  76. package/dist/routing/providers.d.ts +26 -0
  77. package/dist/routing/providers.js +38 -0
  78. package/dist/routing/route-data.d.ts +355 -0
  79. package/dist/routing/route-data.js +1 -0
  80. package/dist/routing/search/compose.d.ts +168 -0
  81. package/dist/routing/search/compose.js +98 -0
  82. package/dist/routing/search/graph.d.ts +639 -0
  83. package/dist/routing/search/graph.js +445 -0
  84. package/dist/routing/search/plan-route-data.d.ts +244 -0
  85. package/dist/routing/search/plan-route-data.js +767 -0
  86. package/dist/routing/search/score.d.ts +324 -0
  87. package/dist/routing/search/score.js +400 -0
  88. package/dist/routing/usd-value.d.ts +17 -0
  89. package/dist/routing/usd-value.js +25 -0
  90. package/dist/settings.d.ts +692 -0
  91. package/dist/{src/settings.js → settings.js} +338 -71
  92. package/dist/ton-execution.d.ts +117 -0
  93. package/dist/ton-execution.js +247 -0
  94. package/dist/tron-execution.d.ts +146 -0
  95. package/dist/tron-execution.js +239 -0
  96. package/dist/{src/types.d.ts → types.d.ts} +8 -1
  97. package/dist/types.js +1 -0
  98. package/package.json +210 -23
  99. package/dist/src/abis.d.ts +0 -384
  100. package/dist/src/abis.js +0 -68
  101. package/dist/src/chains.d.ts +0 -6
  102. package/dist/src/chains.js +0 -86
  103. package/dist/src/config.d.ts +0 -136
  104. package/dist/src/image-links.d.ts +0 -41
  105. package/dist/src/image-links.js +0 -45
  106. package/dist/src/index.d.ts +0 -8
  107. package/dist/src/index.js +0 -8
  108. package/dist/src/settings.d.ts +0 -357
  109. package/dist/tsconfig.tsbuildinfo +0 -1
  110. package/src/abis.ts +0 -70
  111. package/src/chain-info.ts +0 -464
  112. package/src/chains.ts +0 -88
  113. package/src/config.ts +0 -480
  114. package/src/fee-type.ts +0 -14
  115. package/src/image-links.ts +0 -61
  116. package/src/index.ts +0 -8
  117. package/src/settings.ts +0 -898
  118. package/src/types.ts +0 -48
  119. package/tsconfig.json +0 -16
  120. /package/dist/{src/fee-type.d.ts → fee-type.d.ts} +0 -0
  121. /package/dist/{src/fee-type.js → fee-type.js} +0 -0
  122. /package/dist/{src/types.js → routing/carriers/ask-answer.js} +0 -0
@@ -1,7 +1,7 @@
1
- import { concatHex, encodeAbiParameters, encodeFunctionData, getAddress, isAddress, zeroAddress } from "viem";
2
- import { isNative, nativeAssetOut, pathway } from "./config.js";
3
- import { FeeType } from "./fee-type.js";
4
- import * as abis from "./abis.js";
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
5
  export const oneEther = 10n ** 18n;
6
6
  export const basisPoints = 10000n;
7
7
  /**
@@ -11,7 +11,7 @@ export const basisPoints = 10000n;
11
11
  * @param assetIn - the asset in to bridge
12
12
  * @returns the bridge pathway
13
13
  */
14
- export const bridgePathway = ({ bridgeKey, isProd, assetIn, }) => (pathway(bridgeKey, isProd, assetIn?.address) ?? null);
14
+ export const bridgePathway = ({ bridgeKey, isProd, assetIn, }) => pathway(bridgeKey, isProd, assetIn?.address) ?? null;
15
15
  /**
16
16
  * Retrieves the amount to bridge from the amount and asset in. If either of them is nil, returns 0n
17
17
  * @param amountIn - the amount to bridge
@@ -126,7 +126,7 @@ export const estimatedTokenNetworkCost = ({ estimatedNativeNetworkCost, priceCor
126
126
  * @param assetIn - the asset in to bridge
127
127
  * @returns the one token int
128
128
  */
129
- export const oneTokenInt = ({ assetIn, }) => {
129
+ export const oneTokenInt = ({ assetIn }) => {
130
130
  if (!assetIn)
131
131
  return 1n;
132
132
  return 10n ** BigInt(assetIn.decimals);
@@ -154,7 +154,11 @@ export const networkSwitchAssetOutAddress = ({ assetOut, unwrap, toChain, }) =>
154
154
  return null;
155
155
  if (!unwrap)
156
156
  return assetOut.address;
157
- return nativeAssetOut[toChain] === assetOut.address ? zeroAddress : 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;
158
162
  };
159
163
  /**
160
164
  * Calculates the desired excess compensation basis points from the path, asset in, asset out, bridge key, fee type, list, and asset out address
@@ -171,7 +175,7 @@ export const desiredExcessCompensationBasisPoints = ({ path, assetIn, assetOut,
171
175
  if (!path?.requiresDelivery) {
172
176
  return 0n;
173
177
  }
174
- return (!assetIn
178
+ return !assetIn
175
179
  ? 0n
176
180
  : feeType === FeeType.PERCENT
177
181
  ? 1000n
@@ -180,36 +184,51 @@ export const desiredExcessCompensationBasisPoints = ({ path, assetIn, assetOut,
180
184
  : list.has(getAddress(assetIn?.address ?? '')) ||
181
185
  list.has(assetOutAddress ? getAddress(assetOutAddress) : '0x')
182
186
  ? 5000n
183
- : 10000n);
187
+ : 10000n;
184
188
  };
185
189
  /**
186
- * Calculates the estimated cost from the should deliver, amount after bridge fee, percent fee,
187
- * estimated token network cost, reasonable percent on top of gas fee, and fee type
188
- * @param shouldDeliver - whether to deliver the costs
189
- * @param amountAfterBridgeFee - the amount after bridge fee
190
- * @param percentFee - the percent fee
191
- * @param estimatedTokenNetworkCost - the estimated token network cost
192
- * @param reasonablePercentOnTopOfGasFee - the reasonable percent on top of gas fee
193
- * @param feeType - the fee type
194
- * @returns the estimated cost
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
195
213
  */
196
- export const estimatedCost = ({ shouldDeliver, amountAfterBridgeFee, percentFee, estimatedTokenNetworkCost, reasonablePercentOnTopOfGasFee, feeType, }) => {
197
- if (!shouldDeliver) {
198
- return null;
214
+ export const deliveryFee = ({ shouldDeliver, routeOffersDelivery, feeType, amountAfterBridgeFee, percentFee, fixedFee, limit, }) => {
215
+ if (!shouldDeliver || !routeOffersDelivery) {
216
+ return { status: 'not-requested' };
199
217
  }
200
- if (feeType === FeeType.PERCENT) {
201
- if (amountAfterBridgeFee === null)
202
- return null;
203
- return (amountAfterBridgeFee * (percentFee ?? 0n)) / oneEther;
218
+ if (feeType === FeeType.FIXED) {
219
+ return { status: 'requested', amount: fixedFee, rate: null };
204
220
  }
205
- // gas tip
206
- if (!estimatedTokenNetworkCost) {
207
- return null;
221
+ if (feeType === FeeType.GAS_TIP) {
222
+ return { status: 'requested', amount: limit, rate: null };
208
223
  }
209
- if (!reasonablePercentOnTopOfGasFee) {
210
- return null;
224
+ if (amountAfterBridgeFee === null || percentFee === null) {
225
+ return { status: 'requested', amount: null, rate: percentFee };
211
226
  }
212
- return estimatedTokenNetworkCost * (reasonablePercentOnTopOfGasFee + oneEther);
227
+ return {
228
+ status: 'requested',
229
+ amount: (amountAfterBridgeFee * percentFee) / oneEther,
230
+ rate: percentFee,
231
+ };
213
232
  };
214
233
  /**
215
234
  * Calculates the reasonable fixed fee from the estimated token network cost and desired excess compensation basis points
@@ -221,8 +240,7 @@ export const reasonableFixedFee = ({ estimatedTokenNetworkCost, desiredExcessCom
221
240
  if (!estimatedTokenNetworkCost) {
222
241
  return null;
223
242
  }
224
- return ((estimatedTokenNetworkCost * (25000n + desiredExcessCompensationBasisPoints)) /
225
- basisPoints);
243
+ return ((estimatedTokenNetworkCost * (25000n + desiredExcessCompensationBasisPoints)) / basisPoints);
226
244
  };
227
245
  /**
228
246
  * Calculates the reasonable percent on gas limit from the gas tip fee and estimated token network cost
@@ -231,8 +249,7 @@ export const reasonableFixedFee = ({ estimatedTokenNetworkCost, desiredExcessCom
231
249
  * @returns the reasonable percent on gas limit
232
250
  */
233
251
  export const reasonablePercentOnGasLimit = ({ gasTipFee, estimatedTokenNetworkCost, }) => {
234
- return ((25000n *
235
- ((oneEther + (gasTipFee ?? 0n)) * (estimatedTokenNetworkCost ?? 0n))) /
252
+ return ((25000n * ((oneEther + (gasTipFee ?? 0n)) * (estimatedTokenNetworkCost ?? 0n))) /
236
253
  (oneEther * basisPoints));
237
254
  };
238
255
  /**
@@ -306,8 +323,7 @@ export const feeDirectorStructEncoded = ({ recipient, bridgePathway, feeType, pr
306
323
  let multiplier = 0n;
307
324
  if (feeType === FeeType.GAS_TIP && priceCorrective > 0n) {
308
325
  multiplier =
309
- ((oneEther + (gasTipFee ?? 0n)) * 10n ** BigInt(assetOut.decimals)) /
310
- priceCorrective;
326
+ ((oneEther + (gasTipFee ?? 0n)) * 10n ** BigInt(assetOut.decimals)) / priceCorrective;
311
327
  }
312
328
  else if (feeType === FeeType.PERCENT) {
313
329
  multiplier = percentFee ?? 0n;
@@ -340,15 +356,12 @@ export const destinationDataParam = ({ recipient, bridgePathway, assetIn, destin
340
356
  return null;
341
357
  }
342
358
  if (!destinationRouter) {
343
- console.log('no destination router');
344
359
  return null;
345
360
  }
346
361
  if (bridgePathway.requiresDelivery && !feeDirectorStructEncoded) {
347
- console.log('no fee director struct encoded');
348
362
  return null;
349
363
  }
350
364
  if (!assetLink) {
351
- console.log('no asset link');
352
365
  return null;
353
366
  }
354
367
  if (bridgePathway.requiresDelivery) {
@@ -375,7 +388,7 @@ export const destinationDataParam = ({ recipient, bridgePathway, assetIn, destin
375
388
  * @returns the reasonable percent on top of gas fee
376
389
  */
377
390
  export const reasonablePercentOnTopOfGasFee = ({ desiredExcessCompensationBasisPoints, }) => {
378
- return (desiredExcessCompensationBasisPoints * (oneEther / basisPoints));
391
+ return desiredExcessCompensationBasisPoints * (oneEther / basisPoints);
379
392
  };
380
393
  /**
381
394
  * Calculates the desired compensation ratio from the desired excess compensation basis points
@@ -436,8 +449,279 @@ export const isUndercompensated = ({ estimatedTokenNetworkCost, feeType, limit,
436
449
  // Undercompensated if desired compensation exceeds the limit
437
450
  return desiredCompensationForNetworkCost > limit;
438
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
+ };
439
721
  /**
440
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
441
725
  * @param bridgePathway - the bridge pathway to use
442
726
  * @param assetIn - the asset in to bridge
443
727
  * @param recipient - the recipient of the bridge
@@ -447,35 +731,32 @@ export const isUndercompensated = ({ estimatedTokenNetworkCost, feeType, limit,
447
731
  * @param amountToBridge - the amount to bridge
448
732
  * @param feeDirectorStructEncoded - the fee director struct encoded to use
449
733
  * @param shouldDeliver - whether to deliver the tokens to the recipient
450
- * @returns the transaction inputs
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
451
736
  */
452
- export const transactionInputs = ({ bridgePathway, assetIn, recipient, account, assetLink, destinationDataParam, amountToBridge, feeDirectorStructEncoded, shouldDeliver, }) => {
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
+ }
453
743
  if (!bridgePathway) {
454
- console.log('no bridge pathway');
455
744
  return null;
456
745
  }
457
746
  // check that recipient is valid
458
- if (!recipient ||
459
- !isAddress(recipient) ||
460
- account === zeroAddress) {
461
- console.log('no recipient');
747
+ if (!recipient || !isAddress(recipient) || recipient === zeroAddress) {
462
748
  return null;
463
749
  }
464
750
  // check that wallet account is valid
465
- if (!account ||
466
- !isAddress(account) ||
467
- account === zeroAddress) {
468
- console.log('no account');
751
+ if (!account || !isAddress(account) || account === zeroAddress) {
469
752
  return null;
470
753
  }
471
754
  // check that asset in is valid
472
755
  if (!assetIn) {
473
- console.log('no asset in');
474
756
  return null;
475
757
  }
476
758
  // check that asset link is valid
477
759
  if (!assetLink) {
478
- console.log('no asset link');
479
760
  return null;
480
761
  }
481
762
  let value = 0n;
@@ -500,12 +781,7 @@ export const transactionInputs = ({ bridgePathway, assetIn, recipient, account,
500
781
  ? encodeFunctionData({
501
782
  abi: abis.erc677ExtraInput,
502
783
  functionName: 'transferAndCall',
503
- args: [
504
- bridgePathway.from,
505
- amountToBridge,
506
- destinationDataParam,
507
- account,
508
- ],
784
+ args: [bridgePathway.from, amountToBridge, destinationDataParam, account],
509
785
  })
510
786
  : encodeFunctionData({
511
787
  abi: abis.erc677,
@@ -526,11 +802,7 @@ export const transactionInputs = ({ bridgePathway, assetIn, recipient, account,
526
802
  ? encodeFunctionData({
527
803
  abi: abis.nativeRouterExtraInput,
528
804
  functionName: 'relayTokensAndCall',
529
- args: [
530
- bridgePathway.destinationRouter,
531
- feeDirectorStructEncoded,
532
- account,
533
- ],
805
+ args: [bridgePathway.destinationRouter, feeDirectorStructEncoded, account],
534
806
  })
535
807
  : encodeFunctionData({
536
808
  abi: abis.nativeRouter,
@@ -588,12 +860,7 @@ export const transactionInputs = ({ bridgePathway, assetIn, recipient, account,
588
860
  ? encodeFunctionData({
589
861
  abi: abis.inputBridgeExtraInput,
590
862
  functionName: 'relayTokens',
591
- args: [
592
- assetIn.address,
593
- recipient,
594
- amountToBridge,
595
- account,
596
- ],
863
+ args: [assetIn.address, recipient, amountToBridge, account],
597
864
  })
598
865
  : encodeFunctionData({
599
866
  abi: abis.inputBridge,
@@ -0,0 +1,117 @@
1
+ import type { TonDeposit } from './origin-deposit.js';
2
+ /**
3
+ * Executing a deposit on The Open Network, as pure data.
4
+ *
5
+ * Relay describes a deposit as a list of internal messages carrying `to`,
6
+ * `value`, `body` and a `bounce` flag. A connected wallet, however, is driven
7
+ * through the TON Connect protocol, whose request shape is different in one way
8
+ * that silently loses money:
9
+ *
10
+ * **TON Connect has no per-message `bounce` field.** A wallet derives whether a
11
+ * message bounces from the FORM OF THE ADDRESS — the bounceable `EQ…` prefix or
12
+ * the non-bounceable `UQ…` one. Passing Relay's address through unchanged while
13
+ * dropping its `bounce: false` therefore sends a deposit to the bounceable form,
14
+ * and the depository bounces the funds straight back to the sender. The transfer
15
+ * looks like it succeeded and the route never fills.
16
+ *
17
+ * {@link buildTonConnectRequest} folds `bounce` into the address so the flag
18
+ * cannot be dropped on the way to a wallet.
19
+ *
20
+ * Kept free of transport and of clock reads so it is fully testable: nothing
21
+ * here signs, fetches, or calls `Date.now`. The caller supplies the deadline and
22
+ * performs the request.
23
+ *
24
+ * The rules encoded here are those of Relay's own wallet adapter, which is the
25
+ * only reference implementation of this mapping.
26
+ */
27
+ /**
28
+ * The TON Connect network identifier for mainnet. Relay bridges only mainnet, so
29
+ * a request is always tagged with this.
30
+ */
31
+ export declare const tonConnectMainnetNetwork = "-239";
32
+ /** How long a request stays valid by default, in seconds, matching Relay's adapter. */
33
+ export declare const tonConnectDefaultValidForSeconds = 300;
34
+ /** An address split into the parts that survive a change of encoding. */
35
+ type ParsedTonAddress = {
36
+ workchain: number;
37
+ hash: Uint8Array;
38
+ /** Preserved rather than assumed: re-encoding a testnet address as mainnet would misdirect it. */
39
+ testOnly: boolean;
40
+ };
41
+ /**
42
+ * Parses either address form The Open Network uses.
43
+ *
44
+ * @param value - a friendly (`EQ…`/`UQ…`) or raw (`0:…`) address
45
+ * @returns the address parts, or null when it is neither form or fails its checksum
46
+ */
47
+ export declare const parseTonAddress: (value: string) => ParsedTonAddress | null;
48
+ /**
49
+ * Re-encodes an address into the friendly form matching a bounce setting.
50
+ *
51
+ * This is the whole point of the module: the bounce flag has nowhere else to
52
+ * live in a TON Connect request, so it has to be carried by the address itself.
53
+ *
54
+ * @param address - the address in either form
55
+ * @param options.bounceable - whether the message may bounce back to the sender
56
+ * @returns the friendly address, or null when the input cannot be parsed
57
+ */
58
+ export declare const encodeTonAddress: (address: string, { bounceable }: {
59
+ bounceable: boolean;
60
+ }) => string | null;
61
+ /** One message of a TON Connect request. Note `address`/`amount`, not `to`/`value`. */
62
+ export type TonConnectMessage = {
63
+ address: string;
64
+ /** Attached amount in nanotons, as a decimal string. */
65
+ amount: string;
66
+ /** Base64 bag-of-cells body, omitted when the message carries none. */
67
+ payload?: string;
68
+ };
69
+ /** The request handed to a wallet's TON Connect `sendTransaction` call. */
70
+ export type TonConnectRequest = {
71
+ /** Unix seconds after which a wallet must refuse the request. */
72
+ validUntil: number;
73
+ network: string;
74
+ /** The sending account, when the deposit names one. */
75
+ from?: string;
76
+ messages: TonConnectMessage[];
77
+ };
78
+ /**
79
+ * Builds the TON Connect request for a deposit.
80
+ *
81
+ * @param deposit - the parsed origin deposit
82
+ * @param options.validUntil - unix seconds after which the request expires.
83
+ * Supplied by the caller rather than read from a clock here so the transform
84
+ * stays pure; {@link tonConnectDefaultValidForSeconds} is the usual offset.
85
+ * @returns the request, or null when the deposit carries no messages or any
86
+ * destination address cannot be re-encoded — never a request whose bounce
87
+ * setting has been silently dropped
88
+ */
89
+ export declare const buildTonConnectRequest: (deposit: TonDeposit, { validUntil }: {
90
+ validUntil: number;
91
+ }) => TonConnectRequest | null;
92
+ /**
93
+ * Derives the transaction identifier from a wallet's `sendTransaction` response.
94
+ *
95
+ * A wallet answers with a `boc`: the base64 bag-of-cells of the signed
96
+ * external-in message it broadcast — NOT the resulting transaction, and NOT a
97
+ * hash. The identifier explorers and indexers (toncenter, TonAPI) resolve a
98
+ * transaction by is the hash of that external message, but it must be the
99
+ * NORMALIZED hash of TEP-467, not the hash of the bytes the wallet happened to
100
+ * send.
101
+ *
102
+ * The distinction is the whole point, and getting it wrong loses a resume record
103
+ * for real funds. An external-in message can be serialized several equivalent
104
+ * ways — a wallet may or may not include its own `src`, may set a non-zero
105
+ * `import_fee`, may attach a `stateInit`, may store the body inline or as a
106
+ * reference. The raw cell hash changes with each of those wallet-specific
107
+ * choices, so it frequently does NOT match what an indexer keyed the same
108
+ * transaction under. TEP-467 rebuilds the message with those variable fields
109
+ * stripped (`src` to none, `import_fee` to zero, `init` dropped, body forced to a
110
+ * reference) so the hash is stable across wallets and matches the indexers.
111
+ *
112
+ * @param boc - the wallet's base64 `boc` field from `sendTransaction`
113
+ * @returns the 64-character hex normalized hash, or null when the input is not a
114
+ * parseable external-in message
115
+ */
116
+ export declare const tonExternalMessageHash: (boc: string) => string | null;
117
+ export {};