@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,4 +1,41 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.getRequiredNativeBalanceForOutbound = getRequiredNativeBalanceForOutbound;
|
|
27
|
+
exports.shouldSkipFeeLockingForOutbound = shouldSkipFeeLockingForOutbound;
|
|
28
|
+
exports.floorR3NativeValueForGas = floorR3NativeValueForGas;
|
|
29
|
+
exports.resolveR2DestinationFundsToken = resolveR2DestinationFundsToken;
|
|
30
|
+
exports.executeMultiChain = executeMultiChain;
|
|
31
|
+
exports.executeUoaToCea = executeUoaToCea;
|
|
32
|
+
exports.executeUoaToCeaSvm = executeUoaToCeaSvm;
|
|
33
|
+
exports.executeCeaToPush = executeCeaToPush;
|
|
34
|
+
exports.executeCeaToPushSvm = executeCeaToPushSvm;
|
|
35
|
+
exports.executeCeaToCea = executeCeaToCea;
|
|
36
|
+
exports.buildPayloadForRoute = buildPayloadForRoute;
|
|
37
|
+
const gate_1 = require("./pc20/gate");
|
|
38
|
+
const export_1 = require("./pc20/export");
|
|
2
39
|
/**
|
|
3
40
|
* Route handler functions extracted from Orchestrator.
|
|
4
41
|
*
|
|
@@ -14,18 +51,6 @@
|
|
|
14
51
|
* Every method receives an `OrchestratorContext` as first parameter and an
|
|
15
52
|
* `executeFn` callback that replaces the former `this.execute()` call.
|
|
16
53
|
*/
|
|
17
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
-
exports.getRequiredNativeBalanceForOutbound = getRequiredNativeBalanceForOutbound;
|
|
19
|
-
exports.shouldSkipFeeLockingForOutbound = shouldSkipFeeLockingForOutbound;
|
|
20
|
-
exports.resolveR2DestinationFundsToken = resolveR2DestinationFundsToken;
|
|
21
|
-
exports.executeMultiChain = executeMultiChain;
|
|
22
|
-
exports.executeUoaToCea = executeUoaToCea;
|
|
23
|
-
exports.executeUoaToCeaSvm = executeUoaToCeaSvm;
|
|
24
|
-
exports.executeCeaToPush = executeCeaToPush;
|
|
25
|
-
exports.executeCeaToPushSvm = executeCeaToPushSvm;
|
|
26
|
-
exports.executeCeaToCea = executeCeaToCea;
|
|
27
|
-
exports.buildPayloadForRoute = buildPayloadForRoute;
|
|
28
|
-
const tslib_1 = require("tslib");
|
|
29
54
|
const web3_js_1 = require("@solana/web3.js");
|
|
30
55
|
const viem_1 = require("viem");
|
|
31
56
|
const context_1 = require("./context");
|
|
@@ -43,8 +68,11 @@ const price_fetch_1 = require("../../price-fetch/price-fetch");
|
|
|
43
68
|
const route_detector_1 = require("../route-detector");
|
|
44
69
|
const cea_utils_1 = require("../cea-utils");
|
|
45
70
|
const build_payload_1 = require("../svm-idl/build-payload");
|
|
46
|
-
const data_utils_1 = require("../data-utils");
|
|
47
71
|
const payload_builders_1 = require("../payload-builders");
|
|
72
|
+
const svm_1 = require("./pc20/svm");
|
|
73
|
+
const svm_rent_2 = require("./svm-rent");
|
|
74
|
+
const data_utils_1 = require("../data-utils");
|
|
75
|
+
const payload_builders_2 = require("../payload-builders");
|
|
48
76
|
const selectors_1 = require("../../constants/selectors");
|
|
49
77
|
const erc20_evm_1 = require("../../constants/abi/erc20.evm");
|
|
50
78
|
const push_chain_1 = require("../../push-chain/push-chain");
|
|
@@ -55,6 +83,16 @@ function shouldSkipFeeLockingForOutbound(opts) {
|
|
|
55
83
|
return (opts.isUEADeployed &&
|
|
56
84
|
opts.balance >= getRequiredNativeBalanceForOutbound(opts.nativeValueForGas, opts.gasReserve));
|
|
57
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Keep Route 3's USD/category sizing, but never let it undercut the live
|
|
88
|
+
* WPC -> destination-gas-token swap requirement. `bufferedSwapValue` is the
|
|
89
|
+
* balance-aware result from estimateNativeValueForSwap; protocol fees are
|
|
90
|
+
* native PC and therefore sit on top of that swap budget.
|
|
91
|
+
*/
|
|
92
|
+
function floorR3NativeValueForGas(sizedValue, bufferedSwapValue, protocolFee) {
|
|
93
|
+
const livePoolFloor = bufferedSwapValue + protocolFee;
|
|
94
|
+
return livePoolFloor > sizedValue ? livePoolFloor : sizedValue;
|
|
95
|
+
}
|
|
58
96
|
// =============================================================================
|
|
59
97
|
// Route 2 (UEA → CEA) phase helpers
|
|
60
98
|
// =============================================================================
|
|
@@ -64,7 +102,6 @@ function shouldSkipFeeLockingForOutbound(opts) {
|
|
|
64
102
|
* CEA address, so it can run before the 203 account-resolution phase.
|
|
65
103
|
*/
|
|
66
104
|
function resolveR2Prc20TokenEvm(params, targetChain, pushNetwork) {
|
|
67
|
-
var _a;
|
|
68
105
|
let prc20Token = selectors_1.ZERO_ADDRESS;
|
|
69
106
|
let burnAmount = BigInt(0);
|
|
70
107
|
if (params.migration) {
|
|
@@ -72,7 +109,15 @@ function resolveR2Prc20TokenEvm(params, targetChain, pushNetwork) {
|
|
|
72
109
|
prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, pushNetwork);
|
|
73
110
|
burnAmount = BigInt(0);
|
|
74
111
|
}
|
|
75
|
-
else if (
|
|
112
|
+
else if (params.funds?.amount) {
|
|
113
|
+
// PC20 export: the token being locked IS the Push-native PC20. There is no
|
|
114
|
+
// synthetic PRC20 to look up, and the resolver already established it.
|
|
115
|
+
const pc20 = params._pc20;
|
|
116
|
+
if (pc20) {
|
|
117
|
+
prc20Token = pc20.pushAddress;
|
|
118
|
+
burnAmount = params.funds.amount;
|
|
119
|
+
return { prc20Token, burnAmount };
|
|
120
|
+
}
|
|
76
121
|
// User explicitly specified funds with token
|
|
77
122
|
const token = params.funds.token;
|
|
78
123
|
if (token) {
|
|
@@ -93,22 +138,23 @@ function resolveR2Prc20TokenEvm(params, targetChain, pushNetwork) {
|
|
|
93
138
|
return { prc20Token, burnAmount };
|
|
94
139
|
}
|
|
95
140
|
function resolveR2DestinationFundsToken(token, targetChain, pushNetwork) {
|
|
96
|
-
var _a, _b, _c;
|
|
97
141
|
const pushToken = token;
|
|
98
142
|
if (!pushToken.prc20Address || pushToken.sourceChain !== targetChain) {
|
|
99
143
|
return token;
|
|
100
144
|
}
|
|
101
|
-
const destinationTokens =
|
|
145
|
+
const destinationTokens = tokens_1.MOVEABLE_TOKENS[targetChain] ?? [];
|
|
102
146
|
const nativePrc20 = (0, helpers_1.getNativePRC20ForChain)(targetChain, pushNetwork);
|
|
103
147
|
if (pushToken.prc20Address.toLowerCase() === nativePrc20.toLowerCase()) {
|
|
104
|
-
return (
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
148
|
+
return (destinationTokens.find((candidate) => candidate.mechanism === 'native') ??
|
|
149
|
+
{
|
|
150
|
+
symbol: token.symbol,
|
|
151
|
+
decimals: token.decimals,
|
|
152
|
+
address: selectors_1.ZERO_ADDRESS,
|
|
153
|
+
mechanism: 'native',
|
|
154
|
+
});
|
|
110
155
|
}
|
|
111
|
-
return (
|
|
156
|
+
return (destinationTokens.find((candidate) => candidate.symbol === token.symbol) ??
|
|
157
|
+
token);
|
|
112
158
|
}
|
|
113
159
|
/**
|
|
114
160
|
* SVM counterpart to `resolveR2Prc20TokenEvm`. SVM adds an EXECUTE-only
|
|
@@ -116,10 +162,15 @@ function resolveR2DestinationFundsToken(token, targetChain, pushNetwork) {
|
|
|
116
162
|
* a PRC-20 token for the chain-namespace lookup.
|
|
117
163
|
*/
|
|
118
164
|
function resolveR2Prc20TokenSvm(params, targetChain, pushNetwork, hasSvmExecute) {
|
|
119
|
-
var _a;
|
|
120
165
|
let prc20Token = selectors_1.ZERO_ADDRESS;
|
|
121
166
|
let burnAmount = BigInt(0);
|
|
122
|
-
if (
|
|
167
|
+
if (params.funds?.amount) {
|
|
168
|
+
// PC20 export: the locked token IS the Push-native PC20 — no synthetic
|
|
169
|
+
// PRC20 lookup. Mirrors the EVM branch in resolveR2Prc20TokenEvm.
|
|
170
|
+
const pc20 = params._pc20;
|
|
171
|
+
if (pc20) {
|
|
172
|
+
return { prc20Token: pc20.pushAddress, burnAmount: params.funds.amount };
|
|
173
|
+
}
|
|
123
174
|
const token = params.funds.token;
|
|
124
175
|
if (token) {
|
|
125
176
|
prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
|
|
@@ -143,15 +194,23 @@ function resolveR2Prc20TokenSvm(params, targetChain, pushNetwork, hasSvmExecute)
|
|
|
143
194
|
return { prc20Token, burnAmount };
|
|
144
195
|
}
|
|
145
196
|
function resolveR3SvmDrain(params, sourceChain, pushNetwork) {
|
|
146
|
-
var _a;
|
|
147
197
|
let drainAmount = BigInt(0);
|
|
148
198
|
let tokenMintHex;
|
|
149
199
|
let splMintBase58;
|
|
150
200
|
let prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, pushNetwork);
|
|
151
|
-
if (
|
|
201
|
+
if (params.funds?.amount && params.funds.amount > BigInt(0)) {
|
|
152
202
|
drainAmount = params.funds.amount;
|
|
153
203
|
const token = params.funds.token;
|
|
154
|
-
|
|
204
|
+
const pc20 = params._pc20;
|
|
205
|
+
if (pc20?.direction === 'import' && token) {
|
|
206
|
+
// PC20 CEA burn: the drained token is the wrapper mint. The outbound
|
|
207
|
+
// relay leg stays on the native PRC-20 (it is a payload relay, not a
|
|
208
|
+
// PC20 export), so prc20Token is left as initialized above.
|
|
209
|
+
const mintPk = new web3_js_1.PublicKey(token.address);
|
|
210
|
+
tokenMintHex = ('0x' + Buffer.from(mintPk.toBytes()).toString('hex'));
|
|
211
|
+
splMintBase58 = token.address;
|
|
212
|
+
}
|
|
213
|
+
else if (token?.mechanism === 'approve') {
|
|
155
214
|
const mintPk = new web3_js_1.PublicKey(token.address);
|
|
156
215
|
tokenMintHex = ('0x' + Buffer.from(mintPk.toBytes()).toString('hex'));
|
|
157
216
|
splMintBase58 = token.address;
|
|
@@ -164,21 +223,37 @@ function resolveR3SvmDrain(params, sourceChain, pushNetwork) {
|
|
|
164
223
|
return { prc20Token, drainAmount, tokenMintHex, splMintBase58 };
|
|
165
224
|
}
|
|
166
225
|
function buildR3SvmExtraPayload(ctx, params, ueaAddress, sourceChain, inboundNonce) {
|
|
167
|
-
|
|
226
|
+
// PC20: the unlocked Push-native token lands on the UEA (program-enforced
|
|
227
|
+
// recipient), so a funds-only burn whose destination differs needs a
|
|
228
|
+
// Push-side forward — same rule as the EVM R3 branch.
|
|
229
|
+
const extraPc20 = params._pc20;
|
|
230
|
+
// Always forward for PC20 funds — a payload-less burn emits a selector-only
|
|
231
|
+
// raw_payload that the chain hard-fails (see the R3 EVM branch note).
|
|
232
|
+
const pc20Forward = extraPc20?.direction === 'import' && Boolean(params.funds?.amount);
|
|
168
233
|
const shouldBuildPushPayload = (0, data_utils_1.hasExecutablePayloadData)(params.data) ||
|
|
169
|
-
Boolean(
|
|
234
|
+
Boolean(params.funds?.amount && (params.value ?? BigInt(0)) > BigInt(0)) ||
|
|
235
|
+
pc20Forward;
|
|
170
236
|
if (!shouldBuildPushPayload) {
|
|
171
237
|
return undefined;
|
|
172
238
|
}
|
|
173
239
|
const executeParams = (0, helpers_1.toExecuteParams)(params);
|
|
174
|
-
const multicallCtx =
|
|
175
|
-
|
|
176
|
-
|
|
240
|
+
const multicallCtx = {
|
|
241
|
+
...ctx,
|
|
242
|
+
universalSigner: {
|
|
243
|
+
...ctx.universalSigner,
|
|
244
|
+
account: {
|
|
245
|
+
...ctx.universalSigner.account,
|
|
246
|
+
chain: sourceChain,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
const multicallPayload = (0, payload_builder_1.buildMulticallPayloadData)(multicallCtx, executeParams.to, (0, payload_builders_2.buildExecuteMulticall)({
|
|
251
|
+
execute: (0, gate_1.assertLegacyFunds)(executeParams),
|
|
177
252
|
ueaAddress,
|
|
178
253
|
allowSelfValueCall: true,
|
|
179
254
|
}));
|
|
180
|
-
const inboundPayload = (0,
|
|
181
|
-
nonce: inboundNonce
|
|
255
|
+
const inboundPayload = (0, payload_builders_2.buildInboundUniversalPayloadSvm)(multicallPayload, {
|
|
256
|
+
nonce: inboundNonce ?? BigInt(0),
|
|
182
257
|
});
|
|
183
258
|
return (0, viem_1.hexToBytes)(inboundPayload);
|
|
184
259
|
}
|
|
@@ -187,17 +262,27 @@ function buildR3SvmExtraPayload(ctx, params, ueaAddress, sourceChain, inboundNon
|
|
|
187
262
|
* resolved `ceaAddress` because the native-value self-call branch skips the
|
|
188
263
|
* multicall when the target IS the CEA (avoids a CEA._handleMulticall revert).
|
|
189
264
|
*/
|
|
190
|
-
function buildR2CeaPayloadEvm(params, ceaAddress, targetAddress, targetChain, pushNetwork
|
|
191
|
-
|
|
265
|
+
function buildR2CeaPayloadEvm(params, ceaAddress, targetAddress, targetChain, pushNetwork,
|
|
266
|
+
/**
|
|
267
|
+
* PC20 export only: the destination wrapper address (registered, or predicted
|
|
268
|
+
* for a first export). Destination settlement mints the wrapper into the CEA
|
|
269
|
+
* before running user data, so delivering funds to the requested recipient
|
|
270
|
+
* needs an explicit wrapper.transfer() built here.
|
|
271
|
+
*/
|
|
272
|
+
pc20Wrapper) {
|
|
192
273
|
if (params.migration) {
|
|
193
|
-
return (0,
|
|
274
|
+
return (0, payload_builders_2.buildMigrationPayload)();
|
|
194
275
|
}
|
|
195
276
|
const ceaMulticalls = [];
|
|
196
277
|
const hasData = (0, data_utils_1.hasExecutablePayloadData)(params.data);
|
|
197
|
-
const fundsToken =
|
|
198
|
-
?
|
|
278
|
+
const fundsToken = params.funds?.amount
|
|
279
|
+
? pc20Wrapper
|
|
280
|
+
// A PC20 has no MoveableToken form; the wrapper is an ordinary ERC20 on
|
|
281
|
+
// the destination, so it forwards through the same transfer() path.
|
|
282
|
+
? { address: pc20Wrapper, mechanism: 'approve' }
|
|
283
|
+
: resolveR2DestinationFundsToken(params.funds.token, targetChain, pushNetwork)
|
|
199
284
|
: undefined;
|
|
200
|
-
const nativeFundsValue =
|
|
285
|
+
const nativeFundsValue = params.funds?.amount && fundsToken?.mechanism === 'native'
|
|
201
286
|
? params.funds.amount
|
|
202
287
|
: BigInt(0);
|
|
203
288
|
if (hasData) {
|
|
@@ -208,7 +293,7 @@ function buildR2CeaPayloadEvm(params, ceaAddress, targetAddress, targetChain, pu
|
|
|
208
293
|
// When ERC-20 funds are provided with a single payload, auto-prepend a
|
|
209
294
|
// transfer() call so the tokens minted to the CEA are forwarded to the
|
|
210
295
|
// target address. Mirrors the Route 1 behavior in buildExecuteMulticall.
|
|
211
|
-
if (
|
|
296
|
+
if (params.funds?.amount) {
|
|
212
297
|
if (fundsToken && fundsToken.mechanism !== 'native') {
|
|
213
298
|
const erc20Transfer = (0, viem_1.encodeFunctionData)({
|
|
214
299
|
abi: erc20_evm_1.ERC20_EVM,
|
|
@@ -226,13 +311,13 @@ function buildR2CeaPayloadEvm(params, ceaAddress, targetAddress, targetChain, pu
|
|
|
226
311
|
// contract receives it alongside the payload call.
|
|
227
312
|
ceaMulticalls.push({
|
|
228
313
|
to: targetAddress,
|
|
229
|
-
value: (
|
|
314
|
+
value: (params.value ?? BigInt(0)) + nativeFundsValue,
|
|
230
315
|
data: params.data,
|
|
231
316
|
});
|
|
232
317
|
}
|
|
233
318
|
}
|
|
234
319
|
else {
|
|
235
|
-
if (
|
|
320
|
+
if (params.funds?.amount && fundsToken && fundsToken.mechanism !== 'native') {
|
|
236
321
|
// ERC-20 funds: forward via ERC20.transfer from the CEA.
|
|
237
322
|
const erc20Transfer = (0, viem_1.encodeFunctionData)({
|
|
238
323
|
abi: erc20_evm_1.ERC20_EVM,
|
|
@@ -268,7 +353,7 @@ function buildR2CeaPayloadEvm(params, ceaAddress, targetAddress, targetChain, pu
|
|
|
268
353
|
}
|
|
269
354
|
}
|
|
270
355
|
}
|
|
271
|
-
return (0,
|
|
356
|
+
return (0, payload_builders_2.buildCeaMulticallPayload)(ceaMulticalls);
|
|
272
357
|
}
|
|
273
358
|
const SIZER_HOOK_BY_ROUTE = {
|
|
274
359
|
R3: {
|
|
@@ -287,7 +372,7 @@ const SIZER_HOOK_BY_ROUTE = {
|
|
|
287
372
|
*/
|
|
288
373
|
function fireSizingHook(ctx, route, chain, sizing) {
|
|
289
374
|
const hook = SIZER_HOOK_BY_ROUTE[route][sizing.category];
|
|
290
|
-
const ONE_USD_8D = BigInt(
|
|
375
|
+
const ONE_USD_8D = BigInt(100_000_000);
|
|
291
376
|
const totalDepositUSD = sizing.category === 'A' ? ONE_USD_8D : sizing.gasUsd;
|
|
292
377
|
(0, context_1.fireProgressHook)(ctx, hook, chain, sizing.gasLegNativePc, sizing.overflowNativePc, totalDepositUSD);
|
|
293
378
|
}
|
|
@@ -297,44 +382,42 @@ function fireSizingHook(ctx, route, chain, sizing) {
|
|
|
297
382
|
/**
|
|
298
383
|
* Dispatch a universal execution to the correct route handler.
|
|
299
384
|
*/
|
|
300
|
-
function executeMultiChain(ctx, params, executeFn) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
clientChain: ctx.universalSigner.account.chain,
|
|
305
|
-
});
|
|
306
|
-
// Detect the transaction route
|
|
307
|
-
const route = (0, route_detector_1.detectRoute)(params);
|
|
308
|
-
(0, context_1.printLog)(ctx, `executeMultiChain — detected route: ${route}, params: ${JSON.stringify({
|
|
309
|
-
to: typeof params.to === 'string'
|
|
310
|
-
? params.to
|
|
311
|
-
: { address: params.to.address, chain: params.to.chain },
|
|
312
|
-
from: params.from,
|
|
313
|
-
hasValue: params.value !== undefined,
|
|
314
|
-
hasData: params.data !== undefined,
|
|
315
|
-
hasFunds: params.funds !== undefined,
|
|
316
|
-
}, null, 2)}`);
|
|
317
|
-
let response;
|
|
318
|
-
switch (route) {
|
|
319
|
-
case route_detector_1.TransactionRoute.UOA_TO_PUSH:
|
|
320
|
-
response = yield executeFn((0, helpers_1.toExecuteParams)(params));
|
|
321
|
-
break;
|
|
322
|
-
case route_detector_1.TransactionRoute.UOA_TO_CEA:
|
|
323
|
-
response = yield executeUoaToCea(ctx, params, executeFn);
|
|
324
|
-
break;
|
|
325
|
-
case route_detector_1.TransactionRoute.CEA_TO_PUSH:
|
|
326
|
-
response = yield executeCeaToPush(ctx, params, executeFn);
|
|
327
|
-
break;
|
|
328
|
-
case route_detector_1.TransactionRoute.CEA_TO_CEA:
|
|
329
|
-
response = yield executeCeaToCea(ctx, params, executeFn);
|
|
330
|
-
break;
|
|
331
|
-
default:
|
|
332
|
-
throw new Error(`Unknown transaction route: ${route}`);
|
|
333
|
-
}
|
|
334
|
-
// Set the route on the response for .wait() to use
|
|
335
|
-
response.route = route;
|
|
336
|
-
return response;
|
|
385
|
+
async function executeMultiChain(ctx, params, executeFn) {
|
|
386
|
+
// Validate route parameters
|
|
387
|
+
(0, route_detector_1.validateRouteParams)(params, {
|
|
388
|
+
clientChain: ctx.universalSigner.account.chain,
|
|
337
389
|
});
|
|
390
|
+
// Detect the transaction route
|
|
391
|
+
const route = (0, route_detector_1.detectRoute)(params);
|
|
392
|
+
(0, context_1.printLog)(ctx, `executeMultiChain — detected route: ${route}, params: ${JSON.stringify({
|
|
393
|
+
to: typeof params.to === 'string'
|
|
394
|
+
? params.to
|
|
395
|
+
: { address: params.to.address, chain: params.to.chain },
|
|
396
|
+
from: params.from,
|
|
397
|
+
hasValue: params.value !== undefined,
|
|
398
|
+
hasData: params.data !== undefined,
|
|
399
|
+
hasFunds: params.funds !== undefined,
|
|
400
|
+
}, null, 2)}`);
|
|
401
|
+
let response;
|
|
402
|
+
switch (route) {
|
|
403
|
+
case route_detector_1.TransactionRoute.UOA_TO_PUSH:
|
|
404
|
+
response = await executeFn((0, helpers_1.toExecuteParams)(params));
|
|
405
|
+
break;
|
|
406
|
+
case route_detector_1.TransactionRoute.UOA_TO_CEA:
|
|
407
|
+
response = await executeUoaToCea(ctx, params, executeFn);
|
|
408
|
+
break;
|
|
409
|
+
case route_detector_1.TransactionRoute.CEA_TO_PUSH:
|
|
410
|
+
response = await executeCeaToPush(ctx, params, executeFn);
|
|
411
|
+
break;
|
|
412
|
+
case route_detector_1.TransactionRoute.CEA_TO_CEA:
|
|
413
|
+
response = await executeCeaToCea(ctx, params, executeFn);
|
|
414
|
+
break;
|
|
415
|
+
default:
|
|
416
|
+
throw new Error(`Unknown transaction route: ${route}`);
|
|
417
|
+
}
|
|
418
|
+
// Set the route on the response for .wait() to use
|
|
419
|
+
response.route = route;
|
|
420
|
+
return response;
|
|
338
421
|
}
|
|
339
422
|
// ---------------------------------------------------------------------------
|
|
340
423
|
// executeUoaToCea (Route 2 — EVM)
|
|
@@ -349,257 +432,289 @@ function executeMultiChain(ctx, params, executeFn) {
|
|
|
349
432
|
* The multicall is executed through the normal execute() flow which handles
|
|
350
433
|
* fee-locking on the origin chain and signature verification.
|
|
351
434
|
*/
|
|
352
|
-
function executeUoaToCea(ctx, params, executeFn) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
//
|
|
360
|
-
if (
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
throw new Error(`Invalid Solana address: ${targetAddress}. ` +
|
|
364
|
-
`Expected 0x + 64 hex chars (32 bytes).`);
|
|
365
|
-
}
|
|
366
|
-
const ZERO_32 = ('0x' + '0'.repeat(64));
|
|
367
|
-
if (targetAddress.toLowerCase() === ZERO_32.toLowerCase()) {
|
|
368
|
-
throw new Error(`Cannot send to zero address on Solana. ` +
|
|
369
|
-
`This would result in permanent loss of funds.`);
|
|
370
|
-
}
|
|
435
|
+
async function executeUoaToCea(ctx, params, executeFn) {
|
|
436
|
+
const target = params.to;
|
|
437
|
+
const targetChain = target.chain;
|
|
438
|
+
const targetAddress = target.address;
|
|
439
|
+
const isSvm = (0, payload_builders_2.isSvmChain)(targetChain);
|
|
440
|
+
// Validate target address based on VM type
|
|
441
|
+
if (isSvm) {
|
|
442
|
+
// SVM: 32-byte hex address
|
|
443
|
+
if (!(0, payload_builders_2.isValidSolanaHexAddress)(targetAddress)) {
|
|
444
|
+
throw new Error(`Invalid Solana address: ${targetAddress}. ` +
|
|
445
|
+
`Expected 0x + 64 hex chars (32 bytes).`);
|
|
371
446
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if (targetAddress.toLowerCase() === selectors_1.ZERO_ADDRESS.toLowerCase() && !isMulticall) {
|
|
377
|
-
throw new Error(`Cannot send to zero address (0x0000...0000). ` +
|
|
378
|
-
`This would result in permanent loss of funds.`);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
// Validate chain supports outbound operations
|
|
382
|
-
if (!(0, cea_utils_1.chainSupportsOutbound)(targetChain)) {
|
|
383
|
-
throw new Error(`Chain ${targetChain} does not support outbound operations. ` +
|
|
384
|
-
`Supported chains: BNB_TESTNET, ETHEREUM_SEPOLIA, SOLANA_DEVNET, etc.`);
|
|
385
|
-
}
|
|
386
|
-
// Branch based on VM type
|
|
387
|
-
if (isSvm) {
|
|
388
|
-
return executeUoaToCeaSvm(ctx, params, target, executeFn);
|
|
447
|
+
const ZERO_32 = ('0x' + '0'.repeat(64));
|
|
448
|
+
if (targetAddress.toLowerCase() === ZERO_32.toLowerCase()) {
|
|
449
|
+
throw new Error(`Cannot send to zero address on Solana. ` +
|
|
450
|
+
`This would result in permanent loss of funds.`);
|
|
389
451
|
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
//
|
|
394
|
-
const
|
|
395
|
-
(
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
// Chain-local — see `resolveR2Prc20TokenEvm` above.
|
|
399
|
-
const { prc20Token, burnAmount } = resolveR2Prc20TokenEvm(params, targetChain, ctx.pushNetwork);
|
|
400
|
-
if (params.value && params.value > BigInt(0)) {
|
|
401
|
-
(0, context_1.printLog)(ctx, `executeUoaToCea — auto-selected native PRC-20 ${prc20Token} for chain ${targetChain}, amount: ${burnAmount.toString()}`);
|
|
402
|
-
}
|
|
403
|
-
else if (!params.migration &&
|
|
404
|
-
!((_a = params.funds) === null || _a === void 0 ? void 0 : _a.amount) &&
|
|
405
|
-
(0, data_utils_1.hasExecutablePayloadData)(params.data)) {
|
|
406
|
-
(0, context_1.printLog)(ctx, `executeUoaToCea — PAYLOAD-only: using native PRC-20 ${prc20Token} for chain ${targetChain} with zero burn amount`);
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
// EVM: 20-byte hex address
|
|
455
|
+
// Zero address is allowed for multicall (data is array) — the actual targets are in the data entries.
|
|
456
|
+
const isMulticall = Array.isArray(params.data);
|
|
457
|
+
if (targetAddress.toLowerCase() === selectors_1.ZERO_ADDRESS.toLowerCase() && !isMulticall) {
|
|
458
|
+
throw new Error(`Cannot send to zero address (0x0000...0000). ` +
|
|
459
|
+
`This would result in permanent loss of funds.`);
|
|
407
460
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
461
|
+
}
|
|
462
|
+
// Validate chain supports outbound operations
|
|
463
|
+
if (!(0, cea_utils_1.chainSupportsOutbound)(targetChain)) {
|
|
464
|
+
throw new Error(`Chain ${targetChain} does not support outbound operations. ` +
|
|
465
|
+
`Supported chains: BNB_TESTNET, ETHEREUM_SEPOLIA, SOLANA_DEVNET, etc.`);
|
|
466
|
+
}
|
|
467
|
+
// Branch based on VM type
|
|
468
|
+
if (isSvm) {
|
|
469
|
+
return executeUoaToCeaSvm(ctx, params, target, executeFn);
|
|
470
|
+
}
|
|
471
|
+
// ===== EVM path (existing logic) =====
|
|
472
|
+
// R2 pre-broadcast progress: external chain detected
|
|
473
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_201, targetChain, targetAddress);
|
|
474
|
+
// Compute UEA address (sync, offchain derivation — no RPC).
|
|
475
|
+
const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
|
|
476
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — target chain: ${targetChain}, target address: ${targetAddress}, UEA: ${ueaAddress}`);
|
|
477
|
+
// --- Token resolution (chain-local, before account resolution) ---
|
|
478
|
+
// Resolve the PRC-20 burn token + burn amount ahead of CEA resolution.
|
|
479
|
+
// Chain-local — see `resolveR2Prc20TokenEvm` above.
|
|
480
|
+
const { prc20Token, burnAmount } = resolveR2Prc20TokenEvm(params, targetChain, ctx.pushNetwork);
|
|
481
|
+
if (params.value && params.value > BigInt(0)) {
|
|
482
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — auto-selected native PRC-20 ${prc20Token} for chain ${targetChain}, amount: ${burnAmount.toString()}`);
|
|
483
|
+
}
|
|
484
|
+
else if (!params.migration &&
|
|
485
|
+
!params.funds?.amount &&
|
|
486
|
+
(0, data_utils_1.hasExecutablePayloadData)(params.data)) {
|
|
487
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — PAYLOAD-only: using native PRC-20 ${prc20Token} for chain ${targetChain} with zero burn amount`);
|
|
488
|
+
}
|
|
489
|
+
const gasLimitForQuery = params.gasLimit ?? BigInt(0);
|
|
490
|
+
// --- 202: Gas estimation (spec-ordered before 203) ---
|
|
491
|
+
let gasFee = BigInt(0);
|
|
492
|
+
let protocolFee = BigInt(0);
|
|
493
|
+
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
494
|
+
let universalCoreAddress;
|
|
495
|
+
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
496
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_202_01, targetChain);
|
|
497
|
+
try {
|
|
498
|
+
// PC20 exports must not use getOutboundTxGasAndFees — only
|
|
499
|
+
// getPC20ExportGasAndFees accounts for the first-export deployment
|
|
500
|
+
// overhead, and quoting the wrong one strands the destination transfer.
|
|
501
|
+
const pc20Export = params._pc20;
|
|
502
|
+
const result = pc20Export?.direction === 'export'
|
|
503
|
+
? await (0, export_1.queryPC20OutboundGasFee)(ctx, prc20Token, gasLimitForQuery, targetChain)
|
|
504
|
+
: await (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimitForQuery, targetChain);
|
|
505
|
+
gasFee = result.gasFee;
|
|
506
|
+
protocolFee = result.protocolFee;
|
|
507
|
+
gasToken = result.gasToken;
|
|
508
|
+
universalCoreAddress = result.universalCoreAddress;
|
|
509
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — queried gas fee: ${gasFee.toString()}, gasToken: ${gasToken}`);
|
|
510
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_202_02, targetChain, protocolFee, gasFee);
|
|
428
511
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
const signerChain = ctx.universalSigner.account.chain;
|
|
432
|
-
const isNativePushEOA = (0, helpers_1.isPushChain)(signerChain);
|
|
433
|
-
const [{ cea: ceaAddress, isDeployed: ceaDeployed }, [ueaCode, ueaBalance],] = yield Promise.all([
|
|
434
|
-
(0, cea_utils_1.getCEAAddress)(ueaAddress, targetChain, (_c = ctx.rpcUrls[targetChain]) === null || _c === void 0 ? void 0 : _c[0]),
|
|
435
|
-
Promise.all([
|
|
436
|
-
ctx.pushClient.publicClient.getCode({ address: ueaAddress }),
|
|
437
|
-
ctx.pushClient.getBalance(ueaAddress),
|
|
438
|
-
]),
|
|
439
|
-
]);
|
|
440
|
-
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_203_02, ueaAddress, ceaAddress, targetChain, ceaDeployed);
|
|
441
|
-
(0, context_1.printLog)(ctx, `executeUoaToCea — CEA address: ${ceaAddress}, deployed: ${ceaDeployed}`);
|
|
442
|
-
const isUEADeployed = isNativePushEOA || ueaCode !== undefined;
|
|
443
|
-
const ueaNonce = (!isUEADeployed || isNativePushEOA) ? BigInt(0) : yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress);
|
|
444
|
-
// --- Build CEA payload (requires ceaAddress for self-call check) ---
|
|
445
|
-
// Delegates to `buildR2CeaPayloadEvm` — see helper definition above.
|
|
446
|
-
const ceaPayload = buildR2CeaPayloadEvm(params, ceaAddress, targetAddress, targetChain, ctx.pushNetwork);
|
|
447
|
-
if (params.migration) {
|
|
448
|
-
(0, context_1.printLog)(ctx, `executeUoaToCea — MIGRATION: using raw MIGRATION_SELECTOR payload (${ceaPayload}), native PRC-20 ${prc20Token}`);
|
|
512
|
+
catch (err) {
|
|
513
|
+
throw new Error(`Failed to query outbound gas fee: ${err}`);
|
|
449
514
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
(0,
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
515
|
+
}
|
|
516
|
+
// --- 203: CEA resolution + UEA status (single round-trip burst) ---
|
|
517
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_203_01, targetChain);
|
|
518
|
+
const signerChain = ctx.universalSigner.account.chain;
|
|
519
|
+
const isNativePushEOA = (0, helpers_1.isPushChain)(signerChain);
|
|
520
|
+
const [{ cea: ceaAddress, isDeployed: ceaDeployed }, [ueaCode, ueaBalance],] = await Promise.all([
|
|
521
|
+
(0, cea_utils_1.getCEAAddress)(ueaAddress, targetChain, ctx.rpcUrls[targetChain]?.[0]),
|
|
522
|
+
Promise.all([
|
|
523
|
+
ctx.pushClient.publicClient.getCode({ address: ueaAddress }),
|
|
524
|
+
ctx.pushClient.getBalance(ueaAddress),
|
|
525
|
+
]),
|
|
526
|
+
]);
|
|
527
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_203_02, ueaAddress, ceaAddress, targetChain, ceaDeployed);
|
|
528
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — CEA address: ${ceaAddress}, deployed: ${ceaDeployed}`);
|
|
529
|
+
const isUEADeployed = isNativePushEOA || ueaCode !== undefined;
|
|
530
|
+
const ueaNonce = (!isUEADeployed || isNativePushEOA) ? BigInt(0) : await (0, uea_manager_1.getUEANonce)(ctx, ueaAddress);
|
|
531
|
+
// --- Build CEA payload (requires ceaAddress for self-call check) ---
|
|
532
|
+
// Delegates to `buildR2CeaPayloadEvm` — see helper definition above.
|
|
533
|
+
//
|
|
534
|
+
// PC20 export: resolve the destination wrapper BEFORE anything approves or
|
|
535
|
+
// locks the source token. A first export has no registered wrapper, so the
|
|
536
|
+
// address is predicted from the live registered factory; if that cannot be
|
|
537
|
+
// established this throws here, with nothing yet committed.
|
|
538
|
+
const pc20Descriptor = params._pc20;
|
|
539
|
+
const isPc20Export = pc20Descriptor?.direction === 'export';
|
|
540
|
+
let pc20Wrapper;
|
|
541
|
+
if (isPc20Export && pc20Descriptor) {
|
|
542
|
+
const { wrapper } = await (0, export_1.validatePC20Export)(ctx, pc20Descriptor, targetChain, { network: ctx.pushNetwork, rpcUrls: ctx.rpcUrls });
|
|
543
|
+
pc20Wrapper = wrapper.address;
|
|
544
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — PC20 export: destination wrapper ${pc20Wrapper} ` +
|
|
545
|
+
`(${wrapper.deployed ? 'registered' : 'predicted, first export'})`);
|
|
546
|
+
}
|
|
547
|
+
const ceaPayload = buildR2CeaPayloadEvm(params, ceaAddress, targetAddress, targetChain, ctx.pushNetwork, pc20Wrapper);
|
|
548
|
+
if (params.migration) {
|
|
549
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — MIGRATION: using raw MIGRATION_SELECTOR payload (${ceaPayload}), native PRC-20 ${prc20Token}`);
|
|
550
|
+
}
|
|
551
|
+
// Build outbound request struct for the gateway
|
|
552
|
+
// Empty CEA payloads intentionally use zero recipient to park funds in the
|
|
553
|
+
// CEA under the post-audit single-call guard.
|
|
554
|
+
const targetBytes = ceaPayload === '0x'
|
|
555
|
+
? selectors_1.ZERO_ADDRESS
|
|
556
|
+
: ceaAddress;
|
|
557
|
+
(0, payload_builders_2.assertCeaFundsParkingInvariant)(targetBytes, ceaPayload);
|
|
558
|
+
// PC20 export: the gateway expects the PC20 selector plus the destination
|
|
559
|
+
// wrapper metadata ahead of the user data. Inbound is the mirror image — the
|
|
560
|
+
// external gateway prepends the selector there, so the SDK must not.
|
|
561
|
+
const outboundPayload = isPc20Export && pc20Descriptor
|
|
562
|
+
? (0, export_1.buildPC20ExportPayload)({
|
|
563
|
+
destinationChain: targetChain,
|
|
564
|
+
name: pc20Descriptor.name,
|
|
565
|
+
symbol: pc20Descriptor.symbol,
|
|
566
|
+
decimals: pc20Descriptor.decimals,
|
|
567
|
+
destinationUserData: ceaPayload,
|
|
568
|
+
})
|
|
569
|
+
: ceaPayload;
|
|
570
|
+
const outboundReq = (0, payload_builders_2.buildOutboundRequest)(targetBytes, prc20Token, burnAmount, gasLimitForQuery, outboundPayload, ueaAddress, // revert recipient is the UEA
|
|
571
|
+
params.maxPCForGas ?? BigInt(0));
|
|
572
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — outbound request: ${JSON.stringify({
|
|
573
|
+
recipient: outboundReq.recipient,
|
|
574
|
+
token: outboundReq.token,
|
|
575
|
+
amount: outboundReq.amount.toString(),
|
|
576
|
+
gasLimit: outboundReq.gasLimit.toString(),
|
|
577
|
+
maxPCForGas: outboundReq.maxPCForGas.toString(),
|
|
578
|
+
payloadLength: outboundReq.payload.length,
|
|
579
|
+
revertRecipient: outboundReq.revertRecipient,
|
|
580
|
+
}, null, 2)}`);
|
|
581
|
+
// Get UniversalGatewayPC address
|
|
582
|
+
const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
|
|
583
|
+
// Build the multicall that will execute ON Push Chain from UEA context
|
|
584
|
+
// This includes: 1) approve PRC-20 (if needed), 2) call sendUniversalTxOutbound
|
|
585
|
+
const pushChainMulticalls = [];
|
|
586
|
+
// ---------------------------------------------------------------------
|
|
587
|
+
// Native-value selection — R2 does NOT use Case A/B/C gas abstraction.
|
|
588
|
+
// That feature is scoped to R1 (fee-lock USD caps) and R3 (CEA → Push
|
|
589
|
+
// outbound msg.value). For R2 we size `nativeValueForGas` from the
|
|
590
|
+
// live WPC/gasToken Uniswap V3 pool quote plus a 10% safety buffer;
|
|
591
|
+
// `swapAndBurnGas` refunds any excess as PC back to UEA via
|
|
592
|
+
// refundUnusedGas.
|
|
593
|
+
//
|
|
594
|
+
// Fresh-wallet prediction (below) is retained: it estimates post-fee-lock
|
|
595
|
+
// UPC balance for UEAs that haven't been funded yet, independent of
|
|
596
|
+
// Case A/B/C sizing.
|
|
597
|
+
// ---------------------------------------------------------------------
|
|
598
|
+
const EVM_GAS_RESERVE = BigInt(3e18); // 3 UPC for outer-tx gas
|
|
599
|
+
const ROUTE2_MINIMUM_DEPOSIT_USD = utils_1.Utils.helpers.parseUnits('10', 8); // $10
|
|
600
|
+
// Effective balance: real for deployed UEAs; predicted post-fee-lock for fresh.
|
|
601
|
+
let effectiveBalance = ueaBalance;
|
|
602
|
+
if (!isUEADeployed && effectiveBalance <= EVM_GAS_RESERVE) {
|
|
603
|
+
const ethPrice = await new price_fetch_1.PriceFetch(ctx.rpcUrls).getPrice(signerChain);
|
|
604
|
+
const nativeAmountETH = ethPrice > BigInt(0)
|
|
605
|
+
? (ROUTE2_MINIMUM_DEPOSIT_USD * BigInt(1e18) + (ethPrice - BigInt(1))) / ethPrice + BigInt(1)
|
|
606
|
+
: BigInt(0);
|
|
607
|
+
if (nativeAmountETH > BigInt(0)) {
|
|
608
|
+
const originPrc20 = (0, helpers_1.getNativePRC20ForChain)(signerChain, ctx.pushNetwork);
|
|
609
|
+
const predictedUPC = await (0, gas_calculator_1.estimateDepositFromLockedNative)(ctx, nativeAmountETH, originPrc20);
|
|
610
|
+
if (predictedUPC > BigInt(0)) {
|
|
611
|
+
// 10% safety margin for slippage between quote and actual execution
|
|
612
|
+
effectiveBalance = (predictedUPC * BigInt(90)) / BigInt(100);
|
|
613
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — fresh wallet: Uniswap quote predicts ${predictedUPC.toString()} UPC ` +
|
|
614
|
+
`from ${nativeAmountETH.toString()} wei pETH, using ${effectiveBalance.toString()} (90%)`);
|
|
504
615
|
}
|
|
505
616
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
address: prc20Token,
|
|
531
|
-
abi: erc20_evm_1.ERC20_EVM,
|
|
532
|
-
functionName: 'balanceOf',
|
|
533
|
-
args: [ueaAddress],
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
const requiredNativeBalanceR2Evm = getRequiredNativeBalanceForOutbound(nativeValueForGas, EVM_GAS_RESERVE);
|
|
537
|
-
const skipFeeLockingR2Evm = shouldSkipFeeLockingForOutbound({
|
|
538
|
-
isUEADeployed,
|
|
539
|
-
balance: effectiveBalance,
|
|
540
|
-
nativeValueForGas,
|
|
541
|
-
gasReserve: EVM_GAS_RESERVE,
|
|
542
|
-
});
|
|
543
|
-
(0, preflight_1.runPreflight)({
|
|
544
|
-
ctx,
|
|
545
|
-
ueaAddress,
|
|
546
|
-
ueaBalance: effectiveBalance,
|
|
547
|
-
requiredValue: nativeValueForGas,
|
|
548
|
-
gasReserve: EVM_GAS_RESERVE,
|
|
549
|
-
pathTag: 'R2_EVM',
|
|
550
|
-
burnToken: prc20Token,
|
|
551
|
-
burnAmount,
|
|
552
|
-
prc20Balance: prc20BalanceR2Evm,
|
|
553
|
-
enforceGasCheck: ((_e = params.options) === null || _e === void 0 ? void 0 : _e.enforceGasCheck) === true,
|
|
554
|
-
});
|
|
555
|
-
// Preflight success means nativeValueForGas already covers required + reserve; no adjust needed.
|
|
556
|
-
// Build outbound multicalls (approve burn + sendUniversalTxOutbound with native value)
|
|
557
|
-
const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
558
|
-
prc20Token,
|
|
559
|
-
gasToken,
|
|
560
|
-
burnAmount,
|
|
561
|
-
gasFee,
|
|
562
|
-
nativeValueForGas,
|
|
563
|
-
gatewayPcAddress,
|
|
564
|
-
outboundRequest: outboundReq,
|
|
617
|
+
}
|
|
618
|
+
// estimateNativeValueForSwap applies the full 2.2x safety buffer
|
|
619
|
+
// internally (pool-slippage cushion + the 10% headroom that used to be
|
|
620
|
+
// multiplied here). Folded into one place so the function's cap branch
|
|
621
|
+
// bounds the final value — the previous outer ×110/100 on top of the
|
|
622
|
+
// inner cap broke preflight whenever the cap fired. Over-send is refunded
|
|
623
|
+
// by the contract.
|
|
624
|
+
let nativeValueForGas = BigInt(0);
|
|
625
|
+
if (universalCoreAddress && gasFee > BigInt(0)) {
|
|
626
|
+
nativeValueForGas = await (0, gas_calculator_1.estimateNativeValueForSwap)(ctx, universalCoreAddress, gasToken, gasFee, effectiveBalance);
|
|
627
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — nativeValueForGas=${nativeValueForGas.toString()} (includes 2.2x buffer)`);
|
|
628
|
+
}
|
|
629
|
+
// Pre-flight: warns by default when UEA cannot cover (nativeValueForGas +
|
|
630
|
+
// gasReserve) or the PRC-20 burn amount; throws only when
|
|
631
|
+
// options.enforceGasCheck=true. PRC-20 check fires whenever
|
|
632
|
+
// burnAmount > 0 — user must pre-hold the burn token, even when it equals
|
|
633
|
+
// the gas token (the gateway does transferFrom BEFORE the gas swap).
|
|
634
|
+
let prc20BalanceR2Evm;
|
|
635
|
+
if (burnAmount > BigInt(0)) {
|
|
636
|
+
prc20BalanceR2Evm = await ctx.pushClient.readContract({
|
|
637
|
+
address: prc20Token,
|
|
638
|
+
abi: erc20_evm_1.ERC20_EVM,
|
|
639
|
+
functionName: 'balanceOf',
|
|
640
|
+
args: [ueaAddress],
|
|
565
641
|
});
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
isDeployed: isUEADeployed,
|
|
574
|
-
nonce: ueaNonce,
|
|
575
|
-
balance: ueaBalance,
|
|
576
|
-
}, _skipFeeLocking: skipFeeLockingR2Evm, _requiredFundsOverride: requiredNativeBalanceR2Evm }, (!skipFeeLockingR2Evm ? { _minimumDepositUsd: ROUTE2_MINIMUM_DEPOSIT_USD } : {}));
|
|
577
|
-
// Signature request — user is prompted to sign the universal payload.
|
|
578
|
-
// executeFn is monolithic (sign → broadcast), so we emit the logical
|
|
579
|
-
// post-sign / pre-broadcast markers as a burst after it returns so
|
|
580
|
-
// consumers see the spec-ordered stream: 204-01 → 204-02 → 204-03 → 207.
|
|
581
|
-
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_204_01);
|
|
582
|
-
let response;
|
|
583
|
-
try {
|
|
584
|
-
response = yield executeFn(executeParams);
|
|
585
|
-
}
|
|
586
|
-
catch (err) {
|
|
587
|
-
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_204_04, err instanceof Error ? err.message : String(err));
|
|
588
|
-
// Suppress the outer orchestrator catch's 299-02 terminal — 204-04
|
|
589
|
-
// already surfaced the signature/broadcast failure to the consumer.
|
|
590
|
-
ctx._routeTerminalEmitted = true;
|
|
591
|
-
throw err;
|
|
592
|
-
}
|
|
593
|
-
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_204_02);
|
|
594
|
-
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_204_03);
|
|
595
|
-
// R2 broadcast marker — Push Chain tx has been built, signed, and
|
|
596
|
-
// accepted by the node. Replaces the suppressed 107 emission for this route.
|
|
597
|
-
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_207, targetChain);
|
|
598
|
-
// Add chain info to response
|
|
599
|
-
response.chain = targetChain;
|
|
600
|
-
response.chainNamespace = (0, helpers_1.getChainNamespace)(targetChain);
|
|
601
|
-
return response;
|
|
642
|
+
}
|
|
643
|
+
const requiredNativeBalanceR2Evm = getRequiredNativeBalanceForOutbound(nativeValueForGas, EVM_GAS_RESERVE);
|
|
644
|
+
const skipFeeLockingR2Evm = shouldSkipFeeLockingForOutbound({
|
|
645
|
+
isUEADeployed,
|
|
646
|
+
balance: effectiveBalance,
|
|
647
|
+
nativeValueForGas,
|
|
648
|
+
gasReserve: EVM_GAS_RESERVE,
|
|
602
649
|
});
|
|
650
|
+
(0, preflight_1.runPreflight)({
|
|
651
|
+
ctx,
|
|
652
|
+
ueaAddress,
|
|
653
|
+
ueaBalance: effectiveBalance,
|
|
654
|
+
requiredValue: nativeValueForGas,
|
|
655
|
+
gasReserve: EVM_GAS_RESERVE,
|
|
656
|
+
pathTag: 'R2_EVM',
|
|
657
|
+
burnToken: prc20Token,
|
|
658
|
+
burnAmount,
|
|
659
|
+
prc20Balance: prc20BalanceR2Evm,
|
|
660
|
+
enforceGasCheck: params.options?.enforceGasCheck === true,
|
|
661
|
+
});
|
|
662
|
+
// Preflight success means nativeValueForGas already covers required + reserve; no adjust needed.
|
|
663
|
+
// Build outbound multicalls (approve burn + sendUniversalTxOutbound with native value)
|
|
664
|
+
const outboundMulticalls = (0, payload_builders_2.buildOutboundApprovalAndCall)({
|
|
665
|
+
prc20Token,
|
|
666
|
+
gasToken,
|
|
667
|
+
burnAmount,
|
|
668
|
+
gasFee,
|
|
669
|
+
nativeValueForGas,
|
|
670
|
+
gatewayPcAddress,
|
|
671
|
+
outboundRequest: outboundReq,
|
|
672
|
+
});
|
|
673
|
+
pushChainMulticalls.push(...outboundMulticalls);
|
|
674
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — Push Chain multicall has ${pushChainMulticalls.length} operations`);
|
|
675
|
+
// Execute through the normal execute() flow
|
|
676
|
+
// This handles fee-locking on origin chain and executes the multicall from UEA context
|
|
677
|
+
// Sum native values from multicall entries for proper fee calculation
|
|
678
|
+
const multicallNativeValue = pushChainMulticalls.reduce((sum, mc) => sum + (mc.value ?? BigInt(0)), BigInt(0));
|
|
679
|
+
const executeParams = {
|
|
680
|
+
to: ueaAddress, // multicall executes from UEA
|
|
681
|
+
value: multicallNativeValue, // ensures correct requiredFunds calculation
|
|
682
|
+
data: pushChainMulticalls, // array triggers multicall mode
|
|
683
|
+
_ueaStatus: {
|
|
684
|
+
isDeployed: isUEADeployed,
|
|
685
|
+
nonce: ueaNonce,
|
|
686
|
+
balance: ueaBalance,
|
|
687
|
+
},
|
|
688
|
+
_skipFeeLocking: skipFeeLockingR2Evm,
|
|
689
|
+
_requiredFundsOverride: requiredNativeBalanceR2Evm,
|
|
690
|
+
// Ensure fee-locking deposits enough for the outbound swap whenever it is needed.
|
|
691
|
+
...(!skipFeeLockingR2Evm ? { _minimumDepositUsd: ROUTE2_MINIMUM_DEPOSIT_USD } : {}),
|
|
692
|
+
};
|
|
693
|
+
// Signature request — user is prompted to sign the universal payload.
|
|
694
|
+
// executeFn is monolithic (sign → broadcast), so we emit the logical
|
|
695
|
+
// post-sign / pre-broadcast markers as a burst after it returns so
|
|
696
|
+
// consumers see the spec-ordered stream: 204-01 → 204-02 → 204-03 → 207.
|
|
697
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_204_01);
|
|
698
|
+
let response;
|
|
699
|
+
try {
|
|
700
|
+
response = await executeFn(executeParams);
|
|
701
|
+
}
|
|
702
|
+
catch (err) {
|
|
703
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_204_04, err instanceof Error ? err.message : String(err));
|
|
704
|
+
// Suppress the outer orchestrator catch's 299-02 terminal — 204-04
|
|
705
|
+
// already surfaced the signature/broadcast failure to the consumer.
|
|
706
|
+
ctx._routeTerminalEmitted = true;
|
|
707
|
+
throw err;
|
|
708
|
+
}
|
|
709
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_204_02);
|
|
710
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_204_03);
|
|
711
|
+
// R2 broadcast marker — Push Chain tx has been built, signed, and
|
|
712
|
+
// accepted by the node. Replaces the suppressed 107 emission for this route.
|
|
713
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_207, targetChain);
|
|
714
|
+
// Add chain info to response
|
|
715
|
+
response.chain = targetChain;
|
|
716
|
+
response.chainNamespace = (0, helpers_1.getChainNamespace)(targetChain);
|
|
717
|
+
return response;
|
|
603
718
|
}
|
|
604
719
|
// ---------------------------------------------------------------------------
|
|
605
720
|
// executeUoaToCeaSvm (Route 2 — SVM)
|
|
@@ -612,234 +727,324 @@ function executeUoaToCea(ctx, params, executeFn) {
|
|
|
612
727
|
* 2. Withdraw SPL: Burn PRC-20 on Push Chain, recipient gets SPL token
|
|
613
728
|
* 3. Execute (CPI): Burn pSOL + execute CPI on target Solana program
|
|
614
729
|
*/
|
|
615
|
-
function executeUoaToCeaSvm(ctx, params, target, executeFn) {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
});
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
//
|
|
653
|
-
//
|
|
654
|
-
//
|
|
655
|
-
//
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
if (
|
|
661
|
-
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_202_01, targetChain);
|
|
730
|
+
async function executeUoaToCeaSvm(ctx, params, target, executeFn) {
|
|
731
|
+
const targetChain = target.chain;
|
|
732
|
+
const targetAddress = target.address; // 0x-prefixed, 32 bytes
|
|
733
|
+
const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
|
|
734
|
+
// R2 pre-broadcast progress (SVM) — external chain detected
|
|
735
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_201, targetChain, targetAddress);
|
|
736
|
+
const { svmPayload, targetBytes, hasExecute: hasSvmExecute, } = (0, build_payload_1.buildSvmPayloadFromParams)({
|
|
737
|
+
data: params.data,
|
|
738
|
+
to: target,
|
|
739
|
+
senderUea: ueaAddress,
|
|
740
|
+
});
|
|
741
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — target: ${targetAddress}, chain: ${targetChain}, ` +
|
|
742
|
+
`hasSvmExecute: ${hasSvmExecute}, value: ${params.value?.toString() ?? '0'}`);
|
|
743
|
+
if (hasSvmExecute) {
|
|
744
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — encoded execute payload: ${(svmPayload.length - 2) / 2} bytes`);
|
|
745
|
+
}
|
|
746
|
+
(0, payload_builders_2.assertSvmPayloadWithinRelayLimit)(svmPayload, `Route 2 SVM ${targetChain}`);
|
|
747
|
+
// --- PC20 export to Solana ---
|
|
748
|
+
// The destination wrapper is the deterministic pc20_mint PDA; resolving it
|
|
749
|
+
// here (pre-approval) also tells us whether this is a first export, which
|
|
750
|
+
// drives the rent floor below. The generic SVM payload is replaced by the
|
|
751
|
+
// PC20 payload: selector || abi(namespace,name,symbol,decimals) || userData.
|
|
752
|
+
//
|
|
753
|
+
// v1 ships funds-only with EMPTY userData: settlement mints into the
|
|
754
|
+
// sender's CEA ATA (pc20.rs id=5 mints to cea_ata and only userData moves
|
|
755
|
+
// funds onward — and a recipient ATA cannot exist before the mint does on a
|
|
756
|
+
// first export). Tokens in the CEA ATA remain under the sender's control and
|
|
757
|
+
// are retrievable via the existing R2 execute path. Recipient-ATA delivery
|
|
758
|
+
// userData lands with the repeat-export policy.
|
|
759
|
+
const svmPc20Descriptor = params._pc20;
|
|
760
|
+
const isSvmPc20Export = svmPc20Descriptor?.direction === 'export';
|
|
761
|
+
let svmPc20FirstExport = false;
|
|
762
|
+
let pc20SvmPayload = svmPayload;
|
|
763
|
+
let pc20TargetBytes = targetBytes;
|
|
764
|
+
if (isSvmPc20Export && svmPc20Descriptor) {
|
|
765
|
+
const { wrapper } = await (0, export_1.validatePC20Export)(ctx, svmPc20Descriptor, targetChain, { network: ctx.pushNetwork, rpcUrls: ctx.rpcUrls });
|
|
766
|
+
svmPc20FirstExport = !wrapper.deployed;
|
|
767
|
+
// Delivery: settlement mints into the CEA ATA and CPIs exactly one
|
|
768
|
+
// instruction from userData. When the recipient's ATA for the wrapper
|
|
769
|
+
// mint already exists, that instruction is an SPL TransferChecked to it.
|
|
770
|
+
// Otherwise userData stays empty and funds land in the sender's CEA ATA
|
|
771
|
+
// (retrievable via R2 execute) — a first export can never deliver, since
|
|
772
|
+
// an ATA cannot exist before its mint does.
|
|
773
|
+
let deliveryUserData = '0x';
|
|
774
|
+
let deliveryNote = 'funds-only → CEA ATA';
|
|
775
|
+
if (wrapper.deployed) {
|
|
662
776
|
try {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
777
|
+
const { PublicKey, Connection } = await Promise.resolve().then(() => __importStar(require('@solana/web3.js')));
|
|
778
|
+
const { buildPC20SvmDeliveryFields } = await Promise.resolve().then(() => __importStar(require('./pc20/svm')));
|
|
779
|
+
const mint = new PublicKey(wrapper.address);
|
|
780
|
+
const ceaAuthority = (0, svm_rent_2.deriveSvmCeaPda)(ueaAddress);
|
|
781
|
+
const recipientPk = new PublicKey(Buffer.from(targetAddress.slice(2), 'hex'));
|
|
782
|
+
const recipientAta = (0, svm_rent_2.deriveAtaPubkey)(recipientPk, mint);
|
|
783
|
+
const rpc = ctx.rpcUrls[targetChain]?.[0] ?? chain_1.CHAIN_INFO[targetChain].defaultRPC[0];
|
|
784
|
+
// 'confirmed' commitment: an ATA created moments ago must count.
|
|
785
|
+
// Settlement lands ~a minute later, so anything confirmed now is
|
|
786
|
+
// final long before the CPI runs; probing at 'finalized' would
|
|
787
|
+
// misroute fresh ATAs to the CEA fallback.
|
|
788
|
+
const ataInfo = await new Connection(rpc, 'confirmed').getAccountInfo(recipientAta);
|
|
789
|
+
if (ataInfo) {
|
|
790
|
+
const fields = buildPC20SvmDeliveryFields({
|
|
791
|
+
mint,
|
|
792
|
+
ceaAta: (0, svm_rent_2.deriveAtaPubkey)(ceaAuthority, mint),
|
|
793
|
+
recipientAta,
|
|
794
|
+
ceaAuthority,
|
|
795
|
+
// The PC20 gate guarantees funds.amount > 0 for an export;
|
|
796
|
+
// resolveR2Prc20TokenSvm (which defines burnAmount) runs later.
|
|
797
|
+
amount: params.funds.amount,
|
|
798
|
+
decimals: svmPc20Descriptor.decimals,
|
|
799
|
+
});
|
|
800
|
+
deliveryUserData = (0, payload_builders_1.encodeSvmExecutePayload)(fields);
|
|
801
|
+
deliveryNote = `delivering to recipient ATA ${recipientAta.toBase58()}`;
|
|
802
|
+
}
|
|
803
|
+
else {
|
|
804
|
+
deliveryNote = `recipient ATA ${recipientAta.toBase58()} missing → CEA ATA`;
|
|
686
805
|
}
|
|
687
|
-
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — queried gas fee: ${gasFee.toString()}, gasToken: ${gasToken}`);
|
|
688
|
-
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_202_02, targetChain, protocolFeeSvm, gasFee);
|
|
689
806
|
}
|
|
690
807
|
catch (err) {
|
|
691
|
-
|
|
808
|
+
// Delivery is an optimization, not a requirement — funds in the CEA
|
|
809
|
+
// ATA stay under the sender's control. Never fail the export over it.
|
|
810
|
+
deliveryNote = `ATA probe failed (${err instanceof Error ? err.message : String(err)}) → CEA ATA`;
|
|
692
811
|
}
|
|
693
812
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
// targetBytes: for execute = program id (resolver output); for withdraw = to.address
|
|
701
|
-
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetBytes, prc20Token, burnAmount, effectiveGasLimit, svmPayload, ueaAddress, // revert recipient is the UEA
|
|
702
|
-
(_f = params.maxPCForGas) !== null && _f !== void 0 ? _f : BigInt(0));
|
|
703
|
-
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — outbound request: ${JSON.stringify({
|
|
704
|
-
recipient: outboundReq.recipient,
|
|
705
|
-
token: outboundReq.token,
|
|
706
|
-
amount: outboundReq.amount.toString(),
|
|
707
|
-
gasLimit: outboundReq.gasLimit.toString(),
|
|
708
|
-
maxPCForGas: outboundReq.maxPCForGas.toString(),
|
|
709
|
-
payloadLength: (outboundReq.payload.length - 2) / 2,
|
|
710
|
-
revertRecipient: outboundReq.revertRecipient,
|
|
711
|
-
}, null, 2)}`);
|
|
712
|
-
// --- Pre-fetch UEA status — balance is needed for gas value calculation ---
|
|
713
|
-
const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
|
|
714
|
-
const signerChainSvm = ctx.universalSigner.account.chain;
|
|
715
|
-
const isNativePushEOASvm = (0, helpers_1.isPushChain)(signerChainSvm);
|
|
716
|
-
// Skip getCode if accountStatusCache already confirmed deployment
|
|
717
|
-
const deployedHintSvm = (_h = (_g = ctx.accountStatusCache) === null || _g === void 0 ? void 0 : _g.uea) === null || _h === void 0 ? void 0 : _h.deployed;
|
|
718
|
-
let ueaBalance;
|
|
719
|
-
let isUEADeployed;
|
|
720
|
-
if (deployedHintSvm || isNativePushEOASvm) {
|
|
721
|
-
isUEADeployed = true;
|
|
722
|
-
ueaBalance = yield ctx.pushClient.getBalance(ueaAddress);
|
|
723
|
-
}
|
|
724
|
-
else {
|
|
725
|
-
const [ueaCode, balance] = yield Promise.all([
|
|
726
|
-
ctx.pushClient.publicClient.getCode({ address: ueaAddress }),
|
|
727
|
-
ctx.pushClient.getBalance(ueaAddress),
|
|
728
|
-
]);
|
|
729
|
-
isUEADeployed = ueaCode !== undefined;
|
|
730
|
-
ueaBalance = balance;
|
|
731
|
-
}
|
|
732
|
-
const ueaNonce = (!isUEADeployed || isNativePushEOASvm) ? BigInt(0) : yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress);
|
|
733
|
-
// R2 does NOT use Case A/B/C gas abstraction (scoped to R1 + R3 only).
|
|
734
|
-
// Size msg.value from the live WPC/gasToken Uniswap V3 pool quote. The
|
|
735
|
-
// 2.2x safety buffer is applied inside estimateNativeValueForSwap (was
|
|
736
|
-
// split as inner 2x + outer 10% — combined to keep the cap branch
|
|
737
|
-
// mathematically correct). swapAndBurnGas refunds the excess as PC via
|
|
738
|
-
// refundUnusedGas.
|
|
739
|
-
//
|
|
740
|
-
// SVM pools (WPC/pSOL) are thinner than EVM pools so the pool-quote can
|
|
741
|
-
// easily exceed the UEA's balance. Pre-flight (`runPreflight`) handles
|
|
742
|
-
// drain protection: an under-funded quote throws cleanly with
|
|
743
|
-
// `InsufficientUEABalanceError` and no cosmos tx is submitted. The
|
|
744
|
-
// SVM_NATIVE_VALUE_WARN_THRESHOLD below is telemetry-only; it does NOT
|
|
745
|
-
// truncate `nativeValueForGas`. v2 may add a hard cap once telemetry
|
|
746
|
-
// justifies a number — until then the original engineer's pool-skew
|
|
747
|
-
// warning still applies (capping risks false-positive truncation of
|
|
748
|
-
// legitimate quotes).
|
|
749
|
-
const SVM_GAS_RESERVE = BigInt(3e18); // 3 UPC for outer-tx gas
|
|
750
|
-
let nativeValueForGas = BigInt(0);
|
|
751
|
-
if (universalCoreAddress && gasFee > BigInt(0)) {
|
|
752
|
-
// 2.2x safety buffer is applied inside estimateNativeValueForSwap so
|
|
753
|
-
// the function's balance-cap branch bounds the final value correctly.
|
|
754
|
-
nativeValueForGas = yield (0, gas_calculator_1.estimateNativeValueForSwap)(ctx, universalCoreAddress, gasToken, gasFee, ueaBalance);
|
|
755
|
-
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — nativeValueForGas=${nativeValueForGas.toString()} (includes 2.2x buffer)`);
|
|
756
|
-
(0, preflight_1.maybeFireSvmWarnThreshold)(ctx, nativeValueForGas, gasToken, 'R2_SVM');
|
|
757
|
-
}
|
|
758
|
-
// Pre-flight: warns by default when UEA cannot cover (nativeValueForGas +
|
|
759
|
-
// SVM_GAS_RESERVE) or the PRC-20 burn amount; throws only when
|
|
760
|
-
// options.enforceGasCheck=true.
|
|
761
|
-
// PRC-20 burn-balance pre-check fires whenever `burnAmount > 0`, regardless
|
|
762
|
-
// of whether `prc20Token` happens to equal `gasToken`. The gateway's
|
|
763
|
-
// `sendUniversalTxOutbound` does `transferFrom(user, gateway, burnAmount)`
|
|
764
|
-
// BEFORE the gas swap, so user must pre-hold `burnAmount` of `prc20Token`
|
|
765
|
-
// even when it's the same asset as the gas token (verified empirically:
|
|
766
|
-
// EOA with 77k units of pSOL reverted on-chain when trying to burn 10M).
|
|
767
|
-
let prc20BalanceR2Svm;
|
|
768
|
-
if (burnAmount > BigInt(0)) {
|
|
769
|
-
prc20BalanceR2Svm = yield ctx.pushClient.readContract({
|
|
770
|
-
address: prc20Token,
|
|
771
|
-
abi: erc20_evm_1.ERC20_EVM,
|
|
772
|
-
functionName: 'balanceOf',
|
|
773
|
-
args: [ueaAddress],
|
|
774
|
-
});
|
|
775
|
-
}
|
|
776
|
-
const requiredNativeBalanceR2Svm = getRequiredNativeBalanceForOutbound(nativeValueForGas, SVM_GAS_RESERVE);
|
|
777
|
-
const skipFeeLockingR2Svm = shouldSkipFeeLockingForOutbound({
|
|
778
|
-
isUEADeployed,
|
|
779
|
-
balance: ueaBalance,
|
|
780
|
-
nativeValueForGas,
|
|
781
|
-
gasReserve: SVM_GAS_RESERVE,
|
|
782
|
-
});
|
|
783
|
-
(0, preflight_1.runPreflight)({
|
|
784
|
-
ctx,
|
|
785
|
-
ueaAddress,
|
|
786
|
-
ueaBalance,
|
|
787
|
-
requiredValue: nativeValueForGas,
|
|
788
|
-
gasReserve: SVM_GAS_RESERVE,
|
|
789
|
-
pathTag: 'R2_SVM',
|
|
790
|
-
burnToken: prc20Token,
|
|
791
|
-
burnAmount,
|
|
792
|
-
prc20Balance: prc20BalanceR2Svm,
|
|
793
|
-
enforceGasCheck: ((_j = params.options) === null || _j === void 0 ? void 0 : _j.enforceGasCheck) === true,
|
|
794
|
-
});
|
|
795
|
-
// --- Build Push Chain multicalls (approve + sendUniversalTxOutbound) ---
|
|
796
|
-
// Reuse the same builder as EVM — this part is identical
|
|
797
|
-
const pushChainMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
798
|
-
prc20Token,
|
|
799
|
-
gasToken,
|
|
800
|
-
burnAmount,
|
|
801
|
-
gasFee,
|
|
802
|
-
nativeValueForGas,
|
|
803
|
-
gatewayPcAddress,
|
|
804
|
-
outboundRequest: outboundReq,
|
|
813
|
+
pc20SvmPayload = (0, export_1.buildPC20ExportPayload)({
|
|
814
|
+
destinationChain: targetChain,
|
|
815
|
+
name: svmPc20Descriptor.name,
|
|
816
|
+
symbol: svmPc20Descriptor.symbol,
|
|
817
|
+
decimals: svmPc20Descriptor.decimals,
|
|
818
|
+
destinationUserData: deliveryUserData,
|
|
805
819
|
});
|
|
806
|
-
|
|
807
|
-
//
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
820
|
+
// Outbound recipient: the requested Solana address (0x-hex 32B, already
|
|
821
|
+
// normalized by the route detector). The validator parses it as a pubkey
|
|
822
|
+
// for the id=5 TSS message.
|
|
823
|
+
pc20TargetBytes = targetAddress;
|
|
824
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — PC20 export: wrapper mint ${wrapper.address} ` +
|
|
825
|
+
`(${wrapper.deployed ? 'exists' : 'first export'}), ${deliveryNote}`);
|
|
826
|
+
}
|
|
827
|
+
// --- Determine PRC-20 token and burn amount (chain-local, pre-203) ---
|
|
828
|
+
const { prc20Token, burnAmount } = resolveR2Prc20TokenSvm(params, targetChain, ctx.pushNetwork, hasSvmExecute);
|
|
829
|
+
if (params.value && params.value > BigInt(0)) {
|
|
830
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — auto-selected native PRC-20 ${prc20Token} for chain ${targetChain}, amount: ${burnAmount.toString()}`);
|
|
831
|
+
}
|
|
832
|
+
else if (!params.funds?.amount && hasSvmExecute) {
|
|
833
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — EXECUTE-only: using PRC-20 ${prc20Token} with zero burn amount`);
|
|
834
|
+
}
|
|
835
|
+
// --- 202: Gas estimation (spec-ordered before 203) ---
|
|
836
|
+
let gasFee = BigInt(0);
|
|
837
|
+
let protocolFeeSvm = BigInt(0);
|
|
838
|
+
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
839
|
+
let universalCoreAddress;
|
|
840
|
+
// Effective gas limit seeded from the user param. When the user omits
|
|
841
|
+
// gasLimit, it'll be derived from the gas quote so the on-chain record
|
|
842
|
+
// carries a non-zero compute budget the relay can use.
|
|
843
|
+
let effectiveGasLimit = params.gasLimit ?? BigInt(0);
|
|
844
|
+
// SVM finalize budget: the Solana gateway requires gas_fee to cover the
|
|
845
|
+
// relayer signature fee, ExecutedSubTx rent, a compute buffer, and
|
|
846
|
+
// conditionally CEA ATA rent for SPL outbounds. The on-chain quote can be
|
|
847
|
+
// below that floor, so we compare the quoted fee in lamports and re-query
|
|
848
|
+
// with a bumped gasLimit when required.
|
|
849
|
+
const svmFundsToken = burnAmount > BigInt(0)
|
|
850
|
+
? params.funds?.token
|
|
851
|
+
: undefined;
|
|
852
|
+
const splMintBase58 = svmFundsToken?.mechanism === 'approve' ? svmFundsToken.address : undefined;
|
|
853
|
+
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
854
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_202_01, targetChain);
|
|
825
855
|
try {
|
|
826
|
-
|
|
856
|
+
// PC20 exports quote via getPC20ExportGasAndFees — the only quote that
|
|
857
|
+
// includes first-export overhead (mint + state + ATA rent on Solana).
|
|
858
|
+
let result = isSvmPc20Export
|
|
859
|
+
? await (0, export_1.queryPC20OutboundGasFee)(ctx, prc20Token, effectiveGasLimit, targetChain)
|
|
860
|
+
: await (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, effectiveGasLimit, targetChain);
|
|
861
|
+
result = await (0, svm_rent_1.ensureSvmFinalizeGasBudgetQuote)({
|
|
862
|
+
ctx,
|
|
863
|
+
ueaAddress,
|
|
864
|
+
targetChain,
|
|
865
|
+
prc20Token,
|
|
866
|
+
quote: result,
|
|
867
|
+
splMintBase58,
|
|
868
|
+
burnAmount,
|
|
869
|
+
pathTag: 'executeUoaToCeaSvm',
|
|
870
|
+
// PC20: id=5 rent floor (CEA ATA + mint/state on first export), and the
|
|
871
|
+
// bump path must re-quote via getPC20ExportGasAndFees — the default
|
|
872
|
+
// requote reverts for a token with no PRC20 config.
|
|
873
|
+
...(isSvmPc20Export
|
|
874
|
+
? {
|
|
875
|
+
pc20: { isFirstExport: svmPc20FirstExport },
|
|
876
|
+
requoteFn: (gl) => (0, export_1.queryPC20OutboundGasFee)(ctx, prc20Token, gl, targetChain),
|
|
877
|
+
}
|
|
878
|
+
: {}),
|
|
879
|
+
});
|
|
880
|
+
gasFee = result.gasFee;
|
|
881
|
+
protocolFeeSvm = result.protocolFee;
|
|
882
|
+
gasToken = result.gasToken;
|
|
883
|
+
universalCoreAddress = result.universalCoreAddress;
|
|
884
|
+
// When user omits gasLimit (sent as 0), the contract computes fees using
|
|
885
|
+
// its internal baseGasLimitByChainNamespace. But the relay reads the
|
|
886
|
+
// stored gasLimit from the outbound record. Store the resolved value from
|
|
887
|
+
// UniversalCore so the relayer has a non-zero SVM budget.
|
|
888
|
+
effectiveGasLimit = result.gasLimitUsed;
|
|
889
|
+
if (!params.gasLimit) {
|
|
890
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — resolved effectiveGasLimit: ${effectiveGasLimit} ` +
|
|
891
|
+
`(gasFee=${result.gasFee}, gasPrice=${result.gasPrice})`);
|
|
892
|
+
}
|
|
893
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — queried gas fee: ${gasFee.toString()}, gasToken: ${gasToken}`);
|
|
894
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_202_02, targetChain, protocolFeeSvm, gasFee);
|
|
827
895
|
}
|
|
828
896
|
catch (err) {
|
|
829
|
-
|
|
830
|
-
// Suppress the outer orchestrator catch's 299-02 terminal.
|
|
831
|
-
ctx._routeTerminalEmitted = true;
|
|
832
|
-
throw err;
|
|
897
|
+
throw new Error(`Failed to query outbound gas fee: ${err}`);
|
|
833
898
|
}
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
899
|
+
}
|
|
900
|
+
// --- 203: Execution-account resolution (SVM has a trivial CEA — the
|
|
901
|
+
// target address IS the CEA, no async lookup needed). Fire the burst
|
|
902
|
+
// after 202 to keep the progress-hook stream in spec order. ---
|
|
903
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_203_01, targetChain);
|
|
904
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_203_02, ueaAddress, targetAddress, targetChain, true);
|
|
905
|
+
// --- Build outbound request ---
|
|
906
|
+
// targetBytes: for execute = program id (resolver output); for withdraw = to.address
|
|
907
|
+
const outboundReq = (0, payload_builders_2.buildOutboundRequest)(pc20TargetBytes, prc20Token, burnAmount, effectiveGasLimit, pc20SvmPayload, ueaAddress, // revert recipient is the UEA
|
|
908
|
+
params.maxPCForGas ?? BigInt(0));
|
|
909
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — outbound request: ${JSON.stringify({
|
|
910
|
+
recipient: outboundReq.recipient,
|
|
911
|
+
token: outboundReq.token,
|
|
912
|
+
amount: outboundReq.amount.toString(),
|
|
913
|
+
gasLimit: outboundReq.gasLimit.toString(),
|
|
914
|
+
maxPCForGas: outboundReq.maxPCForGas.toString(),
|
|
915
|
+
payloadLength: (outboundReq.payload.length - 2) / 2,
|
|
916
|
+
revertRecipient: outboundReq.revertRecipient,
|
|
917
|
+
}, null, 2)}`);
|
|
918
|
+
// --- Pre-fetch UEA status — balance is needed for gas value calculation ---
|
|
919
|
+
const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
|
|
920
|
+
const signerChainSvm = ctx.universalSigner.account.chain;
|
|
921
|
+
const isNativePushEOASvm = (0, helpers_1.isPushChain)(signerChainSvm);
|
|
922
|
+
// Skip getCode if accountStatusCache already confirmed deployment
|
|
923
|
+
const deployedHintSvm = ctx.accountStatusCache?.uea?.deployed;
|
|
924
|
+
let ueaBalance;
|
|
925
|
+
let isUEADeployed;
|
|
926
|
+
if (deployedHintSvm || isNativePushEOASvm) {
|
|
927
|
+
isUEADeployed = true;
|
|
928
|
+
ueaBalance = await ctx.pushClient.getBalance(ueaAddress);
|
|
929
|
+
}
|
|
930
|
+
else {
|
|
931
|
+
const [ueaCode, balance] = await Promise.all([
|
|
932
|
+
ctx.pushClient.publicClient.getCode({ address: ueaAddress }),
|
|
933
|
+
ctx.pushClient.getBalance(ueaAddress),
|
|
934
|
+
]);
|
|
935
|
+
isUEADeployed = ueaCode !== undefined;
|
|
936
|
+
ueaBalance = balance;
|
|
937
|
+
}
|
|
938
|
+
const ueaNonce = (!isUEADeployed || isNativePushEOASvm) ? BigInt(0) : await (0, uea_manager_1.getUEANonce)(ctx, ueaAddress);
|
|
939
|
+
// R2 does NOT use Case A/B/C gas abstraction (scoped to R1 + R3 only).
|
|
940
|
+
// Size msg.value from the live WPC/gasToken Uniswap V3 pool quote. The
|
|
941
|
+
// 2.2x safety buffer is applied inside estimateNativeValueForSwap (was
|
|
942
|
+
// split as inner 2x + outer 10% — combined to keep the cap branch
|
|
943
|
+
// mathematically correct). swapAndBurnGas refunds the excess as PC via
|
|
944
|
+
// refundUnusedGas.
|
|
945
|
+
//
|
|
946
|
+
// SVM pools (WPC/pSOL) are thinner than EVM pools so the pool-quote can
|
|
947
|
+
// easily exceed the UEA's balance. Pre-flight (`runPreflight`) handles
|
|
948
|
+
// drain protection: an under-funded quote throws cleanly with
|
|
949
|
+
// `InsufficientUEABalanceError` and no cosmos tx is submitted. The
|
|
950
|
+
// SVM_NATIVE_VALUE_WARN_THRESHOLD below is telemetry-only; it does NOT
|
|
951
|
+
// truncate `nativeValueForGas`. v2 may add a hard cap once telemetry
|
|
952
|
+
// justifies a number — until then the original engineer's pool-skew
|
|
953
|
+
// warning still applies (capping risks false-positive truncation of
|
|
954
|
+
// legitimate quotes).
|
|
955
|
+
const SVM_GAS_RESERVE = BigInt(3e18); // 3 UPC for outer-tx gas
|
|
956
|
+
let nativeValueForGas = BigInt(0);
|
|
957
|
+
if (universalCoreAddress && gasFee > BigInt(0)) {
|
|
958
|
+
// 2.2x safety buffer is applied inside estimateNativeValueForSwap so
|
|
959
|
+
// the function's balance-cap branch bounds the final value correctly.
|
|
960
|
+
nativeValueForGas = await (0, gas_calculator_1.estimateNativeValueForSwap)(ctx, universalCoreAddress, gasToken, gasFee, ueaBalance);
|
|
961
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — nativeValueForGas=${nativeValueForGas.toString()} (includes 2.2x buffer)`);
|
|
962
|
+
(0, preflight_1.maybeFireSvmWarnThreshold)(ctx, nativeValueForGas, gasToken, 'R2_SVM');
|
|
963
|
+
}
|
|
964
|
+
// Pre-flight: warns by default when UEA cannot cover (nativeValueForGas +
|
|
965
|
+
// SVM_GAS_RESERVE) or the PRC-20 burn amount; throws only when
|
|
966
|
+
// options.enforceGasCheck=true.
|
|
967
|
+
// PRC-20 burn-balance pre-check fires whenever `burnAmount > 0`, regardless
|
|
968
|
+
// of whether `prc20Token` happens to equal `gasToken`. The gateway's
|
|
969
|
+
// `sendUniversalTxOutbound` does `transferFrom(user, gateway, burnAmount)`
|
|
970
|
+
// BEFORE the gas swap, so user must pre-hold `burnAmount` of `prc20Token`
|
|
971
|
+
// even when it's the same asset as the gas token (verified empirically:
|
|
972
|
+
// EOA with 77k units of pSOL reverted on-chain when trying to burn 10M).
|
|
973
|
+
let prc20BalanceR2Svm;
|
|
974
|
+
if (burnAmount > BigInt(0)) {
|
|
975
|
+
prc20BalanceR2Svm = await ctx.pushClient.readContract({
|
|
976
|
+
address: prc20Token,
|
|
977
|
+
abi: erc20_evm_1.ERC20_EVM,
|
|
978
|
+
functionName: 'balanceOf',
|
|
979
|
+
args: [ueaAddress],
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
const requiredNativeBalanceR2Svm = getRequiredNativeBalanceForOutbound(nativeValueForGas, SVM_GAS_RESERVE);
|
|
983
|
+
const skipFeeLockingR2Svm = shouldSkipFeeLockingForOutbound({
|
|
984
|
+
isUEADeployed,
|
|
985
|
+
balance: ueaBalance,
|
|
986
|
+
nativeValueForGas,
|
|
987
|
+
gasReserve: SVM_GAS_RESERVE,
|
|
988
|
+
});
|
|
989
|
+
(0, preflight_1.runPreflight)({
|
|
990
|
+
ctx,
|
|
991
|
+
ueaAddress,
|
|
992
|
+
ueaBalance,
|
|
993
|
+
requiredValue: nativeValueForGas,
|
|
994
|
+
gasReserve: SVM_GAS_RESERVE,
|
|
995
|
+
pathTag: 'R2_SVM',
|
|
996
|
+
burnToken: prc20Token,
|
|
997
|
+
burnAmount,
|
|
998
|
+
prc20Balance: prc20BalanceR2Svm,
|
|
999
|
+
enforceGasCheck: params.options?.enforceGasCheck === true,
|
|
842
1000
|
});
|
|
1001
|
+
// --- Build Push Chain multicalls (approve + sendUniversalTxOutbound) ---
|
|
1002
|
+
// Reuse the same builder as EVM — this part is identical
|
|
1003
|
+
const pushChainMulticalls = (0, payload_builders_2.buildOutboundApprovalAndCall)({
|
|
1004
|
+
prc20Token,
|
|
1005
|
+
gasToken,
|
|
1006
|
+
burnAmount,
|
|
1007
|
+
gasFee,
|
|
1008
|
+
nativeValueForGas,
|
|
1009
|
+
gatewayPcAddress,
|
|
1010
|
+
outboundRequest: outboundReq,
|
|
1011
|
+
});
|
|
1012
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — Push Chain multicall has ${pushChainMulticalls.length} operations`);
|
|
1013
|
+
// Sum native values from multicall entries for proper fee calculation
|
|
1014
|
+
const multicallNativeValue = pushChainMulticalls.reduce((sum, mc) => sum + (mc.value ?? BigInt(0)), BigInt(0));
|
|
1015
|
+
const executeParams = {
|
|
1016
|
+
to: ueaAddress,
|
|
1017
|
+
value: multicallNativeValue, // ensures correct requiredFunds calculation
|
|
1018
|
+
data: pushChainMulticalls,
|
|
1019
|
+
_ueaStatus: {
|
|
1020
|
+
isDeployed: isUEADeployed,
|
|
1021
|
+
nonce: ueaNonce,
|
|
1022
|
+
balance: ueaBalance,
|
|
1023
|
+
},
|
|
1024
|
+
_skipFeeLocking: skipFeeLockingR2Svm,
|
|
1025
|
+
_requiredFundsOverride: requiredNativeBalanceR2Svm,
|
|
1026
|
+
};
|
|
1027
|
+
// Signature request — user is prompted to sign the universal payload.
|
|
1028
|
+
// Emit spec-ordered burst after executeFn returns (sign → verify → broadcast).
|
|
1029
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_204_01);
|
|
1030
|
+
let response;
|
|
1031
|
+
try {
|
|
1032
|
+
response = await executeFn(executeParams);
|
|
1033
|
+
}
|
|
1034
|
+
catch (err) {
|
|
1035
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_204_04, err instanceof Error ? err.message : String(err));
|
|
1036
|
+
// Suppress the outer orchestrator catch's 299-02 terminal.
|
|
1037
|
+
ctx._routeTerminalEmitted = true;
|
|
1038
|
+
throw err;
|
|
1039
|
+
}
|
|
1040
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_204_02);
|
|
1041
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_204_03);
|
|
1042
|
+
// R2 broadcast marker (SVM)
|
|
1043
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_207, targetChain);
|
|
1044
|
+
// Add chain info to response
|
|
1045
|
+
response.chain = targetChain;
|
|
1046
|
+
response.chainNamespace = (0, helpers_1.getChainNamespace)(targetChain);
|
|
1047
|
+
return response;
|
|
843
1048
|
}
|
|
844
1049
|
// ---------------------------------------------------------------------------
|
|
845
1050
|
// executeCeaToPush (Route 3 — EVM)
|
|
@@ -855,319 +1060,381 @@ function executeUoaToCeaSvm(ctx, params, target, executeFn) {
|
|
|
855
1060
|
* 2. Execute via Route 2 (UOA -> CEA) with PAYLOAD-only (CEA uses its own funds)
|
|
856
1061
|
* 3. CEA executes multicall, Gateway locks funds, relayer mints PRC-20 on Push Chain
|
|
857
1062
|
*/
|
|
858
|
-
function executeCeaToPush(ctx, params, executeFn) {
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
909
|
-
amount = params.funds.amount;
|
|
910
|
-
}
|
|
911
|
-
else {
|
|
912
|
-
// ERC20 token. CEA handles gateway approval internally around sendUniversalTxToUEA.
|
|
913
|
-
tokenAddress = token.address;
|
|
914
|
-
amount = params.funds.amount;
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
else if (params.value && params.value > BigInt(0)) {
|
|
919
|
-
// Native value transfer (e.g., BNB, ETH)
|
|
920
|
-
tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
921
|
-
amount = params.value;
|
|
922
|
-
}
|
|
923
|
-
// bridgeAmount = only the burn amount (what the Vault will actually deposit to CEA).
|
|
924
|
-
// Previously this included ceaExistingBalance (CEA's pre-existing balance on the
|
|
925
|
-
// external chain), but that approach is racy: the balance can change between the SDK
|
|
926
|
-
// query and relay execution, causing sendUniversalTxToUEA to revert with
|
|
927
|
-
// InsufficientBalance. Pre-existing CEA funds remain parked and can be swept separately.
|
|
928
|
-
const bridgeAmount = amount;
|
|
929
|
-
// The post-audit CEA allows amount=0 for payload-only sendUniversalTxToUEA.
|
|
930
|
-
// Pre-fetch UEA nonce — needed for the inbound UniversalPayload struct
|
|
931
|
-
// Skip getCode if accountStatusCache already confirmed deployment
|
|
932
|
-
let isUEADeployed;
|
|
933
|
-
let ueaNonce;
|
|
934
|
-
const deployedHintR3 = (_e = (_d = ctx.accountStatusCache) === null || _d === void 0 ? void 0 : _d.uea) === null || _e === void 0 ? void 0 : _e.deployed;
|
|
935
|
-
if (deployedHintR3) {
|
|
936
|
-
isUEADeployed = true;
|
|
937
|
-
ueaNonce = yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress);
|
|
938
|
-
}
|
|
939
|
-
else {
|
|
940
|
-
const ueaCode = yield ctx.pushClient.publicClient.getCode({ address: ueaAddress });
|
|
941
|
-
isUEADeployed = ueaCode !== undefined;
|
|
942
|
-
ueaNonce = isUEADeployed ? yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress) : BigInt(0);
|
|
943
|
-
}
|
|
944
|
-
// Build payload for Push Chain execution (if any)
|
|
945
|
-
// This is what happens AFTER funds arrive on Push Chain.
|
|
946
|
-
// The relay expects a full UniversalPayload struct (to, value, data, gasLimit, ...),
|
|
947
|
-
// where `data` contains the multicall payload (with UEA_MULTICALL_SELECTOR prefix).
|
|
948
|
-
let pushPayload = '0x';
|
|
949
|
-
if ((0, data_utils_1.hasExecutablePayloadData)(params.data) ||
|
|
950
|
-
(((_f = params.funds) === null || _f === void 0 ? void 0 : _f.amount) && ((_g = params.value) !== null && _g !== void 0 ? _g : BigInt(0)) > BigInt(0))) {
|
|
951
|
-
const multicallData = (0, payload_builders_1.buildExecuteMulticall)({
|
|
952
|
-
execute: {
|
|
953
|
-
to: pushDestination,
|
|
954
|
-
value: params.value,
|
|
955
|
-
data: params.data,
|
|
956
|
-
},
|
|
957
|
-
ueaAddress,
|
|
958
|
-
allowSelfValueCall: true,
|
|
959
|
-
});
|
|
960
|
-
const multicallPayload = (0, payload_builder_1.buildMulticallPayloadData)(ctx, pushDestination, multicallData);
|
|
961
|
-
// Use ueaNonce + 1: the outbound tx itself consumes one nonce via execute(),
|
|
962
|
-
// so the inbound will arrive when the UEA nonce is already incremented.
|
|
963
|
-
pushPayload = (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, { nonce: ueaNonce + BigInt(1) });
|
|
964
|
-
}
|
|
965
|
-
// Build sendUniversalTxToUEA self-call on CEA. CEA self-calls must always
|
|
966
|
-
// use value=0; native sends spend from the CEA's existing balance.
|
|
967
|
-
// CEA internally calls gateway.sendUniversalTxFromCEA(...)
|
|
968
|
-
// Uses bridgeAmount (= burn amount deposited by Vault)
|
|
969
|
-
const sendUniversalTxCall = (0, payload_builders_1.buildSendUniversalTxToUEA)(ceaAddress, // to: CEA address (self-call)
|
|
970
|
-
tokenAddress, // token: address(0) for native, ERC20 address otherwise
|
|
971
|
-
bridgeAmount, // amount: burn amount only (Vault-deposited)
|
|
972
|
-
pushPayload, // payload: Push Chain execution payload
|
|
973
|
-
ueaAddress // revertRecipient: UEA on Push Chain (receives refund if inbound fails)
|
|
974
|
-
);
|
|
975
|
-
ceaMulticalls.push(sendUniversalTxCall);
|
|
976
|
-
// 7. Encode CEA multicalls into outbound payload
|
|
977
|
-
// CEA will self-execute this multicall (to=CEA, value=0)
|
|
978
|
-
const ceaPayload = (0, payload_builders_1.buildCeaMulticallPayload)(ceaMulticalls);
|
|
979
|
-
(0, context_1.printLog)(ctx, `executeCeaToPush — CEA payload (first 100 chars): ${ceaPayload.slice(0, 100)}...`);
|
|
980
|
-
// 8. Determine PRC-20 token for the outbound relay.
|
|
981
|
-
// Route 3: ALWAYS use native PRC-20 for chain namespace lookup + gas fees.
|
|
982
|
-
// The CEA uses its OWN pre-existing balance on the external chain to bridge
|
|
983
|
-
// tokens back — no PRC-20 burn is needed on Push Chain.
|
|
984
|
-
// burnAmount = 0 makes this a payload-only outbound relay.
|
|
985
|
-
//
|
|
986
|
-
// Previously this burned pUSDT/pToken equal to the bridge amount, which failed
|
|
987
|
-
// because the UEA never holds pUSDT — the whole point of Route 3 is to get
|
|
988
|
-
// tokens FROM the CEA back to Push Chain, not to round-trip them.
|
|
989
|
-
const prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
990
|
-
const burnAmount = BigInt(0);
|
|
991
|
-
const outboundGasLimit = (_h = params.gasLimit) !== null && _h !== void 0 ? _h : selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT;
|
|
992
|
-
(0, context_1.printLog)(ctx, `executeCeaToPush — prc20Token: ${prc20Token}, burnAmount: ${burnAmount.toString()}`);
|
|
993
|
-
// 9. Build outbound request (same structure as Route 2)
|
|
994
|
-
// recipient = CEA address (for self-execution), value = 0 in payload
|
|
995
|
-
let outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, // recipient: CEA address (to=CEA for self-execution)
|
|
996
|
-
prc20Token, // token: native PRC-20 for source chain (for namespace lookup)
|
|
997
|
-
burnAmount, // amount: 0 (payload-only, CEA uses its own balance)
|
|
998
|
-
outboundGasLimit, ceaPayload, // payload: ABI-encoded CEA multicall
|
|
999
|
-
ueaAddress, // revertRecipient: UEA
|
|
1000
|
-
(_j = params.maxPCForGas) !== null && _j !== void 0 ? _j : BigInt(0));
|
|
1001
|
-
(0, context_1.printLog)(ctx, `executeCeaToPush — outbound request: ${JSON.stringify({
|
|
1002
|
-
recipient: outboundReq.recipient,
|
|
1003
|
-
token: outboundReq.token,
|
|
1004
|
-
amount: outboundReq.amount.toString(),
|
|
1005
|
-
gasLimit: outboundReq.gasLimit.toString(),
|
|
1006
|
-
maxPCForGas: outboundReq.maxPCForGas.toString(),
|
|
1007
|
-
payloadLength: outboundReq.payload.length,
|
|
1008
|
-
revertRecipient: outboundReq.revertRecipient,
|
|
1009
|
-
}, null, 2)}`);
|
|
1010
|
-
// 10. Fetch UEA balance — needed for gas value calculation
|
|
1011
|
-
// (UEA code + nonce already fetched above for the inbound UniversalPayload)
|
|
1012
|
-
const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
|
|
1013
|
-
const ueaBalance = yield ctx.pushClient.getBalance(ueaAddress);
|
|
1014
|
-
// 11. Query gas fees from UniversalCore
|
|
1015
|
-
let gasFee = BigInt(0);
|
|
1016
|
-
let protocolFeeR3 = BigInt(0);
|
|
1017
|
-
let nativeValueForGas = BigInt(0);
|
|
1018
|
-
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
1019
|
-
let sizingDecisionR3;
|
|
1020
|
-
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
1021
|
-
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_302_01, sourceChain);
|
|
1022
|
-
try {
|
|
1023
|
-
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, outboundGasLimit, sourceChain);
|
|
1024
|
-
gasToken = result.gasToken;
|
|
1025
|
-
gasFee = result.gasFee;
|
|
1026
|
-
protocolFeeR3 = result.protocolFee;
|
|
1027
|
-
nativeValueForGas = result.nativeValueForGas;
|
|
1028
|
-
sizingDecisionR3 = result.sizing;
|
|
1029
|
-
outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, prc20Token, burnAmount, outboundGasLimit, ceaPayload, ueaAddress, (_k = params.maxPCForGas) !== null && _k !== void 0 ? _k : BigInt(0));
|
|
1030
|
-
(0, context_1.printLog)(ctx, `executeCeaToPush — queried gas fee: ${gasFee.toString()}, gasToken: ${gasToken}, nativeValueForGas: ${nativeValueForGas.toString()}, sizing=${(_m = (_l = result.sizing) === null || _l === void 0 ? void 0 : _l.category) !== null && _m !== void 0 ? _m : 'legacy'}`);
|
|
1031
|
-
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_302_02, sourceChain, protocolFeeR3, gasFee);
|
|
1032
|
-
}
|
|
1033
|
-
catch (err) {
|
|
1034
|
-
throw new Error(`Failed to query outbound gas fee for Route 3: ${err}`);
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
// Account-resolution checkpoint fires before the gas-sizing sub-hook so
|
|
1038
|
-
// consumers see the full account-resolution phase (303-xx) complete before
|
|
1039
|
-
// the sizing decision is surfaced.
|
|
1040
|
-
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_01, sourceChain);
|
|
1041
|
-
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_02, ueaAddress, ceaAddress, sourceChain);
|
|
1042
|
-
if (sizingDecisionR3) {
|
|
1043
|
-
fireSizingHook(ctx, 'R3', sourceChain, sizingDecisionR3);
|
|
1044
|
-
}
|
|
1045
|
-
// R3 Case C: unlike R2, R3 has no destination funds-delivery semantic
|
|
1046
|
-
// (CEA self-executes and bridges funds BACK to Push Chain). When the
|
|
1047
|
-
// sizer returns Case C, we simply top up msg.value with the overflow so
|
|
1048
|
-
// `swapAndBurnGas` can afford the full gas cost on destination. No
|
|
1049
|
-
// bridge-swap entries, no fold-in — contract refunds any excess.
|
|
1050
|
-
if ((sizingDecisionR3 === null || sizingDecisionR3 === void 0 ? void 0 : sizingDecisionR3.category) === 'C' &&
|
|
1051
|
-
sizingDecisionR3.overflowNativePc > BigInt(0)) {
|
|
1052
|
-
const bumped = nativeValueForGas + sizingDecisionR3.overflowNativePc;
|
|
1053
|
-
(0, context_1.printLog)(ctx, `executeCeaToPush — Case C: bumping nativeValueForGas from ${nativeValueForGas} to ${bumped} (overflow=${sizingDecisionR3.overflowNativePc})`);
|
|
1054
|
-
nativeValueForGas = bumped;
|
|
1055
|
-
}
|
|
1056
|
-
// Adjust nativeValueForGas using UEA balance (contract refunds excess)
|
|
1057
|
-
// Reuse ueaBalance from line 978 — no tx sent between, balance is stable
|
|
1058
|
-
const currentBalance = ueaBalance;
|
|
1059
|
-
// Cosmos-EVM tx overhead costs ~1 PC per operation; 3 PC covers approve(s) + buffer.
|
|
1060
|
-
const OUTBOUND_GAS_RESERVE_R3 = BigInt(3e18);
|
|
1061
|
-
const ROUTE3_MINIMUM_DEPOSIT_USD = utils_1.Utils.helpers.parseUnits('10', 8); // $10
|
|
1062
|
-
// For fresh wallets: predict post-fee-locking UPC balance using the same
|
|
1063
|
-
// Uniswap V3 quoter the chain uses (pETH → WPC swap), matching Route 2 logic.
|
|
1064
|
-
let effectiveBalance = currentBalance;
|
|
1065
|
-
if (!isUEADeployed && effectiveBalance <= OUTBOUND_GAS_RESERVE_R3) {
|
|
1066
|
-
const signerChain = ctx.universalSigner.account.chain;
|
|
1067
|
-
const ethPrice = yield new price_fetch_1.PriceFetch(ctx.rpcUrls).getPrice(signerChain);
|
|
1068
|
-
const nativeAmountETH = ethPrice > BigInt(0)
|
|
1069
|
-
? (ROUTE3_MINIMUM_DEPOSIT_USD * BigInt(1e18) + (ethPrice - BigInt(1))) / ethPrice + BigInt(1)
|
|
1070
|
-
: BigInt(0);
|
|
1071
|
-
if (nativeAmountETH > BigInt(0)) {
|
|
1072
|
-
const originPrc20 = (0, helpers_1.getNativePRC20ForChain)(signerChain, ctx.pushNetwork);
|
|
1073
|
-
const predictedUPC = yield (0, gas_calculator_1.estimateDepositFromLockedNative)(ctx, nativeAmountETH, originPrc20);
|
|
1074
|
-
if (predictedUPC > BigInt(0)) {
|
|
1075
|
-
effectiveBalance = (predictedUPC * BigInt(90)) / BigInt(100);
|
|
1076
|
-
(0, context_1.printLog)(ctx, `executeCeaToPush — fresh wallet: Uniswap quote predicts ${predictedUPC.toString()} UPC ` +
|
|
1077
|
-
`from ${nativeAmountETH.toString()} wei pETH, using ${effectiveBalance.toString()} (90%)`);
|
|
1078
|
-
}
|
|
1063
|
+
async function executeCeaToPush(ctx, params, executeFn) {
|
|
1064
|
+
// 1. Validate and extract source chain
|
|
1065
|
+
if (!params.from?.chain) {
|
|
1066
|
+
throw new Error('Route 3 (CEA -> Push) requires from.chain to specify the source CEA chain');
|
|
1067
|
+
}
|
|
1068
|
+
const sourceChain = params.from.chain;
|
|
1069
|
+
// SVM chains use a fundamentally different flow (gateway self-call, not CEA multicall)
|
|
1070
|
+
if ((0, payload_builders_2.isSvmChain)(sourceChain)) {
|
|
1071
|
+
return executeCeaToPushSvm(ctx, params, sourceChain, executeFn);
|
|
1072
|
+
}
|
|
1073
|
+
// 2. Extract destination on Push Chain
|
|
1074
|
+
// For Route 3, 'to' is a Push Chain address (string), not a ChainTarget
|
|
1075
|
+
const pushDestination = params.to;
|
|
1076
|
+
if (typeof params.to !== 'string') {
|
|
1077
|
+
throw new Error('Route 3 (CEA -> Push): to must be a Push Chain address (string), not a ChainTarget');
|
|
1078
|
+
}
|
|
1079
|
+
// 3. Get UEA address (will be recipient on Push Chain from CEA's perspective)
|
|
1080
|
+
// 4. Get CEA address on source chain
|
|
1081
|
+
// Both happen before the 301 hook fires — wrap them so a pre-sign RPC
|
|
1082
|
+
// failure produces a descriptive message in the subsequent 399-02 body
|
|
1083
|
+
// instead of a bare "failed to fetch" surface.
|
|
1084
|
+
let ueaAddress;
|
|
1085
|
+
let ceaAddress;
|
|
1086
|
+
let ceaDeployed;
|
|
1087
|
+
try {
|
|
1088
|
+
ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
|
|
1089
|
+
const ceaResult = await (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, ctx.rpcUrls[sourceChain]?.[0]);
|
|
1090
|
+
ceaAddress = ceaResult.cea;
|
|
1091
|
+
ceaDeployed = ceaResult.isDeployed;
|
|
1092
|
+
}
|
|
1093
|
+
catch (err) {
|
|
1094
|
+
throw new Error(`Route 3 setup failed: could not resolve CEA on ${sourceChain}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1095
|
+
}
|
|
1096
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_301, sourceChain, ceaAddress);
|
|
1097
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — sourceChain: ${sourceChain}, CEA: ${ceaAddress}, deployed: ${ceaDeployed}`);
|
|
1098
|
+
// CEA auto-deploys on-chain: Vault.finalizeUniversalTx calls CEAFactory.deployCEA()
|
|
1099
|
+
// if CEA doesn't exist yet. No SDK-side blocking needed.
|
|
1100
|
+
// 6. Build multicall for CEA to execute on source chain (self-calls via sendUniversalTxToUEA)
|
|
1101
|
+
const ceaMulticalls = [];
|
|
1102
|
+
// Determine token and amount for sendUniversalTxToUEA
|
|
1103
|
+
let tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
1104
|
+
let amount = BigInt(0);
|
|
1105
|
+
if (params.funds?.amount) {
|
|
1106
|
+
// ERC20 token transfer from CEA
|
|
1107
|
+
const token = params.funds.token;
|
|
1108
|
+
if (token) {
|
|
1109
|
+
if (token.mechanism === 'native') {
|
|
1110
|
+
// Native token (e.g., BNB on BSC)
|
|
1111
|
+
tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
1112
|
+
amount = params.funds.amount;
|
|
1079
1113
|
}
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
// masking under-funded swaps with opaque ExecutionFailed reverts. The
|
|
1085
|
-
// new logic preserves the "consume extra to reduce STF risk" intent for
|
|
1086
|
-
// small calibrated values while never reducing a calibrated value that
|
|
1087
|
-
// happened to land above 200 PC. Down-clamp + zero-balance throws live
|
|
1088
|
-
// in `runPreflight` below.
|
|
1089
|
-
const EVM_NATIVE_VALUE_TARGET_R3 = BigInt(200e18); // 200 UPC target floor
|
|
1090
|
-
if (effectiveBalance > EVM_NATIVE_VALUE_TARGET_R3 + OUTBOUND_GAS_RESERVE_R3) {
|
|
1091
|
-
// Balance can cover the target. Bump nativeValueForGas UP to 200 PC,
|
|
1092
|
-
// but never reduce a calibrated value already above 200.
|
|
1093
|
-
const newValue = nativeValueForGas > EVM_NATIVE_VALUE_TARGET_R3
|
|
1094
|
-
? nativeValueForGas
|
|
1095
|
-
: EVM_NATIVE_VALUE_TARGET_R3;
|
|
1096
|
-
if (newValue !== nativeValueForGas) {
|
|
1097
|
-
(0, context_1.printLog)(ctx, `executeCeaToPush — adjusting nativeValueForGas UP from ${nativeValueForGas.toString()} to ${newValue.toString()} (EVM_NATIVE_VALUE_TARGET_R3 floor)`);
|
|
1098
|
-
nativeValueForGas = newValue;
|
|
1114
|
+
else {
|
|
1115
|
+
// ERC20 token. CEA handles gateway approval internally around sendUniversalTxToUEA.
|
|
1116
|
+
tokenAddress = token.address;
|
|
1117
|
+
amount = params.funds.amount;
|
|
1099
1118
|
}
|
|
1100
1119
|
}
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
// Native
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1120
|
+
}
|
|
1121
|
+
else if (params.value && params.value > BigInt(0)) {
|
|
1122
|
+
// Native value transfer (e.g., BNB, ETH)
|
|
1123
|
+
tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
1124
|
+
amount = params.value;
|
|
1125
|
+
}
|
|
1126
|
+
// bridgeAmount = only the burn amount (what the Vault will actually deposit to CEA).
|
|
1127
|
+
// Previously this included ceaExistingBalance (CEA's pre-existing balance on the
|
|
1128
|
+
// external chain), but that approach is racy: the balance can change between the SDK
|
|
1129
|
+
// query and relay execution, causing sendUniversalTxToUEA to revert with
|
|
1130
|
+
// InsufficientBalance. Pre-existing CEA funds remain parked and can be swept separately.
|
|
1131
|
+
const bridgeAmount = amount;
|
|
1132
|
+
// The post-audit CEA allows amount=0 for payload-only sendUniversalTxToUEA.
|
|
1133
|
+
// Pre-fetch UEA nonce — needed for the inbound UniversalPayload struct
|
|
1134
|
+
// Skip getCode if accountStatusCache already confirmed deployment
|
|
1135
|
+
let isUEADeployed;
|
|
1136
|
+
let ueaNonce;
|
|
1137
|
+
const isNativePushEOAR3 = (0, helpers_1.isPushChain)(ctx.universalSigner.account.chain);
|
|
1138
|
+
const deployedHintR3 = ctx.accountStatusCache?.uea?.deployed;
|
|
1139
|
+
if (isNativePushEOAR3) {
|
|
1140
|
+
// Native Push accounts execute directly as EOAs. EIP-7702 delegation code
|
|
1141
|
+
// is not a UEA proxy and its executor does not expose the UEA nonce ABI.
|
|
1142
|
+
// Treat the execution account as ready while using no UEA nonce.
|
|
1143
|
+
isUEADeployed = true;
|
|
1144
|
+
ueaNonce = BigInt(0);
|
|
1145
|
+
}
|
|
1146
|
+
else if (deployedHintR3) {
|
|
1147
|
+
isUEADeployed = true;
|
|
1148
|
+
ueaNonce = await (0, uea_manager_1.getUEANonce)(ctx, ueaAddress);
|
|
1149
|
+
}
|
|
1150
|
+
else {
|
|
1151
|
+
const ueaCode = await ctx.pushClient.publicClient.getCode({
|
|
1152
|
+
address: ueaAddress,
|
|
1111
1153
|
});
|
|
1112
|
-
|
|
1113
|
-
|
|
1154
|
+
isUEADeployed = ueaCode !== undefined;
|
|
1155
|
+
ueaNonce = isUEADeployed ? await (0, uea_manager_1.getUEANonce)(ctx, ueaAddress) : BigInt(0);
|
|
1156
|
+
}
|
|
1157
|
+
// Build payload for Push Chain execution (if any)
|
|
1158
|
+
// This is what happens AFTER funds arrive on Push Chain.
|
|
1159
|
+
// The relay expects a full UniversalPayload struct (to, value, data, gasLimit, ...),
|
|
1160
|
+
// where `data` contains the multicall payload (with UEA_MULTICALL_SELECTOR prefix).
|
|
1161
|
+
let pushPayload = '0x';
|
|
1162
|
+
// PC20: the chain unlocks the Push-native token to the UEA (unlockPC20 →
|
|
1163
|
+
// in.Recipient, which the CEA sets to its bound UEA), so a funds-only R3
|
|
1164
|
+
// whose destination is NOT the UEA needs a Push-side transfer to forward
|
|
1165
|
+
// it. PRC20 R3 has the same land-on-UEA semantics; PC20 just makes the
|
|
1166
|
+
// forwarding explicit here because the resolved descriptor lets us.
|
|
1167
|
+
const r3Pc20 = params._pc20;
|
|
1168
|
+
// ALWAYS build the forward for PC20 funds — even when the destination is the
|
|
1169
|
+
// UEA itself (an explicit self-transfer). Two reasons:
|
|
1170
|
+
// 1. semantics: the transfer states where funds go instead of relying on
|
|
1171
|
+
// the unlock's implicit landing spot;
|
|
1172
|
+
// 2. survival: a payload-less PC20 burn produces a selector-only
|
|
1173
|
+
// raw_payload, which the chain classifies FUNDS_AND_PAYLOAD and then
|
|
1174
|
+
// hard-fails decoding ("raw_payload decoded to nil" —
|
|
1175
|
+
// StripSelector yields "0x", the != "" guard admits it). Until that is
|
|
1176
|
+
// fixed chain-side, an empty R3 PC20 payload bricks the inbound with
|
|
1177
|
+
// the wrapper already burned.
|
|
1178
|
+
const r3Pc20NeedsForward = r3Pc20?.direction === 'import' && Boolean(params.funds?.amount);
|
|
1179
|
+
if ((0, data_utils_1.hasExecutablePayloadData)(params.data) ||
|
|
1180
|
+
(params.funds?.amount && (params.value ?? BigInt(0)) > BigInt(0)) ||
|
|
1181
|
+
r3Pc20NeedsForward) {
|
|
1182
|
+
const multicallData = (0, payload_builders_2.buildExecuteMulticall)({
|
|
1183
|
+
execute: {
|
|
1184
|
+
to: pushDestination,
|
|
1185
|
+
value: params.value,
|
|
1186
|
+
data: params.data,
|
|
1187
|
+
// Funds + descriptor let the builder emit the Push-native PC20
|
|
1188
|
+
// transfer (its _pc20 branch); omitted for non-PC20 to keep the
|
|
1189
|
+
// existing PRC20 payload byte-identical.
|
|
1190
|
+
...(r3Pc20NeedsForward
|
|
1191
|
+
? { funds: params.funds, _pc20: r3Pc20 }
|
|
1192
|
+
: {}),
|
|
1193
|
+
},
|
|
1114
1194
|
ueaAddress,
|
|
1115
|
-
|
|
1116
|
-
requiredValue: nativeValueForGas,
|
|
1117
|
-
gasReserve: OUTBOUND_GAS_RESERVE_R3,
|
|
1118
|
-
pathTag: 'R3_EVM',
|
|
1119
|
-
enforceGasCheck: ((_o = params.options) === null || _o === void 0 ? void 0 : _o.enforceGasCheck) === true,
|
|
1195
|
+
allowSelfValueCall: true,
|
|
1120
1196
|
});
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1197
|
+
const multicallPayload = (0, payload_builder_1.buildMulticallPayloadData)(ctx, pushDestination, multicallData);
|
|
1198
|
+
// Use ueaNonce + 1: the outbound tx itself consumes one nonce via execute(),
|
|
1199
|
+
// so the inbound will arrive when the UEA nonce is already incremented.
|
|
1200
|
+
pushPayload = (0, payload_builders_2.buildInboundUniversalPayload)(multicallPayload, { nonce: ueaNonce + BigInt(1) });
|
|
1201
|
+
}
|
|
1202
|
+
(0, gate_1.assertPC20ImportHasPayload)(params, pushPayload !== '0x', `Route 3 EVM ${sourceChain}`);
|
|
1203
|
+
// Build sendUniversalTxToUEA self-call on CEA. CEA self-calls must always
|
|
1204
|
+
// use value=0; native sends spend from the CEA's existing balance.
|
|
1205
|
+
// CEA internally calls gateway.sendUniversalTxFromCEA(...)
|
|
1206
|
+
// Uses bridgeAmount (= burn amount deposited by Vault)
|
|
1207
|
+
const sendUniversalTxCall = (0, payload_builders_2.buildSendUniversalTxToUEA)(ceaAddress, // to: CEA address (self-call)
|
|
1208
|
+
tokenAddress, // token: address(0) for native, ERC20 address otherwise
|
|
1209
|
+
bridgeAmount, // amount: burn amount only (Vault-deposited)
|
|
1210
|
+
pushPayload, // payload: Push Chain execution payload
|
|
1211
|
+
ueaAddress // revertRecipient: UEA on Push Chain (receives refund if inbound fails)
|
|
1212
|
+
);
|
|
1213
|
+
ceaMulticalls.push(sendUniversalTxCall);
|
|
1214
|
+
// 7. Encode CEA multicalls into outbound payload
|
|
1215
|
+
// CEA will self-execute this multicall (to=CEA, value=0)
|
|
1216
|
+
const ceaPayload = (0, payload_builders_2.buildCeaMulticallPayload)(ceaMulticalls);
|
|
1217
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — CEA payload (first 100 chars): ${ceaPayload.slice(0, 100)}...`);
|
|
1218
|
+
// 8. Determine PRC-20 token for the outbound relay.
|
|
1219
|
+
// Route 3: ALWAYS use native PRC-20 for chain namespace lookup + gas fees.
|
|
1220
|
+
// The CEA uses its OWN pre-existing balance on the external chain to bridge
|
|
1221
|
+
// tokens back — no PRC-20 burn is needed on Push Chain.
|
|
1222
|
+
// burnAmount = 0 makes this a payload-only outbound relay.
|
|
1223
|
+
//
|
|
1224
|
+
// Previously this burned pUSDT/pToken equal to the bridge amount, which failed
|
|
1225
|
+
// because the UEA never holds pUSDT — the whole point of Route 3 is to get
|
|
1226
|
+
// tokens FROM the CEA back to Push Chain, not to round-trip them.
|
|
1227
|
+
const prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
1228
|
+
const burnAmount = BigInt(0);
|
|
1229
|
+
const outboundGasLimit = params.gasLimit ?? selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT;
|
|
1230
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — prc20Token: ${prc20Token}, burnAmount: ${burnAmount.toString()}`);
|
|
1231
|
+
// 9. Build outbound request (same structure as Route 2)
|
|
1232
|
+
// recipient = CEA address (for self-execution), value = 0 in payload
|
|
1233
|
+
let outboundReq = (0, payload_builders_2.buildOutboundRequest)(ceaAddress, // recipient: CEA address (to=CEA for self-execution)
|
|
1234
|
+
prc20Token, // token: native PRC-20 for source chain (for namespace lookup)
|
|
1235
|
+
burnAmount, // amount: 0 (payload-only, CEA uses its own balance)
|
|
1236
|
+
outboundGasLimit, ceaPayload, // payload: ABI-encoded CEA multicall
|
|
1237
|
+
ueaAddress, // revertRecipient: UEA
|
|
1238
|
+
params.maxPCForGas ?? BigInt(0));
|
|
1239
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — outbound request: ${JSON.stringify({
|
|
1240
|
+
recipient: outboundReq.recipient,
|
|
1241
|
+
token: outboundReq.token,
|
|
1242
|
+
amount: outboundReq.amount.toString(),
|
|
1243
|
+
gasLimit: outboundReq.gasLimit.toString(),
|
|
1244
|
+
maxPCForGas: outboundReq.maxPCForGas.toString(),
|
|
1245
|
+
payloadLength: outboundReq.payload.length,
|
|
1246
|
+
revertRecipient: outboundReq.revertRecipient,
|
|
1247
|
+
}, null, 2)}`);
|
|
1248
|
+
// 10. Fetch UEA balance — needed for gas value calculation
|
|
1249
|
+
// (UEA code + nonce already fetched above for the inbound UniversalPayload)
|
|
1250
|
+
const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
|
|
1251
|
+
const ueaBalance = await ctx.pushClient.getBalance(ueaAddress);
|
|
1252
|
+
// 11. Query gas fees from UniversalCore
|
|
1253
|
+
let gasFee = BigInt(0);
|
|
1254
|
+
let protocolFeeR3 = BigInt(0);
|
|
1255
|
+
let nativeValueForGas = BigInt(0);
|
|
1256
|
+
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
1257
|
+
let universalCoreAddress;
|
|
1258
|
+
let sizingDecisionR3;
|
|
1259
|
+
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
1260
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_302_01, sourceChain);
|
|
1142
1261
|
try {
|
|
1143
|
-
|
|
1262
|
+
const result = await (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, outboundGasLimit, sourceChain);
|
|
1263
|
+
gasToken = result.gasToken;
|
|
1264
|
+
gasFee = result.gasFee;
|
|
1265
|
+
protocolFeeR3 = result.protocolFee;
|
|
1266
|
+
nativeValueForGas = result.nativeValueForGas;
|
|
1267
|
+
universalCoreAddress = result.universalCoreAddress;
|
|
1268
|
+
sizingDecisionR3 = result.sizing;
|
|
1269
|
+
outboundReq = (0, payload_builders_2.buildOutboundRequest)(ceaAddress, prc20Token, burnAmount, outboundGasLimit, ceaPayload, ueaAddress, params.maxPCForGas ?? BigInt(0));
|
|
1270
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — queried gas fee: ${gasFee.toString()}, gasToken: ${gasToken}, nativeValueForGas: ${nativeValueForGas.toString()}, sizing=${result.sizing?.category ?? 'legacy'}`);
|
|
1271
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_302_02, sourceChain, protocolFeeR3, gasFee);
|
|
1144
1272
|
}
|
|
1145
1273
|
catch (err) {
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1274
|
+
throw new Error(`Failed to query outbound gas fee for Route 3: ${err}`);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
// Account-resolution checkpoint fires before the gas-sizing sub-hook so
|
|
1278
|
+
// consumers see the full account-resolution phase (303-xx) complete before
|
|
1279
|
+
// the sizing decision is surfaced.
|
|
1280
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_01, sourceChain);
|
|
1281
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_02, ueaAddress, ceaAddress, sourceChain);
|
|
1282
|
+
if (sizingDecisionR3) {
|
|
1283
|
+
fireSizingHook(ctx, 'R3', sourceChain, sizingDecisionR3);
|
|
1284
|
+
}
|
|
1285
|
+
// R3 Case C: unlike R2, R3 has no destination funds-delivery semantic
|
|
1286
|
+
// (CEA self-executes and bridges funds BACK to Push Chain). When the
|
|
1287
|
+
// sizer returns Case C, we simply top up msg.value with the overflow so
|
|
1288
|
+
// `swapAndBurnGas` can afford the full gas cost on destination. No
|
|
1289
|
+
// bridge-swap entries, no fold-in — contract refunds any excess.
|
|
1290
|
+
if (sizingDecisionR3?.category === 'C' &&
|
|
1291
|
+
sizingDecisionR3.overflowNativePc > BigInt(0)) {
|
|
1292
|
+
const bumped = nativeValueForGas + sizingDecisionR3.overflowNativePc;
|
|
1293
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — Case C: bumping nativeValueForGas from ${nativeValueForGas} to ${bumped} (overflow=${sizingDecisionR3.overflowNativePc})`);
|
|
1294
|
+
nativeValueForGas = bumped;
|
|
1295
|
+
}
|
|
1296
|
+
// The USD sizer can be a few basis points below the executable pool quote
|
|
1297
|
+
// (for example when native Push falls back to the fixed $0.10 PC price).
|
|
1298
|
+
// Because swapAndBurnGas buys an exact gas-token output, even a small
|
|
1299
|
+
// shortfall reverts the entire atomic Push transaction. Floor the category
|
|
1300
|
+
// result with the same live exact-output quote used by Route 2. The quote's
|
|
1301
|
+
// 2.2x buffer is automatically capped to balance - 3 PC when that still
|
|
1302
|
+
// covers the unbuffered swap minimum, and excess is refunded on-chain.
|
|
1303
|
+
if (universalCoreAddress && gasFee > BigInt(0)) {
|
|
1304
|
+
const balanceAvailableForSwap = ueaBalance > protocolFeeR3
|
|
1305
|
+
? ueaBalance - protocolFeeR3
|
|
1306
|
+
: BigInt(0);
|
|
1307
|
+
const bufferedSwapValue = await (0, gas_calculator_1.estimateNativeValueForSwap)(ctx, universalCoreAddress, gasToken, gasFee, balanceAvailableForSwap);
|
|
1308
|
+
const poolFlooredValue = floorR3NativeValueForGas(nativeValueForGas, bufferedSwapValue, protocolFeeR3);
|
|
1309
|
+
if (poolFlooredValue > nativeValueForGas) {
|
|
1310
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — bumping nativeValueForGas ${nativeValueForGas.toString()} → ${poolFlooredValue.toString()} ` +
|
|
1311
|
+
`(live exact-output pool floor, bufferedSwap=${bufferedSwapValue.toString()}, protocolFee=${protocolFeeR3.toString()})`);
|
|
1312
|
+
nativeValueForGas = poolFlooredValue;
|
|
1151
1313
|
}
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1314
|
+
}
|
|
1315
|
+
// Adjust nativeValueForGas using UEA balance (contract refunds excess)
|
|
1316
|
+
// Reuse ueaBalance from line 978 — no tx sent between, balance is stable
|
|
1317
|
+
const currentBalance = ueaBalance;
|
|
1318
|
+
// Cosmos-EVM tx overhead costs ~1 PC per operation; 3 PC covers approve(s) + buffer.
|
|
1319
|
+
const OUTBOUND_GAS_RESERVE_R3 = BigInt(3e18);
|
|
1320
|
+
const ROUTE3_MINIMUM_DEPOSIT_USD = utils_1.Utils.helpers.parseUnits('10', 8); // $10
|
|
1321
|
+
// For fresh wallets: predict post-fee-locking UPC balance using the same
|
|
1322
|
+
// Uniswap V3 quoter the chain uses (pETH → WPC swap), matching Route 2 logic.
|
|
1323
|
+
let effectiveBalance = currentBalance;
|
|
1324
|
+
if (!isUEADeployed && effectiveBalance <= OUTBOUND_GAS_RESERVE_R3) {
|
|
1325
|
+
const signerChain = ctx.universalSigner.account.chain;
|
|
1326
|
+
const ethPrice = await new price_fetch_1.PriceFetch(ctx.rpcUrls).getPrice(signerChain);
|
|
1327
|
+
const nativeAmountETH = ethPrice > BigInt(0)
|
|
1328
|
+
? (ROUTE3_MINIMUM_DEPOSIT_USD * BigInt(1e18) + (ethPrice - BigInt(1))) / ethPrice + BigInt(1)
|
|
1329
|
+
: BigInt(0);
|
|
1330
|
+
if (nativeAmountETH > BigInt(0)) {
|
|
1331
|
+
const originPrc20 = (0, helpers_1.getNativePRC20ForChain)(signerChain, ctx.pushNetwork);
|
|
1332
|
+
const predictedUPC = await (0, gas_calculator_1.estimateDepositFromLockedNative)(ctx, nativeAmountETH, originPrc20);
|
|
1333
|
+
if (predictedUPC > BigInt(0)) {
|
|
1334
|
+
effectiveBalance = (predictedUPC * BigInt(90)) / BigInt(100);
|
|
1335
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — fresh wallet: Uniswap quote predicts ${predictedUPC.toString()} UPC ` +
|
|
1336
|
+
`from ${nativeAmountETH.toString()} wei pETH, using ${effectiveBalance.toString()} (90%)`);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
// Balance-aware *upward* allocation. The previous behaviour silently
|
|
1341
|
+
// clamped to a 200 PC target whenever balance had headroom — which was
|
|
1342
|
+
// a downward clamp when the calibrated value already exceeded 200 PC,
|
|
1343
|
+
// masking under-funded swaps with opaque ExecutionFailed reverts. The
|
|
1344
|
+
// new logic preserves the "consume extra to reduce STF risk" intent for
|
|
1345
|
+
// small calibrated values while never reducing a calibrated value that
|
|
1346
|
+
// happened to land above 200 PC. Down-clamp + zero-balance throws live
|
|
1347
|
+
// in `runPreflight` below.
|
|
1348
|
+
const EVM_NATIVE_VALUE_TARGET_R3 = BigInt(200e18); // 200 UPC target floor
|
|
1349
|
+
if (effectiveBalance > EVM_NATIVE_VALUE_TARGET_R3 + OUTBOUND_GAS_RESERVE_R3) {
|
|
1350
|
+
// Balance can cover the target. Bump nativeValueForGas UP to 200 PC,
|
|
1351
|
+
// but never reduce a calibrated value already above 200.
|
|
1352
|
+
const newValue = nativeValueForGas > EVM_NATIVE_VALUE_TARGET_R3
|
|
1353
|
+
? nativeValueForGas
|
|
1354
|
+
: EVM_NATIVE_VALUE_TARGET_R3;
|
|
1355
|
+
if (newValue !== nativeValueForGas) {
|
|
1356
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — adjusting nativeValueForGas UP from ${nativeValueForGas.toString()} to ${newValue.toString()} (EVM_NATIVE_VALUE_TARGET_R3 floor)`);
|
|
1357
|
+
nativeValueForGas = newValue;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
// Pre-flight (R3 EVM): no PRC-20 burn check — `burnAmount = 0` makes this
|
|
1361
|
+
// payload-only (UEA never holds the source-chain PRC-20; see plan §9 #4).
|
|
1362
|
+
// Native UPC check still applies for the gas swap. It warns by default and
|
|
1363
|
+
// throws only when options.enforceGasCheck=true.
|
|
1364
|
+
const requiredNativeBalanceR3Evm = getRequiredNativeBalanceForOutbound(nativeValueForGas, OUTBOUND_GAS_RESERVE_R3);
|
|
1365
|
+
const skipFeeLockingR3Evm = shouldSkipFeeLockingForOutbound({
|
|
1366
|
+
isUEADeployed,
|
|
1367
|
+
balance: effectiveBalance,
|
|
1368
|
+
nativeValueForGas,
|
|
1369
|
+
gasReserve: OUTBOUND_GAS_RESERVE_R3,
|
|
1170
1370
|
});
|
|
1371
|
+
(0, preflight_1.runPreflight)({
|
|
1372
|
+
ctx,
|
|
1373
|
+
ueaAddress,
|
|
1374
|
+
ueaBalance: effectiveBalance,
|
|
1375
|
+
requiredValue: nativeValueForGas,
|
|
1376
|
+
gasReserve: OUTBOUND_GAS_RESERVE_R3,
|
|
1377
|
+
pathTag: 'R3_EVM',
|
|
1378
|
+
enforceGasCheck: params.options?.enforceGasCheck === true,
|
|
1379
|
+
});
|
|
1380
|
+
// 12. Build Push Chain multicalls (approvals + sendUniversalTxOutbound)
|
|
1381
|
+
const pushChainMulticalls = (0, payload_builders_2.buildOutboundApprovalAndCall)({
|
|
1382
|
+
prc20Token,
|
|
1383
|
+
gasToken,
|
|
1384
|
+
burnAmount,
|
|
1385
|
+
gasFee,
|
|
1386
|
+
nativeValueForGas,
|
|
1387
|
+
gatewayPcAddress,
|
|
1388
|
+
outboundRequest: outboundReq,
|
|
1389
|
+
});
|
|
1390
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — Push Chain multicall has ${pushChainMulticalls.length} operations`);
|
|
1391
|
+
// Sum native values from multicall entries for proper fee calculation
|
|
1392
|
+
const multicallNativeValue = pushChainMulticalls.reduce((sum, mc) => sum + (mc.value ?? BigInt(0)), BigInt(0));
|
|
1393
|
+
// 13. Execute through the normal execute() flow
|
|
1394
|
+
const executeParams = {
|
|
1395
|
+
to: ueaAddress,
|
|
1396
|
+
value: multicallNativeValue, // ensures correct requiredFunds calculation
|
|
1397
|
+
data: pushChainMulticalls,
|
|
1398
|
+
_ueaStatus: {
|
|
1399
|
+
isDeployed: isUEADeployed,
|
|
1400
|
+
nonce: ueaNonce,
|
|
1401
|
+
balance: ueaBalance,
|
|
1402
|
+
},
|
|
1403
|
+
_skipFeeLocking: skipFeeLockingR3Evm,
|
|
1404
|
+
_requiredFundsOverride: requiredNativeBalanceR3Evm,
|
|
1405
|
+
// Ensure fee-locking deposits enough for the outbound swap whenever it is needed.
|
|
1406
|
+
...(!skipFeeLockingR3Evm ? { _minimumDepositUsd: ROUTE3_MINIMUM_DEPOSIT_USD } : {}),
|
|
1407
|
+
};
|
|
1408
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_304_01);
|
|
1409
|
+
let response;
|
|
1410
|
+
try {
|
|
1411
|
+
response = await executeFn(executeParams);
|
|
1412
|
+
}
|
|
1413
|
+
catch (err) {
|
|
1414
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_304_04, err instanceof Error ? err.message : String(err));
|
|
1415
|
+
// Suppress the outer orchestrator catch's 399-02 terminal — 304-04
|
|
1416
|
+
// already surfaced the signature/broadcast failure to the consumer.
|
|
1417
|
+
ctx._routeTerminalEmitted = true;
|
|
1418
|
+
throw err;
|
|
1419
|
+
}
|
|
1420
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_304_02);
|
|
1421
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_304_03);
|
|
1422
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_307, sourceChain);
|
|
1423
|
+
// Add Route 3 context to response
|
|
1424
|
+
response.chain = sourceChain;
|
|
1425
|
+
const chainInfo = chain_1.CHAIN_INFO[sourceChain];
|
|
1426
|
+
response.chainNamespace = `${chain_1.VM_NAMESPACE[chainInfo.vm]}:${chainInfo.chainId}`;
|
|
1427
|
+
// R3 inbound round-trip tracking: enable only when funds actually flow
|
|
1428
|
+
// back (amount > 0). Payload-only R3 has no child inbound UTX on Push
|
|
1429
|
+
// Chain, so polling would hang until timeout — skip the inbound block
|
|
1430
|
+
// for amount == 0.
|
|
1431
|
+
//
|
|
1432
|
+
// Child-utxId correlation uses the universal-tx-detector's deterministic
|
|
1433
|
+
// sha256(caip:txHash:logIndex) derivation (see inbound-tracker.ts
|
|
1434
|
+
// findChildUtxIdFromExternalTx), with the original cosmos
|
|
1435
|
+
// `universal_tx_created.inbound_tx_hash` search kept as a fallback.
|
|
1436
|
+
response._expectsInboundRoundTrip = amount > BigInt(0);
|
|
1437
|
+
return response;
|
|
1171
1438
|
}
|
|
1172
1439
|
// ---------------------------------------------------------------------------
|
|
1173
1440
|
// executeCeaToPushSvm (Route 3 — SVM)
|
|
@@ -1180,205 +1447,250 @@ function executeCeaToPush(ctx, params, executeFn) {
|
|
|
1180
1447
|
* the SVM gateway program (self-call). For funds-bearing calls the outbound
|
|
1181
1448
|
* request amount must match the drain amount embedded in the instruction data.
|
|
1182
1449
|
*/
|
|
1183
|
-
function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1450
|
+
async function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
|
|
1451
|
+
if (typeof params.to !== 'string') {
|
|
1452
|
+
throw new Error('Route 3 SVM (CEA -> Push): to must be a Push Chain address (string), not a ChainTarget');
|
|
1453
|
+
}
|
|
1454
|
+
const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
|
|
1455
|
+
// Get gateway program ID from chain config and convert to 0x-hex 32 bytes
|
|
1456
|
+
const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
|
|
1457
|
+
if (!lockerContract) {
|
|
1458
|
+
throw new Error(`No SVM gateway program configured for chain ${sourceChain}`);
|
|
1459
|
+
}
|
|
1460
|
+
const programPk = new web3_js_1.PublicKey(lockerContract);
|
|
1461
|
+
const gatewayProgramHex = ('0x' + Buffer.from(programPk.toBytes()).toString('hex'));
|
|
1462
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_301, sourceChain, lockerContract);
|
|
1463
|
+
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — sourceChain: ${sourceChain}, gateway: ${lockerContract}`);
|
|
1464
|
+
// Route 3 SVM drains pre-existing funds from the Solana CEA. The Push-side
|
|
1465
|
+
// outbound is a payload relay; do not burn pSOL/pSPL from the UEA for the
|
|
1466
|
+
// drain amount.
|
|
1467
|
+
const { prc20Token, drainAmount, tokenMintHex } = resolveR3SvmDrain(params, sourceChain, ctx.pushNetwork);
|
|
1468
|
+
// Derive CEA PDA as revert recipient: ["push_identity", ueaAddress_20_bytes]
|
|
1469
|
+
const ueaBytes = Buffer.from(ueaAddress.slice(2), 'hex'); // 20 bytes
|
|
1470
|
+
const [ceaPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('push_identity'), ueaBytes], programPk);
|
|
1471
|
+
const ceaPdaHex = ('0x' + Buffer.from(ceaPda.toBytes()).toString('hex'));
|
|
1472
|
+
// Pre-fetch UEA status before payload construction. The inbound
|
|
1473
|
+
// UniversalPayload must use the nonce after this outbound execution, because
|
|
1474
|
+
// the Push-side execute() below consumes one UEA nonce before the Solana
|
|
1475
|
+
// round-trip payload returns.
|
|
1476
|
+
const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
|
|
1477
|
+
const isNativePushEOAR3Svm = (0, helpers_1.isPushChain)(ctx.universalSigner.account.chain);
|
|
1478
|
+
let isUEADeployed;
|
|
1479
|
+
let ueaNonce;
|
|
1480
|
+
let ueaBalance;
|
|
1481
|
+
if (isNativePushEOAR3Svm) {
|
|
1482
|
+
isUEADeployed = true;
|
|
1483
|
+
ueaNonce = BigInt(0);
|
|
1484
|
+
ueaBalance = await ctx.pushClient.getBalance(ueaAddress);
|
|
1485
|
+
}
|
|
1486
|
+
else {
|
|
1487
|
+
const [ueaCode, balance] = await Promise.all([
|
|
1213
1488
|
ctx.pushClient.publicClient.getCode({ address: ueaAddress }),
|
|
1214
1489
|
ctx.pushClient.getBalance(ueaAddress),
|
|
1215
1490
|
]);
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1491
|
+
isUEADeployed = ueaCode !== undefined;
|
|
1492
|
+
ueaNonce = isUEADeployed ? await (0, uea_manager_1.getUEANonce)(ctx, ueaAddress) : BigInt(0);
|
|
1493
|
+
ueaBalance = balance;
|
|
1494
|
+
}
|
|
1495
|
+
// PC20 CEA burn uses a different self-call than the SPL drain: the payload
|
|
1496
|
+
// targets `send_universal_tx` (not `send_universal_tx_to_uea`) with the
|
|
1497
|
+
// program-enforced account set, and the finalize dispatcher routes it to
|
|
1498
|
+
// route_pc20_burn_from_finalize_cea. The unlocked Push-native token always
|
|
1499
|
+
// lands on the UEA (recipient == push_account is enforced on-chain);
|
|
1500
|
+
// forwarding to a different destination rides in the push payload.
|
|
1501
|
+
const r3SvmPc20 = params._pc20;
|
|
1502
|
+
let svmPayload;
|
|
1503
|
+
if (r3SvmPc20?.direction === 'import' && drainAmount > BigInt(0)) {
|
|
1504
|
+
const mintPk = new web3_js_1.PublicKey(r3SvmPc20.originAddress);
|
|
1505
|
+
const ceaAta = (0, svm_rent_2.deriveAtaPubkey)(ceaPda, mintPk);
|
|
1506
|
+
const extra = buildR3SvmExtraPayload(ctx, params, ueaAddress, sourceChain, ueaNonce + BigInt(1));
|
|
1507
|
+
(0, gate_1.assertPC20ImportHasPayload)(params, Boolean(extra?.length), `Route 3 SVM ${sourceChain}`);
|
|
1508
|
+
const fields = (0, svm_1.buildPC20SvmCeaBurnPayload)({
|
|
1509
|
+
gatewayProgram: programPk,
|
|
1510
|
+
mint: mintPk,
|
|
1511
|
+
ceaAuthority: ceaPda,
|
|
1512
|
+
ceaAta,
|
|
1513
|
+
ueaAddress,
|
|
1514
|
+
amount: drainAmount,
|
|
1515
|
+
pushPayload: extra ?? new Uint8Array(0),
|
|
1516
|
+
});
|
|
1517
|
+
svmPayload = (0, payload_builders_1.encodeSvmExecutePayload)(fields);
|
|
1518
|
+
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — PC20 burn: mint ${mintPk.toBase58()}, ceaAta ${ceaAta.toBase58()}`);
|
|
1519
|
+
}
|
|
1520
|
+
else {
|
|
1521
|
+
svmPayload = (0, payload_builders_2.encodeSvmCeaToUeaPayload)({
|
|
1219
1522
|
gatewayProgramHex,
|
|
1220
1523
|
drainAmount,
|
|
1221
1524
|
tokenMintHex,
|
|
1222
1525
|
extraPayload: buildR3SvmExtraPayload(ctx, params, ueaAddress, sourceChain, ueaNonce + BigInt(1)),
|
|
1223
1526
|
revertRecipientHex: ceaPdaHex,
|
|
1224
1527
|
});
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
let result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, outboundReq.gasLimit, sourceChain);
|
|
1242
|
-
result = yield (0, svm_rent_1.ensureSvmFinalizeGasBudgetQuote)({
|
|
1243
|
-
ctx,
|
|
1244
|
-
ueaAddress,
|
|
1245
|
-
targetChain: sourceChain,
|
|
1246
|
-
prc20Token,
|
|
1247
|
-
quote: result,
|
|
1248
|
-
splMintBase58: undefined,
|
|
1249
|
-
burnAmount,
|
|
1250
|
-
pathTag: 'executeCeaToPushSvm',
|
|
1251
|
-
});
|
|
1252
|
-
gasToken = result.gasToken;
|
|
1253
|
-
gasFee = result.gasFee;
|
|
1254
|
-
protocolFeeR3Svm = result.protocolFee;
|
|
1255
|
-
nativeValueForGas = result.nativeValueForGas;
|
|
1256
|
-
sizingDecisionR3Svm = result.sizing;
|
|
1257
|
-
effectiveGasLimit = result.gasLimitUsed;
|
|
1258
|
-
outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, prc20Token, burnAmount, effectiveGasLimit, svmPayload, ueaAddress, (_c = params.maxPCForGas) !== null && _c !== void 0 ? _c : BigInt(0));
|
|
1259
|
-
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — resolved effectiveGasLimit: ${effectiveGasLimit} ` +
|
|
1260
|
-
`(gasFee=${result.gasFee}, gasPrice=${result.gasPrice})`);
|
|
1261
|
-
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — gasFee: ${gasFee.toString()}, gasToken: ${gasToken}, nativeValueForGas: ${nativeValueForGas.toString()}, sizing=${(_e = (_d = result.sizing) === null || _d === void 0 ? void 0 : _d.category) !== null && _e !== void 0 ? _e : 'legacy'}`);
|
|
1262
|
-
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_302_02, sourceChain, protocolFeeR3Svm, gasFee);
|
|
1263
|
-
}
|
|
1264
|
-
catch (err) {
|
|
1265
|
-
throw new Error(`Failed to query outbound gas fee for SVM Route 3: ${err}`);
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
// Account-resolution checkpoint fires before the gas-sizing sub-hook so
|
|
1269
|
-
// consumers see the full account-resolution phase (303-xx) complete before
|
|
1270
|
-
// the sizing decision is surfaced.
|
|
1271
|
-
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_01, sourceChain);
|
|
1272
|
-
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_02, ueaAddress, lockerContract, sourceChain);
|
|
1273
|
-
if (sizingDecisionR3Svm) {
|
|
1274
|
-
fireSizingHook(ctx, 'R3', sourceChain, sizingDecisionR3Svm);
|
|
1275
|
-
}
|
|
1276
|
-
// R3 SVM Case C: same minimal interpretation as R3 EVM — bump msg.value
|
|
1277
|
-
// by the overflow so swapAndBurnGas can afford the full gas cost. No
|
|
1278
|
-
// bridge-swap entries (R3 has no destination funds-delivery semantic).
|
|
1279
|
-
if ((sizingDecisionR3Svm === null || sizingDecisionR3Svm === void 0 ? void 0 : sizingDecisionR3Svm.category) === 'C' &&
|
|
1280
|
-
sizingDecisionR3Svm.overflowNativePc > BigInt(0)) {
|
|
1281
|
-
const bumped = nativeValueForGas + sizingDecisionR3Svm.overflowNativePc;
|
|
1282
|
-
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — Case C: bumping nativeValueForGas from ${nativeValueForGas} to ${bumped} (overflow=${sizingDecisionR3Svm.overflowNativePc})`);
|
|
1283
|
-
nativeValueForGas = bumped;
|
|
1284
|
-
}
|
|
1285
|
-
// Re-fetch balance to minimize staleness from gas fee query RPC roundtrips.
|
|
1286
|
-
const currentBalance = yield ctx.pushClient.getBalance(ueaAddress);
|
|
1287
|
-
// Cosmos-EVM tx overhead costs ~1 PC per operation; 3 PC covers approve(s) + buffer.
|
|
1288
|
-
const OUTBOUND_GAS_RESERVE_R3_SVM = BigInt(3e18);
|
|
1289
|
-
// Upward-allocation ceiling: cap the upward adjust at max(200 PC, 5x calibrated)
|
|
1290
|
-
// so a UEA with a giant balance doesn't submit msg.value=balance for a small
|
|
1291
|
-
// gas swap. The contract refunds excess via swapAndBurnGas, but pushing
|
|
1292
|
-
// hundreds of thousands of PC into a Uniswap pool moves the price for other
|
|
1293
|
-
// users. 200 PC mirrors EVM's safety cap; 5x calibrated covers pool-skew
|
|
1294
|
-
// scenarios where the calibrated value itself is > 40 PC (5x40 = 200).
|
|
1295
|
-
const UPWARD_BASE_CEILING_R3_SVM = BigInt(200) * BigInt(1e18);
|
|
1296
|
-
const calibratedCeiling = nativeValueForGas * BigInt(5);
|
|
1297
|
-
const upwardCeiling = calibratedCeiling > UPWARD_BASE_CEILING_R3_SVM
|
|
1298
|
-
? calibratedCeiling
|
|
1299
|
-
: UPWARD_BASE_CEILING_R3_SVM;
|
|
1300
|
-
// Upward-only allocation: when balance has headroom over the calibrated
|
|
1301
|
-
// value, consume more of it (up to the ceiling above). Down-clamp +
|
|
1302
|
-
// zero-balance handling lives in pre-flight.
|
|
1303
|
-
// Ordering note: pre-flight runs AFTER the upward adjust below; the
|
|
1304
|
-
// semantics are equivalent to running it before — when upward adjust
|
|
1305
|
-
// applies it sets nativeValueForGas to balance-reserve (capped), and the
|
|
1306
|
-
// post-adjust pre-flight check is always satisfied. When upward adjust is
|
|
1307
|
-
// skipped (balance too low), pre-flight runs against the calibrated value
|
|
1308
|
-
// and either warns or throws based on options.enforceGasCheck.
|
|
1309
|
-
if (currentBalance > OUTBOUND_GAS_RESERVE_R3_SVM &&
|
|
1310
|
-
currentBalance - OUTBOUND_GAS_RESERVE_R3_SVM > nativeValueForGas) {
|
|
1311
|
-
const balanceDriven = currentBalance - OUTBOUND_GAS_RESERVE_R3_SVM;
|
|
1312
|
-
const adjustedValue = balanceDriven > upwardCeiling ? upwardCeiling : balanceDriven;
|
|
1313
|
-
if (adjustedValue !== nativeValueForGas) {
|
|
1314
|
-
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — adjusting nativeValueForGas from ${nativeValueForGas.toString()} to ${adjustedValue.toString()} ` +
|
|
1315
|
-
`(UEA balance: ${currentBalance.toString()}, upwardCeiling: ${upwardCeiling.toString()})`);
|
|
1316
|
-
nativeValueForGas = adjustedValue;
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
// SVM warn-threshold telemetry (no truncation — pre-flight handles drain protection).
|
|
1320
|
-
(0, preflight_1.maybeFireSvmWarnThreshold)(ctx, nativeValueForGas, gasToken, 'R3_SVM');
|
|
1321
|
-
const requiredNativeBalanceR3Svm = getRequiredNativeBalanceForOutbound(nativeValueForGas, OUTBOUND_GAS_RESERVE_R3_SVM);
|
|
1322
|
-
const skipFeeLockingR3Svm = shouldSkipFeeLockingForOutbound({
|
|
1323
|
-
isUEADeployed,
|
|
1324
|
-
balance: currentBalance,
|
|
1325
|
-
nativeValueForGas,
|
|
1326
|
-
gasReserve: OUTBOUND_GAS_RESERVE_R3_SVM,
|
|
1327
|
-
});
|
|
1328
|
-
// Pre-flight (R3 SVM): no PRC-20 burn check. The drain amount lives in the
|
|
1329
|
-
// embedded Solana instruction and is withdrawn from the external CEA.
|
|
1330
|
-
(0, preflight_1.runPreflight)({
|
|
1331
|
-
ctx,
|
|
1332
|
-
ueaAddress,
|
|
1333
|
-
ueaBalance: currentBalance,
|
|
1334
|
-
requiredValue: nativeValueForGas,
|
|
1335
|
-
gasReserve: OUTBOUND_GAS_RESERVE_R3_SVM,
|
|
1336
|
-
pathTag: 'R3_SVM',
|
|
1337
|
-
enforceGasCheck: ((_f = params.options) === null || _f === void 0 ? void 0 : _f.enforceGasCheck) === true,
|
|
1338
|
-
});
|
|
1339
|
-
// Build Push Chain multicalls (approvals + sendUniversalTxOutbound)
|
|
1340
|
-
const pushChainMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
1341
|
-
prc20Token,
|
|
1342
|
-
gasToken,
|
|
1343
|
-
burnAmount,
|
|
1344
|
-
gasFee,
|
|
1345
|
-
nativeValueForGas,
|
|
1346
|
-
gatewayPcAddress,
|
|
1347
|
-
outboundRequest: outboundReq,
|
|
1348
|
-
});
|
|
1349
|
-
// Sum native values from multicall entries for proper fee calculation
|
|
1350
|
-
const multicallNativeValue = pushChainMulticalls.reduce((sum, mc) => { var _a; return sum + ((_a = mc.value) !== null && _a !== void 0 ? _a : BigInt(0)); }, BigInt(0));
|
|
1351
|
-
// Execute through the normal execute() flow
|
|
1352
|
-
const executeParams = Object.assign({ to: ueaAddress, value: multicallNativeValue, data: pushChainMulticalls, _ueaStatus: {
|
|
1353
|
-
isDeployed: isUEADeployed,
|
|
1354
|
-
nonce: ueaNonce,
|
|
1355
|
-
balance: currentBalance,
|
|
1356
|
-
}, _skipFeeLocking: skipFeeLockingR3Svm, _requiredFundsOverride: requiredNativeBalanceR3Svm }, (!skipFeeLockingR3Svm ? { _minimumDepositUsd: utils_1.Utils.helpers.parseUnits('10', 8) } : {}));
|
|
1357
|
-
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_304_01);
|
|
1358
|
-
let response;
|
|
1528
|
+
}
|
|
1529
|
+
(0, payload_builders_2.assertSvmPayloadWithinRelayLimit)(svmPayload, `Route 3 SVM ${sourceChain}`);
|
|
1530
|
+
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — drainAmount: ${drainAmount.toString()}, payload length: ${(svmPayload.length - 2) / 2} bytes`);
|
|
1531
|
+
const burnAmount = BigInt(0);
|
|
1532
|
+
let effectiveGasLimit = params.gasLimit ?? BigInt(0);
|
|
1533
|
+
// Build outbound request: recipient = gateway program (self-call)
|
|
1534
|
+
let outboundReq = (0, payload_builders_2.buildOutboundRequest)(gatewayProgramHex, prc20Token, burnAmount, effectiveGasLimit, svmPayload, ueaAddress, params.maxPCForGas ?? BigInt(0));
|
|
1535
|
+
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — outbound request: recipient=${outboundReq.recipient.slice(0, 20)}..., token=${outboundReq.token}, maxPCForGas=${outboundReq.maxPCForGas}`);
|
|
1536
|
+
// Query gas fees
|
|
1537
|
+
let gasFee = BigInt(0);
|
|
1538
|
+
let protocolFeeR3Svm = BigInt(0);
|
|
1539
|
+
let nativeValueForGas = BigInt(0);
|
|
1540
|
+
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
1541
|
+
let sizingDecisionR3Svm;
|
|
1542
|
+
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
1543
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_302_01, sourceChain);
|
|
1359
1544
|
try {
|
|
1360
|
-
|
|
1545
|
+
let result = await (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, outboundReq.gasLimit, sourceChain);
|
|
1546
|
+
result = await (0, svm_rent_1.ensureSvmFinalizeGasBudgetQuote)({
|
|
1547
|
+
ctx,
|
|
1548
|
+
ueaAddress,
|
|
1549
|
+
targetChain: sourceChain,
|
|
1550
|
+
prc20Token,
|
|
1551
|
+
quote: result,
|
|
1552
|
+
splMintBase58: undefined,
|
|
1553
|
+
burnAmount,
|
|
1554
|
+
pathTag: 'executeCeaToPushSvm',
|
|
1555
|
+
});
|
|
1556
|
+
gasToken = result.gasToken;
|
|
1557
|
+
gasFee = result.gasFee;
|
|
1558
|
+
protocolFeeR3Svm = result.protocolFee;
|
|
1559
|
+
nativeValueForGas = result.nativeValueForGas;
|
|
1560
|
+
sizingDecisionR3Svm = result.sizing;
|
|
1561
|
+
effectiveGasLimit = result.gasLimitUsed;
|
|
1562
|
+
outboundReq = (0, payload_builders_2.buildOutboundRequest)(gatewayProgramHex, prc20Token, burnAmount, effectiveGasLimit, svmPayload, ueaAddress, params.maxPCForGas ?? BigInt(0));
|
|
1563
|
+
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — resolved effectiveGasLimit: ${effectiveGasLimit} ` +
|
|
1564
|
+
`(gasFee=${result.gasFee}, gasPrice=${result.gasPrice})`);
|
|
1565
|
+
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — gasFee: ${gasFee.toString()}, gasToken: ${gasToken}, nativeValueForGas: ${nativeValueForGas.toString()}, sizing=${result.sizing?.category ?? 'legacy'}`);
|
|
1566
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_302_02, sourceChain, protocolFeeR3Svm, gasFee);
|
|
1361
1567
|
}
|
|
1362
1568
|
catch (err) {
|
|
1363
|
-
|
|
1364
|
-
// Suppress the outer orchestrator catch's 399-02 terminal.
|
|
1365
|
-
ctx._routeTerminalEmitted = true;
|
|
1366
|
-
throw err;
|
|
1569
|
+
throw new Error(`Failed to query outbound gas fee for SVM Route 3: ${err}`);
|
|
1367
1570
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1571
|
+
}
|
|
1572
|
+
// Account-resolution checkpoint fires before the gas-sizing sub-hook so
|
|
1573
|
+
// consumers see the full account-resolution phase (303-xx) complete before
|
|
1574
|
+
// the sizing decision is surfaced.
|
|
1575
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_01, sourceChain);
|
|
1576
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_02, ueaAddress, lockerContract, sourceChain);
|
|
1577
|
+
if (sizingDecisionR3Svm) {
|
|
1578
|
+
fireSizingHook(ctx, 'R3', sourceChain, sizingDecisionR3Svm);
|
|
1579
|
+
}
|
|
1580
|
+
// R3 SVM Case C: same minimal interpretation as R3 EVM — bump msg.value
|
|
1581
|
+
// by the overflow so swapAndBurnGas can afford the full gas cost. No
|
|
1582
|
+
// bridge-swap entries (R3 has no destination funds-delivery semantic).
|
|
1583
|
+
if (sizingDecisionR3Svm?.category === 'C' &&
|
|
1584
|
+
sizingDecisionR3Svm.overflowNativePc > BigInt(0)) {
|
|
1585
|
+
const bumped = nativeValueForGas + sizingDecisionR3Svm.overflowNativePc;
|
|
1586
|
+
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — Case C: bumping nativeValueForGas from ${nativeValueForGas} to ${bumped} (overflow=${sizingDecisionR3Svm.overflowNativePc})`);
|
|
1587
|
+
nativeValueForGas = bumped;
|
|
1588
|
+
}
|
|
1589
|
+
// Re-fetch balance to minimize staleness from gas fee query RPC roundtrips.
|
|
1590
|
+
const currentBalance = await ctx.pushClient.getBalance(ueaAddress);
|
|
1591
|
+
// Cosmos-EVM tx overhead costs ~1 PC per operation; 3 PC covers approve(s) + buffer.
|
|
1592
|
+
const OUTBOUND_GAS_RESERVE_R3_SVM = BigInt(3e18);
|
|
1593
|
+
// Upward-allocation ceiling: cap the upward adjust at max(200 PC, 5x calibrated)
|
|
1594
|
+
// so a UEA with a giant balance doesn't submit msg.value=balance for a small
|
|
1595
|
+
// gas swap. The contract refunds excess via swapAndBurnGas, but pushing
|
|
1596
|
+
// hundreds of thousands of PC into a Uniswap pool moves the price for other
|
|
1597
|
+
// users. 200 PC mirrors EVM's safety cap; 5x calibrated covers pool-skew
|
|
1598
|
+
// scenarios where the calibrated value itself is > 40 PC (5x40 = 200).
|
|
1599
|
+
const UPWARD_BASE_CEILING_R3_SVM = BigInt(200) * BigInt(1e18);
|
|
1600
|
+
const calibratedCeiling = nativeValueForGas * BigInt(5);
|
|
1601
|
+
const upwardCeiling = calibratedCeiling > UPWARD_BASE_CEILING_R3_SVM
|
|
1602
|
+
? calibratedCeiling
|
|
1603
|
+
: UPWARD_BASE_CEILING_R3_SVM;
|
|
1604
|
+
// Upward-only allocation: when balance has headroom over the calibrated
|
|
1605
|
+
// value, consume more of it (up to the ceiling above). Down-clamp +
|
|
1606
|
+
// zero-balance handling lives in pre-flight.
|
|
1607
|
+
// Ordering note: pre-flight runs AFTER the upward adjust below; the
|
|
1608
|
+
// semantics are equivalent to running it before — when upward adjust
|
|
1609
|
+
// applies it sets nativeValueForGas to balance-reserve (capped), and the
|
|
1610
|
+
// post-adjust pre-flight check is always satisfied. When upward adjust is
|
|
1611
|
+
// skipped (balance too low), pre-flight runs against the calibrated value
|
|
1612
|
+
// and either warns or throws based on options.enforceGasCheck.
|
|
1613
|
+
if (currentBalance > OUTBOUND_GAS_RESERVE_R3_SVM &&
|
|
1614
|
+
currentBalance - OUTBOUND_GAS_RESERVE_R3_SVM > nativeValueForGas) {
|
|
1615
|
+
const balanceDriven = currentBalance - OUTBOUND_GAS_RESERVE_R3_SVM;
|
|
1616
|
+
const adjustedValue = balanceDriven > upwardCeiling ? upwardCeiling : balanceDriven;
|
|
1617
|
+
if (adjustedValue !== nativeValueForGas) {
|
|
1618
|
+
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — adjusting nativeValueForGas from ${nativeValueForGas.toString()} to ${adjustedValue.toString()} ` +
|
|
1619
|
+
`(UEA balance: ${currentBalance.toString()}, upwardCeiling: ${upwardCeiling.toString()})`);
|
|
1620
|
+
nativeValueForGas = adjustedValue;
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
// SVM warn-threshold telemetry (no truncation — pre-flight handles drain protection).
|
|
1624
|
+
(0, preflight_1.maybeFireSvmWarnThreshold)(ctx, nativeValueForGas, gasToken, 'R3_SVM');
|
|
1625
|
+
const requiredNativeBalanceR3Svm = getRequiredNativeBalanceForOutbound(nativeValueForGas, OUTBOUND_GAS_RESERVE_R3_SVM);
|
|
1626
|
+
const skipFeeLockingR3Svm = shouldSkipFeeLockingForOutbound({
|
|
1627
|
+
isUEADeployed,
|
|
1628
|
+
balance: currentBalance,
|
|
1629
|
+
nativeValueForGas,
|
|
1630
|
+
gasReserve: OUTBOUND_GAS_RESERVE_R3_SVM,
|
|
1381
1631
|
});
|
|
1632
|
+
// Pre-flight (R3 SVM): no PRC-20 burn check. The drain amount lives in the
|
|
1633
|
+
// embedded Solana instruction and is withdrawn from the external CEA.
|
|
1634
|
+
(0, preflight_1.runPreflight)({
|
|
1635
|
+
ctx,
|
|
1636
|
+
ueaAddress,
|
|
1637
|
+
ueaBalance: currentBalance,
|
|
1638
|
+
requiredValue: nativeValueForGas,
|
|
1639
|
+
gasReserve: OUTBOUND_GAS_RESERVE_R3_SVM,
|
|
1640
|
+
pathTag: 'R3_SVM',
|
|
1641
|
+
enforceGasCheck: params.options?.enforceGasCheck === true,
|
|
1642
|
+
});
|
|
1643
|
+
// Build Push Chain multicalls (approvals + sendUniversalTxOutbound)
|
|
1644
|
+
const pushChainMulticalls = (0, payload_builders_2.buildOutboundApprovalAndCall)({
|
|
1645
|
+
prc20Token,
|
|
1646
|
+
gasToken,
|
|
1647
|
+
burnAmount,
|
|
1648
|
+
gasFee,
|
|
1649
|
+
nativeValueForGas,
|
|
1650
|
+
gatewayPcAddress,
|
|
1651
|
+
outboundRequest: outboundReq,
|
|
1652
|
+
});
|
|
1653
|
+
// Sum native values from multicall entries for proper fee calculation
|
|
1654
|
+
const multicallNativeValue = pushChainMulticalls.reduce((sum, mc) => sum + (mc.value ?? BigInt(0)), BigInt(0));
|
|
1655
|
+
// Execute through the normal execute() flow
|
|
1656
|
+
const executeParams = {
|
|
1657
|
+
to: ueaAddress,
|
|
1658
|
+
value: multicallNativeValue, // ensures correct requiredFunds calculation
|
|
1659
|
+
data: pushChainMulticalls,
|
|
1660
|
+
_ueaStatus: {
|
|
1661
|
+
isDeployed: isUEADeployed,
|
|
1662
|
+
nonce: ueaNonce,
|
|
1663
|
+
balance: currentBalance,
|
|
1664
|
+
},
|
|
1665
|
+
_skipFeeLocking: skipFeeLockingR3Svm,
|
|
1666
|
+
_requiredFundsOverride: requiredNativeBalanceR3Svm,
|
|
1667
|
+
// Ensure fee-locking deposits enough for the outbound swap whenever it is needed.
|
|
1668
|
+
...(!skipFeeLockingR3Svm ? { _minimumDepositUsd: utils_1.Utils.helpers.parseUnits('10', 8) } : {}),
|
|
1669
|
+
};
|
|
1670
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_304_01);
|
|
1671
|
+
let response;
|
|
1672
|
+
try {
|
|
1673
|
+
response = await executeFn(executeParams);
|
|
1674
|
+
}
|
|
1675
|
+
catch (err) {
|
|
1676
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_304_04, err instanceof Error ? err.message : String(err));
|
|
1677
|
+
// Suppress the outer orchestrator catch's 399-02 terminal.
|
|
1678
|
+
ctx._routeTerminalEmitted = true;
|
|
1679
|
+
throw err;
|
|
1680
|
+
}
|
|
1681
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_304_02);
|
|
1682
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_304_03);
|
|
1683
|
+
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_307, sourceChain);
|
|
1684
|
+
// Add Route 3 SVM context to response
|
|
1685
|
+
response.chain = sourceChain;
|
|
1686
|
+
const chainInfo = chain_1.CHAIN_INFO[sourceChain];
|
|
1687
|
+
response.chainNamespace = `${chain_1.VM_NAMESPACE[chainInfo.vm]}:${chainInfo.chainId}`;
|
|
1688
|
+
// R3 SVM mirrors EVM R3: when funds drain back to Push, the Solana-side
|
|
1689
|
+
// send_universal_tx_to_uea emits an inbound leg whose payload can execute on
|
|
1690
|
+
// Push. Payload-only R3 has no child inbound UTX, so skip round-trip tracking
|
|
1691
|
+
// when no amount is being drained.
|
|
1692
|
+
response._expectsInboundRoundTrip = drainAmount > BigInt(0);
|
|
1693
|
+
return response;
|
|
1382
1694
|
}
|
|
1383
1695
|
// ---------------------------------------------------------------------------
|
|
1384
1696
|
// executeCeaToCea (Route 4 — stub)
|
|
@@ -1386,13 +1698,11 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
|
|
|
1386
1698
|
/**
|
|
1387
1699
|
* Route 4: Execute CEA to CEA transaction via Push Chain.
|
|
1388
1700
|
*/
|
|
1389
|
-
function executeCeaToCea(ctx, params, executeFn) {
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
'Use prepareTransaction() and executeTransactions() to chain Route 3 -> Route 2 manually.');
|
|
1395
|
-
});
|
|
1701
|
+
async function executeCeaToCea(ctx, params, executeFn) {
|
|
1702
|
+
// CEA -> CEA requires chaining Route 3 (CEA -> Push) and Route 2 (Push -> CEA)
|
|
1703
|
+
// This is a complex flow that requires coordination
|
|
1704
|
+
throw new Error('CEA -> CEA transactions are not yet fully implemented. ' +
|
|
1705
|
+
'Use prepareTransaction() and executeTransactions() to chain Route 3 -> Route 2 manually.');
|
|
1396
1706
|
}
|
|
1397
1707
|
// ---------------------------------------------------------------------------
|
|
1398
1708
|
// buildPayloadForRoute
|
|
@@ -1400,163 +1710,187 @@ function executeCeaToCea(ctx, params, executeFn) {
|
|
|
1400
1710
|
/**
|
|
1401
1711
|
* Build payload for a specific route.
|
|
1402
1712
|
*/
|
|
1403
|
-
function buildPayloadForRoute(ctx, params, route, nonce) {
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1713
|
+
async function buildPayloadForRoute(ctx, params, route, nonce) {
|
|
1714
|
+
const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
|
|
1715
|
+
switch (route) {
|
|
1716
|
+
case route_detector_1.TransactionRoute.UOA_TO_PUSH: {
|
|
1717
|
+
// Build standard Push Chain payload
|
|
1718
|
+
const executeParams = (0, helpers_1.toExecuteParams)(params);
|
|
1719
|
+
const multicallData = (0, payload_builders_2.buildExecuteMulticall)({
|
|
1720
|
+
execute: (0, gate_1.assertLegacyFunds)(executeParams),
|
|
1721
|
+
ueaAddress,
|
|
1722
|
+
});
|
|
1723
|
+
const payload = (0, payload_builder_1.buildMulticallPayloadData)(ctx, executeParams.to, multicallData);
|
|
1724
|
+
const req = (0, payload_builder_1.buildUniversalTxRequest)(ctx.universalSigner.account.address, {
|
|
1725
|
+
recipient: viem_1.zeroAddress,
|
|
1726
|
+
token: viem_1.zeroAddress,
|
|
1727
|
+
amount: BigInt(0),
|
|
1728
|
+
payload,
|
|
1729
|
+
});
|
|
1730
|
+
return { payload, gatewayRequest: req };
|
|
1731
|
+
}
|
|
1732
|
+
case route_detector_1.TransactionRoute.UOA_TO_CEA: {
|
|
1733
|
+
const target = params.to;
|
|
1734
|
+
// Branch: SVM vs EVM
|
|
1735
|
+
if ((0, payload_builders_2.isSvmChain)(target.chain)) {
|
|
1736
|
+
// SVM: build SVM payload (binary or empty for withdraw) via IDL resolver
|
|
1737
|
+
const { svmPayload: payload, targetBytes, hasExecute, } = (0, build_payload_1.buildSvmPayloadFromParams)({
|
|
1738
|
+
data: params.data,
|
|
1739
|
+
to: target,
|
|
1740
|
+
senderUea: ueaAddress,
|
|
1421
1741
|
});
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
data: params.data,
|
|
1431
|
-
to: target,
|
|
1432
|
-
senderUea: ueaAddress,
|
|
1433
|
-
});
|
|
1434
|
-
(0, payload_builders_1.assertSvmPayloadWithinRelayLimit)(payload, `Route 2 SVM ${target.chain}`);
|
|
1435
|
-
let prc20Token = selectors_1.ZERO_ADDRESS;
|
|
1436
|
-
let burnAmount = BigInt(0);
|
|
1437
|
-
if ((_a = params.funds) === null || _a === void 0 ? void 0 : _a.amount) {
|
|
1438
|
-
const token = params.funds.token;
|
|
1439
|
-
if (token) {
|
|
1440
|
-
prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
|
|
1441
|
-
burnAmount = params.funds.amount;
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
else if (params.value && params.value > BigInt(0)) {
|
|
1445
|
-
prc20Token = (0, helpers_1.getNativePRC20ForChain)(target.chain, ctx.pushNetwork);
|
|
1446
|
-
burnAmount = params.value;
|
|
1447
|
-
}
|
|
1448
|
-
else if (hasExecute) {
|
|
1449
|
-
prc20Token = (0, helpers_1.getNativePRC20ForChain)(target.chain, ctx.pushNetwork);
|
|
1450
|
-
burnAmount = BigInt(0);
|
|
1742
|
+
(0, payload_builders_2.assertSvmPayloadWithinRelayLimit)(payload, `Route 2 SVM ${target.chain}`);
|
|
1743
|
+
let prc20Token = selectors_1.ZERO_ADDRESS;
|
|
1744
|
+
let burnAmount = BigInt(0);
|
|
1745
|
+
if (params.funds?.amount) {
|
|
1746
|
+
const token = params.funds.token;
|
|
1747
|
+
if (token) {
|
|
1748
|
+
prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
|
|
1749
|
+
burnAmount = params.funds.amount;
|
|
1451
1750
|
}
|
|
1452
|
-
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetBytes, prc20Token, burnAmount, (_b = params.gasLimit) !== null && _b !== void 0 ? _b : BigInt(0), payload, ueaAddress, (_c = params.maxPCForGas) !== null && _c !== void 0 ? _c : BigInt(0));
|
|
1453
|
-
return { payload, gatewayRequest: outboundReq };
|
|
1454
1751
|
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
const outboundReq = (0,
|
|
1752
|
+
else if (params.value && params.value > BigInt(0)) {
|
|
1753
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(target.chain, ctx.pushNetwork);
|
|
1754
|
+
burnAmount = params.value;
|
|
1755
|
+
}
|
|
1756
|
+
else if (hasExecute) {
|
|
1757
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(target.chain, ctx.pushNetwork);
|
|
1758
|
+
burnAmount = BigInt(0);
|
|
1759
|
+
}
|
|
1760
|
+
const outboundReq = (0, payload_builders_2.buildOutboundRequest)(targetBytes, prc20Token, burnAmount, params.gasLimit ?? BigInt(0), payload, ueaAddress, params.maxPCForGas ?? BigInt(0));
|
|
1464
1761
|
return { payload, gatewayRequest: outboundReq };
|
|
1465
1762
|
}
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1763
|
+
// EVM path: Resolve CEA address first (needed for self-transfer check)
|
|
1764
|
+
const { cea: ceaAddress } = await (0, cea_utils_1.getCEAAddress)(ueaAddress, target.chain, ctx.rpcUrls[target.chain]?.[0]);
|
|
1765
|
+
const payload = buildR2CeaPayloadEvm(params, ceaAddress, target.address, target.chain, ctx.pushNetwork);
|
|
1766
|
+
const { prc20Token, burnAmount } = resolveR2Prc20TokenEvm(params, target.chain, ctx.pushNetwork);
|
|
1767
|
+
const targetBytes = payload === '0x'
|
|
1768
|
+
? selectors_1.ZERO_ADDRESS
|
|
1769
|
+
: ceaAddress;
|
|
1770
|
+
(0, payload_builders_2.assertCeaFundsParkingInvariant)(targetBytes, payload);
|
|
1771
|
+
const outboundReq = (0, payload_builders_2.buildOutboundRequest)(targetBytes, prc20Token, burnAmount, params.gasLimit ?? BigInt(0), payload, ueaAddress, params.maxPCForGas ?? BigInt(0));
|
|
1772
|
+
return { payload, gatewayRequest: outboundReq };
|
|
1773
|
+
}
|
|
1774
|
+
case route_detector_1.TransactionRoute.CEA_TO_PUSH: {
|
|
1775
|
+
// Route 3: CEA -> Push Chain
|
|
1776
|
+
// Build CEA multicall (sendUniversalTxToUEA self-call) and wrap in outbound
|
|
1777
|
+
if (!params.from?.chain) {
|
|
1778
|
+
throw new Error('Route 3 (CEA -> Push) requires from.chain');
|
|
1779
|
+
}
|
|
1780
|
+
const sourceChain = params.from.chain;
|
|
1781
|
+
const pushDestination = params.to;
|
|
1782
|
+
// SVM chains use gateway self-call, not CEA multicall
|
|
1783
|
+
if ((0, payload_builders_2.isSvmChain)(sourceChain)) {
|
|
1784
|
+
const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
|
|
1785
|
+
if (!lockerContract) {
|
|
1786
|
+
throw new Error(`No SVM gateway program configured for chain ${sourceChain}`);
|
|
1471
1787
|
}
|
|
1472
|
-
const
|
|
1473
|
-
const
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
const
|
|
1485
|
-
const
|
|
1486
|
-
|
|
1487
|
-
|
|
1788
|
+
const programPk = new web3_js_1.PublicKey(lockerContract);
|
|
1789
|
+
const gatewayProgramHex = ('0x' + Buffer.from(programPk.toBytes()).toString('hex'));
|
|
1790
|
+
const { prc20Token, drainAmount, tokenMintHex } = resolveR3SvmDrain(params, sourceChain, ctx.pushNetwork);
|
|
1791
|
+
// Derive CEA PDA as revert recipient
|
|
1792
|
+
const ueaBytes2 = Buffer.from(ueaAddress.slice(2), 'hex');
|
|
1793
|
+
const [ceaPda2] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('push_identity'), ueaBytes2], programPk);
|
|
1794
|
+
const ceaPdaHex2 = ('0x' + Buffer.from(ceaPda2.toBytes()).toString('hex'));
|
|
1795
|
+
const extraPayload = buildR3SvmExtraPayload(ctx, params, ueaAddress, sourceChain, nonce + BigInt(1));
|
|
1796
|
+
(0, gate_1.assertPC20ImportHasPayload)(params, Boolean(extraPayload?.length), `Prepared Route 3 SVM ${sourceChain}`);
|
|
1797
|
+
const r3SvmPc20 = params._pc20;
|
|
1798
|
+
let svmPayload;
|
|
1799
|
+
if ((0, gate_1.isPC20ImportWithFunds)(params) && r3SvmPc20) {
|
|
1800
|
+
const mintPk = new web3_js_1.PublicKey(r3SvmPc20.originAddress);
|
|
1801
|
+
const fields = (0, svm_1.buildPC20SvmCeaBurnPayload)({
|
|
1802
|
+
gatewayProgram: programPk,
|
|
1803
|
+
mint: mintPk,
|
|
1804
|
+
ceaAuthority: ceaPda2,
|
|
1805
|
+
ceaAta: (0, svm_rent_2.deriveAtaPubkey)(ceaPda2, mintPk),
|
|
1806
|
+
ueaAddress,
|
|
1807
|
+
amount: drainAmount,
|
|
1808
|
+
pushPayload: extraPayload ?? new Uint8Array(0),
|
|
1809
|
+
});
|
|
1810
|
+
svmPayload = (0, payload_builders_1.encodeSvmExecutePayload)(fields);
|
|
1811
|
+
}
|
|
1812
|
+
else {
|
|
1813
|
+
svmPayload = (0, payload_builders_2.encodeSvmCeaToUeaPayload)({
|
|
1488
1814
|
gatewayProgramHex,
|
|
1489
1815
|
drainAmount,
|
|
1490
1816
|
tokenMintHex,
|
|
1491
|
-
extraPayload
|
|
1817
|
+
extraPayload,
|
|
1492
1818
|
revertRecipientHex: ceaPdaHex2,
|
|
1493
1819
|
});
|
|
1494
|
-
(0, payload_builders_1.assertSvmPayloadWithinRelayLimit)(svmPayload, `Route 3 SVM ${sourceChain}`);
|
|
1495
|
-
// Payload-only outbound: drain amount lives inside ixData.
|
|
1496
|
-
const burnAmount = BigInt(0);
|
|
1497
|
-
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, prc20Token, burnAmount, (_h = params.gasLimit) !== null && _h !== void 0 ? _h : BigInt(0), svmPayload, ueaAddress, (_j = params.maxPCForGas) !== null && _j !== void 0 ? _j : BigInt(0));
|
|
1498
|
-
return { payload: svmPayload, gatewayRequest: outboundReq };
|
|
1499
1820
|
}
|
|
1500
|
-
|
|
1501
|
-
//
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1821
|
+
(0, payload_builders_2.assertSvmPayloadWithinRelayLimit)(svmPayload, `Route 3 SVM ${sourceChain}`);
|
|
1822
|
+
// Payload-only outbound: drain amount lives inside ixData.
|
|
1823
|
+
const burnAmount = BigInt(0);
|
|
1824
|
+
const outboundReq = (0, payload_builders_2.buildOutboundRequest)(gatewayProgramHex, prc20Token, burnAmount, params.gasLimit ?? BigInt(0), svmPayload, ueaAddress, params.maxPCForGas ?? BigInt(0));
|
|
1825
|
+
return { payload: svmPayload, gatewayRequest: outboundReq };
|
|
1826
|
+
}
|
|
1827
|
+
const { cea: ceaAddress } = await (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, ctx.rpcUrls[sourceChain]?.[0]);
|
|
1828
|
+
// CEA auto-deploys on-chain: Vault.finalizeUniversalTx calls CEAFactory.deployCEA()
|
|
1829
|
+
// if CEA doesn't exist yet. No SDK-side blocking needed.
|
|
1830
|
+
// Build CEA multicalls (self-calls via sendUniversalTxToUEA)
|
|
1831
|
+
const ceaMulticalls = [];
|
|
1832
|
+
let tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
1833
|
+
let amount = BigInt(0);
|
|
1834
|
+
if (params.funds?.amount) {
|
|
1835
|
+
const token = params.funds.token;
|
|
1836
|
+
if (token) {
|
|
1837
|
+
if (token.mechanism === 'native') {
|
|
1838
|
+
tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
1839
|
+
amount = params.funds.amount;
|
|
1840
|
+
}
|
|
1841
|
+
else {
|
|
1842
|
+
tokenAddress = token.address;
|
|
1843
|
+
amount = params.funds.amount;
|
|
1518
1844
|
}
|
|
1519
1845
|
}
|
|
1520
|
-
else if (params.value && params.value > BigInt(0)) {
|
|
1521
|
-
tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
1522
|
-
amount = params.value;
|
|
1523
|
-
}
|
|
1524
|
-
// Fetch UEA nonce for inbound UniversalPayload
|
|
1525
|
-
const ueaCodeHop = yield ctx.pushClient.publicClient.getCode({ address: ueaAddress });
|
|
1526
|
-
const ueaNonceHop = ueaCodeHop !== undefined ? yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress) : BigInt(0);
|
|
1527
|
-
// Build Push Chain payload (what executes after inbound arrives)
|
|
1528
|
-
// Wrap in UniversalPayload struct for the relay.
|
|
1529
|
-
let pushPayload = '0x';
|
|
1530
|
-
if ((0, data_utils_1.hasExecutablePayloadData)(params.data) ||
|
|
1531
|
-
(((_m = params.funds) === null || _m === void 0 ? void 0 : _m.amount) && ((_o = params.value) !== null && _o !== void 0 ? _o : BigInt(0)) > BigInt(0))) {
|
|
1532
|
-
const multicallData = (0, payload_builders_1.buildExecuteMulticall)({
|
|
1533
|
-
execute: {
|
|
1534
|
-
to: pushDestination,
|
|
1535
|
-
value: params.value,
|
|
1536
|
-
data: params.data,
|
|
1537
|
-
},
|
|
1538
|
-
ueaAddress,
|
|
1539
|
-
allowSelfValueCall: true,
|
|
1540
|
-
});
|
|
1541
|
-
const multicallPayload = (0, payload_builder_1.buildMulticallPayloadData)(ctx, pushDestination, multicallData);
|
|
1542
|
-
pushPayload = (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, { nonce: ueaNonceHop + BigInt(1) });
|
|
1543
|
-
}
|
|
1544
|
-
// Build sendUniversalTxToUEA self-call on CEA. CEA self-calls must
|
|
1545
|
-
// always use value=0; native sends spend from the CEA balance.
|
|
1546
|
-
const sendCall = (0, payload_builders_1.buildSendUniversalTxToUEA)(ceaAddress, tokenAddress, amount, pushPayload, ceaAddress);
|
|
1547
|
-
ceaMulticalls.push(sendCall);
|
|
1548
|
-
const ceaPayload = (0, payload_builders_1.buildCeaMulticallPayload)(ceaMulticalls);
|
|
1549
|
-
const prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
1550
|
-
// Route 3: CEA uses its own pre-existing balance — no PRC-20 burn needed.
|
|
1551
|
-
// burnAmount = 0 makes this a payload-only outbound relay.
|
|
1552
|
-
const burnAmount = BigInt(0);
|
|
1553
|
-
const outboundGasLimit = (_p = params.gasLimit) !== null && _p !== void 0 ? _p : selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT;
|
|
1554
|
-
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, prc20Token, burnAmount, outboundGasLimit, ceaPayload, ueaAddress, (_q = params.maxPCForGas) !== null && _q !== void 0 ? _q : BigInt(0));
|
|
1555
|
-
return { payload: ceaPayload, gatewayRequest: outboundReq };
|
|
1556
1846
|
}
|
|
1557
|
-
|
|
1558
|
-
|
|
1847
|
+
else if (params.value && params.value > BigInt(0)) {
|
|
1848
|
+
tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
1849
|
+
amount = params.value;
|
|
1850
|
+
}
|
|
1851
|
+
// prepareTransaction already resolved the correct nonce semantics. For a
|
|
1852
|
+
// native Push EOA this is zero; for an external-origin account it is the
|
|
1853
|
+
// current UEA nonce. Do not reclassify EIP-7702 code as a deployed UEA.
|
|
1854
|
+
const ueaNonceHop = nonce;
|
|
1855
|
+
// Build Push Chain payload (what executes after inbound arrives)
|
|
1856
|
+
// Wrap in UniversalPayload struct for the relay.
|
|
1857
|
+
let pushPayload = '0x';
|
|
1858
|
+
const r3Pc20 = params._pc20;
|
|
1859
|
+
const r3Pc20NeedsForward = (0, gate_1.isPC20ImportWithFunds)(params);
|
|
1860
|
+
if ((0, data_utils_1.hasExecutablePayloadData)(params.data) ||
|
|
1861
|
+
(params.funds?.amount && (params.value ?? BigInt(0)) > BigInt(0)) ||
|
|
1862
|
+
r3Pc20NeedsForward) {
|
|
1863
|
+
const multicallData = (0, payload_builders_2.buildExecuteMulticall)({
|
|
1864
|
+
execute: {
|
|
1865
|
+
to: pushDestination,
|
|
1866
|
+
value: params.value,
|
|
1867
|
+
data: params.data,
|
|
1868
|
+
...(r3Pc20NeedsForward
|
|
1869
|
+
? { funds: params.funds, _pc20: r3Pc20 }
|
|
1870
|
+
: {}),
|
|
1871
|
+
},
|
|
1872
|
+
ueaAddress,
|
|
1873
|
+
allowSelfValueCall: true,
|
|
1874
|
+
});
|
|
1875
|
+
const multicallPayload = (0, payload_builder_1.buildMulticallPayloadData)(ctx, pushDestination, multicallData);
|
|
1876
|
+
pushPayload = (0, payload_builders_2.buildInboundUniversalPayload)(multicallPayload, { nonce: ueaNonceHop + BigInt(1) });
|
|
1877
|
+
}
|
|
1878
|
+
(0, gate_1.assertPC20ImportHasPayload)(params, pushPayload !== '0x', `Prepared Route 3 EVM ${sourceChain}`);
|
|
1879
|
+
// Build sendUniversalTxToUEA self-call on CEA. CEA self-calls must
|
|
1880
|
+
// always use value=0; native sends spend from the CEA balance.
|
|
1881
|
+
const sendCall = (0, payload_builders_2.buildSendUniversalTxToUEA)(ceaAddress, tokenAddress, amount, pushPayload, ceaAddress);
|
|
1882
|
+
ceaMulticalls.push(sendCall);
|
|
1883
|
+
const ceaPayload = (0, payload_builders_2.buildCeaMulticallPayload)(ceaMulticalls);
|
|
1884
|
+
const prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
1885
|
+
// Route 3: CEA uses its own pre-existing balance — no PRC-20 burn needed.
|
|
1886
|
+
// burnAmount = 0 makes this a payload-only outbound relay.
|
|
1887
|
+
const burnAmount = BigInt(0);
|
|
1888
|
+
const outboundGasLimit = params.gasLimit ?? selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT;
|
|
1889
|
+
const outboundReq = (0, payload_builders_2.buildOutboundRequest)(ceaAddress, prc20Token, burnAmount, outboundGasLimit, ceaPayload, ueaAddress, params.maxPCForGas ?? BigInt(0));
|
|
1890
|
+
return { payload: ceaPayload, gatewayRequest: outboundReq };
|
|
1559
1891
|
}
|
|
1560
|
-
|
|
1892
|
+
default:
|
|
1893
|
+
throw new Error(`Cannot build payload for route: ${route}`);
|
|
1894
|
+
}
|
|
1561
1895
|
}
|
|
1562
1896
|
//# sourceMappingURL=route-handlers.js.map
|