# kpk On-Chain Investment Vehicles (OIVs) Tooling and smart contracts for deploying **tokenized funds ("OIVs") entirely on-chain** — and giving each fund the **same identity (same addresses) across every supported EVM chain**. A fund is not a single contract. It is a small stack of Safe + [Zodiac Roles](https://www.zodiac.wiki/documentation/roles-modifier) contracts plus an ERC-20 shares token, wired together. This repo provides: - **`KpkOivFactory`** — an on-chain factory that deploys and wires a complete fund stack in **one transaction**, at deterministic addresses. - **`CcipOivDeployer`** — a Chainlink CCIP orchestrator that, from **one transaction on any wired chain**, fans the operational stack out to the others so the fund lands at the **same addresses everywhere**. - **`kpkShares`** — the fund's ERC-20 shares token (request-based subscribe/redeem, fees, multi-asset). This is the externally-audited core; its detailed reference lives in **[docs/KpkShares.md](docs/KpkShares.md)**. --- ## The fund stack Every fund the factory deploys is the same five-to-seven-contract stack: ``` ┌─────────────────────────────────────────────┐ │ Avatar Safe │ holds all fund assets; │ (sole signer = Empty contract — no │ cannot execute directly │ EOA/multisig can execute directly) │ └───────────────▲─────────────────────────────┘ │ execTransactionFromModule ┌───────────────┴───────────┐ │ exec Roles Modifier │ primary execution gate (owned by admin / └───────▲───────────▲────────┘ Security Council) │ │ ┌─────────────┴──┐ ┌────┴───────────────┐ │ sub Roles Mod │ │ Manager Safe │ operational multisig (fund managers) │ (bots/automation) │ + manager Roles Mod │ └────────────────┘ └────────────────────┘ ┌─────────────────────────────────────────────┐ │ kpkShares (UUPS proxy) + per-fund impl │ the ERC-20 investors hold └─────────────────────────────────────────────┘ ``` - **Avatar Safe** — holds the assets. Its only signer is the `Empty` contract (same address on every chain via CREATE2), so **no key can execute on it directly**; all execution must flow through the Roles Modifiers. - **exec / sub / manager Roles Modifiers** — Zodiac Roles v2 instances that gate what can be executed, by whom, against the Avatar and Manager Safes. - **Manager Safe** — the operators' multisig. - **kpkShares** — the fund's shares token; investors subscribe/redeem against it. See [docs/KpkShares.md](docs/KpkShares.md). --- ## `KpkOivFactory` — one-transaction fund deployment `KpkOivFactory` deploys and fully wires that stack via CREATE2, so the addresses are **deterministic** and reproducible across chains. Two permissionless entry points: | Entry point | Deploys | Typical use | |---|---|---| | `deployOiv(config)` | the full fund: the 5-contract operational stack **+** a UUPS `kpkShares` proxy backed by the chain's **shared** `KpkShares` mastercopy, with asset allowances and operator wiring | chains in `sharesChains` | | `deployStack(config)` | the 5-contract operational stack only (no shares token) | every other wired chain | Key properties: - **Cross-chain address invariant.** For the same `(caller, salt)`, `deployOiv` and `deployStack` produce **identical** Avatar Safe / Manager Safe / Roles Modifier addresses on every EVM chain — so a fund has one Avatar Safe address everywhere. The caller is mixed into the salt to prevent front-running of deterministic addresses. - **`oivToStackConfig(config)`** is the single source of truth for the `OivConfig → StackConfig` mapping that both `deployOiv` and off-chain orchestrators use, so sidechain addresses can't drift from mainnet. - **`predictOivAddresses` / `predictStackAddresses`** return, read-only, the addresses a deployment would produce (including the CREATE2-derived `kpkShares` impl/proxy). - **Trust model.** Deployment entry points are permissionless; only infrastructure setters are owner-gated, and the owner **must** be a TimelockController/governance multisig (never an EOA) — a compromised owner could backdoor future deployments. Full reference: **[docs/KpkOivFactory.md](docs/KpkOivFactory.md)**. --- ## Cross-chain deployment via Chainlink CCIP `CcipOivDeployer` extends a fund across chains in a single transaction from any wired chain, preserving the address invariant. Because `KpkOivFactory` mixes `msg.sender` into its salts, identical addresses across chains require the **same caller** on every chain. The orchestrator is deployed at **one identical address on all chains** (deterministic CREATE2, chain-identical creation code) and is therefore the uniform factory caller everywhere — without putting any CCIP logic into the factory's deployment path. - **`deployEverywhere(config, sharesChains, gasLimit)`** (or `deployEverywhere(config, sharesChains, destChainIds, gasLimit)` to target an explicit subset) — deploys this chain's part of the fund and CCIP-sends the derived `StackConfig` to each destination chain, where the sibling orchestrator's `ccipReceive` calls `deployStack`. Result: the same Avatar/Manager/Roles addresses on every chain. **The local half is conditional:** the full OIV when the origin appears in `sharesChains`, the operational stack alone when it does not — so a fan-out started from a stack-only chain is coherent, but gives that chain no shares token. Each remaining shares chain is filled by its own `deployLocal(config, sharesChains)`; the fan-out skips them deliberately. - **`dispatchTo(config, sharesChains, destChainIds, gasLimit)`** — CCIP-only fan-out (no local deploy) to add a fund to a new chain, or re-send after a failed delivery, without changing the salt. - **Permissionless.** `deployEverywhere` and `dispatchTo` are permissionless; only infrastructure setters (`configure` / `withdraw*`) are owner-gated. - **Security.** `ccipReceive` accepts a message only from the configured router, a chain in its own registry, and a source sender equal to its own (sibling) address. The sender check is the load-bearing one — only a contract at that same deterministic address can pass it. - **Symmetric.** Any wired chain can initiate. The origin never entered the address derivation: the orchestrator is the uniform factory caller everywhere and the salt is `keccak256(abi.encode(config-with-zeroed-base-asset, sharesChains))`, composed once and shipped — so a fan-out from Base produces the same addresses as one from Ethereum. Pinned by `test_topology_originChainDoesNotEnterTheDerivation`. - **Fees.** Paid in native gas by the caller via `msg.value` (surplus refunded); size with `quoteDeployEverywhere`. - **Async, not atomic.** Destination stacks land after the source chain's finality (~15 min from Ethereum); a failed CCIP message is manually re-executable. **Supported networks:** **19 wired mainnets**, each on-chain-verified for the full prerequisite stack at canonical addresses (Safe v1.4.1 ∩ Zodiac Roles v2.1.1 ∩ canonical CREATE2 deployer ∩ a live CCIP lane). These are the 19 baked into `CcipOivDeployer`'s constructor registry. The machine-readable registry — **[`script/ccip-networks.json`](script/ccip-networks.json)** — holds **24** entries: the 19 wired, **3 excluded** (`bob`, `katana`: `READY-AFTER-EMPTY` but not onboarded; `robinhood`: salt v4 deployed on 2026-10-06 but not in the baked CCIP registry; `excluded: true` keeps all three out of every seeded array), and **2 not ready** (`sei`, `mode`: no Roles v2.1.1 mastercopy on-chain). Wiring a chain means adding it to the constructor, so the registry file is the record, not the source. Full reference, the supported-network table, and the new-chain onboarding checklist: **[docs/CCIP_CROSS_CHAIN_DEPLOY.md](docs/CCIP_CROSS_CHAIN_DEPLOY.md)**. --- ## `kpkShares` — the shares token `kpkShares` is the externally-audited ERC-20 each fund issues. It implements request-based subscriptions and redemptions with operator approval, management/performance/redemption fees, multi-asset support, and a UUPS upgrade path. The optional performance fee is computed by a pluggable module (`WatermarkFee`, a high-watermark implementation). Full reference: **[docs/KpkShares.md](docs/KpkShares.md)**. --- ## Deploying a fund You don't need to write Solidity. The **`/deploy-oiv`** Claude Code skill walks you through configuration and writes a JSON config; the Foundry script `script/DeployOiv.s.sol` reads it and calls the factory: - `predict(configPath)` — show the expected addresses (no transaction). - `deployOiv(configPath)` — full fund (mainnet). - `deployStack(configPath)` — operational stack only (sidechains). Step-by-step guide, config format, and environment setup: **[DEPLOYMENT.md](DEPLOYMENT.md)**. Visual walk-throughs of the deployment flow (with diagrams): **[docs/FUND_DEPLOYMENT_FLOW.md](docs/FUND_DEPLOYMENT_FLOW.md)** (direct, per-chain) and **[docs/CCIP_FUND_DEPLOYMENT_FLOW.md](docs/CCIP_FUND_DEPLOYMENT_FLOW.md)** (one transaction, multichain via CCIP). > **Note on deployed addresses.** Current production addresses are tracked in **[docs/DEPLOYED_ADDRESSES.md](docs/DEPLOYED_ADDRESSES.md)** and [DEPLOYMENT.md](DEPLOYMENT.md). The CCIP work added `oivToStackConfig` to the factory, which changes its bytecode and therefore its CREATE2 address — the factory must be **redeployed** (and the address tables updated) before the CCIP path is used. See the redeploy checklist in [docs/CCIP_CROSS_CHAIN_DEPLOY.md](docs/CCIP_CROSS_CHAIN_DEPLOY.md). --- ## Repository layout ``` src/ KpkOivFactory.sol on-chain factory: deployOiv / deployStack CcipOivDeployer.sol Chainlink CCIP cross-chain orchestrator KpkTimelockDeployer.sol clones a per-fund TimelockController from a shared mastercopy kpkShares.sol the fund's ERC-20 shares token (audited) IkpkShares.sol kpkShares interface FeeModules/ WatermarkFee (perf fee) + IPerfFeeModule interfaces/ Safe + Zodiac interfaces used by the factory utils/ Empty (Avatar Safe signer), RecoverFunds script/ DeployOiv.s.sol deploy a fund via the factory DeployKpkOivFactory.s.sol verifies an onboarded chain's wiring (sends nothing) chains/Deploy_.s.sol onboards a chain end to end (use this one) DeployCcipOivDeployer.s.sol deterministic orchestrator deployment ccip-networks.json CCIP router / LINK / selector registry (23 chains) README.md script usage guide (kpkShares management scripts) docs/ KpkShares.md kpkShares contract reference KpkOivFactory.md factory reference CCIP_CROSS_CHAIN_DEPLOY.md cross-chain deployment design + networks FUND_DEPLOYMENT_FLOW.md fund deployment flow diagrams (direct, per-chain) CCIP_FUND_DEPLOYMENT_FLOW.md fund deployment flow diagrams (one-tx multichain) DEPLOYED_ADDRESSES.md production addresses test/ Foundry tests (fork-based for factory/CCIP) README.md test-suite breakdown ``` ## Build & test ```bash forge build forge test # unit tests forge test --fork-url $MAINNET_URL # factory + CCIP tests run against a mainnet fork ``` The `KpkOivFactory`, `CcipOivDeployer`, and several `kpkShares` suites fork mainnet to use the canonical Safe/Zodiac infrastructure; set `MAINNET_URL` (see `.env.sample`). The full suite breakdown lives in [test/README.md](test/README.md), and coverage is summarized in [COVERAGE_REPORT.md](COVERAGE_REPORT.md). ## Security `kpkShares` has been audited; reports are in `audit-reports/`: - `cantina-kpk-oivs-oct-2025.pdf` — Cantina (October 2025) - `team-omega-kpk-oivs-oct-2025.pdf` — Team Omega (October 2025)