- 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
194 lines
5.8 KiB
TypeScript
194 lines
5.8 KiB
TypeScript
import { ethers, providers } from "ethers";
|
|
import Safe, { SafeFactory, SafeAccountConfig } from "@safe-global/safe-core-sdk";
|
|
import EthersAdapter from "@safe-global/safe-ethers-lib";
|
|
import { SafeInfo, SmartWalletConfig, OwnerInfo, SmartWalletType } from "../../types";
|
|
|
|
// Gnosis Safe Factory contract addresses per network
|
|
// Note: These are the Safe Factory addresses, not the Safe contract itself
|
|
// The Safe SDK handles the correct addresses internally
|
|
const SAFE_FACTORY_ADDRESSES: Record<number, string> = {
|
|
1: "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2", // Mainnet - Safe Factory v1.3.0
|
|
5: "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2", // Goerli - Safe Factory v1.3.0
|
|
100: "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2", // Gnosis Chain
|
|
137: "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2", // Polygon
|
|
42161: "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2", // Arbitrum
|
|
10: "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2", // Optimism
|
|
8453: "0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2", // Base
|
|
};
|
|
|
|
// Note: The Safe SDK uses its own internal address resolution
|
|
// These addresses are for reference only
|
|
|
|
export async function getSafeInfo(
|
|
safeAddress: string,
|
|
provider: providers.Provider
|
|
): Promise<SafeInfo | null> {
|
|
try {
|
|
// Validate address
|
|
if (!ethers.utils.isAddress(safeAddress)) {
|
|
throw new Error("Invalid Safe address");
|
|
}
|
|
|
|
const network = await provider.getNetwork();
|
|
|
|
// Verify this is actually a Safe contract by checking for Safe-specific functions
|
|
const safeContract = new ethers.Contract(
|
|
safeAddress,
|
|
[
|
|
"function getOwners() view returns (address[])",
|
|
"function getThreshold() view returns (uint256)",
|
|
"function nonce() view returns (uint256)",
|
|
"function VERSION() view returns (string)",
|
|
],
|
|
provider
|
|
);
|
|
|
|
// Try to get VERSION to verify it's a Safe
|
|
let isSafe = false;
|
|
try {
|
|
await safeContract.VERSION();
|
|
isSafe = true;
|
|
} catch {
|
|
// Not a Safe contract
|
|
isSafe = false;
|
|
}
|
|
|
|
if (!isSafe) {
|
|
throw new Error("Address is not a valid Safe contract");
|
|
}
|
|
|
|
const [owners, threshold] = await Promise.all([
|
|
safeContract.getOwners(),
|
|
safeContract.getThreshold(),
|
|
]);
|
|
|
|
// Validate owners array
|
|
if (!Array.isArray(owners) || owners.length === 0) {
|
|
throw new Error("Invalid Safe configuration: no owners");
|
|
}
|
|
|
|
// Validate threshold
|
|
const thresholdNum = threshold.toNumber();
|
|
if (thresholdNum < 1 || thresholdNum > owners.length) {
|
|
throw new Error("Invalid Safe configuration: invalid threshold");
|
|
}
|
|
|
|
const balance = await provider.getBalance(safeAddress);
|
|
|
|
return {
|
|
safeAddress: ethers.utils.getAddress(safeAddress), // Ensure checksummed
|
|
network: network.name as any,
|
|
ethBalance: balance.toString(),
|
|
owners: owners.map((o: string) => ethers.utils.getAddress(o)), // Checksum all owners
|
|
threshold: thresholdNum,
|
|
};
|
|
} catch (error: any) {
|
|
console.error("Failed to get Safe info", error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function connectToSafe(
|
|
safeAddress: string,
|
|
networkId: number,
|
|
provider: providers.Provider
|
|
): Promise<SmartWalletConfig | null> {
|
|
// Validate address
|
|
if (!ethers.utils.isAddress(safeAddress)) {
|
|
throw new Error("Invalid Safe address");
|
|
}
|
|
|
|
const checksummedAddress = ethers.utils.getAddress(safeAddress);
|
|
const safeInfo = await getSafeInfo(checksummedAddress, provider);
|
|
if (!safeInfo) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
id: `safe_${checksummedAddress}_${networkId}`,
|
|
type: SmartWalletType.GNOSIS_SAFE,
|
|
address: checksummedAddress,
|
|
networkId,
|
|
owners: (safeInfo as any).owners || [],
|
|
threshold: (safeInfo as any).threshold || 1,
|
|
createdAt: Date.now(),
|
|
updatedAt: Date.now(),
|
|
};
|
|
}
|
|
|
|
export async function deploySafe(
|
|
owners: string[],
|
|
threshold: number,
|
|
provider: providers.Provider,
|
|
signer: ethers.Signer
|
|
): Promise<string | null> {
|
|
try {
|
|
// Validate inputs
|
|
if (!owners || owners.length === 0) {
|
|
throw new Error("At least one owner is required");
|
|
}
|
|
|
|
if (threshold < 1 || threshold > owners.length) {
|
|
throw new Error("Threshold must be between 1 and owner count");
|
|
}
|
|
|
|
// Validate and checksum all owner addresses
|
|
const validatedOwners = owners.map((owner) => {
|
|
if (!ethers.utils.isAddress(owner)) {
|
|
throw new Error(`Invalid owner address: ${owner}`);
|
|
}
|
|
return ethers.utils.getAddress(owner);
|
|
});
|
|
|
|
// Check for duplicate owners
|
|
const uniqueOwners = new Set(validatedOwners.map(o => o.toLowerCase()));
|
|
if (uniqueOwners.size !== validatedOwners.length) {
|
|
throw new Error("Duplicate owner addresses are not allowed");
|
|
}
|
|
|
|
const ethAdapter = new EthersAdapter({
|
|
ethers,
|
|
signerOrProvider: signer,
|
|
});
|
|
|
|
const safeFactory = await (SafeFactory as any).init({ ethAdapter });
|
|
const safeAccountConfig: SafeAccountConfig = {
|
|
owners: validatedOwners,
|
|
threshold,
|
|
};
|
|
|
|
const safeSdk = await safeFactory.deploySafe({ safeAccountConfig });
|
|
const safeAddress = safeSdk.getAddress();
|
|
|
|
return safeAddress;
|
|
} catch (error: any) {
|
|
console.error("Failed to deploy Safe", error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
export async function getSafeSDK(
|
|
safeAddress: string,
|
|
provider: providers.Provider,
|
|
signer?: ethers.Signer
|
|
): Promise<Safe | null> {
|
|
try {
|
|
// Validate address
|
|
if (!ethers.utils.isAddress(safeAddress)) {
|
|
throw new Error("Invalid Safe address");
|
|
}
|
|
|
|
const checksummedAddress = ethers.utils.getAddress(safeAddress);
|
|
const ethAdapter = new EthersAdapter({
|
|
ethers,
|
|
signerOrProvider: signer || provider,
|
|
});
|
|
|
|
const safeSdk = await (Safe as any).init({ ethAdapter, safeAddress: checksummedAddress });
|
|
return safeSdk;
|
|
} catch (error: any) {
|
|
console.error("Failed to initialize Safe SDK", error);
|
|
return null;
|
|
}
|
|
}
|