byreal-test 1.2.2 → 1.3.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/cli.js +1 -1
- package/package.json +1 -1
- package/src/swap-runner.js +120 -54
package/cli.js
CHANGED
|
@@ -15,7 +15,7 @@ const program = new Command();
|
|
|
15
15
|
program
|
|
16
16
|
.name('byreal-test')
|
|
17
17
|
.description('Byreal CLI test suite — validates byreal-cli commands and generates reports')
|
|
18
|
-
.version('
|
|
18
|
+
.version(require('./package.json').version);
|
|
19
19
|
|
|
20
20
|
program
|
|
21
21
|
.command('run')
|
package/package.json
CHANGED
package/src/swap-runner.js
CHANGED
|
@@ -5,7 +5,12 @@ const { parseJsonOutput } = require('./runner');
|
|
|
5
5
|
const chalk = require('chalk');
|
|
6
6
|
|
|
7
7
|
const USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
|
|
8
|
-
const SWAP_AMOUNT = '
|
|
8
|
+
const SWAP_AMOUNT = '1';
|
|
9
|
+
|
|
10
|
+
const EXCLUDED_SYMBOLS = new Set([
|
|
11
|
+
'SOL', 'bbSOL', 'USDT', 'USD1', 'CRCLx', 'NVDAx',
|
|
12
|
+
'XAUt0', 'METAx', 'AMZNx', 'COINx', 'APPLx', 'MCDx',
|
|
13
|
+
]);
|
|
9
14
|
|
|
10
15
|
// ─── lark notify ─────────────────────────────────────────────────────────────
|
|
11
16
|
|
|
@@ -22,8 +27,11 @@ async function sendLark(webhookUrl, { token, ctx, results }) {
|
|
|
22
27
|
const time = utc8Label();
|
|
23
28
|
const hasAlert = Object.values(results).some(r => r.alertLevel === 'alert');
|
|
24
29
|
const hasWarn = Object.values(results).some(r => r.alertLevel === 'warn');
|
|
30
|
+
const swapFailed = results.swap?.swapFailed;
|
|
25
31
|
|
|
26
|
-
const statusLine =
|
|
32
|
+
const statusLine = swapFailed
|
|
33
|
+
? '🚨 swap 连续5次失败,请立即检查'
|
|
34
|
+
: hasAlert
|
|
27
35
|
? '⚠️ swap 存在超时,请关注'
|
|
28
36
|
: hasWarn
|
|
29
37
|
? '🐢 swap 数据延迟,但已到达'
|
|
@@ -31,6 +39,7 @@ async function sendLark(webhookUrl, { token, ctx, results }) {
|
|
|
31
39
|
|
|
32
40
|
const fmtResult = (r) => {
|
|
33
41
|
if (!r) return '—';
|
|
42
|
+
if (r?.swapFailed) return '🚨 连续5次swap失败';
|
|
34
43
|
if (r.alertLevel === 'alert') return '⚠️ 超时未出现(> 1分钟)';
|
|
35
44
|
if (r.alertLevel === 'warn') return `🐢 ${fmt(r.elapsed)}(第二窗口)`;
|
|
36
45
|
return `${fmt(r.elapsed)}`;
|
|
@@ -98,7 +107,7 @@ function pickRandomToken() {
|
|
|
98
107
|
const tokens = res?.data?.tokens;
|
|
99
108
|
if (!Array.isArray(tokens) || tokens.length === 0)
|
|
100
109
|
throw new Error('tokens list returned empty or invalid data');
|
|
101
|
-
const eligible = tokens.filter(t => t.mint !== USDC_MINT);
|
|
110
|
+
const eligible = tokens.filter(t => t.mint !== USDC_MINT && !EXCLUDED_SYMBOLS.has(t.symbol));
|
|
102
111
|
if (eligible.length === 0) throw new Error('no eligible tokens (only USDC found)');
|
|
103
112
|
const token = eligible[Math.floor(Math.random() * eligible.length)];
|
|
104
113
|
console.log(
|
|
@@ -111,25 +120,25 @@ function pickRandomToken() {
|
|
|
111
120
|
|
|
112
121
|
// ─── swap execution ──────────────────────────────────────────────────────────
|
|
113
122
|
|
|
114
|
-
function executeSwap(outputMint, dryRun) {
|
|
123
|
+
function executeSwap({ inputMint, outputMint, amount, dryRun, label }) {
|
|
115
124
|
const mode = dryRun ? '--dry-run' : '--confirm';
|
|
116
125
|
const cmd = [
|
|
117
126
|
'byreal-cli swap execute',
|
|
118
|
-
`--input-mint ${
|
|
127
|
+
`--input-mint ${inputMint}`,
|
|
119
128
|
`--output-mint ${outputMint}`,
|
|
120
|
-
`--amount ${
|
|
129
|
+
`--amount ${amount}`,
|
|
121
130
|
mode,
|
|
122
131
|
'-o json',
|
|
123
132
|
].join(' ');
|
|
124
133
|
|
|
125
|
-
console.log(chalk.dim(` → ${dryRun ? '[DRY RUN] ' : ''}
|
|
134
|
+
console.log(chalk.dim(` → ${dryRun ? '[DRY RUN] ' : ''}${label}`));
|
|
126
135
|
const t0 = Date.now();
|
|
127
136
|
const out = execSync(cmd, { encoding: 'utf8', timeout: 60000 });
|
|
128
137
|
const elapsed = Date.now() - t0;
|
|
129
138
|
|
|
130
139
|
const res = parseJsonOutput(out);
|
|
131
140
|
if (!res?.success) throw new Error(`swap command failed: ${JSON.stringify(res)}`);
|
|
132
|
-
console.log(chalk.green(` ✓ Swap
|
|
141
|
+
console.log(chalk.green(` ✓ Swap submitted in ${fmt(elapsed)}`));
|
|
133
142
|
return res.data;
|
|
134
143
|
}
|
|
135
144
|
|
|
@@ -190,40 +199,41 @@ async function pollOrderApi(baseUrl, ctx, pollOpts) {
|
|
|
190
199
|
|
|
191
200
|
/**
|
|
192
201
|
* Poll kline API.
|
|
193
|
-
* URL
|
|
194
|
-
*
|
|
195
|
-
* endTime = Math.floor(swapAt / 1000) + 60 (swap time + 1 min, unix seconds)
|
|
196
|
-
* startTime = endTime - 3600 (1 hour window)
|
|
202
|
+
* URL: baseUrl?tokenAddress={outputMint}&startTime={swapAt-2min}&endTime={swapAt+2min}&klineType=1m
|
|
197
203
|
*
|
|
198
|
-
* Found when: result.success === true &&
|
|
204
|
+
* Found when: result.success === true && at least one candle has v > 0
|
|
205
|
+
* Retry schedule: wait 10s → check, wait 30s → check, wait 60s → check, then alert
|
|
199
206
|
*/
|
|
200
|
-
async function pollKlineApi(baseUrl, ctx
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
const
|
|
204
|
-
|
|
207
|
+
async function pollKlineApi(baseUrl, ctx) {
|
|
208
|
+
const startAt = Date.now();
|
|
209
|
+
const swapSec = Math.floor(ctx.swapAt / 1000);
|
|
210
|
+
const startTime = swapSec - 300; // swap time - 5 min
|
|
211
|
+
const endTime = swapSec + 300; // swap time + 5 min
|
|
212
|
+
const url = `${baseUrl}?tokenAddress=${ctx.outputMint}&startTime=${startTime}&endTime=${endTime}&klineType=1m`;
|
|
213
|
+
|
|
214
|
+
// Check at: +10s, +40s, +5min from polling start
|
|
215
|
+
// alertLevel: window1=ok, window2=warn, window3=warn(slow but acceptable), timeout=alert
|
|
216
|
+
const windows = [
|
|
217
|
+
{ wait: 10_000, label: null, phase: 1 },
|
|
218
|
+
{ wait: 30_000, label: '(+30s)', phase: 2 },
|
|
219
|
+
{ wait: 300_000 - 10_000 - 30_000, label: '(+5min)', phase: 3 },
|
|
220
|
+
];
|
|
205
221
|
|
|
206
222
|
process.stdout.write(chalk.dim(' ⏳ Polling [kline] '));
|
|
207
223
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
if (
|
|
211
|
-
process.stdout.write(chalk.yellow('(+30s) '));
|
|
212
|
-
phase = 2;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const endTime = Math.floor(ctx.swapAt / 1000) + 60;
|
|
216
|
-
const startTime = endTime - 3600;
|
|
217
|
-
const url = `${baseUrl}?tokenAddress=${ctx.outputMint}&startTime=${startTime}&endTime=${endTime}&klineType=1m`;
|
|
224
|
+
for (const w of windows) {
|
|
225
|
+
await sleep(w.wait);
|
|
226
|
+
if (w.label) process.stdout.write(chalk.yellow(w.label + ' '));
|
|
218
227
|
|
|
219
228
|
const data = await fetchJson(url);
|
|
220
|
-
|
|
221
|
-
return finish('kline', true, phase, Date.now() - startAt);
|
|
222
|
-
}
|
|
229
|
+
const candles = data?.result?.data;
|
|
223
230
|
|
|
224
|
-
|
|
231
|
+
if (Array.isArray(candles) && candles.some(c => parseFloat(c.v) > 0)) {
|
|
232
|
+
return finish('kline', true, w.phase, Date.now() - startAt);
|
|
233
|
+
}
|
|
225
234
|
}
|
|
226
|
-
|
|
235
|
+
|
|
236
|
+
return finish('kline', false, 3, Date.now() - startAt);
|
|
227
237
|
}
|
|
228
238
|
|
|
229
239
|
// ─── common finish ───────────────────────────────────────────────────────────
|
|
@@ -235,7 +245,8 @@ function finish(label, found, phase, elapsed) {
|
|
|
235
245
|
if (found) {
|
|
236
246
|
const level = phase === 1 ? 'ok' : 'warn';
|
|
237
247
|
const tag = phase === 1 ? chalk.green('✓') : chalk.yellow('⚠');
|
|
238
|
-
|
|
248
|
+
const note = phase === 3 ? ' (5min内)' : '';
|
|
249
|
+
console.log(` ${tag} [${label}] appeared in ${chalk.bold(fmt(elapsed))}${note}`);
|
|
239
250
|
return { found: true, elapsed, alertLevel: level };
|
|
240
251
|
}
|
|
241
252
|
console.log(
|
|
@@ -261,23 +272,43 @@ async function runSwap(opts = {}) {
|
|
|
261
272
|
|
|
262
273
|
console.log(chalk.bold('\n byreal-test run_swap\n'));
|
|
263
274
|
|
|
264
|
-
// ① pick random token
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
token = pickRandomToken();
|
|
268
|
-
} catch (err) {
|
|
269
|
-
console.error(chalk.red(` ✗ Token selection failed: ${err.message}`));
|
|
270
|
-
process.exit(1);
|
|
271
|
-
}
|
|
275
|
+
// ① pick random token + retry swap up to 5 times
|
|
276
|
+
const MAX_SWAP_ATTEMPTS = 5;
|
|
277
|
+
let token, swapData, swapAt;
|
|
272
278
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
279
|
+
for (let attempt = 1; attempt <= MAX_SWAP_ATTEMPTS; attempt++) {
|
|
280
|
+
try {
|
|
281
|
+
token = pickRandomToken();
|
|
282
|
+
} catch (err) {
|
|
283
|
+
console.error(chalk.red(` ✗ Token selection failed: ${err.message}`));
|
|
284
|
+
process.exit(1);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
swapAt = Date.now();
|
|
288
|
+
try {
|
|
289
|
+
swapData = executeSwap({
|
|
290
|
+
inputMint: USDC_MINT,
|
|
291
|
+
outputMint: token.mint,
|
|
292
|
+
amount: SWAP_AMOUNT,
|
|
293
|
+
dryRun,
|
|
294
|
+
label: `Buy: ${SWAP_AMOUNT} USDC → ${token.symbol}`,
|
|
295
|
+
});
|
|
296
|
+
break; // swap succeeded, proceed
|
|
297
|
+
} catch (err) {
|
|
298
|
+
console.log(chalk.yellow(` ⚠ Swap attempt ${attempt}/${MAX_SWAP_ATTEMPTS} failed for ${token.symbol}: ${err.message.split('\n')[0]}`));
|
|
299
|
+
if (attempt === MAX_SWAP_ATTEMPTS) {
|
|
300
|
+
console.error(chalk.red(` ✗ All ${MAX_SWAP_ATTEMPTS} swap attempts failed`));
|
|
301
|
+
if (larkWebhook) {
|
|
302
|
+
await sendLark(larkWebhook, {
|
|
303
|
+
token: { symbol: 'N/A', mint: '' },
|
|
304
|
+
ctx: { txHash: '', orderId: '', outputMint: '' },
|
|
305
|
+
results: { swap: { alertLevel: 'alert', elapsed: 0, swapFailed: true } },
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
console.log(chalk.dim(' → Picking another token...\n'));
|
|
311
|
+
}
|
|
281
312
|
}
|
|
282
313
|
|
|
283
314
|
const ctx = {
|
|
@@ -310,7 +341,7 @@ async function runSwap(opts = {}) {
|
|
|
310
341
|
|
|
311
342
|
// ⑤ poll kline API
|
|
312
343
|
if (klineApiUrl) {
|
|
313
|
-
results.kline = await pollKlineApi(klineApiUrl, ctx
|
|
344
|
+
results.kline = await pollKlineApi(klineApiUrl, ctx);
|
|
314
345
|
} else {
|
|
315
346
|
console.log(chalk.dim(' – kline API not configured (use --kline-api <url>)'));
|
|
316
347
|
}
|
|
@@ -329,12 +360,47 @@ async function runSwap(opts = {}) {
|
|
|
329
360
|
}
|
|
330
361
|
console.log('');
|
|
331
362
|
|
|
332
|
-
// ⑦ lark notification
|
|
333
|
-
|
|
363
|
+
// ⑦ lark notification — only on alert/warn
|
|
364
|
+
const hasAlert = Object.values(results).some(r => r.alertLevel === 'alert');
|
|
365
|
+
const hasWarn = Object.values(results).some(r => r.alertLevel === 'warn');
|
|
366
|
+
if (larkWebhook && (hasAlert || hasWarn)) {
|
|
334
367
|
await sendLark(larkWebhook, { token, ctx, results });
|
|
368
|
+
} else if (larkWebhook) {
|
|
369
|
+
console.log(chalk.dim(' → All OK, Lark skipped'));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ⑧ wait 5 min then sell AToken → USDC
|
|
373
|
+
const receivedAmount = swapData.uiOutAmount;
|
|
374
|
+
if (receivedAmount && parseFloat(receivedAmount) > 0 && !dryRun) {
|
|
375
|
+
const SELL_WAIT = 5 * 60_000;
|
|
376
|
+
console.log(chalk.dim(`\n Waiting 5 min before selling ${token.symbol}...`));
|
|
377
|
+
await sleep(SELL_WAIT);
|
|
378
|
+
|
|
379
|
+
console.log('');
|
|
380
|
+
try {
|
|
381
|
+
const sellData = executeSwap({
|
|
382
|
+
inputMint: token.mint,
|
|
383
|
+
outputMint: USDC_MINT,
|
|
384
|
+
amount: receivedAmount,
|
|
385
|
+
dryRun: false,
|
|
386
|
+
label: `Sell: ${receivedAmount} ${token.symbol} → USDC`,
|
|
387
|
+
});
|
|
388
|
+
const sellHash = (sellData.signatures || [])[0] || '';
|
|
389
|
+
console.log(chalk.dim(` → sell txHash: ${sellHash}`));
|
|
390
|
+
} catch (err) {
|
|
391
|
+
console.log(chalk.yellow(` ⚠ Sell failed: ${err.message.split('\n')[0]}`));
|
|
392
|
+
if (larkWebhook) {
|
|
393
|
+
await sendLark(larkWebhook, {
|
|
394
|
+
token,
|
|
395
|
+
ctx: { ...ctx, txHash: '' },
|
|
396
|
+
results: { sell: { alertLevel: 'alert', elapsed: 0, sellFailed: true } },
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
} else if (dryRun) {
|
|
401
|
+
console.log(chalk.dim('\n [DRY RUN] Sell step skipped'));
|
|
335
402
|
}
|
|
336
403
|
|
|
337
|
-
const hasAlert = Object.values(results).some(r => r.alertLevel === 'alert');
|
|
338
404
|
return { token, swapData, ctx, results, hasAlert };
|
|
339
405
|
}
|
|
340
406
|
|