Contracts

Integration notes

Everything the app does, you can do directly — the frontend has no privileged path. This page lists the calls that matter. Addresses live in the address book; ABIs ship in the repository (frontend/src/config/abis.ts, generated from the verified sources).

Minting

PositionManager.mint(address asset, uint256 amount, uint256 mintRatioBps)
    returns (uint256 receiptId, uint256 debtAmount)

Pull-based: approve the PositionManager for amount of the asset first. mintRatioBps must sit inside the market's [min, max] (currently 0–8000). Debt is minted to the caller in the market's synth, scaled from asset decimals: amount × 10^(synthDec − assetDec) × ratio / 10⁴.

Quote first with previewMint(asset, amount, ratioBps) → (debtAmount, shares, unlockTime).

Reading positions

WithdrawalReceipt.getPosition(uint256 id) returns (ReceiptPosition)
// { asset, principal, shares, mintRatioBps, debtAmount, createdAt, unlockTime, redeemed }
WithdrawalReceipt.receiptsOf(address owner) returns (uint256[])

The receipt is ERC-721 Enumerable; receiptsOf is a convenience for small sets — index events for scale.

Redemption

ReceiptRedeemer.previewRedeem(uint256 id)          returns (uint256 gross)
ReceiptRedeemer.previewRedeemDetailed(uint256 id)  returns (uint256 gross, uint256 fee, uint256 net)
ReceiptRedeemer.redeem(uint256 id)                 returns (uint256 assetsOut)

redeem requires: caller owns the receipt, block.timestamp ≥ unlockTime, not yet redeemed, and an lUSD allowance ≥ debtAmount to the redeemer — burning spends allowance by design. Show users net, not gross: the performance fee (5% of yield only) is inside previewRedeemDetailed.

Marketplace

list(uint256 id, uint256 priceLusd)        // escrows the NFT; price is absolute lUSD
buy(uint256 id)                            // pays the ask, receives the NFT
placeBid(uint256 id, uint256 price, uint256 expiry)  // escrows lUSD; ≥ 1e18; expiry 0 = GTC
acceptBid(uint256 id, address bidder)      // seller-side instant fill
cancel / cancelBid                         // full refunds, any time
getListing(id) / bestBid(id) / activeListings()

Every trade entry reverts with ReceiptRedeemed for spent claims — you cannot buy, list, or settle a husk. bestBid is an O(n) view over that receipt's bidders; fine for RPC, don't call it on-chain in a loop.

Events worth indexing

Minted(user, asset, synth, principal, shares, ratio, debt, receiptId) · Redeemed(receiptId, owner, asset, debtBurned, assetsOut, fee) · Listed / Sold / BidPlaced / BidAccepted / Cancelled on the marketplace · Transfer on the receipt for ownership tracking.

Invariants you can monitor

  • lUSD.totalSupply() == Σ debtAmount over unredeemed receipts — the solvency identity.
  • YieldVault.totalAssets() (via the adapter) what redemption math implies — venue health.
  • Any MinterSet / BurnerSet / AdapterSet / ManagerSet / RedeemerSet event — role changes; on mainnet these will sit behind a timelock, so an event without a preceding queue is an alarm.