Skip to main content

State diagram

Each rental can only be in one terminal state — Released or Refunded are final, the contract reverts on double-spend attempts.

Step 1: createRental

Called by the renter from their wallet during checkout.
  • Pulls amount + fee USDC from msg.sender (the renter)
  • Stores the rental record with status Held
  • Emits RentalCreated(rentalId, renter, owner, amount, fee, endTime)
Reverts if:
  • rentalId already exists (no duplicate creation)
  • owner == address(0) or owner == msg.sender (no self-rentals or burn-to-zero)
  • endTime <= block.timestamp (rental must be in the future)
  • USDC transferFrom fails (renter didn’t approve enough)
Renters pay gas (Base L2, fractions of a cent).

Step 2a: release (happy path)

Called by our backend operator at the rental’s ends_at.
  • Sets rental status to Released
  • Transfers amount USDC → owner’s wallet
  • Transfers fee USDC → treasury wallet
  • Emits RentalReleased(rentalId, owner, amount, fee)
Reverts if:
  • Rental doesn’t exist or isn’t Held
  • Caller isn’t authorized to release

Step 2b: refund (dispute path)

Called by the operator after a dispute is upheld.
  • Sets rental status to Refunded
  • Transfers amount + fee USDC → original renter’s wallet (full amount, fee included)
  • Emits RentalRefunded(rentalId, renter, amount, fee)
Reverts if:
  • Rental doesn’t exist or isn’t Held
  • Caller isn’t operator

Events

Subscribe to these via Basescan, an indexer, or your own RPC eth_getLogs. Useful for owner-side accounting or third-party analytics.

Reading state

The rentals(bytes32) mapping is public, so anyone can read a rental’s current state:
The Rentr backend uses this to re-verify rentals on-chain before persisting them in our database, as a defense-in-depth measure.