Skip to content

📋 Examples

Complete examples demonstrating different TxGraph use cases and integration patterns.

Basic Graph Visualization

Simple transaction graph with minimal setup:

tsx
import { GraphExplorer } from '@trustin/txgraph'
import type { TxNode, TxEdge } from '@trustin/txgraph'

const nodes: TxNode[] = [
  {
    address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
    depth: 0,
    is_root: true,
    risk_level: 'low',
    tags: [{ 
      name: 'Vitalik Buterin',
      primary_category: 'Individual',
      secondary_category: 'Public Figure'
    }],
    total_neighbors: 1,
    visible_neighbors: 1,
    is_stopped: false,
    chain: 'Ethereum'
  },
  {
    address: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
    depth: 1,
    is_root: false,
    risk_level: 'high',
    risk_score: 85,
    tags: [{ 
      name: 'Tornado Cash',
      primary_category: 'Mixer',
      secondary_category: 'Privacy Protocol'
    }],
    total_neighbors: 2,
    visible_neighbors: 1,
    is_stopped: true,
    stop_reason: 'Sanctioned entity',
    chain: 'Ethereum'
  }
]

const edges: TxEdge[] = [
  {
    from: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
    to: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e',
    direction: 'out',
    amount: '1500000000000000000',
    formatted_amount: '1.5 ETH',
    last_timestamp: 1704067200,
    tx_count: 1,
    token: 'ETH'
  }
]

export function BasicExample() {
  const handleNodeSelect = (node: TxNode | null) => {
    if (node) {
      console.log(`Selected: ${node.address}`)
      console.log(`Risk: ${node.risk_level}`)
    } else {
      console.log('Deselected')
    }
  }

  return (
    <div style={{ width: '100%', height: '600px' }}>
      <GraphExplorer
        nodes={nodes}
        edges={edges}
        onNodeSelect={handleNodeSelect}
        onNodeExpand={(address) => console.log('Expand:', address)}
        onNodeDelete={(address) => console.log('Delete:', address)}
      />
    </div>
  )
}

Search and Filter Dashboard

Interactive dashboard with search, filtering, and export capabilities:

tsx
import { 
  GraphExplorer, 
  GraphControlPanel, 
  ExportToolbar 
} from '@trustin/txgraph'
import { useState, useRef } from 'react'

export function SearchFilterDashboard() {
  const containerRef = useRef<HTMLDivElement>(null)
  const [originalNodes, setOriginalNodes] = useState(nodes)
  const [originalEdges, setOriginalEdges] = useState(edges)
  const [filteredNodes, setFilteredNodes] = useState(nodes)
  const [filteredEdges, setFilteredEdges] = useState(edges)
  const [selectedNode, setSelectedNode] = useState<TxNode | null>(null)

  const stats = {
    total_nodes: originalNodes.length,
    total_edges: originalEdges.length,
    max_depth_reached: Math.max(...originalNodes.map(n => n.depth)),
    stopped_nodes: originalNodes.filter(n => n.is_stopped).length
  }

  return (
    <div style={{ 
      display: 'grid', 
      gridTemplateColumns: '1fr 320px', 
      gap: '16px', 
      height: '100vh',
      padding: '16px'
    }}>
      {/* Main graph area */}
      <div 
        ref={containerRef} 
        style={{ position: 'relative', backgroundColor: '#f9fafb', borderRadius: '8px' }}
      >
        <GraphExplorer
          nodes={filteredNodes}
          edges={filteredEdges}
          stats={stats}
          selectedAddress={selectedNode?.address}
          onNodeSelect={setSelectedNode}
        />
        
        {/* Export toolbar */}
        <div style={{ position: 'absolute', top: 16, right: 16, zIndex: 10 }}>
          <ExportToolbar
            nodes={originalNodes}
            edges={originalEdges}
            stats={stats}
            containerRef={containerRef}
          />
        </div>
        
        {/* Selection info */}
        {selectedNode && (
          <div style={{
            position: 'absolute',
            top: 16,
            left: 16,
            background: 'white',
            padding: '12px',
            borderRadius: '8px',
            boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)',
            maxWidth: '300px'
          }}>
            <h3 style={{ margin: 0, fontSize: '14px', fontWeight: 600 }}>
              Selected Address
            </h3>
            <p style={{ margin: '4px 0', fontFamily: 'monospace', fontSize: '12px' }}>
              {selectedNode.address.slice(0, 10)}...{selectedNode.address.slice(-8)}
            </p>
            <div style={{ display: 'flex', gap: '8px', fontSize: '12px' }}>
              <span style={{
                padding: '2px 6px',
                borderRadius: '4px',
                backgroundColor: selectedNode.risk_level === 'high' ? '#fef2f2' : 
                                selectedNode.risk_level === 'medium' ? '#fffbeb' :
                                selectedNode.risk_level === 'low' ? '#f0fdf4' : '#f9fafb',
                color: selectedNode.risk_level === 'high' ? '#dc2626' :
                       selectedNode.risk_level === 'medium' ? '#d97706' :
                       selectedNode.risk_level === 'low' ? '#16a34a' : '#6b7280'
              }}>
                {selectedNode.risk_level.toUpperCase()}
              </span>
              <span style={{ color: '#6b7280' }}>
                Depth: {selectedNode.depth}
              </span>
            </div>
            {selectedNode.tags.length > 0 && (
              <div style={{ marginTop: '8px' }}>
                <div style={{ fontSize: '12px', fontWeight: 500, marginBottom: '4px' }}>
                  Tags:
                </div>
                {selectedNode.tags.map((tag, i) => (
                  <span
                    key={i}
                    style={{
                      display: 'inline-block',
                      padding: '2px 6px',
                      margin: '2px 2px 2px 0',
                      backgroundColor: '#f3f4f6',
                      borderRadius: '4px',
                      fontSize: '11px',
                      color: '#374151'
                    }}
                  >
                    {tag.name || tag.primary_category}
                  </span>
                ))}
              </div>
            )}
          </div>
        )}
      </div>

      {/* Control panel */}
      <div style={{ 
        display: 'flex', 
        flexDirection: 'column', 
        gap: '16px',
        overflow: 'hidden'
      }}>
        <GraphControlPanel
          nodes={originalNodes}
          edges={originalEdges}
          stats={stats}
          onNodeSelect={setSelectedNode}
          onFilterChange={(nodes, edges) => {
            setFilteredNodes(nodes)
            setFilteredEdges(edges)
          }}
        />
      </div>
    </div>
  )
}

Cluster Analysis Example

Advanced pattern detection and anomaly analysis:

tsx
import { 
  GraphExplorer, 
  ClusterAnalysis, 
  SearchBar 
} from '@trustin/txgraph'
import { useState, useEffect } from 'react'

export function ClusterAnalysisExample() {
  const [nodes, setNodes] = useState(sampleNodes)
  const [edges, setEdges] = useState(sampleEdges)
  const [highlightedNodes, setHighlightedNodes] = useState<string[]>([])
  const [selectedCluster, setSelectedCluster] = useState(null)
  const [searchHighlight, setSearchHighlight] = useState<string[]>([])

  // Combine highlights from cluster analysis and search
  const processedNodes = nodes.map(node => ({
    ...node,
    // Add visual indicators for highlights
    className: [
      highlightedNodes.includes(node.address) && 'cluster-highlight',
      searchHighlight.includes(node.address) && 'search-highlight'
    ].filter(Boolean).join(' ')
  }))

  const handleSearchSelect = (result) => {
    if (result.type === 'node') {
      setSearchHighlight([result.item.address])
      // Auto-scroll to node or similar behavior
    } else {
      // Handle edge selection
      setSearchHighlight([result.item.from, result.item.to])
    }
  }

  const handleClusterSelect = (cluster) => {
    setSelectedCluster(cluster)
    if (cluster) {
      setHighlightedNodes(cluster.nodes.map(n => n.address))
    } else {
      setHighlightedNodes([])
    }
  }

  const clearHighlights = () => {
    setHighlightedNodes([])
    setSearchHighlight([])
    setSelectedCluster(null)
  }

  return (
    <div style={{ 
      display: 'grid', 
      gridTemplateColumns: '1fr 360px', 
      gap: '20px',
      height: '100vh',
      padding: '20px'
    }}>
      {/* Main visualization */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
        {/* Search bar */}
        <div style={{ display: 'flex', gap: '12px', alignItems: 'center' }}>
          <div style={{ flex: 1 }}>
            <SearchBar
              nodes={nodes}
              edges={edges}
              onResultSelect={handleSearchSelect}
              onClear={() => setSearchHighlight([])}
              placeholder="Search addresses, risk levels, or entity tags..."
            />
          </div>
          <button 
            onClick={clearHighlights}
            style={{
              padding: '8px 16px',
              background: '#f3f4f6',
              border: '1px solid #d1d5db',
              borderRadius: '6px',
              fontSize: '14px',
              cursor: 'pointer'
            }}
          >
            Clear Highlights
          </button>
        </div>

        {/* Graph */}
        <div style={{ flex: 1, background: '#fafafa', borderRadius: '8px' }}>
          <GraphExplorer
            nodes={processedNodes}
            edges={edges}
            selectedAddress={selectedCluster?.centroid}
          />
        </div>

        {/* Cluster info */}
        {selectedCluster && (
          <div style={{
            background: 'white',
            padding: '16px',
            borderRadius: '8px',
            border: '1px solid #e5e7eb'
          }}>
            <h3 style={{ margin: '0 0 12px 0', fontSize: '16px' }}>
              {selectedCluster.type.charAt(0).toUpperCase() + selectedCluster.type.slice(1)} Cluster
            </h3>
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '12px', fontSize: '14px' }}>
              <div>
                <div style={{ color: '#6b7280', fontSize: '12px' }}>Nodes</div>
                <div style={{ fontWeight: 600 }}>{selectedCluster.nodes.length}</div>
              </div>
              <div>
                <div style={{ color: '#6b7280', fontSize: '12px' }}>Risk</div>
                <div style={{ 
                  fontWeight: 600,
                  color: selectedCluster.riskLevel === 'high' ? '#dc2626' :
                         selectedCluster.riskLevel === 'medium' ? '#d97706' :
                         selectedCluster.riskLevel === 'low' ? '#16a34a' : '#6b7280'
                }}>
                  {selectedCluster.riskLevel.toUpperCase()}
                </div>
              </div>
              <div>
                <div style={{ color: '#6b7280', fontSize: '12px' }}>Total Value</div>
                <div style={{ fontWeight: 600 }}>${selectedCluster.totalValue.toLocaleString()}</div>
              </div>
              <div>
                <div style={{ color: '#6b7280', fontSize: '12px' }}>Confidence</div>
                <div style={{ fontWeight: 600 }}>{(selectedCluster.confidence * 100).toFixed(0)}%</div>
              </div>
            </div>
            <div style={{ marginTop: '12px', fontSize: '13px', color: '#6b7280' }}>
              Centroid: <code>{selectedCluster.centroid.slice(0, 10)}...{selectedCluster.centroid.slice(-8)}</code>
            </div>
          </div>
        )}
      </div>

      {/* Analysis panel */}
      <div>
        <ClusterAnalysis
          nodes={nodes}
          edges={edges}
          onClusterSelect={handleClusterSelect}
          onHighlightNodes={setHighlightedNodes}
        />
      </div>
    </div>
  )
}

Real-Time Monitoring Dashboard

Live transaction monitoring with WebSocket integration:

tsx
import { 
  GraphExplorer,
  RealTimeManager,
  FilterPanel
} from '@trustin/txgraph'
import { useState, useCallback, useEffect } from 'react'

export function RealTimeMonitoringExample() {
  const [nodes, setNodes] = useState(initialNodes)
  const [edges, setEdges] = useState(initialEdges)
  const [watchList, setWatchList] = useState([
    '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
    '0x742d35Cc6634C0532925a3b844Bc454e4438f44e'
  ])
  const [recentUpdates, setRecentUpdates] = useState([])
  const [alertMode, setAlertMode] = useState(true)

  const handleRealTimeUpdate = useCallback((update) => {
    // Add to recent updates list
    setRecentUpdates(prev => [
      { ...update, id: Date.now() },
      ...prev.slice(0, 99) // Keep last 100 updates
    ])

    switch (update.type) {
      case 'new_transaction':
        console.log('New transaction detected:', update.data)
        
        // Show alert for high-value transactions
        if (alertMode && parseFloat(update.data.amount) > 1000000) {
          if ('Notification' in window && Notification.permission === 'granted') {
            new Notification('High Value Transaction', {
              body: `${update.data.formatted_amount} from ${update.address?.slice(0, 10)}...`,
              icon: '⚠️'
            })
          }
        }
        
        // Potentially add new edges to graph
        // This would require implementing proper graph update logic
        break

      case 'risk_update':
        console.log('Risk level updated:', update.address, update.data.riskLevel)
        
        // Update node risk levels
        setNodes(prev => prev.map(node => 
          node.address === update.address 
            ? { ...node, risk_level: update.data.riskLevel, risk_score: update.data.riskScore }
            : node
        ))
        
        // Alert on new high-risk addresses
        if (alertMode && update.data.riskLevel === 'high') {
          if ('Notification' in window && Notification.permission === 'granted') {
            new Notification('High Risk Alert', {
              body: `Address ${update.address?.slice(0, 10)}... flagged as high risk`,
              icon: '🚨'
            })
          }
        }
        break

      case 'graph_update':
        console.log('Graph structure updated')
        // Handle graph structure changes
        if (update.data.nodes) {
          setNodes(prev => [...prev, ...update.data.nodes])
        }
        if (update.data.edges) {
          setEdges(prev => [...prev, ...update.data.edges])
        }
        break
    }
  }, [alertMode])

  // Request notification permission on mount
  useEffect(() => {
    if ('Notification' in window && Notification.permission === 'default') {
      Notification.requestPermission()
    }
  }, [])

  return (
    <div style={{
      display: 'grid',
      gridTemplateRows: '1fr auto',
      gap: '16px',
      height: '100vh',
      padding: '16px'
    }}>
      {/* Main graph area */}
      <div style={{ display: 'flex', gap: '16px', minHeight: 0 }}>
        <div style={{ flex: 1, background: '#f8fafc', borderRadius: '8px' }}>
          <GraphExplorer nodes={nodes} edges={edges} />
        </div>
        
        {/* Monitoring sidebar */}
        <div style={{ width: '320px', display: 'flex', flexDirection: 'column', gap: '16px' }}>
          <RealTimeManager
            wsUrl={process.env.REACT_APP_WS_URL || 'wss://api.example.com/ws'}
            watchedAddresses={watchList}
            onUpdate={handleRealTimeUpdate}
          />
          
          {/* Watch list management */}
          <div style={{
            background: 'white',
            border: '1px solid #e5e7eb',
            borderRadius: '8px',
            padding: '12px'
          }}>
            <h3 style={{ margin: '0 0 12px 0', fontSize: '14px', fontWeight: 600 }}>
              Watch List ({watchList.length})
            </h3>
            <div style={{ maxHeight: '200px', overflowY: 'auto' }}>
              {watchList.map((address, index) => (
                <div
                  key={address}
                  style={{
                    display: 'flex',
                    justifyContent: 'space-between',
                    alignItems: 'center',
                    padding: '6px 0',
                    borderBottom: index < watchList.length - 1 ? '1px solid #f3f4f6' : 'none'
                  }}
                >
                  <code style={{ fontSize: '11px', color: '#6b7280' }}>
                    {address.slice(0, 8)}...{address.slice(-6)}
                  </code>
                  <button
                    onClick={() => setWatchList(prev => prev.filter(a => a !== address))}
                    style={{
                      background: 'none',
                      border: 'none',
                      color: '#ef4444',
                      cursor: 'pointer',
                      fontSize: '12px'
                    }}
                  >
                    ×
                  </button>
                </div>
              ))}
            </div>
            
            <div style={{ marginTop: '12px', display: 'flex', alignItems: 'center', gap: '8px' }}>
              <label style={{ display: 'flex', alignItems: 'center', gap: '6px', fontSize: '13px' }}>
                <input
                  type="checkbox"
                  checked={alertMode}
                  onChange={(e) => setAlertMode(e.target.checked)}
                />
                Browser alerts
              </label>
            </div>
          </div>

          {/* Recent updates */}
          <div style={{
            background: 'white',
            border: '1px solid #e5e7eb',
            borderRadius: '8px',
            padding: '12px',
            flex: 1,
            minHeight: 0
          }}>
            <h3 style={{ margin: '0 0 12px 0', fontSize: '14px', fontWeight: 600 }}>
              Recent Updates ({recentUpdates.length})
            </h3>
            <div style={{ maxHeight: '100%', overflowY: 'auto' }}>
              {recentUpdates.length === 0 ? (
                <div style={{ color: '#6b7280', fontSize: '13px', textAlign: 'center', padding: '20px 0' }}>
                  No updates yet
                </div>
              ) : (
                recentUpdates.slice(0, 20).map((update, index) => (
                  <div
                    key={update.id}
                    style={{
                      padding: '8px 0',
                      borderBottom: index < Math.min(recentUpdates.length, 20) - 1 ? '1px solid #f3f4f6' : 'none'
                    }}
                  >
                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                      <span style={{
                        fontSize: '12px',
                        fontWeight: 500,
                        color: update.type === 'new_transaction' ? '#059669' :
                               update.type === 'risk_update' ? '#dc2626' :
                               '#6b7280'
                      }}>
                        {update.type.replace('_', ' ').toUpperCase()}
                      </span>
                      <span style={{ fontSize: '11px', color: '#9ca3af' }}>
                        {new Date(update.timestamp).toLocaleTimeString()}
                      </span>
                    </div>
                    {update.address && (
                      <div style={{ fontSize: '11px', color: '#6b7280', fontFamily: 'monospace', marginTop: '2px' }}>
                        {update.address.slice(0, 10)}...{update.address.slice(-8)}
                      </div>
                    )}
                  </div>
                ))
              )}
            </div>
          </div>
        </div>
      </div>
    </div>
  )
}

Large Graph Performance Example

Optimized handling for large datasets with the Sigma.js renderer:

tsx
import { 
  GraphExplorerSigma, 
  FilterPanel,
  ExportToolbar 
} from '@trustin/txgraph'
import { useState, useMemo, useRef } from 'react'

export function LargeGraphExample() {
  const containerRef = useRef(null)
  const [allNodes] = useState(generateLargeDataset(1000)) // 1000 nodes
  const [allEdges] = useState(generateEdgesForNodes(allNodes))
  const [filter, setFilter] = useState({
    riskLevels: ['high', 'medium', 'low', 'unknown'],
    chains: [],
    depthRange: [0, 10],
    amountRange: [0, Number.MAX_SAFE_INTEGER],
    dateRange: [null, null],
    tokens: [],
    onlyRootNodes: false,
    onlyStoppedNodes: false,
    hideUntaggedNodes: false
  })

  // Apply filters with useMemo for performance
  const { filteredNodes, filteredEdges } = useMemo(() => {
    console.log('Filtering large dataset...')
    const start = performance.now()
    
    // Filter nodes
    const nodes = allNodes.filter(node => {
      if (!filter.riskLevels.includes(node.risk_level)) return false
      if (filter.chains.length > 0 && node.chain && !filter.chains.includes(node.chain)) return false
      if (node.depth < filter.depthRange[0] || node.depth > filter.depthRange[1]) return false
      if (filter.onlyRootNodes && !node.is_root) return false
      if (filter.onlyStoppedNodes && !node.is_stopped) return false
      if (filter.hideUntaggedNodes && node.tags.length === 0) return false
      return true
    })

    // Filter edges based on remaining nodes
    const nodeAddresses = new Set(nodes.map(n => n.address))
    const edges = allEdges.filter(edge => 
      nodeAddresses.has(edge.from) && nodeAddresses.has(edge.to)
    )

    const elapsed = performance.now() - start
    console.log(`Filtered ${allNodes.length} nodes → ${nodes.length} in ${elapsed.toFixed(2)}ms`)

    return { filteredNodes: nodes, filteredEdges: edges }
  }, [allNodes, allEdges, filter])

  const stats = useMemo(() => ({
    total_nodes: filteredNodes.length,
    total_edges: filteredEdges.length,
    max_depth_reached: Math.max(...filteredNodes.map(n => n.depth)),
    stopped_nodes: filteredNodes.filter(n => n.is_stopped).length
  }), [filteredNodes, filteredEdges])

  return (
    <div style={{
      display: 'grid',
      gridTemplateColumns: '1fr 280px',
      gap: '16px',
      height: '100vh',
      padding: '16px'
    }}>
      {/* Performance-optimized graph */}
      <div ref={containerRef} style={{ position: 'relative' }}>
        {/* Performance indicator */}
        <div style={{
          position: 'absolute',
          top: 16,
          left: 16,
          background: 'rgba(0, 0, 0, 0.8)',
          color: 'white',
          padding: '8px 12px',
          borderRadius: '6px',
          fontSize: '12px',
          zIndex: 10
        }}>
          {filteredNodes.length.toLocaleString()} nodes • {filteredEdges.length.toLocaleString()} edges
          <br />
          Renderer: {filteredNodes.length > 500 ? 'Sigma.js (WebGL)' : 'ReactFlow (DOM)'}
        </div>

        <ExportToolbar
          nodes={filteredNodes}
          edges={filteredEdges}
          stats={stats}
          containerRef={containerRef}
          className="absolute top-16 right-16 z-10"
        />

        {/* Use Sigma for large graphs, ReactFlow for smaller ones */}
        <GraphExplorerSigma
          nodes={filteredNodes}
          edges={filteredEdges}
          stats={stats}
          onNodeSelect={(node) => console.log('Selected large graph node:', node?.address)}
        />
      </div>

      {/* Control panel */}
      <div style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
        <FilterPanel
          nodes={allNodes}
          edges={allEdges}
          filter={filter}
          onChange={setFilter}
        />

        {/* Performance tips */}
        <div style={{
          background: '#f0f9ff',
          border: '1px solid #0ea5e9',
          borderRadius: '8px',
          padding: '12px'
        }}>
          <h4 style={{ margin: '0 0 8px 0', fontSize: '13px', color: '#0369a1' }}>
            Performance Tips
          </h4>
          <ul style={{ margin: 0, paddingLeft: '16px', fontSize: '12px', color: '#0369a1' }}>
            <li>Filtering {allNodes.length.toLocaleString()} total nodes</li>
            <li>WebGL renderer active for large datasets</li>
            <li>Use filters to reduce visible nodes</li>
            <li>Consider pagination for massive datasets</li>
          </ul>
        </div>
      </div>
    </div>
  )
}

// Helper functions for generating test data
function generateLargeDataset(count: number): TxNode[] {
  const nodes: TxNode[] = []
  const riskLevels = ['high', 'medium', 'low', 'unknown']
  const chains = ['Ethereum', 'Bitcoin', 'Tron', 'BSC', 'Polygon']
  
  for (let i = 0; i < count; i++) {
    nodes.push({
      address: `0x${Math.random().toString(16).substring(2).padStart(40, '0')}`,
      depth: Math.floor(Math.random() * 6),
      is_root: i === 0,
      risk_level: riskLevels[Math.floor(Math.random() * riskLevels.length)],
      risk_score: Math.floor(Math.random() * 100),
      tags: Math.random() > 0.7 ? [{
        primary_category: ['Exchange', 'DeFi', 'Mixer', 'Bridge'][Math.floor(Math.random() * 4)],
        name: `Entity ${i}`
      }] : [],
      total_neighbors: Math.floor(Math.random() * 10) + 1,
      visible_neighbors: Math.floor(Math.random() * 5) + 1,
      is_stopped: Math.random() > 0.8,
      chain: chains[Math.floor(Math.random() * chains.length)]
    })
  }
  
  return nodes
}

function generateEdgesForNodes(nodes: TxNode[]): TxEdge[] {
  const edges: TxEdge[] = []
  
  for (let i = 1; i < nodes.length; i++) {
    // Connect each node to a random earlier node
    const targetIndex = Math.floor(Math.random() * i)
    
    edges.push({
      from: nodes[targetIndex].address,
      to: nodes[i].address,
      direction: 'out',
      amount: (Math.random() * 1000000).toFixed(0),
      formatted_amount: `${(Math.random() * 1000).toFixed(2)} ETH`,
      last_timestamp: Date.now() / 1000 - Math.random() * 86400 * 30, // Last 30 days
      tx_count: Math.floor(Math.random() * 10) + 1,
      token: ['ETH', 'USDT', 'USDC', 'BTC'][Math.floor(Math.random() * 4)]
    })
  }
  
  return edges
}

These examples demonstrate the full capabilities of TxGraph across different use cases, from simple visualization to enterprise-grade monitoring dashboards.

Released under the MIT License.