Разработка системы реестра собственности на блокчейне
Традиционные реестры недвижимости — медленные, непрозрачные, уязвимые к мошенничеству и сложные для международных транзакций. Блокчейн-реестр решает конкретные проблемы: прозрачная история владения, атомарные сделки (deeds delivery vs payment одновременно), fractional ownership через токенизацию, и программируемые условия передачи (автоматическое исполнение при выполнении условий).
Правовой контекст: первая сложность
Ключевое ограничение: в большинстве юрисдикций право собственности на недвижимость создаётся государственной регистрацией, не записью в блокчейне. Блокчейн-реестр должен либо быть официальным (государственный проект), либо работать как Layer 2 поверх официального реестра (отслеживает транзакции, упрощает процесс, но финальная регистрация в Росреестре/Кадастре).
Исключение: цифровые активы (виртуальная недвижимость в метавёрсах, mineral rights в некоторых юрисдикциях, securities-based fractional ownership).
Для реального имущества — паттерн "blockchain as notary": фиксируем факты и документы, но правовая сила за официальными органами.
Архитектура property registry
Property NFT
Каждый объект недвижимости — уникальный NFT. Metadata содержит кадастровый номер, адрес, площадь, ссылку на документы в IPFS.
contract PropertyRegistry is ERC721URIStorage, AccessControl {
bytes32 public constant REGISTRAR_ROLE = keccak256("REGISTRAR_ROLE");
bytes32 public constant NOTARY_ROLE = keccak256("NOTARY_ROLE");
struct Property {
string cadastralNumber; // государственный кадастровый номер
string propertyType; // "RESIDENTIAL", "COMMERCIAL", "LAND"
string address_;
uint256 area; // площадь в кв.дм (избегаем float)
bytes32 documentsHash; // IPFS hash пакета документов
uint256 registeredAt;
uint256 lastTransferAt;
bool encumbered; // залог, арест
string encumbranceDetails;
}
// tokenId => Property
mapping(uint256 => Property) public properties;
// cadastralNumber => tokenId
mapping(string => uint256) public cadastralToToken;
// tokenId => liens (залоги и обременения)
mapping(uint256 => Lien[]) public propertyLiens;
struct Lien {
address creditor;
uint256 amount;
uint256 expiresAt;
string description;
bool active;
}
event PropertyRegistered(uint256 indexed tokenId, string cadastralNumber, address owner);
event PropertyTransferred(uint256 indexed tokenId, address from, address to, uint256 price);
event LienAdded(uint256 indexed tokenId, address creditor, uint256 amount);
function registerProperty(
address owner,
string calldata cadastralNumber,
string calldata propertyType,
string calldata address_,
uint256 area,
bytes32 documentsHash,
string calldata metadataURI
) external onlyRole(REGISTRAR_ROLE) returns (uint256 tokenId) {
require(cadastralToToken[cadastralNumber] == 0, "Already registered");
tokenId = uint256(keccak256(abi.encodePacked(cadastralNumber)));
properties[tokenId] = Property({
cadastralNumber: cadastralNumber,
propertyType: propertyType,
address_: address_,
area: area,
documentsHash: documentsHash,
registeredAt: block.timestamp,
lastTransferAt: block.timestamp,
encumbered: false,
encumbranceDetails: ""
});
cadastralToToken[cadastralNumber] = tokenId;
_safeMint(owner, tokenId);
_setTokenURI(tokenId, metadataURI);
emit PropertyRegistered(tokenId, cadastralNumber, owner);
return tokenId;
}
// Обременение запрещает передачу (залог, арест)
function addLien(
uint256 tokenId,
address creditor,
uint256 amount,
uint256 duration,
string calldata description
) external onlyRole(NOTARY_ROLE) {
propertyLiens[tokenId].push(Lien({
creditor: creditor,
amount: amount,
expiresAt: block.timestamp + duration,
description: description,
active: true
}));
properties[tokenId].encumbered = true;
emit LienAdded(tokenId, creditor, amount);
}
// Проверяем обременения перед передачей
function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize)
internal override
{
super._beforeTokenTransfer(from, to, tokenId, batchSize);
if (from != address(0)) { // не mint
require(!hasActiveLiens(tokenId), "Property has active liens");
}
}
function hasActiveLiens(uint256 tokenId) public view returns (bool) {
Lien[] memory liens = propertyLiens[tokenId];
for (uint i = 0; i < liens.length; i++) {
if (liens[i].active && block.timestamp < liens[i].expiresAt) return true;
}
return false;
}
}
Escrow для сделок
Атомарная сделка: покупатель вносит деньги в escrow, при подтверждении всех условий — NFT передаётся покупателю, деньги продавцу одновременно.
contract PropertyEscrow {
enum EscrowState { CREATED, FUNDED, CONDITIONS_MET, COMPLETED, DISPUTED, REFUNDED }
struct EscrowDeal {
uint256 propertyTokenId;
address seller;
address buyer;
address notary; // нотариус как арбитр
uint256 price; // в стейблкоине (USDC)
IERC20 paymentToken;
EscrowState state;
uint256 createdAt;
uint256 completionDeadline;
bytes32[] requiredDocuments; // хеши документов для проверки
mapping(bytes32 => bool) submittedDocuments;
}
PropertyRegistry public registry;
mapping(uint256 => EscrowDeal) public deals;
uint256 private _nextDealId;
function createDeal(
uint256 propertyTokenId,
address buyer,
address notary,
uint256 price,
address paymentToken,
uint256 deadline,
bytes32[] calldata requiredDocs
) external returns (uint256 dealId) {
require(registry.ownerOf(propertyTokenId) == msg.sender, "Not owner");
require(!registry.hasActiveLiens(propertyTokenId), "Property encumbered");
dealId = _nextDealId++;
EscrowDeal storage deal = deals[dealId];
deal.propertyTokenId = propertyTokenId;
deal.seller = msg.sender;
deal.buyer = buyer;
deal.notary = notary;
deal.price = price;
deal.paymentToken = IERC20(paymentToken);
deal.state = EscrowState.CREATED;
deal.completionDeadline = block.timestamp + deadline;
deal.requiredDocuments = requiredDocs;
// Блокируем NFT в контракте
registry.transferFrom(msg.sender, address(this), propertyTokenId);
emit DealCreated(dealId, propertyTokenId, msg.sender, buyer);
}
function fundEscrow(uint256 dealId) external {
EscrowDeal storage deal = deals[dealId];
require(msg.sender == deal.buyer, "Not buyer");
require(deal.state == EscrowState.CREATED, "Wrong state");
deal.paymentToken.transferFrom(msg.sender, address(this), deal.price);
deal.state = EscrowState.FUNDED;
}
// Нотариус подтверждает что все документы проверены
function completeDeal(uint256 dealId) external {
EscrowDeal storage deal = deals[dealId];
require(msg.sender == deal.notary, "Not notary");
require(deal.state == EscrowState.FUNDED, "Not funded");
// Атомарная сделка
registry.transferFrom(address(this), deal.buyer, deal.propertyTokenId);
deal.paymentToken.transfer(deal.seller, deal.price);
deal.state = EscrowState.COMPLETED;
emit DealCompleted(dealId, deal.propertyTokenId, deal.buyer, deal.seller);
}
function refundExpiredDeal(uint256 dealId) external {
EscrowDeal storage deal = deals[dealId];
require(block.timestamp > deal.completionDeadline, "Not expired");
require(deal.state == EscrowState.FUNDED, "Wrong state");
deal.paymentToken.transfer(deal.buyer, deal.price);
registry.transferFrom(address(this), deal.seller, deal.propertyTokenId);
deal.state = EscrowState.REFUNDED;
}
}
Fractional ownership
Токенизация доли в недвижимости — для инвестиционных объектов:
contract FractionalProperty is ERC20 {
uint256 public propertyTokenId;
PropertyRegistry public registry;
uint256 public totalShares = 1_000_000; // 1M долей
// Аренда распределяется пропорционально долям
mapping(address => uint256) public unclaimedRent;
uint256 public accumulatedRentPerShare;
function distributeRent(uint256 amount) external onlyManager {
require(totalSupply() > 0, "No shareholders");
accumulatedRentPerShare += amount * 1e18 / totalSupply();
paymentToken.transferFrom(msg.sender, address(this), amount);
}
function claimRent() external {
uint256 owed = balanceOf(msg.sender) * accumulatedRentPerShare / 1e18
- unclaimedRent[msg.sender];
unclaimedRent[msg.sender] += owed;
paymentToken.transfer(msg.sender, owed);
}
}
История транзакций и цепочка владения
On-chain история — ключевое преимущество. The Graph subgraph индексирует все Transfer события и строит полную цепочку владения с ценами сделок.
Для property history API:
query PropertyHistory($tokenId: String!) {
propertyTransfers(where: { tokenId: $tokenId }, orderBy: timestamp) {
from { id }
to { id }
timestamp
transactionHash
price
notary { id name }
}
liens(where: { propertyId: $tokenId }) {
creditor { id }
amount
expiresAt
active
description
}
}
Стек
| Компонент | Технология |
|---|---|
| Property NFT | ERC-721 + OpenZeppelin |
| Fractional shares | ERC-20 |
| Escrow | кастомный контракт |
| Documents | IPFS / Arweave (permanent storage) |
| Indexing | The Graph |
| Frontend | React + wagmi + Mapbox (карта объектов) |
| Notary portal | Next.js admin panel |
| Payment | USDC / USDT (стейблкоины) |
Сроки разработки
Фаза 1 (3-4 нед): Property Registry контракт, базовые функции регистрации и обременений.
Фаза 2 (2-3 нед): Escrow контракт, notary flow, атомарные сделки.
Фаза 3 (2-3 нед): Fractional ownership токены, rent distribution.
Фаза 4 (3-4 нед): The Graph subgraph, frontend (property browser, ownership history, transaction UI).
Фаза 5 (1-2 нед): Аудит смарт-контрактов (escrow управляет реальными деньгами).
Полная система: 3-4 месяца. MVP без fractional ownership и Mapbox: 6-8 недель.







