@pushchain/core 6.0.8 → 6.0.10

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushchain/core",
3
- "version": "6.0.8",
3
+ "version": "6.0.10",
4
4
  "homepage": "https://push.org",
5
5
  "keywords": [
6
6
  "pushchain",
@@ -262,6 +262,12 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
262
262
  // Build CEA multicalls
263
263
  const ceaMulticalls = [];
264
264
  const hasData = (0, data_utils_1.hasExecutablePayloadData)(params.data);
265
+ const fundsToken = ((_h = params.funds) === null || _h === void 0 ? void 0 : _h.amount)
266
+ ? (0, route_handlers_1.resolveR2DestinationFundsToken)(params.funds.token, targetChain, ctx.pushNetwork)
267
+ : undefined;
268
+ const nativeFundsValue = ((_j = params.funds) === null || _j === void 0 ? void 0 : _j.amount) && (fundsToken === null || fundsToken === void 0 ? void 0 : fundsToken.mechanism) === 'native'
269
+ ? params.funds.amount
270
+ : BigInt(0);
265
271
  if (hasData) {
266
272
  if (Array.isArray(params.data)) {
267
273
  ceaMulticalls.push(...params.data);
@@ -270,16 +276,15 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
270
276
  // When ERC-20 funds are provided with a single payload, auto-prepend a
271
277
  // transfer() call so the tokens minted to the CEA are forwarded to the
272
278
  // target address. This mirrors the Route 1 behavior in buildExecuteMulticall.
273
- if ((_h = params.funds) === null || _h === void 0 ? void 0 : _h.amount) {
274
- const token = params.funds.token;
275
- if (token && token.mechanism !== 'native') {
279
+ if ((_k = params.funds) === null || _k === void 0 ? void 0 : _k.amount) {
280
+ if (fundsToken && fundsToken.mechanism !== 'native') {
276
281
  const erc20Transfer = (0, viem_1.encodeFunctionData)({
277
282
  abi: erc20_evm_1.ERC20_EVM,
278
283
  functionName: 'transfer',
279
284
  args: [target.address, params.funds.amount],
280
285
  });
281
286
  ceaMulticalls.push({
282
- to: token.address,
287
+ to: fundsToken.address,
283
288
  value: BigInt(0),
284
289
  data: erc20Transfer,
285
290
  });
@@ -290,23 +295,20 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
290
295
  // native value to the CEA, and the multicall forwards it to the target.
291
296
  ceaMulticalls.push({
292
297
  to: target.address,
293
- value: (_j = params.value) !== null && _j !== void 0 ? _j : BigInt(0),
298
+ value: ((_l = params.value) !== null && _l !== void 0 ? _l : BigInt(0)) + nativeFundsValue,
294
299
  data: params.data,
295
300
  });
296
301
  }
297
302
  }
298
303
  else {
299
- const token = ((_k = params.funds) === null || _k === void 0 ? void 0 : _k.amount)
300
- ? params.funds.token
301
- : undefined;
302
- if (((_l = params.funds) === null || _l === void 0 ? void 0 : _l.amount) && token && token.mechanism !== 'native') {
304
+ if (((_m = params.funds) === null || _m === void 0 ? void 0 : _m.amount) && fundsToken && fundsToken.mechanism !== 'native') {
303
305
  const erc20Transfer = (0, viem_1.encodeFunctionData)({
304
306
  abi: erc20_evm_1.ERC20_EVM,
305
307
  functionName: 'transfer',
306
308
  args: [target.address, params.funds.amount],
307
309
  });
308
310
  ceaMulticalls.push({
309
- to: token.address,
311
+ to: fundsToken.address,
310
312
  value: BigInt(0),
311
313
  data: erc20Transfer,
312
314
  });
@@ -322,11 +324,11 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
322
324
  });
323
325
  }
324
326
  }
325
- else if (((_m = params.funds) === null || _m === void 0 ? void 0 : _m.amount) && (token === null || token === void 0 ? void 0 : token.mechanism) === 'native') {
327
+ else if (nativeFundsValue > BigInt(0)) {
326
328
  if (target.address.toLowerCase() !== ceaAddress.toLowerCase()) {
327
329
  ceaMulticalls.push({
328
330
  to: target.address,
329
- value: params.funds.amount,
331
+ value: nativeFundsValue,
330
332
  data: '0x',
331
333
  });
332
334
  }
@@ -442,8 +444,8 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
442
444
  effectiveGasLimit = result.gasLimitUsed;
443
445
  }
444
446
  return Object.assign(Object.assign({}, baseDescriptor), { sourceChain, ceaAddress: ceaPdaHex, isSvmTarget: true, prc20Token,
445
- // Route 3 is payload-only on Push: the drain amount lives inside the
446
- // SVM payload. Burning here would require the UEA to pre-hold pSOL.
447
+ // Route 3 drains pre-existing funds from the external CEA. The
448
+ // Push-side outbound is a payload relay, so no PRC-20 is burned.
447
449
  burnAmount: BigInt(0), gasToken,
448
450
  gasFee,
449
451
  gasPrice, gasLimit: effectiveGasLimit, sizing });
@@ -781,7 +783,7 @@ function getInboundFundingForBurnToken(segments, beforeSegmentIndex, burnToken,
781
783
  }
782
784
  function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce, options) {
783
785
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
784
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
786
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
785
787
  let accumulatedPushMulticalls = [];
786
788
  let requiredNativeValue = BigInt(0);
787
789
  const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
@@ -964,12 +966,10 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
964
966
  : (firstHop === null || firstHop === void 0 ? void 0 : firstHop.ceaAddress) || ueaAddress;
965
967
  (0, payload_builders_1.assertCeaFundsParkingInvariant)(targetForOutbound, outboundPayload);
966
968
  }
967
- // Per-segment PRC-20 burn-balance pre-check (cascade R2-style segments
968
- // only — INBOUND_FROM_CEA segments are payload-only with totalBurnAmount=0
969
- // per the 2026-03-19 fix). Skip MIGRATION (totalBurnAmount=0). Fires
970
- // whenever burnAmount > 0 even when burn token equals gas token — the
971
- // gateway's transferFrom happens BEFORE the gas swap, so user must
972
- // pre-hold the burn amount.
969
+ // Per-segment PRC-20 burn-balance pre-check. Fires whenever
970
+ // burnAmount > 0 even when burn token equals gas token — the gateway's
971
+ // transferFrom happens BEFORE the gas swap, so user must pre-hold the
972
+ // burn amount.
973
973
  const segBurnAmount = segment.totalBurnAmount || BigInt(0);
974
974
  const segBurnToken = segment.prc20Token;
975
975
  if (segBurnAmount > BigInt(0) && segBurnToken && !isMigration) {
@@ -1026,7 +1026,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1026
1026
  }
1027
1027
  }
1028
1028
  // Build outbound request
1029
- const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetForOutbound, segment.prc20Token || selectors_1.ZERO_ADDRESS, segment.totalBurnAmount || BigInt(0), (_e = segment.gasLimit) !== null && _e !== void 0 ? _e : BigInt(0), outboundPayload, ueaAddress, (_f = segment.maxPCForGas) !== null && _f !== void 0 ? _f : BigInt(0), (_g = segment.gasPrice) !== null && _g !== void 0 ? _g : BigInt(0));
1029
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetForOutbound, segment.prc20Token || selectors_1.ZERO_ADDRESS, segment.totalBurnAmount || BigInt(0), (_e = segment.gasLimit) !== null && _e !== void 0 ? _e : BigInt(0), outboundPayload, ueaAddress, (_f = segment.maxPCForGas) !== null && _f !== void 0 ? _f : BigInt(0));
1030
1030
  // Build approval + outbound multicalls
1031
1031
  const segGasFee = segment.gasFee || BigInt(0);
1032
1032
  const svmOverride = svmNativeValueBySegment.get(segment);
@@ -1043,7 +1043,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1043
1043
  else {
1044
1044
  evmFallbackValue = segGasFee * BigInt(1000000);
1045
1045
  }
1046
- let nativeValueForGas = (_h = svmOverride !== null && svmOverride !== void 0 ? svmOverride : perOutboundNativeValue) !== null && _h !== void 0 ? _h : evmFallbackValue;
1046
+ let nativeValueForGas = (_g = svmOverride !== null && svmOverride !== void 0 ? svmOverride : perOutboundNativeValue) !== null && _g !== void 0 ? _g : evmFallbackValue;
1047
1047
  // SDK 5.2 Case C: when the segment's sizing is category C, compose
1048
1048
  // R2/OUTBOUND_TO_CEA segments no longer apply Case A/B/C sizing
1049
1049
  // (scoped to R1 + R3 only). segment.sizing may still be populated
@@ -1084,7 +1084,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1084
1084
  // UEA multicall so no nested outbound is needed.
1085
1085
  const mergedHopMulticalls = [];
1086
1086
  for (const h of segment.hops) {
1087
- 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)) {
1087
+ 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)) {
1088
1088
  const hopCalls = (0, payload_builders_1.buildExecuteMulticall)({
1089
1089
  execute: {
1090
1090
  to: h.params.to,
@@ -1105,18 +1105,18 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1105
1105
  ];
1106
1106
  }
1107
1107
  // Primary hop carries the sourceChain / CEA address (shared across merged hops).
1108
- const hop0 = segment.hops[0];
1108
+ const hop = segment.hops[0];
1109
+ const sourceChain = hop.sourceChain;
1110
+ const ceaAddress = hop.ceaAddress || ueaAddress;
1109
1111
  let intermediatePayload = '0x';
1110
1112
  if (accumulatedPushMulticalls.length > 0) {
1111
1113
  const multicallPayload = (0, payload_builder_1.buildMulticallPayloadData)(ctx, ueaAddress, accumulatedPushMulticalls);
1112
1114
  // +1: the outbound tx consumes one nonce via execute()
1113
- intermediatePayload = (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, {
1114
- nonce: (ueaNonce !== null && ueaNonce !== void 0 ? ueaNonce : BigInt(0)) + BigInt(1),
1115
- });
1115
+ const nonce = (ueaNonce !== null && ueaNonce !== void 0 ? ueaNonce : BigInt(0)) + BigInt(1);
1116
+ intermediatePayload = (0, payload_builders_1.isSvmChain)(sourceChain)
1117
+ ? (0, payload_builders_1.buildInboundUniversalPayloadSvm)(multicallPayload, { nonce })
1118
+ : (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, { nonce });
1116
1119
  }
1117
- const hop = segment.hops[0];
1118
- const sourceChain = hop.sourceChain;
1119
- const ceaAddress = hop.ceaAddress || ueaAddress;
1120
1120
  // SVM chains: build SVM CPI payload instead of EVM CEA multicall
1121
1121
  if ((0, payload_builders_1.isSvmChain)(sourceChain)) {
1122
1122
  const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
@@ -1129,7 +1129,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1129
1129
  let drainAmount = BigInt(0);
1130
1130
  let tokenMintHex;
1131
1131
  const params = hop.params;
1132
- if (((_l = params.funds) === null || _l === void 0 ? void 0 : _l.amount) && params.funds.amount > BigInt(0)) {
1132
+ if (((_k = params.funds) === null || _k === void 0 ? void 0 : _k.amount) && params.funds.amount > BigInt(0)) {
1133
1133
  drainAmount = params.funds.amount;
1134
1134
  const token = params.funds.token;
1135
1135
  if (token && token.address) {
@@ -1158,7 +1158,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1158
1158
  });
1159
1159
  const svmBurnAmount = segment.totalBurnAmount || BigInt(0);
1160
1160
  const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, segment.prc20Token ||
1161
- (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), (_p = segment.gasPrice) !== null && _p !== void 0 ? _p : BigInt(0));
1161
+ (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));
1162
1162
  const inboundGasFee = segment.gasFee || BigInt(0);
1163
1163
  let svmInboundFallback;
1164
1164
  if (effectiveUeaBalance > BigInt(0)) {
@@ -1172,7 +1172,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1172
1172
  // SDK 5.2 R3-style Case C bump: when the inbound segment's sizing
1173
1173
  // is C, top up the swap budget by the overflow. No bridge-swap —
1174
1174
  // R3 has no destination funds delivery.
1175
- if (((_q = segment.sizing) === null || _q === void 0 ? void 0 : _q.category) === 'C' &&
1175
+ if (((_o = segment.sizing) === null || _o === void 0 ? void 0 : _o.category) === 'C' &&
1176
1176
  segment.sizing.overflowNativePc > BigInt(0)) {
1177
1177
  const bumped = svmInboundNativeValue + segment.sizing.overflowNativePc;
1178
1178
  (0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (SVM) Case C: bumping nativeValueForGas from ${svmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
@@ -1209,7 +1209,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1209
1209
  let amount = BigInt(0);
1210
1210
  for (const mergedHop of segment.hops) {
1211
1211
  const hParams = mergedHop.params;
1212
- if ((_r = hParams.funds) === null || _r === void 0 ? void 0 : _r.amount) {
1212
+ if ((_p = hParams.funds) === null || _p === void 0 ? void 0 : _p.amount) {
1213
1213
  const token = hParams.funds.token;
1214
1214
  if (token) {
1215
1215
  if (token.mechanism === 'native') {
@@ -1245,7 +1245,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1245
1245
  ? segment.gasLimit
1246
1246
  : selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT;
1247
1247
  const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, segment.prc20Token ||
1248
- (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), segment.totalBurnAmount || BigInt(0), effectiveGasLimit, ceaPayload, ueaAddress, (_s = segment.maxPCForGas) !== null && _s !== void 0 ? _s : BigInt(0), (_t = segment.gasPrice) !== null && _t !== void 0 ? _t : BigInt(0));
1248
+ (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), segment.totalBurnAmount || BigInt(0), effectiveGasLimit, ceaPayload, ueaAddress, (_q = segment.maxPCForGas) !== null && _q !== void 0 ? _q : BigInt(0));
1249
1249
  const inboundGasFee = segment.gasFee || BigInt(0);
1250
1250
  let evmInboundFallback;
1251
1251
  if (effectiveUeaBalance > BigInt(0)) {
@@ -1259,7 +1259,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1259
1259
  // SDK 5.2 R3-style Case C bump: when the inbound segment's sizing
1260
1260
  // is C, top up the swap budget by the overflow. No bridge-swap —
1261
1261
  // R3 has no destination funds delivery.
1262
- if (((_u = segment.sizing) === null || _u === void 0 ? void 0 : _u.category) === 'C' &&
1262
+ if (((_r = segment.sizing) === null || _r === void 0 ? void 0 : _r.category) === 'C' &&
1263
1263
  segment.sizing.overflowNativePc > BigInt(0)) {
1264
1264
  const bumped = evmInboundNativeValue + segment.sizing.overflowNativePc;
1265
1265
  (0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (EVM) Case C: bumping nativeValueForGas from ${evmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
@@ -46,6 +46,12 @@ exports.KNOWN_ERROR_SELECTORS = {
46
46
  hint: 'Likely cause: provided gasLimit is below the chain minimum. Omit `gasLimit` to use the per-chain default.',
47
47
  provenance: 'Observed in dev (selector source unverified — see plan §9 #5)',
48
48
  },
49
+ '0x05aab006': {
50
+ name: 'GasPriceBelowBase',
51
+ hint: 'Likely cause: an explicit outbound gasPrice override is below the current UniversalCore base price. ' +
52
+ 'Omit gasPrice so the gateway resolves the live base price at execution time.',
53
+ provenance: 'TypesUGPC/UniversalGatewayPC live Route 2 docs e2e, 2026-05-19: gateway tx 0xe1b549eb47c3cbf1abc69ba6e85927832e482147b49a01a0364206098367dbf7.',
54
+ },
49
55
  };
50
56
  /** Selector for `Error(string)` (Solidity `require(false, "msg")`). */
51
57
  exports.ERROR_STRING_SELECTOR = '0x08c379a0';
@@ -12,7 +12,7 @@ export declare const OUTBOUND_INITIAL_WAIT_MS = 15000;
12
12
  /** Interval between consecutive polls (ms). */
13
13
  export declare const OUTBOUND_POLL_INTERVAL_MS = 3000;
14
14
  /** Maximum total timeout for the outbound polling loop (ms). */
15
- export declare const OUTBOUND_MAX_TIMEOUT_MS = 180000;
15
+ export declare const OUTBOUND_MAX_TIMEOUT_MS = 300000;
16
16
  /** Maximum total timeout for the R3 inbound round-trip polling loop (ms). */
17
17
  export declare const INBOUND_MAX_TIMEOUT_MS = 300000;
18
18
  /** Initial wait before the first inbound poll (ms). Validator observation + Push commit + indexer ingestion realistically take 15-25 s on testnet (esp. SVM source), so polling immediately just burns RPC on guaranteed-empty results. */
@@ -49,7 +49,7 @@ export declare class OutboundFailedError extends Error {
49
49
  * @internal Used by .wait() for outbound routes - not part of public API.
50
50
  * Uses polling with configurable initial wait, interval, and timeout.
51
51
  *
52
- * Default strategy: 15s initial wait, then poll every 3s, 180s total timeout.
52
+ * Default strategy: 15s initial wait, then poll every 3s, 300s total timeout.
53
53
  *
54
54
  * @param ctx - Orchestrator context
55
55
  * @param pushChainTxHash - The Push Chain transaction hash
@@ -27,7 +27,7 @@ exports.OUTBOUND_INITIAL_WAIT_MS = 15000; // 15s
27
27
  /** Interval between consecutive polls (ms). */
28
28
  exports.OUTBOUND_POLL_INTERVAL_MS = 3000; // 3s
29
29
  /** Maximum total timeout for the outbound polling loop (ms). */
30
- exports.OUTBOUND_MAX_TIMEOUT_MS = 180000; // 180s (3 min)
30
+ exports.OUTBOUND_MAX_TIMEOUT_MS = 300000; // 300s (5 min)
31
31
  /** Maximum total timeout for the R3 inbound round-trip polling loop (ms). */
32
32
  exports.INBOUND_MAX_TIMEOUT_MS = 300000; // 300s (5 min) — covers R3 inbound latency on testnet
33
33
  /** Initial wait before the first inbound poll (ms). Validator observation + Push commit + indexer ingestion realistically take 15-25 s on testnet (esp. SVM source), so polling immediately just burns RPC on guaranteed-empty results. */
@@ -75,7 +75,7 @@ exports.OutboundFailedError = OutboundFailedError;
75
75
  *
76
76
  * Cosmos sometimes populates `observedTx.txHash` while the relayer is still
77
77
  * voting on the outboundStatus update. The Push indexer can stay at
78
- * UNSPECIFIED for the full 180s budget even though the destination tx mined
78
+ * UNSPECIFIED for the full timeout budget even though the destination tx mined
79
79
  * minutes earlier. To avoid those false timeouts we ask the destination
80
80
  * chain's own RPC: if it has a confirmed receipt with status=success, we
81
81
  * trust it and return FOUND; if status=reverted, we throw OutboundFailedError
@@ -132,7 +132,7 @@ function verifyExternalEvmReceipt(ctx, destinationChain, externalTxHash) {
132
132
  * @internal Used by .wait() for outbound routes - not part of public API.
133
133
  * Uses polling with configurable initial wait, interval, and timeout.
134
134
  *
135
- * Default strategy: 15s initial wait, then poll every 3s, 180s total timeout.
135
+ * Default strategy: 15s initial wait, then poll every 3s, 300s total timeout.
136
136
  *
137
137
  * @param ctx - Orchestrator context
138
138
  * @param pushChainTxHash - The Push Chain transaction hash
@@ -15,8 +15,11 @@
15
15
  */
16
16
  import type { OrchestratorContext } from './context';
17
17
  import { CHAIN } from '../../constants/enums';
18
+ import { MoveableToken } from '../../constants/tokens';
18
19
  import { TransactionRoute } from '../route-detector';
19
20
  import type { ChainTarget, ExecuteParams, UniversalExecuteParams, UniversalOutboundTxRequest, UniversalTxRequest, UniversalTxResponse } from '../orchestrator.types';
21
+ import type { PUSH_NETWORK } from '../../constants/enums';
22
+ export declare function resolveR2DestinationFundsToken(token: MoveableToken, targetChain: CHAIN, pushNetwork: PUSH_NETWORK): MoveableToken;
20
23
  type ExecuteFn = (params: ExecuteParams | UniversalExecuteParams) => Promise<UniversalTxResponse>;
21
24
  /**
22
25
  * Dispatch a universal execution to the correct route handler.
@@ -59,8 +62,8 @@ export declare function executeCeaToPush(ctx: OrchestratorContext, params: Unive
59
62
  *
60
63
  * Unlike EVM Route 3 which builds CEA multicalls, SVM Route 3 encodes a
61
64
  * `send_universal_tx_to_uea` instruction as an execute payload targeting
62
- * the SVM gateway program (self-call). The drain amount is embedded in
63
- * the instruction data, not in the outbound request amount.
65
+ * the SVM gateway program (self-call). For funds-bearing calls the outbound
66
+ * request amount must match the drain amount embedded in the instruction data.
64
67
  */
65
68
  export declare function executeCeaToPushSvm(ctx: OrchestratorContext, params: UniversalExecuteParams, sourceChain: CHAIN, executeFn: ExecuteFn): Promise<UniversalTxResponse>;
66
69
  /**
@@ -15,6 +15,7 @@
15
15
  * `executeFn` callback that replaces the former `this.execute()` call.
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.resolveR2DestinationFundsToken = resolveR2DestinationFundsToken;
18
19
  exports.executeMultiChain = executeMultiChain;
19
20
  exports.executeUoaToCea = executeUoaToCea;
20
21
  exports.executeUoaToCeaSvm = executeUoaToCeaSvm;
@@ -34,6 +35,7 @@ const gas_calculator_1 = require("./gas-calculator");
34
35
  const preflight_1 = require("./preflight");
35
36
  const svm_rent_1 = require("./svm-rent");
36
37
  const chain_1 = require("../../constants/chain");
38
+ const tokens_1 = require("../../constants/tokens");
37
39
  const utils_1 = require("../../utils");
38
40
  const price_fetch_1 = require("../../price-fetch/price-fetch");
39
41
  const route_detector_1 = require("../route-detector");
@@ -81,6 +83,24 @@ function resolveR2Prc20TokenEvm(params, targetChain, pushNetwork) {
81
83
  }
82
84
  return { prc20Token, burnAmount };
83
85
  }
86
+ function resolveR2DestinationFundsToken(token, targetChain, pushNetwork) {
87
+ var _a, _b, _c;
88
+ const pushToken = token;
89
+ if (!pushToken.prc20Address || pushToken.sourceChain !== targetChain) {
90
+ return token;
91
+ }
92
+ const destinationTokens = (_a = tokens_1.MOVEABLE_TOKENS[targetChain]) !== null && _a !== void 0 ? _a : [];
93
+ const nativePrc20 = (0, helpers_1.getNativePRC20ForChain)(targetChain, pushNetwork);
94
+ if (pushToken.prc20Address.toLowerCase() === nativePrc20.toLowerCase()) {
95
+ return ((_b = destinationTokens.find((candidate) => candidate.mechanism === 'native')) !== null && _b !== void 0 ? _b : {
96
+ symbol: token.symbol,
97
+ decimals: token.decimals,
98
+ address: selectors_1.ZERO_ADDRESS,
99
+ mechanism: 'native',
100
+ });
101
+ }
102
+ return ((_c = destinationTokens.find((candidate) => candidate.symbol === token.symbol)) !== null && _c !== void 0 ? _c : token);
103
+ }
84
104
  /**
85
105
  * SVM counterpart to `resolveR2Prc20TokenEvm`. SVM adds an EXECUTE-only
86
106
  * branch (no value, no funds, but `hasSvmExecute === true`) that still needs
@@ -117,6 +137,7 @@ function resolveR3SvmDrain(params, sourceChain, pushNetwork) {
117
137
  var _a;
118
138
  let drainAmount = BigInt(0);
119
139
  let tokenMintHex;
140
+ let splMintBase58;
120
141
  let prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, pushNetwork);
121
142
  if (((_a = params.funds) === null || _a === void 0 ? void 0 : _a.amount) && params.funds.amount > BigInt(0)) {
122
143
  drainAmount = params.funds.amount;
@@ -124,13 +145,14 @@ function resolveR3SvmDrain(params, sourceChain, pushNetwork) {
124
145
  if ((token === null || token === void 0 ? void 0 : token.mechanism) === 'approve') {
125
146
  const mintPk = new web3_js_1.PublicKey(token.address);
126
147
  tokenMintHex = ('0x' + Buffer.from(mintPk.toBytes()).toString('hex'));
148
+ splMintBase58 = token.address;
127
149
  prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
128
150
  }
129
151
  }
130
152
  else if (params.value && params.value > BigInt(0)) {
131
153
  drainAmount = params.value;
132
154
  }
133
- return { prc20Token, drainAmount, tokenMintHex };
155
+ return { prc20Token, drainAmount, tokenMintHex, splMintBase58 };
134
156
  }
135
157
  function buildR3SvmExtraPayload(ctx, params, ueaAddress, sourceChain, inboundNonce) {
136
158
  var _a, _b;
@@ -146,7 +168,7 @@ function buildR3SvmExtraPayload(ctx, params, ueaAddress, sourceChain, inboundNon
146
168
  ueaAddress,
147
169
  allowSelfValueCall: true,
148
170
  }));
149
- const inboundPayload = (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, {
171
+ const inboundPayload = (0, payload_builders_1.buildInboundUniversalPayloadSvm)(multicallPayload, {
150
172
  nonce: inboundNonce !== null && inboundNonce !== void 0 ? inboundNonce : BigInt(0),
151
173
  });
152
174
  return (0, viem_1.hexToBytes)(inboundPayload);
@@ -156,13 +178,19 @@ function buildR3SvmExtraPayload(ctx, params, ueaAddress, sourceChain, inboundNon
156
178
  * resolved `ceaAddress` because the native-value self-call branch skips the
157
179
  * multicall when the target IS the CEA (avoids a CEA._handleMulticall revert).
158
180
  */
159
- function buildR2CeaPayloadEvm(params, ceaAddress, targetAddress) {
181
+ function buildR2CeaPayloadEvm(params, ceaAddress, targetAddress, targetChain, pushNetwork) {
160
182
  var _a, _b, _c, _d, _e;
161
183
  if (params.migration) {
162
184
  return (0, payload_builders_1.buildMigrationPayload)();
163
185
  }
164
186
  const ceaMulticalls = [];
165
187
  const hasData = (0, data_utils_1.hasExecutablePayloadData)(params.data);
188
+ const fundsToken = ((_a = params.funds) === null || _a === void 0 ? void 0 : _a.amount)
189
+ ? resolveR2DestinationFundsToken(params.funds.token, targetChain, pushNetwork)
190
+ : undefined;
191
+ const nativeFundsValue = ((_b = params.funds) === null || _b === void 0 ? void 0 : _b.amount) && (fundsToken === null || fundsToken === void 0 ? void 0 : fundsToken.mechanism) === 'native'
192
+ ? params.funds.amount
193
+ : BigInt(0);
166
194
  if (hasData) {
167
195
  if (Array.isArray(params.data)) {
168
196
  ceaMulticalls.push(...params.data);
@@ -171,16 +199,15 @@ function buildR2CeaPayloadEvm(params, ceaAddress, targetAddress) {
171
199
  // When ERC-20 funds are provided with a single payload, auto-prepend a
172
200
  // transfer() call so the tokens minted to the CEA are forwarded to the
173
201
  // target address. Mirrors the Route 1 behavior in buildExecuteMulticall.
174
- if ((_a = params.funds) === null || _a === void 0 ? void 0 : _a.amount) {
175
- const token = params.funds.token;
176
- if (token && token.mechanism !== 'native') {
202
+ if ((_c = params.funds) === null || _c === void 0 ? void 0 : _c.amount) {
203
+ if (fundsToken && fundsToken.mechanism !== 'native') {
177
204
  const erc20Transfer = (0, viem_1.encodeFunctionData)({
178
205
  abi: erc20_evm_1.ERC20_EVM,
179
206
  functionName: 'transfer',
180
207
  args: [targetAddress, params.funds.amount],
181
208
  });
182
209
  ceaMulticalls.push({
183
- to: token.address,
210
+ to: fundsToken.address,
184
211
  value: BigInt(0),
185
212
  data: erc20Transfer,
186
213
  });
@@ -190,29 +217,24 @@ function buildR2CeaPayloadEvm(params, ceaAddress, targetAddress) {
190
217
  // contract receives it alongside the payload call.
191
218
  ceaMulticalls.push({
192
219
  to: targetAddress,
193
- value: (_b = params.value) !== null && _b !== void 0 ? _b : BigInt(0),
220
+ value: ((_d = params.value) !== null && _d !== void 0 ? _d : BigInt(0)) + nativeFundsValue,
194
221
  data: params.data,
195
222
  });
196
223
  }
197
224
  }
198
225
  else {
199
- const token = ((_c = params.funds) === null || _c === void 0 ? void 0 : _c.amount)
200
- ? params.funds.token
201
- : undefined;
202
- if (((_d = params.funds) === null || _d === void 0 ? void 0 : _d.amount) && token && token.mechanism !== 'native') {
226
+ if (((_e = params.funds) === null || _e === void 0 ? void 0 : _e.amount) && fundsToken && fundsToken.mechanism !== 'native') {
203
227
  // ERC-20 funds: forward via ERC20.transfer from the CEA.
204
- if (token) {
205
- const erc20Transfer = (0, viem_1.encodeFunctionData)({
206
- abi: erc20_evm_1.ERC20_EVM,
207
- functionName: 'transfer',
208
- args: [targetAddress, params.funds.amount],
209
- });
210
- ceaMulticalls.push({
211
- to: token.address,
212
- value: BigInt(0),
213
- data: erc20Transfer,
214
- });
215
- }
228
+ const erc20Transfer = (0, viem_1.encodeFunctionData)({
229
+ abi: erc20_evm_1.ERC20_EVM,
230
+ functionName: 'transfer',
231
+ args: [targetAddress, params.funds.amount],
232
+ });
233
+ ceaMulticalls.push({
234
+ to: fundsToken.address,
235
+ value: BigInt(0),
236
+ data: erc20Transfer,
237
+ });
216
238
  }
217
239
  if (params.value) {
218
240
  // Native value transfer only. Skip the multicall when sending to the CEA
@@ -225,13 +247,13 @@ function buildR2CeaPayloadEvm(params, ceaAddress, targetAddress) {
225
247
  });
226
248
  }
227
249
  }
228
- else if (((_e = params.funds) === null || _e === void 0 ? void 0 : _e.amount) && (token === null || token === void 0 ? void 0 : token.mechanism) === 'native') {
250
+ else if (nativeFundsValue > BigInt(0)) {
229
251
  // Native funds (e.g. pETH → ETH): forward value to the recipient.
230
252
  // Self-CEA is skipped — the gateway already deposits into the CEA.
231
253
  if (targetAddress.toLowerCase() !== ceaAddress.toLowerCase()) {
232
254
  ceaMulticalls.push({
233
255
  to: targetAddress,
234
- value: params.funds.amount,
256
+ value: nativeFundsValue,
235
257
  data: '0x',
236
258
  });
237
259
  }
@@ -378,7 +400,6 @@ function executeUoaToCea(ctx, params, executeFn) {
378
400
  // --- 202: Gas estimation (spec-ordered before 203) ---
379
401
  let gasFee = BigInt(0);
380
402
  let protocolFee = BigInt(0);
381
- let outboundGasPrice = BigInt(0);
382
403
  let gasToken = selectors_1.ZERO_ADDRESS;
383
404
  let universalCoreAddress;
384
405
  if (prc20Token !== selectors_1.ZERO_ADDRESS) {
@@ -387,7 +408,6 @@ function executeUoaToCea(ctx, params, executeFn) {
387
408
  const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimitForQuery, targetChain);
388
409
  gasFee = result.gasFee;
389
410
  protocolFee = result.protocolFee;
390
- outboundGasPrice = result.gasPrice;
391
411
  gasToken = result.gasToken;
392
412
  universalCoreAddress = result.universalCoreAddress;
393
413
  (0, context_1.printLog)(ctx, `executeUoaToCea — queried gas fee: ${gasFee.toString()}, gasToken: ${gasToken}`);
@@ -414,7 +434,7 @@ function executeUoaToCea(ctx, params, executeFn) {
414
434
  const ueaNonce = (!isUEADeployed || isNativePushEOA) ? BigInt(0) : yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress);
415
435
  // --- Build CEA payload (requires ceaAddress for self-call check) ---
416
436
  // Delegates to `buildR2CeaPayloadEvm` — see helper definition above.
417
- const ceaPayload = buildR2CeaPayloadEvm(params, ceaAddress, targetAddress);
437
+ const ceaPayload = buildR2CeaPayloadEvm(params, ceaAddress, targetAddress, targetChain, ctx.pushNetwork);
418
438
  if (params.migration) {
419
439
  (0, context_1.printLog)(ctx, `executeUoaToCea — MIGRATION: using raw MIGRATION_SELECTOR payload (${ceaPayload}), native PRC-20 ${prc20Token}`);
420
440
  }
@@ -426,7 +446,7 @@ function executeUoaToCea(ctx, params, executeFn) {
426
446
  : ceaAddress;
427
447
  (0, payload_builders_1.assertCeaFundsParkingInvariant)(targetBytes, ceaPayload);
428
448
  const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetBytes, prc20Token, burnAmount, gasLimitForQuery, ceaPayload, ueaAddress, // revert recipient is the UEA
429
- (_d = params.maxPCForGas) !== null && _d !== void 0 ? _d : BigInt(0), outboundGasPrice);
449
+ (_d = params.maxPCForGas) !== null && _d !== void 0 ? _d : BigInt(0));
430
450
  (0, context_1.printLog)(ctx, `executeUoaToCea — outbound request: ${JSON.stringify({
431
451
  recipient: outboundReq.recipient,
432
452
  token: outboundReq.token,
@@ -605,7 +625,6 @@ function executeUoaToCeaSvm(ctx, params, target, executeFn) {
605
625
  // --- 202: Gas estimation (spec-ordered before 203) ---
606
626
  let gasFee = BigInt(0);
607
627
  let protocolFeeSvm = BigInt(0);
608
- let outboundGasPriceSvm = BigInt(0);
609
628
  let gasToken = selectors_1.ZERO_ADDRESS;
610
629
  let universalCoreAddress;
611
630
  // Effective gas limit seeded from the user param. When the user omits
@@ -637,7 +656,6 @@ function executeUoaToCeaSvm(ctx, params, target, executeFn) {
637
656
  });
638
657
  gasFee = result.gasFee;
639
658
  protocolFeeSvm = result.protocolFee;
640
- outboundGasPriceSvm = result.gasPrice;
641
659
  gasToken = result.gasToken;
642
660
  universalCoreAddress = result.universalCoreAddress;
643
661
  // When user omits gasLimit (sent as 0), the contract computes fees using
@@ -664,7 +682,7 @@ function executeUoaToCeaSvm(ctx, params, target, executeFn) {
664
682
  // --- Build outbound request ---
665
683
  // targetBytes: for execute = program id (resolver output); for withdraw = to.address
666
684
  const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetBytes, prc20Token, burnAmount, effectiveGasLimit, svmPayload, ueaAddress, // revert recipient is the UEA
667
- (_f = params.maxPCForGas) !== null && _f !== void 0 ? _f : BigInt(0), outboundGasPriceSvm);
685
+ (_f = params.maxPCForGas) !== null && _f !== void 0 ? _f : BigInt(0));
668
686
  (0, context_1.printLog)(ctx, `executeUoaToCeaSvm — outbound request: ${JSON.stringify({
669
687
  recipient: outboundReq.recipient,
670
688
  token: outboundReq.token,
@@ -971,7 +989,6 @@ function executeCeaToPush(ctx, params, executeFn) {
971
989
  // 11. Query gas fees from UniversalCore
972
990
  let gasFee = BigInt(0);
973
991
  let protocolFeeR3 = BigInt(0);
974
- let outboundGasPriceR3 = BigInt(0);
975
992
  let nativeValueForGas = BigInt(0);
976
993
  let gasToken = selectors_1.ZERO_ADDRESS;
977
994
  let sizingDecisionR3;
@@ -981,11 +998,10 @@ function executeCeaToPush(ctx, params, executeFn) {
981
998
  const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, outboundGasLimit, sourceChain);
982
999
  gasToken = result.gasToken;
983
1000
  gasFee = result.gasFee;
984
- outboundGasPriceR3 = result.gasPrice;
985
1001
  protocolFeeR3 = result.protocolFee;
986
1002
  nativeValueForGas = result.nativeValueForGas;
987
1003
  sizingDecisionR3 = result.sizing;
988
- outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, prc20Token, burnAmount, outboundGasLimit, ceaPayload, ueaAddress, (_k = params.maxPCForGas) !== null && _k !== void 0 ? _k : BigInt(0), outboundGasPriceR3);
1004
+ outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, prc20Token, burnAmount, outboundGasLimit, ceaPayload, ueaAddress, (_k = params.maxPCForGas) !== null && _k !== void 0 ? _k : BigInt(0));
989
1005
  (0, context_1.printLog)(ctx, `executeCeaToPush — queried gas fee: ${gasFee.toString()}, gasToken: ${gasToken}, nativeValueForGas: ${nativeValueForGas.toString()}, sizing=${(_m = (_l = result.sizing) === null || _l === void 0 ? void 0 : _l.category) !== null && _m !== void 0 ? _m : 'legacy'}`);
990
1006
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_302_02, sourceChain, protocolFeeR3, gasFee);
991
1007
  }
@@ -1129,8 +1145,8 @@ function executeCeaToPush(ctx, params, executeFn) {
1129
1145
  *
1130
1146
  * Unlike EVM Route 3 which builds CEA multicalls, SVM Route 3 encodes a
1131
1147
  * `send_universal_tx_to_uea` instruction as an execute payload targeting
1132
- * the SVM gateway program (self-call). The drain amount is embedded in
1133
- * the instruction data, not in the outbound request amount.
1148
+ * the SVM gateway program (self-call). For funds-bearing calls the outbound
1149
+ * request amount must match the drain amount embedded in the instruction data.
1134
1150
  */
1135
1151
  function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
1136
1152
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
@@ -1148,10 +1164,9 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
1148
1164
  const gatewayProgramHex = ('0x' + Buffer.from(programPk.toBytes()).toString('hex'));
1149
1165
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_301, sourceChain, lockerContract);
1150
1166
  (0, context_1.printLog)(ctx, `executeCeaToPushSvm — sourceChain: ${sourceChain}, gateway: ${lockerContract}`);
1151
- // Route 3 SVM: CEA uses its own pre-existing balance — no PRC-20 burn
1152
- // needed on Push Chain. The outer request token must still match the source
1153
- // asset so the relayer supplies the same Solana mint that the payload
1154
- // encodes; otherwise the gateway rejects with InvalidMint.
1167
+ // Route 3 SVM drains pre-existing funds from the Solana CEA. The Push-side
1168
+ // outbound is a payload relay; do not burn pSOL/pSPL from the UEA for the
1169
+ // drain amount.
1155
1170
  const { prc20Token, drainAmount, tokenMintHex } = resolveR3SvmDrain(params, sourceChain, ctx.pushNetwork);
1156
1171
  // Derive CEA PDA as revert recipient: ["push_identity", ueaAddress_20_bytes]
1157
1172
  const ueaBytes = Buffer.from(ueaAddress.slice(2), 'hex'); // 20 bytes
@@ -1176,12 +1191,6 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
1176
1191
  revertRecipientHex: ceaPdaHex,
1177
1192
  });
1178
1193
  (0, context_1.printLog)(ctx, `executeCeaToPushSvm — drainAmount: ${drainAmount.toString()}, payload length: ${(svmPayload.length - 2) / 2} bytes`);
1179
- // burnAmount = 0 (payload-only outbound — drain amount lives inside ixData).
1180
- // Mirrors the 2026-03-19 cascade fix that removed the 1-wei workaround for
1181
- // SVM payload-only hops because it caused `InsufficientBalance (0xf4d678b8)`
1182
- // reverts on fresh UEAs that don't hold the source-chain native PRC-20
1183
- // (e.g. pSOL). The UGPC upgrade post-2026-03-19 already accepts amount = 0
1184
- // on the cascade path, and the same precompile is used here.
1185
1194
  const burnAmount = BigInt(0);
1186
1195
  let effectiveGasLimit = (_a = params.gasLimit) !== null && _a !== void 0 ? _a : BigInt(0);
1187
1196
  // Build outbound request: recipient = gateway program (self-call)
@@ -1190,7 +1199,6 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
1190
1199
  // Query gas fees
1191
1200
  let gasFee = BigInt(0);
1192
1201
  let protocolFeeR3Svm = BigInt(0);
1193
- let outboundGasPriceR3Svm = BigInt(0);
1194
1202
  let nativeValueForGas = BigInt(0);
1195
1203
  let gasToken = selectors_1.ZERO_ADDRESS;
1196
1204
  let sizingDecisionR3Svm;
@@ -1205,17 +1213,16 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
1205
1213
  prc20Token,
1206
1214
  quote: result,
1207
1215
  splMintBase58: undefined,
1208
- burnAmount: BigInt(0),
1216
+ burnAmount,
1209
1217
  pathTag: 'executeCeaToPushSvm',
1210
1218
  });
1211
1219
  gasToken = result.gasToken;
1212
1220
  gasFee = result.gasFee;
1213
- outboundGasPriceR3Svm = result.gasPrice;
1214
1221
  protocolFeeR3Svm = result.protocolFee;
1215
1222
  nativeValueForGas = result.nativeValueForGas;
1216
1223
  sizingDecisionR3Svm = result.sizing;
1217
1224
  effectiveGasLimit = result.gasLimitUsed;
1218
- outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, prc20Token, burnAmount, effectiveGasLimit, svmPayload, ueaAddress, (_c = params.maxPCForGas) !== null && _c !== void 0 ? _c : BigInt(0), outboundGasPriceR3Svm);
1225
+ outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, prc20Token, burnAmount, effectiveGasLimit, svmPayload, ueaAddress, (_c = params.maxPCForGas) !== null && _c !== void 0 ? _c : BigInt(0));
1219
1226
  (0, context_1.printLog)(ctx, `executeCeaToPushSvm — resolved effectiveGasLimit: ${effectiveGasLimit} ` +
1220
1227
  `(gasFee=${result.gasFee}, gasPrice=${result.gasPrice})`);
1221
1228
  (0, context_1.printLog)(ctx, `executeCeaToPushSvm — gasFee: ${gasFee.toString()}, gasToken: ${gasToken}, nativeValueForGas: ${nativeValueForGas.toString()}, sizing=${(_e = (_d = result.sizing) === null || _d === void 0 ? void 0 : _d.category) !== null && _e !== void 0 ? _e : 'legacy'}`);
@@ -1278,8 +1285,8 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
1278
1285
  }
1279
1286
  // SVM warn-threshold telemetry (no truncation — pre-flight handles drain protection).
1280
1287
  (0, preflight_1.maybeFireSvmWarnThreshold)(ctx, nativeValueForGas, gasToken, 'R3_SVM');
1281
- // Pre-flight (R3 SVM): no PRC-20 burn check. R3 SVM uses burnAmount=0
1282
- // because the drain amount lives inside the SVM instruction data.
1288
+ // Pre-flight (R3 SVM): no PRC-20 burn check. The drain amount lives in the
1289
+ // embedded Solana instruction and is withdrawn from the external CEA.
1283
1290
  (0, preflight_1.runPreflight)({
1284
1291
  ctx,
1285
1292
  ueaAddress,
@@ -1355,7 +1362,7 @@ function executeCeaToCea(ctx, params, executeFn) {
1355
1362
  */
1356
1363
  function buildPayloadForRoute(ctx, params, route, nonce) {
1357
1364
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
1358
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
1365
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
1359
1366
  const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
1360
1367
  switch (route) {
1361
1368
  case route_detector_1.TransactionRoute.UOA_TO_PUSH: {
@@ -1406,56 +1413,19 @@ function buildPayloadForRoute(ctx, params, route, nonce) {
1406
1413
  }
1407
1414
  // EVM path: Resolve CEA address first (needed for self-transfer check)
1408
1415
  const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, target.chain, (_d = ctx.rpcUrls[target.chain]) === null || _d === void 0 ? void 0 : _d[0]);
1409
- // Build CEA outbound payload
1410
- const multicalls = [];
1411
- if ((0, data_utils_1.hasExecutablePayloadData)(params.data)) {
1412
- if (Array.isArray(params.data)) {
1413
- multicalls.push(...params.data);
1414
- }
1415
- else {
1416
- // Single call with data. Native value (if any) is already delivered to
1417
- // CEA by the Vault via executeUniversalTx{value: amount}(). Attaching
1418
- // value to the call would revert if the target function is not payable.
1419
- // To call a payable function with value, use explicit multicalls.
1420
- multicalls.push({
1421
- to: target.address,
1422
- value: BigInt(0),
1423
- data: params.data,
1424
- });
1425
- }
1426
- }
1427
- else if (params.value) {
1428
- // Skip multicall when sending native value to own CEA — gateway deposits directly.
1429
- // Self-call with value would revert (CEA._handleMulticall rejects it).
1430
- if (target.address.toLowerCase() !== ceaAddress.toLowerCase()) {
1431
- multicalls.push({
1432
- to: target.address,
1433
- value: params.value,
1434
- data: '0x',
1435
- });
1436
- }
1437
- }
1438
- const payload = (0, payload_builders_1.buildCeaMulticallPayload)(multicalls);
1439
- let prc20Token = selectors_1.ZERO_ADDRESS;
1440
- let burnAmount = BigInt(0);
1441
- if ((_e = params.funds) === null || _e === void 0 ? void 0 : _e.amount) {
1442
- const token = params.funds.token;
1443
- if (token) {
1444
- prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
1445
- burnAmount = params.funds.amount;
1446
- }
1447
- }
1416
+ const payload = buildR2CeaPayloadEvm(params, ceaAddress, target.address, target.chain, ctx.pushNetwork);
1417
+ const { prc20Token, burnAmount } = resolveR2Prc20TokenEvm(params, target.chain, ctx.pushNetwork);
1448
1418
  const targetBytes = payload === '0x'
1449
1419
  ? selectors_1.ZERO_ADDRESS
1450
1420
  : ceaAddress;
1451
1421
  (0, payload_builders_1.assertCeaFundsParkingInvariant)(targetBytes, payload);
1452
- const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetBytes, prc20Token, burnAmount, (_f = params.gasLimit) !== null && _f !== void 0 ? _f : BigInt(0), payload, ueaAddress, (_g = params.maxPCForGas) !== null && _g !== void 0 ? _g : BigInt(0));
1422
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetBytes, prc20Token, burnAmount, (_e = params.gasLimit) !== null && _e !== void 0 ? _e : BigInt(0), payload, ueaAddress, (_f = params.maxPCForGas) !== null && _f !== void 0 ? _f : BigInt(0));
1453
1423
  return { payload, gatewayRequest: outboundReq };
1454
1424
  }
1455
1425
  case route_detector_1.TransactionRoute.CEA_TO_PUSH: {
1456
1426
  // Route 3: CEA -> Push Chain
1457
1427
  // Build CEA multicall (sendUniversalTxToUEA self-call) and wrap in outbound
1458
- if (!((_h = params.from) === null || _h === void 0 ? void 0 : _h.chain)) {
1428
+ if (!((_g = params.from) === null || _g === void 0 ? void 0 : _g.chain)) {
1459
1429
  throw new Error('Route 3 (CEA -> Push) requires from.chain');
1460
1430
  }
1461
1431
  const sourceChain = params.from.chain;
@@ -1482,17 +1452,17 @@ function buildPayloadForRoute(ctx, params, route, nonce) {
1482
1452
  });
1483
1453
  // Payload-only outbound: drain amount lives inside ixData.
1484
1454
  const burnAmount = BigInt(0);
1485
- const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, prc20Token, burnAmount, (_j = params.gasLimit) !== null && _j !== void 0 ? _j : BigInt(0), svmPayload, ueaAddress, (_k = params.maxPCForGas) !== null && _k !== void 0 ? _k : BigInt(0));
1455
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, prc20Token, burnAmount, (_h = params.gasLimit) !== null && _h !== void 0 ? _h : BigInt(0), svmPayload, ueaAddress, (_j = params.maxPCForGas) !== null && _j !== void 0 ? _j : BigInt(0));
1486
1456
  return { payload: svmPayload, gatewayRequest: outboundReq };
1487
1457
  }
1488
- const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, (_l = ctx.rpcUrls[sourceChain]) === null || _l === void 0 ? void 0 : _l[0]);
1458
+ const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, (_k = ctx.rpcUrls[sourceChain]) === null || _k === void 0 ? void 0 : _k[0]);
1489
1459
  // CEA auto-deploys on-chain: Vault.finalizeUniversalTx calls CEAFactory.deployCEA()
1490
1460
  // if CEA doesn't exist yet. No SDK-side blocking needed.
1491
1461
  // Build CEA multicalls (self-calls via sendUniversalTxToUEA)
1492
1462
  const ceaMulticalls = [];
1493
1463
  let tokenAddress = selectors_1.ZERO_ADDRESS;
1494
1464
  let amount = BigInt(0);
1495
- if ((_m = params.funds) === null || _m === void 0 ? void 0 : _m.amount) {
1465
+ if ((_l = params.funds) === null || _l === void 0 ? void 0 : _l.amount) {
1496
1466
  const token = params.funds.token;
1497
1467
  if (token) {
1498
1468
  if (token.mechanism === 'native') {
@@ -1516,7 +1486,7 @@ function buildPayloadForRoute(ctx, params, route, nonce) {
1516
1486
  // Wrap in UniversalPayload struct for the relay.
1517
1487
  let pushPayload = '0x';
1518
1488
  if ((0, data_utils_1.hasExecutablePayloadData)(params.data) ||
1519
- (((_o = params.funds) === null || _o === void 0 ? void 0 : _o.amount) && ((_p = params.value) !== null && _p !== void 0 ? _p : BigInt(0)) > BigInt(0))) {
1489
+ (((_m = params.funds) === null || _m === void 0 ? void 0 : _m.amount) && ((_o = params.value) !== null && _o !== void 0 ? _o : BigInt(0)) > BigInt(0))) {
1520
1490
  const multicallData = (0, payload_builders_1.buildExecuteMulticall)({
1521
1491
  execute: {
1522
1492
  to: pushDestination,
@@ -1538,8 +1508,8 @@ function buildPayloadForRoute(ctx, params, route, nonce) {
1538
1508
  // Route 3: CEA uses its own pre-existing balance — no PRC-20 burn needed.
1539
1509
  // burnAmount = 0 makes this a payload-only outbound relay.
1540
1510
  const burnAmount = BigInt(0);
1541
- const outboundGasLimit = (_q = params.gasLimit) !== null && _q !== void 0 ? _q : selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT;
1542
- const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, prc20Token, burnAmount, outboundGasLimit, ceaPayload, ueaAddress, (_r = params.maxPCForGas) !== null && _r !== void 0 ? _r : BigInt(0));
1511
+ const outboundGasLimit = (_p = params.gasLimit) !== null && _p !== void 0 ? _p : selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT;
1512
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, prc20Token, burnAmount, outboundGasLimit, ceaPayload, ueaAddress, (_q = params.maxPCForGas) !== null && _q !== void 0 ? _q : BigInt(0));
1543
1513
  return { payload: ceaPayload, gatewayRequest: outboundReq };
1544
1514
  }
1545
1515
  default:
@@ -172,8 +172,9 @@ function reconstructR3(universalTxResponse, universalTxData, isFailed, errorMsg)
172
172
  events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_302_02](sourceChain, BigInt(0), BigInt(0)));
173
173
  events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_01](sourceChain));
174
174
  events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_02](universalTxResponse.from, ceaAddress, sourceChain));
175
- // R3 pre-flight reconstruction — R3 EVM burnAmount=0, R3 SVM burnAmount=0
176
- // (post-fix). Only the NATIVE pre-flight check fires; no PRC-20 second hook.
175
+ // R3 pre-flight reconstruction is intentionally approximate: historical
176
+ // response data does not include the SDK-side PRC-20 balance snapshot, so
177
+ // replay only the native check.
177
178
  // R3 single-route uses 303-04 (R3 bucket); see runPreflight in preflight.ts.
178
179
  events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_04](BigInt(0), BigInt(0), true, universalTxResponse.from, 'R3_EVM', { kind: 'NATIVE' }));
179
180
  events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_304_01]());
@@ -275,7 +275,7 @@ export interface WaitOptions {
275
275
  inboundTimeoutMs?: number;
276
276
  /**
277
277
  * Override the poll interval (ms) between outbound status checks. Default
278
- * is 5000ms. Lower values give faster feedback on fast relays; higher
278
+ * is 3000ms. Lower values give faster feedback on fast relays; higher
279
279
  * values reduce RPC pressure. Only applies to R2 / R3 outbound polling.
280
280
  */
281
281
  outboundPollingIntervalMs?: number;
@@ -671,7 +671,7 @@ export interface CascadeHopInfo {
671
671
  export interface CascadeTrackOptions {
672
672
  /** Polling interval (default: 5000ms) */
673
673
  pollingIntervalMs?: number;
674
- /** Total timeout (default: 600000ms = 10 min) */
674
+ /** Total timeout (default: 300000ms = 5 min) */
675
675
  timeout?: number;
676
676
  /** Per-hop progress callback */
677
677
  progressHook?: (event: CascadeProgressEvent) => void;
@@ -777,16 +777,16 @@ export interface OutboundTxDetails {
777
777
  */
778
778
  export interface WaitForOutboundOptions {
779
779
  /**
780
- * Initial wait before first poll (default: 30000ms)
780
+ * Initial wait before first poll (default: 15000ms)
781
781
  * Gives relay time to process before we start polling
782
782
  */
783
783
  initialWaitMs?: number;
784
784
  /**
785
- * Interval between polls (default: 5000ms)
785
+ * Interval between polls (default: 3000ms)
786
786
  */
787
787
  pollingIntervalMs?: number;
788
788
  /**
789
- * Total timeout in milliseconds (default: 120000ms)
789
+ * Total timeout in milliseconds (default: 300000ms)
790
790
  * Measured from start, includes initial wait
791
791
  */
792
792
  timeout?: number;
@@ -35,7 +35,8 @@ export declare const MULTICALL_TUPLE_TYPE: {
35
35
  */
36
36
  export declare function buildCeaMulticallPayload(multicalls: MultiCall[]): `0x${string}`;
37
37
  /**
38
- * Build an ABI-encoded UniversalPayload struct for inbound relay (Route 3 CEA→Push).
38
+ * Build an ABI-encoded UniversalPayload struct for EVM-origin inbound relay
39
+ * (Route 3 CEA→Push).
39
40
  *
40
41
  * The relay and Push Chain gateway expect the payload parameter of sendUniversalTxToUEA
41
42
  * to be a full UniversalPayload struct: (address to, uint256 value, bytes data, uint256 gasLimit,
@@ -53,6 +54,17 @@ export declare function buildInboundUniversalPayload(multicallData: `0x${string}
53
54
  nonce?: bigint;
54
55
  deadline?: bigint;
55
56
  }): `0x${string}`;
57
+ /**
58
+ * Build a Borsh-encoded UniversalPayload for SVM-origin inbound relay.
59
+ *
60
+ * Push Chain decodes SVM-origin UniversalTx payloads with
61
+ * DecodeUniversalPayloadSolana, not the EVM ABI decoder used for EVM origins.
62
+ */
63
+ export declare function buildInboundUniversalPayloadSvm(multicallData: `0x${string}`, opts?: {
64
+ gasLimit?: bigint;
65
+ nonce?: bigint;
66
+ deadline?: bigint;
67
+ }): `0x${string}`;
56
68
  /**
57
69
  * Build sendUniversalTxToUEA call for CEA self-call (Route 3)
58
70
  *
@@ -4,6 +4,7 @@ exports.MULTICALL_TUPLE_TYPE = void 0;
4
4
  exports.buildExecuteMulticall = buildExecuteMulticall;
5
5
  exports.buildCeaMulticallPayload = buildCeaMulticallPayload;
6
6
  exports.buildInboundUniversalPayload = buildInboundUniversalPayload;
7
+ exports.buildInboundUniversalPayloadSvm = buildInboundUniversalPayloadSvm;
7
8
  exports.buildSendUniversalTxToUEA = buildSendUniversalTxToUEA;
8
9
  exports.buildOutboundRequest = buildOutboundRequest;
9
10
  exports.assertCeaFundsParkingInvariant = assertCeaFundsParkingInvariant;
@@ -24,6 +25,8 @@ const selectors_1 = require("../constants/selectors");
24
25
  const chain_1 = require("../constants/chain");
25
26
  const enums_1 = require("../constants/enums");
26
27
  const data_utils_1 = require("./data-utils");
28
+ const tx_1 = require("../generated/v1/tx");
29
+ const signing_1 = require("./internals/signing");
27
30
  function buildExecuteMulticall({ execute, ueaAddress, logger, allowSelfValueCall = false, }) {
28
31
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
29
32
  const log = (msg) => logger === null || logger === void 0 ? void 0 : logger(msg);
@@ -161,7 +164,8 @@ function buildCeaMulticallPayload(multicalls) {
161
164
  return `${selectors_1.UEA_MULTICALL_SELECTOR}${encoded.slice(2)}`;
162
165
  }
163
166
  /**
164
- * Build an ABI-encoded UniversalPayload struct for inbound relay (Route 3 CEA→Push).
167
+ * Build an ABI-encoded UniversalPayload struct for EVM-origin inbound relay
168
+ * (Route 3 CEA→Push).
165
169
  *
166
170
  * The relay and Push Chain gateway expect the payload parameter of sendUniversalTxToUEA
167
171
  * to be a full UniversalPayload struct: (address to, uint256 value, bytes data, uint256 gasLimit,
@@ -205,6 +209,27 @@ function buildInboundUniversalPayload(multicallData, opts) {
205
209
  },
206
210
  ]);
207
211
  }
212
+ /**
213
+ * Build a Borsh-encoded UniversalPayload for SVM-origin inbound relay.
214
+ *
215
+ * Push Chain decodes SVM-origin UniversalTx payloads with
216
+ * DecodeUniversalPayloadSolana, not the EVM ABI decoder used for EVM origins.
217
+ */
218
+ function buildInboundUniversalPayloadSvm(multicallData, opts) {
219
+ var _a, _b, _c;
220
+ const payload = {
221
+ to: selectors_1.ZERO_ADDRESS,
222
+ value: BigInt(0).toString(),
223
+ data: multicallData,
224
+ gasLimit: ((_a = opts === null || opts === void 0 ? void 0 : opts.gasLimit) !== null && _a !== void 0 ? _a : BigInt(100000000)).toString(),
225
+ maxFeePerGas: BigInt(10000000000).toString(),
226
+ maxPriorityFeePerGas: BigInt(0).toString(),
227
+ nonce: ((_b = opts === null || opts === void 0 ? void 0 : opts.nonce) !== null && _b !== void 0 ? _b : BigInt(0)).toString(),
228
+ deadline: ((_c = opts === null || opts === void 0 ? void 0 : opts.deadline) !== null && _c !== void 0 ? _c : BigInt(9999999999)).toString(),
229
+ vType: tx_1.VerificationType.universalTxVerification,
230
+ };
231
+ return `0x${(0, signing_1.encodeUniversalPayloadSvm)(payload).toString('hex')}`;
232
+ }
208
233
  /**
209
234
  * Build sendUniversalTxToUEA call for CEA self-call (Route 3)
210
235
  *
@@ -6,6 +6,9 @@
6
6
  * utxId = sha256(`<sourceChainCaip>:<txHash>:<logIndex>`)
7
7
  * See push-chain/x/uexecutor/types/keys.go:49-53 (GetInboundUniversalTxKey).
8
8
  *
9
+ * For SVM sources, `<txHash>` is the 0x-prefixed hex encoding of the 64-byte
10
+ * Solana signature and `<logIndex>` is the event's index in `meta.logMessages`.
11
+ *
9
12
  * For Push-initiated outbounds (pcTx entries) the formula drops the log index:
10
13
  * utxId = sha256(`<pcChainCaip>:<pcTxHash>`)
11
14
  * See push-chain/x/uexecutor/types/keys.go:63-67 (GetPcUniversalTxKey).
@@ -14,6 +14,9 @@ const tslib_1 = require("tslib");
14
14
  * utxId = sha256(`<sourceChainCaip>:<txHash>:<logIndex>`)
15
15
  * See push-chain/x/uexecutor/types/keys.go:49-53 (GetInboundUniversalTxKey).
16
16
  *
17
+ * For SVM sources, `<txHash>` is the 0x-prefixed hex encoding of the 64-byte
18
+ * Solana signature and `<logIndex>` is the event's index in `meta.logMessages`.
19
+ *
17
20
  * For Push-initiated outbounds (pcTx entries) the formula drops the log index:
18
21
  * utxId = sha256(`<pcChainCaip>:<pcTxHash>`)
19
22
  * See push-chain/x/uexecutor/types/keys.go:63-67 (GetPcUniversalTxKey).
@@ -22,6 +25,7 @@ const tslib_1 = require("tslib");
22
25
  * `pushClient.getUniversalTxByIdV2`, no event-attribute predicate needed.
23
26
  */
24
27
  const viem_1 = require("viem");
28
+ const bs58_1 = require("../internal/bs58");
25
29
  const normalize_1 = require("./normalize");
26
30
  const STATUS_NAMES = {
27
31
  0: 'UNIVERSAL_TX_STATUS_UNSPECIFIED',
@@ -45,14 +49,25 @@ const INBOUND_EVENT_NAMES = new Set([
45
49
  'UniversalTx',
46
50
  'RevertUniversalTx',
47
51
  ]);
52
+ const HEX_64_BYTE_RE = /^(?:0x)?[0-9a-fA-F]{128}$/;
53
+ function normalizeInboundTxHashForKey(sourceChainCaip, externalTxHash) {
54
+ if (!sourceChainCaip.startsWith('solana:')) {
55
+ return externalTxHash.startsWith('0x')
56
+ ? externalTxHash
57
+ : `0x${externalTxHash}`;
58
+ }
59
+ if (externalTxHash.startsWith('0x'))
60
+ return externalTxHash;
61
+ if (HEX_64_BYTE_RE.test(externalTxHash))
62
+ return `0x${externalTxHash}`;
63
+ return (0, viem_1.toHex)(bs58_1.bs58.decode(externalTxHash));
64
+ }
48
65
  /**
49
66
  * Deterministic child universalTxId for an inbound event on an external chain.
50
67
  * Mirrors `GetInboundUniversalTxKey` from the chain module.
51
68
  */
52
69
  function deriveChildUniversalTxId(sourceChainCaip, externalTxHash, logIndex) {
53
- const normHash = externalTxHash.startsWith('0x')
54
- ? externalTxHash
55
- : `0x${externalTxHash}`;
70
+ const normHash = normalizeInboundTxHashForKey(sourceChainCaip, externalTxHash);
56
71
  const input = `${sourceChainCaip}:${normHash}:${logIndex}`;
57
72
  return (0, viem_1.sha256)((0, viem_1.toBytes)(input));
58
73
  }
@@ -15,12 +15,9 @@ const tslib_1 = require("tslib");
15
15
  * return, same universalTxId derivation strategy (primary-log fallback
16
16
  * via deriveChildUniversalTxId on source-chain inbounds).
17
17
  *
18
- * Open contract with the push-chain cosmos keeper (documented in the
19
- * plan): the keeper must derive SVM inbound universalTxIds using the
20
- * Solana base58 signature verbatim inside `${caip}:${sig}:${logIndex}`.
21
- * If the keeper uses a different representation (hex bytes, sub_tx_id
22
- * field, etc.) the `rawSource` argument to deriveChildUniversalTxId below
23
- * needs to swap — single line.
18
+ * The Push keeper stores SVM inbound ids using the 0x-prefixed hex encoding
19
+ * of the 64-byte Solana signature and the index of the Anchor event inside
20
+ * `meta.logMessages`: `${caip}:${hexSig}:${logMessageIndex}`.
24
21
  */
25
22
  const anchor_1 = require("@coral-xyz/anchor");
26
23
  const web3_js_1 = require("@solana/web3.js");
@@ -91,12 +88,11 @@ function detectUniversalTxSvm(signature_1, chain_2) {
91
88
  }
92
89
  const eventCoder = new anchor_1.BorshEventCoder(universalGatewayV0_json_1.default);
93
90
  const matchingLogs = [];
94
- // Walk logMessages for `Program data:` entries. Each matched event gets
95
- // assigned an ordinal logIndex — its position among matching events.
96
- // This is the deterministic integer the cosmos keeper is expected to use
97
- // when it derives an inbound universalTxId for SVM sources.
98
- let ordinal = 0;
99
- for (const rawLog of logMessages) {
91
+ // Walk logMessages for `Program data:` entries. Each matched event keeps
92
+ // the actual Solana log-message index because that is the value validators
93
+ // submit as the inbound log_index and the keeper uses for the UTX key.
94
+ for (let logIndex = 0; logIndex < logMessages.length; logIndex++) {
95
+ const rawLog = logMessages[logIndex];
100
96
  if (typeof rawLog !== 'string')
101
97
  continue;
102
98
  const prefix = 'Program data: ';
@@ -135,10 +131,9 @@ function detectUniversalTxSvm(signature_1, chain_2) {
135
131
  matchingLogs.push({
136
132
  eventName,
137
133
  address: emitter,
138
- logIndex: ordinal,
134
+ logIndex,
139
135
  args,
140
136
  });
141
- ordinal += 1;
142
137
  }
143
138
  if (matchingLogs.length === 0) {
144
139
  notes.push('svm tx has no decoded universal-tx events');
@@ -148,8 +143,7 @@ function detectUniversalTxSvm(signature_1, chain_2) {
148
143
  notes.push(...classified.notes);
149
144
  const decoded = Object.assign({}, classified.decoded);
150
145
  // Inbound fallback for universalTxId: same formula the EVM branch uses,
151
- // but the raw signature flows through untouched (no 0x-prefix rewrite,
152
- // see deriveSvmChildUniversalTxId in child-inbounds.ts).
146
+ // with the canonical 0x-hex Solana signature and log-message index.
153
147
  if (!decoded.universalTxId) {
154
148
  const caip = chain;
155
149
  if (INBOUND_KINDS.has(classified.kind) &&