@pushchain/core 6.0.15 → 6.0.17

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.
@@ -20,6 +20,9 @@ const svm_client_1 = require("../../vm-client/svm-client");
20
20
  const context_2 = require("./context");
21
21
  const gas_calculator_1 = require("./gas-calculator");
22
22
  const gateway_client_1 = require("./gateway-client");
23
+ const gas_usd_sizer_1 = require("./gas-usd-sizer");
24
+ const price_fetch_1 = require("../../price-fetch/price-fetch");
25
+ const utils_1 = require("../../utils");
23
26
  const helpers_1 = require("./helpers");
24
27
  const payload_builder_1 = require("./payload-builder");
25
28
  const uea_manager_1 = require("./uea-manager");
@@ -100,7 +103,11 @@ function executeFundsOnlyEvm(ctx, execute, eventBuffer, transformFn, chain, rpcU
100
103
  const gasPrice = yield ctx.pushClient.getGasPrice();
101
104
  const requiredGasFee = gasEstimate * gasPrice;
102
105
  const requiredFunds = requiredGasFee + ((_c = execute.value) !== null && _c !== void 0 ? _c : BigInt(0));
103
- let nativeAmount = yield (0, gas_calculator_1.calculateNativeAmountForDeposit)(ctx, chain, requiredFunds, ueaBalanceForGas);
106
+ let nativeAmount = yield (0, gas_calculator_1.calculateNativeAmountForDeposit)(ctx, chain, requiredFunds, ueaBalanceForGas,
107
+ // Defer the 103-03-04 ("Prepaid Deposit Estimated") hook — we emit the full
108
+ // 103-03 sizing sequence below, after 103-01/103-02 (UEA resolution), to
109
+ // match the funds-payload path ordering.
110
+ { emitDepositEstimated: false });
104
111
  // For EVM fee-locking, fixed-rate PC→USD sizing can underfund when the
105
112
  // live WPC/native pool prices PC above the SDK's fallback price. Size the
106
113
  // native deposit against the same Push Chain pool used by the gateway.
@@ -121,6 +128,41 @@ function executeFundsOnlyEvm(ctx, execute, eventBuffer, transformFn, chain, rpcU
121
128
  (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_103_01);
122
129
  (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_103_02, ueaAddress, deployed);
123
130
  (0, context_2.printLog)(ctx, `UEA resolved: ${ueaAddress}, deployed: ${deployed}`);
131
+ // SDK 5.2 R1 deposit-sizing progress (parent + Case A/B/C + terminal),
132
+ // emitted here — after UEA resolution — to match the funds-payload path's
133
+ // ordering. The terminal 103-03-04 hook was suppressed inside
134
+ // calculateNativeAmountForDeposit above so it lands in this position instead
135
+ // of before 103-01/103-02. Purely cosmetic: a pool/price read failure here
136
+ // must never block the bridge, so we fall back to the terminal hook alone.
137
+ try {
138
+ const r1Sizing = yield (0, gas_usd_sizer_1.sizeR1PushGas)(ctx, {
139
+ pushGasFeeWei: requiredGasFee,
140
+ originChain: chain,
141
+ });
142
+ const nativeTokenUsdPrice = yield new price_fetch_1.PriceFetch(ctx.rpcUrls).getPrice(chain);
143
+ const nativeDecimals = chain_1.CHAIN_INFO[chain].vm === enums_1.VM.SVM ? 9 : 18;
144
+ const oneNativeUnit = utils_1.Utils.helpers.parseUnits('1', nativeDecimals);
145
+ const plannedPCDeposit = nativeTokenUsdPrice === BigInt(0)
146
+ ? BigInt(0)
147
+ : (r1Sizing.paddedDepositUsd * oneNativeUnit +
148
+ (nativeTokenUsdPrice - BigInt(1))) /
149
+ nativeTokenUsdPrice +
150
+ BigInt(1);
151
+ const extraDepositPC = plannedPCDeposit > requiredGasFee
152
+ ? plannedPCDeposit - requiredGasFee
153
+ : BigInt(0);
154
+ (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_103_03, chain, r1Sizing.paddedDepositUsd);
155
+ const r1SizingHookId = {
156
+ A: progress_hook_types_1.PROGRESS_HOOK.SEND_TX_103_03_01,
157
+ B: progress_hook_types_1.PROGRESS_HOOK.SEND_TX_103_03_02,
158
+ C: progress_hook_types_1.PROGRESS_HOOK.SEND_TX_103_03_03,
159
+ }[r1Sizing.category];
160
+ (0, context_2.fireProgressHook)(ctx, r1SizingHookId, chain, requiredGasFee, extraDepositPC, r1Sizing.paddedDepositUsd);
161
+ (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_103_03_04, nativeAmount, r1Sizing.paddedDepositUsd, chain);
162
+ }
163
+ catch (_d) {
164
+ (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_103_03_04, nativeAmount, BigInt(0), chain);
165
+ }
124
166
  (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_106_01, execute.funds.amount, execute.funds.token.decimals, symbol);
125
167
  // Approve gateway to pull tokens if ERC-20
126
168
  if (execute.funds.token.mechanism === 'approve') {
@@ -10,4 +10,5 @@ import { ProgressEvent } from '../../progress-hook/progress-hook.types';
10
10
  import type { ExecuteParams, UniversalTxResponse } from '../orchestrator.types';
11
11
  import type { OrchestratorContext } from './context';
12
12
  import { type ResponseBuilderCallbacks } from './response-builder';
13
+ export declare function resolveRequiredFunds(requiredGasFee: bigint, executeValue: bigint, requiredFundsOverride?: bigint): bigint;
13
14
  export declare function executeStandardPayload(ctx: OrchestratorContext, execute: ExecuteParams, eventBuffer: ProgressEvent[], getResponseCallbacks: () => ResponseBuilderCallbacks): Promise<UniversalTxResponse>;
@@ -8,6 +8,7 @@
8
8
  * - Signed verification path for deployed UEA with funds
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.resolveRequiredFunds = resolveRequiredFunds;
11
12
  exports.executeStandardPayload = executeStandardPayload;
12
13
  const tslib_1 = require("tslib");
13
14
  const bs58_1 = require("../../internal/bs58");
@@ -76,6 +77,12 @@ function encodePayloadForOriginIfNeeded(ctx, payload, feeLockingRequired) {
76
77
  }
77
78
  return encodePayloadForOrigin(ctx, payload);
78
79
  }
80
+ function resolveRequiredFunds(requiredGasFee, executeValue, requiredFundsOverride) {
81
+ const baseRequiredFunds = requiredGasFee + executeValue;
82
+ return requiredFundsOverride && requiredFundsOverride > baseRequiredFunds
83
+ ? requiredFundsOverride
84
+ : baseRequiredFunds;
85
+ }
79
86
  function executeStandardPayload(ctx, execute, eventBuffer, getResponseCallbacks) {
80
87
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
81
88
  var _a, _b, _c;
@@ -122,7 +129,7 @@ function executeStandardPayload(ctx, execute, eventBuffer, getResponseCallbacks)
122
129
  const gasEstimate = execute.gasLimit || BigInt(1e7);
123
130
  const gasPrice = yield ctx.pushClient.getGasPrice();
124
131
  const requiredGasFee = gasEstimate * gasPrice;
125
- const requiredFunds = requiredGasFee + execute.value;
132
+ const requiredFunds = resolveRequiredFunds(requiredGasFee, execute.value, execute._requiredFundsOverride);
126
133
  // Fetch UEA Details (or use pre-fetched status if available)
127
134
  const UEA = (0, uea_manager_1.computeUEAOffchain)(ctx);
128
135
  let isUEADeployed;
@@ -25,6 +25,23 @@ export declare function queryOutboundGasFee(ctx: OrchestratorContext, prc20Token
25
25
  * Falls back to a percentage of accountBalance if any on-chain query fails.
26
26
  */
27
27
  export declare function estimateNativeValueForSwap(ctx: OrchestratorContext, universalCoreAddress: `0x${string}`, gasToken: `0x${string}`, gasFee: bigint, accountBalance: bigint): Promise<bigint>;
28
+ /**
29
+ * Bound the buffered swap estimate by the UEA's affordable PC.
30
+ *
31
+ * `result` is the 2.2x-buffered WPC needed for the gas swap; `minNeeded` is the
32
+ * un-buffered minimum the WPC->gasToken `exactOutput` swap actually consumes.
33
+ *
34
+ * Reducing the buffer to fit a tight balance is fine - but ONLY while the
35
+ * affordable amount (`accountBalance - reserve`) still covers `minNeeded`.
36
+ * Below that floor the swap cannot complete and reverts on-chain with Uniswap
37
+ * `STF`. The previous logic returned `accountBalance - reserve` unconditionally,
38
+ * which (a) under-funded `msg.value` into a doomed swap and (b) made the native
39
+ * preflight self-satisfying (`balance >= (balance - reserve) + reserve`), so the
40
+ * shortfall went undetected. When the balance can't cover the floor we now
41
+ * return the full `result` so the caller's preflight reports the real shortfall
42
+ * instead of emitting a cryptic STF.
43
+ */
44
+ export declare function capSwapEstimate(result: bigint, accountBalance: bigint, reserve: bigint, minNeeded?: bigint): bigint;
28
45
  export declare function queryRescueGasFee(ctx: OrchestratorContext, prc20Token: `0x${string}`, destinationChain?: CHAIN): Promise<{
29
46
  gasToken: `0x${string}`;
30
47
  gasFee: bigint;
@@ -33,7 +50,9 @@ export declare function queryRescueGasFee(ctx: OrchestratorContext, prc20Token:
33
50
  nativeValueForGas: bigint;
34
51
  sizing?: GasSizingDecision;
35
52
  }>;
36
- export declare function calculateNativeAmountForDeposit(ctx: OrchestratorContext, chain: CHAIN, requiredFunds: bigint, ueaBalance: bigint): Promise<bigint>;
53
+ export declare function calculateNativeAmountForDeposit(ctx: OrchestratorContext, chain: CHAIN, requiredFunds: bigint, ueaBalance: bigint, opts?: {
54
+ emitDepositEstimated?: boolean;
55
+ }): Promise<bigint>;
37
56
  export declare function calculateGasAmountFromAmountOutMinETH(ctx: OrchestratorContext, gasTokenAddress: `0x${string}`, amountOutMinETH: bigint | string, quoteExactOutputFn: (amountOut: bigint, opts: {
38
57
  from: PayableToken | undefined;
39
58
  to: MoveableToken | undefined;
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
7
7
  exports.ensureErc20Allowance = ensureErc20Allowance;
8
8
  exports.queryOutboundGasFee = queryOutboundGasFee;
9
9
  exports.estimateNativeValueForSwap = estimateNativeValueForSwap;
10
+ exports.capSwapEstimate = capSwapEstimate;
10
11
  exports.queryRescueGasFee = queryRescueGasFee;
11
12
  exports.calculateNativeAmountForDeposit = calculateNativeAmountForDeposit;
12
13
  exports.calculateGasAmountFromAmountOutMinETH = calculateGasAmountFromAmountOutMinETH;
@@ -309,7 +310,7 @@ function estimateNativeValueForSwap(ctx, universalCoreAddress, gasToken, gasFee,
309
310
  const result = (exactWpcNeeded * SWAP_BUFFER_NUM) / SWAP_BUFFER_DEN;
310
311
  (0, context_1.printLog)(ctx, `estimateNativeValueForSwap — exactOutput quoter=${quoterAddress}, ` +
311
312
  `wpcNeeded=${exactWpcNeeded}, withBuffer(2.2x)=${result}`);
312
- return capSwapEstimate(result, accountBalance, GAS_RESERVE);
313
+ return capSwapEstimate(result, accountBalance, GAS_RESERVE, exactWpcNeeded);
313
314
  }
314
315
  catch (quoteErr) {
315
316
  (0, context_1.printLog)(ctx, `estimateNativeValueForSwap — exactOutput quote failed: ${quoteErr instanceof Error ? quoteErr.message : String(quoteErr)}, falling back to slot0 price`);
@@ -355,7 +356,7 @@ function estimateNativeValueForSwap(ctx, universalCoreAddress, gasToken, gasFee,
355
356
  const result = (wpcNeeded * SWAP_BUFFER_NUM) / SWAP_BUFFER_DEN;
356
357
  (0, context_1.printLog)(ctx, `estimateNativeValueForSwap — pool=${poolAddress}, sqrtPriceX96=${sqrtPriceX96}, ` +
357
358
  `wpcNeeded=${wpcNeeded}, withBuffer(2.2x)=${result}`);
358
- return capSwapEstimate(result, accountBalance, GAS_RESERVE);
359
+ return capSwapEstimate(result, accountBalance, GAS_RESERVE, wpcNeeded);
359
360
  }
360
361
  catch (err) {
361
362
  (0, context_1.printLog)(ctx, `estimateNativeValueForSwap — failed: ${err instanceof Error ? err.message : String(err)}, using balance fallback`);
@@ -363,14 +364,30 @@ function estimateNativeValueForSwap(ctx, universalCoreAddress, gasToken, gasFee,
363
364
  }
364
365
  });
365
366
  }
366
- function capSwapEstimate(result, accountBalance, reserve) {
367
+ /**
368
+ * Bound the buffered swap estimate by the UEA's affordable PC.
369
+ *
370
+ * `result` is the 2.2x-buffered WPC needed for the gas swap; `minNeeded` is the
371
+ * un-buffered minimum the WPC->gasToken `exactOutput` swap actually consumes.
372
+ *
373
+ * Reducing the buffer to fit a tight balance is fine - but ONLY while the
374
+ * affordable amount (`accountBalance - reserve`) still covers `minNeeded`.
375
+ * Below that floor the swap cannot complete and reverts on-chain with Uniswap
376
+ * `STF`. The previous logic returned `accountBalance - reserve` unconditionally,
377
+ * which (a) under-funded `msg.value` into a doomed swap and (b) made the native
378
+ * preflight self-satisfying (`balance >= (balance - reserve) + reserve`), so the
379
+ * shortfall went undetected. When the balance can't cover the floor we now
380
+ * return the full `result` so the caller's preflight reports the real shortfall
381
+ * instead of emitting a cryptic STF.
382
+ */
383
+ function capSwapEstimate(result, accountBalance, reserve, minNeeded = BigInt(0)) {
367
384
  if (accountBalance > result + reserve) {
368
385
  return result;
369
386
  }
370
- if (accountBalance > reserve) {
387
+ if (accountBalance > reserve && accountBalance - reserve >= minNeeded) {
371
388
  return accountBalance - reserve;
372
389
  }
373
- return result; // let caller decide if balance is too low
390
+ return result; // balance can't cover the swap floor - surface true need to preflight
374
391
  }
375
392
  function balanceFallback(balance, divisor, reserve) {
376
393
  const fraction = balance / divisor;
@@ -449,7 +466,12 @@ function queryRescueGasFee(ctx, prc20Token, destinationChain) {
449
466
  // ============================================================================
450
467
  // Native Amount Calculation for Deposit
451
468
  // ============================================================================
452
- function calculateNativeAmountForDeposit(ctx, chain, requiredFunds, ueaBalance) {
469
+ function calculateNativeAmountForDeposit(ctx, chain, requiredFunds, ueaBalance,
470
+ // When false, suppress the SEND-TX-103-03-04 ("Prepaid Deposit Estimated")
471
+ // progress hook so the caller can emit the full 103-03 sizing sequence
472
+ // (parent + Case A/B/C + terminal) AFTER 103-01/103-02, matching the
473
+ // funds-payload path ordering. The deposit math is unaffected.
474
+ opts) {
453
475
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
454
476
  var _a, _b;
455
477
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_102_01);
@@ -497,7 +519,9 @@ function calculateNativeAmountForDeposit(ctx, chain, requiredFunds, ueaBalance)
497
519
  let nativeAmount = (depositUsd * oneNativeUnit + (nativeTokenUsdPrice - BigInt(1))) /
498
520
  nativeTokenUsdPrice;
499
521
  nativeAmount = nativeAmount + BigInt(1);
500
- (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_103_03_04, nativeAmount, depositUsd, chain);
522
+ if ((opts === null || opts === void 0 ? void 0 : opts.emitDepositEstimated) !== false) {
523
+ (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_103_03_04, nativeAmount, depositUsd, chain);
524
+ }
501
525
  return nativeAmount;
502
526
  });
503
527
  }
@@ -68,6 +68,7 @@ export declare function waitForAllOutboundTxsV2(ctx: OrchestratorContext, pushCh
68
68
  pollingIntervalMs: number;
69
69
  timeout: number;
70
70
  _resolvedSubTxId?: string;
71
+ _outboundIndexByHop?: Map<number, number>;
71
72
  progressHook?: (event: {
72
73
  hopIndex: number;
73
74
  route: TransactionRouteType;
@@ -180,7 +180,8 @@ function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
180
180
  const pollStart = Date.now();
181
181
  (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} | Elapsed: ${pollStart - startTime}ms / ${timeout}ms`);
182
182
  if (!cachedUniversalSubTxId) {
183
- cachedUniversalSubTxId = (_a = (yield (0, outbound_tracker_1.extractUniversalSubTxIdFromTx)(ctx, pushChainTxHash))) !== null && _a !== void 0 ? _a : undefined;
183
+ cachedUniversalSubTxId =
184
+ (_a = (yield (0, outbound_tracker_1.extractUniversalSubTxIdFromTx)(ctx, pushChainTxHash))) !== null && _a !== void 0 ? _a : undefined;
184
185
  if (!cachedUniversalSubTxId) {
185
186
  cachedUniversalSubTxId = (0, outbound_tracker_1.computeUniversalTxId)(ctx.pushNetwork, pushChainTxHash);
186
187
  }
@@ -228,13 +229,15 @@ function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
228
229
  let matchingOutboundIndex = 0;
229
230
  for (const ob of outbounds) {
230
231
  // If a destination chain filter is set, skip outbound entries that don't match.
231
- if (_expectedDestinationChain && ob.destinationChain !== _expectedDestinationChain) {
232
+ if (_expectedDestinationChain &&
233
+ ob.destinationChain !== _expectedDestinationChain) {
232
234
  (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} | outbound chain '${ob.destinationChain}' does not match expected '${_expectedDestinationChain}', skipping`);
233
235
  continue;
234
236
  }
235
237
  const currentOutboundIndex = matchingOutboundIndex;
236
238
  matchingOutboundIndex += 1;
237
- if (_outboundIndex !== undefined && currentOutboundIndex !== _outboundIndex) {
239
+ if (_outboundIndex !== undefined &&
240
+ currentOutboundIndex !== _outboundIndex) {
238
241
  (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} | outbound index ${currentOutboundIndex} for '${ob.destinationChain}' does not match expected index ${_outboundIndex}, skipping`);
239
242
  continue;
240
243
  }
@@ -262,7 +265,10 @@ function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
262
265
  const verdict = yield verifyExternalEvmReceipt(ctx, chain, ob.observedTx.txHash);
263
266
  if (verdict === 'reverted') {
264
267
  (0, context_1.printLog)(ctx, `[waitForOutboundTx] Source-chain RPC reports REVERTED for ${ob.destinationChain} tx ${ob.observedTx.txHash} (cosmos still ${(_o = types_1.UniversalTxStatus[statusNum]) !== null && _o !== void 0 ? _o : statusNum})`);
265
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'failed', elapsed: Date.now() - startTime });
268
+ progressHook === null || progressHook === void 0 ? void 0 : progressHook({
269
+ status: 'failed',
270
+ elapsed: Date.now() - startTime,
271
+ });
266
272
  throw new OutboundFailedError(`Outbound to ${ob.destinationChain} reverted on source-chain RPC (tx: ${ob.observedTx.txHash}). Push Chain TX: ${pushChainTxHash}.`, pushChainTxHash, ob.destinationChain, ob.observedTx.txHash);
267
273
  }
268
274
  if (verdict !== 'success') {
@@ -277,12 +283,19 @@ function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
277
283
  const displayTxHash = (_s = (0, helpers_1.toExternalTxHashDisplay)(chain, ob.observedTx.txHash)) !== null && _s !== void 0 ? _s : ob.observedTx.txHash;
278
284
  let explorerUrl = '';
279
285
  if (isSvm) {
280
- const cluster = chain === enums_1.CHAIN.SOLANA_DEVNET ? '?cluster=devnet'
281
- : chain === enums_1.CHAIN.SOLANA_TESTNET ? '?cluster=testnet' : '';
282
- explorerUrl = explorerBaseUrl ? `${explorerBaseUrl}/tx/${displayTxHash}${cluster}` : '';
286
+ const cluster = chain === enums_1.CHAIN.SOLANA_DEVNET
287
+ ? '?cluster=devnet'
288
+ : chain === enums_1.CHAIN.SOLANA_TESTNET
289
+ ? '?cluster=testnet'
290
+ : '';
291
+ explorerUrl = explorerBaseUrl
292
+ ? `${explorerBaseUrl}/tx/${displayTxHash}${cluster}`
293
+ : '';
283
294
  }
284
295
  else {
285
- explorerUrl = explorerBaseUrl ? `${explorerBaseUrl}/tx/${displayTxHash}` : '';
296
+ explorerUrl = explorerBaseUrl
297
+ ? `${explorerBaseUrl}/tx/${displayTxHash}`
298
+ : '';
286
299
  }
287
300
  // OutboundTxDetails carries the raw `0x`-hex form so internal
288
301
  // consumers (R3 inbound tracker in response-builder.ts:786 →
@@ -298,7 +311,10 @@ function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
298
311
  assetAddr: ob.externalAssetAddr,
299
312
  };
300
313
  (0, context_1.printLog)(ctx, `[waitForOutboundTx] FOUND on poll #${pollCount} | elapsed: ${Date.now() - startTime}ms | externalTxHash: ${displayTxHash}`);
301
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'found', elapsed: Date.now() - startTime });
314
+ progressHook === null || progressHook === void 0 ? void 0 : progressHook({
315
+ status: 'found',
316
+ elapsed: Date.now() - startTime,
317
+ });
302
318
  return details;
303
319
  }
304
320
  }
@@ -333,18 +349,24 @@ function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
333
349
  */
334
350
  function waitForAllOutboundTxsV2(ctx, pushChainTxHash, outboundHops, options) {
335
351
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
336
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
337
- const { initialWaitMs, pollingIntervalMs, timeout, progressHook, _resolvedSubTxId, } = options;
352
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
353
+ const { initialWaitMs, pollingIntervalMs, timeout, progressHook, _resolvedSubTxId, _outboundIndexByHop, } = options;
338
354
  const startTime = Date.now();
339
- // Build a map: CAIP-2 namespace -> hop(s) for matching outbound entries
355
+ // Build maps for matching outbound entries. When the cascade composer merged
356
+ // multiple logical hops into one outbound request, `_outboundIndexByHop` maps
357
+ // those hops to the same outbound ordinal.
340
358
  const chainToHops = new Map();
341
359
  for (const hop of outboundHops) {
342
360
  const chainInfo = chain_1.CHAIN_INFO[hop.executionChain];
343
361
  if (chainInfo) {
344
362
  const namespace = `${chain_1.VM_NAMESPACE[chainInfo.vm]}:${chainInfo.chainId}`;
345
- const existing = chainToHops.get(namespace) || [];
363
+ const existingByOrdinal = chainToHops.get(namespace) || new Map();
364
+ const fallbackOrdinal = [...existingByOrdinal.values()].reduce((count, hopsForOrdinal) => count + hopsForOrdinal.length, 0);
365
+ const outboundOrdinal = (_a = _outboundIndexByHop === null || _outboundIndexByHop === void 0 ? void 0 : _outboundIndexByHop.get(hop.hopIndex)) !== null && _a !== void 0 ? _a : fallbackOrdinal;
366
+ const existing = existingByOrdinal.get(outboundOrdinal) || [];
346
367
  existing.push(hop);
347
- chainToHops.set(namespace, existing);
368
+ existingByOrdinal.set(outboundOrdinal, existing);
369
+ chainToHops.set(namespace, existingByOrdinal);
348
370
  }
349
371
  }
350
372
  const expectedChains = [...chainToHops.keys()];
@@ -387,7 +409,9 @@ function waitForAllOutboundTxsV2(ctx, pushChainTxHash, outboundHops, options) {
387
409
  // Resolve query ID on first poll
388
410
  if (!cachedQueryId) {
389
411
  const allSubTxIds = yield (0, outbound_tracker_1.extractAllUniversalSubTxIds)(ctx, pushChainTxHash);
390
- const subTxId = allSubTxIds.length > 0 ? allSubTxIds[0] : (0, outbound_tracker_1.computeUniversalTxId)(ctx.pushNetwork, pushChainTxHash);
412
+ const subTxId = allSubTxIds.length > 0
413
+ ? allSubTxIds[0]
414
+ : (0, outbound_tracker_1.computeUniversalTxId)(ctx.pushNetwork, pushChainTxHash);
391
415
  cachedQueryId = subTxId.startsWith('0x') ? subTxId.slice(2) : subTxId;
392
416
  (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Resolved queryId: ${cachedQueryId}`);
393
417
  }
@@ -396,41 +420,45 @@ function waitForAllOutboundTxsV2(ctx, pushChainTxHash, outboundHops, options) {
396
420
  consecutiveErrors = 0; // Reset on successful RPC call
397
421
  const utx = v2Response === null || v2Response === void 0 ? void 0 : v2Response.universalTx;
398
422
  const statusNum = utx === null || utx === void 0 ? void 0 : utx.universalStatus;
399
- const statusName = (_a = types_1.UniversalTxStatus[statusNum]) !== null && _a !== void 0 ? _a : statusNum;
400
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Poll #${pollCount} | status: ${statusNum} (${statusName}) | outboundTx count: ${(_c = (_b = utx === null || utx === void 0 ? void 0 : utx.outboundTx) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0}`);
401
- if ((_d = utx === null || utx === void 0 ? void 0 : utx.outboundTx) === null || _d === void 0 ? void 0 : _d.length) {
423
+ const statusName = (_b = types_1.UniversalTxStatus[statusNum]) !== null && _b !== void 0 ? _b : statusNum;
424
+ (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Poll #${pollCount} | status: ${statusNum} (${statusName}) | outboundTx count: ${(_d = (_c = utx === null || utx === void 0 ? void 0 : utx.outboundTx) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0}`);
425
+ if ((_e = utx === null || utx === void 0 ? void 0 : utx.outboundTx) === null || _e === void 0 ? void 0 : _e.length) {
402
426
  const outboundOrdinalByChain = new Map();
403
427
  for (const ob of utx.outboundTx) {
404
428
  const destChain = ob.destinationChain;
405
- const hopsForChain = chainToHops.get(destChain);
406
- if (!hopsForChain)
429
+ const hopsByOrdinal = chainToHops.get(destChain);
430
+ if (!hopsByOrdinal)
407
431
  continue;
408
- const outboundOrdinal = (_e = outboundOrdinalByChain.get(destChain)) !== null && _e !== void 0 ? _e : 0;
432
+ const outboundOrdinal = (_f = outboundOrdinalByChain.get(destChain)) !== null && _f !== void 0 ? _f : 0;
409
433
  outboundOrdinalByChain.set(destChain, outboundOrdinal + 1);
410
- const targetHop = hopsForChain[outboundOrdinal];
411
- if (!targetHop ||
412
- targetHop.status === 'confirmed' ||
413
- targetHop.status === 'failed') {
434
+ const targetHops = hopsByOrdinal.get(outboundOrdinal) || [];
435
+ const pendingTargetHops = targetHops.filter((hop) => hop.status !== 'confirmed' && hop.status !== 'failed');
436
+ if (pendingTargetHops.length === 0)
414
437
  continue;
415
- }
416
- const externalTxHash = (_f = ob.observedTx) === null || _f === void 0 ? void 0 : _f.txHash;
438
+ const primaryTargetHop = pendingTargetHops[0];
439
+ const externalTxHash = (_g = ob.observedTx) === null || _g === void 0 ? void 0 : _g.txHash;
417
440
  const hasObservedTxHash = !!externalTxHash && externalTxHash !== 'EMPTY';
418
441
  const observedFailure = hasObservedTxHash &&
419
- ((_g = ob.observedTx) === null || _g === void 0 ? void 0 : _g.success) === false &&
420
- Boolean(((_h = ob.observedTx) === null || _h === void 0 ? void 0 : _h.errorMsg) ||
442
+ ((_h = ob.observedTx) === null || _h === void 0 ? void 0 : _h.success) === false &&
443
+ Boolean(((_j = ob.observedTx) === null || _j === void 0 ? void 0 : _j.errorMsg) ||
421
444
  ob.abortReason ||
422
445
  ob.outboundStatus >= types_2.OutboundStatus.REVERTED);
423
446
  // Fail fast on per-outbound REVERTED or observed destination failure.
424
- if (ob.outboundStatus === types_2.OutboundStatus.REVERTED || observedFailure) {
425
- targetHop.status = 'failed';
426
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Outbound to ${destChain} REVERTED | outboundIndex ${outboundOrdinal} | hop ${targetHop.hopIndex} | error: ${ob.abortReason || ((_j = ob.observedTx) === null || _j === void 0 ? void 0 : _j.errorMsg) || 'Unknown'}`);
427
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({
428
- hopIndex: targetHop.hopIndex,
429
- route: targetHop.route,
430
- chain: targetHop.executionChain,
431
- status: 'failed',
432
- });
433
- return { success: false, failedAt: targetHop.hopIndex };
447
+ if (ob.outboundStatus === types_2.OutboundStatus.REVERTED ||
448
+ observedFailure) {
449
+ for (const targetHop of pendingTargetHops) {
450
+ targetHop.status = 'failed';
451
+ progressHook === null || progressHook === void 0 ? void 0 : progressHook({
452
+ hopIndex: targetHop.hopIndex,
453
+ route: targetHop.route,
454
+ chain: targetHop.executionChain,
455
+ status: 'failed',
456
+ });
457
+ }
458
+ (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Outbound to ${destChain} REVERTED | outboundIndex ${outboundOrdinal} | hops ${pendingTargetHops
459
+ .map((hop) => hop.hopIndex)
460
+ .join(', ')} | error: ${ob.abortReason || ((_k = ob.observedTx) === null || _k === void 0 ? void 0 : _k.errorMsg) || 'Unknown'}`);
461
+ return { success: false, failedAt: primaryTargetHop.hopIndex };
434
462
  }
435
463
  // Check for observed tx hash. Cosmos may lag the status transition,
436
464
  // so EVM destinations are verified against the destination receipt.
@@ -442,15 +470,19 @@ function waitForAllOutboundTxsV2(ctx, pushChainTxHash, outboundHops, options) {
442
470
  if (!isCosmosObserved) {
443
471
  const verdict = yield verifyExternalEvmReceipt(ctx, chain, externalTxHash);
444
472
  if (verdict === 'reverted') {
445
- targetHop.status = 'failed';
446
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Source-chain RPC reports REVERTED for ${destChain} tx ${externalTxHash} | outboundIndex ${outboundOrdinal} | hop ${targetHop.hopIndex}`);
447
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({
448
- hopIndex: targetHop.hopIndex,
449
- route: targetHop.route,
450
- chain: targetHop.executionChain,
451
- status: 'failed',
452
- });
453
- return { success: false, failedAt: targetHop.hopIndex };
473
+ for (const targetHop of pendingTargetHops) {
474
+ targetHop.status = 'failed';
475
+ progressHook === null || progressHook === void 0 ? void 0 : progressHook({
476
+ hopIndex: targetHop.hopIndex,
477
+ route: targetHop.route,
478
+ chain: targetHop.executionChain,
479
+ status: 'failed',
480
+ });
481
+ }
482
+ (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Source-chain RPC reports REVERTED for ${destChain} tx ${externalTxHash} | outboundIndex ${outboundOrdinal} | hops ${pendingTargetHops
483
+ .map((hop) => hop.hopIndex)
484
+ .join(', ')}`);
485
+ return { success: false, failedAt: primaryTargetHop.hopIndex };
454
486
  }
455
487
  if (verdict !== 'success') {
456
488
  continue;
@@ -462,38 +494,49 @@ function waitForAllOutboundTxsV2(ctx, pushChainTxHash, outboundHops, options) {
462
494
  // inside this function; it's not exposed to consumers from the
463
495
  // cascade path (cascade.ts doesn't call findChildUtxIdFromExternalTx
464
496
  // for the cascade outbound hash).
465
- const displayTxHash = (_k = (0, helpers_1.toExternalTxHashDisplay)(chain !== null && chain !== void 0 ? chain : undefined, externalTxHash)) !== null && _k !== void 0 ? _k : externalTxHash;
497
+ const displayTxHash = (_l = (0, helpers_1.toExternalTxHashDisplay)(chain !== null && chain !== void 0 ? chain : undefined, externalTxHash)) !== null && _l !== void 0 ? _l : externalTxHash;
466
498
  let explorerUrl = '';
467
499
  if (chain && externalTxHash) {
468
- const explorerBaseUrl = (_l = chain_1.CHAIN_INFO[chain]) === null || _l === void 0 ? void 0 : _l.explorerUrl;
469
- const isSvm = ((_m = chain_1.CHAIN_INFO[chain]) === null || _m === void 0 ? void 0 : _m.vm) === enums_1.VM.SVM;
500
+ const explorerBaseUrl = (_m = chain_1.CHAIN_INFO[chain]) === null || _m === void 0 ? void 0 : _m.explorerUrl;
501
+ const isSvm = ((_o = chain_1.CHAIN_INFO[chain]) === null || _o === void 0 ? void 0 : _o.vm) === enums_1.VM.SVM;
470
502
  if (isSvm) {
471
- const cluster = chain === enums_1.CHAIN.SOLANA_DEVNET ? '?cluster=devnet'
472
- : chain === enums_1.CHAIN.SOLANA_TESTNET ? '?cluster=testnet' : '';
473
- explorerUrl = explorerBaseUrl ? `${explorerBaseUrl}/tx/${displayTxHash}${cluster}` : '';
503
+ const cluster = chain === enums_1.CHAIN.SOLANA_DEVNET
504
+ ? '?cluster=devnet'
505
+ : chain === enums_1.CHAIN.SOLANA_TESTNET
506
+ ? '?cluster=testnet'
507
+ : '';
508
+ explorerUrl = explorerBaseUrl
509
+ ? `${explorerBaseUrl}/tx/${displayTxHash}${cluster}`
510
+ : '';
474
511
  }
475
512
  else {
476
- explorerUrl = explorerBaseUrl ? `${explorerBaseUrl}/tx/${displayTxHash}` : '';
513
+ explorerUrl = explorerBaseUrl
514
+ ? `${explorerBaseUrl}/tx/${displayTxHash}`
515
+ : '';
477
516
  }
478
517
  }
479
- targetHop.status = 'confirmed';
480
- targetHop.txHash = displayTxHash;
481
- targetHop.outboundDetails = {
482
- externalTxHash: displayTxHash,
483
- destinationChain: chain || targetHop.executionChain,
484
- explorerUrl,
485
- recipient: ob.recipient,
486
- amount: ob.amount,
487
- assetAddr: ob.externalAssetAddr,
488
- };
489
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] FOUND outbound for ${destChain} | outboundIndex ${outboundOrdinal} | hop ${targetHop.hopIndex} | externalTxHash: ${displayTxHash}`);
490
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({
491
- hopIndex: targetHop.hopIndex,
492
- route: targetHop.route,
493
- chain: targetHop.executionChain,
494
- status: 'confirmed',
495
- txHash: displayTxHash,
496
- });
518
+ for (const targetHop of pendingTargetHops) {
519
+ targetHop.status = 'confirmed';
520
+ targetHop.txHash = displayTxHash;
521
+ targetHop.outboundDetails = {
522
+ externalTxHash: displayTxHash,
523
+ destinationChain: chain || targetHop.executionChain,
524
+ explorerUrl,
525
+ recipient: ob.recipient,
526
+ amount: ob.amount,
527
+ assetAddr: ob.externalAssetAddr,
528
+ };
529
+ progressHook === null || progressHook === void 0 ? void 0 : progressHook({
530
+ hopIndex: targetHop.hopIndex,
531
+ route: targetHop.route,
532
+ chain: targetHop.executionChain,
533
+ status: 'confirmed',
534
+ txHash: displayTxHash,
535
+ });
536
+ }
537
+ (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] FOUND outbound for ${destChain} | outboundIndex ${outboundOrdinal} | hops ${pendingTargetHops
538
+ .map((hop) => hop.hopIndex)
539
+ .join(', ')} | externalTxHash: ${displayTxHash}`);
497
540
  }
498
541
  }
499
542
  }
@@ -508,7 +551,7 @@ function waitForAllOutboundTxsV2(ctx, pushChainTxHash, outboundHops, options) {
508
551
  if (statusNum === types_1.UniversalTxStatus.PC_EXECUTED_SUCCESS) {
509
552
  const stillUnresolved = outboundHops.filter((h) => h.status !== 'confirmed');
510
553
  if (stillUnresolved.length > 0) {
511
- const hasPendingOutbound = (_o = utx === null || utx === void 0 ? void 0 : utx.outboundTx) === null || _o === void 0 ? void 0 : _o.some((ob) => {
554
+ const hasPendingOutbound = (_p = utx === null || utx === void 0 ? void 0 : utx.outboundTx) === null || _p === void 0 ? void 0 : _p.some((ob) => {
512
555
  var _a;
513
556
  return ob.outboundStatus === types_2.OutboundStatus.PENDING &&
514
557
  (!((_a = ob.observedTx) === null || _a === void 0 ? void 0 : _a.txHash) || ob.observedTx.txHash === 'EMPTY');
@@ -563,14 +563,32 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
563
563
  }
564
564
  }
565
565
  const origin = `${chain_1.VM_NAMESPACE[vm]}:${chainId}:${originAddress}`;
566
- // Create signature from transaction r, s, v values
566
+ // Create signature from transaction r, s, v values.
567
+ //
568
+ // Push Chain's derived (module-injected) inbound txs are reported over the EVM
569
+ // JSON-RPC as **legacy `type 0x0`**, which omits the `yParity` field that viem
570
+ // only populates for typed (EIP-1559/2930) txs — so `tx.yParity` is `undefined`.
571
+ // Per the chain team (the hash inconsistencies were fixed chain-side, but
572
+ // derived txs remain legacy 0x0 by design), the SDK must accept `0x0` and
573
+ // surface a numeric `yParity`. Derive it from `v`: 0/1 → as-is, 27/28 → v-27,
574
+ // EIP-155 → (v-35)%2. (For the zeroed-signature derived txs, v=0 → yParity=0.)
567
575
  let signature;
568
576
  try {
577
+ const vNum = typeof tx.v === 'bigint' ? Number(tx.v) : tx.v || 0;
578
+ let yParity = tx.yParity;
579
+ if (yParity === undefined) {
580
+ if (vNum === 0 || vNum === 1)
581
+ yParity = vNum;
582
+ else if (vNum === 27 || vNum === 28)
583
+ yParity = vNum - 27;
584
+ else
585
+ yParity = (vNum - 35) % 2;
586
+ }
569
587
  signature = {
570
588
  r: tx.r || '0x0',
571
589
  s: tx.s || '0x0',
572
- v: typeof tx.v === 'bigint' ? Number(tx.v) : tx.v || 0,
573
- yParity: tx.yParity,
590
+ v: vNum,
591
+ yParity,
574
592
  };
575
593
  }
576
594
  catch (_d) {
@@ -631,7 +649,7 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
631
649
  accessList: Array.isArray(tx.accessList) ? [...tx.accessList] : [],
632
650
  // 6. Utilities
633
651
  wait: (waitOptions) => tslib_1.__awaiter(this, void 0, void 0, function* () {
634
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
652
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
635
653
  // For trackTransaction replays the tx is historical — cosmos already
636
654
  // has the full graph indexed, so the 20s/30s initial-wait buffers
637
655
  // designed for live sends are pure deadweight. Default them to 0 when
@@ -776,7 +794,15 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
776
794
  // Resolve the terminal hash for the whole journey: inbound (R3) >
777
795
  // external outbound (R2) > the Push tx itself (R1). Single-tx analogue
778
796
  // of CascadeCompletionResult.finalTxHash.
779
- baseReceipt = Object.assign(Object.assign({}, baseReceipt), { finalTxHash: (_l = (_k = baseReceipt.pushInboundTxHash) !== null && _k !== void 0 ? _k : baseReceipt.externalTxHash) !== null && _l !== void 0 ? _l : baseReceipt.hash });
797
+ baseReceipt = Object.assign(Object.assign({}, baseReceipt), { finalTxHash: (_l = (_k = baseReceipt.pushInboundTxHash) !== null && _k !== void 0 ? _k : baseReceipt.externalTxHash) !== null && _l !== void 0 ? _l : baseReceipt.hash,
798
+ // Ensure `externalChain` is populated whenever an external/inbound leg
799
+ // surfaced an `externalTxHash`. The outbound-success branch already sets
800
+ // it to the destination chain (so this `??` never overrides it); this
801
+ // only fills the inbound/R3 and failed/timeout paths, where it should be
802
+ // the route's external chain (= the response's `chain`).
803
+ externalChain: (_m = baseReceipt.externalChain) !== null && _m !== void 0 ? _m : (baseReceipt.externalTxHash
804
+ ? universalTxResponse.chain
805
+ : baseReceipt.externalChain) });
780
806
  return baseReceipt;
781
807
  }),
782
808
  // Internal: register a wait-phase progressHook without replaying
@@ -19,6 +19,13 @@ import { MoveableToken } from '../../constants/tokens';
19
19
  import { TransactionRoute } from '../route-detector';
20
20
  import type { ChainTarget, ExecuteParams, UniversalExecuteParams, UniversalOutboundTxRequest, UniversalTxRequest, UniversalTxResponse } from '../orchestrator.types';
21
21
  import type { PUSH_NETWORK } from '../../constants/enums';
22
+ export declare function getRequiredNativeBalanceForOutbound(nativeValueForGas: bigint, gasReserve: bigint): bigint;
23
+ export declare function shouldSkipFeeLockingForOutbound(opts: {
24
+ isUEADeployed: boolean;
25
+ balance: bigint;
26
+ nativeValueForGas: bigint;
27
+ gasReserve: bigint;
28
+ }): boolean;
22
29
  export declare function resolveR2DestinationFundsToken(token: MoveableToken, targetChain: CHAIN, pushNetwork: PUSH_NETWORK): MoveableToken;
23
30
  type ExecuteFn = (params: ExecuteParams | UniversalExecuteParams) => Promise<UniversalTxResponse>;
24
31
  /**