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.
- package/dist/bin/smre.d.ts +2 -0
- package/dist/bin/smre.js +106 -0
- package/dist/src/commands/build.d.ts +8 -0
- package/dist/src/commands/build.js +139 -0
- package/dist/src/commands/config.d.ts +1 -0
- package/dist/src/commands/config.js +21 -0
- package/dist/src/commands/doctor.d.ts +1 -0
- package/dist/src/commands/doctor.js +44 -0
- package/dist/src/commands/inspect.d.ts +4 -0
- package/dist/src/commands/inspect.js +76 -0
- package/dist/src/commands/recover.d.ts +7 -0
- package/dist/src/commands/recover.js +109 -0
- package/dist/src/commands/scan.d.ts +4 -0
- package/dist/src/commands/scan.js +126 -0
- package/dist/src/commands/sign.d.ts +4 -0
- package/dist/src/commands/sign.js +55 -0
- package/dist/src/commands/simulate.d.ts +3 -0
- package/dist/src/commands/simulate.js +50 -0
- package/dist/src/commands/submit.d.ts +3 -0
- package/dist/src/commands/submit.js +57 -0
- package/dist/src/core/rent.d.ts +19 -0
- package/dist/src/core/rent.js +26 -0
- package/dist/src/core/transaction.d.ts +22 -0
- package/dist/src/core/transaction.js +54 -0
- package/dist/src/core/validation.d.ts +124 -0
- package/dist/src/core/validation.js +35 -0
- package/dist/src/io/file.d.ts +23 -0
- package/dist/src/io/file.js +140 -0
- package/dist/src/io/rpc.d.ts +36 -0
- package/dist/src/io/rpc.js +155 -0
- package/dist/src/signers/signer.d.ts +22 -0
- package/dist/src/signers/signer.js +114 -0
- package/package.json +40 -0
|
@@ -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
|
+
}
|