Skip to main content

EdenCore Contract

The EdenCore contract is the main orchestrator for the EdenVest protocol. It routes deposits, performs token swaps to cNGN, mints LP tokens and NFT positions, handles withdrawals (with optional swap on exit), and coordinates pool lifecycle via the factory.

Assetchain Mainnet

ContractAddress
EdenCore Proxy0xA5f9e65B17ed0B34263285a5bE9059E45Fd61597
EdenAdmin0xa4EbDFC4949Ba52E837Fca3b6a2fAb98c7492b5C
TaxCollector0x5104b1E5fDBa07a4DE396b70Ff19Da5a58b6A2d1
SwapRouter0x0A46C99cA1e64aB05A943907FC6361476899Dd1c
PoolFactory0xd8D7338FB2f854c1F40F55Ef28dC2134FA3141f1
NFTPositionManager0xD200b9D79570beb26F105eBe6ce5D5FE6a9BDC83

Assetchain Enugu Testnet

ContractAddress
EdenCore Proxy0xC2EdCE6C2282B367A1269Ff77FB95855067cdaaf
EdenAdmin0xE15e1aED46cE2D8A319E4885CE52177D0696722D
TaxCollector0x3BAb3110A2C4D986320F83Ac86Bab1229B3e7e4C
SwapRouter0x87A09E8048E06765A21742399018d684f2E690b3
PoolFactory0x4f703B2A060859a38DEFc9c34C8Ab02df160b15E
NFTPositionManager0x1824De8E500F92E5C08de4a152e74224548Daa83

What EdenCore Does

Process Investments

Accepts direct cNGN or performs swap-then-invest in one transaction

Mint Receipts

Issues LP tokens (ERC‑20) and non-transferable NFT positions (ERC‑721)

Handle Withdrawals

Redeem to cNGN or swap to chosen token on exit

Manage Pools

Create and toggle pool activation through the factory

Key Features

Architecture Flow

Key Data Structures

InvestmentParams

Used for swap-and-invest operations:
struct InvestmentParams {
    address pool;         // Target investment pool
    address tokenIn;      // Input token (if swapping)
    uint256 amountIn;     // Amount of tokenIn
    uint256 minAmountOut; // Minimum cNGN expected after swap
    uint256 deadline;     // Expiry timestamp (unix)
    string title;         // Saved in NFT metadata
}

WithdrawAndSwapParams

Used for withdraw-and-swap operations:
struct WithdrawAndSwapParams {
    address pool;           // Pool address
    uint256 tokenId;        // NFT position ID
    uint256 lpTokenAmount;  // LP tokens to redeem
    address tokenOut;       // Desired output token
    uint256 minAmountOut;   // Minimum amount expected
    uint256 deadline;       // Transaction deadline
    uint256 maxSlippageBps; // Max slippage (default 100 = 1%)
}

Core Functions

Investment Functions

invest()

Direct investment using cNGN tokens without swapping.
function invest(
    address pool,
    uint256 amount, 
    string memory title,
    uint256 deadline
) external returns (uint256 tokenId, uint256 lpTokens)
Parameters:
  • pool: Target investment pool address
  • amount: Investment amount in cNGN
  • title: Custom title for the investment
  • deadline: Transaction deadline (0 for no deadline)
Process Flow:
  1. Validates pool registration and active status
  2. Transfers cNGN from user and approves pool
  3. Calls InvestmentPool.invest() to mint LP and NFT
  4. Collects protocol tax in LP tokens to TaxCollector
  5. Emits InvestmentMade event
Returns:
  • tokenId: NFT position token ID
  • lpTokens: LP tokens minted to investor
import { ethers } from "ethers";
import EdenCoreABI from "./abis/EdenCore.json";

const core = new ethers.Contract(
  "0xB3DdDA0f6C116687B70CAcbffC339B1749D9A53B",
  EdenCoreABI,
  signer
);

// 1. Approve cNGN to EdenCore
await cNGNContract.approve(
  core.address, 
  ethers.utils.parseEther("1000")
);

// 2. Make investment
const tx = await core.invest(
  POOL_ADDRESS,
  ethers.utils.parseEther("1000"),
  "My Investment",
  Math.floor(Date.now()/1000) + 300 // 5 min deadline
);

const receipt = await tx.wait();
console.log("Investment successful!", receipt);

investWithSwap()

Invest using any ERC‑20 token. EdenCore swaps to cNGN then invests in one transaction.
function investWithSwap(InvestmentParams memory params) 
    external returns (uint256 tokenId, uint256 lpTokens)
Advanced Features:
  • Slippage Protection: Applies default 1% slippage protection
  • Liquidity Validation: Checks available swap liquidity
  • Balance Verification: Ensures swap executed correctly
InvestmentParams Structure:
struct InvestmentParams {
    address pool;           // Target pool
    address tokenIn;        // Input token to swap
    uint256 amountIn;       // Input token amount
    uint256 minAmountOut;   // Minimum cNGN output
    uint256 deadline;       // Transaction deadline
    string title;           // Investment title
}
// 1. Approve input token to EdenCore
await usdcContract.approve(
  core.address, 
  ethers.utils.parseUnits("1000", 6)
);

// 2. Preview swap quote (optional)
const [expectedOut] = await core.checkSwapLiquidity(
  USDC_ADDRESS, 
  ethers.utils.parseUnits("1000", 6)
);

// 3. Calculate minimum output with user slippage
const minOut = expectedOut.mul(95).div(100); // 5% slippage tolerance

// 4. Execute swap and investment
await core.investWithSwap({
  pool: POOL_ADDRESS,
  tokenIn: USDC_ADDRESS,
  amountIn: ethers.utils.parseUnits("1000", 6),
  minAmountOut: minOut,
  deadline: Math.floor(Date.now()/1000) + 600,
  title: "USDC → cNGN Investment"
});

Withdrawal Functions

withdraw()

Withdraw matured positions to receive cNGN directly, by burning LP tokens and NFT.
function withdraw(
    address pool,
    uint256 tokenId, 
    uint256 lpTokenAmount
) external returns (uint256 withdrawAmount)
Important: User must approve LP tokens to the pool address (not EdenCore). Process:
  1. Validates investment maturity and ownership
  2. Burns LP tokens and NFT position
  3. Transfers principal + returns to investor
// 1. Approve LP tokens to the POOL (not EdenCore!)
await lpTokenContract.approve(POOL_ADDRESS, lpAmount);

// 2. Execute withdrawal
const withdrawnAmount = await core.withdraw(
  POOL_ADDRESS,
  TOKEN_ID,
  lpAmount
);

console.log(`Withdrawn ${ethers.utils.formatEther(withdrawnAmount)} cNGN`);

withdrawAndSwap() - Withdraw with Token Swap

Withdraw to cNGN then optionally swap to another token in a single transaction.
function withdrawAndSwap(WithdrawAndSwapParams calldata params)
    external whenNotPaused nonReentrant
    returns (uint256 amountOut)
Process:
  1. Validates pool and transfers LP tokens
  2. Withdraws to cNGN via InvestmentPool.withdraw()
  3. If tokenOut == cNGN: sends cNGN directly to user
  4. Otherwise: swaps cNGN to tokenOut with slippage protection
import { Address, getContract, parseUnits, zeroAddress } from 'viem'
import { publicClient, walletClient, chain } from './clients' //  viem setup
import coreAbi from './abi/EdenVestCore.json'
import poolAbi from './abi/InvestmentPool.json'
import erc20Abi from './abi/erc20.json'
import quoterAbi from './abi/QuoterV3.json' // or call  router.getAmountOut

// ─────────────────────────────────────────────────────────
// Inputs
// ─────────────────────────────────────────────────────────
const CORE_ADDRESS  : Address = '0x...EdenVestCore'        // proxy
const POOL_ADDRESS  : Address = '0x...InvestmentPool'
const CNGN_ADDRESS  : Address = '0x...cNGN'
const TOKEN_OUT     : Address = '0x...USDC_or_USDT'        //  tokenOut
const TOKEN_ID      : bigint  = 67n                        //  NFT position id
const LP_AMOUNT     : bigint  = 4563252163868174995110n    // lpTokenAmount (full)
const SLIPPAGE_BPS  : bigint  = 100n                       // 1% (100 bps)
const DEADLINE      : bigint  = BigInt(Math.floor(Date.now()/1000) + 600)

// (optional) if you prefer to quote via Uniswap QuoterV2 directly:
const QUOTER_ADDRESS: Address = '0x...QuoterV2'

// ─────────────────────────────────────────────────────────
// Helpers
// ─────────────────────────────────────────────────────────

const core = getContract({ address: CORE_ADDRESS, abi: coreAbi, client: { public: publicClient, wallet: walletClient }})
const pool = getContract({ address: POOL_ADDRESS, abi: poolAbi, client: { public: publicClient, wallet: walletClient }})
const cngn = getContract({ address: CNGN_ADDRESS, abi: erc20Abi, client: { public: publicClient, wallet: walletClient }})

async function getLpToken(): Promise<Address> {
  const { lpToken } = await core.read.poolInfo([POOL_ADDRESS])
  return lpToken as Address
}

async function getInvestmentIdForToken(tokenId: bigint): Promise<bigint> {
  // pool stores NFT→investmentId
  const investId = await pool.read.nftToInvestment([tokenId])
  return investId
}

async function getAmountInCNGN(investmentId: bigint): Promise<bigint> {
  // Full withdrawal pays principal + expected interest (pool computes)
  const inv = await pool.read.getInvestment([investmentId])
  // inv = [investor, amount, title, depositTime, maturityTime, expectedReturn, actualReturn, isWithdrawn, lpTokens]
  const expectedReturn: bigint = inv[5]
  return expectedReturn
}

async function quoteMinOutViaQuoter(amountIn: bigint): Promise<bigint> {
  // Uniswap V3 QuoterV2 single‑hop example (adjust fee if needed)
  const quoter = getContract({ address: QUOTER_ADDRESS, abi: quoterAbi, client: { public: publicClient }})
  // fee must match  router pool fee (e.g., 3000 = 0.3%)
  const fee: number = 3000
  const [amountOut] = await quoter.read.quoteExactInputSingle([{
    tokenIn:  CNGN_ADDRESS,
    tokenOut: TOKEN_OUT,
    amountIn,
    fee,
    sqrtPriceLimitX96: 0n
  }])
  return (amountOut * (10_000n - SLIPPAGE_BPS)) / 10_000n
}

// ─────────────────────────────────────────────────────────
// Main: Withdraw and Swap to TOKEN_OUT
// ─────────────────────────────────────────────────────────

export async function withdrawAndSwapToTokenOut() {
  // 0) Resolve LP token & investment info
  const LP_TOKEN_ADDR = await getLpToken()
  const lpToken = getContract({ address: LP_TOKEN_ADDR, abi: erc20Abi, client: { public: publicClient, wallet: walletClient }})

  const investmentId = await getInvestmentIdForToken(TOKEN_ID)
  const amountInCNGN  = await getAmountInCNGN(investmentId) // full redemption

  // 1) Approve LP → Pool
  const currentLpAllowance = await lpToken.read.allowance([await walletClient.getAddresses().then(a=>a[0]), POOL_ADDRESS])
  if (currentLpAllowance < LP_AMOUNT) {
    await lpToken.write.approve([POOL_ADDRESS, LP_AMOUNT])
  }

  // 2) Approve cNGN → Core (pool pays you, then Core pulls)
  const currentCngnAllowance = await cngn.read.allowance([await walletClient.getAddresses().then(a=>a[0]), CORE_ADDRESS])
  if (currentCngnAllowance < amountInCNGN) {
    // You can also approve max uint if you prefer a one‑time approval:
    await cngn.write.approve([CORE_ADDRESS, amountInCNGN])
  }

  // 3) Compute minAmountOut (1% slippage here)
  const minAmountOut = await quoteMinOutViaQuoter(amountInCNGN)
  // NOTE:  router also enforces its own maxSlippage (default 3%).
  // Ensure minAmountOut >= quoted * (1 - router.maxSlippageBps/10000). With 1% it's fine.

  // 4) Call Core.withdrawAndSwap
  const hash = await core.write.withdrawAndSwap([{
    pool:           POOL_ADDRESS,
    tokenId:        TOKEN_ID,
    lpTokenAmount:  LP_AMOUNT,
    tokenOut:       TOKEN_OUT,
    minAmountOut:   minAmountOut,
    deadline:       DEADLINE,
    maxSlippageBps: SLIPPAGE_BPS  // Core sanity cap (100 = 1%)
  }])

  const receipt = await publicClient.waitForTransactionReceipt({ hash })
  return receipt
}

Pool Management Functions

createPool()

Create new investment pools (Admin only).
function createPool(IPoolFactory.PoolParams memory poolParams)
    external onlyRole(POOL_CREATOR_ROLE) returns (address pool)
PoolParams Structure:
struct PoolParams {
    string name;                // Pool name
    string symbol;              // LP token symbol  
    address admin;              // Pool administrator
    address poolMultisig;       // Pool multisig wallet
    address[] multisigSigners;  // Multisig signers
    address cNGN;              // cNGN token address
    uint256 lockDuration;      // Lock period in seconds
    uint256 minInvestment;     // Minimum investment amount
    uint256 maxInvestment;     // Maximum investment amount  
    uint256 utilizationCap;    // Total pool capacity
    uint256 expectedRate;      // Expected APY in basis points
    uint256 taxRate;           // Pool-specific tax rate
}
const poolParams = {
  name: "High Yield 6-Month Pool",
  symbol: "HY6M",
  admin: adminAddress,
  poolMultisig: multisigWalletAddress,
  multisigSigners: [signer1, signer2, signer3],
  cNGN: CNGN_ADDRESS,
  lockDuration: 6 * 30 * 24 * 60 * 60, // 6 months
  minInvestment: ethers.utils.parseEther("500"),   // 500 cNGN
  maxInvestment: ethers.utils.parseEther("50000"), // 50k cNGN
  utilizationCap: ethers.utils.parseEther("1000000"), // 1M total
  expectedRate: 1500, // 15% APY
  taxRate: 250        // 2.5% tax
};

const poolAddress = await edenCore.createPool(poolParams);
console.log("New pool created:", poolAddress);

View Functions

getAllPools() / getActivePools()

Retrieve pool information.
function getAllPools() external view returns (address[] memory)
function getActivePools() external view returns (address[] memory)

checkSwapLiquidity()

Check available liquidity for token swaps.
function checkSwapLiquidity(address tokenIn, uint256 amountIn) 
    external returns (uint256 expectedOut, bool hasLiquidity)

Access Control

The contract implements OpenZeppelin’s AccessControl with these roles:
RolePurposeFunctions
DEFAULT_ADMIN_ROLESuper adminAll admin functions
ADMIN_ROLEProtocol adminConfiguration updates
POOL_CREATOR_ROLEPool creatorCreate pools
EMERGENCY_ROLEEmergency controlsPause/emergency functions

Events

Investment Events

event InvestmentMade(
    address indexed pool,
    address indexed investor, 
    uint256 tokenId,
    uint256 amount,
    uint256 lpTokens
);

Administrative Events

event TaxRateUpdated(uint256 oldRate, uint256 newRate);
event TreasuryUpdated(address oldTreasury, address newTreasury);
event EmergencyWithdraw(
    address token,
    uint256 amount, 
    address treasury,
    string reason,
    address admin
);

Error Handling

The contract includes comprehensive error checking:
error InvalidPool();
error InvalidAmount(); 
error PoolNotActive();
error InvalidTaxRate();
error DeadlineExpired();
error SwapFailed();
error InsufficientLiquidity();

Integration Examples

React Frontend Integration

import { ethers } from 'ethers';
import EdenCoreABI from './abis/EdenCore.json';

const EDEN_CORE_ADDRESS = '0x5c932dDfA2a0c1283ca73F51970f0C99c6Bdf214';
const CNGN_ADDRESS = '0x5CDDBeBAc2260CF00654887184d6BA31096fE0a5';

class EdenVestIntegration {
  constructor(provider) {
    this.contract = new ethers.Contract(
      EDEN_CORE_ADDRESS,
      EdenCoreABI,
      provider
    );
  }

  async getActivePools() {
    return await this.contract.getActivePools();
  }

  // approve spending cNGN/usdt/usdc tokens
  const cNGNContract = new ethers.Contract(
      CNGN_ADDRESS,
      ['function approve(address spender, uint256 amount)'],
      this.contract.provider
  );
  const tx = await cNGNContract.approve(EDEN_CORE_ADDRESS, amount);
  return await tx.wait();


  async invest(poolAddress, amount, title) {
    const tx = await this.contract.invest(
      poolAddress,
      amount,
      title,
      Math.floor(Date.now() / 1000) + 300 // 5 min deadline
    );
    return await tx.wait();
  }

  async checkSwapQuote(tokenIn, amountIn) {
    return await this.contract.checkSwapLiquidity(tokenIn, amountIn);
  }
}

Pool Creation Example

async function createNewPool() {
  const poolParams = {
    name: "High Yield 30-Day Pool",
    symbol: "HY30",
    admin: adminAddress,
    poolMultisig: multisigAddress,
    multisigSigners: [signer1, signer2, signer3],
    cNGN: cNGN_ADDRESS,
    lockDuration: 30 * 24 * 60 * 60, // 30 days
    minInvestment: ethers.utils.parseEther('100'), // 100 cNGN
    maxInvestment: ethers.utils.parseEther('10000'), // 10k cNGN
    utilizationCap: ethers.utils.parseEther('100000'), // 100k total
    expectedRate: 1500, // 15% APY
    taxRate: 250 // 2.5% tax
  };

  const tx = await edenCore.createPool(poolParams);
  const receipt = await tx.wait();
  
  // Extract pool address from events
  const event = receipt.events.find(e => e.event === 'PoolCreated');
  return event.args.pool;
}

Security Considerations

Input Validation

  • All amounts must be greater than zero
  • Pool addresses must be registered and active
  • Deadlines must be in the future
  • Token addresses must be valid contracts

Slippage Protection

  • Automatic slippage calculation for swaps
  • Configurable maximum slippage limits
  • Real-time liquidity checks

Access Controls

  • Role-based function access
  • Multisig requirements for critical operations
  • Emergency pause capabilities

Upgrade Mechanism

EdenCore uses the UUPS (Universal Upgradeable Proxy Standard) pattern:
  • Proxy Contract: Handles storage and delegatecalls
  • Implementation Contract: Contains business logic
  • Admin Controls: Only ADMIN_ROLE can authorize upgrades
Upgrades must be carefully planned and tested to ensure storage layout compatibility and prevent data corruption.

Next Steps