EIP-5564 Stealth Addresses SDK for Solana - Generate, send to, and spend from unlinkable stealth addresses
npm install sol-stealth-sdkEIP-5564 Stealth Addresses SDK for Solana. Generate, send to, and spend from unlinkable stealth addresses.
Stealth addresses allow a sender to pay a recipient without creating a public link between them on-chain. The recipient publishes a single "meta-address", and senders derive unique one-time addresses for each payment. Only the recipient can detect and spend from these addresses.
``bash`
npm install sol-stealth-sdk @solana/web3.js
`typescript
import { generateStealthMetaAddress } from 'sol-stealth-sdk';
// Generate a new stealth meta-address (do this once)
const metaAddress = generateStealthMetaAddress();
// Share this publicly - senders use it to pay you
console.log(metaAddress.encoded);
// st:sol:1:02abc...def:03xyz...
// KEEP THESE SECRET - needed to detect and spend payments
console.log(metaAddress.spending.privateKey); // 32 bytes
console.log(metaAddress.viewing.privateKey); // 32 bytes
`
`typescript
import { parseStealthMetaAddress, generateStealthAddress } from 'sol-stealth-sdk';
import { Connection, Transaction, SystemProgram, sendAndConfirmTransaction } from '@solana/web3.js';
// Parse recipient's meta-address
const recipientMeta = parseStealthMetaAddress("st:sol:1:02abc...def:03xyz...");
// Generate a unique stealth address for this payment
const stealth = generateStealthAddress(recipientMeta);
// Send SOL to the stealth address
const tx = new Transaction().add(
SystemProgram.transfer({
fromPubkey: senderWallet.publicKey,
toPubkey: stealth.stealthAddress,
lamports: amount,
})
);
await sendAndConfirmTransaction(connection, tx, [senderWallet]);
// Store/emit announcement data so recipient can find the payment
// (ephemeralPubkey, viewTag, stealthAddress)
`
`typescript
import {
checkStealthAddress,
deriveStealthKeypair,
StealthAnnouncement
} from 'sol-stealth-sdk';
// Check if an announcement is for you
const result = checkStealthAddress(
announcement,
myViewingPrivateKey,
mySpendingPublicKey,
mySpendingPrivateKey // optional, needed to derive spending key
);
if (result.isMine) {
// Derive the keypair to spend from this stealth address
const stealthKeypair = deriveStealthKeypair(
announcement.ephemeralPubkey,
mySpendingPrivateKey,
myViewingPrivateKey
);
// Now you can sign transactions from stealthKeypair.publicKey
const withdrawTx = new Transaction().add(
SystemProgram.transfer({
fromPubkey: stealthKeypair.publicKey,
toPubkey: myMainWallet,
lamports: balance,
})
);
withdrawTx.sign(stealthKeypair);
await connection.sendRawTransaction(withdrawTx.serialize());
}
`
#### generateStealthMetaAddress()
Generate a new random stealth meta-address with spending and viewing keypairs.
`typescript`
const meta = generateStealthMetaAddress();
// Returns: {
// spending: { privateKey, publicKey },
// viewing: { privateKey, publicKey },
// encoded: "st:sol:1:..."
// }
#### deriveStealthMetaAddress(walletSecretKey)
Derive a deterministic stealth meta-address from a Solana wallet's secret key.
Same wallet always produces the same meta-address.
`typescript
import { Keypair } from '@solana/web3.js';
const wallet = Keypair.generate(); // or load from file
const meta = deriveStealthMetaAddress(wallet.secretKey);
// Recover later with same wallet:
const metaRecovered = deriveStealthMetaAddress(wallet.secretKey);
// metaRecovered.encoded === meta.encoded ✓
`
#### deriveStealthMetaAddressFromMnemonic(mnemonic, passphrase?)m/5564'/501'/0'
Derive a deterministic stealth meta-address from a BIP39 mnemonic phrase.
Follows EIP-5564 style derivation path:
`typescript
const meta = deriveStealthMetaAddressFromMnemonic(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
);
// With optional passphrase (different keys):
const metaWithPass = deriveStealthMetaAddressFromMnemonic(
"abandon abandon ...",
"my-secret-passphrase"
);
`
#### deriveStealthMetaAddressFromSeed(seed)
Derive from a raw 32-byte seed. Useful for custom key derivation schemes.
`typescript`
const seed = new Uint8Array(32).fill(42);
const meta = deriveStealthMetaAddressFromSeed(seed);
#### parseStealthMetaAddress(encoded: string)
Parse an encoded meta-address string into public keys.
`typescript`
const parsed = parseStealthMetaAddress("st:sol:1:02abc...:03xyz...");
// Returns: { spendingPubkey, viewingPubkey, schemeId }
| Method | Deterministic | Backup | Security |
|--------|--------------|--------|----------|
| generateStealthMetaAddress() | ❌ Random | Must store keys separately | Keys isolated from wallet |deriveStealthMetaAddress()
| | ✅ From wallet | Wallet backup covers stealth | Wallet compromise = stealth compromise |deriveStealthMetaAddressFromMnemonic()
| | ✅ From mnemonic | Seed phrase covers stealth | Seed compromise = stealth compromise |
#### generateStealthAddress(metaAddress)
Generate a one-time stealth address for a recipient.
`typescript`
const result = generateStealthAddress(recipientMeta);
// Returns: {
// stealthAddress: PublicKey, // Send funds here
// ephemeralPubkey: Uint8Array, // Include in announcement
// viewTag: number, // Include in announcement
// schemeId: number,
// stealthSeed: Uint8Array
// }
#### deriveStealthKeypair(ephemeralPubkey, spendingPrivate, viewingPrivate)
Derive the Solana keypair to spend from a stealth address.
`typescript`
const keypair = deriveStealthKeypair(
announcement.ephemeralPubkey,
mySpendingPrivateKey,
myViewingPrivateKey
);
// Returns: Keypair (can sign transactions)
#### checkStealthAddress(announcement, viewingPrivate, spendingPubkey, spendingPrivate?)
Check if an announcement belongs to you.
`typescript`
const result = checkStealthAddress(announcement, viewingKey, spendingPubkey);
// Returns: { isMine: boolean, stealthAddress?, stealthPrivateKey? }
#### scanAnnouncements(announcements[], viewingPrivate, spendingPubkey, spendingPrivate?)
Scan multiple announcements and return matches.
`typescript`
const matches = scanAnnouncements(announcements, viewingKey, spendingPubkey, spendingKey);
// Returns: AnnouncementCheckResult[]
#### isTokenTransfer(metadata: Uint8Array)
Check if announcement metadata contains token transfer info.
`typescript`
if (isTokenTransfer(announcement.metadata)) {
// This is a token transfer, not SOL
}
#### decodeTokenMetadata(metadata: Uint8Array)
Decode token information from announcement metadata.
`typescript
const tokenInfo = decodeTokenMetadata(announcement.metadata);
// Returns: { mint: PublicKey, amount: bigint, decimals: number, extraMetadata: Uint8Array }
console.log(Received ${formatTokenAmount(tokenInfo.amount, tokenInfo.decimals)} tokens);Token mint: ${tokenInfo.mint.toString()}
console.log();`
#### getStealthATA(stealthAddress, mint, tokenProgramId?)
Get the Associated Token Account address for a stealth address.
`typescript`
const stealthAta = getStealthATA(stealthAddress, tokenMint);
// Use this to check token balance or create transfer instructions
#### formatTokenAmount(amount: bigint, decimals: number)
Format raw token amount for display.
`typescript`
formatTokenAmount(1500000n, 6); // Returns: "1.5"
#### parseTokenAmount(amountStr: string, decimals: number)
Parse display amount to raw units.
`typescript`
parseTokenAmount("1.5", 6); // Returns: 1500000n
1. Private Keys: Never share or expose your spending/viewing private keys
2. Viewing Key: Can be shared with a trusted service for scanning (they can detect payments but NOT spend)
3. Spending Key: Required to spend - keep this maximally secure
4. Ephemeral Keys: Generated fresh for each payment, don't reuse
1. Recipient generates secp256k1 keypairs for spending (K_spend) and viewing (K_view)
2. Sender generates ephemeral keypair (r, R = r*G)
3. Sender computes shared secret via ECDH: S = r * K_view
4. Sender derives: hash = keccak256(S), viewTag = hash[0]
5. Sender computes combined point: P = K_spend + hash*G
6. Sender derives Ed25519 seed: seed = sha256(P)
7. Stealth address = Ed25519 keypair from seed
Recipient reverses steps 3-7 using their private keys to derive the same keypair.
This SDK is designed to work with the sol-eip5564 Anchor program for on-chain meta-address registration and transfer announcements.
Devnet Program ID: 463ouCTiiJqyitwkcfwhtuS1vcDrPTEibiYKTQeVu62a`
MIT