Smart Contracts
Overview
Buttonwood V1 consists of two contract packages: Core (the protocol's fundamental logic) and Periphery (user-facing helpers and vaults).
Contract Properties
| Contract | Upgradeable | Pausable | Permissions | Notes |
|---|---|---|---|---|
| OriginationPoolScheduler | Yes (UUPS) | Yes | Yes | |
| OriginationPool | No | Yes | Yes | ERC20 receipt tokens |
| GeneralManager | Yes (UUPS) | Yes | Yes | |
| LoanManager | No | No | No | Flash-swaps via Consol |
| MortgageNFT | No | No | No | ERC721 |
| OrderPool | No | No | Yes | |
| SubConsol | No | No | Yes | ERC20 (not rebasing) |
| USDX | No | No | Yes | Rebasing |
| Consol | No | No | Yes | Rebasing |
| ForfeitedAssetsPool | No | Yes | Yes | ERC20 (not rebasing) |
| ConversionQueue | No | No | Yes | LenderQueue + MortgageQueue |
| UsdxQueue | No | Yes | Yes | LenderQueue |
| ForfeitedAssetsQueue | No | Yes | Yes | LenderQueue |
| PythPriceOracle | No | No | No | Pyth pull-oracle |
| PythInterestRateOracle | No | No | No | Pyth pull-oracle |
Core Contracts
Token Contracts
| Contract | Description |
|---|---|
| USDX | Unified USD-pegged vault wrapping multiple stablecoins with configurable scaling factors into a standard 18-decimal rebasing token |
| Consol | The protocol's main rebasing treasury token — backed by USDX, ForfeitedAssetsPool, and SubConsol tokens. Provides flash swap functionality for efficient asset swapping. Withdrawal restricted to authorized queues. |
| SubConsol | Collateral-specific vault that accepts a single collateral type, mints tokens based on principal borrowed, and can deploy collateral to yield strategies for additional returns. Not rebasing. |
| RebasingERC20 | Abstract shares-based ERC20 where balances adjust based on underlying asset value — enables passive yield accrual |
| MultiTokenVault | Abstract vault accepting multiple ERC20 deposits, minting a single rebasing token. Provides relative token caps and registry. |
Lending & Borrowing
| Contract | Description |
|---|---|
| GeneralManager | Central upgradeable orchestrator. Validates and routes mortgage requests through OriginationPools, coordinates oracles, manages collateral types and terms, handles balance sheet expansions. |
| LoanManager | Manages full mortgage lifecycle — creation (depositing collateral into SubConsols), payment processing with automatic late fees, redemption, refinancing, foreclosure, and conversions |
| MortgageNFT | ERC-721 representing ownership of a mortgage position. Manages user-chosen MortgageId labels. |
| OriginationPool | Non-upgradeable lending pool. Accepts USDX deposits (Deposit phase), flash-deploys for origination receiving Consol repayment (Deploy phase), allows proportional USDX+Consol redemption (Redemption phase). Withdrawals never affected by pause. |
| OriginationPoolScheduler | Deploys at most one OriginationPool per registered config in each daily epoch; each config sets its pools' deposit and deploy phase lengths. Manages pool configurations including limits, growth rates, and multipliers. |
| OrderPool | Order book holding PurchaseOrders for collateral. Authorized fulfillers process orders by providing collateral in exchange for USDX, while expired orders are deleted and NFTs burned. |
Queues & Processing
| Contract | Description |
|---|---|
| ConversionQueue | Double-queue: lenders deposit Consol to get collateral with a premium; borrowers submit mortgages to be converted when prices trigger. Processes by liquidating portions of qualified mortgages at market prices with lump-sum interest rewarded to withdrawers. |
| UsdxQueue | FIFO withdrawal queue — users deposit Consol and receive USDX. Permissionless processing with gas fee collection. |
| ForfeitedAssetsQueue | FIFO withdrawal queue — users deposit Consol to burn ForfeitedAssetsPool tokens for underlying foreclosed collateral. Value received may exceed Consol deposited. |
| MortgageQueue | Sorted linked-list of mortgage positions ordered by trigger price (lowest to highest) for efficient conversion processing |
| LenderQueue | Abstract base for FIFO withdrawal queues with gas fees, minimum amounts, and cancellation logic |
| QueueProcessor | Processes items across the various queues |
Oracles
| Contract | Description |
|---|---|
| PythPriceOracle | Pull-oracle for real-time collateral/USD prices. Validates freshness (max 60s) and confidence thresholds. |
| PythInterestRateOracle | Reads 3yr and 5yr US Treasury rates from Pyth, multiplies by 2, adds 100 bps (+100 bps more if no payment plan). Reverts if age > 60s or confidence > 100 bps. |
| StaticInterestRateOracle | Fallback fixed interest rate oracle |
Other
| Contract | Description |
|---|---|
| ForfeitedAssetsPool | Holds collateral from foreclosed mortgages, issues liability tokens. Users burn Consol to redeem proportional foreclosed collateral at a discount, removing bad debt from the protocol. |
Periphery Contracts
| Contract | Description |
|---|---|
| Router | Entry point for all user transactions — handles multi-step flows (wrap, approve, deposit) |
| RolloverVault | Automated vault that re-invests lender capital across pool epochs |
| FulfillmentVault | Vault providing liquidity for order fulfillment on the chain's trading venue |
| LiquidityVault | Abstract base for vault share token mechanics |
Contract Interactions
┌────────────────┐
│ Router │ ◄── User entry point
└───────┬────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────────┐ ┌─────────────┐ ┌──────────────┐
│GeneralManager│ │ USDX │ │ Origination │
│ │ │ │ │ Pool │
└──────┬───────┘ └──────┬──────┘ └──────┬───────┘
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌─────────────┐ ┌──────────────┐
│ LoanManager │ │ Consol │◄│ OrderPool │
└──────┬───────┘ └──────┬──────┘ └──────────────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐ ┌─────────────┐
│ MortgageNFT │ │ SubConsol │ │ UsdxQueue │
└──────────────┘ │ (escrow) │ ├─────────────┤
└──────┬───────┘ │ForfeitedAsQ │
│ ├─────────────┤
▼ │Conversion Q │
┌──────────────┐ └─────────────┘
│ Conversion │
│ Queue │
└──────────────┘
Access Control
The protocol uses role-based access control across multiple contracts. See the Glossary — Roles section for a complete reference of all roles, their intended holders, and applicable contracts.
Key roles:
- Default Admin (Governance) — Manage supported tokens, set caps, assign roles, upgrade contracts
- Withdraw Role — Authorized contracts that can withdraw/flash-swap from Consol
- Pause Role (Governance + automated safety) — Emergency pause of deposits, deployments, and operations
- Fulfillment Role (Market maker) — Sell collateral to OrderPool and receive USDX
- Deploy Role (GeneralManager) — Flash-loan USDX from OriginationPools
Key Interfaces
| Interface | Purpose |
|---|---|
| IInterestRateOracle | Dynamic interest rates based on term length and payment structure |
| IPriceOracle | Real-time collateral pricing in USDX terms |
| IConsolFlashSwap | Flash swap callback for temporarily borrowing from Consol |
| IOriginationPoolDeployCallback | Callback for origination pool fund deployment (flash-lending) |
| IYieldStrategy | Yield-generating strategies for SubConsol collateral |
| INFTMetadataGenerator | Dynamic metadata generation for Mortgage NFTs |
Deployment
The contracts are chain-agnostic and deploy to any EVM chain carrying supported collateral and a price feed; deployment to Robinhood Chain is forthcoming. The protocol uses deterministic deployment via Foundry scripts for reproducible addresses, and per-chain addresses live in the @repo/deployments address book.
Core Contracts
Reference for every contract in the core package (buttonwood-protocol/cash), generated from the contract source. Each contract lists its state variables, functions, events, and errors.
Consol
Inherits: IConsol, MultiTokenVault, ReentrancyGuard
Title: Consol
Author: @SocksNFlops
A rebasing ERC20 token that is backed by a pool of tokens, notably usdx and a forfeited assets pool. Include a redemption pool for withdrawing usdx.
State Variables
forfeitedAssetsPool
Get the address of the forfeited assets pool.
address public forfeitedAssetsPool
Functions
constructor
Constructor
constructor(
string memory name_,
string memory symbol_,
uint8 decimalsOffset_,
address admin_,
address forfeitedAssetsPool_
) MultiTokenVault(name_, symbol_, decimalsOffset_, admin_);
Parameters
| Name | Type | Description |
|---|---|---|
name_ | string | The name of the token |
symbol_ | string | The symbol of the token |
decimalsOffset_ | uint8 | The number of decimals to pad the internal shares with to avoid precision loss |
admin_ | address | The address of the admin |
forfeitedAssetsPool_ | address | The address of the forfeited assets pool |
supportsInterface
Returns true if this contract implements the interface defined by
interfaceId. See the corresponding
https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
to learn more about how these ids are created.
This function call must use less than 30 000 gas.
function supportsInterface(bytes4 interfaceId) public view virtual override(MultiTokenVault) returns (bool);
setForfeitedAssetsPool
Set the address of the forfeited assets pool.
function setForfeitedAssetsPool(address forfeitedAssetsPool_) external override onlyRole(Roles.SUPPORTED_TOKEN_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
forfeitedAssetsPool_ | address | The address of the forfeited assets pool |
withdraw
Withdraw tokens from the MultiTokenVault and burn an equivalent amount of the MultiTokenVault token.
function withdraw(address token, uint256 amount)
public
virtual
override(IMultiTokenVault, MultiTokenVault)
onlyRole(Roles.WITHDRAW_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
token | address | The address of the token to withdraw |
amount | uint256 | The amount of tokens to withdraw |
flashSwap
Flash swap tokens. Caller must implement the IConsolFlashSwap interface.
function flashSwap(address inputToken, address outputToken, uint256 amount, bytes calldata data)
external
override
onlyRole(Roles.WITHDRAW_ROLE)
nonReentrant;
Parameters
| Name | Type | Description |
|---|---|---|
inputToken | address | The address of the input token |
outputToken | address | The address of the output token |
amount | uint256 | The amount of tokens to swap |
data | bytes | The data to pass into the callback |
_totalSupply
Checks that the forfeited assets pool is set before returning the total supply
function _totalSupply() internal view virtual override returns (uint256);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | The total supply of the Consol token |
ConversionQueue
Inherits: LenderQueue, MortgageQueue, IConversionQueue
Title: ConversionQueue
Author: SocksNFlops
The ConversionQueue contract is responsible for converting mortgages by reducing the principal and collateral as a result of a withdrawal request
State Variables
nativeWrapper
Get the native token wrapper contract (whype/weth/etc).
address public immutable override nativeWrapper
generalManager
Get the GeneralManager contract.
address public immutable override generalManager
decimals
The number of decimals of the collateral
uint8 public immutable override decimals
Functions
constructor
Constructor
constructor(
address asset_,
uint8 decimals_,
address consol_,
address nativeWrapper_,
address generalManager_,
address admin_
) LenderQueue(asset_, consol_, admin_);
Parameters
| Name | Type | Description |
|---|---|---|
asset_ | address | The address of the asset to convert |
decimals_ | uint8 | The number of decimals of the asset |
consol_ | address | The address of the Consol contract |
nativeWrapper_ | address | The address of the native wrapper contract |
generalManager_ | address | The address of the GeneralManager contract |
admin_ | address | The address of the admin |
supportsInterface
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(LenderQueue, MortgageQueue)
returns (bool);
convertingPrice
The current price of the collateral in USD
function convertingPrice() public view override returns (uint256);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | The price of the collateral in USD |
_calculateCollateralToUse
Calculates the collateral to use out of a mortgage position for a withdrawal request
function _calculateCollateralToUse(MortgagePosition memory mortgagePosition, uint256 amountToUse)
internal
view
returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
mortgagePosition | MortgagePosition | The mortgage position to calculate the collateral to use for |
amountToUse | uint256 | The amount of principal to use for the withdrawal request |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | The collateral to use for the withdrawal request |
enqueueMortgage
Enqueues a mortgage position into the conversion queue. If the mortgage position is already in the queue, it will be removed and enqueued again with a potentially new price.
function enqueueMortgage(uint256 mortgageTokenId, uint256 hintPrevId)
external
payable
override
whenNotPaused
nonReentrant;
Parameters
| Name | Type | Description |
|---|---|---|
mortgageTokenId | uint256 | The tokenId of the mortgage position |
hintPrevId | uint256 | The hintPrevId of the mortgage position |
processWithdrawalRequests
Process the requests from the front of the USDX withdrawal queue. Callable by anyone by contracts with the PROCESSOR_ROLE.
function processWithdrawalRequests(uint256 iterations, address receiver)
external
override(ILenderQueue, LenderQueue)
nonReentrant
whenNotPaused
onlyRole(Roles.PROCESSOR_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
iterations | uint256 | The number of iterations to process. Each iteration returns collected gas fees. |
receiver | address | The address to receive the gas fees |
dequeueMortgage
Dequeues a mortgage position from the conversion queue. Only callable on an inactive mortgage position.
function dequeueMortgage(uint256 mortgageTokenId) external override;
Parameters
| Name | Type | Description |
|---|---|---|
mortgageTokenId | uint256 | The tokenId of the mortgage position |
ForfeitedAssetsPool
Inherits: IForfeitedAssetsPool, ERC165, AccessControl, ERC20
Title: The Forfeited Assets Pool contract
Author: SocksNFlops
The Forfeited Assets Pool is a contract that holds assets seized from foreclosed mortgages. They can be purchased for Consol that is burned in exchange.
In order to minimize smart contract risk, we are hedging towards immutability.
State Variables
assets
The set of assets in the forfeited assets pool
EnumerableSet.AddressSet private assets
paused
Get the paused state of the contract
bool public override paused
Functions
constructor
Constructor
constructor(string memory name_, string memory symbol_, address _admin) ERC20(name_, symbol_);
Parameters
| Name | Type | Description |
|---|---|---|
name_ | string | The name of the token |
symbol_ | string | The symbol of the token |
_admin | address | The address of the admin |
whenNotPaused
Modifier to check if the contract is paused
modifier whenNotPaused() ;
supportsInterface
function supportsInterface(bytes4 interfaceId) public view override(ERC165, AccessControl) returns (bool);
addAsset
Add an asset to the forfeited assets pool. Only callable by admin role.
function addAsset(address asset) external override onlyRole(Roles.DEFAULT_ADMIN_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
asset | address | The address of the asset to add |
removeAsset
Remove an asset from the forfeited assets pool. Only callable by admin role.
function removeAsset(address asset) external override onlyRole(Roles.DEFAULT_ADMIN_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
asset | address | The address of the asset to remove |
getAsset
Get the list of assets in the forfeited assets pool
function getAsset(uint256 index) external view override returns (address);
Parameters
| Name | Type | Description |
|---|---|---|
index | uint256 | The index of the asset to get |
Returns
| Name | Type | Description |
|---|---|---|
<none> | address | The address of the asset at the given index |
totalAssets
Get the total amount of assets in the forfeited assets pool
function totalAssets() external view override returns (uint256);
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | The total amount of assets in the forfeited assets pool |
depositAsset
Deposits an asset into the forfeited assets pool and updates the foreclosed liabilities. Only callable by permissioned depositors.
function depositAsset(address asset, uint256 amount, uint256 liability)
external
override
whenNotPaused
onlyRole(Roles.DEPOSITOR_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
asset | address | The address of the asset to deposit |
amount | uint256 | The amount of the asset to deposit |
liability | uint256 | The liability to add to the foreclosed liabilities |
burn
Purchase assets from the forfeited assets pool in proportion to the amount of Consol burned. redemptionPercentage = (amount / foreclosedLiabilities)
function burn(address receiver, uint256 liability)
external
override
whenNotPaused
returns (address[] memory redeemedAssets, uint256[] memory redeemedAmounts);
Parameters
| Name | Type | Description |
|---|---|---|
receiver | address | The address to send the assets to |
liability | uint256 | The amount of liabilities to burn in order to purchase assets from the forfeited assets pool. |
Returns
| Name | Type | Description |
|---|---|---|
redeemedAssets | address[] | The list of assets purchased |
redeemedAmounts | uint256[] | The amount of assets purchased |
setPaused
Pause or unpause the contract
function setPaused(bool pause) external override onlyRole(Roles.PAUSE_ROLE);
Parameters
| Name | Type | Description |
|---|---|---|
pause | bool | The new paused state |
ForfeitedAssetsQueue
Inherits: LenderQueue
Title: ForfeitedAssetsQueue
Author: @SocksNFlops
Queue for withdrawing assets from the ForfeitedAssetsPool contract.
Functions
constructor
Constructor
constructor(address asset_, address consol_, address admin_) LenderQueue(asset_, consol_, admin_);
Parameters
| Name | Type | Description |
|---|---|---|
asset_ | address | The address of the forfeited assets pool |
consol_ | address | The address of the Consol contract |
admin_ | address | The address of the admin |
processWithdrawalRequests
function processWithdrawalRequests(uint256 iterations, address receiver)
external
virtual
override
nonReentrant
whenNotPaused
onlyRole(Roles.PROCESSOR_ROLE);
GeneralManager
Inherits: Initializable, ERC165Upgradeable, AccessControlUpgradeable, UUPSUpgradeable, IGeneralManager, ReentrancyGuard
Title: GeneralManager
Author: SocksNFlops
The GeneralManager contract manages the origination of mortgage positions using origination pools
State Variables
GeneralManagerStorageLocation
The storage location of the GeneralManager contract
keccak256(abi.encode(uint256(keccak256("buttonwood.storage.GeneralManager")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant GeneralManagerStorageLocation =
0xa0fde9a47799f0a64e9adfc0ccfed9fa7d54162399ca936d199d08c2d005ad00
Functions
_getGeneralManagerStorage
Gets the storage location of the GeneralManager contract
function _getGeneralManagerStorage() private pure returns (GeneralManagerStorage storage $);
Returns
| Name | Type | Description |
|---|---|---|
$ | GeneralManagerStorage | The storage location of the GeneralManager contract |
__GeneralManager_init
Initializes the GeneralManager contract and calls parent initializers
function __GeneralManager_init(
address usdx_,
address consol_,
uint16 penaltyRate_,
uint16 refinanceRate_,
uint16 conversionPremiumRate_,
uint16 priceSpread_,
address insuranceFund_,
address interestRateOracle_
) internal onlyInitializing;
Parameters
| Name | Type | Description |
|---|---|---|
usdx_ | address | The address of the USDX token |
consol_ | address | The address of the Consol token |
penaltyRate_ | uint16 | The penalty rate |
refinanceRate_ | uint16 | The refinancing rate |
conversionPremiumRate_ | uint16 | The conversion premium rate |
priceSpread_ | uint16 | The price spread |
insuranceFund_ | address | The address of the insurance fund |
interestRateOracle_ | address | The address of the interest rate oracle |
__GeneralManager_init_unchained
Initializes only the GeneralManager contract
function __GeneralManager_init_unchained(
address usdx_,
address consol_,
uint16 penaltyRate_,
uint16 refinanceRate_,
uint16 conversionPremiumRate_,
uint16 priceSpread_,
address insuranceFund_,
address interestRateOracle_
) internal onlyInitializing;
Parameters
| Name | Type | Description |
|---|---|---|
usdx_ | address | The address of the USDX token |
consol_ | address | The address of the Consol token |
penaltyRate_ | uint16 | The penalty rate |
refinanceRate_ | uint16 | The refinancing rate |
conversionPremiumRate_ | uint16 | The conversion premium rate |
priceSpread_ | uint16 | The price spread |
insuranceFund_ | address | The address of the insurance fund |
interestRateOracle_ | address | The address of the interest rate oracle |
initialize
Initializes the GeneralManager contract
function initialize(
address usdx_,
address consol_,
uint16 penaltyRate_,
uint16 refinanceRate_,
uint16 conversionPremiumRate_,
uint16 priceSpread_,
address insuranceFund_,
address interestRateOracle_
) external initializer;
Parameters
| Name | Type | Description |
|---|---|---|
usdx_ | address | The address of the USDX token |
consol_ | address | The address of the Consol token |
penaltyRate_ | uint16 | The penalty rate |
refinanceRate_ | uint16 | The refinancing rate |
conversionPremiumRate_ | uint16 | The conversion premium rate |
priceSpread_ | uint16 | The price spread |
insuranceFund_ | address | The address of the insurance fund |
interestRateOracle_ | address | The address of the interest rate oracle |
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract. Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal virtual override onlyRole(Roles.DEFAULT_ADMIN_ROLE);
whenNotPaused
Modifier to check if the contract is paused
modifier whenNotPaused() ;
onlyOrderPool
Modifier to check if the caller is the order pool
modifier onlyOrderPool() ;
onlyRegisteredOriginationPool
Modifier to check if the caller is an origination pool
modifier onlyRegisteredOriginationPool() ;
_addConversionQueues
Appends the conversionQueueList to the recorded conversion queues for a mortgage position
function _addConversionQueues(uint256 tokenId, address[] memory conversionQueueList) internal;
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | The tokenId of the mortgage position |
conversionQueueList | address[] | The list of conversion queues to update |
_calculateRequiredGasFee
Calculates the required gas fee for the caller
function _calculateRequiredGasFee(bool usingOrderPool, address[] memory conversionQueueList)
internal
view
returns (uint256 requiredGasFee);
Parameters
| Name | Type | Description |
|---|---|---|
usingOrderPool | bool | Whether the caller is using the order pool |
conversionQueueList | address[] | The list of conversion queues to calculate the required gas fee for |
Returns
| Name | Type | Description |
|---|---|---|
requiredGasFee | uint256 | The required gas fee |
_checkSufficientGas
Checks if the caller sent enough value to cover the required gas fee
function _checkSufficientGas(uint256 requiredGasFee) internal view;
Parameters
| Name | Type | Description |
|---|---|---|
requiredGasFee | uint256 | The required gas fee |
_refundSurplusGas
Refunds the surplus gas to the caller
function _refundSurplusGas(uint256 requiredGasFee) internal;
Parameters
| Name | Type | Description |
|---|---|---|
requiredGasFee | uint256 | The required gas fee |
_validateMortgageOwner
Validates that the caller is the owner of the mortgage
function _validateMortgageOwner(uint256 tokenId) internal view;
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | The ID of the mortgage position |
_validateTotalPeriods
Validates that the total periods is supported for the collateral
function _validateTotalPeriods(address collateral, uint8 totalPeriods) internal view;
Parameters
| Name | Type | Description |
|---|---|---|
collateral | address | The collateral address |
totalPeriods | uint8 | The total periods |
onlyMortgageOwner
Modifier to check if the caller is the owner of the mortgage
modifier onlyMortgageOwner(uint256 tokenId) ;
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | The ID of the mortgage position |