Skip to content

Deposit Widget

Minimal deposit-only widget using the lightweight shield module (~150KB).

Use Case

Perfect for:

  • Payment links
  • Donation buttons
  • Onramp integration
  • Embedded deposit forms

HTML + Vanilla JS

html
<!DOCTYPE html>
<html>
<head>
  <title>Deposit Widget</title>
  <script type="module">
    import { shieldTo, DEPLOYMENTS } from 'https://esm.sh/@zkprivacy/sdk/shield';
    import { createWalletClient, custom, parseUnits } from 'https://esm.sh/viem';
    
    window.deposit = async function() {
      const recipient = document.getElementById('recipient').value;
      const amount = document.getElementById('amount').value;
      const status = document.getElementById('status');
      
      if (!window.ethereum) {
        status.textContent = 'Please install MetaMask';
        return;
      }
      
      try {
        status.textContent = 'Connecting wallet...';
        
        const [account] = await window.ethereum.request({ 
          method: 'eth_requestAccounts' 
        });
        
        const walletClient = createWalletClient({
          transport: custom(window.ethereum),
        });
        
        status.textContent = 'Processing deposit...';
        
        const result = await shieldTo(
          {
            rpcUrl: DEPLOYMENTS.remote.rpcUrl,
            poolAddress: DEPLOYMENTS.remote.pool,
          },
          {
            recipient,
            amount,
            tokenAddress: DEPLOYMENTS.remote.tokens.zkUSD,
            walletClient,
          }
        );
        
        status.textContent = `Success! TX: ${result.txHash.slice(0, 20)}...`;
      } catch (error) {
        status.textContent = `Error: ${error.message}`;
      }
    }
  </script>
</head>
<body>
  <h1>Deposit USDC</h1>
  
  <input id="recipient" placeholder="Recipient (zks1...)" style="width: 100%">
  <br><br>
  
  <input id="amount" placeholder="Amount (e.g., 100)" type="number">
  <br><br>
  
  <button onclick="deposit()">Deposit</button>
  
  <p id="status"></p>
</body>
</html>

React Component

tsx
// components/DepositWidget.tsx
import { useState } from 'react';
import { shieldTo, DEPLOYMENTS } from '@zkprivacy/sdk/shield';
import { createWalletClient, custom } from 'viem';

interface DepositWidgetProps {
  recipient: string;
  token?: 'zkUSD' | 'zkEUR' | 'zkPLN';
  onSuccess?: (txHash: string) => void;
  onError?: (error: Error) => void;
}

export function DepositWidget({ 
  recipient, 
  token = 'zkUSD',
  onSuccess,
  onError,
}: DepositWidgetProps) {
  const [amount, setAmount] = useState('');
  const [status, setStatus] = useState<'idle' | 'connecting' | 'depositing' | 'success' | 'error'>('idle');
  const [txHash, setTxHash] = useState<string | null>(null);

  const handleDeposit = async () => {
    if (!amount || !window.ethereum) return;

    try {
      setStatus('connecting');
      
      await window.ethereum.request({ method: 'eth_requestAccounts' });
      
      const walletClient = createWalletClient({
        transport: custom(window.ethereum),
      });

      setStatus('depositing');

      const result = await shieldTo(
        {
          rpcUrl: DEPLOYMENTS.remote.rpcUrl,
          poolAddress: DEPLOYMENTS.remote.pool,
        },
        {
          recipient,
          amount,
          tokenAddress: DEPLOYMENTS.remote.tokens[token],
          walletClient,
        }
      );

      setTxHash(result.txHash);
      setStatus('success');
      onSuccess?.(result.txHash);
    } catch (error) {
      setStatus('error');
      onError?.(error as Error);
    }
  };

  return (
    <div className="deposit-widget">
      <h3>Deposit {token}</h3>
      
      <input
        type="number"
        value={amount}
        onChange={(e) => setAmount(e.target.value)}
        placeholder="Amount"
        disabled={status === 'connecting' || status === 'depositing'}
      />
      
      <button 
        onClick={handleDeposit}
        disabled={!amount || status === 'connecting' || status === 'depositing'}
      >
        {status === 'connecting' && 'Connecting...'}
        {status === 'depositing' && 'Depositing...'}
        {status === 'idle' && 'Deposit'}
        {status === 'success' && 'Done!'}
        {status === 'error' && 'Try Again'}
      </button>
      
      {txHash && (
        <a href={`https://etherscan.io/tx/${txHash}`} target="_blank">
          View Transaction
        </a>
      )}
    </div>
  );
}

Generate payment links for recipients:

typescript
// utils/payment-link.ts
export function generatePaymentLink(
  recipient: string,
  amount?: string,
  token?: string,
): string {
  const baseUrl = 'https://pay.yourapp.com';
  const params = new URLSearchParams();
  
  params.set('to', recipient);
  if (amount) params.set('amount', amount);
  if (token) params.set('token', token);
  
  return `${baseUrl}?${params.toString()}`;
}

// Usage
const link = generatePaymentLink('zks1abc...', '100', 'zkUSD');
// https://pay.yourapp.com?to=zks1abc...&amount=100&token=zkUSD

Payment Page

tsx
// pages/pay.tsx
import { useSearchParams } from 'react-router-dom';
import { DepositWidget } from '../components/DepositWidget';

export function PaymentPage() {
  const [params] = useSearchParams();
  
  const recipient = params.get('to');
  const amount = params.get('amount');
  const token = params.get('token') as 'zkUSD' | 'zkEUR' | 'zkPLN';

  if (!recipient) {
    return <p>Invalid payment link</p>;
  }

  return (
    <div>
      <h1>Send Payment</h1>
      <p>To: {recipient.slice(0, 20)}...</p>
      {amount && <p>Amount: {amount} {token || 'zkUSD'}</p>}
      
      <DepositWidget 
        recipient={recipient}
        token={token}
        onSuccess={(tx) => {
          alert('Payment sent! TX: ' + tx);
        }}
      />
    </div>
  );
}

Bundle Size Comparison

ImportSize
@zkprivacy/sdk/shield~150KB
@zkprivacy/sdk~2MB

For deposit-only widgets, always use the shield submodule!

Released under the MIT License.