This document focuses on the Soroban smart contracts that enable users to deposit funds and automatically distribute them to various DeFi protocols based on their risk profiles. Our protocol uses an off-chain indexer to monitor and calculate metrics from different DeFi protocols, which are then stored and updated in an oracle contract on Soroban. Users interact through a factory contract, which creates and manages their custom savings account contracts.

Components

  1. Oracle Contract: Stores and updates metrics and scores for DeFi pools, and lists all tracked pools.
  2. Factory Contract: Creates and manages savings account contracts for users.
  3. Account Contract: Manages user deposits, risk profiles, interactions with DeFi protocols, and more.

Oracle Contract

The oracle contract is our on-chain database, storing and updating metrics and scores calculated by our off-chain indexer. It’s the go-to source for the factory and account contracts to make informed decisions based on the latest data.

Interface:

impl Oracle {
    pub fn update_pool_metrics(env: Env, pool_id: BytesN<32>, apr: i128, tvl: i128, liquidity_distribution: i128, risk_score: i128) {
        let metrics = (apr, tvl, liquidity_distribution, risk_score);
        env.storage().persistent().set(&pool_id, &metrics);
    }

    pub fn get_pool_metrics(env: Env, pool_id: BytesN<32>) -> (i128, i128, i128, i128) {
        env.storage().persistent().get(&pool_id).unwrap_or((0, 0, 0, 0))
    }

    pub fn list_tracked_pools(env: Env) -> Vec<BytesN<32>> {
        let mut pools = Vec::new(&env);
        for key in env.storage().persistent().keys() {
            pools.push_back(key);
        }
        pools
    }
}

Factory Contract

The factory contract is the mastermind behind creating and managing savings account contracts for users. It initializes new account contracts with the user's risk profile and facilitates deposits.

Interface:

impl Factory {
    pub fn create_savings_account(env: Env, user: Address, risk_profile: u32) -> Address {
        let account_contract_id = env.deployer().deploy_contract(BytesN::from_array(&env, &[0; 32]), Vec::new());
        env.invoke_contract(&account_contract_id, &Symbol::new(env, "initialize"), (user, risk_profile).into_val(&env));
        account_contract_id
    }
}

Account Contract

The account contract is where the magic happens. It handles user deposits, sets risk profiles, and interacts with DeFi protocols based on the user's preferences. It will also rebalance funds, manage liquidity tokens, and more.

Risk Profile

A user's risk profile is represented as a number, typically ranging from 1 to 100, where a higher number indicates a higher risk tolerance. This profile determines how funds are distributed among various DeFi pools, with riskier pools potentially offering higher returns.

Interface:

impl SavingsAccount {
    pub fn initialize(env: Env, user: Address, risk_profile: u32) {
        env.storage().instance().set(&user, &risk_profile);
    }

    pub fn deposit(env: Env, amount: i128, currency: BytesN<32>) {
        let user: Address = env.invoker().into();
        user.require_auth();
        let risk_profile: u32 = env.storage().instance().get(&user).unwrap();
        // Use risk profile to determine how to distribute funds
    }

    pub fn distribute(env: Env, pools: Vec<Address>, ratios: Vec<u32>) {
        let user: Address = env.invoker().into();
        user.require_auth();
        // Implement distribution logic here
    }

    pub fn rebalance(env: Env) {
        let user: Address = env.invoker().into();
        user.require_auth();
        let risk_profile: u32 = env.storage().instance().get(&user).unwrap();
        // Rebalance logic using risk profile and oracle data
    }

    pub fn withdraw(env: Env, amount: i128) {
        let user: Address = env.invoker().into();
        user.require_auth();
        // Implement withdrawal logic here
    }

    pub fn claim_fees(env: Env) {
        let user: Address = env.invoker().into();
        user.require_auth();
        // Implement fee claiming logic here
    }

    pub fn get_balances(env: Env) -> Map<Address, i128> {
        let user: Address = env.invoker().into();
        user.require_auth();
        let balances = Map::<Address, i128>::new(&env);
        balances
    }

    pub fn update_owner(env: Env, new_owner: Address) {
        let current_owner: Address = env.invoker().into();
        current_owner.require_auth();
        env.storage().instance().set(&"owner", &new_owner);
    }

    pub fn close_account(env: Env) {
        let user: Address = env.invoker().into();
        user.require_auth();
        // Implement close account logic here
    }
}

Workflow