Files
impersonator/components/Body/AddressInput/AddressBook/index.tsx
defiQUG 55fe7d10eb feat: comprehensive project improvements and fixes
- 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
2026-01-14 02:17:26 -08:00

221 lines
6.6 KiB
TypeScript

import { useState, useEffect } from "react";
import {
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
HStack,
ModalCloseButton,
ModalBody,
Text,
Input,
Center,
Button,
Box,
} from "@chakra-ui/react";
import { DeleteIcon } from "@chakra-ui/icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faSave } from "@fortawesome/free-solid-svg-icons";
import { slicedText } from "../../TransactionRequests";
import { SecureStorage } from "@/utils/encryption";
import { validateAddress } from "@/utils/security";
import { STORAGE_KEYS } from "@/utils/constants";
const secureStorage = new SecureStorage();
interface SavedAddressInfo {
address: string;
label: string;
}
interface AddressBookParams {
isAddressBookOpen: boolean;
closeAddressBook: () => void;
showAddress: string;
setShowAddress: (value: string) => void;
setAddress: (value: string) => void;
}
function AddressBook({
isAddressBookOpen,
closeAddressBook,
showAddress,
setShowAddress,
setAddress,
}: AddressBookParams) {
const [newAddressInput, setNewAddressInput] = useState<string>("");
const [newLableInput, setNewLabelInput] = useState<string>("");
const [savedAddresses, setSavedAddresses] = useState<SavedAddressInfo[]>([]);
useEffect(() => {
const loadAddresses = async () => {
try {
const stored = await secureStorage.getItem(STORAGE_KEYS.ADDRESS_BOOK);
if (stored) {
const parsed = JSON.parse(stored) as SavedAddressInfo[];
setSavedAddresses(parsed);
}
} catch (error) {
console.error("Failed to load address book:", error);
// Try to migrate from plain localStorage
try {
const legacy = localStorage.getItem("address-book");
if (legacy) {
const parsed = JSON.parse(legacy) as SavedAddressInfo[];
await secureStorage.setItem(STORAGE_KEYS.ADDRESS_BOOK, legacy);
localStorage.removeItem("address-book");
setSavedAddresses(parsed);
}
} catch (migrationError) {
console.error("Failed to migrate address book:", migrationError);
}
}
};
loadAddresses();
}, []);
useEffect(() => {
setNewAddressInput(showAddress);
}, [showAddress]);
useEffect(() => {
const saveAddresses = async () => {
if (savedAddresses.length > 0) {
try {
await secureStorage.setItem(
STORAGE_KEYS.ADDRESS_BOOK,
JSON.stringify(savedAddresses)
);
} catch (error) {
console.error("Failed to save address book:", error);
}
} else {
secureStorage.removeItem(STORAGE_KEYS.ADDRESS_BOOK);
}
};
saveAddresses();
}, [savedAddresses]);
// reset label when modal is reopened
useEffect(() => {
setNewLabelInput("");
}, [isAddressBookOpen]);
return (
<Modal isOpen={isAddressBookOpen} onClose={closeAddressBook} isCentered>
<ModalOverlay bg="none" backdropFilter="auto" backdropBlur="5px" />
<ModalContent
minW={{
base: 0,
sm: "30rem",
md: "40rem",
lg: "60rem",
}}
pb="6"
bg={"brand.lightBlack"}
>
<ModalHeader>Address Book</ModalHeader>
<ModalCloseButton />
<ModalBody>
<HStack>
<Input
placeholder="address / ens"
value={newAddressInput}
onChange={(e) => setNewAddressInput(e.target.value)}
/>
<Input
placeholder="label"
value={newLableInput}
onChange={(e) => setNewLabelInput(e.target.value)}
/>
</HStack>
<Center mt="3">
<Button
colorScheme={"blue"}
isDisabled={
newAddressInput.length === 0 || newLableInput.length === 0
}
onClick={async () => {
// Validate address
const validation = validateAddress(newAddressInput);
if (!validation.valid) {
// Show error (would use toast in production)
console.error("Invalid address:", validation.error);
return;
}
const checksummedAddress = validation.checksummed!;
// Check for duplicates
const isDuplicate = savedAddresses.some(
(a) => a.address.toLowerCase() === checksummedAddress.toLowerCase()
);
if (isDuplicate) {
console.error("Address already exists in address book");
return;
}
setSavedAddresses([
...savedAddresses,
{
address: checksummedAddress,
label: newLableInput,
},
]);
}}
>
<HStack>
<FontAwesomeIcon icon={faSave} />
<Text>Save</Text>
</HStack>
</Button>
</Center>
{savedAddresses.length > 0 && (
<Box mt="6" px="20">
<Text fontWeight={"bold"}>Select from saved addresses:</Text>
<Box mt="3" px="10">
{savedAddresses.map(({ address, label }, i) => (
<HStack key={i} mt="2">
<Button
key={i}
w="100%"
onClick={() => {
setShowAddress(address);
setAddress(address);
closeAddressBook();
}}
>
{label} (
{address.indexOf(".eth") >= 0
? address
: slicedText(address)}
)
</Button>
<Button
ml="2"
_hover={{
bg: "red.500",
}}
onClick={() => {
const _savedAddresses = savedAddresses;
_savedAddresses.splice(i, 1);
// using spread operator, else useEffect doesn't detect state change
setSavedAddresses([..._savedAddresses]);
}}
>
<DeleteIcon />
</Button>
</HStack>
))}
</Box>
</Box>
)}
</ModalBody>
</ModalContent>
</Modal>
);
}
export default AddressBook;