Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,49 @@

[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 (
<>
<BridgeHistory account={account} />
<div>Total transactions: {transactions.length}</div>
</>
);
}
```

You can configure local-only storage (default) or optional backend tracking via `TransactionProvider`.

## Multi-Bridge Liquidity Monitoring

BridgeWise UI SDK includes liquidity monitoring across bridges for route viability checks.

```tsx
import { useBridgeLiquidity } from '@bridgewise/ui-components';

const { liquidity, refreshLiquidity } = useBridgeLiquidity({
token: 'USDC',
sourceChain: 'Ethereum',
destinationChain: 'Stellar',
refreshIntervalMs: 30000,
});
```

`BridgeCompare` uses this data to prioritize high-liquidity routes and warn on low-liquidity paths.

## Project setup

```bash
Expand Down
111 changes: 111 additions & 0 deletions libs/ui-components/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# @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';

<BridgeHistory account={account} status="confirmed" />;
```

### 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',
});

<TransactionProvider
historyConfig={{ backend: historyBackend }}
onTransactionTracked={(tx) => {
console.log('Tracked transaction', tx.txHash);
}}
>
{children}
</TransactionProvider>;
```

## Multi-Bridge Liquidity Monitoring

Use `useBridgeLiquidity()` to fetch live liquidity per bridge, token, and chain pair.

### Hook usage

```tsx
import { useBridgeLiquidity } from '@bridgewise/ui-components';

const { liquidity, refreshLiquidity } = useBridgeLiquidity({
token: 'USDC',
sourceChain: 'Ethereum',
destinationChain: 'Stellar',
});
```

### Integration examples

- `BridgeCompare` prioritizes higher-liquidity routes and warns/disables low-liquidity options.
- `BridgeStatus` (`TransactionHeartbeat`) can show liquidity alerts via `state.liquidityAlert`.

### Fallback and errors

- If provider APIs fail, the monitor returns last-known cached liquidity (when available).
- Structured provider errors are returned as `{ bridgeName, message }[]`.
- Manual refresh is supported through `refreshLiquidity()` and optional polling via `refreshIntervalMs`.
16 changes: 16 additions & 0 deletions libs/ui-components/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/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',
},
],
},
};
3 changes: 3 additions & 0 deletions libs/ui-components/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
70 changes: 68 additions & 2 deletions libs/ui-components/src/components/BridgeCompare/BridgeCompare.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

import React from 'react';
import { useFeeSlippageBenchmark } from '../../hooks/useFeeSlippageBenchmark';
import { useBridgeLiquidity } from '../../hooks/useBridgeLiquidity';
import { prioritizeRoutesByLiquidity } from '../../liquidity/monitor';
import type { BridgeRoute, ChainId } from '../../../../bridge-core/src/types';

interface BridgeCompareProps {
Expand All @@ -9,6 +11,7 @@ interface BridgeCompareProps {
sourceChain: string;
destinationChain: string;
showBenchmarkComparison?: boolean;
minLiquidityThreshold?: number;
onRouteSelect?: (route: BridgeRoute) => void;
}

Expand All @@ -24,6 +27,7 @@ const BridgeCompare: React.FC<BridgeCompareProps> = ({
sourceChain,
destinationChain,
showBenchmarkComparison = true,
minLiquidityThreshold = 0,
onRouteSelect
}: BridgeCompareProps) => {
// Get benchmark data for comparison
Expand All @@ -38,6 +42,24 @@ const BridgeCompare: React.FC<BridgeCompareProps> = ({
destinationChain: destinationChain as ChainId,
});

const {
liquidity,
loading: liquidityLoading,
errors: liquidityErrors,
usedFallback,
refreshLiquidity,
} = useBridgeLiquidity({
token,
sourceChain,
destinationChain,
refreshIntervalMs: 30000,
});

const orderedRoutes = prioritizeRoutesByLiquidity(routes, liquidity);

const getLiquidityForProvider = (provider: string) =>
liquidity.find((item) => item.bridgeName.toLowerCase() === provider.toLowerCase());

// Helper to get benchmark for a specific bridge
const getBenchmarkForBridge = (provider: string) => {
return benchmarks.find(b => b.bridgeName === provider);
Expand Down Expand Up @@ -82,11 +104,16 @@ const BridgeCompare: React.FC<BridgeCompareProps> = ({

{/* Routes List */}
<div className="space-y-4">
{routes.map((route, index) => {
{orderedRoutes.map((route, index) => {
const benchmark = getBenchmarkForBridge(route.provider);
const feeDiff = benchmark
? getFeeDifference(route.feePercentage, benchmark.avgFee)
: null;
const routeLiquidity = getLiquidityForProvider(route.provider);
const requiredAmount = parseFloat(route.inputAmount);
const threshold = requiredAmount + minLiquidityThreshold;
const hasInsufficientLiquidity =
!!routeLiquidity && routeLiquidity.availableAmount < threshold;

return (
<div
Expand Down Expand Up @@ -151,9 +178,27 @@ const BridgeCompare: React.FC<BridgeCompareProps> = ({
</div>
</div>

<div className="mt-2 text-xs">
{routeLiquidity ? (
<p className={hasInsufficientLiquidity ? 'text-red-600' : 'text-green-600'}>
Liquidity: {routeLiquidity.availableAmount.toLocaleString()} {token}
{hasInsufficientLiquidity ? ' (insufficient for this route)' : ''}
</p>
) : (
<p className="text-amber-600">Liquidity unavailable for this route</p>
)}
</div>

{onRouteSelect && (
<div className="mt-4">
<button className="w-full py-2 px-4 bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors">
<button
disabled={hasInsufficientLiquidity}
className={`w-full py-2 px-4 text-white rounded-md transition-colors ${
hasInsufficientLiquidity
? 'bg-gray-400 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-700'
}`}
>
Select Route
</button>
</div>
Expand All @@ -163,6 +208,17 @@ const BridgeCompare: React.FC<BridgeCompareProps> = ({
})}
</div>

<div className="mt-3 flex items-center justify-between">
<button
type="button"
onClick={() => void refreshLiquidity()}
className="text-xs text-blue-600 hover:text-blue-700"
>
Refresh liquidity
</button>
{usedFallback && <span className="text-xs text-amber-600">Using cached liquidity data</span>}
</div>

{/* Loading/Error States */}
{benchmarkLoading && showBenchmarkComparison && (
<div className="mt-4 text-center text-gray-500 dark:text-gray-400">
Expand All @@ -176,6 +232,16 @@ const BridgeCompare: React.FC<BridgeCompareProps> = ({
</div>
)}

{liquidityLoading && (
<div className="mt-2 text-center text-gray-500 dark:text-gray-400">Loading liquidity data...</div>
)}

{liquidityErrors.length > 0 && (
<div className="mt-2 text-center text-amber-600">
Liquidity providers unavailable: {liquidityErrors.map((error) => error.bridgeName).join(', ')}
</div>
)}

{routes.length === 0 && (
<div className="text-center py-8 text-gray-500 dark:text-gray-400">
No routes available for comparison
Expand Down
1 change: 1 addition & 0 deletions libs/ui-components/src/components/BridgeCompare/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as BridgeCompare } from './BridgeCompare';
Loading
Loading