mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-04-25 23:27:59 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user