@solard/cli 0.2.29 → 0.2.31
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/commands/buy.ts +216 -216
- package/src/commands/sell.ts +248 -248
- package/src/commands/trade-fees.test.ts +15 -15
- package/src/commands/trade-fees.ts +24 -24
- package/src/commands/transfer.test.ts +33 -0
- package/src/commands/transfer.ts +205 -200
- package/src/index.ts +3330 -3330
- package/src/launch-watch-command.ts +301 -301
- package/src/price-feed-client.ts +174 -174
- package/src/price-feed-protocol.ts +45 -45
- package/src/price-feed-server.architecture.test.ts +20 -20
- package/src/price-feed-server.ts +315 -315
- package/src/refs.ts +59 -56
- package/src/trade-strategy-command.ts +1219 -1219
package/src/price-feed-client.ts
CHANGED
|
@@ -1,174 +1,174 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
PriceFeedCommand,
|
|
3
|
-
PriceFeedMessage,
|
|
4
|
-
} from "./price-feed-protocol.ts";
|
|
5
|
-
|
|
6
|
-
function text(value: unknown): string {
|
|
7
|
-
if (typeof value === "string") return value;
|
|
8
|
-
if (value instanceof ArrayBuffer)
|
|
9
|
-
return new TextDecoder().decode(new Uint8Array(value));
|
|
10
|
-
if (ArrayBuffer.isView(value))
|
|
11
|
-
return new TextDecoder().decode(
|
|
12
|
-
new Uint8Array(value.buffer, value.byteOffset, value.byteLength),
|
|
13
|
-
);
|
|
14
|
-
return String(value ?? "");
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function errorText(error: unknown): string {
|
|
18
|
-
return error instanceof Error ? error.message : String(error);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export type PriceFeedClient = {
|
|
22
|
-
readonly connected: boolean;
|
|
23
|
-
subscribeMints(mints: string | readonly string[]): void;
|
|
24
|
-
unsubscribeMints(mints: string | readonly string[]): void;
|
|
25
|
-
listMints(): string[];
|
|
26
|
-
close(): void;
|
|
27
|
-
closed: Promise<void>;
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export async function connectPriceFeed(args: {
|
|
31
|
-
url: string;
|
|
32
|
-
mints?: string | readonly string[];
|
|
33
|
-
onMessage: (message: PriceFeedMessage) => void | Promise<void>;
|
|
34
|
-
onStatus?: (event: string, data?: Record<string, unknown>) => void;
|
|
35
|
-
signal?: AbortSignal;
|
|
36
|
-
}): Promise<PriceFeedClient> {
|
|
37
|
-
let stopped = false;
|
|
38
|
-
let connected = false;
|
|
39
|
-
let ws: WebSocket | null = null;
|
|
40
|
-
const desiredMints = new Set(
|
|
41
|
-
(typeof args.mints === "string" ? [args.mints] : [...(args.mints ?? [])])
|
|
42
|
-
.map((mint) => mint.trim())
|
|
43
|
-
.filter(Boolean),
|
|
44
|
-
);
|
|
45
|
-
let reconnectDelay = 250;
|
|
46
|
-
let resolveClosed!: () => void;
|
|
47
|
-
const closed = new Promise<void>((resolve) => {
|
|
48
|
-
resolveClosed = resolve;
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
const send = (command: PriceFeedCommand): void => {
|
|
52
|
-
if (!connected || !ws || ws.readyState !== WebSocket.OPEN) return;
|
|
53
|
-
ws.send(JSON.stringify(command));
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
const stop = (): void => {
|
|
57
|
-
if (stopped) return;
|
|
58
|
-
stopped = true;
|
|
59
|
-
connected = false;
|
|
60
|
-
try {
|
|
61
|
-
ws?.close();
|
|
62
|
-
} catch {}
|
|
63
|
-
resolveClosed();
|
|
64
|
-
};
|
|
65
|
-
args.signal?.addEventListener("abort", stop, { once: true });
|
|
66
|
-
|
|
67
|
-
const open = async (): Promise<void> => {
|
|
68
|
-
while (!stopped) {
|
|
69
|
-
try {
|
|
70
|
-
const socket = new WebSocket(args.url);
|
|
71
|
-
ws = socket;
|
|
72
|
-
await new Promise<void>((resolve, reject) => {
|
|
73
|
-
let settled = false;
|
|
74
|
-
const timeout = setTimeout(() => {
|
|
75
|
-
if (settled) return;
|
|
76
|
-
settled = true;
|
|
77
|
-
try {
|
|
78
|
-
socket.close();
|
|
79
|
-
} catch {}
|
|
80
|
-
reject(new Error("price feed websocket handshake timed out"));
|
|
81
|
-
}, 5_000);
|
|
82
|
-
socket.addEventListener("open", () => {
|
|
83
|
-
if (settled) return;
|
|
84
|
-
settled = true;
|
|
85
|
-
clearTimeout(timeout);
|
|
86
|
-
connected = true;
|
|
87
|
-
reconnectDelay = 250;
|
|
88
|
-
const mints = [...desiredMints];
|
|
89
|
-
if (mints.length)
|
|
90
|
-
socket.send(JSON.stringify({ op: "subscribe", mints }));
|
|
91
|
-
args.onStatus?.("connected", { url: args.url, mints });
|
|
92
|
-
resolve();
|
|
93
|
-
});
|
|
94
|
-
socket.addEventListener("error", () => {
|
|
95
|
-
if (settled) return;
|
|
96
|
-
settled = true;
|
|
97
|
-
clearTimeout(timeout);
|
|
98
|
-
reject(new Error("price feed websocket error"));
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
await new Promise<void>((resolve) => {
|
|
103
|
-
socket.addEventListener("message", (event) => {
|
|
104
|
-
let message: PriceFeedMessage;
|
|
105
|
-
try {
|
|
106
|
-
message = JSON.parse(text(event.data)) as PriceFeedMessage;
|
|
107
|
-
} catch {
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
void Promise.resolve(args.onMessage(message)).catch((error) =>
|
|
111
|
-
args.onStatus?.("callback-error", { error: errorText(error) }),
|
|
112
|
-
);
|
|
113
|
-
});
|
|
114
|
-
socket.addEventListener("close", () => {
|
|
115
|
-
connected = false;
|
|
116
|
-
args.onStatus?.("disconnected", { url: args.url });
|
|
117
|
-
resolve();
|
|
118
|
-
});
|
|
119
|
-
socket.addEventListener("error", () => {
|
|
120
|
-
connected = false;
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
} catch (error) {
|
|
124
|
-
connected = false;
|
|
125
|
-
args.onStatus?.("connect-error", {
|
|
126
|
-
error: errorText(error),
|
|
127
|
-
retryInMs: reconnectDelay,
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
if (stopped) break;
|
|
131
|
-
await new Promise((resolve) => setTimeout(resolve, reconnectDelay));
|
|
132
|
-
reconnectDelay = Math.min(10_000, reconnectDelay * 2);
|
|
133
|
-
}
|
|
134
|
-
resolveClosed();
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
void open();
|
|
138
|
-
const startedAt = Date.now();
|
|
139
|
-
while (!connected && !stopped && Date.now() - startedAt < 6_000)
|
|
140
|
-
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
141
|
-
if (!connected && !stopped) {
|
|
142
|
-
stop();
|
|
143
|
-
throw new Error(
|
|
144
|
-
`Could not connect to Solard price feed at ${args.url}. Start the shared price feed before starting traders.`,
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return {
|
|
149
|
-
get connected() {
|
|
150
|
-
return connected;
|
|
151
|
-
},
|
|
152
|
-
subscribeMints(value) {
|
|
153
|
-
const mints = (typeof value === "string" ? [value] : [...value])
|
|
154
|
-
.map((mint) => mint.trim())
|
|
155
|
-
.filter((mint) => mint && !desiredMints.has(mint));
|
|
156
|
-
if (!mints.length) return;
|
|
157
|
-
for (const mint of mints) desiredMints.add(mint);
|
|
158
|
-
send({ op: "subscribe", mints });
|
|
159
|
-
},
|
|
160
|
-
unsubscribeMints(value) {
|
|
161
|
-
const mints = (typeof value === "string" ? [value] : [...value])
|
|
162
|
-
.map((mint) => mint.trim())
|
|
163
|
-
.filter((mint) => mint && desiredMints.has(mint));
|
|
164
|
-
if (!mints.length) return;
|
|
165
|
-
for (const mint of mints) desiredMints.delete(mint);
|
|
166
|
-
send({ op: "unsubscribe", mints });
|
|
167
|
-
},
|
|
168
|
-
listMints() {
|
|
169
|
-
return [...desiredMints];
|
|
170
|
-
},
|
|
171
|
-
close: stop,
|
|
172
|
-
closed,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
1
|
+
import type {
|
|
2
|
+
PriceFeedCommand,
|
|
3
|
+
PriceFeedMessage,
|
|
4
|
+
} from "./price-feed-protocol.ts";
|
|
5
|
+
|
|
6
|
+
function text(value: unknown): string {
|
|
7
|
+
if (typeof value === "string") return value;
|
|
8
|
+
if (value instanceof ArrayBuffer)
|
|
9
|
+
return new TextDecoder().decode(new Uint8Array(value));
|
|
10
|
+
if (ArrayBuffer.isView(value))
|
|
11
|
+
return new TextDecoder().decode(
|
|
12
|
+
new Uint8Array(value.buffer, value.byteOffset, value.byteLength),
|
|
13
|
+
);
|
|
14
|
+
return String(value ?? "");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function errorText(error: unknown): string {
|
|
18
|
+
return error instanceof Error ? error.message : String(error);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type PriceFeedClient = {
|
|
22
|
+
readonly connected: boolean;
|
|
23
|
+
subscribeMints(mints: string | readonly string[]): void;
|
|
24
|
+
unsubscribeMints(mints: string | readonly string[]): void;
|
|
25
|
+
listMints(): string[];
|
|
26
|
+
close(): void;
|
|
27
|
+
closed: Promise<void>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export async function connectPriceFeed(args: {
|
|
31
|
+
url: string;
|
|
32
|
+
mints?: string | readonly string[];
|
|
33
|
+
onMessage: (message: PriceFeedMessage) => void | Promise<void>;
|
|
34
|
+
onStatus?: (event: string, data?: Record<string, unknown>) => void;
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
}): Promise<PriceFeedClient> {
|
|
37
|
+
let stopped = false;
|
|
38
|
+
let connected = false;
|
|
39
|
+
let ws: WebSocket | null = null;
|
|
40
|
+
const desiredMints = new Set(
|
|
41
|
+
(typeof args.mints === "string" ? [args.mints] : [...(args.mints ?? [])])
|
|
42
|
+
.map((mint) => mint.trim())
|
|
43
|
+
.filter(Boolean),
|
|
44
|
+
);
|
|
45
|
+
let reconnectDelay = 250;
|
|
46
|
+
let resolveClosed!: () => void;
|
|
47
|
+
const closed = new Promise<void>((resolve) => {
|
|
48
|
+
resolveClosed = resolve;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const send = (command: PriceFeedCommand): void => {
|
|
52
|
+
if (!connected || !ws || ws.readyState !== WebSocket.OPEN) return;
|
|
53
|
+
ws.send(JSON.stringify(command));
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const stop = (): void => {
|
|
57
|
+
if (stopped) return;
|
|
58
|
+
stopped = true;
|
|
59
|
+
connected = false;
|
|
60
|
+
try {
|
|
61
|
+
ws?.close();
|
|
62
|
+
} catch {}
|
|
63
|
+
resolveClosed();
|
|
64
|
+
};
|
|
65
|
+
args.signal?.addEventListener("abort", stop, { once: true });
|
|
66
|
+
|
|
67
|
+
const open = async (): Promise<void> => {
|
|
68
|
+
while (!stopped) {
|
|
69
|
+
try {
|
|
70
|
+
const socket = new WebSocket(args.url);
|
|
71
|
+
ws = socket;
|
|
72
|
+
await new Promise<void>((resolve, reject) => {
|
|
73
|
+
let settled = false;
|
|
74
|
+
const timeout = setTimeout(() => {
|
|
75
|
+
if (settled) return;
|
|
76
|
+
settled = true;
|
|
77
|
+
try {
|
|
78
|
+
socket.close();
|
|
79
|
+
} catch {}
|
|
80
|
+
reject(new Error("price feed websocket handshake timed out"));
|
|
81
|
+
}, 5_000);
|
|
82
|
+
socket.addEventListener("open", () => {
|
|
83
|
+
if (settled) return;
|
|
84
|
+
settled = true;
|
|
85
|
+
clearTimeout(timeout);
|
|
86
|
+
connected = true;
|
|
87
|
+
reconnectDelay = 250;
|
|
88
|
+
const mints = [...desiredMints];
|
|
89
|
+
if (mints.length)
|
|
90
|
+
socket.send(JSON.stringify({ op: "subscribe", mints }));
|
|
91
|
+
args.onStatus?.("connected", { url: args.url, mints });
|
|
92
|
+
resolve();
|
|
93
|
+
});
|
|
94
|
+
socket.addEventListener("error", () => {
|
|
95
|
+
if (settled) return;
|
|
96
|
+
settled = true;
|
|
97
|
+
clearTimeout(timeout);
|
|
98
|
+
reject(new Error("price feed websocket error"));
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
await new Promise<void>((resolve) => {
|
|
103
|
+
socket.addEventListener("message", (event) => {
|
|
104
|
+
let message: PriceFeedMessage;
|
|
105
|
+
try {
|
|
106
|
+
message = JSON.parse(text(event.data)) as PriceFeedMessage;
|
|
107
|
+
} catch {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
void Promise.resolve(args.onMessage(message)).catch((error) =>
|
|
111
|
+
args.onStatus?.("callback-error", { error: errorText(error) }),
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
socket.addEventListener("close", () => {
|
|
115
|
+
connected = false;
|
|
116
|
+
args.onStatus?.("disconnected", { url: args.url });
|
|
117
|
+
resolve();
|
|
118
|
+
});
|
|
119
|
+
socket.addEventListener("error", () => {
|
|
120
|
+
connected = false;
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
} catch (error) {
|
|
124
|
+
connected = false;
|
|
125
|
+
args.onStatus?.("connect-error", {
|
|
126
|
+
error: errorText(error),
|
|
127
|
+
retryInMs: reconnectDelay,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (stopped) break;
|
|
131
|
+
await new Promise((resolve) => setTimeout(resolve, reconnectDelay));
|
|
132
|
+
reconnectDelay = Math.min(10_000, reconnectDelay * 2);
|
|
133
|
+
}
|
|
134
|
+
resolveClosed();
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
void open();
|
|
138
|
+
const startedAt = Date.now();
|
|
139
|
+
while (!connected && !stopped && Date.now() - startedAt < 6_000)
|
|
140
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
141
|
+
if (!connected && !stopped) {
|
|
142
|
+
stop();
|
|
143
|
+
throw new Error(
|
|
144
|
+
`Could not connect to Solard price feed at ${args.url}. Start the shared price feed before starting traders.`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
get connected() {
|
|
150
|
+
return connected;
|
|
151
|
+
},
|
|
152
|
+
subscribeMints(value) {
|
|
153
|
+
const mints = (typeof value === "string" ? [value] : [...value])
|
|
154
|
+
.map((mint) => mint.trim())
|
|
155
|
+
.filter((mint) => mint && !desiredMints.has(mint));
|
|
156
|
+
if (!mints.length) return;
|
|
157
|
+
for (const mint of mints) desiredMints.add(mint);
|
|
158
|
+
send({ op: "subscribe", mints });
|
|
159
|
+
},
|
|
160
|
+
unsubscribeMints(value) {
|
|
161
|
+
const mints = (typeof value === "string" ? [value] : [...value])
|
|
162
|
+
.map((mint) => mint.trim())
|
|
163
|
+
.filter((mint) => mint && desiredMints.has(mint));
|
|
164
|
+
if (!mints.length) return;
|
|
165
|
+
for (const mint of mints) desiredMints.delete(mint);
|
|
166
|
+
send({ op: "unsubscribe", mints });
|
|
167
|
+
},
|
|
168
|
+
listMints() {
|
|
169
|
+
return [...desiredMints];
|
|
170
|
+
},
|
|
171
|
+
close: stop,
|
|
172
|
+
closed,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import type { SolUsdSource, TradeSide, TradeVenue } from "@solard/sdk";
|
|
2
|
-
|
|
3
|
-
export type PriceFeedMarket = {
|
|
4
|
-
quoteMint: string;
|
|
5
|
-
baseDecimals: number;
|
|
6
|
-
quoteDecimals: number;
|
|
7
|
-
supply: number;
|
|
8
|
-
baseReserve: number;
|
|
9
|
-
quoteReserve: number;
|
|
10
|
-
priceQuotePerToken: number;
|
|
11
|
-
marketCapQuote: number;
|
|
12
|
-
priceSol: number | null;
|
|
13
|
-
marketCapSol: number | null;
|
|
14
|
-
solUsd: number | null;
|
|
15
|
-
solUsdSource: SolUsdSource | null;
|
|
16
|
-
solUsdAtMs: number | null;
|
|
17
|
-
priceUsd: number | null;
|
|
18
|
-
marketCapUsd: number | null;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export type PriceFeedPrice = {
|
|
22
|
-
type: "price";
|
|
23
|
-
atMs: number;
|
|
24
|
-
signature: string;
|
|
25
|
-
slot: number;
|
|
26
|
-
mint: string;
|
|
27
|
-
pool: string | null;
|
|
28
|
-
venue: TradeVenue;
|
|
29
|
-
side: TradeSide | null;
|
|
30
|
-
market: PriceFeedMarket;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
export type PriceFeedStatus = {
|
|
34
|
-
type: "status";
|
|
35
|
-
atMs: number;
|
|
36
|
-
event: string;
|
|
37
|
-
data?: Record<string, unknown>;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export type PriceFeedMessage = PriceFeedPrice | PriceFeedStatus;
|
|
41
|
-
|
|
42
|
-
export type PriceFeedCommand =
|
|
43
|
-
| { op: "subscribe"; mints: string[] }
|
|
44
|
-
| { op: "unsubscribe"; mints: string[] }
|
|
45
|
-
| { op: "ping" };
|
|
1
|
+
import type { SolUsdSource, TradeSide, TradeVenue } from "@solard/sdk";
|
|
2
|
+
|
|
3
|
+
export type PriceFeedMarket = {
|
|
4
|
+
quoteMint: string;
|
|
5
|
+
baseDecimals: number;
|
|
6
|
+
quoteDecimals: number;
|
|
7
|
+
supply: number;
|
|
8
|
+
baseReserve: number;
|
|
9
|
+
quoteReserve: number;
|
|
10
|
+
priceQuotePerToken: number;
|
|
11
|
+
marketCapQuote: number;
|
|
12
|
+
priceSol: number | null;
|
|
13
|
+
marketCapSol: number | null;
|
|
14
|
+
solUsd: number | null;
|
|
15
|
+
solUsdSource: SolUsdSource | null;
|
|
16
|
+
solUsdAtMs: number | null;
|
|
17
|
+
priceUsd: number | null;
|
|
18
|
+
marketCapUsd: number | null;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type PriceFeedPrice = {
|
|
22
|
+
type: "price";
|
|
23
|
+
atMs: number;
|
|
24
|
+
signature: string;
|
|
25
|
+
slot: number;
|
|
26
|
+
mint: string;
|
|
27
|
+
pool: string | null;
|
|
28
|
+
venue: TradeVenue;
|
|
29
|
+
side: TradeSide | null;
|
|
30
|
+
market: PriceFeedMarket;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type PriceFeedStatus = {
|
|
34
|
+
type: "status";
|
|
35
|
+
atMs: number;
|
|
36
|
+
event: string;
|
|
37
|
+
data?: Record<string, unknown>;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type PriceFeedMessage = PriceFeedPrice | PriceFeedStatus;
|
|
41
|
+
|
|
42
|
+
export type PriceFeedCommand =
|
|
43
|
+
| { op: "subscribe"; mints: string[] }
|
|
44
|
+
| { op: "unsubscribe"; mints: string[] }
|
|
45
|
+
| { op: "ping" };
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
|
|
5
|
-
const source = readFileSync(
|
|
6
|
-
join(import.meta.dir, "price-feed-server.ts"),
|
|
7
|
-
"utf8",
|
|
8
|
-
);
|
|
9
|
-
|
|
10
|
-
describe("price feed application boundary", () => {
|
|
11
|
-
test("owns one sdk trade subscription and changes its token set dynamically", () => {
|
|
12
|
-
expect(source).toContain("slrd.listenTrades");
|
|
13
|
-
expect(source).toContain("tradeSubscription.add");
|
|
14
|
-
expect(source).toContain("tradeSubscription?.remove");
|
|
15
|
-
expect(source).toContain("const refs = new Map<string, number>()");
|
|
16
|
-
expect(source).not.toContain("subscribeLaunches");
|
|
17
|
-
expect(source).not.toContain('from "@solard/core"');
|
|
18
|
-
expect(source).not.toMatch(/logsSubscribe|onLogs\(/);
|
|
19
|
-
});
|
|
20
|
-
});
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
const source = readFileSync(
|
|
6
|
+
join(import.meta.dir, "price-feed-server.ts"),
|
|
7
|
+
"utf8",
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
describe("price feed application boundary", () => {
|
|
11
|
+
test("owns one sdk trade subscription and changes its token set dynamically", () => {
|
|
12
|
+
expect(source).toContain("slrd.listenTrades");
|
|
13
|
+
expect(source).toContain("tradeSubscription.add");
|
|
14
|
+
expect(source).toContain("tradeSubscription?.remove");
|
|
15
|
+
expect(source).toContain("const refs = new Map<string, number>()");
|
|
16
|
+
expect(source).not.toContain("subscribeLaunches");
|
|
17
|
+
expect(source).not.toContain('from "@solard/core"');
|
|
18
|
+
expect(source).not.toMatch(/logsSubscribe|onLogs\(/);
|
|
19
|
+
});
|
|
20
|
+
});
|