This repository is a Hardhat-based Solidity monorepo for the Nuva protocol. It contains two largely independent product surfaces plus shared infrastructure:
- Cross-Chain Token System (
contracts/): An ERC20 (CustomToken) bridged across chains via Wormhole/Circle CCTP (CrossChainVault,CrossChainManager,RemoteVault), with aPriceOracleand aGovernanceTimelock. - Prime Vault Stack (
contracts/prime/): A UX-focused ERC4626 staking product comprisingDedicatedVaultRouter,NuvaVault, andRedemptionProxy. - Shared Modules (
contracts/modules/): EIP-3009 transfer-with-authorization, Wormhole/CCTP interfaces, and byte/token utilities.
- Language: Solidity
^0.8.20(compilers0.8.20and0.8.28, both withviaIRand optimizerruns: 200). - Framework: Hardhat
2.26.3withhardhat-toolboxand@openzeppelin/hardhat-upgrades. - Libraries: OpenZeppelin Contracts & Contracts-Upgradeable
5.4.0, Wormhole SDK. - Networks:
sepolia(chainId 11155111) andbaseSepolia(chainId 84532); upgrade manifests live in.openzeppelin/.
- CustomToken: An ERC20 token with additional features like minting, burning, and permit functionality.
- CrossChainVault: Cross-chain token bridge using Wormhole infrastructure.
- CrossChainManager: AML-compliant cross-chain token management system.
- GovernanceTimelock: Time-delayed multisig or DAO executor for secure protocol changes.
- RemoteVault: AML-compliant remote vault token management system (Vault V2).
- PriceOracle: Price oracle for nvTokens.
- DedicatedVaultRouter: UX-focused router for multi-hop ERC4626 vault deposits and asynchronous redemptions.
- NuvaVault: Production ERC4626 vault with UUPS upgradeability, inflation-attack protection, and pausability.
- RedemptionProxy: Disposable EIP-1167 minimal-proxy clone that isolates accounting for asynchronous redemptions.
- CrossChainVault Documentation: Comprehensive guide for the cross-chain token vault contract
- CrossChainManager Documentation: Detailed documentation for the AML-compliant cross-chain manager
- RemoteVault Documentation: Detailed documentation for the AML-compliant remote vault
- PriceOracle Documentation: Detailed documentation for the price oracle
- GovernanceTimelock Documentation: Detailed documentation for the time-delayed multisig or DAO executor
- DedicatedVaultRouter Documentation: Multi-hop ERC4626 deposit router and asynchronous redemption orchestrator
- NuvaVault Documentation: Production ERC4626 vault with inflation-attack protection and pausability
- RedemptionProxy Documentation: Disposable EIP-1167 clone handling isolated asynchronous redemptions
CustomToken(contracts/CustomToken.sol): UUPS-upgradeable ERC20 withERC20Permit,EIP3009(transfer-with-authorization), configurabledecimals, andMINTER_ROLE-gatedmint/burn. Access viaAccessControlEnumerable+Ownable2Step.CrossChainVault(contracts/CrossChainVault.sol): Sends tokens cross-chain using Circle CCTP v1 through a Wormhole executor (ICCTPv1WithExecutor). Gated byWHITELISTED_ROLE; emitsTokensSentwith Wormhole/Circle routing metadata.CrossChainManager(contracts/CrossChainManager.sol): Orchestrates AML-verified deposits and burns, routes to destination chains viaDestinationConfig, and integratesCustomToken+CrossChainVault. UsesBURN_ROLE/BURN_ADMIN_ROLEand ECDSA-based AML signature verification.RemoteVault(contracts/RemoteVault.sol): AML-compliant deposit/withdraw vault (Vault V2) supporting both local and bridged modes, EIP-712 typedDeposit/Withdrawsignatures, permit, and pausability.PriceOracle(contracts/PriceOracle.sol): Stores a CoinGecko-trackablecgPricePerSharewith aheartbeatstaleness guard.PRICING_ROLEsets prices; ownership and role renouncement are disabled.GovernanceTimelock(contracts/GovernanceTimelock.sol): Thin wrapper over OpenZeppelinTimelockControllerfor delayed, multi-sig/DAO-governed execution.
The DedicatedVaultRouter implements a three-layer staking flow:
- Hop 1: Asset (e.g., USDC) ->
assetVaultshares. - Hop 2:
assetVaultshares ->stakingVaultshares. - Hop 3:
stakingVaultshares ->nuvaVaultshares. - Mechanism: Uses
forceApprove+ standardIERC4626.deposit, with per-hop slippage checks (SlippageExceeded) andFundsStuckinvariants ensuring no residual balance remains in the router. - Entry points:
depositUncheckedanddepositWithPermitUncheckedare the active paths. The original AML-gateddeposit/depositWithPermit(and theirrequestRedeem*counterparts) are deprecated — they ignore theamlSignature/amlDeadlineargs and emitFunctionDeprecatedWarning.
To isolate accounting and handle asynchronous lock periods, redemptions use EIP-1167 minimal-proxy clones of RedemptionProxy:
- Request: User calls
requestRedeemUnchecked(or permit variant) with a_deadline(≤MAX_DEADLINE= 24h). The router clonesRedemptionProxy, validates the clone's codehash against the expected EIP-1167 bytecode, escrows the user's Nuva shares, and records aUserFundsentry stamped with the currentredemptionImplementationEpoch. - Accept / Reject: A keeper batch-processes requests via
acceptRedeemRequests/rejectRedeemRequests. Each proxy is isolated through an external self-call (_doAcceptRedeemRequest/_doRejectRedeemRequest) so one failure cannot revert the batch (RedemptionAcceptFailed/RedemptionRejectFailed). - Unwind: On accept, the proxy unwinds Nuva -> staking -> asset shares and calls
assetVault.requestRedeem(async lock). - Sweep: After the async period, the keeper calls
sweepRedemptionsto push assets from proxies back to users (RedemptionsSwept), deleting mappings only when a proxy is fully drained. - User safety valves:
reclaimExpiredShares(router) lets a user recover escrowed shares after the deadline;rescueSweepandcancelRedemption(proxy) let the user recover funds/shares after a 7-day timeout.
setRedemptionProxyImplementation bumps redemptionImplementationEpoch. Pending requests stamped with a stale epoch are frozen (RedemptionFrozen): they can only be refunded via rejectRedeemRequests, never accepted. This prevents a newly configured proxy implementation from being applied retroactively to active positions.
- Standards:
ERC4626Upgradeable+ERC20PermitUpgradeable. - Upgradeability: UUPS, owner-authorized.
- Inflation Protection:
_decimalsOffset()of 12 to deter share-manipulation/donation attacks. - Pausability:
PAUSER_ROLEcan pause deposits/mints/withdrawals/redeems (max*return 0 while paused). ERC20 transfers remain enabled during pause to preserve secondary-market access. - Deprecated AML:
depositWithPermit/redeemWithPermitAML variants are deprecated in favor of the*Uncheckedvariants.
eip-3009/:EIP3009,EIP712,ECRecoverimplementing transfer-with-authorization forCustomToken.wormhole/:ICCTPv1WithExecutorplusExecutorArgs/FeeArgsstructs for CCTP relaying.utils/:BytesLib(byte slicing) andICustomTokeninterface.
- Ownable2Step: "Heavy" administration — upgrades, setting the master
RedemptionProxyimplementation, managing roles. Ownership renouncement is disabled across contracts. - AccessControl(Enumerable): "Operational" roles.
KEEPER_ROLE(router):acceptRedeemRequests,rejectRedeemRequests,sweepRedemptions— intended for automated bot wallets.PAUSER_ROLE(NuvaVault),MINTER_ROLE(CustomToken),PRICING_ROLE(PriceOracle),WHITELISTED_ROLE(CrossChainVault),BURN_ROLE/BURN_ADMIN_ROLE(CrossChainManager).DEFAULT_ADMIN_ROLE: used only by contracts that explicitly grant it during initialization; Prime stack operational roles are managed through owner-only helper functions.
- Node.js (v16 or later)
- npm or yarn
- Hardhat
-
Clone the repository:
git clone <repository-url> cd nuva-evm-contracts
-
Install dependencies:
npm install # or yarn install
Run the test suite:
# Run all tests
npm test
# Run tests with gas reporting
REPORT_GAS=true npm test
# Run specific test file
npx hardhat test test/RemoteVault.js
# Run tests with detailed output
npx hardhat test --verbosenpm run lint: Check code stylenpm run lint:fix: Automatically fix code style issuesnpm run lint:solfix: Format Solidity code stylenpm run lint:solcheck: Check Solidity code stylenpx hardhat compile: Compile contractsnpx hardhat clean: Clean cache and artifactsnpx hardhat node: Start local Ethereum nodenpx hardhat coverage: Generate test coverage reportnpx hardhat verify --network sepolia <contract_address>: Verify contract on Etherscan
After any contract modification, run the verification suite:
npm run lint:solcheck # Solidity style check (lint:solfix to auto-format)
npm run lint # JS/TS style check (lint:fix to auto-format)
npx hardhat compile # Compile
npx hardhat test # Full test suite
npx hardhat test test/DedicatedVaultRouter.js # Single suiteCI (.github/workflows/ci.yml) runs compile + tests on push/PR to main and publishes a JUnit summary.
- NatSpec: All public/external functions, events, and state variables must have full
@notice,@param, and@returntags. - Specific Imports: Avoid global imports. Use
{ Symbol } from "path". - Storage Safety: Maintain a
uint256[N] private __gapin all upgradeable contracts, decremented as storage slots are consumed. Deprecated slots are preserved and renamed (__deprecated_*with@custom:oz-renamed-from) rather than removed, and existing gap sizes are kept to match prior deployments. - Initialization: Use
_unchainedinitializers withininitializeto prevent double-initialization across multiple inheritance, and disable initializers in the constructor (_disableInitializers()). - Safety patterns:
nonReentranton external state-changing flows,forceApprovefor allowances,SafeERC20for transfers, balance-delta invariants (FundsStuck) to detect fee-on-transfer/leftover funds, and per-item isolation via try/catch in keeper batch operations.
This project includes security features such as:
- Access control with OpenZeppelin's
AccessControlandOwnable2Step - Reentrancy protection (
ReentrancyGuard) - Input validation and balance-delta invariants
- ERC4626 inflation-attack protection via
_decimalsOffset() - Pausable emergency stops on vault operations
- ECDSA/EIP-712 signature verification (AML paths retained for legacy cross-chain flows; deprecated in the Prime stack)
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request