A credit protocol for assets that already exist.
The intended production venue is Robinhood Chain. The repository currently contains a working local deployment with mock assets; its Robinhood testnet and mainnet entries remain unconfigured. The application therefore demonstrates the complete mechanics without implying that production markets are already live.
Lend
Supply USDG into one market. Receive shares whose asset value rises as borrowers pay interest.
Borrow
Post collateral, draw USDG below the market LLTV, and repay to recover borrowing capacity.
Multiply
Use the router to deposit, borrow, swap, and redeposit in one transaction.
NFT credit
Use exact-token lender offers or instant collection-floor pools with NFT escrow.
Stake
Stake fixed-supply MARK and claim USDG that has been notified to the staking contract.
Season
Route a market deposit through an ownerless vault and earn an escrowed MARK stream on top.
Flash
Borrow unutilized market liquidity for one transaction at zero protocol fee.
Agents
Discover markets and prepare unsigned lending calldata through the MCP endpoint.
Assets, shares, debt, utilization, fee settings, supply caps, oracle, IRM, LLTV, and bad debt are recorded per market ID. Insolvency in one ID reduces only that market’s supplier assets. It cannot debit another market.
One core. Many independent books.
The core owns custody and accounting for fungible lending. Separate contracts add optional behavior without changing the core: a stateless leverage router, an ownerless Season vault, an ownerless staking distributor, a dedicated NFT escrow contract, and oracle adapters.
Six roles. None require permission.
Supplier
Adds loan liquidity and receives supply shares.
Borrower
Posts collateral and owns debt shares.
Liquidator
Repays unhealthy debt for discounted collateral.
Market creator
Instantiates a tuple from enabled LLTV and IRM options.
Staker
Locks MARK and claims notified USDG.
Agent/router
Acts only through calldata, approvals, and explicit authorization.
Market creation is permissionless only after the core owner has enabled the chosen IRM address and LLTV value. This constrains the parameter menu without granting the owner control of user positions.
Five values define a market forever.
struct MarketParams {
address loanToken;
address collateralToken;
address oracle;
address irm;
uint256 lltv; // WAD-scaled, e.g. 0.86e18
}
marketId = keccak256(abi.encode(marketParams));id = keccak256(abi.encode(loanToken, collateralToken, oracle, irm, LLTV))
Addresses and LLTV are encoded as one tuple. Frontends and agents must reproduce this encoding exactly.
Deposit assets. Hold shares. Earn borrower interest.
Approve the core to spend the loan token.
Call supply(params, assets, onBehalf). Assets must be non-zero.
Shares are rounded down when minted. They represent a fraction of market assets.
Borrow interest raises totalSupplyAssets without changing the supplier’s share count.
Redeem by an exact asset amount or by shares. Exactly one input must be non-zero.
shares = assets × (totalShares + 1,000,000) / (totalAssets + 1)
Supply mints round down. Asset-specified withdrawals calculate shares round up; share-specified withdrawals calculate assets round down.
The conversion adds 1 virtual asset and 1,000,000 virtual shares. This makes empty markets expensive to manipulate and mitigates first-depositor share-price inflation. The virtual amounts affect rounding; they are not withdrawable balances.
A supplier can withdraw only while total borrowed assets remain less than or equal to supplied assets after the withdrawal. There is no pause switch, but fully utilized liquidity can still make an immediate withdrawal economically unavailable.
Collateral creates a ceiling, not a target.
maxBorrow = collateralAmount × oraclePrice / 1e36 × LLTV
The oracle price is normalized so collateralAmount × price / 1e36 returns loan-token units.
health = maxBorrow / currentDebt
Health ≥ 1 is valid. Health < 1 is liquidatable. A position with no debt returns the maximum uint256 value.
Borrowing rounds debt shares up and debt valuation rounds assets up. The system intentionally rounds against creating under-collateralized debt. Interfaces should use share-based full repayment when closing a position.
Cheap below the kink. Defensive above it.
U = totalBorrowAssets / totalSupplyAssets
If supplied assets are zero, the model returns its base rate.
if U ≤ 90%: APR = 0% + 4% × U / 90% if U > 90%: APR = 4% + 75% × (U − 90%) / 10%
The deployed default reaches 4% at 90% utilization and 79% at 100%. Parameters are immutable per IRM deployment.
supply APR ≈ borrow APR × utilization × (1 − protocol fee)
This is the economic relationship used by the interface. Actual balances update when interest is accrued.
The core converts the IRM’s annual rate to a per-second rate and uses a third-order Taylor approximation of continuous compounding over elapsed time. The approximation slightly under-accrues relative to exact exponential compounding.
Interest accrues when a state-changing market action calls the internal accrual routine, or when anyone calls accrueInterest. Read-only balance helpers do not first simulate pending interest, so displayed balances can lag until the market is poked.
The line is mechanical.
The market realizes pending borrow interest.
The market oracle values the requested seized collateral.
Healthy positions revert. There is no discretionary liquidation.
The liquidator repays debt at the published liquidation incentive.
The seized ERC-20 collateral transfers to the liquidator.
If all collateral is gone and debt remains, that debt is removed from supplier assets in this market.
incentive = min(1.15, 1 / (1 − 0.3 × (1 − LLTV)))
The multiplier grows as LLTV falls but can never exceed 1.15.
repaidAssets = seizedCollateralValue / incentive
Division rounds up. Debt shares removed are converted from assets rounding down.
There is no close factor or protocol-selected liquidation size. The liquidator chooses seizedAssets; profitability and transaction sizing are the liquidator’s responsibility.
If liquidation drains all collateral while borrow shares remain, the remaining debt is canceled and the same asset amount is subtracted from totalSupplyAssets. Losses are socialized only among suppliers of that market.
Bounded revenue. Explicit capacity.
Core fee shares and staking rewards are separate contracts. To pay MARK stakers, the fee recipient must redeem/route USDG and call notifyReward. The current core does not perform that forwarding automatically.
Deposit, borrow, swap, repost.
multiply optionally pulls initial collateral, posts it for the caller, borrows the loan token to the router, swaps it through an ISwapper, and posts the received collateral back to the caller’s position. minCollateralOut is the user’s slippage bound.
unwind withdraws collateral to the router, sells it for the loan token, repays as much debt as the proceeds allow, and returns surplus loan tokens. A withdrawal must leave the position healthy before the swap occurs, so highly leveraged positions may require several smaller unwind calls.
The user must authorize the router in the core and approve the router for collateral. Authorization permits position management until revoked; token approvals are separate.
The interface caps its slider at 1 + (LLTV × 0.92), retaining an 8% margin beneath the theoretical one-loop limit. It uses a 1% minimum-output buffer. Neither is a guarantee of execution or safety: price, swap output, existing debt, rounding, and current health determine whether the core accepts the transaction.
One transaction. Zero protocol fee.
Interest updates before available liquidity is measured.
The core transfers requested loan assets to the receiver.
onFlashLoan receives initiator, token, amount, fee = 0, and arbitrary data.
The receiver must return keccak256('ERC3156FlashBorrower.onFlashLoan').
The receiver must approve the core, which pulls the exact principal back.
The final token balance must be at least the pre-loan balance.
While a flash loan is active, supply, withdrawal, collateral, borrowing, repayment, and liquidation entry points protected by the guard cannot be re-entered.
Fixed supply in. Notified USDG out.
claimable = storedRewards + stake × accRewardPerShare − rewardDebt
The accumulator uses 1e27 precision. Every stake/unstake harvests accrued rewards before changing balances.
The staking contract has no owner, pause function, duration, or reward-rate schedule. notifyReward increases the accumulator immediately; rewards are claimable pro rata.
MARK is described as a governance token in contract commentary, but this repository contains no governor, proposal, voting, or timelock contract. Its implemented on-chain utility here is staking for notified rewards.
Supply yield plus an escrowed stream.
Deposits pull the market’s loan token, supply it to the core on behalf of the vault, and credit the returned core shares to the user inside SeasonVault. Because balances are denominated in core supply shares, normal market interest remains attributable to depositors.
The debt is fungible. The collateral is exact.
Fund an exact token—or any token in a collection.
A lender escrows principal and publishes principal, repayment, duration, expiry, collection, and token ID. maxUint256 as token ID accepts any token in the collection. The lender may cancel only while the offer remains active.
Borrow instantly against a collection floor.
A pool owner escrows liquidity and chooses collection, oracle, LLTV, flat interest basis points, and duration. Principal cannot exceed available liquidity or floorPrice × LLTV.
repayment = principal × (10,000 + interestBps) / 10,000
This is a fixed premium for the loan, not an annualized rate. MAX_INTEREST_BPS is 5,000 and MAX_LLTV is 80%.
NFT transfers to escrow; principal transfers to the borrower; a due timestamp is fixed.
Anyone may pay the full repayment. The NFT always returns to the recorded borrower.
For pool loans, repayment replenishes pool.available, including the premium.
For peer loans, repayment transfers directly to the lender.
Only the recorded lender may claim the NFT, and only after due has passed.
The current NFT contract has no partial repayments, extensions, refinancing, borrower grace period, or auction. Active loans resolve by full repayment or lender collateral claim after maturity.
Price is an immutable market dependency.
loanAmount = collateralAmount × oracle.price() / 1e36
Constructor-time scaling accounts for collateral decimals, loan decimals, and both feed decimal counts.
The current adapter has no Uniswap TWAP, secondary feed, circuit breaker, or failover. A stale Chainlink feed blocks health-dependent actions until the feed recovers or users migrate to a separately created market.
One interface across every surface.
Queries refetch every eight seconds. On an unsupported or undeployed chain, the current interface silently reads the first active deployment—today, local Anvil. isFallback is not surfaced to the user, and wallet writes are not pinned to that fallback read chain. Verify the wallet chain and target address before every signature.
Local/testnet deployment entries may expose mint controls for mock USDG, collateral, and NFTs. Faucet controls are test infrastructure—not protocol economics—and must not appear on production deployments.
The contracts expose more than the app. NFT repayment, offer acceptance, offer cancellation, and loan lists are not currently surfaced. Season and Portfolio can render no body when their deployment is absent. The injected browser-wallet connector is the only configured connection method.
Discovery and calldata—not custody.
POST /api/mcp
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {}
}{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "prepare_transaction",
"arguments": {
"chainId": 31337,
"market": "dSPY / USDG",
"action": "borrow",
"amount": "100",
"account": "0x..."
}
}
}prepare_transaction does not sign, submit, simulate, calculate approval calldata, or guarantee health. An agent or wallet must verify chain, addresses, allowance, slippage, market state, and post-transaction health before asking a user to sign.
Configured is not the same as deployed.
The test deployment seeds mock prices and USDG liquidity. These values are fixtures, not live market prices: dSPY $650, dTSLA $430, dNFLX $1,250, dRBLX $130, PONS $2, and WETH $4,600 in six-decimal USD units.
Deploy.s.sol installs only the core, default IRM, MARK, and staking contracts, and enables four LLTV values: 62.5%, 77%, 86%, and 91.5%. It does not deploy production markets, Chainlink adapters, a swap venue, the router, SeasonVault, or NftLending. Those production components must be deployed and registered separately. The demo instead uses mock oracles and an infinite-liquidity minting swapper.
Replace mock assets and oracles; deploy Chainlink adapters with reviewed heartbeats; configure real USDG; set a multisig owner and fee recipient; verify every market tuple; publish addresses; run an independent audit; test incident and oracle-failure procedures; then disable every faucet surface.
Read the addresses. Verify the code.
Accounting is externally indexable.
MarketCreated, Supply, Withdraw, SupplyCollateral, WithdrawCollateral, Borrow, Repay, Liquidate, AccrueInterest, AuthorizationSet, SupplyCapSet, and FlashLoan.
Every lifecycle leaves a trace.
Staked, Unstaked, RewardPaid, RewardNotified, SeasonFunded, Deposited, Withdrawn, RewardClaimed, Multiplied, Unwound, OfferCreated, PoolCreated, LoanStarted, LoanRepaid, and CollateralClaimed.
Non-upgradeable does not mean ownerless.
- Transfer core ownership to a non-zero address.
- Set the fee recipient, including zero.
- Enable additional IRM addresses; enabled entries cannot be disabled.
- Enable additional LLTV values below 100%; enabled values cannot be disabled.
- Set each market fee up to the 25% hard cap.
- Set or remove each market supply cap.
- Upgrade or replace core bytecode.
- Pause supply, withdrawal, repayment, or liquidation.
- Seize a user’s collateral or supply shares.
- Change an existing market’s tokens, oracle, IRM, or LLTV.
- Create debt without position authorization and health checks.
- Raise the protocol fee above 25%.
CrossbowStaking and SeasonVault have no owner. LeverageRouter has no owner or stored positions. NftLending pools are individually controlled by their pool creators, who may fund, withdraw available liquidity, and toggle their own pool.
Read this before using real value.
A defect can cause permanent loss. The code has tests but no published independent audit.
Incorrect, manipulated, or stale prices can misprice borrowing and liquidation. The adapter has no fallback.
Unpausable withdrawal does not guarantee immediately available cash when a market is highly utilized.
Tokenized assets may have issuer, bridge, redemption, market-hours, transfer, or legal risks beyond this code.
A fast price move can exhaust collateral. Remaining debt reduces supplier assets inside that market.
The owner can change market fees and caps and enable new parameter options. Operational security matters.
Core fees do not automatically reach staking. An external operator must convert/route fee value and notify USDG.
Only the ISwapper interface and a minting demo swapper exist in-repo; a production venue is not implemented. External swapper behavior, slippage, and authorization add risk.
Floor prices can be thin or discontinuous. The current NFT system has no auction or partial-repayment process.
Prepared calldata is unsigned and unsimulated. Agents can select unsafe amounts or stale market state.
MARK has no implemented governor, voting, proposal, or timelock system in this repository.
Checked-in Robinhood Chain addresses are zero. Local mocks are not production assets or feeds.
CrossbowCore is BUSL-1.1; supporting contracts use MIT. “Open source” usage must respect each file’s license.
This documentation describes source code, not an audited production guarantee. Robinhood Chain mainnet and testnet addresses are zero in the checked-in deployment file. Treat the current application as a local demonstration until verified production addresses and an audit are published.