@solard/sdk 0.2.31 → 0.2.37

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/README.md CHANGED
@@ -26,3 +26,36 @@ before another trade. Buy accepts `minOutputRaw`; sell accepts `minOutputLamport
26
26
  Both use confirmed/failed/unresolved results with phase, code, attempts and accounting.
27
27
  `quote`, `curveLiquidity`, `maxSendableSol`, `transferToken` and `exitWallet` share the
28
28
  core implementation. Raydium AMM/CPMM execution does not supply live pool prices.
29
+
30
+ In 0.2.32, sell an exact positive integer number of raw token units with the
31
+ object API. For a six-decimal mint, `"1000000"` is one token:
32
+
33
+ ```ts
34
+ const sold = await slrd.sell(
35
+ { wallet: "trader-1", token: mint, amountRaw: "1000000", slippageBps: 500 },
36
+ { intentKey: "example:sell:1", priorityFee: { cuLimit: 200_000, microLamports: 20_000 } },
37
+ );
38
+ ```
39
+
40
+ `amountRaw` also accepts bigint and is mutually exclusive with `amount`.
41
+ An economic sell guard rejects output at or below the selected network fee plus
42
+ tip before broadcasting, with `UNECONOMIC_SELL`. Its `sellEconomics` fields describe
43
+ the expected output and selected costs; they are not charges already paid.
44
+ `networkFeeLamports` is the actual on-chain charge only when known. Selected
45
+ priority cost reserves the chosen CU limit, so it differs from consumed CUs.
46
+
47
+ The configured client inherits its database path and RPC endpoints for listeners.
48
+ Live history is recorded by default; disable recording with `history: false`:
49
+
50
+ ```ts
51
+ const slrd = createSolard({ dbPath: "./slrd.db", rpcUrl });
52
+ const listener = await slrd.listenTrades({ tokens: [mint] });
53
+ listener.onTrade((event) => console.log(event));
54
+ // await slrd.listenTrades({ tokens: [mint], history: false });
55
+ await listener.close(); // Wait for queued history writes to flush.
56
+ ```
57
+
58
+ The collected live tail is partial history, not a complete backfill. Processed
59
+ events can later disappear; `slrd.historicalTape(mint)` excludes them by default.
60
+ Pass `{ includeProcessed: true }` to inspect that provisional tail and check the
61
+ returned completeness information before using it for backtests.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solard/sdk",
3
- "version": "0.2.31",
3
+ "version": "0.2.37",
4
4
  "description": "Public Solard SDK for wallets, trading, history, live market subscriptions and token metadata.",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -27,7 +27,7 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@solana/web3.js": "^1.98.4",
30
- "@solard/core": "0.2.31"
30
+ "@solard/core": "0.2.37"
31
31
  },
32
32
  "scripts": {
33
33
  "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.browser.json",
@@ -0,0 +1,95 @@
1
+ import { expect, spyOn, test } from "bun:test";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { Solard } from "../../core/src/core/solard.ts";
6
+ import { tradeResult } from "../../core/src/tx/trade-result.ts";
7
+ import { createSolard, type SolardSellInput } from "./client.ts";
8
+ import { isDefinitivePreSubmissionError } from "@solard/core";
9
+
10
+ test("SDK exact raw sell forwards an isolated position without inserting default bps", async () => {
11
+ const folder = mkdtempSync(join(tmpdir(), "solard-sdk-exact-"));
12
+ const calls: Array<Record<string, unknown>> = [];
13
+ const sell = spyOn(Solard.prototype, "sell").mockImplementation(
14
+ async (_token, _wallet, options) => {
15
+ calls.push(options as unknown as Record<string, unknown>);
16
+ return tradeResult({
17
+ signature: "fixture",
18
+ sender: "rpc",
19
+ status: "confirmed",
20
+ slot: 1,
21
+ });
22
+ },
23
+ );
24
+ const sdk = createSolard({ dbPath: join(folder, "test.sqlite") });
25
+ try {
26
+ await sdk.sell(
27
+ {
28
+ token: "mint",
29
+ wallet: "wallet",
30
+ amountRaw: "284725",
31
+ slippageBps: 500,
32
+ minOutputLamports: 1n,
33
+ },
34
+ { priorityFee: { microLamports: 20_000 } },
35
+ );
36
+ await sdk.sell({
37
+ token: "mint",
38
+ wallet: "wallet",
39
+ amount: { raw: 284725n },
40
+ });
41
+ await sdk.sell({ token: "mint", wallet: "wallet", amount: { bps: 1000 } });
42
+ await sdk.sell({ token: "mint", wallet: "wallet", amount: "all" });
43
+ expect(calls[0]).toMatchObject({
44
+ amountRaw: "284725",
45
+ slippageBps: 500,
46
+ minOutputLamports: 1n,
47
+ priorityFee: { microLamports: 20_000 },
48
+ });
49
+ expect(calls[0]?.bps).toBeUndefined();
50
+ expect(calls[1]?.amountRaw).toBe(284725n);
51
+ expect(calls[1]?.bps).toBeUndefined();
52
+ expect(calls[2]?.bps).toBe(1000);
53
+ expect(calls[3]?.bps).toBe(10000);
54
+ } finally {
55
+ sdk.close();
56
+ sell.mockRestore();
57
+ rmSync(folder, { recursive: true, force: true });
58
+ }
59
+ });
60
+
61
+ test("SDK rejects ambiguous and invalid raw quantities before invoking core sell", async () => {
62
+ const folder = mkdtempSync(join(tmpdir(), "solard-sdk-exact-invalid-"));
63
+ const sell = spyOn(Solard.prototype, "sell").mockImplementation(async () => {
64
+ throw new Error("must not call core");
65
+ });
66
+ const sdk = createSolard({ dbPath: join(folder, "test.sqlite") });
67
+ try {
68
+ for (const size of [
69
+ { amountRaw: "284725", amount: { bps: 1000 } },
70
+ { amountRaw: 0n },
71
+ { amountRaw: "1.5" },
72
+ { amount: { raw: "-1" } },
73
+ { amount: { bps: 1.5 } },
74
+ { amount: { raw: "284725", bps: 1000 } },
75
+ ]) {
76
+ await expect(
77
+ sdk.sell({
78
+ token: "mint",
79
+ wallet: "wallet",
80
+ ...size,
81
+ } as SolardSellInput),
82
+ ).rejects.toThrow();
83
+ }
84
+ expect(sell).not.toHaveBeenCalled();
85
+ try {
86
+ await sdk.sell({ token: "mint", wallet: "wallet", amountRaw: 0n });
87
+ } catch (error) {
88
+ expect(isDefinitivePreSubmissionError(error)).toBe(true);
89
+ }
90
+ } finally {
91
+ sdk.close();
92
+ sell.mockRestore();
93
+ rmSync(folder, { recursive: true, force: true });
94
+ }
95
+ });
@@ -0,0 +1,64 @@
1
+ import { expect, spyOn, test } from "bun:test";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import * as live from "./live.ts";
6
+ import { createSolard } from "./client.ts";
7
+
8
+ test("client listener inherits its configured database and endpoint pool", async () => {
9
+ const folder = mkdtempSync(join(tmpdir(), "solard-sdk-listener-"));
10
+ const dbPath = join(folder, "test.sqlite");
11
+ const seen: Array<live.ListenTradesOptions> = [];
12
+ const fixture = {} as live.TradeListener;
13
+ const listen = spyOn(live, "listenTrades").mockImplementation(
14
+ async (options) => {
15
+ seen.push(options);
16
+ return fixture;
17
+ },
18
+ );
19
+ const sdk = createSolard({
20
+ dbPath,
21
+ rpcUrls: ["https://first.example.com", "https://second.example.com"],
22
+ });
23
+ try {
24
+ expect(await sdk.listenTrades({ tokens: ["mint"] })).toBe(fixture);
25
+ expect(seen[0]).toMatchObject({
26
+ dbPath,
27
+ rpcUrls: ["https://first.example.com", "https://second.example.com"],
28
+ tokens: ["mint"],
29
+ });
30
+ await sdk.listenTrades();
31
+ expect(seen[1]?.tokens).toEqual([]);
32
+ } finally {
33
+ sdk.close();
34
+ listen.mockRestore();
35
+ rmSync(folder, { recursive: true, force: true });
36
+ }
37
+ });
38
+
39
+ test("single RPC configuration is forwarded and absent URLs preserve live environment fallback", async () => {
40
+ const folder = mkdtempSync(join(tmpdir(), "solard-sdk-listener-url-"));
41
+ const seen: Array<live.ListenTradesOptions> = [];
42
+ const listen = spyOn(live, "listenTrades").mockImplementation(
43
+ async (options) => {
44
+ seen.push(options);
45
+ return {} as live.TradeListener;
46
+ },
47
+ );
48
+ const first = createSolard({
49
+ dbPath: join(folder, "first.sqlite"),
50
+ rpcUrl: "https://configured.example.com",
51
+ });
52
+ const second = createSolard({ dbPath: join(folder, "second.sqlite") });
53
+ try {
54
+ await first.listenTrades();
55
+ await second.listenTrades();
56
+ expect(seen[0]?.rpcUrls).toEqual(["https://configured.example.com"]);
57
+ expect(seen[1]?.rpcUrls).toBeUndefined();
58
+ } finally {
59
+ first.close();
60
+ second.close();
61
+ listen.mockRestore();
62
+ rmSync(folder, { recursive: true, force: true });
63
+ }
64
+ });
@@ -5,7 +5,10 @@ import { join } from "node:path";
5
5
  import { Keypair } from "@solana/web3.js";
6
6
  import { Solard } from "../../core/src/core/solard.ts";
7
7
  import { TransactionComposer } from "../../core/src/tx/composer.ts";
8
- import type { PlannedTransaction, TransactionDraft } from "../../core/src/tx/types.ts";
8
+ import type {
9
+ PlannedTransaction,
10
+ TransactionDraft,
11
+ } from "../../core/src/tx/types.ts";
9
12
  import { createSolard } from "./client.ts";
10
13
 
11
14
  test("SDK buy rebuild and sell preserve custom fees in immediate submitted results", async () => {
@@ -14,30 +17,49 @@ test("SDK buy rebuild and sell preserve custom fees in immediate submitted resul
14
17
  // A sentinel plan is never compiled, signed, or broadcast in this test.
15
18
  const payer = Keypair.generate();
16
19
  const plan = { payer: payer.publicKey } as PlannedTransaction;
17
- const build = spyOn(TransactionComposer.prototype, "build").mockImplementation(async function (this: TransactionComposer) {
20
+ const build = spyOn(
21
+ TransactionComposer.prototype,
22
+ "build",
23
+ ).mockImplementation(async function (this: TransactionComposer) {
18
24
  drafts.push(this.snapshot());
19
25
  return { ...plan, draft: this.snapshot() };
20
26
  });
21
- const signer = spyOn(Solard.prototype, "signer").mockImplementation(() => payer);
22
- const compile = spyOn(Solard.prototype, "compile").mockImplementation(async (_payer, draft) => ({ ...plan, draft }));
27
+ const signer = spyOn(Solard.prototype, "signer").mockImplementation(
28
+ () => payer,
29
+ );
30
+ const compile = spyOn(Solard.prototype, "compile").mockImplementation(
31
+ async (_payer, draft) => ({ ...plan, draft }),
32
+ );
23
33
  let submissions = 0;
24
- const submit = spyOn(Solard.prototype, "submitPlan").mockImplementation(async () => {
25
- submissions += 1;
26
- if (submissions === 1) {
27
- throw new Error("Simulation failed: BuySlippageBelowMinBaseAmountOut");
28
- }
29
- return { signature: "test-signature", sender: "rpc", executionId: 42, plan };
30
- });
31
- const settle = spyOn(Solard.prototype, "settleSubmission").mockImplementation(async () => {
32
- throw new Error("Immediate submission must not wait for confirmation");
33
- });
34
+ const submit = spyOn(Solard.prototype, "submitPlan").mockImplementation(
35
+ async () => {
36
+ submissions += 1;
37
+ if (submissions === 1) {
38
+ throw new Error("Simulation failed: BuySlippageBelowMinBaseAmountOut");
39
+ }
40
+ return {
41
+ signature: "test-signature",
42
+ sender: "rpc",
43
+ executionId: 42,
44
+ plan,
45
+ };
46
+ },
47
+ );
48
+ const settle = spyOn(Solard.prototype, "settleSubmission").mockImplementation(
49
+ async () => {
50
+ throw new Error("Immediate submission must not wait for confirmation");
51
+ },
52
+ );
34
53
  const sdk = createSolard({ dbPath: join(directory, "test.sqlite") });
35
54
  try {
36
55
  const options = {
37
56
  priorityFee: { cuLimit: 300_000, microLamports: 0 },
38
57
  waitForConfirmation: false,
39
58
  };
40
- const buy = await sdk.buy({ token: "token", wallet: "wallet", amount: 0.1 }, options);
59
+ const buy = await sdk.buy(
60
+ { token: "token", wallet: "wallet", amount: 0.1 },
61
+ options,
62
+ );
41
63
  const sell = await sdk.sell({ token: "token", wallet: "wallet" }, options);
42
64
  expect(drafts).toHaveLength(3);
43
65
  for (const draft of drafts) {
package/src/client.ts CHANGED
@@ -20,8 +20,8 @@ import {
20
20
  type SendReceipt,
21
21
  type TradeLandingPolicy,
22
22
  type TradeResult,
23
- type TradeExecutionOptions,
24
- type LivePoolReserves,
23
+ type TradeExecutionOptions,
24
+ type LivePoolReserves,
25
25
  type SenderId,
26
26
  type SolardDecodedTransaction,
27
27
  type SolardTransactionOptions,
@@ -35,11 +35,17 @@ import {
35
35
  type WalletRef,
36
36
  type WalletPrivateKeyExport,
37
37
  type WalletPrivateKeyFormat,
38
+ TradePreSubmissionError,
38
39
  } from "@solard/core";
40
+ import {
41
+ listenTrades as openTradeListener,
42
+ type ListenTradesOptions,
43
+ type TradeListener,
44
+ } from "./live.ts";
39
45
 
40
46
  export type SolardOptions = {
41
- rpcUrl?: string;
42
- rpcUrls?: readonly string[];
47
+ rpcUrl?: string;
48
+ rpcUrls?: readonly string[];
43
49
  dbPath?: string;
44
50
  cacheTtlMs?: number;
45
51
  };
@@ -63,9 +69,9 @@ export type SolardEventsApi = {
63
69
  merge(streams: readonly ReplayEventSubscription[]): MergedReplayEventStream;
64
70
  };
65
71
 
66
- export type SolardBuyInput = {
67
- reserves?: LivePoolReserves;
68
- maxPriceSol?: string | number;
72
+ export type SolardBuyInput = {
73
+ reserves?: LivePoolReserves;
74
+ maxPriceSol?: string | number;
69
75
  wallet: WalletRef;
70
76
  token: TokenRef;
71
77
  amount: HumanAmount | string | number;
@@ -73,13 +79,15 @@ export type SolardBuyInput = {
73
79
  minOutputRaw?: bigint | string;
74
80
  };
75
81
 
76
- export type SolardSellInput = {
77
- closeTokenAccount?: boolean;
78
- reserves?: LivePoolReserves;
79
- minPriceSol?: string | number;
82
+ export type SolardSellInput = {
83
+ closeTokenAccount?: boolean;
84
+ reserves?: LivePoolReserves;
85
+ minPriceSol?: string | number;
80
86
  wallet: WalletRef;
81
87
  token: TokenRef;
82
- amount?: "all" | { bps: number };
88
+ amount?: "all" | { bps: number } | { raw: bigint | string };
89
+ /** Exact raw token units; mutually exclusive with amount. */
90
+ amountRaw?: bigint | string;
83
91
  slippageBps?: number;
84
92
  minOutputLamports?: bigint | string;
85
93
  };
@@ -87,19 +95,39 @@ export type SolardSellInput = {
87
95
  export type SolardTradeExecutionOptions = TradeExecutionOptions;
88
96
 
89
97
  export type SolardTradeExecutionResult = TradeResult;
90
- export type Solard = {
91
- resolveCurrentMarket: ReturnType<typeof createCoreSolard>["resolveCurrentMarket"];
92
- getClaimableCreatorFees: ReturnType<typeof createCoreSolard>["getClaimableCreatorFees"];
93
- claimAllCreatorFees: ReturnType<typeof createCoreSolard>["claimAllCreatorFees"];
94
- walletLedger: ReturnType<typeof createCoreSolard>["walletLedger"];
95
- historicalTape: ReturnType<typeof createCoreSolard>["historicalTape"];
96
- getSupportedPumpPairs: ReturnType<typeof createCoreSolard>["getSupportedPumpPairs"];
97
- listVanityMints: ReturnType<typeof createCoreSolard>["listVanityMints"];
98
- releaseVanityMint: ReturnType<typeof createCoreSolard>["releaseVanityMint"];
99
- closeEmptyTokenAccounts: ReturnType<typeof createCoreSolard>["closeEmptyTokenAccounts"];
100
- prepareTokenDeployment: ReturnType<typeof createCoreSolard>["prepareTokenDeployment"];
101
- deployToken: ReturnType<typeof createCoreSolard>["deployToken"];
102
- warmBlockhash: ReturnType<typeof createCoreSolard>["warmBlockhash"];
98
+ export type Solard = {
99
+ listenTrades(
100
+ options?: Omit<ListenTradesOptions, "tokens"> & {
101
+ tokens?: readonly string[];
102
+ },
103
+ ): Promise<TradeListener>;
104
+ resolveCurrentMarket: ReturnType<
105
+ typeof createCoreSolard
106
+ >["resolveCurrentMarket"];
107
+ getClaimableCreatorFees: ReturnType<
108
+ typeof createCoreSolard
109
+ >["getClaimableCreatorFees"];
110
+ claimAllCreatorFees: ReturnType<
111
+ typeof createCoreSolard
112
+ >["claimAllCreatorFees"];
113
+ walletLedger: ReturnType<typeof createCoreSolard>["walletLedger"];
114
+ historicalTape: ReturnType<typeof createCoreSolard>["historicalTape"];
115
+ getSupportedPumpPairs: ReturnType<
116
+ typeof createCoreSolard
117
+ >["getSupportedPumpPairs"];
118
+ getSupportedLaunchLabPairs: ReturnType<
119
+ typeof createCoreSolard
120
+ >["getSupportedLaunchLabPairs"];
121
+ listVanityMints: ReturnType<typeof createCoreSolard>["listVanityMints"];
122
+ releaseVanityMint: ReturnType<typeof createCoreSolard>["releaseVanityMint"];
123
+ closeEmptyTokenAccounts: ReturnType<
124
+ typeof createCoreSolard
125
+ >["closeEmptyTokenAccounts"];
126
+ prepareTokenDeployment: ReturnType<
127
+ typeof createCoreSolard
128
+ >["prepareTokenDeployment"];
129
+ deployToken: ReturnType<typeof createCoreSolard>["deployToken"];
130
+ warmBlockhash: ReturnType<typeof createCoreSolard>["warmBlockhash"];
103
131
  curveLiquidity: ReturnType<typeof createCoreSolard>["curveLiquidity"];
104
132
  maxSendableSol: ReturnType<typeof createCoreSolard>["maxSendableSol"];
105
133
  transferToken: ReturnType<typeof createCoreSolard>["transferToken"];
@@ -174,15 +202,38 @@ function tradeAmount(value: HumanAmount | string | number): HumanAmount {
174
202
  : value;
175
203
  }
176
204
 
177
- function sellBps(value: SolardSellInput["amount"]): number {
178
- if (value == null || value === "all") return 10_000;
179
- const bps = Math.trunc(value.bps);
180
- if (!(bps > 0 && bps <= 10_000))
205
+ function sellSize(input: SolardSellInput): {
206
+ bps?: number;
207
+ amountRaw?: bigint | string;
208
+ } {
209
+ const value = input.amount;
210
+ if (input.amountRaw !== undefined && value !== undefined)
211
+ throw new Error("sell amountRaw and amount are mutually exclusive");
212
+ const raw =
213
+ input.amountRaw !== undefined
214
+ ? input.amountRaw
215
+ : typeof value === "object" && value !== null && "raw" in value
216
+ ? value.raw
217
+ : undefined;
218
+ if (raw !== undefined) {
219
+ if (typeof value === "object" && value !== null && "bps" in value)
220
+ throw new Error("sell raw and bps are mutually exclusive");
221
+ if (
222
+ (typeof raw !== "bigint" &&
223
+ (typeof raw !== "string" || !/^\d+$/.test(raw))) ||
224
+ BigInt(raw) <= 0n
225
+ )
226
+ throw new Error("sell amountRaw must be a positive raw integer");
227
+ return { amountRaw: raw };
228
+ }
229
+ if (value == null || value === "all") return { bps: 10_000 };
230
+ if (!("bps" in value)) throw new Error("sell amount must specify bps or raw");
231
+ const bps = value.bps;
232
+ if (!Number.isInteger(bps) || !(bps > 0 && bps <= 10_000))
181
233
  throw new Error("sell amount.bps must be between 1 and 10000");
182
- return bps;
234
+ return { bps };
183
235
  }
184
236
 
185
-
186
237
  async function executeTrade(
187
238
  core: ReturnType<typeof createCoreSolard>,
188
239
  side: "buy" | "sell",
@@ -191,11 +242,30 @@ async function executeTrade(
191
242
  ): Promise<SolardTradeExecutionResult> {
192
243
  if (side === "buy") {
193
244
  const buy = input as SolardBuyInput;
194
- return await core.buy(buy.token, buy.wallet, tradeAmount(buy.amount), { ...options, slippageBps: buy.slippageBps, minOutputRaw: buy.minOutputRaw, maxPriceSol: buy.maxPriceSol, reserves: buy.reserves });
245
+ return await core.buy(buy.token, buy.wallet, tradeAmount(buy.amount), {
246
+ ...options,
247
+ slippageBps: buy.slippageBps,
248
+ minOutputRaw: buy.minOutputRaw,
249
+ maxPriceSol: buy.maxPriceSol,
250
+ reserves: buy.reserves,
251
+ });
195
252
  }
196
253
  const sell = input as SolardSellInput;
197
- return await core.sell(sell.token, sell.wallet, { ...options, bps: sellBps(sell.amount), slippageBps: sell.slippageBps, minOutputLamports: sell.minOutputLamports, minPriceSol: sell.minPriceSol, reserves: sell.reserves, closeTokenAccount: sell.closeTokenAccount });
198
-
254
+ let size: ReturnType<typeof sellSize>;
255
+ try {
256
+ size = sellSize(sell);
257
+ } catch (error) {
258
+ throw new TradePreSubmissionError(error);
259
+ }
260
+ return await core.sell(sell.token, sell.wallet, {
261
+ ...options,
262
+ ...size,
263
+ slippageBps: sell.slippageBps,
264
+ minOutputLamports: sell.minOutputLamports,
265
+ minPriceSol: sell.minPriceSol,
266
+ reserves: sell.reserves,
267
+ closeTokenAccount: sell.closeTokenAccount,
268
+ });
199
269
  }
200
270
 
201
271
  export function createSolard(options: SolardOptions = {}): Solard {
@@ -210,19 +280,32 @@ export function createSolard(options: SolardOptions = {}): Solard {
210
280
  core.events(token, eventOptions),
211
281
  { merge: core.events.merge },
212
282
  ) as SolardEventsApi;
213
- return Object.freeze({
214
- resolveCurrentMarket: core.resolveCurrentMarket.bind(core),
215
- getClaimableCreatorFees: core.getClaimableCreatorFees.bind(core),
216
- claimAllCreatorFees: core.claimAllCreatorFees.bind(core),
217
- walletLedger: core.walletLedger.bind(core),
218
- historicalTape: core.historicalTape.bind(core),
219
- getSupportedPumpPairs: core.getSupportedPumpPairs.bind(core),
220
- listVanityMints: core.listVanityMints.bind(core),
221
- releaseVanityMint: core.releaseVanityMint.bind(core),
222
- closeEmptyTokenAccounts: core.closeEmptyTokenAccounts.bind(core),
223
- prepareTokenDeployment: core.prepareTokenDeployment.bind(core),
224
- deployToken: core.deployToken.bind(core),
225
- warmBlockhash: core.warmBlockhash.bind(core),
283
+ return Object.freeze({
284
+ listenTrades: (
285
+ listenerOptions: Omit<ListenTradesOptions, "tokens"> & {
286
+ tokens?: readonly string[];
287
+ } = {},
288
+ ) =>
289
+ openTradeListener({
290
+ rpcUrls:
291
+ options.rpcUrls ?? (options.rpcUrl ? [options.rpcUrl] : undefined),
292
+ dbPath: options.dbPath,
293
+ ...listenerOptions,
294
+ tokens: listenerOptions.tokens ?? [],
295
+ }),
296
+ resolveCurrentMarket: core.resolveCurrentMarket.bind(core),
297
+ getClaimableCreatorFees: core.getClaimableCreatorFees.bind(core),
298
+ claimAllCreatorFees: core.claimAllCreatorFees.bind(core),
299
+ walletLedger: core.walletLedger.bind(core),
300
+ historicalTape: core.historicalTape.bind(core),
301
+ getSupportedPumpPairs: core.getSupportedPumpPairs.bind(core),
302
+ getSupportedLaunchLabPairs: core.getSupportedLaunchLabPairs.bind(core),
303
+ listVanityMints: core.listVanityMints.bind(core),
304
+ releaseVanityMint: core.releaseVanityMint.bind(core),
305
+ closeEmptyTokenAccounts: core.closeEmptyTokenAccounts.bind(core),
306
+ prepareTokenDeployment: core.prepareTokenDeployment.bind(core),
307
+ deployToken: core.deployToken.bind(core),
308
+ warmBlockhash: core.warmBlockhash.bind(core),
226
309
  curveLiquidity: core.curveLiquidity.bind(core),
227
310
  maxSendableSol: core.maxSendableSol.bind(core),
228
311
  transferToken: core.transferToken.bind(core),
package/src/default.ts CHANGED
@@ -1,14 +1,9 @@
1
1
  import { getSolUsdPrice } from "@solard/core";
2
- import {
3
- listenTrades,
4
- subscribeLaunches,
5
- subscribeMigrations,
6
- } from "./live.ts";
2
+ import { subscribeLaunches, subscribeMigrations } from "./live.ts";
7
3
  import { createSolard, type Solard } from "./client.ts";
8
4
 
9
5
  type DefaultSolard = Solard & {
10
6
  getSolUsdPrice: typeof getSolUsdPrice;
11
- listenTrades: typeof listenTrades;
12
7
  subscribeLaunches: typeof subscribeLaunches;
13
8
  subscribeMigrations: typeof subscribeMigrations;
14
9
  };
@@ -22,7 +17,6 @@ function client(): Solard {
22
17
 
23
18
  const operations = {
24
19
  getSolUsdPrice,
25
- listenTrades,
26
20
  subscribeLaunches,
27
21
  subscribeMigrations,
28
22
  } as const;
@@ -29,12 +29,19 @@ function exportedNames(value: string) {
29
29
  describe("sdk membrane", () => {
30
30
  test("has an exact curated root export surface", () => {
31
31
  expect(exportedNames(source)).toEqual({
32
- values: [
33
- "normalizeValueBandPolicy", "planValueBandDecision", "valueBandThresholds",
34
- "normalizeTargetWeightPolicy", "planTargetWeightRebalance",
35
- "buildHistoricalCandles", "runHistoricalStrategy", "sweepHistoricalStrategies",
36
- "LAUNCHLAB_PLATFORM_PRESETS", "resolveLaunchLabPlatform",
37
- "createSolard",
32
+ values: [
33
+ "normalizeValueBandPolicy",
34
+ "planValueBandDecision",
35
+ "valueBandThresholds",
36
+ "normalizeTargetWeightPolicy",
37
+ "planTargetWeightRebalance",
38
+ "buildHistoricalCandles",
39
+ "runHistoricalStrategy",
40
+ "sweepHistoricalStrategies",
41
+ "LAUNCHLAB_PLATFORM_PRESETS",
42
+ "resolveLaunchLabPlatform",
43
+ "createSolard",
44
+ "createTraderSolard",
38
45
  "default",
39
46
  "fetchTokenMetadata",
40
47
  "formatRaw",
@@ -47,18 +54,32 @@ describe("sdk membrane", () => {
47
54
  "sol",
48
55
  "tokenAmount",
49
56
  ].sort(),
50
- types: [
51
- "TradeListenerMigration",
52
- "ValueBandPolicy", "ValueBandRuntimeState", "ValueBandDecision",
53
- "TargetWeightPolicy", "TargetWeightRebalancePlan",
54
- "HistoricalTradeTape", "HistoricalCandle", "HistoricalStrategy",
55
- "HistoricalExecutionOptions", "HistoricalStrategyResult",
56
- "CreatorFeeDiscovery", "CreatorFeeGroup", "CreatorFeeItem",
57
- "WalletLedger", "WalletLedgerEntry", "WalletLedgerOptions", "CurrentMarket",
58
- "PreparedTokenDeployment", "PrepareDeploymentArgs",
59
- "TradeLandingPolicy",
60
- "LiveEndpointOptions",
61
- "LivePoolReserves",
57
+ types: [
58
+ "SellEconomics",
59
+ "SupportedLaunchLabPair",
60
+ "TradeListenerMigration",
61
+ "ValueBandPolicy",
62
+ "ValueBandRuntimeState",
63
+ "ValueBandDecision",
64
+ "TargetWeightPolicy",
65
+ "TargetWeightRebalancePlan",
66
+ "HistoricalTradeTape",
67
+ "HistoricalCandle",
68
+ "HistoricalStrategy",
69
+ "HistoricalExecutionOptions",
70
+ "HistoricalStrategyResult",
71
+ "CreatorFeeDiscovery",
72
+ "CreatorFeeGroup",
73
+ "CreatorFeeItem",
74
+ "WalletLedger",
75
+ "WalletLedgerEntry",
76
+ "WalletLedgerOptions",
77
+ "CurrentMarket",
78
+ "PreparedTokenDeployment",
79
+ "PrepareDeploymentArgs",
80
+ "TradeLandingPolicy",
81
+ "LiveEndpointOptions",
82
+ "LivePoolReserves",
62
83
  "ClaimCreatorRewardsOptions",
63
84
  "CreatorRewardClaimPayout",
64
85
  "CreatorRewardClaimResult",
@@ -175,7 +196,7 @@ describe("sdk membrane", () => {
175
196
  expect(defaultClient).toContain("instance ??= createSolard()");
176
197
  expect(defaultClient).toContain('if (key === "close")');
177
198
  expect(defaultClient).toContain("getSolUsdPrice");
178
- expect(defaultClient).toContain("listenTrades");
199
+ expect(client).toContain("listenTrades:");
179
200
  expect(defaultClient).toContain("subscribeLaunches");
180
201
  expect(defaultClient).toContain("subscribeMigrations");
181
202
  expect(defaultClient).not.toContain("export default createSolard()");
@@ -200,7 +221,7 @@ describe("sdk membrane", () => {
200
221
 
201
222
  test("does not expose core repositories or secret-bearing handles", () => {
202
223
  expect(client).not.toMatch(
203
- /\b(?:db|wallets|tokens|groups|executions|positions|prices|alts|senders|venues|claimSources|launches|launchpads|connection|signer)\s*:/,
224
+ /^(?: {2}| {4})(?:db|wallets|tokens|groups|executions|positions|prices|alts|senders|venues|claimSources|launches|launchpads|connection|signer)\s*:/m,
204
225
  );
205
226
  });
206
227
  });