@adelos/sdk 0.1.2 → 0.1.3

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.mjs CHANGED
@@ -1,41 +1,36 @@
1
1
  // src/index.ts
2
2
  import {
3
- Connection as Connection3,
4
- PublicKey as PublicKey5,
3
+ Connection as Connection2,
4
+ PublicKey as PublicKey4,
5
5
  SystemProgram,
6
- Transaction as Transaction2,
7
- TransactionInstruction as TransactionInstruction2,
6
+ Transaction,
7
+ TransactionInstruction,
8
8
  TransactionMessage,
9
9
  VersionedTransaction
10
10
  } from "@solana/web3.js";
11
11
 
12
12
  // src/constants.ts
13
13
  import { PublicKey, clusterApiUrl } from "@solana/web3.js";
14
- var PROGRAM_IDS = {
15
- devnet: new PublicKey("7T1UxHJ6psKiQheKZXxANu6mhgsmgaX55eNKZZL5u4Rp"),
16
- "mainnet-beta": new PublicKey("7T1UxHJ6psKiQheKZXxANu6mhgsmgaX55eNKZZL5u4Rp"),
17
- // Update when deployed to mainnet
18
- localnet: new PublicKey("7T1UxHJ6psKiQheKZXxANu6mhgsmgaX55eNKZZL5u4Rp")
19
- };
20
- var RPC_URLS = {
21
- devnet: clusterApiUrl("devnet"),
22
- "mainnet-beta": clusterApiUrl("mainnet-beta"),
23
- localnet: "http://localhost:8899"
14
+ var ADELOS_CONFIG = {
15
+ PROGRAM_ID: new PublicKey("7T1UxHJ6psKiQheKZXxANu6mhgsmgaX55eNKZZL5u4Rp"),
16
+ RPC_URL: clusterApiUrl("devnet"),
17
+ MEMO_PROGRAM_ID: new PublicKey("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"),
18
+ REGISTRY_SEED: "registry",
19
+ MEMO_PREFIX: "ADLSv1:",
20
+ STEALTH_DOMAIN: "adelos:stealth:v1"
24
21
  };
25
- var PROGRAM_ID = PROGRAM_IDS.devnet;
26
- var MEMO_PROGRAM_ID = new PublicKey(
27
- "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"
28
- );
29
- var REGISTRY_SEED = "registry";
30
- var REGISTRY_ACCOUNT_SIZE = 8 + 32 + 32 + 1;
31
- var MEMO_PREFIX = "ADLSv1:";
32
- var STEALTH_DOMAIN = "adelos:stealth:v1";
22
+ var PROGRAM_ID = ADELOS_CONFIG.PROGRAM_ID;
23
+ var RPC_URL = ADELOS_CONFIG.RPC_URL;
24
+ var MEMO_PROGRAM_ID = ADELOS_CONFIG.MEMO_PROGRAM_ID;
25
+ var REGISTRY_SEED = ADELOS_CONFIG.REGISTRY_SEED;
26
+ var MEMO_PREFIX = ADELOS_CONFIG.MEMO_PREFIX;
27
+ var STEALTH_DOMAIN = ADELOS_CONFIG.STEALTH_DOMAIN;
33
28
 
34
29
  // src/utils.ts
35
30
  import { PublicKey as PublicKey2 } from "@solana/web3.js";
36
- function deriveRegistryPda(owner, programId = PROGRAM_ID) {
31
+ function deriveRegistryPda(owner, programId = ADELOS_CONFIG.PROGRAM_ID) {
37
32
  return PublicKey2.findProgramAddressSync(
38
- [Buffer.from(REGISTRY_SEED), owner.toBuffer()],
33
+ [Buffer.from(ADELOS_CONFIG.REGISTRY_SEED), owner.toBuffer()],
39
34
  programId
40
35
  );
41
36
  }
@@ -308,65 +303,51 @@ var IDL = {
308
303
 
309
304
  // src/crypto.ts
310
305
  import { sha256 } from "@noble/hashes/sha256";
306
+ import { sha512 } from "@noble/hashes/sha512";
311
307
  import * as ed from "@noble/ed25519";
308
+ ed.etc.sha512Sync = (...m) => {
309
+ const h = sha512.create();
310
+ m.forEach((msg) => h.update(msg));
311
+ return h.digest();
312
+ };
313
+ var encoder = new TextEncoder();
312
314
  function generateEphemeralKeypair() {
313
315
  const secretKey = ed.utils.randomPrivateKey();
314
- const publicKey = ed.getPublicKey(secretKey);
315
- return { secretKey, publicKey };
316
+ return { secretKey, publicKey: ed.getPublicKey(secretKey) };
316
317
  }
317
- async function computeSharedSecret(ephemeralSk, recipientMetaPubkey) {
318
- const point = ed.ExtendedPoint.fromHex(recipientMetaPubkey);
319
- const scalar = ed.etc.mod(
320
- BigInt("0x" + bytesToHex(ephemeralSk)),
321
- ed.CURVE.n
322
- );
323
- const sharedPoint = point.multiply(scalar);
324
- const sharedBytes = sharedPoint.toRawBytes();
325
- return sha256(sharedBytes);
318
+ function computeSharedSecret(ephemeralSk, recipientMetaPk) {
319
+ const point = ed.ExtendedPoint.fromHex(bytesToHex(recipientMetaPk));
320
+ const scalar = ed.etc.mod(BigInt("0x" + bytesToHex(ephemeralSk)), ed.CURVE.n);
321
+ return sha256(point.multiply(scalar).toRawBytes());
326
322
  }
327
- function deriveStealthPubkey(metaPubkey, sharedSecret) {
328
- const scalarBytes = sha256(
329
- new Uint8Array([...sharedSecret, ...Buffer.from(STEALTH_DOMAIN)])
330
- );
331
- const scalar = ed.etc.mod(
332
- BigInt("0x" + bytesToHex(scalarBytes)),
333
- ed.CURVE.n
334
- );
335
- const scalarTimesG = ed.ExtendedPoint.BASE.multiply(scalar);
336
- const metaPoint = ed.ExtendedPoint.fromHex(metaPubkey);
337
- const stealthPoint = metaPoint.add(scalarTimesG);
323
+ function computeSharedSecretAsRecipient(metaSk, ephemeralPk) {
324
+ const point = ed.ExtendedPoint.fromHex(bytesToHex(ephemeralPk));
325
+ const scalar = ed.etc.mod(BigInt("0x" + bytesToHex(metaSk)), ed.CURVE.n);
326
+ return sha256(point.multiply(scalar).toRawBytes());
327
+ }
328
+ function deriveStealthPubkey(metaPk, sharedSecret) {
329
+ const domain = encoder.encode(ADELOS_CONFIG.STEALTH_DOMAIN);
330
+ const scalarBytes = sha256(new Uint8Array([...sharedSecret, ...domain]));
331
+ const scalar = ed.etc.mod(BigInt("0x" + bytesToHex(scalarBytes)), ed.CURVE.n);
332
+ const metaPoint = ed.ExtendedPoint.fromHex(bytesToHex(metaPk));
333
+ const stealthPoint = metaPoint.add(ed.ExtendedPoint.BASE.multiply(scalar));
338
334
  return stealthPoint.toRawBytes();
339
335
  }
340
336
  function recoverStealthSecretKey(metaSk, sharedSecret) {
341
- const scalarBytes = sha256(
342
- new Uint8Array([...sharedSecret, ...Buffer.from(STEALTH_DOMAIN)])
343
- );
344
- const scalar = ed.etc.mod(
345
- BigInt("0x" + bytesToHex(scalarBytes)),
346
- ed.CURVE.n
347
- );
348
- const metaScalar = ed.etc.mod(
349
- BigInt("0x" + bytesToHex(metaSk)),
350
- ed.CURVE.n
351
- );
337
+ const domain = encoder.encode(ADELOS_CONFIG.STEALTH_DOMAIN);
338
+ const scalarBytes = sha256(new Uint8Array([...sharedSecret, ...domain]));
339
+ const scalar = ed.etc.mod(BigInt("0x" + bytesToHex(scalarBytes)), ed.CURVE.n);
340
+ const metaScalar = ed.etc.mod(BigInt("0x" + bytesToHex(metaSk)), ed.CURVE.n);
352
341
  const stealthScalar = ed.etc.mod(metaScalar + scalar, ed.CURVE.n);
353
342
  const hex = stealthScalar.toString(16).padStart(64, "0");
354
343
  return hexToBytes(hex);
355
344
  }
356
- async function computeSharedSecretAsRecipient(metaSk, ephemeralPubkey) {
357
- const point = ed.ExtendedPoint.fromHex(ephemeralPubkey);
358
- const scalar = ed.etc.mod(BigInt("0x" + bytesToHex(metaSk)), ed.CURVE.n);
359
- const sharedPoint = point.multiply(scalar);
360
- const sharedBytes = sharedPoint.toRawBytes();
361
- return sha256(sharedBytes);
362
- }
363
345
  function generateStealthMemo(ephemeralPubkey) {
364
- const pubkeyHex = bytesToHex(ephemeralPubkey);
365
- return `${MEMO_PREFIX}${pubkeyHex}`;
346
+ return `${ADELOS_CONFIG.MEMO_PREFIX}${bytesToHex(ephemeralPubkey)}`;
366
347
  }
367
348
  function parseStealthMemo(memo) {
368
- if (!memo.startsWith(MEMO_PREFIX)) return null;
369
- const pubkeyHex = memo.slice(MEMO_PREFIX.length);
349
+ if (!memo.startsWith(ADELOS_CONFIG.MEMO_PREFIX)) return null;
350
+ const pubkeyHex = memo.slice(ADELOS_CONFIG.MEMO_PREFIX.length);
370
351
  if (pubkeyHex.length !== 64) return null;
371
352
  try {
372
353
  return hexToBytes(pubkeyHex);
@@ -374,618 +355,115 @@ function parseStealthMemo(memo) {
374
355
  return null;
375
356
  }
376
357
  }
377
- async function generateStealthAddress(recipientMetaPubkey) {
358
+ function generateStealthAddress(recipientMetaPk) {
378
359
  const ephemeralKeypair = generateEphemeralKeypair();
379
- const sharedSecret = await computeSharedSecret(
380
- ephemeralKeypair.secretKey,
381
- recipientMetaPubkey
382
- );
383
- const stealthPubkey = deriveStealthPubkey(recipientMetaPubkey, sharedSecret);
360
+ const sharedSecret = computeSharedSecret(ephemeralKeypair.secretKey, recipientMetaPk);
361
+ const stealthPubkey = deriveStealthPubkey(recipientMetaPk, sharedSecret);
384
362
  const memo = generateStealthMemo(ephemeralKeypair.publicKey);
385
- return {
386
- stealthPubkey,
387
- ephemeralKeypair,
388
- sharedSecret,
389
- memo
390
- };
391
- }
392
- async function isStealthTransactionForMe(metaSk, metaPubkey, ephemeralPubkey, targetAddress) {
393
- const sharedSecret = await computeSharedSecretAsRecipient(
394
- metaSk,
395
- ephemeralPubkey
396
- );
397
- const expectedStealth = deriveStealthPubkey(metaPubkey, sharedSecret);
398
- return bytesToHex(expectedStealth) === bytesToHex(targetAddress);
399
- }
400
-
401
- // src/light.ts
402
- import {
403
- Connection,
404
- PublicKey as PublicKey3,
405
- Transaction,
406
- TransactionInstruction
407
- } from "@solana/web3.js";
408
- var LIGHT_PROGRAM_IDS = {
409
- LIGHT_SYSTEM_PROGRAM: new PublicKey3(
410
- "SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7"
411
- ),
412
- COMPRESSED_TOKEN_PROGRAM: new PublicKey3(
413
- "cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m"
414
- ),
415
- ACCOUNT_COMPRESSION_PROGRAM: new PublicKey3(
416
- "compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq"
417
- )
418
- };
419
- var LightClient = class _LightClient {
420
- constructor(connection, config) {
421
- this.connection = connection;
422
- this.config = config;
423
- }
424
- /**
425
- * Creates a new Light Protocol client
426
- *
427
- * @param rpcUrl - Solana RPC URL with Light Protocol support
428
- * @returns LightClient instance
429
- */
430
- static create(rpcUrl) {
431
- const connection = new Connection(rpcUrl, "confirmed");
432
- return new _LightClient(connection, { rpcUrl });
433
- }
434
- /**
435
- * Gets compressed SOL balance for an address
436
- *
437
- * @param owner - Owner public key
438
- * @returns Compressed SOL balance in lamports
439
- */
440
- async getCompressedSolBalance(owner) {
441
- try {
442
- const response = await fetch(this.config.rpcUrl, {
443
- method: "POST",
444
- headers: { "Content-Type": "application/json" },
445
- body: JSON.stringify({
446
- jsonrpc: "2.0",
447
- id: 1,
448
- method: "getCompressedAccountsByOwner",
449
- params: [owner.toBase58()]
450
- })
451
- });
452
- const data = await response.json();
453
- if (data.error) {
454
- console.warn("Light RPC error:", data.error);
455
- return BigInt(0);
456
- }
457
- const accounts = data.result?.items || [];
458
- let total = BigInt(0);
459
- for (const acc of accounts) {
460
- total += BigInt(acc.lamports || 0);
461
- }
462
- return total;
463
- } catch (error) {
464
- console.warn("Failed to get compressed balance:", error);
465
- return BigInt(0);
466
- }
467
- }
468
- /**
469
- * Gets compressed token balances for an address
470
- *
471
- * @param owner - Owner public key
472
- * @param mint - Optional token mint to filter
473
- * @returns Array of compressed token balances
474
- */
475
- async getCompressedTokenBalances(owner, mint) {
476
- try {
477
- const response = await fetch(this.config.rpcUrl, {
478
- method: "POST",
479
- headers: { "Content-Type": "application/json" },
480
- body: JSON.stringify({
481
- jsonrpc: "2.0",
482
- id: 1,
483
- method: "getCompressedTokenAccountsByOwner",
484
- params: [owner.toBase58(), mint?.toBase58()]
485
- })
486
- });
487
- const data = await response.json();
488
- if (data.error) {
489
- console.warn("Light RPC error:", data.error);
490
- return [];
491
- }
492
- const balancesByMint = /* @__PURE__ */ new Map();
493
- const accounts = data.result?.items || [];
494
- for (const acc of accounts) {
495
- const mintStr = acc.mint;
496
- if (!balancesByMint.has(mintStr)) {
497
- balancesByMint.set(mintStr, {
498
- mint: new PublicKey3(mintStr),
499
- amount: BigInt(0),
500
- accounts: []
501
- });
502
- }
503
- const balance = balancesByMint.get(mintStr);
504
- balance.amount += BigInt(acc.amount || 0);
505
- balance.accounts.push({
506
- hash: acc.hash,
507
- owner: new PublicKey3(acc.owner),
508
- lamports: acc.lamports || 0,
509
- data: new Uint8Array(acc.data || []),
510
- tree: new PublicKey3(acc.tree),
511
- leafIndex: acc.leafIndex
512
- });
513
- }
514
- return Array.from(balancesByMint.values());
515
- } catch (error) {
516
- console.warn("Failed to get compressed token balances:", error);
517
- return [];
518
- }
519
- }
520
- /**
521
- * Creates a compressed SOL transfer instruction
522
- *
523
- * Note: This creates the instruction data structure.
524
- * Actual ZK proof generation requires Light Protocol SDK.
525
- *
526
- * @param from - Sender public key
527
- * @param to - Recipient public key (can be stealth address)
528
- * @param amount - Amount in lamports
529
- * @returns Transaction instruction (placeholder)
530
- */
531
- createCompressedTransferInstruction(from, to, amount) {
532
- const data = Buffer.alloc(72);
533
- data.write("compressed_transfer", 0);
534
- data.writeBigUInt64LE(amount, 32);
535
- return new TransactionInstruction({
536
- keys: [
537
- { pubkey: from, isSigner: true, isWritable: true },
538
- { pubkey: to, isSigner: false, isWritable: true },
539
- {
540
- pubkey: LIGHT_PROGRAM_IDS.LIGHT_SYSTEM_PROGRAM,
541
- isSigner: false,
542
- isWritable: false
543
- }
544
- ],
545
- programId: LIGHT_PROGRAM_IDS.LIGHT_SYSTEM_PROGRAM,
546
- data
547
- });
548
- }
549
- /**
550
- * Compresses SOL from regular account to compressed account
551
- *
552
- * @param owner - Owner public key
553
- * @param amount - Amount in lamports to compress
554
- * @returns Transaction (unsigned)
555
- */
556
- async createCompressSolTransaction(owner, amount) {
557
- const instruction = this.createCompressedTransferInstruction(
558
- owner,
559
- owner,
560
- // Compress to self
561
- amount
562
- );
563
- const transaction = new Transaction().add(instruction);
564
- transaction.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
565
- transaction.feePayer = owner;
566
- return transaction;
567
- }
568
- /**
569
- * Decompresses SOL from compressed account to regular account
570
- *
571
- * @param owner - Owner public key
572
- * @param amount - Amount in lamports to decompress
573
- * @returns Transaction (unsigned)
574
- */
575
- async createDecompressSolTransaction(owner, amount) {
576
- const instruction = this.createCompressedTransferInstruction(
577
- owner,
578
- owner,
579
- amount
580
- );
581
- const transaction = new Transaction().add(instruction);
582
- transaction.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
583
- transaction.feePayer = owner;
584
- return transaction;
585
- }
586
- /**
587
- * Creates a stealth compressed transfer
588
- * Combines stealth addressing with ZK-compression
589
- *
590
- * @param from - Sender public key
591
- * @param stealthPubkey - Derived stealth address for recipient
592
- * @param amount - Amount in lamports
593
- * @param memo - Stealth memo containing ephemeral pubkey
594
- * @returns Transaction (unsigned)
595
- */
596
- async createStealthCompressedTransfer(from, stealthPubkey, amount, memo) {
597
- const stealthAddress = new PublicKey3(stealthPubkey);
598
- const transferIx = this.createCompressedTransferInstruction(
599
- from,
600
- stealthAddress,
601
- amount
602
- );
603
- const memoIx = new TransactionInstruction({
604
- keys: [],
605
- programId: MEMO_PROGRAM_ID,
606
- data: Buffer.from(memo, "utf-8")
607
- });
608
- const transaction = new Transaction().add(transferIx).add(memoIx);
609
- transaction.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
610
- transaction.feePayer = from;
611
- return transaction;
612
- }
613
- };
614
- function createLightClient(rpcUrl) {
615
- return LightClient.create(rpcUrl);
363
+ return { stealthPubkey, ephemeralKeypair, sharedSecret, memo };
616
364
  }
617
365
 
618
366
  // src/indexer.ts
619
- import { Connection as Connection2, PublicKey as PublicKey4 } from "@solana/web3.js";
367
+ import { Connection } from "@solana/web3.js";
620
368
  var AdelosIndexer = class _AdelosIndexer {
621
369
  constructor(config) {
622
- this.isScanning = false;
623
- this.scanIntervalId = null;
624
- this.config = config;
625
- this.connection = new Connection2(config.rpcUrl, "confirmed");
370
+ this.connection = new Connection(config.rpcUrl, "confirmed");
371
+ this.heliusApiKey = config.heliusApiKey;
626
372
  }
627
- /**
628
- * Creates an indexer instance
629
- */
630
373
  static create(config) {
631
374
  return new _AdelosIndexer(config);
632
375
  }
633
- /**
634
- * Scans recent transactions for stealth transfers to a recipient
635
- *
636
- * IMPORTANT: We scan the Memo Program, NOT the metaPubkey!
637
- * The metaPubkey should never appear in transactions - that's the whole point of stealth addresses.
638
- * We scan memo program for ADLSv1: prefix, then use Trial Decryption to check if it's for us.
639
- *
640
- * @param metaSk - Recipient's meta secret key
641
- * @param metaPubkey - Recipient's meta public key
642
- * @param limit - Number of transactions to scan
643
- * @returns Array of stealth transactions for this recipient
644
- */
645
- async scanForStealthTransfers(metaSk, metaPubkey, limit = 100) {
646
- const results = [];
647
- try {
648
- const signatures = await this.connection.getSignaturesForAddress(
649
- MEMO_PROGRAM_ID,
650
- { limit }
651
- );
652
- for (const sigInfo of signatures) {
653
- const tx = await this.connection.getParsedTransaction(
654
- sigInfo.signature,
655
- { maxSupportedTransactionVersion: 0 }
656
- );
657
- if (!tx) continue;
658
- const memo = this.extractMemo(tx);
659
- if (!memo?.startsWith(MEMO_PREFIX)) continue;
660
- const stealthTx = await this.parseStealthTransaction(
661
- tx,
662
- sigInfo.signature,
663
- metaSk,
664
- metaPubkey
665
- );
666
- if (stealthTx) {
667
- results.push(stealthTx);
668
- }
669
- }
670
- } catch (error) {
671
- console.error("Error scanning transactions:", error);
672
- }
673
- return results;
674
- }
675
- /**
676
- * Scans all transactions with ADLSv1 memo prefix
677
- *
678
- * @param metaSk - Recipient's meta secret key
679
- * @param metaPubkey - Recipient's meta public key
680
- * @param since - Scan transactions after this signature
681
- * @returns Array of stealth transactions
682
- */
683
- async scanByMemoPrefix(metaSk, metaPubkey, since) {
376
+ /** Scan for stealth transfers to this recipient */
377
+ async scanForStealthTransfers(metaSk, metaPk, limit = 100) {
378
+ const sigs = await this.connection.getSignaturesForAddress(
379
+ ADELOS_CONFIG.MEMO_PROGRAM_ID,
380
+ { limit }
381
+ );
684
382
  const results = [];
685
- try {
686
- const signatures = await this.connection.getSignaturesForAddress(
687
- MEMO_PROGRAM_ID,
688
- { limit: 1e3, until: since }
689
- );
690
- for (const sigInfo of signatures) {
691
- const tx = await this.connection.getParsedTransaction(
692
- sigInfo.signature,
693
- { maxSupportedTransactionVersion: 0 }
694
- );
695
- if (!tx) continue;
696
- const memo = this.extractMemo(tx);
697
- if (!memo?.startsWith(MEMO_PREFIX)) continue;
698
- const stealthTx = await this.parseStealthTransaction(
699
- tx,
700
- sigInfo.signature,
701
- metaSk,
702
- metaPubkey
703
- );
704
- if (stealthTx) {
705
- results.push(stealthTx);
706
- }
383
+ for (const s of sigs) {
384
+ const tx = await this.connection.getParsedTransaction(s.signature, {
385
+ maxSupportedTransactionVersion: 0
386
+ });
387
+ if (!tx) continue;
388
+ const memo = this.extractMemo(tx);
389
+ if (!memo?.startsWith(ADELOS_CONFIG.MEMO_PREFIX)) continue;
390
+ const detected = this.attemptDecryption(tx, metaSk, metaPk);
391
+ if (detected) {
392
+ results.push({
393
+ signature: s.signature,
394
+ blockTime: tx.blockTime ?? null,
395
+ stealthAddress: detected.stealthAddress,
396
+ amount: detected.amount
397
+ });
707
398
  }
708
- } catch (error) {
709
- console.error("Error scanning by memo:", error);
710
399
  }
711
400
  return results;
712
401
  }
713
- /**
714
- * Extracts memo from a parsed transaction
715
- */
716
- extractMemo(tx) {
717
- const memoProgram = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr";
718
- for (const ix of tx.transaction.message.instructions) {
719
- if ("programId" in ix && ix.programId.toBase58() === memoProgram) {
720
- if ("parsed" in ix && typeof ix.parsed === "string") {
721
- return ix.parsed;
722
- }
723
- }
724
- }
725
- if (tx.meta?.innerInstructions) {
726
- for (const inner of tx.meta.innerInstructions) {
727
- for (const ix of inner.instructions) {
728
- if ("programId" in ix && ix.programId.toBase58() === memoProgram) {
729
- if ("parsed" in ix && typeof ix.parsed === "string") {
730
- return ix.parsed;
731
- }
732
- }
733
- }
734
- }
735
- }
736
- return null;
737
- }
738
- /**
739
- * Parses a transaction to extract stealth transfer info using Trial Decryption
740
- *
741
- * Trial Decryption: First compute expected stealth address, then find if it exists in tx accounts
742
- */
743
- async parseStealthTransaction(tx, signature, metaSk, metaPubkey) {
402
+ /** Trial Decryption: Check if transaction is for this recipient */
403
+ attemptDecryption(tx, metaSk, metaPk) {
744
404
  const memo = this.extractMemo(tx);
745
- if (!memo) return null;
746
- const ephemeralPubkey = parseStealthMemo(memo);
747
- if (!ephemeralPubkey) return null;
748
- let expectedStealthHex;
749
- try {
750
- const sharedSecret = await computeSharedSecretAsRecipient(
751
- metaSk,
752
- ephemeralPubkey
753
- );
754
- const expectedStealth = deriveStealthPubkey(metaPubkey, sharedSecret);
755
- expectedStealthHex = bytesToHex(expectedStealth);
756
- } catch (error) {
757
- console.warn("Error computing expected stealth:", error);
758
- return null;
759
- }
405
+ const ephemeralPk = parseStealthMemo(memo || "");
406
+ if (!ephemeralPk) return null;
407
+ const secret = computeSharedSecretAsRecipient(metaSk, ephemeralPk);
408
+ const expectedStealthHex = bytesToHex(deriveStealthPubkey(metaPk, secret));
760
409
  const accounts = tx.transaction.message.accountKeys;
410
+ const idx = accounts.findIndex(
411
+ (a) => bytesToHex(a.pubkey.toBytes()) === expectedStealthHex
412
+ );
413
+ if (idx === -1) return null;
761
414
  const preBalances = tx.meta?.preBalances || [];
762
415
  const postBalances = tx.meta?.postBalances || [];
763
- let stealthIndex = -1;
764
- for (let i = 0; i < accounts.length; i++) {
765
- const accountHex = bytesToHex(accounts[i].pubkey.toBytes());
766
- if (accountHex === expectedStealthHex) {
767
- stealthIndex = i;
768
- break;
769
- }
770
- }
771
- if (stealthIndex === -1) return null;
772
- const stealthAddress = accounts[stealthIndex].pubkey;
773
- const change = (postBalances[stealthIndex] || 0) - (preBalances[stealthIndex] || 0);
774
- const amount = BigInt(Math.max(change, 0));
775
- let sender = null;
776
- for (let i = 0; i < accounts.length; i++) {
777
- const accChange = (postBalances[i] || 0) - (preBalances[i] || 0);
778
- if (accChange < 0) {
779
- sender = accounts[i].pubkey;
780
- break;
781
- }
782
- }
783
- return {
784
- signature,
785
- blockTime: tx.blockTime ?? null,
786
- slot: tx.slot,
787
- sender,
788
- stealthAddress,
789
- amount,
790
- ephemeralPubkey,
791
- isForMe: true
792
- // If we reach here, it's definitely for us
793
- };
794
- }
795
- /**
796
- * Starts continuous scanning for stealth transfers
797
- *
798
- * @param metaSk - Recipient's meta secret key
799
- * @param metaPubkey - Recipient's meta public key
800
- */
801
- startScanning(metaSk, metaPubkey) {
802
- if (this.isScanning) return;
803
- this.isScanning = true;
804
- let lastSignature;
805
- const scanInterval = this.config.scanInterval || 1e4;
806
- this.scanIntervalId = setInterval(async () => {
807
- try {
808
- const txs = await this.scanByMemoPrefix(
809
- metaSk,
810
- metaPubkey,
811
- lastSignature
812
- );
813
- for (const tx of txs) {
814
- if (tx.isForMe && this.onTransaction) {
815
- this.onTransaction(tx);
816
- }
817
- }
818
- if (txs.length > 0) {
819
- lastSignature = txs[0].signature;
820
- }
821
- } catch (error) {
822
- console.error("Scan error:", error);
823
- }
824
- }, scanInterval);
825
- }
826
- /**
827
- * Stops continuous scanning
828
- */
829
- stopScanning() {
830
- this.isScanning = false;
831
- if (this.scanIntervalId) {
832
- clearInterval(this.scanIntervalId);
833
- this.scanIntervalId = null;
834
- }
835
- }
836
- /**
837
- * Processes a Helius webhook payload
838
- *
839
- * @param payload - Webhook payload from Helius
840
- * @param metaSk - Recipient's meta secret key
841
- * @param metaPubkey - Recipient's meta public key
842
- * @returns Stealth transaction if detected and for this recipient
843
- */
844
- async processWebhook(payload, metaSk, metaPubkey) {
845
- let memo = null;
846
- const memoProgramStr = MEMO_PROGRAM_ID.toBase58();
847
- for (const ix of payload.instructions) {
848
- if (ix.programId === memoProgramStr) {
849
- memo = Buffer.from(ix.data, "base64").toString("utf-8");
850
- break;
851
- }
852
- }
853
- if (!memo?.startsWith(MEMO_PREFIX)) return null;
854
- const ephemeralPubkey = parseStealthMemo(memo);
855
- if (!ephemeralPubkey) return null;
856
- let stealthAddress = null;
857
- let amount = BigInt(0);
858
- for (const acc of payload.accountData) {
859
- if (acc.nativeBalanceChange > 0) {
860
- stealthAddress = new PublicKey4(acc.account);
861
- amount = BigInt(acc.nativeBalanceChange);
862
- break;
863
- }
864
- }
865
- if (!stealthAddress) return null;
866
- let isForMe = false;
867
- try {
868
- const sharedSecret = await computeSharedSecretAsRecipient(
869
- metaSk,
870
- ephemeralPubkey
871
- );
872
- const expectedStealth = deriveStealthPubkey(metaPubkey, sharedSecret);
873
- isForMe = bytesToHex(expectedStealth) === bytesToHex(stealthAddress.toBytes());
874
- } catch (error) {
875
- console.warn("Error checking webhook tx:", error);
876
- }
877
- if (!isForMe) return null;
416
+ const change = (postBalances[idx] || 0) - (preBalances[idx] || 0);
878
417
  return {
879
- signature: payload.signature,
880
- blockTime: payload.timestamp,
881
- slot: payload.slot,
882
- sender: null,
883
- stealthAddress,
884
- amount,
885
- ephemeralPubkey,
886
- isForMe: true
418
+ stealthAddress: accounts[idx].pubkey,
419
+ amount: BigInt(Math.max(change, 0))
887
420
  };
888
421
  }
889
- /**
890
- * Sets up Helius webhook for real-time notifications
891
- *
892
- * @param webhookUrl - Your server's webhook endpoint
893
- * @returns Webhook ID
894
- */
895
- async setupHeliusWebhook(webhookUrl) {
896
- if (!this.config.heliusApiKey) {
897
- console.warn("Helius API key not configured");
898
- return null;
899
- }
900
- try {
901
- const response = await fetch(
902
- `https://api.helius.xyz/v0/webhooks?api-key=${this.config.heliusApiKey}`,
903
- {
904
- method: "POST",
905
- headers: { "Content-Type": "application/json" },
906
- body: JSON.stringify({
907
- webhookURL: webhookUrl,
908
- transactionTypes: ["TRANSFER"],
909
- accountAddresses: [
910
- MEMO_PROGRAM_ID.toBase58()
911
- ],
912
- webhookType: "enhanced"
913
- })
914
- }
915
- );
916
- const data = await response.json();
917
- return data.webhookID || null;
918
- } catch (error) {
919
- console.error("Failed to setup Helius webhook:", error);
920
- return null;
921
- }
422
+ extractMemo(tx) {
423
+ const ix = tx.transaction.message.instructions.find(
424
+ (i) => i.programId.equals(ADELOS_CONFIG.MEMO_PROGRAM_ID)
425
+ );
426
+ return ix?.parsed || null;
922
427
  }
923
428
  };
924
429
  function createIndexer(config) {
925
- return AdelosIndexer.create(config);
430
+ return new AdelosIndexer(config);
926
431
  }
927
432
 
928
433
  // src/index.ts
929
434
  var AdelosSDK = class {
930
435
  constructor(options = {}) {
931
- this.cluster = options.cluster ?? "devnet";
932
- const rpcUrl = options.rpcUrl ?? RPC_URLS[this.cluster];
933
- this.connection = new Connection3(rpcUrl, "confirmed");
934
- this.programId = PROGRAM_IDS[this.cluster];
935
- this.heliusApiKey = options.heliusApiKey;
436
+ const rpcUrl = options.rpcUrl ?? ADELOS_CONFIG.RPC_URL;
437
+ this.connection = new Connection2(rpcUrl, "confirmed");
438
+ this.programId = ADELOS_CONFIG.PROGRAM_ID;
936
439
  }
937
- /**
938
- * Derives the registry PDA for an owner
939
- */
440
+ // --- Registry Operations ---
940
441
  deriveRegistryAddress(owner) {
941
442
  return deriveRegistryPda(owner, this.programId);
942
443
  }
943
- /**
944
- * Fetches a registry account by owner
945
- */
946
444
  async getRegistry(owner) {
947
445
  const [address] = this.deriveRegistryAddress(owner);
948
446
  const accountInfo = await this.connection.getAccountInfo(address);
949
447
  if (!accountInfo) {
950
- return {
951
- address,
952
- exists: false,
953
- account: null
954
- };
448
+ return { address, exists: false, account: null };
955
449
  }
956
450
  const data = accountInfo.data.slice(8);
957
451
  const account = {
958
- owner: new PublicKey5(data.slice(0, 32)),
452
+ owner: new PublicKey4(data.slice(0, 32)),
959
453
  metaPubkey: new Uint8Array(data.slice(32, 64)),
960
454
  bump: data[64]
961
455
  };
962
456
  return { address, exists: true, account };
963
457
  }
964
- /**
965
- * Gets the meta pubkey for an owner
966
- * @returns The meta pubkey as Uint8Array, or null if not registered
967
- */
968
458
  async getMetaPubkey(owner) {
969
459
  const registry = await this.getRegistry(owner);
970
460
  return registry.exists ? registry.account.metaPubkey : null;
971
461
  }
972
- /**
973
- * Gets the meta pubkey as hex string
974
- */
975
- async getMetaPubkeyHex(owner) {
976
- const metaPubkey = await this.getMetaPubkey(owner);
977
- return metaPubkey ? bytesToHex(metaPubkey) : null;
978
- }
979
- /**
980
- * Checks if an owner has a registered identity
981
- */
982
462
  async isRegistered(owner) {
983
463
  const registry = await this.getRegistry(owner);
984
464
  return registry.exists;
985
465
  }
986
- /**
987
- * Creates a register identity instruction
988
- */
466
+ // --- Instruction Builders ---
989
467
  createRegisterInstruction(owner, metaPubkey) {
990
468
  if (!isValidMetaPubkey(metaPubkey)) {
991
469
  throw new Error("Invalid meta pubkey: must be 32 bytes and not all zeros");
@@ -995,7 +473,7 @@ var AdelosSDK = class {
995
473
  Buffer.from(getDiscriminator("register_identity")),
996
474
  Buffer.from(metaPubkey)
997
475
  ]);
998
- return new TransactionInstruction2({
476
+ return new TransactionInstruction({
999
477
  keys: [
1000
478
  { pubkey: owner, isSigner: true, isWritable: true },
1001
479
  { pubkey: registryPda, isSigner: false, isWritable: true },
@@ -1005,9 +483,6 @@ var AdelosSDK = class {
1005
483
  data
1006
484
  });
1007
485
  }
1008
- /**
1009
- * Creates an update identity instruction
1010
- */
1011
486
  createUpdateInstruction(owner, newMetaPubkey) {
1012
487
  if (!isValidMetaPubkey(newMetaPubkey)) {
1013
488
  throw new Error("Invalid meta pubkey: must be 32 bytes and not all zeros");
@@ -1017,22 +492,20 @@ var AdelosSDK = class {
1017
492
  Buffer.from(getDiscriminator("update_identity")),
1018
493
  Buffer.from(newMetaPubkey)
1019
494
  ]);
1020
- return new TransactionInstruction2({
495
+ return new TransactionInstruction({
1021
496
  keys: [
1022
- { pubkey: owner, isSigner: true, isWritable: false },
1023
- { pubkey: registryPda, isSigner: false, isWritable: true }
497
+ { pubkey: owner, isSigner: true, isWritable: true },
498
+ { pubkey: registryPda, isSigner: false, isWritable: true },
499
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }
1024
500
  ],
1025
501
  programId: this.programId,
1026
502
  data
1027
503
  });
1028
504
  }
1029
- /**
1030
- * Creates a close registry instruction
1031
- */
1032
505
  createCloseInstruction(owner) {
1033
506
  const [registryPda] = this.deriveRegistryAddress(owner);
1034
507
  const data = Buffer.from(getDiscriminator("close_registry"));
1035
- return new TransactionInstruction2({
508
+ return new TransactionInstruction({
1036
509
  keys: [
1037
510
  { pubkey: owner, isSigner: true, isWritable: true },
1038
511
  { pubkey: registryPda, isSigner: false, isWritable: true }
@@ -1041,116 +514,56 @@ var AdelosSDK = class {
1041
514
  data
1042
515
  });
1043
516
  }
1044
- /**
1045
- * Creates a register transaction (unsigned) - Legacy format
1046
- */
1047
- async createRegisterTransaction(owner, metaPubkey) {
1048
- const instruction = this.createRegisterInstruction(owner, metaPubkey);
1049
- const transaction = new Transaction2().add(instruction);
1050
- transaction.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
1051
- transaction.feePayer = owner;
1052
- return transaction;
1053
- }
1054
- /**
1055
- * Creates a register transaction (unsigned) - Versioned format (v0)
1056
- * Recommended for modern Solana applications
1057
- */
1058
- async createRegisterTransactionV0(owner, metaPubkey) {
1059
- const instruction = this.createRegisterInstruction(owner, metaPubkey);
1060
- const { blockhash } = await this.connection.getLatestBlockhash();
1061
- const messageV0 = new TransactionMessage({
1062
- payerKey: owner,
1063
- recentBlockhash: blockhash,
1064
- instructions: [instruction]
1065
- }).compileToV0Message();
1066
- return new VersionedTransaction(messageV0);
1067
- }
1068
- /**
1069
- * Creates an update transaction (unsigned) - Legacy format
1070
- */
1071
- async createUpdateTransaction(owner, newMetaPubkey) {
1072
- const instruction = this.createUpdateInstruction(owner, newMetaPubkey);
1073
- const transaction = new Transaction2().add(instruction);
1074
- transaction.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
1075
- transaction.feePayer = owner;
1076
- return transaction;
1077
- }
1078
- /**
1079
- * Creates an update transaction (unsigned) - Versioned format (v0)
1080
- */
1081
- async createUpdateTransactionV0(owner, newMetaPubkey) {
1082
- const instruction = this.createUpdateInstruction(owner, newMetaPubkey);
517
+ // --- Transaction Builders ---
518
+ async buildTransaction(payer, instructions, version = "v0") {
1083
519
  const { blockhash } = await this.connection.getLatestBlockhash();
1084
- const messageV0 = new TransactionMessage({
1085
- payerKey: owner,
520
+ if (version === "legacy") {
521
+ const tx = new Transaction().add(...instructions);
522
+ tx.recentBlockhash = blockhash;
523
+ tx.feePayer = payer;
524
+ return tx;
525
+ }
526
+ const message = new TransactionMessage({
527
+ payerKey: payer,
1086
528
  recentBlockhash: blockhash,
1087
- instructions: [instruction]
529
+ instructions
1088
530
  }).compileToV0Message();
1089
- return new VersionedTransaction(messageV0);
531
+ return new VersionedTransaction(message);
1090
532
  }
1091
- /**
1092
- * Creates a close transaction (unsigned) - Legacy format
1093
- */
1094
- async createCloseTransaction(owner) {
1095
- const instruction = this.createCloseInstruction(owner);
1096
- const transaction = new Transaction2().add(instruction);
1097
- transaction.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
1098
- transaction.feePayer = owner;
1099
- return transaction;
533
+ async createRegisterTransaction(owner, metaPubkey) {
534
+ const ix = this.createRegisterInstruction(owner, metaPubkey);
535
+ return this.buildTransaction(owner, [ix], "legacy");
1100
536
  }
1101
- /**
1102
- * Creates a close transaction (unsigned) - Versioned format (v0)
1103
- */
1104
- async createCloseTransactionV0(owner) {
1105
- const instruction = this.createCloseInstruction(owner);
1106
- const { blockhash } = await this.connection.getLatestBlockhash();
1107
- const messageV0 = new TransactionMessage({
1108
- payerKey: owner,
1109
- recentBlockhash: blockhash,
1110
- instructions: [instruction]
1111
- }).compileToV0Message();
1112
- return new VersionedTransaction(messageV0);
537
+ async createRegisterTransactionV0(owner, metaPubkey) {
538
+ const ix = this.createRegisterInstruction(owner, metaPubkey);
539
+ return this.buildTransaction(owner, [ix], "v0");
1113
540
  }
1114
- /**
1115
- * Sends a signed legacy transaction and confirms it
1116
- */
1117
- async sendAndConfirm(signedTransaction) {
1118
- const signature = await this.connection.sendRawTransaction(
1119
- signedTransaction.serialize()
1120
- );
1121
- await this.connection.confirmTransaction(signature, "confirmed");
1122
- return signature;
541
+ async sendAndConfirm(signedTx) {
542
+ const sig = await this.connection.sendRawTransaction(signedTx.serialize());
543
+ await this.connection.confirmTransaction(sig, "confirmed");
544
+ return sig;
1123
545
  }
1124
- /**
1125
- * Sends a signed versioned transaction and confirms it
1126
- */
1127
- async sendAndConfirmV0(signedTransaction) {
1128
- const signature = await this.connection.sendRawTransaction(
1129
- signedTransaction.serialize()
1130
- );
1131
- await this.connection.confirmTransaction(signature, "confirmed");
1132
- return signature;
546
+ async sendAndConfirmV0(signedTx) {
547
+ const sig = await this.connection.sendRawTransaction(signedTx.serialize());
548
+ await this.connection.confirmTransaction(sig, "confirmed");
549
+ return sig;
1133
550
  }
1134
551
  };
1135
552
  export {
553
+ ADELOS_CONFIG,
1136
554
  AdelosIndexer,
1137
555
  AdelosSDK,
1138
556
  IDL,
1139
- LIGHT_PROGRAM_IDS,
1140
- LightClient,
1141
557
  MEMO_PREFIX,
1142
558
  MEMO_PROGRAM_ID,
1143
559
  PROGRAM_ID,
1144
- PROGRAM_IDS,
1145
- REGISTRY_ACCOUNT_SIZE,
1146
560
  REGISTRY_SEED,
1147
- RPC_URLS,
561
+ RPC_URL,
1148
562
  STEALTH_DOMAIN,
1149
563
  bytesToHex,
1150
564
  computeSharedSecret,
1151
565
  computeSharedSecretAsRecipient,
1152
566
  createIndexer,
1153
- createLightClient,
1154
567
  deriveRegistryPda,
1155
568
  deriveStealthPubkey,
1156
569
  generateEphemeralKeypair,
@@ -1158,7 +571,6 @@ export {
1158
571
  generateStealthMemo,
1159
572
  getDiscriminator,
1160
573
  hexToBytes,
1161
- isStealthTransactionForMe,
1162
574
  isValidMetaPubkey,
1163
575
  parseStealthMemo,
1164
576
  recoverStealthSecretKey