@solard/sdk 0.2.6 → 0.2.8

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Solard contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Solard contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
- # @solard/sdk
2
-
3
- Public Solard SDK. It intentionally contains no duplicate business logic; it re-exports the supported `@solard/core` API.
4
-
5
- ```ts
6
- import { Solard, sol } from "@solard/sdk";
7
-
8
- const slrd = new Solard();
9
- const wallet = slrd.createWallet("trader-1");
10
- const wallets = slrd.listWallets();
11
-
12
- await slrd.tx(wallet.address).transferSol("RECIPIENT", sol("0.01")).send();
13
- ```
1
+ # @solard/sdk
2
+
3
+ Public Solard SDK. It intentionally contains no duplicate business logic; it re-exports the supported `@solard/core` API.
4
+
5
+ ```ts
6
+ import { Solard, sol } from "@solard/sdk";
7
+
8
+ const slrd = new Solard();
9
+ const wallet = slrd.createWallet("trader-1");
10
+ const wallets = slrd.listWallets();
11
+
12
+ await slrd.tx(wallet.address).transferSol("RECIPIENT", sol("0.01")).send();
13
+ ```
package/package.json CHANGED
@@ -1,13 +1,18 @@
1
1
  {
2
2
  "name": "@solard/sdk",
3
- "version": "0.2.6",
3
+ "version": "0.2.8",
4
4
  "description": "Public Solard SDK for wallets, transactions, trading and token launches.",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
7
7
  "module": "./src/index.ts",
8
8
  "types": "./src/index.ts",
9
9
  "exports": {
10
- ".": "./src/index.ts"
10
+ ".": "./src/index.ts",
11
+ "./browser": {
12
+ "types": "./src/browser/index.ts",
13
+ "import": "./src/browser/index.ts",
14
+ "default": "./src/browser/index.ts"
15
+ }
11
16
  },
12
17
  "files": [
13
18
  "src",
@@ -15,13 +20,12 @@
15
20
  "LICENSE"
16
21
  ],
17
22
  "license": "MIT",
18
- "engines": {
19
- "bun": ">=1.1.0"
20
- },
21
23
  "dependencies": {
22
- "@solard/core": "0.2.6"
24
+ "@solana/web3.js": "^1.98.4",
25
+ "@solard/core": "0.2.8"
23
26
  },
24
27
  "scripts": {
25
- "typecheck": "tsc --noEmit -p ../../tsconfig.json"
28
+ "typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.browser.json",
29
+ "typecheck:browser": "tsc --noEmit -p tsconfig.browser.json"
26
30
  }
27
31
  }
@@ -0,0 +1,435 @@
1
+ import {
2
+ Connection,
3
+ PublicKey,
4
+ SystemProgram,
5
+ TransactionMessage,
6
+ VersionedTransaction,
7
+ } from "@solana/web3.js";
8
+
9
+ import { BrowserSolardStore, defaultBrowserStorage } from "./storage.ts";
10
+ import { buildLocalPumpBuy, buildLocalPumpSell } from "./pump.ts";
11
+ import type {
12
+ BrowserBroadcastResult,
13
+ BrowserPortfolio,
14
+ BrowserSolardOptions,
15
+ BrowserTradeBuild,
16
+ BrowserTradeResult,
17
+ BrowserTokenBalance,
18
+ BrowserWalletSigner,
19
+ } from "./types.ts";
20
+
21
+ export const SOL_MINT = "So11111111111111111111111111111111111111112";
22
+ export const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
23
+ export const TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
24
+ export const TOKEN_2022_PROGRAM_ID =
25
+ "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHn5qCXEpPxuEb";
26
+
27
+ const CANONICAL_LABELS: Record<string, string> = {
28
+ [SOL_MINT]: "WSOL",
29
+ [USDC_MINT]: "USDC",
30
+ };
31
+
32
+ function createRateLimitedFetch(
33
+ rawFetch: typeof globalThis.fetch,
34
+ maxRps: number,
35
+ ): typeof globalThis.fetch {
36
+ const rps = Math.max(0.1, maxRps);
37
+ const spacingMs = Math.ceil(1000 / rps) + 5;
38
+ let tail = Promise.resolve();
39
+ let nextAt = 0;
40
+ return (async (input: RequestInfo | URL, init?: RequestInit) => {
41
+ let release!: () => void;
42
+ const previous = tail;
43
+ tail = new Promise<void>((resolve) => {
44
+ release = resolve;
45
+ });
46
+ await previous;
47
+ try {
48
+ const delay = Math.max(0, nextAt - Date.now());
49
+ if (delay > 0)
50
+ await new Promise<void>((resolve) => setTimeout(resolve, delay));
51
+ nextAt = Date.now() + spacingMs;
52
+ } finally {
53
+ release();
54
+ }
55
+ return await rawFetch(input, init);
56
+ }) as typeof globalThis.fetch;
57
+ }
58
+
59
+ function requireFetch(
60
+ supplied?: typeof globalThis.fetch,
61
+ ): typeof globalThis.fetch {
62
+ const value = supplied ?? globalThis.fetch;
63
+ if (!value) throw new Error("Browser fetch() is unavailable.");
64
+ return value.bind(globalThis);
65
+ }
66
+
67
+ function lamportsFromSol(value: string | number): bigint {
68
+ const text = String(value).trim();
69
+ if (!/^\d+(?:\.\d+)?$/.test(text))
70
+ throw new Error(`Invalid SOL amount: ${text}`);
71
+ const [whole = "0", fraction = ""] = text.split(".");
72
+ if (fraction.length > 9)
73
+ throw new Error("SOL amount supports at most 9 decimal places.");
74
+ return (
75
+ BigInt(whole) * 1_000_000_000n +
76
+ BigInt((fraction + "000000000").slice(0, 9))
77
+ );
78
+ }
79
+
80
+ function formatRaw(raw: bigint, decimals: number): string {
81
+ if (decimals <= 0) return raw.toString();
82
+ const unit = 10n ** BigInt(decimals);
83
+ const whole = raw / unit;
84
+ const fraction = (raw % unit)
85
+ .toString()
86
+ .padStart(decimals, "0")
87
+ .replace(/0+$/, "");
88
+ return `${whole}${fraction ? `.${fraction}` : ""}`;
89
+ }
90
+
91
+ export class BrowserSolard {
92
+ readonly connection: Connection;
93
+ readonly store: BrowserSolardStore;
94
+ readonly contacts;
95
+ readonly tokens;
96
+ private wallet: BrowserWalletSigner | null;
97
+
98
+ constructor(readonly options: BrowserSolardOptions) {
99
+ const rpcFetch = createRateLimitedFetch(
100
+ requireFetch(options.fetch),
101
+ options.rpcMaxRps ?? 5,
102
+ );
103
+ this.connection = new Connection(options.rpcUrl, {
104
+ commitment: options.commitment ?? "confirmed",
105
+ disableRetryOnRateLimit: true,
106
+ fetch: rpcFetch,
107
+ });
108
+ this.wallet = options.wallet ?? null;
109
+ this.store = new BrowserSolardStore(
110
+ options.storage ?? defaultBrowserStorage(),
111
+ options.storageNamespace ?? "solard:browser:v1",
112
+ );
113
+ this.contacts = {
114
+ list: () => this.store.listContacts(),
115
+ add: (
116
+ name: string,
117
+ address: string,
118
+ addOptions?: { overwrite?: boolean },
119
+ ) => this.store.addContact(name, address, addOptions),
120
+ find: (ref: string) => this.store.findContact(ref),
121
+ remove: (ref: string) => this.store.removeContact(ref),
122
+ resolve: (ref: string) => this.store.resolveDestination(ref),
123
+ };
124
+ this.tokens = {
125
+ list: () => this.store.listTokenAliases(),
126
+ register: (
127
+ alias: string,
128
+ mint: string,
129
+ registerOptions?: { overwrite?: boolean },
130
+ ) => this.store.registerToken(alias, mint, registerOptions),
131
+ resolve: (ref: string) => this.store.resolveToken(ref),
132
+ };
133
+ }
134
+
135
+ setWallet(wallet: BrowserWalletSigner | null): void {
136
+ this.wallet = wallet;
137
+ }
138
+ get publicKey(): PublicKey | null {
139
+ return this.wallet?.publicKey ?? null;
140
+ }
141
+
142
+ private signer(): BrowserWalletSigner {
143
+ if (!this.wallet?.publicKey)
144
+ throw new Error(
145
+ "A connected browser wallet is required for this operation.",
146
+ );
147
+ return this.wallet;
148
+ }
149
+
150
+ async connectWallet(): Promise<PublicKey> {
151
+ const wallet = this.signer();
152
+ await wallet.connect?.();
153
+ if (!wallet.publicKey)
154
+ throw new Error("Browser wallet connected without a public key.");
155
+ return wallet.publicKey;
156
+ }
157
+
158
+ resolveDestination(ref: string): string {
159
+ return this.store.resolveDestination(ref);
160
+ }
161
+ resolveToken(ref: string): string {
162
+ return this.store.resolveToken(ref);
163
+ }
164
+
165
+ private resolveTokenOrMint(value: string): string {
166
+ try {
167
+ return new PublicKey(value).toBase58();
168
+ } catch {
169
+ return this.store.resolveToken(value);
170
+ }
171
+ }
172
+
173
+ async getPortfolio(address?: string | PublicKey): Promise<BrowserPortfolio> {
174
+ const owner =
175
+ address instanceof PublicKey
176
+ ? address
177
+ : address
178
+ ? new PublicKey(address)
179
+ : this.signer().publicKey!;
180
+ const [solLamportsNumber, spl, token2022] = await Promise.all([
181
+ this.connection.getBalance(owner, "confirmed"),
182
+ this.connection.getParsedTokenAccountsByOwner(
183
+ owner,
184
+ { programId: new PublicKey(TOKEN_PROGRAM_ID) },
185
+ "confirmed",
186
+ ),
187
+ this.connection.getParsedTokenAccountsByOwner(
188
+ owner,
189
+ { programId: new PublicKey(TOKEN_2022_PROGRAM_ID) },
190
+ "confirmed",
191
+ ),
192
+ ]);
193
+ const aggregate = new Map<
194
+ string,
195
+ {
196
+ amountRaw: bigint;
197
+ decimals: number;
198
+ program: "spl-token" | "token-2022";
199
+ accounts: string[];
200
+ }
201
+ >();
202
+ for (const [program, response] of [
203
+ ["spl-token", spl] as const,
204
+ ["token-2022", token2022] as const,
205
+ ]) {
206
+ for (const account of response.value) {
207
+ const info = (account.account.data as any)?.parsed?.info;
208
+ const amount = info?.tokenAmount;
209
+ const mint = String(info?.mint ?? "");
210
+ const raw = String(amount?.amount ?? "0");
211
+ if (!mint || !/^\d+$/.test(raw)) continue;
212
+ const amountRaw = BigInt(raw);
213
+ if (amountRaw === 0n) continue;
214
+ const decimals = Number(amount?.decimals ?? 0);
215
+ const existing = aggregate.get(mint);
216
+ if (existing) {
217
+ existing.amountRaw += amountRaw;
218
+ existing.accounts.push(account.pubkey.toBase58());
219
+ } else
220
+ aggregate.set(mint, {
221
+ amountRaw,
222
+ decimals,
223
+ program,
224
+ accounts: [account.pubkey.toBase58()],
225
+ });
226
+ }
227
+ }
228
+ const aliases = new Map(
229
+ this.store
230
+ .listTokenAliases()
231
+ .map((row) => [row.mint, row.alias] as const),
232
+ );
233
+ const tokenBalances: BrowserTokenBalance[] = [...aggregate.entries()]
234
+ .map(([mint, row]) => ({
235
+ mint,
236
+ amountRaw: row.amountRaw,
237
+ amountUi: formatRaw(row.amountRaw, row.decimals),
238
+ decimals: row.decimals,
239
+ program: row.program,
240
+ label: CANONICAL_LABELS[mint] ?? aliases.get(mint) ?? mint,
241
+ tokenAccounts: row.accounts,
242
+ }))
243
+ .sort((a, b) => a.label.localeCompare(b.label));
244
+ return {
245
+ address: owner.toBase58(),
246
+ solLamports: BigInt(solLamportsNumber),
247
+ tokenBalances,
248
+ };
249
+ }
250
+
251
+ async signAndBroadcast(
252
+ transaction: VersionedTransaction,
253
+ strategy: {
254
+ blockhash: string;
255
+ lastValidBlockHeight: number;
256
+ skipPreflight?: boolean;
257
+ },
258
+ ): Promise<BrowserBroadcastResult> {
259
+ const signed = await this.signer().signTransaction(transaction);
260
+ if (!(signed instanceof VersionedTransaction))
261
+ throw new Error("Browser wallet did not return a VersionedTransaction.");
262
+ const signature = await this.connection.sendRawTransaction(
263
+ signed.serialize(),
264
+ {
265
+ skipPreflight: strategy.skipPreflight ?? false,
266
+ preflightCommitment: "confirmed",
267
+ maxRetries: 0,
268
+ },
269
+ );
270
+ const confirmation = await this.connection.confirmTransaction(
271
+ {
272
+ signature,
273
+ blockhash: strategy.blockhash,
274
+ lastValidBlockHeight: strategy.lastValidBlockHeight,
275
+ },
276
+ "confirmed",
277
+ );
278
+ if (confirmation.value.err)
279
+ throw new Error(
280
+ `Transaction ${signature} failed: ${JSON.stringify(confirmation.value.err)}`,
281
+ );
282
+ return {
283
+ signature,
284
+ confirmed: true,
285
+ blockhash: strategy.blockhash,
286
+ lastValidBlockHeight: strategy.lastValidBlockHeight,
287
+ };
288
+ }
289
+
290
+ async sendSol(
291
+ destinationRef: string,
292
+ amountSol: string | number,
293
+ ): Promise<BrowserBroadcastResult> {
294
+ const wallet = this.signer();
295
+ const destination = new PublicKey(
296
+ this.store.resolveDestination(destinationRef),
297
+ );
298
+ const lamports = lamportsFromSol(amountSol);
299
+ if (lamports <= 0n || lamports > BigInt(Number.MAX_SAFE_INTEGER))
300
+ throw new Error("Invalid SOL transfer amount.");
301
+ const latest = await this.connection.getLatestBlockhash("confirmed");
302
+ const message = new TransactionMessage({
303
+ payerKey: wallet.publicKey!,
304
+ recentBlockhash: latest.blockhash,
305
+ instructions: [
306
+ SystemProgram.transfer({
307
+ fromPubkey: wallet.publicKey!,
308
+ toPubkey: destination,
309
+ lamports: Number(lamports),
310
+ }),
311
+ ],
312
+ }).compileToV0Message();
313
+ return await this.signAndBroadcast(
314
+ new VersionedTransaction(message),
315
+ latest,
316
+ );
317
+ }
318
+
319
+ async buildBuy(args: {
320
+ token: string;
321
+ sol: string | number;
322
+ slippageBps?: number;
323
+ cuLimit?: number;
324
+ priorityMicroLamports?: number;
325
+ }): Promise<BrowserTradeBuild> {
326
+ return await buildLocalPumpBuy({
327
+ connection: this.connection,
328
+ user: this.signer().publicKey!,
329
+ mint: this.resolveTokenOrMint(args.token),
330
+ quoteInRaw: lamportsFromSol(args.sol),
331
+ slippageBps: args.slippageBps,
332
+ cuLimit: args.cuLimit,
333
+ priorityMicroLamports: args.priorityMicroLamports,
334
+ });
335
+ }
336
+
337
+ async buildSell(args: {
338
+ token: string;
339
+ amountRaw: bigint;
340
+ slippageBps?: number;
341
+ cuLimit?: number;
342
+ priorityMicroLamports?: number;
343
+ }): Promise<BrowserTradeBuild> {
344
+ if (args.amountRaw <= 0n) throw new Error("Sell amount must be positive.");
345
+ return await buildLocalPumpSell({
346
+ connection: this.connection,
347
+ user: this.signer().publicKey!,
348
+ mint: this.resolveTokenOrMint(args.token),
349
+ baseInRaw: args.amountRaw,
350
+ slippageBps: args.slippageBps,
351
+ cuLimit: args.cuLimit,
352
+ priorityMicroLamports: args.priorityMicroLamports,
353
+ });
354
+ }
355
+
356
+ async buy(args: {
357
+ token: string;
358
+ sol: string | number;
359
+ slippageBps?: number;
360
+ cuLimit?: number;
361
+ priorityMicroLamports?: number;
362
+ skipPreflight?: boolean;
363
+ }): Promise<BrowserTradeResult> {
364
+ const build = await this.buildBuy(args);
365
+ const receipt = await this.signAndBroadcast(build.transaction, {
366
+ blockhash: build.blockhash,
367
+ lastValidBlockHeight: build.lastValidBlockHeight,
368
+ skipPreflight: args.skipPreflight,
369
+ });
370
+ return {
371
+ ...receipt,
372
+ side: build.side,
373
+ venue: build.venue,
374
+ mint: build.mint,
375
+ inputRaw: build.inputRaw,
376
+ expectedOutputRaw: build.expectedOutputRaw,
377
+ minimumOutputRaw: build.minimumOutputRaw,
378
+ };
379
+ }
380
+
381
+ async sell(args: {
382
+ token: string;
383
+ amountRaw: bigint;
384
+ slippageBps?: number;
385
+ cuLimit?: number;
386
+ priorityMicroLamports?: number;
387
+ skipPreflight?: boolean;
388
+ }): Promise<BrowserTradeResult> {
389
+ const build = await this.buildSell(args);
390
+ const receipt = await this.signAndBroadcast(build.transaction, {
391
+ blockhash: build.blockhash,
392
+ lastValidBlockHeight: build.lastValidBlockHeight,
393
+ skipPreflight: args.skipPreflight,
394
+ });
395
+ return {
396
+ ...receipt,
397
+ side: build.side,
398
+ venue: build.venue,
399
+ mint: build.mint,
400
+ inputRaw: build.inputRaw,
401
+ expectedOutputRaw: build.expectedOutputRaw,
402
+ minimumOutputRaw: build.minimumOutputRaw,
403
+ };
404
+ }
405
+
406
+ async buyWithSol(args: {
407
+ outputMint: string;
408
+ sol: string | number;
409
+ slippageBps?: number;
410
+ }): Promise<BrowserTradeResult> {
411
+ return await this.buy({
412
+ token: args.outputMint,
413
+ sol: args.sol,
414
+ slippageBps: args.slippageBps,
415
+ });
416
+ }
417
+
418
+ async sellToSol(args: {
419
+ inputMint: string;
420
+ amountRaw: bigint;
421
+ slippageBps?: number;
422
+ }): Promise<BrowserTradeResult> {
423
+ return await this.sell({
424
+ token: args.inputMint,
425
+ amountRaw: args.amountRaw,
426
+ slippageBps: args.slippageBps,
427
+ });
428
+ }
429
+ }
430
+
431
+ export function createBrowserSolard(
432
+ options: BrowserSolardOptions,
433
+ ): BrowserSolard {
434
+ return new BrowserSolard(options);
435
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Browser-safe Solard SDK.
3
+ *
4
+ * Trading uses the same local Pump/PumpSwap venue classes, quote math and
5
+ * instruction builders as the CLI/core engine. No external swap API is used.
6
+ */
7
+ export {
8
+ BrowserSolard,
9
+ SOL_MINT,
10
+ TOKEN_2022_PROGRAM_ID,
11
+ TOKEN_PROGRAM_ID,
12
+ USDC_MINT,
13
+ createBrowserSolard,
14
+ } from "./client.ts";
15
+
16
+ export {
17
+ BrowserSolardStore,
18
+ MemoryBrowserStorage,
19
+ defaultBrowserStorage,
20
+ } from "./storage.ts";
21
+ export { BrowserKeyVault, KeypairBrowserSigner } from "./wallet.ts";
22
+ export { buildLocalPumpBuy, buildLocalPumpSell } from "./pump.ts";
23
+
24
+ export type {
25
+ BrowserBroadcastResult,
26
+ BrowserContact,
27
+ BrowserPortfolio,
28
+ BrowserSolardOptions,
29
+ BrowserStorageLike,
30
+ BrowserTokenAlias,
31
+ BrowserTokenBalance,
32
+ BrowserTradeBuild,
33
+ BrowserTradeResult,
34
+ BrowserTradeSide,
35
+ BrowserWalletSigner,
36
+ } from "./types.ts";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Jupiter is intentionally not part of the browser trading engine.
3
+ * Browser trading uses the same local Pump/PumpSwap venue logic as Solard CLI.
4
+ */
5
+ export const JUPITER_BROWSER_ENGINE_DISABLED = true as const;