@pushchain/core 6.0.9 → 6.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/lib/orchestrator/internals/cascade.js +31 -18
- package/src/lib/orchestrator/internals/inbound-tracker.js +26 -3
- package/src/lib/orchestrator/internals/push-chain-tx.js +38 -6
- package/src/lib/orchestrator/internals/route-handlers.d.ts +2 -2
- package/src/lib/orchestrator/internals/route-handlers.js +12 -17
- package/src/lib/orchestrator/internals/tx-transformer.js +3 -2
- package/src/lib/orchestrator/payload-builders.d.ts +13 -1
- package/src/lib/orchestrator/payload-builders.js +26 -1
- package/src/lib/universal-tx-detector/child-inbounds.d.ts +3 -0
- package/src/lib/universal-tx-detector/child-inbounds.js +18 -3
- package/src/lib/universal-tx-detector/detector-svm.js +10 -16
package/package.json
CHANGED
|
@@ -444,8 +444,8 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
444
444
|
effectiveGasLimit = result.gasLimitUsed;
|
|
445
445
|
}
|
|
446
446
|
return Object.assign(Object.assign({}, baseDescriptor), { sourceChain, ceaAddress: ceaPdaHex, isSvmTarget: true, prc20Token,
|
|
447
|
-
// Route 3
|
|
448
|
-
//
|
|
447
|
+
// Route 3 drains pre-existing funds from the external CEA. The
|
|
448
|
+
// Push-side outbound is a payload relay, so no PRC-20 is burned.
|
|
449
449
|
burnAmount: BigInt(0), gasToken,
|
|
450
450
|
gasFee,
|
|
451
451
|
gasPrice, gasLimit: effectiveGasLimit, sizing });
|
|
@@ -925,6 +925,21 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
|
|
|
925
925
|
}
|
|
926
926
|
for (let segIdxLoop = segments.length - 1; segIdxLoop >= 0; segIdxLoop--) {
|
|
927
927
|
const segment = segments[segIdxLoop];
|
|
928
|
+
const gasPriceForOutboundRequest = (chainForLog) => {
|
|
929
|
+
var _a;
|
|
930
|
+
const quotedGasPrice = (_a = segment.gasPrice) !== null && _a !== void 0 ? _a : BigInt(0);
|
|
931
|
+
// If this outbound is being accumulated into an earlier Route 3
|
|
932
|
+
// inbound payload, it may execute minutes after the quote. Keep
|
|
933
|
+
// immediate outbounds pinned to the quote, but let delayed nested
|
|
934
|
+
// outbounds resolve the live base gas price at execution time.
|
|
935
|
+
const executesAfterInbound = segments
|
|
936
|
+
.slice(0, segIdxLoop)
|
|
937
|
+
.some((s) => s.type === 'INBOUND_FROM_CEA');
|
|
938
|
+
if (executesAfterInbound && quotedGasPrice > BigInt(0)) {
|
|
939
|
+
(0, context_1.printLog)(ctx, `composeCascade — delayed outbound to ${chainForLog !== null && chainForLog !== void 0 ? chainForLog : 'unknown chain'}: using gasPrice=0 instead of quoted ${quotedGasPrice} so UGPC resolves the live base price at execution time`);
|
|
940
|
+
}
|
|
941
|
+
return executesAfterInbound ? BigInt(0) : quotedGasPrice;
|
|
942
|
+
};
|
|
928
943
|
switch (segment.type) {
|
|
929
944
|
case 'PUSH_EXECUTION': {
|
|
930
945
|
// Prepend Push Chain multicalls to accumulated
|
|
@@ -966,12 +981,10 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
|
|
|
966
981
|
: (firstHop === null || firstHop === void 0 ? void 0 : firstHop.ceaAddress) || ueaAddress;
|
|
967
982
|
(0, payload_builders_1.assertCeaFundsParkingInvariant)(targetForOutbound, outboundPayload);
|
|
968
983
|
}
|
|
969
|
-
// Per-segment PRC-20 burn-balance pre-check
|
|
970
|
-
//
|
|
971
|
-
//
|
|
972
|
-
//
|
|
973
|
-
// gateway's transferFrom happens BEFORE the gas swap, so user must
|
|
974
|
-
// pre-hold the burn amount.
|
|
984
|
+
// Per-segment PRC-20 burn-balance pre-check. Fires whenever
|
|
985
|
+
// burnAmount > 0 even when burn token equals gas token — the gateway's
|
|
986
|
+
// transferFrom happens BEFORE the gas swap, so user must pre-hold the
|
|
987
|
+
// burn amount.
|
|
975
988
|
const segBurnAmount = segment.totalBurnAmount || BigInt(0);
|
|
976
989
|
const segBurnToken = segment.prc20Token;
|
|
977
990
|
if (segBurnAmount > BigInt(0) && segBurnToken && !isMigration) {
|
|
@@ -1028,7 +1041,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
|
|
|
1028
1041
|
}
|
|
1029
1042
|
}
|
|
1030
1043
|
// Build outbound request
|
|
1031
|
-
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetForOutbound, segment.prc20Token || selectors_1.ZERO_ADDRESS, segment.totalBurnAmount || BigInt(0), (_e = segment.gasLimit) !== null && _e !== void 0 ? _e : BigInt(0), outboundPayload, ueaAddress, (_f = segment.maxPCForGas) !== null && _f !== void 0 ? _f : BigInt(0));
|
|
1044
|
+
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetForOutbound, segment.prc20Token || selectors_1.ZERO_ADDRESS, segment.totalBurnAmount || BigInt(0), (_e = segment.gasLimit) !== null && _e !== void 0 ? _e : BigInt(0), outboundPayload, ueaAddress, (_f = segment.maxPCForGas) !== null && _f !== void 0 ? _f : BigInt(0), gasPriceForOutboundRequest(firstHop === null || firstHop === void 0 ? void 0 : firstHop.targetChain));
|
|
1032
1045
|
// Build approval + outbound multicalls
|
|
1033
1046
|
const segGasFee = segment.gasFee || BigInt(0);
|
|
1034
1047
|
const svmOverride = svmNativeValueBySegment.get(segment);
|
|
@@ -1107,18 +1120,18 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
|
|
|
1107
1120
|
];
|
|
1108
1121
|
}
|
|
1109
1122
|
// Primary hop carries the sourceChain / CEA address (shared across merged hops).
|
|
1110
|
-
const
|
|
1123
|
+
const hop = segment.hops[0];
|
|
1124
|
+
const sourceChain = hop.sourceChain;
|
|
1125
|
+
const ceaAddress = hop.ceaAddress || ueaAddress;
|
|
1111
1126
|
let intermediatePayload = '0x';
|
|
1112
1127
|
if (accumulatedPushMulticalls.length > 0) {
|
|
1113
1128
|
const multicallPayload = (0, payload_builder_1.buildMulticallPayloadData)(ctx, ueaAddress, accumulatedPushMulticalls);
|
|
1114
1129
|
// +1: the outbound tx consumes one nonce via execute()
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1130
|
+
const nonce = (ueaNonce !== null && ueaNonce !== void 0 ? ueaNonce : BigInt(0)) + BigInt(1);
|
|
1131
|
+
intermediatePayload = (0, payload_builders_1.isSvmChain)(sourceChain)
|
|
1132
|
+
? (0, payload_builders_1.buildInboundUniversalPayloadSvm)(multicallPayload, { nonce })
|
|
1133
|
+
: (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, { nonce });
|
|
1118
1134
|
}
|
|
1119
|
-
const hop = segment.hops[0];
|
|
1120
|
-
const sourceChain = hop.sourceChain;
|
|
1121
|
-
const ceaAddress = hop.ceaAddress || ueaAddress;
|
|
1122
1135
|
// SVM chains: build SVM CPI payload instead of EVM CEA multicall
|
|
1123
1136
|
if ((0, payload_builders_1.isSvmChain)(sourceChain)) {
|
|
1124
1137
|
const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
|
|
@@ -1160,7 +1173,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
|
|
|
1160
1173
|
});
|
|
1161
1174
|
const svmBurnAmount = segment.totalBurnAmount || BigInt(0);
|
|
1162
1175
|
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, segment.prc20Token ||
|
|
1163
|
-
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), svmBurnAmount, (_l = segment.gasLimit) !== null && _l !== void 0 ? _l : BigInt(0), svmPayload, ueaAddress, (_m = segment.maxPCForGas) !== null && _m !== void 0 ? _m : BigInt(0));
|
|
1176
|
+
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), svmBurnAmount, (_l = segment.gasLimit) !== null && _l !== void 0 ? _l : BigInt(0), svmPayload, ueaAddress, (_m = segment.maxPCForGas) !== null && _m !== void 0 ? _m : BigInt(0), gasPriceForOutboundRequest(sourceChain));
|
|
1164
1177
|
const inboundGasFee = segment.gasFee || BigInt(0);
|
|
1165
1178
|
let svmInboundFallback;
|
|
1166
1179
|
if (effectiveUeaBalance > BigInt(0)) {
|
|
@@ -1247,7 +1260,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce,
|
|
|
1247
1260
|
? segment.gasLimit
|
|
1248
1261
|
: selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT;
|
|
1249
1262
|
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, segment.prc20Token ||
|
|
1250
|
-
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), segment.totalBurnAmount || BigInt(0), effectiveGasLimit, ceaPayload, ueaAddress, (_q = segment.maxPCForGas) !== null && _q !== void 0 ? _q : BigInt(0));
|
|
1263
|
+
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), segment.totalBurnAmount || BigInt(0), effectiveGasLimit, ceaPayload, ueaAddress, (_q = segment.maxPCForGas) !== null && _q !== void 0 ? _q : BigInt(0), gasPriceForOutboundRequest(sourceChain));
|
|
1251
1264
|
const inboundGasFee = segment.gasFee || BigInt(0);
|
|
1252
1265
|
let evmInboundFallback;
|
|
1253
1266
|
if (effectiveUeaBalance > BigInt(0)) {
|
|
@@ -112,7 +112,7 @@ function findChildUtxIdFromExternalTx(ctx, externalTxHash, sourceChain) {
|
|
|
112
112
|
*/
|
|
113
113
|
function waitForInboundPushTx(ctx_1, outboundExternalTxHash_1, sourceChain_1) {
|
|
114
114
|
return tslib_1.__awaiter(this, arguments, void 0, function* (ctx, outboundExternalTxHash, sourceChain, opts = {}) {
|
|
115
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k
|
|
115
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
116
116
|
const initialWaitMs = (_a = opts.initialWaitMs) !== null && _a !== void 0 ? _a : 30000;
|
|
117
117
|
const pollingIntervalMs = (_b = opts.pollingIntervalMs) !== null && _b !== void 0 ? _b : 5000;
|
|
118
118
|
const timeout = (_c = opts.timeout) !== null && _c !== void 0 ? _c : 300000;
|
|
@@ -183,6 +183,10 @@ function waitForInboundPushTx(ctx_1, outboundExternalTxHash_1, sourceChain_1) {
|
|
|
183
183
|
pushTxHash = utx.pcTx[0].txHash;
|
|
184
184
|
}
|
|
185
185
|
(0, context_1.printLog)(ctx, `[waitForInboundPushTx] Poll #${pollCount} utx-status | status: ${status} | pcTx[0].txHash: '${pushTxHash !== null && pushTxHash !== void 0 ? pushTxHash : ''}' | pcTx len: ${(_j = (_h = utx === null || utx === void 0 ? void 0 : utx.pcTx) === null || _h === void 0 ? void 0 : _h.length) !== null && _j !== void 0 ? _j : 0}`);
|
|
186
|
+
const failedPcTx = (_k = utx === null || utx === void 0 ? void 0 : utx.pcTx) === null || _k === void 0 ? void 0 : _k.find((pcTx) => {
|
|
187
|
+
const pcTxStatus = String(pcTx.status || '').toUpperCase();
|
|
188
|
+
return pcTxStatus === 'FAILED' || Boolean(pcTx.errorMsg);
|
|
189
|
+
});
|
|
186
190
|
if (TERMINAL_SUCCESS_STATUSES.has(status)) {
|
|
187
191
|
// Wait one more poll cycle if pcTx hasn't populated yet — the
|
|
188
192
|
// indexer occasionally flips status before the pcTx hash is queryable.
|
|
@@ -206,8 +210,8 @@ function waitForInboundPushTx(ctx_1, outboundExternalTxHash_1, sourceChain_1) {
|
|
|
206
210
|
}
|
|
207
211
|
}
|
|
208
212
|
else if (TERMINAL_FAILURE_STATUSES.has(status)) {
|
|
209
|
-
const errMsg = (
|
|
210
|
-
? (0, formatters_1.normalizePcInsufficientFundsError)(
|
|
213
|
+
const errMsg = (failedPcTx === null || failedPcTx === void 0 ? void 0 : failedPcTx.errorMsg)
|
|
214
|
+
? (0, formatters_1.normalizePcInsufficientFundsError)(failedPcTx.errorMsg)
|
|
211
215
|
: `inbound terminated with status ${status}`;
|
|
212
216
|
progressHook === null || progressHook === void 0 ? void 0 : progressHook({
|
|
213
217
|
status: 'failed',
|
|
@@ -224,6 +228,25 @@ function waitForInboundPushTx(ctx_1, outboundExternalTxHash_1, sourceChain_1) {
|
|
|
224
228
|
errorMessage: errMsg,
|
|
225
229
|
};
|
|
226
230
|
}
|
|
231
|
+
else if (failedPcTx) {
|
|
232
|
+
const errMsg = failedPcTx.errorMsg
|
|
233
|
+
? (0, formatters_1.normalizePcInsufficientFundsError)(failedPcTx.errorMsg)
|
|
234
|
+
: `inbound Push tx failed while universalStatus=${status}`;
|
|
235
|
+
progressHook === null || progressHook === void 0 ? void 0 : progressHook({
|
|
236
|
+
status: 'failed',
|
|
237
|
+
elapsedMs: elapsed(),
|
|
238
|
+
childUtxId,
|
|
239
|
+
pushTxHash,
|
|
240
|
+
});
|
|
241
|
+
return {
|
|
242
|
+
childUtxId,
|
|
243
|
+
pushTxHash: pushTxHash !== null && pushTxHash !== void 0 ? pushTxHash : '',
|
|
244
|
+
sourceChain,
|
|
245
|
+
outboundExternalTxHash,
|
|
246
|
+
status: 'failed',
|
|
247
|
+
errorMessage: errMsg,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
227
250
|
else if (pushTxHash) {
|
|
228
251
|
// Tiebreaker: cosmos has the Push Chain tx hash populated but the
|
|
229
252
|
// indexer hasn't transitioned universalStatus to a terminal yet.
|
|
@@ -21,6 +21,40 @@ const error_decoder_1 = require("./error-decoder");
|
|
|
21
21
|
const errors_1 = require("./errors");
|
|
22
22
|
Object.defineProperty(exports, "PushChainExecutionError", { enumerable: true, get: function () { return errors_1.PushChainExecutionError; } });
|
|
23
23
|
const formatters_1 = require("../../formatters");
|
|
24
|
+
const PUSH_TX_INDEX_TIMEOUT_MS = 30000;
|
|
25
|
+
const PUSH_TX_INDEX_POLL_MS = 1000;
|
|
26
|
+
const TX_NOT_INDEXED_RE = /not found|TransactionNotFound|TransactionReceiptNotFound|could not be found|No transaction found/i;
|
|
27
|
+
function getIndexedPushTransaction(ctx, txHash, label) {
|
|
28
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
29
|
+
const startedAt = Date.now();
|
|
30
|
+
let receiptWaited = false;
|
|
31
|
+
let retryLogged = false;
|
|
32
|
+
// eslint-disable-next-line no-constant-condition
|
|
33
|
+
while (true) {
|
|
34
|
+
try {
|
|
35
|
+
if (!receiptWaited) {
|
|
36
|
+
yield ctx.pushClient.publicClient.waitForTransactionReceipt({
|
|
37
|
+
hash: txHash,
|
|
38
|
+
});
|
|
39
|
+
receiptWaited = true;
|
|
40
|
+
}
|
|
41
|
+
return yield ctx.pushClient.getTransaction(txHash);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
const msg = (err === null || err === void 0 ? void 0 : err.shortMessage) || (err === null || err === void 0 ? void 0 : err.message) || String(err);
|
|
45
|
+
const retryable = TX_NOT_INDEXED_RE.test(msg);
|
|
46
|
+
if (!retryable || Date.now() - startedAt >= PUSH_TX_INDEX_TIMEOUT_MS) {
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
if (!retryLogged) {
|
|
50
|
+
(0, context_1.printLog)(ctx, `${label} — Push tx ${txHash} has a receipt but is not indexed yet; retrying transaction lookup`);
|
|
51
|
+
retryLogged = true;
|
|
52
|
+
}
|
|
53
|
+
yield new Promise((r) => setTimeout(r, PUSH_TX_INDEX_POLL_MS));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
24
58
|
// ============================================================================
|
|
25
59
|
// sendPushTx
|
|
26
60
|
// ============================================================================
|
|
@@ -107,7 +141,7 @@ function sendPushTx(ctx, execute, eventBuffer, transformFn) {
|
|
|
107
141
|
(0, context_1.printLog)(ctx, `sendPushTx — operation ${i + 1}/${calls.length} confirmed in block ${receipt.blockNumber}`);
|
|
108
142
|
nonce++;
|
|
109
143
|
}
|
|
110
|
-
const txResponse = yield ctx
|
|
144
|
+
const txResponse = yield getIndexedPushTransaction(ctx, lastTxHash, 'sendPushTx');
|
|
111
145
|
return yield transformFn(txResponse, eventBuffer);
|
|
112
146
|
}
|
|
113
147
|
const txHash = yield ctx.pushClient.sendTransaction({
|
|
@@ -116,7 +150,7 @@ function sendPushTx(ctx, execute, eventBuffer, transformFn) {
|
|
|
116
150
|
value: execute.value,
|
|
117
151
|
signer: ctx.universalSigner,
|
|
118
152
|
});
|
|
119
|
-
const txResponse = yield ctx
|
|
153
|
+
const txResponse = yield getIndexedPushTransaction(ctx, txHash, 'sendPushTx');
|
|
120
154
|
return yield transformFn(txResponse, eventBuffer);
|
|
121
155
|
});
|
|
122
156
|
}
|
|
@@ -275,9 +309,7 @@ function sendUniversalTx(ctx, isUEADeployed, feeLockTxHash, universalPayload, ve
|
|
|
275
309
|
if (ethTxHashes.length === 0) {
|
|
276
310
|
throw new Error('No ethereumTxHash found in transaction events');
|
|
277
311
|
}
|
|
278
|
-
const evmTxs = yield Promise.all(ethTxHashes.map((hash) => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
279
|
-
return yield ctx.pushClient.getTransaction(hash);
|
|
280
|
-
})));
|
|
312
|
+
const evmTxs = yield Promise.all(ethTxHashes.map((hash) => tslib_1.__awaiter(this, void 0, void 0, function* () { return getIndexedPushTransaction(ctx, hash, 'sendUniversalTx'); })));
|
|
281
313
|
const responses = yield Promise.all(evmTxs.map((tx, index) => transformFn(tx, index === evmTxs.length - 1 ? eventBuffer : [])));
|
|
282
314
|
return responses;
|
|
283
315
|
});
|
|
@@ -341,7 +373,7 @@ function extractPcTxAndTransform(ctx, pushChainUniversalTx, gatewayTxHash, event
|
|
|
341
373
|
decodedError: decodedForPayload,
|
|
342
374
|
});
|
|
343
375
|
}
|
|
344
|
-
const tx = yield ctx
|
|
376
|
+
const tx = yield getIndexedPushTransaction(ctx, lastPcTransaction.txHash, label);
|
|
345
377
|
return transformFn(tx, eventBuffer);
|
|
346
378
|
});
|
|
347
379
|
}
|
|
@@ -62,8 +62,8 @@ export declare function executeCeaToPush(ctx: OrchestratorContext, params: Unive
|
|
|
62
62
|
*
|
|
63
63
|
* Unlike EVM Route 3 which builds CEA multicalls, SVM Route 3 encodes a
|
|
64
64
|
* `send_universal_tx_to_uea` instruction as an execute payload targeting
|
|
65
|
-
* the SVM gateway program (self-call).
|
|
66
|
-
* the
|
|
65
|
+
* the SVM gateway program (self-call). For funds-bearing calls the outbound
|
|
66
|
+
* request amount must match the drain amount embedded in the instruction data.
|
|
67
67
|
*/
|
|
68
68
|
export declare function executeCeaToPushSvm(ctx: OrchestratorContext, params: UniversalExecuteParams, sourceChain: CHAIN, executeFn: ExecuteFn): Promise<UniversalTxResponse>;
|
|
69
69
|
/**
|
|
@@ -137,6 +137,7 @@ function resolveR3SvmDrain(params, sourceChain, pushNetwork) {
|
|
|
137
137
|
var _a;
|
|
138
138
|
let drainAmount = BigInt(0);
|
|
139
139
|
let tokenMintHex;
|
|
140
|
+
let splMintBase58;
|
|
140
141
|
let prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, pushNetwork);
|
|
141
142
|
if (((_a = params.funds) === null || _a === void 0 ? void 0 : _a.amount) && params.funds.amount > BigInt(0)) {
|
|
142
143
|
drainAmount = params.funds.amount;
|
|
@@ -144,13 +145,14 @@ function resolveR3SvmDrain(params, sourceChain, pushNetwork) {
|
|
|
144
145
|
if ((token === null || token === void 0 ? void 0 : token.mechanism) === 'approve') {
|
|
145
146
|
const mintPk = new web3_js_1.PublicKey(token.address);
|
|
146
147
|
tokenMintHex = ('0x' + Buffer.from(mintPk.toBytes()).toString('hex'));
|
|
148
|
+
splMintBase58 = token.address;
|
|
147
149
|
prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
|
|
148
150
|
}
|
|
149
151
|
}
|
|
150
152
|
else if (params.value && params.value > BigInt(0)) {
|
|
151
153
|
drainAmount = params.value;
|
|
152
154
|
}
|
|
153
|
-
return { prc20Token, drainAmount, tokenMintHex };
|
|
155
|
+
return { prc20Token, drainAmount, tokenMintHex, splMintBase58 };
|
|
154
156
|
}
|
|
155
157
|
function buildR3SvmExtraPayload(ctx, params, ueaAddress, sourceChain, inboundNonce) {
|
|
156
158
|
var _a, _b;
|
|
@@ -166,7 +168,7 @@ function buildR3SvmExtraPayload(ctx, params, ueaAddress, sourceChain, inboundNon
|
|
|
166
168
|
ueaAddress,
|
|
167
169
|
allowSelfValueCall: true,
|
|
168
170
|
}));
|
|
169
|
-
const inboundPayload = (0, payload_builders_1.
|
|
171
|
+
const inboundPayload = (0, payload_builders_1.buildInboundUniversalPayloadSvm)(multicallPayload, {
|
|
170
172
|
nonce: inboundNonce !== null && inboundNonce !== void 0 ? inboundNonce : BigInt(0),
|
|
171
173
|
});
|
|
172
174
|
return (0, viem_1.hexToBytes)(inboundPayload);
|
|
@@ -1143,8 +1145,8 @@ function executeCeaToPush(ctx, params, executeFn) {
|
|
|
1143
1145
|
*
|
|
1144
1146
|
* Unlike EVM Route 3 which builds CEA multicalls, SVM Route 3 encodes a
|
|
1145
1147
|
* `send_universal_tx_to_uea` instruction as an execute payload targeting
|
|
1146
|
-
* the SVM gateway program (self-call).
|
|
1147
|
-
* the
|
|
1148
|
+
* the SVM gateway program (self-call). For funds-bearing calls the outbound
|
|
1149
|
+
* request amount must match the drain amount embedded in the instruction data.
|
|
1148
1150
|
*/
|
|
1149
1151
|
function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
|
|
1150
1152
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
@@ -1162,10 +1164,9 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
|
|
|
1162
1164
|
const gatewayProgramHex = ('0x' + Buffer.from(programPk.toBytes()).toString('hex'));
|
|
1163
1165
|
(0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_301, sourceChain, lockerContract);
|
|
1164
1166
|
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — sourceChain: ${sourceChain}, gateway: ${lockerContract}`);
|
|
1165
|
-
// Route 3 SVM
|
|
1166
|
-
//
|
|
1167
|
-
//
|
|
1168
|
-
// encodes; otherwise the gateway rejects with InvalidMint.
|
|
1167
|
+
// Route 3 SVM drains pre-existing funds from the Solana CEA. The Push-side
|
|
1168
|
+
// outbound is a payload relay; do not burn pSOL/pSPL from the UEA for the
|
|
1169
|
+
// drain amount.
|
|
1169
1170
|
const { prc20Token, drainAmount, tokenMintHex } = resolveR3SvmDrain(params, sourceChain, ctx.pushNetwork);
|
|
1170
1171
|
// Derive CEA PDA as revert recipient: ["push_identity", ueaAddress_20_bytes]
|
|
1171
1172
|
const ueaBytes = Buffer.from(ueaAddress.slice(2), 'hex'); // 20 bytes
|
|
@@ -1190,12 +1191,6 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
|
|
|
1190
1191
|
revertRecipientHex: ceaPdaHex,
|
|
1191
1192
|
});
|
|
1192
1193
|
(0, context_1.printLog)(ctx, `executeCeaToPushSvm — drainAmount: ${drainAmount.toString()}, payload length: ${(svmPayload.length - 2) / 2} bytes`);
|
|
1193
|
-
// burnAmount = 0 (payload-only outbound — drain amount lives inside ixData).
|
|
1194
|
-
// Mirrors the 2026-03-19 cascade fix that removed the 1-wei workaround for
|
|
1195
|
-
// SVM payload-only hops because it caused `InsufficientBalance (0xf4d678b8)`
|
|
1196
|
-
// reverts on fresh UEAs that don't hold the source-chain native PRC-20
|
|
1197
|
-
// (e.g. pSOL). The UGPC upgrade post-2026-03-19 already accepts amount = 0
|
|
1198
|
-
// on the cascade path, and the same precompile is used here.
|
|
1199
1194
|
const burnAmount = BigInt(0);
|
|
1200
1195
|
let effectiveGasLimit = (_a = params.gasLimit) !== null && _a !== void 0 ? _a : BigInt(0);
|
|
1201
1196
|
// Build outbound request: recipient = gateway program (self-call)
|
|
@@ -1218,7 +1213,7 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
|
|
|
1218
1213
|
prc20Token,
|
|
1219
1214
|
quote: result,
|
|
1220
1215
|
splMintBase58: undefined,
|
|
1221
|
-
burnAmount
|
|
1216
|
+
burnAmount,
|
|
1222
1217
|
pathTag: 'executeCeaToPushSvm',
|
|
1223
1218
|
});
|
|
1224
1219
|
gasToken = result.gasToken;
|
|
@@ -1290,8 +1285,8 @@ function executeCeaToPushSvm(ctx, params, sourceChain, executeFn) {
|
|
|
1290
1285
|
}
|
|
1291
1286
|
// SVM warn-threshold telemetry (no truncation — pre-flight handles drain protection).
|
|
1292
1287
|
(0, preflight_1.maybeFireSvmWarnThreshold)(ctx, nativeValueForGas, gasToken, 'R3_SVM');
|
|
1293
|
-
// Pre-flight (R3 SVM): no PRC-20 burn check.
|
|
1294
|
-
//
|
|
1288
|
+
// Pre-flight (R3 SVM): no PRC-20 burn check. The drain amount lives in the
|
|
1289
|
+
// embedded Solana instruction and is withdrawn from the external CEA.
|
|
1295
1290
|
(0, preflight_1.runPreflight)({
|
|
1296
1291
|
ctx,
|
|
1297
1292
|
ueaAddress,
|
|
@@ -172,8 +172,9 @@ function reconstructR3(universalTxResponse, universalTxData, isFailed, errorMsg)
|
|
|
172
172
|
events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_302_02](sourceChain, BigInt(0), BigInt(0)));
|
|
173
173
|
events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_01](sourceChain));
|
|
174
174
|
events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_02](universalTxResponse.from, ceaAddress, sourceChain));
|
|
175
|
-
// R3 pre-flight reconstruction
|
|
176
|
-
//
|
|
175
|
+
// R3 pre-flight reconstruction is intentionally approximate: historical
|
|
176
|
+
// response data does not include the SDK-side PRC-20 balance snapshot, so
|
|
177
|
+
// replay only the native check.
|
|
177
178
|
// R3 single-route uses 303-04 (R3 bucket); see runPreflight in preflight.ts.
|
|
178
179
|
events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_04](BigInt(0), BigInt(0), true, universalTxResponse.from, 'R3_EVM', { kind: 'NATIVE' }));
|
|
179
180
|
events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_304_01]());
|
|
@@ -35,7 +35,8 @@ export declare const MULTICALL_TUPLE_TYPE: {
|
|
|
35
35
|
*/
|
|
36
36
|
export declare function buildCeaMulticallPayload(multicalls: MultiCall[]): `0x${string}`;
|
|
37
37
|
/**
|
|
38
|
-
* Build an ABI-encoded UniversalPayload struct for inbound relay
|
|
38
|
+
* Build an ABI-encoded UniversalPayload struct for EVM-origin inbound relay
|
|
39
|
+
* (Route 3 CEA→Push).
|
|
39
40
|
*
|
|
40
41
|
* The relay and Push Chain gateway expect the payload parameter of sendUniversalTxToUEA
|
|
41
42
|
* to be a full UniversalPayload struct: (address to, uint256 value, bytes data, uint256 gasLimit,
|
|
@@ -53,6 +54,17 @@ export declare function buildInboundUniversalPayload(multicallData: `0x${string}
|
|
|
53
54
|
nonce?: bigint;
|
|
54
55
|
deadline?: bigint;
|
|
55
56
|
}): `0x${string}`;
|
|
57
|
+
/**
|
|
58
|
+
* Build a Borsh-encoded UniversalPayload for SVM-origin inbound relay.
|
|
59
|
+
*
|
|
60
|
+
* Push Chain decodes SVM-origin UniversalTx payloads with
|
|
61
|
+
* DecodeUniversalPayloadSolana, not the EVM ABI decoder used for EVM origins.
|
|
62
|
+
*/
|
|
63
|
+
export declare function buildInboundUniversalPayloadSvm(multicallData: `0x${string}`, opts?: {
|
|
64
|
+
gasLimit?: bigint;
|
|
65
|
+
nonce?: bigint;
|
|
66
|
+
deadline?: bigint;
|
|
67
|
+
}): `0x${string}`;
|
|
56
68
|
/**
|
|
57
69
|
* Build sendUniversalTxToUEA call for CEA self-call (Route 3)
|
|
58
70
|
*
|
|
@@ -4,6 +4,7 @@ exports.MULTICALL_TUPLE_TYPE = void 0;
|
|
|
4
4
|
exports.buildExecuteMulticall = buildExecuteMulticall;
|
|
5
5
|
exports.buildCeaMulticallPayload = buildCeaMulticallPayload;
|
|
6
6
|
exports.buildInboundUniversalPayload = buildInboundUniversalPayload;
|
|
7
|
+
exports.buildInboundUniversalPayloadSvm = buildInboundUniversalPayloadSvm;
|
|
7
8
|
exports.buildSendUniversalTxToUEA = buildSendUniversalTxToUEA;
|
|
8
9
|
exports.buildOutboundRequest = buildOutboundRequest;
|
|
9
10
|
exports.assertCeaFundsParkingInvariant = assertCeaFundsParkingInvariant;
|
|
@@ -24,6 +25,8 @@ const selectors_1 = require("../constants/selectors");
|
|
|
24
25
|
const chain_1 = require("../constants/chain");
|
|
25
26
|
const enums_1 = require("../constants/enums");
|
|
26
27
|
const data_utils_1 = require("./data-utils");
|
|
28
|
+
const tx_1 = require("../generated/v1/tx");
|
|
29
|
+
const signing_1 = require("./internals/signing");
|
|
27
30
|
function buildExecuteMulticall({ execute, ueaAddress, logger, allowSelfValueCall = false, }) {
|
|
28
31
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
29
32
|
const log = (msg) => logger === null || logger === void 0 ? void 0 : logger(msg);
|
|
@@ -161,7 +164,8 @@ function buildCeaMulticallPayload(multicalls) {
|
|
|
161
164
|
return `${selectors_1.UEA_MULTICALL_SELECTOR}${encoded.slice(2)}`;
|
|
162
165
|
}
|
|
163
166
|
/**
|
|
164
|
-
* Build an ABI-encoded UniversalPayload struct for inbound relay
|
|
167
|
+
* Build an ABI-encoded UniversalPayload struct for EVM-origin inbound relay
|
|
168
|
+
* (Route 3 CEA→Push).
|
|
165
169
|
*
|
|
166
170
|
* The relay and Push Chain gateway expect the payload parameter of sendUniversalTxToUEA
|
|
167
171
|
* to be a full UniversalPayload struct: (address to, uint256 value, bytes data, uint256 gasLimit,
|
|
@@ -205,6 +209,27 @@ function buildInboundUniversalPayload(multicallData, opts) {
|
|
|
205
209
|
},
|
|
206
210
|
]);
|
|
207
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Build a Borsh-encoded UniversalPayload for SVM-origin inbound relay.
|
|
214
|
+
*
|
|
215
|
+
* Push Chain decodes SVM-origin UniversalTx payloads with
|
|
216
|
+
* DecodeUniversalPayloadSolana, not the EVM ABI decoder used for EVM origins.
|
|
217
|
+
*/
|
|
218
|
+
function buildInboundUniversalPayloadSvm(multicallData, opts) {
|
|
219
|
+
var _a, _b, _c;
|
|
220
|
+
const payload = {
|
|
221
|
+
to: selectors_1.ZERO_ADDRESS,
|
|
222
|
+
value: BigInt(0).toString(),
|
|
223
|
+
data: multicallData,
|
|
224
|
+
gasLimit: ((_a = opts === null || opts === void 0 ? void 0 : opts.gasLimit) !== null && _a !== void 0 ? _a : BigInt(100000000)).toString(),
|
|
225
|
+
maxFeePerGas: BigInt(10000000000).toString(),
|
|
226
|
+
maxPriorityFeePerGas: BigInt(0).toString(),
|
|
227
|
+
nonce: ((_b = opts === null || opts === void 0 ? void 0 : opts.nonce) !== null && _b !== void 0 ? _b : BigInt(0)).toString(),
|
|
228
|
+
deadline: ((_c = opts === null || opts === void 0 ? void 0 : opts.deadline) !== null && _c !== void 0 ? _c : BigInt(9999999999)).toString(),
|
|
229
|
+
vType: tx_1.VerificationType.universalTxVerification,
|
|
230
|
+
};
|
|
231
|
+
return `0x${(0, signing_1.encodeUniversalPayloadSvm)(payload).toString('hex')}`;
|
|
232
|
+
}
|
|
208
233
|
/**
|
|
209
234
|
* Build sendUniversalTxToUEA call for CEA self-call (Route 3)
|
|
210
235
|
*
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* utxId = sha256(`<sourceChainCaip>:<txHash>:<logIndex>`)
|
|
7
7
|
* See push-chain/x/uexecutor/types/keys.go:49-53 (GetInboundUniversalTxKey).
|
|
8
8
|
*
|
|
9
|
+
* For SVM sources, `<txHash>` is the 0x-prefixed hex encoding of the 64-byte
|
|
10
|
+
* Solana signature and `<logIndex>` is the event's index in `meta.logMessages`.
|
|
11
|
+
*
|
|
9
12
|
* For Push-initiated outbounds (pcTx entries) the formula drops the log index:
|
|
10
13
|
* utxId = sha256(`<pcChainCaip>:<pcTxHash>`)
|
|
11
14
|
* See push-chain/x/uexecutor/types/keys.go:63-67 (GetPcUniversalTxKey).
|
|
@@ -14,6 +14,9 @@ const tslib_1 = require("tslib");
|
|
|
14
14
|
* utxId = sha256(`<sourceChainCaip>:<txHash>:<logIndex>`)
|
|
15
15
|
* See push-chain/x/uexecutor/types/keys.go:49-53 (GetInboundUniversalTxKey).
|
|
16
16
|
*
|
|
17
|
+
* For SVM sources, `<txHash>` is the 0x-prefixed hex encoding of the 64-byte
|
|
18
|
+
* Solana signature and `<logIndex>` is the event's index in `meta.logMessages`.
|
|
19
|
+
*
|
|
17
20
|
* For Push-initiated outbounds (pcTx entries) the formula drops the log index:
|
|
18
21
|
* utxId = sha256(`<pcChainCaip>:<pcTxHash>`)
|
|
19
22
|
* See push-chain/x/uexecutor/types/keys.go:63-67 (GetPcUniversalTxKey).
|
|
@@ -22,6 +25,7 @@ const tslib_1 = require("tslib");
|
|
|
22
25
|
* `pushClient.getUniversalTxByIdV2`, no event-attribute predicate needed.
|
|
23
26
|
*/
|
|
24
27
|
const viem_1 = require("viem");
|
|
28
|
+
const bs58_1 = require("../internal/bs58");
|
|
25
29
|
const normalize_1 = require("./normalize");
|
|
26
30
|
const STATUS_NAMES = {
|
|
27
31
|
0: 'UNIVERSAL_TX_STATUS_UNSPECIFIED',
|
|
@@ -45,14 +49,25 @@ const INBOUND_EVENT_NAMES = new Set([
|
|
|
45
49
|
'UniversalTx',
|
|
46
50
|
'RevertUniversalTx',
|
|
47
51
|
]);
|
|
52
|
+
const HEX_64_BYTE_RE = /^(?:0x)?[0-9a-fA-F]{128}$/;
|
|
53
|
+
function normalizeInboundTxHashForKey(sourceChainCaip, externalTxHash) {
|
|
54
|
+
if (!sourceChainCaip.startsWith('solana:')) {
|
|
55
|
+
return externalTxHash.startsWith('0x')
|
|
56
|
+
? externalTxHash
|
|
57
|
+
: `0x${externalTxHash}`;
|
|
58
|
+
}
|
|
59
|
+
if (externalTxHash.startsWith('0x'))
|
|
60
|
+
return externalTxHash;
|
|
61
|
+
if (HEX_64_BYTE_RE.test(externalTxHash))
|
|
62
|
+
return `0x${externalTxHash}`;
|
|
63
|
+
return (0, viem_1.toHex)(bs58_1.bs58.decode(externalTxHash));
|
|
64
|
+
}
|
|
48
65
|
/**
|
|
49
66
|
* Deterministic child universalTxId for an inbound event on an external chain.
|
|
50
67
|
* Mirrors `GetInboundUniversalTxKey` from the chain module.
|
|
51
68
|
*/
|
|
52
69
|
function deriveChildUniversalTxId(sourceChainCaip, externalTxHash, logIndex) {
|
|
53
|
-
const normHash = externalTxHash
|
|
54
|
-
? externalTxHash
|
|
55
|
-
: `0x${externalTxHash}`;
|
|
70
|
+
const normHash = normalizeInboundTxHashForKey(sourceChainCaip, externalTxHash);
|
|
56
71
|
const input = `${sourceChainCaip}:${normHash}:${logIndex}`;
|
|
57
72
|
return (0, viem_1.sha256)((0, viem_1.toBytes)(input));
|
|
58
73
|
}
|
|
@@ -15,12 +15,9 @@ const tslib_1 = require("tslib");
|
|
|
15
15
|
* return, same universalTxId derivation strategy (primary-log fallback
|
|
16
16
|
* via deriveChildUniversalTxId on source-chain inbounds).
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* If the keeper uses a different representation (hex bytes, sub_tx_id
|
|
22
|
-
* field, etc.) the `rawSource` argument to deriveChildUniversalTxId below
|
|
23
|
-
* needs to swap — single line.
|
|
18
|
+
* The Push keeper stores SVM inbound ids using the 0x-prefixed hex encoding
|
|
19
|
+
* of the 64-byte Solana signature and the index of the Anchor event inside
|
|
20
|
+
* `meta.logMessages`: `${caip}:${hexSig}:${logMessageIndex}`.
|
|
24
21
|
*/
|
|
25
22
|
const anchor_1 = require("@coral-xyz/anchor");
|
|
26
23
|
const web3_js_1 = require("@solana/web3.js");
|
|
@@ -91,12 +88,11 @@ function detectUniversalTxSvm(signature_1, chain_2) {
|
|
|
91
88
|
}
|
|
92
89
|
const eventCoder = new anchor_1.BorshEventCoder(universalGatewayV0_json_1.default);
|
|
93
90
|
const matchingLogs = [];
|
|
94
|
-
// Walk logMessages for `Program data:` entries. Each matched event
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
for (const rawLog of logMessages) {
|
|
91
|
+
// Walk logMessages for `Program data:` entries. Each matched event keeps
|
|
92
|
+
// the actual Solana log-message index because that is the value validators
|
|
93
|
+
// submit as the inbound log_index and the keeper uses for the UTX key.
|
|
94
|
+
for (let logIndex = 0; logIndex < logMessages.length; logIndex++) {
|
|
95
|
+
const rawLog = logMessages[logIndex];
|
|
100
96
|
if (typeof rawLog !== 'string')
|
|
101
97
|
continue;
|
|
102
98
|
const prefix = 'Program data: ';
|
|
@@ -135,10 +131,9 @@ function detectUniversalTxSvm(signature_1, chain_2) {
|
|
|
135
131
|
matchingLogs.push({
|
|
136
132
|
eventName,
|
|
137
133
|
address: emitter,
|
|
138
|
-
logIndex
|
|
134
|
+
logIndex,
|
|
139
135
|
args,
|
|
140
136
|
});
|
|
141
|
-
ordinal += 1;
|
|
142
137
|
}
|
|
143
138
|
if (matchingLogs.length === 0) {
|
|
144
139
|
notes.push('svm tx has no decoded universal-tx events');
|
|
@@ -148,8 +143,7 @@ function detectUniversalTxSvm(signature_1, chain_2) {
|
|
|
148
143
|
notes.push(...classified.notes);
|
|
149
144
|
const decoded = Object.assign({}, classified.decoded);
|
|
150
145
|
// Inbound fallback for universalTxId: same formula the EVM branch uses,
|
|
151
|
-
//
|
|
152
|
-
// see deriveSvmChildUniversalTxId in child-inbounds.ts).
|
|
146
|
+
// with the canonical 0x-hex Solana signature and log-message index.
|
|
153
147
|
if (!decoded.universalTxId) {
|
|
154
148
|
const caip = chain;
|
|
155
149
|
if (INBOUND_KINDS.has(classified.kind) &&
|