# Integration Guide

This guide is for developers integrating ETH Strategy tokens into smart contracts, wallets, aggregators, dashboards, or any on-chain system. It covers each token's interface, recommended integration patterns, and production caveats that will save you from subtle bugs.

If you're looking for protocol mechanics rather than integration patterns, start with the [Protocol Overview](https://docs.ethstrat.xyz/introduction/protocol-overview). For the complete API reference (every function with parameters, return values, access control, and revert conditions), see the [Contract Reference](https://docs.ethstrat.xyz/developers/contract-reference).

***

## Quick Reference

| Token          | Standard | Rebasing | Transferable | Permit (ERC-2612) | Key Integration Note                                                             |
| -------------- | -------- | -------- | ------------ | ----------------- | -------------------------------------------------------------------------------- |
| **esETH**      | ERC-20   | No       | Yes          | Yes               | Non-rebasing. Balances never change except on transfer/mint/burn. Safe to cache. |
| **STRAT**      | ERC-20   | No       | Yes          | Yes               | Only minted through note conversion. No inflation schedule.                      |
| **CDT**        | ERC-20   | No       | Yes          | Yes               | Each token = \~$1 protocol debt. `totalSupply()` = total debt.                   |
| **sSTRAT-v2**  | ERC-20   | No       | **No**       | No                | Non-transferable receipt token. `transfer()` and `approve()` revert.             |
| **NFT Option** | ERC-721  | N/A      | Yes          | N/A               | Conversion rights. Requires CDT to exercise. Partial exercise supported.         |
| **ESPN**       | ERC-4626 | No       | Yes          | No                | Standard vault interface. Deposit USDS, receive shares.                          |

***

## esETH Integration

esETH is the protocol's non-rebasing wrapper around multiple liquid staking tokens (wstETH, rETH, cbETH, weETH, aWETH), pegged 1:1 with ETH. It is the denomination layer for the entire treasury.

### Why esETH is Easy to Integrate

**Use esETH the way you use wstETH or rETH.** esETH is a standard, non-rebasing ERC-20. Unlike stETH, balances never change from rebase events. You can:

* Store balances in `mapping(address => uint256)` without drift
* Cache balances across blocks without staleness
* Use esETH in AMM pools, lending markets, and vaults without rebase accounting
* Treat 1 esETH as exactly 1 ETH — the peg is fixed at 1:1 (yield is harvested separately, not embedded in the token value)

If you've integrated wstETH or rETH, esETH works the same way. If you've struggled with stETH rebase edge cases, esETH eliminates them.

### Core Interface

```solidity
// === Wrapping / Unwrapping ===

/// @notice Deposit a supported LST and receive esETH at a 1:1 ratio with the ETH value.
/// @param lst Address of the LST to deposit (wstETH, rETH, cbETH, weETH, or aWETH)
/// @param amount Amount of the LST to deposit
/// @param recipient Address to receive the minted esETH
/// @return esEthAmount Amount of esETH minted
/// @dev Reverts if `lst` is not a supported token.
///      Reverts if `amount` is zero.
///      Caller must have approved esETH contract to spend `amount` of `lst`.
function mint(address lst, uint256 amount, address recipient) external returns (uint256 esEthAmount);

/// @notice Deposit raw ETH and receive esETH.
/// @param recipient Address to receive the minted esETH
/// @return esEthAmount Amount of esETH minted
/// @dev The sent ETH is staked via the most favorable LST route.
function mint(address recipient) external payable returns (uint256 esEthAmount);

/// @notice Redeem esETH for a supported LST held by the contract.
/// @param lst Address of the desired LST to receive
/// @param esEthAmount Amount of esETH to burn
/// @param recipient Address to receive the LST
/// @return lstAmount Amount of the LST returned
/// @dev Reverts if the contract does not hold enough of the requested LST.
///      The exchange rate is determined by the LST's native rate, not a market price.
function redeem(address lst, uint256 esEthAmount, address recipient) external returns (uint256 lstAmount);

// === Exchange Rate Queries ===

/// @notice Returns the ETH value of 1 esETH (in wei). Always returns 1e18 (1:1 peg).
/// @dev esETH is pegged 1:1 with ETH. Yield is harvested separately.
///      Use this for pricing — NOT market price from an AMM.
function ethPerEsEth() external view returns (uint256);

/// @notice Returns the esETH amount for a given LST deposit amount (preview).
/// @param lst Address of the LST
/// @param lstAmount Amount of the LST
/// @return esEthAmount Equivalent esETH that would be minted
function previewMint(address lst, uint256 lstAmount) external view returns (uint256 esEthAmount);

/// @notice Returns the LST amount for a given esETH redemption amount (preview).
/// @param lst Address of the LST
/// @param esEthAmount Amount of esETH to redeem
/// @return lstAmount Equivalent LST that would be returned
function previewRedeem(address lst, uint256 esEthAmount) external view returns (uint256 lstAmount);

// === Standard ERC-20 (with ERC-2612 permit) ===

function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
```

### Pricing esETH

**esETH is always 1:1 with ETH.** For lending markets, collateral valuation, and any system that needs esETH's "fair value":

```
esETH/USD = ETH/USD oracle price (since 1 esETH = 1 ETH)
```

Do **not** use AMM spot price for esETH valuation in lending or collateral contexts. The 1:1 peg is maintained by construction — yield from underlying LSTs is harvested separately and does not accrete to esETH's value. AMM prices reflect market dynamics and may diverge from the peg.

```solidity
// Recommended: Use ETH/USD feed directly (1 esETH = 1 ETH)
function getEsEthUsdPrice(
    AggregatorV3Interface ethUsdFeed
) public view returns (uint256) {
    (, int256 ethPrice,,,) = ethUsdFeed.latestRoundData(); // 8 decimals
    // Result in 18 decimals (USD per esETH = USD per ETH)
    return uint256(ethPrice) * 1e10;
}
```

{% hint style="info" %}
**1 esETH = 1 ETH, always.** Unlike wstETH or rETH, esETH does not appreciate over time. Yield from underlying LSTs is harvested and directed to the protocol — not embedded in esETH's value.
{% endhint %}

### Production Warnings

**Rounding: expect 1-2 wei dust on maximum-balance operations.** When transferring a user's entire esETH balance, the last 1-2 wei may remain due to integer division in internal calculations. If your contract needs exact-balance transfers, read `balanceOf()` and transfer that amount rather than a cached value.

**Supported LSTs are governance-configurable.** The set of LSTs that esETH accepts for minting and offers for redemption can change. Do not hardcode LST addresses — query the contract for the current whitelist. A previously supported LST may be removed, and redemption for that LST would revert.

**Redemption depends on contract holdings.** Unlike minting (which can wrap any supported LST), redemption requires the esETH contract to actually hold the requested LST. If the treasury has rebalanced its LST allocation, your preferred LST may have insufficient balance. Check `previewRedeem()` before executing.

**Yield is not reflected in balances.** esETH holders do not receive yield. Underlying LST yield is harvested separately and directed to the protocol's yield receiver. esETH is a denomination layer, not a yield-bearing token for the holder.

**ERC-2612 permit front-running.** As with any permit-based approval, a griefer can front-run your `permit` call by submitting the same signature first. The permit succeeds for the griefer (setting the allowance), then your transaction reverts. Mitigation: wrap permit + action in a try/catch — if permit reverts, check if allowance is already set, and proceed with the action.

```solidity
// Safe permit pattern — handles front-running gracefully
function depositWithPermit(
    IERC20Permit token,
    uint256 amount,
    uint256 deadline,
    uint8 v, bytes32 r, bytes32 s
) external {
    try token.permit(msg.sender, address(this), amount, deadline, v, r, s) {
        // Permit succeeded
    } catch {
        // Permit was front-run — check if allowance was already set
        require(
            token.allowance(msg.sender, address(this)) >= amount,
            "Permit failed and insufficient allowance"
        );
    }
    token.transferFrom(msg.sender, address(this), amount);
}
```

***

## STRAT Integration

STRAT is a standard ERC-20 with ERC-2612 permit support. It does not rebase and is only minted through convertible note conversion.

### Key Facts for Integrators

* **No inflation schedule.** STRAT supply only increases when note holders convert. There is no emission schedule or admin mint.
* **Valuation metric:** ETH per STRAT (EPS) = total esETH in treasury / total STRAT supply. This is the fundamental metric — it increases when the treasury grows faster than STRAT dilution.
* **Contract address:** [0x14cF922aa1512Adfc34409b63e18D391e4a86A2f](https://etherscan.io/address/0x14cF922aa1512Adfc34409b63e18D391e4a86A2f)

### Displaying STRAT Value

For dashboards and portfolio trackers, show EPS alongside the market price:

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

const STRAT = "0x14cF922aa1512Adfc34409b63e18D391e4a86A2f";

async function getStratMetrics(provider: ethers.Provider, esEthContract: ethers.Contract) {
  const strat = new ethers.Contract(STRAT, [
    "function totalSupply() view returns (uint256)"
  ], provider);

  const [stratSupply, treasuryEsEth] = await Promise.all([
    strat.totalSupply(),
    esEthContract.balanceOf(TREASURY_ADDRESS),  // total esETH in treasury (1 esETH = 1 ETH)
  ]);

  // ETH per STRAT (the core valuation metric)
  // Since 1 esETH = 1 ETH, treasuryEsEth directly represents ETH value
  const eps = treasuryEsEth / stratSupply;

  return { stratSupply, eps };
}
```

***

## CDT Integration

CDT is a standard ERC-20 with ERC-2612 permit support. Each CDT represents approximately $1 of protocol debt.

### Key Facts for Integrators

* **`totalSupply()` = total protocol debt.** This is an on-chain transparency guarantee. Display it to users as the protocol's total debt outstanding.
* **CDT does not accrue interest or yield.** Its value comes from: (1) the $1 face-value redemption right after note expiry, and (2) its utility as a "key" required to exercise NFT conversion rights.
* **CDT is required for conversion.** An NFT alone cannot be converted — the holder must also hold and burn CDT. This creates organic demand for CDT beyond its face value.
* **Minting is restricted** to authorized protocol contracts (ConvertibleNote, TreasuryLend). CDT cannot be minted by governance or any external address.

### CDT as a Leverage Indicator

```typescript
// Protocol leverage = total debt / total equity
// In ETH Strategy terms: CDT supply (in ETH) / treasury esETH
async function getProtocolLeverage(
  cdtContract: ethers.Contract,
  esEthContract: ethers.Contract,
  ethUsdOracle: ethers.Contract
) {
  const [cdtSupply, treasuryEth, ethPrice] = await Promise.all([
    cdtContract.totalSupply(),
    esEthContract.balanceOf(TREASURY_ADDRESS),
    ethUsdOracle.latestRoundData().then((r: any) => r.answer)
  ]);

  // CDT is USD-denominated; convert treasury to USD for comparison
  const treasuryUsd = (treasuryEth * ethPrice) / BigInt(1e8);
  const debtToEquity = (cdtSupply * BigInt(1e18)) / treasuryUsd;

  return { cdtSupply, treasuryUsd, debtToEquity };
}
```

***

## StakedStrat (sSTRAT-v2) Integration

StakedStrat is the on-chain staking contract. It accepts STRAT deposits and streams esETH rewards over 7-day periods.

### Core Interface

```solidity
/// @notice Stake STRAT tokens. Receives sSTRAT-v2 1:1.
/// @param amount Amount of STRAT to stake
/// @dev Caller must have approved StakedStrat to spend `amount` of STRAT.
///      Reverts with `ZeroAmount` if amount is 0.
///      Automatically calls syncRewards() before staking.
function stake(uint256 amount) external;

/// @notice Unstake STRAT tokens. Burns sSTRAT-v2 proportionally.
/// @param amount Amount of STRAT to unstake
/// @dev Automatically claims all pending esETH rewards before unstaking.
///      Reverts with `ZeroAmount` if amount is 0.
///      Reverts with `InsufficientStake` if amount exceeds staked balance.
function unstake(uint256 amount) external;

/// @notice Claim accrued esETH rewards without changing staked position.
/// @dev Returns silently if no rewards are pending.
function claim() external;

/// @notice Detect new esETH in the contract and start/extend the reward stream.
/// @dev Permissionless — anyone can call. Called automatically by stake/unstake/claim.
///      Compares current balance against cumulative accounting to find new rewards.
///      New rewards blend with any active stream using value-weighted average duration.
function syncRewards() external;

/// @notice Move staked position to a new address without unstaking.
/// @param to Destination address
/// @param amount Amount to migrate (0 = full balance)
/// @dev Pays out recipient's pending rewards during migration.
///      Reverts if `to` is zero address, caller, or the contract itself.
function migrateStake(address to, uint256 amount) external;

/// @notice View pending (claimable) esETH rewards for an account.
/// @dev May undercount if new rewards arrived but syncRewards() hasn't been called.
function getPendingRewards(address account) external view returns (uint256);
```

### Production Warnings

**sSTRAT-v2 is non-transferable.** `transfer()`, `transferFrom()`, and `approve()` all revert with `TransferDisabled`. Do not attempt to build markets, pools, or collateral positions with sSTRAT-v2. It is an accounting receipt, not a tradable asset. The only way to move a staked position is `migrateStake()`.

**`getPendingRewards()` may undercount.** The view function projects the current reward stream forward but does not account for newly arrived esETH that hasn't been synced. If you need an accurate reading (e.g., for a dashboard), call `syncRewards()` first in a static call:

```typescript
// Accurate pending rewards reading
const stakedStrat = new ethers.Contract(STAKED_STRAT_ADDRESS, STAKED_STRAT_ABI, provider);

// Static call syncRewards first, then read pending
await stakedStrat.syncRewards.staticCall();
const pending = await stakedStrat.getPendingRewards(userAddress);
```

**Unstaking auto-claims rewards.** When a user unstakes, all pending esETH rewards are automatically claimed and sent to them. This is by design — there is no scenario where unstaking forfeits earned yield. Account for this in your UI: after an unstake transaction, the user will have received both their STRAT and their esETH rewards.

**7-day streaming means delayed reward visibility.** When new rewards arrive at the contract, they stream linearly over 7 days. A large deposit into the reward pool doesn't instantly appear as claimable — it drips over the week. Display this to users as "streaming rewards" rather than a lump sum.

***

## Convertible Note Integration

The convertible note system has two components: the EthStrategyConvertibleNote contract (handles bonding, conversion, and redemption) and the NFT options it mints.

### Bonding

```solidity
/// @notice Purchase a USD-denominated convertible note by sending ETH.
/// @param recipient Address to receive CDT and NFT
/// @param minConversionAmountStrat Minimum STRAT entitlement (slippage protection)
/// @param minConversionAmountEth Minimum esETH entitlement (slippage protection)
/// @param deadline Transaction deadline (block.timestamp must be <= deadline)
/// @return tokenId The minted NFT token ID
/// @dev The note is priced in USD via an ETH/USD oracle. CDT amount = USD notional.
///      Reverts with `NoEthSent` if msg.value is 0.
///      Reverts with `ZeroAddress` if recipient is address(0).
///      Reverts with `TransactionStale` if block.timestamp > deadline.
///      Reverts with `InsufficientOutput` if entitlements < minimums.
function bond(
    address recipient,
    uint256 minConversionAmountStrat,
    uint256 minConversionAmountEth,
    uint256 deadline
) external payable returns (uint256 tokenId);

/// @notice Preview conversion entitlements for a given ETH amount without executing.
/// @param ethAmount Amount of ETH (in wei) to preview
/// @return stratAmount STRAT conversion entitlement
/// @return ethEntitlement esETH conversion entitlement
/// @return settlementUsd USD notional (CDT amount)
function conversionEntitlements(uint256 ethAmount)
    external view returns (uint256 stratAmount, uint256 ethEntitlement, uint256 settlementUsd);
```

### Conversion

```solidity
/// @notice Convert note to STRAT before expiry. Burns CDT, mints STRAT.
/// @param tokenId NFT token ID
/// @param cdtAmount Amount of CDT to burn (partial exercise supported)
/// @dev Caller must be ownerOf(tokenId). NFT approvals are NOT accepted.
///      Reverts with `TimelockActive` if timelock hasn't passed (~6.9 days).
///      Reverts with `OptionExpired` if past expiry (~4.2 years).
///      CDT must be approved or permitted to the contract.
function convertToStrat(uint256 tokenId, uint256 cdtAmount) external;

/// @notice Convert note to esETH before expiry. Burns CDT, transfers esETH.
/// @param tokenId NFT token ID
/// @param cdtAmount Amount of CDT to burn (partial exercise supported)
function convertToEth(uint256 tokenId, uint256 cdtAmount) external;

/// @notice Redeem CDT for USD notional value in esETH after expiry.
/// @param tokenId NFT token ID
/// @param minEthOut Minimum esETH to receive (slippage protection against oracle movement)
/// @dev Single-shot — redeems entire remaining position. Burns both CDT and NFT.
///      Pro-rata if protocol is underwater (treasury < total CDT).
function redeem(uint256 tokenId, uint256 minEthOut) external;

// Permit variants — combine CDT approval and action in one transaction:
function convertToStratWithPermit(uint256 tokenId, uint256 cdtAmount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
function convertToEthWithPermit(uint256 tokenId, uint256 cdtAmount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
function redeemWithPermit(uint256 tokenId, uint256 minEthOut, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external;
```

### Reading Note State

Each NFT encodes the full position state. Query it to display note details in your UI:

```solidity
/// @notice Returns the full state of a convertible note.
/// @param tokenId NFT token ID
/// @return note Struct containing all position fields
struct Note {
    uint256 conversionEntitlementStrat;  // STRAT claimable via conversion
    uint256 conversionEntitlementEth;    // esETH claimable via conversion
    uint256 settlementEntitlementUsd;    // USD notional for post-expiry redemption
    uint256 amountOwedCdt;              // CDT required for full exercise (decrements)
    uint256 expiry;                      // Timestamp after which conversion closes
    uint256 timelock;                    // Timestamp before which conversion is blocked
}
function notes(uint256 tokenId) external view returns (Note memory);
```

### Production Warnings

**Only `ownerOf(tokenId)` can convert or redeem.** NFT approvals (`approve`, `setApprovalForAll`) are intentionally not accepted for settlement authorization. If your contract holds note NFTs, it must call conversion/redemption functions directly — it cannot delegate this to another address. This is a security design choice to reduce attack surface.

**Timelock prevents immediate conversion.** After bonding, there is a \~6.9-day window where the note exists but cannot be converted or redeemed. The NFT and CDT can be transferred during this period. Build your UI to show the timelock countdown.

**Partial exercise is supported but CDT must match.** To exercise 50% of a note, burn 50% of `amountOwedCdt`. The NFT's entitlement fields decrement proportionally. The NFT is only burned when `amountOwedCdt` reaches zero (full exercise).

**`conversionEntitlements()` preview may show zero ETH.** If the protocol is underwater (total debt > treasury value in USD), the ETH conversion entitlement for new notes is zero. STRAT conversion rights are still granted. Check the preview before presenting note terms to users.

***

## ESPN (ERC-4626 Vault) Integration

ESPN follows the standard [ERC-4626 Tokenized Vault](https://eips.ethereum.org/EIPS/eip-4626) interface. If your protocol or aggregator already supports ERC-4626, ESPN requires no custom integration work.

### Key Details

| Property          | Value                                                                                                                 |
| ----------------- | --------------------------------------------------------------------------------------------------------------------- |
| **Deposit asset** | USDS                                                                                                                  |
| **Share token**   | ESPN                                                                                                                  |
| **Contract**      | [0xb250C9E0F7bE4cfF13F94374C993aC445A1385fE](https://etherscan.io/address/0xb250C9E0F7bE4cfF13F94374C993aC445A1385fE) |
| **Deposit cap**   | Governance-configurable                                                                                               |
| **Withdrawals**   | May be disabled by governance; exit via LP redemption when direct withdrawals are disabled                            |

```solidity
// Standard ERC-4626 — use your existing vault integration
function deposit(uint256 assets, address receiver) external returns (uint256 shares);
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256 shares);
function redeem(uint256 shares, address receiver, address owner) external returns (uint256 assets);
function convertToShares(uint256 assets) external view returns (uint256);
function convertToAssets(uint256 shares) external view returns (uint256);
function totalAssets() external view returns (uint256);
function maxDeposit(address) external view returns (uint256);
```

{% hint style="warning" %}
**Withdrawals may be disabled.** Check `maxWithdraw(owner)` before presenting a withdraw button. When direct withdrawals are disabled, users exit via the ESPN/USDS LP on secondary markets or the redemption queue.
{% endhint %}

***

## End-to-End Example: Bond, Stake, and Earn

This example walks through the complete flow using ethers.js v6:

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

// Addresses — replace with deployed contract addresses once live
const CONVERTIBLE_NOTE = "0x...";  // EthStrategyConvertibleNote
const STAKED_STRAT     = "0x...";  // StakedStrat
const CDT_TOKEN        = "0x...";  // CDT
const STRAT_TOKEN      = "0x14cF922aa1512Adfc34409b63e18D391e4a86A2f";

async function bondStakeAndEarn(signer: ethers.Signer) {
  const note = new ethers.Contract(CONVERTIBLE_NOTE, NOTE_ABI, signer);
  const strat = new ethers.Contract(STRAT_TOKEN, ERC20_ABI, signer);
  const stakedStrat = new ethers.Contract(STAKED_STRAT, STAKED_STRAT_ABI, signer);

  // Step 1: Preview note terms for 1 ETH (USD-priced via oracle)
  const bondAmount = ethers.parseEther("1.0");
  const [stratEntitlement, ethEntitlement, cdtAmount] =
    await note.conversionEntitlements(bondAmount);

  console.log(`Purchasing note with 1 ETH (USD notional: ${ethers.formatEther(cdtAmount)}):`);
  console.log(`  STRAT conversion rights: ${ethers.formatEther(stratEntitlement)}`);
  console.log(`  esETH conversion rights: ${ethers.formatEther(ethEntitlement)}`);
  console.log(`  CDT received (= USD notional): ${ethers.formatEther(cdtAmount)}`);

  // Step 2: Purchase note by sending ETH — receive CDT + NFT
  // Set slippage at 1% below preview (deadline 30 minutes from now)
  const minStrat = (stratEntitlement * 99n) / 100n;
  const minEth = (ethEntitlement * 99n) / 100n;
  const deadline = BigInt(Math.floor(Date.now() / 1000)) + 1800n;

  const tx = await note.bond(
    await signer.getAddress(),
    minStrat,
    minEth,
    deadline,
    { value: bondAmount }
  );
  const receipt = await tx.wait();
  console.log(`Bonded! NFT token ID in transaction events.`);

  // Step 3: Wait for timelock (~6.9 days), then convert to STRAT
  // ... after timelock passes ...

  // Step 4: Approve and stake STRAT
  const stratBalance = await strat.balanceOf(await signer.getAddress());
  await (await strat.approve(STAKED_STRAT, stratBalance)).wait();
  await (await stakedStrat.stake(stratBalance)).wait();
  console.log(`Staked ${ethers.formatEther(stratBalance)} STRAT`);

  // Step 5: Check pending rewards (call syncRewards first for accuracy)
  await stakedStrat.syncRewards.staticCall();
  const pending = await stakedStrat.getPendingRewards(await signer.getAddress());
  console.log(`Pending esETH rewards: ${ethers.formatEther(pending)}`);
}
```

{% hint style="info" %}
**Contract addresses for esETH, CDT, EthStrategyConvertibleNote, and StakedStrat will be published on the** [**Contracts**](https://docs.ethstrat.xyz/references/contracts) **page before permissionless launch.** Core contracts have completed audits; reports will be released alongside the code.
{% endhint %}

***

## Deployed Contracts

See [Contracts](https://docs.ethstrat.xyz/references/contracts) for all deployed and upcoming contract addresses.

### Live Now

| Contract               | Address                                                                                   | Standard |
| ---------------------- | ----------------------------------------------------------------------------------------- | -------- |
| STRAT Token            | [0x14cF922aa...](https://etherscan.io/address/0x14cF922aa1512Adfc34409b63e18D391e4a86A2f) | ERC-20   |
| ESPN Token             | [0xb250C9E0F...](https://etherscan.io/address/0xb250C9E0F7bE4cfF13F94374C993aC445A1385fE) | ERC-4626 |
| STRAT Option (Presale) | [0xe1e90933...](https://etherscan.io/address/0xe1e9093365545e11Cb02c36B2688E17B4Dc447FC)  | ERC-721  |

### Deploying Soon

| Contract                   | Description                                                |
| -------------------------- | ---------------------------------------------------------- |
| esETH                      | Non-rebasing unified LST wrapper                           |
| CDT                        | Fungible ERC-20 protocol debt token (with ERC-2612 permit) |
| EthStrategyConvertibleNote | Bonding, note issuance, conversion, redemption             |
| StakedStrat                | On-chain STRAT staking with 7-day reward streaming         |

***

## Security Considerations for Integrators

### Access Control

| Contract        | Pattern      | Owner             |
| --------------- | ------------ | ----------------- |
| esETH           | Ownable2Step | Protocol multisig |
| STRAT           | Ownable2Step | Protocol multisig |
| CDT             | Ownable2Step | Protocol multisig |
| StakedStrat     | Ownable2Step | Protocol multisig |
| ConvertibleNote | Ownable2Step | Protocol multisig |

All ownership transfers require a two-step process (propose → accept) to prevent accidental transfers to wrong addresses.

### Reentrancy

All state-changing functions on StakedStrat and ConvertibleNote are guarded by `nonReentrant`. Token transfer callbacks cannot be exploited to corrupt accounting.

### Immutability

ETH Strategy contracts are deployed without proxy-upgrade patterns. Deployed code is immutable. Governance can tune parameters (PCF, GCF, supported LSTs, caps) but cannot change contract logic.

### What Governance Can and Cannot Change

| Can Change (new positions only)           | Cannot Change (ever)          |
| ----------------------------------------- | ----------------------------- |
| PCF/GCF (bonding price factors)           | Existing note entitlements    |
| Supported LST whitelist                   | Existing note timelock/expiry |
| Deposit caps                              | Existing loan terms           |
| Reward distribution addresses             | CDT supply accounting         |
| Borrow rate, loan duration (TreasuryLend) | Contract logic (no upgrades)  |

***

## Getting Help

* **Source code:** [github.com/dangerousfood/ethstrategy](https://github.com/dangerousfood/ethstrategy) (Apache 2.0)
* **Documentation:** [docs.ethstrat.xyz](https://docs.ethstrat.xyz)
* **Telegram:** [t.me/ethstrat](https://t.me/ethstrat)
* **Twitter:** [@eth\_strategy](https://x.com/eth_strategy)
