Skip to main content

Transaction History

This guide covers querying and filtering transaction history using the Privacy Boost React SDK’s useTransactions hook.

Basic Usage

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

function TransactionList() {
  const { transactions, isLoading, fetchHistory } = useTransactions();

  useEffect(() => {
    fetchHistory();
  }, []);

  if (isLoading) return <div>Loading...</div>;

  return (
    <ul>
      {transactions.map((tx) => (
        <li key={tx.txHash}>
          {tx.type}: {tx.amount?.toString() ?? '-'}
        </li>
      ))}
    </ul>
  );
}

Return Value

interface UseTransactionsResult {
  // Reactive data (locally-tracked pending transactions)
  transactions: PendingTransaction[];
  recentTransactions: PendingTransaction[];
  pending: PendingTransaction[];
  isLoading: boolean;
  hasPending: boolean;
  pendingCount: number;

  // Filtered views
  deposits: PendingTransaction[];
  withdrawals: PendingTransaction[];
  transfers: PendingTransaction[];

  // Actions
  fetchHistory(params?: FetchHistoryParams): Promise<TransactionsResponse>;
  refreshPending(): Promise<number>;
  getByHash(txHash: string): PendingTransaction | undefined;
  getByType(type: TransactionType): PendingTransaction[];
  getByStatus(status: TransactionStatus): PendingTransaction[];
}

Transaction Types

interface TransactionNote {
  value: string;
  direction: string;
  isChange: boolean;
  leafIndex?: number;
  nullifier?: string;
  counterParty?: string;
  counterPartyType?: string;
}

interface Transaction {
  txHash: string;
  txType: string;       // 'deposit' | 'withdraw' | 'transfer'
  direction: string;    // 'incoming' | 'outgoing'
  value: string;
  tokenId: number;
  createdAt: number;
  counterparty?: string;
  counterpartyType?: string;
  notes: TransactionNote[];
}

interface TransactionsResponse {
  data: Transaction[];
  limit: number;
  next?: string;
}

Fetching History

Basic Fetch

const { fetchHistory } = useTransactions();

useEffect(() => {
  fetchHistory();
}, []);

With Filters

// Only deposits
await fetchHistory({ txType: 'deposit' });

// Limited results
await fetchHistory({ limit: 10 });

// Cursor-based pagination
await fetchHistory({ limit: 10, cursor: 'next-cursor-value' });

Filter Parameters

interface FetchHistoryParams {
  txType?: string;
  limit?: number;
  cursor?: string;
}

Complete Example

import { useState, useEffect } from 'react';
import { useTransactions } from '@testinprod-io/privacy-boost-react';

function TransactionHistory() {
  const { transactions, isLoading, fetchHistory } = useTransactions();
  const [filter, setFilter] = useState<string>('all');

  useEffect(() => {
    if (filter === 'all') {
      fetchHistory({ limit: 50 });
    } else {
      fetchHistory({ txType: filter, limit: 50 });
    }
  }, [filter, fetchHistory]);

  const formatAmount = (amount: bigint, decimals = 18) => {
    return (Number(amount) / 10 ** decimals).toFixed(4);
  };

  const formatDate = (timestamp: number) => {
    return new Date(timestamp).toLocaleDateString();
  };

  return (
    <div>
      {/* Filter Tabs */}
      <div className="tabs">
        {['all', 'deposit', 'withdraw', 'transfer'].map((type) => (
          <button
            key={type}
            onClick={() => setFilter(type)}
            className={filter === type ? 'active' : ''}
          >
            {type.charAt(0).toUpperCase() + type.slice(1)}
          </button>
        ))}
      </div>

      {/* Loading State */}
      {isLoading && <div>Loading transactions...</div>}

      {/* Transaction List */}
      {!isLoading && transactions.length === 0 && (
        <p>No transactions found</p>
      )}

      <table>
        <thead>
          <tr>
            <th>Type</th>
            <th>Amount</th>
            <th>Status</th>
            <th>Date</th>
          </tr>
        </thead>
        <tbody>
          {transactions.map((tx) => (
            <tr key={tx.txHash}>
              <td>
                <span className={`type-${tx.type}`}>
                  {tx.type}
                </span>
                {tx.direction === 'incoming' && ' (received)'}
              </td>
              <td>{tx.amount ? formatAmount(tx.amount) : '-'}</td>
              <td>
                <span className={`status-${tx.status}`}>
                  {tx.status}
                </span>
              </td>
              <td>{formatDate(tx.createdAt)}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

Transaction Details Modal

function TransactionDetail({ tx }: { tx: Transaction }) {
  return (
    <div className="tx-detail">
      <h3>{tx.txType.toUpperCase()}</h3>

      <div className="row">
        <label>Direction</label>
        <span>{tx.direction}</span>
      </div>

      <div className="row">
        <label>Value</label>
        <span>{tx.value}</span>
      </div>

      <div className="row">
        <label>Transaction Hash</label>
        <a
          href={`https://etherscan.io/tx/${tx.txHash}`}
          target="_blank"
          rel="noopener noreferrer"
        >
          {tx.txHash.slice(0, 20)}...
        </a>
      </div>

      {tx.counterparty && (
        <div className="row">
          <label>Counterparty</label>
          <span>{tx.counterparty.slice(0, 20)}...</span>
        </div>
      )}

      <div className="row">
        <label>Date</label>
        <span>{new Date(tx.createdAt).toLocaleString()}</span>
      </div>

      {tx.notes.length > 0 && (
        <div className="notes">
          <label>Notes ({tx.notes.length})</label>
          {tx.notes.map((note, i) => (
            <div key={i}>
              {note.direction}: {note.value}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

Refresh Transactions

function RefreshableHistory() {
  const { transactions, isLoading, fetchHistory } = useTransactions();
  const [lastRefresh, setLastRefresh] = useState<Date | null>(null);

  const handleRefresh = async () => {
    await fetchHistory();
    setLastRefresh(new Date());
  };

  return (
    <div>
      <div className="header">
        <h3>Transaction History</h3>
        <button onClick={handleRefresh} disabled={isLoading}>
          {isLoading ? 'Refreshing...' : 'Refresh'}
        </button>
      </div>

      {lastRefresh && (
        <small>Last refreshed: {lastRefresh.toLocaleTimeString()}</small>
      )}

      {/* Transaction list */}
    </div>
  );
}

Next Steps