SaabFi
TVL$1.48Lent$0.00Available$10,000.00cbBTC Spot$75,447.88cbBTC TWAP$75,443.44
NetworkBaseTerm30 daysLimit$1.00 – $1,000.00LTV80%Fee @ $5004.00%

Borrow USDCagainst your bitcoin.No liquidations.

Uses cbBTC, Coinbase's 1:1 wrapped Bitcoin on Base.

Built for predictability.

Docs
Lifecycle

Bitcoin moves.
Your loan doesn't.

cbBTC can rise or fall sharply during your term, and your loan is not affected. No margin calls, no forced sales, no price liquidation.

where others liquidateothers miss the recovery
Day 115Day 30
On a price drop
0
liquidations, ever
Fixed term
30
days to repay
Borrow up to
80%
LTV, fixed at open
The only trigger
Time, not price.

Repay within 30 days and your cbBTC is returned. Past the term, an unpaid loan settles to the treasury.

Dashboard

Manage your loans.

app.saabfi/dashboard

Active Loans

4

Total loans

Deposits

$2,000.00

Total collateral

Borrowed

$1,250.00

Total amount

cbBTC Price

$75,443.44

Current TWAP price

Loan activitySample data
Loan IDDays remainingBalance

1042

Active

28

$500.00

1041

Active

22

$320.00

1040

Active
Due soon

6

$120.00

What you can do
  • Track every loan's balance and days remaining.
  • Pay partial or in full at any time.
  • See collateral and payment progress.
  • Browse repaid and historical loans.

The dashboard shows your real loans, payments, and collateral status. Connect your wallet to manage.

Open dashboard
Proof

Immutable by design.

The smart contract holding your cbBTC cannot be upgraded or replaced. The only way it releases is by the rules you agreed to: back to you on repayment, or to treasury after a 30-day default.

CollateralVault.sol
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.35;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";

import {ICollateralVault} from "../interfaces/ICollateralVault.sol";
import {ProtocolOwned} from "./base/ProtocolOwned.sol";

/**
 * @title CollateralVault
 * @author 0xjoma.base.eth
 * @notice Secure storage for cbBTC collateral used in loans
 * @dev Only the main protocol contract can access stored collateral
 * @custom:security-contact security@saabfi.com
 */
contract CollateralVault is ICollateralVault, ReentrancyGuardTransient, ProtocolOwned {
    using SafeERC20 for IERC20;

    /// @notice cbBTC token used as collateral
    IERC20 public immutable cbBtc;

    /// @notice Stores collateral information for each loan
    mapping(uint256 loanId => CollateralInfo info) public loanCollateral;

    /// @notice Tracks total cbBTC recorded for active loans
    uint256 private totalCollateralStored;

    /// @notice Sets up the collateral vault with required addresses
    /// @param _protocol Main protocol contract address
    /// @param _cbBtc cbBTC token contract address
    constructor(address _protocol, address _cbBtc) ProtocolOwned(_protocolOrRevert(_protocol)) {
        if (_cbBtc == address(0)) {
            revert CollateralVault__InvalidParams();
        }

        cbBtc = IERC20(_cbBtc);

        emit VaultDeployed(_protocol, address(cbBtc));
    }

    /// @inheritdoc ICollateralVault
    function recordCollateral(uint256 loanId, uint256 amount) external nonReentrant onlyProtocol {
        if (amount == 0) revert CollateralVault__InvalidParams();

        CollateralInfo storage collateralRef = loanCollateral[loanId];
        if (collateralRef.amount != 0) {
            revert CollateralVault__CollateralExists(loanId);
        }

        uint256 totalStoredBefore = totalCollateralStored;
        collateralRef.amount = SafeCast.toUint96(amount);
        totalCollateralStored = totalStoredBefore + amount;

        uint256 vaultBalance = cbBtc.balanceOf(address(this));
        if (vaultBalance < totalCollateralStored) {
            revert CollateralVault__InvariantViolation(vaultBalance, totalCollateralStored);
        }

        emit CollateralRecorded(loanId, amount);
    }

    /// @inheritdoc ICollateralVault
    function releaseCollateral(uint256 loanId, address to) external nonReentrant onlyProtocol {
        if (to == address(0)) revert CollateralVault__InvalidParams();

        uint256 amount = _releaseCollateral(loanId, to, false);

        emit CollateralReleased(loanId, to, amount);
    }

    /// @inheritdoc ICollateralVault
    function releaseCollateralToTreasury(uint256 loanId, address treasury) external nonReentrant onlyProtocol {
        if (treasury == address(0)) revert CollateralVault__InvalidParams();

        uint256 amount = _releaseCollateral(loanId, treasury, true);

        emit CollateralSentToTreasury(loanId, amount);
    }

    /// @inheritdoc ICollateralVault
    function validateCreditedCollateral(uint256 expectedAmount) external view onlyProtocol {
        if (expectedAmount == 0) revert CollateralVault__InvalidParams();

        uint256 vaultBalance = cbBtc.balanceOf(address(this));
        uint256 totalStored = totalCollateralStored;
        if (vaultBalance < totalStored) {
            revert CollateralVault__InvariantViolation(vaultBalance, totalStored);
        }

        uint256 creditedAmount = vaultBalance - totalStored;
        if (creditedAmount < expectedAmount) {
            revert CollateralVault__InsufficientCreditedCollateral(expectedAmount, creditedAmount);
        }
    }

    /// @inheritdoc ICollateralVault
    function getCollateralInfo(uint256 loanId) external view returns (CollateralInfo memory) {
        return loanCollateral[loanId];
    }

    function _revertUnauthorized(address caller) internal pure override {
        revert CollateralVault__Unauthorized(caller);
    }

    function _releaseCollateral(uint256 loanId, address to, bool markDefaulted) private returns (uint256 amount) {
        CollateralInfo storage collateralRef = loanCollateral[loanId];
        amount = collateralRef.amount;

        if (amount == 0) revert CollateralVault__NoCollateral(loanId);
        if (collateralRef.released) {
            revert CollateralVault__AlreadyReleased(loanId);
        }

        collateralRef.released = true;
        if (markDefaulted) {
            collateralRef.defaulted = true;
            collateralRef.defaultTimestamp = SafeCast.toUint48(block.timestamp);
        }
        totalCollateralStored -= amount;

        cbBtc.safeTransfer(to, amount);

        uint256 vaultBalance = cbBtc.balanceOf(address(this));
        if (vaultBalance < totalCollateralStored) {
            revert CollateralVault__InvariantViolation(vaultBalance, totalCollateralStored);
        }
    }

    function _protocolOrRevert(address protocolAddress) private pure returns (address) {
        if (protocolAddress == address(0)) {
            revert CollateralVault__InvalidParams();
        }
        return protocolAddress;
    }
}
Audited by0xSynthraxRead the report
Get started

Borrowing, in three steps.

See your exact numbers before you connect a wallet.

  1. Pick your amount
  2. Connect Coinbase Wallet
  3. Confirm & receive USDC
Today's termsLive
Network
Base
Term
30 days
LTV
80%
Fee @ $500
4.00% upfront
Limit
$1.00 – $1,000.00
cbBTC
$75,443.44