@pushchain/core 5.1.14 → 5.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/lib/orchestrator/internals/cascade.d.ts +16 -0
- package/src/lib/orchestrator/internals/cascade.js +205 -47
- package/src/lib/orchestrator/internals/error-decoder.d.ts +59 -0
- package/src/lib/orchestrator/internals/error-decoder.js +110 -0
- package/src/lib/orchestrator/internals/errors.d.ts +78 -0
- package/src/lib/orchestrator/internals/errors.js +83 -0
- package/src/lib/orchestrator/internals/outbound-sync.d.ts +2 -2
- package/src/lib/orchestrator/internals/outbound-sync.js +2 -2
- package/src/lib/orchestrator/internals/preflight.d.ts +80 -0
- package/src/lib/orchestrator/internals/preflight.js +145 -0
- package/src/lib/orchestrator/internals/push-chain-tx.d.ts +2 -18
- package/src/lib/orchestrator/internals/push-chain-tx.js +67 -40
- package/src/lib/orchestrator/internals/route-handlers.js +199 -114
- package/src/lib/orchestrator/internals/svm-rent.d.ts +46 -0
- package/src/lib/orchestrator/internals/svm-rent.js +100 -0
- package/src/lib/orchestrator/internals/tx-transformer.js +16 -1
- package/src/lib/orchestrator/orchestrator.js +34 -1
- package/src/lib/orchestrator/orchestrator.types.d.ts +27 -3
- package/src/lib/price-fetch/price-fetch.js +23 -5
- package/src/lib/progress-hook/progress-hook.js +130 -6
- package/src/lib/progress-hook/progress-hook.types.d.ts +8 -2
- package/src/lib/progress-hook/progress-hook.types.js +15 -0
- package/src/lib/universal/account/account.d.ts +1 -1
- package/src/lib/universal/account/account.js +1 -1
- package/src/lib/universal-tx-detector/child-inbounds.js +15 -0
package/package.json
CHANGED
|
@@ -15,6 +15,22 @@ export interface CascadeCallbacks {
|
|
|
15
15
|
failedAt?: number;
|
|
16
16
|
}>;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Fan out a cascade progress event to up to two listeners with dedup.
|
|
20
|
+
*
|
|
21
|
+
* The two listener slots are:
|
|
22
|
+
* - `primary`: explicit `eventHook` from CascadeTrackOptions (passed at call
|
|
23
|
+
* time to `waitForAll` / `wait`).
|
|
24
|
+
* - `secondary`: init-time `ctx.progressHook` from PushChain.initialize.
|
|
25
|
+
*
|
|
26
|
+
* Both may be undefined; if both reference the same function we invoke once.
|
|
27
|
+
* Events with an empty `id` (the no-op sentinel returned by pickWaitHooks for
|
|
28
|
+
* R4/unknown routes) are dropped so consumers don't see placeholder frames.
|
|
29
|
+
*
|
|
30
|
+
* Exported for unit testing. Production code calls this from the
|
|
31
|
+
* `emitHopEvent` closure inside `createCascadedBuilder.waitForAll`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function dispatchCascadeProgressEvent(event: import('../../progress-hook/progress-hook.types').ProgressEvent, primary?: (e: import('../../progress-hook/progress-hook.types').ProgressEvent) => void, secondary?: (e: import('../../progress-hook/progress-hook.types').ProgressEvent) => void): void;
|
|
18
34
|
export declare function prepareTransaction(ctx: OrchestratorContext, params: UniversalExecuteParams, callbacks: CascadeCallbacks): Promise<PreparedUniversalTx>;
|
|
19
35
|
export declare function buildHopDescriptor(ctx: OrchestratorContext, params: UniversalExecuteParams, route: TransactionRoute, ueaAddress: `0x${string}`): Promise<HopDescriptor>;
|
|
20
36
|
export declare function classifyIntoSegments(hops: HopDescriptor[]): CascadeSegment[];
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* getSegmentType, composeCascade, createCascadedBuilder, createChainedBuilder.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.dispatchCascadeProgressEvent = dispatchCascadeProgressEvent;
|
|
9
10
|
exports.prepareTransaction = prepareTransaction;
|
|
10
11
|
exports.buildHopDescriptor = buildHopDescriptor;
|
|
11
12
|
exports.classifyIntoSegments = classifyIntoSegments;
|
|
@@ -33,6 +34,36 @@ const progress_route_hooks_1 = require("./progress-route-hooks");
|
|
|
33
34
|
const progress_hook_1 = tslib_1.__importDefault(require("../../progress-hook/progress-hook"));
|
|
34
35
|
const progress_hook_types_1 = require("../../progress-hook/progress-hook.types");
|
|
35
36
|
const build_payload_1 = require("../svm-idl/build-payload");
|
|
37
|
+
const preflight_1 = require("./preflight");
|
|
38
|
+
const svm_rent_1 = require("./svm-rent");
|
|
39
|
+
const errors_1 = require("./errors");
|
|
40
|
+
const context_2 = require("./context");
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// dispatchCascadeProgressEvent
|
|
43
|
+
// ============================================================================
|
|
44
|
+
/**
|
|
45
|
+
* Fan out a cascade progress event to up to two listeners with dedup.
|
|
46
|
+
*
|
|
47
|
+
* The two listener slots are:
|
|
48
|
+
* - `primary`: explicit `eventHook` from CascadeTrackOptions (passed at call
|
|
49
|
+
* time to `waitForAll` / `wait`).
|
|
50
|
+
* - `secondary`: init-time `ctx.progressHook` from PushChain.initialize.
|
|
51
|
+
*
|
|
52
|
+
* Both may be undefined; if both reference the same function we invoke once.
|
|
53
|
+
* Events with an empty `id` (the no-op sentinel returned by pickWaitHooks for
|
|
54
|
+
* R4/unknown routes) are dropped so consumers don't see placeholder frames.
|
|
55
|
+
*
|
|
56
|
+
* Exported for unit testing. Production code calls this from the
|
|
57
|
+
* `emitHopEvent` closure inside `createCascadedBuilder.waitForAll`.
|
|
58
|
+
*/
|
|
59
|
+
function dispatchCascadeProgressEvent(event, primary, secondary) {
|
|
60
|
+
if (!event.id)
|
|
61
|
+
return;
|
|
62
|
+
if (primary)
|
|
63
|
+
primary(event);
|
|
64
|
+
if (secondary && secondary !== primary)
|
|
65
|
+
secondary(event);
|
|
66
|
+
}
|
|
36
67
|
// ============================================================================
|
|
37
68
|
// prepareTransaction
|
|
38
69
|
// ============================================================================
|
|
@@ -89,7 +120,7 @@ function prepareTransaction(ctx, params, callbacks) {
|
|
|
89
120
|
// ============================================================================
|
|
90
121
|
function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
91
122
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
92
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
123
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
93
124
|
// Pass 0 when user omits gasLimit → contract uses per-chain baseGasLimitByChainNamespace
|
|
94
125
|
const gasLimit = (_a = params.gasLimit) !== null && _a !== void 0 ? _a : BigInt(0);
|
|
95
126
|
const routeStr = route;
|
|
@@ -147,8 +178,21 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
147
178
|
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
148
179
|
let gasFee = BigInt(0);
|
|
149
180
|
let sizing;
|
|
181
|
+
// Conditional CEA-ATA rent bump (SPL-only). Mirrors single-route
|
|
182
|
+
// executeUoaToCeaSvm path. See svm-rent.ts for rationale.
|
|
183
|
+
const splMintBase58 = burnAmount > BigInt(0)
|
|
184
|
+
? (_d = (_c = params.funds) === null || _c === void 0 ? void 0 : _c.token) === null || _d === void 0 ? void 0 : _d.address
|
|
185
|
+
: undefined;
|
|
186
|
+
const effectiveGasLimit = yield (0, svm_rent_1.maybeBumpForCeaAtaRent)({
|
|
187
|
+
ctx,
|
|
188
|
+
ueaAddress,
|
|
189
|
+
targetChain,
|
|
190
|
+
splMintBase58,
|
|
191
|
+
burnAmount,
|
|
192
|
+
effectiveGasLimit: gasLimit,
|
|
193
|
+
});
|
|
150
194
|
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
151
|
-
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token,
|
|
195
|
+
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, effectiveGasLimit, targetChain);
|
|
152
196
|
gasToken = result.gasToken;
|
|
153
197
|
gasFee = result.gasFee;
|
|
154
198
|
sizing = result.sizing;
|
|
@@ -161,7 +205,7 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
161
205
|
sizing });
|
|
162
206
|
}
|
|
163
207
|
// EVM path: Resolve CEA address + build CEA multicalls
|
|
164
|
-
const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, targetChain, (
|
|
208
|
+
const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, targetChain, (_e = ctx.rpcUrls[targetChain]) === null || _e === void 0 ? void 0 : _e[0]);
|
|
165
209
|
// Migration path: raw MIGRATION_SELECTOR payload, no multicall wrapping
|
|
166
210
|
if (params.migration) {
|
|
167
211
|
const prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
|
|
@@ -191,7 +235,7 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
191
235
|
// When ERC-20 funds are provided with a single payload, auto-prepend a
|
|
192
236
|
// transfer() call so the tokens minted to the CEA are forwarded to the
|
|
193
237
|
// target address. This mirrors the Route 1 behavior in buildExecuteMulticall.
|
|
194
|
-
if ((
|
|
238
|
+
if ((_f = params.funds) === null || _f === void 0 ? void 0 : _f.amount) {
|
|
195
239
|
const token = params.funds.token;
|
|
196
240
|
if (token && token.mechanism !== 'native') {
|
|
197
241
|
const erc20Transfer = (0, viem_1.encodeFunctionData)({
|
|
@@ -211,7 +255,7 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
211
255
|
// native value to the CEA, and the multicall forwards it to the target.
|
|
212
256
|
ceaMulticalls.push({
|
|
213
257
|
to: target.address,
|
|
214
|
-
value: (
|
|
258
|
+
value: (_g = params.value) !== null && _g !== void 0 ? _g : BigInt(0),
|
|
215
259
|
data: params.data,
|
|
216
260
|
});
|
|
217
261
|
}
|
|
@@ -227,7 +271,7 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
227
271
|
});
|
|
228
272
|
}
|
|
229
273
|
}
|
|
230
|
-
else if ((
|
|
274
|
+
else if ((_h = params.funds) === null || _h === void 0 ? void 0 : _h.amount) {
|
|
231
275
|
// Funds-only transfer (no data, no value). Without this branch the
|
|
232
276
|
// CEA multicall is empty and relayed funds stay in the CEA instead
|
|
233
277
|
// of reaching the caller's recipient.
|
|
@@ -259,7 +303,7 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
259
303
|
// Determine PRC-20 token and burn amount
|
|
260
304
|
let prc20Token = selectors_1.ZERO_ADDRESS;
|
|
261
305
|
let burnAmount = BigInt(0);
|
|
262
|
-
if ((
|
|
306
|
+
if ((_j = params.funds) === null || _j === void 0 ? void 0 : _j.amount) {
|
|
263
307
|
const token = params.funds.token;
|
|
264
308
|
if (token) {
|
|
265
309
|
prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
|
|
@@ -317,7 +361,7 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
317
361
|
Buffer.from(ceaPda.toBytes()).toString('hex'));
|
|
318
362
|
let amount = BigInt(0);
|
|
319
363
|
let prc20Token;
|
|
320
|
-
if (((
|
|
364
|
+
if (((_k = params.funds) === null || _k === void 0 ? void 0 : _k.amount) && params.funds.amount > BigInt(0)) {
|
|
321
365
|
amount = params.funds.amount;
|
|
322
366
|
const token = params.funds.token;
|
|
323
367
|
if (token && token.address) {
|
|
@@ -348,14 +392,14 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
348
392
|
gasFee,
|
|
349
393
|
sizing });
|
|
350
394
|
}
|
|
351
|
-
const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, (
|
|
395
|
+
const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, (_l = ctx.rpcUrls[sourceChain]) === null || _l === void 0 ? void 0 : _l[0]);
|
|
352
396
|
// CEA auto-deploys on-chain: Vault.finalizeUniversalTx calls CEAFactory.deployCEA()
|
|
353
397
|
// if CEA doesn't exist yet. No SDK-side blocking needed.
|
|
354
398
|
// Determine token/amount for the inbound
|
|
355
399
|
let tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
356
400
|
let amount = BigInt(0);
|
|
357
401
|
let nativeValue = BigInt(0);
|
|
358
|
-
if ((
|
|
402
|
+
if ((_m = params.funds) === null || _m === void 0 ? void 0 : _m.amount) {
|
|
359
403
|
const token = params.funds.token;
|
|
360
404
|
if (token) {
|
|
361
405
|
if (token.mechanism === 'native') {
|
|
@@ -520,7 +564,7 @@ function getSegmentType(route) {
|
|
|
520
564
|
// ============================================================================
|
|
521
565
|
function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
|
|
522
566
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
523
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
567
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
524
568
|
let accumulatedPushMulticalls = [];
|
|
525
569
|
const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
|
|
526
570
|
// Compute per-outbound nativeValueForGas from UEA balance
|
|
@@ -528,6 +572,29 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
|
|
|
528
572
|
// The contract refunds excess, so over-allocating is safe.
|
|
529
573
|
const numOutbounds = segments.filter((s) => s.type !== 'PUSH_EXECUTION').length;
|
|
530
574
|
const CASCADE_GAS_RESERVE = BigInt(3e18); // 3 PC reserve for gas costs
|
|
575
|
+
// Cascade pre-flight: catches the zero-balance silent-segment-zero failure
|
|
576
|
+
// mode. When `ueaBalance <= CASCADE_GAS_RESERVE`, `perOutboundNativeValue`
|
|
577
|
+
// is left undefined and downstream segments silently allocate value=0,
|
|
578
|
+
// producing under-funded swaps that revert inside Uniswap. Throw early
|
|
579
|
+
// with a structured error so the caller knows which path / hop failed.
|
|
580
|
+
// Honor the per-call opt-out from the first non-PUSH segment (segments share
|
|
581
|
+
// the same UniversalExecuteParams.options.allowUnderfundedSwap shape).
|
|
582
|
+
const firstOutboundSegment = segments.find((s) => s.type !== 'PUSH_EXECUTION');
|
|
583
|
+
const cascadeAllowUnderfunded = ((_d = (_c = (_b = (_a = firstOutboundSegment === null || firstOutboundSegment === void 0 ? void 0 : firstOutboundSegment.hops) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.params) === null || _c === void 0 ? void 0 : _c.options) === null || _d === void 0 ? void 0 : _d.allowUnderfundedSwap) ===
|
|
584
|
+
true;
|
|
585
|
+
if (numOutbounds > 0) {
|
|
586
|
+
(0, preflight_1.runPreflight)({
|
|
587
|
+
ctx,
|
|
588
|
+
ueaAddress,
|
|
589
|
+
ueaBalance: ueaBalance !== null && ueaBalance !== void 0 ? ueaBalance : BigInt(0),
|
|
590
|
+
// Required = at least one wei of allocatable PC after reserve. The
|
|
591
|
+
// per-segment min-swap check below catches finer-grained shortfalls.
|
|
592
|
+
requiredValue: BigInt(1),
|
|
593
|
+
gasReserve: CASCADE_GAS_RESERVE,
|
|
594
|
+
pathTag: 'CASCADE',
|
|
595
|
+
allowUnderfundedSwap: cascadeAllowUnderfunded,
|
|
596
|
+
});
|
|
597
|
+
}
|
|
531
598
|
let perOutboundNativeValue;
|
|
532
599
|
if (ueaBalance && numOutbounds > 0 && ueaBalance > CASCADE_GAS_RESERVE) {
|
|
533
600
|
perOutboundNativeValue =
|
|
@@ -568,17 +635,57 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
|
|
|
568
635
|
const perSvmCap = svmTotal / BigInt(svmSegments.length);
|
|
569
636
|
const UNCAPPED_BALANCE = BigInt(1e30); // effectively disable estimator's internal cap
|
|
570
637
|
for (const seg of svmSegments) {
|
|
638
|
+
// Cascade-level segment index (position in `segments`, not in `svmSegments`)
|
|
639
|
+
// so the error's `segmentIndex` lines up with what the caller sees.
|
|
640
|
+
const cascadeIdx = segments.indexOf(seg);
|
|
571
641
|
const estimated = yield (0, gas_calculator_1.estimateNativeValueForSwap)(ctx, universalCoreAddress, seg.gasToken, seg.gasFee, UNCAPPED_BALANCE);
|
|
642
|
+
// SVM warn-threshold telemetry (no truncation — pre-flight handles drain protection).
|
|
643
|
+
(0, preflight_1.maybeFireSvmWarnThreshold)(ctx, estimated, seg.gasToken, 'CASCADE');
|
|
572
644
|
const flat = perOutboundNativeValue !== null && perOutboundNativeValue !== void 0 ? perOutboundNativeValue : BigInt(0);
|
|
573
645
|
let value = estimated > flat ? estimated : flat;
|
|
574
646
|
if (perSvmCap > BigInt(0) && value > perSvmCap)
|
|
575
647
|
value = perSvmCap;
|
|
576
|
-
|
|
648
|
+
// Upward-allocation ceiling (mirrors R3 SVM Fix #4 in route-handlers.ts:1690).
|
|
649
|
+
// When UEA balance is huge, the flat split or perSvmCap can produce a value
|
|
650
|
+
// far above what the swap actually needs. Submitting that into a Uniswap
|
|
651
|
+
// pool moves the price for other users. Cap at max(200 PC, 5×estimated):
|
|
652
|
+
// - fair pool (estimated ≈ 100 PC): cap = 500 PC → caps voluntary excess
|
|
653
|
+
// - skewed pool (estimated > 200 PC): cap = 5×estimated → still allows
|
|
654
|
+
// pool-skew margin; min-swap check below still fires if value < estimated
|
|
655
|
+
// The contract still refunds excess via swapAndBurnGas, so this is purely
|
|
656
|
+
// a guard against submitting wasteful msg.value to fair-priced pools.
|
|
657
|
+
const UPWARD_BASE_CEILING_CASCADE_SVM = BigInt(200) * BigInt(1e18);
|
|
658
|
+
const upwardCeilingCascadeSvm = estimated * BigInt(5) > UPWARD_BASE_CEILING_CASCADE_SVM
|
|
659
|
+
? estimated * BigInt(5)
|
|
660
|
+
: UPWARD_BASE_CEILING_CASCADE_SVM;
|
|
661
|
+
if (value > upwardCeilingCascadeSvm) {
|
|
662
|
+
(0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_e = seg.hops[0]) === null || _e === void 0 ? void 0 : _e.targetChain}: capping value from ${value} to upwardCeiling=${upwardCeilingCascadeSvm} (pool quote=${estimated})`);
|
|
663
|
+
value = upwardCeilingCascadeSvm;
|
|
664
|
+
}
|
|
665
|
+
(0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_f = seg.hops[0]) === null || _f === void 0 ? void 0 : _f.targetChain}: gasFee=${seg.gasFee}, estimatedWpc=${estimated}, flat=${flat}, perSvmCap=${perSvmCap}, upwardCeiling=${upwardCeilingCascadeSvm}, chosen=${value}`);
|
|
666
|
+
// Per-segment minimum-viable-swap check: if the chosen `value` is below
|
|
667
|
+
// the live pool quote (`estimated`), the cap squeezed the budget below
|
|
668
|
+
// what the swap actually needs and the segment will revert inside
|
|
669
|
+
// Uniswap with STF. Throw cleanly with `segmentIndex` so the caller
|
|
670
|
+
// knows which hop's pool is misbehaving.
|
|
671
|
+
if (estimated > BigInt(0) && value < estimated && !cascadeAllowUnderfunded) {
|
|
672
|
+
const shortfall = estimated - value;
|
|
673
|
+
(0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_04, estimated, value, shortfall, ueaAddress, 'CASCADE', { kind: 'NATIVE', segmentIndex: cascadeIdx });
|
|
674
|
+
throw new errors_1.InsufficientUEABalanceError({
|
|
675
|
+
required: estimated,
|
|
676
|
+
available: value,
|
|
677
|
+
shortfall,
|
|
678
|
+
ueaAddress,
|
|
679
|
+
pathTag: 'CASCADE',
|
|
680
|
+
reason: 'NATIVE',
|
|
681
|
+
segmentIndex: cascadeIdx,
|
|
682
|
+
});
|
|
683
|
+
}
|
|
577
684
|
svmNativeValueBySegment.set(seg, value);
|
|
578
685
|
}
|
|
579
686
|
}
|
|
580
|
-
for (let
|
|
581
|
-
const segment = segments[
|
|
687
|
+
for (let segIdxLoop = segments.length - 1; segIdxLoop >= 0; segIdxLoop--) {
|
|
688
|
+
const segment = segments[segIdxLoop];
|
|
582
689
|
switch (segment.type) {
|
|
583
690
|
case 'PUSH_EXECUTION': {
|
|
584
691
|
// Prepend Push Chain multicalls to accumulated
|
|
@@ -596,7 +703,7 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
|
|
|
596
703
|
const isMigration = (firstHop === null || firstHop === void 0 ? void 0 : firstHop.isMigration) === true;
|
|
597
704
|
if (isSvmSegment) {
|
|
598
705
|
// SVM: use the pre-built SVM payload from the hop descriptor
|
|
599
|
-
outboundPayload = (
|
|
706
|
+
outboundPayload = (_g = firstHop.svmPayload) !== null && _g !== void 0 ? _g : '0x';
|
|
600
707
|
// For SVM, to.address IS the target (program for execute, recipient for withdraw)
|
|
601
708
|
const svmTarget = firstHop.params.to;
|
|
602
709
|
targetForOutbound = svmTarget.address;
|
|
@@ -612,8 +719,40 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
|
|
|
612
719
|
// Get CEA address from the first hop
|
|
613
720
|
targetForOutbound = (firstHop === null || firstHop === void 0 ? void 0 : firstHop.ceaAddress) || ueaAddress;
|
|
614
721
|
}
|
|
722
|
+
// Per-segment PRC-20 burn-balance pre-check (cascade R2-style segments
|
|
723
|
+
// only — INBOUND_FROM_CEA segments are payload-only with totalBurnAmount=0
|
|
724
|
+
// per the 2026-03-19 fix). Skip MIGRATION (totalBurnAmount=0). Fires
|
|
725
|
+
// whenever burnAmount > 0 even when burn token equals gas token — the
|
|
726
|
+
// gateway's transferFrom happens BEFORE the gas swap, so user must
|
|
727
|
+
// pre-hold the burn amount.
|
|
728
|
+
const segBurnAmount = segment.totalBurnAmount || BigInt(0);
|
|
729
|
+
const segBurnToken = segment.prc20Token;
|
|
730
|
+
if (segBurnAmount > BigInt(0) && segBurnToken && !isMigration) {
|
|
731
|
+
const onHand = yield ctx.pushClient.readContract({
|
|
732
|
+
address: segBurnToken,
|
|
733
|
+
abi: erc20_evm_1.ERC20_EVM,
|
|
734
|
+
functionName: 'balanceOf',
|
|
735
|
+
args: [ueaAddress],
|
|
736
|
+
});
|
|
737
|
+
const sufficient = onHand >= segBurnAmount;
|
|
738
|
+
(0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_03, segBurnAmount, onHand, sufficient, ueaAddress, 'CASCADE', { kind: 'PRC20', burnToken: segBurnToken, segmentIndex: segIdxLoop });
|
|
739
|
+
if (!sufficient && !cascadeAllowUnderfunded) {
|
|
740
|
+
const shortfall = segBurnAmount - onHand;
|
|
741
|
+
(0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_04, segBurnAmount, onHand, shortfall, ueaAddress, 'CASCADE', { kind: 'PRC20', burnToken: segBurnToken, segmentIndex: segIdxLoop });
|
|
742
|
+
throw new errors_1.InsufficientUEABalanceError({
|
|
743
|
+
required: segBurnAmount,
|
|
744
|
+
available: onHand,
|
|
745
|
+
shortfall,
|
|
746
|
+
ueaAddress,
|
|
747
|
+
pathTag: 'CASCADE',
|
|
748
|
+
reason: 'PRC20',
|
|
749
|
+
burnToken: segBurnToken,
|
|
750
|
+
segmentIndex: segIdxLoop,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
}
|
|
615
754
|
// Build outbound request
|
|
616
|
-
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetForOutbound, segment.prc20Token || selectors_1.ZERO_ADDRESS, segment.totalBurnAmount || BigInt(0), (
|
|
755
|
+
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetForOutbound, segment.prc20Token || selectors_1.ZERO_ADDRESS, segment.totalBurnAmount || BigInt(0), (_h = segment.gasLimit) !== null && _h !== void 0 ? _h : BigInt(0), outboundPayload, ueaAddress);
|
|
617
756
|
// Build approval + outbound multicalls
|
|
618
757
|
const segGasFee = segment.gasFee || BigInt(0);
|
|
619
758
|
const svmOverride = svmNativeValueBySegment.get(segment);
|
|
@@ -629,7 +768,7 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
|
|
|
629
768
|
else {
|
|
630
769
|
evmFallbackValue = segGasFee * BigInt(1000000);
|
|
631
770
|
}
|
|
632
|
-
let nativeValueForGas = (
|
|
771
|
+
let nativeValueForGas = (_j = svmOverride !== null && svmOverride !== void 0 ? svmOverride : perOutboundNativeValue) !== null && _j !== void 0 ? _j : evmFallbackValue;
|
|
633
772
|
// SDK 5.2 Case C: when the segment's sizing is category C, compose
|
|
634
773
|
// R2/OUTBOUND_TO_CEA segments no longer apply Case A/B/C sizing
|
|
635
774
|
// (scoped to R1 + R3 only). segment.sizing may still be populated
|
|
@@ -661,7 +800,7 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
|
|
|
661
800
|
// UEA multicall so no nested outbound is needed.
|
|
662
801
|
const mergedHopMulticalls = [];
|
|
663
802
|
for (const h of segment.hops) {
|
|
664
|
-
if (((
|
|
803
|
+
if (((_k = h === null || h === void 0 ? void 0 : h.params) === null || _k === void 0 ? void 0 : _k.data) || ((_l = h === null || h === void 0 ? void 0 : h.params) === null || _l === void 0 ? void 0 : _l.value)) {
|
|
665
804
|
const hopCalls = (0, payload_builders_1.buildExecuteMulticall)({
|
|
666
805
|
execute: {
|
|
667
806
|
to: h.params.to,
|
|
@@ -702,7 +841,7 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
|
|
|
702
841
|
let drainAmount = BigInt(0);
|
|
703
842
|
let tokenMintHex;
|
|
704
843
|
const params = hop.params;
|
|
705
|
-
if (((
|
|
844
|
+
if (((_m = params.funds) === null || _m === void 0 ? void 0 : _m.amount) && params.funds.amount > BigInt(0)) {
|
|
706
845
|
drainAmount = params.funds.amount;
|
|
707
846
|
const token = params.funds.token;
|
|
708
847
|
if (token && token.address) {
|
|
@@ -731,7 +870,7 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
|
|
|
731
870
|
});
|
|
732
871
|
const svmBurnAmount = segment.totalBurnAmount || BigInt(0);
|
|
733
872
|
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, segment.prc20Token ||
|
|
734
|
-
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), svmBurnAmount, (
|
|
873
|
+
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), svmBurnAmount, (_o = segment.gasLimit) !== null && _o !== void 0 ? _o : BigInt(0), svmPayload, ueaAddress);
|
|
735
874
|
const inboundGasFee = segment.gasFee || BigInt(0);
|
|
736
875
|
let svmInboundFallback;
|
|
737
876
|
if (ueaBalance && ueaBalance > BigInt(0)) {
|
|
@@ -744,7 +883,7 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
|
|
|
744
883
|
// SDK 5.2 R3-style Case C bump: when the inbound segment's sizing
|
|
745
884
|
// is C, top up the swap budget by the overflow. No bridge-swap —
|
|
746
885
|
// R3 has no destination funds delivery.
|
|
747
|
-
if (((
|
|
886
|
+
if (((_p = segment.sizing) === null || _p === void 0 ? void 0 : _p.category) === 'C' &&
|
|
748
887
|
segment.sizing.overflowNativePc > BigInt(0)) {
|
|
749
888
|
const bumped = svmInboundNativeValue + segment.sizing.overflowNativePc;
|
|
750
889
|
(0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (SVM) Case C: bumping nativeValueForGas from ${svmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
|
|
@@ -779,7 +918,7 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
|
|
|
779
918
|
let amount = BigInt(0);
|
|
780
919
|
for (const mergedHop of segment.hops) {
|
|
781
920
|
const hParams = mergedHop.params;
|
|
782
|
-
if ((
|
|
921
|
+
if ((_q = hParams.funds) === null || _q === void 0 ? void 0 : _q.amount) {
|
|
783
922
|
const token = hParams.funds.token;
|
|
784
923
|
if (token) {
|
|
785
924
|
if (token.mechanism === 'native') {
|
|
@@ -854,7 +993,7 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
|
|
|
854
993
|
// SDK 5.2 R3-style Case C bump: when the inbound segment's sizing
|
|
855
994
|
// is C, top up the swap budget by the overflow. No bridge-swap —
|
|
856
995
|
// R3 has no destination funds delivery.
|
|
857
|
-
if (((
|
|
996
|
+
if (((_r = segment.sizing) === null || _r === void 0 ? void 0 : _r.category) === 'C' &&
|
|
858
997
|
segment.sizing.overflowNativePc > BigInt(0)) {
|
|
859
998
|
const bumped = evmInboundNativeValue + segment.sizing.overflowNativePc;
|
|
860
999
|
(0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (EVM) Case C: bumping nativeValueForGas from ${evmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
|
|
@@ -885,6 +1024,7 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
|
|
|
885
1024
|
function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
886
1025
|
return {
|
|
887
1026
|
send: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
1027
|
+
var _a;
|
|
888
1028
|
const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
|
|
889
1029
|
// Extract HopDescriptors
|
|
890
1030
|
const hops = preparedTxs.map((tx) => tx._hop);
|
|
@@ -970,7 +1110,22 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
970
1110
|
const ueaNonceCascade = ueaCodeCascade !== undefined
|
|
971
1111
|
? yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress)
|
|
972
1112
|
: BigInt(0);
|
|
973
|
-
|
|
1113
|
+
let composedMulticalls;
|
|
1114
|
+
try {
|
|
1115
|
+
composedMulticalls = yield composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonceCascade);
|
|
1116
|
+
}
|
|
1117
|
+
catch (err) {
|
|
1118
|
+
// Pre-flight or per-segment min-swap failure inside composeCascade
|
|
1119
|
+
// throws before the cascade ever broadcasts. Fire 999-02 with the
|
|
1120
|
+
// segmentIndex (1-indexed for the failedAt slot) so consumers see a
|
|
1121
|
+
// terminal "cascade failed" frame instead of the stream stopping
|
|
1122
|
+
// silently after 203-04.
|
|
1123
|
+
if (err instanceof errors_1.InsufficientUEABalanceError) {
|
|
1124
|
+
const failedHopIndex = (_a = err.segmentIndex) !== null && _a !== void 0 ? _a : 0;
|
|
1125
|
+
(0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_999_02, failedHopIndex + 1, preparedTxs.length, err.message);
|
|
1126
|
+
}
|
|
1127
|
+
throw err;
|
|
1128
|
+
}
|
|
974
1129
|
// Execute the composed multicall as a single Push Chain tx
|
|
975
1130
|
const executeParams = {
|
|
976
1131
|
to: ueaAddress,
|
|
@@ -998,14 +1153,17 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
998
1153
|
var _a, _b, _c, _d, _e;
|
|
999
1154
|
const { pollingIntervalMs = 10000, timeout = 300000, progressHook: cascadeProgressHook, eventHook, } = opts || {};
|
|
1000
1155
|
const startTime = Date.now();
|
|
1001
|
-
// Per-hop progress emission.
|
|
1002
|
-
//
|
|
1003
|
-
//
|
|
1004
|
-
//
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1156
|
+
// Per-hop progress emission. Fans out cascade markers to BOTH the
|
|
1157
|
+
// explicit `eventHook` (passed into waitForAll) AND the init-time
|
|
1158
|
+
// `ctx.progressHook` (set on PushChain.initialize), deduping if the
|
|
1159
|
+
// two are the same reference. This is the bridge that lets UI-kit
|
|
1160
|
+
// consumers — which only listen on `ctx.progressHook` — receive the
|
|
1161
|
+
// cascade-level marker stream (001 / 002-xx / 203-xx / 204-xx /
|
|
1162
|
+
// 209-xx / 299-01 / 999-xx) without having to manually wire
|
|
1163
|
+
// `eventHook` at every call site. Drops events whose id is '' (the
|
|
1164
|
+
// no-op sentinel returned by pickWaitHooks for R4/unknown routes).
|
|
1165
|
+
const emitHopEvent = (event) => {
|
|
1166
|
+
dispatchCascadeProgressEvent(event, eventHook, ctx.progressHook);
|
|
1009
1167
|
};
|
|
1010
1168
|
// ---- Multichain (multi-hop) cascade markers (001 / 002 / 999) ----
|
|
1011
1169
|
// Only fire when this is genuinely a multi-hop cascade. Single-hop
|
|
@@ -1026,7 +1184,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
1026
1184
|
? signerOriginChain
|
|
1027
1185
|
: chainLabels[hopIndex0 - 1];
|
|
1028
1186
|
const toChain = chainLabels[hopIndex0];
|
|
1029
|
-
emitHopEvent(
|
|
1187
|
+
emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_002_01](n, totalHops, fromChain, toChain));
|
|
1030
1188
|
};
|
|
1031
1189
|
const emitHopComplete = (hopIndex0) => {
|
|
1032
1190
|
if (!isMulti)
|
|
@@ -1034,25 +1192,25 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
1034
1192
|
const n = hopIndex0 + 1;
|
|
1035
1193
|
if (n >= totalHops)
|
|
1036
1194
|
return; // skip after final hop — 999-01 takes over
|
|
1037
|
-
emitHopEvent(
|
|
1195
|
+
emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_002_99_99](n, totalHops));
|
|
1038
1196
|
};
|
|
1039
1197
|
const emitCascadeSuccess = () => {
|
|
1040
1198
|
if (!isMulti)
|
|
1041
1199
|
return;
|
|
1042
|
-
emitHopEvent(
|
|
1200
|
+
emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_999_01](totalHops));
|
|
1043
1201
|
};
|
|
1044
1202
|
const emitCascadeFailed = (failedIdx0, errMsg) => {
|
|
1045
1203
|
if (!isMulti)
|
|
1046
1204
|
return;
|
|
1047
|
-
emitHopEvent(
|
|
1205
|
+
emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_999_02](failedIdx0 + 1, totalHops, errMsg));
|
|
1048
1206
|
};
|
|
1049
1207
|
const emitCascadeTimeout = (failedIdx0) => {
|
|
1050
1208
|
if (!isMulti)
|
|
1051
1209
|
return;
|
|
1052
|
-
emitHopEvent(
|
|
1210
|
+
emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_999_03](failedIdx0 + 1, totalHops));
|
|
1053
1211
|
};
|
|
1054
1212
|
if (isMulti) {
|
|
1055
|
-
emitHopEvent(
|
|
1213
|
+
emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_001](totalHops, chainLabels));
|
|
1056
1214
|
}
|
|
1057
1215
|
try {
|
|
1058
1216
|
// 1. Wait for initial Push Chain tx confirmation
|
|
@@ -1126,7 +1284,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
1126
1284
|
status: 'timeout',
|
|
1127
1285
|
elapsed: Date.now() - startTime,
|
|
1128
1286
|
});
|
|
1129
|
-
emitHopEvent(
|
|
1287
|
+
emitHopEvent(hopHooks.timeout(hop.executionChain, Date.now() - startTime));
|
|
1130
1288
|
emitCascadeTimeout(hop.hopIndex);
|
|
1131
1289
|
return {
|
|
1132
1290
|
success: false,
|
|
@@ -1135,7 +1293,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
1135
1293
|
};
|
|
1136
1294
|
}
|
|
1137
1295
|
emitHopStart(hop.hopIndex);
|
|
1138
|
-
emitHopEvent(
|
|
1296
|
+
emitHopEvent(hopHooks.awaiting(hop.executionChain));
|
|
1139
1297
|
cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
|
|
1140
1298
|
hopIndex: hop.hopIndex,
|
|
1141
1299
|
route: hop.route,
|
|
@@ -1160,14 +1318,14 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
1160
1318
|
if (event.status === 'polling' &&
|
|
1161
1319
|
lastEmittedStatus !== 'polling') {
|
|
1162
1320
|
lastEmittedStatus = 'polling';
|
|
1163
|
-
emitHopEvent(
|
|
1321
|
+
emitHopEvent(hopHooks.polling(hop.executionChain, event.elapsed));
|
|
1164
1322
|
}
|
|
1165
1323
|
},
|
|
1166
1324
|
});
|
|
1167
1325
|
hop.status = 'confirmed';
|
|
1168
1326
|
hop.txHash = outboundDetails.externalTxHash;
|
|
1169
1327
|
hop.outboundDetails = outboundDetails;
|
|
1170
|
-
emitHopEvent(
|
|
1328
|
+
emitHopEvent(hopHooks.success(outboundDetails));
|
|
1171
1329
|
emitHopComplete(hop.hopIndex);
|
|
1172
1330
|
cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
|
|
1173
1331
|
hopIndex: hop.hopIndex,
|
|
@@ -1181,7 +1339,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
1181
1339
|
catch (err) {
|
|
1182
1340
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1183
1341
|
const isTimeout = errMsg.startsWith('Timeout waiting for outbound transaction');
|
|
1184
|
-
emitHopEvent(
|
|
1342
|
+
emitHopEvent(isTimeout
|
|
1185
1343
|
? hopHooks.timeout(hop.executionChain, remainingTimeout)
|
|
1186
1344
|
: hopHooks.failed(hop.executionChain, errMsg));
|
|
1187
1345
|
if (isTimeout) {
|
|
@@ -1211,7 +1369,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
1211
1369
|
// polling begins, using the route-specific awaiting hook.
|
|
1212
1370
|
for (const outHop of outboundHops) {
|
|
1213
1371
|
emitHopStart(outHop.hopIndex);
|
|
1214
|
-
emitHopEvent(
|
|
1372
|
+
emitHopEvent((0, progress_route_hooks_1.pickWaitHooks)(outHop.route).awaiting(outHop.executionChain));
|
|
1215
1373
|
}
|
|
1216
1374
|
const perHopLastStatus = new Map();
|
|
1217
1375
|
const confirmedHops = new Set();
|
|
@@ -1235,7 +1393,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
1235
1393
|
if (event.status === 'polling' &&
|
|
1236
1394
|
prev !== 'polling') {
|
|
1237
1395
|
perHopLastStatus.set(event.hopIndex, 'polling');
|
|
1238
|
-
emitHopEvent(
|
|
1396
|
+
emitHopEvent(eventHooks.polling((_b = (_a = matchedHop === null || matchedHop === void 0 ? void 0 : matchedHop.executionChain) !== null && _a !== void 0 ? _a : event.chain) !== null && _b !== void 0 ? _b : '', Date.now() - startTime));
|
|
1239
1397
|
}
|
|
1240
1398
|
if (event.status === 'confirmed' &&
|
|
1241
1399
|
event.txHash &&
|
|
@@ -1247,7 +1405,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
1247
1405
|
// guard above, so we only reach here with full details.
|
|
1248
1406
|
if (matchedHop === null || matchedHop === void 0 ? void 0 : matchedHop.outboundDetails) {
|
|
1249
1407
|
confirmedHops.add(event.hopIndex);
|
|
1250
|
-
emitHopEvent(
|
|
1408
|
+
emitHopEvent(eventHooks.success(matchedHop.outboundDetails));
|
|
1251
1409
|
emitHopComplete(event.hopIndex);
|
|
1252
1410
|
}
|
|
1253
1411
|
}
|
|
@@ -1259,7 +1417,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
1259
1417
|
const failMsg = failedHop
|
|
1260
1418
|
? `Outbound failed for hop ${failedIdx} on ${failedHop.executionChain}`
|
|
1261
1419
|
: `Outbound cascade failed at hop ${failedIdx}`;
|
|
1262
|
-
emitHopEvent(
|
|
1420
|
+
emitHopEvent((0, progress_route_hooks_1.pickWaitHooks)(failedHop === null || failedHop === void 0 ? void 0 : failedHop.route).failed((_e = failedHop === null || failedHop === void 0 ? void 0 : failedHop.executionChain) !== null && _e !== void 0 ? _e : '', failMsg));
|
|
1263
1421
|
emitCascadeFailed(failedIdx, failMsg);
|
|
1264
1422
|
return {
|
|
1265
1423
|
success: false,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decoder for revert-error messages surfaced by Cosmos `tx.rawLog`.
|
|
3
|
+
*
|
|
4
|
+
* Two extraction paths:
|
|
5
|
+
* 1. 4-byte custom selector (e.g. `0xacfdb444:` or `0xf4d678b8000000…`) —
|
|
6
|
+
* looked up against `KNOWN_ERROR_SELECTORS` for a friendly name + hint.
|
|
7
|
+
* 2. `Error(string)` ABI-encoded revert (`0x08c379a0` selector + offset +
|
|
8
|
+
* length + data) — decoded into the original UTF-8 string.
|
|
9
|
+
*
|
|
10
|
+
* Unknown selectors fall through with `kind: 'unknown'` so callers can log
|
|
11
|
+
* them at INFO and grow the table from real reverts.
|
|
12
|
+
*
|
|
13
|
+
* The selector→name mappings below are EMPIRICAL: they were observed in
|
|
14
|
+
* production e2e logs and SDK test fixtures. `keccak256` of the obvious
|
|
15
|
+
* parameterless signatures (e.g. `keccak256("InsufficientBalance()")` =
|
|
16
|
+
* `0x01e1e567`) does NOT reproduce the listed selectors, so the deployed-
|
|
17
|
+
* source mapping is still unverified. Names and hints are worded as
|
|
18
|
+
* "likely cause" until the bytecode-source trace lands.
|
|
19
|
+
*/
|
|
20
|
+
export interface KnownErrorSelector {
|
|
21
|
+
/** Best-effort error name. Worded as a likely cause until source-traced. */
|
|
22
|
+
name: string;
|
|
23
|
+
/** User-facing remediation hint. */
|
|
24
|
+
hint: string;
|
|
25
|
+
/** Where this mapping was first observed (log path or fixture). */
|
|
26
|
+
provenance: string;
|
|
27
|
+
}
|
|
28
|
+
export declare const KNOWN_ERROR_SELECTORS: Record<`0x${string}`, KnownErrorSelector>;
|
|
29
|
+
/** Selector for `Error(string)` (Solidity `require(false, "msg")`). */
|
|
30
|
+
export declare const ERROR_STRING_SELECTOR: `0x${string}`;
|
|
31
|
+
export type DecodedRevertCustom = {
|
|
32
|
+
kind: 'custom';
|
|
33
|
+
selector: `0x${string}`;
|
|
34
|
+
name: string;
|
|
35
|
+
hint: string;
|
|
36
|
+
provenance: string;
|
|
37
|
+
};
|
|
38
|
+
export type DecodedRevertString = {
|
|
39
|
+
kind: 'string';
|
|
40
|
+
selector: `0x${string}`;
|
|
41
|
+
decoded: string;
|
|
42
|
+
};
|
|
43
|
+
export type DecodedRevertUnknown = {
|
|
44
|
+
kind: 'unknown';
|
|
45
|
+
selector: `0x${string}`;
|
|
46
|
+
};
|
|
47
|
+
export type DecodedRevert = DecodedRevertCustom | DecodedRevertString | DecodedRevertUnknown;
|
|
48
|
+
/**
|
|
49
|
+
* Extracts the first hex blob after `ret ` from a Cosmos tx error message,
|
|
50
|
+
* pulls the 4-byte selector, and looks it up. Returns `null` when no blob
|
|
51
|
+
* is found.
|
|
52
|
+
*
|
|
53
|
+
* **Assumption**: today's Cosmos `tx.rawLog` format contains a single
|
|
54
|
+
* `ret 0x…:` substring per failed message. If a future log format includes
|
|
55
|
+
* nested call traces with multiple `ret` substrings, this regex will pick
|
|
56
|
+
* the first selector — which may not be the most relevant one. Revisit
|
|
57
|
+
* if multi-revert log formats appear in production.
|
|
58
|
+
*/
|
|
59
|
+
export declare function decodeRevert(errorMsg: string): DecodedRevert | null;
|