Skip to content

Commit

Permalink
chore: loan instantiation (#144)
Browse files Browse the repository at this point in the history
* chore: loan instantiation

* chore: deref account_ids

* chore: spawn loan job

* chore: collateralize loan in job

* chore: iteration on loan job

* chore: iteration on business logic

* chore: wip calculating interest using daily rate

* chore: return DateTime<Utc> for next_interest_at

* refactor: interest calculation

* refactor: make terms types more expressive

* refactor: interest calculation

---------

Co-authored-by: bodymindarts <[email protected]>
  • Loading branch information
thevaibhav-dixit and bodymindarts authored Jul 5, 2024
1 parent b16bc9e commit 9b8ce11
Show file tree
Hide file tree
Showing 16 changed files with 864 additions and 33 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions core/src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::{
fixed_term_loan::FixedTermLoans,
job::{JobRegistry, Jobs},
ledger::Ledger,
loan::Loans,
user::Users,
withdraw::Withdraws,
};
Expand All @@ -20,6 +21,7 @@ pub struct LavaApp {
_pool: PgPool,
_jobs: Jobs,
fixed_term_loans: FixedTermLoans,
_loans: Loans,
users: Users,
withdraws: Withdraws,
ledger: Ledger,
Expand All @@ -34,15 +36,18 @@ impl LavaApp {
let applicants = Applicants::new(&pool, &config.sumsub, &users);
let withdraws = Withdraws::new(&pool, &users, &ledger);
let mut fixed_term_loans = FixedTermLoans::new(&pool, &mut registry, users.repo(), &ledger);
let mut loans = Loans::new(&pool, &mut registry, &users, &ledger);
let mut jobs = Jobs::new(&pool, config.job_execution, registry);
fixed_term_loans.set_jobs(&jobs);
loans.set_jobs(&jobs);
jobs.start_poll().await?;
Ok(Self {
_pool: pool,
_jobs: jobs,
users,
withdraws,
fixed_term_loans,
_loans: loans,
ledger,
applicants,
})
Expand Down
2 changes: 1 addition & 1 deletion core/src/fixed_term_loan/job.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ impl JobRunner for FixedTermLoanInterestJobRunner {
self.repo.persist_in_tx(&mut db_tx, &mut loan).await?;

self.ledger
.record_interest(tx_id, loan.account_ids, tx_ref, UsdCents::ONE)
.record_fixed_term_loan_interest(tx_id, loan.account_ids, tx_ref, UsdCents::ONE)
.await?;

match loan.next_interest_at() {
Expand Down
4 changes: 2 additions & 2 deletions core/src/fixed_term_loan/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,10 @@ impl FixedTermLoans {
loan.approve(tx_id, collateral, principal)?;
self.repo.persist_in_tx(&mut tx, &mut loan).await?;
self.ledger
.create_accounts_for_loan(loan.id, loan.account_ids)
.create_accounts_for_fixed_term_loan(loan.id, loan.account_ids)
.await?;
self.ledger
.approve_loan(
.approve_fixed_term_loan(
tx_id,
loan.account_ids,
user.account_ids,
Expand Down
103 changes: 100 additions & 3 deletions core/src/ledger/cala/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::primitives::{
use super::{
constants,
fixed_term_loan::FixedTermLoanAccountIds,
loan::LoanAccountIds,
user::{UserLedgerAccountAddresses, UserLedgerAccountIds},
};

Expand Down Expand Up @@ -156,7 +157,7 @@ impl CalaClient {
}

#[instrument(name = "lava.ledger.cala.create_user_accounts", skip(self), err)]
pub async fn create_loan_accounts(
pub async fn create_fixed_term_loan_accounts(
&self,
loan_id: impl Into<Uuid> + std::fmt::Debug,
FixedTermLoanAccountIds {
Expand Down Expand Up @@ -186,6 +187,37 @@ impl CalaClient {
Ok(())
}

#[instrument(name = "lava.ledger.cala.create_user_accounts", skip(self), err)]
pub async fn create_loan_accounts(
&self,
loan_id: impl Into<Uuid> + std::fmt::Debug,
LoanAccountIds {
collateral_account_id,
outstanding_account_id,
interest_account_id,
}: LoanAccountIds,
) -> Result<(), CalaError> {
let loan_id = loan_id.into();
let variables = create_loan_accounts::Variables {
collateral_account_id: Uuid::from(collateral_account_id),
collateral_account_code: format!("LOANS.COLLATERAL.{}", loan_id),
outstanding_account_id: Uuid::from(outstanding_account_id),
outstanding_account_code: format!("LOANS.OUTSTANDING.{}", loan_id),
loans_account_set_id: super::constants::FIXED_TERM_LOANS_ACCOUNT_SET_ID,
loans_control_account_set_id: super::constants::FIXED_TERM_LOANS_CONTROL_ACCOUNT_SET_ID,
interest_account_id: Uuid::from(interest_account_id),
interest_account_code: format!("LOANS.INTEREST_INCOME.{}", loan_id),
interest_revenue_account_set_id: super::constants::INTEREST_REVENUE_ACCOUNT_SET_ID,
interest_revenue_control_account_set_id:
super::constants::INTEREST_REVENUE_CONTROL_ACCOUNT_SET_ID,
};
let response =
Self::traced_gql_request::<CreateLoanAccounts, _>(&self.client, &self.url, variables)
.await?;
response.data.ok_or(CalaError::MissingDataField)?;
Ok(())
}

#[instrument(name = "lava.ledger.cala.create_account", skip(self), err)]
pub async fn create_account(
&self,
Expand Down Expand Up @@ -447,7 +479,7 @@ impl CalaClient {
}

#[instrument(name = "lava.ledger.cala.execute_approve_loan_tx", skip(self), err)]
pub async fn execute_approve_loan_tx(
pub async fn execute_approve_fixed_term_loan_tx(
&self,
transaction_id: LedgerTxId,
loan_account_ids: FixedTermLoanAccountIds,
Expand Down Expand Up @@ -482,6 +514,42 @@ impl CalaClient {
Ok(())
}

#[instrument(name = "lava.ledger.cala.execute_approve_loan_tx", skip(self), err)]
pub async fn execute_approve_loan_tx(
&self,
transaction_id: LedgerTxId,
loan_account_ids: LoanAccountIds,
user_account_ids: UserLedgerAccountIds,
collateral_amount: Decimal,
principal_amount: Decimal,
external_id: String,
) -> Result<(), CalaError> {
let variables = post_approve_loan_transaction::Variables {
transaction_id: transaction_id.into(),
unallocated_collateral_account: user_account_ids
.off_balance_sheet_deposit_account_id
.into(),
loan_collateral_account: loan_account_ids.collateral_account_id.into(),
loan_outstanding_account: loan_account_ids.outstanding_account_id.into(),
checking_account: user_account_ids.on_balance_sheet_deposit_account_id.into(),
collateral_amount,
principal_amount,
external_id,
};
let response = Self::traced_gql_request::<PostApproveLoanTransaction, _>(
&self.client,
&self.url,
variables,
)
.await?;

response
.data
.map(|d| d.transaction_post.transaction.transaction_id)
.ok_or_else(|| CalaError::MissingDataField)?;
Ok(())
}

pub async fn execute_complete_loan_tx(
&self,
transaction_id: LedgerTxId,
Expand Down Expand Up @@ -545,7 +613,7 @@ impl CalaClient {
}

#[instrument(name = "lava.ledger.cala.execute_incur_interest_tx", skip(self), err)]
pub async fn execute_incur_interest_tx(
pub async fn execute_incur_interest_tx_for_fixed_term_loan(
&self,
transaction_id: LedgerTxId,
loan_account_ids: FixedTermLoanAccountIds,
Expand Down Expand Up @@ -573,6 +641,35 @@ impl CalaClient {
Ok(())
}

#[instrument(name = "lava.ledger.cala.execute_incur_interest_tx", skip(self), err)]
pub async fn execute_incur_interest_tx(
&self,
transaction_id: LedgerTxId,
loan_account_ids: LoanAccountIds,
interest_amount: Decimal,
external_id: String,
) -> Result<(), CalaError> {
let variables = post_incur_interest_transaction::Variables {
transaction_id: transaction_id.into(),
loan_outstanding_account: loan_account_ids.outstanding_account_id.into(),
loan_interest_income_account: loan_account_ids.interest_account_id.into(),
interest_amount,
external_id,
};
let response = Self::traced_gql_request::<PostIncurInterestTransaction, _>(
&self.client,
&self.url,
variables,
)
.await?;

response
.data
.map(|d| d.transaction_post.transaction.transaction_id)
.ok_or_else(|| CalaError::MissingDataField)?;
Ok(())
}

#[instrument(
name = "lava.ledger.cala.create_record_payment_template",
skip(self),
Expand Down
47 changes: 47 additions & 0 deletions core/src/ledger/loan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use crate::primitives::{LedgerAccountId, Satoshis, UsdCents};
use serde::{Deserialize, Serialize};

use super::cala::graphql::*;

#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub struct LoanAccountIds {
pub collateral_account_id: LedgerAccountId,
pub outstanding_account_id: LedgerAccountId,
pub interest_account_id: LedgerAccountId,
}

impl LoanAccountIds {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
Self {
collateral_account_id: LedgerAccountId::new(),
outstanding_account_id: LedgerAccountId::new(),
interest_account_id: LedgerAccountId::new(),
}
}
}

pub struct LoanBalance {
pub collateral: Satoshis,
pub outstanding: UsdCents,
pub interest_incurred: UsdCents,
}

impl From<fixed_term_loan_balance::ResponseData> for LoanBalance {
fn from(data: fixed_term_loan_balance::ResponseData) -> Self {
LoanBalance {
collateral: data
.collateral
.map(|b| Satoshis::from_btc(b.settled.normal_balance.units))
.unwrap_or_else(|| Satoshis::ZERO),
outstanding: data
.loan_outstanding
.map(|b| UsdCents::from_usd(b.settled.normal_balance.units))
.unwrap_or_else(|| UsdCents::ZERO),
interest_incurred: data
.interest_income
.map(|b| UsdCents::from_usd(b.settled.normal_balance.units))
.unwrap_or_else(|| UsdCents::ZERO),
}
}
}
Loading

0 comments on commit 9b8ce11

Please sign in to comment.