Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 199 additions & 24 deletions apps/contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,44 +73,219 @@ yarn test testnet -h
```
it will show the next message where you can see all the available tests and the specific flags to run them.

## Production deployment
## Deployments

### Factory
Make sure you have compiled the contracts:
```
make build
---

### Configuration files

Before deploying anything, understand the three configuration sources:

#### `configs.json`

Located at `apps/contracts/configs.json`. Contains network-level settings and vault role assignments. Each network entry includes:

| Field | Description |
|---|---|
| `network` | Network identifier: `mainnet`, `testnet`, `standalone` |
| `horizon_rpc_url` | Horizon REST API endpoint |
| `soroban_rpc_url` | Soroban RPC endpoint |
| `soroban_network_passphrase` | Network passphrase for transaction signing |
| `friendbot_url` | (testnet only) Friendbot URL for account funding |
| `defindex_factory_admin` | Public key of the factory admin |
| `defindex_fee` | Protocol fee in basis points (e.g. `2000` = 20%) |
| `defindex_fee_receiver` | Public key that receives protocol fees |
| `vault_fee_receiver` | Public key that receives vault-level fees |
| `vault_manager` | Can call operational functions (e.g. `investVault`) |
| `vault_emergency_manager` | Can pause the vault in emergencies |
| `vault_rebalance_manager` | Can rebalance allocations between strategies |
| `blend_keeper` | Account authorized to call `harvest` on Blend strategies |
| `vault_name` | Display name stored in the vault contract |
| `vault_symbol` | Token symbol for vault shares (e.g. `DFXV`) |

> The deployer's secret key must be set in `.env` as `DEPLOYER_SECRET_KEY`. On testnet/standalone, the admin account is airdropped automatically.

---

#### `public/<network>.contracts.json`

Located at `public/testnet.contracts.json` (root of the monorepo, **not** inside `apps/contracts`). This is the **shared address book** used as input by `deploy_vault.ts`. It is also written to by `yarn publish-addresses` (which copies from `apps/contracts/.soroban/`).

It must contain the addresses of all external dependencies:

```json
{
"ids": {
"soroswap_router": "<address>",
"blend_fixed_xlm_usdc_pool": "<address>",
"blend_pool_usdc": "<address>",
"blend_pool_cetes": "<address>",
"blnd_token": "<address>",
"cetes_token": "<address>",
"soroswap_usdc": "<address>",
"XLM_blend_strategy": "<address after strategy deploy>",
"USDC_blend_strategy": "<address after strategy deploy>",
"CETES_blend_strategy": "<address after strategy deploy>"
}
}
```
and then you can deploy the factory with the following command:

**Strategy addresses** (keys like `<ASSET>_blend_strategy`) must be added manually after running `deploy-blend`. The vault deployment reads from this file to find the strategy address.

---

#### `apps/contracts/public/<network>.contracts.json`

A secondary address book scoped to the contracts workspace. Read by `deploy_blend.ts` and `constants.ts` to load token and pool addresses. Must contain the external dependency addresses (tokens, pools, router). **Must be populated manually** — `yarn publish-addresses` writes to the root-level `public/`, not to this file.

---

#### `.soroban/<network>.contracts.json`

Located at `apps/contracts/.soroban/`. This is the **internal address book** written automatically by deployment scripts. It stores every deployed contract address using the key format:

- Strategies: `<asset_symbol>_blend_<name>_<pool_name>_strategy`
e.g. `cetes_blend_regional_starter_pack_rsp_strategy`
- Vaults: `<asset>_paltalabs_vault`
e.g. `cetes_paltalabs_vault`

Do not edit this file manually — it is managed by the scripts.

---

#### `src/strategies/blend_deploy_config.json`

Contains the list of Blend strategies to deploy, organized by network. Each strategy entry:

```json
{
"name": "regional_starter_pack",
"keeper": "<keeper public key>",
"asset": "<token contract address>",
"asset_symbol": "CETES",
"reward_threshold": "40",
"blend_pool_address": "<blend pool contract address>",
"blend_pool_name": "rsp"
}
```
yarn deploy-factory <network>

| Field | Description |
|---|---|
| `name` | Strategy variant name, used in the resulting contract key |
| `keeper` | Public key authorized to call `harvest` |
| `asset` | The underlying token address the strategy manages |
| `asset_symbol` | Symbol used to filter deploys and build contract keys |
| `reward_threshold` | Minimum BLND reward amount that triggers auto-compounding. **Note:** currently hardcoded to `40` in `deploy_blend.ts`; this field is not yet read by the deploy script. |
| `blend_pool_address` | The Blend pool where assets are deposited |
| `blend_pool_name` | Short name for the pool, used in the resulting contract key |

The resulting contract key is: `<asset_symbol>_blend_<name>_<pool_name>_strategy`

> `install_contract: "true"` at the network level controls whether the WASM is re-uploaded. Set to `"false"` to reuse an already-installed WASM hash.

### Deploying a Blend strategy

**1. Configure `blend_deploy_config.json`**
Add or verify the entry for your asset under the target network. Example for CETES on testnet:

```json
{
"name": "regional_starter_pack",
"keeper": "G...",
"asset": "CC72F57YTPX76HAA64JQOEGHQAPSADQWSY5DWVBR66JINPFDLNCQYHIC",
"asset_symbol": "CETES",
"reward_threshold": "40",
"blend_pool_address": "CAPBMXIQTICKWFPWFDJWMAKBXBPJZUKLNONQH3MLPLLBKQ643CYN5PRW",
"blend_pool_name": "rsp"
}
```
Copy the deployed factory address from the output on `.soroban/<network>.contracts.json` and paste it in `public/<network>.contracts.json`
or just run the following command to copy it automatically:

**2. Ensure the deployer has a trustline for the asset**
The deployer account (from `DEPLOYER_SECRET_KEY`) must have a trustline for the asset token and hold at least a small balance (~1001 stroops worth), which is required for the bootstrap deposit made automatically on first deploy.

**3. Run the deploy script**
To deploy all strategies for a network:
```bash
npm run deploy-blend -- <network>
```
yarn publish-addresses <network>

To deploy only a specific asset (avoids re-deploying existing strategies):
```bash
npm run deploy-blend -- <network> <ASSET_SYMBOL>
```

### blend strategies
First you need to complete the following steps:
1. review the `blend_deploy_config.json` file to ensure that the strategies are correctly configured. In this file you can see a list of the strategies to deploy and the parameters for each one.
2. ensure that the addresses in the `<network>.contracts.json` file are correct and match the ones in the `blend_deploy_config.json` file.
3. ensure that the blend_keeper and blend_deployer secret keys are set in the `.env` file. Also, make sure that the `BLEND_KEEPER_SECRET_KEY` and `BLEND_DEPLOYER_SECRET_KEY` has trustlines set with all the assets that will be used in the strategies.
4. run the deploy_blend script to deploy the strategies:
Example:
```bash
npm run deploy-blend -- testnet CETES
```
yarn deploy-blend <network>

The script will:
- Airdrop the admin account (testnet only)
- Install the `blend_strategy` WASM (if `install_contract: "true"`)
- Deploy the strategy contract
- Make an automatic bootstrap deposit to prevent the first-depositor vulnerability
- Save the address to `.soroban/<network>.contracts.json`

**4. Publish the strategy address**
After deploying, copy the new strategy address from `.soroban/<network>.contracts.json` and add it to `public/<network>.contracts.json` (root level) using the key `<ASSET_SYMBOL>_blend_strategy`:

```json
"CETES_blend_strategy": "C..."
```
5. Then, to make it available for the frontend, you need to copy the new deployed strategies from `.soroban/<network>.contracts.json` into the `~/public/<network>.contracts.json` file.

### Deploy vault
Before deploying a vault, ensure that you have the `~/public/<network>.contracts.json` file updated with the latest contract addresses. This file should contain the addresses of all the necesary contracts deployed on the specified network, such as the factory, strategies, and blend addresses.
---

### Deployment instructions

**Prerequisites:**

- Factory deployed and address in `public/<network>.contracts.json`
- Strategy deployed and its address added to `public/<network>.contracts.json` as `<ASSET>_blend_strategy`
- `configs.json` has correct vault roles for the target network

**Run the deploy script:**
```bash
npm run deploy-vault -- <network> <ASSET_SYMBOL>
```

To deploy a vault, you need to run the following command:
Example:
```bash
npm run deploy-vault -- testnet XLM
```
yarn deploy-vault <network> <asset>

The script reads:

- The strategy address from `public/<network>.contracts.json` → key `<ASSET>_blend_strategy`
- The factory address from the same file → key `defindex_factory`
- Vault roles (manager, fee receiver, etc.) from `configs.json`

On success it prints the vault address and saves it to `.soroban/<network>.contracts.json` as `<asset>_paltalabs_vault`.

> After vault deployment, users deposit into the vault and the vault manager calls `investVault` to allocate funds to the strategy. This is a separate operational step.

---

### Full deployment sequence (new asset)

```bash
# 1. Build contracts
make build

# 2. Deploy factory (first time only)
npm run deploy-factory -- testnet
npm run publish-addresses -- testnet

# 3. Add strategy config to blend_deploy_config.json (manual)

# 4. Deploy the strategy
npm run deploy-blend -- testnet CETES

# 5. Add strategy address to public/testnet.contracts.json (manual)
# "CETES_blend_strategy": "<address from .soroban/testnet.contracts.json>"

# 6. Deploy the vault
npm run deploy-vault -- testnet CETES
```
where `<network>` is the network you want to deploy to (e.g., `testnet`, `mainnet`) and `<asset>` is the asset you want to use (e.g., `usdc`, `xlm`).
>[!NOTE] Make sure to double-check that the configuration in the `./configs.json` and the addresses at the public/`<network>`.contracts.json files are correct before deploying.

## Generate Docs
```bash
Expand Down
12 changes: 6 additions & 6 deletions apps/contracts/configs.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@
"soroban_rpc_url": "https://soroban-testnet.stellar.org/",
"soroban_network_passphrase": "Test SDF Network ; September 2015",
"defindex_factory_admin": "GCZSJFSSR44LSQZLCVCR5XXFIJ4L4NUP3G76IKECNW4PYGSU7IFBLAEK",
"defindex_fee_receiver": "GDOFDSMFRPOYTOLWODK4O6BZTGDJ4GRHLHX5THXN4TIFE2SXASQYFLPJ",
"defindex_fee_receiver": "GCXCHOZB44DKHUJNXRSYEQ3FH2ZHZPJM4O6BPLQVAL3H4OPIGX76JQKU",
"defindex_fee":"2000",
"vault_fee_receiver": "GDOFDSMFRPOYTOLWODK4O6BZTGDJ4GRHLHX5THXN4TIFE2SXASQYFLPJ",
"blend_keeper": "GDOFDSMFRPOYTOLWODK4O6BZTGDJ4GRHLHX5THXN4TIFE2SXASQYFLPJ",
"vault_manager": "GDOFDSMFRPOYTOLWODK4O6BZTGDJ4GRHLHX5THXN4TIFE2SXASQYFLPJ",
"vault_emergency_manager": "GDOFDSMFRPOYTOLWODK4O6BZTGDJ4GRHLHX5THXN4TIFE2SXASQYFLPJ",
"vault_rebalance_manager": "GDOFDSMFRPOYTOLWODK4O6BZTGDJ4GRHLHX5THXN4TIFE2SXASQYFLPJ",
"vault_fee_receiver": "GCXCHOZB44DKHUJNXRSYEQ3FH2ZHZPJM4O6BPLQVAL3H4OPIGX76JQKU",
"blend_keeper": "GCXCHOZB44DKHUJNXRSYEQ3FH2ZHZPJM4O6BPLQVAL3H4OPIGX76JQKU",
"vault_manager": "GCXCHOZB44DKHUJNXRSYEQ3FH2ZHZPJM4O6BPLQVAL3H4OPIGX76JQKU",
"vault_emergency_manager": "GCXCHOZB44DKHUJNXRSYEQ3FH2ZHZPJM4O6BPLQVAL3H4OPIGX76JQKU",
"vault_rebalance_manager": "GCXCHOZB44DKHUJNXRSYEQ3FH2ZHZPJM4O6BPLQVAL3H4OPIGX76JQKU",
"vault_name": "Defindex Vault",
"vault_symbol": "DFXV"
}
Expand Down
4 changes: 3 additions & 1 deletion apps/contracts/public/testnet.contracts.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"soroswap_xtar": "CCZGLAUBDKJSQK72QOZHVU7CUWKW45OZWYWCLL27AEK74U2OIBK6LXF2",
"blend_fixed_xlm_usdc_pool": "CCEBVDYM32YNYCVNRXQKDFFPISJJCV557CDZEIRBEE4NCV4KHPQ44HGF",
"blend_pool_usdc": "CAQCFVLOBK5GIULPNZRGATJJMIZL5BSP7X5YJVMGCPTUEPFM4AVSRCJU",
"blnd_token": "CB22KRA3YZVCNCQI64JQ5WE7UY2VAV7WFLK6A2JN3HEX56T2EDAFO7QF"
"blnd_token": "CB22KRA3YZVCNCQI64JQ5WE7UY2VAV7WFLK6A2JN3HEX56T2EDAFO7QF",
"blend_pool_cetes": "CAPBMXIQTICKWFPWFDJWMAKBXBPJZUKLNONQH3MLPLLBKQ643CYN5PRW",
"cetes_token": "CC72F57YTPX76HAA64JQOEGHQAPSADQWSY5DWVBR66JINPFDLNCQYHIC"
},
"hashes": {
"hodl_strategy": "c74ee193f66937b893eb175152f14eaf50b250aadd3700ee1824750d9b37f97d",
Expand Down
12 changes: 11 additions & 1 deletion apps/contracts/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,14 @@ export const BLEND_USDC_ADDRESS = new Address(otherAddressbook.getContractId("bl
export const XTAR_ADDRESS = new Address(otherAddressbook.getContractId("soroswap_xtar"));
export const SOROSWAP_ROUTER = otherAddressbook.getContractId("soroswap_router");
export const BLEND_POOL = otherAddressbook.getContractId("blend_fixed_xlm_usdc_pool");
export const BLEND_TOKEN = otherAddressbook.getContractId("blnd_token");
export const BLEND_TOKEN = otherAddressbook.getContractId("blnd_token");
function tryGetContractId(key: string): string {
try {
return otherAddressbook.getContractId(key);
} catch {
return "";
}
}

export const CETES_BLEND_POOL = tryGetContractId("blend_pool_cetes");
export const CETES_TOKEN = tryGetContractId("cetes_token");
10 changes: 10 additions & 0 deletions apps/contracts/src/strategies/blend_deploy_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,16 @@
"reward_threshold": "40",
"blend_pool_address": "CCEBVDYM32YNYCVNRXQKDFFPISJJCV557CDZEIRBEE4NCV4KHPQ44HGF",
"blend_pool_name": "fixed_xlm_usdc"
},
{
"name": "regional_starter_pack",
"label": "Regional Starter Pack",
"keeper": "GAZORYCZ2X3UGQXZSSOPSTZWEY44BBUA6E7YAF7GWERI26J6YNZL3HAZ",
"asset": "CC72F57YTPX76HAA64JQOEGHQAPSADQWSY5DWVBR66JINPFDLNCQYHIC",
"asset_symbol": "CETES",
"reward_threshold": "40",
"blend_pool_address": "CAPBMXIQTICKWFPWFDJWMAKBXBPJZUKLNONQH3MLPLLBKQ643CYN5PRW",
"blend_pool_name": "rsp"
}
]
}
Expand Down
15 changes: 13 additions & 2 deletions apps/contracts/src/strategies/deploy_blend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,18 @@ export async function deployBlendStrategy(addressBook: AddressBook, asset_symbol
console.log("Not enough balance to deploy the strategy");
return;
}
for (const strategy of blendDeployConfig[network].strategies) {
let strategies = blendDeployConfig[network].strategies;
if (asset_symbol) {
strategies = strategies.filter(
(s: any) => s.asset_symbol.toUpperCase() === asset_symbol.toUpperCase()
);
if (strategies.length === 0) {
console.error(`No strategies found for asset: ${asset_symbol}`);
return;
}
}

for (const strategy of strategies) {
const depositAmount = await calculateDepositAmount(
strategy.blend_pool_address,
strategy.asset,
Expand All @@ -180,7 +191,7 @@ export async function deployBlendStrategy(addressBook: AddressBook, asset_symbol
}


for (const strategy of blendDeployConfig[network].strategies) {
for (const strategy of strategies) {
const args = constructBlendStrategyArgs(strategy);

let contractKey = `${strategy.asset_symbol.toLowerCase()}_blend_${strategy.name}_${strategy.blend_pool_name}_strategy`
Expand Down
6 changes: 5 additions & 1 deletion apps/contracts/src/utils/deploy_tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export enum Strategies {
export enum Assets {
XLM = "XLM",
USDC = "USDC",
CETES = "CETES",
}

export interface StrategyDeployArgs {
Expand All @@ -30,7 +31,7 @@ export interface StrategyData {
export function AssetFromString(asset: string, config: EnvConfig, externalAddressBook:AddressBook): Address {
if (!asset || !Object.values(Assets).includes(asset.toUpperCase() as Assets)) {
console.warn("Please provide a valid asset symbol");
console.warn("Allowed assets are: \n - XLM \n - USDC");
console.warn("Allowed assets are: \n - XLM \n - USDC \n - CETES");
throw new Error("Invalid asset symbol");
}
const xlmAddress = new Address(
Expand All @@ -46,6 +47,9 @@ export function AssetFromString(asset: string, config: EnvConfig, externalAddres
case Assets.USDC:
assetAddress = new Address(externalAddressBook.getContractId("blend_pool_usdc"));
break;
case Assets.CETES:
assetAddress = new Address(externalAddressBook.getContractId("cetes_token"));
break;
default:
throw new Error("Invalid asset symbol");
}
Expand Down
6 changes: 4 additions & 2 deletions public/testnet.contracts.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
{
"ids": {
"defindex_factory": "CDSCWE4GLNBYYTES2OCYDFQA2LLY4RBIAX6ZI32VSUXD7GO6HRPO4A32",
"USDC_blend_strategy": "CALLOM5I7XLQPPOPQMYAHUWW4N7O3JKT42KQ4ASEEVBXDJQNJOALFSUY",
"XLM_blend_strategy": "CDVLOSPJPQOTB6ZCWO5VSGTOLGMKTXSFWYTUP572GTPNOWX4F76X3HPM",
"defindex_factory": "CDSCWE4GLNBYYTES2OCYDFQA2LLY4RBIAX6ZI32VSUXD7GO6HRPO4A32",
"CETES_blend_strategy": "CCP4RBDWPRNO2LWO23XFU4BBLGA73J5N3BK7EHRJUHVN33YEMMFB2MBE",
"usdc_paltalabs_vault": "CBMVK2JK6NTOT2O4HNQAIQFJY232BHKGLIMXDVQVHIIZKDACXDFZDWHN",
"xlm_paltalabs_vault": "CCLV4H7WTLJQ7ATLHBBQV2WW3OINF3FOY5XZ7VPHZO7NH3D2ZS4GFSF6"
"xlm_paltalabs_vault": "CCLV4H7WTLJQ7ATLHBBQV2WW3OINF3FOY5XZ7VPHZO7NH3D2ZS4GFSF6",
"cetes_paltalabs_vault": "CBIS5TEMTNNOTBE3WXPQUAGUEDYZZVIWAKTXEQCOUJ34OJJ3FJ5NLF2P"
},
"hashes": {
"blend_strategy": "0b1f49e25e7863f06acbf5d18caf82c9ad4140a46521e209221a08aa8940a6a1",
Expand Down
Loading