Borrow USDCagainst your bitcoin.No liquidations.
Uses cbBTC, Coinbase's 1:1 wrapped Bitcoin on Base.
Built for predictability.
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.
Repay within 30 days and your cbBTC is returned. Past the term, an unpaid loan settles to the treasury.
Manage your loans.
Active Loans
Total loans
Deposits
Total collateral
Borrowed
$1,250.00
Total amount
cbBTC Price
Current TWAP price
| Loan ID | Days remaining | Balance |
|---|---|---|
1042 Active | 28 | $500.00 |
1041 Active | 22 | $320.00 |
1040 Active Due soon | 6 | $120.00 |
- 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 dashboardImmutable 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.
// 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;
}
}Borrowing, in three steps.
See your exact numbers before you connect a wallet.
- Pick your amount
- Connect Coinbase Wallet
- Confirm & receive USDC
- Network
- Base
- Term
- 30 days
- LTV
- 80%
- Fee @ $500
- 4.00% upfront
- Limit
- $1.00 – $1,000.00
- cbBTC
- $75,443.44