Learn how to handle NFTs in TAC proxy contracts with cross-chain bridging
TAC proxy contracts can receive, process, and bridge NFTs between TON and EVM networks. This guide shows the exact implementation patterns from the TAC engineering team.
NFT proxy contracts must inherit from IERC721Receiver and implement the
required onERC721Received function to correctly receive ERC-721 tokens.
The NFT proxy contract must inherit from IERC721Receiver and implement the required onERC721Received function to correctly receive ERC‑721 tokens.
Copy
Ask AI
// SPDX-License-Identifier: MITpragma solidity ^0.8.28;import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";import { IERC721Receiver } from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";import { TacProxyV1 } from "@tonappchain/evm-ccl/contracts/proxies/TacProxyV1.sol";import { TacHeaderV1, TokenAmount, NFTAmount, OutMessageV1 } from "@tonappchain/evm-ccl/contracts/core/Structs.sol";contract TestNFTProxy is TacProxyV1, IERC721Receiver { constructor(address crossChainLayer) TacProxyV1(crossChainLayer) {} /** * @dev Handles the receipt of an ERC-721 token. * * Returns its Solidity selector to confirm the token transfer. */ function onERC721Received( address, address, uint256, bytes calldata ) external pure override(IERC721Receiver) returns (bytes4) { return this.onERC721Received.selector; } /** * @dev Receives NFTs bridged from TON. */ function receiveNFT(bytes calldata tacHeader, bytes calldata arguments) external _onlyCrossChainLayer { // this arguments just for example, you can define your own NFTAmount[] memory nfts = abi.decode(arguments, (NFTAmount[])); for (uint i = 0; i < nfts.length; i++) { IERC721(nfts[i].evmAddress).approve(_getCrossChainLayerAddress(), nfts[i].tokenId); } TacHeaderV1 memory header = _decodeTacHeader(tacHeader); // Bridge NFT back by creating an OutMessageV1 OutMessageV1 memory outMessage = OutMessageV1( header.shardsKey, header.tvmCaller, "", 0, // roundTripMessages don't require tvmProtocolFee as it's already paid on TON 0, // roundTripMessages don't require tvmExecutorFee as it's already paid on TON new string[](0), // no need to specify validExecutors as it's already specified in initial tx on TON new TokenAmount[](0), // No ERC20 tokens bridged nfts // NFTs to bridge (the 'amount' field is ignored for ERC721) ); _sendMessageV1(outMessage, 0); }}
OutMessageV1 memory outMessage = OutMessageV1( header.shardsKey, // Use from incoming header header.tvmCaller, // Send back to caller "", // Must be empty 0, // roundTrip - already paid on TON 0, // roundTrip - already paid on TON new string[](0), // roundTrip - already defined on TON new TokenAmount[](0), // No ERC20 tokens nfts // NFTs to bridge);
You can name the function whatever you want (e.g., processNFT, handleNFT, etc.) as long as it follows the function <name>(bytes calldata, bytes calldata) external pattern.
Now that you understand NFT handling, learn how to test these contracts:
Testing Framework
Learn comprehensive testing with @tonappchain/evm-ccl
Advanced Testing Scenarios
Testing patterns for NFTs and complex cross-chain operations
Important: NFT proxy contracts must implement IERC721Receiver or NFT
transfers will fail. Always test NFT functionality thoroughly before deploying
to mainnet.