Contract Configuration
The contract configuration module serves as the central source of truth for smart contract addresses and Application Binary Interfaces (ABIs) across all supported networks in RitoSwap. This critical infrastructure component enables seamless multichain functionality by dynamically selecting the appropriate contract address based on the runtime environment.
Configuration Architecture
The contract configuration system implements a sophisticated yet elegant approach to managing contract addresses across multiple blockchain networks. By leveraging environment variables and automated address management, it ensures that the application always interacts with the correct smart contract deployment.
Address Resolution Strategy
The system uses a hierarchical decision tree to determine which contract address to use:
const USE_RITONET = process.env.NEXT_PUBLIC_RITONET === 'true';
const USE_SEPOLIA = process.env.NEXT_PUBLIC_SEPOLIA === 'true';
export const KEY_TOKEN_ADDRESS = (
USE_RITONET
? (localhostAddress.OnePerWalletKeyToken.address as Address)
: USE_SEPOLIA
? (sepoliaAddress.OnePerWalletKeyToken.address as Address)
: (mainnetAddress.OnePerWalletKeyToken.address as Address)
);
This ternary chain evaluates environment flags in priority order, ensuring development and testing environments take precedence over production. The type assertion to Address
ensures type safety throughout the application when interacting with Viem and wagmi.
Address Source Files
Contract addresses are automatically populated by the deployment process in the colored-keys workspace. When contracts are deployed, their addresses are saved to JSON files in the monorepo root:
- local-blockchain.json
- hardhat.json
- mainnet.json
- sepolia.json
Each JSON file follows a consistent structure:
{
"OnePerWalletKeyToken": {
"address": "0x...",
"blockNumber": 123456,
"deployer": "0x..."
}
}
This automation eliminates manual address management and reduces deployment errors. The import aliases @Contract/network.json
provide clean, maintainable imports regardless of the project structure.
ABI Management
The module organizes ABIs into logical groupings that reflect their usage patterns within the application. This modular approach improves code readability and enables selective importing of only required functions.
OnePerWallet ABI
The onePerWalletAbi
contains functions specific to the single-token-per-wallet constraint that defines the Colored Keys NFT system:
Function | Type | Purpose |
---|---|---|
mint() | Write | Creates a new NFT for the caller if they don’t own one |
balanceOf(address) | Read | Returns the number of tokens owned (0 or 1) |
getTokenOfOwner(address) | Read | Returns token ID and ownership status for an address |
The getTokenOfOwner
function provides a gas-efficient way to check both ownership status and retrieve the token ID in a single call, which is particularly useful for UI updates.
Key Token ABI
The keyTokenAbi
contains standard ERC-721 functions plus custom extensions for the Colored Keys system:
Function | Type | Purpose |
---|---|---|
burn(uint256) | Write | Destroys a token, allowing the owner to mint a new one |
getTokenColors(uint256) | Read | Returns the algorithmically generated colors for a token |
transferFrom(...) | Write | Transfers token ownership between addresses |
tokenURI(uint256) | Read | Returns the on-chain metadata and image for a token |
The inclusion of both transferFrom
and safeTransferFrom
ensures compatibility with various NFT marketplaces and protocols that may prefer one method over the other.
Combined ABI
The fullKeyTokenAbi
merges both ABI arrays, providing a comprehensive interface for all contract interactions:
export const fullKeyTokenAbi = [...onePerWalletAbi, ...keyTokenAbi] as const
The as const
assertion ensures TypeScript treats the ABI as a readonly tuple, enabling precise type inference in wagmi hooks. This type safety prevents runtime errors from incorrect function calls or parameter types.
Environment Configuration
The contract configuration relies on environment variables to determine network selection. These variables follow a specific naming convention for clarity and consistency:
Environment variables must be prefixed with NEXT_PUBLIC_
to be accessible in the browser runtime. This is a Next.js security feature that prevents accidental exposure of server-side secrets.
Network Selection Variables
Variable | Purpose | Example Value |
---|---|---|
NEXT_PUBLIC_RITONET | Enables local RitoNet network | "true" or undefined |
NEXT_PUBLIC_SEPOLIA | Enables Sepolia testnet | "true" or undefined |
The absence of both variables defaults to Ethereum mainnet, providing a safe production default. This design prevents accidental testnet deployment in production environments.
Integration Patterns
The contract configuration module integrates seamlessly with wagmi hooks throughout the application. Here are common usage patterns:
Reading Contract Data
import { useReadContract } from 'wagmi';
import { KEY_TOKEN_ADDRESS, fullKeyTokenAbi } from '@/app/config/contracts';
function useTokenOwnership(address: Address) {
return useReadContract({
address: KEY_TOKEN_ADDRESS,
abi: fullKeyTokenAbi,
functionName: 'getTokenOfOwner',
args: [address],
});
}
Writing Contract Data
import { useWriteContract } from 'wagmi';
import { KEY_TOKEN_ADDRESS, fullKeyTokenAbi } from '@/app/config/contracts';
function useMintNFT() {
const { writeContract, ...rest } = useWriteContract();
const mint = useCallback(() => {
writeContract({
address: KEY_TOKEN_ADDRESS,
abi: fullKeyTokenAbi,
functionName: 'mint',
});
}, [writeContract]);
return { mint, ...rest };
}
Event Monitoring
import { useWatchContractEvent } from 'wagmi';
import { KEY_TOKEN_ADDRESS, fullKeyTokenAbi } from '@/app/config/contracts';
function useTransferEvents(onTransfer: (event: any) => void) {
useWatchContractEvent({
address: KEY_TOKEN_ADDRESS,
abi: fullKeyTokenAbi,
eventName: 'Transfer',
onLogs: onTransfer,
});
}
Type Safety Benefits
The configuration module leverages TypeScript’s type system to provide compile-time guarantees:
Address Type Safety
By casting addresses to Viem’s Address
type, the system ensures that only valid Ethereum addresses can be used in contract calls. This prevents common errors like passing undefined or malformed addresses.
ABI Type Inference
The as const
assertion on ABIs enables wagmi to infer exact function signatures, including parameter names and types. This provides autocomplete support and catches type mismatches at compile time rather than runtime.
Network-Aware Types
The configuration could be extended to provide network-specific types:
type NetworkConfig = {
[K in 'ritonet' | 'sepolia' | 'mainnet']: {
address: Address;
chainId: number;
name: string;
};
};
This pattern enables even stronger type safety when building network-aware features.
Deployment Integration
The contract configuration module integrates tightly with the deployment workflow. When deploying contracts through the colored-keys workspace:
Step 1: Contract Compilation
Hardhat compiles the Solidity contracts and generates artifacts.
Step 2: Network Deployment
The deployment script deploys to the specified network.
Step 3: Address Recording
The deployed contract address is automatically saved to the corresponding JSON file.
Step 4: Configuration Update
The dApp’s contract configuration immediately reflects the new deployment.
Step 5: Type Generation
TypeScript types are regenerated to match the new deployment.
This automation ensures zero-downtime updates and eliminates manual configuration errors.
Error Prevention
The configuration module implements several strategies to prevent common errors:
Missing Address Files
If an address file is missing, the TypeScript compiler will fail at build time rather than runtime:
// This will cause a build error if the file doesn't exist
import localhostAddress from '@Contract/local-blockchain.json'
Invalid Network Configuration
The priority-based network selection ensures that invalid configurations fall back to safe defaults rather than failing entirely.
Type Mismatches
Wagmi’s type inference catches ABI/function mismatches at compile time, preventing runtime errors from incorrect contract calls.
Best Practices
When working with the contract configuration module, follow these guidelines:
Environment Variable Management
Store environment-specific configurations in .env.local
files that are not committed to version control. Use .env.example
files to document required variables:
# .env.example
NEXT_PUBLIC_RITONET=
NEXT_PUBLIC_SEPOLIA=
ABI Versioning
When updating smart contracts, consider versioning ABIs to support backward compatibility during migration periods:
export const keyTokenAbiV1 = [...] as const;
export const keyTokenAbiV2 = [...] as const;
export const keyTokenAbi = keyTokenAbiV2; // Current version
Network Validation
Always validate that the connected wallet is on the expected network before attempting contract interactions. The useNetworkCheck
hook provides this functionality.
Troubleshooting Common Issues
Contract Address Not Updating
If contract addresses don’t update after deployment, verify that the JSON files in ContractAddresses are being properly generated and that your imports are not cached by the build system.
Type Errors with ABIs
Ensure ABIs are marked as const
to enable proper type inference. Without this assertion, wagmi cannot determine function signatures accurately.
Network Mismatch Errors
If transactions fail due to network mismatches, verify that environment variables are set correctly and that the wallet is connected to the expected network.
Summary
The contract configuration module exemplifies thoughtful architectural design in a multichain environment. By centralizing address management, organizing ABIs logically, and leveraging TypeScript’s type system, it provides a robust foundation for smart contract interactions across all supported networks. The tight integration with the deployment pipeline and comprehensive type safety ensures that developers can focus on building features rather than managing infrastructure details.