Skip to main content

Authentication

This guide covers wallet connection and authentication flows in the Privacy Boost TypeScript SDK.

Authentication Flow

Privacy Boost authentication is a two-step process:
  1. Connect - Establish wallet connection
  2. Login - Derive privacy keys and register with the indexer
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│ Disconnected│────>│  Connected  │────>│Authenticated│
│             │     │ (Wallet)    │     │ (Privacy)   │
└─────────────┘     └─────────────┘     └─────────────┘
     connect()           login()

Connect Wallet

Using EIP-1193 Provider (MetaMask, etc.)

import { Eip1193WalletAdapter } from '@testinprod-io/privacy-boost';

// Check if wallet is available
if (!window.ethereum) {
  throw new Error('No wallet detected');
}

// Create adapter
const adapter = new Eip1193WalletAdapter(window.ethereum);

// Connect
const { address, chainId } = await sdk.auth.connect(adapter);
console.log(`Connected: ${address} on chain ${chainId}`);

Using WalletConnect

import { createWalletRegistry } from '@testinprod-io/privacy-boost';

const registry = createWalletRegistry({
  chains: [1, 137],
  walletConnect: {
    projectId: 'your-walletconnect-project-id',
    metadata: {
      name: 'Your App',
      description: 'Your app description',
      url: 'https://yourapp.com',
      icons: ['https://yourapp.com/icon.png'],
    },
  },
});

const adapter = registry.getAdapter('walletconnect');
await sdk.auth.connect(adapter);

Using Private Key (Testing Only)

import { PrivateKeyWalletAdapter } from '@testinprod-io/privacy-boost';

// WARNING: Never use in production
const adapter = new PrivateKeyWalletAdapter({
  privateKey: '0x...',
  chainId: 1,
  rpcUrl: 'https://mainnet.infura.io/v3/...',
});

await sdk.auth.connect(adapter);

Login

After connecting, login to derive privacy keys:
const result = await sdk.auth.login();

console.log('Privacy Address:', result.privacyAddress);
console.log('MPK:', result.mpk);

What Happens During Login

  1. SDK requests a challenge from the indexer
  2. User signs an EIP-712 typed data message
  3. SDK derives privacy keys from the signature:
    • Master Public Key (MPK)
    • Viewing Key
    • Nullifying Key
  4. SDK registers with the indexer using the derived keys
  5. Indexer returns a JWT for authenticated requests

Signature Message

The user will see a signature request like:
Privacy Boost Authentication

By signing this message, you are generating your privacy keys.
These keys will be used to encrypt and decrypt your private transactions.

Challenge: abc123...
Timestamp: 2024-01-15T10:30:00Z

Authentication State

Check State

// Connection state
const isConnected = sdk.auth.isConnected();
const walletAddress = sdk.auth.getAddress();
const chainId = sdk.auth.getChainId();

// Authentication state
const isAuthenticated = sdk.auth.isAuthenticated();
const privacyAddress = sdk.auth.getPrivacyAddress();
const mpk = sdk.auth.getMpk();

Combined Authentication

For convenience, you can connect and login in one call:
// This is a custom helper you can create:
async function authenticate(adapter: WalletAdapter) {
  await sdk.auth.connect(adapter);
  return await sdk.auth.login();
}

// Usage
const { privacyAddress } = await authenticate(adapter);

Disconnect and Logout

Disconnect

Disconnects the wallet but keeps privacy keys:
await sdk.auth.disconnect();

console.log(sdk.auth.isConnected()); // false
console.log(sdk.auth.isAuthenticated()); // still true

Logout

Clears all authentication state:
await sdk.auth.logout();

console.log(sdk.auth.isConnected()); // false
console.log(sdk.auth.isAuthenticated()); // false

Session Persistence

Export Session

Save the session for later restoration:
const session = sdk.auth.exportSession();

// Session contains:
// - walletAddress
// - jwt
// - jwtExpiry
// - mpk

// Store securely
localStorage.setItem('privacy_session', JSON.stringify(session));

Import Session

Restore a saved session:
const savedSession = JSON.parse(localStorage.getItem('privacy_session'));

if (savedSession) {
  try {
    await sdk.auth.importSession(savedSession);
    console.log('Session restored successfully');
  } catch (error) {
    console.log('Session invalid or expired');
    // Re-authenticate
    await sdk.auth.login();
  }
}

Check Session Validity

const sessionInfo = await sdk.auth.getSessionInfo();

if (sessionInfo) {
  console.log('Valid:', sessionInfo.valid);
  console.log('Expires:', new Date(sessionInfo.expiresAt * 1000));
  console.log('Is Auditor:', sessionInfo.isAuditor);
} else {
  console.log('No active session');
}

Error Handling

Connection Errors

try {
  await sdk.auth.connect(adapter);
} catch (error) {
  if (error.code === 'USER_REJECTED') {
    console.log('User rejected connection');
  } else if (error.code === 'CHAIN_MISMATCH') {
    console.log('Wrong network, please switch');
  } else {
    console.log('Connection failed:', error.message);
  }
}

Login Errors

try {
  await sdk.auth.login();
} catch (error) {
  if (error.code === 'SIGNATURE_REJECTED') {
    console.log('User rejected signature');
  } else if (error.code === 'CHALLENGE_EXPIRED') {
    console.log('Challenge expired, try again');
  } else if (error.code === 'REGISTRATION_FAILED') {
    console.log('Registration failed:', error.message);
  }
}

Network Switching

Handle network changes:
// Listen for network changes
window.ethereum?.on('chainChanged', async (chainId: string) => {
  const newChainId = parseInt(chainId, 16);

  if (newChainId !== sdk.config.chainId) {
    console.log('Network changed, please reconnect');
    await sdk.auth.disconnect();
  }
});

// Listen for account changes
window.ethereum?.on('accountsChanged', async (accounts: string[]) => {
  if (accounts.length === 0) {
    console.log('Wallet disconnected');
    await sdk.auth.disconnect();
  } else if (accounts[0] !== sdk.auth.getAddress()) {
    console.log('Account changed, please re-authenticate');
    await sdk.auth.logout();
  }
});

Best Practices

1. Always Check Connection Before Operations

async function ensureAuthenticated() {
  if (!sdk.auth.isConnected()) {
    throw new Error('Please connect your wallet');
  }
  if (!sdk.auth.isAuthenticated()) {
    throw new Error('Please login first');
  }
}

2. Handle Session Expiration

async function withSessionRefresh<T>(operation: () => Promise<T>): Promise<T> {
  try {
    return await operation();
  } catch (error) {
    if (error.code === 'SESSION_EXPIRED') {
      await sdk.auth.login();
      return await operation();
    }
    throw error;
  }
}

// Usage
await withSessionRefresh(() => sdk.vault.deposit(params));

3. Secure Session Storage

import CryptoJS from 'crypto-js';

function saveSession(session: unknown, password: string) {
  const encrypted = CryptoJS.AES.encrypt(
    JSON.stringify(session),
    password
  ).toString();
  localStorage.setItem('privacy_session_encrypted', encrypted);
}

function loadSession(password: string): unknown | null {
  const encrypted = localStorage.getItem('privacy_session_encrypted');
  if (!encrypted) return null;

  try {
    const decrypted = CryptoJS.AES.decrypt(encrypted, password);
    return JSON.parse(decrypted.toString(CryptoJS.enc.Utf8));
  } catch {
    return null;
  }
}

Next Steps