@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.
Files changed (86) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +1 -1
  3. package/src/lib/constants/abi/index.d.ts +2 -0
  4. package/src/lib/constants/abi/index.js +6 -1
  5. package/src/lib/constants/abi/uniswap-v3-router.evm.d.ts +58 -0
  6. package/src/lib/constants/abi/uniswap-v3-router.evm.js +41 -0
  7. package/src/lib/constants/abi/wpc.evm.d.ts +24 -0
  8. package/src/lib/constants/abi/wpc.evm.js +27 -0
  9. package/src/lib/constants/chain.d.ts +1 -0
  10. package/src/lib/constants/chain.js +8 -6
  11. package/src/lib/constants/tokens.js +3 -3
  12. package/src/lib/generated/uexecutor/v2/types.d.ts +14 -0
  13. package/src/lib/generated/uexecutor/v2/types.js +96 -3
  14. package/src/lib/orchestrator/internals/cascade.d.ts +1 -1
  15. package/src/lib/orchestrator/internals/cascade.js +521 -239
  16. package/src/lib/orchestrator/internals/confirmation.js +60 -15
  17. package/src/lib/orchestrator/internals/context.d.ts +23 -0
  18. package/src/lib/orchestrator/internals/context.js +54 -0
  19. package/src/lib/orchestrator/internals/execute-funds-only.js +47 -21
  20. package/src/lib/orchestrator/internals/execute-funds-payload.js +53 -31
  21. package/src/lib/orchestrator/internals/execute-standard.js +137 -26
  22. package/src/lib/orchestrator/internals/gas-calculator.d.ts +5 -2
  23. package/src/lib/orchestrator/internals/gas-calculator.js +59 -11
  24. package/src/lib/orchestrator/internals/gas-usd-sizer.d.ts +53 -0
  25. package/src/lib/orchestrator/internals/gas-usd-sizer.js +165 -0
  26. package/src/lib/orchestrator/internals/helpers.js +2 -0
  27. package/src/lib/orchestrator/internals/inbound-tracker.d.ts +83 -0
  28. package/src/lib/orchestrator/internals/inbound-tracker.js +322 -0
  29. package/src/lib/orchestrator/internals/index.d.ts +3 -1
  30. package/src/lib/orchestrator/internals/index.js +8 -2
  31. package/src/lib/orchestrator/internals/outbound-sync.d.ts +32 -1
  32. package/src/lib/orchestrator/internals/outbound-sync.js +154 -17
  33. package/src/lib/orchestrator/internals/pc-usd-oracle.d.ts +28 -0
  34. package/src/lib/orchestrator/internals/pc-usd-oracle.js +285 -0
  35. package/src/lib/orchestrator/internals/progress-route-hooks.d.ts +23 -0
  36. package/src/lib/orchestrator/internals/progress-route-hooks.js +72 -0
  37. package/src/lib/orchestrator/internals/push-chain-tx.d.ts +27 -1
  38. package/src/lib/orchestrator/internals/push-chain-tx.js +111 -15
  39. package/src/lib/orchestrator/internals/rescue.js +1 -1
  40. package/src/lib/orchestrator/internals/response-builder.d.ts +5 -0
  41. package/src/lib/orchestrator/internals/response-builder.js +242 -66
  42. package/src/lib/orchestrator/internals/route-handlers.js +480 -206
  43. package/src/lib/orchestrator/internals/tx-transformer.js +293 -35
  44. package/src/lib/orchestrator/orchestrator.d.ts +7 -1
  45. package/src/lib/orchestrator/orchestrator.js +108 -56
  46. package/src/lib/orchestrator/orchestrator.types.d.ts +111 -5
  47. package/src/lib/orchestrator/payload-builders.d.ts +16 -0
  48. package/src/lib/orchestrator/payload-builders.js +6 -5
  49. package/src/lib/orchestrator/route-detector.js +20 -8
  50. package/src/lib/orchestrator/svm-idl/normalize-address.d.ts +19 -0
  51. package/src/lib/orchestrator/svm-idl/normalize-address.js +56 -0
  52. package/src/lib/orchestrator/svm-idl/registry.d.ts +13 -4
  53. package/src/lib/orchestrator/svm-idl/registry.js +15 -14
  54. package/src/lib/orchestrator/svm-idl/resolve.js +5 -2
  55. package/src/lib/progress-hook/progress-hook.d.ts +28 -0
  56. package/src/lib/progress-hook/progress-hook.js +545 -131
  57. package/src/lib/progress-hook/progress-hook.types.d.ts +76 -29
  58. package/src/lib/progress-hook/progress-hook.types.js +74 -35
  59. package/src/lib/push-client/push-client.d.ts +7 -0
  60. package/src/lib/push-client/push-client.js +18 -1
  61. package/src/lib/universal-tx-detector/__tests__/fixtures/builders.d.ts +32 -0
  62. package/src/lib/universal-tx-detector/__tests__/fixtures/builders.js +65 -0
  63. package/src/lib/universal-tx-detector/__tests__/fixtures/scenarios.d.ts +72 -0
  64. package/src/lib/universal-tx-detector/__tests__/fixtures/scenarios.js +669 -0
  65. package/src/lib/universal-tx-detector/auto-detect.d.ts +37 -0
  66. package/src/lib/universal-tx-detector/auto-detect.js +112 -0
  67. package/src/lib/universal-tx-detector/cascade.d.ts +64 -0
  68. package/src/lib/universal-tx-detector/cascade.js +254 -0
  69. package/src/lib/universal-tx-detector/child-inbounds.d.ts +58 -0
  70. package/src/lib/universal-tx-detector/child-inbounds.js +145 -0
  71. package/src/lib/universal-tx-detector/classify.d.ts +33 -0
  72. package/src/lib/universal-tx-detector/classify.js +228 -0
  73. package/src/lib/universal-tx-detector/detector-svm.d.ts +46 -0
  74. package/src/lib/universal-tx-detector/detector-svm.js +324 -0
  75. package/src/lib/universal-tx-detector/detector.d.ts +13 -0
  76. package/src/lib/universal-tx-detector/detector.js +233 -0
  77. package/src/lib/universal-tx-detector/events.d.ts +765 -0
  78. package/src/lib/universal-tx-detector/events.js +241 -0
  79. package/src/lib/universal-tx-detector/index.d.ts +18 -0
  80. package/src/lib/universal-tx-detector/index.js +36 -0
  81. package/src/lib/universal-tx-detector/svm-events.d.ts +41 -0
  82. package/src/lib/universal-tx-detector/svm-events.js +68 -0
  83. package/src/lib/universal-tx-detector/types.d.ts +100 -0
  84. package/src/lib/universal-tx-detector/types.js +3 -0
  85. package/src/lib/utils.d.ts +30 -13
  86. 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 };