@solard/cli 0.2.23 → 0.2.26
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 +3 -3
- package/src/launch-watch-command.ts +4 -62
- package/src/price-feed-server.ts +13 -68
- package/src/trade-strategy-command.ts +15 -33
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solard/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.26",
|
|
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
|
-
"@solard/sdk": "0.2.
|
|
22
|
+
"@solard/core": "0.2.26",
|
|
23
|
+
"@solard/sdk": "0.2.26",
|
|
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
|
|
168
|
+
if (!state) return;
|
|
216
169
|
state.lastSeenAtMs = event.atMs;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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();
|
package/src/price-feed-server.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1139
|
-
|
|
1140
|
-
|
|
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:
|
|
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
|
|
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"))
|
|
1171
|
-
report(`
|
|
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
|
-
|
|
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
|
-
|
|
1205
|
-
|
|
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
|
|
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
|
-
|
|
1213
|
+
tradeErrors,
|
|
1232
1214
|
});
|
|
1233
1215
|
}
|
|
1234
1216
|
} finally {
|