Розробка токена стандарту FA2 (Tezos)

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

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

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

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

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

Розробка токена FA2 (Tezos)

FA2 (TZIP-12) — мультиассетний стандарт токенів у екосистемі Tezos. На відміну від ERC-20/ERC-721, один FA2 контракт може управляти fungible та non-fungible токенами одночасно з довільним token_id. Це архітектурне рішення: замість розгортання окремого контракту для кожного токена — один контракт для цілого портфеля активів.

Смарт-контракти Tezos пишуться переважно на SmartPy або LIGO. Рантайм — Michelson. Розуміння Michelson потрібно для відладки та газової оптимізації, але писати на ньому напрямку не обов'язково.

Специфікація FA2: обов'язковий інтерфейс

Transfer entrypoint

Ключовий entrypoint — transfer. Приймає список transfer_batch: кожен batch описує відправника, список отримувачів та token_id/amount.

# SmartPy: FA2 transfer тип
# transfer_params: list of {from_: address, txs: list of {to_: address, token_id: nat, amount: nat}}

@sp.entrypoint
def transfer(self, batch):
    for transfer in batch:
        from_ = transfer.from_
        # Перевіряємо авторизацію: відправник сам або approved operator
        sp.verify(
            (from_ == sp.sender) | 
            self.data.operators.contains(sp.record(
                owner=from_, 
                operator=sp.sender, 
                token_id=transfer.txs[0].token_id  # per-token operator
            )),
            "FA2_NOT_OPERATOR"
        )
        for tx in transfer.txs:
            self._transfer_tokens(from_, tx.to_, tx.token_id, tx.amount)

Operators у FA2 — це треті сторони, яким власник дозволяє управляти своїми токенами. Аналог setApprovalForAll в ERC-721, але більш гранулярний: можна дати дозвіл на конкретний token_id.

Balance_of view

@sp.onchain_view()
def balance_of(self, requests):
    # requests: list of {owner: address, token_id: nat}
    # returns: list of {request: ..., balance: nat}
    results = []
    for req in requests:
        balance = self.data.ledger.get(
            sp.record(owner=req.owner, token_id=req.token_id), 
            default=0
        )
        results.append(sp.record(request=req, balance=balance))
    return results

Це on-chain view — не змінює стан, викликається з інших контрактів або off-chain. Tezos відрізняється від EVM тим, що views явно об'являються як onchain_view.

Update_operators

@sp.entrypoint
def update_operators(self, updates):
    for update in updates:
        update.match_cases({
            "add_operator": lambda op: self._add_operator(op),
            "remove_operator": lambda op: self._remove_operator(op),
        })

def _add_operator(self, op):
    # Тільки власник може додавати операторів для своїх токенів
    sp.verify(op.owner == sp.sender, "FA2_NOT_OWNER")
    self.data.operators.add(sp.record(
        owner=op.owner, 
        operator=op.operator, 
        token_id=op.token_id
    ))

Повна реалізація FA2 на SmartPy

import smartpy as sp

FA2_ERRORS = sp.utils.import_script_from_url(
    "https://smartpy.io/templates/fa2_lib.py"
)

@sp.module
def main():
    class FA2Token(
        FA2_ERRORS.Fungible,    # fungible token миксин
        FA2_ERRORS.Admin,       # admin контроль
        FA2_ERRORS.MintFungible,
        FA2_ERRORS.BurnFungible,
        sp.Contract
    ):
        def __init__(self, admin, metadata, token_metadata):
            FA2_ERRORS.Admin.__init__(self, admin)
            FA2_ERRORS.Fungible.__init__(self, {
                "ledger": sp.big_map(),          # (owner, token_id) → баланс
                "operators": sp.big_map(),       # (owner, operator, token_id) → unit
            })
            self.init_metadata("metadata", metadata)
            
            # Ініціалізуємо токен 0
            self.data.token_metadata = sp.big_map({
                0: sp.record(
                    token_id=0,
                    token_info=token_metadata
                )
            })
            self.data.supply = sp.big_map({0: 0})
        
        @sp.entrypoint
        def mint(self, to_, token_id, amount):
            sp.verify(sp.sender == self.data.admin, "NOT_ADMIN")
            # Оновлюємо баланс отримувача
            key = sp.record(owner=to_, token_id=token_id)
            current = self.data.ledger.get(key, default=0)
            self.data.ledger[key] = current + amount
            # Оновлюємо total supply
            self.data.supply[token_id] = self.data.supply.get(token_id, default=0) + amount

Storage: big_map vs map

У Tezos важливо розрізняти map та big_map:

  • map — зберігається повністю в пам'яті при виконанні. Дорого по газу для великих колекцій.
  • big_map — lazy loaded. При доступі до ключа платите тільки за читання цього ключа. Для ledger з тисячами адрес — big_map обов'язковий.
# Правильно: big_map для ledger
self.data.ledger = sp.big_map(
    tkey=sp.TRecord(owner=sp.TAddress, token_id=sp.TNat),
    tvalue=sp.TNat
)

# Неправильно для продакшну: map перераховується повністю при кожному доступі
# self.data.ledger = sp.map(...)  — тільки для малих колекцій

Tzip-16: метаданні токена

TZIP-16 — стандарт метаданих для Tezos контрактів. Метаданні зберігаються в storage контракту або посилаються на IPFS/HTTPS.

# Метаданні контракту (TZIP-16)
contract_metadata = {
    "name": "My FA2 Token",
    "description": "Utility token for platform",
    "interfaces": ["TZIP-012", "TZIP-016"],
    "homepage": "https://example.com"
}

# Метаданні токена (TZIP-21 для NFT)
token_metadata = {
    "name": sp.utils.bytes_of_string("MyToken"),
    "symbol": sp.utils.bytes_of_string("MTK"),
    "decimals": sp.utils.bytes_of_string("6"),
    "description": sp.utils.bytes_of_string("Utility token"),
    "thumbnailUri": sp.utils.bytes_of_string("ipfs://Qm...")
}

Стандарт вимагає зберігати значення як bytes (UTF-8 кодування). Інструменти (кошельки, explorers) декодують назад у рядки. Створює незручність у коді, але забезпечує сумісність.

Multi-asset: кілька токенів в одному контракті

Сила FA2 — token_id. Один контракт, три токени:

# Токен 0: governance токен (fungible)
# Токен 1: utility токен (fungible)
# Токен 2-10000: NFT колекція (non-fungible, amount=1 завжди)

# При мінті NFT
@sp.entrypoint
def mint_nft(self, to_, token_id, metadata):
    sp.verify(sp.sender == self.data.admin, "NOT_ADMIN")
    sp.verify(~self.data.ledger.contains(
        sp.record(owner=to_, token_id=token_id)), "NFT_EXISTS")
    self.data.ledger[sp.record(owner=to_, token_id=token_id)] = 1
    self.data.token_metadata[token_id] = sp.record(
        token_id=token_id,
        token_info=metadata
    )

Для NFT: ніколи не мінтити amount > 1 для одного token_id. Це конвенція, не enforced протоколом.

Тестування з SmartPy

# SmartPy unit тест
@sp.add_test(name="FA2 Transfer Test")
def test():
    sc = sp.test_scenario(main)
    
    admin = sp.test_account("Admin")
    alice = sp.test_account("Alice")
    bob = sp.test_account("Bob")
    
    token = main.FA2Token(
        admin=admin.address,
        metadata={"": sp.utils.bytes_of_string("ipfs://...")},
        token_metadata={"symbol": sp.utils.bytes_of_string("MTK")}
    )
    sc += token
    
    # Мінтимо 1000 токенів Alice
    token.mint(to_=alice.address, token_id=0, amount=1000).run(sender=admin)
    
    # Alice переводить 100 токенів Bob
    token.transfer([sp.record(
        from_=alice.address,
        txs=[sp.record(to_=bob.address, token_id=0, amount=100)]
    )]).run(sender=alice)
    
    # Верифікуємо баланси
    sc.verify(token.balance_of([
        sp.record(owner=alice.address, token_id=0)
    ])[0].balance == 900)
    sc.verify(token.balance_of([
        sp.record(owner=bob.address, token_id=0)
    ])[0].balance == 100)

Розгортання та інструменти

SmartPy IDE (smartpy.io) — браузерний IDE з симулятором. Taquito — JavaScript бібліотека для взаємодії з Tezos (аналог ethers.js). Ghostnet — основний Tezos testnet. Temple Wallet / Kukai — кошельки для тестування.

// Taquito: взаємодія з FA2 контрактом
import { TezosToolkit } from "@taquito/taquito"

const Tezos = new TezosToolkit("https://ghostnet.ecadinfra.com")

const contract = await Tezos.contract.at("KT1...")

// Transfer токенів
const op = await contract.methods.transfer([{
  from_: "tz1Alice...",
  txs: [{ to_: "tz1Bob...", token_id: 0, amount: 100 }]
}]).send()

await op.confirmation(3)

Процес

Аналіз вимог (2-3 дні). Скільки token_id, fungible або NFT або mixed, кастомні entrypoints (whitelist, vesting), вимоги TZIP-16/21.

Розробка (1-3 тижні). FA2 базовий контракт → кастомна логіка → тести → розгортання на Ghostnet → верифікація через explorer (tzkt.io).

Інтеграція (1-2 тижні). Taquito frontend або backend інтеграція, wallet connection, transaction tracking.

Типовий FA2 токен зі стандартним функціоналом — 1-2 тижні. З кастомною токеномікою (vesting, bonding curve, governance) — 3-6 тижнів.