Friend.tech-style social platform development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Friend.tech-style social platform development
Complex
from 2 weeks to 3 months
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1215
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1043
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Development of Friend.tech-Style Platform

Friend.tech launched on Base in August 2023, generated $50M+ in fees in its first months, and set a template for an entire class of SocialFi applications: tokenized access to people. Mechanics: user connects Twitter account, others buy their "keys" (shares), key price increases via bonding curve, key holders gain access to private chat or content. Simple, joyfully efficient in terms of retention, and technically interesting.

Copying friend.tech literally is a losing strategy (audience left, market saturated). But the mechanic of bonding curve + gated access applies to dozens of verticals: expert networks, fan platforms, creator economy, permissioned DAOs. Let's break down how to build this properly.

Bonding Curve: Math and Implementation

Key price is determined by the number of already-issued keys via a formula. Friend.tech used:

price(n) = n² / 16000 ETH

where n is the current number of keys. This is a polynomial curve — price grows quadratically. This creates strong FOMO for early buyers and exponentially high price at large supply.

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

contract SocialBondingCurve {
    // Mapping subject → issued keys count
    mapping(address => uint256) public sharesSupply;
    // Mapping subject → holder → keys count
    mapping(address => mapping(address => uint256)) public sharesBalance;
    
    address public protocolFeeDestination;
    uint256 public protocolFeePercent;   // in basis points
    uint256 public subjectFeePercent;    // fee goes to subject (key owner)
    
    event Trade(
        address indexed trader,
        address indexed subject,
        bool isBuy,
        uint256 shareAmount,
        uint256 ethAmount,
        uint256 protocolEthAmount,
        uint256 subjectEthAmount,
        uint256 supply
    );
    
    // Price of n-th key via 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];
        // First key can only be bought by subject (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");
        
        // Refund overpayment
        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");
    }
}

Problem with polynomial curve: at large supply, keys become astronomically expensive. For niche creators — okay (a star's key is expensive). For mass market — entry barrier kills growth. Alternatives:

Linear curve: price = base_price + supply × slope — predictable, but no FOMO

Sigmoid curve: fast growth at first (FOMO), then plateau — more manageable economics for mass market

// Sigmoid-based price (approximation)
function getSigmoidPrice(uint256 supply, uint256 amount) public pure returns (uint256) {
    // k = steepness, midpoint = inflection point
    uint256 k = 100;
    uint256 midpoint = 1000;  // supply at which half of max_price
    uint256 maxPrice = 1 ether;
    
    // Simplified sigmoid approximation via piece-wise linear
    if (supply < midpoint / 4) {
        return supply * maxPrice / (4 * midpoint);  // lower part
    } else if (supply < 3 * midpoint / 4) {
        return maxPrice / 4 + (supply - midpoint/4) * maxPrice / (2 * midpoint);  // middle
    } else {
        return 3 * maxPrice / 4 + (supply - 3*midpoint/4) * maxPrice / (8 * midpoint);  // upper
    }
}

Gated Access: Keys as Access Tokens

Key holders get access to content. Off-chain verification is the most practical approach:

// Backend: verify access via signature
import { ethers } from 'ethers'

async function verifyAccess(
  userAddress: string,
  subjectAddress: string,
  signature: string,
  nonce: string
): Promise<boolean> {
  // Verify signature (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
  }
  
  // Check on-chain key balance
  const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, provider)
  const balance = await contract.sharesBalance(subjectAddress, userAddress)
  
  return balance > 0n
}

Fully on-chain gating via 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);
}

For real private chat — content is encrypted with recipients' public keys off-chain (e.g. via lit-protocol or custom threshold encryption), on-chain only events.

Social Graph and Identity Verification

Friend.tech used Twitter for verification. This created problems: Twitter could de-authorize OAuth — and platform loses social graph. More resilient approaches:

Lens Protocol integration — decentralized social graph on Polygon. Profile = NFT, followers — on-chain. Creator token tied to Lens Profile ID, not Ethereum address:

// Keys tied to Lens profile
mapping(uint256 => mapping(address => uint256)) public profileSharesBalance;
mapping(uint256 => uint256) public profileSharesSupply;

function buyProfileShares(uint256 profileId, uint256 amount) external payable {
    // Verify profile owner via Lens Hub
    address profileOwner = lensHub.ownerOf(profileId);
    // fees go to profileOwner
    // ...
}

Farcaster integration — another decentralized social protocol. Farcaster ID (FID) used instead of address:

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

function verifyFidOwnership(uint256 fid, address claimer, bytes calldata proof) external {
    // Verify via Farcaster key registry
    require(farcasterKeyRegistry.isSigner(fid, claimer), "Not FID owner");
    fidOwners[fid] = claimer;
}

MEV and Front-running Protection

Bonding curve transactions vulnerable to sandwich attacks: bot sees your buy in mempool, buys before you, sells after, captures difference.

Minimum output protection:

function buySharesWithProtection(
    address sharesSubject,
    uint256 amount,
    uint256 maxPrice  // maximum price you're willing to pay
) external payable {
    uint256 price = getBuyPriceAfterFee(sharesSubject, amount);
    require(price <= maxPrice, "Price too high (slippage)");
    require(msg.value >= price, "Insufficient ETH");
    // ... buy logic
}

Commit-reveal for large purchases:

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];
    // ... execute purchase
}

Extensions to Base Mechanics

Subscription model: besides keys — monthly subscription for content access. Key holders get lifetime access, others — via 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");
    
    // Renew or set subscription
    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 mechanics — friend.tech gave referral fees. Simple implementation:

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;
    }
    
    // During fee distribution, part goes to referrer
    address ref = referrers[msg.sender];
    if (ref != address(0)) {
        uint256 referralFee = protocolFee * referralPercent / 100;
        payable(ref).transfer(referralFee);
        protocolFee -= referralFee;
    }
    // ...
}

Chain Selection

Base is the right choice as was for original friend.tech. Cheap ($0.001–0.01 per transaction), EVM, Coinbase onramp (critical for mass market), active SocialFi ecosystem. Alternative — Arbitrum, if need more mature DeFi ecosystem around.

Development Timeline

Phase Content Time
Design Bonding curve parameters, fee structure, access mechanics 1–2 weeks
Core contracts Bonding curve, access control, fee distribution 3–4 weeks
Social integration Twitter/Farcaster/Lens OAuth or smart wallet 2–3 weeks
Backend API, notifications, encrypted messaging 3–4 weeks
Mobile-first frontend Web app (PWA) + wallet connection 4–5 weeks
Anti-MEV & security Slippage protection, audit 2–3 weeks
Launch Testnet pilot, influencer seeding 2–3 weeks

Total: 17–24 weeks. Key success factor — not technique, but bootstrap strategy: first 20–30 creators with audience determine traction. Technical side we develop; bootstrap needs client-side team.