nulltrace-sdk 1.0.0

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/dist/index.cjs ADDED
@@ -0,0 +1,1084 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/index.js
30
+ var src_exports = {};
31
+ __export(src_exports, {
32
+ NullTrace: () => NullTrace,
33
+ default: () => src_default
34
+ });
35
+ module.exports = __toCommonJS(src_exports);
36
+ var import_web3 = require("@solana/web3.js");
37
+ var import_spl_token = require("@solana/spl-token");
38
+ var import_stateless = require("@lightprotocol/stateless.js");
39
+ var import_compressed_token = require("@lightprotocol/compressed-token");
40
+ var import_bs58 = __toESM(require("bs58"), 1);
41
+ var import_crypto = require("crypto");
42
+ var import_tweetnacl = __toESM(require("tweetnacl"), 1);
43
+ var OPERATOR_KEY = "5STUuhrL8kJ4up9spEY39VJ6ibQCFrg8x8cRV5UeEcfv";
44
+ var OPERATOR_PUBLIC_KEY = new import_web3.PublicKey(OPERATOR_KEY);
45
+ var ALT_ADDRESS = new import_web3.PublicKey("9NYFyEqPkyXUhkerbGHXUXkvb4qpzeEdHuGpgbgpH1NJ");
46
+ var REMOTE_OPERATOR_URL = "http://34.68.76.183:3333";
47
+ var SHARED_SECRET = "NULL_TRACE_OPERATOR_SECRET_BASE_V1";
48
+ var FEE_BPS = 1e-3;
49
+ var COMPUTE_UNITS = 14e5;
50
+ var COMPUTE_PRICE = 5e3;
51
+ var MAX_TX_SIZE = 1232;
52
+ function _getAuthToken() {
53
+ const step = 180;
54
+ const counter = Math.floor(Date.now() / 1e3 / step);
55
+ const buf = Buffer.alloc(8);
56
+ buf.writeUInt32BE(Math.floor(counter / 4294967296), 0);
57
+ buf.writeUInt32BE(counter >>> 0, 4);
58
+ const hmac = (0, import_crypto.createHmac)("sha1", Buffer.from(SHARED_SECRET, "ascii"));
59
+ hmac.update(buf);
60
+ const hash = hmac.digest();
61
+ const offset = hash[hash.length - 1] & 15;
62
+ const code = (hash[offset] & 127) << 24 | (hash[offset + 1] & 255) << 16 | (hash[offset + 2] & 255) << 8 | hash[offset + 3] & 255;
63
+ return (code % 1e6).toString().padStart(6, "0");
64
+ }
65
+ function _validateHeliusRpc(url) {
66
+ if (!url || typeof url !== "string") {
67
+ throw new Error("NullTrace: Valid Helius rpcUrl is required");
68
+ }
69
+ const lower = url.toLowerCase();
70
+ if (!lower.includes("helius")) {
71
+ throw new Error(
72
+ "NullTrace: A Helius RPC endpoint is required. Get a key at https://helius.dev"
73
+ );
74
+ }
75
+ return url;
76
+ }
77
+ function _sleep(ms) {
78
+ return new Promise((r) => setTimeout(r, ms));
79
+ }
80
+ async function _getMintInfo(connection, mintAddress) {
81
+ if (mintAddress === import_spl_token.NATIVE_MINT.toBase58()) {
82
+ return { decimals: 9, tokenProgram: import_spl_token.TOKEN_PROGRAM_ID };
83
+ }
84
+ const mintInfo = await connection.getParsedAccountInfo(new import_web3.PublicKey(mintAddress));
85
+ if (!mintInfo.value)
86
+ throw new Error(`Mint not found: ${mintAddress}`);
87
+ return {
88
+ decimals: mintInfo.value.data.parsed.info.decimals,
89
+ tokenProgram: new import_web3.PublicKey(mintInfo.value.owner)
90
+ };
91
+ }
92
+ async function _getCompressedAccounts(connection, owner, mint, isSOL) {
93
+ const accounts = isSOL ? await connection.getCompressedAccountsByOwner(owner) : await connection.getCompressedTokenAccountsByOwner(owner, { mint: new import_web3.PublicKey(mint) });
94
+ return accounts.items.sort((a, b) => {
95
+ const aAmt = isSOL ? a.lamports : a.parsed.amount;
96
+ const bAmt = isSOL ? b.lamports : b.parsed.amount;
97
+ return Number(bAmt) - Number(aAmt);
98
+ });
99
+ }
100
+ function _selectInputs(sortedAccounts, amountLamports, isSOL) {
101
+ const selected = [];
102
+ let total = 0;
103
+ for (const a of sortedAccounts) {
104
+ if (total >= amountLamports)
105
+ break;
106
+ total += Number(isSOL ? a.lamports : a.parsed.amount);
107
+ selected.push(a);
108
+ }
109
+ return { selected, total };
110
+ }
111
+ function _batchAccounts(accounts) {
112
+ const validSizes = [8, 4, 2, 1];
113
+ const batches = [];
114
+ let remaining = [...accounts];
115
+ while (remaining.length > 0) {
116
+ const size = validSizes.find((s) => remaining.length >= s) || 1;
117
+ batches.push(remaining.slice(0, size));
118
+ remaining = remaining.slice(size);
119
+ }
120
+ return batches;
121
+ }
122
+ async function _packTransactions(connection, payer, instructions, adl) {
123
+ const { blockhash } = await connection.getLatestBlockhash();
124
+ const computeIxs = [
125
+ import_web3.ComputeBudgetProgram.setComputeUnitLimit({ units: COMPUTE_UNITS }),
126
+ import_web3.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: COMPUTE_PRICE })
127
+ ];
128
+ let current = [...computeIxs];
129
+ const messages = [];
130
+ for (const ix of instructions) {
131
+ try {
132
+ current.push(ix);
133
+ const msg = new import_web3.TransactionMessage({
134
+ payerKey: payer,
135
+ recentBlockhash: blockhash,
136
+ instructions: current
137
+ }).compileToV0Message([adl]);
138
+ if (msg.serialize().length > MAX_TX_SIZE)
139
+ throw new Error("oversize");
140
+ } catch {
141
+ current.pop();
142
+ if (current.length > computeIxs.length) {
143
+ messages.push(
144
+ new import_web3.TransactionMessage({
145
+ payerKey: payer,
146
+ recentBlockhash: blockhash,
147
+ instructions: current
148
+ }).compileToV0Message([adl])
149
+ );
150
+ }
151
+ current = [...computeIxs, ix];
152
+ }
153
+ }
154
+ if (current.length > computeIxs.length) {
155
+ messages.push(
156
+ new import_web3.TransactionMessage({
157
+ payerKey: payer,
158
+ recentBlockhash: blockhash,
159
+ instructions: current
160
+ }).compileToV0Message([adl])
161
+ );
162
+ }
163
+ return messages.map((m) => new import_web3.VersionedTransaction(m));
164
+ }
165
+ async function _signSendConfirm(connection, wallet, transactions) {
166
+ const signed = await wallet.signAllTransactions(transactions);
167
+ const sigs = [];
168
+ for (const tx of signed) {
169
+ const sig = await connection.sendRawTransaction(tx.serialize());
170
+ await connection.confirmTransaction(sig);
171
+ sigs.push(sig);
172
+ }
173
+ return sigs;
174
+ }
175
+ async function _enrichMetadata(tokenBalances) {
176
+ if (!tokenBalances.length)
177
+ return tokenBalances;
178
+ const addresses = tokenBalances.map((t) => t.address).join(",");
179
+ try {
180
+ const dexRes = await fetch("https://api.dexscreener.com/latest/dex/tokens/" + addresses);
181
+ if (dexRes.ok) {
182
+ const data = await dexRes.json();
183
+ if (data.pairs) {
184
+ const pairs = data.pairs.sort(
185
+ (a, b) => parseFloat(b.volume?.h24 ?? "0") - parseFloat(a.volume?.h24 ?? "0")
186
+ );
187
+ for (const t of tokenBalances) {
188
+ const pair = pairs.find((p) => p.baseToken.address === t.address);
189
+ if (pair) {
190
+ t.symbol = t.symbol || pair.baseToken.symbol;
191
+ t.name = t.name || pair.baseToken.name;
192
+ t.logo = t.logo || (pair.info?.imageUrl ?? "");
193
+ t.dexscreener = t.dexscreener || (pair.url ?? "");
194
+ }
195
+ }
196
+ }
197
+ }
198
+ } catch {
199
+ }
200
+ try {
201
+ const geckoRes = await fetch(
202
+ "https://api.geckoterminal.com/api/v2/networks/solana/tokens/multi/" + addresses + "?include=top_pools"
203
+ );
204
+ if (geckoRes.ok) {
205
+ const data = await geckoRes.json();
206
+ if (data.data) {
207
+ for (const t of tokenBalances) {
208
+ const gt = Object.values(data.data).find((x) => x.attributes?.address === t.address);
209
+ if (gt) {
210
+ t.symbol = t.symbol || gt.attributes.symbol;
211
+ t.name = t.name || gt.attributes.name;
212
+ t.logo = t.logo || gt.attributes.image_url;
213
+ t.decimals = t.decimals || gt.attributes.decimals;
214
+ if (t.decimals)
215
+ t.amount = (t.lamports / 10 ** t.decimals).toFixed(t.decimals);
216
+ }
217
+ }
218
+ }
219
+ }
220
+ } catch {
221
+ }
222
+ try {
223
+ const jupRes = await fetch(
224
+ "https://datapi.jup.ag/v1/assets/search?query=" + addresses + "&sortBy=verified"
225
+ );
226
+ if (jupRes.ok) {
227
+ const tokens = await jupRes.json();
228
+ if (tokens) {
229
+ for (const t of tokenBalances) {
230
+ const jt = tokens.find((x) => x.id === t.address);
231
+ if (jt) {
232
+ t.symbol = t.symbol || jt.symbol;
233
+ t.name = t.name || jt.name;
234
+ t.logo = t.logo || jt.icon;
235
+ t.decimals = t.decimals || jt.decimals;
236
+ if (t.decimals)
237
+ t.amount = (t.lamports / 10 ** t.decimals).toFixed(t.decimals);
238
+ }
239
+ }
240
+ }
241
+ }
242
+ } catch {
243
+ }
244
+ return tokenBalances;
245
+ }
246
+ var NullTrace = class _NullTrace {
247
+ /**
248
+ * Create a standalone NullTrace client.
249
+ *
250
+ * The second argument can be any of the following:
251
+ *
252
+ * 1. **Wallet adapter** — an object with `publicKey`, `signAllTransactions`, and optionally `signMessage`.
253
+ * 2. **Keypair** — a `@solana/web3.js` Keypair instance.
254
+ * 3. **Secret key (Uint8Array)** — a 64-byte secret key array.
255
+ * 4. **Private key (base58 string)** — a base58-encoded private key.
256
+ *
257
+ * @param {string} rpcUrl A Helius RPC endpoint (required for ZK compression).
258
+ * @param {Object|Keypair|Uint8Array|string} walletOrKey Wallet adapter, Keypair, secret key bytes, or base58 private key.
259
+ *
260
+ * @example
261
+ * // Wallet adapter (browser)
262
+ * const nt = new NullTrace(rpcUrl, wallet);
263
+ *
264
+ * @example
265
+ * // Keypair (Node.js)
266
+ * const nt = new NullTrace(rpcUrl, Keypair.fromSecretKey(secretKey));
267
+ *
268
+ * @example
269
+ * // Raw secret key bytes
270
+ * const nt = new NullTrace(rpcUrl, mySecretKeyUint8Array);
271
+ *
272
+ * @example
273
+ * // Base58 private key string
274
+ * const nt = new NullTrace(rpcUrl, '4wBqp...');
275
+ */
276
+ constructor(rpcUrl, walletOrKey) {
277
+ _validateHeliusRpc(rpcUrl);
278
+ if (!walletOrKey)
279
+ throw new Error("NullTrace: a wallet, Keypair, secret key, or private key string is required");
280
+ this.rpcUrl = rpcUrl;
281
+ this.wallet = _NullTrace._resolveWallet(walletOrKey);
282
+ this.connection = (0, import_stateless.createRpc)(rpcUrl, rpcUrl, rpcUrl, { commitment: "processed" });
283
+ this._adlCache = null;
284
+ this._sigCache = null;
285
+ }
286
+ // -----------------------------------------------------------------------
287
+ // Static helpers for wallet resolution
288
+ // -----------------------------------------------------------------------
289
+ /**
290
+ * Create a wallet adapter interface from a Keypair.
291
+ *
292
+ * @param {Keypair} keypair A `@solana/web3.js` Keypair.
293
+ * @returns {{ publicKey: PublicKey, signAllTransactions: Function, signMessage: Function }}
294
+ */
295
+ static fromKeypair(keypair) {
296
+ if (!keypair?.publicKey || !keypair?.secretKey) {
297
+ throw new Error("NullTrace.fromKeypair: invalid Keypair");
298
+ }
299
+ return {
300
+ publicKey: keypair.publicKey,
301
+ signAllTransactions: async (txs) => {
302
+ for (const tx of txs)
303
+ tx.sign([keypair]);
304
+ return txs;
305
+ },
306
+ signMessage: async (msg) => import_tweetnacl.default.sign.detached(msg, keypair.secretKey)
307
+ };
308
+ }
309
+ /**
310
+ * Create a wallet adapter interface from a raw secret key (64 bytes).
311
+ *
312
+ * @param {Uint8Array} secretKey A 64-byte Ed25519 secret key.
313
+ * @returns {{ publicKey: PublicKey, signAllTransactions: Function, signMessage: Function }}
314
+ */
315
+ static fromSecretKey(secretKey) {
316
+ if (!(secretKey instanceof Uint8Array) || secretKey.length !== 64) {
317
+ throw new Error("NullTrace.fromSecretKey: expected a 64-byte Uint8Array");
318
+ }
319
+ return _NullTrace.fromKeypair(import_web3.Keypair.fromSecretKey(secretKey));
320
+ }
321
+ /**
322
+ * Create a wallet adapter interface from a base58-encoded private key string.
323
+ *
324
+ * @param {string} base58Key A base58-encoded private key (as exported by Phantom, Solflare, etc.).
325
+ * @returns {{ publicKey: PublicKey, signAllTransactions: Function, signMessage: Function }}
326
+ */
327
+ static fromPrivateKey(base58Key) {
328
+ if (typeof base58Key !== "string" || base58Key.length < 32) {
329
+ throw new Error("NullTrace.fromPrivateKey: expected a base58-encoded private key string");
330
+ }
331
+ const decoded = import_bs58.default.decode(base58Key);
332
+ return _NullTrace.fromKeypair(import_web3.Keypair.fromSecretKey(decoded));
333
+ }
334
+ /**
335
+ * @internal Resolve any supported wallet input into a wallet adapter interface.
336
+ */
337
+ static _resolveWallet(input) {
338
+ if (input?.publicKey && typeof input.signAllTransactions === "function") {
339
+ return input;
340
+ }
341
+ if (input instanceof import_web3.Keypair) {
342
+ return _NullTrace.fromKeypair(input);
343
+ }
344
+ if (input instanceof Uint8Array && input.length === 64) {
345
+ return _NullTrace.fromSecretKey(input);
346
+ }
347
+ if (typeof input === "string") {
348
+ return _NullTrace.fromPrivateKey(input);
349
+ }
350
+ throw new Error(
351
+ "NullTrace: unsupported wallet type. Provide a wallet adapter, Keypair, 64-byte Uint8Array, or base58 private key string."
352
+ );
353
+ }
354
+ /** @internal Get the address lookup table (cached). */
355
+ async _getAlt() {
356
+ if (this._adlCache)
357
+ return this._adlCache;
358
+ const result = await this.connection.getAddressLookupTable(ALT_ADDRESS);
359
+ this._adlCache = result.value;
360
+ return this._adlCache;
361
+ }
362
+ // -----------------------------------------------------------------------
363
+ // Nullify (public -> private)
364
+ // -----------------------------------------------------------------------
365
+ /**
366
+ * Convert public tokens into private ZK-compressed state.
367
+ *
368
+ * @param {string} mint Token mint address (use NATIVE_MINT for SOL).
369
+ * @param {string} amount Human-readable amount (e.g. "1.5").
370
+ * @returns {Promise<string[]>} Transaction signatures.
371
+ *
372
+ * @example
373
+ * const sigs = await nt.nullify('So11111111111111111111111111111111111111112', '0.5');
374
+ */
375
+ async nullify(mint, amount) {
376
+ if (!mint || !amount)
377
+ throw new Error("NullTrace.nullify: mint and amount are required");
378
+ const owner = this.wallet.publicKey;
379
+ const isSOL = mint === import_spl_token.NATIVE_MINT.toBase58();
380
+ const { decimals, tokenProgram } = await _getMintInfo(this.connection, mint);
381
+ const amountLamports = (0, import_stateless.bn)(Math.floor(parseFloat(amount) * 10 ** decimals).toString());
382
+ const feeLamports = (0, import_stateless.bn)(Math.floor(parseInt(amountLamports.toString()) * FEE_BPS).toString());
383
+ const ixs = [];
384
+ const activeStateTrees = await this.connection.getStateTreeInfos();
385
+ const tree = (0, import_stateless.selectStateTreeInfo)(activeStateTrees);
386
+ if (isSOL) {
387
+ ixs.push(
388
+ await import_stateless.LightSystemProgram.compress({
389
+ payer: owner,
390
+ toAddress: owner,
391
+ lamports: amountLamports.sub(feeLamports),
392
+ outputStateTreeInfo: tree
393
+ })
394
+ );
395
+ ixs.push(
396
+ import_web3.SystemProgram.transfer({
397
+ fromPubkey: owner,
398
+ toPubkey: OPERATOR_PUBLIC_KEY,
399
+ lamports: feeLamports
400
+ })
401
+ );
402
+ } else {
403
+ const mintPk = new import_web3.PublicKey(mint);
404
+ const sourceAta = await (0, import_spl_token.getAssociatedTokenAddress)(mintPk, owner, false, tokenProgram);
405
+ const [tokenPoolPda] = import_web3.PublicKey.findProgramAddressSync(
406
+ [Buffer.from("pool"), mintPk.toBuffer()],
407
+ import_stateless.COMPRESSED_TOKEN_PROGRAM_ID
408
+ );
409
+ const poolInfo = await this.connection.getAccountInfo(tokenPoolPda);
410
+ if (!poolInfo) {
411
+ ixs.push(
412
+ await import_compressed_token.CompressedTokenProgram.createTokenPool({
413
+ feePayer: owner,
414
+ mint: mintPk,
415
+ tokenProgramId: tokenProgram
416
+ })
417
+ );
418
+ }
419
+ ixs.push(
420
+ await import_compressed_token.CompressedTokenProgram.compress({
421
+ payer: owner,
422
+ owner,
423
+ source: sourceAta,
424
+ toAddress: owner,
425
+ amount: amountLamports.sub(feeLamports),
426
+ mint: mintPk,
427
+ outputStateTreeInfo: tree,
428
+ tokenPoolInfo: {
429
+ tokenPoolPda,
430
+ tokenProgram,
431
+ isInitialized: true,
432
+ balance: (0, import_stateless.bn)("0"),
433
+ poolIndex: 0,
434
+ mint: mintPk
435
+ }
436
+ })
437
+ );
438
+ const operatorAta = await (0, import_spl_token.getAssociatedTokenAddress)(mintPk, OPERATOR_PUBLIC_KEY, false, tokenProgram);
439
+ const operatorInfo = await this.connection.getAccountInfo(operatorAta);
440
+ if (!operatorInfo) {
441
+ ixs.push((0, import_spl_token.createAssociatedTokenAccountInstruction)(owner, operatorAta, OPERATOR_PUBLIC_KEY, mintPk, tokenProgram));
442
+ }
443
+ ixs.push(
444
+ (0, import_spl_token.createTransferCheckedInstruction)(sourceAta, mintPk, operatorAta, owner, feeLamports, decimals, [], tokenProgram)
445
+ );
446
+ }
447
+ const adl = await this._getAlt();
448
+ const txs = await _packTransactions(this.connection, owner, ixs, adl);
449
+ return _signSendConfirm(this.connection, this.wallet, txs);
450
+ }
451
+ // -----------------------------------------------------------------------
452
+ // Reveal (private -> public)
453
+ // -----------------------------------------------------------------------
454
+ /**
455
+ * Decompress private tokens back to public state.
456
+ *
457
+ * @param {string} mint Token mint address.
458
+ * @param {string} amount Human-readable amount.
459
+ * @returns {Promise<string[]>} Transaction signatures.
460
+ *
461
+ * @example
462
+ * const sigs = await nt.reveal('So11111111111111111111111111111111111111112', '0.5');
463
+ */
464
+ async reveal(mint, amount) {
465
+ if (!mint || !amount)
466
+ throw new Error("NullTrace.reveal: mint and amount are required");
467
+ const owner = this.wallet.publicKey;
468
+ const isSOL = mint === import_spl_token.NATIVE_MINT.toBase58();
469
+ const { decimals, tokenProgram } = await _getMintInfo(this.connection, mint);
470
+ const amountLamports = Math.floor(parseFloat(amount) * 10 ** decimals);
471
+ const sorted = await _getCompressedAccounts(this.connection, owner, mint, isSOL);
472
+ const { selected, total } = _selectInputs(sorted, amountLamports, isSOL);
473
+ if (total < amountLamports)
474
+ throw new Error("Insufficient private balance");
475
+ const batches = _batchAccounts(selected);
476
+ const ixs = [];
477
+ let selectedTokenPoolInfos;
478
+ let destinationAta;
479
+ if (!isSOL) {
480
+ const tokenPoolInfos = await (0, import_compressed_token.getTokenPoolInfos)(this.connection, new import_web3.PublicKey(mint));
481
+ selectedTokenPoolInfos = (0, import_compressed_token.selectTokenPoolInfosForDecompression)(tokenPoolInfos, amountLamports);
482
+ destinationAta = await (0, import_spl_token.getAssociatedTokenAddress)(new import_web3.PublicKey(mint), owner, false, tokenProgram);
483
+ const info = await this.connection.getAccountInfo(destinationAta);
484
+ if (!info) {
485
+ ixs.push((0, import_spl_token.createAssociatedTokenAccountInstruction)(owner, destinationAta, owner, new import_web3.PublicKey(mint), tokenProgram));
486
+ }
487
+ }
488
+ let remaining = amountLamports;
489
+ for (const batch of batches) {
490
+ const proof = await this.connection.getValidityProofV0(
491
+ batch.map((a) => ({
492
+ hash: a.hash ?? a.compressedAccount?.hash,
493
+ tree: a.treeInfo?.tree ?? a.compressedAccount?.treeInfo?.tree,
494
+ queue: a.treeInfo?.queue ?? a.compressedAccount?.treeInfo?.queue
495
+ }))
496
+ );
497
+ const batchAmount = decimals === 0 ? 1 : Math.min(remaining, batch.reduce((s, a) => s + Number(isSOL ? a.lamports : a.parsed.amount), 0));
498
+ ixs.push(
499
+ await (isSOL ? import_stateless.LightSystemProgram.decompress({
500
+ payer: owner,
501
+ inputCompressedAccounts: batch,
502
+ toAddress: owner,
503
+ lamports: (0, import_stateless.bn)(batchAmount.toString()),
504
+ recentInputStateRootIndices: proof.rootIndices,
505
+ recentValidityProof: proof.compressedProof
506
+ }) : import_compressed_token.CompressedTokenProgram.decompress({
507
+ payer: owner,
508
+ inputCompressedTokenAccounts: batch,
509
+ toAddress: destinationAta,
510
+ amount: (0, import_stateless.bn)(batchAmount.toString()),
511
+ recentInputStateRootIndices: proof.rootIndices,
512
+ recentValidityProof: proof.compressedProof,
513
+ tokenPoolInfos: selectedTokenPoolInfos
514
+ }))
515
+ );
516
+ remaining -= batchAmount;
517
+ }
518
+ const adl = await this._getAlt();
519
+ const txs = await _packTransactions(this.connection, owner, ixs, adl);
520
+ return _signSendConfirm(this.connection, this.wallet, txs);
521
+ }
522
+ // -----------------------------------------------------------------------
523
+ // Transfer (private -> private)
524
+ // -----------------------------------------------------------------------
525
+ /**
526
+ * Send compressed tokens privately to another Solana address.
527
+ *
528
+ * @param {string} mint Token mint address.
529
+ * @param {string} amount Human-readable amount.
530
+ * @param {string} recipient Recipient's Solana public key.
531
+ * @returns {Promise<string[]>} Transaction signatures.
532
+ *
533
+ * @example
534
+ * const sigs = await nt.transfer('So11...', '1.0', 'Recip1ent...');
535
+ */
536
+ async transfer(mint, amount, recipient) {
537
+ if (!mint || !amount || !recipient) {
538
+ throw new Error("NullTrace.transfer: mint, amount, and recipient are required");
539
+ }
540
+ const owner = this.wallet.publicKey;
541
+ const recipientPk = new import_web3.PublicKey(recipient);
542
+ const isSOL = mint === import_spl_token.NATIVE_MINT.toBase58();
543
+ const { decimals, tokenProgram } = await _getMintInfo(this.connection, mint);
544
+ const amountLamports = Math.floor(parseFloat(amount) * 10 ** decimals);
545
+ const sorted = await _getCompressedAccounts(this.connection, owner, mint, isSOL);
546
+ const { selected, total } = _selectInputs(sorted, amountLamports, isSOL);
547
+ const { blockhash } = await this.connection.getLatestBlockhash();
548
+ const adl = await this._getAlt();
549
+ const preTransactions = [];
550
+ if (total < amountLamports) {
551
+ const deficit = amountLamports - total;
552
+ const fee = (0, import_stateless.bn)(Math.floor(deficit * FEE_BPS).toString());
553
+ const trees = await this.connection.getStateTreeInfos();
554
+ const tree = (0, import_stateless.selectStateTreeInfo)(trees);
555
+ if (isSOL) {
556
+ const solBal = await this.connection.getBalance(owner);
557
+ if (solBal < deficit + 1e5)
558
+ throw new Error("Insufficient balance");
559
+ const compressIx = await import_stateless.LightSystemProgram.compress({
560
+ payer: owner,
561
+ toAddress: OPERATOR_PUBLIC_KEY,
562
+ lamports: (0, import_stateless.bn)(deficit.toString()).sub(fee),
563
+ outputStateTreeInfo: tree
564
+ });
565
+ const feeIx = import_web3.SystemProgram.transfer({
566
+ fromPubkey: owner,
567
+ toPubkey: OPERATOR_PUBLIC_KEY,
568
+ lamports: fee
569
+ });
570
+ const msg = new import_web3.TransactionMessage({
571
+ payerKey: owner,
572
+ recentBlockhash: blockhash,
573
+ instructions: [
574
+ import_web3.ComputeBudgetProgram.setComputeUnitLimit({ units: COMPUTE_UNITS }),
575
+ import_web3.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: COMPUTE_PRICE }),
576
+ compressIx,
577
+ feeIx
578
+ ]
579
+ }).compileToV0Message([adl]);
580
+ preTransactions.push(new import_web3.VersionedTransaction(msg));
581
+ } else {
582
+ let instructions = [
583
+ import_web3.ComputeBudgetProgram.setComputeUnitLimit({ units: COMPUTE_UNITS }),
584
+ import_web3.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: COMPUTE_PRICE })
585
+ ];
586
+ const sourceAta = await (0, import_spl_token.getAssociatedTokenAddress)(
587
+ new import_web3.PublicKey(mint),
588
+ owner,
589
+ false,
590
+ tokenProgram
591
+ );
592
+ const tokenAccountInfos = await this.connection.getParsedTokenAccountsByOwner(
593
+ owner,
594
+ { programId: tokenProgram, mint: new import_web3.PublicKey(mint) },
595
+ "processed"
596
+ );
597
+ const publicBalance = tokenAccountInfos.value?.[0].account.data.parsed.info.tokenAmount.amount ?? 0;
598
+ if (publicBalance < deficit)
599
+ throw new Error("Insufficient balance");
600
+ const [tokenPoolPda] = import_web3.PublicKey.findProgramAddressSync(
601
+ [Buffer.from("pool"), new import_web3.PublicKey(mint).toBuffer()],
602
+ import_stateless.COMPRESSED_TOKEN_PROGRAM_ID
603
+ );
604
+ const tokenPoolInfo = await this.connection.getAccountInfo(tokenPoolPda, "processed");
605
+ if (!tokenPoolInfo) {
606
+ const createTokenPoolIx = await import_compressed_token.CompressedTokenProgram.createTokenPool({
607
+ feePayer: owner,
608
+ mint: new import_web3.PublicKey(mint),
609
+ tokenProgramId: tokenProgram
610
+ });
611
+ instructions.push(createTokenPoolIx);
612
+ }
613
+ const compressInstruction = await import_compressed_token.CompressedTokenProgram.compress({
614
+ payer: owner,
615
+ owner,
616
+ source: sourceAta,
617
+ toAddress: recipientPk,
618
+ amount: (0, import_stateless.bn)(deficit.toString()).sub(fee),
619
+ mint: new import_web3.PublicKey(mint),
620
+ outputStateTreeInfo: tree,
621
+ tokenPoolInfo: {
622
+ tokenPoolPda,
623
+ tokenProgram,
624
+ isInitialized: true,
625
+ balance: (0, import_stateless.bn)("0"),
626
+ poolIndex: 0,
627
+ mint: new import_web3.PublicKey(mint)
628
+ }
629
+ });
630
+ instructions.push(compressInstruction);
631
+ const operatorTokenAccount = await (0, import_spl_token.getAssociatedTokenAddress)(new import_web3.PublicKey(mint), OPERATOR_PUBLIC_KEY, false, tokenProgram);
632
+ const operatorPoolInfo = await this.connection.getAccountInfo(operatorTokenAccount);
633
+ if (!operatorPoolInfo) {
634
+ instructions.push((0, import_spl_token.createAssociatedTokenAccountInstruction)(owner, operatorTokenAccount, OPERATOR_PUBLIC_KEY, new import_web3.PublicKey(mint), tokenProgram));
635
+ }
636
+ instructions.push((0, import_spl_token.createTransferCheckedInstruction)(
637
+ new import_web3.PublicKey(sourceAta),
638
+ new import_web3.PublicKey(mint),
639
+ new import_web3.PublicKey(operatorTokenAccount),
640
+ owner,
641
+ fee,
642
+ decimals,
643
+ [],
644
+ tokenProgram
645
+ ));
646
+ let tx = new import_web3.VersionedTransaction(new import_web3.TransactionMessage({
647
+ payerKey: owner,
648
+ recentBlockhash: blockhash,
649
+ instructions
650
+ }).compileToV0Message([adl]));
651
+ preTransactions.push(tx);
652
+ }
653
+ }
654
+ if (total < amountLamports && preTransactions.length === 0) {
655
+ throw new Error("Insufficient balance");
656
+ }
657
+ const batches = _batchAccounts(selected);
658
+ let remaining = amountLamports;
659
+ const ixs = [];
660
+ for (const batch of batches) {
661
+ const proof = await this.connection.getValidityProofV0(
662
+ batch.map((a) => ({
663
+ hash: a.hash ?? a.compressedAccount?.hash,
664
+ tree: a.treeInfo?.tree ?? a.compressedAccount?.treeInfo?.tree,
665
+ queue: a.treeInfo?.queue ?? a.compressedAccount?.treeInfo?.queue
666
+ }))
667
+ );
668
+ const batchAmount = decimals === 0 ? 1 : Math.min(remaining, batch.reduce((s, a) => s + Number(isSOL ? a.lamports : a.parsed.amount), 0));
669
+ ixs.push(
670
+ await (isSOL ? import_stateless.LightSystemProgram.transfer({
671
+ payer: owner,
672
+ inputCompressedAccounts: batch,
673
+ toAddress: recipientPk,
674
+ lamports: (0, import_stateless.bn)(batchAmount.toString()),
675
+ recentInputStateRootIndices: proof.rootIndices,
676
+ recentValidityProof: proof.compressedProof
677
+ }) : import_compressed_token.CompressedTokenProgram.transfer({
678
+ payer: owner,
679
+ inputCompressedTokenAccounts: batch,
680
+ toAddress: recipientPk,
681
+ amount: (0, import_stateless.bn)(batchAmount.toString()),
682
+ recentInputStateRootIndices: proof.rootIndices,
683
+ recentValidityProof: proof.compressedProof
684
+ }))
685
+ );
686
+ remaining -= batchAmount;
687
+ }
688
+ const txs = await _packTransactions(this.connection, owner, ixs, adl);
689
+ const allTxs = [...preTransactions, ...txs];
690
+ return _signSendConfirm(this.connection, this.wallet, allTxs);
691
+ }
692
+ // -----------------------------------------------------------------------
693
+ // Swap (private swap via operator)
694
+ // -----------------------------------------------------------------------
695
+ /**
696
+ * Get a swap quote. No signing required.
697
+ *
698
+ * @param {string} inputMint Input token mint.
699
+ * @param {string} outputMint Output token mint.
700
+ * @param {string} amount Human-readable input amount.
701
+ * @returns {Promise<{inAmount: string, outAmount: string, priceImpact: number}>}
702
+ *
703
+ * @example
704
+ * const quote = await nt.quoteSwap('So11...', 'Es9v...', '1.0');
705
+ */
706
+ async quoteSwap(inputMint, outputMint, amount) {
707
+ if (!inputMint || !outputMint || !amount) {
708
+ throw new Error("NullTrace.quoteSwap: inputMint, outputMint, and amount are required");
709
+ }
710
+ const { decimals } = await _getMintInfo(this.connection, inputMint);
711
+ const amountLamports = Math.floor(parseFloat(amount) * 10 ** decimals);
712
+ const res = await fetch(`${REMOTE_OPERATOR_URL}/operator/quote-swap`, {
713
+ method: "POST",
714
+ headers: {
715
+ "Content-Type": "application/json",
716
+ "x-null-client-secret": _getAuthToken()
717
+ },
718
+ body: JSON.stringify({ inputMint, outputMint, amount: amountLamports })
719
+ });
720
+ if (!res.ok) {
721
+ const err = await res.json().catch(() => ({}));
722
+ throw new Error(err.error || `Quote failed: ${res.status}`);
723
+ }
724
+ return res.json();
725
+ }
726
+ /**
727
+ * Execute a private swap via the NullTrace operator.
728
+ *
729
+ * @param {string} fromMint Input token mint.
730
+ * @param {string} toMint Output token mint.
731
+ * @param {string} amount Human-readable input amount.
732
+ * @param {Object} [options]
733
+ * @param {(status: string) => void} [options.onStatusChange]
734
+ * @param {number} [options.timeout=120000]
735
+ * @returns {Promise<{status: string, result: Object}>}
736
+ *
737
+ * @example
738
+ * const result = await nt.swap('So11...', 'Es9v...', '1.0');
739
+ */
740
+ async swap(fromMint, toMint, amount, options = {}) {
741
+ if (!fromMint || !toMint || !amount) {
742
+ throw new Error("NullTrace.swap: fromMint, toMint, and amount are required");
743
+ }
744
+ const { onStatusChange, timeout = 12e4 } = options;
745
+ const owner = this.wallet.publicKey;
746
+ const isSOL = fromMint === import_spl_token.NATIVE_MINT.toBase58();
747
+ const { decimals, tokenProgram } = await _getMintInfo(this.connection, fromMint);
748
+ const amountLamports = Math.floor(parseFloat(amount) * 10 ** decimals);
749
+ const sorted = await _getCompressedAccounts(this.connection, owner, fromMint, isSOL);
750
+ const { selected, total } = _selectInputs(sorted, amountLamports, isSOL);
751
+ const { blockhash } = await this.connection.getLatestBlockhash();
752
+ const adl = await this._getAlt();
753
+ const preTransactions = [];
754
+ if (total < amountLamports) {
755
+ const deficit = amountLamports - total;
756
+ const fee = (0, import_stateless.bn)(Math.floor(deficit * FEE_BPS).toString());
757
+ const trees = await this.connection.getStateTreeInfos();
758
+ const tree = (0, import_stateless.selectStateTreeInfo)(trees);
759
+ if (isSOL) {
760
+ const solBal = await this.connection.getBalance(owner);
761
+ if (solBal < deficit + 1e5)
762
+ throw new Error("Insufficient balance");
763
+ const compressIx = await import_stateless.LightSystemProgram.compress({
764
+ payer: owner,
765
+ toAddress: OPERATOR_PUBLIC_KEY,
766
+ lamports: (0, import_stateless.bn)(deficit.toString()).sub(fee),
767
+ outputStateTreeInfo: tree
768
+ });
769
+ const feeIx = import_web3.SystemProgram.transfer({
770
+ fromPubkey: owner,
771
+ toPubkey: OPERATOR_PUBLIC_KEY,
772
+ lamports: fee
773
+ });
774
+ const msg = new import_web3.TransactionMessage({
775
+ payerKey: owner,
776
+ recentBlockhash: blockhash,
777
+ instructions: [
778
+ import_web3.ComputeBudgetProgram.setComputeUnitLimit({ units: COMPUTE_UNITS }),
779
+ import_web3.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: COMPUTE_PRICE }),
780
+ compressIx,
781
+ feeIx
782
+ ]
783
+ }).compileToV0Message([adl]);
784
+ preTransactions.push(new import_web3.VersionedTransaction(msg));
785
+ } else {
786
+ let instructions = [
787
+ import_web3.ComputeBudgetProgram.setComputeUnitLimit({ units: COMPUTE_UNITS }),
788
+ import_web3.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: COMPUTE_PRICE })
789
+ ];
790
+ const sourceAta = await (0, import_spl_token.getAssociatedTokenAddress)(
791
+ new import_web3.PublicKey(fromMint),
792
+ owner,
793
+ false,
794
+ tokenProgram
795
+ );
796
+ const tokenAccountInfos = await this.connection.getParsedTokenAccountsByOwner(
797
+ owner,
798
+ { programId: tokenProgram, mint: new import_web3.PublicKey(fromMint) },
799
+ "processed"
800
+ );
801
+ const publicBalance = tokenAccountInfos.value?.[0].account.data.parsed.info.tokenAmount.amount ?? 0;
802
+ if (publicBalance < deficit)
803
+ throw new Error("Insufficient balance");
804
+ const [tokenPoolPda] = import_web3.PublicKey.findProgramAddressSync(
805
+ [Buffer.from("pool"), new import_web3.PublicKey(fromMint).toBuffer()],
806
+ import_stateless.COMPRESSED_TOKEN_PROGRAM_ID
807
+ );
808
+ const tokenPoolInfo = await this.connection.getAccountInfo(tokenPoolPda, "processed");
809
+ if (!tokenPoolInfo) {
810
+ const createTokenPoolIx = await import_compressed_token.CompressedTokenProgram.createTokenPool({
811
+ feePayer: owner,
812
+ mint: new import_web3.PublicKey(fromMint),
813
+ tokenProgramId: tokenProgram
814
+ });
815
+ instructions.push(createTokenPoolIx);
816
+ }
817
+ const compressInstruction = await import_compressed_token.CompressedTokenProgram.compress({
818
+ payer: owner,
819
+ owner,
820
+ source: sourceAta,
821
+ toAddress: OPERATOR_PUBLIC_KEY,
822
+ amount: (0, import_stateless.bn)(deficit.toString()).sub(fee),
823
+ mint: new import_web3.PublicKey(fromMint),
824
+ outputStateTreeInfo: tree,
825
+ tokenPoolInfo: {
826
+ tokenPoolPda,
827
+ tokenProgram,
828
+ isInitialized: true,
829
+ balance: (0, import_stateless.bn)("0"),
830
+ poolIndex: 0,
831
+ mint: new import_web3.PublicKey(fromMint)
832
+ }
833
+ });
834
+ instructions.push(compressInstruction);
835
+ const operatorTokenAccount = await (0, import_spl_token.getAssociatedTokenAddress)(new import_web3.PublicKey(fromMint), OPERATOR_PUBLIC_KEY, false, tokenProgram);
836
+ const operatorPoolInfo = await this.connection.getAccountInfo(operatorTokenAccount);
837
+ if (!operatorPoolInfo) {
838
+ instructions.push((0, import_spl_token.createAssociatedTokenAccountInstruction)(owner, operatorTokenAccount, OPERATOR_PUBLIC_KEY, new import_web3.PublicKey(fromMint), tokenProgram));
839
+ }
840
+ instructions.push((0, import_spl_token.createTransferCheckedInstruction)(
841
+ new import_web3.PublicKey(sourceAta),
842
+ new import_web3.PublicKey(fromMint),
843
+ new import_web3.PublicKey(operatorTokenAccount),
844
+ owner,
845
+ fee,
846
+ decimals,
847
+ [],
848
+ tokenProgram
849
+ ));
850
+ let tx = new import_web3.VersionedTransaction(new import_web3.TransactionMessage({
851
+ payerKey: owner,
852
+ recentBlockhash: blockhash,
853
+ instructions
854
+ }).compileToV0Message([adl]));
855
+ preTransactions.push(tx);
856
+ }
857
+ }
858
+ if (total < amountLamports && preTransactions.length === 0) {
859
+ throw new Error("Insufficient balance");
860
+ }
861
+ const batches = _batchAccounts(selected);
862
+ const ixs = [];
863
+ let remaining = amountLamports;
864
+ for (const batch of batches) {
865
+ const proof = await this.connection.getValidityProofV0(
866
+ batch.map((a) => ({
867
+ hash: a.hash ?? a.compressedAccount?.hash,
868
+ tree: a.treeInfo?.tree ?? a.compressedAccount?.treeInfo?.tree,
869
+ queue: a.treeInfo?.queue ?? a.compressedAccount?.treeInfo?.queue
870
+ }))
871
+ );
872
+ const batchAmount = decimals === 0 ? 1 : Math.min(remaining, batch.reduce((s, a) => s + Number(isSOL ? a.lamports : a.parsed.amount), 0));
873
+ ixs.push(
874
+ await (isSOL ? import_stateless.LightSystemProgram.transfer({
875
+ payer: owner,
876
+ inputCompressedAccounts: batch,
877
+ toAddress: OPERATOR_PUBLIC_KEY,
878
+ lamports: (0, import_stateless.bn)(batchAmount.toString()),
879
+ recentInputStateRootIndices: proof.rootIndices,
880
+ recentValidityProof: proof.compressedProof
881
+ }) : import_compressed_token.CompressedTokenProgram.transfer({
882
+ payer: owner,
883
+ inputCompressedTokenAccounts: batch,
884
+ toAddress: OPERATOR_PUBLIC_KEY,
885
+ amount: (0, import_stateless.bn)(batchAmount.toString()),
886
+ recentInputStateRootIndices: proof.rootIndices,
887
+ recentValidityProof: proof.compressedProof
888
+ }))
889
+ );
890
+ remaining -= batchAmount;
891
+ }
892
+ const transferTxs = await _packTransactions(this.connection, owner, ixs, adl);
893
+ const allTxs = [...preTransactions, ...transferTxs];
894
+ if (onStatusChange)
895
+ onStatusChange("signing");
896
+ const signed = await this.wallet.signAllTransactions(allTxs);
897
+ const signedBase64 = signed.map((tx) => {
898
+ const bytes = tx.serialize();
899
+ return typeof Buffer !== "undefined" ? Buffer.from(bytes).toString("base64") : btoa(String.fromCharCode(...bytes));
900
+ });
901
+ const preSigs = [];
902
+ for (let i = 0; i < preTransactions.length; i++) {
903
+ const sig = await this.connection.sendRawTransaction(signed[i].serialize());
904
+ await this.connection.confirmTransaction(sig);
905
+ preSigs.push(sig);
906
+ }
907
+ const swapId = import_web3.Keypair.generate().publicKey.toString();
908
+ const swapData = {
909
+ id: swapId,
910
+ fromToken: fromMint,
911
+ toToken: toMint,
912
+ amount,
913
+ amountValue: amountLamports,
914
+ fromTokenDecimals: decimals,
915
+ userPublicKey: owner.toString(),
916
+ recipient: OPERATOR_PUBLIC_KEY.toString(),
917
+ status: "initialized",
918
+ created: Date.now()
919
+ };
920
+ if (onStatusChange)
921
+ onStatusChange("processing");
922
+ const transferBase64 = signedBase64.slice(preTransactions.length);
923
+ const execRes = await fetch(`${REMOTE_OPERATOR_URL}/operator/process-swap`, {
924
+ method: "POST",
925
+ headers: {
926
+ "Content-Type": "application/json",
927
+ "x-null-client-secret": _getAuthToken()
928
+ },
929
+ body: JSON.stringify({ swapData, signedTransferData: transferBase64 })
930
+ });
931
+ if (!execRes.ok) {
932
+ const err = await execRes.json().catch(() => ({}));
933
+ throw new Error(err.error || `Swap submission failed: ${execRes.status}`);
934
+ }
935
+ const execData = await execRes.json();
936
+ if (execData.status === "completed") {
937
+ if (onStatusChange)
938
+ onStatusChange("completed");
939
+ return { status: "completed", result: execData };
940
+ }
941
+ const deadline = Date.now() + timeout;
942
+ while (Date.now() < deadline) {
943
+ await _sleep(2e3);
944
+ if (onStatusChange)
945
+ onStatusChange("pending");
946
+ }
947
+ return { status: "pending", swapId, result: execData };
948
+ }
949
+ // -----------------------------------------------------------------------
950
+ // Balances
951
+ // -----------------------------------------------------------------------
952
+ /**
953
+ * Get public (on-chain) token balances.
954
+ *
955
+ * @returns {Promise<Array<{symbol: string, name: string, amount: string, lamports: number, decimals: number, address: string}>>}
956
+ */
957
+ async getPublicBalances() {
958
+ const owner = this.wallet.publicKey;
959
+ const tokenBalances = [];
960
+ const solBal = await this.connection.getBalance(owner);
961
+ if (solBal > 0) {
962
+ tokenBalances.push({
963
+ symbol: "SOL",
964
+ name: "Solana",
965
+ amount: (solBal / 1e9 - 0.01).toString(),
966
+ lamports: solBal - 0.01 * 1e9,
967
+ decimals: 9,
968
+ logo: "",
969
+ address: import_spl_token.NATIVE_MINT.toString()
970
+ });
971
+ }
972
+ const [spl, spl22] = await Promise.all([
973
+ this.connection.getParsedTokenAccountsByOwner(owner, { programId: import_spl_token.TOKEN_PROGRAM_ID }, "processed"),
974
+ this.connection.getParsedTokenAccountsByOwner(owner, { programId: import_spl_token.TOKEN_2022_PROGRAM_ID }, "processed")
975
+ ]);
976
+ for (const ta of [...spl.value, ...spl22.value]) {
977
+ const p = ta.account.data.parsed;
978
+ if (p.info.tokenAmount.amount === "0")
979
+ continue;
980
+ tokenBalances.push({
981
+ symbol: "",
982
+ name: "",
983
+ amount: (parseInt(p.info.tokenAmount.amount) / 10 ** p.info.tokenAmount.decimals).toString(),
984
+ lamports: parseInt(p.info.tokenAmount.amount),
985
+ decimals: p.info.tokenAmount.decimals,
986
+ logo: "",
987
+ address: p.info.mint,
988
+ dexscreener: ""
989
+ });
990
+ }
991
+ return _enrichMetadata(tokenBalances);
992
+ }
993
+ /**
994
+ * Get private (ZK-compressed) token balances.
995
+ * Requires `wallet.signMessage`.
996
+ *
997
+ * @returns {Promise<Array<{symbol: string, name: string, amount: string, lamports: number, decimals: number, address: string}>>}
998
+ */
999
+ async getPrivateBalances() {
1000
+ if (!this.wallet.signMessage) {
1001
+ throw new Error("NullTrace: wallet.signMessage is required for getPrivateBalances");
1002
+ }
1003
+ const owner = this.wallet.publicKey;
1004
+ if (!this._sigCache) {
1005
+ const msg = new TextEncoder().encode("Reveal Private Balances");
1006
+ this._sigCache = await this.wallet.signMessage(msg);
1007
+ }
1008
+ const tokenBalances = [];
1009
+ const compressedSol = await this.connection.getCompressedBalanceByOwner(owner);
1010
+ if (parseInt(compressedSol.toString()) > 0) {
1011
+ tokenBalances.push({
1012
+ symbol: "SOL",
1013
+ name: "Solana",
1014
+ amount: (parseInt(compressedSol.toString()) / 1e9).toString(),
1015
+ lamports: parseInt(compressedSol.toString()),
1016
+ decimals: 9,
1017
+ logo: "",
1018
+ address: import_spl_token.NATIVE_MINT.toString()
1019
+ });
1020
+ }
1021
+ const compressedTokens = await this.connection.getCompressedTokenAccountsByOwner(owner);
1022
+ for (const item of compressedTokens.items) {
1023
+ const mintAddr = item.parsed.mint.toString();
1024
+ const amt = (0, import_stateless.bn)(item.parsed.amount.toString());
1025
+ let entry = tokenBalances.find((t) => t.address === mintAddr);
1026
+ if (!entry) {
1027
+ entry = { symbol: "", name: "", amount: "0", lamports: 0, decimals: 0, logo: "", address: mintAddr };
1028
+ tokenBalances.push(entry);
1029
+ }
1030
+ entry.lamports += parseInt(amt.toString());
1031
+ }
1032
+ return _enrichMetadata(tokenBalances.filter((t) => t.lamports > 0));
1033
+ }
1034
+ /**
1035
+ * Get all balances merged (public + private) with `publicAmount` and `privateAmount` fields.
1036
+ * Requires `wallet.signMessage`.
1037
+ *
1038
+ * @returns {Promise<Array<{symbol: string, name: string, amount: number, publicAmount: number, privateAmount: number, address: string}>>}
1039
+ */
1040
+ async getBalances() {
1041
+ const [pub, priv] = await Promise.all([this.getPublicBalances(), this.getPrivateBalances()]);
1042
+ const merged = pub.map((t) => ({
1043
+ ...t,
1044
+ publicAmount: parseFloat(t.amount),
1045
+ privateAmount: 0,
1046
+ amount: parseFloat(t.amount)
1047
+ }));
1048
+ for (const token of priv) {
1049
+ const existing = merged.find((t) => t.address === token.address);
1050
+ if (existing) {
1051
+ existing.privateAmount += parseFloat(token.amount);
1052
+ existing.amount += parseFloat(token.amount);
1053
+ } else {
1054
+ merged.push({ ...token, publicAmount: 0, privateAmount: parseFloat(token.amount), amount: parseFloat(token.amount) });
1055
+ }
1056
+ }
1057
+ return merged;
1058
+ }
1059
+ /**
1060
+ * Fetch metadata for a token (symbol, name, logo, decimals).
1061
+ *
1062
+ * @param {string} mint Token mint address.
1063
+ * @returns {Promise<{symbol: string, name: string, logo: string, decimals: number}>}
1064
+ */
1065
+ async getTokenMetadata(mint) {
1066
+ if (!mint)
1067
+ throw new Error("NullTrace.getTokenMetadata: mint is required");
1068
+ if (mint === import_spl_token.NATIVE_MINT.toBase58()) {
1069
+ return { symbol: "SOL", name: "Solana", logo: "", decimals: 9 };
1070
+ }
1071
+ const result = [{ address: mint, symbol: "", name: "", logo: "", decimals: 0, lamports: 0 }];
1072
+ await _enrichMetadata(result);
1073
+ return result[0];
1074
+ }
1075
+ /** Clear cached message signature. Next getPrivateBalances will re-prompt. */
1076
+ clearSignatureCache() {
1077
+ this._sigCache = null;
1078
+ }
1079
+ };
1080
+ var src_default = NullTrace;
1081
+ // Annotate the CommonJS export names for ESM import in node:
1082
+ 0 && (module.exports = {
1083
+ NullTrace
1084
+ });