NEAR Same-Chain Privacy Guide
This guide covers how to integrate NEAR same-chain privacy into your dApp using SIP Protocol. You’ll learn to send private transfers, manage viewing keys for compliance, and scan for incoming payments.
Prerequisites
Section titled “Prerequisites”Before starting, ensure you have:
- Node.js 18+ installed
- A NEAR account (mainnet or testnet)
- Basic understanding of NEAR transactions
Installation
Section titled “Installation”npm install @sip-protocol/sdk @sip-protocol/typesFor React applications:
npm install @sip-protocol/sdk @sip-protocol/reactQuick 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'
// Create adapter for NEAR Intents (cross-chain settlement)const nearAdapter = new NEARIntentsAdapter({ jwtToken: process.env.NEAR_INTENTS_JWT,})
// Create SIP clientconst sip = new SIP({ network: 'mainnet',})2. Generate Stealth Meta-Address
Section titled “2. Generate Stealth Meta-Address”Users need a stealth meta-address to receive private payments:
import { generateEd25519StealthMetaAddress, encodeStealthMetaAddress,} from '@sip-protocol/sdk'
// Generate meta-address for NEAR (uses ed25519)// Returns: { metaAddress, spendingPrivateKey, viewingPrivateKey }const metaAddress = generateEd25519StealthMetaAddress('near')
// Encode the public meta-address for sharingconst encoded = encodeStealthMetaAddress(metaAddress.metaAddress)console.log('Share this address:', encoded)// Output: sip:near:0x...spendingKey...:0x...viewingKey...
// Store privately (for deriving stealth keys)// metaAddress.spendingPrivateKey - NEVER share!// metaAddress.viewingPrivateKey - Share only with auditors3. Send a Private Transfer
Section titled “3. Send a Private Transfer”import { PrivacyLevel } from '@sip-protocol/types'
// Prepare private NEAR transferconst prepared = await nearAdapter.prepareSwap( { requestId: crypto.randomUUID(), privacyLevel: PrivacyLevel.SHIELDED, inputAsset: { chain: 'near', symbol: 'NEAR', decimals: 24 }, inputAmount: 1_000_000_000_000_000_000_000_000n, // 1 NEAR outputAsset: { chain: 'near', symbol: 'NEAR', decimals: 24 }, }, recipientMetaAddress, // Recipient's stealth meta-address senderWalletAddress, // Your wallet for refunds)
// Get quote and deposit addressconst quote = await nearAdapter.getQuote(prepared)
console.log('Deposit to:', quote.depositAddress)console.log('Amount:', quote.amountIn)console.log('Expected output:', quote.amountOut)Privacy Levels
Section titled “Privacy Levels”SIP Protocol supports three privacy levels:
| Level | Sender | Amount | Recipient | Compliance |
|---|---|---|---|---|
TRANSPARENT | Visible | Visible | Visible | N/A |
SHIELDED | Hidden | Hidden | Hidden | None |
COMPLIANT | Hidden | Hidden | Hidden | Viewing key |
Transparent Mode
Section titled “Transparent Mode”Standard NEAR transfer with no privacy:
const prepared = await nearAdapter.prepareSwap( { requestId: crypto.randomUUID(), privacyLevel: PrivacyLevel.TRANSPARENT, inputAsset: { chain: 'near', symbol: 'NEAR', decimals: 24 }, inputAmount: 1_000_000_000_000_000_000_000_000n, outputAsset: { chain: 'near', symbol: 'NEAR', decimals: 24 }, }, undefined, // No meta-address needed senderAddress, recipientAddress, // Direct recipient)Shielded Mode
Section titled “Shielded Mode”Full privacy - amount, sender, and recipient are hidden:
const prepared = await nearAdapter.prepareSwap( { requestId: crypto.randomUUID(), privacyLevel: PrivacyLevel.SHIELDED, inputAsset: { chain: 'near', symbol: 'NEAR', decimals: 24 }, inputAmount: 1_000_000_000_000_000_000_000_000n, outputAsset: { chain: 'near', symbol: 'NEAR', decimals: 24 }, }, recipientMetaAddress, senderAddress,)
// Stealth address is generated automaticallyconsole.log('Stealth recipient:', prepared.stealthAddress?.address)console.log('Ephemeral key:', prepared.stealthAddress?.ephemeralPublicKey)Compliant Mode
Section titled “Compliant Mode”Privacy with viewing key for authorized auditors:
import { generateViewingKey, encryptForViewing } from '@sip-protocol/sdk'
// Create a viewing key (returns { key, path, hash })const viewingKey = generateViewingKey('m/0/auditor')
const prepared = await nearAdapter.prepareSwap( { requestId: crypto.randomUUID(), privacyLevel: PrivacyLevel.COMPLIANT, inputAsset: { chain: 'near', symbol: 'NEAR', decimals: 24 }, inputAmount: 1_000_000_000_000_000_000_000_000n, outputAsset: { chain: 'near', symbol: 'NEAR', decimals: 24 }, }, recipientMetaAddress, senderAddress,)
// Encrypt transaction details for auditorconst encryptedDetails = encryptForViewing( { sender: senderAddress, recipient: recipientAddress, amount: '1000000000000000000000000', timestamp: Date.now(), }, viewingKey,)
// Store encryptedDetails on-chain or in database// Auditor can decrypt with the same viewingKey (decryptWithViewing)Scanning for Payments
Section titled “Scanning for Payments”Recipients scan the chain for incoming stealth payments using
getTransactionHistory, which derives the stealth addresses that belong to
your viewing key and returns the matching transactions:
import { getTransactionHistory, deriveStealthPrivateKey,} from '@sip-protocol/sdk'
// Scan NEAR for transactions belonging to your keysconst history = await getTransactionHistory({ rpcUrl: 'https://rpc.mainnet.near.org', viewingPrivateKey: metaAddress.viewingPrivateKey, spendingPrivateKey: metaAddress.spendingPrivateKey, network: 'mainnet', limit: 50,})
for (const tx of history.transactions) { console.log('Found payment:', { amount: tx.amountFormatted, token: tx.token, txHash: tx.hash, ephemeralKey: tx.ephemeralPublicKey, })
// Reconstruct the stealth address from the transaction const stealthAddress = { address: tx.stealthPublicKey, ephemeralPublicKey: tx.ephemeralPublicKey, viewTag: tx.viewTag, }
// Derive the private key to claim this payment const recovery = deriveStealthPrivateKey( stealthAddress, metaAddress.spendingPrivateKey, metaAddress.viewingPrivateKey, )
// Use recovery.privateKey to sign transactions from this address}Viewing Key Management
Section titled “Viewing Key Management”Creating Viewing Keys
Section titled “Creating Viewing Keys”import { generateViewingKey } from '@sip-protocol/sdk'
const viewingKey = generateViewingKey('m/0/auditor')
// The viewing key is symmetric — there is no public/private split.// Share the whole key with authorized auditors so they can decrypt.console.log('Auditor key:', viewingKey.key)
// Hash uniquely identifies which key can decrypt a given payloadconsole.log('Key hash:', viewingKey.hash)Selective Disclosure
Section titled “Selective Disclosure”Share viewing keys for specific transactions:
import { encryptForViewing, decryptWithViewing } from '@sip-protocol/sdk'
// Sender encrypts transaction details with the auditor's viewing keyconst encrypted = encryptForViewing( { sender: 'alice.near', recipient: 'bob.near', amount: '1000000000000000000000000', timestamp: Date.now(), }, viewingKey,)
// Auditor decrypts with the same viewing keyconst details = decryptWithViewing(encrypted, viewingKey)console.log('Transaction details:', details)Time-Limited Access
Section titled “Time-Limited Access”Derive a viewing key that is only valid for a specific time window using
createTimeWindowedKey. The auditor can decrypt payments within the window,
but the key carries no authority outside [windowStart, windowEnd]:
import { createTimeWindowedKey } from '@sip-protocol/sdk'
const now = Math.floor(Date.now() / 1000)
const windowedKey = createTimeWindowedKey( masterViewingKey.key, // master viewing key (hex) now, // window start (Unix seconds) now + 24 * 60 * 60, // window end — 24 hours later)
console.log('Valid from:', windowedKey.windowStart)console.log('Valid until:', windowedKey.windowEnd)console.log('Epochs covered:', windowedKey.epochs)NEP-141 Token Privacy
Section titled “NEP-141 Token Privacy”Privacy works with any NEP-141 token on NEAR:
// Private USDC transferconst prepared = await nearAdapter.prepareSwap( { requestId: crypto.randomUUID(), privacyLevel: PrivacyLevel.SHIELDED, inputAsset: { chain: 'near', symbol: 'USDC', decimals: 6, address: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1', }, inputAmount: 100_000_000n, // 100 USDC outputAsset: { chain: 'near', symbol: 'USDC', decimals: 6, address: '17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1', }, }, recipientMetaAddress, senderAddress,)React Integration
Section titled “React Integration”Use the React hooks for seamless integration:
import { useSIP, useStealthAddress, useViewingKey,} from '@sip-protocol/react'
function PrivateTransfer() { const { sendPrivate, isLoading, error } = useSIP() const { generate, metaAddress } = useStealthAddress() const { createKey, viewingKey } = useViewingKey()
const handleSend = async () => { const result = await sendPrivate({ chain: 'near', recipient: recipientMetaAddress, amount: '1000000000000000000000000', token: 'NEAR', privacyLevel: 'shielded', })
console.log('Transaction:', result.txHash) }
return ( <div> {!metaAddress && ( <button onClick={generate}>Generate Address</button> )} <button onClick={handleSend} disabled={isLoading}> Send Private NEAR </button> {error && <p>Error: {error.message}</p>} </div> )}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) { console.error('Invalid input:', error.field, error.message) } else if (error instanceof NetworkError) { console.error('Network issue:', error.message) // Retry logic } else if (error instanceof ProofError) { console.error('Proof generation failed:', error.code) }}Troubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”“Meta-address has wrong key size”
NEAR uses ed25519 keys (32 bytes). Ensure you generate the correct type:
// Correct for NEARconst metaAddress = generateEd25519StealthMetaAddress('near')
// Wrong - secp256k1 is for EVM chains// const metaAddress = generateStealthMetaAddress('near') // Don't use for NEAR!“Wallet address format doesn’t match input chain”
Your wallet must match the source chain:
// For NEAR swaps, use a NEAR addressconst senderAddress = 'yourname.near' // or implicit: 0x...64-hex-chars
// For EVM swaps, use an EVM addressconst senderAddress = '0x...' // 40 hex chars“Cross-curve refunds not supported”
When swapping between chains with different curves:
// NEAR (ed25519) -> Ethereum (secp256k1)// Must provide explicit senderAddress for refundsconst prepared = await nearAdapter.prepareSwap( params, evmMetaAddress, 'yourname.near', // Required! Cannot auto-generate cross-curve refund)Best Practices
Section titled “Best Practices”-
Store keys securely - Never expose spending private keys. Use secure storage (Keychain, encrypted storage).
-
Test on mainnet with small amounts - NEAR Intents only works on mainnet. Start with $5-10 for testing.
-
Implement proper scanning - Scan regularly for incoming payments. Consider background jobs.
-
Handle viewing keys carefully - Only share with authorized parties. Use time-limited keys when possible.
-
Validate addresses - Always validate chain/address format matches before sending.