forgex-cli 1.0.59 → 1.0.61
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 +375 -368
- package/dist/bin/forgex.d.ts +5 -5
- package/dist/bin/forgex.js +14 -14
- package/dist/bin/forgex.js.map +1 -1
- package/dist/src/adapters/codex-adapter.d.ts +90 -90
- package/dist/src/adapters/codex-adapter.d.ts.map +1 -1
- package/dist/src/adapters/codex-adapter.js +76 -76
- package/dist/src/adapters/codex-adapter.js.map +1 -1
- package/dist/src/adapters/connection.d.ts +6 -6
- package/dist/src/adapters/connection.js +8 -8
- package/dist/src/adapters/connection.js.map +1 -1
- package/dist/src/adapters/ipfs.d.ts +3 -3
- package/dist/src/adapters/ipfs.js +3 -3
- package/dist/src/adapters/jito-adapter.d.ts +85 -85
- package/dist/src/adapters/jito-adapter.d.ts.map +1 -1
- package/dist/src/adapters/jito-adapter.js +111 -111
- package/dist/src/adapters/jito-adapter.js.map +1 -1
- package/dist/src/adapters/rpc-adapter.d.ts +53 -53
- package/dist/src/adapters/rpc-adapter.d.ts.map +1 -1
- package/dist/src/adapters/rpc-adapter.js +69 -69
- package/dist/src/adapters/rpc-adapter.js.map +1 -1
- package/dist/src/adapters/sdk-adapter.d.ts +21 -21
- package/dist/src/adapters/sdk-adapter.d.ts.map +1 -1
- package/dist/src/adapters/sdk-adapter.js +79 -79
- package/dist/src/adapters/sdk-adapter.js.map +1 -1
- package/dist/src/commands/config/index.d.ts +1 -1
- package/dist/src/commands/config/index.js +15 -15
- package/dist/src/commands/config/index.js.map +1 -1
- package/dist/src/commands/query/index.d.ts +2 -2
- package/dist/src/commands/query/index.js +82 -82
- package/dist/src/commands/query/index.js.map +1 -1
- package/dist/src/commands/token/index.d.ts +8 -8
- package/dist/src/commands/token/index.js +73 -73
- package/dist/src/commands/token/index.js.map +1 -1
- package/dist/src/commands/tools/index.d.ts +9 -9
- package/dist/src/commands/tools/index.js +137 -137
- package/dist/src/commands/tools/index.js.map +1 -1
- package/dist/src/commands/trade/index.d.ts +2 -2
- package/dist/src/commands/trade/index.js +82 -82
- package/dist/src/commands/trade/index.js.map +1 -1
- package/dist/src/commands/transfer/index.d.ts +8 -8
- package/dist/src/commands/transfer/index.js +106 -106
- package/dist/src/commands/transfer/index.js.map +1 -1
- package/dist/src/commands/wallet/index.d.ts +1 -1
- package/dist/src/commands/wallet/index.js +175 -175
- package/dist/src/commands/wallet/index.js.map +1 -1
- package/dist/src/config.d.ts +26 -26
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +28 -28
- package/dist/src/config.js.map +1 -1
- package/dist/src/const/index.js +1 -1
- package/dist/src/const/index.js.map +1 -1
- package/dist/src/data-source.d.ts +81 -81
- package/dist/src/data-source.d.ts.map +1 -1
- package/dist/src/data-source.js +149 -149
- package/dist/src/data-source.js.map +1 -1
- package/dist/src/data-store/index.d.ts +22 -22
- package/dist/src/data-store/index.d.ts.map +1 -1
- package/dist/src/data-store/index.js +46 -46
- package/dist/src/data-store/index.js.map +1 -1
- package/dist/src/data-store/types.d.ts +3 -3
- package/dist/src/data-store/types.js +3 -3
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.js +10 -10
- package/dist/src/index.js.map +1 -1
- package/dist/src/output.d.ts +18 -18
- package/dist/src/output.d.ts.map +1 -1
- package/dist/src/output.js +34 -34
- package/dist/src/output.js.map +1 -1
- package/dist/src/shims/store.d.ts +3 -2
- package/dist/src/shims/store.d.ts.map +1 -1
- package/dist/src/shims/store.js +6 -5
- package/dist/src/shims/store.js.map +1 -1
- package/dist/src/sol-sdk/batch/create.d.ts +4 -1
- package/dist/src/sol-sdk/batch/create.d.ts.map +1 -1
- package/dist/src/sol-sdk/batch/create.js +44 -44
- package/dist/src/sol-sdk/batch/create.js.map +1 -1
- package/dist/src/sol-sdk/batch/index.js +135 -135
- package/dist/src/sol-sdk/batch/index.js.map +1 -1
- package/dist/src/sol-sdk/calc.d.ts +63 -63
- package/dist/src/sol-sdk/calc.d.ts.map +1 -1
- package/dist/src/sol-sdk/calc.js +120 -120
- package/dist/src/sol-sdk/calc.js.map +1 -1
- package/dist/src/sol-sdk/jito/index.js +12 -12
- package/dist/src/sol-sdk/jito/index.js.map +1 -1
- package/dist/src/sol-sdk/launchlab/instructions/create.js +10 -10
- package/dist/src/sol-sdk/launchlab/instructions/create.js.map +1 -1
- package/dist/src/sol-sdk/meteora/index.d.ts +5 -5
- package/dist/src/sol-sdk/meteora/index.js +11 -11
- package/dist/src/sol-sdk/meteora/index.js.map +1 -1
- package/dist/src/sol-sdk/meteora/instructions/buy.js +8 -8
- package/dist/src/sol-sdk/meteora/instructions/buy.js.map +1 -1
- package/dist/src/sol-sdk/meteora/instructions/sell.js +6 -6
- package/dist/src/sol-sdk/meteora/instructions/sell.js.map +1 -1
- package/dist/src/sol-sdk/pump/index.js +3 -3
- package/dist/src/sol-sdk/pump/index.js.map +1 -1
- package/dist/src/sol-sdk/pump/instructions/buy.d.ts +12 -12
- package/dist/src/sol-sdk/pump/instructions/buy.d.ts.map +1 -1
- package/dist/src/sol-sdk/pump/instructions/buy.js +26 -26
- package/dist/src/sol-sdk/pump/instructions/buy.js.map +1 -1
- package/dist/src/sol-sdk/pump/instructions/createAndBuy.d.ts +13 -13
- package/dist/src/sol-sdk/pump/instructions/createAndBuy.js +17 -17
- package/dist/src/sol-sdk/pump/instructions/createAndBuy.js.map +1 -1
- package/dist/src/sol-sdk/pump/instructions/sell.d.ts +2 -2
- package/dist/src/sol-sdk/pump/instructions/sell.d.ts.map +1 -1
- package/dist/src/sol-sdk/pump/instructions/sell.js +7 -7
- package/dist/src/sol-sdk/pump/instructions/sell.js.map +1 -1
- package/dist/src/sol-sdk/pumpswap/index.d.ts +4 -4
- package/dist/src/sol-sdk/pumpswap/index.js +5 -5
- package/dist/src/sol-sdk/pumpswap/index.js.map +1 -1
- package/dist/src/sol-sdk/pumpswap/instructions/buy.d.ts +8 -8
- package/dist/src/sol-sdk/pumpswap/instructions/buy.js +19 -19
- package/dist/src/sol-sdk/pumpswap/instructions/buy.js.map +1 -1
- package/dist/src/sol-sdk/pumpswap/instructions/migrate.js +2 -2
- package/dist/src/sol-sdk/pumpswap/instructions/migrate.js.map +1 -1
- package/dist/src/sol-sdk/pumpswap/instructions/sell.js +4 -4
- package/dist/src/sol-sdk/pumpswap/instructions/sell.js.map +1 -1
- package/dist/src/sol-sdk/pumpswap/rpc/index.js +1 -1
- package/dist/src/sol-sdk/pumpswap/rpc/index.js.map +1 -1
- package/dist/src/sol-sdk/raydium/instructions/cpmmSell.js +3 -3
- package/dist/src/sol-sdk/raydium/instructions/cpmmSell.js.map +1 -1
- package/dist/src/sol-sdk/raydium/instructions/sell.d.ts +40 -8520
- package/dist/src/sol-sdk/raydium/instructions/sell.d.ts.map +1 -1
- package/dist/src/sol-sdk/raydium/instructions/sell.js +6 -6
- package/dist/src/sol-sdk/raydium/instructions/sell.js.map +1 -1
- package/dist/src/sol-sdk/raydium/rpc/index.d.ts +4 -4
- package/dist/src/sol-sdk/rpc/index.d.ts +14 -14
- package/dist/src/sol-sdk/rpc/index.d.ts.map +1 -1
- package/dist/src/sol-sdk/rpc/index.js +17 -17
- package/dist/src/sol-sdk/rpc/index.js.map +1 -1
- package/dist/src/sol-sdk/transfer/index.js +5 -5
- package/dist/src/sol-sdk/transfer/index.js.map +1 -1
- package/dist/src/sol-sdk/turnover/index.d.ts +3 -3
- package/dist/src/sol-sdk/turnover/index.js +56 -56
- package/dist/src/sol-sdk/turnover/index.js.map +1 -1
- package/dist/src/telemetry.d.ts +8 -8
- package/dist/src/telemetry.d.ts.map +1 -1
- package/dist/src/telemetry.js +25 -25
- package/dist/src/telemetry.js.map +1 -1
- package/dist/src/tx-tracker/detail-adapter.d.ts +53 -53
- package/dist/src/tx-tracker/detail-adapter.d.ts.map +1 -1
- package/dist/src/tx-tracker/detail-adapter.js +68 -68
- package/dist/src/tx-tracker/detail-adapter.js.map +1 -1
- package/dist/src/tx-tracker/index.d.ts +67 -67
- package/dist/src/tx-tracker/index.d.ts.map +1 -1
- package/dist/src/tx-tracker/index.js +103 -103
- package/dist/src/tx-tracker/index.js.map +1 -1
- package/dist/src/types/index.d.ts +10 -10
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/types/websocket.js +1 -1
- package/dist/src/types/websocket.js.map +1 -1
- package/dist/src/utils/index.js +20 -20
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/wallet-store.d.ts +51 -51
- package/dist/src/wallet-store.d.ts.map +1 -1
- package/dist/src/wallet-store.js +104 -104
- package/dist/src/wallet-store.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Wallet group management command group
|
|
3
3
|
*
|
|
4
4
|
* forgex wallet create-group | list-groups | group-info | delete-group |
|
|
5
5
|
* generate | add | remove | import | export |
|
|
@@ -15,48 +15,48 @@ import { output, success, error, warn, getOutputFormat } from '../../output.js';
|
|
|
15
15
|
export function registerWalletCommands(program) {
|
|
16
16
|
const walletCmd = program
|
|
17
17
|
.command('wallet')
|
|
18
|
-
.description('
|
|
18
|
+
.description('Wallet group management');
|
|
19
19
|
// ============================================================
|
|
20
20
|
// forgex wallet create-group
|
|
21
21
|
// ============================================================
|
|
22
22
|
walletCmd
|
|
23
23
|
.command('create-group')
|
|
24
|
-
.description('
|
|
25
|
-
.requiredOption('--name <name>', '
|
|
26
|
-
.option('--type <type>', '
|
|
27
|
-
.option('--remark <remark>', '
|
|
28
|
-
.option('--monitor-type <type>', '
|
|
29
|
-
.option('--monitor-ca <ca>', '
|
|
30
|
-
.option('--filter-group-ids <ids>', '
|
|
24
|
+
.description('Create wallet group')
|
|
25
|
+
.requiredOption('--name <name>', 'Wallet group name')
|
|
26
|
+
.option('--type <type>', 'Type: local | monitor', 'local')
|
|
27
|
+
.option('--remark <remark>', 'Remark', '')
|
|
28
|
+
.option('--monitor-type <type>', 'Monitor type: normal | top100 | retail', 'normal')
|
|
29
|
+
.option('--monitor-ca <ca>', 'Monitor token CA')
|
|
30
|
+
.option('--filter-group-ids <ids>', 'Filter wallet group IDs (comma-separated)')
|
|
31
31
|
.action(async (options) => {
|
|
32
32
|
try {
|
|
33
|
-
//
|
|
33
|
+
// Validate --type parameter
|
|
34
34
|
if (options.type !== 'local' && options.type !== 'monitor') {
|
|
35
|
-
error(
|
|
35
|
+
error(`Invalid type "${options.type}", only local or monitor supported`);
|
|
36
36
|
process.exit(1);
|
|
37
37
|
}
|
|
38
38
|
const groupType = options.type;
|
|
39
|
-
//
|
|
39
|
+
// Validate --monitor-type parameter
|
|
40
40
|
const validMonitorTypes = ['normal', 'top100', 'retail'];
|
|
41
41
|
if (!validMonitorTypes.includes(options.monitorType)) {
|
|
42
|
-
error(
|
|
42
|
+
error(`Invalid monitor type "${options.monitorType}", only normal, top100 or retail supported`);
|
|
43
43
|
process.exit(1);
|
|
44
44
|
}
|
|
45
45
|
const monitorType = options.monitorType;
|
|
46
46
|
const filterGroupIds = options.filterGroupIds
|
|
47
47
|
? options.filterGroupIds.split(',')
|
|
48
48
|
: [];
|
|
49
|
-
//
|
|
49
|
+
// Validate filterGroupIds values are valid numbers
|
|
50
50
|
const filterGroupIdsNum = filterGroupIds.map(Number);
|
|
51
51
|
if (filterGroupIdsNum.some(isNaN)) {
|
|
52
|
-
error('--filter-group-ids
|
|
52
|
+
error('--filter-group-ids contains invalid IDs, use comma-separated numbers');
|
|
53
53
|
process.exit(1);
|
|
54
54
|
}
|
|
55
|
-
//
|
|
55
|
+
// Local-only: generate local group ID
|
|
56
56
|
const existingGroups = getAllGroups();
|
|
57
57
|
const maxId = existingGroups.reduce((max, g) => Math.max(max, g.groupId), 0);
|
|
58
58
|
const groupId = maxId + 1;
|
|
59
|
-
//
|
|
59
|
+
// Local storage
|
|
60
60
|
const newGroup = {
|
|
61
61
|
name: options.name,
|
|
62
62
|
groupId,
|
|
@@ -76,7 +76,7 @@ export function registerWalletCommands(program) {
|
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
78
|
catch (e) {
|
|
79
|
-
error('
|
|
79
|
+
error('Failed to create wallet group', e.message);
|
|
80
80
|
process.exit(1);
|
|
81
81
|
}
|
|
82
82
|
});
|
|
@@ -85,13 +85,13 @@ export function registerWalletCommands(program) {
|
|
|
85
85
|
// ============================================================
|
|
86
86
|
walletCmd
|
|
87
87
|
.command('list-groups')
|
|
88
|
-
.description('
|
|
89
|
-
.option('--type <type>', '
|
|
88
|
+
.description('List all wallet groups')
|
|
89
|
+
.option('--type <type>', 'Filter by type: local | monitor')
|
|
90
90
|
.action((options) => {
|
|
91
91
|
try {
|
|
92
|
-
//
|
|
92
|
+
// Validate --type parameter
|
|
93
93
|
if (options.type && options.type !== 'local' && options.type !== 'monitor') {
|
|
94
|
-
error(
|
|
94
|
+
error(`Invalid type "${options.type}", only local or monitor supported`);
|
|
95
95
|
process.exit(1);
|
|
96
96
|
}
|
|
97
97
|
let groups = getAllGroups();
|
|
@@ -99,8 +99,8 @@ export function registerWalletCommands(program) {
|
|
|
99
99
|
groups = groups.filter(g => g.groupType === options.type);
|
|
100
100
|
}
|
|
101
101
|
if (groups.length === 0) {
|
|
102
|
-
const filterMsg = options.type ?
|
|
103
|
-
warn(
|
|
102
|
+
const filterMsg = options.type ? `(type: ${options.type})` : '';
|
|
103
|
+
warn(`No wallet groups found${filterMsg}`);
|
|
104
104
|
}
|
|
105
105
|
const result = groups.map(g => ({
|
|
106
106
|
groupId: g.groupId,
|
|
@@ -113,16 +113,16 @@ export function registerWalletCommands(program) {
|
|
|
113
113
|
output(result, {
|
|
114
114
|
columns: [
|
|
115
115
|
{ key: 'groupId', header: 'ID' },
|
|
116
|
-
{ key: 'name', header: '
|
|
117
|
-
{ key: 'type', header: '
|
|
118
|
-
{ key: 'walletCount', header: '
|
|
119
|
-
{ key: 'monitorType', header: '
|
|
120
|
-
{ key: 'note', header: '
|
|
116
|
+
{ key: 'name', header: 'Name' },
|
|
117
|
+
{ key: 'type', header: 'Type' },
|
|
118
|
+
{ key: 'walletCount', header: 'Wallet Count' },
|
|
119
|
+
{ key: 'monitorType', header: 'Monitor Type' },
|
|
120
|
+
{ key: 'note', header: 'Note' },
|
|
121
121
|
],
|
|
122
122
|
});
|
|
123
123
|
}
|
|
124
124
|
catch (e) {
|
|
125
|
-
error('
|
|
125
|
+
error('Failed to list wallet groups', e.message);
|
|
126
126
|
process.exit(1);
|
|
127
127
|
}
|
|
128
128
|
});
|
|
@@ -131,24 +131,24 @@ export function registerWalletCommands(program) {
|
|
|
131
131
|
// ============================================================
|
|
132
132
|
walletCmd
|
|
133
133
|
.command('group-info')
|
|
134
|
-
.description('
|
|
135
|
-
.requiredOption('--id <groupId>', '
|
|
136
|
-
.option('--show-keys', '
|
|
134
|
+
.description('View wallet group details')
|
|
135
|
+
.requiredOption('--id <groupId>', 'Wallet group ID')
|
|
136
|
+
.option('--show-keys', 'Show private keys (dangerous)', false)
|
|
137
137
|
.action(async (options) => {
|
|
138
138
|
try {
|
|
139
139
|
const groupId = Number(options.id);
|
|
140
140
|
if (isNaN(groupId)) {
|
|
141
|
-
error(
|
|
141
|
+
error(`Invalid wallet group ID "${options.id}", provide a numeric ID`);
|
|
142
142
|
process.exit(1);
|
|
143
143
|
}
|
|
144
|
-
//
|
|
144
|
+
// If showing private keys, ensure password is set and correct first
|
|
145
145
|
if (options.showKeys) {
|
|
146
|
-
warn('
|
|
146
|
+
warn('Warning: About to display plaintext private keys. Ensure a secure environment. Never use this command in public or screen-sharing sessions');
|
|
147
147
|
await ensurePasswordAndValidate();
|
|
148
148
|
}
|
|
149
149
|
const group = getGroup(groupId);
|
|
150
150
|
if (!group) {
|
|
151
|
-
error(
|
|
151
|
+
error(`Wallet group ${groupId} does not exist`);
|
|
152
152
|
process.exit(1);
|
|
153
153
|
}
|
|
154
154
|
const walletsList = group.wallets.map(w => {
|
|
@@ -173,7 +173,7 @@ export function registerWalletCommands(program) {
|
|
|
173
173
|
output(result);
|
|
174
174
|
}
|
|
175
175
|
catch (e) {
|
|
176
|
-
error('
|
|
176
|
+
error('Failed to get wallet group details', e.message);
|
|
177
177
|
process.exit(1);
|
|
178
178
|
}
|
|
179
179
|
});
|
|
@@ -182,40 +182,40 @@ export function registerWalletCommands(program) {
|
|
|
182
182
|
// ============================================================
|
|
183
183
|
walletCmd
|
|
184
184
|
.command('delete-group')
|
|
185
|
-
.description('
|
|
186
|
-
.requiredOption('--id <groupId>', '
|
|
187
|
-
.option('--force', '
|
|
185
|
+
.description('Delete wallet group')
|
|
186
|
+
.requiredOption('--id <groupId>', 'Wallet group ID')
|
|
187
|
+
.option('--force', 'Skip confirmation', false)
|
|
188
188
|
.action(async (options) => {
|
|
189
189
|
try {
|
|
190
190
|
const groupId = Number(options.id);
|
|
191
191
|
if (isNaN(groupId)) {
|
|
192
|
-
error(
|
|
192
|
+
error(`Invalid wallet group ID "${options.id}", provide a numeric ID`);
|
|
193
193
|
process.exit(1);
|
|
194
194
|
}
|
|
195
|
-
//
|
|
195
|
+
// Check if wallet group exists locally
|
|
196
196
|
const group = getGroup(groupId);
|
|
197
197
|
if (!group) {
|
|
198
|
-
error(
|
|
198
|
+
error(`Wallet group ${groupId} does not exist`);
|
|
199
199
|
process.exit(1);
|
|
200
200
|
}
|
|
201
|
-
//
|
|
201
|
+
// In non --force mode, prompt user for confirmation
|
|
202
202
|
if (!options.force) {
|
|
203
203
|
const { confirm } = await import('@inquirer/prompts');
|
|
204
204
|
const confirmed = await confirm({
|
|
205
|
-
message:
|
|
205
|
+
message: `Confirm delete wallet group "${group.name}" (ID: ${groupId}, ${group.wallets.length} wallets)? This action is irreversible.`,
|
|
206
206
|
default: false,
|
|
207
207
|
});
|
|
208
208
|
if (!confirmed) {
|
|
209
|
-
warn('
|
|
209
|
+
warn('Delete operation cancelled');
|
|
210
210
|
return;
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
|
-
//
|
|
213
|
+
// Pure local deletion
|
|
214
214
|
removeGroup(groupId);
|
|
215
|
-
output({ success: true, groupId, message: '
|
|
215
|
+
output({ success: true, groupId, message: 'Wallet group deleted' });
|
|
216
216
|
}
|
|
217
217
|
catch (e) {
|
|
218
|
-
error('
|
|
218
|
+
error('Failed to delete wallet group', e.message);
|
|
219
219
|
process.exit(1);
|
|
220
220
|
}
|
|
221
221
|
});
|
|
@@ -224,40 +224,40 @@ export function registerWalletCommands(program) {
|
|
|
224
224
|
// ============================================================
|
|
225
225
|
walletCmd
|
|
226
226
|
.command('generate')
|
|
227
|
-
.description('
|
|
228
|
-
.requiredOption('--group <groupId>', '
|
|
229
|
-
.option('--count <n>', '
|
|
227
|
+
.description('Generate new wallets')
|
|
228
|
+
.requiredOption('--group <groupId>', 'Target wallet group ID')
|
|
229
|
+
.option('--count <n>', 'Count to generate', '1')
|
|
230
230
|
.action(async (options) => {
|
|
231
231
|
try {
|
|
232
232
|
await ensurePasswordAndValidate();
|
|
233
233
|
const groupId = Number(options.group);
|
|
234
234
|
if (isNaN(groupId)) {
|
|
235
|
-
error(
|
|
235
|
+
error(`Invalid wallet group ID "${options.group}", provide a numeric ID`);
|
|
236
236
|
process.exit(1);
|
|
237
237
|
}
|
|
238
238
|
const count = Number(options.count);
|
|
239
239
|
if (isNaN(count) || !Number.isInteger(count) || count <= 0) {
|
|
240
|
-
error(
|
|
240
|
+
error(`Invalid count "${options.count}", provide a positive integer`);
|
|
241
241
|
process.exit(1);
|
|
242
242
|
}
|
|
243
243
|
const group = getGroup(groupId);
|
|
244
244
|
if (!group) {
|
|
245
|
-
error(
|
|
245
|
+
error(`Wallet group ${groupId} does not exist`);
|
|
246
246
|
process.exit(1);
|
|
247
247
|
}
|
|
248
248
|
if (group.wallets.length + count > 100) {
|
|
249
|
-
error(
|
|
249
|
+
error(`Wallet count exceeded: currently ${group.wallets.length}, max ${100 - group.wallets.length} more (limit 100)`);
|
|
250
250
|
process.exit(1);
|
|
251
251
|
}
|
|
252
252
|
const wallets = generateWallets(count);
|
|
253
|
-
//
|
|
253
|
+
// Save plaintext private keys for output (addWalletsToGroup may encrypt privateKey in wallets)
|
|
254
254
|
const plaintextKeys = wallets.map(w => ({
|
|
255
255
|
address: w.walletAddress,
|
|
256
256
|
privateKey: w.privateKey,
|
|
257
257
|
}));
|
|
258
|
-
//
|
|
258
|
+
// Pure local storage (if store is encrypted, this function encrypts privateKey fields)
|
|
259
259
|
addWalletsToGroup(groupId, wallets);
|
|
260
|
-
// Bug 1 fix: JSON
|
|
260
|
+
// Bug 1 fix: JSON keeps original structure; table/minimal use columns to display generated wallet list
|
|
261
261
|
const fmt = getOutputFormat();
|
|
262
262
|
if (fmt === 'json') {
|
|
263
263
|
output({
|
|
@@ -267,7 +267,7 @@ export function registerWalletCommands(program) {
|
|
|
267
267
|
});
|
|
268
268
|
}
|
|
269
269
|
else {
|
|
270
|
-
success(
|
|
270
|
+
success(`Generated ${plaintextKeys.length} wallets to group ${groupId}`);
|
|
271
271
|
output(plaintextKeys.map((w, i) => ({
|
|
272
272
|
index: i + 1,
|
|
273
273
|
address: w.address,
|
|
@@ -275,14 +275,14 @@ export function registerWalletCommands(program) {
|
|
|
275
275
|
})), {
|
|
276
276
|
columns: [
|
|
277
277
|
{ key: 'index', header: '#' },
|
|
278
|
-
{ key: 'address', header: '
|
|
279
|
-
{ key: 'privateKey', header: '
|
|
278
|
+
{ key: 'address', header: 'Address' },
|
|
279
|
+
{ key: 'privateKey', header: 'Private Key' },
|
|
280
280
|
],
|
|
281
281
|
});
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
catch (e) {
|
|
285
|
-
error('
|
|
285
|
+
error('Failed to generate wallets', e.message);
|
|
286
286
|
process.exit(1);
|
|
287
287
|
}
|
|
288
288
|
});
|
|
@@ -291,37 +291,37 @@ export function registerWalletCommands(program) {
|
|
|
291
291
|
// ============================================================
|
|
292
292
|
walletCmd
|
|
293
293
|
.command('add')
|
|
294
|
-
.description('
|
|
295
|
-
.requiredOption('--group <groupId>', '
|
|
296
|
-
.requiredOption('--private-key <key>', '
|
|
297
|
-
.option('--note <note>', '
|
|
294
|
+
.description('Add wallet to group')
|
|
295
|
+
.requiredOption('--group <groupId>', 'Target wallet group ID')
|
|
296
|
+
.requiredOption('--private-key <key>', 'Private key (Base58 encoded)')
|
|
297
|
+
.option('--note <note>', 'Note', '')
|
|
298
298
|
.action(async (options) => {
|
|
299
299
|
try {
|
|
300
300
|
await ensurePasswordAndValidate();
|
|
301
301
|
const groupId = Number(options.group);
|
|
302
302
|
if (isNaN(groupId)) {
|
|
303
|
-
error(
|
|
303
|
+
error(`Invalid wallet group ID "${options.group}", provide a numeric ID`);
|
|
304
304
|
process.exit(1);
|
|
305
305
|
}
|
|
306
|
-
//
|
|
306
|
+
// Verify wallet group exists
|
|
307
307
|
const group = getGroup(groupId);
|
|
308
308
|
if (!group) {
|
|
309
|
-
error(
|
|
309
|
+
error(`Wallet group ${groupId} does not exist`);
|
|
310
310
|
process.exit(1);
|
|
311
311
|
}
|
|
312
|
-
//
|
|
312
|
+
// Validate private key validity
|
|
313
313
|
let wallet;
|
|
314
314
|
try {
|
|
315
315
|
wallet = walletFromPrivateKey(options.privateKey, options.note);
|
|
316
316
|
}
|
|
317
317
|
catch {
|
|
318
|
-
error('
|
|
318
|
+
error('Invalid private key: provide a valid Base58 encoded private key');
|
|
319
319
|
process.exit(1);
|
|
320
320
|
}
|
|
321
|
-
//
|
|
321
|
+
// Check if address already exists in group
|
|
322
322
|
const exists = group.wallets.some(w => w.walletAddress === wallet.walletAddress);
|
|
323
323
|
if (exists) {
|
|
324
|
-
warn(
|
|
324
|
+
warn(`Wallet ${wallet.walletAddress} already exists in group ${groupId}, skipping duplicate`);
|
|
325
325
|
output({
|
|
326
326
|
success: true,
|
|
327
327
|
groupId,
|
|
@@ -330,7 +330,7 @@ export function registerWalletCommands(program) {
|
|
|
330
330
|
});
|
|
331
331
|
return;
|
|
332
332
|
}
|
|
333
|
-
//
|
|
333
|
+
// Pure local storage
|
|
334
334
|
addWalletsToGroup(groupId, [wallet]);
|
|
335
335
|
output({
|
|
336
336
|
success: true,
|
|
@@ -339,7 +339,7 @@ export function registerWalletCommands(program) {
|
|
|
339
339
|
});
|
|
340
340
|
}
|
|
341
341
|
catch (e) {
|
|
342
|
-
error('
|
|
342
|
+
error('Failed to add wallet', e.message);
|
|
343
343
|
process.exit(1);
|
|
344
344
|
}
|
|
345
345
|
});
|
|
@@ -348,29 +348,29 @@ export function registerWalletCommands(program) {
|
|
|
348
348
|
// ============================================================
|
|
349
349
|
walletCmd
|
|
350
350
|
.command('remove')
|
|
351
|
-
.description('
|
|
352
|
-
.requiredOption('--group <groupId>', '
|
|
353
|
-
.requiredOption('--address <addr>', '
|
|
351
|
+
.description('Remove wallet from group')
|
|
352
|
+
.requiredOption('--group <groupId>', 'Target wallet group ID')
|
|
353
|
+
.requiredOption('--address <addr>', 'Wallet address')
|
|
354
354
|
.action(async (options) => {
|
|
355
355
|
try {
|
|
356
356
|
const groupId = Number(options.group);
|
|
357
357
|
if (isNaN(groupId)) {
|
|
358
|
-
error(
|
|
358
|
+
error(`Invalid wallet group ID "${options.group}", provide a numeric ID`);
|
|
359
359
|
process.exit(1);
|
|
360
360
|
}
|
|
361
|
-
//
|
|
361
|
+
// Verify wallet group exists
|
|
362
362
|
const group = getGroup(groupId);
|
|
363
363
|
if (!group) {
|
|
364
|
-
error(
|
|
364
|
+
error(`Wallet group ${groupId} does not exist`);
|
|
365
365
|
process.exit(1);
|
|
366
366
|
}
|
|
367
|
-
//
|
|
367
|
+
// Verify address exists in wallet group
|
|
368
368
|
const walletExists = group.wallets.some(w => w.walletAddress === options.address);
|
|
369
369
|
if (!walletExists) {
|
|
370
|
-
error(
|
|
370
|
+
error(`Wallet ${options.address} is not in wallet group ${groupId}`);
|
|
371
371
|
process.exit(1);
|
|
372
372
|
}
|
|
373
|
-
//
|
|
373
|
+
// Pure local removal
|
|
374
374
|
removeWalletsFromGroup(groupId, [options.address]);
|
|
375
375
|
output({
|
|
376
376
|
success: true,
|
|
@@ -379,7 +379,7 @@ export function registerWalletCommands(program) {
|
|
|
379
379
|
});
|
|
380
380
|
}
|
|
381
381
|
catch (e) {
|
|
382
|
-
error('
|
|
382
|
+
error('Failed to remove wallet', e.message);
|
|
383
383
|
process.exit(1);
|
|
384
384
|
}
|
|
385
385
|
});
|
|
@@ -388,35 +388,35 @@ export function registerWalletCommands(program) {
|
|
|
388
388
|
// ============================================================
|
|
389
389
|
walletCmd
|
|
390
390
|
.command('import')
|
|
391
|
-
.description('
|
|
392
|
-
.requiredOption('--group <groupId>', '
|
|
393
|
-
.requiredOption('--file <csvPath>', 'CSV
|
|
391
|
+
.description('Import wallets from CSV file')
|
|
392
|
+
.requiredOption('--group <groupId>', 'Target wallet group ID')
|
|
393
|
+
.requiredOption('--file <csvPath>', 'CSV file path')
|
|
394
394
|
.action(async (options) => {
|
|
395
395
|
try {
|
|
396
396
|
await ensurePasswordAndValidate();
|
|
397
397
|
const groupId = Number(options.group);
|
|
398
398
|
if (isNaN(groupId) || !Number.isInteger(groupId) || groupId <= 0) {
|
|
399
|
-
error(
|
|
399
|
+
error(`Invalid wallet group ID "${options.group}", provide a positive integer`);
|
|
400
400
|
process.exit(1);
|
|
401
401
|
}
|
|
402
|
-
//
|
|
402
|
+
// Verify wallet group exists
|
|
403
403
|
const group = getGroup(groupId);
|
|
404
404
|
if (!group) {
|
|
405
|
-
error(
|
|
405
|
+
error(`Wallet group ${groupId} does not exist`);
|
|
406
406
|
process.exit(1);
|
|
407
407
|
}
|
|
408
|
-
//
|
|
408
|
+
// Verify file exists
|
|
409
409
|
if (!fs.existsSync(options.file)) {
|
|
410
|
-
error(`CSV
|
|
410
|
+
error(`CSV file does not exist: ${options.file}`);
|
|
411
411
|
process.exit(1);
|
|
412
412
|
}
|
|
413
413
|
const csvContent = fs.readFileSync(options.file, 'utf-8');
|
|
414
414
|
const wallets = importWalletsFromCsv(csvContent);
|
|
415
415
|
if (wallets.length === 0) {
|
|
416
|
-
error('CSV
|
|
416
|
+
error('No valid wallet data in CSV file');
|
|
417
417
|
process.exit(1);
|
|
418
418
|
}
|
|
419
|
-
//
|
|
419
|
+
// Pure local storage
|
|
420
420
|
addWalletsToGroup(groupId, wallets);
|
|
421
421
|
output({
|
|
422
422
|
success: true,
|
|
@@ -426,7 +426,7 @@ export function registerWalletCommands(program) {
|
|
|
426
426
|
});
|
|
427
427
|
}
|
|
428
428
|
catch (e) {
|
|
429
|
-
error('
|
|
429
|
+
error('Failed to import wallets', e.message);
|
|
430
430
|
process.exit(1);
|
|
431
431
|
}
|
|
432
432
|
});
|
|
@@ -435,20 +435,20 @@ export function registerWalletCommands(program) {
|
|
|
435
435
|
// ============================================================
|
|
436
436
|
walletCmd
|
|
437
437
|
.command('export')
|
|
438
|
-
.description('
|
|
439
|
-
.requiredOption('--group <groupId>', '
|
|
440
|
-
.requiredOption('--file <csvPath>', '
|
|
438
|
+
.description('Export wallet group as CSV file')
|
|
439
|
+
.requiredOption('--group <groupId>', 'Wallet group ID')
|
|
440
|
+
.requiredOption('--file <csvPath>', 'Output file path')
|
|
441
441
|
.action(async (options) => {
|
|
442
442
|
try {
|
|
443
443
|
await ensurePasswordAndValidate();
|
|
444
444
|
const groupId = Number(options.group);
|
|
445
445
|
if (isNaN(groupId) || !Number.isInteger(groupId) || groupId <= 0) {
|
|
446
|
-
error(
|
|
446
|
+
error(`Invalid wallet group ID "${options.group}", provide a positive integer`);
|
|
447
447
|
process.exit(1);
|
|
448
448
|
}
|
|
449
449
|
const csv = exportGroupToCsv(groupId);
|
|
450
450
|
if (!csv) {
|
|
451
|
-
error(
|
|
451
|
+
error(`Wallet group ${groupId} does not exist`);
|
|
452
452
|
process.exit(1);
|
|
453
453
|
}
|
|
454
454
|
fs.writeFileSync(options.file, csv, { encoding: 'utf-8', mode: 0o600 });
|
|
@@ -459,7 +459,7 @@ export function registerWalletCommands(program) {
|
|
|
459
459
|
});
|
|
460
460
|
}
|
|
461
461
|
catch (e) {
|
|
462
|
-
error('
|
|
462
|
+
error('Failed to export wallets', e.message);
|
|
463
463
|
process.exit(1);
|
|
464
464
|
}
|
|
465
465
|
});
|
|
@@ -468,33 +468,33 @@ export function registerWalletCommands(program) {
|
|
|
468
468
|
// ============================================================
|
|
469
469
|
walletCmd
|
|
470
470
|
.command('import-group')
|
|
471
|
-
.description('
|
|
472
|
-
.requiredOption('--file <jsonPath>', 'JSON
|
|
473
|
-
.option('--password <pwd>', '
|
|
471
|
+
.description('Import wallet groups from JSON file')
|
|
472
|
+
.requiredOption('--file <jsonPath>', 'JSON file path')
|
|
473
|
+
.option('--password <pwd>', 'Decryption password (required for encrypted files)')
|
|
474
474
|
.action(async (options) => {
|
|
475
475
|
try {
|
|
476
476
|
await ensurePasswordAndValidate();
|
|
477
|
-
//
|
|
477
|
+
// Verify file exists
|
|
478
478
|
if (!fs.existsSync(options.file)) {
|
|
479
|
-
error(`JSON
|
|
479
|
+
error(`JSON file does not exist: ${options.file}`);
|
|
480
480
|
process.exit(1);
|
|
481
481
|
}
|
|
482
482
|
const content = fs.readFileSync(options.file, 'utf-8');
|
|
483
483
|
const groups = importGroupsFromJson(content, options.password);
|
|
484
484
|
if (!groups || groups.length === 0) {
|
|
485
|
-
error('JSON
|
|
485
|
+
error('No valid wallet group data in JSON file (if encrypted, check --password)');
|
|
486
486
|
process.exit(1);
|
|
487
487
|
}
|
|
488
|
-
//
|
|
488
|
+
// Pure local storage (ensure private keys are encrypted via saveGroup + addWalletsToGroup)
|
|
489
489
|
const existingGroups = getAllGroups();
|
|
490
490
|
let nextLocalId = existingGroups.reduce((max, g) => Math.max(max, g.groupId), 0) + 1;
|
|
491
491
|
groups.forEach((g) => {
|
|
492
492
|
const newId = nextLocalId++;
|
|
493
493
|
const wallets = g.wallets;
|
|
494
494
|
g.groupId = newId;
|
|
495
|
-
g.wallets = []; //
|
|
495
|
+
g.wallets = []; // Save empty wallet group first
|
|
496
496
|
saveGroup(g);
|
|
497
|
-
//
|
|
497
|
+
// Add wallets via addWalletsToGroup, which handles encryption automatically
|
|
498
498
|
if (wallets.length > 0) {
|
|
499
499
|
addWalletsToGroup(newId, wallets);
|
|
500
500
|
}
|
|
@@ -506,7 +506,7 @@ export function registerWalletCommands(program) {
|
|
|
506
506
|
});
|
|
507
507
|
}
|
|
508
508
|
catch (e) {
|
|
509
|
-
error('
|
|
509
|
+
error('Failed to import wallet groups', e.message);
|
|
510
510
|
process.exit(1);
|
|
511
511
|
}
|
|
512
512
|
});
|
|
@@ -515,16 +515,16 @@ export function registerWalletCommands(program) {
|
|
|
515
515
|
// ============================================================
|
|
516
516
|
walletCmd
|
|
517
517
|
.command('export-group')
|
|
518
|
-
.description('
|
|
519
|
-
.requiredOption('--file <jsonPath>', '
|
|
520
|
-
.option('--encrypt', '
|
|
521
|
-
.option('--password <pwd>', '
|
|
518
|
+
.description('Export all wallet groups as JSON file')
|
|
519
|
+
.requiredOption('--file <jsonPath>', 'Output file path')
|
|
520
|
+
.option('--encrypt', 'Encrypted export', false)
|
|
521
|
+
.option('--password <pwd>', 'Encryption password')
|
|
522
522
|
.action(async (options) => {
|
|
523
523
|
try {
|
|
524
524
|
await ensurePasswordAndValidate();
|
|
525
525
|
const groups = getAllGroups();
|
|
526
526
|
if (groups.length === 0) {
|
|
527
|
-
warn('
|
|
527
|
+
warn('No wallet groups to export');
|
|
528
528
|
output({ success: true, file: options.file, encrypted: options.encrypt, groupCount: 0 });
|
|
529
529
|
return;
|
|
530
530
|
}
|
|
@@ -532,7 +532,7 @@ export function registerWalletCommands(program) {
|
|
|
532
532
|
if (options.encrypt) {
|
|
533
533
|
const password = options.password || process.env.FORGEX_PASSWORD;
|
|
534
534
|
if (!password) {
|
|
535
|
-
error('
|
|
535
|
+
error('Encrypted export requires password, use --password or set FORGEX_PASSWORD env var');
|
|
536
536
|
process.exit(1);
|
|
537
537
|
}
|
|
538
538
|
content = exportEncryptedGroupsJson(password);
|
|
@@ -549,7 +549,7 @@ export function registerWalletCommands(program) {
|
|
|
549
549
|
});
|
|
550
550
|
}
|
|
551
551
|
catch (e) {
|
|
552
|
-
error('
|
|
552
|
+
error('Failed to export wallet groups', e.message);
|
|
553
553
|
process.exit(1);
|
|
554
554
|
}
|
|
555
555
|
});
|
|
@@ -558,30 +558,30 @@ export function registerWalletCommands(program) {
|
|
|
558
558
|
// ============================================================
|
|
559
559
|
walletCmd
|
|
560
560
|
.command('overview')
|
|
561
|
-
.description('
|
|
562
|
-
.requiredOption('--groups <ids>', '
|
|
563
|
-
.option('--token <ca>', '
|
|
561
|
+
.description('View wallet group overview')
|
|
562
|
+
.requiredOption('--groups <ids>', 'Wallet group IDs (comma-separated)')
|
|
563
|
+
.option('--token <ca>', 'Filter by token')
|
|
564
564
|
.action(async (options) => {
|
|
565
565
|
try {
|
|
566
566
|
const groupIds = options.groups.split(',').map(Number);
|
|
567
|
-
//
|
|
567
|
+
// Validate all groupIds are valid positive integers
|
|
568
568
|
if (groupIds.some(id => isNaN(id) || !Number.isInteger(id) || id <= 0)) {
|
|
569
|
-
error(
|
|
569
|
+
error(`Invalid wallet group ID list "${options.groups}", provide comma-separated positive integers`);
|
|
570
570
|
process.exit(1);
|
|
571
571
|
}
|
|
572
|
-
//
|
|
572
|
+
// Verify wallet groups exist locally
|
|
573
573
|
const missingIds = groupIds.filter(id => !getGroup(id));
|
|
574
574
|
if (missingIds.length > 0) {
|
|
575
|
-
warn(
|
|
575
|
+
warn(`The following wallet groups do not exist locally: ${missingIds.join(', ')}`);
|
|
576
576
|
}
|
|
577
577
|
const ds = getDataSource();
|
|
578
|
-
//
|
|
578
|
+
// Build overview from DataStore local data
|
|
579
579
|
const overviewResults = [];
|
|
580
580
|
for (const gid of groupIds) {
|
|
581
581
|
const group = getGroup(gid);
|
|
582
582
|
if (!group)
|
|
583
583
|
continue;
|
|
584
|
-
//
|
|
584
|
+
// Determine token list to query
|
|
585
585
|
const tokenCAs = options.token ? [options.token] : ds.listTokens();
|
|
586
586
|
let totalValueSol = 0;
|
|
587
587
|
let totalRealizedPnl = 0;
|
|
@@ -592,14 +592,14 @@ export function registerWalletCommands(program) {
|
|
|
592
592
|
const holdings = ds.getHoldings(ca, gid);
|
|
593
593
|
if (!holdings || holdings.wallets.length === 0)
|
|
594
594
|
continue;
|
|
595
|
-
//
|
|
595
|
+
// Try to get token price
|
|
596
596
|
let priceSol = 0;
|
|
597
597
|
try {
|
|
598
598
|
const priceData = await ds.getTokenPrice(ca);
|
|
599
599
|
priceSol = priceData.priceSol;
|
|
600
600
|
}
|
|
601
601
|
catch {
|
|
602
|
-
//
|
|
602
|
+
// Price unavailable, skip value calculation
|
|
603
603
|
}
|
|
604
604
|
for (const w of holdings.wallets) {
|
|
605
605
|
const positionValue = w.tokenBalance * priceSol;
|
|
@@ -623,22 +623,22 @@ export function registerWalletCommands(program) {
|
|
|
623
623
|
});
|
|
624
624
|
}
|
|
625
625
|
if (overviewResults.length === 0) {
|
|
626
|
-
warn('
|
|
626
|
+
warn('No wallet group overview data');
|
|
627
627
|
}
|
|
628
628
|
output(overviewResults, {
|
|
629
629
|
columns: [
|
|
630
630
|
{ key: 'groupId', header: 'ID' },
|
|
631
|
-
{ key: 'groupName', header: '
|
|
632
|
-
{ key: 'totalValue', header: '
|
|
633
|
-
{ key: 'pnl', header: '
|
|
634
|
-
{ key: 'donePnl', header: '
|
|
635
|
-
{ key: 'unDonePnl', header: '
|
|
636
|
-
{ key: 'avgCost', header: '
|
|
631
|
+
{ key: 'groupName', header: 'Name' },
|
|
632
|
+
{ key: 'totalValue', header: 'Total Value(SOL)' },
|
|
633
|
+
{ key: 'pnl', header: 'Total P&L(SOL)' },
|
|
634
|
+
{ key: 'donePnl', header: 'Realized' },
|
|
635
|
+
{ key: 'unDonePnl', header: 'Unrealized' },
|
|
636
|
+
{ key: 'avgCost', header: 'Avg Cost' },
|
|
637
637
|
],
|
|
638
638
|
});
|
|
639
639
|
}
|
|
640
640
|
catch (e) {
|
|
641
|
-
error('
|
|
641
|
+
error('Failed to get wallet group overview', e.message);
|
|
642
642
|
process.exit(1);
|
|
643
643
|
}
|
|
644
644
|
});
|
|
@@ -647,43 +647,43 @@ export function registerWalletCommands(program) {
|
|
|
647
647
|
// ============================================================
|
|
648
648
|
walletCmd
|
|
649
649
|
.command('grind')
|
|
650
|
-
.description('
|
|
651
|
-
.requiredOption('--suffix <suffix>', '
|
|
652
|
-
.option('--prefix <prefix>', '
|
|
653
|
-
.option('--count <n>', '
|
|
654
|
-
.option('--threads <n>', '
|
|
650
|
+
.description('Generate vanity address (custom suffix/prefix)')
|
|
651
|
+
.requiredOption('--suffix <suffix>', 'Address suffix (e.g. pump)')
|
|
652
|
+
.option('--prefix <prefix>', 'Address prefix')
|
|
653
|
+
.option('--count <n>', 'Count to generate', '1')
|
|
654
|
+
.option('--threads <n>', 'Grind thread count')
|
|
655
655
|
.action(async (options) => {
|
|
656
656
|
try {
|
|
657
|
-
//
|
|
657
|
+
// Check if solana-keygen is available
|
|
658
658
|
try {
|
|
659
659
|
execSync('solana-keygen --version', { stdio: 'ignore' });
|
|
660
660
|
}
|
|
661
661
|
catch {
|
|
662
|
-
error('
|
|
662
|
+
error('solana-keygen not found, please install Solana CLI toolchain first', 'Install: sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)" or visit https://docs.solanalabs.com/cli/install');
|
|
663
663
|
process.exit(1);
|
|
664
664
|
}
|
|
665
665
|
const count = Number(options.count);
|
|
666
666
|
if (isNaN(count) || !Number.isInteger(count) || count <= 0) {
|
|
667
|
-
error(
|
|
667
|
+
error(`Invalid count "${options.count}", provide a positive integer`);
|
|
668
668
|
process.exit(1);
|
|
669
669
|
}
|
|
670
670
|
const suffix = options.suffix.trim();
|
|
671
671
|
if (!suffix) {
|
|
672
|
-
error('--suffix
|
|
672
|
+
error('--suffix cannot be empty');
|
|
673
673
|
process.exit(1);
|
|
674
674
|
}
|
|
675
|
-
//
|
|
675
|
+
// Verify suffix contains only Base58 characters
|
|
676
676
|
const base58Chars = /^[1-9A-HJ-NP-Za-km-z]+$/;
|
|
677
677
|
if (!base58Chars.test(suffix)) {
|
|
678
|
-
error(
|
|
678
|
+
error(`Invalid suffix "${suffix}", only Base58 characters supported (no 0, O, I, l)`);
|
|
679
679
|
process.exit(1);
|
|
680
680
|
}
|
|
681
681
|
if (options.prefix && !base58Chars.test(options.prefix)) {
|
|
682
|
-
error(
|
|
682
|
+
error(`Invalid prefix "${options.prefix}", only Base58 characters supported (no 0, O, I, l)`);
|
|
683
683
|
process.exit(1);
|
|
684
684
|
}
|
|
685
685
|
ensureConfigDir();
|
|
686
|
-
//
|
|
686
|
+
// Build solana-keygen grind arguments
|
|
687
687
|
const args = ['grind'];
|
|
688
688
|
if (options.prefix) {
|
|
689
689
|
args.push('--starts-and-ends-with', `${options.prefix}:${suffix}`);
|
|
@@ -694,19 +694,19 @@ export function registerWalletCommands(program) {
|
|
|
694
694
|
if (options.threads) {
|
|
695
695
|
const threads = Number(options.threads);
|
|
696
696
|
if (isNaN(threads) || !Number.isInteger(threads) || threads <= 0) {
|
|
697
|
-
error(
|
|
697
|
+
error(`Invalid thread count "${options.threads}", provide a positive integer`);
|
|
698
698
|
process.exit(1);
|
|
699
699
|
}
|
|
700
700
|
args.push('--num-threads', String(threads));
|
|
701
701
|
}
|
|
702
702
|
const fmt = getOutputFormat();
|
|
703
703
|
if (fmt !== 'json') {
|
|
704
|
-
success(
|
|
704
|
+
success(`Generating ${count} vanity addresses ending with "${suffix}", please wait...`);
|
|
705
705
|
if (suffix.length >= 5) {
|
|
706
|
-
warn('
|
|
706
|
+
warn('Long suffix, generation may take minutes or longer');
|
|
707
707
|
}
|
|
708
708
|
}
|
|
709
|
-
//
|
|
709
|
+
// Execute grind in VANITY_DIR, generated .json files will be in that directory
|
|
710
710
|
const child = spawn('solana-keygen', args, {
|
|
711
711
|
cwd: VANITY_DIR,
|
|
712
712
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
@@ -719,10 +719,10 @@ export function registerWalletCommands(program) {
|
|
|
719
719
|
child.on('close', resolve);
|
|
720
720
|
});
|
|
721
721
|
if (exitCode !== 0) {
|
|
722
|
-
error('solana-keygen grind
|
|
722
|
+
error('solana-keygen grind execution failed', stderr || stdout);
|
|
723
723
|
process.exit(1);
|
|
724
724
|
}
|
|
725
|
-
//
|
|
725
|
+
// Parse generation results, solana-keygen output format: "Wrote keypair to <address>.json"
|
|
726
726
|
const generatedFiles = [];
|
|
727
727
|
const lines = stdout.split('\n');
|
|
728
728
|
for (const line of lines) {
|
|
@@ -734,12 +734,12 @@ export function registerWalletCommands(program) {
|
|
|
734
734
|
generatedFiles.push({ address, path: filePath });
|
|
735
735
|
}
|
|
736
736
|
}
|
|
737
|
-
// --starts-and-ends-with
|
|
737
|
+
// In --starts-and-ends-with mode, need to manually control count
|
|
738
738
|
if (options.prefix && generatedFiles.length > count) {
|
|
739
739
|
generatedFiles.splice(count);
|
|
740
740
|
}
|
|
741
741
|
if (generatedFiles.length === 0) {
|
|
742
|
-
warn('
|
|
742
|
+
warn('Unable to parse generated key files');
|
|
743
743
|
process.exit(1);
|
|
744
744
|
}
|
|
745
745
|
if (fmt === 'json') {
|
|
@@ -753,7 +753,7 @@ export function registerWalletCommands(program) {
|
|
|
753
753
|
});
|
|
754
754
|
}
|
|
755
755
|
else {
|
|
756
|
-
success(
|
|
756
|
+
success(`Successfully generated ${generatedFiles.length} vanity addresses, saved to ${VANITY_DIR}`);
|
|
757
757
|
output(generatedFiles.map((f, i) => ({
|
|
758
758
|
index: i + 1,
|
|
759
759
|
address: f.address,
|
|
@@ -761,14 +761,14 @@ export function registerWalletCommands(program) {
|
|
|
761
761
|
})), {
|
|
762
762
|
columns: [
|
|
763
763
|
{ key: 'index', header: '#' },
|
|
764
|
-
{ key: 'address', header: '
|
|
765
|
-
{ key: 'path', header: '
|
|
764
|
+
{ key: 'address', header: 'Address' },
|
|
765
|
+
{ key: 'path', header: 'File Path' },
|
|
766
766
|
],
|
|
767
767
|
});
|
|
768
768
|
}
|
|
769
769
|
}
|
|
770
770
|
catch (e) {
|
|
771
|
-
error('
|
|
771
|
+
error('Failed to generate vanity addresses', e.message);
|
|
772
772
|
process.exit(1);
|
|
773
773
|
}
|
|
774
774
|
});
|
|
@@ -777,19 +777,19 @@ export function registerWalletCommands(program) {
|
|
|
777
777
|
// ============================================================
|
|
778
778
|
walletCmd
|
|
779
779
|
.command('grind-list')
|
|
780
|
-
.description('
|
|
781
|
-
.option('--suffix <suffix>', '
|
|
780
|
+
.description('List generated vanity addresses')
|
|
781
|
+
.option('--suffix <suffix>', 'Filter by suffix')
|
|
782
782
|
.action((options) => {
|
|
783
783
|
try {
|
|
784
784
|
ensureConfigDir();
|
|
785
785
|
if (!fs.existsSync(VANITY_DIR)) {
|
|
786
|
-
warn('
|
|
786
|
+
warn('No vanity addresses yet, run forgex wallet grind first');
|
|
787
787
|
output([]);
|
|
788
788
|
return;
|
|
789
789
|
}
|
|
790
790
|
const files = fs.readdirSync(VANITY_DIR).filter(f => f.endsWith('.json'));
|
|
791
791
|
if (files.length === 0) {
|
|
792
|
-
warn('
|
|
792
|
+
warn('No vanity addresses yet, run forgex wallet grind first');
|
|
793
793
|
output([]);
|
|
794
794
|
return;
|
|
795
795
|
}
|
|
@@ -808,19 +808,19 @@ export function registerWalletCommands(program) {
|
|
|
808
808
|
results = results.filter(r => r.address.toLowerCase().endsWith(suffixLower));
|
|
809
809
|
}
|
|
810
810
|
if (results.length === 0) {
|
|
811
|
-
const filterMsg = options.suffix ?
|
|
812
|
-
warn(
|
|
811
|
+
const filterMsg = options.suffix ? `(suffix: ${options.suffix})` : '';
|
|
812
|
+
warn(`No matching vanity addresses found${filterMsg}`);
|
|
813
813
|
}
|
|
814
814
|
output(results, {
|
|
815
815
|
columns: [
|
|
816
|
-
{ key: 'address', header: '
|
|
817
|
-
{ key: 'createdAt', header: '
|
|
818
|
-
{ key: 'path', header: '
|
|
816
|
+
{ key: 'address', header: 'Address' },
|
|
817
|
+
{ key: 'createdAt', header: 'Created At' },
|
|
818
|
+
{ key: 'path', header: 'File Path' },
|
|
819
819
|
],
|
|
820
820
|
});
|
|
821
821
|
}
|
|
822
822
|
catch (e) {
|
|
823
|
-
error('
|
|
823
|
+
error('Failed to list vanity addresses', e.message);
|
|
824
824
|
process.exit(1);
|
|
825
825
|
}
|
|
826
826
|
});
|