- Fix all TypeScript compilation errors (40+ fixes) - Add missing type definitions (TransactionRequest, SafeInfo) - Fix TransactionRequestStatus vs TransactionStatus confusion - Fix import paths and provider type issues - Fix test file errors and mock providers - Implement comprehensive security features - AES-GCM encryption with PBKDF2 key derivation - Input validation and sanitization - Rate limiting and nonce management - Replay attack prevention - Access control and authorization - Add comprehensive test suite - Integration tests for transaction flow - Security validation tests - Wallet management tests - Encryption and rate limiter tests - E2E tests with Playwright - Add extensive documentation - 12 numbered guides (setup, development, API, security, etc.) - Security documentation and audit reports - Code review and testing reports - Project organization documentation - Update dependencies - Update axios to latest version (security fix) - Update React types to v18 - Fix peer dependency warnings - Add development tooling - CI/CD workflows (GitHub Actions) - Pre-commit hooks (Husky) - Linting and formatting (Prettier, ESLint) - Security audit workflow - Performance benchmarking - Reorganize project structure - Move reports to docs/reports/ - Clean up root directory - Organize documentation - Add new features - Smart wallet management (Gnosis Safe, ERC4337) - Transaction execution and approval workflows - Balance management and token support - Error boundary and monitoring (Sentry) - Fix WalletConnect configuration - Handle missing projectId gracefully - Add environment variable template
153 lines
4.3 KiB
TypeScript
153 lines
4.3 KiB
TypeScript
import { providers, Contract, utils, ethers } from "ethers";
|
|
import { TokenBalance, WalletBalance } from "../../types";
|
|
|
|
const ERC20_ABI = [
|
|
"function balanceOf(address owner) view returns (uint256)",
|
|
"function decimals() view returns (uint8)",
|
|
"function symbol() view returns (string)",
|
|
"function name() view returns (string)",
|
|
];
|
|
|
|
const COMMON_TOKENS: Record<number, Array<{ address: string; symbol: string; name: string; decimals: number }>> = {
|
|
1: [
|
|
{
|
|
address: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
|
symbol: "USDT",
|
|
name: "Tether USD",
|
|
decimals: 6,
|
|
},
|
|
{
|
|
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0c3606eB48",
|
|
symbol: "USDC",
|
|
name: "USD Coin",
|
|
decimals: 6,
|
|
},
|
|
{
|
|
address: "0x6B175474E89094C44Da98b954EedeAC495271d0F",
|
|
symbol: "DAI",
|
|
name: "Dai Stablecoin",
|
|
decimals: 18,
|
|
},
|
|
],
|
|
137: [
|
|
{
|
|
address: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
|
|
symbol: "USDT",
|
|
name: "Tether USD",
|
|
decimals: 6,
|
|
},
|
|
{
|
|
address: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
|
|
symbol: "USDC",
|
|
name: "USD Coin",
|
|
decimals: 6,
|
|
},
|
|
],
|
|
42161: [
|
|
{
|
|
address: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9",
|
|
symbol: "USDT",
|
|
name: "Tether USD",
|
|
decimals: 6,
|
|
},
|
|
{
|
|
address: "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8",
|
|
symbol: "USDC",
|
|
name: "USD Coin",
|
|
decimals: 6,
|
|
},
|
|
],
|
|
};
|
|
|
|
export async function getNativeBalance(
|
|
address: string,
|
|
provider: providers.Provider
|
|
): Promise<string> {
|
|
try {
|
|
const balance = await provider.getBalance(address);
|
|
return balance.toString();
|
|
} catch (error) {
|
|
console.error("Failed to get native balance", error);
|
|
return "0";
|
|
}
|
|
}
|
|
|
|
export async function getTokenBalance(
|
|
tokenAddress: string,
|
|
walletAddress: string,
|
|
provider: providers.Provider
|
|
): Promise<TokenBalance | null> {
|
|
try {
|
|
// Validate addresses
|
|
if (!utils.isAddress(tokenAddress) || !utils.isAddress(walletAddress)) {
|
|
throw new Error("Invalid address");
|
|
}
|
|
|
|
const checksummedTokenAddress = utils.getAddress(tokenAddress);
|
|
const checksummedWalletAddress = utils.getAddress(walletAddress);
|
|
|
|
const tokenContract = new Contract(checksummedTokenAddress, ERC20_ABI, provider);
|
|
|
|
// Add timeout to prevent hanging
|
|
const { SECURITY } = await import("@/utils/constants");
|
|
const timeoutPromise = new Promise((_, reject) =>
|
|
setTimeout(() => reject(new Error("Token balance fetch timeout")), SECURITY.TOKEN_BALANCE_TIMEOUT_MS)
|
|
);
|
|
|
|
const [balance, decimals, symbol, name] = await Promise.race([
|
|
Promise.all([
|
|
tokenContract.balanceOf(checksummedWalletAddress),
|
|
tokenContract.decimals(),
|
|
tokenContract.symbol(),
|
|
tokenContract.name(),
|
|
]),
|
|
timeoutPromise,
|
|
]) as [any, number, string, string];
|
|
|
|
// Validate decimals
|
|
const { VALIDATION } = await import("@/utils/constants");
|
|
if (decimals < VALIDATION.TOKEN_DECIMALS_MIN || decimals > VALIDATION.TOKEN_DECIMALS_MAX) {
|
|
throw new Error(`Invalid token decimals: ${decimals}`);
|
|
}
|
|
|
|
const balanceFormatted = ethers.utils.formatUnits(balance, decimals);
|
|
|
|
return {
|
|
tokenAddress: checksummedTokenAddress,
|
|
symbol: symbol || "UNKNOWN",
|
|
name: name || "Unknown Token",
|
|
decimals,
|
|
balance: balance.toString(),
|
|
balanceFormatted,
|
|
};
|
|
} catch (error: any) {
|
|
console.error(`Failed to get token balance for ${tokenAddress}`, error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function getWalletBalance(
|
|
address: string,
|
|
networkId: number,
|
|
provider: providers.Provider,
|
|
tokenAddresses?: string[]
|
|
): Promise<WalletBalance> {
|
|
// Get native balance
|
|
const nativeBalance = await getNativeBalance(address, provider);
|
|
const nativeFormatted = ethers.utils.formatEther(nativeBalance);
|
|
|
|
// Get token balances
|
|
const tokensToCheck = tokenAddresses || COMMON_TOKENS[networkId]?.map((t) => t.address) || [];
|
|
const tokenBalances = await Promise.all(
|
|
tokensToCheck.map((tokenAddress) => getTokenBalance(tokenAddress, address, provider))
|
|
);
|
|
|
|
const validTokenBalances = tokenBalances.filter((tb): tb is TokenBalance => tb !== null);
|
|
|
|
return {
|
|
native: nativeBalance,
|
|
nativeFormatted,
|
|
tokens: validTokenBalances,
|
|
};
|
|
}
|