Розробка NFT-лендингу (залог NFT)
Клієнт приходить з запитом: «ми хочемо дати користувачам можливість брати кредити під JPEG». За цією фразою стоїть один з найбільш інженерно складних класів DeFi-протоколів. Оцінка вартості NFT-залогу — це не просто вивізит latestPrice() до Chainlink-оракула. NFT неліквідні за природою: два токени однієї колекції можуть відрізнятися в ціні на 50x. Це робить стандартні підходи до overcollateralization з Aave або Compound маловживаними без серйозної адаптації.
Дві моделі та чому вони вирішують різні проблеми
Протоколи NFT-лендингу діляться на два принципово різні класи: peer-to-peer (P2P) та peer-to-pool (P2Pool). Вибір архітектури визначає майже все — від моделі оракула до механізму ліквідації.
P2P-лендинг (NFTfi, Arcade, Gondi): позичальник виставляє NFT як залог, кредитор робить пропозицію з конкретними умовами. Смарт-контракт депонує NFT, при дефолту кредитор отримує токен напряму. Немає pool-риску, оракул потрібен тільки опціонально (для UI). Проблема — низька швидкість матчингу та гарний UX для позичальника, який очікує пропозицію годинами.
P2Pool-лендинг (BendDAO, Pine Protocol, JPEG'd): пул ліквідності, миттєвий кредит за ставкою, ціна NFT визначена оракулом. Саме тут починаються справжні інженерні виклики.
Головна проблема P2Pool: оцінка неліквідного залогу
Floor price — недостатній захист від маніпуляцій
Більшість ранніх протоколів використовували floor price як proxy для вартості залогу. BendDAO в серпні 2022 показала, що це критичний ризик: різке падіння floor price BAYC та CryptoPunks привело до каскадних ліквідацій, пул опинився на грані неплатоспроможності, протокол екстрено змінив параметри ліквідації через governance.
Проблема не тільки в волатильності. Floor price маніпульована: достатньо виставити кілька токенів за зниженою ціною на OpenSea, щоб Chainlink NFT Floor Price feed реагував на наступному оновленні. Атака коштує дешево відносно потенційного прибутку від дренування пулу.
Що ми використовуємо замість простого floor price
Багаторівневий оракул з кількома джерелами та часовим усередненням:
TWAP за продажами на чейні — власний оракул, який індексує Transfer+Sale події на Blur, OpenSea Seaport, LooksRare через The Graph. Розраховує зважене середнє за останніми N продажами з decay-функцією (свіжі продажі мають більшу вагу). Реалізується в Solidity через ring buffer для накопичення цінових точок.
Chainlink NFT Floor Price Feeds — використовується як secondary source, не primary. Оновлення раз на кілька годин занадто повільно для реакції на різкі рухи ринку.
Trait-adjusted pricing — для колекцій з виразними trait-рідкостями. Токен з trait rarity score в топ-1% коштує значно дорожче floor. Реалізується через off-chain Merkle-дерево з підписаною ціною від довіреного оракула, proof верифікується на-чейні.
Остаточна ціна для розрахунку LTV береться як min(TWAP, ChainlinkFloor) * haircut. Haircut (зазвичай 0.6–0.7) — консервативний дисконт під ліквідаційний стрес.
Health factor та механізм ліквідації NFT
Ліквідація NFT-залогу принципово відрізняється від ліквідації ERC-20. Токен не можна продати частково. Не можна гарантувати миттєву реалізацію за ринковою ціною — листинг та знаходження покупця займає час.
Рішення — dutch auction при ліквідації: контракт починає аукціон від оціночної вартості з поступовим зниженням ціни. Ліквідатор викликає liquidate(), депонує потрібну суму боргу, отримує NFT. Якщо протягом період аукціону (зазвичай 24-48 годин) ніхто не вийшов — протокол фіксує bad debt.
function liquidate(uint256 loanId) external {
Loan storage loan = loans[loanId];
require(_isLiquidatable(loan), "Not liquidatable");
uint256 auctionPrice = _getDutchAuctionPrice(loan);
uint256 debtAmount = _getDebtWithInterest(loan);
// Ліквідатор покриває борг, отримує NFT
IERC20(loan.borrowToken).transferFrom(msg.sender, address(pool), debtAmount);
IERC721(loan.nftContract).transferFrom(address(this), msg.sender, loan.tokenId);
emit Liquidated(loanId, msg.sender, auctionPrice, debtAmount);
}
Границя ліквідації (health factor < 1) розраховується з урахуванням накопичених процентів. Процентна ставка — змінна, через utilization rate криву, аналогічну Compound (kink model).
Модель процентних ставок
NFT-пули мають принципово іншу динаміку utilization, ніж стандартні пули лендингу. Ліквідність NFT-ринку нерівномірна: у bull-ринку попит на кредити високий, utilization швидко зростає до 80%+. Для захисту депозиторів потрібна агресивна kink-модель з різким зростанням ставки вище kink point (70-80% utilization).
| Параметр | Значення |
|---|---|
| Base rate | 2–5% APR |
| Slope до kink | 10–20% APR |
| Kink point | 75% utilization |
| Slope після kink | 100–200% APR |
Різке зростання після kink економічно стимулює позичальників погасити борг або рефінансувати, повертаючи пул до здорового діапазону utilization.
Flash loan та reentrancy в контексті NFT-лендингу
ERC-721 не має хука transferFrom на відміну від ERC-777, але ERC-1155 має onERC1155Received. Якщо протокол приймає ERC-1155 як залог — вектор reentrancy при отриманні токена реальний. Захист: nonReentrant на всі функції зміни стану + оновлення storage до зовнішнього вивізиту.
Більш тонкий вектор: flash loan + маніпуляція floor price через масове виставлення/видалення на Blur в одному блоці. Атакуючий бере flash loan → маніпулює floor → бере кредит за завищеною оцінкою → повертає flash loan → floor повертається. TWAP з достатнім вікном (мінімум 30 хвилин) робить цю атаку нерентабельною.
Стек та інструменти розробки
Контракти пишемо на Solidity 0.8.24, тестуємо в Foundry з fork-тестами на Ethereum mainnet. Fork-тест дозволяє реально взаємодіяти з Seaport, Blur Exchange, Chainlink price feeds в тестовій середовищі без потреби мокувати всю екосистему.
Для The Graph субграфа використовуємо AssemblyScript, індексуємо Transfer та OrderFulfilled події для побудови ціної історії. Off-chain компонент оракула — Node.js сервіс з Redis для кеширування та підписанням цін через EIP-712 structured data.
Upgradeability: UUPS (EIP-1822) з timelock через OpenZeppelin TimelockController. Для протоколу з TVL > $1M важливо, щоб будь-який апгрейд мав мінімум 48-годинну затримку — це дає користувачам час вийти при виявленні проблеми.
Процес розробки
Аналітика (2–3 дні). Визначаємо тип протоколу (P2P vs P2Pool), цільові колекції, джерела ліквідності. Аналізуємо історичні дані floor price для вибору TWAP-параметрів.
Проектування (3–5 днів). Storage layout контрактів, схема оракула, модель ліквідації, параметри ризику. Formal specification інваріантів системи для property-based тестів.
Розробка (3–6 тижнів). Ядро контрактів, оракульна система, тести (unit + fuzz + fork). Покриття >95%, property-тести через Echidna для інваріантів (сума боргів = сума депозитів + накопичені проценти).
Внутрішній аудит (1 тиждень). Slither, Mythril, ручний review. Перевірка всіх шляхів ліквідації на fork-тесті з реальними даними з минулих NFT-крахів.
Деплой та моніторинг. Gnosis Safe мультисиг для admin-функцій, Tenderly для алертів по аномальним транзакціям, The Graph для аналітики протоколу.
Орієнтири по строкам
P2P-протокол базової версії — 4–6 тижнів. P2Pool з власним оракулом та dutch auction ліквідацією — 8–12 тижнів. Зовнішній аудит (рекомендуємо Trail of Bits, Spearbit або Code4rena contest) додає 2–6 тижнів і критично важливий перед мейннет-деплоєм з реальною ліквідністю.







