solana-mint-recovery-engine 1.0.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.
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RpcAdapter = void 0;
4
+ const web3_js_1 = require("@solana/web3.js");
5
+ const spl_token_1 = require("@solana/spl-token");
6
+ /**
7
+ * Adapter class to query Solana RPC node.
8
+ */
9
+ class RpcAdapter {
10
+ connection;
11
+ constructor(rpcUrl, commitment = 'confirmed') {
12
+ this.connection = new web3_js_1.Connection(rpcUrl, commitment);
13
+ }
14
+ /**
15
+ * Fetches the latest blockhash from the RPC.
16
+ */
17
+ async getLatestBlockhash() {
18
+ const { blockhash } = await this.connection.getLatestBlockhash();
19
+ return blockhash;
20
+ }
21
+ /**
22
+ * Submits a serialized transaction to the network.
23
+ */
24
+ async submitTransaction(rawTransactionBuf) {
25
+ return await this.connection.sendRawTransaction(rawTransactionBuf, {
26
+ skipPreflight: false,
27
+ preflightCommitment: 'confirmed',
28
+ });
29
+ }
30
+ /**
31
+ * Simulates a transaction.
32
+ */
33
+ async simulateTransaction(rawTransactionBuf) {
34
+ // For web3.js v1, we simulate using standard connection
35
+ const base64Tx = rawTransactionBuf.toString('base64');
36
+ const res = await this.connection.simulateTransaction(
37
+ // We can pass the raw transaction using simulateTransaction helper
38
+ // or directly invoke the RPC method.
39
+ // In web3.js v1, Connection.simulateTransaction expects a Transaction object,
40
+ // but we can decode a versioned transaction or legacy transaction.
41
+ // A safe way is to send the raw transaction directly via JSON-RPC.
42
+ // @ts-ignore
43
+ {
44
+ // Connection.simulateTransaction signature takes a Transaction
45
+ // But we can also simulate raw transactions by calling the low level RPC.
46
+ });
47
+ // Let's implement simulation using direct JSON-RPC to support VersionedTransactions perfectly:
48
+ // @ts-ignore
49
+ const rpcResponse = await this.connection._rpcRequest('simulateTransaction', [
50
+ base64Tx,
51
+ { encoding: 'base64', commitment: 'confirmed', replaceRecentBlockhash: true }
52
+ ]);
53
+ if (rpcResponse.error) {
54
+ throw new Error(`Simulation request failed: ${rpcResponse.error.message}`);
55
+ }
56
+ return rpcResponse.result;
57
+ }
58
+ /**
59
+ * Inspects a Solana token or mint account.
60
+ */
61
+ async inspectAccount(address) {
62
+ const accountInfo = await this.connection.getAccountInfo(address);
63
+ if (!accountInfo) {
64
+ throw new Error(`Account ${address.toBase58()} does not exist on-chain.`);
65
+ }
66
+ const ownerProgram = accountInfo.owner;
67
+ const isToken2022 = ownerProgram.equals(spl_token_1.TOKEN_2022_PROGRAM_ID);
68
+ const isLegacyToken = ownerProgram.equals(spl_token_1.TOKEN_PROGRAM_ID);
69
+ if (!isToken2022 && !isLegacyToken) {
70
+ throw new Error(`Account ${address.toBase58()} is owned by ${ownerProgram.toBase58()}, not a recognized Token Program.`);
71
+ }
72
+ const dataSize = accountInfo.data.length;
73
+ const balance = BigInt(accountInfo.lamports);
74
+ // Identify account type by data size
75
+ if (dataSize === spl_token_1.AccountLayout.span || (dataSize > spl_token_1.AccountLayout.span && isToken2022)) {
76
+ // Decode Token Account
77
+ try {
78
+ const decoded = spl_token_1.AccountLayout.decode(accountInfo.data.slice(0, spl_token_1.AccountLayout.span));
79
+ return {
80
+ address,
81
+ type: 'token-account',
82
+ ownerProgram,
83
+ balance,
84
+ dataSize,
85
+ authority: new web3_js_1.PublicKey(decoded.owner),
86
+ isToken2022,
87
+ };
88
+ }
89
+ catch (err) {
90
+ throw new Error(`Failed to decode Token Account layout: ${err.message}`);
91
+ }
92
+ }
93
+ else if (dataSize === spl_token_1.MintLayout.span || (dataSize > spl_token_1.MintLayout.span && isToken2022)) {
94
+ // Decode Mint Account
95
+ try {
96
+ const decoded = spl_token_1.MintLayout.decode(accountInfo.data.slice(0, spl_token_1.MintLayout.span));
97
+ const mintAuthority = decoded.mintAuthorityOption !== 0
98
+ ? new web3_js_1.PublicKey(decoded.mintAuthority)
99
+ : null;
100
+ return {
101
+ address,
102
+ type: 'mint',
103
+ ownerProgram,
104
+ balance,
105
+ dataSize,
106
+ authority: mintAuthority,
107
+ isToken2022,
108
+ };
109
+ }
110
+ catch (err) {
111
+ throw new Error(`Failed to decode Mint Account layout: ${err.message}`);
112
+ }
113
+ }
114
+ else if (dataSize === spl_token_1.MultisigLayout.span) {
115
+ // Decode Multisig Account
116
+ try {
117
+ const decoded = spl_token_1.MultisigLayout.decode(accountInfo.data);
118
+ // Extract threshold and signers list
119
+ const threshold = decoded.m;
120
+ const numSigners = decoded.n;
121
+ // MultisigLayout decodes signer1..signer11. Gather them into an array.
122
+ const allSigners = [
123
+ decoded.signer1,
124
+ decoded.signer2,
125
+ decoded.signer3,
126
+ decoded.signer4,
127
+ decoded.signer5,
128
+ decoded.signer6,
129
+ decoded.signer7,
130
+ decoded.signer8,
131
+ decoded.signer9,
132
+ decoded.signer10,
133
+ decoded.signer11,
134
+ ];
135
+ const signers = allSigners.slice(0, numSigners).map(s => new web3_js_1.PublicKey(s));
136
+ return {
137
+ address,
138
+ type: 'multisig',
139
+ ownerProgram,
140
+ balance,
141
+ dataSize,
142
+ authority: address, // The multisig account authority is itself
143
+ multisigSigners: signers,
144
+ multisigThreshold: threshold,
145
+ isToken2022,
146
+ };
147
+ }
148
+ catch (err) {
149
+ throw new Error(`Failed to decode Multisig Account layout: ${err.message}`);
150
+ }
151
+ }
152
+ throw new Error(`Unknown account data size: ${dataSize} bytes. Cannot determine token account type.`);
153
+ }
154
+ }
155
+ exports.RpcAdapter = RpcAdapter;
@@ -0,0 +1,22 @@
1
+ import { Keypair, PublicKey, Transaction } from '@solana/web3.js';
2
+ export interface Signer {
3
+ getPublicKey(): PublicKey;
4
+ signTransaction(transaction: Transaction): Promise<Transaction>;
5
+ }
6
+ export declare class KeypairSigner implements Signer {
7
+ private keypair;
8
+ constructor(keypair: Keypair);
9
+ getPublicKey(): PublicKey;
10
+ signTransaction(transaction: Transaction): Promise<Transaction>;
11
+ }
12
+ /**
13
+ * Resolves home directory shorthand (~) in file paths.
14
+ */
15
+ export declare function resolveHome(filePath: string): string;
16
+ /**
17
+ * Loads a Keypair from various potential sources:
18
+ * 1. Raw Base58 private key string
19
+ * 2. JSON secret key array file path
20
+ * 3. Environment variable override
21
+ */
22
+ export declare function loadKeypair(secretOrPath: string): Keypair;
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.KeypairSigner = void 0;
40
+ exports.resolveHome = resolveHome;
41
+ exports.loadKeypair = loadKeypair;
42
+ const web3_js_1 = require("@solana/web3.js");
43
+ const fs = __importStar(require("fs"));
44
+ const path = __importStar(require("path"));
45
+ const os = __importStar(require("os"));
46
+ const bs58_1 = __importDefault(require("bs58"));
47
+ class KeypairSigner {
48
+ keypair;
49
+ constructor(keypair) {
50
+ this.keypair = keypair;
51
+ }
52
+ getPublicKey() {
53
+ return this.keypair.publicKey;
54
+ }
55
+ async signTransaction(transaction) {
56
+ transaction.partialSign(this.keypair);
57
+ return transaction;
58
+ }
59
+ }
60
+ exports.KeypairSigner = KeypairSigner;
61
+ /**
62
+ * Resolves home directory shorthand (~) in file paths.
63
+ */
64
+ function resolveHome(filePath) {
65
+ if (filePath.startsWith('~')) {
66
+ return path.join(os.homedir(), filePath.slice(1));
67
+ }
68
+ return filePath;
69
+ }
70
+ /**
71
+ * Loads a Keypair from various potential sources:
72
+ * 1. Raw Base58 private key string
73
+ * 2. JSON secret key array file path
74
+ * 3. Environment variable override
75
+ */
76
+ function loadKeypair(secretOrPath) {
77
+ // Check if it's a file path
78
+ const resolvedPath = resolveHome(secretOrPath);
79
+ if (fs.existsSync(resolvedPath)) {
80
+ try {
81
+ const fileContent = fs.readFileSync(resolvedPath, 'utf8').trim();
82
+ if (fileContent.startsWith('[') && fileContent.endsWith(']')) {
83
+ const secretKey = Uint8Array.from(JSON.parse(fileContent));
84
+ return web3_js_1.Keypair.fromSecretKey(secretKey);
85
+ }
86
+ else {
87
+ // Try decoding as base58 from file
88
+ const secretKey = bs58_1.default.decode(fileContent);
89
+ return web3_js_1.Keypair.fromSecretKey(secretKey);
90
+ }
91
+ }
92
+ catch (err) {
93
+ throw new Error(`Failed to parse keypair file at ${secretOrPath}: ${err.message}`);
94
+ }
95
+ }
96
+ // If not a file, check if it looks like a JSON array string directly
97
+ if (secretOrPath.startsWith('[') && secretOrPath.endsWith(']')) {
98
+ try {
99
+ const secretKey = Uint8Array.from(JSON.parse(secretOrPath));
100
+ return web3_js_1.Keypair.fromSecretKey(secretKey);
101
+ }
102
+ catch (err) {
103
+ throw new Error(`Failed to parse raw keypair array: ${err.message}`);
104
+ }
105
+ }
106
+ // Otherwise, try parsing as a direct base58 private key string
107
+ try {
108
+ const secretKey = bs58_1.default.decode(secretOrPath);
109
+ return web3_js_1.Keypair.fromSecretKey(secretKey);
110
+ }
111
+ catch (err) {
112
+ throw new Error(`Invalid keypair format. Must be a valid file path, JSON array, or base58 string.`);
113
+ }
114
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "solana-mint-recovery-engine",
3
+ "version": "1.0.0",
4
+ "description": "Solana Mint & Token Account Recovery Engine (SMRE) CLI tool",
5
+ "main": "dist/src/index.js",
6
+ "bin": {
7
+ "smre": "./dist/bin/smre.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "start": "ts-node bin/smre.ts",
12
+ "test": "ts-node tests/run.ts",
13
+ "prepack": "npm run build"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "dependencies": {
19
+ "@solana/spl-token": "^0.4.8",
20
+ "@solana/web3.js": "^1.95.3",
21
+ "chalk": "^4.1.2",
22
+ "cli-table3": "^0.6.5",
23
+ "commander": "^11.1.0",
24
+ "dotenv": "^16.4.5",
25
+ "ora": "^5.4.1",
26
+ "zod": "^3.23.8"
27
+ },
28
+ "devDependencies": {
29
+ "@types/jest": "^29.5.12",
30
+ "@types/node": "^20.12.7",
31
+ "jest": "^29.7.0",
32
+ "ts-jest": "^29.1.2",
33
+ "ts-node": "^10.9.2",
34
+ "typescript": "^5.4.5"
35
+ },
36
+ "jest": {
37
+ "preset": "ts-jest",
38
+ "testEnvironment": "node"
39
+ }
40
+ }