keynesol-shared 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +118 -0
- package/dist/components/Common/ErrorBoundary.d.ts +23 -0
- package/dist/components/Common/ErrorBoundary.d.ts.map +1 -0
- package/dist/components/Common/ErrorBoundary.js +93 -0
- package/dist/components/Common/ErrorBoundary.jsx +103 -0
- package/dist/components/Common/ErrorMessage.d.ts +8 -0
- package/dist/components/Common/ErrorMessage.d.ts.map +1 -0
- package/dist/components/Common/ErrorMessage.js +36 -0
- package/dist/components/Common/ErrorMessage.jsx +40 -0
- package/dist/components/Common/Loading.d.ts +8 -0
- package/dist/components/Common/Loading.d.ts.map +1 -0
- package/dist/components/Common/Loading.js +41 -0
- package/dist/components/Common/Loading.jsx +44 -0
- package/dist/components/Common/LoadingIndicator.d.ts +17 -0
- package/dist/components/Common/LoadingIndicator.d.ts.map +1 -0
- package/dist/components/Common/LoadingIndicator.js +95 -0
- package/dist/components/Common/LoadingIndicator.jsx +108 -0
- package/dist/components/Common/ProgramStatus.d.ts +3 -0
- package/dist/components/Common/ProgramStatus.d.ts.map +1 -0
- package/dist/components/Common/ProgramStatus.js +26 -0
- package/dist/components/Common/ProgramStatus.jsx +27 -0
- package/dist/components/Common/Skeleton.d.ts +39 -0
- package/dist/components/Common/Skeleton.d.ts.map +1 -0
- package/dist/components/Common/Skeleton.js +53 -0
- package/dist/components/Common/Skeleton.jsx +67 -0
- package/dist/components/Common/SkeletonScreen.d.ts +18 -0
- package/dist/components/Common/SkeletonScreen.d.ts.map +1 -0
- package/dist/components/Common/SkeletonScreen.js +98 -0
- package/dist/components/Common/SkeletonScreen.jsx +108 -0
- package/dist/components/Common/index.d.ts +11 -0
- package/dist/components/Common/index.d.ts.map +1 -0
- package/dist/components/Common/index.js +10 -0
- package/dist/components/Wallet/TransactionStatus.d.ts +11 -0
- package/dist/components/Wallet/TransactionStatus.d.ts.map +1 -0
- package/dist/components/Wallet/TransactionStatus.js +97 -0
- package/dist/components/Wallet/TransactionStatus.jsx +106 -0
- package/dist/components/Wallet/WalletBalance.d.ts +4 -0
- package/dist/components/Wallet/WalletBalance.d.ts.map +1 -0
- package/dist/components/Wallet/WalletBalance.js +82 -0
- package/dist/components/Wallet/WalletBalance.jsx +86 -0
- package/dist/components/Wallet/WalletButton.d.ts +3 -0
- package/dist/components/Wallet/WalletButton.d.ts.map +1 -0
- package/dist/components/Wallet/WalletButton.js +51 -0
- package/dist/components/Wallet/WalletButton.jsx +53 -0
- package/dist/components/Wallet/WalletConnectionModal.d.ts +8 -0
- package/dist/components/Wallet/WalletConnectionModal.d.ts.map +1 -0
- package/dist/components/Wallet/WalletConnectionModal.js +150 -0
- package/dist/components/Wallet/WalletConnectionModal.jsx +170 -0
- package/dist/components/Wallet/WalletProvider.d.ts +9 -0
- package/dist/components/Wallet/WalletProvider.d.ts.map +1 -0
- package/dist/components/Wallet/WalletProvider.js +70 -0
- package/dist/components/Wallet/WalletProvider.jsx +75 -0
- package/dist/components/Wallet/index.d.ts +9 -0
- package/dist/components/Wallet/index.d.ts.map +1 -0
- package/dist/components/Wallet/index.js +8 -0
- package/dist/components/index.d.ts +7 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +6 -0
- package/dist/hooks/index.d.ts +10 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +9 -0
- package/dist/hooks/useCache.d.ts +16 -0
- package/dist/hooks/useCache.d.ts.map +1 -0
- package/dist/hooks/useCache.js +67 -0
- package/dist/hooks/usePolling.d.ts +16 -0
- package/dist/hooks/usePolling.d.ts.map +1 -0
- package/dist/hooks/usePolling.js +79 -0
- package/dist/hooks/useProgram.d.ts +14 -0
- package/dist/hooks/useProgram.d.ts.map +1 -0
- package/dist/hooks/useProgram.js +88 -0
- package/dist/hooks/useTokenBalance.d.ts +16 -0
- package/dist/hooks/useTokenBalance.d.ts.map +1 -0
- package/dist/hooks/useTokenBalance.js +100 -0
- package/dist/hooks/useVaults.d.ts +23 -0
- package/dist/hooks/useVaults.d.ts.map +1 -0
- package/dist/hooks/useVaults.js +98 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/services/index.d.ts +7 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +6 -0
- package/dist/services/reconciliationService.d.ts +76 -0
- package/dist/services/reconciliationService.d.ts.map +1 -0
- package/dist/services/reconciliationService.js +216 -0
- package/dist/services/syncService.d.ts +51 -0
- package/dist/services/syncService.d.ts.map +1 -0
- package/dist/services/syncService.js +218 -0
- package/dist/types/index.d.ts +201 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/cacheManager.d.ts +73 -0
- package/dist/utils/cacheManager.d.ts.map +1 -0
- package/dist/utils/cacheManager.js +232 -0
- package/dist/utils/errorHandler.d.ts +76 -0
- package/dist/utils/errorHandler.d.ts.map +1 -0
- package/dist/utils/errorHandler.js +267 -0
- package/dist/utils/index.d.ts +12 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +11 -0
- package/dist/utils/performanceMonitor.d.ts +75 -0
- package/dist/utils/performanceMonitor.d.ts.map +1 -0
- package/dist/utils/performanceMonitor.js +197 -0
- package/dist/utils/rpcRetry.d.ts +12 -0
- package/dist/utils/rpcRetry.d.ts.map +1 -0
- package/dist/utils/rpcRetry.js +47 -0
- package/dist/utils/supabase.d.ts +198 -0
- package/dist/utils/supabase.d.ts.map +1 -0
- package/dist/utils/supabase.js +50 -0
- package/dist/utils/toastService.d.ts +52 -0
- package/dist/utils/toastService.d.ts.map +1 -0
- package/dist/utils/toastService.js +139 -0
- package/dist/utils/tokenUtils.d.ts +33 -0
- package/dist/utils/tokenUtils.d.ts.map +1 -0
- package/dist/utils/tokenUtils.js +66 -0
- package/dist/utils/validation.d.ts +35 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +83 -0
- package/package.json +45 -0
- package/src/components/Common/ErrorBoundary.tsx +135 -0
- package/src/components/Common/ErrorMessage.tsx +52 -0
- package/src/components/Common/Loading.tsx +56 -0
- package/src/components/Common/LoadingIndicator.tsx +143 -0
- package/src/components/Common/ProgramStatus.tsx +37 -0
- package/src/components/Common/Skeleton.tsx +83 -0
- package/src/components/Common/SkeletonScreen.tsx +166 -0
- package/src/components/Common/index.ts +10 -0
- package/src/components/Wallet/TransactionStatus.tsx +138 -0
- package/src/components/Wallet/WalletBalance.tsx +94 -0
- package/src/components/Wallet/WalletButton.tsx +65 -0
- package/src/components/Wallet/WalletConnectionModal.tsx +193 -0
- package/src/components/Wallet/WalletProvider.tsx +104 -0
- package/src/components/Wallet/index.ts +8 -0
- package/src/components/index.ts +6 -0
- package/src/hooks/index.ts +10 -0
- package/src/hooks/useCache.ts +87 -0
- package/src/hooks/usePolling.ts +98 -0
- package/src/hooks/useProgram.ts +93 -0
- package/src/hooks/useTokenBalance.ts +113 -0
- package/src/hooks/useVaults.ts +122 -0
- package/src/index.ts +23 -0
- package/src/services/index.ts +6 -0
- package/src/services/reconciliationService.ts +246 -0
- package/src/services/syncService.ts +238 -0
- package/src/types/index.ts +233 -0
- package/src/utils/cacheManager.ts +286 -0
- package/src/utils/errorHandler.ts +336 -0
- package/src/utils/index.ts +12 -0
- package/src/utils/performanceMonitor.ts +222 -0
- package/src/utils/rpcRetry.ts +55 -0
- package/src/utils/supabase.ts +253 -0
- package/src/utils/toastService.ts +166 -0
- package/src/utils/tokenUtils.ts +75 -0
- package/src/utils/validation.ts +107 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Balance Hook
|
|
3
|
+
* Fetches balance for both SOL and SPL tokens (USDC)
|
|
4
|
+
*/
|
|
5
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
6
|
+
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
|
|
7
|
+
import { PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js';
|
|
8
|
+
import { getAccount, getAssociatedTokenAddress, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from '@solana/spl-token';
|
|
9
|
+
import { isNativeSOL, getTokenDecimals, fromLamports } from '../utils/tokenUtils';
|
|
10
|
+
import { retryRpcCall } from '../utils/rpcRetry';
|
|
11
|
+
|
|
12
|
+
export interface TokenBalance {
|
|
13
|
+
sol: number;
|
|
14
|
+
usdc: number;
|
|
15
|
+
loading: boolean;
|
|
16
|
+
error: string | null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const useTokenBalance = (tokenMint?: PublicKey | string) => {
|
|
20
|
+
const { connection } = useConnection();
|
|
21
|
+
const { publicKey } = useWallet();
|
|
22
|
+
const [solBalance, setSolBalance] = useState<number>(0);
|
|
23
|
+
const [usdcBalance, setUsdcBalance] = useState<number>(0);
|
|
24
|
+
const [loading, setLoading] = useState(false);
|
|
25
|
+
const [error, setError] = useState<string | null>(null);
|
|
26
|
+
|
|
27
|
+
const fetchBalances = useCallback(async () => {
|
|
28
|
+
if (!connection || !publicKey) {
|
|
29
|
+
setSolBalance(0);
|
|
30
|
+
setUsdcBalance(0);
|
|
31
|
+
setLoading(false);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
setLoading(true);
|
|
37
|
+
setError(null);
|
|
38
|
+
|
|
39
|
+
// Fetch SOL balance
|
|
40
|
+
const solBalanceLamports = await retryRpcCall(
|
|
41
|
+
() => connection.getBalance(publicKey)
|
|
42
|
+
);
|
|
43
|
+
setSolBalance(Number(solBalanceLamports) / LAMPORTS_PER_SOL);
|
|
44
|
+
|
|
45
|
+
// Fetch USDC balance if needed
|
|
46
|
+
try {
|
|
47
|
+
// USDC devnet mint
|
|
48
|
+
const usdcMint = new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v');
|
|
49
|
+
const usdcTokenAccount = await getAssociatedTokenAddress(
|
|
50
|
+
usdcMint,
|
|
51
|
+
publicKey,
|
|
52
|
+
false,
|
|
53
|
+
TOKEN_PROGRAM_ID,
|
|
54
|
+
ASSOCIATED_TOKEN_PROGRAM_ID
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const tokenAccount = await retryRpcCall(
|
|
59
|
+
() => getAccount(connection, usdcTokenAccount)
|
|
60
|
+
);
|
|
61
|
+
const amount = typeof tokenAccount.amount === 'bigint'
|
|
62
|
+
? Number(tokenAccount.amount)
|
|
63
|
+
: tokenAccount.amount;
|
|
64
|
+
setUsdcBalance(fromLamports(amount, usdcMint));
|
|
65
|
+
} catch (err: any) {
|
|
66
|
+
// Token account doesn't exist, balance is 0
|
|
67
|
+
if (err.message?.includes('InvalidAccountData') || err.message?.includes('could not find account')) {
|
|
68
|
+
setUsdcBalance(0);
|
|
69
|
+
} else {
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch (err: any) {
|
|
74
|
+
console.warn('Error fetching USDC balance:', err);
|
|
75
|
+
setUsdcBalance(0);
|
|
76
|
+
}
|
|
77
|
+
} catch (err: any) {
|
|
78
|
+
console.error('Error fetching balances:', err);
|
|
79
|
+
setError(err.message);
|
|
80
|
+
} finally {
|
|
81
|
+
setLoading(false);
|
|
82
|
+
}
|
|
83
|
+
}, [connection, publicKey]);
|
|
84
|
+
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
fetchBalances();
|
|
87
|
+
|
|
88
|
+
// Poll for balance updates every 5 seconds
|
|
89
|
+
const interval = setInterval(fetchBalances, 5000);
|
|
90
|
+
return () => clearInterval(interval);
|
|
91
|
+
}, [fetchBalances]);
|
|
92
|
+
|
|
93
|
+
// Get balance for specific token mint
|
|
94
|
+
const getBalance = useCallback((mint?: PublicKey | string): number => {
|
|
95
|
+
if (!mint) return solBalance; // Default to SOL
|
|
96
|
+
|
|
97
|
+
if (isNativeSOL(mint)) {
|
|
98
|
+
return solBalance;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Assume USDC for now (can be extended for other tokens)
|
|
102
|
+
return usdcBalance;
|
|
103
|
+
}, [solBalance, usdcBalance]);
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
sol: solBalance,
|
|
107
|
+
usdc: usdcBalance,
|
|
108
|
+
getBalance,
|
|
109
|
+
loading,
|
|
110
|
+
error,
|
|
111
|
+
refresh: fetchBalances,
|
|
112
|
+
};
|
|
113
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { useConnection, useWallet } from '@solana/wallet-adapter-react';
|
|
3
|
+
import { PublicKey } from '@solana/web3.js';
|
|
4
|
+
import { useProgram } from './useProgram';
|
|
5
|
+
import { usePolling } from './usePolling';
|
|
6
|
+
import { retryRpcCall } from '../utils/rpcRetry';
|
|
7
|
+
import * as anchor from '@coral-xyz/anchor';
|
|
8
|
+
|
|
9
|
+
// Frontend Vault type (different from blockchain Vault type in types/index.ts)
|
|
10
|
+
export interface Vault {
|
|
11
|
+
id: number;
|
|
12
|
+
aprRate: number;
|
|
13
|
+
totalStaked: number;
|
|
14
|
+
stakerCount: number;
|
|
15
|
+
isActive: boolean;
|
|
16
|
+
tokenMint: PublicKey | string; // Can be PublicKey or string for flexibility
|
|
17
|
+
// Admin-managed metadata
|
|
18
|
+
name?: string;
|
|
19
|
+
logo?: string;
|
|
20
|
+
lpOperation?: string;
|
|
21
|
+
lpOperationLogo?: string;
|
|
22
|
+
contentTitle?: string;
|
|
23
|
+
content?: string;
|
|
24
|
+
currentEpoch?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const useVaults = () => {
|
|
28
|
+
const { connection } = useConnection();
|
|
29
|
+
const { publicKey } = useWallet();
|
|
30
|
+
const { program } = useProgram();
|
|
31
|
+
const [vaults, setVaults] = useState<Vault[]>([]);
|
|
32
|
+
const [loading, setLoading] = useState(true);
|
|
33
|
+
const [error, setError] = useState<string | null>(null);
|
|
34
|
+
|
|
35
|
+
const loadVaults = useCallback(async () => {
|
|
36
|
+
if (!program) {
|
|
37
|
+
setLoading(false);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
setLoading(true);
|
|
43
|
+
setError(null);
|
|
44
|
+
|
|
45
|
+
// Load all vault accounts with retry
|
|
46
|
+
const vaultAccounts = await retryRpcCall(
|
|
47
|
+
() => (program.account as any).vault.all()
|
|
48
|
+
) as any[];
|
|
49
|
+
|
|
50
|
+
// Load vault metadata from localStorage
|
|
51
|
+
let vaultMetadata: Record<number, any> = {};
|
|
52
|
+
if (typeof window !== 'undefined') {
|
|
53
|
+
try {
|
|
54
|
+
vaultMetadata = JSON.parse(localStorage.getItem('vaultMetadata') || '{}');
|
|
55
|
+
} catch (e) {
|
|
56
|
+
console.warn('Failed to load vault metadata from localStorage:', e);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const loadedVaults: Vault[] = vaultAccounts.map((account: any) => {
|
|
61
|
+
// Handle both camelCase and snake_case field names (depending on IDL generation)
|
|
62
|
+
const vault = account.account;
|
|
63
|
+
const tokenMint = vault.tokenMint || vault.token_mint;
|
|
64
|
+
const vaultId = (vault.id || vault.vaultId)?.toNumber?.() || 0;
|
|
65
|
+
const metadata = vaultMetadata[vaultId] || {};
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
id: vaultId,
|
|
69
|
+
aprRate: (vault.aprRate || vault.apr_rate)?.toNumber?.() || 0,
|
|
70
|
+
totalStaked: (vault.totalStaked || vault.total_staked)?.toNumber?.() || 0,
|
|
71
|
+
stakerCount: (vault.stakerCount || vault.staker_count)?.toNumber?.() || 0,
|
|
72
|
+
isActive: vault.isActive ?? vault.is_active ?? false,
|
|
73
|
+
tokenMint: tokenMint ? (typeof tokenMint === 'string' ? tokenMint : tokenMint.toString()) : '',
|
|
74
|
+
currentEpoch: (vault.currentEpoch || vault.current_epoch)?.toNumber?.() || 0,
|
|
75
|
+
// Load metadata from localStorage
|
|
76
|
+
name: metadata.name,
|
|
77
|
+
logo: metadata.logo,
|
|
78
|
+
lpOperation: metadata.lpOperation,
|
|
79
|
+
lpOperationLogo: metadata.lpOperationLogo,
|
|
80
|
+
contentTitle: metadata.contentTitle,
|
|
81
|
+
content: metadata.content,
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
setVaults(loadedVaults);
|
|
86
|
+
} catch (err: any) {
|
|
87
|
+
console.error('Error loading vaults:', err);
|
|
88
|
+
setError(err.message);
|
|
89
|
+
} finally {
|
|
90
|
+
setLoading(false);
|
|
91
|
+
}
|
|
92
|
+
}, [program]);
|
|
93
|
+
|
|
94
|
+
// Initial load
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
loadVaults();
|
|
97
|
+
}, [loadVaults]);
|
|
98
|
+
|
|
99
|
+
// Real-time synchronization: Requirement 10.1 - 5-second blockchain sync
|
|
100
|
+
usePolling(
|
|
101
|
+
loadVaults,
|
|
102
|
+
{
|
|
103
|
+
interval: 5000, // 5 seconds as per Requirement 10.1
|
|
104
|
+
enabled: !!program, // Only poll when program is available
|
|
105
|
+
onError: (error) => {
|
|
106
|
+
console.error('Polling error in useVaults:', error);
|
|
107
|
+
setError(error.message);
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const refreshVaults = useCallback(() => {
|
|
113
|
+
loadVaults();
|
|
114
|
+
}, [loadVaults]);
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
vaults,
|
|
118
|
+
loading,
|
|
119
|
+
error,
|
|
120
|
+
refreshVaults,
|
|
121
|
+
};
|
|
122
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @keynesol/shared
|
|
3
|
+
* Shared code package for Keynesol Web3 Prediction Platform
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Export all components
|
|
7
|
+
export * from './components';
|
|
8
|
+
|
|
9
|
+
// Export all hooks
|
|
10
|
+
export * from './hooks';
|
|
11
|
+
|
|
12
|
+
// Export all utils
|
|
13
|
+
export * from './utils';
|
|
14
|
+
export * from './utils/supabase';
|
|
15
|
+
|
|
16
|
+
// Export all services
|
|
17
|
+
export * from './services';
|
|
18
|
+
|
|
19
|
+
// Export all types
|
|
20
|
+
export * from './types';
|
|
21
|
+
|
|
22
|
+
// Export IDL path (for reference)
|
|
23
|
+
export const IDL_PATH = '/web3_prediction_platform.json';
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Reconciliation Service
|
|
3
|
+
* Ensures consistency between Supabase and blockchain
|
|
4
|
+
* Requirements: 10.5
|
|
5
|
+
*/
|
|
6
|
+
import { Connection } from '@solana/web3.js';
|
|
7
|
+
import { getSupabaseClient, isSupabaseConfigured, SupabaseConfig } from '../utils/supabase';
|
|
8
|
+
import { errorHandler } from '../utils/errorHandler';
|
|
9
|
+
import { cacheManager } from '../utils/cacheManager';
|
|
10
|
+
|
|
11
|
+
interface ReconciliationResult {
|
|
12
|
+
transactions: {
|
|
13
|
+
missing: number;
|
|
14
|
+
inconsistent: number;
|
|
15
|
+
fixed: number;
|
|
16
|
+
};
|
|
17
|
+
rewards: {
|
|
18
|
+
missing: number;
|
|
19
|
+
inconsistent: number;
|
|
20
|
+
fixed: number;
|
|
21
|
+
};
|
|
22
|
+
vaults: {
|
|
23
|
+
missing: number;
|
|
24
|
+
inconsistent: number;
|
|
25
|
+
fixed: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
class ReconciliationService {
|
|
30
|
+
private connection: Connection | null = null;
|
|
31
|
+
private reconciliationInterval: NodeJS.Timeout | null = null;
|
|
32
|
+
private supabaseConfig?: SupabaseConfig;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Initialize reconciliation service
|
|
36
|
+
*/
|
|
37
|
+
initialize(connection: Connection, supabaseConfig?: SupabaseConfig): void {
|
|
38
|
+
this.connection = connection;
|
|
39
|
+
this.supabaseConfig = supabaseConfig;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Start periodic reconciliation
|
|
44
|
+
*/
|
|
45
|
+
startReconciliation(interval: number = 3600000): void {
|
|
46
|
+
if (this.reconciliationInterval) {
|
|
47
|
+
return; // Already running
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Initial reconciliation
|
|
51
|
+
this.reconcileAll();
|
|
52
|
+
|
|
53
|
+
// Periodic reconciliation (default: 1 hour)
|
|
54
|
+
this.reconciliationInterval = setInterval(() => {
|
|
55
|
+
this.reconcileAll();
|
|
56
|
+
}, interval);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Stop reconciliation
|
|
61
|
+
*/
|
|
62
|
+
stopReconciliation(): void {
|
|
63
|
+
if (this.reconciliationInterval) {
|
|
64
|
+
clearInterval(this.reconciliationInterval);
|
|
65
|
+
this.reconciliationInterval = null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Reconcile all data types
|
|
71
|
+
*/
|
|
72
|
+
async reconcileAll(): Promise<ReconciliationResult> {
|
|
73
|
+
if (!isSupabaseConfigured(this.supabaseConfig) || !this.connection) {
|
|
74
|
+
return {
|
|
75
|
+
transactions: { missing: 0, inconsistent: 0, fixed: 0 },
|
|
76
|
+
rewards: { missing: 0, inconsistent: 0, fixed: 0 },
|
|
77
|
+
vaults: { missing: 0, inconsistent: 0, fixed: 0 },
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const [transactions, rewards, vaults] = await Promise.all([
|
|
83
|
+
this.reconcileTransactions(),
|
|
84
|
+
this.reconcileRewards(),
|
|
85
|
+
this.reconcileVaults(),
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
transactions,
|
|
90
|
+
rewards,
|
|
91
|
+
vaults,
|
|
92
|
+
};
|
|
93
|
+
} catch (error) {
|
|
94
|
+
errorHandler.handleError(error as Error, 'ReconciliationService');
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Reconcile transactions
|
|
101
|
+
*/
|
|
102
|
+
async reconcileTransactions(): Promise<{ missing: number; inconsistent: number; fixed: number }> {
|
|
103
|
+
if (!isSupabaseConfigured(this.supabaseConfig) || !this.connection) {
|
|
104
|
+
return { missing: 0, inconsistent: 0, fixed: 0 };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const supabase = getSupabaseClient(this.supabaseConfig);
|
|
109
|
+
|
|
110
|
+
// Get recent transactions from Supabase
|
|
111
|
+
const { data: supabaseTxs } = await supabase
|
|
112
|
+
.from('transactions')
|
|
113
|
+
.select('signature, status, amount, vault_id')
|
|
114
|
+
.order('timestamp', { ascending: false })
|
|
115
|
+
.limit(100);
|
|
116
|
+
|
|
117
|
+
if (!supabaseTxs) {
|
|
118
|
+
return { missing: 0, inconsistent: 0, fixed: 0 };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let missing = 0;
|
|
122
|
+
let inconsistent = 0;
|
|
123
|
+
let fixed = 0;
|
|
124
|
+
|
|
125
|
+
// Verify each transaction on blockchain
|
|
126
|
+
const transactions = supabaseTxs as any[];
|
|
127
|
+
for (const tx of transactions) {
|
|
128
|
+
try {
|
|
129
|
+
const blockchainTx = await this.connection!.getTransaction(tx.signature, {
|
|
130
|
+
maxSupportedTransactionVersion: 0,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (!blockchainTx) {
|
|
134
|
+
// Transaction not found on blockchain - mark as failed
|
|
135
|
+
if (tx.status !== 'failed') {
|
|
136
|
+
const updateData: Record<string, string> = { status: 'failed' };
|
|
137
|
+
await (supabase
|
|
138
|
+
.from('transactions') as any)
|
|
139
|
+
.update(updateData)
|
|
140
|
+
.eq('signature', tx.signature);
|
|
141
|
+
fixed++;
|
|
142
|
+
}
|
|
143
|
+
missing++;
|
|
144
|
+
} else {
|
|
145
|
+
// Transaction found - verify status
|
|
146
|
+
const isConfirmed = blockchainTx.meta?.err === null;
|
|
147
|
+
const expectedStatus = isConfirmed ? 'confirmed' : 'failed';
|
|
148
|
+
|
|
149
|
+
if (tx.status !== expectedStatus) {
|
|
150
|
+
const updateData = { status: expectedStatus };
|
|
151
|
+
await (supabase
|
|
152
|
+
.from('transactions') as any)
|
|
153
|
+
.update(updateData as any)
|
|
154
|
+
.eq('signature', tx.signature);
|
|
155
|
+
fixed++;
|
|
156
|
+
inconsistent++;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
console.error(`Error reconciling transaction ${tx.signature}:`, error);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Invalidate cache
|
|
165
|
+
cacheManager.invalidate('transactions');
|
|
166
|
+
|
|
167
|
+
return { missing, inconsistent, fixed };
|
|
168
|
+
} catch (error) {
|
|
169
|
+
errorHandler.handleError(error as Error, 'ReconciliationService.reconcileTransactions');
|
|
170
|
+
return { missing: 0, inconsistent: 0, fixed: 0 };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Reconcile rewards
|
|
176
|
+
*/
|
|
177
|
+
async reconcileRewards(): Promise<{ missing: number; inconsistent: number; fixed: number }> {
|
|
178
|
+
if (!isSupabaseConfigured(this.supabaseConfig) || !this.connection) {
|
|
179
|
+
return { missing: 0, inconsistent: 0, fixed: 0 };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const supabase = getSupabaseClient(this.supabaseConfig);
|
|
184
|
+
|
|
185
|
+
// Get unclaimed rewards from Supabase
|
|
186
|
+
const { data: unclaimedRewards } = await supabase
|
|
187
|
+
.from('rewards')
|
|
188
|
+
.select('*')
|
|
189
|
+
.eq('claimed', false)
|
|
190
|
+
.limit(100);
|
|
191
|
+
|
|
192
|
+
if (!unclaimedRewards) {
|
|
193
|
+
return { missing: 0, inconsistent: 0, fixed: 0 };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let missing = 0;
|
|
197
|
+
let inconsistent = 0;
|
|
198
|
+
let fixed = 0;
|
|
199
|
+
|
|
200
|
+
// Verify rewards on blockchain
|
|
201
|
+
// This would require checking reward distribution transactions
|
|
202
|
+
// For now, this is a placeholder
|
|
203
|
+
|
|
204
|
+
cacheManager.invalidate('rewards');
|
|
205
|
+
|
|
206
|
+
return { missing, inconsistent, fixed };
|
|
207
|
+
} catch (error) {
|
|
208
|
+
errorHandler.handleError(error as Error, 'ReconciliationService.reconcileRewards');
|
|
209
|
+
return { missing: 0, inconsistent: 0, fixed: 0 };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Reconcile vaults
|
|
215
|
+
*/
|
|
216
|
+
async reconcileVaults(): Promise<{ missing: number; inconsistent: number; fixed: number }> {
|
|
217
|
+
if (!isSupabaseConfigured(this.supabaseConfig) || !this.connection) {
|
|
218
|
+
return { missing: 0, inconsistent: 0, fixed: 0 };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
// Reconcile vault history with current vault state
|
|
223
|
+
// This would involve:
|
|
224
|
+
// 1. Fetching current vault state from blockchain
|
|
225
|
+
// 2. Comparing with latest vault_history entry
|
|
226
|
+
// 3. Creating new history entry if state changed
|
|
227
|
+
|
|
228
|
+
cacheManager.invalidate('vaultHistory');
|
|
229
|
+
|
|
230
|
+
return { missing: 0, inconsistent: 0, fixed: 0 };
|
|
231
|
+
} catch (error) {
|
|
232
|
+
errorHandler.handleError(error as Error, 'ReconciliationService.reconcileVaults');
|
|
233
|
+
return { missing: 0, inconsistent: 0, fixed: 0 };
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Manual reconciliation trigger
|
|
239
|
+
*/
|
|
240
|
+
async manualReconcile(): Promise<ReconciliationResult> {
|
|
241
|
+
return this.reconcileAll();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Export singleton instance
|
|
246
|
+
export const reconciliationService = new ReconciliationService();
|