@pushchain/core 6.0.24 → 6.0.26
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 +150 -0
- package/package.json +1 -1
- package/src/lib/constants/abi/index.d.ts +1 -0
- package/src/lib/constants/abi/index.js +4 -1
- package/src/lib/constants/abi/prc20.evm.d.ts +32 -0
- package/src/lib/constants/abi/prc20.evm.js +20 -0
- package/src/lib/constants/abi/uea-factory.d.ts +31 -0
- package/src/lib/constants/abi/uea-factory.js +19 -0
- package/src/lib/constants/abi/universalCallback.evm.d.ts +561 -0
- package/src/lib/constants/abi/universalCallback.evm.js +247 -0
- package/src/lib/constants/abi/universalGatewayV0.json +1 -1
- package/src/lib/constants/abi/universalReadRegistry.evm.d.ts +247 -0
- package/src/lib/constants/abi/universalReadRegistry.evm.js +38 -0
- package/src/lib/constants/chain.d.ts +12 -0
- package/src/lib/constants/chain.js +24 -2
- package/src/lib/constants/enums.d.ts +7 -0
- package/src/lib/constants/enums.js +11 -0
- package/src/lib/constants/index.d.ts +54 -0
- package/src/lib/constants/index.js +38 -8
- package/src/lib/constants/read-state.d.ts +57 -0
- package/src/lib/constants/read-state.js +63 -0
- package/src/lib/generated/ucallback/v1/index.d.ts +2 -0
- package/src/lib/generated/ucallback/v1/index.js +6 -0
- package/src/lib/generated/ucallback/v1/query.d.ts +47 -0
- package/src/lib/generated/ucallback/v1/query.js +131 -0
- package/src/lib/generated/ucallback/v1/types.d.ts +99 -0
- package/src/lib/generated/ucallback/v1/types.js +366 -0
- package/src/lib/generated/uexecutor/v1/query.d.ts +29 -0
- package/src/lib/generated/uexecutor/v1/query.js +98 -1
- package/src/lib/index.d.ts +9 -0
- package/src/lib/index.js +38 -1
- package/src/lib/orchestrator/internals/cascade.js +5 -2
- package/src/lib/orchestrator/internals/errors.d.ts +6 -0
- package/src/lib/orchestrator/internals/errors.js +13 -1
- package/src/lib/orchestrator/internals/index.d.ts +1 -0
- package/src/lib/orchestrator/internals/index.js +6 -1
- package/src/lib/orchestrator/internals/push-chain-tx.js +81 -61
- package/src/lib/orchestrator/internals/read-state.d.ts +24 -0
- package/src/lib/orchestrator/internals/read-state.js +47 -0
- package/src/lib/orchestrator/internals/route-handlers.js +6 -1
- package/src/lib/orchestrator/internals/svm-helpers.js +24 -0
- package/src/lib/orchestrator/orchestrator.d.ts +26 -0
- package/src/lib/orchestrator/orchestrator.js +22 -0
- package/src/lib/orchestrator/orchestrator.types.d.ts +2 -0
- package/src/lib/orchestrator/route-detector.js +4 -0
- package/src/lib/progress-hook/progress-hook.js +292 -0
- package/src/lib/progress-hook/progress-hook.types.d.ts +35 -0
- package/src/lib/progress-hook/progress-hook.types.js +37 -0
- package/src/lib/push-chain/push-chain.d.ts +71 -0
- package/src/lib/push-chain/push-chain.js +28 -0
- package/src/lib/push-client/push-client.d.ts +42 -2
- package/src/lib/push-client/push-client.js +152 -13
- package/src/lib/read-state/budget.d.ts +16 -0
- package/src/lib/read-state/budget.js +32 -0
- package/src/lib/read-state/destination.d.ts +12 -0
- package/src/lib/read-state/destination.js +51 -0
- package/src/lib/read-state/envelopes/evm.d.ts +24 -0
- package/src/lib/read-state/envelopes/evm.js +147 -0
- package/src/lib/read-state/envelopes/index.d.ts +12 -0
- package/src/lib/read-state/envelopes/index.js +45 -0
- package/src/lib/read-state/envelopes/svm.d.ts +32 -0
- package/src/lib/read-state/envelopes/svm.js +117 -0
- package/src/lib/read-state/envelopes/web2.d.ts +29 -0
- package/src/lib/read-state/envelopes/web2.js +149 -0
- package/src/lib/read-state/errors.d.ts +70 -0
- package/src/lib/read-state/errors.js +116 -0
- package/src/lib/read-state/index.d.ts +13 -0
- package/src/lib/read-state/index.js +38 -0
- package/src/lib/read-state/preflight.d.ts +21 -0
- package/src/lib/read-state/preflight.js +70 -0
- package/src/lib/read-state/read-events.d.ts +27 -0
- package/src/lib/read-state/read-events.js +135 -0
- package/src/lib/read-state/read-executor.d.ts +18 -0
- package/src/lib/read-state/read-executor.js +216 -0
- package/src/lib/read-state/read-params.d.ts +250 -0
- package/src/lib/read-state/read-params.js +164 -0
- package/src/lib/read-state/read-state.types.d.ts +458 -0
- package/src/lib/read-state/read-state.types.js +72 -0
- package/src/lib/read-state/read-tracker.d.ts +55 -0
- package/src/lib/read-state/read-tracker.js +481 -0
- package/src/lib/read-state/registry.d.ts +21 -0
- package/src/lib/read-state/registry.js +53 -0
- package/src/lib/read-state/result-decoder.d.ts +16 -0
- package/src/lib/read-state/result-decoder.js +79 -0
- package/src/lib/read-state/spec-builder.d.ts +47 -0
- package/src/lib/read-state/spec-builder.js +251 -0
- package/src/lib/read-state/svm-account.d.ts +20 -0
- package/src/lib/read-state/svm-account.js +114 -0
- package/src/lib/read-state/svm-pda.d.ts +13 -0
- package/src/lib/read-state/svm-pda.js +147 -0
- package/src/lib/read-state/validate.d.ts +22 -0
- package/src/lib/read-state/validate.js +80 -0
- package/src/lib/universal-tx-detector/child-inbounds.d.ts +3 -1
- package/src/lib/universal-tx-detector/child-inbounds.js +4 -5
- package/src/lib/universal-tx-detector/detector-svm.js +29 -20
- package/src/lib/universal-tx-detector/svm-events.d.ts +17 -5
- package/src/lib/universal-tx-detector/svm-events.js +81 -6
- package/src/lib/vm-client/evm-client.js +27 -10
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deriveIdlPda = deriveIdlPda;
|
|
4
|
+
exports.isProgramSubject = isProgramSubject;
|
|
5
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
6
|
+
const buffer_1 = require("buffer");
|
|
7
|
+
const normalize_address_1 = require("../orchestrator/svm-idl/normalize-address");
|
|
8
|
+
const errors_1 = require("./errors");
|
|
9
|
+
const svm_account_1 = require("./svm-account");
|
|
10
|
+
/** Every instruction account named like `name` that carries PDA seeds. */
|
|
11
|
+
function findSeedTemplates(idl, name) {
|
|
12
|
+
const key = (0, svm_account_1.idlNameKey)(name);
|
|
13
|
+
const out = [];
|
|
14
|
+
const visit = (instruction, accounts) => {
|
|
15
|
+
for (const a of accounts) {
|
|
16
|
+
if (a.accounts)
|
|
17
|
+
visit(instruction, a.accounts); // composite account groups
|
|
18
|
+
else if (a.pda && (0, svm_account_1.idlNameKey)(a.name) === key)
|
|
19
|
+
out.push({ pda: a.pda, instruction });
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
for (const ix of idl.instructions ?? [])
|
|
23
|
+
visit(ix, ix.accounts);
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
function toPublicKey(value, what) {
|
|
27
|
+
if (value instanceof web3_js_1.PublicKey)
|
|
28
|
+
return value;
|
|
29
|
+
try {
|
|
30
|
+
return new web3_js_1.PublicKey(buffer_1.Buffer.from((0, normalize_address_1.toSvmHexAddress)(value).slice(2), 'hex'));
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
throw new errors_1.InvalidReadQueryError(`${what} must be a Solana pubkey (base58, 0x 32-byte hex, or PublicKey): ${e.message}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const INT_WIDTH = {
|
|
37
|
+
u8: [1, false], i8: [1, true], u16: [2, false], i16: [2, true], u32: [4, false], i32: [4, true],
|
|
38
|
+
u64: [8, false], i64: [8, true], u128: [16, false], i128: [16, true],
|
|
39
|
+
};
|
|
40
|
+
function encodeInt(value, type, what) {
|
|
41
|
+
const [bytes, signed] = INT_WIDTH[type];
|
|
42
|
+
let n;
|
|
43
|
+
try {
|
|
44
|
+
n = typeof value === 'bigint' ? value : BigInt(value);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw new errors_1.InvalidReadQueryError(`${what} must be an integer for ${type}`);
|
|
48
|
+
}
|
|
49
|
+
const bits = BigInt(bytes * 8);
|
|
50
|
+
const min = signed ? -(1n << (bits - 1n)) : 0n;
|
|
51
|
+
const max = signed ? (1n << (bits - 1n)) - 1n : (1n << bits) - 1n;
|
|
52
|
+
if (n < min || n > max)
|
|
53
|
+
throw new errors_1.InvalidReadQueryError(`${what} out of ${type} range: ${n}`);
|
|
54
|
+
let u = n < 0n ? (1n << bits) + n : n;
|
|
55
|
+
const out = buffer_1.Buffer.alloc(bytes);
|
|
56
|
+
for (let i = 0; i < bytes; i++) {
|
|
57
|
+
out[i] = Number(u & 0xffn);
|
|
58
|
+
u >>= 8n;
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
function encodeSeed(value, type, what) {
|
|
63
|
+
if (type === 'pubkey')
|
|
64
|
+
return toPublicKey(value, what).toBuffer();
|
|
65
|
+
if (typeof type === 'string' && type in INT_WIDTH)
|
|
66
|
+
return encodeInt(value, type, what);
|
|
67
|
+
if (type === 'bool') {
|
|
68
|
+
if (typeof value !== 'boolean')
|
|
69
|
+
throw new errors_1.InvalidReadQueryError(`${what} must be a boolean`);
|
|
70
|
+
return buffer_1.Buffer.from([value ? 1 : 0]);
|
|
71
|
+
}
|
|
72
|
+
if (type === 'string') {
|
|
73
|
+
if (typeof value !== 'string')
|
|
74
|
+
throw new errors_1.InvalidReadQueryError(`${what} must be a string`);
|
|
75
|
+
return buffer_1.Buffer.from(value, 'utf8');
|
|
76
|
+
}
|
|
77
|
+
const isBytes = type === 'bytes' || (typeof type === 'object' && type !== null && 'array' in type && type.array[0] === 'u8');
|
|
78
|
+
if (isBytes) {
|
|
79
|
+
if (value instanceof Uint8Array)
|
|
80
|
+
return buffer_1.Buffer.from(value);
|
|
81
|
+
if (typeof value === 'string' && /^0x([0-9a-fA-F]{2})*$/.test(value))
|
|
82
|
+
return buffer_1.Buffer.from(value.slice(2), 'hex');
|
|
83
|
+
throw new errors_1.InvalidReadQueryError(`${what} must be bytes (Uint8Array or 0x-hex)`);
|
|
84
|
+
}
|
|
85
|
+
throw new errors_1.InvalidReadQueryError(`${what}: seed type ${JSON.stringify(type)} is not supported`);
|
|
86
|
+
}
|
|
87
|
+
function argType(ix, path) {
|
|
88
|
+
if (path.includes('.'))
|
|
89
|
+
throw new errors_1.InvalidReadQueryError(`seed arg path ${path} reads a struct field; only top-level instruction args are supported`);
|
|
90
|
+
const arg = ix.args.find(a => a.name === path) ?? ix.args.find(a => (0, svm_account_1.idlNameKey)(a.name) === (0, svm_account_1.idlNameKey)(path));
|
|
91
|
+
if (!arg)
|
|
92
|
+
throw new errors_1.InvalidReadQueryError(`seed arg ${path} is not an argument of ${ix.name}`);
|
|
93
|
+
return arg.type;
|
|
94
|
+
}
|
|
95
|
+
function describeSeeds(t) {
|
|
96
|
+
return t.pda.seeds.map(s => (s.kind === 'const' ? `"${buffer_1.Buffer.from(s.value).toString('utf8')}"` : `${s.kind}:${s.path}`)).join(', ');
|
|
97
|
+
}
|
|
98
|
+
/** Same seeds, same order: two instructions describing one PDA agree. */
|
|
99
|
+
function sameLayout(a, b) {
|
|
100
|
+
return JSON.stringify([a.pda.seeds, a.pda.program]) === JSON.stringify([b.pda.seeds, b.pda.program]);
|
|
101
|
+
}
|
|
102
|
+
/** Derive the PDA of account layout `functionName` from its IDL seed template and positional `args`. */
|
|
103
|
+
function deriveIdlPda(idl, functionName, args = []) {
|
|
104
|
+
const templates = findSeedTemplates(idl, functionName);
|
|
105
|
+
if (templates.length === 0) {
|
|
106
|
+
throw new errors_1.InvalidReadQueryError(`the IDL has no PDA seeds for ${functionName}; pass the account address as the subject instead of the program id`);
|
|
107
|
+
}
|
|
108
|
+
const [template] = templates;
|
|
109
|
+
if (templates.some(t => !sameLayout(t, template))) {
|
|
110
|
+
throw new errors_1.InvalidReadQueryError(`the IDL describes ${functionName} with different seeds in different instructions; pass the account address as the subject`);
|
|
111
|
+
}
|
|
112
|
+
const dynamic = template.pda.seeds.filter(s => s.kind !== 'const');
|
|
113
|
+
if (args.length !== dynamic.length) {
|
|
114
|
+
throw new errors_1.InvalidReadQueryError(`${functionName} is derived from seeds [${describeSeeds(template)}]: expected ${dynamic.length} args, got ${args.length}`);
|
|
115
|
+
}
|
|
116
|
+
let i = 0;
|
|
117
|
+
const seeds = template.pda.seeds.map((seed) => {
|
|
118
|
+
if (seed.kind === 'const')
|
|
119
|
+
return buffer_1.Buffer.from(seed.value);
|
|
120
|
+
const what = `args[${i}] (${seed.kind}:${seed.path})`;
|
|
121
|
+
const value = args[i++];
|
|
122
|
+
if (seed.kind === 'account') {
|
|
123
|
+
if (seed.path.includes('.'))
|
|
124
|
+
throw new errors_1.InvalidReadQueryError(`seed ${seed.path} reads another account's field; pass the account address as the subject`);
|
|
125
|
+
return encodeSeed(value, 'pubkey', what);
|
|
126
|
+
}
|
|
127
|
+
return encodeSeed(value, argType(template.instruction, seed.path), what);
|
|
128
|
+
});
|
|
129
|
+
let programId = new web3_js_1.PublicKey(idl.address);
|
|
130
|
+
const program = template.pda.program;
|
|
131
|
+
if (program) {
|
|
132
|
+
if (program.kind !== 'const')
|
|
133
|
+
throw new errors_1.InvalidReadQueryError(`${functionName} is derived under a non-constant program; pass the account address as the subject`);
|
|
134
|
+
programId = new web3_js_1.PublicKey(buffer_1.Buffer.from(program.value));
|
|
135
|
+
}
|
|
136
|
+
return web3_js_1.PublicKey.findProgramAddressSync(seeds, programId)[0];
|
|
137
|
+
}
|
|
138
|
+
/** Whether `subject` is the IDL's program id (base58 or 0x-hex). */
|
|
139
|
+
function isProgramSubject(idl, subject) {
|
|
140
|
+
try {
|
|
141
|
+
return (0, normalize_address_1.toSvmHexAddress)(subject) === (0, normalize_address_1.toSvmHexAddress)(idl.address);
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=svm-pda.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ReadSpecViolation } from './errors';
|
|
2
|
+
import type { ReadPreflight, ReadSpec } from './read-state.types';
|
|
3
|
+
export interface ValidateReadSpecInput {
|
|
4
|
+
spec: ReadSpec;
|
|
5
|
+
/** msg.value the request will carry. */
|
|
6
|
+
value: bigint;
|
|
7
|
+
callbackGasLimit: bigint;
|
|
8
|
+
/** When present, the height / expiry / fee rules that need chain state are checked too. */
|
|
9
|
+
preflight?: ReadPreflight;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Client-side mirror of every revert in `UniversalCallback.requestExternalReadSelf`
|
|
13
|
+
* (feat-read-state f8d1a0c), plus two SDK invariants, so a developer gets a typed
|
|
14
|
+
* violation list instead of an opaque revert. Line numbers refer to the contract.
|
|
15
|
+
*/
|
|
16
|
+
export declare function validateReadSpec(input: ValidateReadSpecInput): {
|
|
17
|
+
ok: true;
|
|
18
|
+
} | {
|
|
19
|
+
ok: false;
|
|
20
|
+
violations: ReadSpecViolation[];
|
|
21
|
+
};
|
|
22
|
+
export declare function assertValidReadSpec(input: ValidateReadSpecInput): void;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateReadSpec = validateReadSpec;
|
|
4
|
+
exports.assertValidReadSpec = assertValidReadSpec;
|
|
5
|
+
const viem_1 = require("viem");
|
|
6
|
+
const read_state_1 = require("../constants/read-state");
|
|
7
|
+
const errors_1 = require("./errors");
|
|
8
|
+
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
9
|
+
const UINT16_MAX = 65_535;
|
|
10
|
+
const UINT64_MAX = (1n << 64n) - 1n;
|
|
11
|
+
/**
|
|
12
|
+
* Client-side mirror of every revert in `UniversalCallback.requestExternalReadSelf`
|
|
13
|
+
* (feat-read-state f8d1a0c), plus two SDK invariants, so a developer gets a typed
|
|
14
|
+
* violation list instead of an opaque revert. Line numbers refer to the contract.
|
|
15
|
+
*/
|
|
16
|
+
function validateReadSpec(input) {
|
|
17
|
+
const { spec, value, callbackGasLimit, preflight } = input;
|
|
18
|
+
const v = [];
|
|
19
|
+
// :98-103 InvalidAccountId — every account field non-empty
|
|
20
|
+
const ownerLen = isHexLike(spec.account.owner) ? (0, viem_1.size)(spec.account.owner) : 0;
|
|
21
|
+
if (!spec.account.chainNamespace || !spec.account.chainId || ownerLen === 0)
|
|
22
|
+
v.push('INVALID_ACCOUNT_ID');
|
|
23
|
+
// :104-106 EmptyQuery
|
|
24
|
+
if (!isHexLike(spec.query) || (0, viem_1.size)(spec.query) === 0)
|
|
25
|
+
v.push('EMPTY_QUERY');
|
|
26
|
+
// :107-109 InvalidMinConfirmations (uint16)
|
|
27
|
+
if (!Number.isInteger(spec.minConfirmations) || spec.minConfirmations < read_state_1.MIN_CONFIRMATIONS_FLOOR || spec.minConfirmations > UINT16_MAX) {
|
|
28
|
+
v.push('INVALID_MIN_CONFIRMATIONS');
|
|
29
|
+
}
|
|
30
|
+
// :113-134 InvalidBlockNumber — heightless: must be 0; else 1..oracleHeight
|
|
31
|
+
if (spec.blockNumber < 0n || spec.blockNumber > UINT64_MAX) {
|
|
32
|
+
v.push('INVALID_BLOCK_NUMBER');
|
|
33
|
+
}
|
|
34
|
+
else if (preflight) {
|
|
35
|
+
const h = preflight.observedChainHeight;
|
|
36
|
+
const bad = h === 0n ? spec.blockNumber !== 0n : spec.blockNumber === 0n || spec.blockNumber > h;
|
|
37
|
+
if (bad)
|
|
38
|
+
v.push('INVALID_BLOCK_NUMBER');
|
|
39
|
+
}
|
|
40
|
+
// :119-121 InvalidExpiryHeight — strictly in the future
|
|
41
|
+
if (spec.expiryPushChainHeight < 0n || spec.expiryPushChainHeight > UINT64_MAX) {
|
|
42
|
+
v.push('INVALID_EXPIRY_HEIGHT');
|
|
43
|
+
}
|
|
44
|
+
else if (preflight && spec.expiryPushChainHeight <= preflight.pushBlockNumber) {
|
|
45
|
+
v.push('INVALID_EXPIRY_HEIGHT');
|
|
46
|
+
}
|
|
47
|
+
// :122-124 ZeroRevertRecipient
|
|
48
|
+
if (!(0, viem_1.isAddress)(spec.revertRecipient) || spec.revertRecipient.toLowerCase() === ZERO_ADDRESS)
|
|
49
|
+
v.push('ZERO_REVERT_RECIPIENT');
|
|
50
|
+
// :125-130 ZeroCallbackGasLimit / CallbackGasLimitExceeded
|
|
51
|
+
if (callbackGasLimit <= 0n)
|
|
52
|
+
v.push('ZERO_CALLBACK_GAS_LIMIT');
|
|
53
|
+
else if (callbackGasLimit > read_state_1.MAX_CALLBACK_GAS_LIMIT)
|
|
54
|
+
v.push('CALLBACK_GAS_LIMIT_EXCEEDED');
|
|
55
|
+
// :132-139 InsufficientFee / ExcessiveFee
|
|
56
|
+
if (preflight && value < preflight.protocolFee)
|
|
57
|
+
v.push('INSUFFICIENT_FEE');
|
|
58
|
+
if (value > spec.maxFee)
|
|
59
|
+
v.push('EXCESSIVE_FEE');
|
|
60
|
+
// SDK invariant (not a contract revert): a zero budget is never fulfilled by the node.
|
|
61
|
+
if (preflight && value - preflight.protocolFee <= 0n)
|
|
62
|
+
v.push('ZERO_CALLBACK_BUDGET');
|
|
63
|
+
// SDM invariant from svm/read_executor.go:29-32 — validators hard-reject any other length.
|
|
64
|
+
if (spec.account.chainNamespace === read_state_1.READ_NAMESPACE.SVM && ownerLen !== 32)
|
|
65
|
+
v.push('SVM_OWNER_NOT_32_BYTES');
|
|
66
|
+
return v.length === 0 ? { ok: true } : { ok: false, violations: v };
|
|
67
|
+
}
|
|
68
|
+
function assertValidReadSpec(input) {
|
|
69
|
+
const r = validateReadSpec(input);
|
|
70
|
+
if (!r.ok) {
|
|
71
|
+
throw new errors_1.InvalidReadSpecError(r.violations, {
|
|
72
|
+
destination: `${input.spec.account.chainNamespace}:${input.spec.account.chainId}`,
|
|
73
|
+
hint: 'See ReadSpecViolation names — each mirrors a UniversalCallback revert.',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function isHexLike(x) {
|
|
78
|
+
return typeof x === 'string' && /^0x[0-9a-fA-F]*$/.test(x);
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* See push-chain/x/uexecutor/types/keys.go:49-53 (GetInboundUniversalTxKey).
|
|
8
8
|
*
|
|
9
9
|
* For SVM sources, `<txHash>` is the 0x-prefixed hex encoding of the 64-byte
|
|
10
|
-
* Solana signature and `<logIndex>` is the event
|
|
10
|
+
* Solana signature and `<logIndex>` is the gateway-event ordinal. Current
|
|
11
|
+
* gateways derive it from authenticated `emit_cpi` inner instructions;
|
|
12
|
+
* legacy log-emitting gateways used the `meta.logMessages` index.
|
|
11
13
|
*
|
|
12
14
|
* For Push-initiated outbounds (pcTx entries) the formula drops the log index:
|
|
13
15
|
* utxId = sha256(`<pcChainCaip>:<pcTxHash>`)
|
|
@@ -14,7 +14,9 @@ exports.resolveChildInboundsFromLogs = resolveChildInboundsFromLogs;
|
|
|
14
14
|
* See push-chain/x/uexecutor/types/keys.go:49-53 (GetInboundUniversalTxKey).
|
|
15
15
|
*
|
|
16
16
|
* For SVM sources, `<txHash>` is the 0x-prefixed hex encoding of the 64-byte
|
|
17
|
-
* Solana signature and `<logIndex>` is the event
|
|
17
|
+
* Solana signature and `<logIndex>` is the gateway-event ordinal. Current
|
|
18
|
+
* gateways derive it from authenticated `emit_cpi` inner instructions;
|
|
19
|
+
* legacy log-emitting gateways used the `meta.logMessages` index.
|
|
18
20
|
*
|
|
19
21
|
* For Push-initiated outbounds (pcTx entries) the formula drops the log index:
|
|
20
22
|
* utxId = sha256(`<pcChainCaip>:<pcTxHash>`)
|
|
@@ -44,10 +46,7 @@ const OUTBOUND_STATUS_NAMES = {
|
|
|
44
46
|
2: 'OBSERVED',
|
|
45
47
|
3: 'REVERTED',
|
|
46
48
|
};
|
|
47
|
-
const INBOUND_EVENT_NAMES = new Set([
|
|
48
|
-
'UniversalTx',
|
|
49
|
-
'RevertUniversalTx',
|
|
50
|
-
]);
|
|
49
|
+
const INBOUND_EVENT_NAMES = new Set(['UniversalTx', 'RevertUniversalTx']);
|
|
51
50
|
const HEX_64_BYTE_RE = /^(?:0x)?[0-9a-fA-F]{128}$/;
|
|
52
51
|
function normalizeInboundTxHashForKey(sourceChainCaip, externalTxHash) {
|
|
53
52
|
if (!sourceChainCaip.startsWith('solana:')) {
|
|
@@ -6,18 +6,17 @@ const tslib_1 = require("tslib");
|
|
|
6
6
|
/**
|
|
7
7
|
* detectUniversalTxSvm — SVM counterpart to the EVM detector.
|
|
8
8
|
*
|
|
9
|
-
* Fetches the tx via Solana RPC
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* and hands the result to the same classifier used by the EVM branch.
|
|
9
|
+
* Fetches the tx via Solana RPC and decodes gateway events with Anchor's
|
|
10
|
+
* BorshEventCoder. Current gateways use authenticated `emit_cpi!` inner
|
|
11
|
+
* instructions; legacy `Program data:` log events remain as a fallback.
|
|
13
12
|
*
|
|
14
13
|
* Behavior deliberately mirrors detector.ts: same UniversalTxDetection
|
|
15
14
|
* return, same universalTxId derivation strategy (primary-log fallback
|
|
16
15
|
* via deriveChildUniversalTxId on source-chain inbounds).
|
|
17
16
|
*
|
|
18
17
|
* The Push keeper stores SVM inbound ids using the 0x-prefixed hex encoding
|
|
19
|
-
* of the 64-byte Solana signature and the
|
|
20
|
-
*
|
|
18
|
+
* of the 64-byte Solana signature and the gateway-event ordinal:
|
|
19
|
+
* `${caip}:${hexSig}:${eventIndex}`.
|
|
21
20
|
*/
|
|
22
21
|
const anchor_1 = require("@coral-xyz/anchor");
|
|
23
22
|
const web3_js_1 = require("@solana/web3.js");
|
|
@@ -86,17 +85,22 @@ async function detectUniversalTxSvm(signature, chain, opts = {}) {
|
|
|
86
85
|
}
|
|
87
86
|
const eventCoder = new anchor_1.BorshEventCoder(universalGatewayV0_json_1.default);
|
|
88
87
|
const matchingLogs = [];
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
88
|
+
const cpiPayloads = (0, svm_events_1.getSvmEventCpiPayloads)(tx, emitterBase58);
|
|
89
|
+
const eventPayloads = cpiPayloads.length > 0
|
|
90
|
+
? cpiPayloads.map(({ base64Data, eventIndex }) => ({
|
|
91
|
+
base64Data,
|
|
92
|
+
logIndex: eventIndex,
|
|
93
|
+
}))
|
|
94
|
+
: logMessages.flatMap((rawLog, logIndex) => {
|
|
95
|
+
const prefix = 'Program data: ';
|
|
96
|
+
return typeof rawLog === 'string' && rawLog.startsWith(prefix)
|
|
97
|
+
? [{ base64Data: rawLog.slice(prefix.length).trim(), logIndex }]
|
|
98
|
+
: [];
|
|
99
|
+
});
|
|
100
|
+
if (cpiPayloads.length > 0) {
|
|
101
|
+
notes.push('svm: decoded gateway events from emit_cpi inner instructions');
|
|
102
|
+
}
|
|
103
|
+
for (const { base64Data, logIndex } of eventPayloads) {
|
|
100
104
|
// Fast discriminator check — skip non-universal events without running
|
|
101
105
|
// the full Borsh decode.
|
|
102
106
|
let bytes;
|
|
@@ -141,7 +145,7 @@ async function detectUniversalTxSvm(signature, chain, opts = {}) {
|
|
|
141
145
|
notes.push(...classified.notes);
|
|
142
146
|
const decoded = { ...classified.decoded };
|
|
143
147
|
// Inbound fallback for universalTxId: same formula the EVM branch uses,
|
|
144
|
-
// with the canonical 0x-hex Solana signature and
|
|
148
|
+
// with the canonical 0x-hex Solana signature and gateway-event index.
|
|
145
149
|
if (!decoded.universalTxId) {
|
|
146
150
|
const caip = chain;
|
|
147
151
|
if (INBOUND_KINDS.has(classified.kind) &&
|
|
@@ -245,7 +249,9 @@ function coerceArgValue(srcKey, renamedKey, value) {
|
|
|
245
249
|
return (0, viem_1.bytesToHex)(new Uint8Array(value));
|
|
246
250
|
}
|
|
247
251
|
// BN from anchor — has toString().
|
|
248
|
-
if (value &&
|
|
252
|
+
if (value &&
|
|
253
|
+
typeof value === 'object' &&
|
|
254
|
+
typeof value.toString === 'function') {
|
|
249
255
|
const proto = Object.getPrototypeOf(value);
|
|
250
256
|
const ctorName = proto?.constructor?.name;
|
|
251
257
|
if (ctorName === 'BN') {
|
|
@@ -259,7 +265,10 @@ function coerceArgValue(srcKey, renamedKey, value) {
|
|
|
259
265
|
}
|
|
260
266
|
// Anchor enum → { [variantName]: {} }. tx_type must be surfaced as a
|
|
261
267
|
// numeric index to match classify.ts's setTxType expectations.
|
|
262
|
-
if (renamedKey === 'txType' &&
|
|
268
|
+
if (renamedKey === 'txType' &&
|
|
269
|
+
value &&
|
|
270
|
+
typeof value === 'object' &&
|
|
271
|
+
!Array.isArray(value)) {
|
|
263
272
|
return extractTxTypeIndex(value);
|
|
264
273
|
}
|
|
265
274
|
return value;
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SVM-side universal-tx event metadata.
|
|
3
3
|
*
|
|
4
|
-
* Anchor programs emit events
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* converted to lowercase hex for matching.
|
|
4
|
+
* Current Anchor programs emit events through `emit_cpi!`: an inner
|
|
5
|
+
* instruction whose data is EVENT_IX_TAG || event discriminator || Borsh
|
|
6
|
+
* body. Older deployments emitted the discriminator + body as a base64
|
|
7
|
+
* `Program data:` log line. Helpers in this module support both transports.
|
|
9
8
|
*/
|
|
9
|
+
export declare const SVM_EVENT_IX_TAG: Uint8Array;
|
|
10
|
+
export interface SvmGatewayEventPayload {
|
|
11
|
+
/** Base64 discriminator + Borsh body accepted by BorshEventCoder. */
|
|
12
|
+
base64Data: string;
|
|
13
|
+
/** Gateway-event ordinal used by the Push keeper as log_index. */
|
|
14
|
+
eventIndex: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Extract authenticated gateway events from Anchor `emit_cpi!` inner
|
|
18
|
+
* instructions. Only self-CPIs whose program id equals `gatewayAddress` are
|
|
19
|
+
* accepted; matching a discriminator alone is not proof of the emitter.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getSvmEventCpiPayloads(txResp: any, gatewayAddress: string): SvmGatewayEventPayload[];
|
|
10
22
|
/**
|
|
11
23
|
* Mapping: 8-byte discriminator (lowercase hex, no 0x) → Anchor event name.
|
|
12
24
|
* Only events with EVM classifier parity are listed; the keeper-agnostic
|
|
@@ -2,15 +2,90 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* SVM-side universal-tx event metadata.
|
|
4
4
|
*
|
|
5
|
-
* Anchor programs emit events
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* converted to lowercase hex for matching.
|
|
5
|
+
* Current Anchor programs emit events through `emit_cpi!`: an inner
|
|
6
|
+
* instruction whose data is EVENT_IX_TAG || event discriminator || Borsh
|
|
7
|
+
* body. Older deployments emitted the discriminator + body as a base64
|
|
8
|
+
* `Program data:` log line. Helpers in this module support both transports.
|
|
10
9
|
*/
|
|
11
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.SVM_EVENT_FIELD_ALIASES = exports.SVM_FIELD_RENAMES = exports.SVM_EVENT_DISCRIMINATORS = void 0;
|
|
11
|
+
exports.SVM_EVENT_FIELD_ALIASES = exports.SVM_FIELD_RENAMES = exports.SVM_EVENT_DISCRIMINATORS = exports.SVM_EVENT_IX_TAG = void 0;
|
|
12
|
+
exports.getSvmEventCpiPayloads = getSvmEventCpiPayloads;
|
|
13
13
|
exports.discriminatorHex = discriminatorHex;
|
|
14
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
15
|
+
const bs58_1 = require("../internal/bs58");
|
|
16
|
+
exports.SVM_EVENT_IX_TAG = Uint8Array.from([
|
|
17
|
+
0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d,
|
|
18
|
+
]);
|
|
19
|
+
/**
|
|
20
|
+
* Extract authenticated gateway events from Anchor `emit_cpi!` inner
|
|
21
|
+
* instructions. Only self-CPIs whose program id equals `gatewayAddress` are
|
|
22
|
+
* accepted; matching a discriminator alone is not proof of the emitter.
|
|
23
|
+
*/
|
|
24
|
+
function getSvmEventCpiPayloads(txResp, gatewayAddress) {
|
|
25
|
+
const groups = txResp?.meta?.innerInstructions;
|
|
26
|
+
if (!Array.isArray(groups) || groups.length === 0)
|
|
27
|
+
return [];
|
|
28
|
+
let gateway;
|
|
29
|
+
try {
|
|
30
|
+
gateway = new web3_js_1.PublicKey(gatewayAddress);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
const message = txResp?.transaction?.message;
|
|
36
|
+
const loadedAddresses = txResp?.meta?.loadedAddresses;
|
|
37
|
+
const accountKeyAt = (index) => {
|
|
38
|
+
try {
|
|
39
|
+
if (typeof message?.getAccountKeys === 'function') {
|
|
40
|
+
return message
|
|
41
|
+
.getAccountKeys({
|
|
42
|
+
accountKeysFromLookups: loadedAddresses,
|
|
43
|
+
})
|
|
44
|
+
.get(index);
|
|
45
|
+
}
|
|
46
|
+
const keys = [
|
|
47
|
+
...(message?.accountKeys ?? message?.staticAccountKeys ?? []),
|
|
48
|
+
...(loadedAddresses?.writable ?? []),
|
|
49
|
+
...(loadedAddresses?.readonly ?? loadedAddresses?.readOnly ?? []),
|
|
50
|
+
];
|
|
51
|
+
const key = keys[index];
|
|
52
|
+
return key instanceof web3_js_1.PublicKey
|
|
53
|
+
? key
|
|
54
|
+
: key
|
|
55
|
+
? new web3_js_1.PublicKey(key)
|
|
56
|
+
: undefined;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const payloads = [];
|
|
63
|
+
for (const group of groups) {
|
|
64
|
+
for (const instruction of group?.instructions ?? []) {
|
|
65
|
+
const programId = accountKeyAt(Number(instruction?.programIdIndex));
|
|
66
|
+
if (!programId?.equals(gateway) ||
|
|
67
|
+
typeof instruction?.data !== 'string') {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
let raw;
|
|
71
|
+
try {
|
|
72
|
+
raw = bs58_1.bs58.decode(instruction.data);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (raw.length < exports.SVM_EVENT_IX_TAG.length + 8 ||
|
|
78
|
+
!exports.SVM_EVENT_IX_TAG.every((byte, i) => raw[i] === byte)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
payloads.push({
|
|
82
|
+
base64Data: Buffer.from(raw.subarray(exports.SVM_EVENT_IX_TAG.length)).toString('base64'),
|
|
83
|
+
eventIndex: payloads.length,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return payloads;
|
|
88
|
+
}
|
|
14
89
|
const toHex = (bytes) => bytes.map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
15
90
|
/**
|
|
16
91
|
* Mapping: 8-byte discriminator (lowercase hex, no 0x) → Anchor event name.
|
|
@@ -202,18 +202,35 @@ class EvmClient {
|
|
|
202
202
|
* console.log(`ETH transfer sent: ${txHash}`);
|
|
203
203
|
*/
|
|
204
204
|
async sendTransaction({ to, data, value = BigInt(0), signer, nonce: explicitNonce, gas: explicitGas, }) {
|
|
205
|
+
const resolveGas = async () => {
|
|
206
|
+
if (explicitGas)
|
|
207
|
+
return explicitGas;
|
|
208
|
+
const estimate = () => this.publicClient.estimateGas({
|
|
209
|
+
account: signer.account.address,
|
|
210
|
+
to,
|
|
211
|
+
data,
|
|
212
|
+
value,
|
|
213
|
+
});
|
|
214
|
+
if (data !== '0x')
|
|
215
|
+
return estimate();
|
|
216
|
+
// A plain native transfer to a code-free EOA always costs 21k. Empty
|
|
217
|
+
// calldata is not sufficient to identify that case, though: contracts
|
|
218
|
+
// and EIP-7702 delegated accounts still execute code and need estimation.
|
|
219
|
+
// If the code lookup is unavailable, prefer estimation over submitting
|
|
220
|
+
// a transaction that can deterministically run out of gas.
|
|
221
|
+
try {
|
|
222
|
+
const targetCode = await this.publicClient.getCode({ address: to });
|
|
223
|
+
if (!targetCode || targetCode === '0x')
|
|
224
|
+
return BigInt(21000);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// Fall through to estimateGas.
|
|
228
|
+
}
|
|
229
|
+
return estimate();
|
|
230
|
+
};
|
|
205
231
|
// Estimate gas and fees first (can run in parallel)
|
|
206
232
|
const [gas, feePerGas, chainId] = await Promise.all([
|
|
207
|
-
|
|
208
|
-
? Promise.resolve(explicitGas)
|
|
209
|
-
: data === '0x'
|
|
210
|
-
? Promise.resolve(BigInt(21000))
|
|
211
|
-
: this.publicClient.estimateGas({
|
|
212
|
-
account: signer.account.address,
|
|
213
|
-
to,
|
|
214
|
-
data,
|
|
215
|
-
value,
|
|
216
|
-
}),
|
|
233
|
+
resolveGas(),
|
|
217
234
|
this.publicClient.estimateFeesPerGas(),
|
|
218
235
|
this.publicClient.getChainId(),
|
|
219
236
|
]);
|