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 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('1.0.0');
18
+ .version(require('./package.json').version);
19
19
 
20
20
  program
21
21
  .command('run')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "byreal-test",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "description": "Byreal CLI test suite - runs byreal-cli commands and generates test reports",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -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 = '0.01';
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 = hasAlert
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 ${USDC_MINT}`,
127
+ `--input-mint ${inputMint}`,
119
128
  `--output-mint ${outputMint}`,
120
- `--amount ${SWAP_AMOUNT}`,
129
+ `--amount ${amount}`,
121
130
  mode,
122
131
  '-o json',
123
132
  ].join(' ');
124
133
 
125
- console.log(chalk.dim(` → ${dryRun ? '[DRY RUN] ' : ''}Executing swap: ${SWAP_AMOUNT} USDC → ...`));
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 ${dryRun ? 'previewed' : 'submitted'} in ${fmt(elapsed)}`));
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 is built as: baseUrl?tokenAddress={outputMint}&startTime={...}&endTime={...}&klineType=1m
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 && result.data !== null
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, pollOpts) {
201
- const { window1Ms, window2Ms, intervalMs } = pollOpts;
202
- const startAt = Date.now();
203
- const deadline = startAt + window1Ms + window2Ms;
204
- let phase = 1;
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
- while (Date.now() < deadline) {
209
- const now = Date.now();
210
- if (now > startAt + window1Ms && phase === 1) {
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
- if (data?.result?.success === true && data?.result?.data != null) {
221
- return finish('kline', true, phase, Date.now() - startAt);
222
- }
229
+ const candles = data?.result?.data;
223
230
 
224
- await sleep(intervalMs);
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
- return finish('kline', false, phase, Date.now() - startAt);
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
- console.log(` ${tag} [${label}] appeared in ${chalk.bold(fmt(elapsed))}`);
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
- let token;
266
- try {
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
- // execute swap
274
- let swapData;
275
- const swapAt = Date.now();
276
- try {
277
- swapData = executeSwap(token.mint, dryRun);
278
- } catch (err) {
279
- console.error(chalk.red(` ✗ Swap failed: ${err.message}`));
280
- process.exit(1);
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, pollOpts);
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
- if (larkWebhook) {
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