This repository uses the recommended structure for a Soroban project:
.
├── contracts
│ └── hello_world
│ ├── src
│ │ ├── lib.rs
│ │ └── test.rs
│ └── Cargo.toml
├── Cargo.toml
└── README.md
- New Soroban contracts can be put in
contracts, each in their own directory. There is already ahello_worldcontract in there to get you started. - If you initialized this project with any other example contracts via
--with-example, those contracts will be in thecontractsdirectory as well. - Contracts should have their own
Cargo.tomlfiles that rely on the top-levelCargo.tomlworkspace for their dependencies. - Frontend libraries can be added to the top-level directory as well. If you initialized this project with a frontend template via
--frontend-templateyou will have those files already included.
- Network: Stellar Testnet
- Contract ID: CAEGD57WVTVQSYWYB23AISBW334QO7WNA5XQ56S45GH6BP3D2AVHKUG4
LeaseFlow manages the full lifecycle of a property lease on-chain — from creation through rent streaming to deposit settlement and lease closure. The landlord initialises the lease with terms and a deposit requirement; the tenant activates it by funding the deposit; rent streams continuously to the landlord while the lease is active; and when the lease ends the protocol calculates the final settlement, releases the remaining deposit to the tenant, and closes the record. This design keeps funds non-custodial and settlement logic transparent at every step.
sequenceDiagram
autonumber
participant L as Landlord
participant T as Tenant
participant P as LeaseFlow Protocol
participant S as Payment Stream
L->>P: create_lease(landlord, tenant, rent_amount, deposit_amount, start_date, end_date, property_uri)
P-->>L: LeaseCreated (status: Pending, lease record stored)
T->>P: activate_lease(lease_id) + deposit funds
P-->>T: LeaseActivated (status: Active, deposit_amount locked)
P->>S: start_stream(lease_id, rent_amount, recipient: landlord)
S-->>P: StreamActive (streaming rent each ledger tick)
loop Every ledger / payment interval
S->>L: stream rent payment (rent_amount per period)
end
T->>P: stop_stream(lease_id)
P->>S: halt_stream(lease_id)
S-->>P: StreamSettled (final_amount calculated)
P-->>T: release_deposit(lease_id) - remaining deposit refunded
T->>P: return_asset(lease_id)
P-->>L: AssetReturned (landlord notified)
P-->>T: LeaseClosed (status: Closed, lease record finalised)
-
Landlord calls
create_lease— Passes tenant address,rent_amount,deposit_amount,start_date,end_date, and aproperty_uri(e.g. an IPFS link to the property listing). The protocol stores the lease record with statusPending. -
Protocol emits
LeaseCreated— The lease is registered in contract instance storage under theleasekey. No funds move yet. -
Tenant calls
activate_leaseand funds the deposit — The tenant transfersdeposit_amountto the contract. The protocol validates the amount and flips the lease status toActive. -
Protocol emits
LeaseActivated— Deposit is locked in the contract. The lease is now live and the payment stream can begin. -
Protocol starts the payment stream — Internally triggers
start_streamwith the agreedrent_amountrate and the landlord as recipient. The stream is tied to the lease record. -
Stream flows each ledger tick — Rent is continuously streamed from the locked funds to the landlord's address at the agreed rate for the duration of the lease.
-
Tenant signals end of lease via
stop_stream— Tenant calls the protocol to halt the stream, indicating they are ready to return the asset and close out. -
Protocol halts the stream and settles — Calls
halt_streaminternally; the stream calculates thefinal_amountpaid and reports back. Any unstreamed rent is reconciled. -
Protocol releases remaining deposit to tenant — After settlement,
release_depositreturns the unused portion ofdeposit_amountto the tenant's address. -
Tenant calls
return_asset— Signals that the physical or digital asset has been returned to the landlord. The protocol records this on-chain. -
Protocol closes the lease — Emits
AssetReturnedto the landlord andLeaseClosedto the tenant. The lease status is set toClosedand the record is finalised in storage.
-
Tenant never returns the asset — If
return_assetis not called before theend_date+ grace period, the protocol can slash all or part of thedeposit_amountas a penalty and mark the leaseDefaulted. Seetest_deposit_release_disputedfor the disputed-return flow. -
Stream runs out of funds before lease ends — If the tenant's deposited funds are exhausted before
end_date, the stream halts automatically. The protocol records the shortfall; the landlord can claim the remaining deposit as partial compensation. Seetest_deposit_release_partial_refundfor this scenario. -
Landlord disputes the return condition — If the landlord rejects the asset return (e.g. damage claim), the lease enters a
Disputedstate. The deposit is held in escrow until the dispute is resolved — either by on-chain arbitration logic or a manual settlement call. Seetest_deposit_release_disputedfor the disputed-return snapshot.
Successfully implemented the buyout option feature for the LeaseFlow protocol contracts as specified in the GitHub issue.
- Added
buyout_price: Option<i128>field to bothLeaseandLeaseInstancestructs - Allows setting an optional price at which the tenant can buy out the asset
- Added
cumulative_payments: i128field to both lease structs - Updated
pay_rentfunction to track cumulative payments - Added
pay_lease_instance_rentfunction for LeaseInstance payments
- Implemented automatic ownership transfer when
cumulative_payments >= buyout_price - Transfers NFT ownership (if present) from landlord to tenant
- Sets lease status to
Terminatedand archives the lease - Works for both simple leases and LeaseInstance contracts
set_buyout_price(env, lease_id, landlord, buyout_price)- Sets buyout price (landlord only)- Updated
pay_rent()- Now tracks cumulative payments and handles buyout
set_lease_instance_buyout_price(env, lease_id, landlord, buyout_price)- Sets buyout pricepay_lease_instance_rent(env, lease_id, payment_amount)- Processes payments with buyout logic
- Authorization: Only landlords can set buyout prices
- Validation: Buyout prices must be positive
- Automatic Transfer: When cumulative payments reach buyout price, ownership transfers automatically
- NFT Support: If lease has associated NFT, it's transferred to tenant upon buyout
- Archiving: Leases are archived to historical storage after buyout
- Backward Compatibility: All existing functionality preserved
Added comprehensive tests covering:
- Setting buyout prices
- Authorization checks
- Buyout execution for simple leases
- Buyout execution for LeaseInstance contracts
- Cases where buyout price is not reached
- Lease archiving after buyout
// Create lease
client.create_lease(&landlord, &tenant, &1000i128);
// Set buyout price (landlord only)
client.set_buyout_price(&lease_id, &landlord, &5000i128);
// Make payments
client.pay_rent(&lease_id, &2000i128);
client.pay_rent(&lease_id, &3000i128); // This triggers buyout
// Lease is now terminated, ownership transferred to tenant- Buyout can only be set by landlord
- Automatic transfer prevents manual intervention errors
- Leases are properly archived after buyout
- All existing security checks maintained
contracts/leaseflow_contracts/src/lib.rs- Main implementationcontracts/leaseflow_contracts/src/test.rs- Test coverage
The implementation fully satisfies the requirements and provides a robust buyout mechanism for the LeaseFlow protocol.
This document describes the events that the LeaseFlow Protocol Contracts emit to notify the frontend when assets become available or change state.
Emitted when a lease is activated and the asset becomes available to the renter.
Event Structure:
pub struct LeaseStarted {
pub id: u64, // Timestamp-based unique ID
pub renter: Address, // Address of the renter/tenant
pub rate: i128, // Per-second rent rate
}When emitted: When activate_lease is called successfully.
Frontend use case: Show that an asset is now available for use by the renter.
Emitted when a lease terminates, providing payment summary information.
Event Structure:
pub struct LeaseEnded {
pub id: u64, // Lease ID
pub duration: u64, // Total lease duration in seconds
pub total_paid: i128, // Total amount paid during lease
}When emitted: When terminate_lease is called successfully.
Frontend use case: Update UI to show lease completion and payment summary.
Emitted when an asset is reclaimed by the landlord or system.
Event Structure:
pub struct AssetReclaimed {
pub id: u64, // Lease ID
pub reason: String, // Reason for reclamation
}When emitted: When reclaim_asset is called successfully.
Frontend use case: Notify that an asset is no longer available and show the reason.
Frontend applications should listen to these events using the Stellar Soroban SDK event listeners:
// Example: Listen to LeaseStarted events
contract.events().on('LeaseStarted', (event) => {
const { id, renter, rate } = event.data;
// Update UI to show asset is available
updateAssetAvailability(id, renter, rate);
});
// Example: Listen to LeaseEnded events
contract.events().on('LeaseEnded', (event) => {
const { id, duration, total_paid } = event.data;
// Update UI to show lease completion
showLeaseSummary(id, duration, total_paid);
});
// Example: Listen to AssetReclaimed events
contract.events().on('AssetReclaimed', (event) => {
const { id, reason } = event.data;
// Update UI to show asset is no longer available
markAssetUnavailable(id, reason);
});Events can be filtered by specific lease IDs or addresses:
// Listen to events for a specific lease
contract.events()
.filter({ lease_id: specificLeaseId })
.on('LeaseStarted', handleLeaseStarted);
// Listen to events for a specific renter
contract.events()
.filter({ renter: userAddress })
.on('LeaseStarted', handleUserLeaseStarted);The contract includes comprehensive tests for all events. Run tests with:
make test
# or
stellar contract testThese events are additive and do not affect existing contract functionality. Existing integrations will continue to work unchanged.
The new reclaim_asset function provides a dedicated way to emit asset reclamation events, which can be called by landlords, tenants, or system administrators.