Розробка SocialFi-токенів для творців контенту

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

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

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

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

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

Розробка стейкінгу токенів з NFT-наградами

Стейкінг з NFT-наградами — це гібрид двох механік: liquidity lock (користувач блокує токени, отримує yield) та NFT-дистрибуція як додатковий стимул. Проста реалізація «застейкай X токенів, отримай NFT» працює, але швидко теряє привабливість. Цікава механіка будується на динамічних NFT, які еволюціонують разом зі стейкінгом.

Базова архітектура стейкінг-контракту

Стейкінг-контракт реалізує три речі: lock токенів, розрахунок accumulated rewards, mint/upgrade NFT по досягненню milestones.

contract TokenStakingWithNFT {
    IERC20 public immutable stakingToken;
    IRewardNFT public immutable rewardNFT;
    
    struct StakeInfo {
        uint256 amount;
        uint256 stakedAt;
        uint256 rewardDebt;     // для коректного розрахунку rewards
        uint256 nftTokenId;     // 0 якщо NFT ще не виданий
        uint8 nftTier;          // поточний тир NFT (0–4)
    }
    
    mapping(address => StakeInfo) public stakes;
    
    uint256 public accRewardPerShare;   // накопилений reward per share (scaled 1e12)
    uint256 public lastRewardBlock;
    uint256 public rewardPerBlock;
    uint256 public totalStaked;
    
    // Milestones для NFT upgrade (у днях стейкінгу)
    uint256[] public nftTierThresholds = [7, 30, 90, 180, 365];
    
    function stake(uint256 amount) external nonReentrant {
        _updatePool();
        
        StakeInfo storage info = stakes[msg.sender];
        
        // Виплачуємо pending rewards перед зміною стейку
        if (info.amount > 0) {
            uint256 pending = info.amount * accRewardPerShare / 1e12 - info.rewardDebt;
            if (pending > 0) _distributeReward(msg.sender, pending);
        }
        
        stakingToken.safeTransferFrom(msg.sender, address(this), amount);
        info.amount += amount;
        
        if (info.stakedAt == 0) {
            info.stakedAt = block.timestamp;
            // Mint початкового NFT (Tier 0) при першому стейку
            info.nftTokenId = rewardNFT.mint(msg.sender, 0);
        }
        
        totalStaked += amount;
        info.rewardDebt = info.amount * accRewardPerShare / 1e12;
        
        emit Staked(msg.sender, amount);
    }
    
    function checkAndUpgradeNFT() external {
        StakeInfo storage info = stakes[msg.sender];
        require(info.amount > 0, "Not staking");
        
        uint256 stakingDays = (block.timestamp - info.stakedAt) / 1 days;
        uint8 newTier = _calculateTier(stakingDays);
        
        if (newTier > info.nftTier) {
            info.nftTier = newTier;
            rewardNFT.upgrade(info.nftTokenId, newTier);
            emit NFTUpgraded(msg.sender, info.nftTokenId, newTier);
        }
    }
    
    function _calculateTier(uint256 days_) internal view returns (uint8) {
        for (uint8 i = uint8(nftTierThresholds.length); i > 0; i--) {
            if (days_ >= nftTierThresholds[i - 1]) return i;
        }
        return 0;
    }
}

Динамічні NFT через on-chain metadata

ERC-721 з динамічним tokenURI — NFT змінює зображення та атрибути при upgrade. Два підходи: off-chain metadata на IPFS (швидко, але потребує оновлення при upgrade) та fully on-chain SVG (дорожче по газу, але повністю децентралізовано).

contract RewardNFT is ERC721, Ownable {
    mapping(uint256 => uint8) public tokenTier;
    mapping(uint8 => string) public tierImageURI;  // IPFS CID для кожного тиру
    
    address public stakingContract;
    
    function mint(address to, uint8 initialTier) external returns (uint256) {
        require(msg.sender == stakingContract, "Only staking contract");
        uint256 tokenId = ++_tokenCounter;
        _safeMint(to, tokenId);
        tokenTier[tokenId] = initialTier;
        return tokenId;
    }
    
    function upgrade(uint256 tokenId, uint8 newTier) external {
        require(msg.sender == stakingContract, "Only staking contract");
        require(newTier > tokenTier[tokenId], "Cannot downgrade");
        tokenTier[tokenId] = newTier;
        emit TierUpgraded(tokenId, newTier);
    }
    
    function tokenURI(uint256 tokenId) public view override returns (string memory) {
        require(_exists(tokenId), "Token does not exist");
        
        uint8 tier = tokenTier[tokenId];
        string memory imageURI = tierImageURI[tier];
        
        // Генеруємо metadata on-the-fly
        return string(abi.encodePacked(
            'data:application/json;base64,',
            Base64.encode(bytes(abi.encodePacked(
                '{"name":"Staker NFT Tier ', Strings.toString(tier), '",',
                '"description":"Reward NFT for loyal stakers",',
                '"image":"', imageURI, '",',
                '"attributes":[{"trait_type":"Tier","value":', Strings.toString(tier), '},',
                '{"trait_type":"Tier Name","value":"', _tierName(tier), '"}]}'
            )))
        ));
    }
    
    function _tierName(uint8 tier) internal pure returns (string memory) {
        if (tier == 0) return "Bronze";
        if (tier == 1) return "Silver";
        if (tier == 2) return "Gold";
        if (tier == 3) return "Platinum";
        return "Diamond";
    }
}

Важливий нюанс: soulbound або transferable

Якщо NFT transferable — виникає проблема: хтось може купити NFT Diamond tier на вторинному ринку без стейкінгу. Якщо NFT повинен відображати саме стейкінг, потрібно soulbound (ERC-5192) або привязка до адреси у контракті стейкінгу.

// ERC-5192: мінімальний soulbound інтерфейс
function locked(uint256 tokenId) external view returns (bool) {
    return true;  // всі токени locked
}

function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize)
    internal override {
    // Дозволяємо тільки mint (from == address(0)) та burn (to == address(0))
    require(from == address(0) || to == address(0), "Soulbound: non-transferable");
    super._beforeTokenTransfer(from, to, tokenId, batchSize);
}

Reward механіка: токени vs NFT boost

NFT тир може давати не тільки візуальний апгрейд, але й boost до reward rate:

Тир Днів у стейкінгу Базовий boost Доп. привілегії
Bronze (0) 7+ +0% Базовий NFT
Silver (1) 30+ +10% Доступ до закритого Discord
Gold (2) 90+ +25% Whitelist на наступний NFT drop
Platinum (3) 180+ +50% Governance множник x2
Diamond (4) 365+ +100% Фізичний мерч, IRL доступ
function _getUserMultiplier(address user) internal view returns (uint256) {
    uint8 tier = rewardNFT.tokenTier(stakes[user].nftTokenId);
    // basis points: 10000 = 1x, 20000 = 2x
    uint256[5] memory multipliers = [uint256(10000), 11000, 12500, 15000, 20000];
    return multipliers[tier];
}

function pendingReward(address user) public view returns (uint256) {
    StakeInfo storage info = stakes[user];
    uint256 acc = accRewardPerShare;
    
    if (block.number > lastRewardBlock && totalStaked > 0) {
        uint256 blocks = block.number - lastRewardBlock;
        acc += blocks * rewardPerBlock * 1e12 / totalStaked;
    }
    
    uint256 baseReward = info.amount * acc / 1e12 - info.rewardDebt;
    uint256 multiplier = _getUserMultiplier(user);
    return baseReward * multiplier / 10000;
}

Early unstake penalty та lock periods

Механіка penalty стимулює довгостроковий стейкінг та захищає від dump після отримання NFT:

uint256 public constant MIN_LOCK_PERIOD = 7 days;
uint256 public constant PENALTY_RATE = 1000;  // 10% у basis points

function unstake(uint256 amount) external nonReentrant {
    StakeInfo storage info = stakes[msg.sender];
    require(info.amount >= amount, "Insufficient stake");
    
    _updatePool();
    
    uint256 pending = info.amount * accRewardPerShare / 1e12 - info.rewardDebt;
    if (pending > 0) _distributeReward(msg.sender, pending);
    
    uint256 actualAmount = amount;
    
    // Penalty при ранньому выході
    if (block.timestamp < info.stakedAt + MIN_LOCK_PERIOD) {
        uint256 penalty = amount * PENALTY_RATE / 10000;
        actualAmount = amount - penalty;
        stakingToken.safeTransfer(penaltyCollector, penalty);
    }
    
    info.amount -= amount;
    totalStaked -= amount;
    stakingToken.safeTransfer(msg.sender, actualAmount);
    
    // Якщо повністю вийшов — lock або burn NFT
    if (info.amount == 0) {
        rewardNFT.lockOnUnstake(info.nftTokenId);
    }
    
    info.rewardDebt = info.amount * accRewardPerShare / 1e12;
}

Безпека

Два головних вектора атак на стейкінг:

Reentrancy: всі функції, що змінюють state та роблять зовнішні вызови, повинні мати nonReentrant. Особливо stake, unstake, claim.

Overflow у reward розрахунку: накопилений accRewardPerShare може переповнити uint256 при великій кількості блоків або великому rewardPerBlock. Перевіряйте масштабування. MasterChef V2 від SushiSwap — хорошого референсна реалізація.

Контракт обов'язковий до аудиту перед запуском — стейкінг-контракти тримають кошти користувачів постійно та є приоритетними цілями для атак.