Skip to content

Conversation

@Tapanito
Copy link
Collaborator

Discussion thread can be found here: #190
Development branch can be found here: TBD

@sappenin
Copy link
Collaborator

@Tapanito this PR seems ready to merge -- do you agree?

(Note that per the DRAFTS section of the CONTRIBUTING guidelines, merging does not mean endorsement or even a completed spec, but instead offers a way for spec editors to more easily edit using PRs).

@Tapanito
Copy link
Collaborator Author

Tapanito commented Dec 4, 2024

Before merging, I'd like to have at least one review by an engineer to ensure everything is in order. In addition, similarly to Single Asset Vault, it is simpler/more convenient to edit a PR with various small design changes (e.g. names), rather than open a new PR for each of them. As a result, keeping a PR open allows to have a spec. that is more readily in-alignment with implementation.

@sappenin
Copy link
Collaborator

sappenin commented Dec 4, 2024

Makes sense, especially if it's more convenient. Just didn't want you (or anyone reading along) to think that merging here implies "finished product" (that's why we have a d designator, to indicate draft status).

Tapanito and others added 7 commits January 27, 2025 16:40
- Adds VaultNode to LoanBroker object to track in which owner directory of the Vaults pseudo-account the LoanBroker object is referenced.
- Adds LoanBrokerNode to Loan object to track in which owner directory of the LoanBroker object the Loan is references.
- Replaces CurrentTime to LastClosedLedger.CloseTime.
- Changes the LoanBroker.Delete transaction to automatically return any outstanding Cover to the LoanBroker.Owner.
- Adds a balance check to the LoanBrokerCoverDeposit transaction when depositing XRP.
- Adds a check to LoanBrokerCoverWithdraw to ensure the CoverAvailable does not drop below Mimimum Cover Required.
Co-authored-by: Ed Hennis <ed@ripple.com>
@Tapanito
Copy link
Collaborator Author

Tapanito commented Jan 28, 2025 via email

@Tapanito
Copy link
Collaborator Author

Tapanito commented Jan 28, 2025 via email

Copy link
Collaborator

@ximinez ximinez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updates look good.

Co-authored-by: Ed Hennis <ed@ripple.com>
@Tapanito
Copy link
Collaborator Author

Tapanito commented Mar 19, 2025 via email

@ximinez
Copy link
Collaborator

ximinez commented Mar 20, 2025

No, since we introduced a pseudo-account for the LoanBroker, the deposit will go there

Right, I wasn't thinking about that, but if there is a loan default, and funds have to be paid out of the Cover Amount, wouldn't that go to the SAV?

@Tapanito
Copy link
Collaborator Author

No, since we introduced a pseudo-account for the LoanBroker, the deposit will go there

Right, I wasn't thinking about that, but if there is a loan default, and funds have to be paid out of the Cover Amount, wouldn't that go to the SAV?

Yes, 100%, the cover amount liquidation, in essence, will become a transfer of asset from one pseduo-account to another, with some accounting state changes.

@ximinez
Copy link
Collaborator

ximinez commented Mar 21, 2025

No, since we introduced a pseudo-account for the LoanBroker, the deposit will go there

Right, I wasn't thinking about that, but if there is a loan default, and funds have to be paid out of the Cover Amount, wouldn't that go to the SAV?

Yes, 100%, the cover amount liquidation, in essence, will become a transfer of asset from one pseduo-account to another, with some accounting state changes.

Right, so they should be the same currency, no? I'm fine with it being an STAmount as long as we're on the same page that the transaction should fail if it's not the same currency as the SAV.

Comment on lines +885 to +887
The total fee for the transaction will be increased due to the extra signatures that need to be processed, similar to the additional fees for multisigning. The minimum fee will be $(|signatures| + 1) \times base_fee$ where $|signatures| == max(1, |tx.CounterPartySignature.Signers|)$

The total fee calculation for signatures will now be $(1 + |tx.Signers| + |signatures|) \times base_fee$. In other words, even without a `tx.Signers` list, the minimum fee will be $2 \times base_fee$.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The total fee for the transaction will be increased due to the extra signatures that need to be processed, similar to the additional fees for multisigning. The minimum fee will be $(|signatures| + 1) \times base_fee$ where $|signatures| == max(1, |tx.CounterPartySignature.Signers|)$
The total fee calculation for signatures will now be $(1 + |tx.Signers| + |signatures|) \times base_fee$. In other words, even without a `tx.Signers` list, the minimum fee will be $2 \times base_fee$.
The total fee for the transaction will be increased due to the extra signatures that need to be processed, similar to the additional fees for multisigning. The minimum fee will be $(|signatures| + 1) \times base\_fee$ where $|signatures| == max(1, |tx.CounterPartySignature.Signers|)$
The total fee calculation for signatures will now be $(1 + |tx.Signers| + |signatures|) \times base\_fee$. In other words, even without a `tx.Signers` list, the minimum fee will be $2 \times base\_fee$.

I think this is correct LaTeX to get the underscore to appear right.

Copy link
Collaborator

@ximinez ximinez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a comment...

Comment on lines 1216 to 1218
$$
latePaymentInterest = principalOutstanding \times \frac{lateInterestRate \times secondsSinceLastPayment}{365 \times 24 \times 60 \times 60}
$$
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the correct calculation is:

secondsSinceLastPayment = lastLedgerCloseTime - Loan.nextPaymentDate
``

I was thinking about this yesterday, when we were looking at this. I agree.

@amarantha-k
Copy link
Collaborator

@Tapanito Just a quick note to drop the "d" from the directory name before merging. (XLS-0066d-lending-protocol/README.md -> XLS-0066-lending-protocol/README.md).

@mvadari mvadari changed the title XLS-0066d Lending Protocol XLS-0066 Lending Protocol Oct 22, 2025

##### 2.2.2.2 TotalValueOutstanding

The total outstanding value of the Loan, including all fees. To calculate the outstanding interest portion, use this formula: `TotalInterestOutstanding = TotalValueOutstanding - PrincipalOutstanding - ManagementFeeOutstanding`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it includes all fees then why only ManagementFeeOutstanding is subtracted?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a wording issue. TotalValueOutstanding contains the fees that are charged against the interest. It does not contain service fees, for example.

Let me wordsmith that better.


- **PrincipalOutstanding**: Represents the remaining principal balance that the borrower must repay to satisfy the original loan amount.
- **TotalValueOutstanding**: Encompasses the complete remaining loan obligation, comprising both the outstanding principal and all scheduled interest payments based on the original amortization schedule. This value excludes any additional interest charges resulting from late payments.
- **InterestOutstanding**: The total scheduled interest remaining on the loan, derived as `TotalValueOutstanding - PrincipalOutstanding`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the fee not subtracted? This is different from section 2.2.2.2.

- **TotalValueOutstanding**: Encompasses the complete remaining loan obligation, comprising both the outstanding principal and all scheduled interest payments based on the original amortization schedule. This value excludes any additional interest charges resulting from late payments.
- **InterestOutstanding**: The total scheduled interest remaining on the loan, derived as `TotalValueOutstanding - PrincipalOutstanding`.

**Asset-Specific Precision Handling**: For discrete asset types (MPTs and XRP denominated in drops), both `TotalValueOutstanding` and `PrincipalOutstanding` values are truncated to whole numbers to ensure compatibility with the underlying asset precision requirements.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the values always truncated or rounded up or down?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After the new LoanPay changes, values are rounded to the nearest even number.

| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :---------------------------------------------------------------------- |
| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | `77` | Transaction type. |
| `LoanBrokerID` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The Loan Broker ID from which to withdraw First-Loss Capital. |
| `Amount` | :heavy_check_mark: | `object` | `AMOUNT` | 0 | The Fist-Loss Capital amount to withdraw. |
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be NUMBER type? We are going to access Vault anyways so will now specific Asset type. NUMBER is going to make the tx size smaller by 40 bytes(IOU/XRP) or 24 bytes (MPT). It's also fewer checks in preclaim. This also applies to LoanBrokerCoverDeposit, LoanBrokerCoverWithdraw, and LoanPay. By the way, PrincipalRequested in LoanSet is NUMBER. I think it makes sense to make all the Amount fields to have NUMBER type.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be NUMBER type? We are going to access Vault anyways so will now specific Asset type. NUMBER is going to make the tx size smaller by 40 bytes(IOU/XRP) or 24 bytes (MPT). It's also fewer checks in preclaim. This also applies to LoanBrokerCoverDeposit, LoanBrokerCoverWithdraw, and LoanPay. By the way, PrincipalRequested in LoanSet is NUMBER. I think it makes sense to make all the Amount fields to have NUMBER type.

There was an earlier discussion on this topic. #240 (comment)

tl;dr Because of how SFields are defined, any field name Amount must be AMOUNT. It was decided to keep the familiar name rather than create a new field.


### 1.2.2 Freeze

Freeze is a mechanism by which an asset Issuer (IOUT or MPT, not XRP) freezes an `Account`, preventing that account from sending the Asset. Deep Freeze is a mechanism by which an asset Issuer prevents and `Account` from both sending and receiving and Asset. Finally, an Issuer may enact a global freeze, which prevents everyone from sending or receiving the Asset. Note that in both single-account and global freezes, the Asset can be sent to the Issuer.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: IOUT -> IOU.


If the Issuer freezes a Borrower's account, the Borrower cannot make loan payments. However, a frozen account does not lift the obligation to repay a Loan. If the Issuer Deep Freezes a Borrower's account, the Brrower cannot make loan payments.

A Deep Freeze does not affect the Loan Broker's functions. However, a Deep Freeze will prevent the Loan Broker from receing any Lending Protocol Fees.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: receing -> receiving.


A Deep Freeze does not affect the Loan Broker's functions. However, a Deep Freeze will prevent the Loan Broker from receing any Lending Protocol Fees.

The Issuer may also Freeze of Deep Freeze the `_pseudo-account_` of the Loan Broker. A Freeze on the `_pseudo-account_` will prevent the Loan Broker from creating new Loans. However existing Loans will not be affected. In contrast, a Deep Freeze, will also prevent the Loans from being paid.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: Freeze of Deep Freeze -> Freeze or Deep Freeze.

- **`First-Loss Capital`**: The portion of capital that absorbs initial losses in case of a Default, protecting the Vault from loss.
- **`Term`**: The period over which a Borrower must repay the Loan.
- **`Amortization`**: The gradual repayment of a loan through scheduled payments that cover both interest and principal over time.
- **`Repayment Schedule`**: A detailed plan that outlines when and how much a borrower must pay to repay the Loan fLoan.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: to repay the Loan fLoan -> to repay the Loan funds.

The loan's financial state is tracked through three key components:

- **PrincipalOutstanding**: Represents the remaining principal balance that the borrower must repay to satisfy the original loan amount.
- **TotalValueOutstanding**: Encompasses the complete remaining loan obligation, comprising the outstanding principal, all scheduled interest payments based on the original amortization schedule and the management fee paid on the interest. This value excludes any additional interest charges resulting from late payments, overpayments of full payments.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the interest based on the original amortization schedule? If there is an additional amount added to a payment then the amortization schedule has to be updated, no?

managementFee_{late} = latePaymentInterest_{gross} \times managementFeeRate
$$

The change in the total loan value is equent to the late payment interest, excluding any fees.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: equent -> equal.


- **PrincipalOutstanding**: Represents the remaining principal balance that the borrower must repay to satisfy the original loan amount.
- **TotalValueOutstanding**: Encompasses the complete remaining loan obligation, comprising the outstanding principal, all scheduled interest payments based on the original amortization schedule and the management fee paid on the interest. This value excludes any additional interest charges resulting from late payments, overpayments of full payments.
- **InterestOutstanding**: The total scheduled interest (including fee) remaining on the loan, derived as `TotalValueOutstanding - PrincipalOutstanding`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in 2.2.2.2 TotalValueOutstanding, it says it's TotalInterestOutstanding = TotalValueOutstanding - PrincipalOutstanding - ManagementFeeOutstanding, what's the difference?

Comment on lines +1034 to +1036
- Otherwise:

- Increase the `RippleState` balance between the `LoanBroker.Owner` `AccountRoot` and the `Issuer` `AccountRoot` by `Loan.LoanOriginationFee`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why isnt CoverAvailable increased in this case?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nvm, the fund isn't added to the broker's pseudo-account.


### 1.2.2 Freeze

Freeze is a mechanism by which an asset Issuer (IOUT or MPT, not XRP) freezes an `Account`, preventing that account from sending the Asset. Deep Freeze is a mechanism by which an asset Issuer prevents and `Account` from both sending and receiving and Asset. Finally, an Issuer may enact a global freeze, which prevents everyone from sending or receiving the Asset. Note that in both single-account and global freezes, the Asset can be sent to the Issuer.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep Freeze is a mechanism by which an asset Issuer prevents and Account

Typo: an Account

| `LoanBrokerNode` | `No` | `Yes` | :heavy_check_mark: | `number` | `UINT64` | `N/A` | Identifies the page where this item is referenced in the `LoanBroker`s owner directory. |
| `LoanBrokerID` | `No` | `Yes` | :heavy_check_mark: | `string` | `HASH256` | `N/A` | The ID of the `LoanBroker` associated with this Loan Instance. |
| `Borrower` | `No` | `Yes` | :heavy_check_mark: | `string` | `AccountID` | `N/A` | The address of the account that is the borrower. |
| `LoanOriginationFee` | `No` | `Yes` | :heavy_check_mark: | `number` | `NUMBER` | `N/A` | A nominal nds amount paid to the `LoanBroker.Owner` when the Loan is created. |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: A nominal funds amount paid to

2. The Loan Broker creates a `LoanBroker` ledger entry with a `LoanBrokerSet` transaction.
3. The Depositors deposit assets into the `Vault`.
4. Optionally, the Loan Broker deposits First-Loss Capital into the `LoanBroker` with the `LoanBrokerCoverDeposit` transaction.
5. The Loan Broker and Borrower create a `Loan` object with a `LoanSet` transaction and the requested principal (excluding fees) is transered to the Borrower.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: ... is transferred to the Borrower


### 1.2.2 Freeze

Freeze is a mechanism by which an asset Issuer (IOUT or MPT, not XRP) freezes an `Account`, preventing that account from sending the Asset. Deep Freeze is a mechanism by which an asset Issuer prevents and `Account` from both sending and receiving and Asset. Finally, an Issuer may enact a global freeze, which prevents everyone from sending or receiving the Asset. Note that in both single-account and global freezes, the Asset can be sent to the Issuer.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo:

  1. ... (IOU or MPT, not XRP) ...
  2. prevents an Account from both sending and receiving an Asset?


Freeze is a mechanism by which an asset Issuer (IOUT or MPT, not XRP) freezes an `Account`, preventing that account from sending the Asset. Deep Freeze is a mechanism by which an asset Issuer prevents and `Account` from both sending and receiving and Asset. Finally, an Issuer may enact a global freeze, which prevents everyone from sending or receiving the Asset. Note that in both single-account and global freezes, the Asset can be sent to the Issuer.

If the Issuer freezes a Borrower's account, the Borrower cannot make loan payments. However, a frozen account does not lift the obligation to repay a Loan. If the Issuer Deep Freezes a Borrower's account, the Brrower cannot make loan payments.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: ... the Borrower cannot make loan payments

PrincipalRequested = 1,000 Tokens
InterestRate = 0.1 (10%)

# SIMPLIfIED
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

# SIMPLIFIED


- The `MPToken` object for the `Vault(LoanBroker(Loan(LoanID).LoanBrokerID).VaultID).Asset` of the submitter `AccountRoot` has
- `lsfMPTLocked` flag set.
- `MPTAmount` < `totalDue` (inssuficient funds).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: insufficient funds


- The borrower's balance is **decreased** by `totalPaidByBorrower`.
- The `Vault` pseudo-account's balance is **increased** by `totalToVault`.
- The `LoanBroker.Owner`'s balance OR the `LoanBroker` pseudo-account's balance is **increawsed** by `totalToBroker`, depending on the fee destination.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: ... is **increased** by ...

| ----------------- | :----------------: | :-------: | :-----------: | :-----------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TransactionType` | :heavy_check_mark: | `string` | `UINT16` | `78` | Transaction type. |
| `LoanBrokerID` | | `string` | `HASH256` | `N/A` | The Loan Broker ID from which to withdraw First-Loss Capital. Must be provided if the `Amount` is an MPT, or `Amount` is an IOU and `issuer` is specified as the `Account` submitting the transaction. |
| `Amount` | | `object` | `AMOUNT` | 0 | The First-Loss Capital amount to clawback. If the amount is `0` or not provided, clawback funds up to `LoanBroker.DebtTotal * LoanBroker.CoverRateMinimum`. |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that the description of Amount is conflicting with the information in

##### 3.1.5.2 State Changes

- If `Amount` is 0 or unset, set `Amount` to `LoanBroker.CoverAvailable - LoanBroker.DebtTotal * LoanBroker.CoverRateMinimum`.

Do we clawback up to LoanBroker.DebtTotal * LoanBroker.CoverRateMinimum or LoanBroker.CoverAvailable - LoanBroker.DebtTotal * LoanBroker.CoverRateMinimum?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or LoanBroker.CoverAvailable - LoanBroker.DebtTotal * LoanBroker.CoverRateMinimum?

This appears to be the correct interpretation.

Look into this file for more details about LoanBrokerCoverClawback.

Here is the implementation:

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants