Skip to content
This repository was archived by the owner on Feb 12, 2026. It is now read-only.

Commit aa10c7c

Browse files
committed
filter vaults in staking frontend
Signed-off-by: Dominic Wörner <dom.woe@gmail.com>
1 parent b2992f0 commit aa10c7c

8 files changed

Lines changed: 184 additions & 17 deletions

File tree

examples/apps/staking/frontend/src/hooks/useStaking.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { stakingTokenConfig } from '../contracts/stakingToken';
55
import { stakingService } from '../services/stakingService';
66
import { VaultService } from '../services/vaultService';
77
import { useEthereumDerivedIdentity } from './useEthereumDerivedIdentity';
8+
import { filterUserStakingVaults } from '../utils/vaultFiltering';
89
import type { StakingStats, Stake } from '../types/staking';
910
import type { Vault } from '../declarations/btc_vault_backend';
1011
import { ETHEREUM_CHAIN_ID } from '../lib/env';
@@ -59,14 +60,17 @@ export function useStakingData(address?: `0x${string}`) {
5960
.filter((result): result is PromiseFulfilledResult<Vault> => result.status === 'fulfilled')
6061
.map(result => result.value);
6162

63+
// 🔑 NEW: Filter to only include staking vaults (authorized by staking contract)
64+
const stakingVaults = filterUserStakingVaults(successfulVaults);
65+
6266
// Calculate actual statistics
6367
let totalStaked = 0n; // Sum of all active vault balances (funded vaults)
6468
let totalRewards = 0n; // Sum of all pending rewards from stakes
6569
let activeStakes = 0;
6670

67-
// Check vault registration status for all vaults in batch
71+
// Check vault registration status for staking vaults only
6872
const registrationChecks = await Promise.allSettled(
69-
successfulVaults.map(async (vault) => {
73+
stakingVaults.map(async (vault) => {
7074
try {
7175
// Use wagmi's readContract functionality via stakingService
7276
const isRegistered = await stakingService.checkVaultRegistration(BigInt(vault.id));
@@ -85,8 +89,8 @@ export function useStakingData(address?: `0x${string}`) {
8589
}
8690
});
8791

88-
// Sum actual vault balances for total staked (only active vaults: funded, not withdrawn, and registered)
89-
for (const vault of successfulVaults) {
92+
// Sum actual vault balances for total staked (only active staking vaults: funded, not withdrawn, and registered)
93+
for (const vault of stakingVaults) {
9094
const isFunded = hasVaultState(vault, 'Funded');
9195
const isWithdrawn = hasVaultState(vault, 'Withdrawn');
9296
const isRegistered = registrationResults.get(vault.id) ?? false;
@@ -101,8 +105,8 @@ export function useStakingData(address?: `0x${string}`) {
101105
}
102106
}
103107

104-
// Calculate rewards for each active vault using thin layer approach
105-
for (const vault of successfulVaults) {
108+
// Calculate rewards for each active staking vault using thin layer approach
109+
for (const vault of stakingVaults) {
106110
const isFunded = hasVaultState(vault, 'Funded');
107111
const isWithdrawn = hasVaultState(vault, 'Withdrawn');
108112
const isRegistered = registrationResults.get(vault.id) ?? false;
@@ -192,14 +196,15 @@ export function useTokenBalance(address?: `0x${string}`) {
192196
}
193197

194198
/**
195-
* Hook to fetch user's created vaults from the IC backend
199+
* Hook to fetch user's staking vaults from the IC backend
200+
* Only returns vaults that are authorized by the staking contract
196201
*/
197202
export function useUserVaults() {
198203
const { createIdentity, createAgent } = useEthereumDerivedIdentity ();
199204
const { address: userAddress } = useAccount();
200205

201206
return useQuery<Vault[]>({
202-
queryKey: ['user-vaults', userAddress],
207+
queryKey: ['user-staking-vaults', userAddress], // Updated key to reflect filtering
203208
queryFn: async (): Promise<Vault[]> => {
204209
if (!userAddress) return [];
205210

@@ -220,12 +225,15 @@ export function useUserVaults() {
220225
vaultIds.map((vaultId) => vaultService.getVaultInfoPublic(vaultId))
221226
);
222227

223-
return vaultDetails
228+
const successfulVaults = vaultDetails
224229
.filter((result): result is PromiseFulfilledResult<Vault> => result.status === 'fulfilled')
225230
.map(result => result.value);
226231

232+
// 🔑 NEW: Filter to only return staking vaults
233+
return filterUserStakingVaults(successfulVaults);
234+
227235
} catch (error) {
228-
console.error('Error fetching user vaults:', error);
236+
console.error('Error fetching user staking vaults:', error);
229237
return [];
230238
}
231239
},

examples/apps/staking/frontend/src/lib/wagmi.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,10 @@ const stakingChain = defineChain({
5050
});
5151

5252
export const config = createConfig({
53-
autoConnect: false,
5453
chains: [stakingChain, mainnet],
5554
connectors: [
56-
injected({ shimDisconnect: true }),
57-
metaMask({ shimDisconnect: true }),
55+
injected(),
56+
metaMask(),
5857
],
5958
storage: inMemoryStorage,
6059
transports: {

examples/apps/staking/frontend/src/services/vaultService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export function createEthereumStakingUnlockCondition(
103103
* Create vault args for staking application
104104
*/
105105
export function createStakingVaultArgs(
106-
ownerPubkey: number[],
106+
_ownerPubkey: number[], // Not used for perpetual vaults
107107
stakingContractAddress: string | null | undefined,
108108
userAddress: string,
109109
withdrawalAddress?: string
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Simple test file to verify vault filtering logic
2+
import { extractAuthorizedAddress, isStakingVault, filterUserStakingVaults } from './vaultFiltering';
3+
import type { Vault } from '../declarations/btc_vault_backend';
4+
5+
// Mock vault data for testing
6+
const mockStakingVault: Partial<Vault> = {
7+
id: 1n,
8+
unlock_condition: {
9+
ModularUnlock: {
10+
module_id: 'ethereum_vault_unlock',
11+
parameters_json: '{"authorized_address":"0x742d35Cc6648C3532e2f9Dd3C26E7ED3e9f4C5e0","owner_address":"0x1234567890abcdef1234567890abcdef12345678"}',
12+
metadata: ['Ethereum staking vault']
13+
}
14+
}
15+
} as Vault;
16+
17+
const mockNonStakingVault: Partial<Vault> = {
18+
id: 2n,
19+
unlock_condition: {
20+
ModularUnlock: {
21+
module_id: 'ethereum_vault_unlock',
22+
parameters_json: '{"authorized_address":"0x9999999999999999999999999999999999999999","owner_address":"0x1234567890abcdef1234567890abcdef12345678"}',
23+
metadata: ['Non-staking vault']
24+
}
25+
}
26+
} as Vault;
27+
28+
// Test the filtering functions
29+
console.log('=== Vault Filtering Tests ===');
30+
31+
console.log('\n1. Extract authorized address from staking vault:');
32+
const stakingAddress = extractAuthorizedAddress(mockStakingVault as Vault);
33+
console.log('Extracted address:', stakingAddress);
34+
35+
console.log('\n2. Extract authorized address from non-staking vault:');
36+
const nonStakingAddress = extractAuthorizedAddress(mockNonStakingVault as Vault);
37+
console.log('Extracted address:', nonStakingAddress);
38+
39+
console.log('\n3. Check if vault is staking vault:');
40+
const isStaking = isStakingVault(mockStakingVault as Vault, '0x742d35Cc6648C3532e2f9Dd3C26E7ED3e9f4C5e0');
41+
console.log('Is staking vault:', isStaking);
42+
43+
console.log('\n4. Check if non-staking vault is staking vault:');
44+
const isNotStaking = isStakingVault(mockNonStakingVault as Vault, '0x742d35Cc6648C3532e2f9Dd3C26E7ED3e9f4C5e0');
45+
console.log('Is staking vault:', isNotStaking);
46+
47+
console.log('\n5. Filter vaults:');
48+
const vaults = [mockStakingVault, mockNonStakingVault] as Vault[];
49+
const filtered = filterUserStakingVaults(vaults, '0x742d35Cc6648C3532e2f9Dd3C26E7ED3e9f4C5e0');
50+
console.log('Filtered vaults count:', filtered.length);
51+
console.log('Filtered vault IDs:', filtered.map(v => v.id));
52+
53+
console.log('\n=== Tests Complete ===');
54+
55+
export {}; // Make this a module
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { useMemo } from 'react';
2+
import type { Vault } from '../declarations/btc_vault_backend';
3+
import { STAKING_VAULT_ADDRESS } from '../contracts/stakingVault';
4+
5+
/**
6+
* Extract authorized_address from vault's unlock condition parameters
7+
*/
8+
export function extractAuthorizedAddress(vault: Vault): string | null {
9+
try {
10+
const unlockCondition = vault.unlock_condition;
11+
12+
if ('ModularUnlock' in unlockCondition) {
13+
const parametersJson = unlockCondition.ModularUnlock.parameters_json;
14+
const parameters = JSON.parse(parametersJson);
15+
return parameters.authorized_address || null;
16+
}
17+
18+
return null;
19+
} catch (error) {
20+
console.warn(`Failed to extract authorized_address from vault ${vault.id}:`, error);
21+
return null;
22+
}
23+
}
24+
25+
/**
26+
* Check if vault is authorized by the staking contract
27+
*/
28+
export function isStakingVault(vault: Vault, stakingContractAddress: string = STAKING_VAULT_ADDRESS): boolean {
29+
const authorizedAddress = extractAuthorizedAddress(vault);
30+
31+
if (!authorizedAddress || !stakingContractAddress) return false;
32+
33+
// Case-insensitive comparison for Ethereum addresses
34+
return authorizedAddress.toLowerCase() === stakingContractAddress.toLowerCase();
35+
}
36+
37+
/**
38+
* Filter user's vaults to only include those authorized by staking contract
39+
*/
40+
export function filterUserStakingVaults(
41+
vaults: Vault[],
42+
stakingContractAddress: string = STAKING_VAULT_ADDRESS
43+
): Vault[] {
44+
return vaults.filter(vault => isStakingVault(vault, stakingContractAddress));
45+
}
46+
47+
/**
48+
* Cache for authorized addresses to avoid repeated JSON parsing
49+
*/
50+
const authorizedAddressCache = new Map<string, string | null>();
51+
52+
/**
53+
* Cached version of extractAuthorizedAddress for performance
54+
*/
55+
export function extractAuthorizedAddressCached(vault: Vault): string | null {
56+
const cacheKey = `${vault.id}-${JSON.stringify(vault.unlock_condition)}`;
57+
58+
if (authorizedAddressCache.has(cacheKey)) {
59+
return authorizedAddressCache.get(cacheKey)!;
60+
}
61+
62+
const result = extractAuthorizedAddress(vault);
63+
authorizedAddressCache.set(cacheKey, result);
64+
65+
// Clean cache if it gets too large
66+
if (authorizedAddressCache.size > 1000) {
67+
const entries = Array.from(authorizedAddressCache.entries());
68+
authorizedAddressCache.clear();
69+
// Keep the most recent 500 entries
70+
entries.slice(-500).forEach(([key, value]) => {
71+
authorizedAddressCache.set(key, value);
72+
});
73+
}
74+
75+
return result;
76+
}
77+
78+
/**
79+
* Memoized vault filtering hook for performance
80+
*/
81+
export function useFilteredStakingVaults(vaults: Vault[], stakingContractAddress: string = STAKING_VAULT_ADDRESS) {
82+
return useMemo(() => {
83+
return filterUserStakingVaults(vaults, stakingContractAddress);
84+
}, [vaults, stakingContractAddress]);
85+
}
86+
87+
/**
88+
* Get statistics for staking vaults
89+
*/
90+
export function getStakingVaultStats(vaults: Vault[]) {
91+
const stakingVaults = filterUserStakingVaults(vaults);
92+
93+
return {
94+
totalStakingVaults: stakingVaults.length,
95+
fundedStakingVaults: stakingVaults.filter(v => 'Funded' in v.state).length,
96+
withdrawnStakingVaults: stakingVaults.filter(v => 'Withdrawn' in v.state).length,
97+
createdStakingVaults: stakingVaults.filter(v => 'Created' in v.state).length,
98+
totalStakedSats: stakingVaults
99+
.filter(v => 'Funded' in v.state && !('Withdrawn' in v.state))
100+
.reduce((sum, vault) => {
101+
const amount = vault.funding_amount[0];
102+
return sum + (typeof amount === 'bigint' ? amount : 0n);
103+
}, 0n)
104+
};
105+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"root":["./src/app.tsx","./src/env.d.ts","./src/main.tsx","./src/components/debugenvvars.tsx","./src/components/developeroverlay.tsx","./src/components/staking/claimtokensmodal.tsx","./src/components/staking/earningsdisplay.tsx","./src/components/staking/fundingmodal.tsx","./src/components/staking/liveearnings.tsx","./src/components/staking/stakemodal.tsx","./src/components/staking/stakestable.tsx","./src/components/staking/stakingdashboard.tsx","./src/components/staking/unstakemodal.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/dialog.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/contracts/stakingtoken.ts","./src/contracts/stakingvault.ts","./src/contracts/vaultregistry.ts","./src/declarations/btc_vault_backend/btc_vault_backend.did.d.ts","./src/declarations/btc_vault_backend/index.d.ts","./src/hooks/useEthereumDerivedIdentity.ts","./src/hooks/useautochainswitch.ts","./src/hooks/usestaking.ts","./src/lib/env.ts","./src/lib/utils.ts","./src/lib/wagmi.ts","./src/services/stakingservice.ts","./src/services/vaultservice.ts","./src/types/staking.ts","./src/utils/qrcode.ts"],"version":"5.9.2"}
1+
{"root":["./src/app.tsx","./src/env.d.ts","./src/main.tsx","./src/components/developeroverlay.tsx","./src/components/themeswitcher.tsx","./src/components/staking/claimtokensmodal.tsx","./src/components/staking/earningsdisplay.tsx","./src/components/staking/fundingmodal.tsx","./src/components/staking/liveearnings.tsx","./src/components/staking/stakemodal.tsx","./src/components/staking/stakestable.tsx","./src/components/staking/stakingdashboard.tsx","./src/components/staking/unstakemodal.tsx","./src/components/ui/badge.tsx","./src/components/ui/button.tsx","./src/components/ui/card.tsx","./src/components/ui/dialog.tsx","./src/components/ui/input.tsx","./src/components/ui/label.tsx","./src/contexts/themecontext.tsx","./src/contracts/stakingtoken.ts","./src/contracts/stakingvault.ts","./src/contracts/vaultregistry.ts","./src/declarations/btc_vault_backend/btc_vault_backend.did.d.ts","./src/declarations/btc_vault_backend/index.ts","./src/hooks/useautochainswitch.ts","./src/hooks/useethereumderivedidentity.ts","./src/hooks/usestaking.ts","./src/lib/env.ts","./src/lib/utils.ts","./src/lib/wagmi.ts","./src/services/stakingservice.ts","./src/services/vaultservice.ts","./src/types/staking.ts","./src/utils/qrcode.ts","./src/utils/vaultfiltering.ts"],"version":"5.9.2"}

monitoring/prometheus/prometheus.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ scrape_configs:
3838
metrics_path: /c/ulvla-h7777-77774-qaacq-cai/metrics
3939
scheme: http
4040
static_configs:
41-
- targets: ['ic-raw-proxy:80']
41+
- targets: ['ic-raw-proxy.btc_vault']
4242
honor_labels: true
4343
scrape_interval: 30s
4444
scrape_timeout: 10s

monitoring/prometheus/prometheus.yml.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ scrape_configs:
3434
metrics_path: /c/${BTC_VAULT_BACKEND_CANISTER_ID}/metrics
3535
scheme: http
3636
static_configs:
37-
- targets: ['ic-raw-proxy:80']
37+
- targets: ['ic-raw-proxy.btc_vault']
3838
honor_labels: true
3939
scrape_interval: 30s
4040
scrape_timeout: 10s

0 commit comments

Comments
 (0)