Разработка системы P2P-торговли криптовалютой
P2P (peer-to-peer) торговля криптовалютой — это платформа, где пользователи торгуют напрямую между собой, а система выступает escrow и арбитром. В отличие от обычной биржи, здесь нет matching engine — есть объявления, переговоры и dispute resolution. Технически это проще биржи, но организационно сложнее: фрод-менеджмент, репутационная система и защита пользователей — критические компоненты.
Архитектура P2P системы
Основные сущности
Объявление (Offer)
├── Пользователь (maker)
├── Тип (buy/sell)
├── Криптовалюта
├── Фиатная валюта
├── Способы оплаты (методы)
├── Курс (фиксированный или привязанный к рынку)
├── Лимиты (min/max сумма)
└── Условия и требования (только верифицированные, мин. рейтинг)
Сделка (Trade)
├── Объявление (ссылка)
├── Maker + Taker
├── Сумма крипты
├── Сумма фиата
├── Статус (created → paid → released | disputed | cancelled)
├── Escrow адрес
└── История сообщений
Escrow механизм
Escrow — сердце P2P системы. Продавец блокирует криптовалюту в escrow-контракте до подтверждения оплаты. Это защищает покупателя от скама.
On-chain escrow (смарт-контракт):
contract P2PEscrow {
enum TradeStatus { Active, Completed, Disputed, Cancelled }
struct Trade {
address seller;
address buyer;
address token;
uint256 amount;
uint256 releaseTimeout; // автоматический release через N часов
TradeStatus status;
bool sellerConfirmed;
bool buyerPaid;
}
mapping(bytes32 => Trade) public trades;
event TradeCreated(bytes32 tradeId, address seller, address buyer, uint256 amount);
event TradeCompleted(bytes32 tradeId);
event TradeDisputed(bytes32 tradeId);
function createTrade(
bytes32 tradeId,
address buyer,
address token,
uint256 amount,
uint256 timeoutHours
) external {
IERC20(token).transferFrom(msg.sender, address(this), amount);
trades[tradeId] = Trade({
seller: msg.sender,
buyer: buyer,
token: token,
amount: amount,
releaseTimeout: block.timestamp + timeoutHours * 1 hours,
status: TradeStatus.Active,
sellerConfirmed: false,
buyerPaid: false
});
emit TradeCreated(tradeId, msg.sender, buyer, amount);
}
function confirmRelease(bytes32 tradeId) external {
Trade storage trade = trades[tradeId];
require(msg.sender == trade.seller, "Not seller");
require(trade.status == TradeStatus.Active, "Invalid status");
trade.status = TradeStatus.Completed;
IERC20(trade.token).transfer(trade.buyer, trade.amount);
emit TradeCompleted(tradeId);
}
function openDispute(bytes32 tradeId) external {
Trade storage trade = trades[tradeId];
require(
msg.sender == trade.buyer || msg.sender == trade.seller,
"Not party"
);
trade.status = TradeStatus.Disputed;
emit TradeDisputed(tradeId);
}
// Арбитр (мультисиг) решает спор
function resolveDispute(bytes32 tradeId, address winner) external onlyArbitrator {
Trade storage trade = trades[tradeId];
require(trade.status == TradeStatus.Disputed);
trade.status = TradeStatus.Completed;
IERC20(trade.token).transfer(winner, trade.amount);
}
}
Custodial escrow (централизованный): биржа хранит средства на своих кошельках. Технически проще, быстрее, дешевле (нет газа). Биржа несёт полную кастодиальную ответственность. Большинство крупных P2P платформ (LocalBitcoins, Binance P2P) используют именно этот подход.
Ценообразование: фиксированное vs плавающее
Фиксированная цена: продавец указывает конкретный курс. Может устареть к моменту сделки.
Плавающая цена (market-linked): курс = spot цена × (1 + margin%). Например: "Продаю BTC за 102% от Binance spot цены".
def calculate_offer_price(
reference_price: float,
margin_percent: float,
side: str
) -> float:
"""
side='sell': продавец просит premium над рынком
side='buy': покупатель предлагает дисконт
"""
if side == 'sell':
return reference_price * (1 + margin_percent / 100)
else:
return reference_price * (1 - margin_percent / 100)
Reference price агрегируется из нескольких источников (Binance, CoinGecko) с median для защиты от манипуляций.
Защита от фрода
Типичные схемы мошенничества
Chargeback fraud: покупатель платит через reversible метод (PayPal Friends & Family всё равно могут быть возвращены через спор с банком), получает крипту, затем инициирует chargeback.
Counterfeit payment proof: поддельный скриншот об оплате. Продавца давят на скорейший release.
Triangle fraud: A убеждает B продать крипту C, B получает деньги от реальной жертвы (которая думает что покупает что-то другое), скам разворачивается против жертвы.
Account takeover: угон аккаунта с хорошей репутацией для проведения скам-сделок.
Технические защиты
Методы оплаты риск-скоринг:
| Метод | Chargeback risk | Рекомендация |
|---|---|---|
| Банковский перевод (SEPA) | Низкий | Разрешить |
| Наличные лично | Нет | Разрешить |
| PayPal (G&S) | Высокий | Запретить/предупреждение |
| Western Union | Средний | Ограниченно |
| Revolute | Средний | Зависит от страны |
| Crypto-to-crypto | Нет | Разрешить |
KYC верификация: обязательная верификация личности для любой суммы выше порога. SMS верификация недостаточна — нужен ID + selfie.
Репутационная система:
class ReputationScore:
def calculate(self, user_id: str) -> dict:
stats = self.db.get_user_stats(user_id)
completed = stats['completed_trades']
cancelled = stats['cancelled_trades']
disputed_won = stats['disputes_won']
disputed_lost = stats['disputes_lost']
days_active = stats['days_active']
# Completion rate
total = completed + cancelled
completion_rate = completed / total if total > 0 else 0
# Dispute rate (процент сделок с диспутом)
dispute_rate = (disputed_won + disputed_lost) / completed if completed > 0 else 0
score = (
completion_rate * 40 + # 0-40 баллов
(1 - dispute_rate) * 30 + # 0-30 баллов
min(completed / 100, 1) * 20 + # до 20 баллов за объём
min(days_active / 365, 1) * 10 # до 10 баллов за стаж
)
return {
"score": round(score * 100),
"level": self.get_level(score),
"completed": completed,
"completion_rate": completion_rate,
"dispute_rate": dispute_rate
}
Поведенческий анализ (anti-fraud ML):
- Нестандартное время активности
- Множество аккаунтов с одного IP/устройства
- Необычно высокий объём для нового аккаунта
- Паттерны, совпадающие с известными фрод-схемами
Dispute resolution процесс
Workflow спора:
1. Открытие спора (покупатель или продавец)
2. Автоматическое уведомление обеих сторон
3. 24 часа на представление доказательств:
- Скриншоты переписки
- Банковские выписки
- Видео/фото при личной встрече
4. Арбитр рассматривает дело
5. Решение арбитра → release или return
6. Апелляция (опционально, для крупных сумм)
Арбитраж — дорогостоящий процесс при масштабе. Большинство платформ автоматизируют решение простых случаев (например: покупатель прислал bank statement, продавец молчит более 12 часов → автоматический release в пользу покупателя).
Чат и коммуникация
End-to-end encrypted чат
Для P2P сделок желателен E2E чат. Реализация через Signal Protocol или Libsodium:
// Клиентская генерация ключевой пары
const keyPair = nacl.box.keyPair();
// Обмен публичными ключами через сервер
// Шифрование сообщения публичным ключом получателя
function encryptMessage(message, recipientPublicKey, senderSecretKey) {
const nonce = nacl.randomBytes(nacl.box.nonceLength);
const messageBytes = nacl.util.decodeUTF8(message);
const encrypted = nacl.box(
messageBytes,
nonce,
recipientPublicKey,
senderSecretKey
);
return {
nonce: nacl.util.encodeBase64(nonce),
ciphertext: nacl.util.encodeBase64(encrypted)
};
}
Сервер хранит только зашифрованные сообщения — не может читать переписку. Важно для соответствия GDPR и доверия пользователей.
Хранение доказательств для диспутов: сообщения хранятся в базе с подписью сервера о времени отправки. При открытии спора оба участника дают согласие на передачу истории чата арбитру.
Фиатные методы оплаты
Интеграция банковских переводов
Для регионов с доступным instant banking (SEPA Instant в EU, Faster Payments в UK, PIX в Бразилии) можно автоматизировать подтверждение оплаты:
class BankingWebhook:
async def handle_sepa_notification(self, event: dict):
"""Обработка webhook от банка при получении SEPA перевода"""
amount = event['amount']
reference = event['reference'] # структурированный референс
# Парсим trade_id из референса (продавец указал его покупателю)
trade_id = self.parse_trade_reference(reference)
if not trade_id:
return
trade = await self.db.get_trade(trade_id)
if abs(trade.fiat_amount - amount) < 0.01: # допуск $0.01
await self.auto_confirm_payment(trade_id)
await self.notify_seller(trade.seller_id, trade_id)
Автоматическое подтверждение через банковский webhook резко снижает нагрузку на службу поддержки и ускоряет release.
Технический стек
| Компонент | Технология |
|---|---|
| Backend API | Node.js / Go |
| Database | PostgreSQL (сделки, пользователи) |
| Real-time чат | WebSocket (Socket.io) |
| Escrow | Смарт-контракт или custody DB |
| KYC | Sumsub / Sum&Substance |
| Fraud detection | Python ML pipeline |
| Notifications | Firebase FCM + email |
| Фиатная интеграция | Banking API / Webhooks |
P2P платформа технически проще CEX, но требует зрелых операционных процессов: команды поддержки, арбитров, мониторинга фрода. Недооценка операционной стороны — главная причина провала P2P проектов.







