@pushchain/core 5.1.3 → 5.1.4-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +29 -6
- package/src/lib/constants/abi/prc20.evm.d.ts +22 -2
- package/src/lib/constants/abi/prc20.evm.js +11 -3
- package/src/lib/constants/abi/universalGatewayPC.evm.d.ts +59 -0
- package/src/lib/constants/abi/universalGatewayPC.evm.js +26 -0
- package/src/lib/constants/chain.d.ts +4 -0
- package/src/lib/constants/chain.js +6 -2
- package/src/lib/index.d.ts +2 -2
- package/src/lib/internal/bs58.d.ts +2 -0
- package/src/lib/internal/bs58.js +7 -0
- package/src/lib/orchestrator/cea-utils.d.ts +0 -9
- package/src/lib/orchestrator/cea-utils.js +15 -4
- package/src/lib/orchestrator/internals/account-manager.d.ts +16 -0
- package/src/lib/orchestrator/internals/account-manager.js +191 -0
- package/src/lib/orchestrator/internals/cascade.d.ts +23 -0
- package/src/lib/orchestrator/internals/cascade.js +945 -0
- package/src/lib/orchestrator/internals/confirmation.d.ts +9 -0
- package/src/lib/orchestrator/internals/confirmation.js +123 -0
- package/src/lib/orchestrator/internals/context.d.ts +38 -0
- package/src/lib/orchestrator/internals/context.js +49 -0
- package/src/lib/orchestrator/internals/execute-funds-only.d.ts +11 -0
- package/src/lib/orchestrator/internals/execute-funds-only.js +219 -0
- package/src/lib/orchestrator/internals/execute-funds-payload.d.ts +11 -0
- package/src/lib/orchestrator/internals/execute-funds-payload.js +222 -0
- package/src/lib/orchestrator/internals/execute-standard.d.ts +13 -0
- package/src/lib/orchestrator/internals/execute-standard.js +225 -0
- package/src/lib/orchestrator/internals/gas-calculator.d.ts +38 -0
- package/src/lib/orchestrator/internals/gas-calculator.js +424 -0
- package/src/lib/orchestrator/internals/gateway-client.d.ts +24 -0
- package/src/lib/orchestrator/internals/gateway-client.js +191 -0
- package/src/lib/orchestrator/internals/helpers.d.ts +26 -0
- package/src/lib/orchestrator/internals/helpers.js +181 -0
- package/src/lib/orchestrator/internals/index.d.ts +24 -0
- package/src/lib/orchestrator/internals/index.js +132 -0
- package/src/lib/orchestrator/internals/outbound-sync.d.ts +49 -0
- package/src/lib/orchestrator/internals/outbound-sync.js +357 -0
- package/src/lib/orchestrator/internals/outbound-tracker.d.ts +8 -0
- package/src/lib/orchestrator/internals/outbound-tracker.js +64 -0
- package/src/lib/orchestrator/internals/payload-builder.d.ts +24 -0
- package/src/lib/orchestrator/internals/payload-builder.js +178 -0
- package/src/lib/orchestrator/internals/push-chain-tx.d.ts +32 -0
- package/src/lib/orchestrator/internals/push-chain-tx.js +207 -0
- package/src/lib/orchestrator/internals/quote.d.ts +9 -0
- package/src/lib/orchestrator/internals/quote.js +114 -0
- package/src/lib/orchestrator/internals/rescue.d.ts +20 -0
- package/src/lib/orchestrator/internals/rescue.js +112 -0
- package/src/lib/orchestrator/internals/response-builder.d.ts +26 -0
- package/src/lib/orchestrator/internals/response-builder.js +504 -0
- package/src/lib/orchestrator/internals/route-handlers.d.ts +77 -0
- package/src/lib/orchestrator/internals/route-handlers.js +1192 -0
- package/src/lib/orchestrator/internals/signing.d.ts +27 -0
- package/src/lib/orchestrator/internals/signing.js +258 -0
- package/src/lib/orchestrator/internals/svm-bridge.d.ts +21 -0
- package/src/lib/orchestrator/internals/svm-bridge.js +122 -0
- package/src/lib/orchestrator/internals/svm-helpers.d.ts +35 -0
- package/src/lib/orchestrator/internals/svm-helpers.js +143 -0
- package/src/lib/orchestrator/internals/tx-transformer.d.ts +12 -0
- package/src/lib/orchestrator/internals/tx-transformer.js +120 -0
- package/src/lib/orchestrator/internals/uea-manager.d.ts +17 -0
- package/src/lib/orchestrator/internals/uea-manager.js +164 -0
- package/src/lib/orchestrator/orchestrator.d.ts +33 -431
- package/src/lib/orchestrator/orchestrator.js +124 -6125
- package/src/lib/orchestrator/orchestrator.types.d.ts +11 -10
- package/src/lib/orchestrator/payload-builders.js +34 -8
- package/src/lib/price-fetch/price-fetch.js +19 -7
- package/src/lib/push-chain/push-chain.d.ts +6 -0
- package/src/lib/push-chain/push-chain.js +10 -2
- package/src/lib/push-client/push-client.d.ts +3 -0
- package/src/lib/push-client/push-client.js +21 -31
- package/src/lib/universal/account/account.d.ts +35 -16
- package/src/lib/universal/account/account.js +298 -16
- package/src/lib/universal/signer/signer.d.ts +2 -3
- package/src/lib/universal/signer/signer.js +65 -115
- package/src/lib/universal/universal.types.d.ts +41 -0
- package/src/lib/utils.d.ts +7 -2
- package/src/lib/utils.js +65 -34
- package/src/lib/vm-client/evm-client.js +3 -11
- package/src/lib/vm-client/svm-client.d.ts +0 -4
- package/src/lib/vm-client/svm-client.js +2 -8
- package/src/index.js.map +0 -1
- package/src/lib/cache/cache.js.map +0 -1
- package/src/lib/constants/abi/cea.evm.js.map +0 -1
- package/src/lib/constants/abi/ceaFactory.evm.js.map +0 -1
- package/src/lib/constants/abi/erc20.evm.js.map +0 -1
- package/src/lib/constants/abi/factoryV1.js.map +0 -1
- package/src/lib/constants/abi/feeLocker.evm.js.map +0 -1
- package/src/lib/constants/abi/index.js.map +0 -1
- package/src/lib/constants/abi/prc20.evm.js.map +0 -1
- package/src/lib/constants/abi/uea-factory.js.map +0 -1
- package/src/lib/constants/abi/uea.evm.js.map +0 -1
- package/src/lib/constants/abi/uea.svm.js.map +0 -1
- package/src/lib/constants/abi/universalGateway.evm.js.map +0 -1
- package/src/lib/constants/abi/universalGatewayPC.evm.js.map +0 -1
- package/src/lib/constants/abi/universalGatewayV0.evm.js.map +0 -1
- package/src/lib/constants/chain.js.map +0 -1
- package/src/lib/constants/enums.js.map +0 -1
- package/src/lib/constants/index.js.map +0 -1
- package/src/lib/constants/selectors.js.map +0 -1
- package/src/lib/constants/tokens.js.map +0 -1
- package/src/lib/generated/uexecutor/v1/query.js.map +0 -1
- package/src/lib/generated/uexecutor/v1/types.js.map +0 -1
- package/src/lib/generated/uexecutor/v2/index.js.map +0 -1
- package/src/lib/generated/uexecutor/v2/query.js.map +0 -1
- package/src/lib/generated/uexecutor/v2/types.js.map +0 -1
- package/src/lib/generated/v1/tx.js.map +0 -1
- package/src/lib/index.js.map +0 -1
- package/src/lib/internal/buffer.js.map +0 -1
- package/src/lib/internal/global-buffer-polyfill.js.map +0 -1
- package/src/lib/orchestrator/cea-utils.js.map +0 -1
- package/src/lib/orchestrator/orchestrator.js.map +0 -1
- package/src/lib/orchestrator/orchestrator.types.js.map +0 -1
- package/src/lib/orchestrator/payload-builders.js.map +0 -1
- package/src/lib/orchestrator/route-detector.js.map +0 -1
- package/src/lib/price-fetch/price-fetch.js.map +0 -1
- package/src/lib/progress-hook/progress-hook.js.map +0 -1
- package/src/lib/progress-hook/progress-hook.types.js.map +0 -1
- package/src/lib/push-chain/helpers/abis.js.map +0 -1
- package/src/lib/push-chain/helpers/addresses.js.map +0 -1
- package/src/lib/push-chain/push-chain.js.map +0 -1
- package/src/lib/push-client/push-client.js.map +0 -1
- package/src/lib/push-client/push-client.types.js.map +0 -1
- package/src/lib/universal/account/account.js.map +0 -1
- package/src/lib/universal/account/index.js.map +0 -1
- package/src/lib/universal/signer/index.js.map +0 -1
- package/src/lib/universal/signer/signer.js.map +0 -1
- package/src/lib/universal/signer/signer.types.js.map +0 -1
- package/src/lib/universal/universal.types.js.map +0 -1
- package/src/lib/utils.js.map +0 -1
- package/src/lib/vm-client/evm-client.js.map +0 -1
- package/src/lib/vm-client/svm-client.js.map +0 -1
- package/src/lib/vm-client/vm-client.types.js.map +0 -1
|
@@ -0,0 +1,1192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Route handler functions extracted from Orchestrator.
|
|
4
|
+
*
|
|
5
|
+
* Each function corresponds to one of the multichain execution routes:
|
|
6
|
+
* - executeMultiChain (dispatch)
|
|
7
|
+
* - executeUoaToCea (Route 2 EVM)
|
|
8
|
+
* - executeUoaToCeaSvm (Route 2 SVM)
|
|
9
|
+
* - executeCeaToPush (Route 3 EVM)
|
|
10
|
+
* - executeCeaToPushSvm(Route 3 SVM)
|
|
11
|
+
* - executeCeaToCea (Route 4 stub)
|
|
12
|
+
* - buildPayloadForRoute
|
|
13
|
+
*
|
|
14
|
+
* Every method receives an `OrchestratorContext` as first parameter and an
|
|
15
|
+
* `executeFn` callback that replaces the former `this.execute()` call.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.executeMultiChain = executeMultiChain;
|
|
19
|
+
exports.executeUoaToCea = executeUoaToCea;
|
|
20
|
+
exports.executeUoaToCeaSvm = executeUoaToCeaSvm;
|
|
21
|
+
exports.executeCeaToPush = executeCeaToPush;
|
|
22
|
+
exports.executeCeaToPushSvm = executeCeaToPushSvm;
|
|
23
|
+
exports.executeCeaToCea = executeCeaToCea;
|
|
24
|
+
exports.buildPayloadForRoute = buildPayloadForRoute;
|
|
25
|
+
const tslib_1 = require("tslib");
|
|
26
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
27
|
+
const viem_1 = require("viem");
|
|
28
|
+
const context_1 = require("./context");
|
|
29
|
+
const helpers_1 = require("./helpers");
|
|
30
|
+
const payload_builder_1 = require("./payload-builder");
|
|
31
|
+
const uea_manager_1 = require("./uea-manager");
|
|
32
|
+
const gas_calculator_1 = require("./gas-calculator");
|
|
33
|
+
const chain_1 = require("../../constants/chain");
|
|
34
|
+
const route_detector_1 = require("../route-detector");
|
|
35
|
+
const cea_utils_1 = require("../cea-utils");
|
|
36
|
+
const payload_builders_1 = require("../payload-builders");
|
|
37
|
+
const selectors_1 = require("../../constants/selectors");
|
|
38
|
+
const erc20_evm_1 = require("../../constants/abi/erc20.evm");
|
|
39
|
+
const push_chain_1 = require("../../push-chain/push-chain");
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// executeMultiChain
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
/**
|
|
44
|
+
* Dispatch a universal execution to the correct route handler.
|
|
45
|
+
*/
|
|
46
|
+
function executeMultiChain(ctx, params, executeFn) {
|
|
47
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
48
|
+
// Validate route parameters
|
|
49
|
+
(0, route_detector_1.validateRouteParams)(params, {
|
|
50
|
+
clientChain: ctx.universalSigner.account.chain,
|
|
51
|
+
});
|
|
52
|
+
// Detect the transaction route
|
|
53
|
+
const route = (0, route_detector_1.detectRoute)(params);
|
|
54
|
+
(0, context_1.printLog)(ctx, `executeMultiChain — detected route: ${route}, params: ${JSON.stringify({
|
|
55
|
+
to: typeof params.to === 'string'
|
|
56
|
+
? params.to
|
|
57
|
+
: { address: params.to.address, chain: params.to.chain },
|
|
58
|
+
from: params.from,
|
|
59
|
+
hasValue: params.value !== undefined,
|
|
60
|
+
hasData: params.data !== undefined,
|
|
61
|
+
hasFunds: params.funds !== undefined,
|
|
62
|
+
}, null, 2)}`);
|
|
63
|
+
let response;
|
|
64
|
+
switch (route) {
|
|
65
|
+
case route_detector_1.TransactionRoute.UOA_TO_PUSH:
|
|
66
|
+
response = yield executeFn((0, helpers_1.toExecuteParams)(params));
|
|
67
|
+
break;
|
|
68
|
+
case route_detector_1.TransactionRoute.UOA_TO_CEA:
|
|
69
|
+
response = yield executeUoaToCea(ctx, params, executeFn);
|
|
70
|
+
break;
|
|
71
|
+
case route_detector_1.TransactionRoute.CEA_TO_PUSH:
|
|
72
|
+
response = yield executeCeaToPush(ctx, params, executeFn);
|
|
73
|
+
break;
|
|
74
|
+
case route_detector_1.TransactionRoute.CEA_TO_CEA:
|
|
75
|
+
response = yield executeCeaToCea(ctx, params, executeFn);
|
|
76
|
+
break;
|
|
77
|
+
default:
|
|
78
|
+
throw new Error(`Unknown transaction route: ${route}`);
|
|
79
|
+
}
|
|
80
|
+
// Set the route on the response for .wait() to use
|
|
81
|
+
response.route = route;
|
|
82
|
+
return response;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
// executeUoaToCea (Route 2 — EVM)
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
/**
|
|
89
|
+
* Route 2: Execute outbound transaction from Push Chain to external CEA.
|
|
90
|
+
*
|
|
91
|
+
* Builds a multicall that executes on Push Chain (from UEA context):
|
|
92
|
+
* 1. Approves the gateway to spend PRC-20 tokens (if needed)
|
|
93
|
+
* 2. Calls sendUniversalTxOutbound on UniversalGatewayPC precompile
|
|
94
|
+
*
|
|
95
|
+
* The multicall is executed through the normal execute() flow which handles
|
|
96
|
+
* fee-locking on the origin chain and signature verification.
|
|
97
|
+
*/
|
|
98
|
+
function executeUoaToCea(ctx, params, executeFn) {
|
|
99
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
100
|
+
var _a, _b, _c, _d, _e;
|
|
101
|
+
const target = params.to;
|
|
102
|
+
const targetChain = target.chain;
|
|
103
|
+
const targetAddress = target.address;
|
|
104
|
+
const isSvm = (0, payload_builders_1.isSvmChain)(targetChain);
|
|
105
|
+
// Validate target address based on VM type
|
|
106
|
+
if (isSvm) {
|
|
107
|
+
// SVM: 32-byte hex address
|
|
108
|
+
if (!(0, payload_builders_1.isValidSolanaHexAddress)(targetAddress)) {
|
|
109
|
+
throw new Error(`Invalid Solana address: ${targetAddress}. ` +
|
|
110
|
+
`Expected 0x + 64 hex chars (32 bytes).`);
|
|
111
|
+
}
|
|
112
|
+
const ZERO_32 = ('0x' + '0'.repeat(64));
|
|
113
|
+
if (targetAddress.toLowerCase() === ZERO_32.toLowerCase()) {
|
|
114
|
+
throw new Error(`Cannot send to zero address on Solana. ` +
|
|
115
|
+
`This would result in permanent loss of funds.`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
// EVM: 20-byte hex address
|
|
120
|
+
// Zero address is allowed for multicall (data is array) — the actual targets are in the data entries.
|
|
121
|
+
const isMulticall = Array.isArray(params.data);
|
|
122
|
+
if (targetAddress.toLowerCase() === selectors_1.ZERO_ADDRESS.toLowerCase() && !isMulticall) {
|
|
123
|
+
throw new Error(`Cannot send to zero address (0x0000...0000). ` +
|
|
124
|
+
`This would result in permanent loss of funds.`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Validate chain supports outbound operations
|
|
128
|
+
if (!(0, cea_utils_1.chainSupportsOutbound)(targetChain)) {
|
|
129
|
+
throw new Error(`Chain ${targetChain} does not support outbound operations. ` +
|
|
130
|
+
`Supported chains: BNB_TESTNET, ETHEREUM_SEPOLIA, SOLANA_DEVNET, etc.`);
|
|
131
|
+
}
|
|
132
|
+
// Branch based on VM type
|
|
133
|
+
if (isSvm) {
|
|
134
|
+
return executeUoaToCeaSvm(ctx, params, target, executeFn);
|
|
135
|
+
}
|
|
136
|
+
// ===== EVM path (existing logic) =====
|
|
137
|
+
// Get UEA address
|
|
138
|
+
const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
|
|
139
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — target chain: ${targetChain}, target address: ${targetAddress}, UEA: ${ueaAddress}`);
|
|
140
|
+
// Get CEA address for this UEA on target chain
|
|
141
|
+
const { cea: ceaAddress, isDeployed: ceaDeployed } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, targetChain, (_a = ctx.rpcUrls[targetChain]) === null || _a === void 0 ? void 0 : _a[0]);
|
|
142
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — CEA address: ${ceaAddress}, deployed: ${ceaDeployed}`);
|
|
143
|
+
// Migration path: raw MIGRATION_SELECTOR payload, no multicall wrapping
|
|
144
|
+
let ceaPayload;
|
|
145
|
+
let prc20Token = selectors_1.ZERO_ADDRESS;
|
|
146
|
+
let burnAmount = BigInt(0);
|
|
147
|
+
if (params.migration) {
|
|
148
|
+
ceaPayload = (0, payload_builders_1.buildMigrationPayload)();
|
|
149
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
|
|
150
|
+
burnAmount = BigInt(0); // Migration is logic-only — no funds. CEA rejects msg.value != 0.
|
|
151
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — MIGRATION: using raw MIGRATION_SELECTOR payload (${ceaPayload}), native PRC-20 ${prc20Token}`);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// Build multicall for CEA execution on target chain
|
|
155
|
+
const ceaMulticalls = [];
|
|
156
|
+
// If there's data to execute on target
|
|
157
|
+
if (params.data) {
|
|
158
|
+
if (Array.isArray(params.data)) {
|
|
159
|
+
// User provided explicit multicall array
|
|
160
|
+
ceaMulticalls.push(...params.data);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
// When ERC-20 funds are provided with a single payload, auto-prepend a
|
|
164
|
+
// transfer() call so the tokens minted to the CEA are forwarded to the
|
|
165
|
+
// target address. This mirrors the Route 1 behavior in buildExecuteMulticall.
|
|
166
|
+
if ((_b = params.funds) === null || _b === void 0 ? void 0 : _b.amount) {
|
|
167
|
+
const token = params.funds.token;
|
|
168
|
+
if (token && token.mechanism !== 'native') {
|
|
169
|
+
const erc20Transfer = (0, viem_1.encodeFunctionData)({
|
|
170
|
+
abi: erc20_evm_1.ERC20_EVM,
|
|
171
|
+
functionName: 'transfer',
|
|
172
|
+
args: [targetAddress, params.funds.amount],
|
|
173
|
+
});
|
|
174
|
+
ceaMulticalls.push({
|
|
175
|
+
to: token.address,
|
|
176
|
+
value: BigInt(0),
|
|
177
|
+
data: erc20Transfer,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Single call with data. Forward native value (if any) so the target
|
|
182
|
+
// contract receives it alongside the payload call. The vault deposits
|
|
183
|
+
// native value to the CEA, and the multicall forwards it to the target.
|
|
184
|
+
ceaMulticalls.push({
|
|
185
|
+
to: targetAddress,
|
|
186
|
+
value: (_c = params.value) !== null && _c !== void 0 ? _c : BigInt(0),
|
|
187
|
+
data: params.data,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else if (params.value) {
|
|
192
|
+
// Native value transfer only.
|
|
193
|
+
// If sending to the CEA itself, skip the multicall — the gateway deposits native
|
|
194
|
+
// value directly to CEA. A self-call with value would revert (CEA._handleMulticall
|
|
195
|
+
// rejects value-bearing self-calls).
|
|
196
|
+
if (targetAddress.toLowerCase() !== ceaAddress.toLowerCase()) {
|
|
197
|
+
ceaMulticalls.push({
|
|
198
|
+
to: targetAddress,
|
|
199
|
+
value: params.value,
|
|
200
|
+
data: '0x',
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Build CEA multicall payload (this is what gets executed on the external chain)
|
|
205
|
+
ceaPayload = (0, payload_builders_1.buildCeaMulticallPayload)(ceaMulticalls);
|
|
206
|
+
// Determine token to burn on Push Chain
|
|
207
|
+
// NOTE: Even for PAYLOAD-only (no value), we need a valid PRC-20 token to:
|
|
208
|
+
// 1. Look up the target chain namespace in the gateway
|
|
209
|
+
// 2. Query and pay gas fees for the relay
|
|
210
|
+
if ((_d = params.funds) === null || _d === void 0 ? void 0 : _d.amount) {
|
|
211
|
+
// User explicitly specified funds with token
|
|
212
|
+
const token = params.funds.token;
|
|
213
|
+
if (token) {
|
|
214
|
+
prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
|
|
215
|
+
burnAmount = params.funds.amount;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else if (params.value && params.value > BigInt(0)) {
|
|
219
|
+
// Native value transfer: auto-select the PRC-20 token for target chain
|
|
220
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
|
|
221
|
+
burnAmount = params.value;
|
|
222
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — auto-selected native PRC-20 ${prc20Token} for chain ${targetChain}, amount: ${burnAmount.toString()}`);
|
|
223
|
+
}
|
|
224
|
+
else if (params.data) {
|
|
225
|
+
// PAYLOAD-only (no value transfer): still need native token for chain namespace + gas fees
|
|
226
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
|
|
227
|
+
burnAmount = BigInt(0);
|
|
228
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — PAYLOAD-only: using native PRC-20 ${prc20Token} for chain ${targetChain} with zero burn amount`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
// Build outbound request struct for the gateway
|
|
232
|
+
// NOTE: `target` is a LEGACY/DUMMY parameter for contract compatibility.
|
|
233
|
+
// The deployed UniversalGatewayPC still expects this field, but the relay does NOT use it
|
|
234
|
+
// to determine the actual destination. The relay determines destination from the PRC-20 token's
|
|
235
|
+
// SOURCE_CHAIN_NAMESPACE. We pass the CEA address as a non-zero placeholder.
|
|
236
|
+
// This field will be removed in future contract upgrades.
|
|
237
|
+
const targetBytes = ceaAddress; // Dummy value - any non-zero address works
|
|
238
|
+
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetBytes, prc20Token, burnAmount, (_e = params.gasLimit) !== null && _e !== void 0 ? _e : BigInt(0), ceaPayload, ueaAddress // revert recipient is the UEA
|
|
239
|
+
);
|
|
240
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — outbound request: ${JSON.stringify({
|
|
241
|
+
target: outboundReq.target,
|
|
242
|
+
token: outboundReq.token,
|
|
243
|
+
amount: outboundReq.amount.toString(),
|
|
244
|
+
gasLimit: outboundReq.gasLimit.toString(),
|
|
245
|
+
payloadLength: outboundReq.payload.length,
|
|
246
|
+
revertRecipient: outboundReq.revertRecipient,
|
|
247
|
+
}, null, 2)}`);
|
|
248
|
+
// Get UniversalGatewayPC address
|
|
249
|
+
const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
|
|
250
|
+
// Pre-fetch UEA status early — balance is needed for gas value calculation
|
|
251
|
+
const signerChain = ctx.universalSigner.account.chain;
|
|
252
|
+
const isNativePushEOA = (0, helpers_1.isPushChain)(signerChain);
|
|
253
|
+
const [ueaCode, ueaBalance] = yield Promise.all([
|
|
254
|
+
ctx.pushClient.publicClient.getCode({ address: ueaAddress }),
|
|
255
|
+
ctx.pushClient.getBalance(ueaAddress),
|
|
256
|
+
]);
|
|
257
|
+
const isUEADeployed = isNativePushEOA || ueaCode !== undefined;
|
|
258
|
+
if (!isUEADeployed) {
|
|
259
|
+
throw new Error('UEA is not deployed. Please send an inbound transaction to Push Chain first ' +
|
|
260
|
+
'(e.g. sendTransaction with value) to deploy your Universal Execution Account before using outbound transfers.');
|
|
261
|
+
}
|
|
262
|
+
const ueaNonce = isNativePushEOA ? BigInt(0) : yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress);
|
|
263
|
+
// Build the multicall that will execute ON Push Chain from UEA context
|
|
264
|
+
// This includes: 1) approve PRC-20 (if needed), 2) call sendUniversalTxOutbound
|
|
265
|
+
const pushChainMulticalls = [];
|
|
266
|
+
// Query gas fee from UniversalCore contract (needed for approval amount)
|
|
267
|
+
let gasFee = BigInt(0);
|
|
268
|
+
let nativeValueForGas = BigInt(0);
|
|
269
|
+
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
270
|
+
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
271
|
+
try {
|
|
272
|
+
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, outboundReq.gasLimit);
|
|
273
|
+
gasFee = result.gasFee;
|
|
274
|
+
gasToken = result.gasToken;
|
|
275
|
+
nativeValueForGas = result.nativeValueForGas;
|
|
276
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — queried gas fee: ${gasFee.toString()}, gasToken: ${gasToken}, nativeValueForGas: ${nativeValueForGas.toString()}`);
|
|
277
|
+
}
|
|
278
|
+
catch (err) {
|
|
279
|
+
throw new Error(`Failed to query outbound gas fee: ${err}`);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// Adjust nativeValueForGas: the 1Mx multiplier from queryOutboundGasFee produces
|
|
283
|
+
// a value far too low for the actual WPC/gasToken swap price. Set it to 200 UPC
|
|
284
|
+
// (capped by balance) — enough for the swap, but avoids draining thin pools.
|
|
285
|
+
// The contract's swapAndBurnGas does exactOutputSingle and refunds excess PC.
|
|
286
|
+
const EVM_NATIVE_VALUE_TARGET = BigInt(200e18); // 200 UPC
|
|
287
|
+
const EVM_GAS_RESERVE = BigInt(3e18); // 3 UPC for tx overhead
|
|
288
|
+
// Reuse ueaBalance fetched earlier (line 383) — no tx sent between, balance is stable
|
|
289
|
+
const currentBalance = ueaBalance;
|
|
290
|
+
let adjustedValue;
|
|
291
|
+
if (currentBalance > EVM_NATIVE_VALUE_TARGET + EVM_GAS_RESERVE) {
|
|
292
|
+
// Enough balance: use 200 UPC target
|
|
293
|
+
adjustedValue = EVM_NATIVE_VALUE_TARGET;
|
|
294
|
+
}
|
|
295
|
+
else if (currentBalance > EVM_GAS_RESERVE) {
|
|
296
|
+
// Low balance: use what's available minus reserve
|
|
297
|
+
adjustedValue = currentBalance - EVM_GAS_RESERVE;
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
// Very low balance: use original query value as-is
|
|
301
|
+
adjustedValue = nativeValueForGas;
|
|
302
|
+
}
|
|
303
|
+
if (adjustedValue !== nativeValueForGas) {
|
|
304
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — adjusting nativeValueForGas from ${nativeValueForGas.toString()} to ${adjustedValue.toString()} (UEA balance: ${currentBalance.toString()})`);
|
|
305
|
+
nativeValueForGas = adjustedValue;
|
|
306
|
+
}
|
|
307
|
+
// Build outbound multicalls (approve burn + sendUniversalTxOutbound with native value)
|
|
308
|
+
const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
309
|
+
prc20Token,
|
|
310
|
+
gasToken,
|
|
311
|
+
burnAmount,
|
|
312
|
+
gasFee,
|
|
313
|
+
nativeValueForGas,
|
|
314
|
+
gatewayPcAddress,
|
|
315
|
+
outboundRequest: outboundReq,
|
|
316
|
+
});
|
|
317
|
+
pushChainMulticalls.push(...outboundMulticalls);
|
|
318
|
+
(0, context_1.printLog)(ctx, `executeUoaToCea — Push Chain multicall has ${pushChainMulticalls.length} operations`);
|
|
319
|
+
// TODO: Enable pre-flight balance checks once outbound flow is stable
|
|
320
|
+
// if (burnAmount > BigInt(0)) {
|
|
321
|
+
// const prc20Balance = await ctx.pushClient.publicClient.readContract({
|
|
322
|
+
// address: prc20Token,
|
|
323
|
+
// abi: [{ name: 'balanceOf', type: 'function', stateMutability: 'view', inputs: [{ name: 'account', type: 'address' }], outputs: [{ name: '', type: 'uint256' }] }],
|
|
324
|
+
// functionName: 'balanceOf',
|
|
325
|
+
// args: [ueaAddress],
|
|
326
|
+
// }) as bigint;
|
|
327
|
+
// if (prc20Balance < burnAmount) {
|
|
328
|
+
// throw new Error(
|
|
329
|
+
// `Insufficient PRC-20 token balance on UEA. ` +
|
|
330
|
+
// `Required: ${burnAmount.toString()}, Available: ${prc20Balance.toString()}, ` +
|
|
331
|
+
// `Token: ${prc20Token}, UEA: ${ueaAddress}. ` +
|
|
332
|
+
// `Please bridge tokens to Push Chain first.`
|
|
333
|
+
// );
|
|
334
|
+
// }
|
|
335
|
+
// }
|
|
336
|
+
// const currentUeaBalance = await ctx.pushClient.getBalance(ueaAddress);
|
|
337
|
+
// if (currentUeaBalance < nativeValueForGas) {
|
|
338
|
+
// throw new Error(
|
|
339
|
+
// `Insufficient native balance on UEA for outbound gas. ` +
|
|
340
|
+
// `Required: ${nativeValueForGas.toString()} wei, Available: ${currentUeaBalance.toString()} wei, ` +
|
|
341
|
+
// `UEA: ${ueaAddress}. Please send UPC to your UEA first.`
|
|
342
|
+
// );
|
|
343
|
+
// }
|
|
344
|
+
// Execute through the normal execute() flow
|
|
345
|
+
// This handles fee-locking on origin chain and executes the multicall from UEA context
|
|
346
|
+
// Sum native values from multicall entries for proper fee calculation
|
|
347
|
+
const multicallNativeValue = pushChainMulticalls.reduce((sum, mc) => { var _a; return sum + ((_a = mc.value) !== null && _a !== void 0 ? _a : BigInt(0)); }, BigInt(0));
|
|
348
|
+
const executeParams = {
|
|
349
|
+
to: ueaAddress, // multicall executes from UEA
|
|
350
|
+
value: multicallNativeValue, // ensures correct requiredFunds calculation
|
|
351
|
+
data: pushChainMulticalls, // array triggers multicall mode
|
|
352
|
+
_ueaStatus: {
|
|
353
|
+
isDeployed: isUEADeployed,
|
|
354
|
+
nonce: ueaNonce,
|
|
355
|
+
balance: ueaBalance,
|
|
356
|
+
},
|
|
357
|
+
_skipFeeLocking: true, // outbound executes on Push Chain, no external fee locking
|
|
358
|
+
};
|
|
359
|
+
const response = yield executeFn(executeParams);
|
|
360
|
+
// Add chain info to response
|
|
361
|
+
response.chain = targetChain;
|
|
362
|
+
response.chainNamespace = (0, helpers_1.getChainNamespace)(targetChain);
|
|
363
|
+
return response;
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
// ---------------------------------------------------------------------------
|
|
367
|
+
// executeUoaToCeaSvm (Route 2 — SVM)
|
|
368
|
+
// ---------------------------------------------------------------------------
|
|
369
|
+
/**
|
|
370
|
+
* Route 2 for SVM targets: Outbound from Push Chain to Solana.
|
|
371
|
+
*
|
|
372
|
+
* Three cases:
|
|
373
|
+
* 1. Withdraw SOL: Burn pSOL on Push Chain, recipient gets native SOL
|
|
374
|
+
* 2. Withdraw SPL: Burn PRC-20 on Push Chain, recipient gets SPL token
|
|
375
|
+
* 3. Execute (CPI): Burn pSOL + execute CPI on target Solana program
|
|
376
|
+
*/
|
|
377
|
+
function executeUoaToCeaSvm(ctx, params, target, executeFn) {
|
|
378
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
379
|
+
var _a, _b, _c, _d, _e, _f;
|
|
380
|
+
const targetChain = target.chain;
|
|
381
|
+
const targetAddress = target.address; // 0x-prefixed, 32 bytes
|
|
382
|
+
const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
|
|
383
|
+
const hasSvmExecute = !!params.svmExecute;
|
|
384
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — target: ${targetAddress}, chain: ${targetChain}, ` +
|
|
385
|
+
`hasSvmExecute: ${hasSvmExecute}, value: ${(_b = (_a = params.value) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : '0'}`);
|
|
386
|
+
// --- Build SVM payload ---
|
|
387
|
+
let svmPayload = '0x'; // empty for withdraw (SOL/SPL)
|
|
388
|
+
if (hasSvmExecute) {
|
|
389
|
+
// Execute case: encode the CPI payload
|
|
390
|
+
const exec = params.svmExecute;
|
|
391
|
+
svmPayload = (0, payload_builders_1.encodeSvmExecutePayload)({
|
|
392
|
+
targetProgram: exec.targetProgram,
|
|
393
|
+
accounts: exec.accounts,
|
|
394
|
+
ixData: exec.ixData,
|
|
395
|
+
instructionId: 2,
|
|
396
|
+
});
|
|
397
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — encoded execute payload: ${(svmPayload.length - 2) / 2} bytes, ` +
|
|
398
|
+
`${exec.accounts.length} accounts`);
|
|
399
|
+
}
|
|
400
|
+
// --- Determine PRC-20 token and burn amount ---
|
|
401
|
+
let prc20Token = selectors_1.ZERO_ADDRESS;
|
|
402
|
+
let burnAmount = BigInt(0);
|
|
403
|
+
if ((_c = params.funds) === null || _c === void 0 ? void 0 : _c.amount) {
|
|
404
|
+
// User explicitly specified funds with token
|
|
405
|
+
const token = params.funds.token;
|
|
406
|
+
if (token) {
|
|
407
|
+
prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
|
|
408
|
+
burnAmount = params.funds.amount;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
else if (params.value && params.value > BigInt(0)) {
|
|
412
|
+
// Native value transfer: auto-select pSOL for Solana chains
|
|
413
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
|
|
414
|
+
burnAmount = params.value;
|
|
415
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — auto-selected native PRC-20 ${prc20Token} for chain ${targetChain}, amount: ${burnAmount.toString()}`);
|
|
416
|
+
}
|
|
417
|
+
else if (hasSvmExecute) {
|
|
418
|
+
// Execute-only (no value): check if user specified an SPL token context
|
|
419
|
+
const token = params.funds && params.funds.token;
|
|
420
|
+
if (token) {
|
|
421
|
+
prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
|
|
425
|
+
}
|
|
426
|
+
burnAmount = BigInt(0);
|
|
427
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — EXECUTE-only: using PRC-20 ${prc20Token} with zero burn amount`);
|
|
428
|
+
}
|
|
429
|
+
// --- Determine target bytes ---
|
|
430
|
+
// For withdraw: target is the Solana recipient pubkey
|
|
431
|
+
// For execute: target is the target Solana program
|
|
432
|
+
const targetBytes = hasSvmExecute
|
|
433
|
+
? params.svmExecute.targetProgram
|
|
434
|
+
: targetAddress;
|
|
435
|
+
// --- Build outbound request ---
|
|
436
|
+
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetBytes, prc20Token, burnAmount, (_d = params.gasLimit) !== null && _d !== void 0 ? _d : BigInt(0), svmPayload, ueaAddress // revert recipient is the UEA
|
|
437
|
+
);
|
|
438
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — outbound request: ${JSON.stringify({
|
|
439
|
+
target: outboundReq.target,
|
|
440
|
+
token: outboundReq.token,
|
|
441
|
+
amount: outboundReq.amount.toString(),
|
|
442
|
+
gasLimit: outboundReq.gasLimit.toString(),
|
|
443
|
+
payloadLength: (outboundReq.payload.length - 2) / 2,
|
|
444
|
+
revertRecipient: outboundReq.revertRecipient,
|
|
445
|
+
}, null, 2)}`);
|
|
446
|
+
// --- Pre-fetch UEA status early — balance is needed for gas value calculation ---
|
|
447
|
+
const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
|
|
448
|
+
const signerChainSvm = ctx.universalSigner.account.chain;
|
|
449
|
+
const isNativePushEOASvm = (0, helpers_1.isPushChain)(signerChainSvm);
|
|
450
|
+
// Skip getCode if accountStatusCache already confirmed deployment
|
|
451
|
+
const deployedHintSvm = (_f = (_e = ctx.accountStatusCache) === null || _e === void 0 ? void 0 : _e.uea) === null || _f === void 0 ? void 0 : _f.deployed;
|
|
452
|
+
let ueaBalance;
|
|
453
|
+
let isUEADeployed;
|
|
454
|
+
if (deployedHintSvm || isNativePushEOASvm) {
|
|
455
|
+
isUEADeployed = true;
|
|
456
|
+
ueaBalance = yield ctx.pushClient.getBalance(ueaAddress);
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
const [ueaCode, balance] = yield Promise.all([
|
|
460
|
+
ctx.pushClient.publicClient.getCode({ address: ueaAddress }),
|
|
461
|
+
ctx.pushClient.getBalance(ueaAddress),
|
|
462
|
+
]);
|
|
463
|
+
isUEADeployed = ueaCode !== undefined;
|
|
464
|
+
ueaBalance = balance;
|
|
465
|
+
}
|
|
466
|
+
if (!isUEADeployed) {
|
|
467
|
+
throw new Error('UEA is not deployed. Please send an inbound transaction to Push Chain first ' +
|
|
468
|
+
'(e.g. sendTransaction with value) to deploy your Universal Execution Account before using outbound transfers.');
|
|
469
|
+
}
|
|
470
|
+
const ueaNonce = isNativePushEOASvm ? BigInt(0) : yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress);
|
|
471
|
+
// --- Query gas fee (identical to EVM path) ---
|
|
472
|
+
let gasFee = BigInt(0);
|
|
473
|
+
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
474
|
+
let universalCoreAddress;
|
|
475
|
+
let nativeValueForGas = BigInt(0);
|
|
476
|
+
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
477
|
+
const gasLimit = outboundReq.gasLimit;
|
|
478
|
+
try {
|
|
479
|
+
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit);
|
|
480
|
+
gasFee = result.gasFee;
|
|
481
|
+
gasToken = result.gasToken;
|
|
482
|
+
nativeValueForGas = result.nativeValueForGas;
|
|
483
|
+
universalCoreAddress = result.universalCoreAddress;
|
|
484
|
+
// When user omits gasLimit (sent as 0), the contract computes fees using its internal
|
|
485
|
+
// baseGasLimitByChainNamespace. But the relay reads the stored gasLimit=0 from the
|
|
486
|
+
// on-chain outbound record and uses it as the Solana compute budget — 0 CU means the
|
|
487
|
+
// relay cannot execute the tx. Derive the effective limit from gasFee/gasPrice so
|
|
488
|
+
// the stored record has a non-zero compute budget the relay can use.
|
|
489
|
+
if (!params.gasLimit && result.gasPrice > BigInt(0)) {
|
|
490
|
+
outboundReq.gasLimit = result.gasFee / result.gasPrice;
|
|
491
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — derived effectiveGasLimit: ${outboundReq.gasLimit} (gasFee=${result.gasFee} / gasPrice=${result.gasPrice})`);
|
|
492
|
+
}
|
|
493
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — queried gas fee: ${gasFee.toString()}, gasToken: ${gasToken}, nativeValueForGas: ${nativeValueForGas.toString()}`);
|
|
494
|
+
}
|
|
495
|
+
catch (err) {
|
|
496
|
+
throw new Error(`Failed to query outbound gas fee: ${err}`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
// Estimate the actual WPC needed for the swap using on-chain pool price.
|
|
500
|
+
// The static 1Mx multiplier from queryOutboundGasFee doesn't reflect the real
|
|
501
|
+
// WPC/gasToken exchange rate. estimateNativeValueForSwap reads the Uniswap V3
|
|
502
|
+
// pool's slot0, computes the true cost, and adds a 2x buffer.
|
|
503
|
+
// Excess msg.value is refunded by the contract's swapAndBurnGas.
|
|
504
|
+
if (universalCoreAddress && gasFee > BigInt(0)) {
|
|
505
|
+
const estimated = yield (0, gas_calculator_1.estimateNativeValueForSwap)(ctx, universalCoreAddress, gasToken, gasFee, ueaBalance);
|
|
506
|
+
if (estimated > nativeValueForGas) {
|
|
507
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — adjusting nativeValueForGas from ${nativeValueForGas.toString()} to ${estimated.toString()} (pool-price estimate, UEA balance: ${ueaBalance.toString()})`);
|
|
508
|
+
nativeValueForGas = estimated;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
// --- Build Push Chain multicalls (approve + sendUniversalTxOutbound) ---
|
|
512
|
+
// Reuse the same builder as EVM — this part is identical
|
|
513
|
+
const pushChainMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
514
|
+
prc20Token,
|
|
515
|
+
gasToken,
|
|
516
|
+
burnAmount,
|
|
517
|
+
gasFee,
|
|
518
|
+
nativeValueForGas,
|
|
519
|
+
gatewayPcAddress,
|
|
520
|
+
outboundRequest: outboundReq,
|
|
521
|
+
});
|
|
522
|
+
(0, context_1.printLog)(ctx, `executeUoaToCeaSvm — Push Chain multicall has ${pushChainMulticalls.length} operations`);
|
|
523
|
+
// Sum native values from multicall entries for proper fee calculation
|
|
524
|
+
const multicallNativeValue = pushChainMulticalls.reduce((sum, mc) => { var _a; return sum + ((_a = mc.value) !== null && _a !== void 0 ? _a : BigInt(0)); }, BigInt(0));
|
|
525
|
+
const executeParams = {
|
|
526
|
+
to: ueaAddress,
|
|
527
|
+
value: multicallNativeValue, // ensures correct requiredFunds calculation
|
|
528
|
+
data: pushChainMulticalls,
|
|
529
|
+
_ueaStatus: {
|
|
530
|
+
isDeployed: isUEADeployed,
|
|
531
|
+
nonce: ueaNonce,
|
|
532
|
+
balance: ueaBalance,
|
|
533
|
+
},
|
|
534
|
+
_skipFeeLocking: true, // outbound executes on Push Chain, no external fee locking
|
|
535
|
+
};
|
|
536
|
+
const response = yield executeFn(executeParams);
|
|
537
|
+
// Add chain info to response
|
|
538
|
+
response.chain = targetChain;
|
|
539
|
+
response.chainNamespace = (0, helpers_1.getChainNamespace)(targetChain);
|
|
540
|
+
return response;
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
// ---------------------------------------------------------------------------
|
|
544
|
+
// executeCeaToPush (Route 3 — EVM)
|
|
545
|
+
// ---------------------------------------------------------------------------
|
|
546
|
+
/**
|
|
547
|
+
* Route 3: Execute inbound transaction from CEA to Push Chain.
|
|
548
|
+
*
|
|
549
|
+
* This route instructs CEA on an external chain to call sendUniversalTxFromCEA,
|
|
550
|
+
* bridging funds/payloads back to Push Chain.
|
|
551
|
+
*
|
|
552
|
+
* Flow:
|
|
553
|
+
* 1. Build multicall for CEA: [approve Gateway (if ERC20), sendUniversalTxFromCEA]
|
|
554
|
+
* 2. Execute via Route 2 (UOA -> CEA) with PAYLOAD-only (CEA uses its own funds)
|
|
555
|
+
* 3. CEA executes multicall, Gateway locks funds, relayer mints PRC-20 on Push Chain
|
|
556
|
+
*/
|
|
557
|
+
function executeCeaToPush(ctx, params, executeFn) {
|
|
558
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
559
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
560
|
+
// 1. Validate and extract source chain
|
|
561
|
+
if (!((_a = params.from) === null || _a === void 0 ? void 0 : _a.chain)) {
|
|
562
|
+
throw new Error('Route 3 (CEA -> Push) requires from.chain to specify the source CEA chain');
|
|
563
|
+
}
|
|
564
|
+
const sourceChain = params.from.chain;
|
|
565
|
+
// SVM chains use a fundamentally different flow (gateway self-call, not CEA multicall)
|
|
566
|
+
if ((0, payload_builders_1.isSvmChain)(sourceChain)) {
|
|
567
|
+
return executeCeaToPushSvm(ctx, params, sourceChain, executeFn);
|
|
568
|
+
}
|
|
569
|
+
// 2. Extract destination on Push Chain
|
|
570
|
+
// For Route 3, 'to' is a Push Chain address (string), not a ChainTarget
|
|
571
|
+
const pushDestination = params.to;
|
|
572
|
+
if (typeof params.to !== 'string') {
|
|
573
|
+
throw new Error('Route 3 (CEA -> Push): to must be a Push Chain address (string), not a ChainTarget');
|
|
574
|
+
}
|
|
575
|
+
// 3. Get UEA address (will be recipient on Push Chain from CEA's perspective)
|
|
576
|
+
const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
|
|
577
|
+
// 4. Get CEA address on source chain
|
|
578
|
+
const { cea: ceaAddress, isDeployed: ceaDeployed } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, (_b = ctx.rpcUrls[sourceChain]) === null || _b === void 0 ? void 0 : _b[0]);
|
|
579
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — sourceChain: ${sourceChain}, CEA: ${ceaAddress}, deployed: ${ceaDeployed}`);
|
|
580
|
+
if (!ceaDeployed) {
|
|
581
|
+
throw new Error(`CEA not deployed on ${sourceChain}. ` +
|
|
582
|
+
`Deploy CEA first using Route 2 (UOA -> CEA) before using Route 3.`);
|
|
583
|
+
}
|
|
584
|
+
// 5. Get UniversalGateway address on source chain
|
|
585
|
+
const gatewayAddress = chain_1.UNIVERSAL_GATEWAY_ADDRESSES[sourceChain];
|
|
586
|
+
if (!gatewayAddress) {
|
|
587
|
+
throw new Error(`No UniversalGateway address configured for chain ${sourceChain}`);
|
|
588
|
+
}
|
|
589
|
+
// 6. Build multicall for CEA to execute on source chain (self-calls via sendUniversalTxToUEA)
|
|
590
|
+
const ceaMulticalls = [];
|
|
591
|
+
// Determine token and amount for sendUniversalTxToUEA
|
|
592
|
+
let tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
593
|
+
let amount = BigInt(0);
|
|
594
|
+
if ((_c = params.funds) === null || _c === void 0 ? void 0 : _c.amount) {
|
|
595
|
+
// ERC20 token transfer from CEA
|
|
596
|
+
const token = params.funds.token;
|
|
597
|
+
if (token) {
|
|
598
|
+
if (token.mechanism === 'native') {
|
|
599
|
+
// Native token (e.g., BNB on BSC)
|
|
600
|
+
tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
601
|
+
amount = params.funds.amount;
|
|
602
|
+
}
|
|
603
|
+
else {
|
|
604
|
+
// ERC20 token - need approval for gateway before sendUniversalTxToUEA
|
|
605
|
+
tokenAddress = token.address;
|
|
606
|
+
amount = params.funds.amount;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
else if (params.value && params.value > BigInt(0)) {
|
|
611
|
+
// Native value transfer (e.g., BNB, ETH)
|
|
612
|
+
tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
613
|
+
amount = params.value;
|
|
614
|
+
}
|
|
615
|
+
// bridgeAmount = only the burn amount (what the Vault will actually deposit to CEA).
|
|
616
|
+
// Previously this included ceaExistingBalance (CEA's pre-existing balance on the
|
|
617
|
+
// external chain), but that approach is racy: the balance can change between the SDK
|
|
618
|
+
// query and relay execution, causing sendUniversalTxToUEA to revert with
|
|
619
|
+
// InsufficientBalance. Pre-existing CEA funds remain parked and can be swept separately.
|
|
620
|
+
let bridgeAmount = amount;
|
|
621
|
+
// Note: CEA contract may reject amount=0 in sendUniversalTxToUEA.
|
|
622
|
+
// Keeping bridgeAmount as-is (0) for payload-only to test precompile behavior.
|
|
623
|
+
// For ERC20 tokens, add approve call for the bridge amount
|
|
624
|
+
// (CEA approves gateway to spend the Vault-deposited amount)
|
|
625
|
+
// Reset to 0 first for USDT-style tokens that revert on non-zero to non-zero approve.
|
|
626
|
+
if (tokenAddress !== selectors_1.ZERO_ADDRESS && bridgeAmount > BigInt(0)) {
|
|
627
|
+
const approveZeroData = (0, viem_1.encodeFunctionData)({
|
|
628
|
+
abi: erc20_evm_1.ERC20_EVM,
|
|
629
|
+
functionName: 'approve',
|
|
630
|
+
args: [gatewayAddress, BigInt(0)],
|
|
631
|
+
});
|
|
632
|
+
ceaMulticalls.push({
|
|
633
|
+
to: tokenAddress,
|
|
634
|
+
value: BigInt(0),
|
|
635
|
+
data: approveZeroData,
|
|
636
|
+
});
|
|
637
|
+
const approveData = (0, viem_1.encodeFunctionData)({
|
|
638
|
+
abi: erc20_evm_1.ERC20_EVM,
|
|
639
|
+
functionName: 'approve',
|
|
640
|
+
args: [gatewayAddress, bridgeAmount],
|
|
641
|
+
});
|
|
642
|
+
ceaMulticalls.push({
|
|
643
|
+
to: tokenAddress,
|
|
644
|
+
value: BigInt(0),
|
|
645
|
+
data: approveData,
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
// Pre-fetch UEA nonce — needed for the inbound UniversalPayload struct
|
|
649
|
+
// Skip getCode if accountStatusCache already confirmed deployment
|
|
650
|
+
let isUEADeployed;
|
|
651
|
+
let ueaNonce;
|
|
652
|
+
const deployedHintR3 = (_e = (_d = ctx.accountStatusCache) === null || _d === void 0 ? void 0 : _d.uea) === null || _e === void 0 ? void 0 : _e.deployed;
|
|
653
|
+
if (deployedHintR3) {
|
|
654
|
+
isUEADeployed = true;
|
|
655
|
+
ueaNonce = yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress);
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
const ueaCode = yield ctx.pushClient.publicClient.getCode({ address: ueaAddress });
|
|
659
|
+
isUEADeployed = ueaCode !== undefined;
|
|
660
|
+
ueaNonce = isUEADeployed ? yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress) : BigInt(0);
|
|
661
|
+
}
|
|
662
|
+
// Build payload for Push Chain execution (if any)
|
|
663
|
+
// This is what happens AFTER funds arrive on Push Chain.
|
|
664
|
+
// The relay expects a full UniversalPayload struct (to, value, data, gasLimit, ...),
|
|
665
|
+
// where `data` contains the multicall payload (with UEA_MULTICALL_SELECTOR prefix).
|
|
666
|
+
let pushPayload = '0x';
|
|
667
|
+
if (params.data) {
|
|
668
|
+
const multicallData = (0, payload_builders_1.buildExecuteMulticall)({
|
|
669
|
+
execute: {
|
|
670
|
+
to: pushDestination,
|
|
671
|
+
value: params.value,
|
|
672
|
+
data: params.data,
|
|
673
|
+
},
|
|
674
|
+
ueaAddress,
|
|
675
|
+
});
|
|
676
|
+
const multicallPayload = (0, payload_builder_1.buildMulticallPayloadData)(ctx, pushDestination, multicallData);
|
|
677
|
+
// Use ueaNonce + 1: the outbound tx itself consumes one nonce via execute(),
|
|
678
|
+
// so the inbound will arrive when the UEA nonce is already incremented.
|
|
679
|
+
pushPayload = (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, { nonce: ueaNonce + BigInt(1) });
|
|
680
|
+
}
|
|
681
|
+
// Build sendUniversalTxToUEA self-call on CEA
|
|
682
|
+
// CEA multicall: to=CEA (self-call), value=0
|
|
683
|
+
// CEA internally calls gateway.sendUniversalTxFromCEA(...)
|
|
684
|
+
// Uses bridgeAmount (= burn amount deposited by Vault)
|
|
685
|
+
const sendUniversalTxCall = (0, payload_builders_1.buildSendUniversalTxToUEA)(ceaAddress, // to: CEA address (self-call)
|
|
686
|
+
tokenAddress, // token: address(0) for native, ERC20 address otherwise
|
|
687
|
+
bridgeAmount, // amount: burn amount only (Vault-deposited)
|
|
688
|
+
pushPayload, // payload: Push Chain execution payload
|
|
689
|
+
ueaAddress // revertRecipient: UEA on Push Chain (receives refund if inbound fails)
|
|
690
|
+
);
|
|
691
|
+
ceaMulticalls.push(sendUniversalTxCall);
|
|
692
|
+
// 7. Encode CEA multicalls into outbound payload
|
|
693
|
+
// CEA will self-execute this multicall (to=CEA, value=0)
|
|
694
|
+
const ceaPayload = (0, payload_builders_1.buildCeaMulticallPayload)(ceaMulticalls);
|
|
695
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — CEA payload (first 100 chars): ${ceaPayload.slice(0, 100)}...`);
|
|
696
|
+
// 8. Determine PRC-20 token for the outbound burn on Push Chain.
|
|
697
|
+
// For ERC20 flows (params.funds with token), use the token's PRC-20 (e.g. pUSDT)
|
|
698
|
+
// so the Vault deposits ERC20 to CEA. For native flows, use the chain's native PRC-20 (e.g. pBNB).
|
|
699
|
+
let prc20Token;
|
|
700
|
+
if (((_f = params.funds) === null || _f === void 0 ? void 0 : _f.amount) && params.funds.token) {
|
|
701
|
+
const token = params.funds.token;
|
|
702
|
+
prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
|
|
703
|
+
}
|
|
704
|
+
else {
|
|
705
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
706
|
+
}
|
|
707
|
+
// burnAmount = PRC20 to burn on Push Chain (NOT the bridge amount).
|
|
708
|
+
// Vault deposits burnAmount to CEA. CEA uses burnAmount + pre-existing balance for the bridge.
|
|
709
|
+
const burnAmount = amount;
|
|
710
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — prc20Token: ${prc20Token}, burnAmount: ${burnAmount.toString()}`);
|
|
711
|
+
// 9. Build outbound request (same structure as Route 2)
|
|
712
|
+
// target = CEA address (for self-execution), value = 0 in payload
|
|
713
|
+
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, // target: CEA address (to=CEA for self-execution)
|
|
714
|
+
prc20Token, // token: native PRC-20 for source chain (for namespace lookup)
|
|
715
|
+
burnAmount, // amount: 1 wei (precompile workaround)
|
|
716
|
+
(_g = params.gasLimit) !== null && _g !== void 0 ? _g : BigInt(0), ceaPayload, // payload: ABI-encoded CEA multicall
|
|
717
|
+
ueaAddress // revertRecipient: UEA
|
|
718
|
+
);
|
|
719
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — outbound request: ${JSON.stringify({
|
|
720
|
+
target: outboundReq.target,
|
|
721
|
+
token: outboundReq.token,
|
|
722
|
+
amount: outboundReq.amount.toString(),
|
|
723
|
+
gasLimit: outboundReq.gasLimit.toString(),
|
|
724
|
+
payloadLength: outboundReq.payload.length,
|
|
725
|
+
revertRecipient: outboundReq.revertRecipient,
|
|
726
|
+
}, null, 2)}`);
|
|
727
|
+
// 10. Fetch UEA balance — needed for gas value calculation
|
|
728
|
+
// (UEA code + nonce already fetched above for the inbound UniversalPayload)
|
|
729
|
+
const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
|
|
730
|
+
const ueaBalance = yield ctx.pushClient.getBalance(ueaAddress);
|
|
731
|
+
// 11. Query gas fees from UniversalCore
|
|
732
|
+
let gasFee = BigInt(0);
|
|
733
|
+
let nativeValueForGas = BigInt(0);
|
|
734
|
+
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
735
|
+
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
736
|
+
const gasLimit = outboundReq.gasLimit;
|
|
737
|
+
try {
|
|
738
|
+
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit);
|
|
739
|
+
gasToken = result.gasToken;
|
|
740
|
+
gasFee = result.gasFee;
|
|
741
|
+
nativeValueForGas = result.nativeValueForGas;
|
|
742
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — queried gas fee: ${gasFee.toString()}, gasToken: ${gasToken}, nativeValueForGas: ${nativeValueForGas.toString()}`);
|
|
743
|
+
}
|
|
744
|
+
catch (err) {
|
|
745
|
+
throw new Error(`Failed to query outbound gas fee for Route 3: ${err}`);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
// Adjust nativeValueForGas using UEA balance (contract refunds excess)
|
|
749
|
+
// Reuse ueaBalance from line 978 — no tx sent between, balance is stable
|
|
750
|
+
const currentBalance = ueaBalance;
|
|
751
|
+
// Cosmos-EVM tx overhead costs ~1 PC per operation; 3 PC covers approve(s) + buffer.
|
|
752
|
+
const OUTBOUND_GAS_RESERVE_R3 = BigInt(3e18);
|
|
753
|
+
if (currentBalance > OUTBOUND_GAS_RESERVE_R3 && currentBalance - OUTBOUND_GAS_RESERVE_R3 > nativeValueForGas) {
|
|
754
|
+
const adjustedValue = currentBalance - OUTBOUND_GAS_RESERVE_R3;
|
|
755
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — adjusting nativeValueForGas from ${nativeValueForGas.toString()} to ${adjustedValue.toString()} (UEA balance: ${currentBalance.toString()})`);
|
|
756
|
+
nativeValueForGas = adjustedValue;
|
|
757
|
+
}
|
|
758
|
+
// 12. Build Push Chain multicalls (approvals + sendUniversalTxOutbound)
|
|
759
|
+
const pushChainMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
760
|
+
prc20Token,
|
|
761
|
+
gasToken,
|
|
762
|
+
burnAmount,
|
|
763
|
+
gasFee,
|
|
764
|
+
nativeValueForGas,
|
|
765
|
+
gatewayPcAddress,
|
|
766
|
+
outboundRequest: outboundReq,
|
|
767
|
+
});
|
|
768
|
+
(0, context_1.printLog)(ctx, `executeCeaToPush — Push Chain multicall has ${pushChainMulticalls.length} operations`);
|
|
769
|
+
// Sum native values from multicall entries for proper fee calculation
|
|
770
|
+
const multicallNativeValue = pushChainMulticalls.reduce((sum, mc) => { var _a; return sum + ((_a = mc.value) !== null && _a !== void 0 ? _a : BigInt(0)); }, BigInt(0));
|
|
771
|
+
// 13. Execute through the normal execute() flow
|
|
772
|
+
const executeParams = {
|
|
773
|
+
to: ueaAddress,
|
|
774
|
+
value: multicallNativeValue, // ensures correct requiredFunds calculation
|
|
775
|
+
data: pushChainMulticalls,
|
|
776
|
+
_ueaStatus: {
|
|
777
|
+
isDeployed: isUEADeployed,
|
|
778
|
+
nonce: ueaNonce,
|
|
779
|
+
balance: ueaBalance,
|
|
780
|
+
},
|
|
781
|
+
_skipFeeLocking: true, // outbound executes on Push Chain, no external fee locking
|
|
782
|
+
};
|
|
783
|
+
const response = yield executeFn(executeParams);
|
|
784
|
+
// Add Route 3 context to response
|
|
785
|
+
response.chain = sourceChain;
|
|
786
|
+
const chainInfo = chain_1.CHAIN_INFO[sourceChain];
|
|
787
|
+
response.chainNamespace = `${chain_1.VM_NAMESPACE[chainInfo.vm]}:${chainInfo.chainId}`;
|
|
788
|
+
return response;
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
// ---------------------------------------------------------------------------
|
|
792
|
+
// executeCeaToPushSvm (Route 3 — SVM)
|
|
793
|
+
// ---------------------------------------------------------------------------
|
|
794
|
+
/**
|
|
795
|
+
* Route 3 SVM: Execute CEA-to-Push for Solana chains.
|
|
796
|
+
*
|
|
797
|
+
* Unlike EVM Route 3 which builds CEA multicalls, SVM Route 3 encodes a
|
|
798
|
+
* `send_universal_tx_to_uea` instruction as an execute payload targeting
|
|
799
|
+
* the SVM gateway program (self-call). The drain amount is embedded in
|
|
800
|
+
* the instruction data, not in the outbound request amount.
|
|
801
|
+
*/
|
|
802
|
+
function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
|
|
803
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
804
|
+
var _a, _b;
|
|
805
|
+
if (typeof params.to !== 'string') {
|
|
806
|
+
throw new Error('Route 3 SVM (CEA -> Push): to must be a Push Chain address (string), not a ChainTarget');
|
|
807
|
+
}
|
|
808
|
+
const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
|
|
809
|
+
// Get gateway program ID from chain config and convert to 0x-hex 32 bytes
|
|
810
|
+
const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
|
|
811
|
+
if (!lockerContract) {
|
|
812
|
+
throw new Error(`No SVM gateway program configured for chain ${sourceChain}`);
|
|
813
|
+
}
|
|
814
|
+
const programPk = new web3_js_1.PublicKey(lockerContract);
|
|
815
|
+
const gatewayProgramHex = ('0x' + Buffer.from(programPk.toBytes()).toString('hex'));
|
|
816
|
+
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — sourceChain: ${sourceChain}, gateway: ${lockerContract}`);
|
|
817
|
+
// Determine drain amount and token
|
|
818
|
+
let drainAmount = BigInt(0);
|
|
819
|
+
let tokenMintHex;
|
|
820
|
+
let prc20Token;
|
|
821
|
+
if (((_a = params.funds) === null || _a === void 0 ? void 0 : _a.amount) && params.funds.amount > BigInt(0)) {
|
|
822
|
+
// SPL token drain
|
|
823
|
+
drainAmount = params.funds.amount;
|
|
824
|
+
const token = params.funds.token;
|
|
825
|
+
if (token && token.address) {
|
|
826
|
+
// Convert SPL mint address to 32-byte hex
|
|
827
|
+
const mintPk = new web3_js_1.PublicKey(token.address);
|
|
828
|
+
tokenMintHex = ('0x' + Buffer.from(mintPk.toBytes()).toString('hex'));
|
|
829
|
+
prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
|
|
830
|
+
}
|
|
831
|
+
else {
|
|
832
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
else if (params.value && params.value > BigInt(0)) {
|
|
836
|
+
// Native SOL drain
|
|
837
|
+
drainAmount = params.value;
|
|
838
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
839
|
+
}
|
|
840
|
+
else {
|
|
841
|
+
// Payload-only Route 3 SVM: no funds to drain, just execute data on Push Chain
|
|
842
|
+
drainAmount = BigInt(0);
|
|
843
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
844
|
+
}
|
|
845
|
+
// Build the SVM CPI payload (send_universal_tx_to_uea wrapped in execute)
|
|
846
|
+
// If params.data is provided, pass it as extraPayload for Push Chain execution
|
|
847
|
+
let extraPayload;
|
|
848
|
+
if (params.data && typeof params.data === 'string') {
|
|
849
|
+
extraPayload = (0, viem_1.hexToBytes)(params.data);
|
|
850
|
+
}
|
|
851
|
+
// Derive CEA PDA as revert recipient: ["push_identity", ueaAddress_20_bytes]
|
|
852
|
+
const ueaBytes = Buffer.from(ueaAddress.slice(2), 'hex'); // 20 bytes
|
|
853
|
+
const [ceaPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('push_identity'), ueaBytes], programPk);
|
|
854
|
+
const ceaPdaHex = ('0x' + Buffer.from(ceaPda.toBytes()).toString('hex'));
|
|
855
|
+
const svmPayload = (0, payload_builders_1.encodeSvmCeaToUeaPayload)({
|
|
856
|
+
gatewayProgramHex,
|
|
857
|
+
drainAmount,
|
|
858
|
+
tokenMintHex,
|
|
859
|
+
extraPayload,
|
|
860
|
+
revertRecipientHex: ceaPdaHex,
|
|
861
|
+
});
|
|
862
|
+
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — drainAmount: ${drainAmount.toString()}, payload length: ${(svmPayload.length - 2) / 2} bytes`);
|
|
863
|
+
// burnAmount = 1 (minimum for precompile; drain amount lives inside the ixData)
|
|
864
|
+
// The precompile rejects amount=0, so we use BigInt(1) as a workaround.
|
|
865
|
+
const burnAmount = BigInt(1);
|
|
866
|
+
// Build outbound request: target = gateway program (self-call)
|
|
867
|
+
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, prc20Token, burnAmount, (_b = params.gasLimit) !== null && _b !== void 0 ? _b : BigInt(0), svmPayload, ueaAddress);
|
|
868
|
+
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — outbound request: target=${outboundReq.target.slice(0, 20)}..., token=${outboundReq.token}`);
|
|
869
|
+
// Pre-fetch UEA status early — balance is needed for gas value calculation
|
|
870
|
+
const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
|
|
871
|
+
const [ueaCode, ueaBalance] = yield Promise.all([
|
|
872
|
+
ctx.pushClient.publicClient.getCode({ address: ueaAddress }),
|
|
873
|
+
ctx.pushClient.getBalance(ueaAddress),
|
|
874
|
+
]);
|
|
875
|
+
const isUEADeployed = ueaCode !== undefined;
|
|
876
|
+
const ueaNonce = isUEADeployed ? yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress) : BigInt(0);
|
|
877
|
+
// Query gas fees
|
|
878
|
+
let gasFee = BigInt(0);
|
|
879
|
+
let nativeValueForGas = BigInt(0);
|
|
880
|
+
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
881
|
+
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
882
|
+
try {
|
|
883
|
+
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, outboundReq.gasLimit);
|
|
884
|
+
gasToken = result.gasToken;
|
|
885
|
+
gasFee = result.gasFee;
|
|
886
|
+
nativeValueForGas = result.nativeValueForGas;
|
|
887
|
+
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — gasFee: ${gasFee.toString()}, gasToken: ${gasToken}, nativeValueForGas: ${nativeValueForGas.toString()}`);
|
|
888
|
+
}
|
|
889
|
+
catch (err) {
|
|
890
|
+
throw new Error(`Failed to query outbound gas fee for SVM Route 3: ${err}`);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
// Adjust nativeValueForGas using UEA balance (contract refunds excess)
|
|
894
|
+
// Re-fetch balance to minimize staleness from gas fee query RPC roundtrips
|
|
895
|
+
const currentBalance = yield ctx.pushClient.getBalance(ueaAddress);
|
|
896
|
+
// Cosmos-EVM tx overhead costs ~1 PC per operation; 3 PC covers approve(s) + buffer.
|
|
897
|
+
const OUTBOUND_GAS_RESERVE_R3_SVM = BigInt(3e18);
|
|
898
|
+
if (currentBalance > OUTBOUND_GAS_RESERVE_R3_SVM && currentBalance - OUTBOUND_GAS_RESERVE_R3_SVM > nativeValueForGas) {
|
|
899
|
+
const adjustedValue = currentBalance - OUTBOUND_GAS_RESERVE_R3_SVM;
|
|
900
|
+
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — adjusting nativeValueForGas from ${nativeValueForGas.toString()} to ${adjustedValue.toString()} (UEA balance: ${currentBalance.toString()})`);
|
|
901
|
+
nativeValueForGas = adjustedValue;
|
|
902
|
+
}
|
|
903
|
+
// Build Push Chain multicalls (approvals + sendUniversalTxOutbound)
|
|
904
|
+
const pushChainMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
905
|
+
prc20Token,
|
|
906
|
+
gasToken,
|
|
907
|
+
burnAmount,
|
|
908
|
+
gasFee,
|
|
909
|
+
nativeValueForGas,
|
|
910
|
+
gatewayPcAddress,
|
|
911
|
+
outboundRequest: outboundReq,
|
|
912
|
+
});
|
|
913
|
+
// Sum native values from multicall entries for proper fee calculation
|
|
914
|
+
const multicallNativeValue = pushChainMulticalls.reduce((sum, mc) => { var _a; return sum + ((_a = mc.value) !== null && _a !== void 0 ? _a : BigInt(0)); }, BigInt(0));
|
|
915
|
+
// Execute through the normal execute() flow
|
|
916
|
+
const executeParams = {
|
|
917
|
+
to: ueaAddress,
|
|
918
|
+
value: multicallNativeValue, // ensures correct requiredFunds calculation
|
|
919
|
+
data: pushChainMulticalls,
|
|
920
|
+
_ueaStatus: {
|
|
921
|
+
isDeployed: isUEADeployed,
|
|
922
|
+
nonce: ueaNonce,
|
|
923
|
+
balance: ueaBalance,
|
|
924
|
+
},
|
|
925
|
+
_skipFeeLocking: true, // outbound executes on Push Chain, no external fee locking
|
|
926
|
+
};
|
|
927
|
+
const response = yield executeFn(executeParams);
|
|
928
|
+
// Add Route 3 SVM context to response
|
|
929
|
+
response.chain = sourceChain;
|
|
930
|
+
const chainInfo = chain_1.CHAIN_INFO[sourceChain];
|
|
931
|
+
response.chainNamespace = `${chain_1.VM_NAMESPACE[chainInfo.vm]}:${chainInfo.chainId}`;
|
|
932
|
+
return response;
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
// ---------------------------------------------------------------------------
|
|
936
|
+
// executeCeaToCea (Route 4 — stub)
|
|
937
|
+
// ---------------------------------------------------------------------------
|
|
938
|
+
/**
|
|
939
|
+
* Route 4: Execute CEA to CEA transaction via Push Chain.
|
|
940
|
+
*/
|
|
941
|
+
function executeCeaToCea(ctx, params, executeFn) {
|
|
942
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
943
|
+
// CEA -> CEA requires chaining Route 3 (CEA -> Push) and Route 2 (Push -> CEA)
|
|
944
|
+
// This is a complex flow that requires coordination
|
|
945
|
+
throw new Error('CEA -> CEA transactions are not yet fully implemented. ' +
|
|
946
|
+
'Use prepareTransaction().thenOn() to chain Route 3 -> Route 2 manually.');
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
// ---------------------------------------------------------------------------
|
|
950
|
+
// buildPayloadForRoute
|
|
951
|
+
// ---------------------------------------------------------------------------
|
|
952
|
+
/**
|
|
953
|
+
* Build payload for a specific route.
|
|
954
|
+
*/
|
|
955
|
+
function buildPayloadForRoute(ctx, params, route, nonce) {
|
|
956
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
957
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
958
|
+
const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
|
|
959
|
+
switch (route) {
|
|
960
|
+
case route_detector_1.TransactionRoute.UOA_TO_PUSH: {
|
|
961
|
+
// Build standard Push Chain payload
|
|
962
|
+
const executeParams = (0, helpers_1.toExecuteParams)(params);
|
|
963
|
+
const multicallData = (0, payload_builders_1.buildExecuteMulticall)({
|
|
964
|
+
execute: executeParams,
|
|
965
|
+
ueaAddress,
|
|
966
|
+
});
|
|
967
|
+
const payload = (0, payload_builder_1.buildMulticallPayloadData)(ctx, executeParams.to, multicallData);
|
|
968
|
+
const req = (0, payload_builder_1.buildUniversalTxRequest)(ctx.universalSigner.account.address, {
|
|
969
|
+
recipient: viem_1.zeroAddress,
|
|
970
|
+
token: viem_1.zeroAddress,
|
|
971
|
+
amount: BigInt(0),
|
|
972
|
+
payload,
|
|
973
|
+
});
|
|
974
|
+
return { payload, gatewayRequest: req };
|
|
975
|
+
}
|
|
976
|
+
case route_detector_1.TransactionRoute.UOA_TO_CEA: {
|
|
977
|
+
const target = params.to;
|
|
978
|
+
// Branch: SVM vs EVM
|
|
979
|
+
if ((0, payload_builders_1.isSvmChain)(target.chain)) {
|
|
980
|
+
// SVM: build SVM payload (binary or empty for withdraw)
|
|
981
|
+
let payload = '0x';
|
|
982
|
+
if (params.svmExecute) {
|
|
983
|
+
payload = (0, payload_builders_1.encodeSvmExecutePayload)({
|
|
984
|
+
targetProgram: params.svmExecute.targetProgram,
|
|
985
|
+
accounts: params.svmExecute.accounts,
|
|
986
|
+
ixData: params.svmExecute.ixData,
|
|
987
|
+
instructionId: 2,
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
const targetBytes = (_b = (_a = params.svmExecute) === null || _a === void 0 ? void 0 : _a.targetProgram) !== null && _b !== void 0 ? _b : target.address;
|
|
991
|
+
let prc20Token = selectors_1.ZERO_ADDRESS;
|
|
992
|
+
let burnAmount = BigInt(0);
|
|
993
|
+
if ((_c = params.funds) === null || _c === void 0 ? void 0 : _c.amount) {
|
|
994
|
+
const token = params.funds.token;
|
|
995
|
+
if (token) {
|
|
996
|
+
prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
|
|
997
|
+
burnAmount = params.funds.amount;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
else if (params.value && params.value > BigInt(0)) {
|
|
1001
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(target.chain, ctx.pushNetwork);
|
|
1002
|
+
burnAmount = params.value;
|
|
1003
|
+
}
|
|
1004
|
+
else if (params.svmExecute) {
|
|
1005
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(target.chain, ctx.pushNetwork);
|
|
1006
|
+
burnAmount = BigInt(1);
|
|
1007
|
+
}
|
|
1008
|
+
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetBytes, prc20Token, burnAmount, (_d = params.gasLimit) !== null && _d !== void 0 ? _d : BigInt(0), payload, ueaAddress);
|
|
1009
|
+
return { payload, gatewayRequest: outboundReq };
|
|
1010
|
+
}
|
|
1011
|
+
// EVM path: Resolve CEA address first (needed for self-transfer check)
|
|
1012
|
+
const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, target.chain, (_e = ctx.rpcUrls[target.chain]) === null || _e === void 0 ? void 0 : _e[0]);
|
|
1013
|
+
// Build CEA outbound payload
|
|
1014
|
+
const multicalls = [];
|
|
1015
|
+
if (params.data) {
|
|
1016
|
+
if (Array.isArray(params.data)) {
|
|
1017
|
+
multicalls.push(...params.data);
|
|
1018
|
+
}
|
|
1019
|
+
else {
|
|
1020
|
+
// Single call with data. Native value (if any) is already delivered to
|
|
1021
|
+
// CEA by the Vault via executeUniversalTx{value: amount}(). Attaching
|
|
1022
|
+
// value to the call would revert if the target function is not payable.
|
|
1023
|
+
// To call a payable function with value, use explicit multicalls.
|
|
1024
|
+
multicalls.push({
|
|
1025
|
+
to: target.address,
|
|
1026
|
+
value: BigInt(0),
|
|
1027
|
+
data: params.data,
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
else if (params.value) {
|
|
1032
|
+
// Skip multicall when sending native value to own CEA — gateway deposits directly.
|
|
1033
|
+
// Self-call with value would revert (CEA._handleMulticall rejects it).
|
|
1034
|
+
if (target.address.toLowerCase() !== ceaAddress.toLowerCase()) {
|
|
1035
|
+
multicalls.push({
|
|
1036
|
+
to: target.address,
|
|
1037
|
+
value: params.value,
|
|
1038
|
+
data: '0x',
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
const payload = (0, payload_builders_1.buildCeaMulticallPayload)(multicalls);
|
|
1043
|
+
let prc20Token = selectors_1.ZERO_ADDRESS;
|
|
1044
|
+
let burnAmount = BigInt(0);
|
|
1045
|
+
if ((_f = params.funds) === null || _f === void 0 ? void 0 : _f.amount) {
|
|
1046
|
+
const token = params.funds.token;
|
|
1047
|
+
if (token) {
|
|
1048
|
+
prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
|
|
1049
|
+
burnAmount = params.funds.amount;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
const targetBytes = ceaAddress;
|
|
1053
|
+
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetBytes, prc20Token, burnAmount, (_g = params.gasLimit) !== null && _g !== void 0 ? _g : BigInt(0), payload, ueaAddress);
|
|
1054
|
+
return { payload, gatewayRequest: outboundReq };
|
|
1055
|
+
}
|
|
1056
|
+
case route_detector_1.TransactionRoute.CEA_TO_PUSH: {
|
|
1057
|
+
// Route 3: CEA -> Push Chain
|
|
1058
|
+
// Build CEA multicall (approve + sendUniversalTxFromCEA) and wrap in outbound
|
|
1059
|
+
if (!((_h = params.from) === null || _h === void 0 ? void 0 : _h.chain)) {
|
|
1060
|
+
throw new Error('Route 3 (CEA -> Push) requires from.chain');
|
|
1061
|
+
}
|
|
1062
|
+
const sourceChain = params.from.chain;
|
|
1063
|
+
const pushDestination = params.to;
|
|
1064
|
+
// SVM chains use gateway self-call, not CEA multicall
|
|
1065
|
+
if ((0, payload_builders_1.isSvmChain)(sourceChain)) {
|
|
1066
|
+
const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
|
|
1067
|
+
if (!lockerContract) {
|
|
1068
|
+
throw new Error(`No SVM gateway program configured for chain ${sourceChain}`);
|
|
1069
|
+
}
|
|
1070
|
+
const programPk = new web3_js_1.PublicKey(lockerContract);
|
|
1071
|
+
const gatewayProgramHex = ('0x' + Buffer.from(programPk.toBytes()).toString('hex'));
|
|
1072
|
+
let drainAmount = BigInt(0);
|
|
1073
|
+
let tokenMintHex;
|
|
1074
|
+
let prc20Token;
|
|
1075
|
+
if (((_j = params.funds) === null || _j === void 0 ? void 0 : _j.amount) && params.funds.amount > BigInt(0)) {
|
|
1076
|
+
drainAmount = params.funds.amount;
|
|
1077
|
+
const token = params.funds.token;
|
|
1078
|
+
if (token && token.address) {
|
|
1079
|
+
const mintPk = new web3_js_1.PublicKey(token.address);
|
|
1080
|
+
tokenMintHex = ('0x' + Buffer.from(mintPk.toBytes()).toString('hex'));
|
|
1081
|
+
prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
|
|
1082
|
+
}
|
|
1083
|
+
else {
|
|
1084
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
else if (params.value && params.value > BigInt(0)) {
|
|
1088
|
+
drainAmount = params.value;
|
|
1089
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
1090
|
+
}
|
|
1091
|
+
else {
|
|
1092
|
+
// Payload-only Route 3 SVM: no funds to drain, just execute data on Push Chain
|
|
1093
|
+
drainAmount = BigInt(0);
|
|
1094
|
+
prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
1095
|
+
}
|
|
1096
|
+
// Derive CEA PDA as revert recipient
|
|
1097
|
+
const ueaBytes2 = Buffer.from(ueaAddress.slice(2), 'hex');
|
|
1098
|
+
const [ceaPda2] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('push_identity'), ueaBytes2], programPk);
|
|
1099
|
+
const ceaPdaHex2 = ('0x' + Buffer.from(ceaPda2.toBytes()).toString('hex'));
|
|
1100
|
+
const svmPayload = (0, payload_builders_1.encodeSvmCeaToUeaPayload)({
|
|
1101
|
+
gatewayProgramHex,
|
|
1102
|
+
drainAmount,
|
|
1103
|
+
tokenMintHex,
|
|
1104
|
+
revertRecipientHex: ceaPdaHex2,
|
|
1105
|
+
});
|
|
1106
|
+
// burnAmount = 1 (minimum for precompile; drain amount lives inside the ixData)
|
|
1107
|
+
const burnAmount = BigInt(1);
|
|
1108
|
+
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, prc20Token, burnAmount, (_k = params.gasLimit) !== null && _k !== void 0 ? _k : BigInt(0), svmPayload, ueaAddress);
|
|
1109
|
+
return { payload: svmPayload, gatewayRequest: outboundReq };
|
|
1110
|
+
}
|
|
1111
|
+
const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, (_l = ctx.rpcUrls[sourceChain]) === null || _l === void 0 ? void 0 : _l[0]);
|
|
1112
|
+
const gatewayAddr = chain_1.UNIVERSAL_GATEWAY_ADDRESSES[sourceChain];
|
|
1113
|
+
if (!gatewayAddr) {
|
|
1114
|
+
throw new Error(`No UniversalGateway address configured for chain ${sourceChain}`);
|
|
1115
|
+
}
|
|
1116
|
+
// Build CEA multicalls (self-calls via sendUniversalTxToUEA)
|
|
1117
|
+
const ceaMulticalls = [];
|
|
1118
|
+
let tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
1119
|
+
let amount = BigInt(0);
|
|
1120
|
+
if ((_m = params.funds) === null || _m === void 0 ? void 0 : _m.amount) {
|
|
1121
|
+
const token = params.funds.token;
|
|
1122
|
+
if (token) {
|
|
1123
|
+
if (token.mechanism === 'native') {
|
|
1124
|
+
tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
1125
|
+
amount = params.funds.amount;
|
|
1126
|
+
}
|
|
1127
|
+
else {
|
|
1128
|
+
tokenAddress = token.address;
|
|
1129
|
+
amount = params.funds.amount;
|
|
1130
|
+
// Reset allowance to 0 first for USDT-style tokens, then approve
|
|
1131
|
+
const approveZeroData = (0, viem_1.encodeFunctionData)({
|
|
1132
|
+
abi: erc20_evm_1.ERC20_EVM,
|
|
1133
|
+
functionName: 'approve',
|
|
1134
|
+
args: [gatewayAddr, BigInt(0)],
|
|
1135
|
+
});
|
|
1136
|
+
ceaMulticalls.push({
|
|
1137
|
+
to: tokenAddress,
|
|
1138
|
+
value: BigInt(0),
|
|
1139
|
+
data: approveZeroData,
|
|
1140
|
+
});
|
|
1141
|
+
const approveData = (0, viem_1.encodeFunctionData)({
|
|
1142
|
+
abi: erc20_evm_1.ERC20_EVM,
|
|
1143
|
+
functionName: 'approve',
|
|
1144
|
+
args: [gatewayAddr, amount],
|
|
1145
|
+
});
|
|
1146
|
+
ceaMulticalls.push({
|
|
1147
|
+
to: tokenAddress,
|
|
1148
|
+
value: BigInt(0),
|
|
1149
|
+
data: approveData,
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
else if (params.value && params.value > BigInt(0)) {
|
|
1155
|
+
tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
1156
|
+
amount = params.value;
|
|
1157
|
+
}
|
|
1158
|
+
// Fetch UEA nonce for inbound UniversalPayload
|
|
1159
|
+
const ueaCodeHop = yield ctx.pushClient.publicClient.getCode({ address: ueaAddress });
|
|
1160
|
+
const ueaNonceHop = ueaCodeHop !== undefined ? yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress) : BigInt(0);
|
|
1161
|
+
// Build Push Chain payload (what executes after inbound arrives)
|
|
1162
|
+
// Wrap in UniversalPayload struct for the relay.
|
|
1163
|
+
let pushPayload = '0x';
|
|
1164
|
+
if (params.data) {
|
|
1165
|
+
const multicallData = (0, payload_builders_1.buildExecuteMulticall)({
|
|
1166
|
+
execute: {
|
|
1167
|
+
to: pushDestination,
|
|
1168
|
+
value: params.value,
|
|
1169
|
+
data: params.data,
|
|
1170
|
+
},
|
|
1171
|
+
ueaAddress,
|
|
1172
|
+
});
|
|
1173
|
+
const multicallPayload = (0, payload_builder_1.buildMulticallPayloadData)(ctx, pushDestination, multicallData);
|
|
1174
|
+
pushPayload = (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, { nonce: ueaNonceHop + BigInt(1) });
|
|
1175
|
+
}
|
|
1176
|
+
// Build sendUniversalTxToUEA self-call on CEA (to=CEA, value=0)
|
|
1177
|
+
const sendCall = (0, payload_builders_1.buildSendUniversalTxToUEA)(ceaAddress, tokenAddress, amount, pushPayload, ceaAddress);
|
|
1178
|
+
ceaMulticalls.push(sendCall);
|
|
1179
|
+
const ceaPayload = (0, payload_builders_1.buildCeaMulticallPayload)(ceaMulticalls);
|
|
1180
|
+
const prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
1181
|
+
// burnAmount = actual amount needed by CEA. Vault deposits this as msg.value.
|
|
1182
|
+
// Fallback to BigInt(1) for payload-only outbound (precompile rejects amount=0).
|
|
1183
|
+
const burnAmount = amount > BigInt(0) ? amount : BigInt(1);
|
|
1184
|
+
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, prc20Token, burnAmount, (_o = params.gasLimit) !== null && _o !== void 0 ? _o : BigInt(0), ceaPayload, ueaAddress);
|
|
1185
|
+
return { payload: ceaPayload, gatewayRequest: outboundReq };
|
|
1186
|
+
}
|
|
1187
|
+
default:
|
|
1188
|
+
throw new Error(`Cannot build payload for route: ${route}`);
|
|
1189
|
+
}
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
//# sourceMappingURL=route-handlers.js.map
|