PRIV ProtocolPRIV Docs
Plugin

Wallet Integration

Connect your Web3 wallet to receive PRIV token earnings.

Wallet Integration

The PRIV extension connects to your Web3 wallet to receive earnings. This guide covers the connection flow, account management, and best practices.


Supported Wallets

WalletStatusNotes
MetaMaskSupportedRecommended
Coinbase WalletSupportedVia browser extension
Brave WalletSupportedBuilt into Brave browser
RainbowSupportedVia WalletConnect
Trust WalletSupportedVia WalletConnect

Any wallet that injects the standard window.ethereum provider will work with the extension.


Connection Flow

Overview

sequenceDiagram
    participant User
    participant Popup as Extension Popup
    participant Wallet as Web3 Wallet
    participant BG as Background Script
    participant API as PRIV API

    User->>Popup: Click "Connect Wallet"
    Popup->>Wallet: eth_requestAccounts
    Wallet-->>User: Approve Connection
    User->>Wallet: Approve
    Wallet-->>Popup: Return Address
    Popup->>BG: WALLET_CONNECTED
    BG->>API: Register/Verify Wallet
    API-->>BG: Success
    BG-->>Popup: Connection Complete

Step-by-Step

  1. User Initiates: Click "Connect Wallet" in the extension
  2. Wallet Prompt: Your wallet opens a connection request
  3. User Approves: Approve the connection in your wallet
  4. Address Retrieved: Extension receives your wallet address
  5. Backend Sync: Address is registered with PRIV servers
  6. Ready: You can now receive earnings

Connecting Your Wallet

Prerequisites

  • A Web3 wallet extension installed (MetaMask recommended)
  • At least one account in your wallet
  • The wallet should be on a supported network (Base recommended)

Connection Steps

  1. Open the PRIV extension by clicking its icon
  2. Click the Connect Wallet button in the header
  3. Your wallet extension will open with a connection request
  4. Select the account you want to use
  5. Click Connect in your wallet
  6. Your address will appear in the extension header

[Screenshot: Extension showing Connect Wallet button and MetaMask approval popup]


Code Implementation

Checking Wallet Availability

/**
 * Check if wallet provider is available
 */
function isWalletAvailable(): boolean {
  return typeof window !== 'undefined' && !!window.ethereum;
}

Requesting Connection

/**
 * Request wallet connection
 */
async function connectWallet(): Promise<WalletState> {
  const provider = window.ethereum;

  if (!provider) {
    return {
      isConnected: false,
      address: null,
      error: 'No wallet found. Please install MetaMask.',
    };
  }

  try {
    // Request account access
    const accounts = await provider.request({
      method: 'eth_requestAccounts',
    }) as string[];

    // Get current chain
    const chainIdHex = await provider.request({
      method: 'eth_chainId',
    }) as string;

    const chainId = parseInt(chainIdHex, 16);
    const address = accounts[0] ?? null;

    return {
      isConnected: true,
      address,
      chainId,
      error: null,
    };
  } catch (error) {
    return {
      isConnected: false,
      address: null,
      error: error.message,
    };
  }
}

Wallet State Interface

interface WalletState {
  isConnected: boolean;
  address: string | null;
  chainId: number | null;
  isConnecting: boolean;
  error: string | null;
}

Account Change Handling

The extension automatically handles account changes in your wallet.

Listening for Changes

/**
 * Listen for account changes
 */
function onAccountsChanged(
  callback: (accounts: string[]) => void
): () => void {
  const provider = window.ethereum;

  if (!provider) {
    return () => {};
  }

  const handler = (accounts: unknown) => {
    callback(accounts as string[]);
  };

  provider.on('accountsChanged', handler);

  // Return unsubscribe function
  return () => provider.removeListener('accountsChanged', handler);
}

Handling Account Changes

When you switch accounts in your wallet:

  1. Extension detects the change via accountsChanged event
  2. New address is validated and sanitized
  3. Settings are updated with new address
  4. Background script is notified
  5. Cache is invalidated to refresh data
  6. UI updates to show new account
// In the popup App component
useEffect(() => {
  const unsubscribe = onAccountsChanged(async (accounts) => {
    if (accounts.length === 0) {
      // Wallet disconnected
      await handleDisconnect();
    } else if (accounts[0] !== currentAddress) {
      // Account changed
      await handleAccountChange(accounts[0]);
    }
  });

  return () => unsubscribe();
}, []);

Address Validation

All wallet addresses are validated before use.

Validation Function

/**
 * Validate and sanitize wallet address
 */
function sanitizeWalletAddress(
  address: string | null | undefined
): string | null {
  if (!address) return null;

  const trimmed = address.trim();

  // Validate Ethereum address format
  if (!/^0x[a-fA-F0-9]{40}$/.test(trimmed)) {
    return null;
  }

  // Normalize to lowercase
  return trimmed.toLowerCase();
}

Validation Rules

RuleDescription
PrefixMust start with 0x
LengthExactly 42 characters
CharactersOnly hexadecimal (0-9, a-f)
CaseNormalized to lowercase

Disconnect Process

User-Initiated Disconnect

  1. Open extension settings or header menu
  2. Click Disconnect Wallet
  3. Confirm disconnection
  4. Wallet address is cleared from storage
  5. Data sharing is automatically disabled

Code Flow

/**
 * Disconnect wallet
 */
async function handleDisconnectWallet() {
  // Clear wallet from local storage
  await clearWalletFromStorage();

  // Update settings
  await updateSettings({
    walletAddress: null,
    dataSharingEnabled: false
  });

  // Notify background script
  await chrome.runtime.sendMessage({ type: 'WALLET_DISCONNECTED' });

  // Reset earnings display
  setEarnings(EMPTY_EARNINGS);
  setBalance(EMPTY_BALANCE);
}

What Gets Cleared

  • Wallet address from settings
  • Cached earnings data
  • Sync state
  • Data sharing preference

What Remains

  • Collected data (local, unsubmitted)
  • Other settings (notifications, etc.)
  • Server-side earnings (accessible with same wallet)

Network Handling

Supported Networks

NetworkChain IDStatus
Base8453Production
Base Sepolia84532Testnet

Chain Detection

/**
 * Get current chain ID
 */
async function getChainId(): Promise<number | null> {
  const provider = window.ethereum;

  if (!provider) return null;

  try {
    const chainIdHex = await provider.request({
      method: 'eth_chainId',
    }) as string;

    return parseInt(chainIdHex, 16);
  } catch {
    return null;
  }
}

Switching Networks

/**
 * Switch to Base Sepolia network
 */
async function switchToBaseSepolia(): Promise<boolean> {
  const provider = window.ethereum;
  const BASE_SEPOLIA_CHAIN_ID = '0x14a34'; // 84532

  try {
    await provider.request({
      method: 'wallet_switchEthereumChain',
      params: [{ chainId: BASE_SEPOLIA_CHAIN_ID }],
    });
    return true;
  } catch (error) {
    // Chain not added - add it
    if (error.code === 4902) {
      await provider.request({
        method: 'wallet_addEthereumChain',
        params: [{
          chainId: BASE_SEPOLIA_CHAIN_ID,
          chainName: 'Base Sepolia',
          nativeCurrency: {
            name: 'Ethereum',
            symbol: 'ETH',
            decimals: 18,
          },
          rpcUrls: ['https://sepolia.base.org'],
          blockExplorerUrls: ['https://sepolia.basescan.org'],
        }],
      });
      return true;
    }
    return false;
  }
}

Wallet Verification

For enhanced security, users can verify wallet ownership.

Verification Flow

sequenceDiagram
    participant User
    participant Extension
    participant Wallet
    participant API

    Extension->>Extension: Generate Message
    Extension->>Wallet: Request Signature
    Wallet-->>User: Sign Request
    User->>Wallet: Approve
    Wallet-->>Extension: Signature
    Extension->>API: Verify Signature
    API-->>Extension: Auth Token

Verification Message

function generateVerificationMessage(address: string): string {
  const timestamp = Date.now();
  const nonce = Math.random().toString(36).substring(2, 15);

  return `PRIV Protocol Wallet Verification

Address: ${address}
Timestamp: ${timestamp}
Nonce: ${nonce}

By signing this message, you verify ownership of this wallet address for use with PRIV Protocol.

This signature does not authorize any blockchain transactions.`;
}

Signing Process

async function signMessage(
  message: string,
  address: string
): Promise<{ signature: string | null; error: string | null }> {
  const provider = window.ethereum;

  try {
    const signature = await provider.request({
      method: 'personal_sign',
      params: [message, address],
    }) as string;

    return { signature, error: null };
  } catch (error) {
    return { signature: null, error: error.message };
  }
}

Display Formatting

Address Formatting

/**
 * Format wallet address for display
 */
function formatAddress(address: string): string {
  if (address.length < 10) return address;
  return `${address.slice(0, 6)}...${address.slice(-4)}`;
}

// Example: 0x1234...5678

Balance Formatting

/**
 * Format token amount with decimals
 */
function formatTokenAmount(amount: bigint, decimals = 18): string {
  const divisor = BigInt(10 ** decimals);
  const wholePart = amount / divisor;
  const fractionalPart = amount % divisor;

  if (fractionalPart === BigInt(0)) {
    return wholePart.toString();
  }

  const fractionalStr = fractionalPart
    .toString()
    .padStart(decimals, '0')
    .slice(0, 4)
    .replace(/0+$/, '');

  return fractionalStr
    ? `${wholePart}.${fractionalStr}`
    : wholePart.toString();
}

// Example: 1234.5678 PRIV

Error Handling

Common Errors

ErrorCauseSolution
No wallet foundNo provider injectedInstall MetaMask
User rejectedUser denied connectionTry again
Already pendingConnection request openCheck wallet
UnauthorizedAccount not connectedReconnect

Error Handling Code

try {
  const walletState = await connectWallet();

  if (walletState.error) {
    if (walletState.error.includes('User rejected')) {
      showMessage('Connection cancelled');
    } else if (walletState.error.includes('No wallet')) {
      showMessage('Please install MetaMask');
    } else {
      showMessage(`Error: ${walletState.error}`);
    }
  }
} catch (error) {
  console.error('Wallet connection failed:', error);
}

Security Best Practices

Do

  • Always validate addresses before use
  • Show abbreviated addresses to users
  • Handle account changes promptly
  • Clear sensitive data on disconnect

Don't

  • Store private keys
  • Request unnecessary permissions
  • Sign transactions without user consent
  • Trust unvalidated addresses

Next Steps