@lobstercove/lichen-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.
@@ -0,0 +1,813 @@
1
+ // Lichen SDK - Connection Class
2
+ import WebSocket from 'ws';
3
+ import { createHash } from 'crypto';
4
+ import { TransactionBuilder } from './transaction.js';
5
+ import { encodeTransaction, hexToBytes, bytesToHex } from './bincode.js';
6
+ /** SHA-256 hash as Uint8Array */
7
+ function sha256(data) {
8
+ return new Uint8Array(createHash('sha256').update(data).digest());
9
+ }
10
+ /**
11
+ * RPC/WebSocket connection to Lichen
12
+ */
13
+ export class Connection {
14
+ constructor(rpcUrl, wsUrl, options) {
15
+ this.subscriptions = new Map();
16
+ this.nextId = 1;
17
+ this.rpcUrl = rpcUrl;
18
+ this.wsUrl = wsUrl;
19
+ this.timeoutMs = options?.timeoutMs ?? 30000;
20
+ }
21
+ /**
22
+ * Make an RPC call
23
+ */
24
+ async rpc(method, params = []) {
25
+ const controller = new AbortController();
26
+ const timer = setTimeout(() => controller.abort(), this.timeoutMs);
27
+ let response;
28
+ try {
29
+ response = await fetch(this.rpcUrl, {
30
+ method: 'POST',
31
+ headers: { 'Content-Type': 'application/json' },
32
+ body: JSON.stringify({
33
+ jsonrpc: '2.0',
34
+ id: this.nextId++,
35
+ method,
36
+ params,
37
+ }),
38
+ signal: controller.signal,
39
+ });
40
+ }
41
+ catch (err) {
42
+ clearTimeout(timer);
43
+ if (err.name === 'AbortError') {
44
+ throw new Error(`RPC request timed out after ${this.timeoutMs}ms: ${method}`);
45
+ }
46
+ throw err;
47
+ }
48
+ finally {
49
+ clearTimeout(timer);
50
+ }
51
+ if (!response.ok) {
52
+ const text = await response.text().catch(() => '');
53
+ throw new Error(`RPC HTTP ${response.status}: ${text}`);
54
+ }
55
+ const data = await response.json();
56
+ if (data.error) {
57
+ throw new Error(`RPC Error: ${data.error.message}`);
58
+ }
59
+ return data.result;
60
+ }
61
+ // ============================================================================
62
+ // BASIC QUERIES
63
+ // ============================================================================
64
+ /**
65
+ * Get account balance
66
+ */
67
+ async getBalance(pubkey) {
68
+ return this.rpc('getBalance', [pubkey.toBase58()]);
69
+ }
70
+ /**
71
+ * Get account information
72
+ */
73
+ async getAccount(pubkey) {
74
+ return this.rpc('getAccount', [pubkey.toBase58()]);
75
+ }
76
+ /**
77
+ * Get block by slot number
78
+ */
79
+ async getBlock(slot) {
80
+ return this.rpc('getBlock', [slot]);
81
+ }
82
+ /**
83
+ * Get latest block
84
+ */
85
+ async getLatestBlock() {
86
+ return this.rpc('getLatestBlock');
87
+ }
88
+ /**
89
+ * Get current slot
90
+ */
91
+ async getSlot() {
92
+ const result = await this.rpc('getSlot');
93
+ return typeof result === 'number' ? result : result.slot;
94
+ }
95
+ /**
96
+ * Get recent blockhash for transactions
97
+ */
98
+ async getRecentBlockhash() {
99
+ const result = await this.rpc('getRecentBlockhash');
100
+ return typeof result === 'string' ? result : result.blockhash;
101
+ }
102
+ /**
103
+ * Get transaction by signature.
104
+ * For contract calls, response includes: return_code (u32), return_data (base64), contract_logs (string[]).
105
+ */
106
+ async getTransaction(signature) {
107
+ return this.rpc('getTransaction', [signature]);
108
+ }
109
+ /**
110
+ * Get a Merkle inclusion proof for a transaction by its signature.
111
+ */
112
+ async getTransactionProof(signature) {
113
+ return this.rpc('getTransactionProof', [signature]);
114
+ }
115
+ /**
116
+ * Verify a Merkle inclusion proof for a transaction against a root.
117
+ * Uses SHA-256 with domain-separated leaf (0x00) and internal (0x01) nodes.
118
+ *
119
+ * This is a pure static function — no RPC call needed.
120
+ */
121
+ static verifyTransactionProof(root, txHash, proof) {
122
+ // Domain-separated leaf: SHA256(0x00 || tx_hash_bytes)
123
+ const leafData = new Uint8Array(33);
124
+ leafData[0] = 0x00;
125
+ leafData.set(hexToBytes(txHash), 1);
126
+ let current = sha256(leafData);
127
+ for (const step of proof) {
128
+ const sibling = hexToBytes(step.hash);
129
+ const nodeData = new Uint8Array(65);
130
+ nodeData[0] = 0x01; // internal node domain tag
131
+ if (step.direction === 'left') {
132
+ nodeData.set(sibling, 1);
133
+ nodeData.set(current, 33);
134
+ }
135
+ else {
136
+ nodeData.set(current, 1);
137
+ nodeData.set(sibling, 33);
138
+ }
139
+ current = sha256(nodeData);
140
+ }
141
+ return bytesToHex(current) === root;
142
+ }
143
+ /**
144
+ * Send transaction
145
+ */
146
+ async sendTransaction(transaction) {
147
+ const txBytes = encodeTransaction(transaction);
148
+ const txBase64 = Buffer.from(txBytes).toString('base64');
149
+ const result = await this.rpc('sendTransaction', [txBase64]);
150
+ return typeof result === 'string' ? result : result.signature;
151
+ }
152
+ /**
153
+ * Get total burned LICN
154
+ */
155
+ async getTotalBurned() {
156
+ return this.rpc('getTotalBurned');
157
+ }
158
+ /**
159
+ * Get all validators
160
+ */
161
+ async getValidators() {
162
+ const result = await this.rpc('getValidators');
163
+ return result.validators;
164
+ }
165
+ /**
166
+ * Get performance metrics
167
+ */
168
+ async getMetrics() {
169
+ return this.rpc('getMetrics');
170
+ }
171
+ /**
172
+ * Health check
173
+ */
174
+ async health() {
175
+ return this.rpc('health');
176
+ }
177
+ // ============================================================================
178
+ // NETWORK ENDPOINTS
179
+ // ============================================================================
180
+ /**
181
+ * Get connected peers
182
+ */
183
+ async getPeers() {
184
+ const result = await this.rpc('getPeers');
185
+ return result.peers;
186
+ }
187
+ /**
188
+ * Get network information
189
+ */
190
+ async getNetworkInfo() {
191
+ return this.rpc('getNetworkInfo');
192
+ }
193
+ // ============================================================================
194
+ // VALIDATOR ENDPOINTS
195
+ // ============================================================================
196
+ /**
197
+ * Get detailed validator information
198
+ */
199
+ async getValidatorInfo(pubkey) {
200
+ return this.rpc('getValidatorInfo', [pubkey.toBase58()]);
201
+ }
202
+ /**
203
+ * Get validator performance metrics
204
+ */
205
+ async getValidatorPerformance(pubkey) {
206
+ return this.rpc('getValidatorPerformance', [pubkey.toBase58()]);
207
+ }
208
+ /**
209
+ * Get comprehensive chain status
210
+ */
211
+ async getChainStatus() {
212
+ return this.rpc('getChainStatus');
213
+ }
214
+ // ============================================================================
215
+ // STAKING ENDPOINTS
216
+ // ============================================================================
217
+ /**
218
+ * Create stake transaction
219
+ */
220
+ async stake(from, validator, amount) {
221
+ const blockhash = await this.getRecentBlockhash();
222
+ const instruction = TransactionBuilder.stake(from.pubkey(), validator, amount);
223
+ const transaction = new TransactionBuilder()
224
+ .add(instruction)
225
+ .setRecentBlockhash(blockhash)
226
+ .buildAndSign(from);
227
+ return this.sendTransaction(transaction);
228
+ }
229
+ /**
230
+ * Create unstake transaction
231
+ */
232
+ async unstake(from, validator, amount) {
233
+ const blockhash = await this.getRecentBlockhash();
234
+ const instruction = TransactionBuilder.unstake(from.pubkey(), validator, amount);
235
+ const transaction = new TransactionBuilder()
236
+ .add(instruction)
237
+ .setRecentBlockhash(blockhash)
238
+ .buildAndSign(from);
239
+ return this.sendTransaction(transaction);
240
+ }
241
+ /**
242
+ * Get staking status
243
+ */
244
+ async getStakingStatus(pubkey) {
245
+ return this.rpc('getStakingStatus', [pubkey.toBase58()]);
246
+ }
247
+ /**
248
+ * Get staking rewards
249
+ */
250
+ async getStakingRewards(pubkey) {
251
+ return this.rpc('getStakingRewards', [pubkey.toBase58()]);
252
+ }
253
+ // ============================================================================
254
+ // TRANSFER & CONTRACT ENDPOINTS
255
+ // ============================================================================
256
+ /**
257
+ * Transfer native LICN (spores) from one account to another.
258
+ *
259
+ * @param from - Sender keypair (signer)
260
+ * @param to - Recipient public key
261
+ * @param amount - Amount in spores (1 LICN = 1,000,000,000 spores)
262
+ * @returns Transaction signature
263
+ */
264
+ async transfer(from, to, amount) {
265
+ const blockhash = await this.getRecentBlockhash();
266
+ const instruction = TransactionBuilder.transfer(from.pubkey(), to, amount);
267
+ const transaction = new TransactionBuilder()
268
+ .add(instruction)
269
+ .setRecentBlockhash(blockhash)
270
+ .buildAndSign(from);
271
+ return this.sendTransaction(transaction);
272
+ }
273
+ /**
274
+ * Deploy a WASM smart contract.
275
+ *
276
+ * @param deployer - Deployer keypair (signer, pays deploy fee)
277
+ * @param code - WASM bytecode (must start with \\0asm magic, max 512 KB)
278
+ * @param initData - Optional initialization data passed to contract init
279
+ * @returns Transaction signature
280
+ */
281
+ async deployContract(deployer, code, initData = new Uint8Array(0)) {
282
+ const blockhash = await this.getRecentBlockhash();
283
+ const instruction = TransactionBuilder.deployContract(deployer.pubkey(), code, initData);
284
+ const transaction = new TransactionBuilder()
285
+ .add(instruction)
286
+ .setRecentBlockhash(blockhash)
287
+ .buildAndSign(deployer);
288
+ return this.sendTransaction(transaction);
289
+ }
290
+ /**
291
+ * Call a function on a deployed WASM smart contract.
292
+ *
293
+ * @param caller - Caller keypair (signer)
294
+ * @param contract - Contract account public key
295
+ * @param functionName - Name of the contract function to invoke
296
+ * @param args - Serialized function arguments (default: empty)
297
+ * @param value - Native LICN to send with the call in spores (default: 0)
298
+ * @returns Transaction signature
299
+ */
300
+ async callContract(caller, contract, functionName, args = new Uint8Array(0), value = 0) {
301
+ const blockhash = await this.getRecentBlockhash();
302
+ const instruction = TransactionBuilder.callContract(caller.pubkey(), contract, functionName, args, value);
303
+ const transaction = new TransactionBuilder()
304
+ .add(instruction)
305
+ .setRecentBlockhash(blockhash)
306
+ .buildAndSign(caller);
307
+ return this.sendTransaction(transaction);
308
+ }
309
+ /**
310
+ * Upgrade a deployed WASM smart contract (owner only).
311
+ *
312
+ * @param owner - Contract owner keypair (signer)
313
+ * @param contract - Contract account public key
314
+ * @param code - New WASM bytecode
315
+ * @returns Transaction signature
316
+ */
317
+ async upgradeContract(owner, contract, code) {
318
+ const blockhash = await this.getRecentBlockhash();
319
+ const instruction = TransactionBuilder.upgradeContract(owner.pubkey(), contract, code);
320
+ const transaction = new TransactionBuilder()
321
+ .add(instruction)
322
+ .setRecentBlockhash(blockhash)
323
+ .buildAndSign(owner);
324
+ return this.sendTransaction(transaction);
325
+ }
326
+ // ============================================================================
327
+ // ACCOUNT ENDPOINTS
328
+ // ============================================================================
329
+ /**
330
+ * Get enhanced account information
331
+ */
332
+ async getAccountInfo(pubkey) {
333
+ return this.rpc('getAccountInfo', [pubkey.toBase58()]);
334
+ }
335
+ /**
336
+ * Get transaction history
337
+ */
338
+ async getTransactionHistory(pubkey, limit = 10) {
339
+ return this.rpc('getTransactionHistory', [pubkey.toBase58(), limit]);
340
+ }
341
+ /**
342
+ * Get program accounts
343
+ */
344
+ async getProgramAccounts(programId) {
345
+ const result = await this.rpc('getProgramAccounts', [programId.toBase58()]);
346
+ return result.accounts || [];
347
+ }
348
+ /**
349
+ * Simulate transaction (dry run)
350
+ */
351
+ async simulateTransaction(transaction) {
352
+ const txBytes = encodeTransaction(transaction);
353
+ const txBase64 = Buffer.from(txBytes).toString('base64');
354
+ return this.rpc('simulateTransaction', [txBase64]);
355
+ }
356
+ /**
357
+ * Execute a read-only contract call without submitting a transaction.
358
+ */
359
+ async callReadonlyContract(contractId, functionName, args = new Uint8Array(), from) {
360
+ const params = [contractId.toBase58(), functionName, Buffer.from(args).toString('base64')];
361
+ if (from) {
362
+ params.push(from.toBase58());
363
+ }
364
+ return this.rpc('callContract', params);
365
+ }
366
+ // ============================================================================
367
+ // CONTRACT ENDPOINTS
368
+ // ============================================================================
369
+ /**
370
+ * Get contract information
371
+ */
372
+ async getContractInfo(contractId) {
373
+ return this.rpc('getContractInfo', [contractId.toBase58()]);
374
+ }
375
+ /**
376
+ * Get contract logs
377
+ */
378
+ async getContractLogs(contractId) {
379
+ return this.rpc('getContractLogs', [contractId.toBase58()]);
380
+ }
381
+ /**
382
+ * Get contract ABI/IDL (machine-readable function and event interface)
383
+ */
384
+ async getContractAbi(contractId) {
385
+ return this.rpc('getContractAbi', [contractId.toBase58()]);
386
+ }
387
+ /**
388
+ * Set/update contract ABI (owner only)
389
+ */
390
+ async setContractAbi(contractId, abi) {
391
+ return this.rpc('setContractAbi', [contractId.toBase58(), abi]);
392
+ }
393
+ /**
394
+ * Get all deployed contracts
395
+ */
396
+ async getAllContracts() {
397
+ return this.rpc('getAllContracts');
398
+ }
399
+ /**
400
+ * Get a symbol-registry entry.
401
+ */
402
+ async getSymbolRegistry(symbol) {
403
+ return this.rpc('getSymbolRegistry', [symbol]);
404
+ }
405
+ /**
406
+ * Get the complete LichenID profile for an address.
407
+ */
408
+ async getLichenIdProfile(pubkey) {
409
+ return this.rpc('getLichenIdProfile', [pubkey.toBase58()]);
410
+ }
411
+ /**
412
+ * Get the LichenID reputation summary for an address.
413
+ */
414
+ async getLichenIdReputation(pubkey) {
415
+ return this.rpc('getLichenIdReputation', [pubkey.toBase58()]);
416
+ }
417
+ /**
418
+ * Get LichenID skills for an address.
419
+ */
420
+ async getLichenIdSkills(pubkey) {
421
+ return this.rpc('getLichenIdSkills', [pubkey.toBase58()]);
422
+ }
423
+ /**
424
+ * Get LichenID vouches for an address.
425
+ */
426
+ async getLichenIdVouches(pubkey) {
427
+ return this.rpc('getLichenIdVouches', [pubkey.toBase58()]);
428
+ }
429
+ /**
430
+ * Resolve a .lichen name to its owner.
431
+ */
432
+ async resolveLichenName(name) {
433
+ return this.rpc('resolveLichenName', [name]);
434
+ }
435
+ /**
436
+ * Get premium-name auction state for a .lichen label.
437
+ */
438
+ async getNameAuction(name) {
439
+ return this.rpc('getNameAuction', [name]);
440
+ }
441
+ /**
442
+ * Get the LichenID agent directory.
443
+ */
444
+ async getLichenIdAgentDirectory(options = {}) {
445
+ return this.rpc('getLichenIdAgentDirectory', [options]);
446
+ }
447
+ /**
448
+ * Get aggregated LichenID statistics.
449
+ */
450
+ async getLichenIdStats() {
451
+ return this.rpc('getLichenIdStats');
452
+ }
453
+ /**
454
+ * Get aggregated SporePay streaming statistics.
455
+ */
456
+ async getSporePayStats() {
457
+ return this.rpc('getSporePayStats');
458
+ }
459
+ /**
460
+ * Get aggregated LichenSwap AMM statistics.
461
+ */
462
+ async getLichenSwapStats() {
463
+ return this.rpc('getLichenSwapStats');
464
+ }
465
+ /**
466
+ * Get aggregated ThallLend lending statistics.
467
+ */
468
+ async getThallLendStats() {
469
+ return this.rpc('getThallLendStats');
470
+ }
471
+ /**
472
+ * Get aggregated SporeVault yield-vault statistics.
473
+ */
474
+ async getSporeVaultStats() {
475
+ return this.rpc('getSporeVaultStats');
476
+ }
477
+ /**
478
+ * Get aggregated BountyBoard marketplace statistics.
479
+ */
480
+ async getBountyBoardStats() {
481
+ return this.rpc('getBountyBoardStats');
482
+ }
483
+ // ==========================================================================
484
+ // PROGRAM ENDPOINTS (DRAFT)
485
+ // ==========================================================================
486
+ async getProgram(programId) {
487
+ return this.rpc('getProgram', [programId.toBase58()]);
488
+ }
489
+ async getProgramStats(programId) {
490
+ return this.rpc('getProgramStats', [programId.toBase58()]);
491
+ }
492
+ async getPrograms() {
493
+ return this.rpc('getPrograms');
494
+ }
495
+ async getProgramCalls(programId) {
496
+ return this.rpc('getProgramCalls', [programId.toBase58()]);
497
+ }
498
+ async getProgramStorage(programId) {
499
+ return this.rpc('getProgramStorage', [programId.toBase58()]);
500
+ }
501
+ // ==========================================================================
502
+ // NFT ENDPOINTS (DRAFT)
503
+ // ==========================================================================
504
+ async getCollection(collectionId) {
505
+ return this.rpc('getCollection', [collectionId.toBase58()]);
506
+ }
507
+ async getNFT(collectionId, tokenId) {
508
+ return this.rpc('getNFT', [collectionId.toBase58(), tokenId]);
509
+ }
510
+ async getNFTsByOwner(owner) {
511
+ return this.rpc('getNFTsByOwner', [owner.toBase58()]);
512
+ }
513
+ async getNFTsByCollection(collectionId) {
514
+ return this.rpc('getNFTsByCollection', [collectionId.toBase58()]);
515
+ }
516
+ async getNFTActivity(collectionId, tokenId) {
517
+ return this.rpc('getNFTActivity', [collectionId.toBase58(), tokenId]);
518
+ }
519
+ // ============================================================================
520
+ // WEBSOCKET SUBSCRIPTIONS
521
+ // ============================================================================
522
+ /**
523
+ * Connect WebSocket
524
+ */
525
+ async connectWs() {
526
+ if (!this.wsUrl) {
527
+ throw new Error('WebSocket URL not provided');
528
+ }
529
+ if (this.ws?.readyState === WebSocket.OPEN) {
530
+ return;
531
+ }
532
+ return new Promise((resolve, reject) => {
533
+ this.ws = new WebSocket(this.wsUrl);
534
+ this.ws.on('open', () => {
535
+ resolve();
536
+ });
537
+ this.ws.on('message', (data) => {
538
+ // AUDIT-FIX J-1: Guard against malformed WebSocket messages
539
+ let msg;
540
+ try {
541
+ msg = JSON.parse(data.toString());
542
+ }
543
+ catch {
544
+ console.warn('Lichen WS: ignoring non-JSON message');
545
+ return;
546
+ }
547
+ if (msg.method === 'subscription') {
548
+ const { subscription, result } = msg.params;
549
+ const handler = this.subscriptions.get(subscription);
550
+ if (handler) {
551
+ handler(result);
552
+ }
553
+ }
554
+ });
555
+ this.ws.on('error', (error) => {
556
+ console.error('WebSocket error:', error);
557
+ reject(error);
558
+ });
559
+ });
560
+ }
561
+ /**
562
+ * Subscribe to method
563
+ */
564
+ async subscribe(method, params = null) {
565
+ await this.connectWs();
566
+ return new Promise((resolve, reject) => {
567
+ const id = this.nextId++;
568
+ const messageHandler = (data) => {
569
+ // AUDIT-FIX J-1: Guard against malformed WebSocket messages
570
+ let msg;
571
+ try {
572
+ msg = JSON.parse(data.toString());
573
+ }
574
+ catch {
575
+ return; // skip non-JSON frames
576
+ }
577
+ if (msg.id === id) {
578
+ clearTimeout(timeout);
579
+ this.ws.off('message', messageHandler);
580
+ if (msg.error) {
581
+ reject(new Error(msg.error.message));
582
+ }
583
+ else {
584
+ resolve(msg.result);
585
+ }
586
+ }
587
+ };
588
+ const timeout = setTimeout(() => {
589
+ this.ws?.off('message', messageHandler);
590
+ reject(new Error('Subscription timeout'));
591
+ }, 5000);
592
+ this.ws.on('message', messageHandler);
593
+ this.ws.send(JSON.stringify({
594
+ jsonrpc: '2.0',
595
+ id,
596
+ method,
597
+ params,
598
+ }));
599
+ });
600
+ }
601
+ /**
602
+ * Unsubscribe from subscription
603
+ */
604
+ async unsubscribe(method, subscriptionId) {
605
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
606
+ this.subscriptions.delete(subscriptionId);
607
+ return false;
608
+ }
609
+ const id = this.nextId++;
610
+ return new Promise((resolve, reject) => {
611
+ const messageHandler = (data) => {
612
+ // AUDIT-FIX J-1: Guard against malformed WebSocket messages
613
+ let msg;
614
+ try {
615
+ msg = JSON.parse(data.toString());
616
+ }
617
+ catch {
618
+ return; // skip non-JSON frames
619
+ }
620
+ if (msg.id === id) {
621
+ clearTimeout(timeout);
622
+ this.ws.off('message', messageHandler);
623
+ this.subscriptions.delete(subscriptionId);
624
+ if (msg.error) {
625
+ reject(new Error(msg.error.message));
626
+ }
627
+ else {
628
+ resolve(msg.result);
629
+ }
630
+ }
631
+ };
632
+ const timeout = setTimeout(() => {
633
+ this.ws?.off('message', messageHandler);
634
+ reject(new Error('Unsubscribe timeout'));
635
+ }, 5000);
636
+ this.ws.on('message', messageHandler);
637
+ this.ws.send(JSON.stringify({
638
+ jsonrpc: '2.0',
639
+ id,
640
+ method,
641
+ params: subscriptionId,
642
+ }));
643
+ });
644
+ }
645
+ /**
646
+ * Subscribe to slot updates
647
+ */
648
+ async onSlot(callback) {
649
+ const subId = await this.subscribe('subscribeSlots');
650
+ this.subscriptions.set(subId, (data) => callback(data.slot));
651
+ return subId;
652
+ }
653
+ /**
654
+ * Unsubscribe from slots
655
+ */
656
+ async offSlot(subscriptionId) {
657
+ return this.unsubscribe('unsubscribeSlots', subscriptionId);
658
+ }
659
+ /**
660
+ * Subscribe to block updates
661
+ */
662
+ async onBlock(callback) {
663
+ const subId = await this.subscribe('subscribeBlocks');
664
+ this.subscriptions.set(subId, callback);
665
+ return subId;
666
+ }
667
+ /**
668
+ * Unsubscribe from blocks
669
+ */
670
+ async offBlock(subscriptionId) {
671
+ return this.unsubscribe('unsubscribeBlocks', subscriptionId);
672
+ }
673
+ /**
674
+ * Subscribe to transaction updates
675
+ */
676
+ async onTransaction(callback) {
677
+ const subId = await this.subscribe('subscribeTransactions');
678
+ this.subscriptions.set(subId, callback);
679
+ return subId;
680
+ }
681
+ /**
682
+ * Unsubscribe from transactions
683
+ */
684
+ async offTransaction(subscriptionId) {
685
+ return this.unsubscribe('unsubscribeTransactions', subscriptionId);
686
+ }
687
+ /**
688
+ * Subscribe to account changes
689
+ */
690
+ async onAccountChange(pubkey, callback) {
691
+ const subId = await this.subscribe('subscribeAccount', pubkey.toBase58());
692
+ this.subscriptions.set(subId, callback);
693
+ return subId;
694
+ }
695
+ /**
696
+ * Unsubscribe from account changes
697
+ */
698
+ async offAccountChange(subscriptionId) {
699
+ return this.unsubscribe('unsubscribeAccount', subscriptionId);
700
+ }
701
+ /**
702
+ * Subscribe to contract logs
703
+ */
704
+ async onLogs(callback, contractId) {
705
+ const params = contractId ? contractId.toBase58() : null;
706
+ const subId = await this.subscribe('subscribeLogs', params);
707
+ this.subscriptions.set(subId, callback);
708
+ return subId;
709
+ }
710
+ /**
711
+ * Unsubscribe from logs
712
+ */
713
+ async offLogs(subscriptionId) {
714
+ return this.unsubscribe('unsubscribeLogs', subscriptionId);
715
+ }
716
+ /**
717
+ * Subscribe to program updates
718
+ */
719
+ async onProgramUpdates(callback) {
720
+ const subId = await this.subscribe('subscribeProgramUpdates');
721
+ this.subscriptions.set(subId, callback);
722
+ return subId;
723
+ }
724
+ /**
725
+ * Unsubscribe from program updates
726
+ */
727
+ async offProgramUpdates(subscriptionId) {
728
+ return this.unsubscribe('unsubscribeProgramUpdates', subscriptionId);
729
+ }
730
+ /**
731
+ * Subscribe to program calls
732
+ */
733
+ async onProgramCalls(callback, programId) {
734
+ const params = programId ? programId.toBase58() : null;
735
+ const subId = await this.subscribe('subscribeProgramCalls', params);
736
+ this.subscriptions.set(subId, callback);
737
+ return subId;
738
+ }
739
+ /**
740
+ * Unsubscribe from program calls
741
+ */
742
+ async offProgramCalls(subscriptionId) {
743
+ return this.unsubscribe('unsubscribeProgramCalls', subscriptionId);
744
+ }
745
+ /**
746
+ * Subscribe to NFT mints
747
+ */
748
+ async onNftMints(callback, collectionId) {
749
+ const params = collectionId ? collectionId.toBase58() : null;
750
+ const subId = await this.subscribe('subscribeNftMints', params);
751
+ this.subscriptions.set(subId, callback);
752
+ return subId;
753
+ }
754
+ /**
755
+ * Unsubscribe from NFT mints
756
+ */
757
+ async offNftMints(subscriptionId) {
758
+ return this.unsubscribe('unsubscribeNftMints', subscriptionId);
759
+ }
760
+ /**
761
+ * Subscribe to NFT transfers
762
+ */
763
+ async onNftTransfers(callback, collectionId) {
764
+ const params = collectionId ? collectionId.toBase58() : null;
765
+ const subId = await this.subscribe('subscribeNftTransfers', params);
766
+ this.subscriptions.set(subId, callback);
767
+ return subId;
768
+ }
769
+ /**
770
+ * Unsubscribe from NFT transfers
771
+ */
772
+ async offNftTransfers(subscriptionId) {
773
+ return this.unsubscribe('unsubscribeNftTransfers', subscriptionId);
774
+ }
775
+ /**
776
+ * Subscribe to marketplace listings
777
+ */
778
+ async onMarketListings(callback) {
779
+ const subId = await this.subscribe('subscribeMarketListings');
780
+ this.subscriptions.set(subId, callback);
781
+ return subId;
782
+ }
783
+ /**
784
+ * Unsubscribe from marketplace listings
785
+ */
786
+ async offMarketListings(subscriptionId) {
787
+ return this.unsubscribe('unsubscribeMarketListings', subscriptionId);
788
+ }
789
+ /**
790
+ * Subscribe to marketplace sales
791
+ */
792
+ async onMarketSales(callback) {
793
+ const subId = await this.subscribe('subscribeMarketSales');
794
+ this.subscriptions.set(subId, callback);
795
+ return subId;
796
+ }
797
+ /**
798
+ * Unsubscribe from marketplace sales
799
+ */
800
+ async offMarketSales(subscriptionId) {
801
+ return this.unsubscribe('unsubscribeMarketSales', subscriptionId);
802
+ }
803
+ /**
804
+ * Close connection
805
+ */
806
+ close() {
807
+ if (this.ws) {
808
+ this.ws.close();
809
+ this.ws = undefined;
810
+ }
811
+ this.subscriptions.clear();
812
+ }
813
+ }