This protocol implements a peer-to-peer lending system for ERC20 tokens, available in two main versions: non-vaulted and vaulted. Both allow ERC20 token owners to use their assets as collateral to borrow cryptocurrency, while lenders can provide loans and earn interest. The protocol is designed to be trustless, efficient, and flexible, with support for various ERC20 tokens as collateral. It also includes features for dynamic collateral management, partial liquidations, and loan refinancing. The vaulted version introduces a robust vault system for enhanced compliance and isolated collateral management.
The protocol is implemented in Vyper 0.4.3. It exists in three primary versions:
- Non-vaulted: The initial version, where collateral is held directly by the main lending contract. The main component is the
P2PLendingErc20contract, which supports peer-to-peer lending backed by ERC20 collateral. It utilizesP2PLendingBasefor shared state and common logic,P2PLendingRefinanceas a facet for complex refinancing operations,P2PLendingLiquidationas a facet for liquidation operations, and interacts with aKYCValidatorcontract for compliance checks. - Vaulted: An updated version featuring a dedicated vault system for managing collateral. This enhancement provides better isolation of borrower collateral, which is crucial for compliance requirements and enhanced security. The main entry point for the vaulted version is
P2PLendingVaultedErc20, which leveragesP2PLendingVaultedBasefor core logic,P2PLendingVaultedRefinanceandP2PLendingVaultedLiquidationas facets, and integrates withKYCValidatorand a newP2PLendingVaultcontract for collateral handling. - Securitize: A specialized version designed for Securitize DS Token collateral. This version supports the unique requirements of security tokens, including a redemption workflow where collateral can be converted back to payment tokens via Securitize. The main entry point is
P2PLendingSecuritizeErc20, usingP2PLendingSecuritizeBasefor core logic,P2PLendingSecuritizeRefinanceandP2PLendingSecuritizeLiquidationas facets, andP2PLendingVaultSecuritizefor vault management with SecuritizeSwap integration.
The lending of an NFT in the context of this protocol means that:
- A lender provides a loan offer with specific terms
- A borrower creates a loan using their NFT as collateral
- The loan is created when the borrower accepts an offer
- The borrower repays the loan within the specified term
- If the borrower defaults, the lender or a liquidator can trigger a full liquidation to claim the ERC20 collateral.
- The loan can be partially liquidated if the Loan-to-Value (LTV) ratio exceeds a certain threshold.
- The lender can "call" a loan, initiating a repayment window before maturity.
- Borrowers can add or remove collateral from an ongoing loan.
- A loan may be replaced by the borrower while still ongoing, by accepting a new offer (refinancing).
- A loan may be replaced by the lender while still ongoing, within some defined conditions (lender-initiated refinancing).
The protocol consists of the following core contracts:
P2PLendingErc20.vy: The main entry point for users to interact with the lending protocol.P2PLendingBase.vy: An abstract base contract that holds the core state variables and implements common internal logic shared across lending contracts.P2PLendingRefinance.vy: A facet contract (called viadelegatecall) that handles the complex logic for loan refinancing, for both borrower- and lender-initiated replacements.P2PLendingLiquidation.vy: A facet contract (called viadelegatecall) that handles the logic for partial and full loan liquidations.KYCValidator.vy: A contract responsible for validating signed KYC (Know Your Customer) attestations for borrowers and lenders.
P2PLendingVaultedErc20.vy: The main entry point for users to interact with the vaulted lending protocol, incorporating the vault system.P2PLendingVaultedBase.vy: An abstract base contract for the vaulted version that holds core state variables and implements common internal logic, including vault interactions.P2PLendingVaultedRefinance.vy: A facet contract for the vaulted version (called viadelegatecall) handling loan refinancing logic.P2PLendingVaultedLiquidation.vy: A facet contract for the vaulted version (called viadelegatecall) handling loan liquidation logic.P2PLendingVault.vy: A minimal proxy factory and implementation for individual borrower collateral vaults, deployed via CREATE2. Each vault holds the collateral for a borrower's loans and provides isolated management.KYCValidator.vy: (Same as non-vaulted) A contract responsible for validating signed KYC (Know Your Customer) attestations for borrowers and lenders.
P2PLendingSecuritizeErc20.vy: The main entry point for Securitize DS Token lending, with redemption workflow support.P2PLendingSecuritizeBase.vy: An abstract base contract for the Securitize version that holds core state variables, implements redemption verification, and manages multiple vaults per borrower.P2PLendingSecuritizeRefinance.vy: A facet contract (called viadelegatecall) handling loan refinancing and maturity extension logic.P2PLendingSecuritizeLiquidation.vy: A facet contract (called viadelegatecall) handling loan liquidation with redemption-aware settlement.P2PLendingVaultSecuritize.vy: A vault implementation with SecuritizeSwap integration, enabling the purchase of DS tokens from stablecoins.KYCValidator.vy: (Same as other versions) A contract responsible for validating signed KYC attestations.
SecuritizeRegistrarV1Connector.vy: A bridge contract that connects P2P lending vaults to the Securitize Vault Registrar. The P2P lending contracts (both vaulted and securitize) call the connector'sregister_vault(vault, investor_wallet)function automatically during vault creation. The contract maintains an allowlist of authorized P2P lending contracts and can only be managed by the owner.
The current status of the protocol follows certain assumptions:
- Support for any ERC20 token as collateral, specified at deployment (held directly by the main contract in the non-vaulted version, and within a dedicated vault for each borrower in the vaulted version).
- Use of an ERC20 token (e.g., USDC) as a payment token, defined at deployment time for each instance of
P2PLendingErc20orP2PLendingVaultedErc20. - Integration with an oracle (Chainlink AggregatorV3) for collateral valuation.
- All participants (borrower and lender) must have valid KYC attestations signed by a whitelisted KYC validator.
- Loan terms are part of the lender offers, which are signed and kept off-chain.
- Offers have an expiration timestamp and can be revoked on-chain.
- Loans can be callable by the lender after a specified
call_eligibilityperiod, starting acall_windowfor repayment before default. - Loans can be partially liquidated if the LTV exceeds a
partial_liquidation_ltvthreshold. - Dynamic collateral management (add/remove ERC20 collateral) is supported, managed directly by the main contract in the non-vaulted version, and through the borrower's dedicated vault in the vaulted version.
- Additional fees are supported both for the protocol (upfront and settlement) and for the lender (origination).
- The vaulted version uses a vault system (
P2PLendingVault) to hold collateral. Each borrower has a unique vault, deployed via CREATE2, enhancing collateral isolation and compliance. - The Securitize version creates a new vault for each loan, supports collateral redemption via Securitize, and allows maturity extensions. Callable loans are not supported.
Below are the smart contract audits performed for the protocol so far.
| Auditor | Version | Status | |
|---|---|---|---|
| Hexens | v0 | Done | hexens-zharta-oct-25-final.pdf |
| Hexens | v1 | Done | hexens-zharta-dec-25-final.pdf |
| Hexens | Securitize | Done | hexens-zharta-feb-26-final.pdf |
The P2PLendingErc20.vy contract is the main entry point for users, inheriting most of its state and common logic from P2PLendingBase.vy. The P2PLendingRefinance.vy contract handles the complex refinancing flows, being called via a delegatecall from P2PLendingErc20.vy to share the same storage. Similarly, P2PLendingLiquidation.vy handles liquidation logic via delegatecall. The KYCValidator.vy contract is an external dependency used for compliance checks.
Users and other protocols should primarily interact with the P2PLendingErc20.vy contract. This contract is responsible for:
- Creating loans based on signed offers and ERC20 collateral, including KYC validation (collateral held directly by the contract).
- Settling loans and distributing funds (returning collateral directly).
- Handling defaulted loans by allowing lenders to claim collateral (collateral transferred directly to lender).
- Performing partial liquidations based on LTV thresholds (collateral transferred directly to liquidator/lender).
- Initiating loan calls by lenders.
- Allowing borrowers to add or remove collateral from existing loans (collateral deposited/withdrawn directly).
- Facilitating loan refinancing for both borrowers and lenders via the
P2PLendingRefinancefacet. - Managing protocol fees and authorized proxies.
- Revoking unused offers.
The P2PLendingVaultedErc20.vy contract serves as the main entry point for the vaulted version. It uses P2PLendingVaultedBase.vy for common logic and state, which has been updated to interact with the P2PLendingVault system. Each borrower has a unique, minimal proxy vault deployed via CREATE2 (P2PLendingVault), which securely holds their collateral. Refinancing and liquidation logic are handled by P2PLendingVaultedRefinance.vy and P2PLendingVaultedLiquidation.vy facets, respectively, which also interact with the vaults. The KYCValidator.vy remains an external dependency.
Users and other protocols should primarily interact with the P2PLendingVaultedErc20.vy contract. This contract is responsible for:
- Creating loans based on signed offers and ERC20 collateral, including KYC validation (collateral deposited into the borrower's dedicated vault).
- Settling loans and distributing funds (collateral withdrawn from the vault and returned to the borrower).
- Handling defaulted loans by allowing lenders to claim collateral (collateral transferred from the borrower's vault to the lender).
- Performing partial liquidations based on LTV thresholds (collateral transferred from the borrower's vault to the liquidator/lender).
- Initiating loan calls by lenders.
- Allowing borrowers to add or remove collateral from existing loans (collateral deposited into/widhrawn from the borrower's vault).
- Facilitating loan refinancing for both borrowers and lenders via the
P2PLendingVaultedRefinancefacet. - Managing protocol fees and authorized proxies.
- Revoking unused offers.
- Supporting loan transfers to new borrowers, including transferring collateral ownership in their respective vaults.
The P2PLendingSecuritizeErc20.vy contract serves as the main entry point for Securitize DS Token lending. It builds upon the vaulted architecture but adds specific features for security tokens:
- Multiple Vaults per Borrower: Unlike the standard vaulted version where each borrower has a single vault, the Securitize version creates a new vault for each loan (
vault_id). This allows for better tracking and isolation of collateral per loan, as well as to track redeption results back to the redeemed loan. - Redemption Workflow: Borrowers can initiate a
redeemprocess to convert their DS Token collateral back to payment tokens via the Securitize platform. Theredeem_starttimestamp andredeem_residual_collateraltrack this process. - Maturity Extensions: Unlike other versions, the Securitize contracts support extending loan maturity via
extend_loan(borrower-initiated with lender's signed offer) andextend_loan_lender(lender-initiated, no borrower action needed). - No Callable Loans: The
call_eligibilityandcall_windowfeatures are disabled for Securitize loans. - SecuritizeSwap Integration: The
P2PLendingVaultSecuritizevault includes abuy()function that allows purchasing DS tokens from stablecoins via the SecuritizeSwap contract. - Redemption-Aware Settlement/Liquidation: When settling or liquidating a redeemed loan, the contract verifies the redemption result (signed by the protocol owner) and handles the mixed collateral/payment token balances appropriately.
Users interact with P2PLendingSecuritizeErc20.vy for:
- Creating loans with DS Token collateral (each loan gets a new vault).
- Initiating redemption of collateral via
redeem(). - Settling loans (with or without redemption).
- Extending loan maturity.
- Adding or removing collateral (only before redemption starts).
- Loan refinancing for both borrowers and lenders.
- Managing protocol configuration.
Loans are created based on the borrower acceptance of offers from lenders, which specify the loan terms. The general features of an offer are:
-
Offer Structure: An offer is defined by the
Offerstructure (fromP2PLendingBase/P2PLendingVaultedBase), which includes:principal: Principal amount of the loan (optional, can be 0 for borrower-defined).apr: Annual Percentage Rate.payment_token: Address of the payment ERC20 token.collateral_token: Address of the collateral ERC20 token.duration: Duration of the loan in seconds.origination_fee_bps: Origination fee percentage (in basis points) paid to the lender.min_collateral_amount: Minimum amount of collateral required (optional).max_iltv: Maximum Initial Loan-to-Value (optional, used ifmin_collateral_amountisn't specified).available_liquidity: The total principal amount the lender has allocated to this offer.call_eligibility: Time in seconds after loan start when the lender can call the loan (0 if not callable).call_window: Time in seconds after a loan is called for the borrower to repay before default (0 if not callable).partial_liquidation_ltv: LTV threshold (in basis points) for partial liquidation (0 if not applicable).oracle_addr: Address of the oracle contract for collateral valuation.expiration: Expiration timestamp of the offer.lender: Address of the lender.borrower: Specific borrower address for the offer (empty address for general offers).tracing_id: A unique identifier for tracking offers, enabling multiple loans from one offer.
-
Signed Offers: Lenders create and sign offers off-chain. These signed offers (
SignedOffer) combine theOfferstructure with an EIP-712 signature. -
Offer Validation: When a borrower wants to create a loan using an offer, the protocol verifies the offer's signature, checks if it's still valid (not expired), if the
payment_tokenandcollateral_tokenmatch the contract's configuration, and if theoracle_addris valid. -
Offer Utilization: Offers track
available_liquidityandcommited_liquidity(pertracing_id). It is important to note that bothavailable_liquidityandcommited_liquiditystrictly track the principal amount of the loan, not including any protocol or origination fees. When an offer is used to create a loan, the loan's principal is deducted from the offer'savailable_liquidity(viacommited_liquidity), preventing overuse beyond the specified limit. -
Offer Revocation: Lenders can revoke their offers before they expire or are fully utilized. This is a one-time revocation per offer ID.
As offers are kept off-chain, to prevent abusive usage, several on-chain validations are in place:
- Each offer has an
expirationtimestamp, after which it cannot be used. - Offers can be revoked before expiration by calling
revoke_offerinP2PLendingErc20orP2PLendingVaultedErc20. - Each offer has
available_liquidityto define the maximum total principal that can be lent through it.
-
Loan Creation (
create_loan): The process involves verifying the offer's signature and validity, along with KYC validation for both borrower and lender. The principal amount is then transferred from the lender to the borrower, along with adjustments for upfront fees. Upfront fees are distributed, and a loan record is created (base.Loanstruct). Initial LTV is checked againstmax_iltv. For a loan to be successfully created, the lender must approve/hold funds for the total amount to be transferred from their wallet, which is calculated asprincipal + protocol_upfront_fee_amount - origination_fee_amount. If the lender's wallet does not hold sufficient funds or has not provided adequate allowance to the protocol for this calculated amount, thecreate_loantransaction will revert.- For the non-vaulted version, the ERC20 collateral is transferred directly to the main
P2PLendingErc20contract. - For the vaulted version, the ERC20 collateral is transferred to a dedicated
P2PLendingVaultfor the borrower. If a vault doesn't exist for the borrower, it's created via CREATE2.
- For the non-vaulted version, the ERC20 collateral is transferred directly to the main
-
Loan Settlement (
settle_loan): To settle a loan, the contract calculates the total repayment amount (principal + accrued interest + protocol settlement fee). The borrower transfers this amount to the contract, which then distributes the funds to the lender and the protocol wallet.- For the non-vaulted version, the ERC20 collateral is transferred directly back to the borrower from the
P2PLendingErc20contract. - For the vaulted version, the ERC20 collateral is transferred from the borrower's
P2PLendingVaultback to the borrower.
- For the non-vaulted version, the ERC20 collateral is transferred directly back to the borrower from the
-
Defaulted Loan Collateral Claim (
liquidate_loan): If a loan defaults (either by reachingmaturityor failing to repay withincall_windowafter acall_loan), the lender or any address (liquidator) can trigger a full liquidation vialiquidate_loan(handled by theP2PLendingLiquidationorP2PLendingVaultedLiquidationfacet). The collateral is transferred to the lender (or liquidator for a fee) without any fund transfers.- For the non-vaulted version, collateral is transferred directly from the
P2PLendingErc20contract. - For the vaulted version, collateral is transferred from the borrower's
P2PLendingVault. - Important: A full liquidation can also be triggered on a non-defaulted loan if its LTV exceeds the
partial_liquidation_ltvthreshold (meaning partial liquidation is enabled and triggered) AND the loan is so severely undercollateralized that even a partial liquidation (aimed at restoring theinitial_ltv) would result in the entire outstanding debt being written off. In such cases, the system proceeds with a full liquidation.
- For the non-vaulted version, collateral is transferred directly from the
-
Partial Liquidation (
partially_liquidate_loan): If the current Loan-to-Value (LTV) ratio of an active loan exceeds thepartial_liquidation_ltvthreshold defined in the offer, any address can trigger a partial liquidation viapartially_liquidate_loan(handled by theP2PLendingLiquidationorP2PLendingVaultedLiquidationfacet). In this process:- A portion of the outstanding debt is "written off" (reduced).
- The goal is to bring the LTV back to the
initial_ltvratio, thereby "healing" the loan. This is only possible if the calculated debt write-off amount is less than the total outstanding debt. If a partial liquidation would write off the entire debt, the loan would instead be fully liquidated. - The
liquidatorreceives an amount of collateral equal to thecollateral_claimedand thepartial_liquidation_fee(both in collateral tokens). - If the
liquidatoris not thelender, theliquidatoralso transfers theprincipal_written_offamount (in payment tokens) to thelender, effectively buying out the written-off portion of the debt. The lender'scommited_liquidityfor the offer is reduced by thisprincipal_written_off. - The loan's
accrual_start_timeis reset to the currentblock.timestamp. - For the non-vaulted version, collateral is claimed directly from the
P2PLendingErc20contract. - For the vaulted version, collateral is claimed from the borrower's
P2PLendingVault.
-
Call Loan (
call_loan): Lenders can initiate a loan call if thecall_eligibilityperiod has passed and the loan is not yet called or defaulted. This sets acall_timetimestamp, and the borrower then hascall_windowseconds to repay the loan before it automatically defaults. -
Add Collateral (
add_collateral_to_loan): Borrowers can add more ERC20 collateral to an ongoing loan at any time, which reduces the loan's LTV.- For the non-vaulted version, collateral is deposited directly to the
P2PLendingErc20contract. - For the vaulted version, collateral is deposited into the borrower's
P2PLendingVault.
- For the non-vaulted version, collateral is deposited directly to the
-
Remove Collateral (
remove_collateral_from_loan): Borrowers can remove collateral from an ongoing loan as long as the remaining collateral is at leastmin_collateral_amountand the LTV does not exceed theinitial_ltv(to prevent immediately increasing risk beyond the initial agreement).- For the non-vaulted version, collateral is withdrawn directly from the
P2PLendingErc20contract. - For the vaulted version, collateral is withdrawn from the borrower's
P2PLendingVault.
- For the non-vaulted version, collateral is withdrawn directly from the
-
Loan Replacement by Borrower (
replace_loan): A borrower can refinance an existing loan by accepting a new offer (which might be from the same or a different lender). The function, handled by theP2PLendingRefinance(non-vaulted) orP2PLendingVaultedRefinance(vaulted) facet, effectively settles the old loan and creates a new one using the same collateral. Liquidity adjustments are made for both borrower and lender, and any difference in collateral amount is transferred. KYC for the new lender is required. For the vaulted version, collateral remains within the borrower's vault, with only internal adjustments if the amount changes. -
Loan Replacement by Lender (
replace_loan_lender): A lender can initiate a replacement of an existing loan, effectively selling it to a new lender or refinancing it themselves. This is handled by theP2PLendingRefinance(non-vaulted) orP2PLendingVaultedRefinance(vaulted) facet. The borrower's terms are protected, ensuring:- No additional liquidity is required from the borrower.
- The borrower's repayment obligations (principal, interest, call eligibility, LTV thresholds) under the new conditions are not worse than the original loan's conditions up until the original loan's maturity.
- Any necessary compensation is calculated and handled by the protocol.
-
Loan Borrower Transfer (
transfer_loan) (vaulted only): In the vaulted version, thetransfer_loanfunction allows a privilegedtransfer_agentto change the borrower of an existing loan. This is designed to support special cases (e.g., death, lost keys, or legal transfers). When a loan is transferred, the collateral is also moved from the old borrower'sP2PLendingVaultto the new borrower'sP2PLendingVault(creating it if necessary).
The protocol supports several types of fees:
- Protocol Upfront Fee: A percentage (in basis points) of the principal, paid to the
protocol_walletwhen the loan is created. Configurable by the owner. - Protocol Settlement Fee: A percentage (in basis points) of the interest, paid to the
protocol_walletduring loan settlement. Configurable by the owner. - Origination Fee: An upfront fee (in basis points of the principal) paid to the lender when a loan is created. It is part of the loan terms defined in the
Offerstructure. - Partial Liquidation Fee: A percentage (in basis points) of the claimed collateral value, paid to the liquidator during a partial liquidation. Configurable by the owner.
- Full Liquidation Fee: A percentage (in basis points) of the claimed collateral value, paid to the liquidator during a full liquidation. Configurable by the owner.
All upfront fees are paid during loan creation, while settlement fees are paid as a fraction of the interest amount during loan settlement.
The protocol defines the following key roles:
Owner: The privileged address that can update protocol-wide parameters (e.g., protocol fees, partial/full liquidation fees), manage authorized proxies, and propose/claim ownership.Borrower: The recipient of the loan, identified byloan.borrower. Can settle loans, add/remove collateral, and initiate loan replacements.Lender: The provider of the loan, identified byloan.lender. Can initiate loan replacements and claim collateral for defaulted loans, and call loans.Liquidator: Any address that can trigger apartially_liquidate_loanorliquidate_loanif the LTV conditions are met. Receives thepartial_liquidation_feeorfull_liquidation_feefor performing this action.KYC Validator: An external address registered in theKYCValidatorcontract that signs wallet attestations, ensuring compliance.Transfer Agent: A privileged address (vaulted only) that can transfer a loan's ownership to a new borrower, primarily for compliance and recovery scenarios.
The protocol includes two versions, non-vaulted and vaulted. The non-vaulted version (P2PLendingErc20) handles collateral directly within the main contract. The vaulted version (P2PLendingVaultedErc20) introduces a dedicated vault system (P2PLendingVault) for each borrower to manage collateral, enhancing security and compliance.
The P2PLendingErc20 contract serves as the main entry point for the non-vaulted protocol, handling the core logic for loan origination, management, and settlement using ERC20 tokens as collateral. It inherits common state and functions from P2PLendingBase.
| Variable | Type | Mutable | Description |
|---|---|---|---|
payment_token |
immutable(address) |
No | Address of the payment ERC20 token contract |
collateral_token |
immutable(address) |
No | Address of the collateral ERC20 token contract |
oracle_addr |
immutable(address) |
No | Address of the Chainlink AggregatorV3 oracle contract for collateral valuation |
oracle_reverse |
immutable(bool) |
No | Flag indicating if the oracle returns 1/price |
kyc_validator_addr |
immutable(address) |
No | Address of the KYCValidator contract |
max_protocol_upfront_fee |
immutable(uint256) |
No | Maximum allowed upfront protocol fee (in BPS) |
max_protocol_settlement_fee |
immutable(uint256) |
No | Maximum allowed settlement protocol fee (in BPS) |
payment_token_decimals |
immutable(uint256) |
No | Decimal precision of the payment token |
collateral_token_decimals |
immutable(uint256) |
No | Decimal precision of the collateral token |
offer_sig_domain_separator |
immutable(bytes32) |
No | EIP-712 domain separator for offer signatures |
refinance_addr |
public(immutable(address)) |
No | Address of the P2PLendingRefinance facet contract |
liquidation_addr |
public(immutable(address)) |
No | Address of the P2PLendingLiquidation facet contract |
| Variable | Type | Mutable | Description |
|---|---|---|---|
owner |
public(address) |
Yes | Address of the contract owner |
proposed_owner |
public(address) |
Yes | Address of the proposed new owner |
transfer_agent |
public(address) |
Yes | Address of the transfer agent |
loans |
public(HashMap[bytes32, bytes32]) |
Yes | Mapping of loan IDs to loan state hashes |
protocol_wallet |
public(address) |
Yes | Address where protocol fees are accrued |
protocol_upfront_fee |
public(uint256) |
Yes | Current upfront fee for the protocol (in BPS) |
partial_liquidation_fee |
public(uint256) |
Yes | Fee charged during partial liquidation (in BPS of collateral claimed) |
full_liquidation_fee |
public(uint256) |
Yes | Fee charged during full liquidation (in BPS of collateral claimed) |
protocol_settlement_fee |
public(uint256) |
Yes | Current settlement fee for the protocol (in BPS of interest) |
commited_liquidity |
public(HashMap[bytes32, uint256]) |
Yes | Mapping of offer tracing_id to committed principal |
revoked_offers |
public(HashMap[bytes32, bool]) |
Yes | Mapping of offer IDs to their revocation status |
authorized_proxies |
public(HashMap[address, bool]) |
Yes | Mapping of authorized proxy addresses |
pending_transfers |
public(HashMap[address, uint256]) |
Yes | Funds that failed ERC20 transfer and can be claimed later |
pending_collateral |
public(HashMap[address, uint256]) |
Yes | Collateral that failed ERC20 transfer and can be claimed later |
To reduce gas costs, certain state information, particularly for Loan and Offer structures, is externalized rather than stored fully on-chain:
- For Loans: The
loansmapping storeskeccak256hashes ofbase.Loanstructs instead of the full data. When interacting with a loan (e.g., insettle_loan), the fullbase.Loanstruct is passed as an argument, and its hash is validated against the stored hash. State changes are reflected by updating the stored hash and emitting events. - For Offers: Full
base.Offerdata is not stored on-chain. Instead,commited_liquiditytracks usage pertracing_id, andrevoked_offerstracks revocation status peroffer_id. When creating a loan, the fullbase.SignedOfferdata is passed, and its validity is checked using these mappings and its signature.
| Struct | Variable | Type | Description |
|---|---|---|---|
WalletValidation |
wallet |
address |
Wallet address being validated |
expiration_time |
uint256 |
Timestamp until which the validation is valid | |
Signature |
v |
uint256 |
EIP-712 signature component |
r |
uint256 |
EIP-712 signature component | |
s |
uint256 |
EIP-712 signature component | |
SignedWalletValidation |
validation |
WalletValidation |
Signed attestation of a wallet's KYC status |
signature |
Signature |
Signature of the validation | |
Offer |
principal |
uint256 |
Loan principal amount (0 for borrower-defined) |
apr |
uint256 |
Annual Percentage Rate (in BPS) | |
payment_token |
address |
Address of the payment token | |
collateral_token |
address |
Address of the collateral token | |
duration |
uint256 |
Loan duration in seconds | |
origination_fee_bps |
uint256 |
Origination fee percentage (in BPS) for the lender | |
min_collateral_amount |
uint256 |
Minimum collateral amount required | |
max_iltv |
uint256 |
Maximum initial LTV allowed (in BPS) | |
available_liquidity |
uint256 |
Total principal amount available for this offer | |
call_eligibility |
uint256 |
Time in seconds after loan start when the loan becomes callable (0 if not callable) | |
call_window |
uint256 |
Time in seconds after a loan is called for repayment before default | |
liquidation_ltv |
uint256 |
LTV threshold (in BPS) for partial liquidation | |
oracle_addr |
address |
Address of the oracle for collateral valuation | |
expiration |
uint256 |
Offer expiration timestamp | |
lender |
address |
Lender's address | |
borrower |
address |
Specific borrower for the offer (0x0 for general offers) | |
tracing_id |
bytes32 |
Unique identifier for tracking offer usage | |
SignedOffer |
offer |
Offer |
The offer details |
signature |
Signature |
EIP-712 signature of the offer | |
Loan |
id |
bytes32 |
Unique identifier of the loan |
offer_id |
bytes32 |
ID of the offer that created the loan | |
offer_tracing_id |
bytes32 |
Tracing ID from the offer | |
initial_amount |
uint256 |
Initial principal amount of the loan | |
amount |
uint256 |
Current outstanding principal amount | |
apr |
uint256 |
Annual Percentage Rate (in BPS) | |
payment_token |
address |
Address of the payment token | |
maturity |
uint256 |
Maturity timestamp of the loan | |
start_time |
uint256 |
Start timestamp of the loan | |
accrual_start_time |
uint256 |
Timestamp from which interest accrual is calculated (reset after partial liquidation) | |
borrower |
address |
Borrower's address | |
lender |
address |
Lender's address | |
collateral_token |
address |
Address of the collateral token | |
collateral_amount |
uint256 |
Current amount of collateral tokens held by the contract | |
min_collateral_amount |
uint256 |
Minimum collateral amount required | |
origination_fee_amount |
uint256 |
Total origination fee for the lender | |
protocol_upfront_fee_amount |
uint256 |
Upfront protocol fee amount | |
protocol_settlement_fee |
uint256 |
Protocol settlement fee percentage (in BPS) | |
partial_liquidation_fee |
uint256 |
Partial liquidation fee percentage (in BPS) | |
full_liquidation_fee |
uint256 |
Full liquidation fee percentage (in BPS) | |
call_eligibility |
uint256 |
Time in seconds after loan start when the loan becomes callable | |
call_window |
uint256 |
Time in seconds after a loan is called for repayment before default | |
liquidation_ltv |
uint256 |
LTV threshold (in BPS) for partial liquidation | |
oracle_addr |
address |
Address of the oracle for collateral valuation | |
initial_ltv |
uint256 |
Initial LTV of the loan (in BPS). This is the target LTV for partial liquidations. | |
call_time |
uint256 |
Timestamp when the loan was called (0 if not called) | |
vault_id |
uint256 |
(Securitize only) Vault identifier for this loan | |
redeem_start |
uint256 |
(Securitize only) Timestamp when redemption started (0 if not redeemed) | |
redeem_residual_collateral |
uint256 |
(Securitize only) Collateral amount kept in vault during redemption | |
UInt256Rational |
numerator |
uint256 |
Numerator of a rational number |
denominator |
uint256 |
Denominator of a rational number | |
PartialLiquidationResult |
collateral_claimed |
uint256 |
Amount of collateral claimed in a partial liquidation simulation |
liquidation_fee |
uint256 |
Liquidation fee in a partial liquidation simulation | |
debt_written_off |
uint256 |
Amount of debt written off in a partial liquidation simulation | |
updated_ltv |
uint256 |
Calculated LTV after a partial liquidation simulation |
| Function | Roles Allowed | Modifier | Description |
|---|---|---|---|
create_loan |
Any (caller is borrower) | Nonpayable | Creates a new loan based on a signed offer and ERC20 collateral |
settle_loan |
Borrower | Payable | Settles an existing loan |
partially_liquidate_loan |
Any (Liquidator) | Nonpayable | Performs a partial liquidation on a loan if LTV conditions are met |
liquidate_loan |
Any (Liquidator) | Nonpayable | Performs a full liquidation on a defaulted loan |
call_loan |
Lender | Nonpayable | Calls an eligible loan, starting the repayment window |
add_collateral_to_loan |
Borrower | Nonpayable | Adds ERC20 collateral to an existing loan |
remove_collateral_from_loan |
Borrower | Nonpayable | Removes ERC20 collateral from an existing loan, subject to LTV checks |
revoke_offer |
Lender | Nonpayable | Revokes a signed offer |
claim_pending_transfers |
Any (receiver) | Nonpayable | Claims any ERC20 tokens that failed a direct transfer call (payment token) |
claim_pending_collateral |
Any (receiver) | Nonpayable | Claims any ERC20 tokens that failed a direct transfer call (collateral token) |
replace_loan |
Borrower | Payable | Replaces an existing loan with a new one (borrower-initiated refinance) |
replace_loan_lender |
Lender | Payable | Replaces a loan by the lender (lender-initiated refinance) |
set_partial_liquidation_fee |
Owner | Nonpayable | Sets the partial liquidation fee |
set_full_liquidation_fee |
Owner | Nonpayable | Sets the full liquidation fee |
set_protocol_fee |
Owner | Nonpayable | Sets the protocol upfront and settlement fees |
change_protocol_wallet |
Owner | Nonpayable | Changes the protocol wallet address |
set_proxy_authorization |
Owner | Nonpayable | Sets authorization for a proxy address |
propose_owner |
Owner | Nonpayable | Proposes a new owner for the contract |
claim_ownership |
Proposed Owner | Nonpayable | Claims ownership of the contract |
set_transfer_agent |
Owner/Transfer Agent | Nonpayable | Sets the transfer agent address |
current_ltv |
Any | View | Gets the current LTV of a loan |
is_loan_defaulted |
Any | View | Checks if a loan is defaulted |
simulate_partial_liquidation |
Any | View | Simulates the outcome of a partial liquidation |
The P2PLendingVaultedErc20 contract serves as the main entry point for the vaulted protocol. It extends the core logic of the non-vaulted version by integrating a dedicated vault system for collateral management. It inherits common state and functions from P2PLendingVaultedBase and includes a new _vault_impl_addr parameter in its constructor. Collateral interactions are now handled through calls to the borrower's P2PLendingVault.
(Adds vault_impl_addr, otherwise similar to non-vaulted P2PLendingErc20.vy)
| Variable | Type | Mutable | Description |
|---|---|---|---|
payment_token |
immutable(address) |
No | Address of the payment ERC20 token contract |
collateral_token |
immutable(address) |
No | Address of the collateral ERC20 token contract |
oracle_addr |
immutable(address) |
No | Address of the Chainlink AggregatorV3 oracle contract for collateral valuation |
oracle_reverse |
immutable(bool) |
No | Flag indicating if the oracle returns 1/price |
kyc_validator_addr |
immutable(address) |
No | Address of the KYCValidator contract |
max_protocol_upfront_fee |
immutable(uint256) |
No | Maximum allowed upfront protocol fee (in BPS) |
max_protocol_settlement_fee |
immutable(uint256) |
No | Maximum allowed settlement protocol fee (in BPS) |
payment_token_decimals |
immutable(uint256) |
No | Decimal precision of the payment token |
collateral_token_decimals |
immutable(uint256) |
No | Decimal precision of the collateral token |
offer_sig_domain_separator |
immutable(bytes32) |
No | EIP-712 domain separator for offer signatures |
refinance_addr |
public(immutable(address)) |
No | Address of the P2PLendingVaultedRefinance facet contract |
liquidation_addr |
public(immutable(address)) |
No | Address of the P2PLendingVaultedLiquidation facet contract |
vault_impl_addr |
public(immutable(address)) |
No | Address of the P2PLendingVault implementation contract |
(Similar to non-vaulted P2PLendingBase, but its internal collateral functions (_send_collateral, _receive_collateral) now interact with vaults. Adds vault_registrar state variable for automatic vault registration with the Securitize Vault Registrar during vault creation.)
(Similar to non-vaulted P2PLendingErc20.vy, with additional transfer_loan and wallet_to_vault functions, and all collateral interactions updated to use vaults.)
| Function | Roles Allowed | Modifier | Description |
|---|---|---|---|
create_loan |
Any (caller is borrower) | Nonpayable | Creates a new loan (collateral deposited to borrower's vault) |
settle_loan |
Borrower | Payable | Settles an existing loan (collateral withdrawn from vault) |
partially_liquidate_loan |
Any (Liquidator) | Nonpayable | Performs a partial liquidation on a loan (collateral claimed from vault) |
liquidate_loan |
Any (Liquidator) | Nonpayable | Performs a full liquidation on a defaulted loan (collateral claimed from vault) |
call_loan |
Lender | Nonpayable | Calls an eligible loan, starting the repayment window |
add_collateral_to_loan |
Borrower | Nonpayable | Adds ERC20 collateral to an existing loan (deposited to vault) |
remove_collateral_from_loan |
Borrower | Nonpayable | Removes ERC20 collateral from an existing loan (withdrawn from vault) |
revoke_offer |
Lender | Nonpayable | Revokes a signed offer |
claim_pending_transfers |
Any (receiver) | Nonpayable | Claims any ERC20 tokens that failed a direct transfer call (payment token) |
replace_loan |
Borrower | Payable | Replaces an existing loan with a new one (borrower-initiated refinance) |
replace_loan_lender |
Lender | Payable | Replaces a loan by the lender (lender-initiated refinance) |
transfer_loan |
Transfer Agent | Nonpayable | Transfers a loan's ownership and its collateral to a new borrower (vaulted only) |
set_partial_liquidation_fee |
Owner | Nonpayable | Sets the partial liquidation fee |
set_full_liquidation_fee |
Owner | Nonpayable | Sets the full liquidation fee |
set_protocol_fee |
Owner | Nonpayable | Sets the protocol upfront and settlement fees |
change_protocol_wallet |
Owner | Nonpayable | Changes the protocol wallet address |
set_proxy_authorization |
Owner | Nonpayable | Sets authorization for a proxy address |
propose_owner |
Owner | Nonpayable | Proposes a new owner for the contract |
claim_ownership |
Proposed Owner | Nonpayable | Claims ownership of the contract |
set_transfer_agent |
Owner/Transfer Agent | Nonpayable | Sets the transfer agent address |
change_vault_registrar |
Owner | Nonpayable | Changes the vault registrar connector address |
current_ltv |
Any | View | Gets the current LTV of a loan |
is_loan_defaulted |
Any | View | Checks if a loan is defaulted |
simulate_partial_liquidation |
Any | View | Simulates the outcome of a partial liquidation |
wallet_to_vault |
Any | View | Gets the deterministic vault address for a given wallet |
The P2PLendingSecuritizeErc20 contract extends the vaulted architecture for Securitize DS Token collateral, adding redemption workflow and maturity extension support.
| Variable | Type | Mutable | Description |
|---|---|---|---|
payment_token |
immutable(address) |
No | Address of the payment ERC20 token contract |
collateral_token |
immutable(address) |
No | Address of the Securitize DS Token contract |
oracle_addr |
immutable(address) |
No | Address of the Chainlink AggregatorV3 oracle contract |
oracle_reverse |
immutable(bool) |
No | Flag indicating if the oracle returns 1/price |
kyc_validator_addr |
immutable(address) |
No | Address of the KYCValidator contract |
refinance_addr |
public(immutable(address)) |
No | Address of the P2PLendingSecuritizeRefinance facet |
liquidation_addr |
public(immutable(address)) |
No | Address of the P2PLendingSecuritizeLiquidation facet |
vault_impl_addr |
public(immutable(address)) |
No | Address of the P2PLendingVaultSecuritize implementation |
| Variable | Type | Mutable | Description |
|---|---|---|---|
vault_count |
public(HashMap[address, uint256]) |
Yes | Number of vaults created per borrower |
securitize_redemption_wallet |
public(address) |
Yes | Wallet address for Securitize redemptions |
vault_registrar |
public(address) |
Yes | Address of the vault registrar connector |
| Function | Roles Allowed | Modifier | Description |
|---|---|---|---|
create_loan |
Any (caller is borrower) | Nonpayable | Creates a new loan with a new vault for the collateral |
settle_loan |
Borrower | Nonpayable | Settles a loan (handles redeemed collateral if applicable) |
partially_liquidate_loan |
Any (Liquidator) | Nonpayable | Performs a partial liquidation (not allowed on redeemed loans) |
liquidate_loan |
Any (Liquidator) | Nonpayable | Performs a full liquidation (handles redeemed collateral) |
add_collateral_to_loan |
Borrower | Nonpayable | Adds collateral (not allowed after redemption starts) |
remove_collateral_from_loan |
Borrower | Nonpayable | Removes collateral (not allowed after redemption starts) |
redeem |
Borrower | Nonpayable | Initiates redemption of collateral via Securitize |
extend_loan |
Borrower | Nonpayable | Extends loan maturity with lender's signed offer |
extend_loan_lender |
Lender | Nonpayable | Extends loan maturity (lender-initiated) |
replace_loan |
Borrower | Nonpayable | Replaces a loan (not allowed on redeemed loans) |
replace_loan_lender |
Lender | Nonpayable | Replaces a loan as lender (not allowed on redeemed loans) |
set_securitize_redemption_wallet |
Owner | Nonpayable | Sets the redemption wallet address |
change_vault_registrar |
Owner | Nonpayable | Changes the vault registrar connector address |
wallet_to_vault |
Any | View | Gets the latest vault address for a wallet |
vault_id_to_vault |
Any | View | Gets a specific vault address by ID |
is_loan_redeemed |
Any | View | Checks if a loan has started redemption |
P2PLendingBase.vy provides common state and logic for the non-vaulted version. P2PLendingVaultedBase.vy extends this by modifying the internal collateral handling functions (_send_collateral, _receive_collateral) to interact with the new P2PLendingVault system. It also introduces functions like _get_vault, _create_vault_if_needed, and _wallet_to_vault to manage the lifecycle and address computation for borrower-specific vaults.
These contracts act as facets for refinancing logic. P2PLendingRefinance.vy (non-vaulted) operates on the main P2PLendingErc20's storage, handling collateral directly. P2PLendingVaultedRefinance.vy (vaulted) takes vault_impl_addr as an additional parameter in its delegatecall context and internally uses the P2PLendingVaultedBase's vault-aware collateral functions.
These contracts act as facets for liquidation logic. P2PLendingLiquidation.vy (non-vaulted) operates on the main P2PLendingErc20's storage, handling collateral directly. P2PLendingVaultedLiquidation.vy (vaulted) takes vault_impl_addr as an additional parameter in its delegatecall context and internally uses the P2PLendingVaultedBase's vault-aware collateral functions.
This contract implements the logic for individual collateral vaults in the vaulted version. It is designed to be deployed as a minimal proxy via CREATE2 for each borrower, ensuring isolated collateral management.
| Variable | Type | Mutable | Description |
|---|---|---|---|
owner |
public(address) |
Yes | The wallet address of the borrower owning this vault |
caller |
public(address) |
Yes | The address of the P2PLendingVaultedErc20 contract (or other authorized caller) |
token |
public(address) |
Yes | The address of the ERC20 collateral token this vault holds |
pending_transfers |
public(HashMap[address, uint256]) |
Yes | Map for pending withdrawals if a transfer failed (for withdraw_pending) |
pending_transfers_total |
public(uint256) |
Yes | Total pending withdrawals in the vault |
| Function | Roles Allowed | Modifier | Description |
|---|---|---|---|
initialise |
Deployer (once) | Nonpayable | Initializes the vault with an owner and token (called by P2PLendingVaultedErc20 upon creation) |
deposit |
Caller (e.g., P2PLendingVaultedErc20) |
Nonpayable | Deposits tokens into the vault (transfers from borrower) |
withdraw |
Caller (e.g., P2PLendingVaultedErc20) |
Nonpayable | Withdraws tokens from the vault (transfers to recipient) |
withdraw_pending |
Any (sender) | Nonpayable | Allows a recipient to claim tokens if a previous withdraw failed |
The KYCValidator contract is an external dependency that ensures regulatory compliance by verifying KYC attestations. It holds a trusted validator address that is authorized to sign WalletValidation messages.
| Variable | Type | Mutable | Description |
|---|---|---|---|
owner |
public(address) |
Yes | Address of the contract owner |
proposed_owner |
public(address) |
Yes | Address of the proposed new owner |
validator |
public(address) |
Yes | Address of the trusted KYC validator |
validation_sig_domain_separator |
immutable(bytes32) |
No | EIP-712 domain separator for validation signatures |
| Function | Roles Allowed | Modifier | Description |
|---|---|---|---|
check_validation |
Any | View | Checks if a single SignedWalletValidation is valid (signed by validator and not expired). |
check_validations_pair |
Any | View | Checks if two SignedWalletValidation structs are both valid. |
propose_owner |
Owner | Nonpayable | Proposes a new owner for the contract. |
claim_ownership |
Proposed Owner | Nonpayable | Claims ownership of the contract. |
set_validator |
Owner | Nonpayable | Sets the address of the trusted KYC validator. |
The P2PLendingErc20 (non-vaulted) and P2PLendingVaultedErc20 (vaulted) contracts include support for authorized proxies, allowing for more flexible interaction with the protocol. This feature is particularly useful for integrations with other protocols or for implementing advanced user interfaces.
Key aspects of proxy support include:
- Authorized Proxies: The contract maintains a mapping of
authorized_proxies: public(HashMap[address, bool]). The contract owner can set or revoke proxy authorization usingset_proxy_authorization. - User Checks: An internal function
_check_userverifies if themsg.senderis the expected user or anauthorized_proxies[msg.sender]acting on behalf oftx.origin. This is used for user-specific actions (e.g., settling loans, revoking offers). - Proxy Usage: When an authorized proxy calls a function,
tx.originis used to identify the actual user, allowing proxies to perform actions on behalf of users while maintaining access control. - Security Considerations: Only the contract owner can authorize or deauthorize proxies.
tx.originis only considered when themsg.senderis an authorized proxy; otherwise,msg.senderis used for authentication.
Both non-vaulted and vaulted versions of the protocol have comprehensive test suites. There are two types of tests implemented, running on py-evm using titanoboa:
- Unit tests focus on individual functions for each contract, mocking external dependencies (e.g., ERC20 tokens, oracle, KYC validator, and
P2PLendingVaultfor the vaulted version). - Integration tests run on a forked chain, testing the integration between the contracts in the protocol and real implementations of the external dependencies
For deployments in private and test networks, mock implementations of external dependencies are used. These mocks are NOT part of the core protocol but facilitate local development and testing. Key mock contracts include:
MockERC20.vy: For payment and collateral ERC20 tokens.MockAggregatorV3.vy: For the Chainlink Price Feed oracle.MockKYCValidator.vy: For the KYC validation contract.MockVault.vy: For theP2PLendingVaultin vaulted version testing.VaultRegistrarMock.vy: A mock implementation of the Securitize Vault Registrar interface. ProvidesregisterVaultandisRegisteredfunctions.
Run the following command to set everything up:
make install-dev
To run the tests (ensure make install-dev is run first):
- Unit tests:
- Important note: when running the unit tests for the first time, run the command twice! The first time might encounter errors due to uninitialized titanoboa cache.
make unit-tests
make coverage
make branch-coverage
make gas
In order to run the protocol locally:
- Install Foundry
- In a terminal, run
make install-dev - In another terminal, run
anvil - In the line 39 of
scripts/deployment.py, ensure the statement for deployment isdm.deploy(changes, dryrun=False). (Setdryrun=Trueto simulate deployment without actual transactions). - In the first terminal, run
make deploy-local. This will deploy the latest version of the protocol (vaulted by default). - To interact with the deployed contracts manually, run
make console-localand use the console.
After step 5, the config file configs/local/p2p.json will be populated with all deployed contract addresses and other relevant information. To redeploy, you can copy the contents of configs/local/p2p.json.template to configs/local/p2p.json and run make deploy-local again.
For each environment (e.g., DEV, INT, PROD), a Makefile rule is available to deploy the contracts. For example, for the DEV environment:
make deploy-dev
The protocol relies on external contracts (ERC20 tokens, oracle, KYC validator). Mock implementations are used in development environments.
| Component | DEV | INT | PROD |
|---|---|---|---|
| Network | Private network (Anvil) | Sepolia | Mainnet |
| Payment Token | MockERC20 | MockERC20 | USDC (0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) |
| Collateral Token | MockERC20 | MockERC20 | WETH (0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2) or other ERC20 |
| Oracle Contract | MockAggregatorV3 | Chainlink AggregatorV3 (e.g., ETH/USD) |
Chainlink AggregatorV3 (e.g., ETH/USD) |
| KYC Validator | MockKYCValidator | KYCValidator instance | KYCValidator instance |
The P2PLendingErc20 (non-vaulted) / P2PLendingVaultedErc20 (vaulted), their respective Base contracts, Refinance, and Liquidation facets are deployed in each environment. The main entry point P2PLendingErc20 or P2PLendingVaultedErc20 is deployed with the following parameters:
| Parameter | Type | Description |
|---|---|---|
_payment_token |
address |
Address of the payment ERC20 token contract. |
_collateral_token |
address |
Address of the collateral ERC20 token contract. |
_oracle_addr |
address |
Address of the oracle contract for collateral valuation. |
_oracle_reverse |
bool |
Whether the oracle returns the collateral price in reverse (i.e., 1 / price). |
_kyc_validator_addr |
address |
Address of the KYCValidator contract. |
_protocol_upfront_fee |
uint256 |
The percentage (bps) of the principal paid to the protocol at origination. |
_protocol_settlement_fee |
uint256 |
The percentage (bps) of the interest paid to the protocol at settlement. |
_protocol_wallet |
address |
Address where the protocol fees are accrued. |
_max_protocol_upfront_fee |
uint256 |
The maximum percentage (bps) that can be charged as protocol upfront fee. |
_max_protocol_settlement_fee |
uint256 |
The maximum percentage (bps) that can be charged as protocol settlement fee. |
_partial_liquidation_fee |
uint256 |
The percentage (bps) charged as a liquidation fee during partial liquidation. |
_full_liquidation_fee |
uint256 |
The percentage (bps) charged as a liquidation fee during full liquidation. |
_refinance_addr |
address |
The address of the P2PLendingRefinance / P2PLendingVaultedRefinance facet contract. |
_liquidation_addr |
address |
The address of the P2PLendingLiquidation / P2PLendingVaultedLiquidation facet contract. |
_vault_impl_addr |
address |
The address of the P2PLendingVault implementation contract (for vaulted only). |
_transfer_agent |
address |
The wallet address for the transfer agent role (for vaulted only). |
The protocol allows borrowers to actively manage their collateral positions:
- Add Collateral: Borrowers can add more collateral tokens to reduce their LTV ratio and improve loan health.
- Remove Collateral: Borrowers can remove excess collateral as long as the remaining collateral maintains the minimum required amount and doesn't exceed the initial LTV threshold.
Unlike traditional hard liquidations, the protocol implements a partial liquidation mechanism:
- Trigger: When LTV exceeds the
partial_liquidation_ltvthreshold. - Process: A portion of debt is written off and corresponding collateral is claimed.
- Goal: Bring the LTV back to the initial level, effectively "healing" the loan.
- Incentive: Liquidators receive a fee for performing this service.
Lenders have the ability to "call" loans under specific conditions:
- Eligibility: Only after the
call_eligibilityperiod has passed. - Window: Borrowers get a
call_windowperiod to repay before default. - Protection: Prevents lenders from arbitrarily calling loans early.
Both borrowers and lenders can initiate loan refinancing:
- Borrower-Initiated: Accept better offers from other lenders.
- Lender-Initiated: Sell loan positions or refinance terms.
- Borrower Protection: Ensures new terms are not worse than original terms.
All participants must pass KYC validation:
- Signed Attestations: Off-chain KYC validation with on-chain verification.
- Expiration: Validations have time limits for security.
- Compliance: Ensures regulatory requirements are met.
- Introduces isolated vaults for each borrower's collateral, enhancing security and meeting compliance requirements by separating collateral from the main lending contract.
- Vaults are deployed as minimal proxies via CREATE2, ensuring gas efficiency and deterministic addresses.
- Multiple Vaults per Borrower: Each loan creates a new vault, enabling per-loan collateral isolation.
- Redemption Workflow: Borrowers can redeem DS Token collateral back to payment tokens via Securitize.
- Maturity Extensions: Loans can be extended without full refinancing, either by borrower (with lender's signed offer) or by lender directly.
- SecuritizeSwap Integration: Vaults can purchase DS tokens from stablecoins directly.
- Uses Chainlink AggregatorV3 for reliable price feeds.
- Supports reverse oracle pricing for different token pairs.
- Regular price updates ensure accurate LTV calculations.
- Owner-controlled protocol parameter updates.
- Proxy authorization for integration flexibility.
- Role-based access for loan operations.
- The Transfer Agent role (vaulted only) provides a mechanism for compliance-driven loan transfers.
- Maximum fee limits prevent excessive protocol fees.
- LTV thresholds protect against under-collateralization.
- Partial liquidation prevents complete loan defaults.
- Collateral is held in individual vaults, reducing single points of failure and simplifying compliance audits.
- Vaults are minimal proxies, reducing attack surface and deployment costs.
- Strict access control ensures only the main lending contract can manage collateral in a vault.
- Obtain KYC validation from approved validator.
- Browse available loan offers.
- Accept suitable offer with collateral transfer.
- For the non-vaulted version, transfer collateral directly to the main lending contract.
- For the vaulted version, deposit collateral into your dedicated vault.
- Manage collateral position during loan term.
- For the non-vaulted version, interact directly with the main lending contract.
- For the vaulted version, all collateral operations go through your vault via the main lending contract.
- Repay loan or refinance as needed.
- Obtain KYC validation from approved validator.
- Create signed loan offers with desired terms.
- Monitor loan positions and LTV ratios.
- Call loans or initiate refinancing when appropriate.
- Claim collateral in case of defaults.
The protocol supports integration through:
- Direct contract interactions with
P2PLendingErc20(non-vaulted) orP2PLendingVaultedErc20(vaulted). - Authorized proxy contracts.
- Event monitoring for loan state changes.
- View functions for loan analytics.
- For the vaulted version, direct interaction with individual
P2PLendingVaultcontracts is generally not required for borrowers, as the main lending contract manages vault operations. However, developers integrating deeply might need to understand the vault creation and management patterns.
- Partial Liquidation: Protects against market volatility without full liquidation.
- Callable Loans: Provides flexibility for lenders while protecting borrowers.
- Dynamic Collateral Management: Allows borrowers to adjust collateral levels.
- KYC Integration: Ensures regulatory compliance through signed validations.
- Gas Optimization: Externalized state design reduces transaction costs.
- Refinancing Support: Both borrowers and lenders can replace existing loans.
- Vault Collateral System (vaulted): Individualized, minimal proxy vaults for each borrower's collateral, improving security, compliance, and asset segregation.
- Securitize Integration (securitize): Native support for security tokens with redemption workflow, maturity extensions, and SecuritizeSwap integration for DS token purchases.
The protocol represents a significant advancement in DeFi lending by providing sophisticated risk management tools while maintaining user-friendly operations.
Potential future developments include:
- Support for multiple collateral types.
- Cross-chain functionality.
- Advanced risk management features.
- Governance mechanisms for protocol upgrades.
- Insurance integration options.
For technical support, integration questions, or security concerns, please refer to the official documentation or contact the development team through official channels.
Note: This protocol is under active development. Always verify contract addresses and conduct thorough testing before production use.