Skip to content

Wallet as module #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
May 14, 2025
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
4 changes: 2 additions & 2 deletions front/.env
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
VITE_SERVER_BASE_URL=http://localhost:4000
VITE_WS_URL=ws://localhost:8081/ws
VITE_WALLET_SERVER_BASE_URL=http://localhost:4000
VITE_WALLET_WS_URL=ws://localhost:8081/ws
VITE_NODE_BASE_URL=http://localhost:4321
VITE_INDEXER_BASE_URL=http://localhost:4321
VITE_TX_EXPLORER_URL=http://localhost:8000
Expand Down
4 changes: 2 additions & 2 deletions front/.env.production
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
VITE_SERVER_BASE_URL=https://wallet.testnet.hyle.eu
VITE_WS_URL=wss://wallet.testnet.hyle.eu/ws
VITE_WALLET_SERVER_BASE_URL=https://wallet.testnet.hyle.eu
VITE_WALLET_WS_URL=wss://wallet.testnet.hyle.eu/ws
VITE_NODE_BASE_URL=https://node.testnet.hyle.eu
VITE_INDEXER_BASE_URL=https://indexer.testnet.hyle.eu
VITE_TX_EXPLORER_URL=https://hyleou.hyle.eu/
Expand Down
Binary file modified front/bun.lockb
Binary file not shown.
27 changes: 14 additions & 13 deletions front/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "hyle-wallet",
"name": "hyle-wallet-front",
"private": true,
"version": "0.0.0",
"type": "module",
Expand All @@ -17,24 +17,25 @@
"elliptic": "^6.6.1",
"hyle": "^0.2.5",
"hyle-check-secret": "^0.3.2",
"hyle-wallet": "file:../hyle-wallet",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^7.5.0"
"react-router-dom": "^7.6.0"
},
"devDependencies": {
"@eslint/js": "^9.24.0",
"@types/react": "^19.1.2",
"@types/react-dom": "^19.1.2",
"@vitejs/plugin-react": "^4.4.0",
"@eslint/js": "^9.26.0",
"@types/react": "^19.1.4",
"@types/react-dom": "^19.1.5",
"@vitejs/plugin-react": "^4.4.1",
"autoprefixer": "^10.4.21",
"eslint": "^9.24.0",
"eslint": "^9.26.0",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.15.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.1.0",
"postcss": "^8.5.3",
"tailwindcss": "^4.1.4",
"typescript": "~5.7.3",
"typescript-eslint": "^8.30.1",
"vite": "^6.2.6"
"tailwindcss": "^4.1.6",
"typescript": "~5.8.3",
"typescript-eslint": "^8.32.1",
"vite": "^6.3.5"
}
}
189 changes: 69 additions & 120 deletions front/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,141 +1,90 @@
import { useState, useEffect } from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { useEffect } from 'react';
import { BrowserRouter, Routes, Route, useNavigate } from 'react-router-dom';
import './App.css';
import { Balance } from './components/wallet/Balance';
import { Send } from './components/wallet/Send';
import { History } from './components/wallet/History';
import { SessionKeys } from './components/wallet/SessionKeys';
import { WalletLayout } from './components/layout/WalletLayout';
import { Wallet, Transaction } from './types/wallet';
import { indexerService } from './services/IndexerService';
import { useConfig } from './hooks/useConfig';
import { AppEvent, webSocketService } from './services/WebSocketService';
import { ConnectWallet } from './components/connect/ConnectWallet';
import { ConnectWalletExamples } from './components/connect/ConnectWalletExamples';

function App() {
const [wallet, setWallet] = useState<Wallet | null>(null);
const [balance, setBalance] = useState<number>(0);
const [transactions, setTransactions] = useState<Transaction[]>([]);
import { WalletShowcase } from './components/WalletShowcase';
import { useWalletBalance } from './hooks/useWalletBalance';
import { useWalletTransactions } from './hooks/useWalletTransactions';
import { useWebSocketConnection } from './hooks/useWebSocketConnection';
import { getPublicRoutes, getProtectedRoutes, ROUTES } from './routes/routes';
import { WalletProvider, useWallet } from 'hyle-wallet';
import { useConfig } from 'hyle-wallet';
import { LoadingErrorState } from './components/common/LoadingErrorState';

function AppContent() {
const { isLoading: isLoadingConfig, error: configError } = useConfig();

// Function to fetch balance
const fetchBalance = async () => {
if (wallet) {
const balance = await indexerService.getBalance(wallet.address);
setBalance(balance);
}
};

// Function to fetch transaction history
const fetchTransactions = async () => {
if (wallet) {
const transactions = await indexerService.getTransactionHistory(wallet.address);
setTransactions(transactions);
const { wallet, logout, stage, error } = useWallet();
const navigate = useNavigate();

// Use custom hooks
const { balance, fetchBalance } = useWalletBalance(wallet?.address);
const {
transactions,
handleTxEvent
} = useWalletTransactions(wallet?.address);

// Setup WebSocket connection
useWebSocketConnection(wallet?.address, event => {
handleTxEvent(event);
// If transaction was successful, update balance
if (event.tx.status === 'Success') {
fetchBalance();
}
};
});

// Initialize WebSocket connection when wallet is set
// Redirect back to root on auth settlement error and show message via state
useEffect(() => {
if (wallet) {
webSocketService.connect(wallet.address);

const handleTxEvent = async (event: AppEvent['TxEvent']) => {
console.log('Received transaction event:', event);
if (event.tx.status === 'Success') {
// Update balance
await fetchBalance();
}

// Update transactions
const newTransaction: Transaction = event.tx;

setTransactions(prevTransactions => {
const existingIndex = prevTransactions.findIndex(tx => tx.id === newTransaction.id);
if (existingIndex !== -1) {
console.log('Updating existing transaction');
// Update existing transaction in-place
const updatedTransactions = [...prevTransactions];
updatedTransactions[existingIndex] = newTransaction;
return updatedTransactions;
} else {
console.log('Adding new transaction');
// Add new transaction at the beginning of the list
return [newTransaction, ...prevTransactions];
}
});
};

const unsubscribeTxEvents = webSocketService.subscribeToTxEvents(handleTxEvent);

// Initial data fetch
fetchBalance();
fetchTransactions();

return () => {
unsubscribeTxEvents();
webSocketService.disconnect();
};
if (stage === 'error') {
navigate(ROUTES.ROOT, { state: { authError: error } });
}
}, [wallet]);

const handleWalletLoggedIn = (loggedInWallet: Wallet) => {
setWallet(loggedInWallet);
localStorage.setItem('wallet', JSON.stringify(loggedInWallet));
};
}, [stage, error, navigate]);

const handleLogout = () => {
setWallet(null);
localStorage.removeItem('wallet');
logout();
navigate(ROUTES.ROOT);
};

// Check if wallet exists in localStorage on component mount
useEffect(() => {
const storedWallet = localStorage.getItem('wallet');
if (storedWallet) {
setWallet(JSON.parse(storedWallet));
}
}, []);

if (isLoadingConfig) {
return <div>Loading configuration...</div>;
return <LoadingErrorState isLoading={true} error={null} loadingMessage="Loading configuration..." />;
}

if (configError) {
return <div>Error loading configuration: {configError}</div>;
return <LoadingErrorState isLoading={false} error={`Error loading configuration: ${configError}`} />;
}

return (
<BrowserRouter>
{/* Global connect wallet modal (renders its own button) */}
{!wallet && (
<div className="showcase-container">
<div className="showcase-header">
<h1>Wallet Integration</h1>
<p>Connect to your wallet using a fully customizable modal component.</p>
</div>
<ConnectWalletExamples onWalletConnected={handleWalletLoggedIn} />
</div>
)}
<Routes>
<Route path="/" element={
wallet ? <Navigate to="/wallet/balance" replace /> : <div />
} />
// If wallet is not connected, show the showcase screen
if (!wallet) {
return <WalletShowcase providers={['password', 'google', 'github']} />;
}

{wallet && (
<Route path="/wallet" element={<WalletLayout wallet={wallet} onLogout={handleLogout} />}>
<Route path="balance" element={<Balance wallet={wallet} balance={balance} />} />
<Route path="send" element={<Send wallet={wallet} />} />
<Route path="history" element={<History transactions={transactions} />} />
<Route path="session-keys" element={<SessionKeys wallet={wallet} />} />
<Route index element={<Navigate to="balance" replace />} />
</Route>
)}
// Generate routes based on authentication state
const publicRoutes = getPublicRoutes();
const protectedRoutes = getProtectedRoutes(wallet, balance, transactions, handleLogout);
const allRoutes = [...publicRoutes, ...protectedRoutes];

return <Routes>{allRoutes.map(route =>
<Route
key={route.path}
path={route.path}
element={route.element}
>
{route.children?.map(childRoute => (
<Route
key={childRoute.path}
path={childRoute.path}
element={childRoute.element}
index={childRoute.index}
/>
))}
</Route>
)}</Routes>;
}

<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
export default function App() {
return (
<BrowserRouter>
<WalletProvider>
<AppContent />
</WalletProvider>
</BrowserRouter>
);
}

export default App;
25 changes: 25 additions & 0 deletions front/src/components/WalletShowcase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { HyleWallet } from 'hyle-wallet';
import { useLocation } from 'react-router-dom';

type ProviderOption = 'password' | 'google' | 'github';

interface WalletShowcaseProps {
providers: ProviderOption[];
}

export const WalletShowcase: React.FC<WalletShowcaseProps> = ({ providers }) => {
const location = useLocation();
const authError = (location.state as any)?.authError as string | undefined;

return (
<div className="showcase-container">
<div className="showcase-header">
<h1>Wallet Integration</h1>
<p>Connect to your wallet using the default modal or your own custom UI.</p>
</div>
{authError && <div className="error-message" style={{ color: 'red' }}>{authError}</div>}
<HyleWallet providers={providers} />
</div>
);
};
Loading