Voltar para Documentação

Docs Técnicas

Wallet Loan

This is the canonical loan example used by the current test fixtures.

O conteúdo abaixo vem das fontes técnicas do repositório e é prerenderizado no site para leitura direta por pessoas, crawlers e agentes.

This is the canonical loan example used by the current test fixtures.

Commented source is available at wallet-loan-commented.trea.

python
contract WalletLoan:
    storage:
        active: bool = False
        lender: Address
        borrower: Address
        requested_principal: u128 = 0
        requested_rate_ppm: u64 = 0
        rate_ppm: u64 = 0
        principal_due: u128 = 0
        last_accrual_ts: u64 = 0

    @view
    def is_active() -> bool:
        return self.active

    @tx
    def request_loan(lender: Address, principal: u128, rate_ppm: u64):
        require(self.active == False, "loan_already_active")
        self.lender = lender
        self.borrower = ctx.caller
        self.requested_principal = principal
        self.requested_rate_ppm = rate_ppm
        emit LoanRequested(lender, ctx.caller, principal, rate_ppm)

    @tx
    def approve_loan():
        require(ctx.caller == self.lender, "only_lender_can_approve")
        require(self.active == False, "loan_already_active")
        require(self.requested_principal != 0, "no_pending_request")
        self.rate_ppm = self.requested_rate_ppm
        self.principal_due = self.requested_principal
        self.last_accrual_ts = ctx.block_timestamp
        self.active = True
        post(loan.origination_lines(ctx.asset, self.lender, self.borrower, self.requested_principal))
        emit LoanApproved(self.lender, self.borrower, self.requested_principal, self.rate_ppm)

    @tx
    def accrue():
        require(self.active == True, "loan_not_active")
        let interest = loan.accrue_interest(self.principal_due, self.rate_ppm, self.last_accrual_ts, ctx.block_timestamp)
        post(loan.accrual_lines(ctx.asset, self.lender, self.borrower, interest))
        self.principal_due = self.principal_due + interest
        self.last_accrual_ts = ctx.block_timestamp
        emit InterestAccrued(self.principal_due, ctx.block_timestamp)

    @tx
    def repay(amount: u128):
        require(ctx.caller == self.borrower, "only_borrower_can_repay")
        require(self.active == True, "loan_not_active")
        require(self.principal_due >= amount, "amount_exceeds_principal_due")
        self.principal_due = self.principal_due + loan.accrue_interest(self.principal_due, self.rate_ppm, self.last_accrual_ts, ctx.block_timestamp)
        post(loan.repayment_lines(ctx.asset, self.lender, self.borrower, amount))
        self.principal_due = self.principal_due - amount
        self.active = self.principal_due != 0
        self.last_accrual_ts = ctx.block_timestamp
        emit LoanRepaid(self.borrower, self.lender, amount)

Execution Order

  • guards happen before state transitions and postings;
  • loan origination uses the canonical helper;
  • accrual is explicit and timestamp-based;
  • repayment posts accounting meaning before reducing business obligation;
  • active closes when principal reaches zero.

State Transition Rules

  • request_loan records the pending request without creating an obligation;
  • approve_loan activates the contract and initializes principal state;
  • accrue updates business state after emitting the accrual posting plan;
  • repay accrues up to the current timestamp before applying repayment lines.

Ledger Effect Semantics

Loan origination is not a simple transfer. The borrower receives spendable capacity and also carries an obligation.

The canonical origination helper produces the spendable and obligation lines together, preventing contracts from forgetting the liability side.

Invariants Preserved

  • lender receivable and borrower payable stay aligned;
  • principal due matches the business-side obligation after each successful call;
  • active only remains true while principal due is non-zero.

Commented Entry Point Pattern

Use structured comments when a contract needs more review guidance than the raw code can carry:

python
@tx
def approve_loan():
    # Purpose:
    # Activate a pending loan request for the current lender.

    # Guards:
    require(ctx.caller == self.lender, "only_lender_can_approve")
    require(self.active == False, "loan_already_active")
    require(self.requested_principal != 0, "no_pending_request")

    # State:
    self.rate_ppm = self.requested_rate_ppm
    self.principal_due = self.requested_principal
    self.last_accrual_ts = ctx.block_timestamp
    self.active = True

    # Ledger effect:
    post(loan.origination_lines(ctx.asset, self.lender, self.borrower, self.requested_principal))

    # Event:
    emit LoanApproved(self.lender, self.borrower, self.requested_principal, self.rate_ppm)