@pushchain/core 6.0.20 → 6.0.21
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 +48 -0
- package/package.json +1 -1
- package/src/lib/cache/cache.js +2 -0
- package/src/lib/constants/abi/index.d.ts +1 -0
- package/src/lib/constants/abi/index.js +8 -1
- package/src/lib/constants/abi/pc20.evm.d.ts +217 -0
- package/src/lib/constants/abi/pc20.evm.js +150 -0
- package/src/lib/constants/abi/prc20.evm.d.ts +172 -0
- package/src/lib/constants/abi/prc20.evm.js +97 -0
- package/src/lib/constants/abi/universalGatewayV0.json +1 -1
- package/src/lib/constants/chain.js +38 -8
- package/src/lib/constants/selectors.js +2 -2
- package/src/lib/constants/tokens.js +4 -3
- package/src/lib/formatters.js +16 -6
- package/src/lib/generated/uexecutor/v1/query.js +2 -4
- package/src/lib/generated/uexecutor/v1/types.d.ts +14 -0
- package/src/lib/generated/uexecutor/v1/types.js +90 -47
- package/src/lib/generated/uexecutor/v2/query.js +2 -4
- package/src/lib/generated/uexecutor/v2/types.d.ts +20 -0
- package/src/lib/generated/uexecutor/v2/types.js +49 -33
- package/src/lib/generated/v1/tx.js +30 -37
- package/src/lib/index.d.ts +3 -0
- package/src/lib/index.js +21 -3
- package/src/lib/internal/buffer.js +1 -2
- package/src/lib/orchestrator/cea-utils.js +73 -83
- package/src/lib/orchestrator/internals/account-manager.js +130 -139
- package/src/lib/orchestrator/internals/cascade.d.ts +0 -6
- package/src/lib/orchestrator/internals/cascade.js +1184 -1059
- package/src/lib/orchestrator/internals/confirmation.js +138 -148
- package/src/lib/orchestrator/internals/error-decoder.js +1 -1
- package/src/lib/orchestrator/internals/errors.js +12 -3
- package/src/lib/orchestrator/internals/execute-funds-only.d.ts +2 -2
- package/src/lib/orchestrator/internals/execute-funds-only.js +297 -259
- package/src/lib/orchestrator/internals/execute-funds-payload.d.ts +2 -2
- package/src/lib/orchestrator/internals/execute-funds-payload.js +220 -220
- package/src/lib/orchestrator/internals/execute-standard.d.ts +0 -8
- package/src/lib/orchestrator/internals/execute-standard.js +339 -342
- package/src/lib/orchestrator/internals/gas-calculator.js +497 -520
- package/src/lib/orchestrator/internals/gas-usd-sizer.js +101 -110
- package/src/lib/orchestrator/internals/gateway-client.js +102 -103
- package/src/lib/orchestrator/internals/helpers.js +30 -27
- package/src/lib/orchestrator/internals/inbound-tracker.js +234 -238
- package/src/lib/orchestrator/internals/outbound-sync.js +463 -452
- package/src/lib/orchestrator/internals/outbound-tracker.js +27 -34
- package/src/lib/orchestrator/internals/payload-builder.d.ts +2 -2
- package/src/lib/orchestrator/internals/payload-builder.js +119 -100
- package/src/lib/orchestrator/internals/pc-usd-oracle.js +121 -129
- package/src/lib/orchestrator/internals/pc20/address-codec.d.ts +31 -0
- package/src/lib/orchestrator/internals/pc20/address-codec.js +124 -0
- package/src/lib/orchestrator/internals/pc20/chain-namespace.d.ts +68 -0
- package/src/lib/orchestrator/internals/pc20/chain-namespace.js +136 -0
- package/src/lib/orchestrator/internals/pc20/errors.d.ts +160 -0
- package/src/lib/orchestrator/internals/pc20/errors.js +236 -0
- package/src/lib/orchestrator/internals/pc20/export.d.ts +172 -0
- package/src/lib/orchestrator/internals/pc20/export.js +412 -0
- package/src/lib/orchestrator/internals/pc20/gate.d.ts +67 -0
- package/src/lib/orchestrator/internals/pc20/gate.js +184 -0
- package/src/lib/orchestrator/internals/pc20/resolver.d.ts +187 -0
- package/src/lib/orchestrator/internals/pc20/resolver.js +658 -0
- package/src/lib/orchestrator/internals/pc20/svm.d.ts +189 -0
- package/src/lib/orchestrator/internals/pc20/svm.js +286 -0
- package/src/lib/orchestrator/internals/pc20/tracking.d.ts +46 -0
- package/src/lib/orchestrator/internals/pc20/tracking.js +109 -0
- package/src/lib/orchestrator/internals/preflight.js +1 -1
- package/src/lib/orchestrator/internals/progress-route-hooks.js +2 -2
- package/src/lib/orchestrator/internals/push-chain-tx.js +319 -329
- package/src/lib/orchestrator/internals/quote.js +90 -94
- package/src/lib/orchestrator/internals/rescue.js +76 -79
- package/src/lib/orchestrator/internals/response-builder.d.ts +28 -1
- package/src/lib/orchestrator/internals/response-builder.js +817 -701
- package/src/lib/orchestrator/internals/route-handlers.d.ts +7 -15
- package/src/lib/orchestrator/internals/route-handlers.js +1512 -1178
- package/src/lib/orchestrator/internals/signing.js +78 -83
- package/src/lib/orchestrator/internals/svm-bridge.d.ts +2 -2
- package/src/lib/orchestrator/internals/svm-bridge.js +161 -93
- package/src/lib/orchestrator/internals/svm-helpers.d.ts +15 -1
- package/src/lib/orchestrator/internals/svm-helpers.js +68 -24
- package/src/lib/orchestrator/internals/svm-rent.d.ts +19 -1
- package/src/lib/orchestrator/internals/svm-rent.js +163 -141
- package/src/lib/orchestrator/internals/tx-transformer.js +26 -23
- package/src/lib/orchestrator/internals/uea-manager.js +89 -86
- package/src/lib/orchestrator/max-pc-for-gas.js +5 -6
- package/src/lib/orchestrator/orchestrator.js +217 -200
- package/src/lib/orchestrator/orchestrator.types.d.ts +121 -1
- package/src/lib/orchestrator/orchestrator.types.js +19 -0
- package/src/lib/orchestrator/payload-builders.d.ts +2 -2
- package/src/lib/orchestrator/payload-builders.js +26 -24
- package/src/lib/orchestrator/route-detector.js +26 -9
- package/src/lib/orchestrator/svm-idl/ix-encoder.js +1 -2
- package/src/lib/orchestrator/svm-idl/normalize-address.js +2 -2
- package/src/lib/orchestrator/svm-idl/resolve.js +1 -2
- package/src/lib/price-fetch/price-fetch.js +67 -68
- package/src/lib/progress-hook/progress-hook.js +124 -72
- package/src/lib/push-chain/push-chain.js +171 -153
- package/src/lib/push-client/push-client.js +223 -237
- package/src/lib/universal/account/account.js +349 -365
- package/src/lib/universal/signer/signer.js +306 -294
- package/src/lib/universal-tx-detector/auto-detect.js +55 -60
- package/src/lib/universal-tx-detector/cascade.js +160 -172
- package/src/lib/universal-tx-detector/child-inbounds.js +67 -75
- package/src/lib/universal-tx-detector/classify.js +2 -3
- package/src/lib/universal-tx-detector/detector-svm.js +139 -147
- package/src/lib/universal-tx-detector/detector.js +145 -157
- package/src/lib/universal-tx-detector/normalize.js +7 -7
- package/src/lib/utils/external-tx-hash.js +1 -2
- package/src/lib/utils.d.ts +44 -0
- package/src/lib/utils.js +762 -683
- package/src/lib/vm-client/evm-client.js +186 -202
- package/src/lib/vm-client/svm-client.d.ts +1 -1
- package/src/lib/vm-client/svm-client.js +220 -224
- package/src/lib/vm-client/vm-client.types.d.ts +14 -2
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Cascade composition functions extracted from Orchestrator.
|
|
4
|
-
*
|
|
5
|
-
* Covers: prepareTransaction, buildHopDescriptor, classifyIntoSegments,
|
|
6
|
-
* getSegmentType, composeCascade, createCascadedBuilder, createChainedBuilder.
|
|
7
|
-
*/
|
|
8
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
3
|
exports.dispatchCascadeProgressEvent = dispatchCascadeProgressEvent;
|
|
10
4
|
exports.prepareTransaction = prepareTransaction;
|
|
@@ -15,11 +9,19 @@ exports.composeCascadeDetailed = composeCascadeDetailed;
|
|
|
15
9
|
exports.composeCascade = composeCascade;
|
|
16
10
|
exports.createCascadedBuilder = createCascadedBuilder;
|
|
17
11
|
const tslib_1 = require("tslib");
|
|
12
|
+
const gate_1 = require("./pc20/gate");
|
|
13
|
+
/**
|
|
14
|
+
* Cascade composition functions extracted from Orchestrator.
|
|
15
|
+
*
|
|
16
|
+
* Covers: prepareTransaction, buildHopDescriptor, classifyIntoSegments,
|
|
17
|
+
* getSegmentType, composeCascade, createCascadedBuilder, createChainedBuilder.
|
|
18
|
+
*/
|
|
18
19
|
const web3_js_1 = require("@solana/web3.js");
|
|
19
20
|
const viem_1 = require("viem");
|
|
20
21
|
const erc20_evm_1 = require("../../constants/abi/erc20.evm");
|
|
21
22
|
const chain_1 = require("../../constants/chain");
|
|
22
23
|
const enums_1 = require("../../constants/enums");
|
|
24
|
+
const orchestrator_types_1 = require("../orchestrator.types");
|
|
23
25
|
const selectors_1 = require("../../constants/selectors");
|
|
24
26
|
const route_detector_1 = require("../route-detector");
|
|
25
27
|
const cea_utils_1 = require("../cea-utils");
|
|
@@ -39,6 +41,7 @@ const progress_hook_types_1 = require("../../progress-hook/progress-hook.types")
|
|
|
39
41
|
const build_payload_1 = require("../svm-idl/build-payload");
|
|
40
42
|
const preflight_1 = require("./preflight");
|
|
41
43
|
const svm_rent_1 = require("./svm-rent");
|
|
44
|
+
const svm_1 = require("./pc20/svm");
|
|
42
45
|
const errors_1 = require("./errors");
|
|
43
46
|
const context_2 = require("./context");
|
|
44
47
|
const inbound_tracker_1 = require("./inbound-tracker");
|
|
@@ -71,277 +74,122 @@ function dispatchCascadeProgressEvent(event, primary, secondary) {
|
|
|
71
74
|
// ============================================================================
|
|
72
75
|
// prepareTransaction
|
|
73
76
|
// ============================================================================
|
|
74
|
-
function prepareTransaction(ctx, params, callbacks) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
77
|
+
async function prepareTransaction(ctx, params, callbacks) {
|
|
78
|
+
(0, route_detector_1.validateRouteParams)(params, {
|
|
79
|
+
clientChain: ctx.universalSigner.account.chain,
|
|
80
|
+
});
|
|
81
|
+
const route = (0, route_detector_1.detectRoute)(params);
|
|
82
|
+
// Push native EOA: block Route 1 — use sendTransaction() for direct Push calls
|
|
83
|
+
const signerChain = ctx.universalSigner.account.chain;
|
|
84
|
+
const isPushNativeEOA = (0, helpers_1.isPushChain)(signerChain);
|
|
85
|
+
if (isPushNativeEOA && route === route_detector_1.TransactionRoute.UOA_TO_PUSH) {
|
|
86
|
+
throw new Error('Push native accounts cannot use prepareTransaction for Push Chain transactions. ' +
|
|
87
|
+
'Use sendTransaction() instead for direct Push Chain calls.');
|
|
88
|
+
}
|
|
89
|
+
// Fetch UEA nonce (skip for Push native EOA — uses EVM nonce, not UEA contract nonce)
|
|
90
|
+
let nonce;
|
|
91
|
+
const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
|
|
92
|
+
if (isPushNativeEOA) {
|
|
93
|
+
nonce = BigInt(0);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
const deployedHintCascade = ctx.accountStatusCache?.uea?.deployed;
|
|
97
|
+
if (deployedHintCascade) {
|
|
98
|
+
nonce = await (0, uea_manager_1.getUEANonce)(ctx, ueaAddress);
|
|
93
99
|
}
|
|
94
100
|
else {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
nonce = yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress);
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
const status = yield (0, uea_manager_1.getUeaStatusAndNonce)(ctx);
|
|
101
|
-
nonce = status.nonce;
|
|
102
|
-
}
|
|
101
|
+
const status = await (0, uea_manager_1.getUeaStatusAndNonce)(ctx);
|
|
102
|
+
nonce = status.nonce;
|
|
103
103
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
104
|
+
}
|
|
105
|
+
// PC20 resolution for a prepared transaction.
|
|
106
|
+
//
|
|
107
|
+
// A prepared tx is signed now and sent later, so the registry state it was
|
|
108
|
+
// built against can move in between — most importantly, a first export can
|
|
109
|
+
// land and create a mapping that did not exist at prepare time. The
|
|
110
|
+
// descriptor is therefore resolved here to build the payload, and
|
|
111
|
+
// deliberately NOT cached as authoritative: `revalidatePreparedPC20` re-runs
|
|
112
|
+
// the critical reads at send time.
|
|
113
|
+
const preparedParams = (await (0, gate_1.gateFunds)(ctx, params));
|
|
114
|
+
// Build the payload based on route
|
|
115
|
+
const { payload, gatewayRequest } = await (0, route_handlers_1.buildPayloadForRoute)(ctx, preparedParams, route, nonce);
|
|
116
|
+
const gasEstimate = params.gasLimit || selectors_1.DEFAULT_OUTBOUND_GAS_LIMIT;
|
|
117
|
+
const deadline = params.deadline || BigInt(Math.floor(Date.now() / 1000) + 3600);
|
|
118
|
+
// Build the HopDescriptor with all metadata needed for cascade nesting
|
|
119
|
+
const hop = await buildHopDescriptor(ctx, preparedParams, route, ueaAddress);
|
|
120
|
+
const prepared = {
|
|
121
|
+
route,
|
|
122
|
+
payload,
|
|
123
|
+
gatewayRequest,
|
|
124
|
+
estimatedGas: gasEstimate,
|
|
125
|
+
nonce,
|
|
126
|
+
deadline,
|
|
127
|
+
_hop: hop,
|
|
128
|
+
};
|
|
129
|
+
return prepared;
|
|
121
130
|
}
|
|
122
131
|
// ============================================================================
|
|
123
132
|
// buildHopDescriptor
|
|
124
133
|
// ============================================================================
|
|
125
|
-
function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
execute: executeParams,
|
|
158
|
-
ueaAddress,
|
|
159
|
-
});
|
|
160
|
-
return Object.assign(Object.assign({}, baseDescriptor), { pushMulticalls });
|
|
134
|
+
async function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
135
|
+
// Pass 0 when user omits gasLimit so the contract uses the per-chain base,
|
|
136
|
+
// except EVM Route 3 where a CEA deploy + sendUniversalTxToUEA needs more
|
|
137
|
+
// than the generic 500k source-chain base.
|
|
138
|
+
const defaultGasLimit = route === route_detector_1.TransactionRoute.CEA_TO_PUSH &&
|
|
139
|
+
params.from?.chain &&
|
|
140
|
+
!(0, payload_builders_1.isSvmChain)(params.from.chain)
|
|
141
|
+
? selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT
|
|
142
|
+
: BigInt(0);
|
|
143
|
+
const gasLimit = params.gasLimit ?? defaultGasLimit;
|
|
144
|
+
const routeStr = route;
|
|
145
|
+
const baseDescriptor = {
|
|
146
|
+
params,
|
|
147
|
+
route: routeStr,
|
|
148
|
+
gasLimit,
|
|
149
|
+
maxPCForGas: params.maxPCForGas ?? BigInt(0),
|
|
150
|
+
ueaAddress,
|
|
151
|
+
revertRecipient: ueaAddress,
|
|
152
|
+
};
|
|
153
|
+
switch (route) {
|
|
154
|
+
case route_detector_1.TransactionRoute.UOA_TO_PUSH: {
|
|
155
|
+
// Route 1: Build Push Chain multicalls
|
|
156
|
+
const seedAmount = params.value ?? BigInt(0);
|
|
157
|
+
if (isValueOnlyNativeSeedToOwnUea(params, ueaAddress, {
|
|
158
|
+
allowFunds: true,
|
|
159
|
+
})) {
|
|
160
|
+
return {
|
|
161
|
+
...baseDescriptor,
|
|
162
|
+
pushMulticalls: [],
|
|
163
|
+
nativeSeedOnly: true,
|
|
164
|
+
nativeSeedAmount: seedAmount,
|
|
165
|
+
};
|
|
161
166
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
|
|
186
|
-
burnAmount = params.value;
|
|
187
|
-
}
|
|
188
|
-
else if (hasSvmExecute) {
|
|
189
|
-
// PAYLOAD-only SVM execute: still need native PRC-20 for chain namespace
|
|
190
|
-
// + gas fees, but burn amount must be zero. Matches executeUoaToCeaSvm
|
|
191
|
-
// (route-handlers.ts) and the EVM cascade branch below.
|
|
192
|
-
// Using burnAmount=1 would trigger transferFrom on the PRC-20 token,
|
|
193
|
-
// which reverts with InsufficientBalance when the UEA has no pSOL balance.
|
|
194
|
-
prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
|
|
195
|
-
burnAmount = BigInt(0);
|
|
196
|
-
}
|
|
197
|
-
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
198
|
-
let gasFee = BigInt(0);
|
|
199
|
-
let gasPrice = BigInt(0);
|
|
200
|
-
let sizing;
|
|
201
|
-
// SVM finalize budget: compare the quoted gasFee in lamports against
|
|
202
|
-
// the gateway finalize minimum, then re-query with a bumped gasLimit
|
|
203
|
-
// if the default quote is too low.
|
|
204
|
-
const svmFundsToken = burnAmount > BigInt(0)
|
|
205
|
-
? (_f = params.funds) === null || _f === void 0 ? void 0 : _f.token
|
|
206
|
-
: undefined;
|
|
207
|
-
const splMintBase58 = (svmFundsToken === null || svmFundsToken === void 0 ? void 0 : svmFundsToken.mechanism) === 'approve'
|
|
208
|
-
? svmFundsToken.address
|
|
209
|
-
: undefined;
|
|
210
|
-
let effectiveGasLimit = gasLimit;
|
|
211
|
-
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
212
|
-
let result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, effectiveGasLimit, targetChain);
|
|
213
|
-
result = yield (0, svm_rent_1.ensureSvmFinalizeGasBudgetQuote)({
|
|
214
|
-
ctx,
|
|
215
|
-
ueaAddress,
|
|
216
|
-
targetChain,
|
|
217
|
-
prc20Token,
|
|
218
|
-
quote: result,
|
|
219
|
-
splMintBase58,
|
|
220
|
-
burnAmount,
|
|
221
|
-
pathTag: 'buildHopDescriptor',
|
|
222
|
-
});
|
|
223
|
-
gasToken = result.gasToken;
|
|
224
|
-
gasFee = result.gasFee;
|
|
225
|
-
gasPrice = result.gasPrice;
|
|
226
|
-
sizing = result.sizing;
|
|
227
|
-
effectiveGasLimit = result.gasLimitUsed;
|
|
228
|
-
if (!params.gasLimit) {
|
|
229
|
-
(0, context_1.printLog)(ctx, `buildHopDescriptor — SVM resolved effectiveGasLimit: ${effectiveGasLimit} ` +
|
|
230
|
-
`(gasFee=${result.gasFee}, gasPrice=${result.gasPrice})`);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
return Object.assign(Object.assign({}, baseDescriptor), { targetChain, isSvmTarget: true, svmPayload,
|
|
234
|
-
prc20Token,
|
|
235
|
-
burnAmount,
|
|
236
|
-
gasToken,
|
|
237
|
-
gasFee,
|
|
238
|
-
gasPrice, gasLimit: effectiveGasLimit, sizing });
|
|
239
|
-
}
|
|
240
|
-
// EVM path: Resolve CEA address + build CEA multicalls
|
|
241
|
-
const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, targetChain, (_g = ctx.rpcUrls[targetChain]) === null || _g === void 0 ? void 0 : _g[0]);
|
|
242
|
-
// Migration path: raw MIGRATION_SELECTOR payload, no multicall wrapping
|
|
243
|
-
if (params.migration) {
|
|
244
|
-
const prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
|
|
245
|
-
const burnAmount = BigInt(0); // Migration is logic-only — no funds. CEA rejects msg.value != 0.
|
|
246
|
-
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
247
|
-
let gasFee = BigInt(0);
|
|
248
|
-
let gasPrice = BigInt(0);
|
|
249
|
-
let sizing;
|
|
250
|
-
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
251
|
-
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, targetChain);
|
|
252
|
-
gasToken = result.gasToken;
|
|
253
|
-
gasFee = result.gasFee;
|
|
254
|
-
gasPrice = result.gasPrice;
|
|
255
|
-
sizing = result.sizing;
|
|
256
|
-
}
|
|
257
|
-
return Object.assign(Object.assign({}, baseDescriptor), { targetChain,
|
|
258
|
-
ceaAddress, ceaMulticalls: [], prc20Token,
|
|
259
|
-
burnAmount,
|
|
260
|
-
gasToken,
|
|
261
|
-
gasFee,
|
|
262
|
-
gasPrice, isMigration: true, sizing });
|
|
263
|
-
}
|
|
264
|
-
// Build CEA multicalls
|
|
265
|
-
const ceaMulticalls = [];
|
|
266
|
-
const hasData = (0, data_utils_1.hasExecutablePayloadData)(params.data);
|
|
267
|
-
const fundsToken = ((_h = params.funds) === null || _h === void 0 ? void 0 : _h.amount)
|
|
268
|
-
? (0, route_handlers_1.resolveR2DestinationFundsToken)(params.funds.token, targetChain, ctx.pushNetwork)
|
|
269
|
-
: undefined;
|
|
270
|
-
const nativeFundsValue = ((_j = params.funds) === null || _j === void 0 ? void 0 : _j.amount) && (fundsToken === null || fundsToken === void 0 ? void 0 : fundsToken.mechanism) === 'native'
|
|
271
|
-
? params.funds.amount
|
|
272
|
-
: BigInt(0);
|
|
273
|
-
if (hasData) {
|
|
274
|
-
if (Array.isArray(params.data)) {
|
|
275
|
-
ceaMulticalls.push(...params.data);
|
|
276
|
-
}
|
|
277
|
-
else {
|
|
278
|
-
// When ERC-20 funds are provided with a single payload, auto-prepend a
|
|
279
|
-
// transfer() call so the tokens minted to the CEA are forwarded to the
|
|
280
|
-
// target address. This mirrors the Route 1 behavior in buildExecuteMulticall.
|
|
281
|
-
if ((_k = params.funds) === null || _k === void 0 ? void 0 : _k.amount) {
|
|
282
|
-
if (fundsToken && fundsToken.mechanism !== 'native') {
|
|
283
|
-
const erc20Transfer = (0, viem_1.encodeFunctionData)({
|
|
284
|
-
abi: erc20_evm_1.ERC20_EVM,
|
|
285
|
-
functionName: 'transfer',
|
|
286
|
-
args: [target.address, params.funds.amount],
|
|
287
|
-
});
|
|
288
|
-
ceaMulticalls.push({
|
|
289
|
-
to: fundsToken.address,
|
|
290
|
-
value: BigInt(0),
|
|
291
|
-
data: erc20Transfer,
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
// Single call with data. Forward native value (if any) so the target
|
|
296
|
-
// contract receives it alongside the payload call. The vault deposits
|
|
297
|
-
// native value to the CEA, and the multicall forwards it to the target.
|
|
298
|
-
ceaMulticalls.push({
|
|
299
|
-
to: target.address,
|
|
300
|
-
value: ((_l = params.value) !== null && _l !== void 0 ? _l : BigInt(0)) + nativeFundsValue,
|
|
301
|
-
data: params.data,
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
else {
|
|
306
|
-
if (((_m = params.funds) === null || _m === void 0 ? void 0 : _m.amount) &&
|
|
307
|
-
fundsToken &&
|
|
308
|
-
fundsToken.mechanism !== 'native') {
|
|
309
|
-
const erc20Transfer = (0, viem_1.encodeFunctionData)({
|
|
310
|
-
abi: erc20_evm_1.ERC20_EVM,
|
|
311
|
-
functionName: 'transfer',
|
|
312
|
-
args: [target.address, params.funds.amount],
|
|
313
|
-
});
|
|
314
|
-
ceaMulticalls.push({
|
|
315
|
-
to: fundsToken.address,
|
|
316
|
-
value: BigInt(0),
|
|
317
|
-
data: erc20Transfer,
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
if (params.value) {
|
|
321
|
-
// Skip multicall when sending native value to own CEA — gateway deposits directly.
|
|
322
|
-
// Self-call with value would revert (CEA._handleMulticall rejects it).
|
|
323
|
-
if (target.address.toLowerCase() !== ceaAddress.toLowerCase()) {
|
|
324
|
-
ceaMulticalls.push({
|
|
325
|
-
to: target.address,
|
|
326
|
-
value: params.value,
|
|
327
|
-
data: '0x',
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
else if (nativeFundsValue > BigInt(0)) {
|
|
332
|
-
if (target.address.toLowerCase() !== ceaAddress.toLowerCase()) {
|
|
333
|
-
ceaMulticalls.push({
|
|
334
|
-
to: target.address,
|
|
335
|
-
value: nativeFundsValue,
|
|
336
|
-
data: '0x',
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
// Determine PRC-20 token and burn amount
|
|
167
|
+
const executeParams = (0, gate_1.assertLegacyFunds)((0, helpers_1.toExecuteParams)(params));
|
|
168
|
+
const pushMulticalls = (0, payload_builders_1.buildExecuteMulticall)({
|
|
169
|
+
execute: executeParams,
|
|
170
|
+
ueaAddress,
|
|
171
|
+
});
|
|
172
|
+
return {
|
|
173
|
+
...baseDescriptor,
|
|
174
|
+
pushMulticalls,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
case route_detector_1.TransactionRoute.UOA_TO_CEA: {
|
|
178
|
+
// Route 2: Build outbound metadata
|
|
179
|
+
const target = params.to;
|
|
180
|
+
const targetChain = target.chain;
|
|
181
|
+
// Branch: SVM vs EVM
|
|
182
|
+
if ((0, payload_builders_1.isSvmChain)(targetChain)) {
|
|
183
|
+
// SVM path: no CEA lookup, build SVM payload via IDL resolver
|
|
184
|
+
const { svmPayload, hasExecute: hasSvmExecute } = (0, build_payload_1.buildSvmPayloadFromParams)({
|
|
185
|
+
data: params.data,
|
|
186
|
+
to: target,
|
|
187
|
+
senderUea: ueaAddress,
|
|
188
|
+
});
|
|
189
|
+
(0, payload_builders_1.assertSvmPayloadWithinRelayLimit)(svmPayload, `Route 2 SVM ${targetChain}`);
|
|
342
190
|
let prc20Token = selectors_1.ZERO_ADDRESS;
|
|
343
191
|
let burnAmount = BigInt(0);
|
|
344
|
-
if (
|
|
192
|
+
if (params.funds?.amount) {
|
|
345
193
|
const token = params.funds.token;
|
|
346
194
|
if (token) {
|
|
347
195
|
prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
|
|
@@ -352,160 +200,371 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
352
200
|
prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
|
|
353
201
|
burnAmount = params.value;
|
|
354
202
|
}
|
|
355
|
-
else if (
|
|
356
|
-
// PAYLOAD-only
|
|
357
|
-
//
|
|
358
|
-
//
|
|
203
|
+
else if (hasSvmExecute) {
|
|
204
|
+
// PAYLOAD-only SVM execute: still need native PRC-20 for chain namespace
|
|
205
|
+
// + gas fees, but burn amount must be zero. Matches executeUoaToCeaSvm
|
|
206
|
+
// (route-handlers.ts) and the EVM cascade branch below.
|
|
359
207
|
// Using burnAmount=1 would trigger transferFrom on the PRC-20 token,
|
|
360
|
-
// which reverts when the UEA has no
|
|
208
|
+
// which reverts with InsufficientBalance when the UEA has no pSOL balance.
|
|
361
209
|
prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
|
|
362
210
|
burnAmount = BigInt(0);
|
|
363
211
|
}
|
|
364
|
-
|
|
212
|
+
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
213
|
+
let gasFee = BigInt(0);
|
|
214
|
+
let gasPrice = BigInt(0);
|
|
215
|
+
let sizing;
|
|
216
|
+
// SVM finalize budget: compare the quoted gasFee in lamports against
|
|
217
|
+
// the gateway finalize minimum, then re-query with a bumped gasLimit
|
|
218
|
+
// if the default quote is too low.
|
|
219
|
+
const svmFundsToken = burnAmount > BigInt(0)
|
|
220
|
+
? params.funds?.token
|
|
221
|
+
: undefined;
|
|
222
|
+
const splMintBase58 = svmFundsToken?.mechanism === 'approve'
|
|
223
|
+
? svmFundsToken.address
|
|
224
|
+
: undefined;
|
|
225
|
+
let effectiveGasLimit = gasLimit;
|
|
226
|
+
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
227
|
+
let result = await (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, effectiveGasLimit, targetChain);
|
|
228
|
+
result = await (0, svm_rent_1.ensureSvmFinalizeGasBudgetQuote)({
|
|
229
|
+
ctx,
|
|
230
|
+
ueaAddress,
|
|
231
|
+
targetChain,
|
|
232
|
+
prc20Token,
|
|
233
|
+
quote: result,
|
|
234
|
+
splMintBase58,
|
|
235
|
+
burnAmount,
|
|
236
|
+
pathTag: 'buildHopDescriptor',
|
|
237
|
+
});
|
|
238
|
+
gasToken = result.gasToken;
|
|
239
|
+
gasFee = result.gasFee;
|
|
240
|
+
gasPrice = result.gasPrice;
|
|
241
|
+
sizing = result.sizing;
|
|
242
|
+
effectiveGasLimit = result.gasLimitUsed;
|
|
243
|
+
if (!params.gasLimit) {
|
|
244
|
+
(0, context_1.printLog)(ctx, `buildHopDescriptor — SVM resolved effectiveGasLimit: ${effectiveGasLimit} ` +
|
|
245
|
+
`(gasFee=${result.gasFee}, gasPrice=${result.gasPrice})`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
...baseDescriptor,
|
|
250
|
+
targetChain,
|
|
251
|
+
isSvmTarget: true,
|
|
252
|
+
svmPayload,
|
|
253
|
+
prc20Token,
|
|
254
|
+
burnAmount,
|
|
255
|
+
gasToken,
|
|
256
|
+
gasFee,
|
|
257
|
+
gasPrice,
|
|
258
|
+
gasLimit: effectiveGasLimit,
|
|
259
|
+
sizing,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
// EVM path: Resolve CEA address + build CEA multicalls
|
|
263
|
+
const { cea: ceaAddress } = await (0, cea_utils_1.getCEAAddress)(ueaAddress, targetChain, ctx.rpcUrls[targetChain]?.[0]);
|
|
264
|
+
// Migration path: raw MIGRATION_SELECTOR payload, no multicall wrapping
|
|
265
|
+
if (params.migration) {
|
|
266
|
+
const prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
|
|
267
|
+
const burnAmount = BigInt(0); // Migration is logic-only — no funds. CEA rejects msg.value != 0.
|
|
365
268
|
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
366
269
|
let gasFee = BigInt(0);
|
|
367
270
|
let gasPrice = BigInt(0);
|
|
368
271
|
let sizing;
|
|
369
272
|
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
370
|
-
const result =
|
|
273
|
+
const result = await (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, targetChain);
|
|
371
274
|
gasToken = result.gasToken;
|
|
372
275
|
gasFee = result.gasFee;
|
|
373
276
|
gasPrice = result.gasPrice;
|
|
374
277
|
sizing = result.sizing;
|
|
375
278
|
}
|
|
376
|
-
return
|
|
279
|
+
return {
|
|
280
|
+
...baseDescriptor,
|
|
281
|
+
targetChain,
|
|
377
282
|
ceaAddress,
|
|
378
|
-
ceaMulticalls,
|
|
283
|
+
ceaMulticalls: [],
|
|
379
284
|
prc20Token,
|
|
380
285
|
burnAmount,
|
|
381
286
|
gasToken,
|
|
382
287
|
gasFee,
|
|
383
288
|
gasPrice,
|
|
384
|
-
|
|
289
|
+
isMigration: true,
|
|
290
|
+
sizing,
|
|
291
|
+
};
|
|
385
292
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
293
|
+
// Build CEA multicalls
|
|
294
|
+
const ceaMulticalls = [];
|
|
295
|
+
const hasData = (0, data_utils_1.hasExecutablePayloadData)(params.data);
|
|
296
|
+
const fundsToken = params.funds?.amount
|
|
297
|
+
? (0, route_handlers_1.resolveR2DestinationFundsToken)(params.funds.token, targetChain, ctx.pushNetwork)
|
|
298
|
+
: undefined;
|
|
299
|
+
const nativeFundsValue = params.funds?.amount && fundsToken?.mechanism === 'native'
|
|
300
|
+
? params.funds.amount
|
|
301
|
+
: BigInt(0);
|
|
302
|
+
if (hasData) {
|
|
303
|
+
if (Array.isArray(params.data)) {
|
|
304
|
+
ceaMulticalls.push(...params.data);
|
|
392
305
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if (((_q = params.funds) === null || _q === void 0 ? void 0 : _q.amount) && params.funds.amount > BigInt(0)) {
|
|
410
|
-
amount = params.funds.amount;
|
|
411
|
-
const token = params.funds.token;
|
|
412
|
-
if (token && token.address) {
|
|
413
|
-
prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
|
|
414
|
-
}
|
|
415
|
-
else {
|
|
416
|
-
prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
306
|
+
else {
|
|
307
|
+
// When ERC-20 funds are provided with a single payload, auto-prepend a
|
|
308
|
+
// transfer() call so the tokens minted to the CEA are forwarded to the
|
|
309
|
+
// target address. This mirrors the Route 1 behavior in buildExecuteMulticall.
|
|
310
|
+
if (params.funds?.amount) {
|
|
311
|
+
if (fundsToken && fundsToken.mechanism !== 'native') {
|
|
312
|
+
const erc20Transfer = (0, viem_1.encodeFunctionData)({
|
|
313
|
+
abi: erc20_evm_1.ERC20_EVM,
|
|
314
|
+
functionName: 'transfer',
|
|
315
|
+
args: [target.address, params.funds.amount],
|
|
316
|
+
});
|
|
317
|
+
ceaMulticalls.push({
|
|
318
|
+
to: fundsToken.address,
|
|
319
|
+
value: BigInt(0),
|
|
320
|
+
data: erc20Transfer,
|
|
321
|
+
});
|
|
417
322
|
}
|
|
418
323
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
324
|
+
// Single call with data. Forward native value (if any) so the target
|
|
325
|
+
// contract receives it alongside the payload call. The vault deposits
|
|
326
|
+
// native value to the CEA, and the multicall forwards it to the target.
|
|
327
|
+
ceaMulticalls.push({
|
|
328
|
+
to: target.address,
|
|
329
|
+
value: (params.value ?? BigInt(0)) + nativeFundsValue,
|
|
330
|
+
data: params.data,
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
if (params.funds?.amount &&
|
|
336
|
+
fundsToken &&
|
|
337
|
+
fundsToken.mechanism !== 'native') {
|
|
338
|
+
const erc20Transfer = (0, viem_1.encodeFunctionData)({
|
|
339
|
+
abi: erc20_evm_1.ERC20_EVM,
|
|
340
|
+
functionName: 'transfer',
|
|
341
|
+
args: [target.address, params.funds.amount],
|
|
342
|
+
});
|
|
343
|
+
ceaMulticalls.push({
|
|
344
|
+
to: fundsToken.address,
|
|
345
|
+
value: BigInt(0),
|
|
346
|
+
data: erc20Transfer,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
if (params.value) {
|
|
350
|
+
// Skip multicall when sending native value to own CEA — gateway deposits directly.
|
|
351
|
+
// Self-call with value would revert (CEA._handleMulticall rejects it).
|
|
352
|
+
if (target.address.toLowerCase() !== ceaAddress.toLowerCase()) {
|
|
353
|
+
ceaMulticalls.push({
|
|
354
|
+
to: target.address,
|
|
355
|
+
value: params.value,
|
|
356
|
+
data: '0x',
|
|
357
|
+
});
|
|
426
358
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
result = yield (0, svm_rent_1.ensureSvmFinalizeGasBudgetQuote)({
|
|
435
|
-
ctx,
|
|
436
|
-
ueaAddress,
|
|
437
|
-
targetChain: sourceChain,
|
|
438
|
-
prc20Token,
|
|
439
|
-
quote: result,
|
|
440
|
-
splMintBase58: undefined,
|
|
441
|
-
burnAmount: BigInt(0),
|
|
442
|
-
pathTag: 'buildHopDescriptor',
|
|
359
|
+
}
|
|
360
|
+
else if (nativeFundsValue > BigInt(0)) {
|
|
361
|
+
if (target.address.toLowerCase() !== ceaAddress.toLowerCase()) {
|
|
362
|
+
ceaMulticalls.push({
|
|
363
|
+
to: target.address,
|
|
364
|
+
value: nativeFundsValue,
|
|
365
|
+
data: '0x',
|
|
443
366
|
});
|
|
444
|
-
gasToken = result.gasToken;
|
|
445
|
-
gasFee = result.gasFee;
|
|
446
|
-
gasPrice = result.gasPrice;
|
|
447
|
-
sizing = result.sizing;
|
|
448
|
-
effectiveGasLimit = result.gasLimitUsed;
|
|
449
367
|
}
|
|
450
|
-
return Object.assign(Object.assign({}, baseDescriptor), { sourceChain, ceaAddress: ceaPdaHex, isSvmTarget: true, prc20Token,
|
|
451
|
-
// Route 3 drains pre-existing funds from the external CEA. The
|
|
452
|
-
// Push-side outbound is a payload relay, so no PRC-20 is burned.
|
|
453
|
-
burnAmount: BigInt(0), gasToken,
|
|
454
|
-
gasFee,
|
|
455
|
-
gasPrice, gasLimit: effectiveGasLimit, sizing });
|
|
456
368
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
369
|
+
}
|
|
370
|
+
// Determine PRC-20 token and burn amount
|
|
371
|
+
let prc20Token = selectors_1.ZERO_ADDRESS;
|
|
372
|
+
let burnAmount = BigInt(0);
|
|
373
|
+
if (params.funds?.amount) {
|
|
374
|
+
const token = params.funds.token;
|
|
375
|
+
if (token) {
|
|
376
|
+
prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
|
|
377
|
+
burnAmount = params.funds.amount;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
else if (params.value && params.value > BigInt(0)) {
|
|
381
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
|
|
382
|
+
burnAmount = params.value;
|
|
383
|
+
}
|
|
384
|
+
else if ((0, data_utils_1.hasExecutablePayloadData)(params.data)) {
|
|
385
|
+
// PAYLOAD-only (no value transfer): still need native PRC-20 token for
|
|
386
|
+
// chain namespace lookup + gas fees, but burn amount must be zero.
|
|
387
|
+
// Matches the direct executeUoaToCea path (route-handlers.ts).
|
|
388
|
+
// Using burnAmount=1 would trigger transferFrom on the PRC-20 token,
|
|
389
|
+
// which reverts when the UEA has no PRC-20 balance for that chain.
|
|
390
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
|
|
391
|
+
burnAmount = BigInt(0);
|
|
392
|
+
}
|
|
393
|
+
// Query gas fee
|
|
394
|
+
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
395
|
+
let gasFee = BigInt(0);
|
|
396
|
+
let gasPrice = BigInt(0);
|
|
397
|
+
let sizing;
|
|
398
|
+
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
399
|
+
const result = await (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, targetChain);
|
|
400
|
+
gasToken = result.gasToken;
|
|
401
|
+
gasFee = result.gasFee;
|
|
402
|
+
gasPrice = result.gasPrice;
|
|
403
|
+
sizing = result.sizing;
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
...baseDescriptor,
|
|
407
|
+
targetChain,
|
|
408
|
+
ceaAddress,
|
|
409
|
+
ceaMulticalls,
|
|
410
|
+
prc20Token,
|
|
411
|
+
burnAmount,
|
|
412
|
+
gasToken,
|
|
413
|
+
gasFee,
|
|
414
|
+
gasPrice,
|
|
415
|
+
sizing,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
case route_detector_1.TransactionRoute.CEA_TO_PUSH: {
|
|
419
|
+
// Route 3: Build CEA multicalls for sendUniversalTxFromCEA
|
|
420
|
+
const sourceChain = params.from.chain;
|
|
421
|
+
const seedAmount = params.value ?? BigInt(0);
|
|
422
|
+
if (isValueOnlyNativeSeedToOwnUea(params, ueaAddress)) {
|
|
423
|
+
return {
|
|
424
|
+
...baseDescriptor,
|
|
425
|
+
sourceChain,
|
|
426
|
+
burnAmount: BigInt(0),
|
|
427
|
+
gasToken: selectors_1.ZERO_ADDRESS,
|
|
428
|
+
gasFee: BigInt(0),
|
|
429
|
+
nativeSeedOnly: true,
|
|
430
|
+
nativeSeedAmount: seedAmount,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
// SVM chains use PDA-based CEA, not factory-deployed CEA
|
|
434
|
+
if ((0, payload_builders_1.isSvmChain)(sourceChain)) {
|
|
435
|
+
const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
|
|
436
|
+
if (!lockerContract) {
|
|
437
|
+
throw new Error(`No SVM gateway program configured for chain ${sourceChain}`);
|
|
438
|
+
}
|
|
439
|
+
const programPk = new web3_js_1.PublicKey(lockerContract);
|
|
440
|
+
const gatewayProgramHex = ('0x' +
|
|
441
|
+
Buffer.from(programPk.toBytes()).toString('hex'));
|
|
442
|
+
// Derive CEA PDA
|
|
443
|
+
const ueaBytes = Buffer.from(ueaAddress.slice(2), 'hex');
|
|
444
|
+
const [ceaPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('push_identity'), ueaBytes], programPk);
|
|
445
|
+
const ceaPdaHex = ('0x' +
|
|
446
|
+
Buffer.from(ceaPda.toBytes()).toString('hex'));
|
|
462
447
|
let amount = BigInt(0);
|
|
463
|
-
let
|
|
464
|
-
if (
|
|
448
|
+
let prc20Token;
|
|
449
|
+
if (params.funds?.amount && params.funds.amount > BigInt(0)) {
|
|
450
|
+
amount = params.funds.amount;
|
|
465
451
|
const token = params.funds.token;
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
452
|
+
const pc20 = params._pc20;
|
|
453
|
+
if (pc20?.direction === 'import') {
|
|
454
|
+
// PC20 CEA burns use the native PRC-20 only for the outbound
|
|
455
|
+
// relay's chain namespace and gas. The wrapper amount is carried
|
|
456
|
+
// inside the Solana self-call and must not be burned on Push.
|
|
457
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
458
|
+
}
|
|
459
|
+
else if (token && token.address) {
|
|
460
|
+
prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
475
464
|
}
|
|
476
465
|
}
|
|
477
466
|
else if (params.value && params.value > BigInt(0)) {
|
|
478
467
|
amount = params.value;
|
|
479
|
-
|
|
468
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
// Payload-only Route 3 SVM
|
|
472
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
480
473
|
}
|
|
481
|
-
// The PRC-20 for the outbound wrapper (Route 2 to source chain)
|
|
482
|
-
const prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
483
474
|
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
484
475
|
let gasFee = BigInt(0);
|
|
485
476
|
let gasPrice = BigInt(0);
|
|
486
477
|
let sizing;
|
|
478
|
+
let effectiveGasLimit = gasLimit;
|
|
487
479
|
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
488
|
-
|
|
480
|
+
let result = await (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, effectiveGasLimit, sourceChain);
|
|
481
|
+
result = await (0, svm_rent_1.ensureSvmFinalizeGasBudgetQuote)({
|
|
482
|
+
ctx,
|
|
483
|
+
ueaAddress,
|
|
484
|
+
targetChain: sourceChain,
|
|
485
|
+
prc20Token,
|
|
486
|
+
quote: result,
|
|
487
|
+
splMintBase58: undefined,
|
|
488
|
+
burnAmount: BigInt(0),
|
|
489
|
+
pathTag: 'buildHopDescriptor',
|
|
490
|
+
});
|
|
489
491
|
gasToken = result.gasToken;
|
|
490
492
|
gasFee = result.gasFee;
|
|
491
493
|
gasPrice = result.gasPrice;
|
|
492
494
|
sizing = result.sizing;
|
|
495
|
+
effectiveGasLimit = result.gasLimitUsed;
|
|
493
496
|
}
|
|
494
|
-
return
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
497
|
+
return {
|
|
498
|
+
...baseDescriptor,
|
|
499
|
+
sourceChain,
|
|
500
|
+
ceaAddress: ceaPdaHex,
|
|
501
|
+
isSvmTarget: true,
|
|
502
|
+
prc20Token,
|
|
503
|
+
// Route 3 drains pre-existing funds from the external CEA. The
|
|
504
|
+
// Push-side outbound is a payload relay, so no PRC-20 is burned.
|
|
505
|
+
burnAmount: BigInt(0),
|
|
506
|
+
gasToken,
|
|
501
507
|
gasFee,
|
|
502
508
|
gasPrice,
|
|
503
|
-
|
|
509
|
+
gasLimit: effectiveGasLimit,
|
|
510
|
+
sizing,
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
const { cea: ceaAddress } = await (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, ctx.rpcUrls[sourceChain]?.[0]);
|
|
514
|
+
// CEA auto-deploys on-chain: Vault.finalizeUniversalTx calls CEAFactory.deployCEA()
|
|
515
|
+
// if CEA doesn't exist yet. No SDK-side blocking needed.
|
|
516
|
+
// Determine token/amount for the inbound
|
|
517
|
+
let tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
518
|
+
let amount = BigInt(0);
|
|
519
|
+
let nativeValue = BigInt(0);
|
|
520
|
+
if (params.funds?.amount) {
|
|
521
|
+
const token = params.funds.token;
|
|
522
|
+
if (token) {
|
|
523
|
+
if (token.mechanism === 'native') {
|
|
524
|
+
amount = params.funds.amount;
|
|
525
|
+
nativeValue = params.funds.amount;
|
|
526
|
+
}
|
|
527
|
+
else {
|
|
528
|
+
tokenAddress = token.address;
|
|
529
|
+
amount = params.funds.amount;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
else if (params.value && params.value > BigInt(0)) {
|
|
534
|
+
amount = params.value;
|
|
535
|
+
nativeValue = params.value;
|
|
504
536
|
}
|
|
505
|
-
|
|
506
|
-
|
|
537
|
+
// The PRC-20 for the outbound wrapper (Route 2 to source chain)
|
|
538
|
+
const prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
539
|
+
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
540
|
+
let gasFee = BigInt(0);
|
|
541
|
+
let gasPrice = BigInt(0);
|
|
542
|
+
let sizing;
|
|
543
|
+
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
544
|
+
const result = await (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, sourceChain);
|
|
545
|
+
gasToken = result.gasToken;
|
|
546
|
+
gasFee = result.gasFee;
|
|
547
|
+
gasPrice = result.gasPrice;
|
|
548
|
+
sizing = result.sizing;
|
|
549
|
+
}
|
|
550
|
+
return {
|
|
551
|
+
...baseDescriptor,
|
|
552
|
+
sourceChain,
|
|
553
|
+
ceaAddress,
|
|
554
|
+
prc20Token,
|
|
555
|
+
// Route 3 is payload-only on Push: the CEA-side sendUniversalTxToUEA
|
|
556
|
+
// carries `amount`. Burning here would require the UEA to pre-hold the
|
|
557
|
+
// token it is trying to receive from the external CEA.
|
|
558
|
+
burnAmount: BigInt(0),
|
|
559
|
+
gasToken,
|
|
560
|
+
gasFee,
|
|
561
|
+
gasPrice,
|
|
562
|
+
sizing,
|
|
563
|
+
};
|
|
507
564
|
}
|
|
508
|
-
|
|
565
|
+
default:
|
|
566
|
+
return baseDescriptor;
|
|
567
|
+
}
|
|
509
568
|
}
|
|
510
569
|
// ============================================================================
|
|
511
570
|
// classifyIntoSegments
|
|
@@ -622,15 +681,15 @@ function categoryRank(c) {
|
|
|
622
681
|
return c === 'C' ? 2 : c === 'B' ? 1 : 0;
|
|
623
682
|
}
|
|
624
683
|
function mergeMaxPCForGas(current, next) {
|
|
625
|
-
const a = current
|
|
626
|
-
const b = next
|
|
684
|
+
const a = current ?? BigInt(0);
|
|
685
|
+
const b = next ?? BigInt(0);
|
|
627
686
|
if (a === BigInt(0) || b === BigInt(0))
|
|
628
687
|
return BigInt(0);
|
|
629
688
|
return a + b;
|
|
630
689
|
}
|
|
631
690
|
function mergeGasPrice(current, next) {
|
|
632
|
-
const a = current
|
|
633
|
-
const b = next
|
|
691
|
+
const a = current ?? BigInt(0);
|
|
692
|
+
const b = next ?? BigInt(0);
|
|
634
693
|
return a > b ? a : b;
|
|
635
694
|
}
|
|
636
695
|
// ============================================================================
|
|
@@ -649,12 +708,11 @@ function getSegmentType(route) {
|
|
|
649
708
|
}
|
|
650
709
|
}
|
|
651
710
|
function sumMulticallNativeValue(multicalls) {
|
|
652
|
-
return (multicalls
|
|
711
|
+
return (multicalls ?? []).reduce((sum, mc) => sum + (mc.value ?? BigInt(0)), BigInt(0));
|
|
653
712
|
}
|
|
654
713
|
function isValueOnlyNativeSeedToOwnUea(params, ueaAddress, options) {
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
((options === null || options === void 0 ? void 0 : options.allowFunds) === true || !((_b = params.funds) === null || _b === void 0 ? void 0 : _b.amount)) &&
|
|
714
|
+
return ((params.value ?? BigInt(0)) > BigInt(0) &&
|
|
715
|
+
(options?.allowFunds === true || !params.funds?.amount) &&
|
|
658
716
|
(0, data_utils_1.isEmptyPayloadData)(params.data) &&
|
|
659
717
|
typeof params.to === 'string' &&
|
|
660
718
|
params.to.toLowerCase() === ueaAddress.toLowerCase());
|
|
@@ -668,30 +726,42 @@ function getRootFundsDeposit(hops) {
|
|
|
668
726
|
if (hop.route !== 'UOA_TO_PUSH')
|
|
669
727
|
break;
|
|
670
728
|
const funds = hop.params.funds;
|
|
671
|
-
if (!
|
|
729
|
+
if (!funds?.amount || funds.amount <= BigInt(0))
|
|
672
730
|
continue;
|
|
673
731
|
if (!rootFunds) {
|
|
674
|
-
rootFunds =
|
|
732
|
+
rootFunds = { ...funds };
|
|
675
733
|
continue;
|
|
676
734
|
}
|
|
735
|
+
// Token identity across hops. Compared on the fields both union members
|
|
736
|
+
// share (address) plus, for MoveableTokens, symbol/mechanism. A PC20
|
|
737
|
+
// reference is identified by chain+address alone — it has no symbol or
|
|
738
|
+
// mechanism to compare, and its chain is what distinguishes two wrappers
|
|
739
|
+
// that happen to share an address on different chains.
|
|
677
740
|
const currentToken = rootFunds.token;
|
|
678
741
|
const nextToken = funds.token;
|
|
679
742
|
const sameToken = currentToken === nextToken ||
|
|
680
743
|
(!!currentToken &&
|
|
681
744
|
!!nextToken &&
|
|
682
|
-
currentToken.address.toLowerCase() ===
|
|
683
|
-
|
|
684
|
-
currentToken
|
|
685
|
-
|
|
745
|
+
currentToken.address.toLowerCase() === nextToken.address.toLowerCase() &&
|
|
746
|
+
(0, orchestrator_types_1.isPC20Reference)(currentToken) === (0, orchestrator_types_1.isPC20Reference)(nextToken) &&
|
|
747
|
+
((0, orchestrator_types_1.isPC20Reference)(currentToken) && (0, orchestrator_types_1.isPC20Reference)(nextToken)
|
|
748
|
+
? currentToken.chain === nextToken.chain
|
|
749
|
+
: currentToken.symbol ===
|
|
750
|
+
nextToken.symbol &&
|
|
751
|
+
currentToken.mechanism ===
|
|
752
|
+
nextToken.mechanism));
|
|
686
753
|
if (!sameToken) {
|
|
687
754
|
throw new Error('Cascade can only bridge one origin token in root Push execution hops. Split mixed-token deposits into separate transactions.');
|
|
688
755
|
}
|
|
689
|
-
rootFunds =
|
|
756
|
+
rootFunds = {
|
|
757
|
+
...rootFunds,
|
|
758
|
+
amount: rootFunds.amount + funds.amount,
|
|
759
|
+
};
|
|
690
760
|
}
|
|
691
761
|
return rootFunds;
|
|
692
762
|
}
|
|
693
763
|
function isNativeSeedOnlyHop(hop) {
|
|
694
|
-
return
|
|
764
|
+
return hop?.nativeSeedOnly === true;
|
|
695
765
|
}
|
|
696
766
|
function isNativeSeedOnlySegment(segment) {
|
|
697
767
|
return (segment.type === 'INBOUND_FROM_CEA' &&
|
|
@@ -702,7 +772,6 @@ function segmentNeedsOutboundGas(segment) {
|
|
|
702
772
|
return segment.type !== 'PUSH_EXECUTION' && !isNativeSeedOnlySegment(segment);
|
|
703
773
|
}
|
|
704
774
|
function buildRootOutboundIndexByHop(segments, hops) {
|
|
705
|
-
var _a;
|
|
706
775
|
const hopIndexByDescriptor = new Map();
|
|
707
776
|
hops.forEach((hop, index) => hopIndexByDescriptor.set(hop, index));
|
|
708
777
|
const nextIndexByChain = new Map();
|
|
@@ -718,7 +787,7 @@ function buildRootOutboundIndexByHop(segments, hops) {
|
|
|
718
787
|
}
|
|
719
788
|
if (!chain)
|
|
720
789
|
continue;
|
|
721
|
-
const outboundIndex =
|
|
790
|
+
const outboundIndex = nextIndexByChain.get(chain) ?? 0;
|
|
722
791
|
for (const hop of segment.hops) {
|
|
723
792
|
const hopIndex = hopIndexByDescriptor.get(hop);
|
|
724
793
|
if (hopIndex !== undefined) {
|
|
@@ -730,16 +799,15 @@ function buildRootOutboundIndexByHop(segments, hops) {
|
|
|
730
799
|
return result;
|
|
731
800
|
}
|
|
732
801
|
function sumNativeSeedAmount(hops) {
|
|
733
|
-
return hops.reduce((sum, hop) =>
|
|
802
|
+
return hops.reduce((sum, hop) => sum + (hop.nativeSeedAmount ?? BigInt(0)), BigInt(0));
|
|
734
803
|
}
|
|
735
804
|
function getCascadeNativeSeed(segments) {
|
|
736
|
-
var _a, _b, _c;
|
|
737
805
|
let total = BigInt(0);
|
|
738
806
|
for (const segment of segments) {
|
|
739
807
|
for (const hop of segment.hops) {
|
|
740
|
-
const value =
|
|
808
|
+
const value = hop.params.value ?? BigInt(0);
|
|
741
809
|
if (isNativeSeedOnlyHop(hop)) {
|
|
742
|
-
total +=
|
|
810
|
+
total += hop.nativeSeedAmount ?? value;
|
|
743
811
|
continue;
|
|
744
812
|
}
|
|
745
813
|
// In a CEA->Push hop that also supplies `funds`, `value` is native PC
|
|
@@ -747,7 +815,7 @@ function getCascadeNativeSeed(segments) {
|
|
|
747
815
|
// later Push-side cascade calls and must be counted for sizing fresh UEAs.
|
|
748
816
|
if (segment.type === 'INBOUND_FROM_CEA' &&
|
|
749
817
|
value > BigInt(0) &&
|
|
750
|
-
|
|
818
|
+
hop.params.funds?.amount) {
|
|
751
819
|
total += value;
|
|
752
820
|
}
|
|
753
821
|
}
|
|
@@ -755,7 +823,6 @@ function getCascadeNativeSeed(segments) {
|
|
|
755
823
|
return total;
|
|
756
824
|
}
|
|
757
825
|
function getInboundFundingForBurnToken(segments, beforeSegmentIndex, burnToken, pushNetwork) {
|
|
758
|
-
var _a;
|
|
759
826
|
let total = BigInt(0);
|
|
760
827
|
const normalizedBurnToken = burnToken.toLowerCase();
|
|
761
828
|
for (const segment of segments.slice(0, beforeSegmentIndex)) {
|
|
@@ -765,7 +832,7 @@ function getInboundFundingForBurnToken(segments, beforeSegmentIndex, burnToken,
|
|
|
765
832
|
const params = hop.params;
|
|
766
833
|
let fundedToken;
|
|
767
834
|
let fundedAmount = BigInt(0);
|
|
768
|
-
if (
|
|
835
|
+
if (params.funds?.amount) {
|
|
769
836
|
const token = params.funds.token;
|
|
770
837
|
if (token) {
|
|
771
838
|
fundedToken = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
|
|
@@ -779,630 +846,673 @@ function getInboundFundingForBurnToken(segments, beforeSegmentIndex, burnToken,
|
|
|
779
846
|
fundedToken = (0, helpers_1.getNativePRC20ForChain)(hop.sourceChain, pushNetwork);
|
|
780
847
|
fundedAmount = params.value;
|
|
781
848
|
}
|
|
782
|
-
if (
|
|
849
|
+
if (fundedToken?.toLowerCase() === normalizedBurnToken) {
|
|
783
850
|
total += fundedAmount;
|
|
784
851
|
}
|
|
785
852
|
}
|
|
786
853
|
}
|
|
787
854
|
return total;
|
|
788
855
|
}
|
|
789
|
-
function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce, options) {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
856
|
+
async function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce, options) {
|
|
857
|
+
let accumulatedPushMulticalls = [];
|
|
858
|
+
let requiredNativeValue = BigInt(0);
|
|
859
|
+
const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
|
|
860
|
+
const currentUeaBalance = ueaBalance ?? BigInt(0);
|
|
861
|
+
const cascadeNativeSeed = getCascadeNativeSeed(segments);
|
|
862
|
+
const effectiveUeaBalance = currentUeaBalance + cascadeNativeSeed;
|
|
863
|
+
const enforceGasCheck = options?.enforceGasCheck === true ||
|
|
864
|
+
segments.some((segment) => segment.hops.some((hop) => hop.params.options?.enforceGasCheck === true));
|
|
865
|
+
if (cascadeNativeSeed > BigInt(0)) {
|
|
866
|
+
(0, context_1.printLog)(ctx, `composeCascade — counting cascade native seed ${cascadeNativeSeed.toString()} for cascade sizing (current UEA balance ${currentUeaBalance.toString()})`);
|
|
867
|
+
}
|
|
868
|
+
// Compute per-outbound nativeValueForGas from UEA balance
|
|
869
|
+
// Each outbound segment needs native value for the gas swap on the destination chain.
|
|
870
|
+
// The contract refunds excess, so over-allocating is safe.
|
|
871
|
+
const numOutbounds = segments.filter(segmentNeedsOutboundGas).length;
|
|
872
|
+
const CASCADE_GAS_RESERVE = BigInt(3e18); // 3 PC reserve for gas costs
|
|
873
|
+
// Cascade pre-flight: catches the zero-balance silent-segment-zero failure
|
|
874
|
+
// mode. When `ueaBalance <= CASCADE_GAS_RESERVE`, `perOutboundNativeValue`
|
|
875
|
+
// is left undefined and downstream segments silently allocate value=0,
|
|
876
|
+
// producing under-funded swaps that revert inside Uniswap. Warn by default
|
|
877
|
+
// and throw only when enforceGasCheck=true.
|
|
878
|
+
if (numOutbounds > 0) {
|
|
879
|
+
(0, preflight_1.runPreflight)({
|
|
880
|
+
ctx,
|
|
881
|
+
ueaAddress,
|
|
882
|
+
ueaBalance: effectiveUeaBalance,
|
|
883
|
+
// Required = at least one wei of allocatable PC after reserve. The
|
|
884
|
+
// per-segment min-swap check below catches finer-grained shortfalls.
|
|
885
|
+
requiredValue: BigInt(1),
|
|
886
|
+
gasReserve: CASCADE_GAS_RESERVE,
|
|
887
|
+
pathTag: 'CASCADE',
|
|
888
|
+
enforceGasCheck,
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
let perOutboundNativeValue;
|
|
892
|
+
if (numOutbounds > 0 && effectiveUeaBalance > CASCADE_GAS_RESERVE) {
|
|
893
|
+
perOutboundNativeValue =
|
|
894
|
+
(effectiveUeaBalance - CASCADE_GAS_RESERVE) / BigInt(numOutbounds);
|
|
895
|
+
}
|
|
896
|
+
let universalCoreAddressForSwap;
|
|
897
|
+
const getUniversalCoreAddressForSwap = async () => {
|
|
898
|
+
if (!universalCoreAddressForSwap) {
|
|
899
|
+
universalCoreAddressForSwap =
|
|
900
|
+
await ctx.pushClient.readContract({
|
|
901
|
+
address: gatewayPcAddress,
|
|
902
|
+
abi: abi_1.UNIVERSAL_GATEWAY_PC,
|
|
903
|
+
functionName: 'universalCore',
|
|
904
|
+
args: [],
|
|
905
|
+
});
|
|
802
906
|
}
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
(0,
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
907
|
+
return universalCoreAddressForSwap;
|
|
908
|
+
};
|
|
909
|
+
// Pool-price-based native-value override for SVM outbounds.
|
|
910
|
+
// The flat split above works for EVM gas tokens (pBNB/pETH — cheap per unit),
|
|
911
|
+
// but pSOL has a much higher WPC/pSOL pool price, so a flat balance split can
|
|
912
|
+
// underfund the gas swap and make Uniswap revert with "STF". Mirror the
|
|
913
|
+
// direct SVM route handlers' pool-price read for every SVM outbound-bearing
|
|
914
|
+
// segment, including Route 3 SVM inbound round trips.
|
|
915
|
+
const svmSegments = segments.filter((s) => ((s.type === 'OUTBOUND_TO_CEA' && s.hops[0]?.isSvmTarget === true) ||
|
|
916
|
+
(s.type === 'INBOUND_FROM_CEA' &&
|
|
917
|
+
!isNativeSeedOnlySegment(s) &&
|
|
918
|
+
(0, payload_builders_1.isSvmChain)(s.sourceChain))) &&
|
|
919
|
+
s.gasToken &&
|
|
920
|
+
s.gasFee &&
|
|
921
|
+
s.gasFee > BigInt(0));
|
|
922
|
+
const svmNativeValueBySegment = new Map();
|
|
923
|
+
if (svmSegments.length > 0 && effectiveUeaBalance > CASCADE_GAS_RESERVE) {
|
|
924
|
+
const universalCoreAddress = await getUniversalCoreAddressForSwap();
|
|
925
|
+
// Compute per-SVM budget = ueaBalance − (PC needed for EVM outbounds) − 1 PC safety
|
|
926
|
+
// (outer-tx gas + refund headroom). Pass a huge accountBalance to the estimator so
|
|
927
|
+
// its internal cap is effectively disabled; we cap externally to the per-SVM budget.
|
|
928
|
+
const numEvmOutbounds = numOutbounds - svmSegments.length;
|
|
929
|
+
const evmReservation = perOutboundNativeValue && numEvmOutbounds > 0
|
|
930
|
+
? perOutboundNativeValue * BigInt(numEvmOutbounds)
|
|
931
|
+
: BigInt(0);
|
|
932
|
+
const outerSafety = BigInt(1e18); // 1 PC for outer-tx gas
|
|
933
|
+
const svmTotal = effectiveUeaBalance > evmReservation + outerSafety
|
|
934
|
+
? effectiveUeaBalance - evmReservation - outerSafety
|
|
935
|
+
: BigInt(0);
|
|
936
|
+
const perSvmCap = svmTotal / BigInt(svmSegments.length);
|
|
937
|
+
const UNCAPPED_BALANCE = BigInt('1000000000000000000000000000000'); // effectively disable estimator's internal cap
|
|
938
|
+
for (const seg of svmSegments) {
|
|
939
|
+
// Cascade-level segment index (position in `segments`, not in `svmSegments`)
|
|
940
|
+
// so the error's `segmentIndex` lines up with what the caller sees.
|
|
941
|
+
const cascadeIdx = segments.indexOf(seg);
|
|
942
|
+
const estimated = await (0, gas_calculator_1.estimateNativeValueForSwap)(ctx, universalCoreAddress, seg.gasToken, seg.gasFee, UNCAPPED_BALANCE);
|
|
943
|
+
// SVM warn-threshold telemetry (no truncation — pre-flight handles drain protection).
|
|
944
|
+
(0, preflight_1.maybeFireSvmWarnThreshold)(ctx, estimated, seg.gasToken, 'CASCADE');
|
|
945
|
+
const flat = perOutboundNativeValue ?? BigInt(0);
|
|
946
|
+
let value = estimated > flat ? estimated : flat;
|
|
947
|
+
if (perSvmCap > BigInt(0) && value > perSvmCap) {
|
|
948
|
+
const cappedValue = perSvmCap >= estimated ? perSvmCap : estimated;
|
|
949
|
+
if (cappedValue !== perSvmCap) {
|
|
950
|
+
(0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${seg.targetChain ?? seg.sourceChain}: perSvmCap=${perSvmCap} is below live pool quote=${estimated}; using quote to avoid underfunded gas swap`);
|
|
951
|
+
}
|
|
952
|
+
value = cappedValue;
|
|
953
|
+
}
|
|
954
|
+
// Upward-allocation ceiling (mirrors R3 SVM Fix #4 in route-handlers.ts:1690).
|
|
955
|
+
// When UEA balance is huge, the flat split or perSvmCap can produce a value
|
|
956
|
+
// far above what the swap actually needs. Submitting that into a Uniswap
|
|
957
|
+
// pool moves the price for other users. Cap at max(200 PC, 5×estimated):
|
|
958
|
+
// - fair pool (estimated ≈ 100 PC): cap = 500 PC → caps voluntary excess
|
|
959
|
+
// - skewed pool (estimated > 200 PC): cap = 5×estimated → still allows
|
|
960
|
+
// pool-skew margin; min-swap check below still fires if value < estimated
|
|
961
|
+
// The contract still refunds excess via swapAndBurnGas, so this is purely
|
|
962
|
+
// a guard against submitting wasteful msg.value to fair-priced pools.
|
|
963
|
+
const UPWARD_BASE_CEILING_CASCADE_SVM = BigInt(200) * BigInt(1e18);
|
|
964
|
+
const upwardCeilingCascadeSvm = estimated * BigInt(5) > UPWARD_BASE_CEILING_CASCADE_SVM
|
|
965
|
+
? estimated * BigInt(5)
|
|
966
|
+
: UPWARD_BASE_CEILING_CASCADE_SVM;
|
|
967
|
+
if (value > upwardCeilingCascadeSvm) {
|
|
968
|
+
(0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${seg.targetChain ?? seg.sourceChain}: capping value from ${value} to upwardCeiling=${upwardCeilingCascadeSvm} (pool quote=${estimated})`);
|
|
969
|
+
value = upwardCeilingCascadeSvm;
|
|
970
|
+
}
|
|
971
|
+
(0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${seg.targetChain ?? seg.sourceChain}: gasFee=${seg.gasFee}, estimatedWpc=${estimated}, flat=${flat}, perSvmCap=${perSvmCap}, upwardCeiling=${upwardCeilingCascadeSvm}, chosen=${value}`);
|
|
972
|
+
// Per-segment minimum-viable-swap check: if the chosen `value` is below
|
|
973
|
+
// the live pool quote (`estimated`), the cap squeezed the budget below
|
|
974
|
+
// what the swap actually needs and the segment will revert inside
|
|
975
|
+
// Uniswap with STF. Throw cleanly with `segmentIndex` so the caller
|
|
976
|
+
// knows which hop's pool is misbehaving.
|
|
977
|
+
if (estimated > BigInt(0) && value < estimated) {
|
|
978
|
+
const shortfall = estimated - value;
|
|
979
|
+
(0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_03, estimated, value, false, ueaAddress, 'CASCADE', {
|
|
980
|
+
kind: 'NATIVE',
|
|
981
|
+
segmentIndex: cascadeIdx,
|
|
982
|
+
enforceGasCheck,
|
|
983
|
+
shortfall,
|
|
984
|
+
});
|
|
985
|
+
if (!enforceGasCheck) {
|
|
986
|
+
(0, context_1.printLog)(ctx, `composeCascade — SVM outbound segment ${cascadeIdx} has native shortfall ${shortfall.toString()}; proceeding because enforceGasCheck=false`);
|
|
987
|
+
svmNativeValueBySegment.set(seg, value);
|
|
988
|
+
continue;
|
|
989
|
+
}
|
|
990
|
+
(0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_04, estimated, value, shortfall, ueaAddress, 'CASCADE', { kind: 'NATIVE', segmentIndex: cascadeIdx });
|
|
991
|
+
throw new errors_1.InsufficientUEABalanceError({
|
|
992
|
+
required: estimated,
|
|
993
|
+
available: value,
|
|
994
|
+
shortfall,
|
|
995
|
+
ueaAddress,
|
|
996
|
+
pathTag: 'CASCADE',
|
|
997
|
+
reason: 'NATIVE',
|
|
998
|
+
segmentIndex: cascadeIdx,
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
svmNativeValueBySegment.set(seg, value);
|
|
825
1002
|
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
1003
|
+
}
|
|
1004
|
+
// Every EVM gas-bearing segment should follow the direct-route path: use the
|
|
1005
|
+
// live WPC/gasToken quote instead of blindly spending the flat balance split.
|
|
1006
|
+
// This includes Route 3 wrappers, which also call sendUniversalTxOutbound on
|
|
1007
|
+
// Push Chain before the source CEA can return. A fixed ceiling below the live
|
|
1008
|
+
// exact-output requirement deterministically reverts inside Uniswap with
|
|
1009
|
+
// `STF`; the caller-facing maxPCForGas field is the explicit spend ceiling.
|
|
1010
|
+
const evmSegments = segments.filter((s) => segmentNeedsOutboundGas(s) &&
|
|
1011
|
+
((s.type === 'OUTBOUND_TO_CEA' && s.hops[0]?.isSvmTarget !== true) ||
|
|
1012
|
+
(s.type === 'INBOUND_FROM_CEA' &&
|
|
1013
|
+
!(0, payload_builders_1.isSvmChain)(s.sourceChain))) &&
|
|
1014
|
+
s.gasToken &&
|
|
1015
|
+
s.gasFee &&
|
|
1016
|
+
s.gasFee > BigInt(0));
|
|
1017
|
+
const evmNativeValueBySegment = new Map();
|
|
1018
|
+
if (evmSegments.length > 0 && effectiveUeaBalance > CASCADE_GAS_RESERVE) {
|
|
1019
|
+
const universalCoreAddress = await getUniversalCoreAddressForSwap();
|
|
1020
|
+
const UNCAPPED_BALANCE = BigInt('1000000000000000000000000000000');
|
|
1021
|
+
for (const seg of evmSegments) {
|
|
1022
|
+
const value = await (0, gas_calculator_1.estimateNativeValueForSwap)(ctx, universalCoreAddress, seg.gasToken, seg.gasFee, UNCAPPED_BALANCE);
|
|
1023
|
+
(0, context_1.printLog)(ctx, `composeCascade — EVM outbound to ${seg.targetChain ?? seg.sourceChain}: gasFee=${seg.gasFee}, quotedNativeValue=${value}, flat=${perOutboundNativeValue ?? BigInt(0)}`);
|
|
1024
|
+
evmNativeValueBySegment.set(seg, value);
|
|
830
1025
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
1026
|
+
}
|
|
1027
|
+
for (let segIdxLoop = segments.length - 1; segIdxLoop >= 0; segIdxLoop--) {
|
|
1028
|
+
const segment = segments[segIdxLoop];
|
|
1029
|
+
const gasPriceForOutboundRequest = (chainForLog) => {
|
|
1030
|
+
const quotedGasPrice = segment.gasPrice ?? BigInt(0);
|
|
1031
|
+
const isInboundFromCea = segment.type === 'INBOUND_FROM_CEA';
|
|
1032
|
+
// If this outbound is being accumulated into an earlier Route 3
|
|
1033
|
+
// inbound payload, it may execute minutes after the quote. Keep
|
|
1034
|
+
// immediate outbounds pinned to the quote, but let delayed nested
|
|
1035
|
+
// outbounds resolve the live base gas price at execution time. Route 3
|
|
1036
|
+
// source-chain outbounds have the same quote-staleness risk because the
|
|
1037
|
+
// source CEA leg can execute after the Push Chain gateway quote changes.
|
|
1038
|
+
const executesAfterInbound = segments
|
|
1039
|
+
.slice(0, segIdxLoop)
|
|
1040
|
+
.some((s) => s.type === 'INBOUND_FROM_CEA');
|
|
1041
|
+
const shouldUseLiveGasPrice = isInboundFromCea || executesAfterInbound;
|
|
1042
|
+
if (shouldUseLiveGasPrice && quotedGasPrice > BigInt(0)) {
|
|
1043
|
+
(0, context_1.printLog)(ctx, `composeCascade — ${isInboundFromCea ? 'Route 3' : 'delayed'} outbound to ${chainForLog ?? 'unknown chain'}: using gasPrice=0 instead of quoted ${quotedGasPrice} so UGPC resolves the live base price at execution time`);
|
|
841
1044
|
}
|
|
842
|
-
return
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
const outerSafety = BigInt(1e18); // 1 PC for outer-tx gas
|
|
871
|
-
const svmTotal = effectiveUeaBalance > evmReservation + outerSafety
|
|
872
|
-
? effectiveUeaBalance - evmReservation - outerSafety
|
|
873
|
-
: BigInt(0);
|
|
874
|
-
const perSvmCap = svmTotal / BigInt(svmSegments.length);
|
|
875
|
-
const UNCAPPED_BALANCE = BigInt('1000000000000000000000000000000'); // effectively disable estimator's internal cap
|
|
876
|
-
for (const seg of svmSegments) {
|
|
877
|
-
// Cascade-level segment index (position in `segments`, not in `svmSegments`)
|
|
878
|
-
// so the error's `segmentIndex` lines up with what the caller sees.
|
|
879
|
-
const cascadeIdx = segments.indexOf(seg);
|
|
880
|
-
const estimated = yield (0, gas_calculator_1.estimateNativeValueForSwap)(ctx, universalCoreAddress, seg.gasToken, seg.gasFee, UNCAPPED_BALANCE);
|
|
881
|
-
// SVM warn-threshold telemetry (no truncation — pre-flight handles drain protection).
|
|
882
|
-
(0, preflight_1.maybeFireSvmWarnThreshold)(ctx, estimated, seg.gasToken, 'CASCADE');
|
|
883
|
-
const flat = perOutboundNativeValue !== null && perOutboundNativeValue !== void 0 ? perOutboundNativeValue : BigInt(0);
|
|
884
|
-
let value = estimated > flat ? estimated : flat;
|
|
885
|
-
if (perSvmCap > BigInt(0) && value > perSvmCap) {
|
|
886
|
-
const cappedValue = perSvmCap >= estimated ? perSvmCap : estimated;
|
|
887
|
-
if (cappedValue !== perSvmCap) {
|
|
888
|
-
(0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_a = seg.targetChain) !== null && _a !== void 0 ? _a : seg.sourceChain}: perSvmCap=${perSvmCap} is below live pool quote=${estimated}; using quote to avoid underfunded gas swap`);
|
|
889
|
-
}
|
|
890
|
-
value = cappedValue;
|
|
1045
|
+
return shouldUseLiveGasPrice ? BigInt(0) : quotedGasPrice;
|
|
1046
|
+
};
|
|
1047
|
+
switch (segment.type) {
|
|
1048
|
+
case 'PUSH_EXECUTION': {
|
|
1049
|
+
// Prepend Push Chain multicalls to accumulated
|
|
1050
|
+
const seedAmount = sumNativeSeedAmount(segment.hops);
|
|
1051
|
+
if (seedAmount > requiredNativeValue) {
|
|
1052
|
+
requiredNativeValue = seedAmount;
|
|
1053
|
+
}
|
|
1054
|
+
requiredNativeValue += sumMulticallNativeValue(segment.mergedPushMulticalls);
|
|
1055
|
+
accumulatedPushMulticalls = [
|
|
1056
|
+
...(segment.mergedPushMulticalls || []),
|
|
1057
|
+
...accumulatedPushMulticalls,
|
|
1058
|
+
];
|
|
1059
|
+
break;
|
|
1060
|
+
}
|
|
1061
|
+
case 'OUTBOUND_TO_CEA': {
|
|
1062
|
+
const firstHop = segment.hops[0];
|
|
1063
|
+
const isSvmSegment = firstHop?.isSvmTarget === true;
|
|
1064
|
+
let outboundPayload;
|
|
1065
|
+
let targetForOutbound;
|
|
1066
|
+
const isMigration = firstHop?.isMigration === true;
|
|
1067
|
+
if (isSvmSegment) {
|
|
1068
|
+
// SVM: use the pre-built SVM payload from the hop descriptor
|
|
1069
|
+
outboundPayload = firstHop.svmPayload ?? '0x';
|
|
1070
|
+
// For SVM, to.address IS the target (program for execute, recipient for withdraw)
|
|
1071
|
+
const svmTarget = firstHop.params.to;
|
|
1072
|
+
targetForOutbound = svmTarget.address;
|
|
891
1073
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
// - fair pool (estimated ≈ 100 PC): cap = 500 PC → caps voluntary excess
|
|
897
|
-
// - skewed pool (estimated > 200 PC): cap = 5×estimated → still allows
|
|
898
|
-
// pool-skew margin; min-swap check below still fires if value < estimated
|
|
899
|
-
// The contract still refunds excess via swapAndBurnGas, so this is purely
|
|
900
|
-
// a guard against submitting wasteful msg.value to fair-priced pools.
|
|
901
|
-
const UPWARD_BASE_CEILING_CASCADE_SVM = BigInt(200) * BigInt(1e18);
|
|
902
|
-
const upwardCeilingCascadeSvm = estimated * BigInt(5) > UPWARD_BASE_CEILING_CASCADE_SVM
|
|
903
|
-
? estimated * BigInt(5)
|
|
904
|
-
: UPWARD_BASE_CEILING_CASCADE_SVM;
|
|
905
|
-
if (value > upwardCeilingCascadeSvm) {
|
|
906
|
-
(0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_b = seg.targetChain) !== null && _b !== void 0 ? _b : seg.sourceChain}: capping value from ${value} to upwardCeiling=${upwardCeilingCascadeSvm} (pool quote=${estimated})`);
|
|
907
|
-
value = upwardCeilingCascadeSvm;
|
|
1074
|
+
else if (isMigration) {
|
|
1075
|
+
// Migration: use raw 4-byte MIGRATION_SELECTOR, no multicall wrapping
|
|
1076
|
+
outboundPayload = (0, payload_builders_1.buildMigrationPayload)();
|
|
1077
|
+
targetForOutbound = firstHop?.ceaAddress || ueaAddress;
|
|
908
1078
|
}
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
(0,
|
|
918
|
-
|
|
919
|
-
|
|
1079
|
+
else {
|
|
1080
|
+
// EVM: build CEA payload from merged multicalls
|
|
1081
|
+
outboundPayload = (0, payload_builders_1.buildCeaMulticallPayload)(segment.mergedCeaMulticalls || []);
|
|
1082
|
+
// Get CEA address from the first hop
|
|
1083
|
+
targetForOutbound =
|
|
1084
|
+
outboundPayload === '0x'
|
|
1085
|
+
? selectors_1.ZERO_ADDRESS
|
|
1086
|
+
: firstHop?.ceaAddress || ueaAddress;
|
|
1087
|
+
(0, payload_builders_1.assertCeaFundsParkingInvariant)(targetForOutbound, outboundPayload);
|
|
1088
|
+
}
|
|
1089
|
+
// Per-segment PRC-20 burn-balance pre-check. Fires whenever
|
|
1090
|
+
// burnAmount > 0 even when burn token equals gas token — the gateway's
|
|
1091
|
+
// transferFrom happens BEFORE the gas swap, so user must pre-hold the
|
|
1092
|
+
// burn amount.
|
|
1093
|
+
const segBurnAmount = segment.totalBurnAmount || BigInt(0);
|
|
1094
|
+
const segBurnToken = segment.prc20Token;
|
|
1095
|
+
if (segBurnAmount > BigInt(0) && segBurnToken && !isMigration) {
|
|
1096
|
+
const onHand = await ctx.pushClient.readContract({
|
|
1097
|
+
address: segBurnToken,
|
|
1098
|
+
abi: erc20_evm_1.ERC20_EVM,
|
|
1099
|
+
functionName: 'balanceOf',
|
|
1100
|
+
args: [ueaAddress],
|
|
1101
|
+
});
|
|
1102
|
+
const sufficient = onHand >= segBurnAmount;
|
|
1103
|
+
(0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_03, segBurnAmount, onHand, sufficient, ueaAddress, 'CASCADE', {
|
|
1104
|
+
kind: 'PRC20',
|
|
1105
|
+
burnToken: segBurnToken,
|
|
1106
|
+
segmentIndex: segIdxLoop,
|
|
920
1107
|
enforceGasCheck,
|
|
921
|
-
shortfall,
|
|
1108
|
+
shortfall: sufficient ? BigInt(0) : segBurnAmount - onHand,
|
|
922
1109
|
});
|
|
923
|
-
|
|
924
|
-
(0,
|
|
925
|
-
|
|
926
|
-
|
|
1110
|
+
const priorPushExecutionMayFundBurn = segments
|
|
1111
|
+
.slice(0, segIdxLoop)
|
|
1112
|
+
.some((s) => s.type === 'PUSH_EXECUTION');
|
|
1113
|
+
const priorInboundFundingForBurn = getInboundFundingForBurnToken(segments, segIdxLoop, segBurnToken, ctx.pushNetwork);
|
|
1114
|
+
const priorInboundMayFundBurn = onHand + priorInboundFundingForBurn >= segBurnAmount;
|
|
1115
|
+
if (!sufficient &&
|
|
1116
|
+
!priorPushExecutionMayFundBurn &&
|
|
1117
|
+
!priorInboundMayFundBurn) {
|
|
1118
|
+
const projectedOnHand = onHand + priorInboundFundingForBurn;
|
|
1119
|
+
const shortfall = segBurnAmount - projectedOnHand;
|
|
1120
|
+
if (!enforceGasCheck) {
|
|
1121
|
+
(0, context_1.printLog)(ctx, `composeCascade — PRC-20 shortfall ${shortfall.toString()} for segment ${segIdxLoop}; proceeding because enforceGasCheck=false`);
|
|
1122
|
+
}
|
|
1123
|
+
else {
|
|
1124
|
+
(0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_04, segBurnAmount, projectedOnHand, shortfall, ueaAddress, 'CASCADE', {
|
|
1125
|
+
kind: 'PRC20',
|
|
1126
|
+
burnToken: segBurnToken,
|
|
1127
|
+
segmentIndex: segIdxLoop,
|
|
1128
|
+
});
|
|
1129
|
+
throw new errors_1.InsufficientUEABalanceError({
|
|
1130
|
+
required: segBurnAmount,
|
|
1131
|
+
available: projectedOnHand,
|
|
1132
|
+
shortfall,
|
|
1133
|
+
ueaAddress,
|
|
1134
|
+
pathTag: 'CASCADE',
|
|
1135
|
+
reason: 'PRC20',
|
|
1136
|
+
burnToken: segBurnToken,
|
|
1137
|
+
segmentIndex: segIdxLoop,
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
else if (!sufficient && priorPushExecutionMayFundBurn) {
|
|
1142
|
+
(0, context_1.printLog)(ctx, `composeCascade — skipping PRC-20 preflight failure for segment ${segIdxLoop}; earlier Push execution may fund ${segBurnToken} before this outbound runs`);
|
|
1143
|
+
}
|
|
1144
|
+
else if (!sufficient && priorInboundMayFundBurn) {
|
|
1145
|
+
(0, context_1.printLog)(ctx, `composeCascade — skipping PRC-20 preflight failure for segment ${segIdxLoop}; earlier inbound funds ${priorInboundFundingForBurn.toString()} units of ${segBurnToken} before this outbound runs`);
|
|
927
1146
|
}
|
|
928
|
-
(0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_04, estimated, value, shortfall, ueaAddress, 'CASCADE', { kind: 'NATIVE', segmentIndex: cascadeIdx });
|
|
929
|
-
throw new errors_1.InsufficientUEABalanceError({
|
|
930
|
-
required: estimated,
|
|
931
|
-
available: value,
|
|
932
|
-
shortfall,
|
|
933
|
-
ueaAddress,
|
|
934
|
-
pathTag: 'CASCADE',
|
|
935
|
-
reason: 'NATIVE',
|
|
936
|
-
segmentIndex: cascadeIdx,
|
|
937
|
-
});
|
|
938
1147
|
}
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
});
|
|
954
|
-
const evmNativeValueBySegment = new Map();
|
|
955
|
-
if (evmSegments.length > 0 && effectiveUeaBalance > CASCADE_GAS_RESERVE) {
|
|
956
|
-
const universalCoreAddress = yield getUniversalCoreAddressForSwap();
|
|
957
|
-
const ONE_PC = BigInt('1000000000000000000');
|
|
958
|
-
const EVM_NATIVE_VALUE_SAFETY_CAP_CASCADE = BigInt(200) * ONE_PC;
|
|
959
|
-
const UNCAPPED_BALANCE = BigInt('1000000000000000000000000000000');
|
|
960
|
-
for (const seg of evmSegments) {
|
|
961
|
-
let value = yield (0, gas_calculator_1.estimateNativeValueForSwap)(ctx, universalCoreAddress, seg.gasToken, seg.gasFee, UNCAPPED_BALANCE);
|
|
962
|
-
if (value > EVM_NATIVE_VALUE_SAFETY_CAP_CASCADE) {
|
|
963
|
-
(0, context_1.printLog)(ctx, `composeCascade — EVM outbound to ${seg.targetChain}: capping nativeValueForGas at 200 PC ceiling (was ${value.toString()})`);
|
|
964
|
-
value = EVM_NATIVE_VALUE_SAFETY_CAP_CASCADE;
|
|
1148
|
+
// Build outbound request
|
|
1149
|
+
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetForOutbound, segment.prc20Token || selectors_1.ZERO_ADDRESS, segment.totalBurnAmount || BigInt(0), segment.gasLimit ?? BigInt(0), outboundPayload, ueaAddress, segment.maxPCForGas ?? BigInt(0), gasPriceForOutboundRequest(firstHop?.targetChain));
|
|
1150
|
+
// Build approval + outbound multicalls
|
|
1151
|
+
const segGasFee = segment.gasFee || BigInt(0);
|
|
1152
|
+
const svmOverride = svmNativeValueBySegment.get(segment);
|
|
1153
|
+
// When UEA balance < CASCADE_GAS_RESERVE, perOutboundNativeValue is
|
|
1154
|
+
// undefined. Mirror the drained-UEA branch in executeUoaToCea
|
|
1155
|
+
// (route-handlers.ts): send the entire available balance divided
|
|
1156
|
+
// across outbound segments. The contract refunds excess via
|
|
1157
|
+
// swapAndBurnGas, so overshooting is safe; undershooting causes STF.
|
|
1158
|
+
let evmFallbackValue;
|
|
1159
|
+
if (effectiveUeaBalance > BigInt(0)) {
|
|
1160
|
+
evmFallbackValue =
|
|
1161
|
+
effectiveUeaBalance / BigInt(Math.max(numOutbounds, 1));
|
|
965
1162
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
for (let segIdxLoop = segments.length - 1; segIdxLoop >= 0; segIdxLoop--) {
|
|
971
|
-
const segment = segments[segIdxLoop];
|
|
972
|
-
const gasPriceForOutboundRequest = (chainForLog) => {
|
|
973
|
-
var _a;
|
|
974
|
-
const quotedGasPrice = (_a = segment.gasPrice) !== null && _a !== void 0 ? _a : BigInt(0);
|
|
975
|
-
const isInboundFromCea = segment.type === 'INBOUND_FROM_CEA';
|
|
976
|
-
// If this outbound is being accumulated into an earlier Route 3
|
|
977
|
-
// inbound payload, it may execute minutes after the quote. Keep
|
|
978
|
-
// immediate outbounds pinned to the quote, but let delayed nested
|
|
979
|
-
// outbounds resolve the live base gas price at execution time. Route 3
|
|
980
|
-
// source-chain outbounds have the same quote-staleness risk because the
|
|
981
|
-
// source CEA leg can execute after the Push Chain gateway quote changes.
|
|
982
|
-
const executesAfterInbound = segments
|
|
983
|
-
.slice(0, segIdxLoop)
|
|
984
|
-
.some((s) => s.type === 'INBOUND_FROM_CEA');
|
|
985
|
-
const shouldUseLiveGasPrice = isInboundFromCea || executesAfterInbound;
|
|
986
|
-
if (shouldUseLiveGasPrice && quotedGasPrice > BigInt(0)) {
|
|
987
|
-
(0, context_1.printLog)(ctx, `composeCascade — ${isInboundFromCea ? 'Route 3' : 'delayed'} outbound to ${chainForLog !== null && chainForLog !== void 0 ? chainForLog : 'unknown chain'}: using gasPrice=0 instead of quoted ${quotedGasPrice} so UGPC resolves the live base price at execution time`);
|
|
1163
|
+
else {
|
|
1164
|
+
evmFallbackValue = segGasFee * BigInt(1000000);
|
|
988
1165
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1166
|
+
const nativeValueForGas = svmOverride ??
|
|
1167
|
+
evmNativeValueBySegment.get(segment) ??
|
|
1168
|
+
perOutboundNativeValue ??
|
|
1169
|
+
evmFallbackValue;
|
|
1170
|
+
// SDK 5.2 Case C: when the segment's sizing is category C, compose
|
|
1171
|
+
// R2/OUTBOUND_TO_CEA segments no longer apply Case A/B/C sizing
|
|
1172
|
+
// (scoped to R1 + R3 only). segment.sizing may still be populated
|
|
1173
|
+
// from the hop descriptor merge, but it is not consulted here —
|
|
1174
|
+
// nativeValueForGas is driven by pool-quote on the single-hop path
|
|
1175
|
+
// and by the cascade-level per-outbound allocation above.
|
|
1176
|
+
const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
1177
|
+
prc20Token: segment.prc20Token || selectors_1.ZERO_ADDRESS,
|
|
1178
|
+
gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
|
|
1179
|
+
burnAmount: segment.totalBurnAmount || BigInt(0),
|
|
1180
|
+
gasFee: segGasFee,
|
|
1181
|
+
nativeValueForGas,
|
|
1182
|
+
gatewayPcAddress,
|
|
1183
|
+
outboundRequest: outboundReq,
|
|
1184
|
+
});
|
|
1185
|
+
requiredNativeValue += sumMulticallNativeValue(outboundMulticalls);
|
|
1186
|
+
// Prepend to accumulated
|
|
1187
|
+
accumulatedPushMulticalls = [
|
|
1188
|
+
...outboundMulticalls,
|
|
1189
|
+
...accumulatedPushMulticalls,
|
|
1190
|
+
];
|
|
1191
|
+
break;
|
|
1192
|
+
}
|
|
1193
|
+
case 'INBOUND_FROM_CEA': {
|
|
1194
|
+
// The accumulated multicalls = what runs on Push Chain AFTER inbound arrives.
|
|
1195
|
+
// Wrap in UniversalPayload struct with correct UEA nonce for the relay.
|
|
1196
|
+
if (isNativeSeedOnlySegment(segment)) {
|
|
994
1197
|
const seedAmount = sumNativeSeedAmount(segment.hops);
|
|
995
1198
|
if (seedAmount > requiredNativeValue) {
|
|
996
1199
|
requiredNativeValue = seedAmount;
|
|
997
1200
|
}
|
|
998
|
-
|
|
999
|
-
accumulatedPushMulticalls = [
|
|
1000
|
-
...(segment.mergedPushMulticalls || []),
|
|
1001
|
-
...accumulatedPushMulticalls,
|
|
1002
|
-
];
|
|
1201
|
+
(0, context_1.printLog)(ctx, `composeCascade — value-only CEA_TO_PUSH seed ${seedAmount.toString()} folded into root Push execution; no source-chain outbound needed`);
|
|
1003
1202
|
break;
|
|
1004
1203
|
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
const
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
outboundPayload = (0, payload_builders_1.buildCeaMulticallPayload)(segment.mergedCeaMulticalls || []);
|
|
1026
|
-
// Get CEA address from the first hop
|
|
1027
|
-
targetForOutbound =
|
|
1028
|
-
outboundPayload === '0x'
|
|
1029
|
-
? selectors_1.ZERO_ADDRESS
|
|
1030
|
-
: (firstHop === null || firstHop === void 0 ? void 0 : firstHop.ceaAddress) || ueaAddress;
|
|
1031
|
-
(0, payload_builders_1.assertCeaFundsParkingInvariant)(targetForOutbound, outboundPayload);
|
|
1032
|
-
}
|
|
1033
|
-
// Per-segment PRC-20 burn-balance pre-check. Fires whenever
|
|
1034
|
-
// burnAmount > 0 even when burn token equals gas token — the gateway's
|
|
1035
|
-
// transferFrom happens BEFORE the gas swap, so user must pre-hold the
|
|
1036
|
-
// burn amount.
|
|
1037
|
-
const segBurnAmount = segment.totalBurnAmount || BigInt(0);
|
|
1038
|
-
const segBurnToken = segment.prc20Token;
|
|
1039
|
-
if (segBurnAmount > BigInt(0) && segBurnToken && !isMigration) {
|
|
1040
|
-
const onHand = yield ctx.pushClient.readContract({
|
|
1041
|
-
address: segBurnToken,
|
|
1042
|
-
abi: erc20_evm_1.ERC20_EVM,
|
|
1043
|
-
functionName: 'balanceOf',
|
|
1044
|
-
args: [ueaAddress],
|
|
1045
|
-
});
|
|
1046
|
-
const sufficient = onHand >= segBurnAmount;
|
|
1047
|
-
(0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_03, segBurnAmount, onHand, sufficient, ueaAddress, 'CASCADE', {
|
|
1048
|
-
kind: 'PRC20',
|
|
1049
|
-
burnToken: segBurnToken,
|
|
1050
|
-
segmentIndex: segIdxLoop,
|
|
1051
|
-
enforceGasCheck,
|
|
1052
|
-
shortfall: sufficient ? BigInt(0) : segBurnAmount - onHand,
|
|
1204
|
+
// Build push multicalls from EVERY merged hop's own data in original
|
|
1205
|
+
// order (e.g. counter.increment() + value transfer). Multi-hop R3
|
|
1206
|
+
// segments (same sourceChain) bundle all target ops into the single
|
|
1207
|
+
// UEA multicall so no nested outbound is needed.
|
|
1208
|
+
const mergedHopMulticalls = [];
|
|
1209
|
+
for (const h of segment.hops) {
|
|
1210
|
+
const pc20Forward = (0, gate_1.isPC20ImportWithFunds)(h.params);
|
|
1211
|
+
if ((0, data_utils_1.hasExecutablePayloadData)(h?.params?.data) ||
|
|
1212
|
+
h?.params?.value ||
|
|
1213
|
+
pc20Forward) {
|
|
1214
|
+
const hopCalls = (0, payload_builders_1.buildExecuteMulticall)({
|
|
1215
|
+
execute: (pc20Forward
|
|
1216
|
+
? (0, gate_1.assertLegacyFunds)((0, helpers_1.toExecuteParams)(h.params))
|
|
1217
|
+
: {
|
|
1218
|
+
to: h.params.to,
|
|
1219
|
+
value: h.params.value,
|
|
1220
|
+
data: h.params.data,
|
|
1221
|
+
}),
|
|
1222
|
+
ueaAddress,
|
|
1223
|
+
allowSelfValueCall: true,
|
|
1053
1224
|
});
|
|
1054
|
-
|
|
1055
|
-
.slice(0, segIdxLoop)
|
|
1056
|
-
.some((s) => s.type === 'PUSH_EXECUTION');
|
|
1057
|
-
const priorInboundFundingForBurn = getInboundFundingForBurnToken(segments, segIdxLoop, segBurnToken, ctx.pushNetwork);
|
|
1058
|
-
const priorInboundMayFundBurn = onHand + priorInboundFundingForBurn >= segBurnAmount;
|
|
1059
|
-
if (!sufficient &&
|
|
1060
|
-
!priorPushExecutionMayFundBurn &&
|
|
1061
|
-
!priorInboundMayFundBurn) {
|
|
1062
|
-
const projectedOnHand = onHand + priorInboundFundingForBurn;
|
|
1063
|
-
const shortfall = segBurnAmount - projectedOnHand;
|
|
1064
|
-
if (!enforceGasCheck) {
|
|
1065
|
-
(0, context_1.printLog)(ctx, `composeCascade — PRC-20 shortfall ${shortfall.toString()} for segment ${segIdxLoop}; proceeding because enforceGasCheck=false`);
|
|
1066
|
-
}
|
|
1067
|
-
else {
|
|
1068
|
-
(0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_04, segBurnAmount, projectedOnHand, shortfall, ueaAddress, 'CASCADE', {
|
|
1069
|
-
kind: 'PRC20',
|
|
1070
|
-
burnToken: segBurnToken,
|
|
1071
|
-
segmentIndex: segIdxLoop,
|
|
1072
|
-
});
|
|
1073
|
-
throw new errors_1.InsufficientUEABalanceError({
|
|
1074
|
-
required: segBurnAmount,
|
|
1075
|
-
available: projectedOnHand,
|
|
1076
|
-
shortfall,
|
|
1077
|
-
ueaAddress,
|
|
1078
|
-
pathTag: 'CASCADE',
|
|
1079
|
-
reason: 'PRC20',
|
|
1080
|
-
burnToken: segBurnToken,
|
|
1081
|
-
segmentIndex: segIdxLoop,
|
|
1082
|
-
});
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
else if (!sufficient && priorPushExecutionMayFundBurn) {
|
|
1086
|
-
(0, context_1.printLog)(ctx, `composeCascade — skipping PRC-20 preflight failure for segment ${segIdxLoop}; earlier Push execution may fund ${segBurnToken} before this outbound runs`);
|
|
1087
|
-
}
|
|
1088
|
-
else if (!sufficient && priorInboundMayFundBurn) {
|
|
1089
|
-
(0, context_1.printLog)(ctx, `composeCascade — skipping PRC-20 preflight failure for segment ${segIdxLoop}; earlier inbound funds ${priorInboundFundingForBurn.toString()} units of ${segBurnToken} before this outbound runs`);
|
|
1090
|
-
}
|
|
1225
|
+
mergedHopMulticalls.push(...hopCalls);
|
|
1091
1226
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
const segGasFee = segment.gasFee || BigInt(0);
|
|
1096
|
-
const svmOverride = svmNativeValueBySegment.get(segment);
|
|
1097
|
-
// When UEA balance < CASCADE_GAS_RESERVE, perOutboundNativeValue is
|
|
1098
|
-
// undefined. Mirror the drained-UEA branch in executeUoaToCea
|
|
1099
|
-
// (route-handlers.ts): send the entire available balance divided
|
|
1100
|
-
// across outbound segments. The contract refunds excess via
|
|
1101
|
-
// swapAndBurnGas, so overshooting is safe; undershooting causes STF.
|
|
1102
|
-
let evmFallbackValue;
|
|
1103
|
-
if (effectiveUeaBalance > BigInt(0)) {
|
|
1104
|
-
evmFallbackValue =
|
|
1105
|
-
effectiveUeaBalance / BigInt(Math.max(numOutbounds, 1));
|
|
1106
|
-
}
|
|
1107
|
-
else {
|
|
1108
|
-
evmFallbackValue = segGasFee * BigInt(1000000);
|
|
1109
|
-
}
|
|
1110
|
-
const nativeValueForGas = (_h = (_g = svmOverride !== null && svmOverride !== void 0 ? svmOverride : evmNativeValueBySegment.get(segment)) !== null && _g !== void 0 ? _g : perOutboundNativeValue) !== null && _h !== void 0 ? _h : evmFallbackValue;
|
|
1111
|
-
// SDK 5.2 Case C: when the segment's sizing is category C, compose
|
|
1112
|
-
// R2/OUTBOUND_TO_CEA segments no longer apply Case A/B/C sizing
|
|
1113
|
-
// (scoped to R1 + R3 only). segment.sizing may still be populated
|
|
1114
|
-
// from the hop descriptor merge, but it is not consulted here —
|
|
1115
|
-
// nativeValueForGas is driven by pool-quote on the single-hop path
|
|
1116
|
-
// and by the cascade-level per-outbound allocation above.
|
|
1117
|
-
const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
1118
|
-
prc20Token: segment.prc20Token || selectors_1.ZERO_ADDRESS,
|
|
1119
|
-
gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
|
|
1120
|
-
burnAmount: segment.totalBurnAmount || BigInt(0),
|
|
1121
|
-
gasFee: segGasFee,
|
|
1122
|
-
nativeValueForGas,
|
|
1123
|
-
gatewayPcAddress,
|
|
1124
|
-
outboundRequest: outboundReq,
|
|
1125
|
-
});
|
|
1126
|
-
requiredNativeValue += sumMulticallNativeValue(outboundMulticalls);
|
|
1127
|
-
// Prepend to accumulated
|
|
1227
|
+
}
|
|
1228
|
+
if (mergedHopMulticalls.length > 0) {
|
|
1229
|
+
requiredNativeValue += sumMulticallNativeValue(mergedHopMulticalls);
|
|
1128
1230
|
accumulatedPushMulticalls = [
|
|
1129
|
-
...
|
|
1231
|
+
...mergedHopMulticalls,
|
|
1130
1232
|
...accumulatedPushMulticalls,
|
|
1131
1233
|
];
|
|
1132
|
-
break;
|
|
1133
1234
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1235
|
+
// Primary hop carries the sourceChain / CEA address (shared across merged hops).
|
|
1236
|
+
const hop = segment.hops[0];
|
|
1237
|
+
const sourceChain = hop.sourceChain;
|
|
1238
|
+
const ceaAddress = hop.ceaAddress || ueaAddress;
|
|
1239
|
+
let intermediatePayload = '0x';
|
|
1240
|
+
if (accumulatedPushMulticalls.length > 0) {
|
|
1241
|
+
const multicallPayload = (0, payload_builder_1.buildMulticallPayloadData)(ctx, ueaAddress, accumulatedPushMulticalls);
|
|
1242
|
+
// +1: the outbound tx consumes one nonce via execute()
|
|
1243
|
+
const nonce = (ueaNonce ?? BigInt(0)) + BigInt(1);
|
|
1244
|
+
intermediatePayload = (0, payload_builders_1.isSvmChain)(sourceChain)
|
|
1245
|
+
? (0, payload_builders_1.buildInboundUniversalPayloadSvm)(multicallPayload, { nonce })
|
|
1246
|
+
: (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, { nonce });
|
|
1247
|
+
}
|
|
1248
|
+
for (const segmentHop of segment.hops) {
|
|
1249
|
+
(0, gate_1.assertPC20ImportHasPayload)(segmentHop.params, intermediatePayload !== '0x', `Cascaded Route 3 ${sourceChain}`);
|
|
1250
|
+
}
|
|
1251
|
+
// SVM chains: build SVM CPI payload instead of EVM CEA multicall
|
|
1252
|
+
if ((0, payload_builders_1.isSvmChain)(sourceChain)) {
|
|
1253
|
+
const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
|
|
1254
|
+
if (!lockerContract) {
|
|
1255
|
+
throw new Error(`No SVM gateway program configured for chain ${sourceChain}`);
|
|
1144
1256
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
ueaAddress,
|
|
1159
|
-
allowSelfValueCall: true,
|
|
1160
|
-
});
|
|
1161
|
-
mergedHopMulticalls.push(...hopCalls);
|
|
1257
|
+
const programPk = new web3_js_1.PublicKey(lockerContract);
|
|
1258
|
+
const gatewayProgramHex = ('0x' +
|
|
1259
|
+
Buffer.from(programPk.toBytes()).toString('hex'));
|
|
1260
|
+
let drainAmount = BigInt(0);
|
|
1261
|
+
let tokenMintHex;
|
|
1262
|
+
const params = hop.params;
|
|
1263
|
+
if (params.funds?.amount && params.funds.amount > BigInt(0)) {
|
|
1264
|
+
drainAmount = params.funds.amount;
|
|
1265
|
+
const token = params.funds.token;
|
|
1266
|
+
if (token && token.address) {
|
|
1267
|
+
const mintPk = new web3_js_1.PublicKey(token.address);
|
|
1268
|
+
tokenMintHex = ('0x' +
|
|
1269
|
+
Buffer.from(mintPk.toBytes()).toString('hex'));
|
|
1162
1270
|
}
|
|
1163
1271
|
}
|
|
1164
|
-
if (
|
|
1165
|
-
|
|
1166
|
-
accumulatedPushMulticalls = [
|
|
1167
|
-
...mergedHopMulticalls,
|
|
1168
|
-
...accumulatedPushMulticalls,
|
|
1169
|
-
];
|
|
1272
|
+
else if (params.value && params.value > BigInt(0)) {
|
|
1273
|
+
drainAmount = params.value;
|
|
1170
1274
|
}
|
|
1171
|
-
//
|
|
1172
|
-
const
|
|
1173
|
-
const
|
|
1174
|
-
const
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1275
|
+
// Derive CEA PDA as revert recipient
|
|
1276
|
+
const ueaBytes = Buffer.from(ueaAddress.slice(2), 'hex');
|
|
1277
|
+
const [ceaPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('push_identity'), ueaBytes], programPk);
|
|
1278
|
+
const ceaPdaHex = ('0x' +
|
|
1279
|
+
Buffer.from(ceaPda.toBytes()).toString('hex'));
|
|
1280
|
+
// Build SVM payload with intermediate Push Chain payload embedded.
|
|
1281
|
+
// PC20 burns use the gateway's dedicated send_universal_tx shape;
|
|
1282
|
+
// SPL/native drains keep send_universal_tx_to_uea.
|
|
1283
|
+
const extraPayload = intermediatePayload !== '0x'
|
|
1284
|
+
? new Uint8Array(Buffer.from(intermediatePayload.slice(2), 'hex'))
|
|
1285
|
+
: undefined;
|
|
1286
|
+
const pc20 = params._pc20;
|
|
1287
|
+
let svmPayload;
|
|
1288
|
+
if ((0, gate_1.isPC20ImportWithFunds)(params) && pc20) {
|
|
1289
|
+
const mintPk = new web3_js_1.PublicKey(pc20.originAddress);
|
|
1290
|
+
const fields = (0, svm_1.buildPC20SvmCeaBurnPayload)({
|
|
1291
|
+
gatewayProgram: programPk,
|
|
1292
|
+
mint: mintPk,
|
|
1293
|
+
ceaAuthority: ceaPda,
|
|
1294
|
+
ceaAta: (0, svm_rent_1.deriveAtaPubkey)(ceaPda, mintPk),
|
|
1295
|
+
ueaAddress,
|
|
1296
|
+
amount: drainAmount,
|
|
1297
|
+
pushPayload: extraPayload ?? new Uint8Array(0),
|
|
1298
|
+
});
|
|
1299
|
+
svmPayload = (0, payload_builders_1.encodeSvmExecutePayload)(fields);
|
|
1183
1300
|
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
|
|
1187
|
-
if (!lockerContract) {
|
|
1188
|
-
throw new Error(`No SVM gateway program configured for chain ${sourceChain}`);
|
|
1189
|
-
}
|
|
1190
|
-
const programPk = new web3_js_1.PublicKey(lockerContract);
|
|
1191
|
-
const gatewayProgramHex = ('0x' +
|
|
1192
|
-
Buffer.from(programPk.toBytes()).toString('hex'));
|
|
1193
|
-
let drainAmount = BigInt(0);
|
|
1194
|
-
let tokenMintHex;
|
|
1195
|
-
const params = hop.params;
|
|
1196
|
-
if (((_l = params.funds) === null || _l === void 0 ? void 0 : _l.amount) && params.funds.amount > BigInt(0)) {
|
|
1197
|
-
drainAmount = params.funds.amount;
|
|
1198
|
-
const token = params.funds.token;
|
|
1199
|
-
if (token && token.address) {
|
|
1200
|
-
const mintPk = new web3_js_1.PublicKey(token.address);
|
|
1201
|
-
tokenMintHex = ('0x' +
|
|
1202
|
-
Buffer.from(mintPk.toBytes()).toString('hex'));
|
|
1203
|
-
}
|
|
1204
|
-
}
|
|
1205
|
-
else if (params.value && params.value > BigInt(0)) {
|
|
1206
|
-
drainAmount = params.value;
|
|
1207
|
-
}
|
|
1208
|
-
// Derive CEA PDA as revert recipient
|
|
1209
|
-
const ueaBytes = Buffer.from(ueaAddress.slice(2), 'hex');
|
|
1210
|
-
const [ceaPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('push_identity'), ueaBytes], programPk);
|
|
1211
|
-
const ceaPdaHex = ('0x' +
|
|
1212
|
-
Buffer.from(ceaPda.toBytes()).toString('hex'));
|
|
1213
|
-
// Build SVM payload with intermediate Push Chain payload embedded
|
|
1214
|
-
const svmPayload = (0, payload_builders_1.encodeSvmCeaToUeaPayload)({
|
|
1301
|
+
else {
|
|
1302
|
+
svmPayload = (0, payload_builders_1.encodeSvmCeaToUeaPayload)({
|
|
1215
1303
|
gatewayProgramHex,
|
|
1216
1304
|
drainAmount,
|
|
1217
1305
|
tokenMintHex,
|
|
1218
1306
|
revertRecipientHex: ceaPdaHex,
|
|
1219
|
-
extraPayload
|
|
1220
|
-
? new Uint8Array(Buffer.from(intermediatePayload.slice(2), 'hex'))
|
|
1221
|
-
: undefined,
|
|
1307
|
+
extraPayload,
|
|
1222
1308
|
});
|
|
1223
|
-
(0, payload_builders_1.assertSvmPayloadWithinRelayLimit)(svmPayload, `Route 3 SVM cascade ${sourceChain}`);
|
|
1224
|
-
const svmBurnAmount = segment.totalBurnAmount || BigInt(0);
|
|
1225
|
-
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, segment.prc20Token ||
|
|
1226
|
-
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), svmBurnAmount, (_m = segment.gasLimit) !== null && _m !== void 0 ? _m : BigInt(0), svmPayload, ueaAddress, (_o = segment.maxPCForGas) !== null && _o !== void 0 ? _o : BigInt(0), gasPriceForOutboundRequest(sourceChain));
|
|
1227
|
-
const inboundGasFee = segment.gasFee || BigInt(0);
|
|
1228
|
-
let svmInboundFallback;
|
|
1229
|
-
if (effectiveUeaBalance > BigInt(0)) {
|
|
1230
|
-
svmInboundFallback =
|
|
1231
|
-
effectiveUeaBalance / BigInt(Math.max(numOutbounds, 1));
|
|
1232
|
-
}
|
|
1233
|
-
else {
|
|
1234
|
-
svmInboundFallback = inboundGasFee * BigInt(1000000);
|
|
1235
|
-
}
|
|
1236
|
-
let svmInboundNativeValue = (_q = (_p = svmNativeValueBySegment.get(segment)) !== null && _p !== void 0 ? _p : perOutboundNativeValue) !== null && _q !== void 0 ? _q : svmInboundFallback;
|
|
1237
|
-
// SDK 5.2 R3-style Case C bump: when the inbound segment's sizing
|
|
1238
|
-
// is C, top up the swap budget by the overflow. No bridge-swap —
|
|
1239
|
-
// R3 has no destination funds delivery.
|
|
1240
|
-
if (((_r = segment.sizing) === null || _r === void 0 ? void 0 : _r.category) === 'C' &&
|
|
1241
|
-
segment.sizing.overflowNativePc > BigInt(0)) {
|
|
1242
|
-
const bumped = svmInboundNativeValue + segment.sizing.overflowNativePc;
|
|
1243
|
-
(0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (SVM) Case C: bumping nativeValueForGas from ${svmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
|
|
1244
|
-
svmInboundNativeValue = bumped;
|
|
1245
|
-
}
|
|
1246
|
-
const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
1247
|
-
prc20Token: segment.prc20Token ||
|
|
1248
|
-
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork),
|
|
1249
|
-
gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
|
|
1250
|
-
burnAmount: svmBurnAmount,
|
|
1251
|
-
gasFee: inboundGasFee,
|
|
1252
|
-
nativeValueForGas: svmInboundNativeValue,
|
|
1253
|
-
gatewayPcAddress,
|
|
1254
|
-
outboundRequest: outboundReq,
|
|
1255
|
-
});
|
|
1256
|
-
requiredNativeValue += sumMulticallNativeValue(outboundMulticalls);
|
|
1257
|
-
accumulatedPushMulticalls = [...outboundMulticalls];
|
|
1258
|
-
break;
|
|
1259
|
-
}
|
|
1260
|
-
// EVM path: Build CEA multicall: sendUniversalTxToUEA(payload).
|
|
1261
|
-
// CEA handles ERC20 gateway approval internally.
|
|
1262
|
-
const ceaMulticalls = [];
|
|
1263
|
-
// Add primary hop's own CEA operations if any
|
|
1264
|
-
// (e.g., approve + swap before bridging back)
|
|
1265
|
-
if (hop.ceaMulticalls && hop.ceaMulticalls.length > 0) {
|
|
1266
|
-
ceaMulticalls.push(...hop.ceaMulticalls);
|
|
1267
|
-
}
|
|
1268
|
-
// Aggregate token/amount across all merged hops so the single
|
|
1269
|
-
// sendUniversalTxToUEA call carries the combined forward amount.
|
|
1270
|
-
// For multi-hop R3 segments (same sourceChain), ERC20 funds across
|
|
1271
|
-
// hops must resolve to the same token — otherwise this path can't
|
|
1272
|
-
// express it in one call and the caller should split manually.
|
|
1273
|
-
let tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
1274
|
-
let amount = BigInt(0);
|
|
1275
|
-
for (const mergedHop of segment.hops) {
|
|
1276
|
-
const hParams = mergedHop.params;
|
|
1277
|
-
if ((_s = hParams.funds) === null || _s === void 0 ? void 0 : _s.amount) {
|
|
1278
|
-
const token = hParams.funds.token;
|
|
1279
|
-
if (token) {
|
|
1280
|
-
if (token.mechanism === 'native') {
|
|
1281
|
-
amount += hParams.funds.amount;
|
|
1282
|
-
}
|
|
1283
|
-
else {
|
|
1284
|
-
const addr = token.address;
|
|
1285
|
-
if (tokenAddress !== selectors_1.ZERO_ADDRESS &&
|
|
1286
|
-
tokenAddress.toLowerCase() !== addr.toLowerCase()) {
|
|
1287
|
-
throw new Error(`composeCascade: merged R3 hops target different ERC20 tokens (${tokenAddress} vs ${addr}); split into separate executeTransactions batches`);
|
|
1288
|
-
}
|
|
1289
|
-
tokenAddress = addr;
|
|
1290
|
-
amount += hParams.funds.amount;
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
else if (hParams.value && hParams.value > BigInt(0)) {
|
|
1295
|
-
amount += hParams.value;
|
|
1296
|
-
}
|
|
1297
1309
|
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
const
|
|
1301
|
-
|
|
1302
|
-
// Wrap CEA multicall in outbound from Push Chain
|
|
1303
|
-
const ceaPayload = (0, payload_builders_1.buildCeaMulticallPayload)(ceaMulticalls);
|
|
1304
|
-
// The nested outbound back to the source chain needs a non-zero
|
|
1305
|
-
// gasLimit — UGPC.sendUniversalTxOutbound reverts with
|
|
1306
|
-
// "Missing or invalid parameters" on 0. When the hop didn't supply
|
|
1307
|
-
// one (typical for a pure-R3 leg composed inside a cascade), fall
|
|
1308
|
-
// back to the EVM Route 3 default.
|
|
1309
|
-
const effectiveGasLimit = segment.gasLimit && segment.gasLimit > BigInt(0)
|
|
1310
|
-
? segment.gasLimit
|
|
1311
|
-
: selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT;
|
|
1312
|
-
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, segment.prc20Token ||
|
|
1313
|
-
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), segment.totalBurnAmount || BigInt(0), effectiveGasLimit, ceaPayload, ueaAddress, (_t = segment.maxPCForGas) !== null && _t !== void 0 ? _t : BigInt(0), gasPriceForOutboundRequest(sourceChain));
|
|
1310
|
+
(0, payload_builders_1.assertSvmPayloadWithinRelayLimit)(svmPayload, `Route 3 SVM cascade ${sourceChain}`);
|
|
1311
|
+
const svmBurnAmount = segment.totalBurnAmount || BigInt(0);
|
|
1312
|
+
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, segment.prc20Token ||
|
|
1313
|
+
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), svmBurnAmount, segment.gasLimit ?? BigInt(0), svmPayload, ueaAddress, segment.maxPCForGas ?? BigInt(0), gasPriceForOutboundRequest(sourceChain));
|
|
1314
1314
|
const inboundGasFee = segment.gasFee || BigInt(0);
|
|
1315
|
-
let
|
|
1315
|
+
let svmInboundFallback;
|
|
1316
1316
|
if (effectiveUeaBalance > BigInt(0)) {
|
|
1317
|
-
|
|
1317
|
+
svmInboundFallback =
|
|
1318
1318
|
effectiveUeaBalance / BigInt(Math.max(numOutbounds, 1));
|
|
1319
1319
|
}
|
|
1320
1320
|
else {
|
|
1321
|
-
|
|
1321
|
+
svmInboundFallback = inboundGasFee * BigInt(1000000);
|
|
1322
1322
|
}
|
|
1323
|
-
let
|
|
1323
|
+
let svmInboundNativeValue = svmNativeValueBySegment.get(segment) ??
|
|
1324
|
+
perOutboundNativeValue ??
|
|
1325
|
+
svmInboundFallback;
|
|
1324
1326
|
// SDK 5.2 R3-style Case C bump: when the inbound segment's sizing
|
|
1325
1327
|
// is C, top up the swap budget by the overflow. No bridge-swap —
|
|
1326
1328
|
// R3 has no destination funds delivery.
|
|
1327
|
-
if (
|
|
1329
|
+
if (segment.sizing?.category === 'C' &&
|
|
1328
1330
|
segment.sizing.overflowNativePc > BigInt(0)) {
|
|
1329
|
-
const bumped =
|
|
1330
|
-
(0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (
|
|
1331
|
-
|
|
1331
|
+
const bumped = svmInboundNativeValue + segment.sizing.overflowNativePc;
|
|
1332
|
+
(0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (SVM) Case C: bumping nativeValueForGas from ${svmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
|
|
1333
|
+
svmInboundNativeValue = bumped;
|
|
1332
1334
|
}
|
|
1333
1335
|
const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
1334
1336
|
prc20Token: segment.prc20Token ||
|
|
1335
1337
|
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork),
|
|
1336
1338
|
gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
|
|
1337
|
-
burnAmount:
|
|
1339
|
+
burnAmount: svmBurnAmount,
|
|
1338
1340
|
gasFee: inboundGasFee,
|
|
1339
|
-
nativeValueForGas:
|
|
1341
|
+
nativeValueForGas: svmInboundNativeValue,
|
|
1340
1342
|
gatewayPcAddress,
|
|
1341
1343
|
outboundRequest: outboundReq,
|
|
1342
1344
|
});
|
|
1343
1345
|
requiredNativeValue += sumMulticallNativeValue(outboundMulticalls);
|
|
1344
|
-
// Reset accumulated -- everything is now inside this outbound
|
|
1345
1346
|
accumulatedPushMulticalls = [...outboundMulticalls];
|
|
1346
1347
|
break;
|
|
1347
1348
|
}
|
|
1349
|
+
// EVM path: Build CEA multicall: sendUniversalTxToUEA(payload).
|
|
1350
|
+
// CEA handles ERC20 gateway approval internally.
|
|
1351
|
+
const ceaMulticalls = [];
|
|
1352
|
+
// Add primary hop's own CEA operations if any
|
|
1353
|
+
// (e.g., approve + swap before bridging back)
|
|
1354
|
+
if (hop.ceaMulticalls && hop.ceaMulticalls.length > 0) {
|
|
1355
|
+
ceaMulticalls.push(...hop.ceaMulticalls);
|
|
1356
|
+
}
|
|
1357
|
+
// Aggregate token/amount across all merged hops so the single
|
|
1358
|
+
// sendUniversalTxToUEA call carries the combined forward amount.
|
|
1359
|
+
// For multi-hop R3 segments (same sourceChain), ERC20 funds across
|
|
1360
|
+
// hops must resolve to the same token — otherwise this path can't
|
|
1361
|
+
// express it in one call and the caller should split manually.
|
|
1362
|
+
let tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
1363
|
+
let amount = BigInt(0);
|
|
1364
|
+
for (const mergedHop of segment.hops) {
|
|
1365
|
+
const hParams = mergedHop.params;
|
|
1366
|
+
if (hParams.funds?.amount) {
|
|
1367
|
+
const token = hParams.funds.token;
|
|
1368
|
+
if (token) {
|
|
1369
|
+
if (token.mechanism === 'native') {
|
|
1370
|
+
amount += hParams.funds.amount;
|
|
1371
|
+
}
|
|
1372
|
+
else {
|
|
1373
|
+
const addr = token.address;
|
|
1374
|
+
if (tokenAddress !== selectors_1.ZERO_ADDRESS &&
|
|
1375
|
+
tokenAddress.toLowerCase() !== addr.toLowerCase()) {
|
|
1376
|
+
throw new Error(`composeCascade: merged R3 hops target different ERC20 tokens (${tokenAddress} vs ${addr}); split into separate executeTransactions batches`);
|
|
1377
|
+
}
|
|
1378
|
+
tokenAddress = addr;
|
|
1379
|
+
amount += hParams.funds.amount;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
else if (hParams.value && hParams.value > BigInt(0)) {
|
|
1384
|
+
amount += hParams.value;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
// Build sendUniversalTxToUEA self-call on CEA. CEA self-calls must
|
|
1388
|
+
// always use value=0; native funds are spent from the CEA balance.
|
|
1389
|
+
const sendCall = (0, payload_builders_1.buildSendUniversalTxToUEA)(ceaAddress, tokenAddress, amount, intermediatePayload, ceaAddress);
|
|
1390
|
+
ceaMulticalls.push(sendCall);
|
|
1391
|
+
// Wrap CEA multicall in outbound from Push Chain
|
|
1392
|
+
const ceaPayload = (0, payload_builders_1.buildCeaMulticallPayload)(ceaMulticalls);
|
|
1393
|
+
// The nested outbound back to the source chain needs a non-zero
|
|
1394
|
+
// gasLimit — UGPC.sendUniversalTxOutbound reverts with
|
|
1395
|
+
// "Missing or invalid parameters" on 0. When the hop didn't supply
|
|
1396
|
+
// one (typical for a pure-R3 leg composed inside a cascade), fall
|
|
1397
|
+
// back to the EVM Route 3 default.
|
|
1398
|
+
const effectiveGasLimit = segment.gasLimit && segment.gasLimit > BigInt(0)
|
|
1399
|
+
? segment.gasLimit
|
|
1400
|
+
: selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT;
|
|
1401
|
+
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, segment.prc20Token ||
|
|
1402
|
+
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), segment.totalBurnAmount || BigInt(0), effectiveGasLimit, ceaPayload, ueaAddress, segment.maxPCForGas ?? BigInt(0), gasPriceForOutboundRequest(sourceChain));
|
|
1403
|
+
const inboundGasFee = segment.gasFee || BigInt(0);
|
|
1404
|
+
let evmInboundFallback;
|
|
1405
|
+
if (effectiveUeaBalance > BigInt(0)) {
|
|
1406
|
+
evmInboundFallback =
|
|
1407
|
+
effectiveUeaBalance / BigInt(Math.max(numOutbounds, 1));
|
|
1408
|
+
}
|
|
1409
|
+
else {
|
|
1410
|
+
evmInboundFallback = inboundGasFee * BigInt(1000000);
|
|
1411
|
+
}
|
|
1412
|
+
let evmInboundNativeValue = evmNativeValueBySegment.get(segment) ??
|
|
1413
|
+
perOutboundNativeValue ??
|
|
1414
|
+
evmInboundFallback;
|
|
1415
|
+
// SDK 5.2 R3-style Case C bump: when the inbound segment's sizing
|
|
1416
|
+
// is C, top up the swap budget by the overflow. No bridge-swap —
|
|
1417
|
+
// R3 has no destination funds delivery.
|
|
1418
|
+
if (segment.sizing?.category === 'C' &&
|
|
1419
|
+
segment.sizing.overflowNativePc > BigInt(0)) {
|
|
1420
|
+
const bumped = evmInboundNativeValue + segment.sizing.overflowNativePc;
|
|
1421
|
+
(0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (EVM) Case C: bumping nativeValueForGas from ${evmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
|
|
1422
|
+
evmInboundNativeValue = bumped;
|
|
1423
|
+
}
|
|
1424
|
+
const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
1425
|
+
prc20Token: segment.prc20Token ||
|
|
1426
|
+
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork),
|
|
1427
|
+
gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
|
|
1428
|
+
burnAmount: segment.totalBurnAmount || BigInt(0),
|
|
1429
|
+
gasFee: inboundGasFee,
|
|
1430
|
+
nativeValueForGas: evmInboundNativeValue,
|
|
1431
|
+
gatewayPcAddress,
|
|
1432
|
+
outboundRequest: outboundReq,
|
|
1433
|
+
});
|
|
1434
|
+
requiredNativeValue += sumMulticallNativeValue(outboundMulticalls);
|
|
1435
|
+
// Reset accumulated -- everything is now inside this outbound
|
|
1436
|
+
accumulatedPushMulticalls = [...outboundMulticalls];
|
|
1437
|
+
break;
|
|
1348
1438
|
}
|
|
1349
1439
|
}
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1440
|
+
}
|
|
1441
|
+
if (requiredNativeValue > BigInt(0)) {
|
|
1442
|
+
(0, preflight_1.runPreflight)({
|
|
1443
|
+
ctx,
|
|
1444
|
+
ueaAddress,
|
|
1445
|
+
ueaBalance: effectiveUeaBalance,
|
|
1446
|
+
requiredValue: requiredNativeValue,
|
|
1447
|
+
gasReserve: CASCADE_GAS_RESERVE,
|
|
1448
|
+
pathTag: 'CASCADE',
|
|
1449
|
+
enforceGasCheck,
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
return {
|
|
1453
|
+
multicalls: accumulatedPushMulticalls,
|
|
1454
|
+
requiredNativeValue,
|
|
1455
|
+
};
|
|
1355
1456
|
}
|
|
1356
|
-
function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
return result.multicalls;
|
|
1360
|
-
});
|
|
1457
|
+
async function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
|
|
1458
|
+
const result = await composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce);
|
|
1459
|
+
return result.multicalls;
|
|
1361
1460
|
}
|
|
1362
1461
|
// ============================================================================
|
|
1363
1462
|
// createCascadedBuilder
|
|
1364
1463
|
// ============================================================================
|
|
1365
1464
|
function txHashFromProgressEvent(event) {
|
|
1366
|
-
var _a, _b, _c, _d, _e, _f;
|
|
1367
1465
|
const response = event.response;
|
|
1368
|
-
return (
|
|
1466
|
+
return (response?.txHash ??
|
|
1467
|
+
response?.pushTxHash ??
|
|
1468
|
+
response?.rootTxHash ??
|
|
1469
|
+
response?.response?.[0]?.hash ??
|
|
1470
|
+
'');
|
|
1369
1471
|
}
|
|
1370
|
-
function withMultihopRootProgressMapping(ctx, run) {
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
return;
|
|
1379
|
-
}
|
|
1380
|
-
originalHook(event);
|
|
1381
|
-
};
|
|
1382
|
-
try {
|
|
1383
|
-
return yield run();
|
|
1472
|
+
async function withMultihopRootProgressMapping(ctx, run) {
|
|
1473
|
+
const originalHook = ctx.progressHook;
|
|
1474
|
+
if (!originalHook)
|
|
1475
|
+
return run();
|
|
1476
|
+
ctx.progressHook = (event) => {
|
|
1477
|
+
if (event.id === progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_01) {
|
|
1478
|
+
originalHook(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_99](txHashFromProgressEvent(event)));
|
|
1479
|
+
return;
|
|
1384
1480
|
}
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1481
|
+
originalHook(event);
|
|
1482
|
+
};
|
|
1483
|
+
try {
|
|
1484
|
+
return await run();
|
|
1485
|
+
}
|
|
1486
|
+
finally {
|
|
1487
|
+
ctx.progressHook = originalHook;
|
|
1488
|
+
}
|
|
1389
1489
|
}
|
|
1390
1490
|
function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, options) {
|
|
1391
1491
|
return {
|
|
1392
|
-
send: () =>
|
|
1393
|
-
var _a;
|
|
1492
|
+
send: async () => {
|
|
1394
1493
|
const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
|
|
1395
1494
|
// Extract HopDescriptors
|
|
1396
1495
|
const hops = preparedTxs.map((tx) => tx._hop);
|
|
1397
|
-
|
|
1398
|
-
|
|
1496
|
+
// Prepared PC20 imports pin registry-derived identity into their
|
|
1497
|
+
// payload. Re-check that binding immediately before any route is
|
|
1498
|
+
// executed or composed; a stale descriptor must never reach broadcast.
|
|
1499
|
+
await Promise.all(hops.map((hop) => (0, gate_1.revalidatePreparedPC20)(ctx, hop.params)));
|
|
1500
|
+
const enforceGasCheck = options?.enforceGasCheck === true ||
|
|
1501
|
+
hops.some((hop) => hop.params.options?.enforceGasCheck === true);
|
|
1399
1502
|
const withCascadeOptions = (params) => enforceGasCheck
|
|
1400
|
-
?
|
|
1503
|
+
? {
|
|
1504
|
+
...params,
|
|
1505
|
+
options: {
|
|
1506
|
+
...params.options,
|
|
1507
|
+
enforceGasCheck: true,
|
|
1508
|
+
},
|
|
1509
|
+
}
|
|
1510
|
+
: params;
|
|
1401
1511
|
// Classify into segments
|
|
1402
1512
|
const segments = classifyIntoSegments(hops);
|
|
1403
1513
|
// Check if this is a single-hop Route 1 (no composition needed)
|
|
1404
1514
|
if (preparedTxs.length === 1 && preparedTxs[0].route === 'UOA_TO_PUSH') {
|
|
1405
|
-
const response =
|
|
1515
|
+
const response = await callbacks.executeFn(withCascadeOptions(hops[0].params));
|
|
1406
1516
|
const singleRoute1Result = {
|
|
1407
1517
|
initialTxHash: response.hash,
|
|
1408
1518
|
initialTxResponse: response,
|
|
@@ -1417,29 +1527,27 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1417
1527
|
],
|
|
1418
1528
|
hopCount: 1,
|
|
1419
1529
|
finalTxHash: response.hash,
|
|
1420
|
-
waitForAll: () =>
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
finalTxResponse: singleRoute1Result,
|
|
1434
|
-
});
|
|
1530
|
+
waitForAll: async () => ({
|
|
1531
|
+
success: true,
|
|
1532
|
+
hops: [
|
|
1533
|
+
{
|
|
1534
|
+
hopIndex: 0,
|
|
1535
|
+
route: hops[0].route,
|
|
1536
|
+
executionChain: (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
|
|
1537
|
+
status: 'confirmed',
|
|
1538
|
+
txHash: response.hash,
|
|
1539
|
+
},
|
|
1540
|
+
],
|
|
1541
|
+
finalTxHash: response.hash,
|
|
1542
|
+
finalTxResponse: singleRoute1Result,
|
|
1435
1543
|
}),
|
|
1436
|
-
wait: (opts) =>
|
|
1544
|
+
wait: async (opts) => singleRoute1Result.waitForAll(opts),
|
|
1437
1545
|
};
|
|
1438
1546
|
return singleRoute1Result;
|
|
1439
1547
|
}
|
|
1440
1548
|
// Check if single-hop Route 2 (just execute directly)
|
|
1441
1549
|
if (preparedTxs.length === 1 && preparedTxs[0].route === 'UOA_TO_CEA') {
|
|
1442
|
-
const response =
|
|
1550
|
+
const response = await callbacks.executeFn(withCascadeOptions(hops[0].params));
|
|
1443
1551
|
const targetChain = hops[0].targetChain || (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork);
|
|
1444
1552
|
const singleRoute2Result = {
|
|
1445
1553
|
initialTxHash: response.hash,
|
|
@@ -1455,39 +1563,37 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1455
1563
|
],
|
|
1456
1564
|
hopCount: 1,
|
|
1457
1565
|
finalTxHash: response.hash,
|
|
1458
|
-
waitForAll: () =>
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
finalTxResponse: singleRoute2Result,
|
|
1472
|
-
});
|
|
1566
|
+
waitForAll: async () => ({
|
|
1567
|
+
success: true,
|
|
1568
|
+
hops: [
|
|
1569
|
+
{
|
|
1570
|
+
hopIndex: 0,
|
|
1571
|
+
route: hops[0].route,
|
|
1572
|
+
executionChain: targetChain,
|
|
1573
|
+
status: 'confirmed',
|
|
1574
|
+
txHash: response.hash,
|
|
1575
|
+
},
|
|
1576
|
+
],
|
|
1577
|
+
finalTxHash: response.hash,
|
|
1578
|
+
finalTxResponse: singleRoute2Result,
|
|
1473
1579
|
}),
|
|
1474
|
-
wait: (opts) =>
|
|
1580
|
+
wait: async (opts) => singleRoute2Result.waitForAll(opts),
|
|
1475
1581
|
};
|
|
1476
1582
|
return singleRoute2Result;
|
|
1477
1583
|
}
|
|
1478
1584
|
// Multi-hop: compose cascade bottom-to-top
|
|
1479
1585
|
// Fetch UEA balance + nonce so composeCascade can allocate native value and build inbound payloads
|
|
1480
|
-
const ueaBalance =
|
|
1481
|
-
const ueaCodeCascade =
|
|
1586
|
+
const ueaBalance = await ctx.pushClient.getBalance(ueaAddress);
|
|
1587
|
+
const ueaCodeCascade = await ctx.pushClient.publicClient.getCode({
|
|
1482
1588
|
address: ueaAddress,
|
|
1483
1589
|
});
|
|
1484
1590
|
const ueaNonceCascade = ueaCodeCascade !== undefined
|
|
1485
|
-
?
|
|
1591
|
+
? await (0, uea_manager_1.getUEANonce)(ctx, ueaAddress)
|
|
1486
1592
|
: BigInt(0);
|
|
1487
1593
|
let composedMulticalls;
|
|
1488
1594
|
let cascadeRequiredNativeValue = BigInt(0);
|
|
1489
1595
|
try {
|
|
1490
|
-
const composed =
|
|
1596
|
+
const composed = await composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonceCascade, { enforceGasCheck });
|
|
1491
1597
|
composedMulticalls = composed.multicalls;
|
|
1492
1598
|
cascadeRequiredNativeValue = composed.requiredNativeValue;
|
|
1493
1599
|
}
|
|
@@ -1498,7 +1604,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1498
1604
|
// terminal "cascade failed" frame instead of the stream stopping
|
|
1499
1605
|
// silently after 203-04.
|
|
1500
1606
|
if (err instanceof errors_1.InsufficientUEABalanceError) {
|
|
1501
|
-
const failedHopIndex =
|
|
1607
|
+
const failedHopIndex = err.segmentIndex ?? 0;
|
|
1502
1608
|
(0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_999_02, failedHopIndex + 1, preparedTxs.length, err.message);
|
|
1503
1609
|
}
|
|
1504
1610
|
throw err;
|
|
@@ -1513,7 +1619,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1513
1619
|
if (rootFundsDeposit) {
|
|
1514
1620
|
executeParams.funds = rootFundsDeposit;
|
|
1515
1621
|
}
|
|
1516
|
-
const response =
|
|
1622
|
+
const response = await withMultihopRootProgressMapping(ctx, () => callbacks.executeFn(executeParams));
|
|
1517
1623
|
// Build hop info for tracking
|
|
1518
1624
|
const hopInfos = hops.map((hop, index) => ({
|
|
1519
1625
|
hopIndex: index,
|
|
@@ -1533,8 +1639,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1533
1639
|
initialTxResponse: response,
|
|
1534
1640
|
hops: hopInfos,
|
|
1535
1641
|
hopCount: hops.length,
|
|
1536
|
-
waitForAll: (opts) =>
|
|
1537
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
1642
|
+
waitForAll: async (opts) => {
|
|
1538
1643
|
const { pollingIntervalMs = 10000, timeout = 300000, progressHook: cascadeProgressHook, eventHook = defaultEventHook, } = opts || {};
|
|
1539
1644
|
const startTime = Date.now();
|
|
1540
1645
|
// Per-hop progress emission. Fans out cascade markers to BOTH the
|
|
@@ -1555,11 +1660,10 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1555
1660
|
// but guard explicitly in case someone wires a 1-element prepared list.
|
|
1556
1661
|
const totalHops = hopInfos.length;
|
|
1557
1662
|
const isMulti = totalHops > 1;
|
|
1558
|
-
const chainLabels = hops.map((h) =>
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
const signerOriginChain = String((_a = ctx.universalSigner.account.chain) !== null && _a !== void 0 ? _a : 'origin');
|
|
1663
|
+
const chainLabels = hops.map((h) => String(h.targetChain ??
|
|
1664
|
+
h.sourceChain ??
|
|
1665
|
+
(0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork)));
|
|
1666
|
+
const signerOriginChain = String(ctx.universalSigner.account.chain ?? 'origin');
|
|
1563
1667
|
const emitHopStart = (hopIndex0) => {
|
|
1564
1668
|
if (!isMulti)
|
|
1565
1669
|
return;
|
|
@@ -1576,12 +1680,10 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1576
1680
|
return; // skip after final hop — 999-01 takes over
|
|
1577
1681
|
emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_002_99_99](n, totalHops));
|
|
1578
1682
|
};
|
|
1579
|
-
const currentFinalTxHash = () =>
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
.find((hop) => hop.status === 'confirmed' && hop.txHash)) === null || _a === void 0 ? void 0 : _a.txHash) !== null && _b !== void 0 ? _b : response.hash;
|
|
1584
|
-
};
|
|
1683
|
+
const currentFinalTxHash = () => [...hopInfos]
|
|
1684
|
+
.reverse()
|
|
1685
|
+
.find((hop) => hop.status === 'confirmed' && hop.txHash)
|
|
1686
|
+
?.txHash ?? response.hash;
|
|
1585
1687
|
const emitCascadeSuccess = () => {
|
|
1586
1688
|
if (!isMulti)
|
|
1587
1689
|
return;
|
|
@@ -1598,13 +1700,12 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1598
1700
|
emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_999_03](failedIdx0 + 1, totalHops, txHash, pushTxHash));
|
|
1599
1701
|
};
|
|
1600
1702
|
const emitRouteIntermediateComplete = (hop, txHash) => {
|
|
1601
|
-
var _a;
|
|
1602
1703
|
if (!isMulti)
|
|
1603
1704
|
return;
|
|
1604
1705
|
const n = hop.hopIndex + 1;
|
|
1605
1706
|
if (n >= totalHops)
|
|
1606
1707
|
return; // final terminal is 999-01
|
|
1607
|
-
const resolvedTxHash =
|
|
1708
|
+
const resolvedTxHash = txHash ?? hop.txHash ?? response.hash;
|
|
1608
1709
|
if (hop.route === 'UOA_TO_PUSH') {
|
|
1609
1710
|
emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_99](resolvedTxHash));
|
|
1610
1711
|
return;
|
|
@@ -1620,7 +1721,13 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1620
1721
|
const buildCompletionResult = (success, failedAt) => {
|
|
1621
1722
|
const finalTxHash = currentFinalTxHash();
|
|
1622
1723
|
cascadeResponse.finalTxHash = finalTxHash;
|
|
1623
|
-
return
|
|
1724
|
+
return {
|
|
1725
|
+
success,
|
|
1726
|
+
hops: hopInfos,
|
|
1727
|
+
finalTxHash,
|
|
1728
|
+
finalTxResponse: cascadeResponse,
|
|
1729
|
+
...(failedAt !== undefined ? { failedAt } : {}),
|
|
1730
|
+
};
|
|
1624
1731
|
};
|
|
1625
1732
|
if (isMulti) {
|
|
1626
1733
|
emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_001](totalHops, chainLabels));
|
|
@@ -1631,33 +1738,32 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1631
1738
|
// its "start" coincides with submitting the composed Push tx. For
|
|
1632
1739
|
// cascades that begin with R2/R3, the per-outbound/inbound branches
|
|
1633
1740
|
// below own the emitHopStart for hop 0 to avoid a duplicate marker.
|
|
1634
|
-
if (
|
|
1741
|
+
if (hopInfos[0]?.route === 'UOA_TO_PUSH') {
|
|
1635
1742
|
emitHopStart(0);
|
|
1636
1743
|
}
|
|
1637
|
-
cascadeProgressHook
|
|
1744
|
+
cascadeProgressHook?.({
|
|
1638
1745
|
hopIndex: 0,
|
|
1639
|
-
route:
|
|
1746
|
+
route: hopInfos[0]?.route || 'UOA_TO_PUSH',
|
|
1640
1747
|
chain: (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
|
|
1641
1748
|
status: 'waiting',
|
|
1642
1749
|
elapsed: Date.now() - startTime,
|
|
1643
1750
|
});
|
|
1644
|
-
|
|
1751
|
+
await response.wait();
|
|
1645
1752
|
const ceaToPushIndex = hopInfos.findIndex((h, index) => h.route === 'CEA_TO_PUSH' && !isNativeSeedOnlyHop(hops[index]));
|
|
1646
1753
|
const isAfterCeaToPush = (index) => ceaToPushIndex >= 0 && index > ceaToPushIndex;
|
|
1647
1754
|
const rootOutboundIndexByHop = buildRootOutboundIndexByHop(segments, hops);
|
|
1648
|
-
const trackOutboundHops = (outboundHops, pushTxHash, resolvedSubTxId, outboundIndexByHop, trackingOptions) =>
|
|
1649
|
-
var _a, _b, _c, _d, _e;
|
|
1755
|
+
const trackOutboundHops = async (outboundHops, pushTxHash, resolvedSubTxId, outboundIndexByHop, trackingOptions) => {
|
|
1650
1756
|
if (outboundHops.length === 0)
|
|
1651
1757
|
return null;
|
|
1652
|
-
const shouldDeferHopCompletion = (hop) =>
|
|
1653
|
-
|
|
1758
|
+
const shouldDeferHopCompletion = (hop) => trackingOptions?.deferCeaToPushCompletion === true &&
|
|
1759
|
+
hop?.route === 'CEA_TO_PUSH';
|
|
1654
1760
|
if (outboundHops.length === 1) {
|
|
1655
1761
|
const hop = outboundHops[0];
|
|
1656
1762
|
const hopHooks = (0, progress_route_hooks_1.pickWaitHooks)(hop.route);
|
|
1657
1763
|
const remainingTimeout = timeout - (Date.now() - startTime);
|
|
1658
1764
|
if (remainingTimeout <= 0) {
|
|
1659
1765
|
hop.status = 'failed';
|
|
1660
|
-
cascadeProgressHook
|
|
1766
|
+
cascadeProgressHook?.({
|
|
1661
1767
|
hopIndex: hop.hopIndex,
|
|
1662
1768
|
route: hop.route,
|
|
1663
1769
|
chain: hop.executionChain,
|
|
@@ -1670,7 +1776,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1670
1776
|
}
|
|
1671
1777
|
emitHopStart(hop.hopIndex);
|
|
1672
1778
|
emitHopEvent(hopHooks.awaiting(hop.executionChain, pushTxHash));
|
|
1673
|
-
cascadeProgressHook
|
|
1779
|
+
cascadeProgressHook?.({
|
|
1674
1780
|
hopIndex: hop.hopIndex,
|
|
1675
1781
|
route: hop.route,
|
|
1676
1782
|
chain: hop.executionChain,
|
|
@@ -1679,12 +1785,19 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1679
1785
|
});
|
|
1680
1786
|
let lastEmittedStatus;
|
|
1681
1787
|
try {
|
|
1682
|
-
const outboundDetails =
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
:
|
|
1687
|
-
|
|
1788
|
+
const outboundDetails = await callbacks.waitForOutboundTxFn(pushTxHash, {
|
|
1789
|
+
initialWaitMs: Math.min(60000, remainingTimeout),
|
|
1790
|
+
pollingIntervalMs,
|
|
1791
|
+
timeout: remainingTimeout,
|
|
1792
|
+
_resolvedSubTxId: resolvedSubTxId,
|
|
1793
|
+
_expectedDestinationChain: hop.executionChain,
|
|
1794
|
+
...(outboundIndexByHop
|
|
1795
|
+
? {
|
|
1796
|
+
_outboundIndex: outboundIndexByHop.get(hop.hopIndex) ?? 0,
|
|
1797
|
+
}
|
|
1798
|
+
: {}),
|
|
1799
|
+
progressHook: (event) => {
|
|
1800
|
+
cascadeProgressHook?.({
|
|
1688
1801
|
hopIndex: hop.hopIndex,
|
|
1689
1802
|
route: hop.route,
|
|
1690
1803
|
chain: hop.executionChain,
|
|
@@ -1696,14 +1809,18 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1696
1809
|
lastEmittedStatus = 'polling';
|
|
1697
1810
|
emitHopEvent(hopHooks.polling(hop.executionChain, event.elapsed, pushTxHash));
|
|
1698
1811
|
}
|
|
1699
|
-
}
|
|
1812
|
+
},
|
|
1813
|
+
});
|
|
1700
1814
|
// `outboundDetails.externalTxHash` from waitForOutboundTx
|
|
1701
1815
|
// is the raw `0x`-hex form (internal canonical). For the
|
|
1702
1816
|
// user-facing `hop.txHash`, `hop.outboundDetails`, and
|
|
1703
1817
|
// cascade progress event, normalize to base58 for SVM so
|
|
1704
1818
|
// consumers can paste it straight into a Solana explorer.
|
|
1705
|
-
const displayTxHash = (
|
|
1706
|
-
const userFacingOutboundDetails =
|
|
1819
|
+
const displayTxHash = (0, helpers_1.toExternalTxHashDisplay)(outboundDetails.destinationChain, outboundDetails.externalTxHash) ?? outboundDetails.externalTxHash;
|
|
1820
|
+
const userFacingOutboundDetails = {
|
|
1821
|
+
...outboundDetails,
|
|
1822
|
+
externalTxHash: displayTxHash,
|
|
1823
|
+
};
|
|
1707
1824
|
const deferHopCompletion = shouldDeferHopCompletion(hop);
|
|
1708
1825
|
hop.status = deferHopCompletion ? 'pending' : 'confirmed';
|
|
1709
1826
|
hop.txHash = displayTxHash;
|
|
@@ -1717,7 +1834,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1717
1834
|
}
|
|
1718
1835
|
emitHopComplete(hop.hopIndex);
|
|
1719
1836
|
}
|
|
1720
|
-
cascadeProgressHook
|
|
1837
|
+
cascadeProgressHook?.({
|
|
1721
1838
|
hopIndex: hop.hopIndex,
|
|
1722
1839
|
route: hop.route,
|
|
1723
1840
|
chain: hop.executionChain,
|
|
@@ -1729,8 +1846,8 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1729
1846
|
catch (err) {
|
|
1730
1847
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1731
1848
|
const isTimeout = errMsg.startsWith('Timeout waiting for outbound transaction');
|
|
1732
|
-
const failedExternalTxHash = err
|
|
1733
|
-
const failedDisplayTxHash = (
|
|
1849
|
+
const failedExternalTxHash = err?.externalTxHash;
|
|
1850
|
+
const failedDisplayTxHash = (0, helpers_1.toExternalTxHashDisplay)(hop.executionChain, failedExternalTxHash) ?? failedExternalTxHash;
|
|
1734
1851
|
emitHopEvent(isTimeout
|
|
1735
1852
|
? hopHooks.timeout(hop.executionChain, remainingTimeout, pushTxHash)
|
|
1736
1853
|
: hopHooks.failed(hop.executionChain, errMsg, {
|
|
@@ -1746,7 +1863,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1746
1863
|
if (failedDisplayTxHash)
|
|
1747
1864
|
hop.txHash = failedDisplayTxHash;
|
|
1748
1865
|
hop.status = 'failed';
|
|
1749
|
-
cascadeProgressHook
|
|
1866
|
+
cascadeProgressHook?.({
|
|
1750
1867
|
hopIndex: hop.hopIndex,
|
|
1751
1868
|
route: hop.route,
|
|
1752
1869
|
chain: hop.executionChain,
|
|
@@ -1764,17 +1881,22 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1764
1881
|
}
|
|
1765
1882
|
const perHopLastStatus = new Map();
|
|
1766
1883
|
const confirmedHops = new Set();
|
|
1767
|
-
const allOutboundDetails =
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1884
|
+
const allOutboundDetails = await callbacks.waitForAllOutboundTxsFn(pushTxHash, outboundHops, {
|
|
1885
|
+
initialWaitMs: Math.min(60000, timeout - (Date.now() - startTime)),
|
|
1886
|
+
pollingIntervalMs,
|
|
1887
|
+
timeout: timeout - (Date.now() - startTime),
|
|
1888
|
+
_resolvedSubTxId: resolvedSubTxId,
|
|
1889
|
+
...(outboundIndexByHop
|
|
1890
|
+
? { _outboundIndexByHop: outboundIndexByHop }
|
|
1891
|
+
: {}),
|
|
1892
|
+
progressHook: (event) => {
|
|
1771
1893
|
const matchedHop = outboundHops.find((h) => h.hopIndex === event.hopIndex);
|
|
1772
|
-
const eventHooks = (0, progress_route_hooks_1.pickWaitHooks)(matchedHop
|
|
1894
|
+
const eventHooks = (0, progress_route_hooks_1.pickWaitHooks)(matchedHop?.route);
|
|
1773
1895
|
const deferHopCompletion = shouldDeferHopCompletion(matchedHop);
|
|
1774
1896
|
const progressStatus = deferHopCompletion && event.status === 'confirmed'
|
|
1775
1897
|
? 'found'
|
|
1776
1898
|
: event.status;
|
|
1777
|
-
cascadeProgressHook
|
|
1899
|
+
cascadeProgressHook?.({
|
|
1778
1900
|
hopIndex: event.hopIndex,
|
|
1779
1901
|
route: event.route,
|
|
1780
1902
|
chain: event.chain,
|
|
@@ -1785,7 +1907,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1785
1907
|
const prev = perHopLastStatus.get(event.hopIndex);
|
|
1786
1908
|
if (event.status === 'polling' && prev !== 'polling') {
|
|
1787
1909
|
perHopLastStatus.set(event.hopIndex, 'polling');
|
|
1788
|
-
emitHopEvent(eventHooks.polling(
|
|
1910
|
+
emitHopEvent(eventHooks.polling(matchedHop?.executionChain ?? event.chain ?? '', Date.now() - startTime, pushTxHash));
|
|
1789
1911
|
}
|
|
1790
1912
|
if (event.status === 'confirmed' &&
|
|
1791
1913
|
event.txHash &&
|
|
@@ -1795,7 +1917,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1795
1917
|
// outbound-sync.ts:348-355). Auto-promotion paths
|
|
1796
1918
|
// without a txHash are skipped by the `event.txHash`
|
|
1797
1919
|
// guard above, so we only reach here with full details.
|
|
1798
|
-
if (matchedHop
|
|
1920
|
+
if (matchedHop?.outboundDetails) {
|
|
1799
1921
|
confirmedHops.add(event.hopIndex);
|
|
1800
1922
|
if (!deferHopCompletion) {
|
|
1801
1923
|
if (isMulti) {
|
|
@@ -1808,21 +1930,22 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1808
1930
|
}
|
|
1809
1931
|
}
|
|
1810
1932
|
}
|
|
1811
|
-
}
|
|
1933
|
+
},
|
|
1934
|
+
});
|
|
1812
1935
|
if (!allOutboundDetails.success) {
|
|
1813
|
-
const failedIdx =
|
|
1936
|
+
const failedIdx = allOutboundDetails.failedAt ?? 0;
|
|
1814
1937
|
const failedHop = outboundHops.find((h) => h.hopIndex === failedIdx);
|
|
1815
1938
|
const failMsg = failedHop
|
|
1816
1939
|
? `Outbound failed for hop ${failedIdx} on ${failedHop.executionChain}`
|
|
1817
1940
|
: `Outbound cascade failed at hop ${failedIdx}`;
|
|
1818
|
-
emitHopEvent((0, progress_route_hooks_1.pickWaitHooks)(failedHop
|
|
1941
|
+
emitHopEvent((0, progress_route_hooks_1.pickWaitHooks)(failedHop?.route).failed(failedHop?.executionChain ?? '', failMsg, {
|
|
1819
1942
|
pushTxHash,
|
|
1820
|
-
txHash: failedHop
|
|
1943
|
+
txHash: failedHop?.txHash,
|
|
1821
1944
|
}));
|
|
1822
|
-
emitCascadeFailed(failedIdx, failMsg, failedHop
|
|
1945
|
+
emitCascadeFailed(failedIdx, failMsg, failedHop?.txHash, pushTxHash);
|
|
1823
1946
|
return buildCompletionResult(false, failedIdx);
|
|
1824
1947
|
}
|
|
1825
|
-
if (trackingOptions
|
|
1948
|
+
if (trackingOptions?.deferCeaToPushCompletion) {
|
|
1826
1949
|
for (const outHop of outboundHops) {
|
|
1827
1950
|
if (shouldDeferHopCompletion(outHop) &&
|
|
1828
1951
|
outHop.outboundDetails) {
|
|
@@ -1832,7 +1955,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1832
1955
|
}
|
|
1833
1956
|
}
|
|
1834
1957
|
return null;
|
|
1835
|
-
}
|
|
1958
|
+
};
|
|
1836
1959
|
// Mark only root Push Chain (Route 1) hops as confirmed. Route 1
|
|
1837
1960
|
// hops after a Route 3 leg execute in the child inbound Push tx and
|
|
1838
1961
|
// must wait for that child UTX to reach terminal success.
|
|
@@ -1842,7 +1965,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1842
1965
|
emitHopStart(hop.hopIndex);
|
|
1843
1966
|
hop.status = 'confirmed';
|
|
1844
1967
|
hop.txHash = response.hash;
|
|
1845
|
-
cascadeProgressHook
|
|
1968
|
+
cascadeProgressHook?.({
|
|
1846
1969
|
hopIndex: hop.hopIndex,
|
|
1847
1970
|
route: hop.route,
|
|
1848
1971
|
chain: (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
|
|
@@ -1858,7 +1981,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1858
1981
|
!isAfterCeaToPush(hop.hopIndex)) {
|
|
1859
1982
|
hop.status = 'confirmed';
|
|
1860
1983
|
hop.txHash = response.hash;
|
|
1861
|
-
cascadeProgressHook
|
|
1984
|
+
cascadeProgressHook?.({
|
|
1862
1985
|
hopIndex: hop.hopIndex,
|
|
1863
1986
|
route: hop.route,
|
|
1864
1987
|
chain: (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
|
|
@@ -1871,14 +1994,11 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1871
1994
|
}
|
|
1872
1995
|
// 2. Track direct outbound hops (Route 2: UOA_TO_CEA) that belong
|
|
1873
1996
|
// to the root Push UTX.
|
|
1874
|
-
const rootOutboundHops = hopInfos.filter((h) =>
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
((_a = chain_1.CHAIN_INFO[h.executionChain]) === null || _a === void 0 ? void 0 : _a.vm) === enums_1.VM.EVM);
|
|
1880
|
-
});
|
|
1881
|
-
const rootOutboundFailure = yield trackOutboundHops(rootOutboundHops, response.hash, undefined, rootOutboundIndexByHop, { deferCeaToPushCompletion: true });
|
|
1997
|
+
const rootOutboundHops = hopInfos.filter((h) => (h.route === 'UOA_TO_CEA' && !isAfterCeaToPush(h.hopIndex)) ||
|
|
1998
|
+
(h.hopIndex === ceaToPushIndex &&
|
|
1999
|
+
h.route === 'CEA_TO_PUSH' &&
|
|
2000
|
+
chain_1.CHAIN_INFO[h.executionChain]?.vm === enums_1.VM.EVM));
|
|
2001
|
+
const rootOutboundFailure = await trackOutboundHops(rootOutboundHops, response.hash, undefined, rootOutboundIndexByHop, { deferCeaToPushCompletion: true });
|
|
1882
2002
|
if (rootOutboundFailure)
|
|
1883
2003
|
return rootOutboundFailure;
|
|
1884
2004
|
let childInbound;
|
|
@@ -1893,12 +2013,12 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1893
2013
|
return buildCompletionResult(false, inboundHop.hopIndex);
|
|
1894
2014
|
}
|
|
1895
2015
|
const hasTrackedSourceOutbound = Boolean(inboundHop.outboundDetails) &&
|
|
1896
|
-
|
|
2016
|
+
chain_1.CHAIN_INFO[inboundHop.executionChain]?.vm === enums_1.VM.EVM;
|
|
1897
2017
|
if (!hasTrackedSourceOutbound) {
|
|
1898
2018
|
emitHopStart(inboundHop.hopIndex);
|
|
1899
2019
|
emitHopEvent(hopHooks.awaiting(inboundHop.executionChain, response.hash));
|
|
1900
2020
|
}
|
|
1901
|
-
cascadeProgressHook
|
|
2021
|
+
cascadeProgressHook?.({
|
|
1902
2022
|
hopIndex: inboundHop.hopIndex,
|
|
1903
2023
|
route: inboundHop.route,
|
|
1904
2024
|
chain: inboundHop.executionChain,
|
|
@@ -1914,14 +2034,14 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1914
2034
|
inboundHop.outboundDetails;
|
|
1915
2035
|
}
|
|
1916
2036
|
else {
|
|
1917
|
-
sourceOutboundDetails =
|
|
2037
|
+
sourceOutboundDetails = await callbacks.waitForOutboundTxFn(response.hash, {
|
|
1918
2038
|
initialWaitMs: Math.min(60000, remainingTimeout),
|
|
1919
2039
|
pollingIntervalMs,
|
|
1920
2040
|
timeout: remainingTimeout,
|
|
1921
2041
|
_expectedDestinationChain: inboundHop.executionChain,
|
|
1922
|
-
_outboundIndex:
|
|
2042
|
+
_outboundIndex: rootOutboundIndexByHop.get(inboundHop.hopIndex) ?? 0,
|
|
1923
2043
|
progressHook: (event) => {
|
|
1924
|
-
cascadeProgressHook
|
|
2044
|
+
cascadeProgressHook?.({
|
|
1925
2045
|
hopIndex: inboundHop.hopIndex,
|
|
1926
2046
|
route: inboundHop.route,
|
|
1927
2047
|
chain: inboundHop.executionChain,
|
|
@@ -1940,8 +2060,8 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1940
2060
|
catch (err) {
|
|
1941
2061
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1942
2062
|
const isTimeout = errMsg.startsWith('Timeout waiting for outbound transaction');
|
|
1943
|
-
const failedExternalTxHash = err
|
|
1944
|
-
const failedDisplayTxHash = (
|
|
2063
|
+
const failedExternalTxHash = err?.externalTxHash;
|
|
2064
|
+
const failedDisplayTxHash = (0, helpers_1.toExternalTxHashDisplay)(inboundHop.executionChain, failedExternalTxHash) ?? failedExternalTxHash;
|
|
1945
2065
|
emitHopEvent(isTimeout
|
|
1946
2066
|
? hopHooks.timeout(inboundHop.executionChain, remainingTimeout, response.hash)
|
|
1947
2067
|
: hopHooks.failed(inboundHop.executionChain, errMsg, {
|
|
@@ -1955,7 +2075,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1955
2075
|
inboundHop.status = 'failed';
|
|
1956
2076
|
if (failedDisplayTxHash)
|
|
1957
2077
|
inboundHop.txHash = failedDisplayTxHash;
|
|
1958
|
-
cascadeProgressHook
|
|
2078
|
+
cascadeProgressHook?.({
|
|
1959
2079
|
hopIndex: inboundHop.hopIndex,
|
|
1960
2080
|
route: inboundHop.route,
|
|
1961
2081
|
chain: inboundHop.executionChain,
|
|
@@ -1965,15 +2085,19 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1965
2085
|
});
|
|
1966
2086
|
return buildCompletionResult(false, inboundHop.hopIndex);
|
|
1967
2087
|
}
|
|
1968
|
-
const displayExternalTxHash = (
|
|
1969
|
-
inboundHop.outboundDetails =
|
|
2088
|
+
const displayExternalTxHash = (0, helpers_1.toExternalTxHashDisplay)(sourceOutboundDetails.destinationChain, sourceOutboundDetails.externalTxHash) ?? sourceOutboundDetails.externalTxHash;
|
|
2089
|
+
inboundHop.outboundDetails = {
|
|
2090
|
+
...sourceOutboundDetails,
|
|
2091
|
+
externalTxHash: displayExternalTxHash,
|
|
2092
|
+
};
|
|
1970
2093
|
inboundHop.txHash = displayExternalTxHash;
|
|
1971
2094
|
emitHopEvent(hopHooks.success(inboundHop.outboundDetails));
|
|
1972
2095
|
emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_310_01](inboundHop.executionChain, response.hash));
|
|
1973
2096
|
let inboundLastStatus;
|
|
1974
2097
|
const inboundRemainingTimeout = timeout - (Date.now() - startTime);
|
|
1975
2098
|
try {
|
|
1976
|
-
childInbound =
|
|
2099
|
+
childInbound = await (callbacks.waitForInboundPushTxFn ??
|
|
2100
|
+
((externalTxHash, sourceChain, opts) => (0, inbound_tracker_1.waitForInboundPushTx)(ctx, externalTxHash, sourceChain, opts)))(sourceOutboundDetails.externalTxHash, inboundHop.executionChain, {
|
|
1977
2101
|
initialWaitMs: Math.min(60000, inboundRemainingTimeout),
|
|
1978
2102
|
pollingIntervalMs,
|
|
1979
2103
|
timeout: inboundRemainingTimeout,
|
|
@@ -1997,7 +2121,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
1997
2121
|
else
|
|
1998
2122
|
emitCascadeFailed(inboundHop.hopIndex, errMsg, undefined, response.hash);
|
|
1999
2123
|
inboundHop.status = 'failed';
|
|
2000
|
-
cascadeProgressHook
|
|
2124
|
+
cascadeProgressHook?.({
|
|
2001
2125
|
hopIndex: inboundHop.hopIndex,
|
|
2002
2126
|
route: inboundHop.route,
|
|
2003
2127
|
chain: inboundHop.executionChain,
|
|
@@ -2007,13 +2131,14 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
2007
2131
|
return buildCompletionResult(false, inboundHop.hopIndex);
|
|
2008
2132
|
}
|
|
2009
2133
|
if (childInbound.status !== 'confirmed') {
|
|
2010
|
-
const failMsg =
|
|
2134
|
+
const failMsg = childInbound.errorMessage ??
|
|
2135
|
+
'inbound execution failed on Push Chain';
|
|
2011
2136
|
inboundHop.status = 'failed';
|
|
2012
2137
|
inboundHop.txHash =
|
|
2013
2138
|
childInbound.pushTxHash || displayExternalTxHash;
|
|
2014
2139
|
emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](failMsg, 'inbound', inboundHop.executionChain, undefined, response.hash, childInbound.pushTxHash || undefined));
|
|
2015
2140
|
emitCascadeFailed(inboundHop.hopIndex, failMsg, childInbound.pushTxHash || undefined, response.hash);
|
|
2016
|
-
cascadeProgressHook
|
|
2141
|
+
cascadeProgressHook?.({
|
|
2017
2142
|
hopIndex: inboundHop.hopIndex,
|
|
2018
2143
|
route: inboundHop.route,
|
|
2019
2144
|
chain: inboundHop.executionChain,
|
|
@@ -2033,7 +2158,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
2033
2158
|
emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_01](inboundHop.executionChain, childInbound.pushTxHash));
|
|
2034
2159
|
}
|
|
2035
2160
|
emitHopComplete(inboundHop.hopIndex);
|
|
2036
|
-
cascadeProgressHook
|
|
2161
|
+
cascadeProgressHook?.({
|
|
2037
2162
|
hopIndex: inboundHop.hopIndex,
|
|
2038
2163
|
route: inboundHop.route,
|
|
2039
2164
|
chain: inboundHop.executionChain,
|
|
@@ -2049,7 +2174,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
2049
2174
|
emitHopStart(hop.hopIndex);
|
|
2050
2175
|
hop.status = 'confirmed';
|
|
2051
2176
|
hop.txHash = childInbound.pushTxHash;
|
|
2052
|
-
cascadeProgressHook
|
|
2177
|
+
cascadeProgressHook?.({
|
|
2053
2178
|
hopIndex: hop.hopIndex,
|
|
2054
2179
|
route: hop.route,
|
|
2055
2180
|
chain: (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
|
|
@@ -2062,7 +2187,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
2062
2187
|
}
|
|
2063
2188
|
}
|
|
2064
2189
|
const childOutboundHops = hopInfos.filter((h) => h.route === 'UOA_TO_CEA' && isAfterCeaToPush(h.hopIndex));
|
|
2065
|
-
const childOutboundFailure =
|
|
2190
|
+
const childOutboundFailure = await trackOutboundHops(childOutboundHops, childInbound.pushTxHash || response.hash, childInbound.childUtxId);
|
|
2066
2191
|
if (childOutboundFailure)
|
|
2067
2192
|
return childOutboundFailure;
|
|
2068
2193
|
}
|
|
@@ -2073,7 +2198,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
2073
2198
|
const failedIdx = hopInfos.findIndex((h) => h.status !== 'confirmed');
|
|
2074
2199
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2075
2200
|
const failedAt = failedIdx >= 0 ? failedIdx : 0;
|
|
2076
|
-
const failedTxHash =
|
|
2201
|
+
const failedTxHash = hopInfos[failedAt]?.txHash;
|
|
2077
2202
|
const isTimeout = errMsg.startsWith('Timeout');
|
|
2078
2203
|
if (isTimeout) {
|
|
2079
2204
|
emitCascadeTimeout(failedAt, failedTxHash, response.hash);
|
|
@@ -2083,11 +2208,11 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
|
|
|
2083
2208
|
}
|
|
2084
2209
|
return buildCompletionResult(false, failedAt);
|
|
2085
2210
|
}
|
|
2086
|
-
}
|
|
2087
|
-
wait: (opts) =>
|
|
2211
|
+
},
|
|
2212
|
+
wait: async (opts) => cascadeResponse.waitForAll(opts),
|
|
2088
2213
|
};
|
|
2089
2214
|
return cascadeResponse;
|
|
2090
|
-
}
|
|
2215
|
+
},
|
|
2091
2216
|
};
|
|
2092
2217
|
}
|
|
2093
2218
|
//# sourceMappingURL=cascade.js.map
|