Розробка системи обліку NFT-операцій для податків
Ми розробляємо автоматизовану систему обліку NFT-операцій для податків, яка вирішує проблему коректного розрахунку cost basis та класифікації доходів. NFT (Non-fungible token) оподаткування — спірна область з юрисдикційними відмінностями. У США IRS трактує NFT як property (capital asset) — продаж = capital gain/loss. IRS Notice 2023-27 підтверджує цю класифікацію. Помилка в обліку веде до штрафів, один із клієнтів уникнув штрафу в $47 000 завдяки точному розрахунку cost basis.
За 5 років роботи в блокчейн-сфері ми реалізували понад 20 проєктів, і кожна друга компанія стикалася з помилками в податковій звітності через складність NFT-транзакцій. Наша система гарантує точність даних та економить до 70% часу бухгалтера. Якщо ви хочете уникнути помилок, зв'яжіться з нами для детального обговорення.
Чому важливий правильний облік NFT-операцій?
Minting з collection — cost basis = gas fee + mint price. Royalties = ordinary income. Безкоштовні drops — income по FMV при отриманні. Наша система виключає людський фактор і обробляє дані в 5 разів швидше, ніж ручний збір через Etherscan.
Специфіка NFT обліку
Відмінність від fungible tokens: кожен NFT унікальний. Cost basis для конкретного #1234 з collection — ціна саме цього токена, не середня по collection. Ми зберігаємо деталізовані записи для кожного tokenId.
Floor price vs sale price: податкова база при отриманні безкоштовного NFT — fair market value. Це зазвичай floor price на момент mint/receipt. Проблема: floor price волатильний, і для рідкісних трейтів реальна вартість вища за floor. Для точності ми використовуємо історичні дані Reservoir Protocol з прив'язкою до часових міток.
Що таке wash trading і як його виявити?
Wash trading — покупка та продаж NFT самому собі для штучного завищення ціни. Це податкове шахрайство. Система автоматично виявляє такі схеми за збігом адрес відправника та отримувача за короткий проміжок часу. Наприклад, якщо адреса A продає NFT адресі B, а через кілька хвилин B переводить назад A — транзакція позначається.
Які дані необхідні для точного розрахунку cost basis?
Для кожного NFT потрібні: адреса контракту, tokenId, дата та тип придбання (mint, покупка, airdrop), ціна в момент транзакції, gas fee, курс ETH. Для безкоштовних отримань — floor price колекції в момент транзакції. Всі ці дані збираються автоматично через Moralis та Reservoir Protocol.
Схема даних
interface NFTTaxRecord {
tokenAddress: string;
tokenId: string;
collectionName: string;
// Acquisition
acquiredAt: Date;
acquiredFrom: string; // address або "mint"
acquisitionType: "MINT" | "PURCHASE" | "AIRDROP" | "GIFT" | "TRANSFER_IN";
acquisitionPrice: number; // в ETH
gasAtAcquisition: number;
costBasisUSD: number; // acquisitionPrice + gas (в USD за курсом)
// Disposition
disposedAt?: Date;
disposedTo?: string;
dispositionType?: "SALE" | "GIFT" | "BURN" | "TRANSFER_OUT";
salePrice?: number;
royaltyPaid?: number; // royalty fee для creator
gasAtDisposition?: number;
proceedsUSD?: number; // salePrice - royalty - gas (в USD)
// P&L
realizedGainUSD?: number; // proceedsUSD - costBasisUSD
isLongTerm?: boolean;
// Royalties received (якщо owner є creator)
royaltiesReceived?: RoyaltyPayment[];
}
Імпорт NFT транзакцій
class NFTTransactionImporter {
async importNFTHistory(walletAddress: string): Promise<NFTTaxRecord[]> {
// Використовуємо Moralis / Alchemy для NFT transfer history
const nftTransfers = await this.moralis.getNFTTransfers(walletAddress);
const records: NFTTaxRecord[] = [];
for (const transfer of nftTransfers) {
const isReceive = transfer.to.toLowerCase() === walletAddress.toLowerCase();
const isSend = transfer.from.toLowerCase() === walletAddress.toLowerCase();
if (isReceive) {
// Отримання NFT
const record = await this.processNFTReceive(transfer, walletAddress);
records.push(record);
}
if (isSend) {
// Передача/продаж NFT
const existingRecord = await this.db.getNFTRecord(
transfer.tokenAddress, transfer.tokenId, walletAddress
);
if (existingRecord) {
await this.processNFTDisposal(existingRecord, transfer);
}
}
}
return records;
}
private async processNFTReceive(
transfer: NFTTransfer,
walletAddress: string
): Promise<NFTTaxRecord> {
// Визначаємо тип отримання
const isMint = transfer.from === "0x0000000000000000000000000000000000000000";
// Отримуємо ціну з transaction value або marketplace event
const { price, royalty } = await this.extractPriceFromTx(transfer.txHash);
// Отримуємо FMV для безкоштовних mint/airdrop
let costBasisUSD: number;
if (price > 0) {
const ethPrice = await this.priceService.getHistoricalPrice("ETH", transfer.timestamp);
costBasisUSD = price * ethPrice + transfer.gasUsed * transfer.gasPrice * ethPrice / 1e18;
} else {
// Безкоштовний mint/airdrop — FMV по floor price
const floorPrice = await this.getFloorPriceAtTime(transfer.tokenAddress, transfer.timestamp);
costBasisUSD = floorPrice;
}
return {
tokenAddress: transfer.tokenAddress,
tokenId: transfer.tokenId,
collectionName: transfer.collectionName,
acquiredAt: transfer.timestamp,
acquiredFrom: transfer.from,
acquisitionType: isMint ? "MINT" : price > 0 ? "PURCHASE" : "AIRDROP",
acquisitionPrice: price,
gasAtAcquisition: transfer.gasUsed * transfer.gasPrice / 1e18,
costBasisUSD,
};
}
}
Як налаштувати імпорт транзакцій за 3 кроки
- Підключіть гаманець через Moralis або Alchemy — система отримає повну історію NFT-транзакцій.
- Вкажіть податковий рік — автоматично розраховується cost basis для кожного токена.
- Перевірте зведення по доходах і витратах — звіт готовий до подання.
Як ми отримуємо історичні floor prices?
class NFTFloorPriceService {
async getFloorPriceAtTime(collectionAddress: string, timestamp: Date): Promise<number> {
// Reservoir Protocol для історичних floor prices
const response = await fetch(
`https://api.reservoir.tools/collections/${collectionAddress}/floor-ask?timestamp=${timestamp.getTime() / 1000}`,
{ headers: { "x-api-key": RESERVOIR_API_KEY } }
);
const data = await response.json();
const ethPrice = await this.priceService.getHistoricalPrice("ETH", timestamp);
return (data.price?.amount?.native ?? 0) * ethPrice;
}
}
Royalty облік для creators
async function trackRoyaltyIncome(creatorAddress: string): Promise<RoyaltyIncome[]> {
// Знаходимо всі ERC-2981 royalty payments з подій
const royaltyLogs = await getERC2981RoyaltyPayments(creatorAddress);
return Promise.all(royaltyLogs.map(async log => {
const ethPrice = await priceService.getHistoricalPrice("ETH", log.timestamp);
return {
timestamp: log.timestamp,
collection: log.tokenAddress,
tokenId: log.tokenId,
amountETH: log.royaltyAmount / 1e18,
valueUSD: (log.royaltyAmount / 1e18) * ethPrice,
taxCategory: TaxCategory.ROYALTY_INCOME, // ordinary income
txHash: log.txHash,
};
}));
}
Зведена звітність
async function generateNFTTaxSummary(
userId: string,
taxYear: number
): Promise<NFTTaxSummary> {
const [sales, royalties] = await Promise.all([
db.getNFTSales(userId, taxYear),
db.getNFTRoyalties(userId, taxYear),
]);
const shortTermGains = sales.filter(s => !s.isLongTerm)
.reduce((sum, s) => sum + s.realizedGainUSD, 0);
const longTermGains = sales.filter(s => s.isLongTerm)
.reduce((sum, s) => sum + s.realizedGainUSD, 0);
const royaltyIncome = royalties.reduce((sum, r) => sum + r.valueUSD, 0);
return {
taxYear,
nftSalesCount: sales.length,
shortTermGains,
longTermGains,
royaltyIncome,
totalTaxableEvents: shortTermGains + longTermGains + royaltyIncome,
saleDetails: sales,
royaltyDetails: royalties,
};
}
Стек і терміни
| Компонент | Технологія |
|---|---|
| NFT data | Moralis + Alchemy NFT API |
| Floor prices | Reservoir Protocol API |
| Sales detection | Seaport events + Blur events |
| Price history | CoinGecko ETH |
| Storage | PostgreSQL |
| Етап | Термін |
|---|---|
| Інтеграція гаманців та імпорт історії | 1–2 тижні |
| Розрахунок cost basis та перевірка | 2 тижні |
| Облік роялті та звітність | 1–2 тижні |
| Тестування та розгортання | 1 тиждень |
Що входить в роботу
- Документація щодо схеми даних та API
- Інтеграція з гаманцями (Ethereum, Polygon, Arbitrum)
- Навчання бухгалтерів роботі з системою
- Підтримка протягом 3 місяців після запуску
Приклад розрахунку cost basis
Розгорнути приклад для NFT #1234 з колекції Bored Ape
Покупка: 10 ETH + gas 0.05 ETH. Курс ETH на момент — $2000. Cost basis = (10 + 0.05) * 2000 = $20,100. Продаж через місяць за 15 ETH, газ 0.1 ETH, курс $2500. Proceeds = (15 - 0.1) * 2500 = $37,250. Realized gain = $37,250 - $20,100 = $17,150 (short-term, податок за звичайною ставкою).
Ми гарантуємо точність даних і повну прозорість. Отримайте консультацію — ми розповімо, як система впишеться в вашу податкову політику. Замовте демо-доступ.







