@pushchain/core 5.1.17 → 6.0.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 (68) hide show
  1. package/package.json +1 -1
  2. package/src/lib/constants/abi/cea.evm.d.ts +52 -0
  3. package/src/lib/constants/abi/cea.evm.js +66 -1
  4. package/src/lib/constants/abi/prc20.evm.d.ts +117 -11
  5. package/src/lib/constants/abi/prc20.evm.js +38 -11
  6. package/src/lib/constants/abi/uea.evm.js +41 -0
  7. package/src/lib/constants/abi/universalGatewayPC.evm.d.ts +117 -1
  8. package/src/lib/constants/abi/universalGatewayPC.evm.js +40 -1
  9. package/src/lib/constants/abi/universalGatewayV0.json +1 -1
  10. package/src/lib/constants/chain.js +14 -14
  11. package/src/lib/constants/index.d.ts +1 -1
  12. package/src/lib/constants/index.js +2 -1
  13. package/src/lib/constants/selectors.d.ts +8 -0
  14. package/src/lib/constants/selectors.js +9 -1
  15. package/src/lib/constants/tokens.js +1 -0
  16. package/src/lib/formatters.d.ts +7 -0
  17. package/src/lib/formatters.js +46 -0
  18. package/src/lib/index.d.ts +2 -0
  19. package/src/lib/index.js +7 -1
  20. package/src/lib/orchestrator/internals/cascade.d.ts +8 -1
  21. package/src/lib/orchestrator/internals/cascade.js +593 -192
  22. package/src/lib/orchestrator/internals/error-decoder.d.ts +2 -6
  23. package/src/lib/orchestrator/internals/error-decoder.js +10 -10
  24. package/src/lib/orchestrator/internals/errors.d.ts +1 -1
  25. package/src/lib/orchestrator/internals/errors.js +8 -5
  26. package/src/lib/orchestrator/internals/execute-funds-only.js +3 -2
  27. package/src/lib/orchestrator/internals/execute-funds-payload.js +2 -1
  28. package/src/lib/orchestrator/internals/execute-standard.js +11 -10
  29. package/src/lib/orchestrator/internals/gas-calculator.d.ts +1 -0
  30. package/src/lib/orchestrator/internals/gas-calculator.js +91 -22
  31. package/src/lib/orchestrator/internals/helpers.d.ts +2 -1
  32. package/src/lib/orchestrator/internals/helpers.js +14 -11
  33. package/src/lib/orchestrator/internals/inbound-tracker.js +4 -2
  34. package/src/lib/orchestrator/internals/outbound-sync.d.ts +1 -0
  35. package/src/lib/orchestrator/internals/outbound-sync.js +140 -71
  36. package/src/lib/orchestrator/internals/pc-usd-oracle.d.ts +1 -1
  37. package/src/lib/orchestrator/internals/pc-usd-oracle.js +4 -3
  38. package/src/lib/orchestrator/internals/preflight.d.ts +2 -16
  39. package/src/lib/orchestrator/internals/preflight.js +2 -36
  40. package/src/lib/orchestrator/internals/push-chain-tx.js +5 -1
  41. package/src/lib/orchestrator/internals/response-builder.js +23 -7
  42. package/src/lib/orchestrator/internals/route-handlers.js +182 -227
  43. package/src/lib/orchestrator/internals/signing.js +7 -2
  44. package/src/lib/orchestrator/internals/svm-helpers.js +3 -3
  45. package/src/lib/orchestrator/internals/svm-rent.d.ts +42 -9
  46. package/src/lib/orchestrator/internals/svm-rent.js +148 -14
  47. package/src/lib/orchestrator/internals/tx-transformer.js +5 -2
  48. package/src/lib/orchestrator/max-pc-for-gas.d.ts +60 -0
  49. package/src/lib/orchestrator/max-pc-for-gas.js +66 -0
  50. package/src/lib/orchestrator/orchestrator.d.ts +8 -1
  51. package/src/lib/orchestrator/orchestrator.js +40 -2
  52. package/src/lib/orchestrator/orchestrator.types.d.ts +35 -23
  53. package/src/lib/orchestrator/payload-builders.d.ts +7 -6
  54. package/src/lib/orchestrator/payload-builders.js +18 -6
  55. package/src/lib/progress-hook/progress-hook.js +39 -38
  56. package/src/lib/push-chain/push-chain.d.ts +11 -1
  57. package/src/lib/push-chain/push-chain.js +2 -2
  58. package/src/lib/universal-tx-detector/cascade.js +14 -4
  59. package/src/lib/universal-tx-detector/child-inbounds.js +9 -1
  60. package/src/lib/universal-tx-detector/detector.js +9 -3
  61. package/src/lib/universal-tx-detector/events.d.ts +3 -1
  62. package/src/lib/universal-tx-detector/events.js +3 -1
  63. package/src/lib/universal-tx-detector/normalize.d.ts +42 -0
  64. package/src/lib/universal-tx-detector/normalize.js +127 -0
  65. package/src/lib/universal-tx-detector/svm-events.d.ts +1 -1
  66. package/src/lib/universal-tx-detector/svm-events.js +1 -1
  67. package/src/lib/utils/external-tx-hash.d.ts +37 -0
  68. package/src/lib/utils/external-tx-hash.js +62 -0
@@ -11,11 +11,11 @@ exports.waitForOutboundTx = waitForOutboundTx;
11
11
  exports.waitForAllOutboundTxsV2 = waitForAllOutboundTxsV2;
12
12
  const tslib_1 = require("tslib");
13
13
  const viem_1 = require("viem");
14
- const bs58_1 = require("../../internal/bs58");
15
14
  const chain_1 = require("../../constants/chain");
16
15
  const enums_1 = require("../../constants/enums");
17
16
  const types_1 = require("../../generated/uexecutor/v1/types");
18
17
  const types_2 = require("../../generated/uexecutor/v2/types");
18
+ const formatters_1 = require("../../formatters");
19
19
  const context_1 = require("./context");
20
20
  const helpers_1 = require("./helpers");
21
21
  const outbound_tracker_1 = require("./outbound-tracker");
@@ -142,8 +142,8 @@ function verifyExternalEvmReceipt(ctx, destinationChain, externalTxHash) {
142
142
  */
143
143
  function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
144
144
  return tslib_1.__awaiter(this, arguments, void 0, function* (ctx, pushChainTxHash, options = {}) {
145
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
146
- const { initialWaitMs = exports.OUTBOUND_INITIAL_WAIT_MS, pollingIntervalMs = exports.OUTBOUND_POLL_INTERVAL_MS, timeout = exports.OUTBOUND_MAX_TIMEOUT_MS, progressHook, _resolvedSubTxId, _expectedDestinationChain, } = options;
145
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
146
+ const { initialWaitMs = exports.OUTBOUND_INITIAL_WAIT_MS, pollingIntervalMs = exports.OUTBOUND_POLL_INTERVAL_MS, timeout = exports.OUTBOUND_MAX_TIMEOUT_MS, progressHook, _resolvedSubTxId, _expectedDestinationChain, _outboundIndex, } = options;
147
147
  // Terminal failure states — fail fast instead of polling until timeout
148
148
  const TERMINAL_FAILURE_STATES = new Set([
149
149
  types_1.UniversalTxStatus.OUTBOUND_FAILED,
@@ -195,20 +195,54 @@ function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
195
195
  const statusNum = (_b = utxResponse === null || utxResponse === void 0 ? void 0 : utxResponse.universalTx) === null || _b === void 0 ? void 0 : _b.universalStatus;
196
196
  const statusName = (_c = types_1.UniversalTxStatus[statusNum]) !== null && _c !== void 0 ? _c : statusNum;
197
197
  const outbounds = ((_d = utxResponse === null || utxResponse === void 0 ? void 0 : utxResponse.universalTx) === null || _d === void 0 ? void 0 : _d.outboundTx) || [];
198
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} | status: ${statusNum} (${statusName}) | outboundTx count: ${outbounds.length} | first txHash: '${((_f = (_e = outbounds[0]) === null || _e === void 0 ? void 0 : _e.observedTx) === null || _f === void 0 ? void 0 : _f.txHash) || ''}' | first dest: '${((_g = outbounds[0]) === null || _g === void 0 ? void 0 : _g.destinationChain) || ''}'`);
198
+ const firstOutbound = outbounds[0];
199
+ const firstOutboundChain = (firstOutbound === null || firstOutbound === void 0 ? void 0 : firstOutbound.destinationChain)
200
+ ? (0, helpers_1.chainFromNamespace)(firstOutbound.destinationChain)
201
+ : undefined;
202
+ const firstDisplayTxHash = (_h = (_f = (0, helpers_1.toExternalTxHashDisplay)(firstOutboundChain !== null && firstOutboundChain !== void 0 ? firstOutboundChain : undefined, (_e = firstOutbound === null || firstOutbound === void 0 ? void 0 : firstOutbound.observedTx) === null || _e === void 0 ? void 0 : _e.txHash)) !== null && _f !== void 0 ? _f : (_g = firstOutbound === null || firstOutbound === void 0 ? void 0 : firstOutbound.observedTx) === null || _g === void 0 ? void 0 : _g.txHash) !== null && _h !== void 0 ? _h : '';
203
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} | status: ${statusNum} (${statusName}) | outboundTx count: ${outbounds.length} | first txHash: '${firstDisplayTxHash}' | first dest: '${(firstOutbound === null || firstOutbound === void 0 ? void 0 : firstOutbound.destinationChain) || ''}'`);
199
204
  // Check for terminal failure states — fail fast
200
205
  if (TERMINAL_FAILURE_STATES.has(statusNum)) {
201
206
  (0, context_1.printLog)(ctx, `[waitForOutboundTx] Terminal failure state: ${statusName}`);
202
207
  progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'failed', elapsed: Date.now() - startTime });
203
208
  throw new OutboundFailedError(`Outbound transaction failed with status ${statusName}. Push Chain TX: ${pushChainTxHash}.`, pushChainTxHash);
204
209
  }
205
- // Iterate V2 outbound array
210
+ const pcTxs = ((_j = utxResponse === null || utxResponse === void 0 ? void 0 : utxResponse.universalTx) === null || _j === void 0 ? void 0 : _j.pcTx) || [];
211
+ const failedPcTx = pcTxs.find((pcTx) => {
212
+ const status = String(pcTx.status || '').toUpperCase();
213
+ return status === 'FAILED' || Boolean(pcTx.errorMsg);
214
+ });
215
+ if (failedPcTx) {
216
+ const failedTxHash = failedPcTx.txHash || pushChainTxHash;
217
+ const reason = failedPcTx.errorMsg
218
+ ? (0, formatters_1.normalizePcInsufficientFundsError)(failedPcTx.errorMsg)
219
+ : 'Push Chain execution failed';
220
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Push Chain execution failed before outbound emission: ${reason}`);
221
+ progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'failed', elapsed: Date.now() - startTime });
222
+ throw new OutboundFailedError(`Push Chain execution failed before outbound emission: ${reason}. Push Chain TX: ${failedTxHash}.`, pushChainTxHash, undefined, failedPcTx.txHash || undefined);
223
+ }
224
+ // Iterate V2 outbound array. For cascade tracking, multiple outbound
225
+ // entries can target the same destination chain (for example R2 BSC
226
+ // followed by R3 BSC -> Push). `_outboundIndex` disambiguates those
227
+ // same-chain entries by order after `_expectedDestinationChain` filtering.
228
+ let matchingOutboundIndex = 0;
206
229
  for (const ob of outbounds) {
230
+ // If a destination chain filter is set, skip outbound entries that don't match.
231
+ if (_expectedDestinationChain && ob.destinationChain !== _expectedDestinationChain) {
232
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} | outbound chain '${ob.destinationChain}' does not match expected '${_expectedDestinationChain}', skipping`);
233
+ continue;
234
+ }
235
+ const currentOutboundIndex = matchingOutboundIndex;
236
+ matchingOutboundIndex += 1;
237
+ if (_outboundIndex !== undefined && currentOutboundIndex !== _outboundIndex) {
238
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} | outbound index ${currentOutboundIndex} for '${ob.destinationChain}' does not match expected index ${_outboundIndex}, skipping`);
239
+ continue;
240
+ }
207
241
  // Fail fast on per-outbound REVERTED status
208
242
  if (ob.outboundStatus === types_2.OutboundStatus.REVERTED) {
209
243
  (0, context_1.printLog)(ctx, `[waitForOutboundTx] Outbound to ${ob.destinationChain} REVERTED`);
210
244
  progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'failed', elapsed: Date.now() - startTime });
211
- throw new OutboundFailedError(`Outbound to ${ob.destinationChain} reverted: ${((_h = ob.observedTx) === null || _h === void 0 ? void 0 : _h.errorMsg) || 'Unknown'}. Push Chain TX: ${pushChainTxHash}.`, pushChainTxHash, ob.destinationChain, (_j = ob.observedTx) === null || _j === void 0 ? void 0 : _j.txHash);
245
+ throw new OutboundFailedError(`Outbound to ${ob.destinationChain} reverted: ${((_k = ob.observedTx) === null || _k === void 0 ? void 0 : _k.errorMsg) || 'Unknown'}. Push Chain TX: ${pushChainTxHash}.`, pushChainTxHash, ob.destinationChain, (_l = ob.observedTx) === null || _l === void 0 ? void 0 : _l.txHash);
212
246
  }
213
247
  // FOUND gating logic:
214
248
  // 1. Primary path — cosmos has transitioned to OBSERVED (the
@@ -218,12 +252,7 @@ function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
218
252
  // destination chain's own RPC: if its receipt is success,
219
253
  // treat as FOUND; if reverted, throw OutboundFailedError.
220
254
  // Pending receipts keep polling.
221
- if ((_k = ob.observedTx) === null || _k === void 0 ? void 0 : _k.txHash) {
222
- // If a destination chain filter is set, skip outbound entries that don't match
223
- if (_expectedDestinationChain && ob.destinationChain !== _expectedDestinationChain) {
224
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} | outbound chain '${ob.destinationChain}' does not match expected '${_expectedDestinationChain}', skipping`);
225
- continue;
226
- }
255
+ if ((_m = ob.observedTx) === null || _m === void 0 ? void 0 : _m.txHash) {
227
256
  const chain = (0, helpers_1.chainFromNamespace)(ob.destinationChain);
228
257
  if (!chain)
229
258
  continue;
@@ -232,7 +261,7 @@ function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
232
261
  // Tiebreaker: ask the destination-chain RPC directly.
233
262
  const verdict = yield verifyExternalEvmReceipt(ctx, chain, ob.observedTx.txHash);
234
263
  if (verdict === 'reverted') {
235
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Source-chain RPC reports REVERTED for ${ob.destinationChain} tx ${ob.observedTx.txHash} (cosmos still ${(_l = types_1.UniversalTxStatus[statusNum]) !== null && _l !== void 0 ? _l : statusNum})`);
264
+ (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})`);
236
265
  progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'failed', elapsed: Date.now() - startTime });
237
266
  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);
238
267
  }
@@ -240,24 +269,26 @@ function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
240
269
  // 'pending' or 'unsupported' — keep polling, cosmos will catch up.
241
270
  continue;
242
271
  }
243
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Source-chain RPC tiebreaker: ${chain} tx ${ob.observedTx.txHash} succeeded; treating as FOUND despite cosmos status ${(_m = types_1.UniversalTxStatus[statusNum]) !== null && _m !== void 0 ? _m : statusNum}`);
272
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Source-chain RPC tiebreaker: ${chain} tx ${ob.observedTx.txHash} succeeded; treating as FOUND despite cosmos status ${(_p = types_1.UniversalTxStatus[statusNum]) !== null && _p !== void 0 ? _p : statusNum}`);
244
273
  }
245
274
  {
246
- const explorerBaseUrl = (_o = chain_1.CHAIN_INFO[chain]) === null || _o === void 0 ? void 0 : _o.explorerUrl;
247
- const isSvm = ((_p = chain_1.CHAIN_INFO[chain]) === null || _p === void 0 ? void 0 : _p.vm) === enums_1.VM.SVM;
248
- // For SVM chains, convert hex txHash to base58 and append cluster param
249
- let displayTxHash = ob.observedTx.txHash;
275
+ const explorerBaseUrl = (_q = chain_1.CHAIN_INFO[chain]) === null || _q === void 0 ? void 0 : _q.explorerUrl;
276
+ const isSvm = ((_r = chain_1.CHAIN_INFO[chain]) === null || _r === void 0 ? void 0 : _r.vm) === enums_1.VM.SVM;
277
+ const displayTxHash = (_s = (0, helpers_1.toExternalTxHashDisplay)(chain, ob.observedTx.txHash)) !== null && _s !== void 0 ? _s : ob.observedTx.txHash;
250
278
  let explorerUrl = '';
251
- if (isSvm && ob.observedTx.txHash.startsWith('0x')) {
252
- const bytes = new Uint8Array(Buffer.from(ob.observedTx.txHash.slice(2), 'hex'));
253
- displayTxHash = bs58_1.bs58.encode(Buffer.from(bytes));
279
+ if (isSvm) {
254
280
  const cluster = chain === enums_1.CHAIN.SOLANA_DEVNET ? '?cluster=devnet'
255
281
  : chain === enums_1.CHAIN.SOLANA_TESTNET ? '?cluster=testnet' : '';
256
282
  explorerUrl = explorerBaseUrl ? `${explorerBaseUrl}/tx/${displayTxHash}${cluster}` : '';
257
283
  }
258
284
  else {
259
- explorerUrl = explorerBaseUrl ? `${explorerBaseUrl}/tx/${ob.observedTx.txHash}` : '';
285
+ explorerUrl = explorerBaseUrl ? `${explorerBaseUrl}/tx/${displayTxHash}` : '';
260
286
  }
287
+ // OutboundTxDetails carries the raw `0x`-hex form so internal
288
+ // consumers (R3 inbound tracker in response-builder.ts:786 →
289
+ // findChildUtxIdFromExternalTx → Cosmos query) keep working.
290
+ // The base58 display form is applied at every user-facing
291
+ // boundary via `toExternalTxHashDisplay` (helpers.ts).
261
292
  const details = {
262
293
  externalTxHash: ob.observedTx.txHash,
263
294
  destinationChain: chain,
@@ -266,7 +297,7 @@ function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
266
297
  amount: ob.amount,
267
298
  assetAddr: ob.externalAssetAddr,
268
299
  };
269
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] FOUND on poll #${pollCount} | elapsed: ${Date.now() - startTime}ms | externalTxHash: ${details.externalTxHash}`);
300
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] FOUND on poll #${pollCount} | elapsed: ${Date.now() - startTime}ms | externalTxHash: ${displayTxHash}`);
270
301
  progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'found', elapsed: Date.now() - startTime });
271
302
  return details;
272
303
  }
@@ -302,8 +333,8 @@ function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
302
333
  */
303
334
  function waitForAllOutboundTxsV2(ctx, pushChainTxHash, outboundHops, options) {
304
335
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
305
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
306
- const { initialWaitMs, pollingIntervalMs, timeout, progressHook } = options;
336
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
337
+ const { initialWaitMs, pollingIntervalMs, timeout, progressHook, _resolvedSubTxId, } = options;
307
338
  const startTime = Date.now();
308
339
  // Build a map: CAIP-2 namespace -> hop(s) for matching outbound entries
309
340
  const chainToHops = new Map();
@@ -341,7 +372,11 @@ function waitForAllOutboundTxsV2(ctx, pushChainTxHash, outboundHops, options) {
341
372
  });
342
373
  }
343
374
  // Extract sub-tx ID for V2 query
344
- let cachedQueryId;
375
+ let cachedQueryId = _resolvedSubTxId
376
+ ? _resolvedSubTxId.startsWith('0x')
377
+ ? _resolvedSubTxId.slice(2)
378
+ : _resolvedSubTxId
379
+ : undefined;
345
380
  let pollCount = 0;
346
381
  let consecutiveErrors = 0;
347
382
  const MAX_CONSECUTIVE_ERRORS = 10;
@@ -364,67 +399,101 @@ function waitForAllOutboundTxsV2(ctx, pushChainTxHash, outboundHops, options) {
364
399
  const statusName = (_a = types_1.UniversalTxStatus[statusNum]) !== null && _a !== void 0 ? _a : statusNum;
365
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}`);
366
401
  if ((_d = utx === null || utx === void 0 ? void 0 : utx.outboundTx) === null || _d === void 0 ? void 0 : _d.length) {
402
+ const outboundOrdinalByChain = new Map();
367
403
  for (const ob of utx.outboundTx) {
368
404
  const destChain = ob.destinationChain;
369
405
  const hopsForChain = chainToHops.get(destChain);
370
406
  if (!hopsForChain)
371
407
  continue;
372
- const unconfirmedForChain = hopsForChain.filter((h) => h.status !== 'confirmed' && h.status !== 'failed');
373
- if (unconfirmedForChain.length === 0)
408
+ const outboundOrdinal = (_e = outboundOrdinalByChain.get(destChain)) !== null && _e !== void 0 ? _e : 0;
409
+ outboundOrdinalByChain.set(destChain, outboundOrdinal + 1);
410
+ const targetHop = hopsForChain[outboundOrdinal];
411
+ if (!targetHop ||
412
+ targetHop.status === 'confirmed' ||
413
+ targetHop.status === 'failed') {
374
414
  continue;
375
- // Fail fast on per-outbound REVERTED
376
- if (ob.outboundStatus === types_2.OutboundStatus.REVERTED) {
377
- for (const hop of unconfirmedForChain) {
378
- hop.status = 'failed';
379
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Outbound to ${destChain} REVERTED | hop ${hop.hopIndex} | error: ${((_e = ob.observedTx) === null || _e === void 0 ? void 0 : _e.errorMsg) || 'Unknown'}`);
380
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({
381
- hopIndex: hop.hopIndex,
382
- route: hop.route,
383
- chain: hop.executionChain,
384
- status: 'failed',
385
- });
386
- }
387
- return { success: false, failedAt: unconfirmedForChain[0].hopIndex };
388
415
  }
389
- // Check for OBSERVED with txHash
390
416
  const externalTxHash = (_f = ob.observedTx) === null || _f === void 0 ? void 0 : _f.txHash;
391
- if (externalTxHash && (ob.outboundStatus === types_2.OutboundStatus.OBSERVED || ob.outboundStatus === 0)) {
417
+ const hasObservedTxHash = !!externalTxHash && externalTxHash !== 'EMPTY';
418
+ 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) ||
421
+ ob.abortReason ||
422
+ ob.outboundStatus >= types_2.OutboundStatus.REVERTED);
423
+ // 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 };
434
+ }
435
+ // Check for observed tx hash. Cosmos may lag the status transition,
436
+ // so EVM destinations are verified against the destination receipt.
437
+ if (hasObservedTxHash && externalTxHash) {
392
438
  const chain = (0, helpers_1.chainFromNamespace)(destChain);
439
+ if (!chain)
440
+ continue;
441
+ const isCosmosObserved = ob.outboundStatus === types_2.OutboundStatus.OBSERVED;
442
+ if (!isCosmosObserved) {
443
+ const verdict = yield verifyExternalEvmReceipt(ctx, chain, externalTxHash);
444
+ 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 };
454
+ }
455
+ if (verdict !== 'success') {
456
+ continue;
457
+ }
458
+ }
459
+ // Build the display form for SVM (base58) used in `hop.txHash` /
460
+ // `hop.outboundDetails.externalTxHash` / the progressHook event —
461
+ // these are USER-FACING. The internal raw-hex form stays only
462
+ // inside this function; it's not exposed to consumers from the
463
+ // cascade path (cascade.ts doesn't call findChildUtxIdFromExternalTx
464
+ // 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;
393
466
  let explorerUrl = '';
394
467
  if (chain && externalTxHash) {
395
- const explorerBaseUrl = (_g = chain_1.CHAIN_INFO[chain]) === null || _g === void 0 ? void 0 : _g.explorerUrl;
396
- const isSvm = ((_h = chain_1.CHAIN_INFO[chain]) === null || _h === void 0 ? void 0 : _h.vm) === enums_1.VM.SVM;
397
- if (isSvm && externalTxHash.startsWith('0x')) {
398
- const bytes = new Uint8Array(Buffer.from(externalTxHash.slice(2), 'hex'));
399
- const base58Hash = bs58_1.bs58.encode(Buffer.from(bytes));
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;
470
+ if (isSvm) {
400
471
  const cluster = chain === enums_1.CHAIN.SOLANA_DEVNET ? '?cluster=devnet'
401
472
  : chain === enums_1.CHAIN.SOLANA_TESTNET ? '?cluster=testnet' : '';
402
- explorerUrl = explorerBaseUrl ? `${explorerBaseUrl}/tx/${base58Hash}${cluster}` : '';
473
+ explorerUrl = explorerBaseUrl ? `${explorerBaseUrl}/tx/${displayTxHash}${cluster}` : '';
403
474
  }
404
475
  else {
405
- explorerUrl = explorerBaseUrl ? `${explorerBaseUrl}/tx/${externalTxHash}` : '';
476
+ explorerUrl = explorerBaseUrl ? `${explorerBaseUrl}/tx/${displayTxHash}` : '';
406
477
  }
407
478
  }
408
- for (const hop of unconfirmedForChain) {
409
- hop.status = 'confirmed';
410
- hop.txHash = externalTxHash;
411
- hop.outboundDetails = {
412
- externalTxHash,
413
- destinationChain: chain || hop.executionChain,
414
- explorerUrl,
415
- recipient: ob.recipient,
416
- amount: ob.amount,
417
- assetAddr: ob.externalAssetAddr,
418
- };
419
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] FOUND outbound for ${destChain} | hop ${hop.hopIndex} | externalTxHash: ${externalTxHash}`);
420
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({
421
- hopIndex: hop.hopIndex,
422
- route: hop.route,
423
- chain: hop.executionChain,
424
- status: 'confirmed',
425
- txHash: externalTxHash,
426
- });
427
- }
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
+ });
428
497
  }
429
498
  }
430
499
  }
@@ -439,7 +508,7 @@ function waitForAllOutboundTxsV2(ctx, pushChainTxHash, outboundHops, options) {
439
508
  if (statusNum === types_1.UniversalTxStatus.PC_EXECUTED_SUCCESS) {
440
509
  const stillUnresolved = outboundHops.filter((h) => h.status !== 'confirmed');
441
510
  if (stillUnresolved.length > 0) {
442
- const hasPendingOutbound = (_j = utx === null || utx === void 0 ? void 0 : utx.outboundTx) === null || _j === void 0 ? void 0 : _j.some((ob) => {
511
+ const hasPendingOutbound = (_o = utx === null || utx === void 0 ? void 0 : utx.outboundTx) === null || _o === void 0 ? void 0 : _o.some((ob) => {
443
512
  var _a;
444
513
  return ob.outboundStatus === types_2.OutboundStatus.PENDING &&
445
514
  (!((_a = ob.observedTx) === null || _a === void 0 ? void 0 : _a.txHash) || ob.observedTx.txHash === 'EMPTY');
@@ -1,6 +1,6 @@
1
1
  import { CHAIN } from '../../constants/enums';
2
2
  import type { OrchestratorContext } from './context';
3
- type StableKey = 'USDT_ETH' | 'USDT_ARB' | 'USDT_BASE' | 'USDT_BNB' | 'USDT_SOL';
3
+ type StableKey = 'USDT_ETH' | 'USDT_ARB' | 'USDT_BASE' | 'USDT_BNB' | 'USDC_BNB' | 'USDT_SOL';
4
4
  /**
5
5
  * Get $PC/USD price (8 decimals) for the given origin chain's route.
6
6
  * Returns 0 on any failure — caller is expected to fall back to
@@ -15,8 +15,9 @@ const tslib_1 = require("tslib");
15
15
  * - Ethereum / Arbitrum / Base / BNB → WPC/USDT.<route>
16
16
  * - Solana → WPC/USDT.sol
17
17
  *
18
- * Pool pair (USDT vs USDC) is pending Zaryab/Zartaj confirmation — flip the
19
- * ORIGIN_TO_STABLE map below if they pick USDC.
18
+ * Confirmed testnet pool-token addresses are sourced from
19
+ * SYNTHETIC_PUSH_ERC20. Flip ORIGIN_TO_STABLE if a route should use USDC
20
+ * instead of USDT.
20
21
  *
21
22
  * Returns price in **8 decimals** (USD per 1 WPC) to match the convention
22
23
  * used by PriceFetch.getPrice.
@@ -153,7 +154,7 @@ function getPcUsdPrice(ctx, originChain) {
153
154
  const universalCoreAddress = yield ctx.pushClient.readContract({
154
155
  address: gatewayPcAddress,
155
156
  abi: abi_1.UNIVERSAL_GATEWAY_PC,
156
- functionName: 'UNIVERSAL_CORE',
157
+ functionName: 'universalCore',
157
158
  args: [],
158
159
  });
159
160
  const [wpcAddress, factoryAddress, defaultFeeTier] = yield Promise.all([
@@ -23,7 +23,7 @@ import { type PreflightPathTag } from './errors';
23
23
  export interface RunPreflightOpts {
24
24
  ctx: OrchestratorContext;
25
25
  ueaAddress: `0x${string}`;
26
- /** Effective UEA balance in wei UPC (already includes fresh-wallet prediction). */
26
+ /** Effective UEA balance in native PC wei (already includes fresh-wallet prediction). */
27
27
  ueaBalance: bigint;
28
28
  /** Buffered pool quote (pre-clamp) — what the SDK would actually attempt. */
29
29
  requiredValue: bigint;
@@ -36,30 +36,16 @@ export interface RunPreflightOpts {
36
36
  prc20Balance?: bigint;
37
37
  /** Cascade-only: identifies which segment failed. */
38
38
  segmentIndex?: number;
39
- /**
40
- * Per-call opt-out (`params.options.allowUnderfundedSwap`). When `true`,
41
- * pre-flight logs the shortfall but does NOT fire 203-04 or throw —
42
- * caller can fall back to the legacy clamp-and-refund branch.
43
- */
44
- allowUnderfundedSwap?: boolean;
45
39
  }
46
40
  export type PreflightResult = {
47
41
  ok: true;
48
42
  /** What `nativeValueForGas` should be (== requiredValue when sufficient). */
49
43
  adjustedValue: bigint;
50
- } | {
51
- ok: false;
52
- /** Set when allowUnderfundedSwap=true: the legacy clamped value. */
53
- legacyClampedValue: bigint;
54
44
  };
55
45
  /**
56
46
  * Pre-flight check. PRC-20 balance is checked before the native check —
57
47
  * a missing burn token fails before the gas-balance check, so the user
58
- * sees the actionable PRC-20 shortfall instead of a stale UPC error.
59
- *
60
- * Returns a result object instead of throwing for the legacy opt-out
61
- * path. The throw happens here (not in the caller) for the default
62
- * `allowUnderfundedSwap=false` case so all 6 paths share one throw site.
48
+ * sees the actionable PRC-20 shortfall instead of a stale native PC error.
63
49
  */
64
50
  export declare function runPreflight(opts: RunPreflightOpts): PreflightResult;
65
51
  /**
@@ -26,31 +26,13 @@ exports.maybeFireSvmWarnThreshold = maybeFireSvmWarnThreshold;
26
26
  const context_1 = require("./context");
27
27
  const progress_hook_types_1 = require("../../progress-hook/progress-hook.types");
28
28
  const errors_1 = require("./errors");
29
- /**
30
- * Internal: compute the legacy clamp value (matches the pre-existing
31
- * branches in route-handlers.ts:643-659 / :968-980 / :1396-1412).
32
- * Returned only when `allowUnderfundedSwap=true`.
33
- */
34
- function legacyClamp(ueaBalance, requiredValue, gasReserve) {
35
- if (ueaBalance >= requiredValue + gasReserve)
36
- return requiredValue;
37
- if (ueaBalance > gasReserve)
38
- return ueaBalance - gasReserve;
39
- if (ueaBalance > BigInt(0))
40
- return ueaBalance < requiredValue ? ueaBalance : requiredValue;
41
- return BigInt(0);
42
- }
43
29
  /**
44
30
  * Pre-flight check. PRC-20 balance is checked before the native check —
45
31
  * a missing burn token fails before the gas-balance check, so the user
46
- * sees the actionable PRC-20 shortfall instead of a stale UPC error.
47
- *
48
- * Returns a result object instead of throwing for the legacy opt-out
49
- * path. The throw happens here (not in the caller) for the default
50
- * `allowUnderfundedSwap=false` case so all 6 paths share one throw site.
32
+ * sees the actionable PRC-20 shortfall instead of a stale native PC error.
51
33
  */
52
34
  function runPreflight(opts) {
53
- const { ctx, ueaAddress, ueaBalance, requiredValue, gasReserve, pathTag, burnToken, burnAmount, prc20Balance, segmentIndex, allowUnderfundedSwap, } = opts;
35
+ const { ctx, ueaAddress, ueaBalance, requiredValue, gasReserve, pathTag, burnToken, burnAmount, prc20Balance, segmentIndex, } = opts;
54
36
  // Each route bucket gets its own preflight IDs so hook streams stay
55
37
  // self-consistent within their convention (R2 → 203-xx, R3 → 303-xx,
56
38
  // cascade → 003-xx). The three hook formatters share an identical
@@ -75,15 +57,6 @@ function runPreflight(opts) {
75
57
  (0, context_1.fireProgressHook)(ctx, HOOK_PRE_INFO, burnAmount, onHand, sufficient, ueaAddress, pathTag, { kind: 'PRC20', burnToken, segmentIndex });
76
58
  if (!sufficient) {
77
59
  const shortfall = burnAmount - onHand;
78
- if (allowUnderfundedSwap) {
79
- (0, context_1.printLog)(ctx, `[preflight ${pathTag}] PRC-20 shortfall ${shortfall} of ${burnToken}; ` +
80
- `allowUnderfundedSwap=true — proceeding with legacy clamp behaviour, ` +
81
- `swap is expected to revert with InsufficientBalance(0xf4d678b8) on-chain.`);
82
- return {
83
- ok: false,
84
- legacyClampedValue: legacyClamp(ueaBalance, requiredValue, gasReserve),
85
- };
86
- }
87
60
  (0, context_1.fireProgressHook)(ctx, HOOK_PRE_FAIL, burnAmount, onHand, shortfall, ueaAddress, pathTag, { kind: 'PRC20', burnToken, segmentIndex });
88
61
  throw new errors_1.InsufficientUEABalanceError({
89
62
  required: burnAmount,
@@ -105,13 +78,6 @@ function runPreflight(opts) {
105
78
  return { ok: true, adjustedValue: requiredValue };
106
79
  }
107
80
  const shortfall = totalRequired - ueaBalance;
108
- if (allowUnderfundedSwap) {
109
- const clamped = legacyClamp(ueaBalance, requiredValue, gasReserve);
110
- (0, context_1.printLog)(ctx, `[preflight ${pathTag}] UPC shortfall ${shortfall} wei; ` +
111
- `allowUnderfundedSwap=true — proceeding with legacy clamped value ${clamped}, ` +
112
- `swap may revert inside Uniswap with Error("STF") if the clamp under-funds the pool quote.`);
113
- return { ok: false, legacyClampedValue: clamped };
114
- }
115
81
  (0, context_1.fireProgressHook)(ctx, HOOK_PRE_FAIL, totalRequired, ueaBalance, shortfall, ueaAddress, pathTag, { kind: 'NATIVE', segmentIndex });
116
82
  throw new errors_1.InsufficientUEABalanceError({
117
83
  required: totalRequired,
@@ -20,6 +20,7 @@ const context_1 = require("./context");
20
20
  const error_decoder_1 = require("./error-decoder");
21
21
  const errors_1 = require("./errors");
22
22
  Object.defineProperty(exports, "PushChainExecutionError", { enumerable: true, get: function () { return errors_1.PushChainExecutionError; } });
23
+ const formatters_1 = require("../../formatters");
23
24
  // ============================================================================
24
25
  // sendPushTx
25
26
  // ============================================================================
@@ -302,7 +303,10 @@ function extractPcTxAndTransform(ctx, pushChainUniversalTx, gatewayTxHash, event
302
303
  const failedPcTx = (lastPcTransaction === null || lastPcTransaction === void 0 ? void 0 : lastPcTransaction.status) === 'FAILED'
303
304
  ? lastPcTransaction
304
305
  : pushChainUniversalTx.pcTx.find((pcTx) => pcTx.status === 'FAILED' && pcTx.errorMsg);
305
- const errorDetails = (failedPcTx === null || failedPcTx === void 0 ? void 0 : failedPcTx.errorMsg) ? `: ${failedPcTx.errorMsg}` : '';
306
+ const failedPcTxErrorMsg = (failedPcTx === null || failedPcTx === void 0 ? void 0 : failedPcTx.errorMsg)
307
+ ? (0, formatters_1.normalizePcInsufficientFundsError)(failedPcTx.errorMsg)
308
+ : '';
309
+ const errorDetails = failedPcTxErrorMsg ? `: ${failedPcTxErrorMsg}` : '';
306
310
  // Decode known revert selectors and ABI-encoded `Error(string)` reverts
307
311
  // into actionable hints. Unknown selectors are logged at INFO so the
308
312
  // table can grow from real reverts.
@@ -536,7 +536,7 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
536
536
  accessList: Array.isArray(tx.accessList) ? [...tx.accessList] : [],
537
537
  // 6. Utilities
538
538
  wait: (waitOptions) => tslib_1.__awaiter(this, void 0, void 0, function* () {
539
- var _a, _b, _c, _d, _e, _f;
539
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
540
540
  // For trackTransaction replays the tx is historical — cosmos already
541
541
  // has the full graph indexed, so the 20s/30s initial-wait buffers
542
542
  // designed for live sends are pure deadweight. Default them to 0 when
@@ -605,9 +605,17 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
605
605
  timeout: outboundTimeoutMs,
606
606
  progressHook: outboundTranslator,
607
607
  });
608
- emit(hooks.success(outboundDetails));
609
- // Merge external chain details into receipt
610
- baseReceipt = Object.assign(Object.assign({}, baseReceipt), { externalTxHash: outboundDetails.externalTxHash, externalChain: outboundDetails.destinationChain, externalExplorerUrl: outboundDetails.explorerUrl, externalRecipient: outboundDetails.recipient, externalAmount: outboundDetails.amount, externalAssetAddr: outboundDetails.assetAddr, externalStatus: 'success' });
608
+ // `outboundDetails.externalTxHash` is the raw `0x`-hex form (kept
609
+ // internally so the R3 inbound tracker below can query Cosmos with
610
+ // it via `waitForInboundPushTx → findChildUtxIdFromExternalTx`).
611
+ // For every USER-FACING surface (progress hook event + receipt) we
612
+ // want the chain-native form so consumers can paste it straight
613
+ // into an explorer / RPC: base58 for SVM, hex for EVM. Build a
614
+ // display copy and use it for both the hook and the receipt.
615
+ const displayExternalTxHash = (_f = (0, helpers_1.toExternalTxHashDisplay)(outboundDetails.destinationChain, outboundDetails.externalTxHash)) !== null && _f !== void 0 ? _f : outboundDetails.externalTxHash;
616
+ const userFacingOutboundDetails = Object.assign(Object.assign({}, outboundDetails), { externalTxHash: displayExternalTxHash });
617
+ emit(hooks.success(userFacingOutboundDetails));
618
+ baseReceipt = Object.assign(Object.assign({}, baseReceipt), { externalTxHash: displayExternalTxHash, externalChain: outboundDetails.destinationChain, externalExplorerUrl: outboundDetails.explorerUrl, externalRecipient: outboundDetails.recipient, externalAmount: outboundDetails.amount, externalAssetAddr: outboundDetails.assetAddr, externalStatus: 'success' });
611
619
  // R3 round-trip: poll Push Chain for the inbound tx that the
612
620
  // source-chain CEA's sendUniversalTxToUEA call produces. Correlate
613
621
  // via the outbound external tx hash → child UTX id → child pcTx.
@@ -637,7 +645,7 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
637
645
  }
638
646
  else {
639
647
  // status === 'failed' (terminal failure status from chain)
640
- const failMsg = (_f = inbound.errorMessage) !== null && _f !== void 0 ? _f : 'inbound execution failed on Push Chain';
648
+ const failMsg = (_g = inbound.errorMessage) !== null && _g !== void 0 ? _g : 'inbound execution failed on Push Chain';
641
649
  emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](failMsg, 'inbound', targetChain));
642
650
  baseReceipt = Object.assign(Object.assign({}, baseReceipt), { pushInboundUtxId: inbound.childUtxId, pushInboundTxHash: inbound.pushTxHash || undefined, externalStatus: 'failed', externalError: failMsg });
643
651
  }
@@ -669,8 +677,16 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
669
677
  // observed tx hash is carried by OutboundFailedError — expose it
670
678
  // as externalTxHash so consumers can link to the explorer.
671
679
  const failedExternalTxHash = error instanceof outbound_sync_1.OutboundFailedError ? error.externalTxHash : undefined;
672
- baseReceipt = Object.assign(Object.assign(Object.assign({}, baseReceipt), { externalStatus: isTimeout ? 'timeout' : 'failed', externalError: errMsg }), (failedExternalTxHash
673
- ? { externalTxHash: failedExternalTxHash }
680
+ // Normalize to chain-native display form for SVM (base58); EVM hex
681
+ // passes through unchanged. `OutboundFailedError.destinationChain`
682
+ // is a chain namespace string (e.g., 'solana:devnet') — convert to
683
+ // the CHAIN enum via the same helper used in outbound-sync.
684
+ const failedChain = error instanceof outbound_sync_1.OutboundFailedError && error.destinationChain
685
+ ? (_h = (0, helpers_1.chainFromNamespace)(error.destinationChain)) !== null && _h !== void 0 ? _h : undefined
686
+ : undefined;
687
+ const failedExternalTxHashDisplay = (_j = (0, helpers_1.toExternalTxHashDisplay)(failedChain, failedExternalTxHash)) !== null && _j !== void 0 ? _j : failedExternalTxHash;
688
+ baseReceipt = Object.assign(Object.assign(Object.assign({}, baseReceipt), { externalStatus: isTimeout ? 'timeout' : 'failed', externalError: errMsg }), (failedExternalTxHashDisplay
689
+ ? { externalTxHash: failedExternalTxHashDisplay }
674
690
  : {}));
675
691
  // Outbound polling timed out or failed — return the annotated
676
692
  // receipt without throwing. Push Chain tx succeeded, external