@solard/cli 0.2.23 → 0.2.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solard/cli",
3
- "version": "0.2.23",
3
+ "version": "0.2.25",
4
4
  "description": "Command-line interface for the Solard SDK.",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -19,8 +19,8 @@
19
19
  },
20
20
  "dependencies": {
21
21
  "@solana/web3.js": "^1.98.4",
22
- "@solard/core": "0.2.23",
23
- "@solard/sdk": "0.2.23",
22
+ "@solard/core": "0.2.25",
23
+ "@solard/sdk": "0.2.25",
24
24
  "bs58": "^6.0.0",
25
25
  "measure-fn": "5.2.0"
26
26
  },
@@ -22,9 +22,6 @@ type WatchState = {
22
22
  lastNotifiedAthUsd: number | null;
23
23
  };
24
24
 
25
- const WSOL_MINT = "So11111111111111111111111111111111111111112";
26
- const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
27
-
28
25
  function flag(flags: Flags, key: string): string | undefined {
29
26
  const value = flags.get(key);
30
27
  return value && value !== "true" ? value : undefined;
@@ -87,38 +84,6 @@ function wsUrl(flags: Flags): string | undefined {
87
84
  );
88
85
  }
89
86
 
90
- async function fetchJson(url: string): Promise<unknown> {
91
- const controller = new AbortController();
92
- const timer = setTimeout(() => controller.abort(), 5_000);
93
- try {
94
- const response = await fetch(url, {
95
- headers: { accept: "application/json" },
96
- signal: controller.signal,
97
- });
98
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
99
- return await response.json();
100
- } finally {
101
- clearTimeout(timer);
102
- }
103
- }
104
-
105
- async function loadSolUsd(): Promise<number> {
106
- try {
107
- const raw = (await fetchJson(
108
- "https://api.coinbase.com/v2/prices/SOL-USD/spot",
109
- )) as { data?: { amount?: unknown } };
110
- const value = Number(raw.data?.amount);
111
- if (Number.isFinite(value) && value > 0) return value;
112
- } catch {}
113
- const raw = (await fetchJson(
114
- "https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd",
115
- )) as { solana?: { usd?: unknown } };
116
- const value = Number(raw.solana?.usd);
117
- if (!Number.isFinite(value) || value <= 0)
118
- throw new Error("SOL/USD unavailable");
119
- return value;
120
- }
121
-
122
87
  export async function runLaunchWatchCommand(args: {
123
88
  flags: Flags;
124
89
  emit: Emit;
@@ -166,21 +131,9 @@ export async function runLaunchWatchCommand(args: {
166
131
  let thresholdNotifications = 0;
167
132
  let athNotifications = 0;
168
133
  let ignoredMayhem = 0;
169
- let solUsd: number | null = null;
170
134
  let stopped = false;
171
135
  let tradeSubscription: TradeSubscription | null = null;
172
136
 
173
- const refreshSolUsd = async () => {
174
- try {
175
- solUsd = await loadSolUsd();
176
- } catch (error) {
177
- report("sol/usd unavailable", {
178
- error: error instanceof Error ? error.message : String(error),
179
- cached: solUsd,
180
- });
181
- }
182
- };
183
-
184
137
  const onLaunch = (event: LaunchEvent) => {
185
138
  if (!venues.includes(event.venue)) return;
186
139
  if (event.isMayhemMode === true) {
@@ -212,18 +165,11 @@ export async function runLaunchWatchCommand(args: {
212
165
 
213
166
  const onTrade = (event: TradeEvent) => {
214
167
  const state = states.get(event.mint);
215
- if (!state || event.priceQuote == null) return;
168
+ if (!state) return;
216
169
  state.lastSeenAtMs = event.atMs;
217
- let priceSol: number | null = null;
218
- let priceUsd: number | null = null;
219
- if (event.quoteMint === WSOL_MINT) {
220
- priceSol = event.priceQuote;
221
- priceUsd = solUsd == null ? null : event.priceQuote * solUsd;
222
- } else if (event.quoteMint === USDC_MINT) {
223
- priceUsd = event.priceQuote;
224
- priceSol = solUsd == null ? null : event.priceQuote / solUsd;
225
- }
226
- const marketCapUsd = priceUsd == null ? null : priceUsd * state.supplyUi;
170
+ const priceSol = event.market.priceSol;
171
+ const priceUsd = event.market.priceUsd;
172
+ const marketCapUsd = event.market.marketCapUsd;
227
173
  prices += 1;
228
174
  if (showPrices)
229
175
  report("price", {
@@ -317,8 +263,6 @@ export async function runLaunchWatchCommand(args: {
317
263
  },
318
264
  });
319
265
 
320
- await refreshSolUsd();
321
- const solTimer = setInterval(() => void refreshSolUsd(), 60_000);
322
266
  const stop = () => {
323
267
  stopped = true;
324
268
  controller.abort();
@@ -353,7 +297,6 @@ export async function runLaunchWatchCommand(args: {
353
297
  thresholdNotifications,
354
298
  athNotifications,
355
299
  watchedTokens: tradeSubscription?.listTokens().length ?? 0,
356
- solUsd,
357
300
  });
358
301
  },
359
302
  Math.max(1_000, Math.trunc(numberFlag(args.flags, "heartbeat-ms", 15_000))),
@@ -362,7 +305,6 @@ export async function runLaunchWatchCommand(args: {
362
305
  try {
363
306
  while (!stopped) await new Promise((resolve) => setTimeout(resolve, 250));
364
307
  } finally {
365
- clearInterval(solTimer);
366
308
  clearInterval(heartbeat);
367
309
  controller.abort();
368
310
  await launchSubscription.close();
@@ -1,6 +1,7 @@
1
1
  import { configure, createMeasure } from "measure-fn";
2
2
  import { Connection } from "@solana/web3.js";
3
3
  import {
4
+ getSolUsdPrice,
4
5
  subscribeLaunches,
5
6
  subscribeTrades,
6
7
  type LaunchEvent,
@@ -26,9 +27,6 @@ type MintState = {
26
27
  isMayhemMode: boolean | null;
27
28
  };
28
29
 
29
- const WSOL_MINT = "So11111111111111111111111111111111111111112";
30
- const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
31
-
32
30
  function flag(flags: Flags, key: string): string | undefined {
33
31
  const value = flags.get(key);
34
32
  return value && value !== "true" ? value : undefined;
@@ -92,39 +90,6 @@ function errorText(error: unknown): string {
92
90
  return error instanceof Error ? error.message : String(error);
93
91
  }
94
92
 
95
- async function fetchJson(url: string): Promise<unknown> {
96
- const controller = new AbortController();
97
- const timer = setTimeout(() => controller.abort(), 5_000);
98
- try {
99
- const response = await fetch(url, {
100
- headers: { accept: "application/json" },
101
- signal: controller.signal,
102
- });
103
- if (!response.ok) throw new Error(`HTTP ${response.status}`);
104
- return await response.json();
105
- } finally {
106
- clearTimeout(timer);
107
- }
108
- }
109
-
110
- async function loadSolUsd(): Promise<number> {
111
- try {
112
- const raw = (await fetchJson(
113
- "https://api.coinbase.com/v2/prices/SOL-USD/spot",
114
- )) as { data?: { amount?: unknown } };
115
- const value = Number(raw.data?.amount);
116
- if (Number.isFinite(value) && value > 0) return value;
117
- } catch {}
118
- const raw = (await fetchJson(
119
- "https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd",
120
- )) as { solana?: { usd?: unknown } };
121
- const value = Number(raw.solana?.usd);
122
- if (!Number.isFinite(value) || value <= 0) {
123
- throw new Error("SOL/USD unavailable");
124
- }
125
- return value;
126
- }
127
-
128
93
  export async function runPriceFeedServerCommand(args: {
129
94
  flags: Flags;
130
95
  emit: Emit;
@@ -159,6 +124,13 @@ export async function runPriceFeedServerCommand(args: {
159
124
  let tradeSubscription: TradeSubscription | null = null;
160
125
  let allPriceClients = 0;
161
126
  let solUsd: number | null = null;
127
+ try {
128
+ const quote = await getSolUsdPrice();
129
+ solUsd = quote.price;
130
+ report("sol-usd", quote);
131
+ } catch (error) {
132
+ report("sol-usd-unavailable", { error: errorText(error) });
133
+ }
162
134
  let launchCount = 0;
163
135
  let tradeCount = 0;
164
136
  let priceCount = 0;
@@ -237,18 +209,9 @@ export async function runPriceFeedServerCommand(args: {
237
209
 
238
210
  const onTrade = (event: TradeEvent): void => {
239
211
  tradeCount += 1;
240
- if (event.priceQuote == null) return;
241
212
  const state = mintState.get(event.mint);
242
213
  if (state?.isMayhemMode === true && !includeMayhem) return;
243
- let priceSol: number | null = null;
244
- let priceUsd: number | null = null;
245
- if (event.quoteMint === WSOL_MINT) {
246
- priceSol = event.priceQuote;
247
- priceUsd = solUsd == null ? null : event.priceQuote * solUsd;
248
- } else if (event.quoteMint === USDC_MINT) {
249
- priceUsd = event.priceQuote;
250
- priceSol = solUsd == null ? null : event.priceQuote / solUsd;
251
- }
214
+ solUsd = event.market.solUsd ?? solUsd;
252
215
  const value: PriceFeedPrice = {
253
216
  type: "price",
254
217
  atMs: event.atMs,
@@ -256,13 +219,10 @@ export async function runPriceFeedServerCommand(args: {
256
219
  slot: event.slot,
257
220
  mint: event.mint,
258
221
  venue: event.venue,
259
- priceSol,
260
- priceUsd,
261
- marketCapUsd:
262
- priceUsd != null && state?.supplyUi != null
263
- ? priceUsd * state.supplyUi
264
- : null,
265
- source: `${event.venue}-trade-event`,
222
+ priceSol: event.market.priceSol,
223
+ priceUsd: event.market.priceUsd,
224
+ marketCapUsd: event.market.marketCapUsd,
225
+ source: `${event.venue}-trade`,
266
226
  };
267
227
  latest.set(event.mint, value);
268
228
  priceCount += 1;
@@ -344,7 +304,6 @@ export async function runPriceFeedServerCommand(args: {
344
304
  connection,
345
305
  tokens: [],
346
306
  commitment: "confirmed",
347
- metadata: false,
348
307
  signal: controller.signal,
349
308
  onTrade,
350
309
  onStatus(event, data) {
@@ -428,19 +387,6 @@ export async function runPriceFeedServerCommand(args: {
428
387
  },
429
388
  });
430
389
 
431
- const refreshSolUsd = async (): Promise<void> => {
432
- try {
433
- solUsd = await loadSolUsd();
434
- } catch (error) {
435
- report("sol/usd unavailable", {
436
- error: errorText(error),
437
- cached: solUsd,
438
- });
439
- }
440
- };
441
-
442
- await refreshSolUsd();
443
- const solTimer = setInterval(() => void refreshSolUsd(), 60_000);
444
390
  const heartbeat = setInterval(
445
391
  () =>
446
392
  report("heartbeat", {
@@ -478,7 +424,6 @@ export async function runPriceFeedServerCommand(args: {
478
424
  try {
479
425
  await Promise.all([launchSubscription.closed, tradeSubscription.closed]);
480
426
  } finally {
481
- clearInterval(solTimer);
482
427
  clearInterval(heartbeat);
483
428
  process.removeListener("SIGINT", stop);
484
429
  process.removeListener("SIGTERM", stop);
@@ -476,23 +476,6 @@ async function fetchJson(url: string): Promise<any> {
476
476
  }
477
477
  }
478
478
 
479
- async function fetchSolUsd(): Promise<number> {
480
- try {
481
- const raw = await fetchJson(
482
- "https://api.coinbase.com/v2/prices/SOL-USD/spot",
483
- );
484
- const value = Number(raw?.data?.amount);
485
- if (Number.isFinite(value) && value > 0) return value;
486
- } catch {}
487
- const raw = await fetchJson(
488
- "https://api.coingecko.com/api/v3/simple/price?ids=solana&vs_currencies=usd",
489
- );
490
- const value = Number(raw?.solana?.usd);
491
- if (!Number.isFinite(value) || value <= 0)
492
- throw new Error("SOL/USD price unavailable");
493
- return value;
494
- }
495
-
496
479
  async function discoverRaydiumPools(mint: string): Promise<string[]> {
497
480
  const query = new URLSearchParams({
498
481
  mint1: mint,
@@ -1017,7 +1000,7 @@ export async function runTradeStrategyCommand(args: {
1017
1000
  let priceCount = 0;
1018
1001
  let lastPrice: StrategyPriceTick | null = null;
1019
1002
  let callbackErrors = 0;
1020
- let marketErrors = 0;
1003
+ let tradeErrors = 0;
1021
1004
  let callbackQueue: Promise<void> = Promise.resolve();
1022
1005
 
1023
1006
  try {
@@ -1135,18 +1118,17 @@ export async function runTradeStrategyCommand(args: {
1135
1118
  };
1136
1119
 
1137
1120
  const onPrice = async (event: TradeEvent) => {
1138
- if (event.mint !== mint || stopped || event.priceQuote == null) return;
1139
- const priceSol = event.quoteMint === WSOL_MINT ? event.priceQuote : null;
1140
- const priceUsd = event.quoteMint === USDC_MINT ? event.priceQuote : null;
1141
- if (priceSol == null && priceUsd == null) return;
1121
+ if (event.mint !== mint || stopped) return;
1122
+ if (event.market.priceSol == null && event.market.priceUsd == null)
1123
+ return;
1142
1124
  const price: StrategyPriceTick = {
1143
1125
  signature: event.signature,
1144
1126
  slot: event.slot,
1145
1127
  atMs: event.atMs,
1146
1128
  mint,
1147
- priceSol,
1148
- priceUsd,
1149
- marketCapUsd: null,
1129
+ priceSol: event.market.priceSol,
1130
+ priceUsd: event.market.priceUsd,
1131
+ marketCapUsd: event.market.marketCapUsd,
1150
1132
  };
1151
1133
  priceCount += 1;
1152
1134
  lastPrice = price;
@@ -1159,7 +1141,7 @@ export async function runTradeStrategyCommand(args: {
1159
1141
  };
1160
1142
 
1161
1143
  const controller = new AbortController();
1162
- const marketSubscription = await subscribeTrades({
1144
+ const tradeSubscription = await subscribeTrades({
1163
1145
  connection: slrd.connection(),
1164
1146
  tokens: [mint],
1165
1147
  signal: controller.signal,
@@ -1167,8 +1149,8 @@ export async function runTradeStrategyCommand(args: {
1167
1149
  callbackQueue = callbackQueue.then(() => onPrice(event));
1168
1150
  },
1169
1151
  onStatus(event, data) {
1170
- if (event.includes("error")) marketErrors += 1;
1171
- report(`market ${event}`, data ?? {});
1152
+ if (event.includes("error")) tradeErrors += 1;
1153
+ report(`trades ${event}`, data ?? {});
1172
1154
  },
1173
1155
  });
1174
1156
 
@@ -1179,7 +1161,7 @@ export async function runTradeStrategyCommand(args: {
1179
1161
  walletAddress,
1180
1162
  live,
1181
1163
  event: "direct-trade-subscription",
1182
- upstreamMarketSubscriptions: marketSubscription.listTokens().length,
1164
+ upstreamTradeSubscriptions: tradeSubscription.listTokens().length,
1183
1165
  });
1184
1166
 
1185
1167
  if (strategy.onStart) {
@@ -1201,8 +1183,8 @@ export async function runTradeStrategyCommand(args: {
1201
1183
  report("strategy heartbeat", {
1202
1184
  prices: priceCount,
1203
1185
  callbackErrors,
1204
- marketErrors,
1205
- upstreamMarketSubscriptions: marketSubscription.listTokens().length,
1186
+ tradeErrors,
1187
+ upstreamTradeSubscriptions: tradeSubscription.listTokens().length,
1206
1188
  executionVenue: executor.venue ?? "unresolved",
1207
1189
  });
1208
1190
  }, heartbeatMs);
@@ -1214,7 +1196,7 @@ export async function runTradeStrategyCommand(args: {
1214
1196
  process.removeListener("SIGINT", stopSignal);
1215
1197
  process.removeListener("SIGTERM", stopSignal);
1216
1198
  controller.abort();
1217
- await marketSubscription.close();
1199
+ await tradeSubscription.close();
1218
1200
  await callbackQueue.catch(() => undefined);
1219
1201
  if (strategy.onStop) {
1220
1202
  try {
@@ -1228,7 +1210,7 @@ export async function runTradeStrategyCommand(args: {
1228
1210
  reason: stopReason,
1229
1211
  prices: priceCount,
1230
1212
  callbackErrors,
1231
- marketErrors,
1213
+ tradeErrors,
1232
1214
  });
1233
1215
  }
1234
1216
  } finally {