diff --git a/src/popup/History/TransactionDetails.tsx b/src/popup/History/TransactionDetails.tsx
new file mode 100644
index 0000000..4b2e3a2
--- /dev/null
+++ b/src/popup/History/TransactionDetails.tsx
@@ -0,0 +1,258 @@
+import React from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import {
+ faXmark,
+ faCircleCheck,
+ faCircleXmark,
+ faExternalLink,
+ faCopy,
+} from '@fortawesome/free-solid-svg-icons';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { Transfer } from './queries';
+import Uik from '@reef-chain/ui-kit';
+import BigNumber from 'bignumber.js';
+import { CopyToClipboard } from 'react-copy-to-clipboard';
+import { toast } from 'react-toastify';
+
+interface TransactionDetailsProps {
+ transfer: Transfer;
+ currentAddress: string;
+ reefscanUrl: string;
+ isDarkMode: boolean;
+ onClose: () => void;
+}
+
+/**
+ * Format token amount with full precision
+ */
+function formatFullAmount(amount: string, decimals: number): string {
+ const bn = new BigNumber(amount).div(new BigNumber(10).pow(decimals));
+ return bn.toFormat();
+}
+
+/**
+ * Format timestamp to full date and time
+ */
+function formatFullDate(timestamp: string): string {
+ const date = new Date(timestamp);
+ return date.toLocaleString('en-US', {
+ weekday: 'long',
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ hour: 'numeric',
+ minute: '2-digit',
+ second: '2-digit',
+ timeZoneName: 'short',
+ });
+}
+
+/**
+ * InfoRow component for displaying transaction details
+ */
+const InfoRow: React.FC<{
+ label: string;
+ value: string;
+ copyable?: boolean;
+ isDarkMode: boolean;
+}> = ({ label, value, copyable = false, isDarkMode }) => (
+
+
+
+
+ {copyable && (
+ toast.success('Copied to clipboard!')}
+ >
+
+
+ )}
+
+
+);
+
+/**
+ * TransactionDetails Modal Component
+ * Shows detailed information about a transaction
+ */
+export const TransactionDetails: React.FC = ({
+ transfer,
+ currentAddress,
+ reefscanUrl,
+ isDarkMode,
+ onClose,
+}) => {
+ const isSent = transfer.from.toLowerCase() === currentAddress.toLowerCase();
+
+ return (
+ <>
+ {/* Backdrop */}
+
+
+ {/* Modal */}
+
+ {/* Header */}
+
+
+
+
+
+ {/* Content */}
+
+ {/* Status Badge */}
+
+
+ {/* Amount */}
+
+
+
+
+
+
+
+ {/* Details */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* View on Explorer Button */}
+
+ }
+ className="w-full"
+ />
+
+
+
+ >
+ );
+};
diff --git a/src/popup/History/TransactionHistory.tsx b/src/popup/History/TransactionHistory.tsx
new file mode 100644
index 0000000..d7e2756
--- /dev/null
+++ b/src/popup/History/TransactionHistory.tsx
@@ -0,0 +1,255 @@
+import React, { useState, useContext } from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import {
+ faHistory,
+ faRefresh,
+ faSpinner,
+ faExclamationTriangle,
+} from '@fortawesome/free-solid-svg-icons';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import Uik from '@reef-chain/ui-kit';
+import { useTheme } from '../context/ThemeContext';
+import ReefSigners from '../context/ReefSigners';
+import { useTransactionHistory } from './useTransactionHistory';
+import { TransactionItem } from './TransactionItem';
+import { TransactionDetails } from './TransactionDetails';
+import { Transfer } from './queries';
+import strings from '../../i18n/locales';
+
+/**
+ * Empty State Component
+ */
+const EmptyState: React.FC<{ isDarkMode: boolean }> = ({ isDarkMode }) => (
+
+
+
+
+
+);
+
+/**
+ * Error State Component
+ */
+const ErrorState: React.FC<{
+ error: string;
+ onRetry: () => void;
+ isDarkMode: boolean;
+}> = ({ error, onRetry, isDarkMode }) => (
+
+
+
+
+ }
+ />
+
+);
+
+/**
+ * Loading Skeleton Component
+ */
+const LoadingSkeleton: React.FC<{ isDarkMode: boolean }> = ({ isDarkMode }) => (
+
+ {[...Array(5)].map((_, index) => (
+
+ ))}
+
+);
+
+/**
+ * Transaction History Component
+ * Main component that displays the transaction history page
+ */
+const TransactionHistory: React.FC = () => {
+ const { isDarkMode } = useTheme();
+ const { accounts, network } = useContext(ReefSigners);
+
+ const [selectedTransfer, setSelectedTransfer] = useState(null);
+
+ // Get selected account address
+ const selectedAccount = accounts?.find(acc => !!(acc as any).isSelected);
+ const currentAddress = selectedAccount?.address;
+
+ // Get GraphQL URL from network config
+ const graphqlUrl = network?.graphqlExplorerUrl || 'https://squid.subsquid.io/reef-explorer/graphql';
+ const reefscanUrl = network?.reefscanUrl || 'https://reefscan.com';
+
+ // Fetch transaction history
+ const {
+ transfers,
+ loading,
+ error,
+ hasMore,
+ loadMore,
+ refresh,
+ } = useTransactionHistory(graphqlUrl, currentAddress);
+
+ // Handle transaction click
+ const handleTransactionClick = (transfer: Transfer) => {
+ setSelectedTransfer(transfer);
+ };
+
+ return (
+
+ {/* Header */}
+
+
+ {/* Content */}
+
+ {/* No address selected */}
+ {!currentAddress && (
+
+
+
+ )}
+
+ {/* Loading initial data */}
+ {loading && transfers.length === 0 &&
}
+
+ {/* Error state */}
+ {error && !loading && (
+
+ )}
+
+ {/* Empty state */}
+ {!loading && !error && transfers.length === 0 && currentAddress && (
+
+ )}
+
+ {/* Transaction List */}
+ {transfers.length > 0 && (
+ <>
+
+ {transfers.map((transfer) => (
+ handleTransactionClick(transfer)}
+ />
+ ))}
+
+
+ {/* Load More Button */}
+ {hasMore && (
+
+
+ ) : undefined
+ }
+ />
+
+ )}
+
+ {/* End of list message */}
+ {!hasMore && transfers.length > 0 && (
+
+
+
+ )}
+ >
+ )}
+
+
+ {/* Transaction Details Modal */}
+ {selectedTransfer && (
+
setSelectedTransfer(null)}
+ />
+ )}
+
+ );
+};
+
+export default TransactionHistory;
diff --git a/src/popup/History/TransactionItem.tsx b/src/popup/History/TransactionItem.tsx
new file mode 100644
index 0000000..e5e4517
--- /dev/null
+++ b/src/popup/History/TransactionItem.tsx
@@ -0,0 +1,170 @@
+import React from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import {
+ faArrowUp,
+ faArrowDown,
+ faCircleCheck,
+ faCircleXmark,
+ faExternalLink,
+} from '@fortawesome/free-solid-svg-icons';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { Transfer } from './queries';
+import Uik from '@reef-chain/ui-kit';
+import BigNumber from 'bignumber.js';
+
+interface TransactionItemProps {
+ transfer: Transfer;
+ currentAddress: string;
+ reefscanUrl: string;
+ isDarkMode: boolean;
+ onClick: () => void;
+}
+
+/**
+ * Truncate address to show first and last characters
+ * @example "5F3sa2TJ...5EYjQX"
+ */
+function truncateAddress(address: string, start: number = 6, end: number = 6): string {
+ if (address.length <= start + end) return address;
+ return `${address.slice(0, start)}...${address.slice(-end)}`;
+}
+
+/**
+ * Format timestamp to readable date
+ * @example "Dec 22, 2025 10:30 PM"
+ */
+function formatDate(timestamp: string): string {
+ const date = new Date(timestamp);
+ return date.toLocaleString('en-US', {
+ month: 'short',
+ day: 'numeric',
+ year: 'numeric',
+ hour: 'numeric',
+ minute: '2-digit',
+ });
+}
+
+/**
+ * Format token amount with proper decimals
+ * @example "1,234.56 REEF"
+ */
+function formatAmount(amount: string, decimals: number, symbol: string): string {
+ const bn = new BigNumber(amount).div(new BigNumber(10).pow(decimals));
+ return `${bn.toFormat(2)} ${symbol}`;
+}
+
+/**
+ * TransactionItem Component
+ * Displays a single transaction/transfer in the history list
+ */
+export const TransactionItem: React.FC = ({
+ transfer,
+ currentAddress,
+ reefscanUrl,
+ isDarkMode,
+ onClick,
+}) => {
+ const isSent = transfer.from.toLowerCase() === currentAddress.toLowerCase();
+ const isReceived = transfer.to.toLowerCase() === currentAddress.toLowerCase();
+
+ const otherAddress = isSent ? transfer.to : transfer.from;
+ const direction = isSent ? 'Sent' : 'Received';
+ const directionColor = isSent ? 'text-red-500' : 'text-green-500';
+ const directionIcon = isSent ? faArrowUp : faArrowDown;
+
+ return (
+
+
+ {/* Left section: Icon + Details */}
+
+ {/* Direction Icon */}
+
+
+
+
+ {/* Transaction Details */}
+
+
+
+
+
+
+
+
+
+ {/* Success/Failed badge */}
+
+
+
+
+
+
+
+ {/* Right section: Amount + Link */}
+
+
+
+ );
+};
diff --git a/src/popup/History/queries.ts b/src/popup/History/queries.ts
new file mode 100644
index 0000000..0205ca5
--- /dev/null
+++ b/src/popup/History/queries.ts
@@ -0,0 +1,199 @@
+// GraphQL queries for Reef blockchain transaction history via Subsquid
+
+export interface Transfer {
+ id: string;
+ blockNumber: number;
+ timestamp: string;
+ extrinsicHash: string;
+ from: string;
+ to: string;
+ token: {
+ id: string;
+ name: string;
+ symbol: string;
+ decimals: number;
+ };
+ amount: string;
+ success: boolean;
+ type: 'EVM' | 'Native';
+}
+
+export interface Extrinsic {
+ id: string;
+ hash: string;
+ blockNumber: number;
+ timestamp: string;
+ signer: string;
+ method: string;
+ section: string;
+ args: string;
+ success: boolean;
+ fee: string;
+}
+
+export interface TransactionHistoryResponse {
+ transfers: Transfer[];
+ extrinsics: Extrinsic[];
+}
+
+/**
+ * GraphQL query to fetch transfer history for an address
+ * Queries both incoming and outgoing transfers
+ */
+export const TRANSFER_HISTORY_QUERY = `
+ query GetTransferHistory(
+ $address: String!
+ $limit: Int!
+ $offset: Int!
+ ) {
+ sentTransfers: transfers(
+ where: { from_eq: $address }
+ orderBy: timestamp_DESC
+ limit: $limit
+ offset: $offset
+ ) {
+ id
+ blockNumber
+ timestamp
+ extrinsicHash
+ from
+ to
+ token {
+ id
+ name
+ symbol
+ decimals
+ }
+ amount
+ success
+ type
+ }
+ receivedTransfers: transfers(
+ where: { to_eq: $address }
+ orderBy: timestamp_DESC
+ limit: $limit
+ offset: $offset
+ ) {
+ id
+ blockNumber
+ timestamp
+ extrinsicHash
+ from
+ to
+ token {
+ id
+ name
+ symbol
+ decimals
+ }
+ amount
+ success
+ type
+ }
+ }
+`;
+
+/**
+ * GraphQL query to fetch extrinsic (transaction) history for an address
+ */
+export const EXTRINSIC_HISTORY_QUERY = `
+ query GetExtrinsicHistory(
+ $address: String!
+ $limit: Int!
+ $offset: Int!
+ ) {
+ extrinsics(
+ where: { signer_eq: $address }
+ orderBy: timestamp_DESC
+ limit: $limit
+ offset: $offset
+ ) {
+ id
+ hash
+ blockNumber
+ timestamp
+ signer
+ method
+ section
+ args
+ success
+ fee
+ }
+ }
+`;
+
+/**
+ * Fetch transaction history from Reef GraphQL API
+ */
+export async function fetchTransactionHistory(
+ graphqlUrl: string,
+ address: string,
+ limit: number = 20,
+ offset: number = 0
+): Promise {
+ try {
+ // Fetch transfers
+ const transferResponse = await fetch(graphqlUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ query: TRANSFER_HISTORY_QUERY,
+ variables: { address, limit, offset },
+ }),
+ });
+
+ if (!transferResponse.ok) {
+ throw new Error(`Transfer query failed: ${transferResponse.statusText}`);
+ }
+
+ const transferData = await transferResponse.json();
+
+ if (transferData.errors) {
+ throw new Error(`GraphQL errors: ${JSON.stringify(transferData.errors)}`);
+ }
+
+ // Combine sent and received transfers
+ const sentTransfers = transferData.data?.sentTransfers || [];
+ const receivedTransfers = transferData.data?.receivedTransfers || [];
+ const allTransfers = [...sentTransfers, ...receivedTransfers];
+
+ // Remove duplicates and sort by timestamp
+ const uniqueTransfers = Array.from(
+ new Map(allTransfers.map((t) => [t.id, t])).values()
+ ).sort((a, b) => {
+ return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime();
+ });
+
+ // Fetch extrinsics
+ const extrinsicResponse = await fetch(graphqlUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ query: EXTRINSIC_HISTORY_QUERY,
+ variables: { address, limit, offset },
+ }),
+ });
+
+ if (!extrinsicResponse.ok) {
+ throw new Error(`Extrinsic query failed: ${extrinsicResponse.statusText}`);
+ }
+
+ const extrinsicData = await extrinsicResponse.json();
+
+ if (extrinsicData.errors) {
+ console.error('GraphQL errors:', extrinsicData.errors);
+ }
+
+ return {
+ transfers: uniqueTransfers.slice(0, limit),
+ extrinsics: extrinsicData.data?.extrinsics || [],
+ };
+ } catch (error) {
+ console.error('Error fetching transaction history:', error);
+ throw error;
+ }
+}
diff --git a/src/popup/History/useTransactionHistory.ts b/src/popup/History/useTransactionHistory.ts
new file mode 100644
index 0000000..9984580
--- /dev/null
+++ b/src/popup/History/useTransactionHistory.ts
@@ -0,0 +1,115 @@
+import { useState, useEffect, useCallback } from 'react';
+import { fetchTransactionHistory, Transfer, Extrinsic } from './queries';
+
+export interface TransactionHistoryState {
+ transfers: Transfer[];
+ extrinsics: Extrinsic[];
+ loading: boolean;
+ error: string | null;
+ hasMore: boolean;
+}
+
+export interface UseTransactionHistoryResult extends TransactionHistoryState {
+ loadMore: () => void;
+ refresh: () => void;
+}
+
+const ITEMS_PER_PAGE = 20;
+
+/**
+ * React hook to fetch and manage transaction history
+ *
+ * @param graphqlUrl - GraphQL endpoint URL (mainnet or testnet)
+ * @param address - Blockchain address to query transactions for
+ * @returns Transaction history state and actions
+ *
+ * @example
+ * const { transfers, loading, error, loadMore } = useTransactionHistory(
+ * 'https://squid.subsquid.io/reef-explorer/graphql',
+ * '5F3sa2TJAWMqDhXG6jhV4N8ko9SxwGy8TpaNS1repo5EYjQX'
+ * );
+ */
+export function useTransactionHistory(
+ graphqlUrl: string,
+ address: string | undefined
+): UseTransactionHistoryResult {
+ const [state, setState] = useState({
+ transfers: [],
+ extrinsics: [],
+ loading: false,
+ error: null,
+ hasMore: true,
+ });
+
+ const [offset, setOffset] = useState(0);
+
+ const fetchData = useCallback(
+ async (currentOffset: number, append: boolean = false) => {
+ if (!address || !graphqlUrl) {
+ setState(prev => ({
+ ...prev,
+ loading: false,
+ error: 'No address or GraphQL URL provided',
+ }));
+ return;
+ }
+
+ setState(prev => ({ ...prev, loading: true, error: null }));
+
+ try {
+ const data = await fetchTransactionHistory(
+ graphqlUrl,
+ address,
+ ITEMS_PER_PAGE,
+ currentOffset
+ );
+
+ setState(prev => ({
+ transfers: append
+ ? [...prev.transfers, ...data.transfers]
+ : data.transfers,
+ extrinsics: append
+ ? [...prev.extrinsics, ...data.extrinsics]
+ : data.extrinsics,
+ loading: false,
+ error: null,
+ hasMore: data.transfers.length === ITEMS_PER_PAGE,
+ }));
+ } catch (error) {
+ setState(prev => ({
+ ...prev,
+ loading: false,
+ error: error instanceof Error ? error.message : 'Failed to fetch transaction history',
+ }));
+ }
+ },
+ [graphqlUrl, address]
+ );
+
+ // Initial load
+ useEffect(() => {
+ setOffset(0);
+ fetchData(0, false);
+ }, [fetchData]);
+
+ // Load more transactions
+ const loadMore = useCallback(() => {
+ if (state.loading || !state.hasMore) return;
+
+ const newOffset = offset + ITEMS_PER_PAGE;
+ setOffset(newOffset);
+ fetchData(newOffset, true);
+ }, [offset, state.loading, state.hasMore, fetchData]);
+
+ // Refresh from beginning
+ const refresh = useCallback(() => {
+ setOffset(0);
+ fetchData(0, false);
+ }, [fetchData]);
+
+ return {
+ ...state,
+ loadMore,
+ refresh,
+ };
+}
diff --git a/src/popup/popup.tsx b/src/popup/popup.tsx
index 1ffbeac..11522db 100644
--- a/src/popup/popup.tsx
+++ b/src/popup/popup.tsx
@@ -15,6 +15,7 @@ import {
faLanguage,
faSun,
faMoon,
+ faHistory,
} from "@fortawesome/free-solid-svg-icons";
import "./popup.css";
@@ -73,6 +74,7 @@ import { faThemeco } from "@fortawesome/free-brands-svg-icons";
import { useReefSigners } from "./hooks/useReefSigners";
import Tokens from "./Tokens/Tokens";
import VDA from "./VDA/VDA";
+import TransactionHistory from "./History/TransactionHistory";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
const accountToReefSigner = async (
@@ -381,6 +383,12 @@ const Popup = () => {
onClick={() => _onAction("/vda")}
/>}
+ {selectedNetwork && _onAction("/history")}
+ />}
{!location.pathname.startsWith("/account/") && (
{
path="/vda"
element={}
/>
+ }
+ />
}
diff --git a/tsconfig.json b/tsconfig.json
index 70325a6..f169032 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,7 +4,9 @@
"module": "es6",
"target": "es6",
"moduleResolution": "node",
- "esModuleInterop": true
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "noEmitOnError": false
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules"]