@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 +21 -21
- package/README.md +13 -13
- package/package.json +11 -7
- package/src/browser/client.ts +435 -0
- package/src/browser/index.ts +36 -0
- package/src/browser/jupiter.ts +5 -0
- package/src/browser/pump.ts +230 -0
- package/src/browser/storage.ts +254 -0
- package/src/browser/types.ts +99 -0
- package/src/browser/wallet.ts +228 -0
- package/src/index.ts +19 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ComputeBudgetProgram,
|
|
3
|
+
PublicKey,
|
|
4
|
+
TransactionMessage,
|
|
5
|
+
VersionedTransaction,
|
|
6
|
+
type Connection,
|
|
7
|
+
type TransactionInstruction,
|
|
8
|
+
} from "@solana/web3.js";
|
|
9
|
+
|
|
10
|
+
import { readMint } from "@solard/core/chain/state.ts";
|
|
11
|
+
import { rawAmount } from "@solard/core/core/amounts.ts";
|
|
12
|
+
import type { TokenRow } from "@solard/core/db/schema.ts";
|
|
13
|
+
import { PumpCurveVenue } from "@solard/core/venues/pump/pump-curve-venue.ts";
|
|
14
|
+
import { PumpSwapVenue } from "@solard/core/venues/pump/pumpswap-venue.ts";
|
|
15
|
+
import { VenueRegistry } from "@solard/core/venues/route-resolver.ts";
|
|
16
|
+
|
|
17
|
+
import type { BrowserTradeBuild } from "./types.ts";
|
|
18
|
+
|
|
19
|
+
const PACKET_LIMIT = 1232;
|
|
20
|
+
|
|
21
|
+
function tokenShell(args: {
|
|
22
|
+
mint: string;
|
|
23
|
+
decimals: number;
|
|
24
|
+
baseTokenProgram: string;
|
|
25
|
+
}): TokenRow {
|
|
26
|
+
const now = Date.now();
|
|
27
|
+
return {
|
|
28
|
+
id: 0,
|
|
29
|
+
mint: args.mint,
|
|
30
|
+
name: null,
|
|
31
|
+
symbol: null,
|
|
32
|
+
decimals: args.decimals,
|
|
33
|
+
createKind: "unknown",
|
|
34
|
+
creator: null,
|
|
35
|
+
quoteMint: null,
|
|
36
|
+
quoteTokenProgram: null,
|
|
37
|
+
baseTokenProgram: args.baseTokenProgram,
|
|
38
|
+
bondingCurve: null,
|
|
39
|
+
pool: null,
|
|
40
|
+
sharingConfig: null,
|
|
41
|
+
venueHint: "unknown",
|
|
42
|
+
metadataJson: null,
|
|
43
|
+
refreshedAtMs: null,
|
|
44
|
+
createdAtMs: now,
|
|
45
|
+
updatedAtMs: now,
|
|
46
|
+
} as TokenRow;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function resolvePumpToken(
|
|
50
|
+
connection: Connection,
|
|
51
|
+
mint: PublicKey,
|
|
52
|
+
): Promise<{ registry: VenueRegistry; token: TokenRow }> {
|
|
53
|
+
const mintInfo = await readMint(connection, mint);
|
|
54
|
+
const registry = new VenueRegistry()
|
|
55
|
+
.register(new PumpCurveVenue())
|
|
56
|
+
.register(new PumpSwapVenue());
|
|
57
|
+
const shell = tokenShell({
|
|
58
|
+
mint: mint.toBase58(),
|
|
59
|
+
decimals: mintInfo.decimals,
|
|
60
|
+
baseTokenProgram: mintInfo.tokenProgram.toBase58(),
|
|
61
|
+
});
|
|
62
|
+
const inspected = await registry.inspect(connection, mint);
|
|
63
|
+
if (!inspected) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Mint ${mint.toBase58()} is not a supported Pump/PumpSwap token.`,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return { registry, token: { ...shell, ...inspected } as TokenRow };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function priorityInstructions(options: {
|
|
72
|
+
cuLimit?: number;
|
|
73
|
+
priorityMicroLamports?: number;
|
|
74
|
+
}) {
|
|
75
|
+
const out = [];
|
|
76
|
+
if (options.cuLimit != null) {
|
|
77
|
+
out.push(
|
|
78
|
+
ComputeBudgetProgram.setComputeUnitLimit({
|
|
79
|
+
units: Math.max(1, Math.trunc(options.cuLimit)),
|
|
80
|
+
}),
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (options.priorityMicroLamports != null) {
|
|
84
|
+
out.push(
|
|
85
|
+
ComputeBudgetProgram.setComputeUnitPrice({
|
|
86
|
+
microLamports: Math.max(0, Math.trunc(options.priorityMicroLamports)),
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function compile(args: {
|
|
94
|
+
connection: Connection;
|
|
95
|
+
user: PublicKey;
|
|
96
|
+
instructions: TransactionInstruction[];
|
|
97
|
+
side: "buy" | "sell";
|
|
98
|
+
venue: string;
|
|
99
|
+
mint: PublicKey;
|
|
100
|
+
inputRaw: bigint;
|
|
101
|
+
expectedOutputRaw: bigint;
|
|
102
|
+
minimumOutputRaw: bigint;
|
|
103
|
+
quoteMint: PublicKey;
|
|
104
|
+
quoteDecimals: number;
|
|
105
|
+
cuLimit?: number;
|
|
106
|
+
priorityMicroLamports?: number;
|
|
107
|
+
}): Promise<BrowserTradeBuild> {
|
|
108
|
+
const latest = await args.connection.getLatestBlockhash("confirmed");
|
|
109
|
+
const message = new TransactionMessage({
|
|
110
|
+
payerKey: args.user,
|
|
111
|
+
recentBlockhash: latest.blockhash,
|
|
112
|
+
instructions: [...priorityInstructions(args), ...args.instructions],
|
|
113
|
+
}).compileToV0Message();
|
|
114
|
+
const transaction = new VersionedTransaction(message);
|
|
115
|
+
const serializedSize = transaction.serialize().length;
|
|
116
|
+
if (serializedSize > PACKET_LIMIT) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`Local ${args.venue} ${args.side} transaction is ${serializedSize} bytes; ` +
|
|
119
|
+
`Solana packet limit is ${PACKET_LIMIT}. Configure browser lookup tables before sending.`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
transaction,
|
|
124
|
+
side: args.side,
|
|
125
|
+
venue: args.venue,
|
|
126
|
+
mint: args.mint.toBase58(),
|
|
127
|
+
inputRaw: args.inputRaw,
|
|
128
|
+
expectedOutputRaw: args.expectedOutputRaw,
|
|
129
|
+
minimumOutputRaw: args.minimumOutputRaw,
|
|
130
|
+
quoteMint: args.quoteMint.toBase58(),
|
|
131
|
+
quoteDecimals: args.quoteDecimals,
|
|
132
|
+
blockhash: latest.blockhash,
|
|
133
|
+
lastValidBlockHeight: latest.lastValidBlockHeight,
|
|
134
|
+
serializedSize,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function buildLocalPumpBuy(args: {
|
|
139
|
+
connection: Connection;
|
|
140
|
+
user: PublicKey;
|
|
141
|
+
mint: string | PublicKey;
|
|
142
|
+
quoteInRaw: bigint;
|
|
143
|
+
slippageBps?: number;
|
|
144
|
+
cuLimit?: number;
|
|
145
|
+
priorityMicroLamports?: number;
|
|
146
|
+
}): Promise<BrowserTradeBuild> {
|
|
147
|
+
const mint =
|
|
148
|
+
args.mint instanceof PublicKey ? args.mint : new PublicKey(args.mint);
|
|
149
|
+
const { registry, token } = await resolvePumpToken(args.connection, mint);
|
|
150
|
+
const { plugin, market } = await registry.resolve(
|
|
151
|
+
args.connection,
|
|
152
|
+
token,
|
|
153
|
+
args.user,
|
|
154
|
+
);
|
|
155
|
+
if (market.quoteAsset.kind !== "native-sol") {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`Pump token ${mint.toBase58()} is quoted in ${market.quoteAsset.mint.toBase58()}, not native SOL.`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
const ctx = { connection: args.connection, token, user: args.user };
|
|
161
|
+
const quote = await plugin.quoteBuy(
|
|
162
|
+
ctx,
|
|
163
|
+
market,
|
|
164
|
+
rawAmount(args.quoteInRaw, market.quoteAsset),
|
|
165
|
+
args.slippageBps ?? 1_500,
|
|
166
|
+
);
|
|
167
|
+
const built = await plugin.buildBuy(ctx, market, quote);
|
|
168
|
+
return await compile({
|
|
169
|
+
connection: args.connection,
|
|
170
|
+
user: args.user,
|
|
171
|
+
instructions: built.instructions,
|
|
172
|
+
side: "buy",
|
|
173
|
+
venue: market.venue,
|
|
174
|
+
mint,
|
|
175
|
+
inputRaw: quote.inputRaw,
|
|
176
|
+
expectedOutputRaw: quote.expectedOutputRaw,
|
|
177
|
+
minimumOutputRaw: quote.minimumOutputRaw,
|
|
178
|
+
quoteMint: market.quoteAsset.mint,
|
|
179
|
+
quoteDecimals: market.quoteAsset.decimals,
|
|
180
|
+
cuLimit: args.cuLimit ?? 350_000,
|
|
181
|
+
priorityMicroLamports: args.priorityMicroLamports ?? 0,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export async function buildLocalPumpSell(args: {
|
|
186
|
+
connection: Connection;
|
|
187
|
+
user: PublicKey;
|
|
188
|
+
mint: string | PublicKey;
|
|
189
|
+
baseInRaw: bigint;
|
|
190
|
+
slippageBps?: number;
|
|
191
|
+
cuLimit?: number;
|
|
192
|
+
priorityMicroLamports?: number;
|
|
193
|
+
}): Promise<BrowserTradeBuild> {
|
|
194
|
+
const mint =
|
|
195
|
+
args.mint instanceof PublicKey ? args.mint : new PublicKey(args.mint);
|
|
196
|
+
const { registry, token } = await resolvePumpToken(args.connection, mint);
|
|
197
|
+
const { plugin, market } = await registry.resolve(
|
|
198
|
+
args.connection,
|
|
199
|
+
token,
|
|
200
|
+
args.user,
|
|
201
|
+
);
|
|
202
|
+
if (market.quoteAsset.kind !== "native-sol") {
|
|
203
|
+
throw new Error(
|
|
204
|
+
`Pump token ${mint.toBase58()} is quoted in ${market.quoteAsset.mint.toBase58()}, not native SOL.`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
const ctx = { connection: args.connection, token, user: args.user };
|
|
208
|
+
const quote = await plugin.quoteSell(
|
|
209
|
+
ctx,
|
|
210
|
+
market,
|
|
211
|
+
args.baseInRaw,
|
|
212
|
+
args.slippageBps ?? 1_500,
|
|
213
|
+
);
|
|
214
|
+
const built = await plugin.buildSell(ctx, market, quote);
|
|
215
|
+
return await compile({
|
|
216
|
+
connection: args.connection,
|
|
217
|
+
user: args.user,
|
|
218
|
+
instructions: built.instructions,
|
|
219
|
+
side: "sell",
|
|
220
|
+
venue: market.venue,
|
|
221
|
+
mint,
|
|
222
|
+
inputRaw: quote.inputRaw,
|
|
223
|
+
expectedOutputRaw: quote.expectedOutputRaw,
|
|
224
|
+
minimumOutputRaw: quote.minimumOutputRaw,
|
|
225
|
+
quoteMint: market.quoteAsset.mint,
|
|
226
|
+
quoteDecimals: market.quoteAsset.decimals,
|
|
227
|
+
cuLimit: args.cuLimit ?? 350_000,
|
|
228
|
+
priorityMicroLamports: args.priorityMicroLamports ?? 0,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { PublicKey } from "@solana/web3.js";
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
BrowserContact,
|
|
5
|
+
BrowserStorageLike,
|
|
6
|
+
BrowserTokenAlias,
|
|
7
|
+
} from "./types.ts";
|
|
8
|
+
|
|
9
|
+
type StoredCollection<T> = {
|
|
10
|
+
version: 1;
|
|
11
|
+
rows: T[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function cleanAlias(value: string, kind: string): string {
|
|
15
|
+
const alias = value.trim().replace(/^@/, "");
|
|
16
|
+
if (!alias) throw new Error(`${kind} alias is required.`);
|
|
17
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(alias)) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`${kind} alias must be 1..64 characters using letters, numbers, dot, underscore, or hyphen.`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return alias;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function canonicalAddress(value: string): string {
|
|
26
|
+
try {
|
|
27
|
+
return new PublicKey(value.trim()).toBase58();
|
|
28
|
+
} catch {
|
|
29
|
+
throw new Error(`Invalid Solana address: ${value}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function sameName(left: string, right: string): boolean {
|
|
34
|
+
return left.toLowerCase() === right.toLowerCase();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class MemoryBrowserStorage implements BrowserStorageLike {
|
|
38
|
+
private readonly values = new Map<string, string>();
|
|
39
|
+
|
|
40
|
+
get length(): number {
|
|
41
|
+
return this.values.size;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
clear(): void {
|
|
45
|
+
this.values.clear();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
getItem(key: string): string | null {
|
|
49
|
+
return this.values.get(key) ?? null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
key(index: number): string | null {
|
|
53
|
+
return [...this.values.keys()][index] ?? null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
removeItem(key: string): void {
|
|
57
|
+
this.values.delete(key);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
setItem(key: string, value: string): void {
|
|
61
|
+
this.values.set(key, value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function defaultBrowserStorage(): BrowserStorageLike {
|
|
66
|
+
const storage = globalThis.localStorage;
|
|
67
|
+
if (!storage) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
"localStorage is unavailable. Pass a BrowserStorageLike adapter explicitly.",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return storage;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class BrowserSolardStore {
|
|
76
|
+
constructor(
|
|
77
|
+
readonly storage: BrowserStorageLike = defaultBrowserStorage(),
|
|
78
|
+
readonly namespace = "solard:browser:v1",
|
|
79
|
+
) {}
|
|
80
|
+
|
|
81
|
+
private key(name: string): string {
|
|
82
|
+
return `${this.namespace}:${name}`;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private readCollection<T>(name: string): StoredCollection<T> {
|
|
86
|
+
const raw = this.storage.getItem(this.key(name));
|
|
87
|
+
if (!raw) return { version: 1, rows: [] };
|
|
88
|
+
|
|
89
|
+
const parsed = JSON.parse(raw) as Partial<StoredCollection<T>>;
|
|
90
|
+
if (!Array.isArray(parsed.rows)) {
|
|
91
|
+
throw new Error(`Invalid browser storage collection: ${name}`);
|
|
92
|
+
}
|
|
93
|
+
return { version: 1, rows: parsed.rows };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private writeCollection<T>(name: string, rows: T[]): void {
|
|
97
|
+
this.storage.setItem(
|
|
98
|
+
this.key(name),
|
|
99
|
+
JSON.stringify({ version: 1, rows } satisfies StoredCollection<T>),
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
listContacts(): BrowserContact[] {
|
|
104
|
+
return this.readCollection<BrowserContact>("contacts")
|
|
105
|
+
.rows.slice()
|
|
106
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
addContact(
|
|
110
|
+
nameInput: string,
|
|
111
|
+
addressInput: string,
|
|
112
|
+
options: { overwrite?: boolean } = {},
|
|
113
|
+
): BrowserContact {
|
|
114
|
+
const name = cleanAlias(nameInput, "Contact");
|
|
115
|
+
const address = canonicalAddress(addressInput);
|
|
116
|
+
const rows = this.listContacts();
|
|
117
|
+
const byName = rows.find((row) => sameName(row.name, name));
|
|
118
|
+
const byAddress = rows.find((row) => row.address === address);
|
|
119
|
+
|
|
120
|
+
if (byAddress && (!byName || !sameName(byAddress.name, byName.name))) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`${address} is already saved as contact @${byAddress.name}.`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (byName) {
|
|
127
|
+
if (byName.address === address) return byName;
|
|
128
|
+
if (!options.overwrite) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Contact @${byName.name} already points to ${byName.address}.`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
byName.address = address;
|
|
134
|
+
byName.updatedAtMs = Date.now();
|
|
135
|
+
this.writeCollection("contacts", rows);
|
|
136
|
+
return { ...byName };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const now = Date.now();
|
|
140
|
+
const row: BrowserContact = {
|
|
141
|
+
name,
|
|
142
|
+
address,
|
|
143
|
+
createdAtMs: now,
|
|
144
|
+
updatedAtMs: now,
|
|
145
|
+
};
|
|
146
|
+
rows.push(row);
|
|
147
|
+
this.writeCollection("contacts", rows);
|
|
148
|
+
return { ...row };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
findContact(ref: string): BrowserContact | null {
|
|
152
|
+
const value = ref.trim().replace(/^@/, "");
|
|
153
|
+
return (
|
|
154
|
+
this.listContacts().find((row) => sameName(row.name, value)) ??
|
|
155
|
+
this.listContacts().find((row) => row.address === value) ??
|
|
156
|
+
null
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
removeContact(ref: string): BrowserContact {
|
|
161
|
+
const value = ref.trim().replace(/^@/, "");
|
|
162
|
+
const rows = this.listContacts();
|
|
163
|
+
const index = rows.findIndex(
|
|
164
|
+
(row) => sameName(row.name, value) || row.address === value,
|
|
165
|
+
);
|
|
166
|
+
if (index < 0) throw new Error(`Unknown browser contact: ${ref}`);
|
|
167
|
+
const [removed] = rows.splice(index, 1);
|
|
168
|
+
this.writeCollection("contacts", rows);
|
|
169
|
+
return { ...removed! };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
listTokenAliases(): BrowserTokenAlias[] {
|
|
173
|
+
return this.readCollection<BrowserTokenAlias>("tokens")
|
|
174
|
+
.rows.slice()
|
|
175
|
+
.sort((a, b) => a.alias.localeCompare(b.alias));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
registerToken(
|
|
179
|
+
aliasInput: string,
|
|
180
|
+
mintInput: string,
|
|
181
|
+
options: { overwrite?: boolean } = {},
|
|
182
|
+
): BrowserTokenAlias {
|
|
183
|
+
const alias = cleanAlias(aliasInput.replace(/^\$/, ""), "Token");
|
|
184
|
+
const mint = canonicalAddress(mintInput);
|
|
185
|
+
const rows = this.listTokenAliases();
|
|
186
|
+
const existing = rows.find((row) => sameName(row.alias, alias));
|
|
187
|
+
|
|
188
|
+
if (existing) {
|
|
189
|
+
if (existing.mint === mint) return existing;
|
|
190
|
+
if (!options.overwrite) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
`Token alias ${alias} already points to ${existing.mint}.`,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
existing.mint = mint;
|
|
196
|
+
existing.updatedAtMs = Date.now();
|
|
197
|
+
this.writeCollection("tokens", rows);
|
|
198
|
+
return { ...existing };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const now = Date.now();
|
|
202
|
+
const row: BrowserTokenAlias = {
|
|
203
|
+
alias,
|
|
204
|
+
mint,
|
|
205
|
+
createdAtMs: now,
|
|
206
|
+
updatedAtMs: now,
|
|
207
|
+
};
|
|
208
|
+
rows.push(row);
|
|
209
|
+
this.writeCollection("tokens", rows);
|
|
210
|
+
return { ...row };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
resolveToken(ref: string): string {
|
|
214
|
+
const value = ref.trim().replace(/^\$/, "");
|
|
215
|
+
try {
|
|
216
|
+
return new PublicKey(value).toBase58();
|
|
217
|
+
} catch {
|
|
218
|
+
// Alias resolution below.
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const lowered = value.toLowerCase();
|
|
222
|
+
const accepted =
|
|
223
|
+
lowered === "slrd" ? new Set(["slrd", "solard"]) : new Set([lowered]);
|
|
224
|
+
|
|
225
|
+
const row = this.listTokenAliases().find((item) =>
|
|
226
|
+
accepted.has(item.alias.toLowerCase()),
|
|
227
|
+
);
|
|
228
|
+
if (!row) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
`Unknown browser token alias "${ref}". Register it with tokens.register(alias, mint) or pass a mint address.`,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
return row.mint;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
setSetting<T>(name: string, value: T): void {
|
|
237
|
+
this.storage.setItem(this.key(`setting:${name}`), JSON.stringify(value));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
getSetting<T>(name: string, fallback: T): T {
|
|
241
|
+
const raw = this.storage.getItem(this.key(`setting:${name}`));
|
|
242
|
+
return raw == null ? fallback : (JSON.parse(raw) as T);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
removeSetting(name: string): void {
|
|
246
|
+
this.storage.removeItem(this.key(`setting:${name}`));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
resolveDestination(ref: string): string {
|
|
250
|
+
const contact = this.findContact(ref);
|
|
251
|
+
if (contact) return contact.address;
|
|
252
|
+
return canonicalAddress(ref);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PublicKey,
|
|
3
|
+
Transaction,
|
|
4
|
+
VersionedTransaction,
|
|
5
|
+
} from "@solana/web3.js";
|
|
6
|
+
|
|
7
|
+
export interface BrowserWalletSigner {
|
|
8
|
+
publicKey: PublicKey | null;
|
|
9
|
+
connect?: () => Promise<unknown>;
|
|
10
|
+
disconnect?: () => Promise<unknown>;
|
|
11
|
+
signTransaction<T extends Transaction | VersionedTransaction>(
|
|
12
|
+
transaction: T,
|
|
13
|
+
): Promise<T>;
|
|
14
|
+
signAllTransactions?<T extends Transaction | VersionedTransaction>(
|
|
15
|
+
transactions: T[],
|
|
16
|
+
): Promise<T[]>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface BrowserStorageLike {
|
|
20
|
+
readonly length: number;
|
|
21
|
+
clear(): void;
|
|
22
|
+
getItem(key: string): string | null;
|
|
23
|
+
key(index: number): string | null;
|
|
24
|
+
removeItem(key: string): void;
|
|
25
|
+
setItem(key: string, value: string): void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type BrowserContact = {
|
|
29
|
+
name: string;
|
|
30
|
+
address: string;
|
|
31
|
+
createdAtMs: number;
|
|
32
|
+
updatedAtMs: number;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type BrowserTokenAlias = {
|
|
36
|
+
alias: string;
|
|
37
|
+
mint: string;
|
|
38
|
+
createdAtMs: number;
|
|
39
|
+
updatedAtMs: number;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type BrowserTokenBalance = {
|
|
43
|
+
mint: string;
|
|
44
|
+
amountRaw: bigint;
|
|
45
|
+
amountUi: string;
|
|
46
|
+
decimals: number;
|
|
47
|
+
program: "spl-token" | "token-2022";
|
|
48
|
+
label: string;
|
|
49
|
+
tokenAccounts: string[];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type BrowserPortfolio = {
|
|
53
|
+
address: string;
|
|
54
|
+
solLamports: bigint;
|
|
55
|
+
tokenBalances: BrowserTokenBalance[];
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export type BrowserBroadcastResult = {
|
|
59
|
+
signature: string;
|
|
60
|
+
confirmed: boolean;
|
|
61
|
+
blockhash: string;
|
|
62
|
+
lastValidBlockHeight: number;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type BrowserTradeSide = "buy" | "sell";
|
|
66
|
+
|
|
67
|
+
export type BrowserTradeBuild = {
|
|
68
|
+
transaction: VersionedTransaction;
|
|
69
|
+
side: BrowserTradeSide;
|
|
70
|
+
venue: string;
|
|
71
|
+
mint: string;
|
|
72
|
+
inputRaw: bigint;
|
|
73
|
+
expectedOutputRaw: bigint;
|
|
74
|
+
minimumOutputRaw: bigint;
|
|
75
|
+
quoteMint: string;
|
|
76
|
+
quoteDecimals: number;
|
|
77
|
+
blockhash: string;
|
|
78
|
+
lastValidBlockHeight: number;
|
|
79
|
+
serializedSize: number;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export type BrowserTradeResult = BrowserBroadcastResult & {
|
|
83
|
+
side: BrowserTradeSide;
|
|
84
|
+
venue: string;
|
|
85
|
+
mint: string;
|
|
86
|
+
inputRaw: bigint;
|
|
87
|
+
expectedOutputRaw: bigint;
|
|
88
|
+
minimumOutputRaw: bigint;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export type BrowserSolardOptions = {
|
|
92
|
+
rpcUrl: string;
|
|
93
|
+
wallet?: BrowserWalletSigner | null;
|
|
94
|
+
storage?: BrowserStorageLike;
|
|
95
|
+
storageNamespace?: string;
|
|
96
|
+
commitment?: "processed" | "confirmed" | "finalized";
|
|
97
|
+
rpcMaxRps?: number;
|
|
98
|
+
fetch?: typeof globalThis.fetch;
|
|
99
|
+
};
|