@pushchain/core 5.1.14 → 5.1.16
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 +1 -1
- package/src/lib/orchestrator/internals/cascade.d.ts +16 -0
- package/src/lib/orchestrator/internals/cascade.js +205 -47
- package/src/lib/orchestrator/internals/error-decoder.d.ts +59 -0
- package/src/lib/orchestrator/internals/error-decoder.js +110 -0
- package/src/lib/orchestrator/internals/errors.d.ts +78 -0
- package/src/lib/orchestrator/internals/errors.js +83 -0
- package/src/lib/orchestrator/internals/outbound-sync.d.ts +2 -2
- package/src/lib/orchestrator/internals/outbound-sync.js +2 -2
- package/src/lib/orchestrator/internals/preflight.d.ts +80 -0
- package/src/lib/orchestrator/internals/preflight.js +145 -0
- package/src/lib/orchestrator/internals/push-chain-tx.d.ts +2 -18
- package/src/lib/orchestrator/internals/push-chain-tx.js +67 -40
- package/src/lib/orchestrator/internals/route-handlers.js +199 -114
- package/src/lib/orchestrator/internals/svm-rent.d.ts +46 -0
- package/src/lib/orchestrator/internals/svm-rent.js +100 -0
- package/src/lib/orchestrator/internals/tx-transformer.js +16 -1
- package/src/lib/orchestrator/orchestrator.js +34 -1
- package/src/lib/orchestrator/orchestrator.types.d.ts +27 -3
- package/src/lib/price-fetch/price-fetch.js +23 -5
- package/src/lib/progress-hook/progress-hook.js +130 -6
- package/src/lib/progress-hook/progress-hook.types.d.ts +8 -2
- package/src/lib/progress-hook/progress-hook.types.js +15 -0
- package/src/lib/universal/account/account.d.ts +1 -1
- package/src/lib/universal/account/account.js +1 -1
- package/src/lib/universal-tx-detector/child-inbounds.js +15 -0
|
@@ -17,28 +17,9 @@ const chain_1 = require("../../constants/chain");
|
|
|
17
17
|
const enums_1 = require("../../constants/enums");
|
|
18
18
|
const progress_hook_types_1 = require("../../progress-hook/progress-hook.types");
|
|
19
19
|
const context_1 = require("./context");
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Typed error for Route 1 Push Chain tx failures. Thrown by
|
|
25
|
-
* `extractPcTxAndTransform` when the final pcTx commits with `status === 'FAILED'`,
|
|
26
|
-
* and surfaced as `SEND_TX_199_02` on the live stream. The readonly `code`
|
|
27
|
-
* discriminator lets callers classify via `instanceof PushChainExecutionError`
|
|
28
|
-
* instead of sniffing error messages.
|
|
29
|
-
*
|
|
30
|
-
* Carries the origin-chain gateway tx hash. There is no successful Push Chain
|
|
31
|
-
* tx to reference on this path — by definition the pcTx committed `FAILED`.
|
|
32
|
-
*/
|
|
33
|
-
class PushChainExecutionError extends Error {
|
|
34
|
-
constructor(message, opts = {}) {
|
|
35
|
-
super(message);
|
|
36
|
-
this.code = 'PUSH_CHAIN_EXECUTION_FAILED';
|
|
37
|
-
this.name = 'PushChainExecutionError';
|
|
38
|
-
this.gatewayTxHash = opts.gatewayTxHash;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
exports.PushChainExecutionError = PushChainExecutionError;
|
|
20
|
+
const error_decoder_1 = require("./error-decoder");
|
|
21
|
+
const errors_1 = require("./errors");
|
|
22
|
+
Object.defineProperty(exports, "PushChainExecutionError", { enumerable: true, get: function () { return errors_1.PushChainExecutionError; } });
|
|
42
23
|
// ============================================================================
|
|
43
24
|
// sendPushTx
|
|
44
25
|
// ============================================================================
|
|
@@ -167,7 +148,7 @@ const SEQ_RETRY_BASE_DELAY_MS = 400;
|
|
|
167
148
|
*/
|
|
168
149
|
function sendUniversalTx(ctx, isUEADeployed, feeLockTxHash, universalPayload, verificationData, eventBuffer, transformFn, resignFn) {
|
|
169
150
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
170
|
-
var _a, _b, _c, _d;
|
|
151
|
+
var _a, _b, _c, _d, _e;
|
|
171
152
|
const { chain, address } = ctx.universalSigner.account;
|
|
172
153
|
const { vm, chainId } = chain_1.CHAIN_INFO[chain];
|
|
173
154
|
const universalAccountId = {
|
|
@@ -241,24 +222,55 @@ function sendUniversalTx(ctx, isUEADeployed, feeLockTxHash, universalPayload, ve
|
|
|
241
222
|
var _a;
|
|
242
223
|
return (_a = e.attributes) === null || _a === void 0 ? void 0 : _a.filter((attr) => attr.key === 'ethereumTxHash').map((attr) => attr.value);
|
|
243
224
|
})) !== null && _b !== void 0 ? _b : [];
|
|
225
|
+
// Decode known revert selectors (e.g. `ret 0xacfdb444`) from the cosmos
|
|
226
|
+
// rawLog so the consumer-facing error AND the diagnostic failureSummary
|
|
227
|
+
// log carry an actionable hint instead of raw 4-byte hex. Mirrors the
|
|
228
|
+
// wiring in `extractPcTxAndTransform` for the post-success `pcTx FAILED`
|
|
229
|
+
// path. Also feeds the structured `decodedError` payload through to the
|
|
230
|
+
// terminal hook (199-02 / 299-02 / 399-02 / 999-02).
|
|
231
|
+
let augmentedMessage = (_c = tx.rawLog) !== null && _c !== void 0 ? _c : '';
|
|
232
|
+
let decodedForPayload;
|
|
233
|
+
const decoded = (0, error_decoder_1.decodeRevert)(augmentedMessage);
|
|
234
|
+
if ((decoded === null || decoded === void 0 ? void 0 : decoded.kind) === 'custom') {
|
|
235
|
+
augmentedMessage = `${augmentedMessage} [${decoded.name}: ${decoded.hint}]`;
|
|
236
|
+
decodedForPayload = {
|
|
237
|
+
name: decoded.name,
|
|
238
|
+
hint: decoded.hint,
|
|
239
|
+
selector: decoded.selector,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
else if ((decoded === null || decoded === void 0 ? void 0 : decoded.kind) === 'string') {
|
|
243
|
+
augmentedMessage = `${augmentedMessage} [Error(string): "${decoded.decoded}"]`;
|
|
244
|
+
decodedForPayload = {
|
|
245
|
+
decoded: decoded.decoded,
|
|
246
|
+
selector: decoded.selector,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
else if ((decoded === null || decoded === void 0 ? void 0 : decoded.kind) === 'unknown') {
|
|
250
|
+
(0, context_1.printLog)(ctx, `[error-decoder] unknown selector ${decoded.selector} — consider adding to KNOWN_ERROR_SELECTORS`);
|
|
251
|
+
}
|
|
244
252
|
const failureSummary = [
|
|
245
253
|
`PUSH CHAIN TRANSACTION FAILED`,
|
|
246
254
|
`Cosmos TX Hash: ${tx.transactionHash}`,
|
|
247
255
|
`Block Height: ${tx.height}, TX Code: ${tx.code} (error)`,
|
|
248
256
|
`Gas Used: ${tx.gasUsed}, Gas Wanted: ${tx.gasWanted}`,
|
|
249
257
|
...(failedEthTxHashes.length > 0 ? [`Ethereum TX Hash(es): ${failedEthTxHashes.join(', ')}`] : []),
|
|
250
|
-
`Error: ${
|
|
258
|
+
`Error: ${augmentedMessage}`,
|
|
251
259
|
].join(' | ');
|
|
252
260
|
(0, context_1.printLog)(ctx, failureSummary);
|
|
253
|
-
|
|
261
|
+
// Throw a typed error so the orchestrator's outer catch can lift the
|
|
262
|
+
// structured `decodedError` straight onto the terminal-hook payload.
|
|
263
|
+
throw new errors_1.PushChainExecutionError(augmentedMessage, {
|
|
264
|
+
decodedError: decodedForPayload,
|
|
265
|
+
});
|
|
254
266
|
}
|
|
255
267
|
if (!tx || tx.code !== 0) {
|
|
256
268
|
throw new Error(`sendUniversalTx — unreachable: loop exited without success or throw`);
|
|
257
269
|
}
|
|
258
|
-
const ethTxHashes = (
|
|
270
|
+
const ethTxHashes = (_e = (_d = tx.events) === null || _d === void 0 ? void 0 : _d.filter((e) => e.type === 'ethereum_tx').flatMap((e) => {
|
|
259
271
|
var _a;
|
|
260
272
|
return (_a = e.attributes) === null || _a === void 0 ? void 0 : _a.filter((attr) => attr.key === 'ethereumTxHash').map((attr) => attr.value);
|
|
261
|
-
})) !== null &&
|
|
273
|
+
})) !== null && _e !== void 0 ? _e : [];
|
|
262
274
|
if (ethTxHashes.length === 0) {
|
|
263
275
|
throw new Error('No ethereumTxHash found in transaction events');
|
|
264
276
|
}
|
|
@@ -278,7 +290,7 @@ function sendUniversalTx(ctx, isUEADeployed, feeLockTxHash, universalPayload, ve
|
|
|
278
290
|
*/
|
|
279
291
|
function extractPcTxAndTransform(ctx, pushChainUniversalTx, gatewayTxHash, eventBuffer, label, transformFn) {
|
|
280
292
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
281
|
-
var _a;
|
|
293
|
+
var _a, _b;
|
|
282
294
|
if (!((_a = pushChainUniversalTx === null || pushChainUniversalTx === void 0 ? void 0 : pushChainUniversalTx.pcTx) === null || _a === void 0 ? void 0 : _a.length)) {
|
|
283
295
|
throw new Error(`Failed to retrieve Push Chain transaction status for gateway tx: ${gatewayTxHash}. ` +
|
|
284
296
|
`The transaction may have failed on Push Chain or not been indexed yet.`);
|
|
@@ -291,24 +303,39 @@ function extractPcTxAndTransform(ctx, pushChainUniversalTx, gatewayTxHash, event
|
|
|
291
303
|
? lastPcTransaction
|
|
292
304
|
: pushChainUniversalTx.pcTx.find((pcTx) => pcTx.status === 'FAILED' && pcTx.errorMsg);
|
|
293
305
|
const errorDetails = (failedPcTx === null || failedPcTx === void 0 ? void 0 : failedPcTx.errorMsg) ? `: ${failedPcTx.errorMsg}` : '';
|
|
294
|
-
//
|
|
306
|
+
// Decode known revert selectors and ABI-encoded `Error(string)` reverts
|
|
307
|
+
// into actionable hints. Unknown selectors are logged at INFO so the
|
|
308
|
+
// table can grow from real reverts.
|
|
295
309
|
let hint = '';
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
310
|
+
let decodedForPayload;
|
|
311
|
+
const decoded = (0, error_decoder_1.decodeRevert)((_b = failedPcTx === null || failedPcTx === void 0 ? void 0 : failedPcTx.errorMsg) !== null && _b !== void 0 ? _b : '');
|
|
312
|
+
if ((decoded === null || decoded === void 0 ? void 0 : decoded.kind) === 'custom') {
|
|
313
|
+
hint = ` [${decoded.name}: ${decoded.hint}]`;
|
|
314
|
+
decodedForPayload = {
|
|
315
|
+
name: decoded.name,
|
|
316
|
+
hint: decoded.hint,
|
|
317
|
+
selector: decoded.selector,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
else if ((decoded === null || decoded === void 0 ? void 0 : decoded.kind) === 'string') {
|
|
321
|
+
hint = ` [Error(string): "${decoded.decoded}"]`;
|
|
322
|
+
decodedForPayload = {
|
|
323
|
+
decoded: decoded.decoded,
|
|
324
|
+
selector: decoded.selector,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
else if ((decoded === null || decoded === void 0 ? void 0 : decoded.kind) === 'unknown') {
|
|
328
|
+
(0, context_1.printLog)(ctx, `[error-decoder] unknown selector ${decoded.selector} — consider adding to KNOWN_ERROR_SELECTORS`);
|
|
305
329
|
}
|
|
306
330
|
const fullMessage = `Push Chain transaction failed for gateway tx: ${gatewayTxHash}${errorDetails}${hint}`;
|
|
307
331
|
// Live-emit 199-02 before throwing so the terminal failure hook reaches
|
|
308
332
|
// any caller-registered progress stream (parity with reconstructProgressEvents
|
|
309
333
|
// which emits 199-02 on replay).
|
|
310
|
-
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02, fullMessage);
|
|
311
|
-
throw new PushChainExecutionError(fullMessage, {
|
|
334
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02, fullMessage, decodedForPayload);
|
|
335
|
+
throw new errors_1.PushChainExecutionError(fullMessage, {
|
|
336
|
+
gatewayTxHash,
|
|
337
|
+
decodedError: decodedForPayload,
|
|
338
|
+
});
|
|
312
339
|
}
|
|
313
340
|
const tx = yield ctx.pushClient.getTransaction(lastPcTransaction.txHash);
|
|
314
341
|
return transformFn(tx, eventBuffer);
|
|
@@ -31,6 +31,8 @@ const helpers_1 = require("./helpers");
|
|
|
31
31
|
const payload_builder_1 = require("./payload-builder");
|
|
32
32
|
const uea_manager_1 = require("./uea-manager");
|
|
33
33
|
const gas_calculator_1 = require("./gas-calculator");
|
|
34
|
+
const preflight_1 = require("./preflight");
|
|
35
|
+
const svm_rent_1 = require("./svm-rent");
|
|
34
36
|
const chain_1 = require("../../constants/chain");
|
|
35
37
|
const utils_1 = require("../../utils");
|
|
36
38
|
const price_fetch_1 = require("../../price-fetch/price-fetch");
|
|
@@ -279,7 +281,7 @@ function executeMultiChain(ctx, params, executeFn) {
|
|
|
279
281
|
*/
|
|
280
282
|
function executeUoaToCea(ctx, params, executeFn) {
|
|
281
283
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
282
|
-
var _a, _b, _c;
|
|
284
|
+
var _a, _b, _c, _d;
|
|
283
285
|
const target = params.to;
|
|
284
286
|
const targetChain = target.chain;
|
|
285
287
|
const targetAddress = target.address;
|
|
@@ -440,29 +442,39 @@ function executeUoaToCea(ctx, params, executeFn) {
|
|
|
440
442
|
(0, context_1.printLog)(ctx, `executeUoaToCea — capping nativeValueForGas at 200 UPC ceiling (was ${nativeValueForGas.toString()})`);
|
|
441
443
|
nativeValueForGas = EVM_NATIVE_VALUE_SAFETY_CAP;
|
|
442
444
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
else {
|
|
458
|
-
throw new Error(`UEA ${ueaAddress} has zero UPC balance; cannot fund outbound gas swap. ` +
|
|
459
|
-
`Bridge UPC to the UEA before retrying.`);
|
|
445
|
+
// Pre-flight: throws InsufficientUEABalanceError when UEA cannot cover
|
|
446
|
+
// (nativeValueForGas + gasReserve) or the PRC-20 burn amount. Replaces the
|
|
447
|
+
// legacy silent-clamp that previously submitted under-funded swaps and
|
|
448
|
+
// bubbled up as Uniswap STF reverts. PRC-20 check fires whenever
|
|
449
|
+
// burnAmount > 0 — user must pre-hold the burn token, even when it equals
|
|
450
|
+
// the gas token (the gateway does transferFrom BEFORE the gas swap).
|
|
451
|
+
let prc20BalanceR2Evm;
|
|
452
|
+
if (burnAmount > BigInt(0)) {
|
|
453
|
+
prc20BalanceR2Evm = yield ctx.pushClient.readContract({
|
|
454
|
+
address: prc20Token,
|
|
455
|
+
abi: erc20_evm_1.ERC20_EVM,
|
|
456
|
+
functionName: 'balanceOf',
|
|
457
|
+
args: [ueaAddress],
|
|
458
|
+
});
|
|
460
459
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
460
|
+
const allowUnderfundedR2Evm = ((_d = params.options) === null || _d === void 0 ? void 0 : _d.allowUnderfundedSwap) === true;
|
|
461
|
+
const preflightR2Evm = (0, preflight_1.runPreflight)({
|
|
462
|
+
ctx,
|
|
463
|
+
ueaAddress,
|
|
464
|
+
ueaBalance: effectiveBalance,
|
|
465
|
+
requiredValue: nativeValueForGas,
|
|
466
|
+
gasReserve: EVM_GAS_RESERVE,
|
|
467
|
+
pathTag: 'R2_EVM',
|
|
468
|
+
burnToken: prc20Token,
|
|
469
|
+
burnAmount,
|
|
470
|
+
prc20Balance: prc20BalanceR2Evm,
|
|
471
|
+
allowUnderfundedSwap: allowUnderfundedR2Evm,
|
|
472
|
+
});
|
|
473
|
+
if (!preflightR2Evm.ok) {
|
|
474
|
+
nativeValueForGas = preflightR2Evm.legacyClampedValue;
|
|
475
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — allowUnderfundedSwap=true; using legacy clamped value ${nativeValueForGas.toString()}`);
|
|
465
476
|
}
|
|
477
|
+
// ok=true → nativeValueForGas already covers required + reserve; no adjust needed.
|
|
466
478
|
// Build outbound multicalls (approve burn + sendUniversalTxOutbound with native value)
|
|
467
479
|
const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
468
480
|
prc20Token,
|
|
@@ -475,31 +487,6 @@ function executeUoaToCea(ctx, params, executeFn) {
|
|
|
475
487
|
});
|
|
476
488
|
pushChainMulticalls.push(...outboundMulticalls);
|
|
477
489
|
(0, context_1.printLog)(ctx, `executeUoaToCea — Push Chain multicall has ${pushChainMulticalls.length} operations`);
|
|
478
|
-
// TODO: Enable pre-flight balance checks once outbound flow is stable
|
|
479
|
-
// if (burnAmount > BigInt(0)) {
|
|
480
|
-
// const prc20Balance = await ctx.pushClient.publicClient.readContract({
|
|
481
|
-
// address: prc20Token,
|
|
482
|
-
// abi: [{ name: 'balanceOf', type: 'function', stateMutability: 'view', inputs: [{ name: 'account', type: 'address' }], outputs: [{ name: '', type: 'uint256' }] }],
|
|
483
|
-
// functionName: 'balanceOf',
|
|
484
|
-
// args: [ueaAddress],
|
|
485
|
-
// }) as bigint;
|
|
486
|
-
// if (prc20Balance < burnAmount) {
|
|
487
|
-
// throw new Error(
|
|
488
|
-
// `Insufficient PRC-20 token balance on UEA. ` +
|
|
489
|
-
// `Required: ${burnAmount.toString()}, Available: ${prc20Balance.toString()}, ` +
|
|
490
|
-
// `Token: ${prc20Token}, UEA: ${ueaAddress}. ` +
|
|
491
|
-
// `Please bridge tokens to Push Chain first.`
|
|
492
|
-
// );
|
|
493
|
-
// }
|
|
494
|
-
// }
|
|
495
|
-
// const currentUeaBalance = await ctx.pushClient.getBalance(ueaAddress);
|
|
496
|
-
// if (currentUeaBalance < nativeValueForGas) {
|
|
497
|
-
// throw new Error(
|
|
498
|
-
// `Insufficient native balance on UEA for outbound gas. ` +
|
|
499
|
-
// `Required: ${nativeValueForGas.toString()} wei, Available: ${currentUeaBalance.toString()} wei, ` +
|
|
500
|
-
// `UEA: ${ueaAddress}. Please send UPC to your UEA first.`
|
|
501
|
-
// );
|
|
502
|
-
// }
|
|
503
490
|
// Execute through the normal execute() flow
|
|
504
491
|
// This handles fee-locking on origin chain and executes the multicall from UEA context
|
|
505
492
|
// Sum native values from multicall entries for proper fee calculation
|
|
@@ -549,7 +536,7 @@ function executeUoaToCea(ctx, params, executeFn) {
|
|
|
549
536
|
*/
|
|
550
537
|
function executeUoaToCeaSvm(ctx, params, target, executeFn) {
|
|
551
538
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
552
|
-
var _a, _b, _c, _d, _e, _f;
|
|
539
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
553
540
|
const targetChain = target.chain;
|
|
554
541
|
const targetAddress = target.address; // 0x-prefixed, 32 bytes
|
|
555
542
|
const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
|
|
@@ -582,6 +569,22 @@ function executeUoaToCeaSvm(ctx, params, target, executeFn) {
|
|
|
582
569
|
// gasLimit, it'll be derived from the gasFee/gasPrice response so the
|
|
583
570
|
// on-chain record carries a non-zero compute budget the relay can use.
|
|
584
571
|
let effectiveGasLimit = (_d = params.gasLimit) !== null && _d !== void 0 ? _d : BigInt(0);
|
|
572
|
+
// Conditional CEA-ATA rent bump: when the SPL outbound's destination ATA
|
|
573
|
+
// does not yet exist on Solana, the gateway needs an extra ~2.04M lamports
|
|
574
|
+
// for ATA-create rent. The on-chain quote does NOT include this, so bump
|
|
575
|
+
// effectiveGasLimit BEFORE queryOutboundGasFee so the returned gasFee
|
|
576
|
+
// already covers the rent.
|
|
577
|
+
const splMintBase58 = burnAmount > BigInt(0)
|
|
578
|
+
? (_f = (_e = params.funds) === null || _e === void 0 ? void 0 : _e.token) === null || _f === void 0 ? void 0 : _f.address
|
|
579
|
+
: undefined;
|
|
580
|
+
effectiveGasLimit = yield (0, svm_rent_1.maybeBumpForCeaAtaRent)({
|
|
581
|
+
ctx,
|
|
582
|
+
ueaAddress,
|
|
583
|
+
targetChain,
|
|
584
|
+
splMintBase58,
|
|
585
|
+
burnAmount,
|
|
586
|
+
effectiveGasLimit,
|
|
587
|
+
});
|
|
585
588
|
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
586
589
|
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_202_01, targetChain);
|
|
587
590
|
try {
|
|
@@ -628,7 +631,7 @@ function executeUoaToCeaSvm(ctx, params, target, executeFn) {
|
|
|
628
631
|
const signerChainSvm = ctx.universalSigner.account.chain;
|
|
629
632
|
const isNativePushEOASvm = (0, helpers_1.isPushChain)(signerChainSvm);
|
|
630
633
|
// Skip getCode if accountStatusCache already confirmed deployment
|
|
631
|
-
const deployedHintSvm = (
|
|
634
|
+
const deployedHintSvm = (_h = (_g = ctx.accountStatusCache) === null || _g === void 0 ? void 0 : _g.uea) === null || _h === void 0 ? void 0 : _h.deployed;
|
|
632
635
|
let ueaBalance;
|
|
633
636
|
let isUEADeployed;
|
|
634
637
|
if (deployedHintSvm || isNativePushEOASvm) {
|
|
@@ -650,37 +653,57 @@ function executeUoaToCeaSvm(ctx, params, target, executeFn) {
|
|
|
650
653
|
// refundUnusedGas.
|
|
651
654
|
//
|
|
652
655
|
// SVM pools (WPC/pSOL) are thinner than EVM pools so the pool-quote can
|
|
653
|
-
// easily exceed the UEA's balance.
|
|
654
|
-
//
|
|
655
|
-
//
|
|
656
|
-
//
|
|
656
|
+
// easily exceed the UEA's balance. Pre-flight (`runPreflight`) handles
|
|
657
|
+
// drain protection: an under-funded quote throws cleanly with
|
|
658
|
+
// `InsufficientUEABalanceError` and no cosmos tx is submitted. The
|
|
659
|
+
// SVM_NATIVE_VALUE_WARN_THRESHOLD below is telemetry-only; it does NOT
|
|
660
|
+
// truncate `nativeValueForGas`. v2 may add a hard cap once telemetry
|
|
661
|
+
// justifies a number — until then the original engineer's pool-skew
|
|
662
|
+
// warning still applies (capping risks false-positive truncation of
|
|
663
|
+
// legitimate quotes).
|
|
657
664
|
const SVM_GAS_RESERVE = BigInt(3e18); // 3 UPC for outer-tx gas
|
|
658
665
|
let nativeValueForGas = BigInt(0);
|
|
659
666
|
if (universalCoreAddress && gasFee > BigInt(0)) {
|
|
660
667
|
const estimated = yield (0, gas_calculator_1.estimateNativeValueForSwap)(ctx, universalCoreAddress, gasToken, gasFee, ueaBalance);
|
|
661
668
|
nativeValueForGas = (estimated * BigInt(110)) / BigInt(100);
|
|
662
669
|
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — nativeValueForGas: pool-quote=${estimated.toString()}, with 10% buffer=${nativeValueForGas.toString()}`);
|
|
670
|
+
(0, preflight_1.maybeFireSvmWarnThreshold)(ctx, nativeValueForGas, gasToken, 'R2_SVM');
|
|
671
|
+
}
|
|
672
|
+
// Pre-flight: throws InsufficientUEABalanceError when UEA cannot cover
|
|
673
|
+
// (nativeValueForGas + SVM_GAS_RESERVE) or the PRC-20 burn amount.
|
|
674
|
+
// Replaces the legacy silent-clamp that previously submitted under-funded
|
|
675
|
+
// swaps and bubbled up as Uniswap STF reverts.
|
|
676
|
+
// PRC-20 burn-balance pre-check fires whenever `burnAmount > 0`, regardless
|
|
677
|
+
// of whether `prc20Token` happens to equal `gasToken`. The gateway's
|
|
678
|
+
// `sendUniversalTxOutbound` does `transferFrom(user, gateway, burnAmount)`
|
|
679
|
+
// BEFORE the gas swap, so user must pre-hold `burnAmount` of `prc20Token`
|
|
680
|
+
// even when it's the same asset as the gas token (verified empirically:
|
|
681
|
+
// EOA with 77k units of pSOL reverted on-chain when trying to burn 10M).
|
|
682
|
+
let prc20BalanceR2Svm;
|
|
683
|
+
if (burnAmount > BigInt(0)) {
|
|
684
|
+
prc20BalanceR2Svm = yield ctx.pushClient.readContract({
|
|
685
|
+
address: prc20Token,
|
|
686
|
+
abi: erc20_evm_1.ERC20_EVM,
|
|
687
|
+
functionName: 'balanceOf',
|
|
688
|
+
args: [ueaAddress],
|
|
689
|
+
});
|
|
663
690
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
if (adjustedValueSvm !== nativeValueForGas) {
|
|
681
|
-
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — adjusting nativeValueForGas from ${nativeValueForGas.toString()} ` +
|
|
682
|
-
`to ${adjustedValueSvm.toString()} (UEA balance: ${ueaBalance.toString()})`);
|
|
683
|
-
nativeValueForGas = adjustedValueSvm;
|
|
691
|
+
const allowUnderfundedR2Svm = ((_j = params.options) === null || _j === void 0 ? void 0 : _j.allowUnderfundedSwap) === true;
|
|
692
|
+
const preflightR2Svm = (0, preflight_1.runPreflight)({
|
|
693
|
+
ctx,
|
|
694
|
+
ueaAddress,
|
|
695
|
+
ueaBalance,
|
|
696
|
+
requiredValue: nativeValueForGas,
|
|
697
|
+
gasReserve: SVM_GAS_RESERVE,
|
|
698
|
+
pathTag: 'R2_SVM',
|
|
699
|
+
burnToken: prc20Token,
|
|
700
|
+
burnAmount,
|
|
701
|
+
prc20Balance: prc20BalanceR2Svm,
|
|
702
|
+
allowUnderfundedSwap: allowUnderfundedR2Svm,
|
|
703
|
+
});
|
|
704
|
+
if (!preflightR2Svm.ok) {
|
|
705
|
+
nativeValueForGas = preflightR2Svm.legacyClampedValue;
|
|
706
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — allowUnderfundedSwap=true; using legacy clamped value ${nativeValueForGas.toString()}`);
|
|
684
707
|
}
|
|
685
708
|
// --- Build Push Chain multicalls (approve + sendUniversalTxOutbound) ---
|
|
686
709
|
// Reuse the same builder as EVM — this part is identical
|
|
@@ -746,7 +769,7 @@ function executeUoaToCeaSvm(ctx, params, target, executeFn) {
|
|
|
746
769
|
*/
|
|
747
770
|
function executeCeaToPush(ctx, params, executeFn) {
|
|
748
771
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
749
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
772
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
750
773
|
// 1. Validate and extract source chain
|
|
751
774
|
if (!((_a = params.from) === null || _a === void 0 ? void 0 : _a.chain)) {
|
|
752
775
|
throw new Error('Route 3 (CEA -> Push) requires from.chain to specify the source CEA chain');
|
|
@@ -994,31 +1017,42 @@ function executeCeaToPush(ctx, params, executeFn) {
|
|
|
994
1017
|
}
|
|
995
1018
|
}
|
|
996
1019
|
}
|
|
997
|
-
// Balance-aware
|
|
998
|
-
//
|
|
999
|
-
//
|
|
1000
|
-
// -
|
|
1001
|
-
|
|
1002
|
-
|
|
1020
|
+
// Balance-aware *upward* allocation. The previous behaviour silently
|
|
1021
|
+
// clamped to a 200 PC target whenever balance had headroom — which was
|
|
1022
|
+
// a downward clamp when the calibrated value already exceeded 200 PC,
|
|
1023
|
+
// masking under-funded swaps with opaque ExecutionFailed reverts. The
|
|
1024
|
+
// new logic preserves the "consume extra to reduce STF risk" intent for
|
|
1025
|
+
// small calibrated values while never reducing a calibrated value that
|
|
1026
|
+
// happened to land above 200 PC. Down-clamp + zero-balance throws live
|
|
1027
|
+
// in `runPreflight` below.
|
|
1028
|
+
const EVM_NATIVE_VALUE_TARGET_R3 = BigInt(200e18); // 200 UPC target floor
|
|
1003
1029
|
if (effectiveBalance > EVM_NATIVE_VALUE_TARGET_R3 + OUTBOUND_GAS_RESERVE_R3) {
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
adjustedValue = effectiveBalance < nativeValueForGas ? effectiveBalance : nativeValueForGas;
|
|
1014
|
-
}
|
|
1015
|
-
else {
|
|
1016
|
-
throw new Error(`UEA ${ueaAddress} has zero UPC balance; cannot fund outbound gas swap. ` +
|
|
1017
|
-
`Bridge UPC to the UEA before retrying.`);
|
|
1030
|
+
// Balance can cover the target. Bump nativeValueForGas UP to 200 PC,
|
|
1031
|
+
// but never reduce a calibrated value already above 200.
|
|
1032
|
+
const newValue = nativeValueForGas > EVM_NATIVE_VALUE_TARGET_R3
|
|
1033
|
+
? nativeValueForGas
|
|
1034
|
+
: EVM_NATIVE_VALUE_TARGET_R3;
|
|
1035
|
+
if (newValue !== nativeValueForGas) {
|
|
1036
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — adjusting nativeValueForGas UP from ${nativeValueForGas.toString()} to ${newValue.toString()} (EVM_NATIVE_VALUE_TARGET_R3 floor)`);
|
|
1037
|
+
nativeValueForGas = newValue;
|
|
1038
|
+
}
|
|
1018
1039
|
}
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1040
|
+
// Pre-flight (R3 EVM): no PRC-20 burn check — `burnAmount = 0` makes this
|
|
1041
|
+
// payload-only (UEA never holds the source-chain PRC-20; see plan §9 #4).
|
|
1042
|
+
// Native UPC check still applies for the gas swap.
|
|
1043
|
+
const allowUnderfundedR3Evm = ((_j = params.options) === null || _j === void 0 ? void 0 : _j.allowUnderfundedSwap) === true;
|
|
1044
|
+
const preflightR3Evm = (0, preflight_1.runPreflight)({
|
|
1045
|
+
ctx,
|
|
1046
|
+
ueaAddress,
|
|
1047
|
+
ueaBalance: effectiveBalance,
|
|
1048
|
+
requiredValue: nativeValueForGas,
|
|
1049
|
+
gasReserve: OUTBOUND_GAS_RESERVE_R3,
|
|
1050
|
+
pathTag: 'R3_EVM',
|
|
1051
|
+
allowUnderfundedSwap: allowUnderfundedR3Evm,
|
|
1052
|
+
});
|
|
1053
|
+
if (!preflightR3Evm.ok) {
|
|
1054
|
+
nativeValueForGas = preflightR3Evm.legacyClampedValue;
|
|
1055
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — allowUnderfundedSwap=true; using legacy clamped value ${nativeValueForGas.toString()}`);
|
|
1022
1056
|
}
|
|
1023
1057
|
// 12. Build Push Chain multicalls (approvals + sendUniversalTxOutbound)
|
|
1024
1058
|
const pushChainMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
@@ -1084,7 +1118,7 @@ function executeCeaToPush(ctx, params, executeFn) {
|
|
|
1084
1118
|
*/
|
|
1085
1119
|
function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
|
|
1086
1120
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
1087
|
-
var _a, _b, _c, _d;
|
|
1121
|
+
var _a, _b, _c, _d, _e;
|
|
1088
1122
|
if (typeof params.to !== 'string') {
|
|
1089
1123
|
throw new Error('Route 3 SVM (CEA -> Push): to must be a Push Chain address (string), not a ChainTarget');
|
|
1090
1124
|
}
|
|
@@ -1136,9 +1170,13 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
|
|
|
1136
1170
|
revertRecipientHex: ceaPdaHex,
|
|
1137
1171
|
});
|
|
1138
1172
|
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — drainAmount: ${drainAmount.toString()}, payload length: ${(svmPayload.length - 2) / 2} bytes`);
|
|
1139
|
-
// burnAmount =
|
|
1140
|
-
//
|
|
1141
|
-
|
|
1173
|
+
// burnAmount = 0 (payload-only outbound — drain amount lives inside ixData).
|
|
1174
|
+
// Mirrors the 2026-03-19 cascade fix that removed the 1-wei workaround for
|
|
1175
|
+
// SVM payload-only hops because it caused `InsufficientBalance (0xf4d678b8)`
|
|
1176
|
+
// reverts on fresh UEAs that don't hold the source-chain native PRC-20
|
|
1177
|
+
// (e.g. pSOL). The UGPC upgrade post-2026-03-19 already accepts amount = 0
|
|
1178
|
+
// on the cascade path, and the same precompile is used here.
|
|
1179
|
+
const burnAmount = BigInt(0);
|
|
1142
1180
|
// Build outbound request: target = gateway program (self-call)
|
|
1143
1181
|
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, prc20Token, burnAmount, (_b = params.gasLimit) !== null && _b !== void 0 ? _b : BigInt(0), svmPayload, ueaAddress);
|
|
1144
1182
|
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — outbound request: target=${outboundReq.target.slice(0, 20)}..., token=${outboundReq.token}`);
|
|
@@ -1189,15 +1227,58 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
|
|
|
1189
1227
|
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — Case C: bumping nativeValueForGas from ${nativeValueForGas} to ${bumped} (overflow=${sizingDecisionR3Svm.overflowNativePc})`);
|
|
1190
1228
|
nativeValueForGas = bumped;
|
|
1191
1229
|
}
|
|
1192
|
-
//
|
|
1193
|
-
// Re-fetch balance to minimize staleness from gas fee query RPC roundtrips
|
|
1230
|
+
// Re-fetch balance to minimize staleness from gas fee query RPC roundtrips.
|
|
1194
1231
|
const currentBalance = yield ctx.pushClient.getBalance(ueaAddress);
|
|
1195
1232
|
// Cosmos-EVM tx overhead costs ~1 PC per operation; 3 PC covers approve(s) + buffer.
|
|
1196
1233
|
const OUTBOUND_GAS_RESERVE_R3_SVM = BigInt(3e18);
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1234
|
+
// Upward-allocation ceiling: cap the upward adjust at max(200 PC, 5x calibrated)
|
|
1235
|
+
// so a UEA with a giant balance doesn't submit msg.value=balance for a small
|
|
1236
|
+
// gas swap. The contract refunds excess via swapAndBurnGas, but pushing
|
|
1237
|
+
// hundreds of thousands of PC into a Uniswap pool moves the price for other
|
|
1238
|
+
// users. 200 PC mirrors EVM's safety cap; 5x calibrated covers pool-skew
|
|
1239
|
+
// scenarios where the calibrated value itself is > 40 PC (5x40 = 200).
|
|
1240
|
+
const UPWARD_BASE_CEILING_R3_SVM = BigInt(200) * BigInt(1e18);
|
|
1241
|
+
const calibratedCeiling = nativeValueForGas * BigInt(5);
|
|
1242
|
+
const upwardCeiling = calibratedCeiling > UPWARD_BASE_CEILING_R3_SVM
|
|
1243
|
+
? calibratedCeiling
|
|
1244
|
+
: UPWARD_BASE_CEILING_R3_SVM;
|
|
1245
|
+
// Upward-only allocation: when balance has headroom over the calibrated
|
|
1246
|
+
// value, consume more of it (up to the ceiling above). Down-clamp +
|
|
1247
|
+
// zero-balance throws live in pre-flight.
|
|
1248
|
+
// Ordering note: pre-flight runs AFTER the upward adjust below; the
|
|
1249
|
+
// semantics are equivalent to running it before — when upward adjust
|
|
1250
|
+
// applies it sets nativeValueForGas to balance-reserve (capped), and the
|
|
1251
|
+
// post-adjust pre-flight check is always satisfied. When upward adjust is
|
|
1252
|
+
// skipped (balance too low), pre-flight runs against the calibrated value
|
|
1253
|
+
// and throws cleanly on shortfall.
|
|
1254
|
+
if (currentBalance > OUTBOUND_GAS_RESERVE_R3_SVM &&
|
|
1255
|
+
currentBalance - OUTBOUND_GAS_RESERVE_R3_SVM > nativeValueForGas) {
|
|
1256
|
+
const balanceDriven = currentBalance - OUTBOUND_GAS_RESERVE_R3_SVM;
|
|
1257
|
+
const adjustedValue = balanceDriven > upwardCeiling ? upwardCeiling : balanceDriven;
|
|
1258
|
+
if (adjustedValue !== nativeValueForGas) {
|
|
1259
|
+
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — adjusting nativeValueForGas from ${nativeValueForGas.toString()} to ${adjustedValue.toString()} ` +
|
|
1260
|
+
`(UEA balance: ${currentBalance.toString()}, upwardCeiling: ${upwardCeiling.toString()})`);
|
|
1261
|
+
nativeValueForGas = adjustedValue;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
// SVM warn-threshold telemetry (no truncation — pre-flight handles drain protection).
|
|
1265
|
+
(0, preflight_1.maybeFireSvmWarnThreshold)(ctx, nativeValueForGas, gasToken, 'R3_SVM');
|
|
1266
|
+
// Pre-flight (R3 SVM): no PRC-20 burn check — `burnAmount = 1` is a legacy
|
|
1267
|
+
// precompile-validation shim against a token UEAs structurally don't hold
|
|
1268
|
+
// (see plan §9 #4 / §10.2 latent-bug ticket). Native UPC check still applies.
|
|
1269
|
+
const allowUnderfundedR3Svm = ((_e = params.options) === null || _e === void 0 ? void 0 : _e.allowUnderfundedSwap) === true;
|
|
1270
|
+
const preflightR3Svm = (0, preflight_1.runPreflight)({
|
|
1271
|
+
ctx,
|
|
1272
|
+
ueaAddress,
|
|
1273
|
+
ueaBalance: currentBalance,
|
|
1274
|
+
requiredValue: nativeValueForGas,
|
|
1275
|
+
gasReserve: OUTBOUND_GAS_RESERVE_R3_SVM,
|
|
1276
|
+
pathTag: 'R3_SVM',
|
|
1277
|
+
allowUnderfundedSwap: allowUnderfundedR3Svm,
|
|
1278
|
+
});
|
|
1279
|
+
if (!preflightR3Svm.ok) {
|
|
1280
|
+
nativeValueForGas = preflightR3Svm.legacyClampedValue;
|
|
1281
|
+
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — allowUnderfundedSwap=true; using legacy clamped value ${nativeValueForGas.toString()}`);
|
|
1201
1282
|
}
|
|
1202
1283
|
// Build Push Chain multicalls (approvals + sendUniversalTxOutbound)
|
|
1203
1284
|
const pushChainMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
@@ -1235,13 +1316,17 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
|
|
|
1235
1316
|
response.chain = sourceChain;
|
|
1236
1317
|
const chainInfo = chain_1.CHAIN_INFO[sourceChain];
|
|
1237
1318
|
response.chainNamespace = `${chain_1.VM_NAMESPACE[chainInfo.vm]}:${chainInfo.chainId}`;
|
|
1238
|
-
// R3 SVM inbound round-trip tracking
|
|
1239
|
-
//
|
|
1240
|
-
//
|
|
1241
|
-
//
|
|
1242
|
-
//
|
|
1243
|
-
//
|
|
1244
|
-
|
|
1319
|
+
// R3 SVM inbound round-trip tracking is disabled.
|
|
1320
|
+
//
|
|
1321
|
+
// For Route 3 SVM drains (rescue_funds-style flow, txType=RESCUE_FUNDS),
|
|
1322
|
+
// empirical check on donut showed the keeper does NOT create a child
|
|
1323
|
+
// universalTxId on Push for the Solana-side `UniversalTx(fromCEA=true)`
|
|
1324
|
+
// log — the parent utxId's `outboundStatus=OBSERVED` is the terminal state.
|
|
1325
|
+
// Polling for a deterministically-derived child id therefore never
|
|
1326
|
+
// resolves, and the cosmos `inbound_tx_hash` text-search fallback also
|
|
1327
|
+
// returns 0 hits. Receipts now resolve as soon as the Solana outbound is
|
|
1328
|
+
// observed instead of timing out at 300s.
|
|
1329
|
+
response._expectsInboundRoundTrip = false;
|
|
1245
1330
|
return response;
|
|
1246
1331
|
});
|
|
1247
1332
|
}
|