mirror of
https://github.com/luxfhe/research-ocp-fhe.git
synced 2026-11-03 06:05:38 +00:00
194 lines
4.9 KiB
TypeScript
194 lines
4.9 KiB
TypeScript
import { Company, Round, Investment } from '@/lib/types/company';
|
|
|
|
export interface OwnershipData {
|
|
address: string;
|
|
name: string;
|
|
totalShares: number;
|
|
ownershipPercentage: number;
|
|
rounds: string[]; // Round types the investor participated in
|
|
securityIds: string[]; // Security IDs for decryption
|
|
}
|
|
|
|
export interface TreasuryData {
|
|
shares: number;
|
|
ownershipPercentage: number;
|
|
}
|
|
|
|
export interface OwnershipCalculation {
|
|
treasury: TreasuryData;
|
|
investors: OwnershipData[];
|
|
totalShares: number;
|
|
totalInvestorShares: number;
|
|
}
|
|
|
|
const TREASURY_SHARES = 10_000;
|
|
|
|
/**
|
|
* Calculate ownership percentages for a company
|
|
*/
|
|
export function calculateOwnership(
|
|
company: Company | null
|
|
): OwnershipCalculation {
|
|
if (!company || !company.rounds) {
|
|
return {
|
|
treasury: {
|
|
shares: TREASURY_SHARES,
|
|
ownershipPercentage: 100,
|
|
},
|
|
investors: [],
|
|
totalShares: TREASURY_SHARES,
|
|
totalInvestorShares: 0,
|
|
};
|
|
}
|
|
|
|
// Aggregate investor data across all rounds
|
|
const investorMap = new Map<
|
|
string,
|
|
{
|
|
name: string;
|
|
totalShares: number;
|
|
rounds: Set<string>;
|
|
securityIds: string[];
|
|
}
|
|
>();
|
|
|
|
let totalInvestorShares = 0;
|
|
|
|
// Process all rounds and investments
|
|
company.rounds.forEach((round: Round) => {
|
|
if (!round.investments || !Array.isArray(round.investments)) {
|
|
return;
|
|
}
|
|
|
|
round.investments.forEach((investment: Investment) => {
|
|
const address = investment.investor.address.toLowerCase();
|
|
const shares = investment.shareAmount || 0;
|
|
|
|
totalInvestorShares += shares;
|
|
|
|
if (!investorMap.has(address)) {
|
|
investorMap.set(address, {
|
|
name: investment.investor.name || 'Unknown Investor',
|
|
totalShares: 0,
|
|
rounds: new Set(),
|
|
securityIds: [],
|
|
});
|
|
}
|
|
|
|
const investorData = investorMap.get(address)!;
|
|
investorData.totalShares += shares;
|
|
investorData.rounds.add(round.type);
|
|
|
|
// Add security ID if available
|
|
if (investment.investor.securityId) {
|
|
investorData.securityIds.push(investment.investor.securityId);
|
|
}
|
|
});
|
|
});
|
|
|
|
const totalShares = TREASURY_SHARES + totalInvestorShares;
|
|
|
|
// Calculate treasury percentage
|
|
const treasury: TreasuryData = {
|
|
shares: TREASURY_SHARES,
|
|
ownershipPercentage:
|
|
totalShares > 0 ? (TREASURY_SHARES / totalShares) * 100 : 100,
|
|
};
|
|
|
|
// Convert investor map to array with ownership percentages
|
|
const investors: OwnershipData[] = Array.from(investorMap.entries()).map(
|
|
([address, data]) => ({
|
|
address,
|
|
name: data.name,
|
|
totalShares: data.totalShares,
|
|
ownershipPercentage:
|
|
totalShares > 0 ? (data.totalShares / totalShares) * 100 : 0,
|
|
rounds: Array.from(data.rounds),
|
|
securityIds: data.securityIds,
|
|
})
|
|
);
|
|
|
|
// Sort investors by ownership percentage (descending)
|
|
investors.sort((a, b) => b.ownershipPercentage - a.ownershipPercentage);
|
|
|
|
return {
|
|
treasury,
|
|
investors,
|
|
totalShares,
|
|
totalInvestorShares,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Format ownership percentage for display
|
|
*/
|
|
export function formatOwnershipPercentage(percentage: number): string {
|
|
if (percentage === 0) return '0%';
|
|
if (percentage < 0.01) return '<0.01%';
|
|
if (percentage >= 99.99) return '>99.99%';
|
|
return `${percentage.toFixed(2)}%`;
|
|
}
|
|
|
|
/**
|
|
* Format share count for display
|
|
*/
|
|
export function formatShares(shares: number): string {
|
|
if (shares >= 1_000_000) {
|
|
return `${(shares / 1_000_000).toFixed(1)}M`;
|
|
}
|
|
if (shares >= 1_000) {
|
|
return `${(shares / 1_000).toFixed(1)}K`;
|
|
}
|
|
return shares.toLocaleString();
|
|
}
|
|
|
|
/**
|
|
* Get unique security IDs that need decryption for a given wallet address and role
|
|
*/
|
|
export function getSecurityIdsForDecryption(
|
|
ownership: OwnershipCalculation,
|
|
walletAddress: string | undefined,
|
|
role: string
|
|
): string[] {
|
|
if (!walletAddress) return [];
|
|
|
|
const securityIds: string[] = [];
|
|
|
|
if (role === 'FOUNDER' || role === 'ADMIN') {
|
|
// Founders and admins can decrypt all securities
|
|
ownership.investors.forEach(investor => {
|
|
securityIds.push(...investor.securityIds);
|
|
});
|
|
} else if (role === 'INVESTOR') {
|
|
// Investors can only decrypt their own securities
|
|
const investor = ownership.investors.find(
|
|
inv => inv.address.toLowerCase() === walletAddress.toLowerCase()
|
|
);
|
|
if (investor) {
|
|
securityIds.push(...investor.securityIds);
|
|
}
|
|
}
|
|
|
|
// Remove duplicates
|
|
return Array.from(new Set(securityIds));
|
|
}
|
|
|
|
/**
|
|
* Check if an investor's data can be decrypted by the current user
|
|
*/
|
|
export function canDecryptInvestor(
|
|
investorAddress: string,
|
|
walletAddress: string | undefined,
|
|
role: string
|
|
): boolean {
|
|
if (!walletAddress) return false;
|
|
|
|
if (role === 'FOUNDER' || role === 'ADMIN') return true;
|
|
|
|
if (role === 'INVESTOR') {
|
|
return walletAddress.toLowerCase() === investorAddress.toLowerCase();
|
|
}
|
|
|
|
return false; // PUBLIC role
|
|
}
|