@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.
- package/CHANGELOG.md +137 -1
- package/package.json +1 -1
- package/src/lib/orchestrator/internals/cascade.d.ts +2 -2
- package/src/lib/orchestrator/internals/cascade.js +176 -92
- package/src/lib/orchestrator/internals/confirmation.js +35 -6
- package/src/lib/orchestrator/internals/execute-funds-only.js +43 -1
- package/src/lib/orchestrator/internals/execute-standard.d.ts +1 -0
- package/src/lib/orchestrator/internals/execute-standard.js +8 -1
- package/src/lib/orchestrator/internals/gas-calculator.d.ts +20 -1
- package/src/lib/orchestrator/internals/gas-calculator.js +31 -7
- package/src/lib/orchestrator/internals/outbound-sync.d.ts +1 -0
- package/src/lib/orchestrator/internals/outbound-sync.js +117 -74
- package/src/lib/orchestrator/internals/response-builder.js +31 -5
- package/src/lib/orchestrator/internals/route-handlers.d.ts +7 -0
- package/src/lib/orchestrator/internals/route-handlers.js +47 -5
- package/src/lib/orchestrator/internals/signing.js +37 -13
- package/src/lib/orchestrator/orchestrator.js +5 -2
- package/src/lib/orchestrator/orchestrator.types.d.ts +16 -2
- package/src/lib/orchestrator/payload-builders.d.ts +13 -5
- package/src/lib/orchestrator/payload-builders.js +26 -6
- package/src/lib/orchestrator/svm-idl/build-payload.js +30 -6
- package/src/lib/push-chain/push-chain.d.ts +4 -0
- package/src/lib/push-client/push-client.js +27 -3
- package/src/lib/vm-client/evm-client.js +4 -1
|
@@ -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:
|
|
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:
|
|
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:
|
|
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:
|
|
1316
|
-
}, _skipFeeLocking:
|
|
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' : '
|
|
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) :
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
144
|
-
*
|
|
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
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
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
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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 ===
|
|
76
|
+
if (attempt === totalAttempts - 1) {
|
|
59
77
|
break;
|
|
60
78
|
}
|
|
61
|
-
|
|
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
|
-
|
|
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),
|