> For the complete documentation index, see [llms.txt](https://docs.kpk.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kpk.io/funds/infrastructure/deployment/code-examples.md).

# Code examples

Practical examples for looking up a fund's contract addresses, predicting them ahead of a deployment, and deploying a fund across chains — through the [`KpkOivFactory`](/funds/infrastructure/deployment.md) and the [`CcipOivDeployer`](/funds/infrastructure/deployment/cross-chain-deployment.md) orchestrator.

**Source:** [`KpkOivFactory.sol`](https://github.com/karpatkey/onchain-investment-vehicles/blob/main/src/KpkOivFactory.sol) · [`CcipOivDeployer.sol`](https://github.com/karpatkey/onchain-investment-vehicles/blob/main/src/CcipOivDeployer.sol)

{% hint style="info" %}
The factory and orchestrator share the **same address on every chain** — take them from [Deployment addresses](/funds/infrastructure/deployment/deployment-addresses.md). The addresses below are placeholders.
{% endhint %}

{% hint style="info" %}
In the returned `OivInstance`, the field named **`avatarSafe`** is the fund's **Portfolio Safe** (the Zodiac module's *avatar* — the Safe that holds the fund's assets); **`kpkSharesProxy`** is the ERC-20 shares token investors hold.
{% endhint %}

***

## 1. Look up a deployed fund

Funds registered on the factory are returned by `getFund(registeredFundId)` as an `OivInstance` — the seven per-fund addresses.

```solidity
struct OivInstance {
    address avatarSafe;           // the fund's Portfolio Safe (holds assets)
    address managerSafe;
    address execRolesModifier;
    address subRolesModifier;
    address managerRolesModifier;
    address kpkSharesImpl;
    address kpkSharesProxy;        // the ERC-20 shares token
}

function getFund(uint256 registeredFundId) external view returns (OivInstance memory);
```

{% tabs %}
{% tab title="viem" %}

```typescript
import { createPublicClient, http, parseAbi } from "viem";
import { mainnet } from "viem/chains";

const FACTORY = "0xbafbca1804B6e46D4c54Cac0A0273F5B2A8F677F"; // same on every chain

const ABI = parseAbi([
  `function getFund(uint256 registeredFundId) view returns (
     (address avatarSafe, address managerSafe, address execRolesModifier,
      address subRolesModifier, address managerRolesModifier,
      address kpkSharesImpl, address kpkSharesProxy))`,
]);

const client = createPublicClient({ chain: mainnet, transport: http() });

const fund = await client.readContract({
  address: FACTORY, abi: ABI, functionName: "getFund", args: [0n],
});
console.log("Portfolio Safe:", fund.avatarSafe);
console.log("Shares token:  ", fund.kpkSharesProxy);
```

{% endtab %}

{% tab title="ethers.js" %}

```typescript
import { ethers } from "ethers";

const FACTORY = "0xbafbca1804B6e46D4c54Cac0A0273F5B2A8F677F";

const ABI = [
  `function getFund(uint256 registeredFundId) view returns (
     tuple(address avatarSafe, address managerSafe, address execRolesModifier,
           address subRolesModifier, address managerRolesModifier,
           address kpkSharesImpl, address kpkSharesProxy))`,
];

const provider = new ethers.JsonRpcProvider("https://eth.llamarpc.com");
const factory = new ethers.Contract(FACTORY, ABI, provider);

const fund = await factory.getFund(0);
console.log("Portfolio Safe:", fund.avatarSafe);
console.log("Shares token:  ", fund.kpkSharesProxy);
```

{% endtab %}

{% tab title="Python" %}

```python
from web3 import Web3

FACTORY = "0xbafbca1804B6e46D4c54Cac0A0273F5B2A8F677F"

OIV_INSTANCE = [
    {"name": "avatarSafe",           "type": "address"},
    {"name": "managerSafe",          "type": "address"},
    {"name": "execRolesModifier",    "type": "address"},
    {"name": "subRolesModifier",     "type": "address"},
    {"name": "managerRolesModifier", "type": "address"},
    {"name": "kpkSharesImpl",        "type": "address"},
    {"name": "kpkSharesProxy",       "type": "address"},
]
ABI = [{
    "name": "getFund", "type": "function", "stateMutability": "view",
    "inputs": [{"name": "registeredFundId", "type": "uint256"}],
    "outputs": [{"name": "", "type": "tuple", "components": OIV_INSTANCE}],
}]

w3 = Web3(Web3.HTTPProvider("https://eth.llamarpc.com"))
fund = w3.eth.contract(address=FACTORY, abi=ABI).functions.getFund(0).call()
print("Portfolio Safe:", fund[0])   # avatarSafe
print("Shares token:  ", fund[6])   # kpkSharesProxy
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
`predictOivAddresses(config, caller)` on the factory returns the same `OivInstance` for a set of inputs **without** sending a transaction — handy to look up a fund's Portfolio Safe before it is deployed. For CCIP-deployed funds, predict through the orchestrator instead (next section), which applies the config-bound salt.
{% endhint %}

***

## 2. Deploy a fund across chains

A single mainnet call to `CcipOivDeployer.deployEverywhere` deploys the full OIV on mainnet and fans the operational stack out to every configured sidechain (see [Cross-chain deployment](/funds/infrastructure/deployment/cross-chain-deployment.md)). Build an `OivConfig`, **predict** the resulting addresses, **quote** the native CCIP fee, then **deploy** with that fee as `msg.value`.

```solidity
function predictOiv(OivConfig config) external view returns (OivInstance);
function quoteDeployEverywhere(OivConfig config, uint256 gasLimit)
    external view returns (uint256 totalFee, uint256[] memory feePerDestination);
function deployEverywhere(OivConfig config, uint256 gasLimit)
    external payable returns (OivInstance instance, bytes32[] memory messageIds);
```

{% hint style="warning" %}
`deployEverywhere` must run on **Ethereum mainnet** (the source chain) and is **payable** — send at least `totalFee` from `quoteDeployEverywhere` as `msg.value`; any surplus is refunded. The **config-bound salt** means every field (notably `admin`) affects every deployed address, so predict with the exact config you will deploy.
{% endhint %}

{% tabs %}
{% tab title="viem" %}

```typescript
import { createPublicClient, createWalletClient, http, parseAbi, zeroAddress } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";

const CCIP = "0x6F2A3D35Ff275d6B76dB47eFB0Da1b2358daf11b"; // orchestrator, same on every chain
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";

// viem's parseAbi understands struct definitions — declare them once, reuse below.
const ABI = parseAbi([
  "struct SafeConfig { address[] owners; uint256 threshold; }",
  "struct AssetConfig { address asset; bool canDeposit; bool canRedeem; }",
  "struct ConstructorParams { address asset; address admin; string name; string symbol; address safe; uint64 subscriptionRequestTtl; uint64 redemptionRequestTtl; address feeReceiver; uint256 managementFeeRate; uint256 redemptionFeeRate; address performanceFeeModule; uint256 performanceFeeRate; }",
  "struct OivConfig { SafeConfig managerSafe; uint256 salt; address admin; ConstructorParams sharesParams; AssetConfig[] additionalAssets; }",
  "struct OivInstance { address avatarSafe; address managerSafe; address execRolesModifier; address subRolesModifier; address managerRolesModifier; address kpkSharesImpl; address kpkSharesProxy; }",
  "function predictOiv(OivConfig config) view returns (OivInstance)",
  "function quoteDeployEverywhere(OivConfig config, uint256 gasLimit) view returns (uint256 totalFee, uint256[] feePerDestination)",
  "function deployEverywhere(OivConfig config, uint256 gasLimit) payable returns (OivInstance instance, bytes32[] messageIds)",
]);

const MANAGER_SAFE_OWNERS = ["0x1111...", "0x2222...", "0x3333..."];
const ADMIN = "0xAdmiSafe..."; // exec-modifier owner + DEFAULT_ADMIN_ROLE on shares

const config = {
  managerSafe: { owners: MANAGER_SAFE_OWNERS, threshold: 2n },
  salt: 1n,                              // any uint256; same inputs → same addresses
  admin: ADMIN,
  sharesParams: {
    asset: USDC,                         // base asset
    admin: zeroAddress,                  // ignored (overridden by config.admin)
    name: "KPK USD Alpha",
    symbol: "kUSD",
    safe: zeroAddress,                   // ignored (set to the deployed Portfolio Safe)
    subscriptionRequestTtl: 86400n,      // ≤ 7 days
    redemptionRequestTtl: 86400n,        // ≤ 7 days
    feeReceiver: "0xFeeReceiver...",
    managementFeeRate: 100n,             // 1.00% (bps, ≤ 2000)
    redemptionFeeRate: 0n,
    performanceFeeModule: zeroAddress,   // disabled
    performanceFeeRate: 0n,
  },
  additionalAssets: [],                  // e.g. [{ asset: DAI, canDeposit: true, canRedeem: true }]
} as const;

const GAS_LIMIT = 1_800_000n;            // per-destination deployStack budget (~1.45M measured)

const client = createPublicClient({ chain: mainnet, transport: http() });

// 1) predict the deterministic addresses
const predicted = await client.readContract({
  address: CCIP, abi: ABI, functionName: "predictOiv", args: [config],
});
console.log("Portfolio Safe will be:", predicted.avatarSafe);

// 2) quote the native CCIP fee
const [totalFee] = await client.readContract({
  address: CCIP, abi: ABI, functionName: "quoteDeployEverywhere", args: [config, GAS_LIMIT],
});

// 3) deploy (mainnet only), paying the fee as msg.value
const wallet = createWalletClient({
  account: privateKeyToAccount("0x<PRIVATE_KEY>"), chain: mainnet, transport: http(),
});
const hash = await wallet.writeContract({
  address: CCIP, abi: ABI, functionName: "deployEverywhere", args: [config, GAS_LIMIT],
  value: totalFee,
});
console.log("deployEverywhere tx:", hash);
```

{% endtab %}

{% tab title="ethers.js" %}

```typescript
import { ethers } from "ethers";

const CCIP = "0x6F2A3D35Ff275d6B76dB47eFB0Da1b2358daf11b";
const USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";

// Reused tuple shapes (ethers has no struct keyword — inline tuples).
const OIV_CONFIG =
  "tuple(tuple(address[] owners, uint256 threshold) managerSafe, uint256 salt, address admin, " +
  "tuple(address asset, address admin, string name, string symbol, address safe, " +
  "uint64 subscriptionRequestTtl, uint64 redemptionRequestTtl, address feeReceiver, " +
  "uint256 managementFeeRate, uint256 redemptionFeeRate, address performanceFeeModule, " +
  "uint256 performanceFeeRate) sharesParams, tuple(address asset, bool canDeposit, bool canRedeem)[] additionalAssets)";
const OIV_INSTANCE =
  "tuple(address avatarSafe, address managerSafe, address execRolesModifier, address subRolesModifier, " +
  "address managerRolesModifier, address kpkSharesImpl, address kpkSharesProxy)";

const ABI = [
  `function predictOiv(${OIV_CONFIG} config) view returns (${OIV_INSTANCE})`,
  `function quoteDeployEverywhere(${OIV_CONFIG} config, uint256 gasLimit) view returns (uint256 totalFee, uint256[] feePerDestination)`,
  `function deployEverywhere(${OIV_CONFIG} config, uint256 gasLimit) payable returns (${OIV_INSTANCE} instance, bytes32[] messageIds)`,
];

const config = {
  managerSafe: { owners: ["0x1111...", "0x2222...", "0x3333..."], threshold: 2 },
  salt: 1,
  admin: "0xAdminSafe...",
  sharesParams: {
    asset: USDC,
    admin: ethers.ZeroAddress,           // ignored
    name: "KPK USD Alpha",
    symbol: "kUSD",
    safe: ethers.ZeroAddress,            // ignored
    subscriptionRequestTtl: 86400,
    redemptionRequestTtl: 86400,
    feeReceiver: "0xFeeReceiver...",
    managementFeeRate: 100,              // 1.00%
    redemptionFeeRate: 0,
    performanceFeeModule: ethers.ZeroAddress,
    performanceFeeRate: 0,
  },
  additionalAssets: [],
};
const GAS_LIMIT = 1_800_000n;

const provider = new ethers.JsonRpcProvider("https://eth.llamarpc.com");
const orchestrator = new ethers.Contract(CCIP, ABI, provider);

const predicted = await orchestrator.predictOiv(config);
console.log("Portfolio Safe will be:", predicted.avatarSafe);

const [totalFee] = await orchestrator.quoteDeployEverywhere(config, GAS_LIMIT);

const signer = new ethers.Wallet("0x<PRIVATE_KEY>", provider);
const tx = await orchestrator.connect(signer).deployEverywhere(config, GAS_LIMIT, { value: totalFee });
console.log("deployEverywhere tx:", tx.hash);
```

{% endtab %}

{% tab title="Python" %}

```python
from web3 import Web3

CCIP = "0x6F2A3D35Ff275d6B76dB47eFB0Da1b2358daf11b"
USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"

SAFE_CONFIG = [{"name": "owners", "type": "address[]"}, {"name": "threshold", "type": "uint256"}]
ASSET_CONFIG = [{"name": "asset", "type": "address"}, {"name": "canDeposit", "type": "bool"}, {"name": "canRedeem", "type": "bool"}]
PARAMS = [
    {"name": "asset", "type": "address"}, {"name": "admin", "type": "address"},
    {"name": "name", "type": "string"}, {"name": "symbol", "type": "string"}, {"name": "safe", "type": "address"},
    {"name": "subscriptionRequestTtl", "type": "uint64"}, {"name": "redemptionRequestTtl", "type": "uint64"},
    {"name": "feeReceiver", "type": "address"}, {"name": "managementFeeRate", "type": "uint256"},
    {"name": "redemptionFeeRate", "type": "uint256"}, {"name": "performanceFeeModule", "type": "address"},
    {"name": "performanceFeeRate", "type": "uint256"},
]
OIV_CONFIG = {"name": "config", "type": "tuple", "components": [
    {"name": "managerSafe", "type": "tuple", "components": SAFE_CONFIG},
    {"name": "salt", "type": "uint256"}, {"name": "admin", "type": "address"},
    {"name": "sharesParams", "type": "tuple", "components": PARAMS},
    {"name": "additionalAssets", "type": "tuple[]", "components": ASSET_CONFIG},
]}
OIV_INSTANCE = [{"name": n, "type": "address"} for n in
    ["avatarSafe", "managerSafe", "execRolesModifier", "subRolesModifier",
     "managerRolesModifier", "kpkSharesImpl", "kpkSharesProxy"]]

ABI = [
    {"name": "predictOiv", "type": "function", "stateMutability": "view",
     "inputs": [OIV_CONFIG], "outputs": [{"name": "", "type": "tuple", "components": OIV_INSTANCE}]},
    {"name": "quoteDeployEverywhere", "type": "function", "stateMutability": "view",
     "inputs": [OIV_CONFIG, {"name": "gasLimit", "type": "uint256"}],
     "outputs": [{"name": "totalFee", "type": "uint256"}, {"name": "feePerDestination", "type": "uint256[]"}]},
    {"name": "deployEverywhere", "type": "function", "stateMutability": "payable",
     "inputs": [OIV_CONFIG, {"name": "gasLimit", "type": "uint256"}],
     "outputs": [{"name": "instance", "type": "tuple", "components": OIV_INSTANCE},
                 {"name": "messageIds", "type": "bytes32[]"}]},
]

config = (
    (["0x1111...", "0x2222...", "0x3333..."], 2),   # managerSafe (owners, threshold)
    1,                                              # salt
    "0xAdminSafe...",                               # admin
    (USDC, "0x" + "0" * 40, "KPK USD Alpha", "kUSD", "0x" + "0" * 40,
     86400, 86400, "0xFeeReceiver...", 100, 0, "0x" + "0" * 40, 0),  # sharesParams
    [],                                             # additionalAssets
)
GAS_LIMIT = 1_800_000

w3 = Web3(Web3.HTTPProvider("https://eth.llamarpc.com"))
orchestrator = w3.eth.contract(address=CCIP, abi=ABI)

predicted = orchestrator.functions.predictOiv(config).call()
print("Portfolio Safe will be:", predicted[0])   # avatarSafe

total_fee, _ = orchestrator.functions.quoteDeployEverywhere(config, GAS_LIMIT).call()

# Deploy (mainnet only): build, sign, and send with value = total_fee
acct = w3.eth.account.from_key("0x<PRIVATE_KEY>")
tx = orchestrator.functions.deployEverywhere(config, GAS_LIMIT).build_transaction({
    "from": acct.address, "value": total_fee, "nonce": w3.eth.get_transaction_count(acct.address),
})
signed = acct.sign_transaction(tx)
print("deployEverywhere tx:", w3.eth.send_raw_transaction(signed.raw_transaction).hex())
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
Deploy to a **subset** of chains with the `deployEverywhere(config, destChainIds[], gasLimit)` overload, and add a chain to an existing fund later — or retry a permanently-failed CCIP delivery — with `dispatchTo(config, destChainIds[], gasLimit)` using the **same** `config`. See [Cross-chain deployment](/funds/infrastructure/deployment/cross-chain-deployment.md#operational-model).
{% endhint %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.kpk.io/funds/infrastructure/deployment/code-examples.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
