TypeScript SDK for ShadowWire - private payments on Solana with zero-knowledge proofs.
ShadowWire lets you make private transfers on Solana. You can hide transaction amounts using Bulletproofs (zero-knowledge proofs) while keeping everything on-chain and verifiable. Think of it as a privacy layer for Solana transfers.
- Private transfers - Hide payment amounts on-chain
- Multi-token - SOL, USDC, ORE, BONK, JIM, GODL
- Flexible - Client-side or backend proof generation
- Type-safe - Full TypeScript support
npm install @radr/shadowwireimport { ShadowWireClient } from '@radr/shadowwire';
const client = new ShadowWireClient();
// Check balance
const balance = await client.getBalance('YOUR_WALLET');
console.log(`Available: ${balance.available / 1e9} SOL`);
// Make a private transfer
await client.transfer({
sender: 'YOUR_WALLET',
recipient: 'RECIPIENT_WALLET',
amount: 0.5,
token: 'SOL',
type: 'internal' // Amount stays private
});Internal Transfers - Amount is completely hidden using zero-knowledge proofs. Both sender and recipient must be ShadowWire users. Perfect for maximum privacy.
External Transfers - Amount is visible, but sender stays anonymous. Works with any Solana wallet. Good for paying people outside the system.
Proofs are generated via:
- Client-side - Generate proofs in the browser using WASM. Maximum privacy since the backend never sees your amount.
const client = new ShadowWireClient({
debug: true // Optional: log requests
});const balance = await client.getBalance('WALLET_ADDRESS', 'SOL');
console.log(balance.available); // Available lamports
console.log(balance.pool_address); // Pool PDAconst tx = await client.deposit({
wallet: 'YOUR_WALLET',
amount: 100000000 // 0.1 SOL in lamports
});
// Returns unsigned transaction - you need to sign it with your walletconst tx = await client.withdraw({
wallet: 'YOUR_WALLET',
amount: 50000000 // 0.05 SOL
});The main method - handles everything for you:
const result = await client.transfer({
sender: 'YOUR_WALLET',
recipient: 'RECIPIENT_WALLET',
amount: 0.1, // In SOL, not lamports
token: 'SOL',
type: 'internal' // or 'external'
});
console.log(result.tx_signature);
console.log(result.amount_hidden); // true for internal, false for external| Token | Decimals |
|---|---|
| SOL | 9 |
| USDC | 6 |
| ORE | 11 |
| BONK | 5 |
| JIM | 9 |
| GODL | 11 |
import { TokenUtils } from '@radr/shadowwire';
// Convert SOL to lamports
TokenUtils.toSmallestUnit(0.1, 'SOL'); // 100000000
// Convert back
TokenUtils.fromSmallestUnit(100000000, 'SOL'); // 0.1If you want maximum privacy, generate proofs in the browser:
import { initWASM, generateRangeProof, isWASMSupported } from '@radr/shadowwire';
// Check if browser supports WASM
if (!isWASMSupported()) {
console.log('Use backend proofs instead');
return;
}
// Initialize WASM (only needed once)
await initWASM();
// Generate proof locally
const amountLamports = 100000000; // 0.1 SOL
const proof = await generateRangeProof(amountLamports, 64);
// Use it in a transfer
await client.transferWithClientProofs({
sender: 'YOUR_WALLET',
recipient: 'RECIPIENT_WALLET',
amount: 0.1,
token: 'SOL',
type: 'internal',
customProof: proof
});Proof generation takes 2-3 seconds. Show a loading indicator.
The SDK throws typed errors:
import { RecipientNotFoundError, InsufficientBalanceError } from '@radr/shadowwire';
try {
await client.transfer({
sender: 'YOUR_WALLET',
recipient: 'RECIPIENT',
amount: 1.0,
token: 'SOL',
type: 'internal'
});
} catch (error) {
if (error instanceof RecipientNotFoundError) {
// Recipient hasn't used ShadowWire before
// Try external transfer instead
} else if (error instanceof InsufficientBalanceError) {
// Not enough balance
}
}// Send 0.5 SOL privately
const result = await client.transfer({
sender: 'YOUR_WALLET',
recipient: 'RECIPIENT_WALLET',
amount: 0.5,
token: 'SOL',
type: 'internal'
});
console.log('Done!', result.tx_signature);
// Amount is hidden on-chain// Send to any Solana wallet
const result = await client.transfer({
sender: 'YOUR_WALLET',
recipient: 'ANY_SOLANA_WALLET',
amount: 100, // USDC
token: 'USDC',
type: 'external'
});
// Amount is visible, but sender stays anonymousFor advanced users who want more control:
// Step 1: Upload proof
const proofResult = await client.uploadProof({
sender_wallet: 'YOUR_WALLET',
token: 'SOL',
amount: 100000000,
nonce: Math.floor(Date.now() / 1000)
});
// Step 2: Execute transfer
const result = await client.internalTransfer({
sender_wallet: 'YOUR_WALLET',
recipient_wallet: 'RECIPIENT',
token: 'SOL',
nonce: proofResult.nonce,
relayer_fee: 1000000
});Do I need an API key?
Nope. The API is open.
What's the fee?
1% relayer fee automatically applied to all transfers.
Can I transfer to myself?
No, blocked for security.
What if the recipient hasn't used ShadowWire?
Use an external transfer instead. The SDK will throw RecipientNotFoundError if you try internal transfer.
Are my funds safe?
Yes. The smart contracts are audited and you always control your keys.
Why does client-side proof generation take so long?
Bulletproofs are computationally heavy. 2-3 seconds is normal for proper zero-knowledge proofs.
Should I use client-side or backend proofs?
Backend for almost everything. Client-side only if you really don't trust the backend to see your amounts.
Client-side proofs work in:
- Chrome/Edge 57+
- Firefox 52+
- Safari 11+
- Node.js 8+
Backend proofs work everywhere.
const client = new ShadowWireClient({ debug: true });
// Logs all API calls to consoleconst client = new ShadowWireClient({
apiBaseUrl: 'https://your-api.com'
});- Telegram: https://t.me/radrportal
- Twitter: https://x.com/radrdotfun
- Email: hello@radrlabs.io
MIT