The creator sends a message to the collection contract, which deploys a new NFT item with the specified data: the initial owner and the item-specific content. The NFT standard does not prescribe how this data must be supplied; implementations may vary. Typically, the creator provides the initial owner and item-specific content for each NFT, or this information is derived from the collection itself (see cNFT).Since the deployment process is not specified by the standard, logic can vary, and the recipes on this page might not apply to every contract. The reference NFT implementation and most modifications follow the same path: the collection owner sends a message with deploy parameters to the collection, and the collection deploys the item.
A funded Testnet wallet mnemonic in the MNEMONIC environment variable
The sending wallet must be the collection owner; otherwise the collection rejects deployments
Funds and secretsThis procedure spends funds, uses a wallet mnemonic, and interacts with a collection contract. Run it on Testnet first to test the desired behavior.
The following example uses the @ton/ton stack for TypeScript. These libraries provide interfaces to work with wallet contracts and compose messages.
Copy
Ask AI
import { Address, beginCell, internal, toNano } from "@ton/core";import { TonClient, WalletContractV5R1, SendMode } from "@ton/ton";import { mnemonicToPrivateKey } from "@ton/crypto";const collectionAddress = Address.parse("<COLLECTION_ADDRESS>");const recipientAddress = Address.parse("<RECIPIENT_ADDRESS>");const itemContent = "<ITEM_CONTENT>";async function main() { // Toncenter endpoint (Testnet) const client = new TonClient({ endpoint: "https://testnet.toncenter.com/api/v2/jsonRPC", }); // Obtain the next item index const nextItemIndex = ( await client.runMethod( collectionAddress, "get_collection_data", ) ).stack.readBigNumber(); // Read the next item index. See the explanation after the code. // individual content const content = beginCell() .storeStringTail(itemContent) .endCell(); const body = beginCell() // deploy opcode .storeUint(1, 32) // query id .storeUint(0, 64) .storeUint(nextItemIndex, 64) // Forwarded to the new item as its initial balance. // Ensure `value` >= this amount + all fees. .storeCoins(toNano("0.005")) .storeRef( beginCell() .storeAddress(recipientAddress) .storeRef(content) .endCell(), ) .endCell(); // Compose deploy message const msg = internal({ to: collectionAddress, // Total attached to the collection. Must cover // the forwarded amount below (0.005) plus // execution/storage fees; // keep a safety margin (e.g., 0.01-0.02 TON) value: toNano("0.01"), bounce: true, body, }); // Initialize wallet const mnemonic = process.env.MNEMONIC; if (!mnemonic) { throw new Error("Set MNEMONIC"); } const keyPair = await mnemonicToPrivateKey( mnemonic.split(" ") ); const walletContract = client.open( WalletContractV5R1.create({ workchain: 0, // basechain publicKey: keyPair.publicKey, }), ); // Send the mint message through the wallet const seqno = await walletContract.getSeqno(); await walletContract.sendTransfer({ seqno: seqno, secretKey: keyPair.secretKey, // Good practice to use these modes for // regular wallet transfers sendMode: SendMode.IGNORE_ERRORS | SendMode.PAY_GAS_SEPARATELY, messages: [msg], });}void main();
Where
<COLLECTION_ADDRESS> — the collection contract address.
<RECIPIENT_ADDRESS> — the initial owner address for the new item.
<ITEM_CONTENT> — item-specific content path or key (for example, 0.json).
Explanation of body cell composition:
.storeUint(1, 32) — operation code 1 selects “deploy single item” in the reference collection contract. TEP-62 does not specify this opcode, so in custom implementations, this can differ.
.storeUint(0, 64) — query_id. Used for correlating responses with requests. It has no impact on deployment logic and 0 is a commonly used placeholder in cases where no extra logic relies on it.
.storeUint(nextItemIndex, 64) — item_index. Index of the item to deploy, obtained from the collection’s get_collection_data get-method. See Return collection data.
.storeCoins(toNano("0.005")) — amount forwarded to the new item at deployment to cover its initial balance/fees. Adjust if extra item contract logic requires more.
In a block explorer, confirm the transaction for <COLLECTION_ADDRESS> succeeded and inspect the transaction trace to see the internal message that deployed the item.
To verify via code: call get_nft_address_by_index(<INDEX>) — where <INDEX> is the item index used in the deploy message (the next_item_index read from get_collection_data) — on the collection to obtain the item address; then call get_nft_data on the item and check that the owner is <RECIPIENT_ADDRESS> and the content is <ITEM_CONTENT>.
The calling contract must be the collection owner; otherwise the collection rejects deployments
Funds and secretsThis smart contract interacts with a collection contract. Run it on Testnet first to test the desired behavior.
The following example is a minimal smart contract that only implements the item deployment logic. In real deployments, this is integrated into a larger flow.
Copy
Ask AI
// SnakeString describes a (potentially long) string inside a cell;// short strings are stored as-is, like "my-picture.png";// long strings are nested refs, like "xxxx".ref("yyyy".ref("zzzz"))type SnakeString = slicefun SnakeString.unpackFromSlice(mutate s: slice) { // SnakeString can only be the last: it's "the remainder"; // for correctness, it's better to validate it has no more refs: assert (s.remainingRefsCount() <= 1) throw 5; val snakeRemainder = s; s = createEmptySlice(); // no more left to read return snakeRemainder}fun SnakeString.packToBuilder(self, mutate b: builder) { b.storeSlice(self)}struct NftItemInitAtDeployment { recipientAddress: address content: Cell<SnakeString>}struct (0x00000001) DeployNft { queryId: uint64 itemIndex: uint64 attachTonAmount: coins initParams: Cell<NftItemInitAtDeployment>}fun onInternalMessage(in: InMessage) { // The whole logic will be in `onInternalMessage` // for demonstration purposes. In real deployments this should // usually be gated behind authorization and other checks. val deploy = DeployNft { queryId: 0, itemIndex: <INDEX>, // will be sent to the item contract on deployment attachTonAmount: ton("0.005"), initParams: NftItemInitAtDeployment { recipientAddress: address("<RECIPIENT_ADDRESS>"), content: ("<ITEM_CONTENT>" as SnakeString).toCell() }.toCell() }; val msg = createMessage({ bounce: true, dest: address("<COLLECTION_ADDRESS>"), value: ton("0.01"), body: deploy }); msg.send(SEND_MODE_PAY_FEES_SEPARATELY);}
Where
<COLLECTION_ADDRESS> — the collection contract address.
<RECIPIENT_ADDRESS> — the initial owner address for the new item.
<INDEX> — item’s index. Note that obtaining the actual index on-chain is not possible, so a smart contract that performs these deployments should handle that logic itself (for example, store the latest used index and increment it on each deployment).
<ITEM_CONTENT> — item-specific content path or key (for example, 0.json).
The top of the snippet defines structs for the deploy message and can be modified depending on the NFT implementation specifics. The sending logic lives in onInternalMessage for simplicity. It composes a message with hard-coded example values and sends that message to the collection contract.
In a block explorer, confirm the transaction from the calling contract to <COLLECTION_ADDRESS> succeeded and inspect the transaction trace to see the internal message that deployed the item.
To verify via code: call get_nft_address_by_index(<INDEX>) on the collection to obtain the item address, then call get_nft_data on the item and check that the owner is <RECIPIENT_ADDRESS> and the content is <ITEM_CONTENT>.