Ethereum Same-Chain Privacy Guide
This guide covers implementing privacy-preserving transactions on Ethereum and EVM-compatible chains (Polygon, Arbitrum, Optimism, Base) using SIP Protocol’s EIP-5564 compliant stealth address implementation.
Prerequisites
Section titled “Prerequisites”- Node.js 18+
- An Ethereum wallet (MetaMask, WalletConnect, etc.)
- Basic understanding of EVM transactions
Installation
Section titled “Installation”npm install @sip-protocol/sdk @sip-protocol/types ethersFor React applications:
npm install @sip-protocol/sdk @sip-protocol/react ethersQuick Start
Section titled “Quick Start”1. Initialize the SDK
Section titled “1. Initialize the SDK”import { SIP, NEARIntentsAdapter } from '@sip-protocol/sdk'import { PrivacyLevel } from '@sip-protocol/types'import { ethers } from 'ethers'
// Connect to Ethereumconst provider = new ethers.BrowserProvider(window.ethereum)const signer = await provider.getSigner()
// Create SIP clientconst sip = new SIP({ network: 'mainnet',})
// For cross-chain, use NEAR Intentsconst nearAdapter = new NEARIntentsAdapter({ jwtToken: process.env.NEAR_INTENTS_JWT,})2. Generate Stealth Meta-Address
Section titled “2. Generate Stealth Meta-Address”EVM chains use secp256k1 compressed public keys (33 bytes):
import { generateStealthMetaAddress, encodeStealthMetaAddress,} from '@sip-protocol/sdk'
// Generate for Ethereum (secp256k1)const metaAddress = generateStealthMetaAddress('ethereum')
// Encode for sharingconst encoded = encodeStealthMetaAddress(metaAddress.metaAddress)console.log('Share this:', encoded)// Output: sip:ethereum:0x02...spending...:0x03...viewing...
// Store keys securely// metaAddress.spendingPrivateKey - NEVER expose// metaAddress.viewingPrivateKey - Share only with authorized auditors3. Send a Private Transfer
Section titled “3. Send a Private Transfer”import { PrivacyLevel } from '@sip-protocol/types'
// Prepare private ETH transfer via NEAR Intentsconst prepared = await nearAdapter.prepareSwap( { requestId: crypto.randomUUID(), privacyLevel: PrivacyLevel.SHIELDED, inputAsset: { chain: 'ethereum', symbol: 'ETH', decimals: 18 }, inputAmount: ethers.parseEther('1.0'), outputAsset: { chain: 'ethereum', symbol: 'ETH', decimals: 18 }, }, recipientMetaAddress, await signer.getAddress(),)
// Get quoteconst quote = await nearAdapter.getQuote(prepared)
console.log('Deposit to:', quote.depositAddress)console.log('Stealth recipient:', prepared.stealthAddress?.address)EIP-5564 Compliance
Section titled “EIP-5564 Compliance”SIP Protocol implements EIP-5564 stealth addresses for Ethereum. Key properties:
| Property | Value |
|---|---|
| Curve | secp256k1 |
| Key format | Compressed (33 bytes, prefix 02 or 03) |
| View tag | First byte of shared secret hash |
| Address derivation | Keccak256 of uncompressed public key |
Stealth Address Generation
Section titled “Stealth Address Generation”import { generateStealthAddress, publicKeyToEthAddress,} from '@sip-protocol/sdk'
// Generate one-time stealth addressconst { stealthAddress, sharedSecret } = generateStealthAddress(recipientMetaAddress)
// Convert to Ethereum address (20 bytes, checksummed)const ethAddress = publicKeyToEthAddress(stealthAddress.address)console.log('Send to:', ethAddress) // 0x742d35Cc6634C0532925a3b844Bc454e4438f44eScanning with View Tag
Section titled “Scanning with View Tag”The view tag enables efficient scanning:
import { scanForStealthPayments } from '@sip-protocol/sdk'
// Scan Ethereum for paymentsconst payments = await scanForStealthPayments({ chain: 'ethereum', viewingPrivateKey: myMetaAddress.viewingPrivateKey, spendingPublicKey: myMetaAddress.metaAddress.spendingKey, fromBlock: 19000000, rpcUrl: 'https://mainnet.infura.io/v3/YOUR_KEY',})
for (const payment of payments) { // View tag allows quick rejection of non-matching payments if (payment.viewTag !== expectedViewTag) continue
console.log('Payment found:', { amount: ethers.formatEther(payment.amount), txHash: payment.txHash, block: payment.blockNumber, })}Privacy Levels
Section titled “Privacy Levels”Transparent Mode
Section titled “Transparent Mode”Standard Ethereum transaction with no privacy:
const prepared = await nearAdapter.prepareSwap( { requestId: crypto.randomUUID(), privacyLevel: PrivacyLevel.TRANSPARENT, inputAsset: { chain: 'ethereum', symbol: 'ETH', decimals: 18 }, inputAmount: ethers.parseEther('1.0'), outputAsset: { chain: 'ethereum', symbol: 'ETH', decimals: 18 }, }, undefined, senderAddress, recipientAddress,)Shielded Mode
Section titled “Shielded Mode”Full privacy - sender, amount, and recipient are hidden:
const prepared = await nearAdapter.prepareSwap( { requestId: crypto.randomUUID(), privacyLevel: PrivacyLevel.SHIELDED, inputAsset: { chain: 'ethereum', symbol: 'ETH', decimals: 18 }, inputAmount: ethers.parseEther('1.0'), outputAsset: { chain: 'ethereum', symbol: 'ETH', decimals: 18 }, }, recipientMetaAddress, senderAddress,)
// Stealth address is generatedconsole.log('Stealth public key:', prepared.stealthAddress?.address)console.log('Ephemeral key:', prepared.stealthAddress?.ephemeralPublicKey)console.log('View tag:', prepared.stealthAddress?.viewTag)Compliant Mode
Section titled “Compliant Mode”Privacy with viewing key for regulatory compliance:
import { createViewingKey, encryptForViewing } from '@sip-protocol/sdk'
const viewingKey = createViewingKey()
const prepared = await nearAdapter.prepareSwap( { requestId: crypto.randomUUID(), privacyLevel: PrivacyLevel.COMPLIANT, inputAsset: { chain: 'ethereum', symbol: 'ETH', decimals: 18 }, inputAmount: ethers.parseEther('10.0'), outputAsset: { chain: 'ethereum', symbol: 'ETH', decimals: 18 }, }, recipientMetaAddress, senderAddress,)
// Encrypt transaction details for auditorconst encryptedAuditData = encryptForViewing( { sender: senderAddress, recipient: recipientAddress, amount: '10.0', asset: 'ETH', timestamp: Date.now(), purpose: 'Payment for consulting services', }, auditorPublicKey,)Private Key Recovery
Section titled “Private Key Recovery”When receiving a stealth payment, derive the private key:
import { deriveStealthPrivateKey } from '@sip-protocol/sdk'
// From a detected paymentconst stealthPrivateKey = deriveStealthPrivateKey( payment.stealthAddress, myMetaAddress.spendingPrivateKey, myMetaAddress.viewingPrivateKey,)
// Create wallet from derived keyconst stealthWallet = new ethers.Wallet(stealthPrivateKey, provider)
// Now you can spend from this addressconst tx = await stealthWallet.sendTransaction({ to: myMainWallet, value: payment.amount - gasEstimate,})ERC-20 Token Privacy
Section titled “ERC-20 Token Privacy”Privacy works with any ERC-20 token:
// Private USDC transferconst prepared = await nearAdapter.prepareSwap( { requestId: crypto.randomUUID(), privacyLevel: PrivacyLevel.SHIELDED, inputAsset: { chain: 'ethereum', symbol: 'USDC', decimals: 6, address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', }, inputAmount: 100_000_000n, // 100 USDC outputAsset: { chain: 'ethereum', symbol: 'USDC', decimals: 6, address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', }, }, recipientMetaAddress, senderAddress,)Multi-Chain Support
Section titled “Multi-Chain Support”Same API works across all EVM chains:
// Polygonconst polygonMeta = generateStealthMetaAddress('polygon')
// Arbitrumconst arbitrumMeta = generateStealthMetaAddress('arbitrum')
// Optimismconst optimismMeta = generateStealthMetaAddress('optimism')
// Baseconst baseMeta = generateStealthMetaAddress('base')Cross-Chain Privacy Transfer
Section titled “Cross-Chain Privacy Transfer”// ETH on Ethereum → USDC on Polygon (private)const crossChain = await nearAdapter.prepareSwap( { requestId: crypto.randomUUID(), privacyLevel: PrivacyLevel.SHIELDED, inputAsset: { chain: 'ethereum', symbol: 'ETH', decimals: 18 }, inputAmount: ethers.parseEther('1.0'), outputAsset: { chain: 'polygon', symbol: 'USDC', decimals: 6, address: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', }, }, recipientPolygonMetaAddress, // Must be secp256k1 for EVM ethSenderAddress,)React Integration
Section titled “React Integration”import { useSIP, useStealthAddress, useViewingKey,} from '@sip-protocol/react'import { useAccount, useSignMessage } from 'wagmi'
function PrivateTransfer() { const { address } = useAccount() const { sendPrivate, isLoading } = useSIP() const { generate, metaAddress } = useStealthAddress('ethereum')
const handleSend = async () => { const result = await sendPrivate({ chain: 'ethereum', recipient: recipientMetaAddress, amount: '1.0', token: 'ETH', privacyLevel: 'shielded', })
console.log('Tx hash:', result.txHash) }
return ( <div> <p>Connected: {address}</p> {!metaAddress && ( <button onClick={generate}>Generate Stealth Address</button> )} <button onClick={handleSend} disabled={isLoading}> Send Private ETH </button> </div> )}Address Format Reference
Section titled “Address Format Reference”| Chain | Format | Example |
|---|---|---|
| Ethereum | Checksummed hex | 0x742d35Cc6634C0532925a3b844Bc454e4438f44e |
| Polygon | Checksummed hex | 0x742d35Cc6634C0532925a3b844Bc454e4438f44e |
| Arbitrum | Checksummed hex | 0x742d35Cc6634C0532925a3b844Bc454e4438f44e |
| Optimism | Checksummed hex | 0x742d35Cc6634C0532925a3b844Bc454e4438f44e |
| Base | Checksummed hex | 0x742d35Cc6634C0532925a3b844Bc454e4438f44e |
Key Formats
Section titled “Key Formats”| Type | Length | Prefix | Example |
|---|---|---|---|
| Compressed public | 33 bytes | 02 or 03 | 0x02abc123... |
| Uncompressed public | 65 bytes | 04 | 0x04abc123... |
| Private key | 32 bytes | - | 0xabc123... |
| Ethereum address | 20 bytes | 0x | 0x742d35Cc... |
Error Handling
Section titled “Error Handling”import { ValidationError, NetworkError, ProofError } from '@sip-protocol/sdk'
try { const result = await nearAdapter.prepareSwap(params, metaAddress, sender)} catch (error) { if (error instanceof ValidationError) { if (error.field === 'recipientMetaAddress') { // Wrong key format - likely using ed25519 keys for EVM chain console.error('Use secp256k1 meta-address for Ethereum') } }}Security Considerations
Section titled “Security Considerations”Key Management
Section titled “Key Management”- Spending private key - Full control. Treat like a seed phrase. Never expose.
- Viewing private key - Read-only access. Share only with authorized auditors.
- Meta-address - Public. Safe to share for receiving payments.
Stealth Address Properties
Section titled “Stealth Address Properties”- Each payment uses a unique address (unlinkable)
- View tag enables efficient scanning without revealing amounts
- Ephemeral key must be published for recipient to claim
Gas Considerations
Section titled “Gas Considerations”Claiming from a stealth address requires gas. Options:
- Self-fund: Send small ETH to stealth address before claiming
- Relayer: Use a meta-transaction relayer (coming soon)
- Bundler: Use account abstraction (ERC-4337)
Troubleshooting
Section titled “Troubleshooting””spendingKey must be valid secp256k1”
Section titled “”spendingKey must be valid secp256k1””You’re using ed25519 keys (for Solana/NEAR) on an EVM chain:
// Wrong - ed25519 keysconst wrong = generateEd25519StealthMetaAddress('ethereum')
// Correct - secp256k1 keysconst correct = generateStealthMetaAddress('ethereum')“address must start with 02 or 03”
Section titled ““address must start with 02 or 03””Compressed public keys must start with 02 or 03:
// Valid compressed keys'0x02abc...' // Y is even'0x03abc...' // Y is odd
// Invalid (uncompressed - 04 prefix)'0x04abc...'“cross-curve refunds not supported”
Section titled ““cross-curve refunds not supported””When bridging between different curve types:
// Solana (ed25519) → Ethereum (secp256k1)// Must provide explicit sender address for refundsconst prepared = await nearAdapter.prepareSwap( params, evmMetaAddress, // secp256k1 solanaAddress, // Required - can't auto-generate)