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

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка платформи в стилі friend.tech
Складний
від 2 тижнів до 3 місяців
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1288
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    902
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1123
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    860

Розробка платформи у стилі friend.tech

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

Копіювати friend.tech буквально — програшна стратегія (аудиторія пішла, ринок насичений). Але механіка bonding curve + gated access застосована до десятків вертикалей: expert networks, fan платформи, creator economy, дозволені DAOs. Розберемо як це будувати правильно.

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

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

price(n) = n² / 16000 ETH

де n — поточна кількість ключів. Це polynomial крива — ціна зростає квадратично. Це створює сильний FOMO для ранніх покупців та експоненціально високу ціну при великому supply.

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

contract SocialBondingCurve {
    // Маппінг субʼєкт → кількість виданих ключів
    mapping(address => uint256) public sharesSupply;
    // Маппінг субʼєкт → власник → кількість ключів
    mapping(address => mapping(address => uint256)) public sharesBalance;
    
    address public protocolFeeDestination;
    uint256 public protocolFeePercent;   // у basis points
    uint256 public subjectFeePercent;    // комісія йде субʼєкту (власнику ключів)
    
    event Trade(
        address indexed trader,
        address indexed subject,
        bool isBuy,
        uint256 shareAmount,
        uint256 ethAmount,
        uint256 protocolEthAmount,
        uint256 subjectEthAmount,
        uint256 supply
    );
    
    // Ціна n-го ключа за polynomial кривою
    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 кривої: при великому supply ключі стають астрономічно дорогими. Для нішевих творців — це окей (ключ «зірки» коштує дорого). Для широкого ринку — бар'єр входу вбиває зростання. Альтернативи:

Лінійна крива: price = base_price + supply × slope — передбачувана, але немає FOMO

Сигмоїдна крива: швидке зростання спочатку (FOMO), потім плато — більш управлювана економіка для mass market

// Сигмоїдна ціна (апроксимація)
function getSigmoidPrice(uint256 supply, uint256 amount) public pure returns (uint256) {
    // k = крутизна, midpoint = точка перегину
    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 токени

Власники ключів отримують доступ до контенту. 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
}

Повністю 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. Більш стійкі підходи:

Інтеграція Lens Protocol — децентралізований social graph на Polygon. Profile = NFT, followers — on-chain. Creator token привязаний до 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);
    // комісії йдуть profileOwner
    // ...
}

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

mapping(uint256 => mapping(address => uint256)) public fidSharesBalance;  // fid → покупець → 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, купує перед вами, продає після, захоплює різницю.

Захист мінімального виходу:

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];
    // ... виконуємо покупку
}

Розширення базової механіки

Модель підписки: крім ключів — щомісячна підписка за доступ до контенту. Власники ключів отримують довічний доступ, інші — через recurring платіж.

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;
    
    // Розподіл: 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;
    }
    
    // При розподілі комісій частина йде 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 (критично для mass market), активна SocialFi екосистема. Альтернатива — Arbitrum, якщо потрібна більш зріла DeFi екосистема навколо.

Таймлайн розробки

Фаза Вміст Час
Design Параметри bonding curve, структура комісій, механіка доступу 1–2 тижні
Core контракти Bonding curve, access control, розподіл комісій 3–4 тижні
Social інтеграція Twitter/Farcaster/Lens OAuth або smart wallet 2–3 тижні
Backend API, уведомлення, зашифроване месенджер 3–4 тижні
Mobile-first frontend Web app (PWA) + підключення гаманця 4–5 тижнів
Anti-MEV & безпека Захист slippage, аудит 2–3 тижні
Launch Testnet пілот, influencer seeding 2–3 тижні

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