From 08c116a45f3650eada652e8ba9e67fd9b23fbbae Mon Sep 17 00:00:00 2001
From: Musa Khalid <112591148+Mkalbani@users.noreply.github.com>
Date: Fri, 20 Feb 2026 16:09:16 +0000
Subject: [PATCH 1/2] feat: implement multi-chain transaction history and
storage system
---
README.md | 26 ++
libs/ui-components/README.md | 84 +++++++
libs/ui-components/jest.config.js | 16 ++
libs/ui-components/package.json | 3 +
.../BridgeHistory/BridgeHistory.tsx | 80 ++++++
.../src/components/BridgeHistory/index.ts | 2 +
.../TransactionContext.tsx | 113 ++++++++-
.../TransactionHeartbeat.headless.tsx | 8 +-
.../src/hooks/useTransactionHistory.ts | 59 +++++
libs/ui-components/src/index.ts | 14 ++
.../__tests__/filter.spec.ts | 64 +++++
.../__tests__/storage.spec.ts | 97 +++++++
.../src/transaction-history/filter.ts | 63 +++++
.../src/transaction-history/index.ts | 3 +
.../src/transaction-history/storage.ts | 236 ++++++++++++++++++
.../src/transaction-history/types.ts | 42 ++++
16 files changed, 902 insertions(+), 8 deletions(-)
create mode 100644 libs/ui-components/README.md
create mode 100644 libs/ui-components/jest.config.js
create mode 100644 libs/ui-components/src/components/BridgeHistory/BridgeHistory.tsx
create mode 100644 libs/ui-components/src/components/BridgeHistory/index.ts
create mode 100644 libs/ui-components/src/hooks/useTransactionHistory.ts
create mode 100644 libs/ui-components/src/transaction-history/__tests__/filter.spec.ts
create mode 100644 libs/ui-components/src/transaction-history/__tests__/storage.spec.ts
create mode 100644 libs/ui-components/src/transaction-history/filter.ts
create mode 100644 libs/ui-components/src/transaction-history/index.ts
create mode 100644 libs/ui-components/src/transaction-history/storage.ts
create mode 100644 libs/ui-components/src/transaction-history/types.ts
diff --git a/README.md b/README.md
index 8f0f65f..cb1dabf 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,32 @@
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
+## Transaction History
+
+BridgeWise UI SDK includes a multi-chain transaction history system for Stellar and EVM bridge flows.
+
+Usage example:
+
+```tsx
+import { useTransactionHistory, BridgeHistory } from '@bridgewise/ui-components';
+
+function HistoryPanel({ account }: { account: string }) {
+ const transactions = useTransactionHistory(account, {
+ filter: { status: 'confirmed' },
+ sortOrder: 'desc',
+ }).transactions;
+
+ return (
+ <>
+
+
Total transactions: {transactions.length}
+ >
+ );
+}
+```
+
+You can configure local-only storage (default) or optional backend tracking via `TransactionProvider`.
+
## Project setup
```bash
diff --git a/libs/ui-components/README.md b/libs/ui-components/README.md
new file mode 100644
index 0000000..e61e27b
--- /dev/null
+++ b/libs/ui-components/README.md
@@ -0,0 +1,84 @@
+# @bridgewise/ui-components
+
+BridgeWise UI SDK components and hooks for cross-chain UX.
+
+## Transaction History
+
+The transaction history module provides a unified view across Stellar and EVM bridge executions.
+
+### Data model
+
+```ts
+interface BridgeTransaction {
+ txHash: string;
+ bridgeName: string;
+ sourceChain: string;
+ destinationChain: string;
+ sourceToken: string;
+ destinationToken: string;
+ amount: number;
+ fee: number;
+ slippagePercent: number;
+ status: 'pending' | 'confirmed' | 'failed';
+ timestamp: Date;
+ account: string;
+}
+```
+
+### Hook usage
+
+```tsx
+import { useTransactionHistory } from '@bridgewise/ui-components';
+
+const transactions = useTransactionHistory(account).transactions;
+```
+
+### Filtering and sorting
+
+```tsx
+const { transactions } = useTransactionHistory(account, {
+ filter: {
+ chain: 'ethereum',
+ bridgeName: 'layerzero',
+ status: 'confirmed',
+ startDate: new Date('2026-01-01'),
+ endDate: new Date('2026-12-31'),
+ },
+ sortOrder: 'desc',
+ includeBackend: true,
+});
+```
+
+### Demo component
+
+```tsx
+import { BridgeHistory } from '@bridgewise/ui-components';
+
+;
+```
+
+### Storage configuration
+
+By default, history is persisted in browser local storage.
+
+For server-side tracking, configure an optional backend in `TransactionProvider`:
+
+```tsx
+import {
+ TransactionProvider,
+ createHttpTransactionHistoryBackend,
+} from '@bridgewise/ui-components';
+
+const historyBackend = createHttpTransactionHistoryBackend({
+ baseUrl: 'https://api.bridgewise.example.com',
+});
+
+ {
+ console.log('Tracked transaction', tx.txHash);
+ }}
+>
+ {children}
+;
+```
diff --git a/libs/ui-components/jest.config.js b/libs/ui-components/jest.config.js
new file mode 100644
index 0000000..37544f5
--- /dev/null
+++ b/libs/ui-components/jest.config.js
@@ -0,0 +1,16 @@
+module.exports = {
+ preset: 'ts-jest',
+ testEnvironment: 'node',
+ roots: ['/src'],
+ testMatch: ['**/__tests__/**/*.spec.ts', '**/?(*.)+(spec|test).ts'],
+ moduleFileExtensions: ['ts', 'tsx', 'js', 'json'],
+ testPathIgnorePatterns: ['/dist/', '/node_modules/'],
+ transform: {
+ '^.+\\.(t|j)sx?$': [
+ 'ts-jest',
+ {
+ tsconfig: 'tsconfig.json',
+ },
+ ],
+ },
+};
diff --git a/libs/ui-components/package.json b/libs/ui-components/package.json
index 0bd8bad..ebefc07 100644
--- a/libs/ui-components/package.json
+++ b/libs/ui-components/package.json
@@ -1,6 +1,9 @@
{
"name": "@bridgewise/ui-components",
"version": "0.1.0",
+ "scripts": {
+ "test": "jest --config jest.config.js"
+ },
"main": "src/index.ts",
"exports": {
".": "./src/index.ts",
diff --git a/libs/ui-components/src/components/BridgeHistory/BridgeHistory.tsx b/libs/ui-components/src/components/BridgeHistory/BridgeHistory.tsx
new file mode 100644
index 0000000..5e76f73
--- /dev/null
+++ b/libs/ui-components/src/components/BridgeHistory/BridgeHistory.tsx
@@ -0,0 +1,80 @@
+'use client';
+
+import React from 'react';
+import { useTransactionHistory } from '../../hooks/useTransactionHistory';
+import type {
+ BridgeTransactionStatus,
+ TransactionHistoryConfig,
+ TransactionHistoryFilter,
+} from '../../transaction-history/types';
+
+export interface BridgeHistoryProps {
+ account: string;
+ chain?: string;
+ bridgeName?: string;
+ status?: BridgeTransactionStatus;
+ startDate?: Date;
+ endDate?: Date;
+ sortOrder?: 'asc' | 'desc';
+ includeBackend?: boolean;
+ historyConfig?: TransactionHistoryConfig;
+ emptyStateMessage?: string;
+}
+
+export const BridgeHistory: React.FC = ({
+ account,
+ chain,
+ bridgeName,
+ status,
+ startDate,
+ endDate,
+ sortOrder = 'desc',
+ includeBackend = false,
+ historyConfig,
+ emptyStateMessage = 'No transactions found for this account.',
+}) => {
+ const filter: TransactionHistoryFilter = {
+ chain,
+ bridgeName,
+ status,
+ startDate,
+ endDate,
+ };
+
+ const { transactions, loading } = useTransactionHistory(
+ account,
+ {
+ filter,
+ sortOrder,
+ includeBackend,
+ },
+ historyConfig,
+ );
+
+ if (!account) {
+ return Connect a wallet to view transaction history.
;
+ }
+
+ if (loading) {
+ return Loading transaction history...
;
+ }
+
+ if (transactions.length === 0) {
+ return {emptyStateMessage}
;
+ }
+
+ return (
+
+
Bridge History
+
+ {transactions.map((transaction) => (
+ -
+ {transaction.bridgeName} • {transaction.sourceChain} →{' '}
+ {transaction.destinationChain} • {transaction.amount} {transaction.sourceToken} •{' '}
+ {transaction.status} • {transaction.timestamp.toLocaleString()}
+
+ ))}
+
+
+ );
+};
diff --git a/libs/ui-components/src/components/BridgeHistory/index.ts b/libs/ui-components/src/components/BridgeHistory/index.ts
new file mode 100644
index 0000000..39bedb1
--- /dev/null
+++ b/libs/ui-components/src/components/BridgeHistory/index.ts
@@ -0,0 +1,2 @@
+export { BridgeHistory } from './BridgeHistory';
+export type { BridgeHistoryProps } from './BridgeHistory';
diff --git a/libs/ui-components/src/components/TransactionHeartbeat/TransactionContext.tsx b/libs/ui-components/src/components/TransactionHeartbeat/TransactionContext.tsx
index c5fe48f..b2030fb 100644
--- a/libs/ui-components/src/components/TransactionHeartbeat/TransactionContext.tsx
+++ b/libs/ui-components/src/components/TransactionHeartbeat/TransactionContext.tsx
@@ -6,6 +6,12 @@
'use client';
import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react';
+import { TransactionHistoryStorage } from '../../transaction-history/storage';
+import type {
+ BridgeTransaction,
+ BridgeTransactionStatus,
+ TransactionHistoryConfig,
+} from '../../transaction-history/types';
export interface TransactionState {
id: string;
@@ -13,6 +19,15 @@ export interface TransactionState {
progress: number;
step: string;
txHash?: string;
+ bridgeName?: string;
+ sourceChain?: string;
+ destinationChain?: string;
+ sourceToken?: string;
+ destinationToken?: string;
+ amount?: number;
+ fee?: number;
+ slippagePercent?: number;
+ account?: string;
timestamp: number;
}
@@ -20,14 +35,60 @@ interface TransactionContextType {
state: TransactionState;
updateState: (updates: Partial) => void;
clearState: () => void;
- startTransaction: (id: string) => void;
+ startTransaction: (id: string, initialState?: Partial) => void;
+ recordBridgeTransaction: (transaction: Partial) => Promise;
}
const TransactionContext = createContext(undefined);
const STORAGE_KEY = 'bridgewise_tx_state';
-export const TransactionProvider = ({ children }: { children: ReactNode }) => {
+const mapStatusToHistory = (
+ status: TransactionState['status'],
+): BridgeTransactionStatus | null => {
+ if (status === 'pending') return 'pending';
+ if (status === 'success') return 'confirmed';
+ if (status === 'failed') return 'failed';
+ return null;
+};
+
+const normalizeNumber = (value?: number): number =>
+ typeof value === 'number' && Number.isFinite(value) ? value : 0;
+
+const normalizeBridgeTransaction = (
+ payload: Partial,
+): BridgeTransaction => {
+ const now = new Date();
+ return {
+ txHash: payload.txHash ?? `unknown-${now.getTime()}`,
+ bridgeName: payload.bridgeName ?? 'unknown',
+ sourceChain: payload.sourceChain ?? 'unknown',
+ destinationChain: payload.destinationChain ?? 'unknown',
+ sourceToken: payload.sourceToken ?? 'unknown',
+ destinationToken: payload.destinationToken ?? 'unknown',
+ amount: normalizeNumber(payload.amount),
+ fee: normalizeNumber(payload.fee),
+ slippagePercent: normalizeNumber(payload.slippagePercent),
+ status:
+ payload.status === 'pending' || payload.status === 'confirmed' || payload.status === 'failed'
+ ? payload.status
+ : 'pending',
+ timestamp: payload.timestamp ?? now,
+ account: payload.account ?? 'unknown',
+ };
+};
+
+export interface TransactionProviderProps {
+ children: ReactNode;
+ historyConfig?: TransactionHistoryConfig;
+ onTransactionTracked?: (transaction: BridgeTransaction) => void;
+}
+
+export const TransactionProvider = ({
+ children,
+ historyConfig,
+ onTransactionTracked,
+}: TransactionProviderProps) => {
const [state, setState] = useState({
id: '',
status: 'idle',
@@ -35,6 +96,7 @@ export const TransactionProvider = ({ children }: { children: ReactNode }) => {
step: '',
timestamp: 0,
});
+ const [historyStorage] = useState(() => new TransactionHistoryStorage(historyConfig));
// Load from storage on mount
useEffect(() => {
@@ -63,6 +125,31 @@ export const TransactionProvider = ({ children }: { children: ReactNode }) => {
}
}, [state]);
+ useEffect(() => {
+ const historyStatus = mapStatusToHistory(state.status);
+ if (!historyStatus) {
+ return;
+ }
+
+ const tracked = normalizeBridgeTransaction({
+ txHash: state.txHash ?? (state.id ? `pending-${state.id}` : undefined),
+ bridgeName: state.bridgeName,
+ sourceChain: state.sourceChain,
+ destinationChain: state.destinationChain,
+ sourceToken: state.sourceToken,
+ destinationToken: state.destinationToken,
+ amount: state.amount,
+ fee: state.fee,
+ slippagePercent: state.slippagePercent,
+ status: historyStatus,
+ timestamp: state.timestamp ? new Date(state.timestamp) : new Date(),
+ account: state.account,
+ });
+
+ void historyStorage.upsertTransaction(tracked);
+ onTransactionTracked?.(tracked);
+ }, [historyStorage, onTransactionTracked, state]);
+
const updateState = useCallback((updates: Partial) => {
setState((prev) => ({ ...prev, ...updates, timestamp: Date.now() }));
}, []);
@@ -75,21 +162,35 @@ export const TransactionProvider = ({ children }: { children: ReactNode }) => {
step: '',
timestamp: 0,
});
- localStorage.removeItem(STORAGE_KEY);
+ if (typeof window !== 'undefined') {
+ localStorage.removeItem(STORAGE_KEY);
+ }
}, []);
- const startTransaction = useCallback((id: string) => {
+ const startTransaction = useCallback((id: string, initialState?: Partial) => {
setState({
id,
status: 'pending',
progress: 0,
step: 'Initializing...',
- timestamp: Date.now()
+ timestamp: Date.now(),
+ ...initialState,
});
}, []);
+ const recordBridgeTransaction = useCallback(
+ async (transaction: Partial) => {
+ const normalized = normalizeBridgeTransaction(transaction);
+ await historyStorage.upsertTransaction(normalized);
+ onTransactionTracked?.(normalized);
+ },
+ [historyStorage, onTransactionTracked],
+ );
+
return (
-
+
{children}
);
diff --git a/libs/ui-components/src/components/TransactionHeartbeat/TransactionHeartbeat.headless.tsx b/libs/ui-components/src/components/TransactionHeartbeat/TransactionHeartbeat.headless.tsx
index b646661..0702a57 100644
--- a/libs/ui-components/src/components/TransactionHeartbeat/TransactionHeartbeat.headless.tsx
+++ b/libs/ui-components/src/components/TransactionHeartbeat/TransactionHeartbeat.headless.tsx
@@ -9,6 +9,7 @@
import React from 'react';
import { useTransaction } from './TransactionContext';
import type { TransactionState } from './TransactionContext';
+import type { BridgeTransaction } from '../../transaction-history/types';
export interface TransactionHeartbeatRenderProps {
/** Full transaction state object */
@@ -18,7 +19,9 @@ export interface TransactionHeartbeatRenderProps {
/** Update transaction state with partial updates */
updateState: (updates: Partial) => void;
/** Start a new transaction */
- startTransaction: (id: string) => void;
+ startTransaction: (id: string, initialState?: Partial) => void;
+ /** Record a normalized transaction directly into history */
+ recordBridgeTransaction: (transaction: Partial) => Promise;
/** Convenience boolean: true if status is 'success' */
isSuccess: boolean;
/** Convenience boolean: true if status is 'failed' */
@@ -55,7 +58,7 @@ export interface TransactionHeartbeatHeadlessProps {
export const TransactionHeartbeatHeadless: React.FC = ({
children,
}) => {
- const { state, clearState, updateState, startTransaction } = useTransaction();
+ const { state, clearState, updateState, startTransaction, recordBridgeTransaction } = useTransaction();
// Don't render if transaction is idle
if (state.status === 'idle') {
@@ -74,6 +77,7 @@ export const TransactionHeartbeatHeadless: React.FC Promise;
+}
+
+export function useTransactionHistory(
+ account: string,
+ options?: UseTransactionHistoryOptions,
+ config?: TransactionHistoryConfig,
+): UseTransactionHistoryHookResult {
+ const [allTransactions, setAllTransactions] = useState([]);
+ const [loading, setLoading] = useState(false);
+
+ const storage = useMemo(() => new TransactionHistoryStorage(config), [config]);
+
+ const refresh = useCallback(async () => {
+ if (!account || typeof window === 'undefined') {
+ setAllTransactions([]);
+ return;
+ }
+
+ setLoading(true);
+ try {
+ const data = await storage.getTransactionsByAccount(account, {
+ includeBackend: options?.includeBackend,
+ });
+ setAllTransactions(data);
+ } finally {
+ setLoading(false);
+ }
+ }, [account, options?.includeBackend, storage]);
+
+ useEffect(() => {
+ void refresh();
+ }, [refresh]);
+
+ const transactions = useMemo(() => {
+ const filtered = filterTransactions(allTransactions, options?.filter);
+ return sortTransactions(filtered, options?.sortOrder ?? 'desc');
+ }, [allTransactions, options?.filter, options?.sortOrder]);
+
+ return {
+ transactions,
+ loading,
+ refresh,
+ };
+}
diff --git a/libs/ui-components/src/index.ts b/libs/ui-components/src/index.ts
index e65af4e..be59574 100644
--- a/libs/ui-components/src/index.ts
+++ b/libs/ui-components/src/index.ts
@@ -36,9 +36,23 @@ export {
TransactionProvider,
useTransaction,
} from './components/TransactionHeartbeat';
+export { BridgeHistory } from './components/BridgeHistory';
export type { TransactionState } from './components/TransactionHeartbeat';
+export type { BridgeHistoryProps } from './components/BridgeHistory';
// Hooks
export { useFeeSlippageBenchmark } from './hooks/useFeeSlippageBenchmark';
+export { useTransactionHistory } from './hooks/useTransactionHistory';
export type { FeeSlippageBenchmarkHookProps, FeeSlippageBenchmarkHookReturn } from './hooks/useFeeSlippageBenchmark';
+
+// Transaction history
+export { createHttpTransactionHistoryBackend } from './transaction-history/storage';
+export type {
+ BridgeTransaction,
+ BridgeTransactionStatus,
+ TransactionHistoryBackend,
+ TransactionHistoryConfig,
+ TransactionHistoryFilter,
+ UseTransactionHistoryOptions,
+} from './transaction-history/types';
diff --git a/libs/ui-components/src/transaction-history/__tests__/filter.spec.ts b/libs/ui-components/src/transaction-history/__tests__/filter.spec.ts
new file mode 100644
index 0000000..9b6601f
--- /dev/null
+++ b/libs/ui-components/src/transaction-history/__tests__/filter.spec.ts
@@ -0,0 +1,64 @@
+import { filterTransactions, sortTransactions } from '../filter';
+import type { BridgeTransaction } from '../types';
+
+const transactions: BridgeTransaction[] = [
+ {
+ txHash: '0x1',
+ bridgeName: 'layerzero',
+ sourceChain: 'ethereum',
+ destinationChain: 'stellar',
+ sourceToken: 'USDC',
+ destinationToken: 'USDC',
+ amount: 100,
+ fee: 1,
+ slippagePercent: 0.5,
+ status: 'confirmed',
+ timestamp: new Date('2026-01-02T00:00:00.000Z'),
+ account: '0xuser',
+ },
+ {
+ txHash: '0x2',
+ bridgeName: 'hop',
+ sourceChain: 'polygon',
+ destinationChain: 'arbitrum',
+ sourceToken: 'USDT',
+ destinationToken: 'USDT',
+ amount: 42,
+ fee: 0.2,
+ slippagePercent: 0.1,
+ status: 'failed',
+ timestamp: new Date('2026-01-01T00:00:00.000Z'),
+ account: '0xuser',
+ },
+];
+
+describe('transaction history filtering', () => {
+ it('filters by chain and status', () => {
+ const result = filterTransactions(transactions, {
+ chain: 'ethereum',
+ status: 'confirmed',
+ });
+
+ expect(result).toHaveLength(1);
+ expect(result[0].txHash).toBe('0x1');
+ });
+
+ it('filters by bridge name', () => {
+ const result = filterTransactions(transactions, {
+ bridgeName: 'hop',
+ });
+
+ expect(result).toHaveLength(1);
+ expect(result[0].txHash).toBe('0x2');
+ });
+
+ it('sorts by timestamp descending by default', () => {
+ const result = sortTransactions(transactions);
+ expect(result[0].txHash).toBe('0x1');
+ });
+
+ it('sorts by timestamp ascending', () => {
+ const result = sortTransactions(transactions, 'asc');
+ expect(result[0].txHash).toBe('0x2');
+ });
+});
diff --git a/libs/ui-components/src/transaction-history/__tests__/storage.spec.ts b/libs/ui-components/src/transaction-history/__tests__/storage.spec.ts
new file mode 100644
index 0000000..84366cc
--- /dev/null
+++ b/libs/ui-components/src/transaction-history/__tests__/storage.spec.ts
@@ -0,0 +1,97 @@
+import { TransactionHistoryStorage } from '../storage';
+import type { BridgeTransaction } from '../types';
+
+const createTransaction = (overrides?: Partial): BridgeTransaction => ({
+ txHash: '0xabc',
+ bridgeName: 'layerzero',
+ sourceChain: 'ethereum',
+ destinationChain: 'stellar',
+ sourceToken: 'USDC',
+ destinationToken: 'USDC',
+ amount: 100,
+ fee: 1,
+ slippagePercent: 0.5,
+ status: 'confirmed',
+ timestamp: new Date('2026-01-01T00:00:00.000Z'),
+ account: '0xuser-1',
+ ...overrides,
+});
+
+describe('TransactionHistoryStorage', () => {
+ const makeLocalStorage = () => {
+ const store = new Map();
+
+ return {
+ getItem: jest.fn((key: string) => store.get(key) ?? null),
+ setItem: jest.fn((key: string, value: string) => {
+ store.set(key, value);
+ }),
+ removeItem: jest.fn((key: string) => {
+ store.delete(key);
+ }),
+ clear: jest.fn(() => {
+ store.clear();
+ }),
+ };
+ };
+
+ beforeEach(() => {
+ const localStorage = makeLocalStorage();
+ Object.defineProperty(global, 'window', {
+ configurable: true,
+ writable: true,
+ value: { localStorage },
+ });
+ });
+
+ afterEach(() => {
+ delete (global as { window?: unknown }).window;
+ });
+
+ it('stores and retrieves transactions by account', async () => {
+ const storage = new TransactionHistoryStorage({ storageKey: 'test-history' });
+
+ await storage.upsertTransaction(createTransaction({ txHash: '0x1', account: '0xalice' }));
+ await storage.upsertTransaction(createTransaction({ txHash: '0x2', account: '0xbob' }));
+ await storage.upsertTransaction(createTransaction({ txHash: '0x3', account: '0xalice' }));
+
+ const aliceTransactions = await storage.getTransactionsByAccount('0xalice');
+
+ expect(aliceTransactions).toHaveLength(2);
+ expect(aliceTransactions.every((transaction) => transaction.account === '0xalice')).toBe(true);
+ });
+
+ it('updates existing transaction when tx hash already exists', async () => {
+ const storage = new TransactionHistoryStorage({ storageKey: 'test-history' });
+
+ await storage.upsertTransaction(createTransaction({ txHash: '0x1', status: 'pending' }));
+ await storage.upsertTransaction(createTransaction({ txHash: '0x1', status: 'confirmed' }));
+
+ const transactions = await storage.getTransactionsByAccount('0xuser-1');
+
+ expect(transactions).toHaveLength(1);
+ expect(transactions[0].status).toBe('confirmed');
+ });
+
+ it('falls back to local data when backend fails', async () => {
+ const backend = {
+ saveTransaction: jest.fn(async () => undefined),
+ getTransactionsByAccount: jest.fn(async () => {
+ throw new Error('backend unavailable');
+ }),
+ };
+
+ const storage = new TransactionHistoryStorage({
+ storageKey: 'test-history',
+ backend,
+ });
+
+ await storage.upsertTransaction(createTransaction({ txHash: '0xlocal' }));
+ const transactions = await storage.getTransactionsByAccount('0xuser-1', {
+ includeBackend: true,
+ });
+
+ expect(transactions).toHaveLength(1);
+ expect(transactions[0].txHash).toBe('0xlocal');
+ });
+});
diff --git a/libs/ui-components/src/transaction-history/filter.ts b/libs/ui-components/src/transaction-history/filter.ts
new file mode 100644
index 0000000..0bdbac9
--- /dev/null
+++ b/libs/ui-components/src/transaction-history/filter.ts
@@ -0,0 +1,63 @@
+import type { BridgeTransaction, TransactionHistoryFilter } from './types';
+
+export function filterTransactions(
+ transactions: BridgeTransaction[],
+ filter?: TransactionHistoryFilter,
+): BridgeTransaction[] {
+ if (!filter) {
+ return transactions;
+ }
+
+ return transactions.filter((transaction) => {
+ if (filter.chain) {
+ const normalizedChain = filter.chain.toLowerCase();
+ const sourceMatches = transaction.sourceChain.toLowerCase() === normalizedChain;
+ const destinationMatches = transaction.destinationChain.toLowerCase() === normalizedChain;
+
+ if (!sourceMatches && !destinationMatches) {
+ return false;
+ }
+ }
+
+ if (
+ filter.bridgeName &&
+ transaction.bridgeName.toLowerCase() !== filter.bridgeName.toLowerCase()
+ ) {
+ return false;
+ }
+
+ if (filter.status && transaction.status !== filter.status) {
+ return false;
+ }
+
+ if (filter.startDate && transaction.timestamp < filter.startDate) {
+ return false;
+ }
+
+ if (filter.endDate && transaction.timestamp > filter.endDate) {
+ return false;
+ }
+
+ return true;
+ });
+}
+
+export function sortTransactions(
+ transactions: BridgeTransaction[],
+ sortOrder: 'asc' | 'desc' = 'desc',
+): BridgeTransaction[] {
+ const copy = [...transactions];
+
+ copy.sort((a, b) => {
+ const left = a.timestamp.getTime();
+ const right = b.timestamp.getTime();
+
+ if (sortOrder === 'asc') {
+ return left - right;
+ }
+
+ return right - left;
+ });
+
+ return copy;
+}
diff --git a/libs/ui-components/src/transaction-history/index.ts b/libs/ui-components/src/transaction-history/index.ts
new file mode 100644
index 0000000..1aeb918
--- /dev/null
+++ b/libs/ui-components/src/transaction-history/index.ts
@@ -0,0 +1,3 @@
+export * from './types';
+export * from './storage';
+export * from './filter';
diff --git a/libs/ui-components/src/transaction-history/storage.ts b/libs/ui-components/src/transaction-history/storage.ts
new file mode 100644
index 0000000..8be4b71
--- /dev/null
+++ b/libs/ui-components/src/transaction-history/storage.ts
@@ -0,0 +1,236 @@
+import type {
+ BridgeTransaction,
+ TransactionHistoryBackend,
+ TransactionHistoryConfig,
+} from './types';
+
+const DEFAULT_STORAGE_KEY = 'bridgewise_transaction_history_v1';
+const DEFAULT_MAX_TRANSACTIONS_PER_ACCOUNT = 200;
+
+interface StorageLike {
+ getItem: (key: string) => string | null;
+ setItem: (key: string, value: string) => void;
+}
+
+type StoredBridgeTransaction = Omit & { timestamp: string };
+
+function getStorage(): StorageLike | null {
+ const globalWithWindow = globalThis as {
+ window?: { localStorage?: StorageLike };
+ localStorage?: StorageLike;
+ };
+
+ try {
+ return globalWithWindow.window?.localStorage ?? globalWithWindow.localStorage ?? null;
+ } catch {
+ return null;
+ }
+}
+
+function toStoredTransaction(transaction: BridgeTransaction): StoredBridgeTransaction {
+ return {
+ ...transaction,
+ timestamp: transaction.timestamp.toISOString(),
+ };
+}
+
+function fromStoredTransaction(
+ transaction: StoredBridgeTransaction | Partial,
+): BridgeTransaction {
+ const now = new Date();
+ const parsedTimestamp = transaction.timestamp ? new Date(transaction.timestamp) : now;
+
+ return {
+ txHash: transaction.txHash ?? `unknown-${now.getTime()}`,
+ bridgeName: transaction.bridgeName ?? 'unknown',
+ sourceChain: transaction.sourceChain ?? 'unknown',
+ destinationChain: transaction.destinationChain ?? 'unknown',
+ sourceToken: transaction.sourceToken ?? 'unknown',
+ destinationToken: transaction.destinationToken ?? 'unknown',
+ amount: typeof transaction.amount === 'number' && Number.isFinite(transaction.amount)
+ ? transaction.amount
+ : 0,
+ fee: typeof transaction.fee === 'number' && Number.isFinite(transaction.fee) ? transaction.fee : 0,
+ slippagePercent:
+ typeof transaction.slippagePercent === 'number' && Number.isFinite(transaction.slippagePercent)
+ ? transaction.slippagePercent
+ : 0,
+ status:
+ transaction.status === 'pending' || transaction.status === 'confirmed' || transaction.status === 'failed'
+ ? transaction.status
+ : 'pending',
+ timestamp: Number.isNaN(parsedTimestamp.getTime()) ? now : parsedTimestamp,
+ account: transaction.account ?? 'unknown',
+ };
+}
+
+export class TransactionHistoryStorage {
+ private backend?: TransactionHistoryBackend;
+
+ private storageKey: string;
+
+ private maxTransactionsPerAccount: number;
+
+ constructor(config?: TransactionHistoryConfig) {
+ this.backend = config?.backend;
+ this.storageKey = config?.storageKey ?? DEFAULT_STORAGE_KEY;
+ this.maxTransactionsPerAccount =
+ config?.maxTransactionsPerAccount ?? DEFAULT_MAX_TRANSACTIONS_PER_ACCOUNT;
+ }
+
+ async upsertTransaction(transaction: BridgeTransaction): Promise {
+ try {
+ const transactions = this.getLocalTransactions();
+ const next = this.upsert(transactions, transaction);
+ this.saveLocalTransactions(next);
+ } catch {
+ // No-op fallback: local history unavailable
+ }
+
+ if (this.backend) {
+ try {
+ await this.backend.saveTransaction(transaction);
+ } catch {
+ // No-op fallback: backend history unavailable
+ }
+ }
+ }
+
+ async getTransactionsByAccount(
+ account: string,
+ options?: { includeBackend?: boolean },
+ ): Promise {
+ const local = this.getLocalTransactions().filter((tx) => tx.account === account);
+
+ if (!options?.includeBackend || !this.backend) {
+ return local;
+ }
+
+ try {
+ const backendTransactions = await this.backend.getTransactionsByAccount(account);
+ return this.merge(local, backendTransactions);
+ } catch {
+ return local;
+ }
+ }
+
+ private merge(
+ current: BridgeTransaction[],
+ incoming: BridgeTransaction[],
+ ): BridgeTransaction[] {
+ let merged = [...current];
+ for (const transaction of incoming) {
+ merged = this.upsert(merged, transaction);
+ }
+ return merged;
+ }
+
+ private upsert(
+ transactions: BridgeTransaction[],
+ transaction: BridgeTransaction,
+ ): BridgeTransaction[] {
+ const key = `${transaction.account}:${transaction.txHash}`;
+ const mapped = new Map();
+
+ for (const item of transactions) {
+ mapped.set(`${item.account}:${item.txHash}`, item);
+ }
+
+ mapped.set(key, transaction);
+
+ const perAccount = new Map();
+ for (const value of mapped.values()) {
+ const list = perAccount.get(value.account) ?? [];
+ list.push(value);
+ perAccount.set(value.account, list);
+ }
+
+ const flattened: BridgeTransaction[] = [];
+ for (const list of perAccount.values()) {
+ list.sort((left, right) => right.timestamp.getTime() - left.timestamp.getTime());
+ flattened.push(...list.slice(0, this.maxTransactionsPerAccount));
+ }
+
+ return flattened;
+ }
+
+ private getLocalTransactions(): BridgeTransaction[] {
+ const storage = getStorage();
+ if (!storage) {
+ return [];
+ }
+
+ try {
+ const raw = storage.getItem(this.storageKey);
+ if (!raw) {
+ return [];
+ }
+
+ const parsed = JSON.parse(raw) as Array>;
+ if (!Array.isArray(parsed)) {
+ return [];
+ }
+
+ return parsed.map((transaction) => fromStoredTransaction(transaction));
+ } catch {
+ return [];
+ }
+ }
+
+ private saveLocalTransactions(transactions: BridgeTransaction[]): void {
+ const storage = getStorage();
+ if (!storage) {
+ return;
+ }
+
+ const serialized = JSON.stringify(transactions.map((tx) => toStoredTransaction(tx)));
+ storage.setItem(this.storageKey, serialized);
+ }
+}
+
+export interface HttpTransactionHistoryBackendConfig {
+ baseUrl: string;
+ fetcher?: typeof fetch;
+ headers?: Record;
+}
+
+export function createHttpTransactionHistoryBackend(
+ config: HttpTransactionHistoryBackendConfig,
+): TransactionHistoryBackend {
+ const fetcher = config.fetcher ?? fetch;
+
+ return {
+ saveTransaction: async (transaction) => {
+ await fetcher(`${config.baseUrl}/transactions/history`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ ...config.headers,
+ },
+ body: JSON.stringify(transaction),
+ });
+ },
+ getTransactionsByAccount: async (account) => {
+ const response = await fetcher(
+ `${config.baseUrl}/transactions/history?account=${encodeURIComponent(account)}`,
+ {
+ method: 'GET',
+ headers: {
+ ...config.headers,
+ },
+ },
+ );
+
+ if (!response.ok) {
+ throw new Error('Failed to load transaction history');
+ }
+
+ const payload = (await response.json()) as Array>;
+ if (!Array.isArray(payload)) {
+ return [];
+ }
+
+ return payload.map((transaction) => fromStoredTransaction(transaction));
+ },
+ };
+}
diff --git a/libs/ui-components/src/transaction-history/types.ts b/libs/ui-components/src/transaction-history/types.ts
new file mode 100644
index 0000000..6b5e666
--- /dev/null
+++ b/libs/ui-components/src/transaction-history/types.ts
@@ -0,0 +1,42 @@
+export type BridgeTransactionStatus = 'pending' | 'confirmed' | 'failed';
+
+export interface BridgeTransaction {
+ txHash: string;
+ bridgeName: string;
+ sourceChain: string;
+ destinationChain: string;
+ sourceToken: string;
+ destinationToken: string;
+ amount: number;
+ fee: number;
+ slippagePercent: number;
+ status: BridgeTransactionStatus;
+ timestamp: Date;
+ account: string;
+}
+
+export interface TransactionHistoryBackend {
+ saveTransaction: (transaction: BridgeTransaction) => Promise;
+ getTransactionsByAccount: (account: string) => Promise;
+}
+
+export interface TransactionHistoryFilter {
+ chain?: string;
+ bridgeName?: string;
+ status?: BridgeTransactionStatus;
+ startDate?: Date;
+ endDate?: Date;
+}
+
+export interface UseTransactionHistoryOptions {
+ filter?: TransactionHistoryFilter;
+ sortBy?: 'timestamp';
+ sortOrder?: 'asc' | 'desc';
+ includeBackend?: boolean;
+}
+
+export interface TransactionHistoryConfig {
+ backend?: TransactionHistoryBackend;
+ storageKey?: string;
+ maxTransactionsPerAccount?: number;
+}
From d5190e6d7acd396548a8760d0b52f2420d3290aa Mon Sep 17 00:00:00 2001
From: Musa Khalid <112591148+Mkalbani@users.noreply.github.com>
Date: Fri, 20 Feb 2026 16:34:04 +0000
Subject: [PATCH 2/2] feat: update @nestjs/swagger to version 11.2.6 in
package.json and package-lock.json
---
package-lock.json | 165 ++++++++++++++++++++++++----------------------
package.json | 2 +-
2 files changed, 87 insertions(+), 80 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 26c9a81..c0bd0fd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,7 +16,7 @@
"@nestjs/event-emitter": "^3.0.1",
"@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^11.0.1",
- "@nestjs/swagger": "^7.1.17",
+ "@nestjs/swagger": "^11.2.6",
"@nestjs/throttler": "^6.5.0",
"@nestjs/typeorm": "^11.0.0",
"@stellar/freighter-api": "^6.0.1",
@@ -240,6 +240,7 @@
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/code-frame": "^7.28.6",
"@babel/generator": "^7.28.6",
@@ -750,7 +751,7 @@
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "0.3.9"
@@ -763,7 +764,7 @@
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.0.3",
@@ -2025,7 +2026,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -2046,7 +2047,7 @@
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
@@ -2070,9 +2071,9 @@
}
},
"node_modules/@microsoft/tsdoc": {
- "version": "0.15.1",
- "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.15.1.tgz",
- "integrity": "sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==",
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.16.0.tgz",
+ "integrity": "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==",
"license": "MIT"
},
"node_modules/@napi-rs/wasm-runtime": {
@@ -2149,6 +2150,7 @@
"resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.12.tgz",
"integrity": "sha512-v6U3O01YohHO+IE3EIFXuRuu3VJILWzyMmSYZXpyBbnp0hk0mFyHxK2w3dF4I5WnbwiRbWlEXdeXFvPQ7qaZzw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"file-type": "21.3.0",
"iterare": "1.2.1",
@@ -2196,6 +2198,7 @@
"integrity": "sha512-97DzTYMf5RtGAVvX1cjwpKRiCUpkeQ9CCzSAenqkAhOmNVVFaApbhuw+xrDt13rsCa2hHVOYPrV4dBgOYMJjsA==",
"hasInstallScript": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@nuxt/opencollective": "0.4.1",
"fast-safe-stringify": "2.1.1",
@@ -2269,6 +2272,7 @@
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.12.tgz",
"integrity": "sha512-GYK/vHI0SGz5m8mxr7v3Urx8b9t78Cf/dj5aJMZlGd9/1D9OI1hAl00BaphjEXINUJ/BQLxIlF2zUjrYsd6enQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"cors": "2.8.5",
"express": "5.2.1",
@@ -2384,22 +2388,22 @@
}
},
"node_modules/@nestjs/swagger": {
- "version": "7.4.2",
- "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.4.2.tgz",
- "integrity": "sha512-Mu6TEn1M/owIvAx2B4DUQObQXqo2028R2s9rSZ/hJEgBK95+doTwS0DjmVA2wTeZTyVtXOoN7CsoM5pONBzvKQ==",
+ "version": "11.2.6",
+ "resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-11.2.6.tgz",
+ "integrity": "sha512-oiXOxMQqDFyv1AKAqFzSo6JPvMEs4uA36Eyz/s2aloZLxUjcLfUMELSLSNQunr61xCPTpwEOShfmO7NIufKXdA==",
"license": "MIT",
"dependencies": {
- "@microsoft/tsdoc": "^0.15.0",
- "@nestjs/mapped-types": "2.0.5",
- "js-yaml": "4.1.0",
- "lodash": "4.17.21",
- "path-to-regexp": "3.3.0",
- "swagger-ui-dist": "5.17.14"
+ "@microsoft/tsdoc": "0.16.0",
+ "@nestjs/mapped-types": "2.1.0",
+ "js-yaml": "4.1.1",
+ "lodash": "4.17.23",
+ "path-to-regexp": "8.3.0",
+ "swagger-ui-dist": "5.31.0"
},
"peerDependencies": {
- "@fastify/static": "^6.0.0 || ^7.0.0",
- "@nestjs/common": "^9.0.0 || ^10.0.0",
- "@nestjs/core": "^9.0.0 || ^10.0.0",
+ "@fastify/static": "^8.0.0 || ^9.0.0",
+ "@nestjs/common": "^11.0.1",
+ "@nestjs/core": "^11.0.1",
"class-transformer": "*",
"class-validator": "*",
"reflect-metadata": "^0.1.12 || ^0.2.0"
@@ -2416,42 +2420,10 @@
}
}
},
- "node_modules/@nestjs/swagger/node_modules/@nestjs/mapped-types": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.5.tgz",
- "integrity": "sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg==",
- "license": "MIT",
- "peerDependencies": {
- "@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0",
- "class-transformer": "^0.4.0 || ^0.5.0",
- "class-validator": "^0.13.0 || ^0.14.0",
- "reflect-metadata": "^0.1.12 || ^0.2.0"
- },
- "peerDependenciesMeta": {
- "class-transformer": {
- "optional": true
- },
- "class-validator": {
- "optional": true
- }
- }
- },
- "node_modules/@nestjs/swagger/node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "license": "MIT",
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
- "node_modules/@nestjs/swagger/node_modules/path-to-regexp": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz",
- "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==",
+ "node_modules/@nestjs/swagger/node_modules/lodash": {
+ "version": "4.17.23",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
+ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
"license": "MIT"
},
"node_modules/@nestjs/testing": {
@@ -2568,6 +2540,13 @@
"url": "https://opencollective.com/pkgr"
}
},
+ "node_modules/@scarf/scarf": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
+ "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0"
+ },
"node_modules/@sinclair/typebox": {
"version": "0.34.48",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz",
@@ -2664,28 +2643,28 @@
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
"integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/@tybys/wasm-util": {
@@ -2778,6 +2757,7 @@
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
@@ -2889,8 +2869,9 @@
"version": "22.19.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz",
"integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
@@ -2915,6 +2896,7 @@
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -3049,6 +3031,7 @@
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/types": "8.54.0",
@@ -3754,8 +3737,9 @@
"version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -3790,7 +3774,7 @@
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"acorn": "^8.11.0"
@@ -3805,6 +3789,7 @@
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -3973,7 +3958,7 @@
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/argparse": {
@@ -4022,6 +4007,7 @@
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz",
"integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
@@ -4330,6 +4316,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -4545,6 +4532,7 @@
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"readdirp": "^4.0.1"
},
@@ -4592,13 +4580,15 @@
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/class-validator": {
"version": "0.14.3",
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.3.tgz",
"integrity": "sha512-rXXekcjofVN1LTOSw+u4u9WXVEUvNBVjORW154q/IdmYWy1nMbOU9aNtZB0t8m+FJQ9q91jlr2f9CwwUFdFMRA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/validator": "^13.15.3",
"libphonenumber-js": "^1.11.1",
@@ -4917,7 +4907,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/cross-spawn": {
@@ -5068,7 +5058,7 @@
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz",
"integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==",
- "dev": true,
+ "devOptional": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.3.1"
@@ -5272,6 +5262,7 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -5332,6 +5323,7 @@
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
@@ -5605,6 +5597,7 @@
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.1",
@@ -6707,6 +6700,7 @@
"integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@jest/core": "30.2.0",
"@jest/types": "30.2.0",
@@ -7468,7 +7462,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
@@ -7724,7 +7717,7 @@
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
- "dev": true,
+ "devOptional": true,
"license": "ISC"
},
"node_modules/makeerror": {
@@ -8364,6 +8357,7 @@
"resolved": "https://registry.npmjs.org/pg/-/pg-8.17.2.tgz",
"integrity": "sha512-vjbKdiBJRqzcYw1fNU5KuHyYvdJ1qpcQg1CeBrHFqV1pWgHeVR6j/+kX0E1AAXfyuLUGY1ICrN2ELKA/z2HWzw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"pg-connection-string": "^2.10.1",
"pg-pool": "^3.11.0",
@@ -8621,6 +8615,7 @@
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -8771,6 +8766,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -8826,7 +8822,8 @@
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz",
"integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==",
- "license": "Apache-2.0"
+ "license": "Apache-2.0",
+ "peer": true
},
"node_modules/require-addon": {
"version": "1.2.0",
@@ -8935,6 +8932,7 @@
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
"license": "Apache-2.0",
+ "peer": true,
"dependencies": {
"tslib": "^2.1.0"
}
@@ -9524,10 +9522,13 @@
}
},
"node_modules/swagger-ui-dist": {
- "version": "5.17.14",
- "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.17.14.tgz",
- "integrity": "sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw==",
- "license": "Apache-2.0"
+ "version": "5.31.0",
+ "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz",
+ "integrity": "sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@scarf/scarf": "=1.4.0"
+ }
},
"node_modules/swagger-ui-express": {
"version": "5.0.1",
@@ -9644,6 +9645,7 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -10002,8 +10004,9 @@
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7",
@@ -10170,6 +10173,7 @@
"resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.28.tgz",
"integrity": "sha512-6GH7wXhtfq2D33ZuRXYwIsl/qM5685WZcODZb7noOOcRMteM9KF2x2ap3H0EBjnSV0VO4gNAfJT5Ukp0PkOlvg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@sqltools/formatter": "^1.2.5",
"ansis": "^4.2.0",
@@ -10362,8 +10366,9 @@
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "dev": true,
+ "devOptional": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -10438,7 +10443,7 @@
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/universalify": {
@@ -10565,7 +10570,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/v8-to-istanbul": {
@@ -10641,6 +10646,7 @@
"integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.8",
@@ -10710,6 +10716,7 @@
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
@@ -10988,7 +10995,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"engines": {
"node": ">=6"
diff --git a/package.json b/package.json
index 1e0458f..d1dce8f 100644
--- a/package.json
+++ b/package.json
@@ -27,7 +27,7 @@
"@nestjs/event-emitter": "^3.0.1",
"@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^11.0.1",
- "@nestjs/swagger": "^7.1.17",
+ "@nestjs/swagger": "^11.2.6",
"@nestjs/throttler": "^6.5.0",
"@nestjs/typeorm": "^11.0.0",
"@stellar/freighter-api": "^6.0.1",