Продажа NFT через sale-контракт

Выставишь NFT на продажу через sale: деплой sale, transfer item, разбор куда уходят цена, fee маркетплейса и royalty.

Зачем нужен sale-контракт

На TON нет approve + transferFrom как в ERC-721. Продажа — отдельный sale-контракт: продавец переводит NFT на sale (new_owner = sale), покупатель шлёт Gram на sale. Sale атомарно (в рамках своей логики) распределяет оплату и делает transfer item покупателю.

Пока NFT на sale, owner айтема — адрес sale, не кошелёк продавца.


Выставляем fixed-price sale и разбираем платеж

typescript
// @ton/core ^0.59.0, @ton/ton ^15.1.0
// Паттерн: Getgems NFT Fixed Price Sale (упрощённая схема сообщений)
// Конкретные opcode/поля — сверь с nft-contracts Getgems или своим sale
import { Address, beginCell, toNano, Cell } from "@ton/core";

const OP_NFT_TRANSFER = 0x5fcc3d14;

/**
 * После деплоя sale продавец переводит NFT на адрес sale.
 * forward_amount > 0 — sale получает ownership_assigned и «активируется».
 */
export function buildTransferNftToSale(opts: {
  saleAddress: Address;
  sellerAddress: Address;
  forwardTon: bigint;
}) {
  return beginCell()
    .storeUint(OP_NFT_TRANSFER, 32)
    .storeUint(0n, 64)
    .storeAddress(opts.saleAddress) // new_owner = sale
    .storeAddress(opts.sellerAddress) // excesses продавцу
    .storeBit(false)
    .storeCoins(opts.forwardTon)
    .storeBit(false)
    .endCell();
}

/**
 * Покупка: покупатель шлёт Gram ≥ price на sale.
 * Многие fixed-price sale принимают пустое тело или op::buy — смотри контракт.
 */
export function buildBuyMessage(saleAddress: Address, price: bigint) {
  return {
    to: saleAddress,
    value: price + toNano("0.1"), // цена + газ на transfer NFT и рассылки
    body: beginCell().endCell(), // или opcode buy — по исходнику sale
  };
}

/**
 * Куда уходит цена при успешной покупке (типичный fixed-price + TEP-66):
 *
 *   payment = msg_value (фактически price)
 *   marketplace_fee = price * marketplaceFeeNumerator / marketplaceFeeDenominator
 *   royalty         = price * royaltyNumerator / royaltyDenominator   // из collection
 *   seller_gets     = price - marketplace_fee - royalty
 *
 * Sale шлёт:
 *   marketplace_fee → marketplaceAddress (комиссия площадки)
 *   royalty         → royaltyDestination (из royalty_params коллекции)
 *   seller_gets     → fullPrice / seller address
 *   NFT transfer    → buyer (new_owner)
 */
export function splitSalePayment(opts: {
  price: bigint;
  marketplaceFeeNumerator: number;
  marketplaceFeeDenominator: number;
  royaltyNumerator: number;
  royaltyDenominator: number;
}) {
  const marketplaceFee =
    (opts.price * BigInt(opts.marketplaceFeeNumerator)) /
    BigInt(opts.marketplaceFeeDenominator);
  const royalty =
    opts.royaltyDenominator === 0
      ? 0n
      : (opts.price * BigInt(opts.royaltyNumerator)) /
        BigInt(opts.royaltyDenominator);
  const sellerGets = opts.price - marketplaceFee - royalty;
  return { marketplaceFee, royalty, sellerGets };
}

// Пример: price 10 GRAM, marketplace 5%, royalty 5%
const split = splitSalePayment({
  price: toNano("10"),
  marketplaceFeeNumerator: 5,
  marketplaceFeeDenominator: 100,
  royaltyNumerator: 50,
  royaltyDenominator: 1000,
});
// marketplaceFee = 0.5, royalty = 0.5, sellerGets = 9.0 GRAM

Поток:

text
1. Deploy sale(price, marketplaceFee, seller, nftAddress, ...)
2. Seller → NFT transfer → sale   (owner item = sale)
3. Buyer  → GRAM ≥ price → sale
4. Sale   → fee → marketplace
        → royalty → author (TEP-66)
        → rest → seller
        → NFT transfer → buyer

Как это работает

  • Sale — временный owner NFT; отмена листинга = transfer item обратно продавцу (если sale это умеет).
  • Комиссия маркетплейса задаётся при деплое sale / офертой площадки, не TEP-62.
  • Royalty sale читает с collection (royalty_params) и платит destination.
  • Покупатель всегда кладёт запас Gram сверху цены — sale должен оплатить газ transfer NFT.

Частые ошибки

Transfer на sale с forward_amount = 0 → sale не получает ownership_assigned и не считает NFT принятым.

Смотришь только «продавец получил price» → часть ушла в marketplace fee и royalty; в эксплорере три (или больше) исходящих перевода.

Покупка ровно price без газа → sale не завершает transfer item; риск застревания средств/NFT по логике конкретного контракта.


Что дальше

Материалы gramdocs.tech носят образовательный характер и не являются финансовой, юридической или инвестиционной рекомендацией. Работа с блокчейном TON и токеном Gram связана с рисками потери средств. Правовая информация