@pushchain/core 6.0.15 → 6.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,6 +15,8 @@
15
15
  * `executeFn` callback that replaces the former `this.execute()` call.
16
16
  */
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.getRequiredNativeBalanceForOutbound = getRequiredNativeBalanceForOutbound;
19
+ exports.shouldSkipFeeLockingForOutbound = shouldSkipFeeLockingForOutbound;
18
20
  exports.resolveR2DestinationFundsToken = resolveR2DestinationFundsToken;
19
21
  exports.executeMultiChain = executeMultiChain;
20
22
  exports.executeUoaToCea = executeUoaToCea;
@@ -46,6 +48,13 @@ const payload_builders_1 = require("../payload-builders");
46
48
  const selectors_1 = require("../../constants/selectors");
47
49
  const erc20_evm_1 = require("../../constants/abi/erc20.evm");
48
50
  const push_chain_1 = require("../../push-chain/push-chain");
51
+ function getRequiredNativeBalanceForOutbound(nativeValueForGas, gasReserve) {
52
+ return nativeValueForGas + gasReserve;
53
+ }
54
+ function shouldSkipFeeLockingForOutbound(opts) {
55
+ return (opts.isUEADeployed &&
56
+ opts.balance >= getRequiredNativeBalanceForOutbound(opts.nativeValueForGas, opts.gasReserve));
57
+ }
49
58
  // =============================================================================
50
59
  // Route 2 (UEA → CEA) phase helpers
51
60
  // =============================================================================
@@ -524,6 +533,13 @@ function executeUoaToCea(ctx, params, executeFn) {
524
533
  args: [ueaAddress],
525
534
  });
526
535
  }
536
+ const requiredNativeBalanceR2Evm = getRequiredNativeBalanceForOutbound(nativeValueForGas, EVM_GAS_RESERVE);
537
+ const skipFeeLockingR2Evm = shouldSkipFeeLockingForOutbound({
538
+ isUEADeployed,
539
+ balance: effectiveBalance,
540
+ nativeValueForGas,
541
+ gasReserve: EVM_GAS_RESERVE,
542
+ });
527
543
  (0, preflight_1.runPreflight)({
528
544
  ctx,
529
545
  ueaAddress,
@@ -557,7 +573,7 @@ function executeUoaToCea(ctx, params, executeFn) {
557
573
  isDeployed: isUEADeployed,
558
574
  nonce: ueaNonce,
559
575
  balance: ueaBalance,
560
- }, _skipFeeLocking: isUEADeployed }, (!isUEADeployed ? { _minimumDepositUsd: ROUTE2_MINIMUM_DEPOSIT_USD } : {}));
576
+ }, _skipFeeLocking: skipFeeLockingR2Evm, _requiredFundsOverride: requiredNativeBalanceR2Evm }, (!skipFeeLockingR2Evm ? { _minimumDepositUsd: ROUTE2_MINIMUM_DEPOSIT_USD } : {}));
561
577
  // Signature request — user is prompted to sign the universal payload.
562
578
  // executeFn is monolithic (sign → broadcast), so we emit the logical
563
579
  // post-sign / pre-broadcast markers as a burst after it returns so
@@ -614,6 +630,7 @@ function executeUoaToCeaSvm(ctx, params, target, executeFn) {
614
630
  if (hasSvmExecute) {
615
631
  (0, context_1.printLog)(ctx, `executeUoaToCeaSvm — encoded execute payload: ${(svmPayload.length - 2) / 2} bytes`);
616
632
  }
633
+ (0, payload_builders_1.assertSvmPayloadWithinRelayLimit)(svmPayload, `Route 2 SVM ${targetChain}`);
617
634
  // --- Determine PRC-20 token and burn amount (chain-local, pre-203) ---
618
635
  const { prc20Token, burnAmount } = resolveR2Prc20TokenSvm(params, targetChain, ctx.pushNetwork, hasSvmExecute);
619
636
  if (params.value && params.value > BigInt(0)) {
@@ -756,6 +773,13 @@ function executeUoaToCeaSvm(ctx, params, target, executeFn) {
756
773
  args: [ueaAddress],
757
774
  });
758
775
  }
776
+ const requiredNativeBalanceR2Svm = getRequiredNativeBalanceForOutbound(nativeValueForGas, SVM_GAS_RESERVE);
777
+ const skipFeeLockingR2Svm = shouldSkipFeeLockingForOutbound({
778
+ isUEADeployed,
779
+ balance: ueaBalance,
780
+ nativeValueForGas,
781
+ gasReserve: SVM_GAS_RESERVE,
782
+ });
759
783
  (0, preflight_1.runPreflight)({
760
784
  ctx,
761
785
  ueaAddress,
@@ -791,7 +815,8 @@ function executeUoaToCeaSvm(ctx, params, target, executeFn) {
791
815
  nonce: ueaNonce,
792
816
  balance: ueaBalance,
793
817
  },
794
- _skipFeeLocking: isUEADeployed, // skip fee-locking only if UEA is already deployed
818
+ _skipFeeLocking: skipFeeLockingR2Svm,
819
+ _requiredFundsOverride: requiredNativeBalanceR2Svm,
795
820
  };
796
821
  // Signature request — user is prompted to sign the universal payload.
797
822
  // Emit spec-ordered burst after executeFn returns (sign → verify → broadcast).
@@ -1077,6 +1102,13 @@ function executeCeaToPush(ctx, params, executeFn) {
1077
1102
  // payload-only (UEA never holds the source-chain PRC-20; see plan §9 #4).
1078
1103
  // Native UPC check still applies for the gas swap. It warns by default and
1079
1104
  // throws only when options.enforceGasCheck=true.
1105
+ const requiredNativeBalanceR3Evm = getRequiredNativeBalanceForOutbound(nativeValueForGas, OUTBOUND_GAS_RESERVE_R3);
1106
+ const skipFeeLockingR3Evm = shouldSkipFeeLockingForOutbound({
1107
+ isUEADeployed,
1108
+ balance: effectiveBalance,
1109
+ nativeValueForGas,
1110
+ gasReserve: OUTBOUND_GAS_RESERVE_R3,
1111
+ });
1080
1112
  (0, preflight_1.runPreflight)({
1081
1113
  ctx,
1082
1114
  ueaAddress,
@@ -1104,7 +1136,7 @@ function executeCeaToPush(ctx, params, executeFn) {
1104
1136
  isDeployed: isUEADeployed,
1105
1137
  nonce: ueaNonce,
1106
1138
  balance: ueaBalance,
1107
- }, _skipFeeLocking: isUEADeployed }, (!isUEADeployed ? { _minimumDepositUsd: ROUTE3_MINIMUM_DEPOSIT_USD } : {}));
1139
+ }, _skipFeeLocking: skipFeeLockingR3Evm, _requiredFundsOverride: requiredNativeBalanceR3Evm }, (!skipFeeLockingR3Evm ? { _minimumDepositUsd: ROUTE3_MINIMUM_DEPOSIT_USD } : {}));
1108
1140
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_304_01);
1109
1141
  let response;
1110
1142
  try {
@@ -1190,6 +1222,7 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
1190
1222
  extraPayload: buildR3SvmExtraPayload(ctx, params, ueaAddress, sourceChain, ueaNonce + BigInt(1)),
1191
1223
  revertRecipientHex: ceaPdaHex,
1192
1224
  });
1225
+ (0, payload_builders_1.assertSvmPayloadWithinRelayLimit)(svmPayload, `Route 3 SVM ${sourceChain}`);
1193
1226
  (0, context_1.printLog)(ctx, `executeCeaToPushSvm — drainAmount: ${drainAmount.toString()}, payload length: ${(svmPayload.length - 2) / 2} bytes`);
1194
1227
  const burnAmount = BigInt(0);
1195
1228
  let effectiveGasLimit = (_a = params.gasLimit) !== null && _a !== void 0 ? _a : BigInt(0);
@@ -1285,6 +1318,13 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
1285
1318
  }
1286
1319
  // SVM warn-threshold telemetry (no truncation — pre-flight handles drain protection).
1287
1320
  (0, preflight_1.maybeFireSvmWarnThreshold)(ctx, nativeValueForGas, gasToken, 'R3_SVM');
1321
+ const requiredNativeBalanceR3Svm = getRequiredNativeBalanceForOutbound(nativeValueForGas, OUTBOUND_GAS_RESERVE_R3_SVM);
1322
+ const skipFeeLockingR3Svm = shouldSkipFeeLockingForOutbound({
1323
+ isUEADeployed,
1324
+ balance: currentBalance,
1325
+ nativeValueForGas,
1326
+ gasReserve: OUTBOUND_GAS_RESERVE_R3_SVM,
1327
+ });
1288
1328
  // Pre-flight (R3 SVM): no PRC-20 burn check. The drain amount lives in the
1289
1329
  // embedded Solana instruction and is withdrawn from the external CEA.
1290
1330
  (0, preflight_1.runPreflight)({
@@ -1312,8 +1352,8 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
1312
1352
  const executeParams = Object.assign({ to: ueaAddress, value: multicallNativeValue, data: pushChainMulticalls, _ueaStatus: {
1313
1353
  isDeployed: isUEADeployed,
1314
1354
  nonce: ueaNonce,
1315
- balance: ueaBalance,
1316
- }, _skipFeeLocking: isUEADeployed }, (!isUEADeployed ? { _minimumDepositUsd: utils_1.Utils.helpers.parseUnits('10', 8) } : {}));
1355
+ balance: currentBalance,
1356
+ }, _skipFeeLocking: skipFeeLockingR3Svm, _requiredFundsOverride: requiredNativeBalanceR3Svm }, (!skipFeeLockingR3Svm ? { _minimumDepositUsd: utils_1.Utils.helpers.parseUnits('10', 8) } : {}));
1317
1357
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_304_01);
1318
1358
  let response;
1319
1359
  try {
@@ -1391,6 +1431,7 @@ function buildPayloadForRoute(ctx, params, route, nonce) {
1391
1431
  to: target,
1392
1432
  senderUea: ueaAddress,
1393
1433
  });
1434
+ (0, payload_builders_1.assertSvmPayloadWithinRelayLimit)(payload, `Route 2 SVM ${target.chain}`);
1394
1435
  let prc20Token = selectors_1.ZERO_ADDRESS;
1395
1436
  let burnAmount = BigInt(0);
1396
1437
  if ((_a = params.funds) === null || _a === void 0 ? void 0 : _a.amount) {
@@ -1450,6 +1491,7 @@ function buildPayloadForRoute(ctx, params, route, nonce) {
1450
1491
  extraPayload: buildR3SvmExtraPayload(ctx, params, ueaAddress, sourceChain, nonce + BigInt(1)),
1451
1492
  revertRecipientHex: ceaPdaHex2,
1452
1493
  });
1494
+ (0, payload_builders_1.assertSvmPayloadWithinRelayLimit)(svmPayload, `Route 3 SVM ${sourceChain}`);
1453
1495
  // Payload-only outbound: drain amount lives inside ixData.
1454
1496
  const burnAmount = BigInt(0);
1455
1497
  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));
@@ -25,15 +25,25 @@ function buildDomainSeparator(vm, chainId, version, verifyingContract) {
25
25
  const domainTypeHash = (0, viem_1.keccak256)((0, viem_1.toBytes)(vm === enums_1.VM.EVM
26
26
  ? 'EIP712Domain(string version,uint256 chainId,address verifyingContract)'
27
27
  : 'EIP712Domain_SVM(string version,string chainId,address verifyingContract)'));
28
+ // NOTE (SVM chainId encoding): the UEA_SVM currently deployed on Donut
29
+ // abi-encodes `_universalAccountId.chainId` as a RAW string in its
30
+ // domainSeparator() (pre-EIP-712-fix). We must match that byte-for-byte or the
31
+ // ed25519 signature is over the wrong digest and `executeUniversalTx` reverts
32
+ // with `InvalidSVMSignature` (0xa764e90c). The newer UEA_SVM (core-contracts
33
+ // commits 5d97ff4 "hash SVM domain separator chainId per EIP-712" + 62d47cc
34
+ // "bind … via EIP-712 salt") keccak-hashes chainId AND adds a `bytes32 salt =
35
+ // bytes32(block.chainid)`. When Donut is upgraded to that version, change the
36
+ // SVM branch back to `keccak256(toBytes(chainId))` and append the salt field.
37
+ // Verified against on-chain getUniversalPayloadHash on UEA 0xad977a75 (Donut).
28
38
  return (0, viem_1.keccak256)((0, viem_1.encodeAbiParameters)([
29
39
  { name: 'typeHash', type: 'bytes32' },
30
40
  { name: 'version', type: 'bytes32' },
31
- { name: 'chainId', type: vm === enums_1.VM.EVM ? 'uint256' : 'bytes32' },
41
+ { name: 'chainId', type: vm === enums_1.VM.EVM ? 'uint256' : 'string' },
32
42
  { name: 'verifyingContract', type: 'address' },
33
43
  ], [
34
44
  domainTypeHash,
35
45
  (0, viem_1.keccak256)((0, viem_1.toBytes)(version)),
36
- vm === enums_1.VM.EVM ? BigInt(chainId) : (0, viem_1.keccak256)((0, viem_1.toBytes)(chainId)),
46
+ vm === enums_1.VM.EVM ? BigInt(chainId) : chainId,
37
47
  verifyingContract,
38
48
  ]));
39
49
  }
@@ -203,14 +213,28 @@ function encodeUniversalPayload(payload) {
203
213
  ]);
204
214
  }
205
215
  function encodeUniversalPayloadSvm(payload) {
206
- const writeU64 = (val) => {
216
+ const U64_MAX = (BigInt(1) << BigInt(64)) - BigInt(1);
217
+ const I64_MIN = -(BigInt(1) << BigInt(63));
218
+ const I64_MAX = (BigInt(1) << BigInt(63)) - BigInt(1);
219
+ const viewFor = (buf) => new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
220
+ const assertRange = (field, val, min, max) => {
221
+ if (val < min || val > max) {
222
+ throw new RangeError(`SVM payload field ${field} out of range: ${val.toString()} ` +
223
+ `(expected ${min.toString()}..${max.toString()})`);
224
+ }
225
+ };
226
+ const writeU64 = (field, val) => {
227
+ const n = BigInt(val);
228
+ assertRange(field, n, BigInt(0), U64_MAX);
207
229
  const b = Buffer.alloc(8);
208
- b.writeBigUInt64LE(BigInt(val), 0);
230
+ viewFor(b).setBigUint64(0, n, true);
209
231
  return b;
210
232
  };
211
- const writeI64 = (val) => {
233
+ const writeI64 = (field, val) => {
234
+ const n = BigInt(val);
235
+ assertRange(field, n, I64_MIN, I64_MAX);
212
236
  const b = Buffer.alloc(8);
213
- b.writeBigInt64LE(BigInt(val), 0);
237
+ viewFor(b).setBigInt64(0, n, true);
214
238
  return b;
215
239
  };
216
240
  const writeVecU8 = (val) => {
@@ -218,7 +242,7 @@ function encodeUniversalPayloadSvm(payload) {
218
242
  ? val
219
243
  : Buffer.from(val);
220
244
  const len = Buffer.alloc(4);
221
- len.writeUInt32LE(bytes.length, 0);
245
+ viewFor(len).setUint32(0, bytes.length, true);
222
246
  return Buffer.concat([len, bytes]);
223
247
  };
224
248
  const writeU8 = (val) => Buffer.from([val]);
@@ -227,18 +251,18 @@ function encodeUniversalPayloadSvm(payload) {
227
251
  const hex = to.slice(2).padStart(40, '0');
228
252
  return Buffer.from(hex, 'hex');
229
253
  })();
230
- const valueBytes = writeU64(payload.value);
254
+ const valueBytes = writeU64('value', payload.value);
231
255
  const dataBytes = (() => {
232
256
  const data = payload.data;
233
257
  const hex = data.slice(2);
234
258
  const buf = hex.length ? Buffer.from(hex, 'hex') : Buffer.alloc(0);
235
259
  return writeVecU8(buf);
236
260
  })();
237
- const gasLimitBytes = writeU64(payload.gasLimit);
238
- const maxFeePerGasBytes = writeU64(payload.maxFeePerGas);
239
- const maxPriorityFeePerGasBytes = writeU64(payload.maxPriorityFeePerGas);
240
- const nonceBytes = writeU64(payload.nonce);
241
- const deadlineBytes = writeI64(payload.deadline);
261
+ const gasLimitBytes = writeU64('gasLimit', payload.gasLimit);
262
+ const maxFeePerGasBytes = writeU64('maxFeePerGas', payload.maxFeePerGas);
263
+ const maxPriorityFeePerGasBytes = writeU64('maxPriorityFeePerGas', payload.maxPriorityFeePerGas);
264
+ const nonceBytes = writeU64('nonce', payload.nonce);
265
+ const deadlineBytes = writeI64('deadline', payload.deadline);
242
266
  const vTypeVal = (() => {
243
267
  const v = payload.vType;
244
268
  if (typeof v === 'number')
@@ -339,8 +339,11 @@ class Orchestrator {
339
339
  */
340
340
  createCascadedBuilder(preparedTxs, options) {
341
341
  const perCallHook = options === null || options === void 0 ? void 0 : options.progressHook;
342
+ const cascadeOptions = (options === null || options === void 0 ? void 0 : options.enforceGasCheck) === true
343
+ ? { enforceGasCheck: true }
344
+ : undefined;
342
345
  if (!perCallHook) {
343
- return (0, internals_1.createCascadedBuilder)(this.ctx, preparedTxs, this._getCascadeCallbacks(), undefined);
346
+ return (0, internals_1.createCascadedBuilder)(this.ctx, preparedTxs, this._getCascadeCallbacks(), undefined, cascadeOptions);
344
347
  }
345
348
  // Wrap ctx.progressHook for the synchronous send() phase — captures every
346
349
  // fireProgressHook(ctx, ...) call during pre-flight, composeCascade, and
@@ -353,7 +356,7 @@ class Orchestrator {
353
356
  // `dispatchCascadeProgressEvent` path delivers events to BOTH that hook
354
357
  // and ctx.progressHook (the init-time hook, restored by then) with
355
358
  // reference-dedup.
356
- const innerBuilder = (0, internals_1.createCascadedBuilder)(this.ctx, preparedTxs, this._getCascadeCallbacks(), perCallHook);
359
+ const innerBuilder = (0, internals_1.createCascadedBuilder)(this.ctx, preparedTxs, this._getCascadeCallbacks(), perCallHook, cascadeOptions);
357
360
  return {
358
361
  send: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
359
362
  const originalHook = this.progressHook;
@@ -140,11 +140,19 @@ export type ExecuteParams = {
140
140
  };
141
141
  /**
142
142
  * Internal: Skip fee locking for outbound flows (UEA→CEA).
143
- * Outbound txs execute entirely on Push Chain and don't need
144
- * external-chain fee locking.
143
+ * Outbound txs can skip external-chain fee locking only when the executing
144
+ * Push account already has enough native PC for the outbound gas swap plus
145
+ * Push-side execution reserve.
145
146
  * @internal
146
147
  */
147
148
  _skipFeeLocking?: boolean;
149
+ /**
150
+ * Internal: Minimum native PC balance required before standard execution may
151
+ * skip fee locking. Route handlers use this to include their own outbound
152
+ * gas-swap reserve in executeStandardPayload's fee-locking decision.
153
+ * @internal
154
+ */
155
+ _requiredFundsOverride?: bigint;
148
156
  /**
149
157
  * Internal: Minimum fee-locking deposit in USD (8 decimals).
150
158
  * Overrides the default $1 floor when the caller needs a larger deposit
@@ -717,6 +725,12 @@ export interface CascadeTrackOptions {
717
725
  eventHook?: (event: import('../progress-hook/progress-hook.types').ProgressEvent) => void;
718
726
  }
719
727
  export interface CascadeExecutionOptions {
728
+ /**
729
+ * Enforce SDK pre-flight gas/balance checks across the whole cascade.
730
+ * - false/omitted: individual prepared-hop settings decide enforcement
731
+ * - true: shortfalls throw InsufficientUEABalanceError before broadcast
732
+ */
733
+ enforceGasCheck?: boolean;
720
734
  /** Per-call progress callback. */
721
735
  progressHook?: (event: import('../progress-hook/progress-hook.types').ProgressEvent) => void;
722
736
  }
@@ -127,14 +127,14 @@ export declare function buildMigrationPayload(): `0x${string}`;
127
127
  * Build approval multicalls + sendUniversalTxOutbound call for Push Chain.
128
128
  *
129
129
  * Extracted from executeUoaToCea to be reusable in cascade composition.
130
- * Handles two cases:
131
- * - gasToken === prc20Token: single approval for burnAmount + gasFee
132
- * - gasToken !== prc20Token: two separate approvals
130
+ * The PRC-20 approval covers only the burn amount. Destination gas is funded
131
+ * through native PC msg.value on sendUniversalTxOutbound; UniversalCore swaps
132
+ * that native value into the route gas token inside swapAndBurnGas.
133
133
  *
134
134
  * @param opts.prc20Token - PRC-20 token to burn
135
- * @param opts.gasToken - Gas token for fee payment
135
+ * @param opts.gasToken - Gas token for fee payment, used by the outbound request
136
136
  * @param opts.burnAmount - Amount to burn
137
- * @param opts.gasFee - Gas fee amount
137
+ * @param opts.gasFee - Gas fee amount, used only for fallback native value sizing
138
138
  * @param opts.gatewayPcAddress - UniversalGatewayPC precompile address
139
139
  * @param opts.outboundRequest - The outbound request struct
140
140
  * @returns Array of MultiCall operations (approvals + outbound call)
@@ -157,6 +157,14 @@ export declare function isSvmChain(chain: CHAIN): boolean;
157
157
  * Must be exactly 32 bytes (0x + 64 hex chars = 66 characters total).
158
158
  */
159
159
  export declare function isValidSolanaHexAddress(address: string): boolean;
160
+ /**
161
+ * Conservative upper bound for SVM outbound instruction data. Solana's packet
162
+ * limit is 1232 bytes before the transaction envelope, accounts, blockhash,
163
+ * and signatures, so SDK-built instruction data must stay well below that.
164
+ */
165
+ export declare const SVM_RELAYABLE_PAYLOAD_MAX_BYTES = 1000;
166
+ export declare function getHexPayloadByteLength(payload: `0x${string}`): number;
167
+ export declare function assertSvmPayloadWithinRelayLimit(payload: `0x${string}`, context: string, maxBytes?: number): void;
160
168
  /**
161
169
  * Encode SVM execute payload for CPI execution on Solana.
162
170
  *
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MULTICALL_TUPLE_TYPE = void 0;
3
+ exports.SVM_RELAYABLE_PAYLOAD_MAX_BYTES = exports.MULTICALL_TUPLE_TYPE = void 0;
4
4
  exports.buildExecuteMulticall = buildExecuteMulticall;
5
5
  exports.buildCeaMulticallPayload = buildCeaMulticallPayload;
6
6
  exports.buildInboundUniversalPayload = buildInboundUniversalPayload;
@@ -14,6 +14,8 @@ exports.buildMigrationPayload = buildMigrationPayload;
14
14
  exports.buildOutboundApprovalAndCall = buildOutboundApprovalAndCall;
15
15
  exports.isSvmChain = isSvmChain;
16
16
  exports.isValidSolanaHexAddress = isValidSolanaHexAddress;
17
+ exports.getHexPayloadByteLength = getHexPayloadByteLength;
18
+ exports.assertSvmPayloadWithinRelayLimit = assertSvmPayloadWithinRelayLimit;
17
19
  exports.encodeSvmExecutePayload = encodeSvmExecutePayload;
18
20
  exports.encodeSvmCeaToUeaPayload = encodeSvmCeaToUeaPayload;
19
21
  const viem_1 = require("viem");
@@ -340,14 +342,14 @@ function buildMigrationPayload() {
340
342
  * Build approval multicalls + sendUniversalTxOutbound call for Push Chain.
341
343
  *
342
344
  * Extracted from executeUoaToCea to be reusable in cascade composition.
343
- * Handles two cases:
344
- * - gasToken === prc20Token: single approval for burnAmount + gasFee
345
- * - gasToken !== prc20Token: two separate approvals
345
+ * The PRC-20 approval covers only the burn amount. Destination gas is funded
346
+ * through native PC msg.value on sendUniversalTxOutbound; UniversalCore swaps
347
+ * that native value into the route gas token inside swapAndBurnGas.
346
348
  *
347
349
  * @param opts.prc20Token - PRC-20 token to burn
348
- * @param opts.gasToken - Gas token for fee payment
350
+ * @param opts.gasToken - Gas token for fee payment, used by the outbound request
349
351
  * @param opts.burnAmount - Amount to burn
350
- * @param opts.gasFee - Gas fee amount
352
+ * @param opts.gasFee - Gas fee amount, used only for fallback native value sizing
351
353
  * @param opts.gatewayPcAddress - UniversalGatewayPC precompile address
352
354
  * @param opts.outboundRequest - The outbound request struct
353
355
  * @returns Array of MultiCall operations (approvals + outbound call)
@@ -413,6 +415,24 @@ function isSvmChain(chain) {
413
415
  function isValidSolanaHexAddress(address) {
414
416
  return /^0x[a-fA-F0-9]{64}$/.test(address);
415
417
  }
418
+ /**
419
+ * Conservative upper bound for SVM outbound instruction data. Solana's packet
420
+ * limit is 1232 bytes before the transaction envelope, accounts, blockhash,
421
+ * and signatures, so SDK-built instruction data must stay well below that.
422
+ */
423
+ exports.SVM_RELAYABLE_PAYLOAD_MAX_BYTES = 1000;
424
+ function getHexPayloadByteLength(payload) {
425
+ return Math.max(0, (payload.length - 2) / 2);
426
+ }
427
+ function assertSvmPayloadWithinRelayLimit(payload, context, maxBytes = exports.SVM_RELAYABLE_PAYLOAD_MAX_BYTES) {
428
+ const payloadBytes = getHexPayloadByteLength(payload);
429
+ if (payloadBytes <= maxBytes)
430
+ return;
431
+ throw new Error(`SVM outbound payload for ${context} is ${payloadBytes} bytes, ` +
432
+ `exceeding relay-safe limit ${maxBytes} bytes. Solana cannot carry ` +
433
+ `this much instruction data in one transaction; split the cascade into ` +
434
+ `separate transactions or reduce the nested payload.`);
435
+ }
416
436
  /** Convert 0x-prefixed hex string to Uint8Array */
417
437
  function hexToBytes(hex) {
418
438
  const h = hex.startsWith('0x') ? hex.slice(2) : hex;
@@ -15,12 +15,36 @@ function buildSvmPayloadFromParams(params) {
15
15
  hasExecute: false,
16
16
  };
17
17
  }
18
- const resolved = (0, resolve_1.resolveSvmCall)({
19
- programAddress: params.to.address,
20
- data: (0, viem_1.hexToBytes)(params.data),
21
- senderUea: params.senderUea,
22
- targetChain: params.to.chain,
23
- });
18
+ // `data` is the EVM-oriented calldata field. On an SVM hop it is only a real
19
+ // instruction when it resolves against a registered Anchor IDL (8-byte
20
+ // discriminator + matching instruction). EVM calldata (e.g. a 4-byte selector
21
+ // like `increment()`) is NOT an SVM instruction for the target program — treat
22
+ // it as "no execute" (empty svmPayload), matching the documented behaviour that
23
+ // the `data` field is ignored for SVM chains, rather than throwing mid-prepare.
24
+ //
25
+ // We deliberately only swallow "the data isn't a valid instruction for this
26
+ // program's IDL" (too short / no discriminator match). A "no IDL found" error
27
+ // still throws — that means the caller targeted a program whose IDL was never
28
+ // registered, which is a real configuration mistake they need to see.
29
+ let resolved;
30
+ try {
31
+ resolved = (0, resolve_1.resolveSvmCall)({
32
+ programAddress: params.to.address,
33
+ data: (0, viem_1.hexToBytes)(params.data),
34
+ senderUea: params.senderUea,
35
+ targetChain: params.to.chain,
36
+ });
37
+ }
38
+ catch (err) {
39
+ if (err instanceof Error && /no IDL found/i.test(err.message)) {
40
+ throw err;
41
+ }
42
+ return {
43
+ svmPayload: '0x',
44
+ targetBytes: params.to.address,
45
+ hasExecute: false,
46
+ };
47
+ }
24
48
  const svmPayload = (0, payload_builders_1.encodeSvmExecutePayload)({
25
49
  targetProgram: resolved.targetProgram,
26
50
  accounts: resolved.accounts,
@@ -95,6 +95,10 @@ export declare class PushChain {
95
95
  * Accepts an array of PreparedUniversalTx (from prepareTransaction).
96
96
  *
97
97
  * @param txs - Prepared transactions to execute as a cascade.
98
+ * @param options.enforceGasCheck - Defaults to false. When true, the
99
+ * cascade stops on pre-flight gas/balance shortfall instead of warning
100
+ * and proceeding. This applies to every prepared transaction in the
101
+ * cascade without having to set the flag on each prepared hop.
98
102
  * @param options.progressHook - Optional per-call progress callback.
99
103
  * Additive with the init-time `progressHook` passed to `initialize`:
100
104
  * both hooks receive every `ProgressEvent` emitted during this call
@@ -43,7 +43,25 @@ class PushClient extends evm_client_1.EvmClient {
43
43
  return tslib_1.__awaiter(this, arguments, void 0, function* (operation, operationName = 'operation') {
44
44
  const rpcUrls = this.pushChainInfo.tendermintRpc;
45
45
  let lastError = null;
46
- for (let attempt = 0; attempt < rpcUrls.length; attempt++) {
46
+ // Cycle the RPC URLs, but also RETRY transient failures (5xx / network /
47
+ // "HTTP request failed") with backoff — otherwise a single 502 on a
48
+ // single-endpoint chain (e.g. Donut) aborts a tx mid status-poll. Total
49
+ // attempts = urls × (RETRIES_PER_URL + 1).
50
+ const RETRIES_PER_URL = 4;
51
+ const totalAttempts = rpcUrls.length * (RETRIES_PER_URL + 1);
52
+ const isTransient = (e) => {
53
+ var _a;
54
+ const m = `${(_a = e === null || e === void 0 ? void 0 : e.message) !== null && _a !== void 0 ? _a : ''}`.toLowerCase();
55
+ return (/\b(502|503|504|429)\b/.test(m) ||
56
+ m.includes('http request failed') ||
57
+ m.includes('server error') ||
58
+ m.includes('timeout') ||
59
+ m.includes('timed out') ||
60
+ m.includes('econnreset') ||
61
+ m.includes('socket hang up') ||
62
+ m.includes('fetch failed'));
63
+ };
64
+ for (let attempt = 0; attempt < totalAttempts; attempt++) {
47
65
  const rpcIndex = (this.currentRpcIndex + attempt) % rpcUrls.length;
48
66
  const rpcUrl = rpcUrls[rpcIndex];
49
67
  try {
@@ -55,10 +73,16 @@ class PushClient extends evm_client_1.EvmClient {
55
73
  }
56
74
  catch (error) {
57
75
  lastError = error;
58
- if (attempt === rpcUrls.length - 1) {
76
+ if (attempt === totalAttempts - 1) {
59
77
  break;
60
78
  }
61
- yield new Promise((resolve) => setTimeout(resolve, 100));
79
+ // Non-transient errors: move to the next URL quickly. Transient ones:
80
+ // back off (200ms → 400 → 800 …, capped) so a flaky RPC can recover.
81
+ const cycle = Math.floor(attempt / rpcUrls.length);
82
+ const delay = isTransient(lastError)
83
+ ? Math.min(2000, 200 * Math.pow(2, cycle))
84
+ : 100;
85
+ yield new Promise((resolve) => setTimeout(resolve, delay));
62
86
  }
63
87
  }
64
88
  throw new Error(`All RPC endpoints failed for ${operationName}. Last error: ${lastError === null || lastError === void 0 ? void 0 : lastError.message}`);
@@ -14,7 +14,10 @@ const viem_1 = require("viem");
14
14
  */
15
15
  class EvmClient {
16
16
  constructor({ rpcUrls, chain }) {
17
- const transports = rpcUrls.map((rpcUrl) => (0, viem_1.http)(rpcUrl));
17
+ // retryCount/retryDelay make reads (incl. contract reads + Push status
18
+ // queries via PushClient) resilient to transient RPC 5xx (e.g. 502s under
19
+ // load), which otherwise abort a tx mid-flight. viem retries with backoff.
20
+ const transports = rpcUrls.map((rpcUrl) => (0, viem_1.http)(rpcUrl, { retryCount: 5, retryDelay: 500 }));
18
21
  this.publicClient = (0, viem_1.createPublicClient)({
19
22
  chain,
20
23
  transport: (0, viem_1.fallback)(transports),