@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.
@@ -19,6 +19,7 @@ const web3_js_1 = require("@solana/web3.js");
19
19
  const viem_1 = require("viem");
20
20
  const erc20_evm_1 = require("../../constants/abi/erc20.evm");
21
21
  const chain_1 = require("../../constants/chain");
22
+ const enums_1 = require("../../constants/enums");
22
23
  const selectors_1 = require("../../constants/selectors");
23
24
  const route_detector_1 = require("../route-detector");
24
25
  const cea_utils_1 = require("../cea-utils");
@@ -170,6 +171,7 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
170
171
  to: target,
171
172
  senderUea: ueaAddress,
172
173
  });
174
+ (0, payload_builders_1.assertSvmPayloadWithinRelayLimit)(svmPayload, `Route 2 SVM ${targetChain}`);
173
175
  let prc20Token = selectors_1.ZERO_ADDRESS;
174
176
  let burnAmount = BigInt(0);
175
177
  if ((_e = params.funds) === null || _e === void 0 ? void 0 : _e.amount) {
@@ -301,7 +303,9 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
301
303
  }
302
304
  }
303
305
  else {
304
- if (((_m = params.funds) === null || _m === void 0 ? void 0 : _m.amount) && fundsToken && fundsToken.mechanism !== 'native') {
306
+ if (((_m = params.funds) === null || _m === void 0 ? void 0 : _m.amount) &&
307
+ fundsToken &&
308
+ fundsToken.mechanism !== 'native') {
305
309
  const erc20Transfer = (0, viem_1.encodeFunctionData)({
306
310
  abi: erc20_evm_1.ERC20_EVM,
307
311
  functionName: 'transfer',
@@ -675,7 +679,8 @@ function getRootFundsDeposit(hops) {
675
679
  const sameToken = currentToken === nextToken ||
676
680
  (!!currentToken &&
677
681
  !!nextToken &&
678
- currentToken.address.toLowerCase() === nextToken.address.toLowerCase() &&
682
+ currentToken.address.toLowerCase() ===
683
+ nextToken.address.toLowerCase() &&
679
684
  currentToken.symbol === nextToken.symbol &&
680
685
  currentToken.mechanism === nextToken.mechanism);
681
686
  if (!sameToken) {
@@ -783,7 +788,7 @@ function getInboundFundingForBurnToken(segments, beforeSegmentIndex, burnToken,
783
788
  }
784
789
  function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce, options) {
785
790
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
786
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
791
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
787
792
  let accumulatedPushMulticalls = [];
788
793
  let requiredNativeValue = BigInt(0);
789
794
  const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
@@ -823,27 +828,38 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
823
828
  perOutboundNativeValue =
824
829
  (effectiveUeaBalance - CASCADE_GAS_RESERVE) / BigInt(numOutbounds);
825
830
  }
831
+ let universalCoreAddressForSwap;
832
+ const getUniversalCoreAddressForSwap = () => tslib_1.__awaiter(this, void 0, void 0, function* () {
833
+ if (!universalCoreAddressForSwap) {
834
+ universalCoreAddressForSwap =
835
+ yield ctx.pushClient.readContract({
836
+ address: gatewayPcAddress,
837
+ abi: abi_1.UNIVERSAL_GATEWAY_PC,
838
+ functionName: 'universalCore',
839
+ args: [],
840
+ });
841
+ }
842
+ return universalCoreAddressForSwap;
843
+ });
826
844
  // Pool-price-based native-value override for SVM outbounds.
827
845
  // The flat split above works for EVM gas tokens (pBNB/pETH — cheap per unit),
828
- // but pSOL has a much higher WPC/pSOL pool price, so the flat 1 PC split is
829
- // insufficient to swap for gasFee wei of pSOL and Uniswap reverts "STF".
830
- // Mirror executeUoaToCeaSvm's pool-price read (route-handlers.ts) per SVM segment.
846
+ // but pSOL has a much higher WPC/pSOL pool price, so a flat balance split can
847
+ // underfund the gas swap and make Uniswap revert with "STF". Mirror the
848
+ // direct SVM route handlers' pool-price read for every SVM outbound-bearing
849
+ // segment, including Route 3 SVM inbound round trips.
831
850
  const svmSegments = segments.filter((s) => {
832
851
  var _a;
833
- return s.type === 'OUTBOUND_TO_CEA' &&
834
- ((_a = s.hops[0]) === null || _a === void 0 ? void 0 : _a.isSvmTarget) === true &&
852
+ return ((s.type === 'OUTBOUND_TO_CEA' && ((_a = s.hops[0]) === null || _a === void 0 ? void 0 : _a.isSvmTarget) === true) ||
853
+ (s.type === 'INBOUND_FROM_CEA' &&
854
+ !isNativeSeedOnlySegment(s) &&
855
+ (0, payload_builders_1.isSvmChain)(s.sourceChain))) &&
835
856
  s.gasToken &&
836
857
  s.gasFee &&
837
858
  s.gasFee > BigInt(0);
838
859
  });
839
860
  const svmNativeValueBySegment = new Map();
840
861
  if (svmSegments.length > 0 && effectiveUeaBalance > CASCADE_GAS_RESERVE) {
841
- const universalCoreAddress = yield ctx.pushClient.readContract({
842
- address: gatewayPcAddress,
843
- abi: abi_1.UNIVERSAL_GATEWAY_PC,
844
- functionName: 'universalCore',
845
- args: [],
846
- });
862
+ const universalCoreAddress = yield getUniversalCoreAddressForSwap();
847
863
  // Compute per-SVM budget = ueaBalance − (PC needed for EVM outbounds) − 1 PC safety
848
864
  // (outer-tx gas + refund headroom). Pass a huge accountBalance to the estimator so
849
865
  // its internal cap is effectively disabled; we cap externally to the per-SVM budget.
@@ -869,7 +885,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
869
885
  if (perSvmCap > BigInt(0) && value > perSvmCap) {
870
886
  const cappedValue = perSvmCap >= estimated ? perSvmCap : estimated;
871
887
  if (cappedValue !== perSvmCap) {
872
- (0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_a = seg.hops[0]) === null || _a === void 0 ? void 0 : _a.targetChain}: perSvmCap=${perSvmCap} is below live pool quote=${estimated}; using quote to avoid underfunded gas swap`);
888
+ (0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_a = seg.targetChain) !== null && _a !== void 0 ? _a : seg.sourceChain}: perSvmCap=${perSvmCap} is below live pool quote=${estimated}; using quote to avoid underfunded gas swap`);
873
889
  }
874
890
  value = cappedValue;
875
891
  }
@@ -887,10 +903,10 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
887
903
  ? estimated * BigInt(5)
888
904
  : UPWARD_BASE_CEILING_CASCADE_SVM;
889
905
  if (value > upwardCeilingCascadeSvm) {
890
- (0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_b = seg.hops[0]) === null || _b === void 0 ? void 0 : _b.targetChain}: capping value from ${value} to upwardCeiling=${upwardCeilingCascadeSvm} (pool quote=${estimated})`);
906
+ (0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_b = seg.targetChain) !== null && _b !== void 0 ? _b : seg.sourceChain}: capping value from ${value} to upwardCeiling=${upwardCeilingCascadeSvm} (pool quote=${estimated})`);
891
907
  value = upwardCeilingCascadeSvm;
892
908
  }
893
- (0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_c = seg.hops[0]) === null || _c === void 0 ? void 0 : _c.targetChain}: gasFee=${seg.gasFee}, estimatedWpc=${estimated}, flat=${flat}, perSvmCap=${perSvmCap}, upwardCeiling=${upwardCeilingCascadeSvm}, chosen=${value}`);
909
+ (0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_c = seg.targetChain) !== null && _c !== void 0 ? _c : seg.sourceChain}: gasFee=${seg.gasFee}, estimatedWpc=${estimated}, flat=${flat}, perSvmCap=${perSvmCap}, upwardCeiling=${upwardCeilingCascadeSvm}, chosen=${value}`);
894
910
  // Per-segment minimum-viable-swap check: if the chosen `value` is below
895
911
  // the live pool quote (`estimated`), the cap squeezed the budget below
896
912
  // what the swap actually needs and the segment will revert inside
@@ -923,22 +939,54 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
923
939
  svmNativeValueBySegment.set(seg, value);
924
940
  }
925
941
  }
942
+ // EVM cascade outbounds should follow the direct R2 path: use the live
943
+ // WPC/gasToken quote instead of blindly spending the flat balance split.
944
+ // Over-allocating into thin pETH/pBNB pools can revert with Uniswap "STF"
945
+ // before the gateway has a chance to refund unused PC.
946
+ const evmSegments = segments.filter((s) => {
947
+ var _a;
948
+ return s.type === 'OUTBOUND_TO_CEA' &&
949
+ ((_a = s.hops[0]) === null || _a === void 0 ? void 0 : _a.isSvmTarget) !== true &&
950
+ s.gasToken &&
951
+ s.gasFee &&
952
+ s.gasFee > BigInt(0);
953
+ });
954
+ const evmNativeValueBySegment = new Map();
955
+ if (evmSegments.length > 0 && effectiveUeaBalance > CASCADE_GAS_RESERVE) {
956
+ const universalCoreAddress = yield getUniversalCoreAddressForSwap();
957
+ const ONE_PC = BigInt('1000000000000000000');
958
+ const EVM_NATIVE_VALUE_SAFETY_CAP_CASCADE = BigInt(200) * ONE_PC;
959
+ const UNCAPPED_BALANCE = BigInt('1000000000000000000000000000000');
960
+ for (const seg of evmSegments) {
961
+ let value = yield (0, gas_calculator_1.estimateNativeValueForSwap)(ctx, universalCoreAddress, seg.gasToken, seg.gasFee, UNCAPPED_BALANCE);
962
+ if (value > EVM_NATIVE_VALUE_SAFETY_CAP_CASCADE) {
963
+ (0, context_1.printLog)(ctx, `composeCascade — EVM outbound to ${seg.targetChain}: capping nativeValueForGas at 200 PC ceiling (was ${value.toString()})`);
964
+ value = EVM_NATIVE_VALUE_SAFETY_CAP_CASCADE;
965
+ }
966
+ (0, context_1.printLog)(ctx, `composeCascade — EVM outbound to ${seg.targetChain}: gasFee=${seg.gasFee}, quotedNativeValue=${value}, flat=${perOutboundNativeValue !== null && perOutboundNativeValue !== void 0 ? perOutboundNativeValue : BigInt(0)}`);
967
+ evmNativeValueBySegment.set(seg, value);
968
+ }
969
+ }
926
970
  for (let segIdxLoop = segments.length - 1; segIdxLoop >= 0; segIdxLoop--) {
927
971
  const segment = segments[segIdxLoop];
928
972
  const gasPriceForOutboundRequest = (chainForLog) => {
929
973
  var _a;
930
974
  const quotedGasPrice = (_a = segment.gasPrice) !== null && _a !== void 0 ? _a : BigInt(0);
975
+ const isInboundFromCea = segment.type === 'INBOUND_FROM_CEA';
931
976
  // If this outbound is being accumulated into an earlier Route 3
932
977
  // inbound payload, it may execute minutes after the quote. Keep
933
978
  // immediate outbounds pinned to the quote, but let delayed nested
934
- // outbounds resolve the live base gas price at execution time.
979
+ // outbounds resolve the live base gas price at execution time. Route 3
980
+ // source-chain outbounds have the same quote-staleness risk because the
981
+ // source CEA leg can execute after the Push Chain gateway quote changes.
935
982
  const executesAfterInbound = segments
936
983
  .slice(0, segIdxLoop)
937
984
  .some((s) => s.type === 'INBOUND_FROM_CEA');
938
- if (executesAfterInbound && quotedGasPrice > BigInt(0)) {
939
- (0, context_1.printLog)(ctx, `composeCascade — delayed outbound to ${chainForLog !== null && chainForLog !== void 0 ? chainForLog : 'unknown chain'}: using gasPrice=0 instead of quoted ${quotedGasPrice} so UGPC resolves the live base price at execution time`);
985
+ const shouldUseLiveGasPrice = isInboundFromCea || executesAfterInbound;
986
+ if (shouldUseLiveGasPrice && quotedGasPrice > BigInt(0)) {
987
+ (0, context_1.printLog)(ctx, `composeCascade — ${isInboundFromCea ? 'Route 3' : 'delayed'} outbound to ${chainForLog !== null && chainForLog !== void 0 ? chainForLog : 'unknown chain'}: using gasPrice=0 instead of quoted ${quotedGasPrice} so UGPC resolves the live base price at execution time`);
940
988
  }
941
- return executesAfterInbound ? BigInt(0) : quotedGasPrice;
989
+ return shouldUseLiveGasPrice ? BigInt(0) : quotedGasPrice;
942
990
  };
943
991
  switch (segment.type) {
944
992
  case 'PUSH_EXECUTION': {
@@ -976,9 +1024,10 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
976
1024
  // EVM: build CEA payload from merged multicalls
977
1025
  outboundPayload = (0, payload_builders_1.buildCeaMulticallPayload)(segment.mergedCeaMulticalls || []);
978
1026
  // Get CEA address from the first hop
979
- targetForOutbound = outboundPayload === '0x'
980
- ? selectors_1.ZERO_ADDRESS
981
- : (firstHop === null || firstHop === void 0 ? void 0 : firstHop.ceaAddress) || ueaAddress;
1027
+ targetForOutbound =
1028
+ outboundPayload === '0x'
1029
+ ? selectors_1.ZERO_ADDRESS
1030
+ : (firstHop === null || firstHop === void 0 ? void 0 : firstHop.ceaAddress) || ueaAddress;
982
1031
  (0, payload_builders_1.assertCeaFundsParkingInvariant)(targetForOutbound, outboundPayload);
983
1032
  }
984
1033
  // Per-segment PRC-20 burn-balance pre-check. Fires whenever
@@ -1058,7 +1107,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1058
1107
  else {
1059
1108
  evmFallbackValue = segGasFee * BigInt(1000000);
1060
1109
  }
1061
- let nativeValueForGas = (_g = svmOverride !== null && svmOverride !== void 0 ? svmOverride : perOutboundNativeValue) !== null && _g !== void 0 ? _g : evmFallbackValue;
1110
+ let nativeValueForGas = (_h = (_g = svmOverride !== null && svmOverride !== void 0 ? svmOverride : evmNativeValueBySegment.get(segment)) !== null && _g !== void 0 ? _g : perOutboundNativeValue) !== null && _h !== void 0 ? _h : evmFallbackValue;
1062
1111
  // SDK 5.2 Case C: when the segment's sizing is category C, compose
1063
1112
  // R2/OUTBOUND_TO_CEA segments no longer apply Case A/B/C sizing
1064
1113
  // (scoped to R1 + R3 only). segment.sizing may still be populated
@@ -1099,7 +1148,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1099
1148
  // UEA multicall so no nested outbound is needed.
1100
1149
  const mergedHopMulticalls = [];
1101
1150
  for (const h of segment.hops) {
1102
- if ((0, data_utils_1.hasExecutablePayloadData)((_h = h === null || h === void 0 ? void 0 : h.params) === null || _h === void 0 ? void 0 : _h.data) || ((_j = h === null || h === void 0 ? void 0 : h.params) === null || _j === void 0 ? void 0 : _j.value)) {
1151
+ if ((0, data_utils_1.hasExecutablePayloadData)((_j = h === null || h === void 0 ? void 0 : h.params) === null || _j === void 0 ? void 0 : _j.data) || ((_k = h === null || h === void 0 ? void 0 : h.params) === null || _k === void 0 ? void 0 : _k.value)) {
1103
1152
  const hopCalls = (0, payload_builders_1.buildExecuteMulticall)({
1104
1153
  execute: {
1105
1154
  to: h.params.to,
@@ -1144,7 +1193,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1144
1193
  let drainAmount = BigInt(0);
1145
1194
  let tokenMintHex;
1146
1195
  const params = hop.params;
1147
- if (((_k = params.funds) === null || _k === void 0 ? void 0 : _k.amount) && params.funds.amount > BigInt(0)) {
1196
+ if (((_l = params.funds) === null || _l === void 0 ? void 0 : _l.amount) && params.funds.amount > BigInt(0)) {
1148
1197
  drainAmount = params.funds.amount;
1149
1198
  const token = params.funds.token;
1150
1199
  if (token && token.address) {
@@ -1171,9 +1220,10 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1171
1220
  ? new Uint8Array(Buffer.from(intermediatePayload.slice(2), 'hex'))
1172
1221
  : undefined,
1173
1222
  });
1223
+ (0, payload_builders_1.assertSvmPayloadWithinRelayLimit)(svmPayload, `Route 3 SVM cascade ${sourceChain}`);
1174
1224
  const svmBurnAmount = segment.totalBurnAmount || BigInt(0);
1175
1225
  const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, segment.prc20Token ||
1176
- (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), svmBurnAmount, (_l = segment.gasLimit) !== null && _l !== void 0 ? _l : BigInt(0), svmPayload, ueaAddress, (_m = segment.maxPCForGas) !== null && _m !== void 0 ? _m : BigInt(0), gasPriceForOutboundRequest(sourceChain));
1226
+ (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), svmBurnAmount, (_m = segment.gasLimit) !== null && _m !== void 0 ? _m : BigInt(0), svmPayload, ueaAddress, (_o = segment.maxPCForGas) !== null && _o !== void 0 ? _o : BigInt(0), gasPriceForOutboundRequest(sourceChain));
1177
1227
  const inboundGasFee = segment.gasFee || BigInt(0);
1178
1228
  let svmInboundFallback;
1179
1229
  if (effectiveUeaBalance > BigInt(0)) {
@@ -1183,11 +1233,11 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1183
1233
  else {
1184
1234
  svmInboundFallback = inboundGasFee * BigInt(1000000);
1185
1235
  }
1186
- let svmInboundNativeValue = perOutboundNativeValue !== null && perOutboundNativeValue !== void 0 ? perOutboundNativeValue : svmInboundFallback;
1236
+ let svmInboundNativeValue = (_q = (_p = svmNativeValueBySegment.get(segment)) !== null && _p !== void 0 ? _p : perOutboundNativeValue) !== null && _q !== void 0 ? _q : svmInboundFallback;
1187
1237
  // SDK 5.2 R3-style Case C bump: when the inbound segment's sizing
1188
1238
  // is C, top up the swap budget by the overflow. No bridge-swap —
1189
1239
  // R3 has no destination funds delivery.
1190
- if (((_o = segment.sizing) === null || _o === void 0 ? void 0 : _o.category) === 'C' &&
1240
+ if (((_r = segment.sizing) === null || _r === void 0 ? void 0 : _r.category) === 'C' &&
1191
1241
  segment.sizing.overflowNativePc > BigInt(0)) {
1192
1242
  const bumped = svmInboundNativeValue + segment.sizing.overflowNativePc;
1193
1243
  (0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (SVM) Case C: bumping nativeValueForGas from ${svmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
@@ -1224,7 +1274,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1224
1274
  let amount = BigInt(0);
1225
1275
  for (const mergedHop of segment.hops) {
1226
1276
  const hParams = mergedHop.params;
1227
- if ((_p = hParams.funds) === null || _p === void 0 ? void 0 : _p.amount) {
1277
+ if ((_s = hParams.funds) === null || _s === void 0 ? void 0 : _s.amount) {
1228
1278
  const token = hParams.funds.token;
1229
1279
  if (token) {
1230
1280
  if (token.mechanism === 'native') {
@@ -1260,7 +1310,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1260
1310
  ? segment.gasLimit
1261
1311
  : selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT;
1262
1312
  const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, segment.prc20Token ||
1263
- (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), segment.totalBurnAmount || BigInt(0), effectiveGasLimit, ceaPayload, ueaAddress, (_q = segment.maxPCForGas) !== null && _q !== void 0 ? _q : BigInt(0), gasPriceForOutboundRequest(sourceChain));
1313
+ (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), segment.totalBurnAmount || BigInt(0), effectiveGasLimit, ceaPayload, ueaAddress, (_t = segment.maxPCForGas) !== null && _t !== void 0 ? _t : BigInt(0), gasPriceForOutboundRequest(sourceChain));
1264
1314
  const inboundGasFee = segment.gasFee || BigInt(0);
1265
1315
  let evmInboundFallback;
1266
1316
  if (effectiveUeaBalance > BigInt(0)) {
@@ -1274,7 +1324,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1274
1324
  // SDK 5.2 R3-style Case C bump: when the inbound segment's sizing
1275
1325
  // is C, top up the swap budget by the overflow. No bridge-swap —
1276
1326
  // R3 has no destination funds delivery.
1277
- if (((_r = segment.sizing) === null || _r === void 0 ? void 0 : _r.category) === 'C' &&
1327
+ if (((_u = segment.sizing) === null || _u === void 0 ? void 0 : _u.category) === 'C' &&
1278
1328
  segment.sizing.overflowNativePc > BigInt(0)) {
1279
1329
  const bumped = evmInboundNativeValue + segment.sizing.overflowNativePc;
1280
1330
  (0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (EVM) Case C: bumping nativeValueForGas from ${evmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
@@ -1337,14 +1387,15 @@ function withMultihopRootProgressMapping(ctx, run) {
1337
1387
  }
1338
1388
  });
1339
1389
  }
1340
- function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1390
+ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, options) {
1341
1391
  return {
1342
1392
  send: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
1343
1393
  var _a;
1344
1394
  const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
1345
1395
  // Extract HopDescriptors
1346
1396
  const hops = preparedTxs.map((tx) => tx._hop);
1347
- const enforceGasCheck = hops.some((hop) => { var _a; return ((_a = hop.params.options) === null || _a === void 0 ? void 0 : _a.enforceGasCheck) === true; });
1397
+ const enforceGasCheck = (options === null || options === void 0 ? void 0 : options.enforceGasCheck) === true ||
1398
+ hops.some((hop) => { var _a; return ((_a = hop.params.options) === null || _a === void 0 ? void 0 : _a.enforceGasCheck) === true; });
1348
1399
  const withCascadeOptions = (params) => enforceGasCheck
1349
1400
  ? Object.assign(Object.assign({}, params), { options: Object.assign(Object.assign({}, params.options), { enforceGasCheck: true }) }) : params;
1350
1401
  // Classify into segments
@@ -1483,7 +1534,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1483
1534
  hops: hopInfos,
1484
1535
  hopCount: hops.length,
1485
1536
  waitForAll: (opts) => tslib_1.__awaiter(this, void 0, void 0, function* () {
1486
- var _a, _b, _c, _d, _e, _f, _g, _h;
1537
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1487
1538
  const { pollingIntervalMs = 10000, timeout = 300000, progressHook: cascadeProgressHook, eventHook = defaultEventHook, } = opts || {};
1488
1539
  const startTime = Date.now();
1489
1540
  // Per-hop progress emission. Fans out cascade markers to BOTH the
@@ -1591,10 +1642,12 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1591
1642
  const ceaToPushIndex = hopInfos.findIndex((h, index) => h.route === 'CEA_TO_PUSH' && !isNativeSeedOnlyHop(hops[index]));
1592
1643
  const isAfterCeaToPush = (index) => ceaToPushIndex >= 0 && index > ceaToPushIndex;
1593
1644
  const rootOutboundIndexByHop = buildRootOutboundIndexByHop(segments, hops);
1594
- const trackOutboundHops = (outboundHops, pushTxHash, resolvedSubTxId, outboundIndexByHop) => tslib_1.__awaiter(this, void 0, void 0, function* () {
1645
+ const trackOutboundHops = (outboundHops, pushTxHash, resolvedSubTxId, outboundIndexByHop, trackingOptions) => tslib_1.__awaiter(this, void 0, void 0, function* () {
1595
1646
  var _a, _b, _c, _d;
1596
1647
  if (outboundHops.length === 0)
1597
1648
  return null;
1649
+ const shouldDeferHopCompletion = (hop) => (trackingOptions === null || trackingOptions === void 0 ? void 0 : trackingOptions.deferCeaToPushCompletion) === true &&
1650
+ (hop === null || hop === void 0 ? void 0 : hop.route) === 'CEA_TO_PUSH';
1598
1651
  if (outboundHops.length === 1) {
1599
1652
  const hop = outboundHops[0];
1600
1653
  const hopHooks = (0, progress_route_hooks_1.pickWaitHooks)(hop.route);
@@ -1648,21 +1701,24 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1648
1701
  // consumers can paste it straight into a Solana explorer.
1649
1702
  const displayTxHash = (_b = (0, helpers_1.toExternalTxHashDisplay)(outboundDetails.destinationChain, outboundDetails.externalTxHash)) !== null && _b !== void 0 ? _b : outboundDetails.externalTxHash;
1650
1703
  const userFacingOutboundDetails = Object.assign(Object.assign({}, outboundDetails), { externalTxHash: displayTxHash });
1651
- hop.status = 'confirmed';
1704
+ const deferHopCompletion = shouldDeferHopCompletion(hop);
1705
+ hop.status = deferHopCompletion ? 'pending' : 'confirmed';
1652
1706
  hop.txHash = displayTxHash;
1653
1707
  hop.outboundDetails = userFacingOutboundDetails;
1654
- if (isMulti) {
1655
- emitRouteIntermediateComplete(hop, displayTxHash);
1656
- }
1657
- else {
1658
- emitHopEvent(hopHooks.success(userFacingOutboundDetails));
1708
+ if (!deferHopCompletion) {
1709
+ if (isMulti) {
1710
+ emitRouteIntermediateComplete(hop, displayTxHash);
1711
+ }
1712
+ else {
1713
+ emitHopEvent(hopHooks.success(userFacingOutboundDetails));
1714
+ }
1715
+ emitHopComplete(hop.hopIndex);
1659
1716
  }
1660
- emitHopComplete(hop.hopIndex);
1661
1717
  cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1662
1718
  hopIndex: hop.hopIndex,
1663
1719
  route: hop.route,
1664
1720
  chain: hop.executionChain,
1665
- status: 'confirmed',
1721
+ status: deferHopCompletion ? 'found' : 'confirmed',
1666
1722
  txHash: displayTxHash,
1667
1723
  elapsed: Date.now() - startTime,
1668
1724
  });
@@ -1697,23 +1753,24 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1697
1753
  }
1698
1754
  const perHopLastStatus = new Map();
1699
1755
  const confirmedHops = new Set();
1700
- const allOutboundDetails = yield callbacks.waitForAllOutboundTxsFn(pushTxHash, outboundHops, {
1701
- initialWaitMs: Math.min(60000, timeout - (Date.now() - startTime)),
1702
- pollingIntervalMs,
1703
- timeout: timeout - (Date.now() - startTime),
1704
- _resolvedSubTxId: resolvedSubTxId,
1705
- progressHook: (event) => {
1756
+ const allOutboundDetails = yield callbacks.waitForAllOutboundTxsFn(pushTxHash, outboundHops, Object.assign(Object.assign({ initialWaitMs: Math.min(60000, timeout - (Date.now() - startTime)), pollingIntervalMs, timeout: timeout - (Date.now() - startTime), _resolvedSubTxId: resolvedSubTxId }, (outboundIndexByHop
1757
+ ? { _outboundIndexByHop: outboundIndexByHop }
1758
+ : {})), { progressHook: (event) => {
1706
1759
  var _a, _b;
1760
+ const matchedHop = outboundHops.find((h) => h.hopIndex === event.hopIndex);
1761
+ const eventHooks = (0, progress_route_hooks_1.pickWaitHooks)(matchedHop === null || matchedHop === void 0 ? void 0 : matchedHop.route);
1762
+ const deferHopCompletion = shouldDeferHopCompletion(matchedHop);
1763
+ const progressStatus = deferHopCompletion && event.status === 'confirmed'
1764
+ ? 'found'
1765
+ : event.status;
1707
1766
  cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1708
1767
  hopIndex: event.hopIndex,
1709
1768
  route: event.route,
1710
1769
  chain: event.chain,
1711
- status: event.status,
1770
+ status: progressStatus,
1712
1771
  txHash: event.txHash,
1713
1772
  elapsed: Date.now() - startTime,
1714
1773
  });
1715
- const matchedHop = outboundHops.find((h) => h.hopIndex === event.hopIndex);
1716
- const eventHooks = (0, progress_route_hooks_1.pickWaitHooks)(matchedHop === null || matchedHop === void 0 ? void 0 : matchedHop.route);
1717
1774
  const prev = perHopLastStatus.get(event.hopIndex);
1718
1775
  if (event.status === 'polling' && prev !== 'polling') {
1719
1776
  perHopLastStatus.set(event.hopIndex, 'polling');
@@ -1729,17 +1786,18 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1729
1786
  // guard above, so we only reach here with full details.
1730
1787
  if (matchedHop === null || matchedHop === void 0 ? void 0 : matchedHop.outboundDetails) {
1731
1788
  confirmedHops.add(event.hopIndex);
1732
- if (isMulti) {
1733
- emitRouteIntermediateComplete(matchedHop, event.txHash);
1734
- }
1735
- else {
1736
- emitHopEvent(eventHooks.success(matchedHop.outboundDetails));
1789
+ if (!deferHopCompletion) {
1790
+ if (isMulti) {
1791
+ emitRouteIntermediateComplete(matchedHop, event.txHash);
1792
+ }
1793
+ else {
1794
+ emitHopEvent(eventHooks.success(matchedHop.outboundDetails));
1795
+ }
1796
+ emitHopComplete(event.hopIndex);
1737
1797
  }
1738
- emitHopComplete(event.hopIndex);
1739
1798
  }
1740
1799
  }
1741
- },
1742
- });
1800
+ } }));
1743
1801
  if (!allOutboundDetails.success) {
1744
1802
  const failedIdx = (_c = allOutboundDetails.failedAt) !== null && _c !== void 0 ? _c : 0;
1745
1803
  const failedHop = outboundHops.find((h) => h.hopIndex === failedIdx);
@@ -1750,6 +1808,14 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1750
1808
  emitCascadeFailed(failedIdx, failMsg);
1751
1809
  return buildCompletionResult(false, failedIdx);
1752
1810
  }
1811
+ if (trackingOptions === null || trackingOptions === void 0 ? void 0 : trackingOptions.deferCeaToPushCompletion) {
1812
+ for (const outHop of outboundHops) {
1813
+ if (shouldDeferHopCompletion(outHop) &&
1814
+ outHop.outboundDetails) {
1815
+ outHop.status = 'pending';
1816
+ }
1817
+ }
1818
+ }
1753
1819
  }
1754
1820
  return null;
1755
1821
  });
@@ -1774,7 +1840,8 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1774
1840
  }
1775
1841
  }
1776
1842
  for (const hop of hopInfos) {
1777
- if (hop.route === 'UOA_TO_PUSH' && !isAfterCeaToPush(hop.hopIndex)) {
1843
+ if (hop.route === 'UOA_TO_PUSH' &&
1844
+ !isAfterCeaToPush(hop.hopIndex)) {
1778
1845
  hop.status = 'confirmed';
1779
1846
  hop.txHash = response.hash;
1780
1847
  cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
@@ -1790,8 +1857,14 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1790
1857
  }
1791
1858
  // 2. Track direct outbound hops (Route 2: UOA_TO_CEA) that belong
1792
1859
  // to the root Push UTX.
1793
- const rootOutboundHops = hopInfos.filter((h) => h.route === 'UOA_TO_CEA' && !isAfterCeaToPush(h.hopIndex));
1794
- const rootOutboundFailure = yield trackOutboundHops(rootOutboundHops, response.hash, undefined, rootOutboundIndexByHop);
1860
+ const rootOutboundHops = hopInfos.filter((h) => {
1861
+ var _a;
1862
+ return (h.route === 'UOA_TO_CEA' && !isAfterCeaToPush(h.hopIndex)) ||
1863
+ (h.hopIndex === ceaToPushIndex &&
1864
+ h.route === 'CEA_TO_PUSH' &&
1865
+ ((_a = chain_1.CHAIN_INFO[h.executionChain]) === null || _a === void 0 ? void 0 : _a.vm) === enums_1.VM.EVM);
1866
+ });
1867
+ const rootOutboundFailure = yield trackOutboundHops(rootOutboundHops, response.hash, undefined, rootOutboundIndexByHop, { deferCeaToPushCompletion: true });
1795
1868
  if (rootOutboundFailure)
1796
1869
  return rootOutboundFailure;
1797
1870
  let childInbound;
@@ -1805,39 +1878,50 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1805
1878
  emitCascadeTimeout(inboundHop.hopIndex);
1806
1879
  return buildCompletionResult(false, inboundHop.hopIndex);
1807
1880
  }
1808
- emitHopStart(inboundHop.hopIndex);
1809
- emitHopEvent(hopHooks.awaiting(inboundHop.executionChain));
1881
+ const hasTrackedSourceOutbound = Boolean(inboundHop.outboundDetails) &&
1882
+ ((_d = chain_1.CHAIN_INFO[inboundHop.executionChain]) === null || _d === void 0 ? void 0 : _d.vm) === enums_1.VM.EVM;
1883
+ if (!hasTrackedSourceOutbound) {
1884
+ emitHopStart(inboundHop.hopIndex);
1885
+ emitHopEvent(hopHooks.awaiting(inboundHop.executionChain));
1886
+ }
1810
1887
  cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1811
1888
  hopIndex: inboundHop.hopIndex,
1812
1889
  route: inboundHop.route,
1813
1890
  chain: inboundHop.executionChain,
1814
1891
  status: 'polling',
1892
+ txHash: inboundHop.txHash,
1815
1893
  elapsed: Date.now() - startTime,
1816
1894
  });
1817
1895
  let outboundLastStatus;
1818
1896
  let sourceOutboundDetails;
1819
1897
  try {
1820
- sourceOutboundDetails = yield callbacks.waitForOutboundTxFn(response.hash, {
1821
- initialWaitMs: Math.min(60000, remainingTimeout),
1822
- pollingIntervalMs,
1823
- timeout: remainingTimeout,
1824
- _expectedDestinationChain: inboundHop.executionChain,
1825
- _outboundIndex: (_d = rootOutboundIndexByHop.get(inboundHop.hopIndex)) !== null && _d !== void 0 ? _d : 0,
1826
- progressHook: (event) => {
1827
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1828
- hopIndex: inboundHop.hopIndex,
1829
- route: inboundHop.route,
1830
- chain: inboundHop.executionChain,
1831
- status: event.status,
1832
- elapsed: Date.now() - startTime,
1833
- });
1834
- if (event.status === 'polling' &&
1835
- outboundLastStatus !== 'polling') {
1836
- outboundLastStatus = 'polling';
1837
- emitHopEvent(hopHooks.polling(inboundHop.executionChain, event.elapsed));
1838
- }
1839
- },
1840
- });
1898
+ if (hasTrackedSourceOutbound) {
1899
+ sourceOutboundDetails =
1900
+ inboundHop.outboundDetails;
1901
+ }
1902
+ else {
1903
+ sourceOutboundDetails = yield callbacks.waitForOutboundTxFn(response.hash, {
1904
+ initialWaitMs: Math.min(60000, remainingTimeout),
1905
+ pollingIntervalMs,
1906
+ timeout: remainingTimeout,
1907
+ _expectedDestinationChain: inboundHop.executionChain,
1908
+ _outboundIndex: (_e = rootOutboundIndexByHop.get(inboundHop.hopIndex)) !== null && _e !== void 0 ? _e : 0,
1909
+ progressHook: (event) => {
1910
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1911
+ hopIndex: inboundHop.hopIndex,
1912
+ route: inboundHop.route,
1913
+ chain: inboundHop.executionChain,
1914
+ status: event.status,
1915
+ elapsed: Date.now() - startTime,
1916
+ });
1917
+ if (event.status === 'polling' &&
1918
+ outboundLastStatus !== 'polling') {
1919
+ outboundLastStatus = 'polling';
1920
+ emitHopEvent(hopHooks.polling(inboundHop.executionChain, event.elapsed));
1921
+ }
1922
+ },
1923
+ });
1924
+ }
1841
1925
  }
1842
1926
  catch (err) {
1843
1927
  const errMsg = err instanceof Error ? err.message : String(err);
@@ -1851,7 +1935,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1851
1935
  emitCascadeFailed(inboundHop.hopIndex, errMsg);
1852
1936
  inboundHop.status = 'failed';
1853
1937
  const failedExternalTxHash = err === null || err === void 0 ? void 0 : err.externalTxHash;
1854
- const failedDisplayTxHash = (_e = (0, helpers_1.toExternalTxHashDisplay)(inboundHop.executionChain, failedExternalTxHash)) !== null && _e !== void 0 ? _e : failedExternalTxHash;
1938
+ const failedDisplayTxHash = (_f = (0, helpers_1.toExternalTxHashDisplay)(inboundHop.executionChain, failedExternalTxHash)) !== null && _f !== void 0 ? _f : failedExternalTxHash;
1855
1939
  if (failedDisplayTxHash)
1856
1940
  inboundHop.txHash = failedDisplayTxHash;
1857
1941
  cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
@@ -1864,7 +1948,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1864
1948
  });
1865
1949
  return buildCompletionResult(false, inboundHop.hopIndex);
1866
1950
  }
1867
- const displayExternalTxHash = (_f = (0, helpers_1.toExternalTxHashDisplay)(sourceOutboundDetails.destinationChain, sourceOutboundDetails.externalTxHash)) !== null && _f !== void 0 ? _f : sourceOutboundDetails.externalTxHash;
1951
+ const displayExternalTxHash = (_g = (0, helpers_1.toExternalTxHashDisplay)(sourceOutboundDetails.destinationChain, sourceOutboundDetails.externalTxHash)) !== null && _g !== void 0 ? _g : sourceOutboundDetails.externalTxHash;
1868
1952
  inboundHop.outboundDetails = Object.assign(Object.assign({}, sourceOutboundDetails), { externalTxHash: displayExternalTxHash });
1869
1953
  inboundHop.txHash = displayExternalTxHash;
1870
1954
  emitHopEvent(hopHooks.success(inboundHop.outboundDetails));
@@ -1872,7 +1956,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1872
1956
  let inboundLastStatus;
1873
1957
  const inboundRemainingTimeout = timeout - (Date.now() - startTime);
1874
1958
  try {
1875
- childInbound = yield ((_g = callbacks.waitForInboundPushTxFn) !== null && _g !== void 0 ? _g : ((externalTxHash, sourceChain, opts) => (0, inbound_tracker_1.waitForInboundPushTx)(ctx, externalTxHash, sourceChain, opts)))(sourceOutboundDetails.externalTxHash, inboundHop.executionChain, {
1959
+ childInbound = yield ((_h = callbacks.waitForInboundPushTxFn) !== null && _h !== void 0 ? _h : ((externalTxHash, sourceChain, opts) => (0, inbound_tracker_1.waitForInboundPushTx)(ctx, externalTxHash, sourceChain, opts)))(sourceOutboundDetails.externalTxHash, inboundHop.executionChain, {
1876
1960
  initialWaitMs: Math.min(60000, inboundRemainingTimeout),
1877
1961
  pollingIntervalMs,
1878
1962
  timeout: inboundRemainingTimeout,
@@ -1906,7 +1990,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1906
1990
  return buildCompletionResult(false, inboundHop.hopIndex);
1907
1991
  }
1908
1992
  if (childInbound.status !== 'confirmed') {
1909
- const failMsg = (_h = childInbound.errorMessage) !== null && _h !== void 0 ? _h : 'inbound execution failed on Push Chain';
1993
+ const failMsg = (_j = childInbound.errorMessage) !== null && _j !== void 0 ? _j : 'inbound execution failed on Push Chain';
1910
1994
  inboundHop.status = 'failed';
1911
1995
  inboundHop.txHash =
1912
1996
  childInbound.pushTxHash || displayExternalTxHash;
@@ -15,6 +15,39 @@ const enums_1 = require("../../constants/enums");
15
15
  const progress_hook_types_1 = require("../../progress-hook/progress-hook.types");
16
16
  const svm_client_1 = require("../../vm-client/svm-client");
17
17
  const context_2 = require("./context");
18
+ const EVM_RECEIPT_POLL_MS = 3000;
19
+ const EVM_RECEIPT_NOT_FOUND_RE = /not found|TransactionNotFound|TransactionReceiptNotFound|could not be found|No transaction found/i;
20
+ function getErrorMessage(err) {
21
+ var _a, _b;
22
+ if (err instanceof Error)
23
+ return err.message;
24
+ if (typeof err === 'object' && err !== null) {
25
+ const maybe = err;
26
+ return String((_b = (_a = maybe.shortMessage) !== null && _a !== void 0 ? _a : maybe.message) !== null && _b !== void 0 ? _b : err);
27
+ }
28
+ return String(err);
29
+ }
30
+ function waitForEvmReceipt(evmClient, txHash, timeoutMs) {
31
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
32
+ const start = Date.now();
33
+ // eslint-disable-next-line no-constant-condition
34
+ while (true) {
35
+ try {
36
+ return yield evmClient.publicClient.getTransactionReceipt({
37
+ hash: txHash,
38
+ });
39
+ }
40
+ catch (err) {
41
+ const msg = getErrorMessage(err);
42
+ const retryable = EVM_RECEIPT_NOT_FOUND_RE.test(msg);
43
+ if (!retryable || Date.now() - start > timeoutMs) {
44
+ throw err;
45
+ }
46
+ yield new Promise((r) => setTimeout(r, EVM_RECEIPT_POLL_MS));
47
+ }
48
+ }
49
+ });
50
+ }
18
51
  function waitForEvmConfirmationsWithCountdown(ctx, evmClient, txHash, confirmations, timeoutMs) {
19
52
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
20
53
  if (confirmations <= 0) {
@@ -23,9 +56,7 @@ function waitForEvmConfirmationsWithCountdown(ctx, evmClient, txHash, confirmati
23
56
  }
24
57
  (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_106_03, confirmations);
25
58
  const start = Date.now();
26
- const receipt = yield evmClient.publicClient.waitForTransactionReceipt({
27
- hash: txHash,
28
- });
59
+ const receipt = yield waitForEvmReceipt(evmClient, txHash, timeoutMs);
29
60
  const targetBlock = receipt.blockNumber + BigInt(confirmations);
30
61
  let lastEmitted = 0;
31
62
  // eslint-disable-next-line no-constant-condition
@@ -112,9 +143,7 @@ function waitForLockerFeeConfirmation(ctx, txHashBytes) {
112
143
  case enums_1.VM.EVM: {
113
144
  const evmClient = (0, context_1.getOriginEvmClient)(ctx);
114
145
  const txHash = (0, viem_1.bytesToHex)(txHashBytes);
115
- const receipt = yield evmClient.publicClient.waitForTransactionReceipt({
116
- hash: txHash,
117
- });
146
+ const receipt = yield waitForEvmReceipt(evmClient, txHash, timeout);
118
147
  if (fastConfirmations <= 1)
119
148
  return;
120
149
  const targetBlock = receipt.blockNumber + BigInt(fastConfirmations);