@pushchain/core 5.1.18 → 6.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) 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 +118 -2
  8. package/src/lib/constants/abi/universalGatewayPC.evm.js +41 -2
  9. package/src/lib/constants/abi/universalGatewayV0.json +1 -1
  10. package/src/lib/constants/chain.d.ts +2 -0
  11. package/src/lib/constants/chain.js +18 -14
  12. package/src/lib/constants/index.d.ts +1 -1
  13. package/src/lib/constants/tokens.d.ts +10 -1
  14. package/src/lib/constants/tokens.js +5 -2
  15. package/src/lib/formatters.d.ts +6 -0
  16. package/src/lib/formatters.js +14 -0
  17. package/src/lib/index.d.ts +2 -0
  18. package/src/lib/index.js +7 -1
  19. package/src/lib/orchestrator/internals/cascade.d.ts +3 -1
  20. package/src/lib/orchestrator/internals/cascade.js +189 -103
  21. package/src/lib/orchestrator/internals/errors.js +1 -1
  22. package/src/lib/orchestrator/internals/execute-funds-only.js +3 -2
  23. package/src/lib/orchestrator/internals/execute-funds-payload.js +2 -1
  24. package/src/lib/orchestrator/internals/execute-standard.js +4 -4
  25. package/src/lib/orchestrator/internals/gas-calculator.d.ts +1 -0
  26. package/src/lib/orchestrator/internals/gas-calculator.js +13 -11
  27. package/src/lib/orchestrator/internals/helpers.js +2 -0
  28. package/src/lib/orchestrator/internals/inbound-tracker.js +4 -2
  29. package/src/lib/orchestrator/internals/outbound-sync.js +73 -60
  30. package/src/lib/orchestrator/internals/pc-usd-oracle.d.ts +1 -1
  31. package/src/lib/orchestrator/internals/pc-usd-oracle.js +4 -3
  32. package/src/lib/orchestrator/internals/preflight.d.ts +8 -5
  33. package/src/lib/orchestrator/internals/preflight.js +35 -21
  34. package/src/lib/orchestrator/internals/push-chain-tx.js +5 -1
  35. package/src/lib/orchestrator/internals/route-handlers.js +175 -202
  36. package/src/lib/orchestrator/internals/signing.js +7 -2
  37. package/src/lib/orchestrator/internals/svm-helpers.js +3 -3
  38. package/src/lib/orchestrator/internals/svm-rent.d.ts +42 -9
  39. package/src/lib/orchestrator/internals/svm-rent.js +157 -14
  40. package/src/lib/orchestrator/internals/tx-transformer.js +5 -2
  41. package/src/lib/orchestrator/max-pc-for-gas.d.ts +60 -0
  42. package/src/lib/orchestrator/max-pc-for-gas.js +66 -0
  43. package/src/lib/orchestrator/orchestrator.d.ts +6 -6
  44. package/src/lib/orchestrator/orchestrator.js +10 -3
  45. package/src/lib/orchestrator/orchestrator.types.d.ts +48 -10
  46. package/src/lib/orchestrator/payload-builders.d.ts +7 -6
  47. package/src/lib/orchestrator/payload-builders.js +19 -7
  48. package/src/lib/progress-hook/progress-hook.js +59 -27
  49. package/src/lib/progress-hook/progress-hook.types.d.ts +3 -3
  50. package/src/lib/progress-hook/progress-hook.types.js +4 -2
  51. package/src/lib/push-chain/push-chain.d.ts +10 -4
  52. package/src/lib/push-chain/push-chain.js +2 -2
  53. package/src/lib/universal-tx-detector/events.d.ts +3 -1
  54. package/src/lib/universal-tx-detector/events.js +3 -1
  55. package/src/lib/universal-tx-detector/svm-events.d.ts +1 -1
  56. package/src/lib/universal-tx-detector/svm-events.js +1 -1
  57. package/src/lib/utils.js +1 -1
@@ -29,7 +29,7 @@ const formatters_1 = require("../../formatters");
29
29
  */
30
30
  class PushChainExecutionError extends Error {
31
31
  constructor(message, opts = {}) {
32
- super(message);
32
+ super((0, formatters_1.normalizePcInsufficientFundsError)(message));
33
33
  this.code = 'PUSH_CHAIN_EXECUTION_FAILED';
34
34
  this.name = 'PushChainExecutionError';
35
35
  this.gatewayTxHash = opts.gatewayTxHash;
@@ -29,6 +29,7 @@ const push_chain_tx_1 = require("./push-chain-tx");
29
29
  const svm_helpers_1 = require("./svm-helpers");
30
30
  const helpers_2 = require("./helpers");
31
31
  const response_builder_2 = require("./response-builder");
32
+ const formatters_1 = require("../../formatters");
32
33
  function executeFundsOnly(ctx, execute, eventBuffer, getResponseCallbacks) {
33
34
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
34
35
  const transformFn = (tx, buf = []) => (0, response_builder_2.transformToUniversalTxResponse)(ctx, tx, buf, getResponseCallbacks());
@@ -139,7 +140,7 @@ function executeFundsOnlyEvm(ctx, execute, eventBuffer, transformFn, chain, rpcU
139
140
  }
140
141
  catch (err) {
141
142
  if (!(err instanceof push_chain_tx_1.PushChainExecutionError)) {
142
- const errMsg = err instanceof Error ? err.message : String(err);
143
+ const errMsg = (0, formatters_1.normalizePcInsufficientFundsError)(err instanceof Error ? err.message : String(err));
143
144
  (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02, errMsg);
144
145
  throw new push_chain_tx_1.PushChainExecutionError(errMsg, { gatewayTxHash: txHash });
145
146
  }
@@ -230,7 +231,7 @@ function executeFundsOnlySvm(ctx, execute, eventBuffer, transformFn, chain, rpcU
230
231
  }
231
232
  catch (err) {
232
233
  if (!(err instanceof push_chain_tx_1.PushChainExecutionError)) {
233
- const errMsg = err instanceof Error ? err.message : String(err);
234
+ const errMsg = (0, formatters_1.normalizePcInsufficientFundsError)(err instanceof Error ? err.message : String(err));
234
235
  (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02, errMsg);
235
236
  throw new push_chain_tx_1.PushChainExecutionError(errMsg, { gatewayTxHash: txSignature });
236
237
  }
@@ -32,6 +32,7 @@ const push_chain_tx_1 = require("./push-chain-tx");
32
32
  const svm_bridge_1 = require("./svm-bridge");
33
33
  const signing_1 = require("./signing");
34
34
  const quote_1 = require("./quote");
35
+ const formatters_1 = require("../../formatters");
35
36
  function executeFundsWithPayload(ctx, execute, eventBuffer, getResponseCallbacks) {
36
37
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
37
38
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
@@ -240,7 +241,7 @@ function executeFundsWithPayload(ctx, execute, eventBuffer, getResponseCallbacks
240
241
  }
241
242
  catch (err) {
242
243
  if (!(err instanceof push_chain_tx_1.PushChainExecutionError)) {
243
- const errMsg = err instanceof Error ? err.message : String(err);
244
+ const errMsg = (0, formatters_1.normalizePcInsufficientFundsError)(err instanceof Error ? err.message : String(err));
244
245
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02, errMsg);
245
246
  throw new push_chain_tx_1.PushChainExecutionError(errMsg, { gatewayTxHash: txHash });
246
247
  }
@@ -104,7 +104,7 @@ function executeStandardPayload(ctx, execute, eventBuffer, getResponseCallbacks)
104
104
  catch (err) {
105
105
  // Push Chain broadcast failed. Wallet rejection surfaces as 104-04;
106
106
  // everything else (RPC fail, on-chain revert) as 199-02.
107
- const errMsg = err instanceof Error ? err.message : String(err);
107
+ const errMsg = (0, formatters_1.normalizePcInsufficientFundsError)(err instanceof Error ? err.message : String(err));
108
108
  const { isUserDecline } = (0, progress_hook_1.classifyDeclineError)(errMsg);
109
109
  (0, context_1.fireProgressHook)(ctx, isUserDecline ? progress_hook_types_1.PROGRESS_HOOK.SEND_TX_104_04 : progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02, errMsg);
110
110
  // Mark that a terminal-ish error hook has already fired so the
@@ -267,7 +267,7 @@ function executeStandardPayload(ctx, execute, eventBuffer, getResponseCallbacks)
267
267
  // Wallet decline, contract revert during sign, or RPC failure — the
268
268
  // 104-04 builder's classifyDeclineError heuristic picks the right
269
269
  // copy (real decline → "Verification Declined"; else "Signature Failed").
270
- const errMsg = err instanceof Error ? err.message : String(err);
270
+ const errMsg = (0, formatters_1.normalizePcInsufficientFundsError)(err instanceof Error ? err.message : String(err));
271
271
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_104_04, errMsg);
272
272
  throw err;
273
273
  }
@@ -413,7 +413,7 @@ function executeStandardPayload(ctx, execute, eventBuffer, getResponseCallbacks)
413
413
  }
414
414
  catch (err) {
415
415
  if (!(err instanceof push_chain_tx_1.PushChainExecutionError)) {
416
- const errMsg = err instanceof Error ? err.message : String(err);
416
+ const errMsg = (0, formatters_1.normalizePcInsufficientFundsError)(err instanceof Error ? err.message : String(err));
417
417
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02, errMsg);
418
418
  throw new push_chain_tx_1.PushChainExecutionError(errMsg, { gatewayTxHash: feeLockTxHash });
419
419
  }
@@ -447,7 +447,7 @@ function executeStandardPayload(ctx, execute, eventBuffer, getResponseCallbacks)
447
447
  // exhaustion). Surface terminal 199-02 before re-throwing so the stream
448
448
  // ends with the spec'd error hook. Typed PushChainExecutionError lets
449
449
  // callers classify via instanceof without message sniffing.
450
- const errMsg = err instanceof Error ? err.message : String(err);
450
+ const errMsg = (0, formatters_1.normalizePcInsufficientFundsError)(err instanceof Error ? err.message : String(err));
451
451
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02, errMsg);
452
452
  if (err instanceof push_chain_tx_1.PushChainExecutionError)
453
453
  throw err;
@@ -14,6 +14,7 @@ export declare function queryOutboundGasFee(ctx: OrchestratorContext, prc20Token
14
14
  protocolFee: bigint;
15
15
  nativeValueForGas: bigint;
16
16
  gasPrice: bigint;
17
+ gasLimitUsed: bigint;
17
18
  universalCoreAddress: `0x${string}`;
18
19
  sizing?: GasSizingDecision;
19
20
  }>;
@@ -158,7 +158,7 @@ function ensureErc20Allowance(ctx, evmClient, tokenAddress, spender, requiredAmo
158
158
  // ============================================================================
159
159
  function queryOutboundGasFee(ctx, prc20Token, gasLimit, destinationChain) {
160
160
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
161
- var _a, _b;
161
+ var _a, _b, _c;
162
162
  const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
163
163
  const pushChain = (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork);
164
164
  const rpcUrl = ((_b = (_a = chain_1.CHAIN_INFO[pushChain]) === null || _a === void 0 ? void 0 : _a.defaultRPC) === null || _b === void 0 ? void 0 : _b[0]) || 'unknown';
@@ -168,13 +168,13 @@ function queryOutboundGasFee(ctx, prc20Token, gasLimit, destinationChain) {
168
168
  universalCoreAddress = yield ctx.pushClient.readContract({
169
169
  address: gatewayPcAddress,
170
170
  abi: abi_1.UNIVERSAL_GATEWAY_PC,
171
- functionName: 'UNIVERSAL_CORE',
171
+ functionName: 'universalCore',
172
172
  args: [],
173
173
  });
174
- (0, context_1.printLog)(ctx, `queryOutboundGasFee — [step 2] UNIVERSAL_CORE resolved to: ${universalCoreAddress}`);
174
+ (0, context_1.printLog)(ctx, `queryOutboundGasFee — [step 2] universalCore resolved to: ${universalCoreAddress}`);
175
175
  }
176
176
  catch (err) {
177
- (0, context_1.printLog)(ctx, `queryOutboundGasFee — [step 2] FAILED to read UNIVERSAL_CORE: ${err instanceof Error ? err.message : String(err)}`);
177
+ (0, context_1.printLog)(ctx, `queryOutboundGasFee — [step 2] FAILED to read universalCore: ${err instanceof Error ? err.message : String(err)}`);
178
178
  throw err;
179
179
  }
180
180
  const callData = (0, viem_1.encodeFunctionData)({
@@ -188,6 +188,7 @@ function queryOutboundGasFee(ctx, prc20Token, gasLimit, destinationChain) {
188
188
  let gasFee;
189
189
  let protocolFee;
190
190
  let gasPrice = BigInt(0);
191
+ let gasLimitUsed = gasLimit;
191
192
  try {
192
193
  const result = yield ctx.pushClient.readContract({
193
194
  address: universalCoreAddress,
@@ -199,7 +200,8 @@ function queryOutboundGasFee(ctx, prc20Token, gasLimit, destinationChain) {
199
200
  gasFee = result[1];
200
201
  protocolFee = result[2];
201
202
  gasPrice = result[3];
202
- (0, context_1.printLog)(ctx, `queryOutboundGasFee — [step 4] success: gasToken=${gasToken}, gasFee=${gasFee}, protocolFee=${protocolFee}, gasPrice=${gasPrice}`);
203
+ gasLimitUsed = (_c = result[5]) !== null && _c !== void 0 ? _c : gasLimit;
204
+ (0, context_1.printLog)(ctx, `queryOutboundGasFee — [step 4] success: gasToken=${gasToken}, gasFee=${gasFee}, protocolFee=${protocolFee}, gasPrice=${gasPrice}, gasLimitUsed=${gasLimitUsed}`);
203
205
  }
204
206
  catch (err) {
205
207
  (0, context_1.printLog)(ctx, `queryOutboundGasFee — [step 3] FAILED: ${err instanceof Error ? err.message : String(err)}`);
@@ -230,7 +232,7 @@ function queryOutboundGasFee(ctx, prc20Token, gasLimit, destinationChain) {
230
232
  nativeValueForGas = protocolFee + gasFee * GAS_FEE_BUFFER_MULTIPLIER;
231
233
  (0, context_1.printLog)(ctx, `queryOutboundGasFee — [step 5] no destinationChain provided; legacy 1M buffer: nativeValueForGas=${nativeValueForGas}`);
232
234
  }
233
- return { gasToken, gasFee, protocolFee, nativeValueForGas, gasPrice, universalCoreAddress, sizing };
235
+ return { gasToken, gasFee, protocolFee, nativeValueForGas, gasPrice, gasLimitUsed, universalCoreAddress, sizing };
234
236
  });
235
237
  }
236
238
  // ============================================================================
@@ -390,13 +392,13 @@ function queryRescueGasFee(ctx, prc20Token, destinationChain) {
390
392
  universalCoreAddress = yield ctx.pushClient.readContract({
391
393
  address: gatewayPcAddress,
392
394
  abi: abi_1.UNIVERSAL_GATEWAY_PC,
393
- functionName: 'UNIVERSAL_CORE',
395
+ functionName: 'universalCore',
394
396
  args: [],
395
397
  });
396
- (0, context_1.printLog)(ctx, `queryRescueGasFee — UNIVERSAL_CORE resolved to: ${universalCoreAddress}`);
398
+ (0, context_1.printLog)(ctx, `queryRescueGasFee — universalCore resolved to: ${universalCoreAddress}`);
397
399
  }
398
400
  catch (err) {
399
- (0, context_1.printLog)(ctx, `queryRescueGasFee — FAILED to read UNIVERSAL_CORE: ${err instanceof Error ? err.message : String(err)}`);
401
+ (0, context_1.printLog)(ctx, `queryRescueGasFee — FAILED to read universalCore: ${err instanceof Error ? err.message : String(err)}`);
400
402
  throw err;
401
403
  }
402
404
  let gasToken;
@@ -562,7 +564,7 @@ function estimateDepositFromLockedNative(ctx, nativeAmountForLocker, prc20Token)
562
564
  const universalCoreAddress = yield ctx.pushClient.readContract({
563
565
  address: gatewayPcAddress,
564
566
  abi: abi_1.UNIVERSAL_GATEWAY_PC,
565
- functionName: 'UNIVERSAL_CORE',
567
+ functionName: 'universalCore',
566
568
  args: [],
567
569
  });
568
570
  const [wpcAddress, feeTier] = yield Promise.all([
@@ -654,7 +656,7 @@ function estimateNativeForDesiredDeposit(ctx, desiredWpc, prc20Token) {
654
656
  const universalCoreAddress = yield ctx.pushClient.readContract({
655
657
  address: gatewayPcAddress,
656
658
  abi: abi_1.UNIVERSAL_GATEWAY_PC,
657
- functionName: 'UNIVERSAL_CORE',
659
+ functionName: 'universalCore',
658
660
  args: [],
659
661
  });
660
662
  const [wpcAddress, feeTier] = yield Promise.all([
@@ -142,9 +142,11 @@ function toExecuteParams(params) {
142
142
  data: params.data,
143
143
  funds: params.funds,
144
144
  gasLimit: params.gasLimit,
145
+ maxPCForGas: params.maxPCForGas,
145
146
  maxFeePerGas: params.maxFeePerGas,
146
147
  maxPriorityFeePerGas: params.maxPriorityFeePerGas,
147
148
  deadline: params.deadline,
149
+ options: params.options,
148
150
  payGasWith: params.payGasWith,
149
151
  feeLockTxHash: params.feeLockTxHash,
150
152
  };
@@ -20,6 +20,7 @@ const types_1 = require("../../generated/uexecutor/v2/types");
20
20
  const context_1 = require("./context");
21
21
  const detector_1 = require("../../universal-tx-detector/detector");
22
22
  const child_inbounds_1 = require("../../universal-tx-detector/child-inbounds");
23
+ const formatters_1 = require("../../formatters");
23
24
  const TERMINAL_SUCCESS_STATUSES = new Set([
24
25
  types_1.UniversalTxStatus.INBOUND_SUCCESS,
25
26
  types_1.UniversalTxStatus.PC_EXECUTED_SUCCESS,
@@ -205,8 +206,9 @@ function waitForInboundPushTx(ctx_1, outboundExternalTxHash_1, sourceChain_1) {
205
206
  }
206
207
  }
207
208
  else if (TERMINAL_FAILURE_STATUSES.has(status)) {
208
- const errMsg = ((_l = (_k = utx === null || utx === void 0 ? void 0 : utx.pcTx) === null || _k === void 0 ? void 0 : _k[0]) === null || _l === void 0 ? void 0 : _l.errorMsg) ||
209
- `inbound terminated with status ${status}`;
209
+ const errMsg = ((_l = (_k = utx === null || utx === void 0 ? void 0 : utx.pcTx) === null || _k === void 0 ? void 0 : _k[0]) === null || _l === void 0 ? void 0 : _l.errorMsg)
210
+ ? (0, formatters_1.normalizePcInsufficientFundsError)(utx.pcTx[0].errorMsg)
211
+ : `inbound terminated with status ${status}`;
210
212
  progressHook === null || progressHook === void 0 ? void 0 : progressHook({
211
213
  status: 'failed',
212
214
  elapsedMs: elapsed(),
@@ -15,6 +15,7 @@ const chain_1 = require("../../constants/chain");
15
15
  const enums_1 = require("../../constants/enums");
16
16
  const types_1 = require("../../generated/uexecutor/v1/types");
17
17
  const types_2 = require("../../generated/uexecutor/v2/types");
18
+ const formatters_1 = require("../../formatters");
18
19
  const context_1 = require("./context");
19
20
  const helpers_1 = require("./helpers");
20
21
  const outbound_tracker_1 = require("./outbound-tracker");
@@ -142,7 +143,7 @@ function verifyExternalEvmReceipt(ctx, destinationChain, externalTxHash) {
142
143
  function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
143
144
  return tslib_1.__awaiter(this, arguments, void 0, function* (ctx, pushChainTxHash, options = {}) {
144
145
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
145
- const { initialWaitMs = exports.OUTBOUND_INITIAL_WAIT_MS, pollingIntervalMs = exports.OUTBOUND_POLL_INTERVAL_MS, timeout = exports.OUTBOUND_MAX_TIMEOUT_MS, progressHook, _resolvedSubTxId, _expectedDestinationChain, } = options;
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;
146
147
  // Terminal failure states — fail fast instead of polling until timeout
147
148
  const TERMINAL_FAILURE_STATES = new Set([
148
149
  types_1.UniversalTxStatus.OUTBOUND_FAILED,
@@ -213,13 +214,30 @@ function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
213
214
  });
214
215
  if (failedPcTx) {
215
216
  const failedTxHash = failedPcTx.txHash || pushChainTxHash;
216
- const reason = failedPcTx.errorMsg || 'Push Chain execution failed';
217
+ const reason = failedPcTx.errorMsg
218
+ ? (0, formatters_1.normalizePcInsufficientFundsError)(failedPcTx.errorMsg)
219
+ : 'Push Chain execution failed';
217
220
  (0, context_1.printLog)(ctx, `[waitForOutboundTx] Push Chain execution failed before outbound emission: ${reason}`);
218
221
  progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'failed', elapsed: Date.now() - startTime });
219
222
  throw new OutboundFailedError(`Push Chain execution failed before outbound emission: ${reason}. Push Chain TX: ${failedTxHash}.`, pushChainTxHash, undefined, failedPcTx.txHash || undefined);
220
223
  }
221
- // Iterate V2 outbound array
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;
222
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
+ }
223
241
  // Fail fast on per-outbound REVERTED status
224
242
  if (ob.outboundStatus === types_2.OutboundStatus.REVERTED) {
225
243
  (0, context_1.printLog)(ctx, `[waitForOutboundTx] Outbound to ${ob.destinationChain} REVERTED`);
@@ -235,11 +253,6 @@ function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
235
253
  // treat as FOUND; if reverted, throw OutboundFailedError.
236
254
  // Pending receipts keep polling.
237
255
  if ((_m = ob.observedTx) === null || _m === void 0 ? void 0 : _m.txHash) {
238
- // If a destination chain filter is set, skip outbound entries that don't match
239
- if (_expectedDestinationChain && ob.destinationChain !== _expectedDestinationChain) {
240
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} | outbound chain '${ob.destinationChain}' does not match expected '${_expectedDestinationChain}', skipping`);
241
- continue;
242
- }
243
256
  const chain = (0, helpers_1.chainFromNamespace)(ob.destinationChain);
244
257
  if (!chain)
245
258
  continue;
@@ -320,7 +333,7 @@ function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
320
333
  */
321
334
  function waitForAllOutboundTxsV2(ctx, pushChainTxHash, outboundHops, options) {
322
335
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
323
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
336
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
324
337
  const { initialWaitMs, pollingIntervalMs, timeout, progressHook, _resolvedSubTxId, } = options;
325
338
  const startTime = Date.now();
326
339
  // Build a map: CAIP-2 namespace -> hop(s) for matching outbound entries
@@ -386,34 +399,38 @@ function waitForAllOutboundTxsV2(ctx, pushChainTxHash, outboundHops, options) {
386
399
  const statusName = (_a = types_1.UniversalTxStatus[statusNum]) !== null && _a !== void 0 ? _a : statusNum;
387
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}`);
388
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();
389
403
  for (const ob of utx.outboundTx) {
390
404
  const destChain = ob.destinationChain;
391
405
  const hopsForChain = chainToHops.get(destChain);
392
406
  if (!hopsForChain)
393
407
  continue;
394
- const unconfirmedForChain = hopsForChain.filter((h) => h.status !== 'confirmed' && h.status !== 'failed');
395
- 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') {
396
414
  continue;
397
- const externalTxHash = (_e = ob.observedTx) === null || _e === void 0 ? void 0 : _e.txHash;
415
+ }
416
+ const externalTxHash = (_f = ob.observedTx) === null || _f === void 0 ? void 0 : _f.txHash;
398
417
  const hasObservedTxHash = !!externalTxHash && externalTxHash !== 'EMPTY';
399
418
  const observedFailure = hasObservedTxHash &&
400
- ((_f = ob.observedTx) === null || _f === void 0 ? void 0 : _f.success) === false &&
401
- Boolean(((_g = ob.observedTx) === null || _g === void 0 ? void 0 : _g.errorMsg) ||
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) ||
402
421
  ob.abortReason ||
403
422
  ob.outboundStatus >= types_2.OutboundStatus.REVERTED);
404
423
  // Fail fast on per-outbound REVERTED or observed destination failure.
405
424
  if (ob.outboundStatus === types_2.OutboundStatus.REVERTED || observedFailure) {
406
- for (const hop of unconfirmedForChain) {
407
- hop.status = 'failed';
408
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Outbound to ${destChain} REVERTED | hop ${hop.hopIndex} | error: ${ob.abortReason || ((_h = ob.observedTx) === null || _h === void 0 ? void 0 : _h.errorMsg) || 'Unknown'}`);
409
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({
410
- hopIndex: hop.hopIndex,
411
- route: hop.route,
412
- chain: hop.executionChain,
413
- status: 'failed',
414
- });
415
- }
416
- return { success: false, failedAt: unconfirmedForChain[0].hopIndex };
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 };
417
434
  }
418
435
  // Check for observed tx hash. Cosmos may lag the status transition,
419
436
  // so EVM destinations are verified against the destination receipt.
@@ -425,17 +442,15 @@ function waitForAllOutboundTxsV2(ctx, pushChainTxHash, outboundHops, options) {
425
442
  if (!isCosmosObserved) {
426
443
  const verdict = yield verifyExternalEvmReceipt(ctx, chain, externalTxHash);
427
444
  if (verdict === 'reverted') {
428
- for (const hop of unconfirmedForChain) {
429
- hop.status = 'failed';
430
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Source-chain RPC reports REVERTED for ${destChain} tx ${externalTxHash} | hop ${hop.hopIndex}`);
431
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({
432
- hopIndex: hop.hopIndex,
433
- route: hop.route,
434
- chain: hop.executionChain,
435
- status: 'failed',
436
- });
437
- }
438
- return { success: false, failedAt: unconfirmedForChain[0].hopIndex };
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 };
439
454
  }
440
455
  if (verdict !== 'success') {
441
456
  continue;
@@ -447,11 +462,11 @@ function waitForAllOutboundTxsV2(ctx, pushChainTxHash, outboundHops, options) {
447
462
  // inside this function; it's not exposed to consumers from the
448
463
  // cascade path (cascade.ts doesn't call findChildUtxIdFromExternalTx
449
464
  // for the cascade outbound hash).
450
- const displayTxHash = (_j = (0, helpers_1.toExternalTxHashDisplay)(chain !== null && chain !== void 0 ? chain : undefined, externalTxHash)) !== null && _j !== void 0 ? _j : externalTxHash;
465
+ const displayTxHash = (_k = (0, helpers_1.toExternalTxHashDisplay)(chain !== null && chain !== void 0 ? chain : undefined, externalTxHash)) !== null && _k !== void 0 ? _k : externalTxHash;
451
466
  let explorerUrl = '';
452
467
  if (chain && externalTxHash) {
453
- const explorerBaseUrl = (_k = chain_1.CHAIN_INFO[chain]) === null || _k === void 0 ? void 0 : _k.explorerUrl;
454
- const isSvm = ((_l = chain_1.CHAIN_INFO[chain]) === null || _l === void 0 ? void 0 : _l.vm) === enums_1.VM.SVM;
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;
455
470
  if (isSvm) {
456
471
  const cluster = chain === enums_1.CHAIN.SOLANA_DEVNET ? '?cluster=devnet'
457
472
  : chain === enums_1.CHAIN.SOLANA_TESTNET ? '?cluster=testnet' : '';
@@ -461,26 +476,24 @@ function waitForAllOutboundTxsV2(ctx, pushChainTxHash, outboundHops, options) {
461
476
  explorerUrl = explorerBaseUrl ? `${explorerBaseUrl}/tx/${displayTxHash}` : '';
462
477
  }
463
478
  }
464
- for (const hop of unconfirmedForChain) {
465
- hop.status = 'confirmed';
466
- hop.txHash = displayTxHash;
467
- hop.outboundDetails = {
468
- externalTxHash: displayTxHash,
469
- destinationChain: chain || hop.executionChain,
470
- explorerUrl,
471
- recipient: ob.recipient,
472
- amount: ob.amount,
473
- assetAddr: ob.externalAssetAddr,
474
- };
475
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] FOUND outbound for ${destChain} | hop ${hop.hopIndex} | externalTxHash: ${displayTxHash}`);
476
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({
477
- hopIndex: hop.hopIndex,
478
- route: hop.route,
479
- chain: hop.executionChain,
480
- status: 'confirmed',
481
- txHash: displayTxHash,
482
- });
483
- }
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
+ });
484
497
  }
485
498
  }
486
499
  }
@@ -495,7 +508,7 @@ function waitForAllOutboundTxsV2(ctx, pushChainTxHash, outboundHops, options) {
495
508
  if (statusNum === types_1.UniversalTxStatus.PC_EXECUTED_SUCCESS) {
496
509
  const stillUnresolved = outboundHops.filter((h) => h.status !== 'confirmed');
497
510
  if (stillUnresolved.length > 0) {
498
- const hasPendingOutbound = (_m = utx === null || utx === void 0 ? void 0 : utx.outboundTx) === null || _m === void 0 ? void 0 : _m.some((ob) => {
511
+ const hasPendingOutbound = (_o = utx === null || utx === void 0 ? void 0 : utx.outboundTx) === null || _o === void 0 ? void 0 : _o.some((ob) => {
499
512
  var _a;
500
513
  return ob.outboundStatus === types_2.OutboundStatus.PENDING &&
501
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_BSC' | '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([
@@ -8,12 +8,13 @@
8
8
  * with `Error("STF")`, fees are burned, and the user sees an opaque
9
9
  * library string with no balance signal.
10
10
  *
11
- * `runPreflight` replaces that branch: it checks the **pre-clamp**
12
- * buffered pool quote against UEA balance (and optionally a PRC-20 burn
13
- * balance) and either:
14
- * - emits `SEND-TX-203-03` (INFO, sufficient) and lets the caller proceed; or
11
+ * `runPreflight` checks the **pre-clamp** buffered pool quote against UEA
12
+ * balance (and optionally a PRC-20 burn balance) and either:
13
+ * - emits `SEND-TX-203-03` (INFO, sufficient) and lets the caller proceed;
14
+ * - emits `SEND-TX-203-03` (WARNING, insufficient) and proceeds when
15
+ * enforceGasCheck is false/omitted; or
15
16
  * - emits `SEND-TX-203-03` (ERROR, insufficient) + `SEND-TX-203-04` and
16
- * throws `InsufficientUEABalanceError`.
17
+ * throws `InsufficientUEABalanceError` when enforceGasCheck is true.
17
18
  *
18
19
  * The helper is intentionally not "pure" — it emits hooks and may throw.
19
20
  * Callers fetch all balances (so the helper itself does no I/O).
@@ -34,6 +35,8 @@ export interface RunPreflightOpts {
34
35
  burnToken?: `0x${string}`;
35
36
  burnAmount?: bigint;
36
37
  prc20Balance?: bigint;
38
+ /** Defaults to false: warn on shortfall and proceed. */
39
+ enforceGasCheck?: boolean;
37
40
  /** Cascade-only: identifies which segment failed. */
38
41
  segmentIndex?: number;
39
42
  }
@@ -9,12 +9,13 @@
9
9
  * with `Error("STF")`, fees are burned, and the user sees an opaque
10
10
  * library string with no balance signal.
11
11
  *
12
- * `runPreflight` replaces that branch: it checks the **pre-clamp**
13
- * buffered pool quote against UEA balance (and optionally a PRC-20 burn
14
- * balance) and either:
15
- * - emits `SEND-TX-203-03` (INFO, sufficient) and lets the caller proceed; or
12
+ * `runPreflight` checks the **pre-clamp** buffered pool quote against UEA
13
+ * balance (and optionally a PRC-20 burn balance) and either:
14
+ * - emits `SEND-TX-203-03` (INFO, sufficient) and lets the caller proceed;
15
+ * - emits `SEND-TX-203-03` (WARNING, insufficient) and proceeds when
16
+ * enforceGasCheck is false/omitted; or
16
17
  * - emits `SEND-TX-203-03` (ERROR, insufficient) + `SEND-TX-203-04` and
17
- * throws `InsufficientUEABalanceError`.
18
+ * throws `InsufficientUEABalanceError` when enforceGasCheck is true.
18
19
  *
19
20
  * The helper is intentionally not "pure" — it emits hooks and may throw.
20
21
  * Callers fetch all balances (so the helper itself does no I/O).
@@ -32,7 +33,7 @@ const errors_1 = require("./errors");
32
33
  * sees the actionable PRC-20 shortfall instead of a stale native PC error.
33
34
  */
34
35
  function runPreflight(opts) {
35
- const { ctx, ueaAddress, ueaBalance, requiredValue, gasReserve, pathTag, burnToken, burnAmount, prc20Balance, segmentIndex, } = opts;
36
+ const { ctx, ueaAddress, ueaBalance, requiredValue, gasReserve, pathTag, burnToken, burnAmount, prc20Balance, enforceGasCheck = false, segmentIndex, } = opts;
36
37
  // Each route bucket gets its own preflight IDs so hook streams stay
37
38
  // self-consistent within their convention (R2 → 203-xx, R3 → 303-xx,
38
39
  // cascade → 003-xx). The three hook formatters share an identical
@@ -54,9 +55,15 @@ function runPreflight(opts) {
54
55
  if (burnAmount && burnAmount > BigInt(0) && burnToken) {
55
56
  const onHand = prc20Balance !== null && prc20Balance !== void 0 ? prc20Balance : BigInt(0);
56
57
  const sufficient = onHand >= burnAmount;
57
- (0, context_1.fireProgressHook)(ctx, HOOK_PRE_INFO, burnAmount, onHand, sufficient, ueaAddress, pathTag, { kind: 'PRC20', burnToken, segmentIndex });
58
- if (!sufficient) {
59
- const shortfall = burnAmount - onHand;
58
+ const shortfall = sufficient ? BigInt(0) : burnAmount - onHand;
59
+ (0, context_1.fireProgressHook)(ctx, HOOK_PRE_INFO, burnAmount, onHand, sufficient, ueaAddress, pathTag, {
60
+ kind: 'PRC20',
61
+ burnToken,
62
+ segmentIndex,
63
+ enforceGasCheck,
64
+ shortfall,
65
+ });
66
+ if (!sufficient && enforceGasCheck) {
60
67
  (0, context_1.fireProgressHook)(ctx, HOOK_PRE_FAIL, burnAmount, onHand, shortfall, ueaAddress, pathTag, { kind: 'PRC20', burnToken, segmentIndex });
61
68
  throw new errors_1.InsufficientUEABalanceError({
62
69
  required: burnAmount,
@@ -69,25 +76,32 @@ function runPreflight(opts) {
69
76
  segmentIndex,
70
77
  });
71
78
  }
79
+ else if (!sufficient) {
80
+ (0, context_1.printLog)(ctx, `[preflight ${pathTag}] PRC-20 shortfall ${shortfall.toString()} for ${burnToken}; proceeding because enforceGasCheck=false`);
81
+ }
72
82
  }
73
83
  // 2. Native (UPC) balance check.
74
84
  const totalRequired = requiredValue + gasReserve;
75
85
  const sufficient = ueaBalance >= totalRequired;
76
- (0, context_1.fireProgressHook)(ctx, HOOK_PRE_INFO, totalRequired, ueaBalance, sufficient, ueaAddress, pathTag, { kind: 'NATIVE', segmentIndex });
86
+ const shortfall = sufficient ? BigInt(0) : totalRequired - ueaBalance;
87
+ (0, context_1.fireProgressHook)(ctx, HOOK_PRE_INFO, totalRequired, ueaBalance, sufficient, ueaAddress, pathTag, { kind: 'NATIVE', segmentIndex, enforceGasCheck, shortfall });
77
88
  if (sufficient) {
78
89
  return { ok: true, adjustedValue: requiredValue };
79
90
  }
80
- const shortfall = totalRequired - ueaBalance;
81
- (0, context_1.fireProgressHook)(ctx, HOOK_PRE_FAIL, totalRequired, ueaBalance, shortfall, ueaAddress, pathTag, { kind: 'NATIVE', segmentIndex });
82
- throw new errors_1.InsufficientUEABalanceError({
83
- required: totalRequired,
84
- available: ueaBalance,
85
- shortfall,
86
- ueaAddress,
87
- pathTag,
88
- reason: 'NATIVE',
89
- segmentIndex,
90
- });
91
+ if (enforceGasCheck) {
92
+ (0, context_1.fireProgressHook)(ctx, HOOK_PRE_FAIL, totalRequired, ueaBalance, shortfall, ueaAddress, pathTag, { kind: 'NATIVE', segmentIndex });
93
+ throw new errors_1.InsufficientUEABalanceError({
94
+ required: totalRequired,
95
+ available: ueaBalance,
96
+ shortfall,
97
+ ueaAddress,
98
+ pathTag,
99
+ reason: 'NATIVE',
100
+ segmentIndex,
101
+ });
102
+ }
103
+ (0, context_1.printLog)(ctx, `[preflight ${pathTag}] native shortfall ${shortfall.toString()}; proceeding because enforceGasCheck=false`);
104
+ return { ok: true, adjustedValue: requiredValue };
91
105
  }
92
106
  /**
93
107
  * SVM-only telemetry threshold. Buffered pool quotes above this value
@@ -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.