feat: Add comprehensive GitHub security integration

Security Infrastructure:
- Add .gitattributes for merge conflict protection and sensitive file handling
- Add SECURITY.md with detailed security policies and procedures
- Add pre-commit hook template for local secret detection
- Add GitHub Actions workflow for automated security scanning
- Add comprehensive documentation for git hooks

Code Security Improvements:
- Fix AuthContext.tsx: Remove hardcoded credentials, use environment variables
- Migrate WalletContext.tsx: Replace Ethereum/MetaMask with Polkadot.js
- Refactor lib/wallet.ts: Complete Substrate configuration with asset management
- Update TokenSwap.tsx: Add real API integration for balance queries
- Update StakingDashboard.tsx: Add blockchain integration placeholders

Environment Management:
- Update .env with proper security warnings
- Update .env.example with comprehensive template
- All sensitive data now uses environment variables
- Demo mode controllable via VITE_ENABLE_DEMO_MODE flag

Security Measures Implemented:
 4-layer protection (gitignore + gitattributes + pre-commit + CI/CD)
 Automated secret scanning (TruffleHog + Gitleaks)
 Pre-commit hooks prevent accidental commits
 CI/CD pipeline validates all PRs
 Environment variable validation
 Dependency security auditing

Breaking Changes:
- WalletContext now uses Polkadot.js instead of MetaMask
- lib/wallet.ts completely rewritten for Substrate
- ASSET_IDs and CHAIN_CONFIG exported from lib/wallet.ts
- Demo mode must be explicitly enabled

Migration Notes:
- Install pre-commit hook: cp .git-hooks/pre-commit.example .git/hooks/pre-commit
- Copy environment: cp .env.example .env
- Update .env with your credentials
- Enable GitHub Actions in repository settings

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-28 21:48:48 +03:00
parent 9dea336f92
commit 93767711d9
13 changed files with 1652 additions and 299 deletions
+107 -8
View File
@@ -1,11 +1,15 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { ArrowDownUp, Settings, Info, TrendingUp, Clock } from 'lucide-react';
import { Card } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { usePolkadot } from '@/contexts/PolkadotContext';
import { ASSET_IDS, formatBalance } from '@/lib/wallet';
import { toast } from '@/components/ui/use-toast';
const TokenSwap = () => {
const { api, isApiReady, selectedAccount } = usePolkadot();
const [fromToken, setFromToken] = useState('PEZ');
const [toToken, setToToken] = useState('HEZ');
const [fromAmount, setFromAmount] = useState('');
@@ -14,21 +18,112 @@ const TokenSwap = () => {
const [showConfirm, setShowConfirm] = useState(false);
const [isSwapping, setIsSwapping] = useState(false);
const exchangeRate = fromToken === 'PEZ' ? 2.5 : 0.4;
// Real balances from blockchain
const [fromBalance, setFromBalance] = useState('0');
const [toBalance, setToBalance] = useState('0');
const [exchangeRate, setExchangeRate] = useState(2.5); // Will be fetched from pool
const [isLoadingBalances, setIsLoadingBalances] = useState(false);
const toAmount = fromAmount ? (parseFloat(fromAmount) * exchangeRate).toFixed(4) : '';
// Fetch balances from blockchain
useEffect(() => {
const fetchBalances = async () => {
if (!api || !isApiReady || !selectedAccount) {
return;
}
setIsLoadingBalances(true);
try {
const fromAssetId = ASSET_IDS[fromToken as keyof typeof ASSET_IDS];
const toAssetId = ASSET_IDS[toToken as keyof typeof ASSET_IDS];
// Fetch balances from Assets pallet
const [fromAssetBalance, toAssetBalance] = await Promise.all([
api.query.assets.account(fromAssetId, selectedAccount.address),
api.query.assets.account(toAssetId, selectedAccount.address),
]);
// Format balances (12 decimals for PEZ/HEZ tokens)
const fromBal = fromAssetBalance.toJSON() as any;
const toBal = toAssetBalance.toJSON() as any;
setFromBalance(fromBal ? formatBalance(fromBal.balance.toString(), 12) : '0');
setToBalance(toBal ? formatBalance(toBal.balance.toString(), 12) : '0');
} catch (error) {
console.error('Failed to fetch balances:', error);
toast({
title: 'Error',
description: 'Failed to fetch token balances',
variant: 'destructive',
});
} finally {
setIsLoadingBalances(false);
}
};
fetchBalances();
}, [api, isApiReady, selectedAccount, fromToken, toToken]);
// TODO: Fetch exchange rate from DEX pool
// This should query the liquidity pool to get real-time exchange rates
useEffect(() => {
// Placeholder: In real implementation, query pool reserves
// const fetchExchangeRate = async () => {
// if (!api || !isApiReady) return;
// const pool = await api.query.dex.pools([fromAssetId, toAssetId]);
// // Calculate rate from pool reserves
// };
// Mock exchange rate for now
const mockRate = fromToken === 'PEZ' ? 2.5 : 0.4;
setExchangeRate(mockRate);
}, [api, isApiReady, fromToken, toToken]);
const handleSwap = () => {
setFromToken(toToken);
setToToken(fromToken);
setFromAmount('');
};
const handleConfirmSwap = () => {
const handleConfirmSwap = async () => {
if (!api || !selectedAccount) {
toast({
title: 'Error',
description: 'Please connect your wallet',
variant: 'destructive',
});
return;
}
setIsSwapping(true);
setTimeout(() => {
setIsSwapping(false);
try {
// TODO: Implement actual swap transaction
// const fromAssetId = ASSET_IDS[fromToken];
// const toAssetId = ASSET_IDS[toToken];
// const amount = parseAmount(fromAmount, 12);
// await api.tx.dex.swap(fromAssetId, toAssetId, amount, minReceive).signAndSend(...);
// Simulated swap for now
await new Promise(resolve => setTimeout(resolve, 2000));
toast({
title: 'Success',
description: `Swapped ${fromAmount} ${fromToken} for ${toAmount} ${toToken}`,
});
setShowConfirm(false);
setFromAmount('');
}, 2000);
} catch (error: any) {
console.error('Swap failed:', error);
toast({
title: 'Error',
description: error.message || 'Swap transaction failed',
variant: 'destructive',
});
} finally {
setIsSwapping(false);
}
};
const liquidityData = [
@@ -58,7 +153,9 @@ const TokenSwap = () => {
<div className="bg-gray-50 rounded-lg p-4">
<div className="flex justify-between mb-2">
<span className="text-sm text-gray-600">From</span>
<span className="text-sm text-gray-600">Balance: 10,000</span>
<span className="text-sm text-gray-600">
Balance: {isLoadingBalances ? '...' : fromBalance} {fromToken}
</span>
</div>
<div className="flex gap-3">
<Input
@@ -88,7 +185,9 @@ const TokenSwap = () => {
<div className="bg-gray-50 rounded-lg p-4">
<div className="flex justify-between mb-2">
<span className="text-sm text-gray-600">To</span>
<span className="text-sm text-gray-600">Balance: 5,000</span>
<span className="text-sm text-gray-600">
Balance: {isLoadingBalances ? '...' : toBalance} {toToken}
</span>
</div>
<div className="flex gap-3">
<Input
+160 -22
View File
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@@ -8,6 +8,9 @@ import { Progress } from '@/components/ui/progress';
import { Badge } from '@/components/ui/badge';
import { TrendingUp, Coins, Lock, Clock, Gift, Calculator, Info } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { usePolkadot } from '@/contexts/PolkadotContext';
import { ASSET_IDS, formatBalance } from '@/lib/wallet';
import { toast } from '@/components/ui/use-toast';
interface StakingPool {
id: string;
@@ -23,39 +26,43 @@ interface StakingPool {
export const StakingDashboard: React.FC = () => {
const { t } = useTranslation();
const { api, isApiReady, selectedAccount } = usePolkadot();
const [selectedPool, setSelectedPool] = useState<StakingPool | null>(null);
const [stakeAmount, setStakeAmount] = useState('');
const [unstakeAmount, setUnstakeAmount] = useState('');
const [isLoadingPools, setIsLoadingPools] = useState(false);
const stakingPools: StakingPool[] = [
// Real staking pools data from blockchain
const [stakingPools, setStakingPools] = useState<StakingPool[]>([
// Fallback mock data - will be replaced with real data
{
id: '1',
name: 'HEZ Flexible',
token: 'HEZ',
apy: 8.5,
totalStaked: 1500000,
totalStaked: 0,
minStake: 100,
lockPeriod: 0,
userStaked: 5000,
rewards: 42.5
userStaked: 0,
rewards: 0
},
{
id: '2',
name: 'HEZ Locked 30 Days',
token: 'HEZ',
apy: 12.0,
totalStaked: 3200000,
totalStaked: 0,
minStake: 500,
lockPeriod: 30,
userStaked: 10000,
rewards: 100
userStaked: 0,
rewards: 0
},
{
id: '3',
name: 'PEZ High Yield',
token: 'PEZ',
apy: 15.5,
totalStaked: 800000,
totalStaked: 0,
minStake: 1000,
lockPeriod: 60,
userStaked: 0,
@@ -66,27 +73,158 @@ export const StakingDashboard: React.FC = () => {
name: 'PEZ Governance',
token: 'PEZ',
apy: 18.0,
totalStaked: 2100000,
totalStaked: 0,
minStake: 2000,
lockPeriod: 90,
userStaked: 25000,
rewards: 375
userStaked: 0,
rewards: 0
}
];
]);
const handleStake = (pool: StakingPool) => {
console.log('Staking', stakeAmount, pool.token, 'in pool', pool.name);
// Implement staking logic
// Fetch staking pools data from blockchain
useEffect(() => {
const fetchStakingData = async () => {
if (!api || !isApiReady) {
return;
}
setIsLoadingPools(true);
try {
// TODO: Query staking pools from chain
// This would query your custom staking pallet
// const pools = await api.query.staking.pools.entries();
// For now, using mock data
// In real implementation, parse pool data from chain
console.log('Staking pools would be fetched from chain here');
// If user is connected, fetch their staking info
if (selectedAccount) {
// TODO: Query user staking positions
// const userStakes = await api.query.staking.ledger(selectedAccount.address);
// Update stakingPools with user data
}
} catch (error) {
console.error('Failed to fetch staking data:', error);
toast({
title: 'Error',
description: 'Failed to fetch staking pools',
variant: 'destructive',
});
} finally {
setIsLoadingPools(false);
}
};
fetchStakingData();
}, [api, isApiReady, selectedAccount]);
const handleStake = async (pool: StakingPool) => {
if (!api || !selectedAccount) {
toast({
title: 'Error',
description: 'Please connect your wallet',
variant: 'destructive',
});
return;
}
if (!stakeAmount || parseFloat(stakeAmount) < pool.minStake) {
toast({
title: 'Error',
description: `Minimum stake is ${pool.minStake} ${pool.token}`,
variant: 'destructive',
});
return;
}
try {
// TODO: Implement staking transaction
// const assetId = ASSET_IDS[pool.token];
// const amount = parseAmount(stakeAmount, 12);
// await api.tx.staking.stake(pool.id, amount).signAndSend(...);
console.log('Staking', stakeAmount, pool.token, 'in pool', pool.name);
toast({
title: 'Success',
description: `Staked ${stakeAmount} ${pool.token}`,
});
setStakeAmount('');
setSelectedPool(null);
} catch (error: any) {
console.error('Staking failed:', error);
toast({
title: 'Error',
description: error.message || 'Staking failed',
variant: 'destructive',
});
}
};
const handleUnstake = (pool: StakingPool) => {
console.log('Unstaking', unstakeAmount, pool.token, 'from pool', pool.name);
// Implement unstaking logic
const handleUnstake = async (pool: StakingPool) => {
if (!api || !selectedAccount) {
toast({
title: 'Error',
description: 'Please connect your wallet',
variant: 'destructive',
});
return;
}
try {
// TODO: Implement unstaking transaction
// const amount = parseAmount(unstakeAmount, 12);
// await api.tx.staking.unstake(pool.id, amount).signAndSend(...);
console.log('Unstaking', unstakeAmount, pool.token, 'from pool', pool.name);
toast({
title: 'Success',
description: `Unstaked ${unstakeAmount} ${pool.token}`,
});
setUnstakeAmount('');
setSelectedPool(null);
} catch (error: any) {
console.error('Unstaking failed:', error);
toast({
title: 'Error',
description: error.message || 'Unstaking failed',
variant: 'destructive',
});
}
};
const handleClaimRewards = (pool: StakingPool) => {
console.log('Claiming rewards from pool', pool.name);
// Implement claim rewards logic
const handleClaimRewards = async (pool: StakingPool) => {
if (!api || !selectedAccount) {
toast({
title: 'Error',
description: 'Please connect your wallet',
variant: 'destructive',
});
return;
}
try {
// TODO: Implement claim rewards transaction
// await api.tx.staking.claimRewards(pool.id).signAndSend(...);
console.log('Claiming rewards from pool', pool.name);
toast({
title: 'Success',
description: `Claimed ${pool.rewards} ${pool.token} rewards`,
});
} catch (error: any) {
console.error('Claim rewards failed:', error);
toast({
title: 'Error',
description: error.message || 'Claim rewards failed',
variant: 'destructive',
});
}
};
const totalStaked = stakingPools.reduce((sum, pool) => sum + (pool.userStaked || 0), 0);