chore: initial commit for v0.0.1

DChain single-node blockchain + React Native messenger client.

Core:
- PBFT consensus with multi-sig validator admission + equivocation slashing
- BadgerDB + schema migration scaffold (CurrentSchemaVersion=0)
- libp2p gossipsub (tx/v1, blocks/v1, relay/v1, version/v1)
- Native Go contracts (username_registry) alongside WASM (wazero)
- WebSocket gateway with topic-based fanout + Ed25519-nonce auth
- Relay mailbox with NaCl envelope encryption (X25519 + Ed25519)
- Prometheus /metrics, per-IP rate limit, body-size cap

Deployment:
- Single-node compose (deploy/single/) with Caddy TLS + optional Prometheus
- 3-node dev compose (docker-compose.yml) with mocked internet topology
- 3-validator prod compose (deploy/prod/) for federation
- Auto-update from Gitea via /api/update-check + systemd timer
- Build-time version injection (ldflags → node --version)
- UI / Swagger toggle flags (DCHAIN_DISABLE_UI, DCHAIN_DISABLE_SWAGGER)

Client (client-app/):
- Expo / React Native / NativeWind
- E2E NaCl encryption, typing indicator, contact requests
- Auto-discovery of canonical contracts, chain_id aware, WS reconnect on node switch

Documentation:
- README.md, CHANGELOG.md, CONTEXT.md
- deploy/single/README.md with 6 operator scenarios
- deploy/UPDATE_STRATEGY.md with 4-layer forward-compat design
- docs/contracts/*.md per contract
This commit is contained in:
vsecoder
2026-04-17 14:16:44 +03:00
commit 7e7393e4f8
196 changed files with 55947 additions and 0 deletions

59
docs/contracts/README.md Normal file
View File

@@ -0,0 +1,59 @@
# Production Smart Contracts
DChain поставляется с четырьмя production-контрактами, задеплоенными из genesis-кошелька.
## Обзор
| Контракт | Назначение | Ключевые фичи |
|---------|-----------|--------------|
| [username_registry](username_registry.md) | Username ↔ адрес | Тарифная сетка, treasury fees, reverse lookup |
| [governance](governance.md) | On-chain параметры | Propose/approve workflow, admin role |
| [auction](auction.md) | English auction | Token escrow, автоматический refund, settle |
| [escrow](escrow.md) | Двусторонний escrow | Dispute/resolve, admin arbitration |
## Деплой
```bash
docker compose --profile deploy run --rm deploy
```
Все 4 контракта деплоятся автоматически. ID сохраняются в `/tmp/contracts.env`.
## Вызов контракта
```bash
docker exec node1 client call-contract \
--key /keys/node1.json \
--contract <CONTRACT_ID> \
--method <METHOD> \
--arg <STRING_ARG> # строковый аргумент (можно несколько)
--arg64 <UINT64_ARG> # числовой аргумент uint64
--gas <GAS_LIMIT> # рекомендуется 20000 для записи, 5000 для чтения
--node http://node1:8080
```
## Contract Treasury
У каждого контракта есть **ownerless treasury address**`hex(sha256(contractID + ":treasury"))`.
Это эскроу-адрес без private key. Только сам контракт может снять с него деньги через host function `transfer`.
Используется в `auction` и `escrow` для хранения заблокированных токенов.
## Просмотр состояния
```bash
# Через REST API
curl http://localhost:8081/api/contracts/<ID>/state/<key>
# Через Explorer
open http://localhost:8081/contract?id=<ID>
```
## Логи контракта
```bash
# REST
curl "http://localhost:8081/api/contracts/<ID>/logs?limit=20"
# Explorer → вкладка Logs
```

213
docs/contracts/auction.md Normal file
View File

@@ -0,0 +1,213 @@
# auction
English auction с on-chain token escrow. Ставки хранятся в contract treasury. При перебивании ставки предыдущий топ-биддер получает автоматический refund.
## Жизненный цикл
```
seller buyer-1 buyer-2 anyone
│ │ │ │
│─ create ──────▶│ │ │
│ (min_bid, │ │ │
│ duration) │ │ │
│ │ │ │
│ │─ bid(500) ───▶│ │
│ │ treasury ←500│ │
│ │ │ │
│ │ │─ bid(800) ───▶│
│ │ refund→500 │ treasury ←800│
│ │ │ │
│ │ │ │ [end_block reached]
│ │ │ │
│◀─────────────────────────────────── settle ────│
│ treasury→800 │ │ │
│ (seller gets │ │ │
│ winning bid) │ │ │
```
**Статусы:** `open``settled` / `cancelled`
## Auction ID
Формат: `<block_height>:<seq>`, например `42:0`, `42:1`.
Генерируется автоматически при `create`, логируется как `created: <id>`.
## Методы
### `create`
Создать новый аукцион.
**Аргументы:**
| # | Имя | Тип | Описание |
|---|-----|-----|---------|
| 0 | `title` | string | Описание лота (max 128 байт) |
| 1 | `min_bid` | uint64 | Минимальная ставка в µT |
| 2 | `duration` | uint64 | Длительность в блоках |
**Поведение:**
- Сохраняет seller = caller
- `end_block = current_block + duration`
- Лог: `created: <auction_id>`
```bash
# Аукцион: мин. ставка 1 T, длительность 100 блоков
client call-contract --method create \
--arg "Rare NFT #42" --arg64 1000000 --arg64 100 \
--contract $AUC_ID --key /keys/node1.json \
--gas 20000 --node http://node1:8080
```
---
### `bid`
Поставить ставку. Средства переводятся из кошелька caller в treasury контракта.
**Аргументы:**
| # | Имя | Тип | Описание |
|---|-----|-----|---------|
| 0 | `auction_id` | string | ID аукциона |
| 1 | `amount` | uint64 | Ставка в µT |
**Проверки:**
- Аукцион в статусе `open`
- `current_block ≤ end_block`
- `amount > current_top_bid` (или `amount ≥ min_bid` если ставок нет)
**Поведение:**
- Переводит `amount` с caller → treasury
- Если был предыдущий топ-биддер → refund ему его ставки из treasury
- Обновляет `top_bidder` и `top_bid`
- Лог: `bid: <auction_id>`
```bash
client call-contract --method bid \
--arg "42:0" --arg64 2000000 \
--contract $AUC_ID --key /keys/node1.json \
--gas 30000 --node http://node1:8080
```
---
### `settle`
Завершить аукцион после истечения `end_block`. Переводит топ-ставку продавцу.
**Аргументы:**
| # | Имя | Тип |
|---|-----|-----|
| 0 | `auction_id` | string |
**Проверки:** `current_block > end_block`, статус `open`
**Поведение:**
- Если есть ставки: переводит `top_bid` из treasury → seller, статус → `settled`
- Если ставок нет: статус → `cancelled`
- Лог: `settled: <id>` или `cancelled: <id> (no bids)`
- Может вызвать **любой** (не только seller)
```bash
client call-contract --method settle --arg "42:0" \
--contract $AUC_ID --key /keys/node1.json \
--gas 20000 --node http://node1:8080
```
---
### `cancel`
Отменить аукцион без ставок. Только seller.
**Аргументы:**
| # | Имя | Тип |
|---|-----|-----|
| 0 | `auction_id` | string |
**Проверки:** статус `open`, ставок нет, `caller == seller`
```bash
client call-contract --method cancel --arg "42:0" \
--contract $AUC_ID --key /keys/node1.json \
--gas 10000 --node http://node1:8080
```
---
### `info`
Запросить состояние аукциона.
**Аргументы:**
| # | Имя | Тип |
|---|-----|-----|
| 0 | `auction_id` | string |
**Логи:**
```
seller: <pubkey>
title: <title>
top_bid: <amount>
end_block: <block>
status: open|settled|cancelled
```
```bash
client call-contract --method info --arg "42:0" \
--contract $AUC_ID --key /keys/node1.json \
--gas 5000 --node http://node1:8080
```
---
## State Layout
```
cstate:<contractID>:seq → uint64 (глобальный счётчик аукционов)
cstate:<contractID>:a:<id>:s → seller pubkey
cstate:<contractID>:a:<id>:t → title
cstate:<contractID>:a:<id>:b → top_bid (uint64 big-endian)
cstate:<contractID>:a:<id>:e → end_block (uint64 big-endian)
cstate:<contractID>:a:<id>:w → top_bidder pubkey
cstate:<contractID>:a:<id>:x → status byte ('o', 's', 'c')
```
## Полный пример сценария
```bash
# 1. Alice создаёт аукцион
docker exec node1 client call-contract \
--key /keys/node1.json --contract $AUC_ID \
--method create \
--arg "Special Edition #1" --arg64 500000 --arg64 50 \
--gas 20000 --node http://node1:8080
# Лог: created: 5:0
AUC_ITEM="5:0"
# 2. Bob делает ставку 1 T
docker exec node1 client call-contract \
--key /tmp/bob.json --contract $AUC_ID \
--method bid --arg $AUC_ITEM --arg64 1000000 \
--gas 30000 --node http://node1:8080
# 3. Charlie перебивает ставку — Bob получает refund автоматически
docker exec node1 client call-contract \
--key /tmp/charlie.json --contract $AUC_ID \
--method bid --arg $AUC_ITEM --arg64 1500000 \
--gas 30000 --node http://node1:8080
# 4. Проверить статус
docker exec node1 client call-contract \
--key /keys/node1.json --contract $AUC_ID \
--method info --arg $AUC_ITEM \
--gas 5000 --node http://node1:8080
# 5. После end_block — завершить (любой может)
docker exec node1 client call-contract \
--key /keys/node1.json --contract $AUC_ID \
--method settle --arg $AUC_ITEM \
--gas 20000 --node http://node1:8080
# Лог: settled: 5:0
# Alice получает 1.5 T на кошелёк
```

267
docs/contracts/escrow.md Normal file
View File

@@ -0,0 +1,267 @@
# escrow
Двусторонний trustless escrow. Buyer блокирует средства в contract treasury. Seller выполняет условие. Buyer подтверждает или открывает спор. При споре admin-арбитр решает исход.
## Жизненный цикл
```
buyer seller admin
│ │ │
│─ create ─────▶│ │ treasury ← amount (locked)
│ (seller, │ │ status: active
│ amount) │ │
│ │ │
│ [seller delivered] │
│ │ │
│─ release ────▶│ │ treasury → seller
│ │ │ status: released
OR:
│─ dispute ────▶│ │ status: disputed
│ │─ dispute ────▶│ (either party can)
│ │ │
│ │ │─ resolve(winner=buyer) ──▶
│ │ │ treasury → buyer
│ │ │ status: refunded
│ │ │
│ │ │─ resolve(winner=seller) ──▶
│ │ │ treasury → seller
│ │ │ status: released
OR:
│ │─ refund ─────▶│ treasury → buyer (voluntary)
│ │ │ status: refunded
```
**Статусы:** `active``released` / `refunded` / `disputed`
## Методы
### `init`
Установить caller как admin (арбитр споров).
**Аргументы:** нет
Вызывается один раз после деплоя (deploy-скрипт делает автоматически).
```bash
client call-contract --method init \
--contract $ESC_ID --key /keys/node1.json \
--gas 5000 --node http://node1:8080
```
---
### `create`
Buyer создаёт escrow и блокирует средства.
**Аргументы:**
| # | Имя | Тип | Описание |
|---|-----|-----|---------|
| 0 | `id` | string | Уникальный ID эскроу (задаёт пользователь) |
| 1 | `seller` | string | Pubkey продавца (hex) |
| 2 | `amount` | uint64 | Сумма в µT |
**Поведение:**
- Переводит `amount` с caller (buyer) → treasury
- Сохраняет buyer, seller, amount, status=active
- Лог: `created: <id>`
**Ограничения:**
- ID должен быть уникальным — если `cstate[e:<id>:b]` уже есть, tx отклоняется
```bash
client call-contract --method create \
--arg "deal-001" \
--arg <SELLER_PUBKEY> \
--arg64 10000000 \
--contract $ESC_ID --key /tmp/buyer.json \
--gas 30000 --node http://node1:8080
```
---
### `release`
Buyer подтверждает получение и освобождает средства seller'у.
**Аргументы:**
| # | Имя | Тип |
|---|-----|-----|
| 0 | `id` | string |
**Права:** только buyer.
**Статус:** должен быть `active`.
**Поведение:**
- Переводит `amount` с treasury → seller
- Статус → released
- Лог: `released: <id>`
```bash
client call-contract --method release --arg "deal-001" \
--contract $ESC_ID --key /tmp/buyer.json \
--gas 20000 --node http://node1:8080
```
---
### `refund`
Seller добровольно возвращает деньги buyer'у.
**Аргументы:**
| # | Имя | Тип |
|---|-----|-----|
| 0 | `id` | string |
**Права:** только seller.
**Статус:** должен быть `active`.
**Поведение:**
- Переводит `amount` с treasury → buyer
- Статус → refunded
- Лог: `refunded: <id>`
```bash
client call-contract --method refund --arg "deal-001" \
--contract $ESC_ID --key /tmp/seller.json \
--gas 20000 --node http://node1:8080
```
---
### `dispute`
Открыть спор. Может вызвать buyer или seller.
**Аргументы:**
| # | Имя | Тип |
|---|-----|-----|
| 0 | `id` | string |
**Статус:** должен быть `active`.
**Поведение:**
- Статус → disputed
- Лог: `disputed: <id>`
- Средства остаются заблокированы в treasury
```bash
client call-contract --method dispute --arg "deal-001" \
--contract $ESC_ID --key /tmp/buyer.json \
--gas 10000 --node http://node1:8080
```
---
### `resolve`
Admin разрешает спор.
**Аргументы:**
| # | Имя | Тип | Значения |
|---|-----|-----|---------|
| 0 | `id` | string | ID эскроу |
| 1 | `winner` | string | `buyer` или `seller` |
**Права:** только admin.
**Статус:** должен быть `disputed`.
**Поведение:**
- `winner=buyer` → treasury → buyer, статус → refunded
- `winner=seller` → treasury → seller, статус → released
- Лог: `resolved: <id>`
```bash
# Admin решает в пользу buyer
client call-contract --method resolve \
--arg "deal-001" --arg buyer \
--contract $ESC_ID --key /keys/node1.json \
--gas 20000 --node http://node1:8080
# Admin решает в пользу seller
client call-contract --method resolve \
--arg "deal-001" --arg seller \
--contract $ESC_ID --key /keys/node1.json \
--gas 20000 --node http://node1:8080
```
---
### `info`
Запросить состояние эскроу.
**Аргументы:**
| # | Имя | Тип |
|---|-----|-----|
| 0 | `id` | string |
**Логи:**
```
buyer: <pubkey>
seller: <pubkey>
amount: <µT>
status: active|released|refunded|disputed
```
```bash
client call-contract --method info --arg "deal-001" \
--contract $ESC_ID --key /keys/node1.json \
--gas 5000 --node http://node1:8080
```
---
## State Layout
```
cstate:<contractID>:admin → admin pubkey
cstate:<contractID>:e:<id>:b → buyer pubkey
cstate:<contractID>:e:<id>:s → seller pubkey
cstate:<contractID>:e:<id>:a → amount (uint64 big-endian)
cstate:<contractID>:e:<id>:x → status byte ('a'=active, 'd'=disputed, 'r'=released, 'f'=refunded)
```
## Полный сценарий с dispute
```bash
# Параметры
BUYER_KEY=/tmp/buyer.json
SELLER_KEY=/tmp/seller.json
ADMIN_KEY=/keys/node1.json
ID="escrow-001"
# 1. Buyer создаёт эскроу на 5 T
docker exec node1 client call-contract \
--key $BUYER_KEY --contract $ESC_ID \
--method create \
--arg $ID --arg $SELLER_PUB --arg64 5000000 \
--gas 30000 --node http://node1:8080
# 2. Проверить статус
docker exec node1 client call-contract \
--key $BUYER_KEY --contract $ESC_ID \
--method info --arg $ID \
--gas 5000 --node http://node1:8080
# status: active
# 3. Buyer не доволен — открывает спор
docker exec node1 client call-contract \
--key $BUYER_KEY --contract $ESC_ID \
--method dispute --arg $ID \
--gas 10000 --node http://node1:8080
# 4. Admin рассматривает дело и решает в пользу seller
docker exec node1 client call-contract \
--key $ADMIN_KEY --contract $ESC_ID \
--method resolve --arg $ID --arg seller \
--gas 20000 --node http://node1:8080
# Лог: resolved: escrow-001
# Seller получает 5 T
```

View File

@@ -0,0 +1,203 @@
# governance
On-chain governance контракт для управления параметрами сети. Deployer становится admin. Любой может предложить изменение параметра; admin принимает или отклоняет.
## Концепция
```
Участник Admin
│ │
│── propose ──▶ │ state[prop:<key>] = value
│ │
│ │── approve ──▶ state[param:<key>] = value (live)
│ │ удаляет prop:<key>
│ │
│ │── reject ──▶ удаляет prop:<key>
```
После approve параметр становится **live** и читается нодой при следующем вызове контракта.
## Методы
### `init`
Инициализировать контракт, установить caller как admin.
**Аргументы:** нет
**Вызывается:** один раз после деплоя (deploy-скрипт делает это автоматически).
**Поведение:**
- Устанавливает `state[admin] = caller_pubkey`
- Идемпотентен: повторный вызов не меняет admin если уже установлен
```bash
client call-contract --method init --contract $GOV_ID \
--key /keys/node1.json --gas 5000 --node http://node1:8080
```
---
### `propose`
Предложить новое значение параметра. Доступно всем.
**Аргументы:**
| # | Имя | Тип | Ограничение |
|---|-----|-----|------------|
| 0 | `key` | string | max 64 байта |
| 1 | `value` | string | max 256 байт |
**Поведение:**
- Сохраняет `state[prop:<key>] = value`
- Лог: `proposed: <key>=<value>`
- Если pending-предложение уже есть → перезаписывает
```bash
# Предложить новую цену газа 5 µT/gas
client call-contract --method propose \
--arg gas_price --arg 5 \
--contract $GOV_ID --key /keys/node1.json \
--gas 10000 --node http://node1:8080
```
---
### `approve`
Admin принимает pending-предложение. Параметр становится live.
**Аргументы:**
| # | Имя | Тип |
|---|-----|-----|
| 0 | `key` | string |
**Права:** только admin.
**Поведение:**
- Копирует `state[prop:<key>]``state[param:<key>]`
- Удаляет `state[prop:<key>]`
- Лог: `approved: <key>`
- Если нет pending → лог `no pending: <key>`
```bash
client call-contract --method approve --arg gas_price \
--contract $GOV_ID --key /keys/node1.json \
--gas 10000 --node http://node1:8080
```
---
### `reject`
Admin отклоняет pending-предложение.
**Аргументы:**
| # | Имя | Тип |
|---|-----|-----|
| 0 | `key` | string |
**Права:** только admin.
```bash
client call-contract --method reject --arg gas_price \
--contract $GOV_ID --key /keys/node1.json \
--gas 5000 --node http://node1:8080
```
---
### `get`
Прочитать текущее live-значение параметра.
**Аргументы:**
| # | Имя | Тип |
|---|-----|-----|
| 0 | `key` | string |
**Лог:**
- `value: <value>` — параметр установлен
- `not set: <key>` — параметр не установлен
```bash
client call-contract --method get --arg gas_price \
--contract $GOV_ID --key /keys/node1.json \
--gas 3000 --node http://node1:8080
```
Через REST (без транзакции):
```bash
curl http://localhost:8081/api/contracts/$GOV_ID/state/param:gas_price
```
---
### `get_pending`
Прочитать pending-предложение.
**Аргументы:**
| # | Имя | Тип |
|---|-----|-----|
| 0 | `key` | string |
**Лог:**
- `pending: <value>`
- `no pending: <key>`
---
### `set_admin`
Передать роль admin другому адресу.
**Аргументы:**
| # | Имя | Тип |
|---|-----|-----|
| 0 | `new_admin` | string (hex pubkey) |
**Права:** только текущий admin.
```bash
client call-contract --method set_admin --arg <NEW_ADMIN_PUBKEY> \
--contract $GOV_ID --key /keys/node1.json \
--gas 10000 --node http://node1:8080
```
---
## Управляемые параметры
Governance хранит произвольные string-параметры. Нода читает следующие:
| Ключ | Тип | Описание | По умолчанию |
|------|-----|---------|-------------|
| `gas_price` | uint64 (строка) | µT за 1 gas unit | 1 µT |
> Нода читает `gas_price` при каждом `CALL_CONTRACT` через `chain.GetEffectiveGasPrice()`.
> Изменение вступает в силу немедленно после `approve` — без перезапуска.
Можно хранить любые параметры приложения (messenger_entry_fee, relay_fee, etc.) и читать их из других контрактов через межконтрактные вызовы.
## State Layout
```
cstate:<contractID>:admin → admin pubkey (hex string bytes)
cstate:<contractID>:param:<key> → live value
cstate:<contractID>:prop:<key> → pending proposal
```
## Интеграция с нодой
После деплоя governance необходимо его **привязать** к ноде:
```bash
# Автоматически через deploy-скрипт
# Или вручную:
curl -X POST http://localhost:8081/api/governance/link \
-H "Content-Type: application/json" \
-d "{\"governance\": \"$GOV_ID\"}"
```
Подробнее: [Governance интеграция](../node/governance.md)

View File

@@ -0,0 +1,133 @@
# username_registry
Привязка читаемых `@username` к адресам (Ed25519 pubkey). Forward
(`name → addr`) и reverse (`addr → name`) lookup, передача ownership,
освобождение имени.
**Реализация: native Go** (`blockchain/native_username.go`). Работает
без WASM VM — нулевая латентность, не может повесить AddBlock. Старая
WASM-версия заменена; клиенты получают канонический ID через
`/api/well-known-contracts`.
## Ключевые свойства
| | |
|---|---|
| **Contract ID** | `native:username_registry` |
| **Version** | `2.1.0-native` |
| **Регистрация** | Flat fee 10 000 µT (0.01 T), **burn** — никому не идёт |
| **Ограничения имени** | 432 символа, `a-z 0-9 _ -`, первый символ — буква |
| **Зарезервированные** | `system`, `admin`, `root`, `dchain`, `null`, `none` |
| **Один адрес — одно имя** | Чтобы сменить, нужно `release` + `register` |
Комиссия 10 000 µT выбрана как баланс: достаточно дёшево для реальных
пользователей, достаточно дорого чтобы один ID-squatter не забил тысячу
имён из скучающего бота.
## Оплата
Плата передаётся в `tx.Amount` (видна в истории транзакций), контракт
проверяет её точное значение и вычитает из баланса отправителя. Не
направляется никуда — токены сгорают.
Дополнительно платится `tx.Fee = 1 000 µT` за саму транзакцию — это
network fee валидатору, стандартный `MinFee` для любой tx.
Итого: `register(name)` стоит **11 000 µT ≈ 0.011 T**.
## Методы
### `register(name)` — payable 10000
Зарегистрировать имя для `tx.From`.
**Проверки** (в порядке):
1. `name` валиден (длина, символы, не reserved)
2. `tx.Amount == 10 000` (ошибка `register requires tx.amount = 10000 µT`)
3. Имя не занято
4. `tx.From` не владеет другим именем
5. Баланс отправителя достаточен для `amount + fee + gas`
**Успех:**
- Списывает 10 000 µT с баланса (burn)
- `cstate[name:<name>] = tx.From`
- `cstate[addr:<tx.From>] = name`
- Лог `registered: <name> → <pubkey>`
**CLI-пример:**
```bash
client call-contract \
--key my.json \
--contract native:username_registry \
--method register \
--args '["alice"]' \
--amount 10000 \
--node http://localhost:8081
```
**Клиент-приложение** делает это автоматически через Settings →
«Купить никнейм».
### `resolve(name)` — free
Прочитать владельца имени.
Логирует `owner: <pubkey>` или `not found: <name>`. Клиент в HTTP-API
обычно использует прямое чтение state:
```
GET /api/contracts/native:username_registry/state/name:alice
→ { value_hex: "<hex of owner pubkey ASCII>" }
```
### `lookup(address)` — free
Обратный lookup. Логирует `name: <name>` или `no name: <address>`.
Клиент использует `GET .../state/addr:<pubkey>`.
### `transfer(name, new_owner)` — free
Передать `name` другому адресу. Только текущий владелец может вызвать.
Новый владелец не должен уже иметь имя.
### `release(name)` — free
Удалить привязку. Только текущий владелец. Освобождает `name` и
`addr:<caller>` для будущих регистраций.
## State layout
Все ключи записываются в общий namespace `cstate:native:username_registry:`:
| Key | Value |
|-----|-------|
| `name:<name>` | Hex pubkey владельца (64 символа) |
| `addr:<pubkey>` | ASCII-строка с именем |
Значения хранятся как байты; HTTP endpoint
`/api/contracts/:id/state/:key` возвращает их в `value_hex`, клиент
делает `hex → UTF-8` перед показом.
## Клиентская интеграция
- `useWellKnownContracts` хук авто-синхронизирует `settings.contractId =
native:username_registry`
- `resolveUsername(contractId, name)` и `reverseResolve(contractId, addr)`
в `client-app/lib/api.ts` делают HTTP-запрос + hex-decode
- Settings экран покупает никнейм через `buildCallContractTx({amount:
10_000, ...})` + автоматический `reverseResolve` polling после submit
для показа `@name` в профиле за ~1 блок
## Отличия от предыдущей WASM-версии
| | WASM v1 | Native v2 |
|---|---------|-----------|
| **Fee** | 10^(7-len), tiered | Flat 10 000 µT |
| **Fee destination** | Contract treasury | Burn |
| **Min length** | 1 | **4** |
| **Payment point** | Debited inside contract, invisible in tx | `tx.Amount`, visible in history |
| **Latency** | ~10 ms (wazero startup) | ~50 µs (direct Go call) |
| **VM hang risk** | Да (требует timeout) | Нет |
Зарегистрированные в старой WASM-версии имена не мигрируются
автоматически — операторам нужно пере-зарегистрировать после reset
chain (см. `CHANGELOG.md` «Compatibility notes»).