@pushchain/core 5.1.5-alpha.1 → 5.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/package.json +1 -1
- package/src/lib/constants/abi/index.d.ts +2 -0
- package/src/lib/constants/abi/index.js +6 -1
- package/src/lib/constants/abi/uniswap-v3-router.evm.d.ts +58 -0
- package/src/lib/constants/abi/uniswap-v3-router.evm.js +41 -0
- package/src/lib/constants/abi/wpc.evm.d.ts +24 -0
- package/src/lib/constants/abi/wpc.evm.js +27 -0
- package/src/lib/constants/chain.d.ts +1 -0
- package/src/lib/constants/chain.js +8 -6
- package/src/lib/constants/tokens.js +3 -3
- package/src/lib/generated/uexecutor/v2/types.d.ts +14 -0
- package/src/lib/generated/uexecutor/v2/types.js +96 -3
- package/src/lib/orchestrator/internals/cascade.d.ts +1 -1
- package/src/lib/orchestrator/internals/cascade.js +521 -239
- package/src/lib/orchestrator/internals/confirmation.js +60 -15
- package/src/lib/orchestrator/internals/context.d.ts +23 -0
- package/src/lib/orchestrator/internals/context.js +54 -0
- package/src/lib/orchestrator/internals/execute-funds-only.js +47 -21
- package/src/lib/orchestrator/internals/execute-funds-payload.js +53 -31
- package/src/lib/orchestrator/internals/execute-standard.js +137 -26
- package/src/lib/orchestrator/internals/gas-calculator.d.ts +5 -2
- package/src/lib/orchestrator/internals/gas-calculator.js +59 -11
- package/src/lib/orchestrator/internals/gas-usd-sizer.d.ts +53 -0
- package/src/lib/orchestrator/internals/gas-usd-sizer.js +165 -0
- package/src/lib/orchestrator/internals/helpers.js +2 -0
- package/src/lib/orchestrator/internals/inbound-tracker.d.ts +83 -0
- package/src/lib/orchestrator/internals/inbound-tracker.js +322 -0
- package/src/lib/orchestrator/internals/index.d.ts +3 -1
- package/src/lib/orchestrator/internals/index.js +8 -2
- package/src/lib/orchestrator/internals/outbound-sync.d.ts +32 -1
- package/src/lib/orchestrator/internals/outbound-sync.js +154 -17
- package/src/lib/orchestrator/internals/pc-usd-oracle.d.ts +28 -0
- package/src/lib/orchestrator/internals/pc-usd-oracle.js +285 -0
- package/src/lib/orchestrator/internals/progress-route-hooks.d.ts +23 -0
- package/src/lib/orchestrator/internals/progress-route-hooks.js +72 -0
- package/src/lib/orchestrator/internals/push-chain-tx.d.ts +27 -1
- package/src/lib/orchestrator/internals/push-chain-tx.js +111 -15
- package/src/lib/orchestrator/internals/rescue.js +1 -1
- package/src/lib/orchestrator/internals/response-builder.d.ts +5 -0
- package/src/lib/orchestrator/internals/response-builder.js +242 -66
- package/src/lib/orchestrator/internals/route-handlers.js +480 -206
- package/src/lib/orchestrator/internals/tx-transformer.js +293 -35
- package/src/lib/orchestrator/orchestrator.d.ts +7 -1
- package/src/lib/orchestrator/orchestrator.js +108 -56
- package/src/lib/orchestrator/orchestrator.types.d.ts +111 -5
- package/src/lib/orchestrator/payload-builders.d.ts +16 -0
- package/src/lib/orchestrator/payload-builders.js +6 -5
- package/src/lib/orchestrator/route-detector.js +20 -8
- package/src/lib/orchestrator/svm-idl/normalize-address.d.ts +19 -0
- package/src/lib/orchestrator/svm-idl/normalize-address.js +56 -0
- package/src/lib/orchestrator/svm-idl/registry.d.ts +13 -4
- package/src/lib/orchestrator/svm-idl/registry.js +15 -14
- package/src/lib/orchestrator/svm-idl/resolve.js +5 -2
- package/src/lib/progress-hook/progress-hook.d.ts +28 -0
- package/src/lib/progress-hook/progress-hook.js +545 -131
- package/src/lib/progress-hook/progress-hook.types.d.ts +76 -29
- package/src/lib/progress-hook/progress-hook.types.js +74 -35
- package/src/lib/push-client/push-client.d.ts +7 -0
- package/src/lib/push-client/push-client.js +18 -1
- package/src/lib/universal-tx-detector/__tests__/fixtures/builders.d.ts +32 -0
- package/src/lib/universal-tx-detector/__tests__/fixtures/builders.js +65 -0
- package/src/lib/universal-tx-detector/__tests__/fixtures/scenarios.d.ts +72 -0
- package/src/lib/universal-tx-detector/__tests__/fixtures/scenarios.js +669 -0
- package/src/lib/universal-tx-detector/auto-detect.d.ts +37 -0
- package/src/lib/universal-tx-detector/auto-detect.js +112 -0
- package/src/lib/universal-tx-detector/cascade.d.ts +64 -0
- package/src/lib/universal-tx-detector/cascade.js +254 -0
- package/src/lib/universal-tx-detector/child-inbounds.d.ts +58 -0
- package/src/lib/universal-tx-detector/child-inbounds.js +145 -0
- package/src/lib/universal-tx-detector/classify.d.ts +33 -0
- package/src/lib/universal-tx-detector/classify.js +228 -0
- package/src/lib/universal-tx-detector/detector-svm.d.ts +46 -0
- package/src/lib/universal-tx-detector/detector-svm.js +324 -0
- package/src/lib/universal-tx-detector/detector.d.ts +13 -0
- package/src/lib/universal-tx-detector/detector.js +233 -0
- package/src/lib/universal-tx-detector/events.d.ts +765 -0
- package/src/lib/universal-tx-detector/events.js +241 -0
- package/src/lib/universal-tx-detector/index.d.ts +18 -0
- package/src/lib/universal-tx-detector/index.js +36 -0
- package/src/lib/universal-tx-detector/svm-events.d.ts +41 -0
- package/src/lib/universal-tx-detector/svm-events.js +68 -0
- package/src/lib/universal-tx-detector/types.d.ts +100 -0
- package/src/lib/universal-tx-detector/types.js +3 -0
- package/src/lib/utils.d.ts +30 -13
- package/src/lib/utils.js +47 -21
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { KnownEventName } from './events';
|
|
2
|
+
import type { DecodedIdentifiers, MatchingLog, TxTypeName, UniversalTxKind } from './types';
|
|
3
|
+
export interface ClassifyResult {
|
|
4
|
+
kind: UniversalTxKind;
|
|
5
|
+
decoded: DecodedIdentifiers;
|
|
6
|
+
matchingLogs: MatchingLog[];
|
|
7
|
+
emitters: `0x${string}`[];
|
|
8
|
+
notes: string[];
|
|
9
|
+
/** The log that determined the kind (for downstream enrichment). */
|
|
10
|
+
primaryLog?: MatchingLog;
|
|
11
|
+
}
|
|
12
|
+
export interface DetectionEntry {
|
|
13
|
+
kind: UniversalTxKind;
|
|
14
|
+
decoded: DecodedIdentifiers;
|
|
15
|
+
log: MatchingLog;
|
|
16
|
+
}
|
|
17
|
+
export declare function classify(parsedLogs: MatchingLog[]): ClassifyResult;
|
|
18
|
+
/**
|
|
19
|
+
* Per-log classification. Each matching universal-tx log yields one detection
|
|
20
|
+
* entry — so a receipt carrying `UniversalTxFinalized` + a CEA-originated
|
|
21
|
+
* `UniversalTx(fromCEA=true)` produces two entries.
|
|
22
|
+
*
|
|
23
|
+
* Unknown event names are dropped (they wouldn't have reached this stage
|
|
24
|
+
* anyway — parseEventLogs only emits logs that matched the ABI).
|
|
25
|
+
*/
|
|
26
|
+
export declare function classifyAll(logs: MatchingLog[]): DetectionEntry[];
|
|
27
|
+
export declare const __internal: {
|
|
28
|
+
TX_TYPE_NAMES: Record<number, TxTypeName>;
|
|
29
|
+
CLASSIFICATION_PRIORITY: {
|
|
30
|
+
eventName: KnownEventName;
|
|
31
|
+
kind: UniversalTxKind | ((args: Record<string, unknown>) => UniversalTxKind);
|
|
32
|
+
}[];
|
|
33
|
+
};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.__internal = void 0;
|
|
4
|
+
exports.classify = classify;
|
|
5
|
+
exports.classifyAll = classifyAll;
|
|
6
|
+
/**
|
|
7
|
+
* TX_TYPE enum from push-chain-gateway-contracts/src/libraries/Types.sol:5-22.
|
|
8
|
+
*/
|
|
9
|
+
const TX_TYPE_NAMES = {
|
|
10
|
+
0: 'GAS',
|
|
11
|
+
1: 'GAS_AND_PAYLOAD',
|
|
12
|
+
2: 'FUNDS',
|
|
13
|
+
3: 'FUNDS_AND_PAYLOAD',
|
|
14
|
+
4: 'RESCUE_FUNDS',
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Priority order: the first match wins. Mirrors the plan doc.
|
|
18
|
+
*/
|
|
19
|
+
const CLASSIFICATION_PRIORITY = [
|
|
20
|
+
{ eventName: 'UniversalTxFinalized', kind: 'OUTBOUND_FINALIZED' },
|
|
21
|
+
{ eventName: 'UniversalTxReverted', kind: 'OUTBOUND_REVERTED' },
|
|
22
|
+
{ eventName: 'UniversalTxOutbound', kind: 'OUTBOUND_INITIATED' },
|
|
23
|
+
{ eventName: 'RevertUniversalTx', kind: 'INBOUND_REVERTED' },
|
|
24
|
+
{
|
|
25
|
+
eventName: 'UniversalTx',
|
|
26
|
+
kind: (args) => args['fromCEA'] === true ? 'INBOUND_FROM_CEA' : 'INBOUND_FROM_EOA',
|
|
27
|
+
},
|
|
28
|
+
{ eventName: 'UniversalTxExecuted', kind: 'EXECUTED_ON_DEST' },
|
|
29
|
+
{ eventName: 'FundsRescued', kind: 'RESCUED_FUNDS' },
|
|
30
|
+
];
|
|
31
|
+
function classify(parsedLogs) {
|
|
32
|
+
const emitters = Array.from(new Set(parsedLogs.map((l) => l.address)));
|
|
33
|
+
if (parsedLogs.length === 0) {
|
|
34
|
+
return {
|
|
35
|
+
kind: 'UNKNOWN',
|
|
36
|
+
decoded: {},
|
|
37
|
+
matchingLogs: [],
|
|
38
|
+
emitters: [],
|
|
39
|
+
notes: ['no universal-tx events decoded'],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
for (const { eventName, kind } of CLASSIFICATION_PRIORITY) {
|
|
43
|
+
const hit = parsedLogs.find((l) => l.eventName === eventName);
|
|
44
|
+
if (!hit)
|
|
45
|
+
continue;
|
|
46
|
+
const resolvedKind = typeof kind === 'function' ? kind(hit.args) : kind;
|
|
47
|
+
const decoded = buildDecoded(resolvedKind, hit, parsedLogs);
|
|
48
|
+
return {
|
|
49
|
+
kind: resolvedKind,
|
|
50
|
+
decoded,
|
|
51
|
+
matchingLogs: parsedLogs,
|
|
52
|
+
emitters,
|
|
53
|
+
notes: [],
|
|
54
|
+
primaryLog: hit,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
kind: 'UNKNOWN',
|
|
59
|
+
decoded: {},
|
|
60
|
+
matchingLogs: parsedLogs,
|
|
61
|
+
emitters,
|
|
62
|
+
notes: ['no known universal-tx event among decoded logs'],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// ── Identifier extraction ─────────────────────────────────────────────
|
|
66
|
+
function buildDecoded(kind, primary, allLogs) {
|
|
67
|
+
const out = {};
|
|
68
|
+
const a = primary.args;
|
|
69
|
+
assignHex(out, 'subTxId', a['subTxId']);
|
|
70
|
+
assignHex(out, 'universalTxId', a['universalTxId']);
|
|
71
|
+
switch (primary.eventName) {
|
|
72
|
+
case 'UniversalTx':
|
|
73
|
+
out.sender = asAddress(a['sender']);
|
|
74
|
+
out.recipient = asAddress(a['recipient']);
|
|
75
|
+
out.token = asAddress(a['token']);
|
|
76
|
+
out.amount = asBigint(a['amount']);
|
|
77
|
+
setPayload(out, a['payload']);
|
|
78
|
+
out.revertRecipient = asAddress(a['revertRecipient']);
|
|
79
|
+
setTxType(out, a['txType']);
|
|
80
|
+
out.fromCEA = Boolean(a['fromCEA']);
|
|
81
|
+
break;
|
|
82
|
+
case 'UniversalTxExecuted':
|
|
83
|
+
out.pushAccount = asAddress(a['pushAccount']);
|
|
84
|
+
out.token = asAddress(a['token']);
|
|
85
|
+
out.amount = asBigint(a['amount']);
|
|
86
|
+
setPayload(out, a['data']);
|
|
87
|
+
break;
|
|
88
|
+
case 'RevertUniversalTx':
|
|
89
|
+
out.recipient = asAddress(a['to']);
|
|
90
|
+
out.token = asAddress(a['token']);
|
|
91
|
+
out.amount = asBigint(a['amount']);
|
|
92
|
+
setRevertInstruction(out, a['revertInstruction']);
|
|
93
|
+
break;
|
|
94
|
+
case 'FundsRescued':
|
|
95
|
+
out.token = asAddress(a['token']);
|
|
96
|
+
out.amount = asBigint(a['amount']);
|
|
97
|
+
setRevertInstruction(out, a['revertInstruction']);
|
|
98
|
+
break;
|
|
99
|
+
case 'UniversalTxFinalized':
|
|
100
|
+
out.pushAccount = asAddress(a['pushAccount']);
|
|
101
|
+
out.recipient = asAddress(a['recipient']);
|
|
102
|
+
out.token = asAddress(a['token']);
|
|
103
|
+
out.amount = asBigint(a['amount']);
|
|
104
|
+
setPayload(out, a['data']);
|
|
105
|
+
break;
|
|
106
|
+
case 'UniversalTxReverted':
|
|
107
|
+
out.token = asAddress(a['token']);
|
|
108
|
+
out.amount = asBigint(a['amount']);
|
|
109
|
+
setRevertInstruction(out, a['revertInstruction']);
|
|
110
|
+
break;
|
|
111
|
+
case 'UniversalTxOutbound':
|
|
112
|
+
out.sender = asAddress(a['sender']);
|
|
113
|
+
out.token = asAddress(a['token']);
|
|
114
|
+
out.amount = asBigint(a['amount']);
|
|
115
|
+
if (typeof a['recipient'] === 'string') {
|
|
116
|
+
out.recipient = a['recipient'];
|
|
117
|
+
}
|
|
118
|
+
setPayload(out, a['payload']);
|
|
119
|
+
if (typeof a['chainNamespace'] === 'string') {
|
|
120
|
+
out.destinationChainNamespace = a['chainNamespace'];
|
|
121
|
+
}
|
|
122
|
+
out.gasFee = asBigint(a['gasFee']);
|
|
123
|
+
out.gasLimit = asBigint(a['gasLimit']);
|
|
124
|
+
out.protocolFee = asBigint(a['protocolFee']);
|
|
125
|
+
out.revertRecipient = asAddress(a['revertRecipient']);
|
|
126
|
+
setTxType(out, a['txType']);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
// Enrichment: OUTBOUND_FINALIZED receipts usually also carry a UniversalTxExecuted
|
|
130
|
+
// fired by the CEA during the inner call. Use it to surface extra fields when
|
|
131
|
+
// the primary log was sparse.
|
|
132
|
+
if (kind === 'OUTBOUND_FINALIZED') {
|
|
133
|
+
const executed = allLogs.find((l) => l.eventName === 'UniversalTxExecuted');
|
|
134
|
+
if (executed && !out.pushAccount) {
|
|
135
|
+
out.pushAccount = asAddress(executed.args['pushAccount']);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
// ── Type coercers ─────────────────────────────────────────────────────
|
|
141
|
+
function asAddress(v) {
|
|
142
|
+
return typeof v === 'string' && v.startsWith('0x')
|
|
143
|
+
? v
|
|
144
|
+
: undefined;
|
|
145
|
+
}
|
|
146
|
+
function asBigint(v) {
|
|
147
|
+
if (typeof v === 'bigint')
|
|
148
|
+
return v;
|
|
149
|
+
if (typeof v === 'number')
|
|
150
|
+
return BigInt(v);
|
|
151
|
+
if (typeof v === 'string') {
|
|
152
|
+
try {
|
|
153
|
+
return BigInt(v);
|
|
154
|
+
}
|
|
155
|
+
catch (_a) {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
function assignHex(out, key, v) {
|
|
162
|
+
if (typeof v === 'string' && v.startsWith('0x')) {
|
|
163
|
+
out[key] = v;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function setTxType(out, raw) {
|
|
167
|
+
var _a;
|
|
168
|
+
const n = typeof raw === 'number'
|
|
169
|
+
? raw
|
|
170
|
+
: typeof raw === 'bigint'
|
|
171
|
+
? Number(raw)
|
|
172
|
+
: undefined;
|
|
173
|
+
if (n === undefined)
|
|
174
|
+
return;
|
|
175
|
+
out.txType = n;
|
|
176
|
+
out.txTypeName = (_a = TX_TYPE_NAMES[n]) !== null && _a !== void 0 ? _a : 'UNKNOWN';
|
|
177
|
+
}
|
|
178
|
+
function setPayload(out, raw) {
|
|
179
|
+
if (typeof raw !== 'string' || !raw.startsWith('0x'))
|
|
180
|
+
return;
|
|
181
|
+
const hex = raw;
|
|
182
|
+
// byte-length = (hex.length - 2) / 2
|
|
183
|
+
out.payloadLength = Math.max(0, (hex.length - 2) / 2);
|
|
184
|
+
out.payloadPreview = hex.slice(0, 2 + 64); // 32 bytes hex
|
|
185
|
+
}
|
|
186
|
+
function setRevertInstruction(out, raw) {
|
|
187
|
+
if (!raw)
|
|
188
|
+
return;
|
|
189
|
+
// viem decodes a (address,bytes) tuple as either an object with named keys
|
|
190
|
+
// (when ABI has components with names) or a positional array.
|
|
191
|
+
if (Array.isArray(raw)) {
|
|
192
|
+
const [recipient, msg] = raw;
|
|
193
|
+
out.revertRecipient = asAddress(recipient);
|
|
194
|
+
if (typeof msg === 'string' && msg.startsWith('0x')) {
|
|
195
|
+
out.revertMsgPreview = msg.slice(0, 2 + 64);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else if (typeof raw === 'object') {
|
|
199
|
+
const obj = raw;
|
|
200
|
+
out.revertRecipient = asAddress(obj['revertRecipient']);
|
|
201
|
+
const msg = obj['revertMsg'];
|
|
202
|
+
if (typeof msg === 'string' && msg.startsWith('0x')) {
|
|
203
|
+
out.revertMsgPreview = msg.slice(0, 2 + 64);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Per-log classification. Each matching universal-tx log yields one detection
|
|
209
|
+
* entry — so a receipt carrying `UniversalTxFinalized` + a CEA-originated
|
|
210
|
+
* `UniversalTx(fromCEA=true)` produces two entries.
|
|
211
|
+
*
|
|
212
|
+
* Unknown event names are dropped (they wouldn't have reached this stage
|
|
213
|
+
* anyway — parseEventLogs only emits logs that matched the ABI).
|
|
214
|
+
*/
|
|
215
|
+
function classifyAll(logs) {
|
|
216
|
+
const kindByEventName = new Map(CLASSIFICATION_PRIORITY.map(({ eventName, kind }) => [eventName, kind]));
|
|
217
|
+
const out = [];
|
|
218
|
+
for (const log of logs) {
|
|
219
|
+
const resolver = kindByEventName.get(log.eventName);
|
|
220
|
+
if (!resolver)
|
|
221
|
+
continue;
|
|
222
|
+
const kind = typeof resolver === 'function' ? resolver(log.args) : resolver;
|
|
223
|
+
out.push({ kind, decoded: buildDecoded(kind, log, logs), log });
|
|
224
|
+
}
|
|
225
|
+
return out;
|
|
226
|
+
}
|
|
227
|
+
exports.__internal = { TX_TYPE_NAMES, CLASSIFICATION_PRIORITY };
|
|
228
|
+
//# sourceMappingURL=classify.js.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Connection } from '@solana/web3.js';
|
|
2
|
+
import { CHAIN } from '../constants/enums';
|
|
3
|
+
import type { DetectUniversalTxOptions, UniversalTxDetection } from './types';
|
|
4
|
+
export interface DetectUniversalTxSvmOptions extends DetectUniversalTxOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Injected Solana connection for tests. In production, the detector
|
|
7
|
+
* constructs a Connection from CHAIN_INFO.defaultRPC (or opts.rpcUrls).
|
|
8
|
+
*/
|
|
9
|
+
connection?: Connection;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* SVM detector entry. Accepts a base58 Solana signature and returns the
|
|
13
|
+
* same UniversalTxDetection shape as the EVM detector so every downstream
|
|
14
|
+
* consumer (classify, cascade, child-inbounds, inbound-tracker) can treat
|
|
15
|
+
* EVM and SVM results uniformly.
|
|
16
|
+
*/
|
|
17
|
+
export declare function detectUniversalTxSvm(signature: string, chain: CHAIN, opts?: DetectUniversalTxSvmOptions): Promise<UniversalTxDetection>;
|
|
18
|
+
/**
|
|
19
|
+
* Convert an Anchor-decoded event struct into the shape classify.ts expects.
|
|
20
|
+
*
|
|
21
|
+
* - snake_case → camelCase for known keys (subTxId, universalTxId, etc.)
|
|
22
|
+
* - PublicKey → `0x${hex}` 32-byte string
|
|
23
|
+
* - Buffer/Uint8Array fixed arrays → `0x${hex}`
|
|
24
|
+
* - BN/bigint numerics → bigint
|
|
25
|
+
* - Anchor enum objects for tx_type → numeric variant index
|
|
26
|
+
* - Event-specific aliases (UniversalTxFinalized.payload → data,
|
|
27
|
+
* RevertUniversalTx.revertRecipient → to) so downstream switch arms find
|
|
28
|
+
* the expected keys.
|
|
29
|
+
*/
|
|
30
|
+
declare function normalizeSvmEventArgs(eventName: string, raw: unknown): Record<string, unknown>;
|
|
31
|
+
declare function extractTxTypeIndex(v: Record<string, unknown>): number | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* Accept either a base58 Solana signature (native form) or a 0x-prefixed
|
|
34
|
+
* 128-char hex of the 64-byte signature bytes (the form used by cosmos
|
|
35
|
+
* keeper storage and by the cascade walker when it hands off an
|
|
36
|
+
* `observedTx.txHash`). Return the base58 form the RPC expects.
|
|
37
|
+
*/
|
|
38
|
+
declare function normalizeSolanaSignature(input: string): string;
|
|
39
|
+
declare function solanaSigToHex(base58Sig: string): `0x${string}`;
|
|
40
|
+
export declare const __svmInternal: {
|
|
41
|
+
normalizeSvmEventArgs: typeof normalizeSvmEventArgs;
|
|
42
|
+
extractTxTypeIndex: typeof extractTxTypeIndex;
|
|
43
|
+
normalizeSolanaSignature: typeof normalizeSolanaSignature;
|
|
44
|
+
solanaSigToHex: typeof solanaSigToHex;
|
|
45
|
+
};
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.__svmInternal = void 0;
|
|
4
|
+
exports.detectUniversalTxSvm = detectUniversalTxSvm;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
/**
|
|
7
|
+
* detectUniversalTxSvm — SVM counterpart to the EVM detector.
|
|
8
|
+
*
|
|
9
|
+
* Fetches the tx via Solana RPC, walks `meta.logMessages`, decodes each
|
|
10
|
+
* `Program data:` entry with Anchor's BorshEventCoder against the gateway
|
|
11
|
+
* IDL, normalizes field names to match classify.ts's EVM-derived shape,
|
|
12
|
+
* and hands the result to the same classifier used by the EVM branch.
|
|
13
|
+
*
|
|
14
|
+
* Behavior deliberately mirrors detector.ts: same UniversalTxDetection
|
|
15
|
+
* return, same universalTxId derivation strategy (primary-log fallback
|
|
16
|
+
* via deriveChildUniversalTxId on source-chain inbounds).
|
|
17
|
+
*
|
|
18
|
+
* Open contract with the push-chain cosmos keeper (documented in the
|
|
19
|
+
* plan): the keeper must derive SVM inbound universalTxIds using the
|
|
20
|
+
* Solana base58 signature verbatim inside `${caip}:${sig}:${logIndex}`.
|
|
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.
|
|
24
|
+
*/
|
|
25
|
+
const anchor_1 = require("@coral-xyz/anchor");
|
|
26
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
27
|
+
const viem_1 = require("viem");
|
|
28
|
+
const bs58_1 = require("../internal/bs58");
|
|
29
|
+
const universalGatewayV0_json_1 = tslib_1.__importDefault(require("../constants/abi/universalGatewayV0.json"));
|
|
30
|
+
const chain_1 = require("../constants/chain");
|
|
31
|
+
const classify_1 = require("./classify");
|
|
32
|
+
const svm_events_1 = require("./svm-events");
|
|
33
|
+
const INBOUND_KINDS = new Set([
|
|
34
|
+
'INBOUND_FROM_EOA',
|
|
35
|
+
'INBOUND_FROM_CEA',
|
|
36
|
+
]);
|
|
37
|
+
/**
|
|
38
|
+
* SVM detector entry. Accepts a base58 Solana signature and returns the
|
|
39
|
+
* same UniversalTxDetection shape as the EVM detector so every downstream
|
|
40
|
+
* consumer (classify, cascade, child-inbounds, inbound-tracker) can treat
|
|
41
|
+
* EVM and SVM results uniformly.
|
|
42
|
+
*/
|
|
43
|
+
function detectUniversalTxSvm(signature_1, chain_2) {
|
|
44
|
+
return tslib_1.__awaiter(this, arguments, void 0, function* (signature, chain, opts = {}) {
|
|
45
|
+
var _a;
|
|
46
|
+
const notes = [];
|
|
47
|
+
const chainInfo = chain_1.CHAIN_INFO[chain];
|
|
48
|
+
const castHash = signature; // intentional — we widen downstream
|
|
49
|
+
if (!chainInfo) {
|
|
50
|
+
throw new Error(`detectUniversalTxSvm: unknown chain ${chain}`);
|
|
51
|
+
}
|
|
52
|
+
const connection = yield resolveConnection(chain, opts);
|
|
53
|
+
// The cascade (and cosmos) stores Solana signatures as 0x-prefixed hex of
|
|
54
|
+
// the 64-byte signature bytes. Solana RPC needs base58 — normalize here
|
|
55
|
+
// so callers can pass either form interchangeably.
|
|
56
|
+
const rpcSig = normalizeSolanaSignature(signature);
|
|
57
|
+
let tx;
|
|
58
|
+
try {
|
|
59
|
+
tx = yield connection.getTransaction(rpcSig, {
|
|
60
|
+
commitment: 'confirmed',
|
|
61
|
+
maxSupportedTransactionVersion: 0,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
notes.push(`svm tx fetch failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
66
|
+
return emptyDetection(castHash, chain, notes);
|
|
67
|
+
}
|
|
68
|
+
if (!tx || !tx.meta) {
|
|
69
|
+
notes.push('no svm tx returned by RPC');
|
|
70
|
+
return emptyDetection(castHash, chain, notes);
|
|
71
|
+
}
|
|
72
|
+
// Reverted with no logs — same early-out as EVM path.
|
|
73
|
+
const err = tx.meta.err;
|
|
74
|
+
const logMessages = ((_a = tx.meta.logMessages) !== null && _a !== void 0 ? _a : []);
|
|
75
|
+
if (err && logMessages.length === 0) {
|
|
76
|
+
notes.push('svm tx reverted with no logs');
|
|
77
|
+
return emptyDetection(castHash, chain, notes);
|
|
78
|
+
}
|
|
79
|
+
// Emitter for SVM is the gateway program id from CHAIN_INFO.
|
|
80
|
+
// Encoded as 0x-prefixed 32-byte hex so MatchingLog.address stays
|
|
81
|
+
// assignable to the `0x${string}` type without widening.
|
|
82
|
+
const emitterBase58 = chainInfo.lockerContract || '';
|
|
83
|
+
let emitter = '0x';
|
|
84
|
+
if (emitterBase58 && emitterBase58 !== 'TBD') {
|
|
85
|
+
try {
|
|
86
|
+
emitter = (0, viem_1.bytesToHex)(new web3_js_1.PublicKey(emitterBase58).toBytes());
|
|
87
|
+
}
|
|
88
|
+
catch (_b) {
|
|
89
|
+
emitter = '0x';
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const eventCoder = new anchor_1.BorshEventCoder(universalGatewayV0_json_1.default);
|
|
93
|
+
const matchingLogs = [];
|
|
94
|
+
// Walk logMessages for `Program data:` entries. Each matched event gets
|
|
95
|
+
// assigned an ordinal logIndex — its position among matching events.
|
|
96
|
+
// This is the deterministic integer the cosmos keeper is expected to use
|
|
97
|
+
// when it derives an inbound universalTxId for SVM sources.
|
|
98
|
+
let ordinal = 0;
|
|
99
|
+
for (const rawLog of logMessages) {
|
|
100
|
+
if (typeof rawLog !== 'string')
|
|
101
|
+
continue;
|
|
102
|
+
const prefix = 'Program data: ';
|
|
103
|
+
if (!rawLog.startsWith(prefix))
|
|
104
|
+
continue;
|
|
105
|
+
const base64Data = rawLog.slice(prefix.length).trim();
|
|
106
|
+
// Fast discriminator check — skip non-universal events without running
|
|
107
|
+
// the full Borsh decode.
|
|
108
|
+
let bytes;
|
|
109
|
+
try {
|
|
110
|
+
bytes = new Uint8Array(Buffer.from(base64Data, 'base64'));
|
|
111
|
+
}
|
|
112
|
+
catch (_c) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const disc = (0, svm_events_1.discriminatorHex)(bytes);
|
|
116
|
+
const eventName = svm_events_1.SVM_EVENT_DISCRIMINATORS[disc];
|
|
117
|
+
if (!eventName)
|
|
118
|
+
continue;
|
|
119
|
+
// BorshEventCoder can throw when the on-chain layout drifts from the
|
|
120
|
+
// bundled IDL (seen with older UniversalTx emissions). Isolate the
|
|
121
|
+
// failure to this event so the rest of the matching logs still surface.
|
|
122
|
+
let decoded = null;
|
|
123
|
+
try {
|
|
124
|
+
decoded = eventCoder.decode(base64Data);
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
notes.push(`svm: borsh decode threw for ${eventName}: ${e instanceof Error ? e.message : String(e)}`);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (!decoded || decoded.name !== eventName) {
|
|
131
|
+
notes.push(`svm: discriminator matched ${eventName} but borsh decode failed`);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const args = normalizeSvmEventArgs(eventName, decoded.data);
|
|
135
|
+
matchingLogs.push({
|
|
136
|
+
eventName,
|
|
137
|
+
address: emitter,
|
|
138
|
+
logIndex: ordinal,
|
|
139
|
+
args,
|
|
140
|
+
});
|
|
141
|
+
ordinal += 1;
|
|
142
|
+
}
|
|
143
|
+
if (matchingLogs.length === 0) {
|
|
144
|
+
notes.push('svm tx has no decoded universal-tx events');
|
|
145
|
+
return emptyDetection(castHash, chain, notes);
|
|
146
|
+
}
|
|
147
|
+
const classified = (0, classify_1.classify)(matchingLogs);
|
|
148
|
+
notes.push(...classified.notes);
|
|
149
|
+
const decoded = Object.assign({}, classified.decoded);
|
|
150
|
+
// Inbound fallback for universalTxId: same formula the EVM branch uses,
|
|
151
|
+
// but the raw signature flows through untouched (no 0x-prefix rewrite,
|
|
152
|
+
// see deriveSvmChildUniversalTxId in child-inbounds.ts).
|
|
153
|
+
if (!decoded.universalTxId) {
|
|
154
|
+
const caip = chain;
|
|
155
|
+
if (INBOUND_KINDS.has(classified.kind) &&
|
|
156
|
+
classified.primaryLog &&
|
|
157
|
+
classified.primaryLog.logIndex >= 0) {
|
|
158
|
+
// Use the hex form (matches cosmos keeper's on-chain storage).
|
|
159
|
+
const hexSig = solanaSigToHex(rpcSig);
|
|
160
|
+
const input = `${caip}:${hexSig}:${classified.primaryLog.logIndex}`;
|
|
161
|
+
decoded.universalTxId = (0, viem_1.sha256)((0, viem_1.toBytes)(input));
|
|
162
|
+
notes.push(`universalTxId derived via sha256("${caip}:<hexSig>:${classified.primaryLog.logIndex}") — svm`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const detections = (0, classify_1.classifyAll)(classified.matchingLogs);
|
|
166
|
+
return {
|
|
167
|
+
txHash: castHash,
|
|
168
|
+
chain,
|
|
169
|
+
kind: classified.kind,
|
|
170
|
+
emitters: classified.emitters,
|
|
171
|
+
decoded,
|
|
172
|
+
matchingLogs: classified.matchingLogs,
|
|
173
|
+
detections,
|
|
174
|
+
// SVM detector doesn't do the cosmos cross-reference itself; upstream
|
|
175
|
+
// (orchestrator / cascade) already does its own lookup when needed.
|
|
176
|
+
notes,
|
|
177
|
+
};
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
// ── Helpers ───────────────────────────────────────────────────────────
|
|
181
|
+
function resolveConnection(chain, opts) {
|
|
182
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
183
|
+
var _a, _b;
|
|
184
|
+
if (opts.connection)
|
|
185
|
+
return opts.connection;
|
|
186
|
+
const info = chain_1.CHAIN_INFO[chain];
|
|
187
|
+
const rpcUrls = ((_a = opts.rpcUrls) === null || _a === void 0 ? void 0 : _a[chain]) && opts.rpcUrls[chain].length > 0
|
|
188
|
+
? opts.rpcUrls[chain]
|
|
189
|
+
: ((_b = info === null || info === void 0 ? void 0 : info.defaultRPC) !== null && _b !== void 0 ? _b : []).filter((r) => r && r.length > 0);
|
|
190
|
+
if (rpcUrls.length === 0) {
|
|
191
|
+
throw new Error(`detectUniversalTxSvm: no RPC URL configured for ${chain}`);
|
|
192
|
+
}
|
|
193
|
+
// Lazy import to avoid a top-level dependency cycle through @solana/web3.js
|
|
194
|
+
// for consumers of the EVM-only path.
|
|
195
|
+
const { Connection } = yield Promise.resolve().then(() => tslib_1.__importStar(require('@solana/web3.js')));
|
|
196
|
+
return new Connection(rpcUrls[0], 'confirmed');
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
function emptyDetection(txHash, chain, notes) {
|
|
200
|
+
return {
|
|
201
|
+
txHash,
|
|
202
|
+
chain,
|
|
203
|
+
kind: 'UNKNOWN',
|
|
204
|
+
emitters: [],
|
|
205
|
+
decoded: {},
|
|
206
|
+
matchingLogs: [],
|
|
207
|
+
detections: [],
|
|
208
|
+
notes,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Convert an Anchor-decoded event struct into the shape classify.ts expects.
|
|
213
|
+
*
|
|
214
|
+
* - snake_case → camelCase for known keys (subTxId, universalTxId, etc.)
|
|
215
|
+
* - PublicKey → `0x${hex}` 32-byte string
|
|
216
|
+
* - Buffer/Uint8Array fixed arrays → `0x${hex}`
|
|
217
|
+
* - BN/bigint numerics → bigint
|
|
218
|
+
* - Anchor enum objects for tx_type → numeric variant index
|
|
219
|
+
* - Event-specific aliases (UniversalTxFinalized.payload → data,
|
|
220
|
+
* RevertUniversalTx.revertRecipient → to) so downstream switch arms find
|
|
221
|
+
* the expected keys.
|
|
222
|
+
*/
|
|
223
|
+
function normalizeSvmEventArgs(eventName, raw) {
|
|
224
|
+
var _a, _b;
|
|
225
|
+
const src = raw;
|
|
226
|
+
const out = {};
|
|
227
|
+
for (const [srcKey, value] of Object.entries(src)) {
|
|
228
|
+
const renamed = (_a = svm_events_1.SVM_FIELD_RENAMES[srcKey]) !== null && _a !== void 0 ? _a : srcKey;
|
|
229
|
+
out[renamed] = coerceArgValue(srcKey, renamed, value);
|
|
230
|
+
}
|
|
231
|
+
const aliases = (_b = svm_events_1.SVM_EVENT_FIELD_ALIASES[eventName]) !== null && _b !== void 0 ? _b : [];
|
|
232
|
+
for (const { from, to } of aliases) {
|
|
233
|
+
if (out[to] === undefined && out[from] !== undefined) {
|
|
234
|
+
out[to] = out[from];
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return out;
|
|
238
|
+
}
|
|
239
|
+
function coerceArgValue(srcKey, renamedKey, value) {
|
|
240
|
+
var _a;
|
|
241
|
+
// Anchor returns PublicKey instances for `pubkey` types. Normalize to
|
|
242
|
+
// `0x${hex}` so asAddress() in classify.ts accepts them.
|
|
243
|
+
if (value && typeof value === 'object' && value instanceof web3_js_1.PublicKey) {
|
|
244
|
+
return (0, viem_1.bytesToHex)(value.toBytes());
|
|
245
|
+
}
|
|
246
|
+
// Fixed-size byte arrays (u8; N) come through as Buffer/Uint8Array.
|
|
247
|
+
if (value &&
|
|
248
|
+
typeof value === 'object' &&
|
|
249
|
+
(value instanceof Uint8Array ||
|
|
250
|
+
(globalThis.Buffer && value instanceof globalThis.Buffer))) {
|
|
251
|
+
return (0, viem_1.bytesToHex)(new Uint8Array(value));
|
|
252
|
+
}
|
|
253
|
+
// Some @coral-xyz/anchor versions return `array:[u8,N]` fields as plain
|
|
254
|
+
// number arrays rather than Buffers. Treat a homogeneous byte-array
|
|
255
|
+
// exactly like a Uint8Array here.
|
|
256
|
+
if (Array.isArray(value) &&
|
|
257
|
+
value.length > 0 &&
|
|
258
|
+
value.every((v) => typeof v === 'number' && v >= 0 && v <= 255)) {
|
|
259
|
+
return (0, viem_1.bytesToHex)(new Uint8Array(value));
|
|
260
|
+
}
|
|
261
|
+
// BN from anchor — has toString().
|
|
262
|
+
if (value && typeof value === 'object' && typeof value.toString === 'function') {
|
|
263
|
+
const proto = Object.getPrototypeOf(value);
|
|
264
|
+
const ctorName = (_a = proto === null || proto === void 0 ? void 0 : proto.constructor) === null || _a === void 0 ? void 0 : _a.name;
|
|
265
|
+
if (ctorName === 'BN') {
|
|
266
|
+
try {
|
|
267
|
+
return BigInt(value.toString(10));
|
|
268
|
+
}
|
|
269
|
+
catch (_b) {
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Anchor enum → { [variantName]: {} }. tx_type must be surfaced as a
|
|
275
|
+
// numeric index to match classify.ts's setTxType expectations.
|
|
276
|
+
if (renamedKey === 'txType' && value && typeof value === 'object' && !Array.isArray(value)) {
|
|
277
|
+
return extractTxTypeIndex(value);
|
|
278
|
+
}
|
|
279
|
+
return value;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Resolve a Borsh-decoded Anchor enum value into its numeric variant index.
|
|
283
|
+
*
|
|
284
|
+
* The SVM IDL `TxType` enum is declared in the same order as the EVM solidity
|
|
285
|
+
* enum (GAS, GAS_AND_PAYLOAD, FUNDS, FUNDS_AND_PAYLOAD, RESCUE_FUNDS), so a
|
|
286
|
+
* positional lookup suffices.
|
|
287
|
+
*/
|
|
288
|
+
const TX_TYPE_ORDER = [
|
|
289
|
+
'gas',
|
|
290
|
+
'gasAndPayload',
|
|
291
|
+
'funds',
|
|
292
|
+
'fundsAndPayload',
|
|
293
|
+
'rescueFunds',
|
|
294
|
+
];
|
|
295
|
+
function extractTxTypeIndex(v) {
|
|
296
|
+
const key = Object.keys(v)[0];
|
|
297
|
+
if (!key)
|
|
298
|
+
return undefined;
|
|
299
|
+
const norm = key.toLowerCase();
|
|
300
|
+
const i = TX_TYPE_ORDER.findIndex((k) => k.toLowerCase() === norm);
|
|
301
|
+
return i < 0 ? undefined : i;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Accept either a base58 Solana signature (native form) or a 0x-prefixed
|
|
305
|
+
* 128-char hex of the 64-byte signature bytes (the form used by cosmos
|
|
306
|
+
* keeper storage and by the cascade walker when it hands off an
|
|
307
|
+
* `observedTx.txHash`). Return the base58 form the RPC expects.
|
|
308
|
+
*/
|
|
309
|
+
function normalizeSolanaSignature(input) {
|
|
310
|
+
if (input.startsWith('0x') && input.length === 2 + 128) {
|
|
311
|
+
return bs58_1.bs58.encode((0, viem_1.hexToBytes)(input));
|
|
312
|
+
}
|
|
313
|
+
return input;
|
|
314
|
+
}
|
|
315
|
+
function solanaSigToHex(base58Sig) {
|
|
316
|
+
return (0, viem_1.bytesToHex)(bs58_1.bs58.decode(base58Sig));
|
|
317
|
+
}
|
|
318
|
+
exports.__svmInternal = {
|
|
319
|
+
normalizeSvmEventArgs,
|
|
320
|
+
extractTxTypeIndex,
|
|
321
|
+
normalizeSolanaSignature,
|
|
322
|
+
solanaSigToHex,
|
|
323
|
+
};
|
|
324
|
+
//# sourceMappingURL=detector-svm.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { CHAIN } from '../constants/enums';
|
|
2
|
+
import { computeUniversalTxId } from '../orchestrator/internals/outbound-tracker';
|
|
3
|
+
import type { DetectUniversalTxOptions, UniversalTxDetection } from './types';
|
|
4
|
+
export declare function detectUniversalTx(txHash: `0x${string}`, chain: CHAIN, opts?: DetectUniversalTxOptions): Promise<UniversalTxDetection>;
|
|
5
|
+
export declare const __internalStatusNames: Record<number, string>;
|
|
6
|
+
/**
|
|
7
|
+
* Re-export the push-side legacy helper (keccak-based; retained for
|
|
8
|
+
* backwards-compat with orchestrator paths that already use it). For accurate
|
|
9
|
+
* universalTxId derivation matching the chain module, use
|
|
10
|
+
* {@link deriveChildUniversalTxId} or {@link derivePcUniversalTxId} from
|
|
11
|
+
* child-inbounds.ts.
|
|
12
|
+
*/
|
|
13
|
+
export { computeUniversalTxId };
|