evm-selector-extractor 0.1.0

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/README.md ADDED
@@ -0,0 +1,156 @@
1
+ # evm-selector-extractor
2
+
3
+ [![npm version](https://img.shields.io/npm/v/evm-selector-extractor?style=flat-square)](https://www.npmjs.com/package/evm-selector-extractor)
4
+ [![npm downloads](https://img.shields.io/npm/dm/evm-selector-extractor?style=flat-square)](https://www.npmjs.com/package/evm-selector-extractor)
5
+ [![GitHub](https://img.shields.io/badge/GitHub-Repository-black?style=flat-square&logo=github)](https://github.com/snipe-dev/evm-selector-extractor)
6
+
7
+
8
+ Lightweight Ethereum Virtual Machine (EVM) bytecode selector extractor.
9
+
10
+ - Zero dependencies
11
+ - Works in Node.js and Browser
12
+ - ESM + CommonJS compatible
13
+ - Extracts `PUSH4` function selectors directly from raw bytecode
14
+
15
+ ------------------------------------------------------------------------
16
+
17
+ ## Installation
18
+
19
+ ``` bash
20
+ npm install evm-selector-extractor
21
+ ```
22
+
23
+ ------------------------------------------------------------------------
24
+
25
+ ## Usage (Node.js + ethers v6)
26
+
27
+ This example:
28
+
29
+ - Creates a provider
30
+ - Fetches bytecode of PEPE (ERC-20)
31
+ - Extracts function selectors
32
+ - Prints them to console
33
+
34
+ ``` ts
35
+ import { JsonRpcProvider } from "ethers";
36
+ import { EVM } from "evm-selector-extractor";
37
+
38
+ const provider = new JsonRpcProvider("https://ethereum-rpc.publicnode.com");
39
+
40
+ const address = "0x6982508145454ce325ddbe47a25d4ec3d2311933"; // PEPE ERC-20
41
+
42
+ async function main() {
43
+ const bytecode = await provider.getCode(address);
44
+
45
+ const evm = new EVM(bytecode);
46
+ const selectors = evm.getSelectors();
47
+
48
+ console.log("Extracted selectors:");
49
+ console.log(selectors);
50
+ }
51
+
52
+ main();
53
+ ```
54
+
55
+ ------------------------------------------------------------------------
56
+
57
+ ## Usage (CommonJS)
58
+
59
+ ``` js
60
+ const { JsonRpcProvider } = require("ethers");
61
+ const { EVM } = require("evm-selector-extractor");
62
+
63
+ const provider = new JsonRpcProvider("https://ethereum-rpc.publicnode.com");
64
+
65
+ async function main() {
66
+ const bytecode = await provider.getCode(
67
+ "0x6982508145454ce325ddbe47a25d4ec3d2311933"
68
+ );
69
+
70
+ const evm = new EVM(bytecode);
71
+ console.log(evm.getSelectors());
72
+ }
73
+
74
+ main();
75
+ ```
76
+
77
+ ------------------------------------------------------------------------
78
+
79
+ ## Browser Example
80
+
81
+ ``` ts
82
+ import { EVM } from "evm-selector-extractor";
83
+
84
+ const bytecode = "0x63a9059cbb...";
85
+ const evm = new EVM(bytecode);
86
+
87
+ console.log(evm.getSelectors());
88
+ ```
89
+
90
+ No Node.js APIs are used internally. The library relies only on
91
+ `Uint8Array`.
92
+
93
+ ------------------------------------------------------------------------
94
+
95
+ ## API
96
+
97
+ ### new EVM(bytecode: string)
98
+
99
+ Creates an EVM bytecode parser instance.
100
+
101
+ - Accepts hex string with or without `0x`
102
+ - Throws if invalid hex
103
+
104
+ ------------------------------------------------------------------------
105
+
106
+ ### getOpcodes(): EvmOpcode\[\]
107
+
108
+ Returns parsed opcode list.
109
+
110
+ ``` ts
111
+ interface EvmOpcode {
112
+ name: string;
113
+ opcode: number;
114
+ pc: number;
115
+ pushData?: Uint8Array;
116
+ }
117
+ ```
118
+
119
+ ------------------------------------------------------------------------
120
+
121
+ ### getSelectors(): string\[\]
122
+
123
+ Extracts unique `PUSH4` selectors.
124
+
125
+ Returns:
126
+
127
+ ``` ts
128
+ string[] // e.g. ["0xa9059cbb", "0x095ea7b3"]
129
+ ```
130
+
131
+ Hidden internal selectors are automatically filtered.
132
+
133
+ ------------------------------------------------------------------------
134
+
135
+ ## Why not use ethereum.js / ethers ABI parsing?
136
+
137
+ This library:
138
+
139
+ - Does not require ABI
140
+ - Does not depend on heavy EVM stacks
141
+ - Works directly on raw bytecode
142
+ - Designed for lightweight analysis tools and browser environments
143
+
144
+ ------------------------------------------------------------------------
145
+
146
+ ## Build
147
+
148
+ ``` bash
149
+ npm run build
150
+ ```
151
+
152
+ ------------------------------------------------------------------------
153
+
154
+ ## Repository
155
+
156
+ https://github.com/snipe-dev/evm-selector-extractor
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EVM = void 0;
4
+ /**
5
+ * Lightweight EVM bytecode parser for extracting opcodes and function selectors.
6
+ *
7
+ * Zero dependencies.
8
+ * Works in both Node.js and browser environments.
9
+ */
10
+ class EVM {
11
+ /**
12
+ * Creates an EVM bytecode parser instance.
13
+ *
14
+ * @param bytecode - Raw EVM bytecode as a hex string (with or without '0x' prefix)
15
+ * @throws {Error} If bytecode is not a valid hex string
16
+ */
17
+ constructor(bytecode) {
18
+ this.opcodes = [];
19
+ const hexCode = bytecode.startsWith("0x")
20
+ ? bytecode.slice(2)
21
+ : bytecode;
22
+ if (!EVM.isValidHex(hexCode)) {
23
+ throw new Error("Invalid hex string provided as bytecode");
24
+ }
25
+ this.code = EVM.hexToBytes(hexCode);
26
+ }
27
+ /**
28
+ * Parses and returns all opcodes from the bytecode.
29
+ *
30
+ * @returns Array of parsed opcode objects with their metadata
31
+ */
32
+ getOpcodes() {
33
+ if (this.opcodes.length > 0) {
34
+ return this.opcodes;
35
+ }
36
+ for (let pc = 0; pc < this.code.length; pc++) {
37
+ const opcodeByte = this.code[pc];
38
+ const opcodeInfo = this.getOpcodeInfo(opcodeByte);
39
+ const currentOp = {
40
+ name: opcodeInfo.name,
41
+ opcode: opcodeByte,
42
+ pc: pc
43
+ };
44
+ if (opcodeInfo.name.startsWith("PUSH")) {
45
+ const pushSize = parseInt(opcodeInfo.name.replace("PUSH", ""), 10);
46
+ if (pc + pushSize < this.code.length) {
47
+ currentOp.pushData = this.code.slice(pc + 1, pc + pushSize + 1);
48
+ pc += pushSize;
49
+ }
50
+ }
51
+ this.opcodes.push(currentOp);
52
+ }
53
+ return this.opcodes;
54
+ }
55
+ /**
56
+ * Extracts unique 4-byte function selectors (PUSH4) from EVM bytecode.
57
+ *
58
+ * @returns Array of unique function selectors as hex strings with `0x` prefix
59
+ */
60
+ getSelectors() {
61
+ const hidden = new Set([
62
+ "0xffffffff",
63
+ "0x00000000",
64
+ "0x4e487b71",
65
+ "0xad5c4648"
66
+ ]);
67
+ const opcodes = this.getOpcodes();
68
+ const push4codes = opcodes
69
+ .filter(op => op.name === "PUSH4" && op.pushData)
70
+ .map(op => EVM.bytesToHex(op.pushData));
71
+ return Array.from(new Set(push4codes))
72
+ .sort()
73
+ .map(hex => "0x" + hex)
74
+ .filter(selector => !hidden.has(selector));
75
+ }
76
+ /**
77
+ * Maps opcode byte values to their human-readable names.
78
+ */
79
+ getOpcodeInfo(opcode) {
80
+ const opcodeMap = {
81
+ 0x60: "PUSH1", 0x61: "PUSH2", 0x62: "PUSH3", 0x63: "PUSH4",
82
+ 0x64: "PUSH5", 0x65: "PUSH6", 0x66: "PUSH7", 0x67: "PUSH8",
83
+ 0x68: "PUSH9", 0x69: "PUSH10", 0x6A: "PUSH11", 0x6B: "PUSH12",
84
+ 0x6C: "PUSH13", 0x6D: "PUSH14", 0x6E: "PUSH15", 0x6F: "PUSH16",
85
+ 0x70: "PUSH17", 0x71: "PUSH18", 0x72: "PUSH19", 0x73: "PUSH20",
86
+ 0x74: "PUSH21", 0x75: "PUSH22", 0x76: "PUSH23", 0x77: "PUSH24",
87
+ 0x78: "PUSH25", 0x79: "PUSH26", 0x7A: "PUSH27", 0x7B: "PUSH28",
88
+ 0x7C: "PUSH29", 0x7D: "PUSH30", 0x7E: "PUSH31", 0x7F: "PUSH32",
89
+ 0x00: "STOP", 0x01: "ADD", 0x02: "MUL", 0x03: "SUB",
90
+ 0x50: "POP", 0x51: "MLOAD", 0x52: "MSTORE", 0x53: "MSTORE8",
91
+ 0x54: "SLOAD", 0x55: "SSTORE", 0x56: "JUMP", 0x57: "JUMPI",
92
+ 0x58: "PC", 0x59: "MSIZE", 0x5A: "GAS", 0x5B: "JUMPDEST",
93
+ 0xF0: "CREATE", 0xF1: "CALL", 0xF2: "CALLCODE", 0xF3: "RETURN",
94
+ 0xF4: "DELEGATECALL", 0xFA: "STATICCALL",
95
+ 0xFD: "REVERT", 0xFF: "SELFDESTRUCT"
96
+ };
97
+ return {
98
+ name: opcodeMap[opcode] ||
99
+ `0x${opcode.toString(16).toUpperCase().padStart(2, "0")}`
100
+ };
101
+ }
102
+ /**
103
+ * Validates hex string.
104
+ */
105
+ static isValidHex(hex) {
106
+ return hex.length % 2 === 0 && /^[0-9a-fA-F]*$/.test(hex);
107
+ }
108
+ /**
109
+ * Converts hex string to Uint8Array.
110
+ */
111
+ static hexToBytes(hex) {
112
+ const bytes = new Uint8Array(hex.length / 2);
113
+ for (let i = 0; i < bytes.length; i++) {
114
+ bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
115
+ }
116
+ return bytes;
117
+ }
118
+ /**
119
+ * Converts Uint8Array to hex string (without 0x prefix).
120
+ */
121
+ static bytesToHex(bytes) {
122
+ return Array.from(bytes)
123
+ .map(b => b.toString(16).padStart(2, "0"))
124
+ .join("");
125
+ }
126
+ }
127
+ exports.EVM = EVM;
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Lightweight EVM bytecode parser for extracting opcodes and function selectors.
3
+ *
4
+ * Zero dependencies.
5
+ * Works in both Node.js and browser environments.
6
+ */
7
+ export class EVM {
8
+ /**
9
+ * Creates an EVM bytecode parser instance.
10
+ *
11
+ * @param bytecode - Raw EVM bytecode as a hex string (with or without '0x' prefix)
12
+ * @throws {Error} If bytecode is not a valid hex string
13
+ */
14
+ constructor(bytecode) {
15
+ this.opcodes = [];
16
+ const hexCode = bytecode.startsWith("0x")
17
+ ? bytecode.slice(2)
18
+ : bytecode;
19
+ if (!EVM.isValidHex(hexCode)) {
20
+ throw new Error("Invalid hex string provided as bytecode");
21
+ }
22
+ this.code = EVM.hexToBytes(hexCode);
23
+ }
24
+ /**
25
+ * Parses and returns all opcodes from the bytecode.
26
+ *
27
+ * @returns Array of parsed opcode objects with their metadata
28
+ */
29
+ getOpcodes() {
30
+ if (this.opcodes.length > 0) {
31
+ return this.opcodes;
32
+ }
33
+ for (let pc = 0; pc < this.code.length; pc++) {
34
+ const opcodeByte = this.code[pc];
35
+ const opcodeInfo = this.getOpcodeInfo(opcodeByte);
36
+ const currentOp = {
37
+ name: opcodeInfo.name,
38
+ opcode: opcodeByte,
39
+ pc: pc
40
+ };
41
+ if (opcodeInfo.name.startsWith("PUSH")) {
42
+ const pushSize = parseInt(opcodeInfo.name.replace("PUSH", ""), 10);
43
+ if (pc + pushSize < this.code.length) {
44
+ currentOp.pushData = this.code.slice(pc + 1, pc + pushSize + 1);
45
+ pc += pushSize;
46
+ }
47
+ }
48
+ this.opcodes.push(currentOp);
49
+ }
50
+ return this.opcodes;
51
+ }
52
+ /**
53
+ * Extracts unique 4-byte function selectors (PUSH4) from EVM bytecode.
54
+ *
55
+ * @returns Array of unique function selectors as hex strings with `0x` prefix
56
+ */
57
+ getSelectors() {
58
+ const hidden = new Set([
59
+ "0xffffffff",
60
+ "0x00000000",
61
+ "0x4e487b71",
62
+ "0xad5c4648"
63
+ ]);
64
+ const opcodes = this.getOpcodes();
65
+ const push4codes = opcodes
66
+ .filter(op => op.name === "PUSH4" && op.pushData)
67
+ .map(op => EVM.bytesToHex(op.pushData));
68
+ return Array.from(new Set(push4codes))
69
+ .sort()
70
+ .map(hex => "0x" + hex)
71
+ .filter(selector => !hidden.has(selector));
72
+ }
73
+ /**
74
+ * Maps opcode byte values to their human-readable names.
75
+ */
76
+ getOpcodeInfo(opcode) {
77
+ const opcodeMap = {
78
+ 0x60: "PUSH1", 0x61: "PUSH2", 0x62: "PUSH3", 0x63: "PUSH4",
79
+ 0x64: "PUSH5", 0x65: "PUSH6", 0x66: "PUSH7", 0x67: "PUSH8",
80
+ 0x68: "PUSH9", 0x69: "PUSH10", 0x6A: "PUSH11", 0x6B: "PUSH12",
81
+ 0x6C: "PUSH13", 0x6D: "PUSH14", 0x6E: "PUSH15", 0x6F: "PUSH16",
82
+ 0x70: "PUSH17", 0x71: "PUSH18", 0x72: "PUSH19", 0x73: "PUSH20",
83
+ 0x74: "PUSH21", 0x75: "PUSH22", 0x76: "PUSH23", 0x77: "PUSH24",
84
+ 0x78: "PUSH25", 0x79: "PUSH26", 0x7A: "PUSH27", 0x7B: "PUSH28",
85
+ 0x7C: "PUSH29", 0x7D: "PUSH30", 0x7E: "PUSH31", 0x7F: "PUSH32",
86
+ 0x00: "STOP", 0x01: "ADD", 0x02: "MUL", 0x03: "SUB",
87
+ 0x50: "POP", 0x51: "MLOAD", 0x52: "MSTORE", 0x53: "MSTORE8",
88
+ 0x54: "SLOAD", 0x55: "SSTORE", 0x56: "JUMP", 0x57: "JUMPI",
89
+ 0x58: "PC", 0x59: "MSIZE", 0x5A: "GAS", 0x5B: "JUMPDEST",
90
+ 0xF0: "CREATE", 0xF1: "CALL", 0xF2: "CALLCODE", 0xF3: "RETURN",
91
+ 0xF4: "DELEGATECALL", 0xFA: "STATICCALL",
92
+ 0xFD: "REVERT", 0xFF: "SELFDESTRUCT"
93
+ };
94
+ return {
95
+ name: opcodeMap[opcode] ||
96
+ `0x${opcode.toString(16).toUpperCase().padStart(2, "0")}`
97
+ };
98
+ }
99
+ /**
100
+ * Validates hex string.
101
+ */
102
+ static isValidHex(hex) {
103
+ return hex.length % 2 === 0 && /^[0-9a-fA-F]*$/.test(hex);
104
+ }
105
+ /**
106
+ * Converts hex string to Uint8Array.
107
+ */
108
+ static hexToBytes(hex) {
109
+ const bytes = new Uint8Array(hex.length / 2);
110
+ for (let i = 0; i < bytes.length; i++) {
111
+ bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
112
+ }
113
+ return bytes;
114
+ }
115
+ /**
116
+ * Converts Uint8Array to hex string (without 0x prefix).
117
+ */
118
+ static bytesToHex(bytes) {
119
+ return Array.from(bytes)
120
+ .map(b => b.toString(16).padStart(2, "0"))
121
+ .join("");
122
+ }
123
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Interface representing an EVM opcode with its metadata
3
+ */
4
+ export interface EvmOpcode {
5
+ name: string;
6
+ opcode: number;
7
+ pc: number;
8
+ pushData?: Uint8Array;
9
+ }
10
+ /**
11
+ * Lightweight EVM bytecode parser for extracting opcodes and function selectors.
12
+ *
13
+ * Zero dependencies.
14
+ * Works in both Node.js and browser environments.
15
+ */
16
+ export declare class EVM {
17
+ private readonly code;
18
+ private opcodes;
19
+ /**
20
+ * Creates an EVM bytecode parser instance.
21
+ *
22
+ * @param bytecode - Raw EVM bytecode as a hex string (with or without '0x' prefix)
23
+ * @throws {Error} If bytecode is not a valid hex string
24
+ */
25
+ constructor(bytecode: string);
26
+ /**
27
+ * Parses and returns all opcodes from the bytecode.
28
+ *
29
+ * @returns Array of parsed opcode objects with their metadata
30
+ */
31
+ getOpcodes(): EvmOpcode[];
32
+ /**
33
+ * Extracts unique 4-byte function selectors (PUSH4) from EVM bytecode.
34
+ *
35
+ * @returns Array of unique function selectors as hex strings with `0x` prefix
36
+ */
37
+ getSelectors(): string[];
38
+ /**
39
+ * Maps opcode byte values to their human-readable names.
40
+ */
41
+ private getOpcodeInfo;
42
+ /**
43
+ * Validates hex string.
44
+ */
45
+ private static isValidHex;
46
+ /**
47
+ * Converts hex string to Uint8Array.
48
+ */
49
+ private static hexToBytes;
50
+ /**
51
+ * Converts Uint8Array to hex string (without 0x prefix).
52
+ */
53
+ private static bytesToHex;
54
+ }
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "evm-selector-extractor",
3
+ "version": "0.1.0",
4
+ "description": "Lightweight Ethereum Virtual Machine (EVM) bytecode selector extractor (Node + Browser, zero dependencies)",
5
+ "author": "snipe-dev",
6
+ "license": "MIT",
7
+
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/snipe-dev/evm-selector-extractor.git"
11
+ },
12
+ "homepage": "https://github.com/snipe-dev/evm-selector-extractor#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/snipe-dev/evm-selector-extractor/issues"
15
+ },
16
+
17
+ "main": "./dist/cjs/index.js",
18
+ "module": "./dist/esm/index.js",
19
+ "types": "./dist/types/index.d.ts",
20
+
21
+ "exports": {
22
+ ".": {
23
+ "import": "./dist/esm/index.js",
24
+ "require": "./dist/cjs/index.js",
25
+ "types": "./dist/types/index.d.ts"
26
+ }
27
+ },
28
+
29
+ "files": [
30
+ "dist"
31
+ ],
32
+
33
+ "scripts": {
34
+ "build:esm": "tsc -p tsconfig.esm.json",
35
+ "build:cjs": "tsc -p tsconfig.cjs.json",
36
+ "build": "npm run build:esm && npm run build:cjs",
37
+ "prepublishOnly": "npm run build"
38
+ },
39
+
40
+ "keywords": [
41
+ "ethereum",
42
+ "evm",
43
+ "bytecode",
44
+ "selector",
45
+ "solidity",
46
+ "push4",
47
+ "typescript",
48
+ "decompiler"
49
+ ],
50
+
51
+ "engines": {
52
+ "node": ">=16"
53
+ },
54
+
55
+ "devDependencies": {
56
+ "typescript": "^5.0.0"
57
+ }
58
+ }