Разработка платформы в стиле friend.tech

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка платформы в стиле friend.tech
Сложная
от 2 недель до 3 месяцев
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1221
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1163
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    855
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1062
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    561
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    828

Разработка платформы в стиле friend.tech

Friend.tech запустился в августе 2023 на Base, сгенерировал $50M+ в fees за первые месяцы и задал шаблон для целого класса SocialFi приложений: токенизированный доступ к людям. Механика: пользователь привязывает Twitter-аккаунт, другие покупают его «ключи» (shares), цена ключей растёт по bonding curve, держатели ключей получают доступ к приватному чату или контенту. Просто, жадно эффективно с точки зрения retention, и технически интересно.

Копировать friend.tech буквально — проигрышная стратегия (аудитория ушла, рынок насыщен). Но механика bonding curve + gated access применима к десяткам вертикалей: expert networks, fan platforms, creator economy, permissioned DAOs. Разберём как это строится правильно.

Bonding Curve: математика и реализация

Цена ключа определяется количеством уже выпущенных ключей по формуле. Friend.tech использовал:

price(n) = n² / 16000 ETH

где n — текущее количество ключей. Это polynomial curve — цена растёт квадратично. Это создаёт сильный FOMO для ранних покупателей и экспоненциально высокую цену при большом supply.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract SocialBondingCurve {
    // Маппинг субъект → количество выпущенных ключей
    mapping(address => uint256) public sharesSupply;
    // Маппинг субъект → holder → количество ключей
    mapping(address => mapping(address => uint256)) public sharesBalance;
    
    address public protocolFeeDestination;
    uint256 public protocolFeePercent;   // в basis points
    uint256 public subjectFeePercent;    // fee идёт субъекту (owner ключей)
    
    event Trade(
        address indexed trader,
        address indexed subject,
        bool isBuy,
        uint256 shareAmount,
        uint256 ethAmount,
        uint256 protocolEthAmount,
        uint256 subjectEthAmount,
        uint256 supply
    );
    
    // Цена n-го ключа по polynomial curve
    function getPrice(uint256 supply, uint256 amount) public pure returns (uint256) {
        uint256 sum1 = supply == 0 ? 0 : (supply - 1) * supply * (2 * (supply - 1) + 1) / 6;
        uint256 sum2 = (supply + amount - 1) * (supply + amount) * (2 * (supply + amount - 1) + 1) / 6;
        uint256 summation = sum2 - sum1;
        return summation * 1 ether / 16000;
    }
    
    function getBuyPrice(address sharesSubject, uint256 amount) public view returns (uint256) {
        return getPrice(sharesSupply[sharesSubject], amount);
    }
    
    function getSellPrice(address sharesSubject, uint256 amount) public view returns (uint256) {
        return getPrice(sharesSupply[sharesSubject] - amount, amount);
    }
    
    function getBuyPriceAfterFee(address sharesSubject, uint256 amount) public view returns (uint256) {
        uint256 price = getBuyPrice(sharesSubject, amount);
        uint256 protocolFee = price * protocolFeePercent / 1 ether;
        uint256 subjectFee = price * subjectFeePercent / 1 ether;
        return price + protocolFee + subjectFee;
    }
    
    function buyShares(address sharesSubject, uint256 amount) external payable {
        uint256 supply = sharesSupply[sharesSubject];
        // Первый ключ может купить только сам субъект (bootstrap)
        require(supply > 0 || sharesSubject == msg.sender, "Only subject can buy first share");
        
        uint256 price = getPrice(supply, amount);
        uint256 protocolFee = price * protocolFeePercent / 1 ether;
        uint256 subjectFee = price * subjectFeePercent / 1 ether;
        
        require(msg.value >= price + protocolFee + subjectFee, "Insufficient ETH");
        
        sharesBalance[sharesSubject][msg.sender] += amount;
        sharesSupply[sharesSubject] = supply + amount;
        
        emit Trade(msg.sender, sharesSubject, true, amount, price, protocolFee, subjectFee, supply + amount);
        
        (bool success1, ) = protocolFeeDestination.call{value: protocolFee}("");
        (bool success2, ) = sharesSubject.call{value: subjectFee}("");
        require(success1 && success2, "Unable to send funds");
        
        // Возврат излишка
        if (msg.value > price + protocolFee + subjectFee) {
            (bool refundSuccess, ) = msg.sender.call{value: msg.value - price - protocolFee - subjectFee}("");
            require(refundSuccess, "Refund failed");
        }
    }
    
    function sellShares(address sharesSubject, uint256 amount) external {
        uint256 supply = sharesSupply[sharesSubject];
        require(supply > amount, "Cannot sell the last share");
        
        uint256 price = getPrice(supply - amount, amount);
        uint256 protocolFee = price * protocolFeePercent / 1 ether;
        uint256 subjectFee = price * subjectFeePercent / 1 ether;
        
        require(sharesBalance[sharesSubject][msg.sender] >= amount, "Insufficient shares");
        
        sharesBalance[sharesSubject][msg.sender] -= amount;
        sharesSupply[sharesSubject] = supply - amount;
        
        emit Trade(msg.sender, sharesSubject, false, amount, price, protocolFee, subjectFee, supply - amount);
        
        uint256 netAmount = price - protocolFee - subjectFee;
        (bool success, ) = msg.sender.call{value: netAmount}("");
        require(success, "Unable to send funds");
        
        (bool success1, ) = protocolFeeDestination.call{value: protocolFee}("");
        (bool success2, ) = sharesSubject.call{value: subjectFee}("");
        require(success1 && success2, "Fee transfer failed");
    }
}

Проблема polynomial curve: при большом supply ключи становятся astronomically дорогими. Для нишевых создателей — это работает (ключ «звезды» стоит дорого — окей). Для широкого рынка — барьер входа убивает рост. Альтернативы:

Linear curve: price = base_price + supply × slope — предсказуема, но нет FOMO

Sigmoid curve: сначала быстрый рост (FOMO), потом плато — более управляемая экономика для mass market

// Sigmoid-based price (аппроксимация)
function getSigmoidPrice(uint256 supply, uint256 amount) public pure returns (uint256) {
    // k = steepness, midpoint = inflection point
    uint256 k = 100;
    uint256 midpoint = 1000;  // supply при котором половина max_price
    uint256 maxPrice = 1 ether;
    
    // Упрощённая аппроксимация sigmoid через piece-wise linear
    if (supply < midpoint / 4) {
        return supply * maxPrice / (4 * midpoint);  // нижняя часть
    } else if (supply < 3 * midpoint / 4) {
        return maxPrice / 4 + (supply - midpoint/4) * maxPrice / (2 * midpoint);  // средняя
    } else {
        return 3 * maxPrice / 4 + (supply - 3*midpoint/4) * maxPrice / (8 * midpoint);  // верхняя
    }
}

Gated Access: ключи как access tokens

Держатели ключей получают доступ к контенту. Верификация off-chain — самый практичный подход:

// Backend: верификация доступа через подпись
import { ethers } from 'ethers'

async function verifyAccess(
  userAddress: string,
  subjectAddress: string,
  signature: string,
  nonce: string
): Promise<boolean> {
  // Верифицируем подпись (EIP-712)
  const message = {
    user: userAddress,
    subject: subjectAddress,
    nonce,
    timestamp: Math.floor(Date.now() / 1000),
  }
  
  const recoveredAddress = ethers.verifyTypedData(
    DOMAIN,
    TYPES,
    message,
    signature
  )
  
  if (recoveredAddress.toLowerCase() !== userAddress.toLowerCase()) {
    return false
  }
  
  // Проверяем on-chain баланс ключей
  const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, provider)
  const balance = await contract.sharesBalance(subjectAddress, userAddress)
  
  return balance > 0n
}

Fully on-chain gating через modifier:

modifier onlyKeyHolder(address subject) {
    require(sharesBalance[subject][msg.sender] > 0, "No key");
    _;
}

function sendPrivateMessage(address subject, bytes calldata encryptedContent) 
    external 
    onlyKeyHolder(subject) 
{
    emit PrivateMessage(subject, msg.sender, encryptedContent);
}

Для реального приватного чата — контент шифруется публичным ключом получателей off-chain (например, через lit-protocol или кастомный threshold encryption), on-chain только события.

Social Graph и верификация идентичности

Friend.tech использовал Twitter для верификации. Это создаёт проблему: Twitter мог деавторизовать OAuth — и платформа теряла social graph. Более resilient подходы:

Lens Protocol интеграция — децентрализованный social graph на Polygon. Profile = NFT, followers — on-chain. Ключи привязаны к Lens Profile ID, а не к Ethereum адресу:

// Ключи привязаны к Lens profile
mapping(uint256 => mapping(address => uint256)) public profileSharesBalance;
mapping(uint256 => uint256) public profileSharesSupply;

function buyProfileShares(uint256 profileId, uint256 amount) external payable {
    // Верифицируем владельца profile через Lens Hub
    address profileOwner = lensHub.ownerOf(profileId);
    // fees идут profileOwner
    // ...
}

Farcaster интеграция — другой децентрализованный social protocol. Farcaster ID (FID) используется вместо адреса:

mapping(uint256 => mapping(address => uint256)) public fidSharesBalance;  // fid → buyer → amount

function verifyFidOwnership(uint256 fid, address claimer, bytes calldata proof) external {
    // Верифицируем через Farcaster key registry
    require(farcasterKeyRegistry.isSigner(fid, claimer), "Not FID owner");
    fidOwners[fid] = claimer;
}

MEV и front-running защита

Bonding curve транзакции уязвимы к sandwich атакам: бот видит вашу buy транзакцию в mempool, покупает перед вами, продаёт после, забирает разницу.

Minimum output protection:

function buySharesWithProtection(
    address sharesSubject,
    uint256 amount,
    uint256 maxPrice  // максимальная цена которую готов заплатить
) external payable {
    uint256 price = getBuyPriceAfterFee(sharesSubject, amount);
    require(price <= maxPrice, "Price too high (slippage)");
    require(msg.value >= price, "Insufficient ETH");
    // ... логика покупки
}

Commit-reveal для крупных покупок:

mapping(bytes32 => address) public pendingBuys;
mapping(bytes32 => uint256) public commitBlocks;

function commitBuy(bytes32 commitment) external payable {
    pendingBuys[commitment] = msg.sender;
    commitBlocks[commitment] = block.number;
}

function revealBuy(
    address subject,
    uint256 amount,
    bytes32 salt
) external {
    bytes32 commitment = keccak256(abi.encodePacked(subject, amount, salt, msg.sender));
    require(pendingBuys[commitment] == msg.sender, "No commit");
    require(block.number > commitBlocks[commitment], "Same block");
    require(block.number <= commitBlocks[commitment] + 10, "Expired");
    
    delete pendingBuys[commitment];
    // ... выполняем покупку
}

Расширения базовой механики

Subscription модель: помимо ключей — ежемесячная подписка за доступ к контенту. Держатели ключей получают бессрочный доступ, остальные — через recurring payment.

mapping(address => mapping(address => uint256)) public subscriptionExpiry;

function subscribe(address subject) external payable {
    uint256 price = getSubscriptionPrice(subject);
    require(msg.value >= price, "Insufficient ETH");
    
    // Продлеваем или устанавливаем подписку
    uint256 currentExpiry = subscriptionExpiry[subject][msg.sender];
    uint256 newExpiry = max(currentExpiry, block.timestamp) + 30 days;
    subscriptionExpiry[subject][msg.sender] = newExpiry;
    
    // Distribute: 80% subject, 20% protocol
    payable(subject).transfer(msg.value * 80 / 100);
}

function hasAccess(address subject, address user) public view returns (bool) {
    return sharesBalance[subject][user] > 0 || 
           subscriptionExpiry[subject][user] > block.timestamp;
}

Referral механика — friend.tech давал referral fees. Простая реализация:

mapping(address => address) public referrers;

function buySharesWithReferral(
    address sharesSubject,
    uint256 amount,
    address referrer
) external payable {
    if (referrers[msg.sender] == address(0) && referrer != msg.sender) {
        referrers[msg.sender] = referrer;
    }
    
    // При распределении fees часть идёт referrer
    address ref = referrers[msg.sender];
    if (ref != address(0)) {
        uint256 referralFee = protocolFee * referralPercent / 100;
        payable(ref).transfer(referralFee);
        protocolFee -= referralFee;
    }
    // ...
}

Выбор сети

Base — правильный выбор как и для оригинального friend.tech. Дёшево ($0.001–0.01 за транзакцию), EVM, Coinbase onramp (критично для масс-маркета), активная SocialFi экосистема. Альтернатива — Arbitrum, если нужна более зрелая DeFi экосистема вокруг.

Этапы разработки

Фаза Содержание Срок
Design Bonding curve параметры, fee структура, access mechanics 1–2 нед
Core contracts Bonding curve, access control, fee distribution 3–4 нед
Social integration Twitter/Farcaster/Lens OAuth или smart wallet 2–3 нед
Backend API, notifications, encrypted messaging 3–4 нед
Mobile-first frontend Web app (PWA) + wallet connection 4–5 нед
Anti-MEV & security Slippage protection, audit 2–3 нед
Launch Testnet pilot, influencer seeding 2–3 нед

Итого: 17–24 недели. Ключевой фактор успеха — не техника, а bootstrap стратегия: первые 20–30 создателей с аудиторией определяют traction. Техническую часть разработаем, с bootstrap нужна команда на стороне клиента.