forgex-cli 1.0.58 → 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 +28 -27
- 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,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Market making tools command group - real transaction execution
|
|
3
3
|
*
|
|
4
4
|
* forgex tools turnover | volume | robot-price
|
|
5
5
|
*
|
|
6
|
-
* Phase 3
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
6
|
+
* Phase 3 upgrade:
|
|
7
|
+
* - Use DataSource facade instead of direct sdk-adapter balance queries
|
|
8
|
+
* - Integrate TxTracker for automatic transaction result tracking
|
|
9
|
+
* - Add ensurePasswordAndValidate for password verification
|
|
10
|
+
* - Add suppressConsole/restoreConsole to prevent JSON output pollution
|
|
11
|
+
* - Improve input parameter validation
|
|
12
|
+
* - Improve --daemon mode support
|
|
13
|
+
* - Fix slippage bps -> percentage conversion
|
|
14
14
|
*/
|
|
15
15
|
import { getGroup, getDecryptedPrivateKey, ensurePasswordAndValidate } from '../../wallet-store.js';
|
|
16
16
|
import { loadConfig } from '../../config.js';
|
|
@@ -20,80 +20,80 @@ import { getDataSource } from '../../data-source.js';
|
|
|
20
20
|
export function registerToolsCommands(program) {
|
|
21
21
|
const toolsCmd = program
|
|
22
22
|
.command('tools')
|
|
23
|
-
.description('
|
|
23
|
+
.description('Professional market making tools');
|
|
24
24
|
// ============================================================
|
|
25
|
-
// forgex tools turnover -
|
|
25
|
+
// forgex tools turnover - real turnover trade
|
|
26
26
|
// ============================================================
|
|
27
27
|
toolsCmd
|
|
28
28
|
.command('turnover')
|
|
29
|
-
.description('
|
|
30
|
-
.requiredOption('--from-group <id>', '
|
|
31
|
-
.requiredOption('--to-group <id>', '
|
|
32
|
-
.requiredOption('--token <ca>', '
|
|
33
|
-
.option('--amount <value>', '
|
|
34
|
-
.option('--interval <ms>', '
|
|
35
|
-
.option('--priority-fee <sol>', '
|
|
36
|
-
.option('--slippage <bps>', '
|
|
37
|
-
.option('--dry-run', '
|
|
38
|
-
.option('--fallback-send-tx', 'Bundle
|
|
39
|
-
.option('--daemon', '
|
|
40
|
-
.option('--rounds <n>', '
|
|
29
|
+
.description('Turnover trade (zero-slippage turnover between wallets, using Jito Bundle)')
|
|
30
|
+
.requiredOption('--from-group <id>', 'Source wallet group ID')
|
|
31
|
+
.requiredOption('--to-group <id>', 'Target wallet group ID')
|
|
32
|
+
.requiredOption('--token <ca>', 'Token contract address')
|
|
33
|
+
.option('--amount <value>', 'Amount: fixed token count | percentage (e.g. 50%) | all', 'all')
|
|
34
|
+
.option('--interval <ms>', 'Transfer interval (ms)', '1000')
|
|
35
|
+
.option('--priority-fee <sol>', 'Priority fee (SOL)')
|
|
36
|
+
.option('--slippage <bps>', 'Slippage (bps, e.g. 100 = 1%)', '100')
|
|
37
|
+
.option('--dry-run', 'Simulate only, do not execute', false)
|
|
38
|
+
.option('--fallback-send-tx', 'Fallback to sendTransaction when Bundle fails (no same-block guarantee)', false)
|
|
39
|
+
.option('--daemon', 'Daemon mode (continuous execution)', false)
|
|
40
|
+
.option('--rounds <n>', 'Number of rounds (0=infinite)', '1')
|
|
41
41
|
.action(async (options) => {
|
|
42
42
|
try {
|
|
43
|
-
//
|
|
43
|
+
// Password verification
|
|
44
44
|
await ensurePasswordAndValidate();
|
|
45
45
|
const config = loadConfig();
|
|
46
46
|
const fromGroupId = Number(options.fromGroup);
|
|
47
47
|
const toGroupId = Number(options.toGroup);
|
|
48
|
-
//
|
|
48
|
+
// Parameter validation
|
|
49
49
|
if (isNaN(fromGroupId)) {
|
|
50
|
-
error('--from-group
|
|
50
|
+
error('--from-group must be a valid number');
|
|
51
51
|
process.exit(1);
|
|
52
52
|
}
|
|
53
53
|
if (isNaN(toGroupId)) {
|
|
54
|
-
error('--to-group
|
|
54
|
+
error('--to-group must be a valid number');
|
|
55
55
|
process.exit(1);
|
|
56
56
|
}
|
|
57
57
|
const fromGroup = getGroup(fromGroupId);
|
|
58
58
|
const toGroup = getGroup(toGroupId);
|
|
59
59
|
if (!fromGroup) {
|
|
60
|
-
error(
|
|
60
|
+
error(`Source wallet group ${fromGroupId} does not exist`);
|
|
61
61
|
process.exit(1);
|
|
62
62
|
}
|
|
63
63
|
if (!toGroup) {
|
|
64
|
-
error(
|
|
64
|
+
error(`Target wallet group ${toGroupId} does not exist`);
|
|
65
65
|
process.exit(1);
|
|
66
66
|
}
|
|
67
67
|
if (fromGroup.wallets.length === 0) {
|
|
68
|
-
error('
|
|
68
|
+
error('Source wallet group has no wallets');
|
|
69
69
|
process.exit(1);
|
|
70
70
|
}
|
|
71
71
|
if (toGroup.wallets.length === 0) {
|
|
72
|
-
error('
|
|
72
|
+
error('Target wallet group has no wallets');
|
|
73
73
|
process.exit(1);
|
|
74
74
|
}
|
|
75
75
|
const slippageBps = Number(options.slippage);
|
|
76
76
|
if (isNaN(slippageBps) || slippageBps <= 0 || slippageBps > 5000) {
|
|
77
|
-
error('--slippage
|
|
77
|
+
error('--slippage must be a number between 1-5000 (bps, 5000 = 50%)');
|
|
78
78
|
process.exit(1);
|
|
79
79
|
}
|
|
80
80
|
const priorityFee = options.priorityFee
|
|
81
81
|
? Number(options.priorityFee)
|
|
82
82
|
: config.defaultPriorityFee;
|
|
83
83
|
if (isNaN(priorityFee) || priorityFee < 0) {
|
|
84
|
-
error('--priority-fee
|
|
84
|
+
error('--priority-fee must be a non-negative number');
|
|
85
85
|
process.exit(1);
|
|
86
86
|
}
|
|
87
87
|
const maxRounds = Number(options.rounds);
|
|
88
88
|
if (isNaN(maxRounds) || maxRounds < 0) {
|
|
89
|
-
error('--rounds
|
|
89
|
+
error('--rounds must be a non-negative integer');
|
|
90
90
|
process.exit(1);
|
|
91
91
|
}
|
|
92
|
-
//
|
|
92
|
+
// Slippage conversion: bps -> decimal (SDK expects 0.03 for 3%, i.e. 300bps / 10000)
|
|
93
93
|
const slippage = slippageBps / 10000;
|
|
94
|
-
info(
|
|
94
|
+
info(`Fetching trade context for token ${options.token}...`);
|
|
95
95
|
const context = await fetchTradeContext(options.token);
|
|
96
|
-
info(`DEX: ${context.exchangeName},
|
|
96
|
+
info(`DEX: ${context.exchangeName}, price: ${context.priceInSol} SOL`);
|
|
97
97
|
if (options.dryRun) {
|
|
98
98
|
const estimatedFee = 0;
|
|
99
99
|
output({
|
|
@@ -103,25 +103,25 @@ export function registerToolsCommands(program) {
|
|
|
103
103
|
fromWallets: fromGroup.wallets.length,
|
|
104
104
|
toWallets: toGroup.wallets.length,
|
|
105
105
|
estimatedFee,
|
|
106
|
-
message: '
|
|
106
|
+
message: 'Simulation successful',
|
|
107
107
|
});
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
|
-
// DataSource
|
|
110
|
+
// DataSource for balance queries
|
|
111
111
|
const ds = getDataSource();
|
|
112
112
|
let currentRound = 0;
|
|
113
113
|
const executeOneRound = async () => {
|
|
114
114
|
currentRound++;
|
|
115
|
-
info(
|
|
116
|
-
//
|
|
115
|
+
info(`Executing round ${currentRound} turnover...`);
|
|
116
|
+
// Suppress SDK console.log in JSON mode
|
|
117
117
|
if (getOutputFormat() === 'json')
|
|
118
118
|
suppressConsole();
|
|
119
|
-
//
|
|
120
|
-
//
|
|
119
|
+
// Build turnover pairs
|
|
120
|
+
// Support 1-to-many: if from wallets < to wallets, split tokens proportionally to multiple targets
|
|
121
121
|
const turnoverItems = [];
|
|
122
122
|
const fromWalletAddresses = [];
|
|
123
123
|
if (fromGroup.wallets.length >= toGroup.wallets.length) {
|
|
124
|
-
// from >= to:
|
|
124
|
+
// from >= to: 1:1 pairing (original logic)
|
|
125
125
|
const pairCount = Math.min(fromGroup.wallets.length, toGroup.wallets.length);
|
|
126
126
|
for (let i = 0; i < pairCount; i++) {
|
|
127
127
|
const fromWallet = fromGroup.wallets[i];
|
|
@@ -147,8 +147,8 @@ export function registerToolsCommands(program) {
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
else {
|
|
150
|
-
// from < to: 1
|
|
151
|
-
//
|
|
150
|
+
// from < to: 1-to-many split mode
|
|
151
|
+
// Each from wallet's tokens are evenly distributed to multiple to wallets
|
|
152
152
|
const toPerFrom = Math.ceil(toGroup.wallets.length / fromGroup.wallets.length);
|
|
153
153
|
let toIndex = 0;
|
|
154
154
|
for (let i = 0; i < fromGroup.wallets.length; i++) {
|
|
@@ -165,7 +165,7 @@ export function registerToolsCommands(program) {
|
|
|
165
165
|
}
|
|
166
166
|
if (totalAmount <= 0)
|
|
167
167
|
continue;
|
|
168
|
-
//
|
|
168
|
+
// Calculate how many to wallets this from wallet should distribute to
|
|
169
169
|
const remainingTo = toGroup.wallets.length - toIndex;
|
|
170
170
|
const remainingFrom = fromGroup.wallets.length - i;
|
|
171
171
|
const allocCount = Math.min(Math.ceil(remainingTo / remainingFrom), remainingTo);
|
|
@@ -184,8 +184,8 @@ export function registerToolsCommands(program) {
|
|
|
184
184
|
}
|
|
185
185
|
if (turnoverItems.length === 0) {
|
|
186
186
|
restoreConsole();
|
|
187
|
-
info('
|
|
188
|
-
output({ round: currentRound, total: 0, success: 0, failed: 0, message: '
|
|
187
|
+
info('No available turnover pairs (balance is 0)');
|
|
188
|
+
output({ round: currentRound, total: 0, success: 0, failed: 0, message: 'No available turnover pairs (balance is 0)' });
|
|
189
189
|
return;
|
|
190
190
|
}
|
|
191
191
|
const results = await executeBatchTurnoverTrade({
|
|
@@ -217,20 +217,20 @@ export function registerToolsCommands(program) {
|
|
|
217
217
|
const scheduleNext = () => {
|
|
218
218
|
setTimeout(async () => {
|
|
219
219
|
try {
|
|
220
|
-
//
|
|
220
|
+
// Refresh trade context (price may have changed)
|
|
221
221
|
const newContext = await fetchTradeContext(options.token);
|
|
222
222
|
Object.assign(context, newContext);
|
|
223
223
|
await executeOneRound();
|
|
224
224
|
}
|
|
225
225
|
catch (e) {
|
|
226
226
|
restoreConsole();
|
|
227
|
-
error(
|
|
227
|
+
error(`Round ${currentRound + 1} execution error: ${e.message}`);
|
|
228
228
|
}
|
|
229
229
|
if (maxRounds === 0 || currentRound < maxRounds) {
|
|
230
230
|
scheduleNext();
|
|
231
231
|
}
|
|
232
232
|
else {
|
|
233
|
-
info(
|
|
233
|
+
info(`Turnover completed ${maxRounds} rounds`);
|
|
234
234
|
process.exit(0);
|
|
235
235
|
}
|
|
236
236
|
}, Number(options.interval));
|
|
@@ -238,36 +238,36 @@ export function registerToolsCommands(program) {
|
|
|
238
238
|
scheduleNext();
|
|
239
239
|
process.on('SIGINT', () => {
|
|
240
240
|
restoreConsole();
|
|
241
|
-
info(
|
|
241
|
+
info(`Turnover stopped, executed ${currentRound} rounds total`);
|
|
242
242
|
process.exit(0);
|
|
243
243
|
});
|
|
244
244
|
}
|
|
245
245
|
}
|
|
246
246
|
catch (e) {
|
|
247
247
|
restoreConsole();
|
|
248
|
-
error('
|
|
248
|
+
error('Turnover failed', e.message);
|
|
249
249
|
process.exit(1);
|
|
250
250
|
}
|
|
251
251
|
});
|
|
252
252
|
// ============================================================
|
|
253
|
-
// forgex tools volume -
|
|
253
|
+
// forgex tools volume - real volume trading
|
|
254
254
|
// ============================================================
|
|
255
255
|
toolsCmd
|
|
256
256
|
.command('volume')
|
|
257
|
-
.description('
|
|
258
|
-
.requiredOption('--group <groupId>', '
|
|
259
|
-
.requiredOption('--token <ca>', '
|
|
260
|
-
.option('--mode <mode>', '
|
|
261
|
-
.option('--amount <sol>', '
|
|
262
|
-
.option('--interval <ms>', '
|
|
263
|
-
.option('--rounds <n>', '
|
|
264
|
-
.option('--slippage <bps>', '
|
|
265
|
-
.option('--priority-fee <sol>', '
|
|
266
|
-
.option('--dry-run', '
|
|
267
|
-
.option('--daemon', '
|
|
257
|
+
.description('Volume tool (zero-loss volume boost)')
|
|
258
|
+
.requiredOption('--group <groupId>', 'Wallet group ID')
|
|
259
|
+
.requiredOption('--token <ca>', 'Token contract address')
|
|
260
|
+
.option('--mode <mode>', 'Volume mode: 1b1s | 1b2s | 1b3s | 2b1s | 3b1s', '1b1s')
|
|
261
|
+
.option('--amount <sol>', 'Trade amount (SOL)', '0.01')
|
|
262
|
+
.option('--interval <ms>', 'Trade interval (ms)', '5000')
|
|
263
|
+
.option('--rounds <n>', 'Number of rounds (0=infinite)', '1')
|
|
264
|
+
.option('--slippage <bps>', 'Slippage (bps)', '300')
|
|
265
|
+
.option('--priority-fee <sol>', 'Priority fee (SOL)')
|
|
266
|
+
.option('--dry-run', 'Simulate only, do not execute', false)
|
|
267
|
+
.option('--daemon', 'Daemon mode', false)
|
|
268
268
|
.action(async (options) => {
|
|
269
269
|
try {
|
|
270
|
-
//
|
|
270
|
+
// Password verification
|
|
271
271
|
await ensurePasswordAndValidate();
|
|
272
272
|
const config = loadConfig();
|
|
273
273
|
const groupId = Number(options.group);
|
|
@@ -276,48 +276,48 @@ export function registerToolsCommands(program) {
|
|
|
276
276
|
const priorityFee = options.priorityFee
|
|
277
277
|
? Number(options.priorityFee)
|
|
278
278
|
: config.defaultPriorityFee;
|
|
279
|
-
//
|
|
279
|
+
// Parameter validation
|
|
280
280
|
if (isNaN(groupId)) {
|
|
281
|
-
error('--group
|
|
281
|
+
error('--group must be a valid number');
|
|
282
282
|
process.exit(1);
|
|
283
283
|
}
|
|
284
284
|
if (isNaN(amount) || amount <= 0) {
|
|
285
|
-
error('--amount
|
|
285
|
+
error('--amount must be a number greater than 0');
|
|
286
286
|
process.exit(1);
|
|
287
287
|
}
|
|
288
288
|
if (isNaN(slippageBps) || slippageBps <= 0 || slippageBps > 5000) {
|
|
289
|
-
error('--slippage
|
|
289
|
+
error('--slippage must be a number between 1-5000 (bps, 5000 = 50%)');
|
|
290
290
|
process.exit(1);
|
|
291
291
|
}
|
|
292
292
|
if (isNaN(priorityFee) || priorityFee < 0) {
|
|
293
|
-
error('--priority-fee
|
|
293
|
+
error('--priority-fee must be a non-negative number');
|
|
294
294
|
process.exit(1);
|
|
295
295
|
}
|
|
296
|
-
// --mode
|
|
296
|
+
// --mode validation
|
|
297
297
|
const validModes = ['1b1s', '1b2s', '1b3s', '2b1s', '3b1s'];
|
|
298
298
|
if (!validModes.includes(options.mode)) {
|
|
299
|
-
error(`--mode
|
|
299
|
+
error(`--mode invalid: "${options.mode}", must be 1b1s | 1b2s | 1b3s | 2b1s | 3b1s`);
|
|
300
300
|
process.exit(1);
|
|
301
301
|
}
|
|
302
302
|
const maxRounds = Number(options.rounds);
|
|
303
303
|
if (isNaN(maxRounds) || maxRounds < 0) {
|
|
304
|
-
error('--rounds
|
|
304
|
+
error('--rounds must be a non-negative integer');
|
|
305
305
|
process.exit(1);
|
|
306
306
|
}
|
|
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
|
if (group.wallets.length === 0) {
|
|
313
|
-
error('
|
|
313
|
+
error('Wallet group has no wallets');
|
|
314
314
|
process.exit(1);
|
|
315
315
|
}
|
|
316
|
-
//
|
|
316
|
+
// Slippage conversion: bps -> percentage
|
|
317
317
|
const slippage = slippageBps / 10;
|
|
318
|
-
info(
|
|
318
|
+
info(`Fetching trade context for token ${options.token}...`);
|
|
319
319
|
let context = await fetchTradeContext(options.token);
|
|
320
|
-
info(`DEX: ${context.exchangeName},
|
|
320
|
+
info(`DEX: ${context.exchangeName}, price: ${context.priceInSol} SOL`);
|
|
321
321
|
if (options.dryRun) {
|
|
322
322
|
output({
|
|
323
323
|
dryRun: true,
|
|
@@ -326,7 +326,7 @@ export function registerToolsCommands(program) {
|
|
|
326
326
|
wallets: group.wallets.length,
|
|
327
327
|
mode: options.mode,
|
|
328
328
|
estimatedFeePerRound: 0,
|
|
329
|
-
message: '
|
|
329
|
+
message: 'Simulation successful',
|
|
330
330
|
});
|
|
331
331
|
return;
|
|
332
332
|
}
|
|
@@ -336,13 +336,13 @@ export function registerToolsCommands(program) {
|
|
|
336
336
|
let currentRound = 0;
|
|
337
337
|
const executeOneRound = async () => {
|
|
338
338
|
currentRound++;
|
|
339
|
-
info(
|
|
339
|
+
info(`Executing round ${currentRound} volume (mode: ${options.mode})...`);
|
|
340
340
|
try {
|
|
341
|
-
//
|
|
341
|
+
// Refresh context each round for latest price
|
|
342
342
|
if (currentRound > 1) {
|
|
343
343
|
context = await fetchTradeContext(options.token);
|
|
344
344
|
}
|
|
345
|
-
//
|
|
345
|
+
// Suppress SDK console.log in JSON mode
|
|
346
346
|
if (getOutputFormat() === 'json')
|
|
347
347
|
suppressConsole();
|
|
348
348
|
const result = await executeTrades({
|
|
@@ -363,7 +363,7 @@ export function registerToolsCommands(program) {
|
|
|
363
363
|
}
|
|
364
364
|
catch (e) {
|
|
365
365
|
restoreConsole();
|
|
366
|
-
error(
|
|
366
|
+
error(`Round ${currentRound} failed: ${e.message}`);
|
|
367
367
|
}
|
|
368
368
|
};
|
|
369
369
|
await executeOneRound();
|
|
@@ -375,7 +375,7 @@ export function registerToolsCommands(program) {
|
|
|
375
375
|
scheduleNext();
|
|
376
376
|
}
|
|
377
377
|
else {
|
|
378
|
-
info(
|
|
378
|
+
info(`Volume completed ${maxRounds} rounds`);
|
|
379
379
|
process.exit(0);
|
|
380
380
|
}
|
|
381
381
|
}, Number(options.interval));
|
|
@@ -383,39 +383,39 @@ export function registerToolsCommands(program) {
|
|
|
383
383
|
scheduleNext();
|
|
384
384
|
process.on('SIGINT', () => {
|
|
385
385
|
restoreConsole();
|
|
386
|
-
info(
|
|
386
|
+
info(`Volume trading stopped, executed ${currentRound} rounds`);
|
|
387
387
|
process.exit(0);
|
|
388
388
|
});
|
|
389
389
|
}
|
|
390
390
|
}
|
|
391
391
|
catch (e) {
|
|
392
392
|
restoreConsole();
|
|
393
|
-
error('
|
|
393
|
+
error('Volume trading failed', e.message);
|
|
394
394
|
process.exit(1);
|
|
395
395
|
}
|
|
396
396
|
});
|
|
397
397
|
// ============================================================
|
|
398
|
-
// forgex tools robot-price -
|
|
398
|
+
// forgex tools robot-price - real price robot
|
|
399
399
|
// ============================================================
|
|
400
400
|
toolsCmd
|
|
401
401
|
.command('robot-price')
|
|
402
|
-
.description('
|
|
403
|
-
.requiredOption('--group <groupId>', '
|
|
404
|
-
.requiredOption('--token <ca>', '
|
|
405
|
-
.requiredOption('--direction <dir>', '
|
|
406
|
-
.requiredOption('--target-price <price>', '
|
|
407
|
-
.option('--amount <sol>', '
|
|
408
|
-
.option('--amount-type <type>', '
|
|
409
|
-
.option('--amount-max <sol>', '
|
|
410
|
-
.option('--interval <ms>', '
|
|
411
|
-
.option('--max-cost <sol>', '
|
|
412
|
-
.option('--slippage <bps>', '
|
|
413
|
-
.option('--priority-fee <sol>', '
|
|
414
|
-
.option('--dry-run', '
|
|
415
|
-
.option('--daemon', '
|
|
402
|
+
.description('Price robot (auto pump/dump to target price)')
|
|
403
|
+
.requiredOption('--group <groupId>', 'Wallet group ID')
|
|
404
|
+
.requiredOption('--token <ca>', 'Token contract address')
|
|
405
|
+
.requiredOption('--direction <dir>', 'Direction: up (pump) | down (dump)')
|
|
406
|
+
.requiredOption('--target-price <price>', 'Target price (SOL)')
|
|
407
|
+
.option('--amount <sol>', 'Amount per trade (SOL)', '0.01')
|
|
408
|
+
.option('--amount-type <type>', 'Amount type: fixed | random', 'fixed')
|
|
409
|
+
.option('--amount-max <sol>', 'Max amount (random mode)')
|
|
410
|
+
.option('--interval <ms>', 'Trade interval (ms)', '5000')
|
|
411
|
+
.option('--max-cost <sol>', 'Max total cost (SOL)')
|
|
412
|
+
.option('--slippage <bps>', 'Slippage (bps)', '300')
|
|
413
|
+
.option('--priority-fee <sol>', 'Priority fee (SOL)')
|
|
414
|
+
.option('--dry-run', 'Dry run only, no actual execution', false)
|
|
415
|
+
.option('--daemon', 'Daemon mode (enabled by default, price robot runs continuously)', true)
|
|
416
416
|
.action(async (options) => {
|
|
417
417
|
try {
|
|
418
|
-
//
|
|
418
|
+
// Password verification
|
|
419
419
|
await ensurePasswordAndValidate();
|
|
420
420
|
const config = loadConfig();
|
|
421
421
|
const groupId = Number(options.group);
|
|
@@ -425,47 +425,47 @@ export function registerToolsCommands(program) {
|
|
|
425
425
|
const priorityFee = options.priorityFee
|
|
426
426
|
? Number(options.priorityFee)
|
|
427
427
|
: config.defaultPriorityFee;
|
|
428
|
-
//
|
|
428
|
+
// Parameter validation
|
|
429
429
|
if (isNaN(groupId)) {
|
|
430
|
-
error('--group
|
|
430
|
+
error('--group must be a valid number');
|
|
431
431
|
process.exit(1);
|
|
432
432
|
}
|
|
433
433
|
if (isNaN(amountVal) || amountVal <= 0) {
|
|
434
|
-
error('--amount
|
|
434
|
+
error('--amount must be a number greater than 0');
|
|
435
435
|
process.exit(1);
|
|
436
436
|
}
|
|
437
437
|
if (isNaN(slippageBps) || slippageBps <= 0 || slippageBps > 5000) {
|
|
438
|
-
error('--slippage
|
|
438
|
+
error('--slippage must be a number between 1-5000 (bps, 5000 = 50%)');
|
|
439
439
|
process.exit(1);
|
|
440
440
|
}
|
|
441
441
|
if (isNaN(priorityFee) || priorityFee < 0) {
|
|
442
|
-
error('--priority-fee
|
|
442
|
+
error('--priority-fee must be a non-negative number');
|
|
443
443
|
process.exit(1);
|
|
444
444
|
}
|
|
445
445
|
if (isNaN(targetPrice) || targetPrice <= 0) {
|
|
446
|
-
error('--target-price
|
|
446
|
+
error('--target-price must be a number greater than 0');
|
|
447
447
|
process.exit(1);
|
|
448
448
|
}
|
|
449
|
-
// --direction
|
|
449
|
+
// --direction validation
|
|
450
450
|
const validDirections = ['up', 'down'];
|
|
451
451
|
if (!validDirections.includes(options.direction)) {
|
|
452
|
-
error(`--direction
|
|
452
|
+
error(`--direction invalid: "${options.direction}", must be up | down`);
|
|
453
453
|
process.exit(1);
|
|
454
454
|
}
|
|
455
455
|
const group = getGroup(groupId);
|
|
456
456
|
if (!group) {
|
|
457
|
-
error(
|
|
457
|
+
error(`Wallet group ${groupId} does not exist`);
|
|
458
458
|
process.exit(1);
|
|
459
459
|
}
|
|
460
460
|
if (group.wallets.length === 0) {
|
|
461
|
-
error('
|
|
461
|
+
error('No wallets in wallet group');
|
|
462
462
|
process.exit(1);
|
|
463
463
|
}
|
|
464
|
-
//
|
|
464
|
+
// Slippage conversion: bps -> percentage
|
|
465
465
|
const slippage = slippageBps / 10;
|
|
466
|
-
info(
|
|
466
|
+
info(`Fetching trade context for token ${options.token}...`);
|
|
467
467
|
let context = await fetchTradeContext(options.token);
|
|
468
|
-
info(`DEX: ${context.exchangeName},
|
|
468
|
+
info(`DEX: ${context.exchangeName}, Current price: ${context.priceInSol} SOL, Target: ${targetPrice} SOL`);
|
|
469
469
|
if (options.dryRun) {
|
|
470
470
|
output({
|
|
471
471
|
dryRun: true,
|
|
@@ -473,7 +473,7 @@ export function registerToolsCommands(program) {
|
|
|
473
473
|
currentPrice: context.priceInSol,
|
|
474
474
|
targetPrice,
|
|
475
475
|
direction: options.direction,
|
|
476
|
-
message: '
|
|
476
|
+
message: 'Simulation successful',
|
|
477
477
|
});
|
|
478
478
|
return;
|
|
479
479
|
}
|
|
@@ -485,22 +485,22 @@ export function registerToolsCommands(program) {
|
|
|
485
485
|
if (options.maxCost) {
|
|
486
486
|
maxCost = Number(options.maxCost);
|
|
487
487
|
if (isNaN(maxCost) || maxCost <= 0) {
|
|
488
|
-
error('--max-cost
|
|
488
|
+
error('--max-cost must be a number greater than 0');
|
|
489
489
|
process.exit(1);
|
|
490
490
|
}
|
|
491
491
|
}
|
|
492
|
-
info(
|
|
492
|
+
info(`Price robot started: ${options.direction === 'up' ? 'pump' : 'dump'} -> ${targetPrice} SOL`);
|
|
493
493
|
const execute = async () => {
|
|
494
494
|
executeCount++;
|
|
495
|
-
//
|
|
495
|
+
// Refresh price
|
|
496
496
|
context = await fetchTradeContext(options.token);
|
|
497
497
|
const currentPrice = Number(context.priceInSol);
|
|
498
|
-
//
|
|
498
|
+
// Check if target reached
|
|
499
499
|
if (options.direction === 'up' && currentPrice >= targetPrice) {
|
|
500
500
|
output({
|
|
501
501
|
success: true, direction: options.direction,
|
|
502
502
|
targetPrice, currentPrice, totalCost, executeCount,
|
|
503
|
-
message: '
|
|
503
|
+
message: 'Target price reached',
|
|
504
504
|
});
|
|
505
505
|
process.exit(0);
|
|
506
506
|
}
|
|
@@ -508,40 +508,40 @@ export function registerToolsCommands(program) {
|
|
|
508
508
|
output({
|
|
509
509
|
success: true, direction: options.direction,
|
|
510
510
|
targetPrice, currentPrice, totalCost, executeCount,
|
|
511
|
-
message: '
|
|
511
|
+
message: 'Target price reached',
|
|
512
512
|
});
|
|
513
513
|
process.exit(0);
|
|
514
514
|
}
|
|
515
|
-
//
|
|
515
|
+
// Check cost limit
|
|
516
516
|
if (totalCost >= maxCost) {
|
|
517
517
|
output({
|
|
518
518
|
success: false, direction: options.direction,
|
|
519
519
|
targetPrice, currentPrice, totalCost, executeCount,
|
|
520
|
-
message:
|
|
520
|
+
message: `Cost limit ${maxCost} SOL reached`,
|
|
521
521
|
});
|
|
522
522
|
process.exit(0);
|
|
523
523
|
}
|
|
524
|
-
//
|
|
524
|
+
// Calculate trade amount
|
|
525
525
|
let amount = Number(options.amount);
|
|
526
526
|
if (options.amountType === 'random' && options.amountMax) {
|
|
527
527
|
amount = Math.random() * (Number(options.amountMax) - amount) + amount;
|
|
528
528
|
}
|
|
529
529
|
const tradeType = options.direction === 'up' ? 'buy' : 'sell';
|
|
530
530
|
const currentWallet = wallets[executeCount % wallets.length];
|
|
531
|
-
// sell
|
|
532
|
-
// buy
|
|
531
|
+
// sell direction: amount is SOL value, convert to equivalent token amount
|
|
532
|
+
// buy direction: pass SOL amount directly
|
|
533
533
|
let amountForTrade;
|
|
534
534
|
if (tradeType === 'sell') {
|
|
535
|
-
// SOL ->
|
|
535
|
+
// SOL -> token amount: amount(SOL) / pricePerToken(SOL)
|
|
536
536
|
const tokenAmount = currentPrice > 0 ? amount / currentPrice : 0;
|
|
537
537
|
amountForTrade = String(tokenAmount);
|
|
538
|
-
info(
|
|
538
|
+
info(`Trade #${executeCount} ${tradeType}, amount: ${amount.toFixed(6)} SOL ≈ ${tokenAmount.toFixed(2)} tokens, current price: ${currentPrice}`);
|
|
539
539
|
}
|
|
540
540
|
else {
|
|
541
541
|
amountForTrade = String(amount);
|
|
542
|
-
info(
|
|
542
|
+
info(`Trade #${executeCount} ${tradeType}, amount: ${amount.toFixed(6)} SOL, current price: ${currentPrice}`);
|
|
543
543
|
}
|
|
544
|
-
//
|
|
544
|
+
// Suppress SDK console.log in JSON mode
|
|
545
545
|
if (getOutputFormat() === 'json')
|
|
546
546
|
suppressConsole();
|
|
547
547
|
try {
|
|
@@ -568,11 +568,11 @@ export function registerToolsCommands(program) {
|
|
|
568
568
|
}
|
|
569
569
|
catch (e) {
|
|
570
570
|
restoreConsole();
|
|
571
|
-
error(
|
|
571
|
+
error(`Execution error: ${e.message}`);
|
|
572
572
|
}
|
|
573
573
|
};
|
|
574
574
|
await execute();
|
|
575
|
-
//
|
|
575
|
+
// Continuous execution (daemon mode)
|
|
576
576
|
const getInterval = () => {
|
|
577
577
|
if (options.intervalType === 'random' && options.intervalMax) {
|
|
578
578
|
const min = Number(options.interval);
|
|
@@ -590,13 +590,13 @@ export function registerToolsCommands(program) {
|
|
|
590
590
|
scheduleNext();
|
|
591
591
|
process.on('SIGINT', () => {
|
|
592
592
|
restoreConsole();
|
|
593
|
-
info(
|
|
593
|
+
info(`Price robot stopped, executed ${executeCount} times, total cost ${totalCost.toFixed(6)} SOL`);
|
|
594
594
|
process.exit(0);
|
|
595
595
|
});
|
|
596
596
|
}
|
|
597
597
|
catch (e) {
|
|
598
598
|
restoreConsole();
|
|
599
|
-
error('
|
|
599
|
+
error('Price robot start failed', e.message);
|
|
600
600
|
process.exit(1);
|
|
601
601
|
}
|
|
602
602
|
});
|