@pushchain/core 6.0.9 → 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.9",
3
+ "version": "6.0.10",
4
4
  "homepage": "https://push.org",
5
5
  "keywords": [
6
6
  "pushchain",
@@ -444,8 +444,8 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
444
444
  effectiveGasLimit = result.gasLimitUsed;
445
445
  }
446
446
  return Object.assign(Object.assign({}, baseDescriptor), { sourceChain, ceaAddress: ceaPdaHex, isSvmTarget: true, prc20Token,
447
- // Route 3 is payload-only on Push: the drain amount lives inside the
448
- // 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.
449
449
  burnAmount: BigInt(0), gasToken,
450
450
  gasFee,
451
451
  gasPrice, gasLimit: effectiveGasLimit, sizing });
@@ -966,12 +966,10 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
966
966
  : (firstHop === null || firstHop === void 0 ? void 0 : firstHop.ceaAddress) || ueaAddress;
967
967
  (0, payload_builders_1.assertCeaFundsParkingInvariant)(targetForOutbound, outboundPayload);
968
968
  }
969
- // Per-segment PRC-20 burn-balance pre-check (cascade R2-style segments
970
- // only — INBOUND_FROM_CEA segments are payload-only with totalBurnAmount=0
971
- // per the 2026-03-19 fix). Skip MIGRATION (totalBurnAmount=0). Fires
972
- // whenever burnAmount > 0 even when burn token equals gas token — the
973
- // gateway's transferFrom happens BEFORE the gas swap, so user must
974
- // 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.
975
973
  const segBurnAmount = segment.totalBurnAmount || BigInt(0);
976
974
  const segBurnToken = segment.prc20Token;
977
975
  if (segBurnAmount > BigInt(0) && segBurnToken && !isMigration) {
@@ -1107,18 +1105,18 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
1107
1105
  ];
1108
1106
  }
1109
1107
  // Primary hop carries the sourceChain / CEA address (shared across merged hops).
1110
- const hop0 = segment.hops[0];
1108
+ const hop = segment.hops[0];
1109
+ const sourceChain = hop.sourceChain;
1110
+ const ceaAddress = hop.ceaAddress || ueaAddress;
1111
1111
  let intermediatePayload = '0x';
1112
1112
  if (accumulatedPushMulticalls.length > 0) {
1113
1113
  const multicallPayload = (0, payload_builder_1.buildMulticallPayloadData)(ctx, ueaAddress, accumulatedPushMulticalls);
1114
1114
  // +1: the outbound tx consumes one nonce via execute()
1115
- intermediatePayload = (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, {
1116
- nonce: (ueaNonce !== null && ueaNonce !== void 0 ? ueaNonce : BigInt(0)) + BigInt(1),
1117
- });
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 });
1118
1119
  }
1119
- const hop = segment.hops[0];
1120
- const sourceChain = hop.sourceChain;
1121
- const ceaAddress = hop.ceaAddress || ueaAddress;
1122
1120
  // SVM chains: build SVM CPI payload instead of EVM CEA multicall
1123
1121
  if ((0, payload_builders_1.isSvmChain)(sourceChain)) {
1124
1122
  const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
@@ -62,8 +62,8 @@ export declare function executeCeaToPush(ctx: OrchestratorContext, params: Unive
62
62
  *
63
63
  * Unlike EVM Route 3 which builds CEA multicalls, SVM Route 3 encodes a
64
64
  * `send_universal_tx_to_uea` instruction as an execute payload targeting
65
- * the SVM gateway program (self-call). The drain amount is embedded in
66
- * 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.
67
67
  */
68
68
  export declare function executeCeaToPushSvm(ctx: OrchestratorContext, params: UniversalExecuteParams, sourceChain: CHAIN, executeFn: ExecuteFn): Promise<UniversalTxResponse>;
69
69
  /**
@@ -137,6 +137,7 @@ function resolveR3SvmDrain(params, sourceChain, pushNetwork) {
137
137
  var _a;
138
138
  let drainAmount = BigInt(0);
139
139
  let tokenMintHex;
140
+ let splMintBase58;
140
141
  let prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, pushNetwork);
141
142
  if (((_a = params.funds) === null || _a === void 0 ? void 0 : _a.amount) && params.funds.amount > BigInt(0)) {
142
143
  drainAmount = params.funds.amount;
@@ -144,13 +145,14 @@ function resolveR3SvmDrain(params, sourceChain, pushNetwork) {
144
145
  if ((token === null || token === void 0 ? void 0 : token.mechanism) === 'approve') {
145
146
  const mintPk = new web3_js_1.PublicKey(token.address);
146
147
  tokenMintHex = ('0x' + Buffer.from(mintPk.toBytes()).toString('hex'));
148
+ splMintBase58 = token.address;
147
149
  prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
148
150
  }
149
151
  }
150
152
  else if (params.value && params.value > BigInt(0)) {
151
153
  drainAmount = params.value;
152
154
  }
153
- return { prc20Token, drainAmount, tokenMintHex };
155
+ return { prc20Token, drainAmount, tokenMintHex, splMintBase58 };
154
156
  }
155
157
  function buildR3SvmExtraPayload(ctx, params, ueaAddress, sourceChain, inboundNonce) {
156
158
  var _a, _b;
@@ -166,7 +168,7 @@ function buildR3SvmExtraPayload(ctx, params, ueaAddress, sourceChain, inboundNon
166
168
  ueaAddress,
167
169
  allowSelfValueCall: true,
168
170
  }));
169
- const inboundPayload = (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, {
171
+ const inboundPayload = (0, payload_builders_1.buildInboundUniversalPayloadSvm)(multicallPayload, {
170
172
  nonce: inboundNonce !== null && inboundNonce !== void 0 ? inboundNonce : BigInt(0),
171
173
  });
172
174
  return (0, viem_1.hexToBytes)(inboundPayload);
@@ -1143,8 +1145,8 @@ function executeCeaToPush(ctx, params, executeFn) {
1143
1145
  *
1144
1146
  * Unlike EVM Route 3 which builds CEA multicalls, SVM Route 3 encodes a
1145
1147
  * `send_universal_tx_to_uea` instruction as an execute payload targeting
1146
- * the SVM gateway program (self-call). The drain amount is embedded in
1147
- * 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.
1148
1150
  */
1149
1151
  function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
1150
1152
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
@@ -1162,10 +1164,9 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
1162
1164
  const gatewayProgramHex = ('0x' + Buffer.from(programPk.toBytes()).toString('hex'));
1163
1165
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_301, sourceChain, lockerContract);
1164
1166
  (0, context_1.printLog)(ctx, `executeCeaToPushSvm — sourceChain: ${sourceChain}, gateway: ${lockerContract}`);
1165
- // Route 3 SVM: CEA uses its own pre-existing balance — no PRC-20 burn
1166
- // needed on Push Chain. The outer request token must still match the source
1167
- // asset so the relayer supplies the same Solana mint that the payload
1168
- // 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.
1169
1170
  const { prc20Token, drainAmount, tokenMintHex } = resolveR3SvmDrain(params, sourceChain, ctx.pushNetwork);
1170
1171
  // Derive CEA PDA as revert recipient: ["push_identity", ueaAddress_20_bytes]
1171
1172
  const ueaBytes = Buffer.from(ueaAddress.slice(2), 'hex'); // 20 bytes
@@ -1190,12 +1191,6 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
1190
1191
  revertRecipientHex: ceaPdaHex,
1191
1192
  });
1192
1193
  (0, context_1.printLog)(ctx, `executeCeaToPushSvm — drainAmount: ${drainAmount.toString()}, payload length: ${(svmPayload.length - 2) / 2} bytes`);
1193
- // burnAmount = 0 (payload-only outbound — drain amount lives inside ixData).
1194
- // Mirrors the 2026-03-19 cascade fix that removed the 1-wei workaround for
1195
- // SVM payload-only hops because it caused `InsufficientBalance (0xf4d678b8)`
1196
- // reverts on fresh UEAs that don't hold the source-chain native PRC-20
1197
- // (e.g. pSOL). The UGPC upgrade post-2026-03-19 already accepts amount = 0
1198
- // on the cascade path, and the same precompile is used here.
1199
1194
  const burnAmount = BigInt(0);
1200
1195
  let effectiveGasLimit = (_a = params.gasLimit) !== null && _a !== void 0 ? _a : BigInt(0);
1201
1196
  // Build outbound request: recipient = gateway program (self-call)
@@ -1218,7 +1213,7 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
1218
1213
  prc20Token,
1219
1214
  quote: result,
1220
1215
  splMintBase58: undefined,
1221
- burnAmount: BigInt(0),
1216
+ burnAmount,
1222
1217
  pathTag: 'executeCeaToPushSvm',
1223
1218
  });
1224
1219
  gasToken = result.gasToken;
@@ -1290,8 +1285,8 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
1290
1285
  }
1291
1286
  // SVM warn-threshold telemetry (no truncation — pre-flight handles drain protection).
1292
1287
  (0, preflight_1.maybeFireSvmWarnThreshold)(ctx, nativeValueForGas, gasToken, 'R3_SVM');
1293
- // Pre-flight (R3 SVM): no PRC-20 burn check. R3 SVM uses burnAmount=0
1294
- // 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.
1295
1290
  (0, preflight_1.runPreflight)({
1296
1291
  ctx,
1297
1292
  ueaAddress,
@@ -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]());
@@ -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) &&