This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Origin DeFi's OTokens monorepo containing smart contracts for:
- OUSD (Origin Dollar) - a yield-bearing stablecoin
- OETH (Origin Ether) - an Ethereum liquid staking token
- OS (Origin Sonic) - Sonic chain native token
Deployed on Ethereum Mainnet, Base, Arbitrum, Sonic, Plume, Holesky, and Hoodi. All smart contract work happens in the contracts/ directory.
cd contracts
cp dev.env .env # Set PROVIDER_URL to Alchemy/Infura endpoint
pnpm iKey .env variables: PROVIDER_URL, SONIC_PROVIDER_URL, BASE_PROVIDER_URL, BLOCK_NUMBER, ACCOUNTS_TO_FUND.
pnpm hardhat compile # Compile changed contracts
pnpm clean && pnpm hardhat compile # Full recompilepnpm lint # Run all linters (Solidity + JS)
pnpm lint:sol # solhint for Solidity
pnpm lint:js # eslint for JavaScript
pnpm prettier:check # Check formatting
pnpm prettier # Format all filespnpm test # Mainnet unit tests
pnpm test:base # Base network unit tests
pnpm test:sonic # Sonic network unit tests
pnpm test:coverage # Mainnet unit tests with coverage
pnpm test test/**/FILE_NAME.js # Running a specific test file# Option 1: Fork each time (like CI)
pnpm test:fork # All mainnet fork tests
pnpm test:fork -- test/strategies/foo.fork-test.js # Single fork test file
# Option 2: Nested forking (faster for dev iteration)
FORK=true pnpm run node # Terminal 1: start forked node with deployments
pnpm test:fork # Terminal 2: tests reuse running node
# Other networks
pnpm test:arb-fork
pnpm test:base-fork
pnpm test:sonic-fork
pnpm test:hol-forkexport DEBUG=origin:* # Enable all debug logging
export REPORT_GAS=true # Show gas usage in test output
export CONTRACT_SIZE=true # Show contract sizes after compile- Run repo commands from
contracts/for smart contract work. - After making code changes, run Prettier before finishing.
- For JS edits under
contracts/, runpnpm prettier:js. - For Solidity edits under
contracts/, runpnpm prettier:sol. - If both JS and Solidity files changed, run both commands.
- Prefer the smallest relevant verification after edits.
- Do not reformat or modify unrelated files just to satisfy style.
- Do not fix unrelated failing tests or lint issues unless explicitly asked.
All major contracts use the OpenZeppelin upgradeable proxy pattern. Each has a *Proxy contract (minimal proxy) pointing to an implementation. Proxies are deployed via hardhat-deploy scripts in deploy/.
Vaults (contracts/vault/) are the core of each OToken. They handle:
- Minting/burning OTokens
- Managing collateral allocation across strategies
- Rebalancing via
allocate() - Yield accounting via
rebase()
Each chain/token has its own vault: OUSDVault, OETHVault, OETHBVault (Base), OETHSVault (Sonic) and OETPVault (Plume). OETPVault is being shut down and will be removed from the repo after all funds are withdrawn.
Vault logic is split across two implementation contracts: VaultCore (user-facing mint/redeem) and VaultAdmin (governance functions).
VaultCore inherits from VaultAdmin and is now deployed as a single implementation contract for simplicity.
Previously they were deployed as separate implementations with a shared proxy. This was because the contract was too big to deploy as a single implementation, but after the simplification, it can now be deployed as one.
Located in contracts/strategies/. Each strategy:
- Inherits from
InitializableAbstractStrategy - Implements
deposit(),withdraw(),withdrawAll(),checkBalance(),collectRewardTokens() - Is registered with a vault and allocated collateral
Key strategies: Aave, Compound, Convex/Curve, Balancer, Morpho, Native Staking (SSV validators).
- For SSV Cluster migrations to ETH billing, use the SSV ETH payment calculator: https://ssv-eth-forecasting.vercel.app/
contracts/token/OUSD.sol and contracts/token/OETH.sol - rebasing ERC-20 tokens. OUSD rebases to all holders; OETH uses a similar mechanism for ETH-denominated yield.
contracts/oracle/ - price feed aggregation. OracleRouter routes price queries to appropriate Chainlink feeds or Curve pool oracles, with staleness checks. Each network has its own router.
contracts/harvest/ - collect reward tokens from strategies and swap to yield-bearing assets. Harvester for OUSD, OETHHarvester for OETH, network-specific variants exist.
scripts/defender-actions/ - OpenZeppelin Defender automation scripts for:
doAccounting- periodic vault accountingharvest- automated harvestingsonicRequestWithdrawal/sonicClaimWithdrawals- Sonic staking lifecyclecrossChainRelay- Base ↔ Mainnet CCTP relay
Bundle with: pnpm rollup -c ./scripts/defender-actions/rollup.config.cjs
- CCTP (Circle) for USDC bridging
- Network-specific bridge contracts in
contracts/bridges/
contracts/poolBooster/ - Merkl distribution contracts for incentivizing liquidity pools. PoolBoostCentralRegistry tracks all boosters.
utils/addresses.js- master address registry for all networks/contracts (~32KB)utils/deploy.js- deployment helper functions (use these patterns when writing deploy scripts)utils/constants.js- protocol constants
test/
_fixture.js # Main fixture: deploys all contracts + mocks for unit tests
_fixture-base.js # Base network fixture
_fixture-sonic.js # Sonic network fixture
_hot-deploy.js # Hot deploy support for dev iteration
vault/ # Vault tests (unit + fork)
strategies/ # Strategy tests (unit + fork)
behaviour/ # Shared behavioral test suites (used across strategies)
Fork test files are named *.fork-test.js and run against real deployed contracts on a network fork.
Unit test files are named *.js (without .fork-test) and run against local mocks.
Behavior tests (test/behaviour/) define reusable test suites (e.g., shouldBehaveLikeStrategy) that are composed into strategy-specific test files.
Fixtures: Each test file imports from _fixture.js which uses loadFixture() for snapshot-based test isolation. The fixture deploys mocks and wires up contracts identically to mainnet structure.
Located in deploy/ and numbered sequentially (e.g., 001_ousd.js, 002_vault.js). Each script uses hardhat-deploy plugin conventions - exports a deploy function and tags.
When adding a new deployment script, increment the number and follow existing patterns in utils/deploy.js (especially deployWithConfirmation and withConfirmation).
Four key roles used across all contracts:
- Deployer - deploys contracts (set via
DEPLOYER_PKenv var) - Governor - timelock-controlled governance address (set via
GOVERNOR_PKenv var) - Strategist - multisig for day-to-day operations
- Guardian - emergency pause capability
For fork tests, these addresses are impersonated. Set IMPERSONATE=0x... env var to impersonate any account on a running fork node.
Use yarn (not pnpm) for verification. Always pass --contract flag to avoid slowdowns:
yarn hardhat --network mainnet verify --contract contracts/vault/VaultAdmin.sol:VaultAdmin 0xADDRESSAuto-verify on deploy: VERIFY_CONTRACTS=true pnpm deploy:mainnet
const log = require("../utils/logger")("module-name");
log("something happened");
// Enable: export DEBUG=origin:module-name*