@adelos/sdk 0.1.2 → 0.1.5

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,52 @@ 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 secretKey = metaSk.length === 64 ? metaSk.slice(0, 32) : metaSk;
325
+ const point = ed.ExtendedPoint.fromHex(bytesToHex(ephemeralPk));
326
+ const scalar = ed.etc.mod(BigInt("0x" + bytesToHex(secretKey)), ed.CURVE.n);
327
+ return sha256(point.multiply(scalar).toRawBytes());
328
+ }
329
+ function deriveStealthPubkey(metaPk, sharedSecret) {
330
+ const domain = encoder.encode(ADELOS_CONFIG.STEALTH_DOMAIN);
331
+ const scalarBytes = sha256(new Uint8Array([...sharedSecret, ...domain]));
332
+ const scalar = ed.etc.mod(BigInt("0x" + bytesToHex(scalarBytes)), ed.CURVE.n);
333
+ const metaPoint = ed.ExtendedPoint.fromHex(bytesToHex(metaPk));
334
+ const stealthPoint = metaPoint.add(ed.ExtendedPoint.BASE.multiply(scalar));
338
335
  return stealthPoint.toRawBytes();
339
336
  }
340
337
  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
- );
338
+ const domain = encoder.encode(ADELOS_CONFIG.STEALTH_DOMAIN);
339
+ const scalarBytes = sha256(new Uint8Array([...sharedSecret, ...domain]));
340
+ const scalar = ed.etc.mod(BigInt("0x" + bytesToHex(scalarBytes)), ed.CURVE.n);
341
+ const metaScalar = ed.etc.mod(BigInt("0x" + bytesToHex(metaSk)), ed.CURVE.n);
352
342
  const stealthScalar = ed.etc.mod(metaScalar + scalar, ed.CURVE.n);
353
343
  const hex = stealthScalar.toString(16).padStart(64, "0");
354
344
  return hexToBytes(hex);
355
345
  }
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
346
  function generateStealthMemo(ephemeralPubkey) {
364
- const pubkeyHex = bytesToHex(ephemeralPubkey);
365
- return `${MEMO_PREFIX}${pubkeyHex}`;
347
+ return `${ADELOS_CONFIG.MEMO_PREFIX}${bytesToHex(ephemeralPubkey)}`;
366
348
  }
367
349
  function parseStealthMemo(memo) {
368
- if (!memo.startsWith(MEMO_PREFIX)) return null;
369
- const pubkeyHex = memo.slice(MEMO_PREFIX.length);
350
+ if (!memo.startsWith(ADELOS_CONFIG.MEMO_PREFIX)) return null;
351
+ const pubkeyHex = memo.slice(ADELOS_CONFIG.MEMO_PREFIX.length);
370
352
  if (pubkeyHex.length !== 64) return null;
371
353
  try {
372
354
  return hexToBytes(pubkeyHex);
@@ -374,618 +356,115 @@ function parseStealthMemo(memo) {
374
356
  return null;
375
357
  }
376
358
  }
377
- async function generateStealthAddress(recipientMetaPubkey) {
359
+ function generateStealthAddress(recipientMetaPk) {
378
360
  const ephemeralKeypair = generateEphemeralKeypair();
379
- const sharedSecret = await computeSharedSecret(
380
- ephemeralKeypair.secretKey,
381
- recipientMetaPubkey
382
- );
383
- const stealthPubkey = deriveStealthPubkey(recipientMetaPubkey, sharedSecret);
361
+ const sharedSecret = computeSharedSecret(ephemeralKeypair.secretKey, recipientMetaPk);
362
+ const stealthPubkey = deriveStealthPubkey(recipientMetaPk, sharedSecret);
384
363
  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);
364
+ return { stealthPubkey, ephemeralKeypair, sharedSecret, memo };
616
365
  }
617
366
 
618
367
  // src/indexer.ts
619
- import { Connection as Connection2, PublicKey as PublicKey4 } from "@solana/web3.js";
368
+ import { Connection } from "@solana/web3.js";
620
369
  var AdelosIndexer = class _AdelosIndexer {
621
370
  constructor(config) {
622
- this.isScanning = false;
623
- this.scanIntervalId = null;
624
- this.config = config;
625
- this.connection = new Connection2(config.rpcUrl, "confirmed");
371
+ this.connection = new Connection(config.rpcUrl, "confirmed");
372
+ this.heliusApiKey = config.heliusApiKey;
626
373
  }
627
- /**
628
- * Creates an indexer instance
629
- */
630
374
  static create(config) {
631
375
  return new _AdelosIndexer(config);
632
376
  }
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) {
377
+ /** Scan for stealth transfers to this recipient */
378
+ async scanForStealthTransfers(metaSk, metaPk, limit = 100) {
379
+ const sigs = await this.connection.getSignaturesForAddress(
380
+ ADELOS_CONFIG.MEMO_PROGRAM_ID,
381
+ { limit }
382
+ );
684
383
  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
- }
384
+ for (const s of sigs) {
385
+ const tx = await this.connection.getParsedTransaction(s.signature, {
386
+ maxSupportedTransactionVersion: 0
387
+ });
388
+ if (!tx) continue;
389
+ const memo = this.extractMemo(tx);
390
+ if (!memo?.startsWith(ADELOS_CONFIG.MEMO_PREFIX)) continue;
391
+ const detected = this.attemptDecryption(tx, metaSk, metaPk);
392
+ if (detected) {
393
+ results.push({
394
+ signature: s.signature,
395
+ blockTime: tx.blockTime ?? null,
396
+ stealthAddress: detected.stealthAddress,
397
+ amount: detected.amount
398
+ });
707
399
  }
708
- } catch (error) {
709
- console.error("Error scanning by memo:", error);
710
400
  }
711
401
  return results;
712
402
  }
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) {
403
+ /** Trial Decryption: Check if transaction is for this recipient */
404
+ attemptDecryption(tx, metaSk, metaPk) {
744
405
  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
- }
406
+ const ephemeralPk = parseStealthMemo(memo || "");
407
+ if (!ephemeralPk) return null;
408
+ const secret = computeSharedSecretAsRecipient(metaSk, ephemeralPk);
409
+ const expectedStealthHex = bytesToHex(deriveStealthPubkey(metaPk, secret));
760
410
  const accounts = tx.transaction.message.accountKeys;
411
+ const idx = accounts.findIndex(
412
+ (a) => bytesToHex(a.pubkey.toBytes()) === expectedStealthHex
413
+ );
414
+ if (idx === -1) return null;
761
415
  const preBalances = tx.meta?.preBalances || [];
762
416
  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;
417
+ const change = (postBalances[idx] || 0) - (preBalances[idx] || 0);
878
418
  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
419
+ stealthAddress: accounts[idx].pubkey,
420
+ amount: BigInt(Math.max(change, 0))
887
421
  };
888
422
  }
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
- }
423
+ extractMemo(tx) {
424
+ const ix = tx.transaction.message.instructions.find(
425
+ (i) => i.programId.equals(ADELOS_CONFIG.MEMO_PROGRAM_ID)
426
+ );
427
+ return ix?.parsed || null;
922
428
  }
923
429
  };
924
430
  function createIndexer(config) {
925
- return AdelosIndexer.create(config);
431
+ return new AdelosIndexer(config);
926
432
  }
927
433
 
928
434
  // src/index.ts
929
435
  var AdelosSDK = class {
930
436
  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;
437
+ const rpcUrl = options.rpcUrl ?? ADELOS_CONFIG.RPC_URL;
438
+ this.connection = new Connection2(rpcUrl, "confirmed");
439
+ this.programId = ADELOS_CONFIG.PROGRAM_ID;
936
440
  }
937
- /**
938
- * Derives the registry PDA for an owner
939
- */
441
+ // --- Registry Operations ---
940
442
  deriveRegistryAddress(owner) {
941
443
  return deriveRegistryPda(owner, this.programId);
942
444
  }
943
- /**
944
- * Fetches a registry account by owner
945
- */
946
445
  async getRegistry(owner) {
947
446
  const [address] = this.deriveRegistryAddress(owner);
948
447
  const accountInfo = await this.connection.getAccountInfo(address);
949
448
  if (!accountInfo) {
950
- return {
951
- address,
952
- exists: false,
953
- account: null
954
- };
449
+ return { address, exists: false, account: null };
955
450
  }
956
451
  const data = accountInfo.data.slice(8);
957
452
  const account = {
958
- owner: new PublicKey5(data.slice(0, 32)),
453
+ owner: new PublicKey4(data.slice(0, 32)),
959
454
  metaPubkey: new Uint8Array(data.slice(32, 64)),
960
455
  bump: data[64]
961
456
  };
962
457
  return { address, exists: true, account };
963
458
  }
964
- /**
965
- * Gets the meta pubkey for an owner
966
- * @returns The meta pubkey as Uint8Array, or null if not registered
967
- */
968
459
  async getMetaPubkey(owner) {
969
460
  const registry = await this.getRegistry(owner);
970
461
  return registry.exists ? registry.account.metaPubkey : null;
971
462
  }
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
463
  async isRegistered(owner) {
983
464
  const registry = await this.getRegistry(owner);
984
465
  return registry.exists;
985
466
  }
986
- /**
987
- * Creates a register identity instruction
988
- */
467
+ // --- Instruction Builders ---
989
468
  createRegisterInstruction(owner, metaPubkey) {
990
469
  if (!isValidMetaPubkey(metaPubkey)) {
991
470
  throw new Error("Invalid meta pubkey: must be 32 bytes and not all zeros");
@@ -995,7 +474,7 @@ var AdelosSDK = class {
995
474
  Buffer.from(getDiscriminator("register_identity")),
996
475
  Buffer.from(metaPubkey)
997
476
  ]);
998
- return new TransactionInstruction2({
477
+ return new TransactionInstruction({
999
478
  keys: [
1000
479
  { pubkey: owner, isSigner: true, isWritable: true },
1001
480
  { pubkey: registryPda, isSigner: false, isWritable: true },
@@ -1005,9 +484,6 @@ var AdelosSDK = class {
1005
484
  data
1006
485
  });
1007
486
  }
1008
- /**
1009
- * Creates an update identity instruction
1010
- */
1011
487
  createUpdateInstruction(owner, newMetaPubkey) {
1012
488
  if (!isValidMetaPubkey(newMetaPubkey)) {
1013
489
  throw new Error("Invalid meta pubkey: must be 32 bytes and not all zeros");
@@ -1017,22 +493,20 @@ var AdelosSDK = class {
1017
493
  Buffer.from(getDiscriminator("update_identity")),
1018
494
  Buffer.from(newMetaPubkey)
1019
495
  ]);
1020
- return new TransactionInstruction2({
496
+ return new TransactionInstruction({
1021
497
  keys: [
1022
- { pubkey: owner, isSigner: true, isWritable: false },
1023
- { pubkey: registryPda, isSigner: false, isWritable: true }
498
+ { pubkey: owner, isSigner: true, isWritable: true },
499
+ { pubkey: registryPda, isSigner: false, isWritable: true },
500
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }
1024
501
  ],
1025
502
  programId: this.programId,
1026
503
  data
1027
504
  });
1028
505
  }
1029
- /**
1030
- * Creates a close registry instruction
1031
- */
1032
506
  createCloseInstruction(owner) {
1033
507
  const [registryPda] = this.deriveRegistryAddress(owner);
1034
508
  const data = Buffer.from(getDiscriminator("close_registry"));
1035
- return new TransactionInstruction2({
509
+ return new TransactionInstruction({
1036
510
  keys: [
1037
511
  { pubkey: owner, isSigner: true, isWritable: true },
1038
512
  { pubkey: registryPda, isSigner: false, isWritable: true }
@@ -1041,116 +515,56 @@ var AdelosSDK = class {
1041
515
  data
1042
516
  });
1043
517
  }
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);
518
+ // --- Transaction Builders ---
519
+ async buildTransaction(payer, instructions, version = "v0") {
1083
520
  const { blockhash } = await this.connection.getLatestBlockhash();
1084
- const messageV0 = new TransactionMessage({
1085
- payerKey: owner,
521
+ if (version === "legacy") {
522
+ const tx = new Transaction().add(...instructions);
523
+ tx.recentBlockhash = blockhash;
524
+ tx.feePayer = payer;
525
+ return tx;
526
+ }
527
+ const message = new TransactionMessage({
528
+ payerKey: payer,
1086
529
  recentBlockhash: blockhash,
1087
- instructions: [instruction]
530
+ instructions
1088
531
  }).compileToV0Message();
1089
- return new VersionedTransaction(messageV0);
532
+ return new VersionedTransaction(message);
1090
533
  }
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;
534
+ async createRegisterTransaction(owner, metaPubkey) {
535
+ const ix = this.createRegisterInstruction(owner, metaPubkey);
536
+ return this.buildTransaction(owner, [ix], "legacy");
1100
537
  }
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);
538
+ async createRegisterTransactionV0(owner, metaPubkey) {
539
+ const ix = this.createRegisterInstruction(owner, metaPubkey);
540
+ return this.buildTransaction(owner, [ix], "v0");
1113
541
  }
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;
542
+ async sendAndConfirm(signedTx) {
543
+ const sig = await this.connection.sendRawTransaction(signedTx.serialize());
544
+ await this.connection.confirmTransaction(sig, "confirmed");
545
+ return sig;
1123
546
  }
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;
547
+ async sendAndConfirmV0(signedTx) {
548
+ const sig = await this.connection.sendRawTransaction(signedTx.serialize());
549
+ await this.connection.confirmTransaction(sig, "confirmed");
550
+ return sig;
1133
551
  }
1134
552
  };
1135
553
  export {
554
+ ADELOS_CONFIG,
1136
555
  AdelosIndexer,
1137
556
  AdelosSDK,
1138
557
  IDL,
1139
- LIGHT_PROGRAM_IDS,
1140
- LightClient,
1141
558
  MEMO_PREFIX,
1142
559
  MEMO_PROGRAM_ID,
1143
560
  PROGRAM_ID,
1144
- PROGRAM_IDS,
1145
- REGISTRY_ACCOUNT_SIZE,
1146
561
  REGISTRY_SEED,
1147
- RPC_URLS,
562
+ RPC_URL,
1148
563
  STEALTH_DOMAIN,
1149
564
  bytesToHex,
1150
565
  computeSharedSecret,
1151
566
  computeSharedSecretAsRecipient,
1152
567
  createIndexer,
1153
- createLightClient,
1154
568
  deriveRegistryPda,
1155
569
  deriveStealthPubkey,
1156
570
  generateEphemeralKeypair,
@@ -1158,7 +572,6 @@ export {
1158
572
  generateStealthMemo,
1159
573
  getDiscriminator,
1160
574
  hexToBytes,
1161
- isStealthTransactionForMe,
1162
575
  isValidMetaPubkey,
1163
576
  parseStealthMemo,
1164
577
  recoverStealthSecretKey