From 877599464227b02be87ad3c394a82b0dd48a1113 Mon Sep 17 00:00:00 2001 From: Gusarich Date: Fri, 7 Nov 2025 07:07:43 +0300 Subject: [PATCH 01/10] feat: how to mint nft --- resources/images/nft/nft_deploy.svg | 4 + resources/images/nft/nft_deploy_dark.svg | 1 - resources/images/nft/nft_deploy_light.svg | 1 - standard/tokens/nft/how-to-deploy-item.mdx | 134 ++++++++++++++++++++- 4 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 resources/images/nft/nft_deploy.svg delete mode 100644 resources/images/nft/nft_deploy_dark.svg delete mode 100644 resources/images/nft/nft_deploy_light.svg diff --git a/resources/images/nft/nft_deploy.svg b/resources/images/nft/nft_deploy.svg new file mode 100644 index 000000000..ade996c9d --- /dev/null +++ b/resources/images/nft/nft_deploy.svg @@ -0,0 +1,4 @@ + + +Collection adminCollectionItemDeployrequestDeploy \ No newline at end of file diff --git a/resources/images/nft/nft_deploy_dark.svg b/resources/images/nft/nft_deploy_dark.svg deleted file mode 100644 index b65c0e395..000000000 --- a/resources/images/nft/nft_deploy_dark.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/resources/images/nft/nft_deploy_light.svg b/resources/images/nft/nft_deploy_light.svg deleted file mode 100644 index db69ac2b8..000000000 --- a/resources/images/nft/nft_deploy_light.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/standard/tokens/nft/how-to-deploy-item.mdx b/standard/tokens/nft/how-to-deploy-item.mdx index c76510ce4..58698bcf3 100644 --- a/standard/tokens/nft/how-to-deploy-item.mdx +++ b/standard/tokens/nft/how-to-deploy-item.mdx @@ -5,14 +5,136 @@ sidebarTitle: "How to deploy NFT item" import { Aside } from '/snippets/aside.jsx'; import { Image } from '/snippets/image.jsx'; -import { Stub } from '/snippets/stub.jsx'; - - -The creator sends a message to the collection contract, which deploys a new NFT item with the specified data (initial owner and item‑specific content). The standard does not prescribe exactly how this data must be supplied. 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](/standard/tokens/nft/cNFT-how-it-works)). +The creator sends a message to the collection contract, which deploys a new NFT item with the specified data, including initial owner and item‑specific content. The [TEP‑62](https://github.com/ton-blockchain/TEPs/blob/1fbc23cac69723c53251f686ec90d81bf0e83443/text/0062-nft-standard.md) standard does not prescribe exactly how this data must be supplied. Custom modifications of NFT contracts can implement this logic differently. 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](/standard/tokens/nft/cNFT-how-it-works)). NFT deployment + +Since the deployment process is not specified by the standard, the logic can vary and recipes listed on this page might not apply. But the [reference NFT implementation](/standard/tokens/nft/reference-implementation) and most modifications follow the same path where collection's owner sends a message with deploy parameters to collection, and collection then actually deploys the item. + +## Deploy with wallet + +To deploy an item from a [wallet](/standard/wallets/how-it-works) we have to send a message from that wallet to collection contract. The following example uses @ton/ton stack for TypeScript. These libraries provide interfaces to work with wallet contracts and composse messages. + +```ts +import { Address, beginCell, internal, toNano } from "@ton/core"; +import { TonClient, WalletContractV5R1, SendMode } from "@ton/ton"; +import { mnemonicToPrivateKey } from "@ton/crypto"; + +async function main() { + const msg = internal({ + to: Address.parse("EQASkeWqAUdq4-9AS68fsvxuHr8sze7KushNeDKHdCDCFCVF"), // collection + value: toNano("0.01"), // 0.01 ton for fees + bounce: true, + body: beginCell() + .storeUint(1, 32) // deploy opcode + .storeUint(123, 64) // query id + .storeUint(0, 64) // item index + .storeCoins(toNano("0.005")) // 0.005 ton will be sent to item contract on deployment + .storeRef( + beginCell() + .storeAddress( + Address.parse("UQBKgXCNLPexWhs2L79kiARR1phGH1LwXxRbNsCFF9doczSI"), + ) // owner + .storeRef(beginCell().storeStringTail("0.json").endCell()) // individual content + .endCell(), + ) + .endCell(), + }); + + // testnet toncenter endpoint + const client = new TonClient({ + endpoint: "https://testnet.toncenter.com/api/v2/jsonRPC", + }); + + const MNEMONIC = process.env.MNEMONIC; + if (!MNEMONIC) throw new Error("Set MNEMONIC to a test mnemonic (testnet)."); + const keyPair = await mnemonicToPrivateKey(MNEMONIC.split(" ")); + const walletContract = client.open( + WalletContractV5R1.create({ + workchain: 0, // basechain + publicKey: keyPair.publicKey, + }), + ); + + // send the mint message through your 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(); +``` + +It composes a message, creates a `TonClient` object to interact with the chain through [Toncenter](/ecosystem/api/toncenter/overview), initializes a wallet from mnemonic, and send a message. + +## Deploy with smart contract + +To deploy an item from a smart contract we have to send a message from that smart contract to collection contract. The following example implements a minimal reproducible example, without any extra logic apart from the item deployment. In realistic cases this would be integrated as a part of some larger logic. + +```tolk +// 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 = slice + +fun SnakeString.unpackFromSlice(mutate s: slice) { + // SnakeString can be only 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 { + ownerAddress: address + content: Cell +} + +struct (0x00000001) DeployNft { + queryId: uint64 + itemIndex: uint64 + attachTonAmount: coins + initParams: Cell +} + +fun onInternalMessage(in: InMessage) { + // The whole logic will be in `onInternalMessage` + // for example purposes. In realistic cases this should + // usually be gated behind some auth and other checks. + + val deploy = DeployNft { + queryId: 123, + itemIndex: 0, + attachTonAmount: ton("0.005"), // will be sent to item contract on deployment + initParams: NftItemInitAtDeployment { + ownerAddress: address("UQBKgXCNLPexWhs2L79kiARR1phGH1LwXxRbNsCFF9doczSI"), + content: ("0.json" as SnakeString).toCell() + }.toCell() + }; + + val msg = createMessage({ + bounce: true, + dest: address("EQDmJlN-cAQp9AQFmJ6an8MTF90FDsjNazuQg4YbKZwlzNnz"), // collection + value: ton("0.01"), + body: deploy + }); + + msg.send(SEND_MODE_PAY_FEES_SEPARATELY); +} +``` + +Top of the snippet define 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 hardcoded example values and sends that message to collection contract. From 70d240100be70000d061ac03915aea57ff61a790 Mon Sep 17 00:00:00 2001 From: Gusarich Date: Fri, 7 Nov 2025 09:42:13 +0300 Subject: [PATCH 02/10] feat: improvements --- standard/tokens/nft/how-to-deploy-item.mdx | 73 +++++++++++++++------- 1 file changed, 51 insertions(+), 22 deletions(-) diff --git a/standard/tokens/nft/how-to-deploy-item.mdx b/standard/tokens/nft/how-to-deploy-item.mdx index 58698bcf3..0bd5ae3be 100644 --- a/standard/tokens/nft/how-to-deploy-item.mdx +++ b/standard/tokens/nft/how-to-deploy-item.mdx @@ -1,23 +1,28 @@ --- -title: "How to deploy NFT item" -sidebarTitle: "How to deploy NFT item" +title: "How to deploy an NFT item" +sidebarTitle: "Deploy an NFT item" --- -import { Aside } from '/snippets/aside.jsx'; import { Image } from '/snippets/image.jsx'; -The creator sends a message to the collection contract, which deploys a new NFT item with the specified data, including initial owner and item‑specific content. The [TEP‑62](https://github.com/ton-blockchain/TEPs/blob/1fbc23cac69723c53251f686ec90d81bf0e83443/text/0062-nft-standard.md) standard does not prescribe exactly how this data must be supplied. Custom modifications of NFT contracts can implement this logic differently. 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](/standard/tokens/nft/cNFT-how-it-works)). +The creator sends a message to the collection contract, which deploys a new non‑fungible token (NFT) item with the specified data: the initial owner and the item‑specific content. The [TEP‑62](https://github.com/ton-blockchain/TEPs/blob/1fbc23cac69723c53251f686ec90d81bf0e83443/text/0062-nft-standard.md) 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](/standard/tokens/nft/cNFT-how-it-works)). NFT deployment -Since the deployment process is not specified by the standard, the logic can vary and recipes listed on this page might not apply. But the [reference NFT implementation](/standard/tokens/nft/reference-implementation) and most modifications follow the same path where collection's owner sends a message with deploy parameters to collection, and collection then actually deploys the item. +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](/standard/tokens/nft/reference-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. ## Deploy with wallet -To deploy an item from a [wallet](/standard/wallets/how-it-works) we have to send a message from that wallet to collection contract. The following example uses @ton/ton stack for TypeScript. These libraries provide interfaces to work with wallet contracts and composse messages. +To deploy an item from a [wallet](/standard/wallets/how-it-works), send a message from the wallet to the collection contract. The following example uses the @ton/ton stack for TypeScript. These libraries provide interfaces to work with wallet contracts and compose messages. + +### Prerequisites + +- Node.js 22+ +- Packages: `@ton/ton`, `@ton/core`, `@ton/crypto` +- A funded Testnet wallet mnemonic in `MNEMONIC` environment variable ```ts import { Address, beginCell, internal, toNano } from "@ton/core"; @@ -26,26 +31,24 @@ import { mnemonicToPrivateKey } from "@ton/crypto"; async function main() { const msg = internal({ - to: Address.parse("EQASkeWqAUdq4-9AS68fsvxuHr8sze7KushNeDKHdCDCFCVF"), // collection - value: toNano("0.01"), // 0.01 ton for fees + to: Address.parse(""), + value: toNano("0.01"), // 0.01 TON for fees bounce: true, body: beginCell() .storeUint(1, 32) // deploy opcode - .storeUint(123, 64) // query id + .storeUint(0, 64) // query id .storeUint(0, 64) // item index - .storeCoins(toNano("0.005")) // 0.005 ton will be sent to item contract on deployment + .storeCoins(toNano("0.005")) // 0.005 TON will be sent to item contract on deployment .storeRef( beginCell() - .storeAddress( - Address.parse("UQBKgXCNLPexWhs2L79kiARR1phGH1LwXxRbNsCFF9doczSI"), - ) // owner - .storeRef(beginCell().storeStringTail("0.json").endCell()) // individual content + .storeAddress(Address.parse("")) + .storeRef(beginCell().storeStringTail("").endCell()) // individual content .endCell(), ) .endCell(), }); - // testnet toncenter endpoint + // Testnet Toncenter endpoint const client = new TonClient({ endpoint: "https://testnet.toncenter.com/api/v2/jsonRPC", }); @@ -60,7 +63,7 @@ async function main() { }), ); - // send the mint message through your wallet + // send the mint message through the wallet const seqno = await walletContract.getSeqno(); await walletContract.sendTransfer({ seqno: seqno, @@ -74,11 +77,26 @@ async function main() { void main(); ``` +Where + +- `` — the collection contract address. +- `` — the initial owner address for the new item. +- `` — item‑specific content path or key (for example, `0.json`). + +### Verify + +- Confirm the transaction succeeded in a block explorer for ``. +- Call `get_nft_address_by_index(0)` on the collection to fetch the new item address, or open the item link in the explorer. + It composes a message, creates a `TonClient` object to interact with the chain through [Toncenter](/ecosystem/api/toncenter/overview), initializes a wallet from mnemonic, and send a message. ## Deploy with smart contract -To deploy an item from a smart contract we have to send a message from that smart contract to collection contract. The following example implements a minimal reproducible example, without any extra logic apart from the item deployment. In realistic cases this would be integrated as a part of some larger logic. +To deploy an item from a smart contract, send a message from the contract to the collection contract. The following example provides a minimal example that implements only the item deployment logic. In realistic cases this is integrated into a larger flow. + +### Prerequisites + +- Enough Testnet TON to cover fees for contract and item deployments ```tolk // SnakeString describes a (potentially long) string inside a cell; @@ -117,18 +135,18 @@ fun onInternalMessage(in: InMessage) { // usually be gated behind some auth and other checks. val deploy = DeployNft { - queryId: 123, + queryId: 0, itemIndex: 0, attachTonAmount: ton("0.005"), // will be sent to item contract on deployment initParams: NftItemInitAtDeployment { - ownerAddress: address("UQBKgXCNLPexWhs2L79kiARR1phGH1LwXxRbNsCFF9doczSI"), - content: ("0.json" as SnakeString).toCell() + ownerAddress: address(""), + content: ("" as SnakeString).toCell() }.toCell() }; val msg = createMessage({ bounce: true, - dest: address("EQDmJlN-cAQp9AQFmJ6an8MTF90FDsjNazuQg4YbKZwlzNnz"), // collection + dest: address(""), value: ton("0.01"), body: deploy }); @@ -137,4 +155,15 @@ fun onInternalMessage(in: InMessage) { } ``` -Top of the snippet define 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 hardcoded example values and sends that message to collection contract. +Where + +- `` — the collection contract address. +- `` — the initial owner address for the new item. +- `` — 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. + +### Verify + +- Confirm the transaction succeeded in a block explorer for ``. +- Call `get_nft_address_by_index(0)` on the collection to fetch the new item address, or open the item link in the explorer. From 2cda46fed739f378cb4e888c5fbebbfd74a0e19b Mon Sep 17 00:00:00 2001 From: Gusarich Date: Mon, 10 Nov 2025 23:51:32 +0300 Subject: [PATCH 03/10] feat: improvements --- standard/tokens/nft/how-to-deploy-item.mdx | 120 ++++++++++++++------- 1 file changed, 81 insertions(+), 39 deletions(-) diff --git a/standard/tokens/nft/how-to-deploy-item.mdx b/standard/tokens/nft/how-to-deploy-item.mdx index 0bd5ae3be..88585758f 100644 --- a/standard/tokens/nft/how-to-deploy-item.mdx +++ b/standard/tokens/nft/how-to-deploy-item.mdx @@ -3,26 +3,34 @@ title: "How to deploy an NFT item" sidebarTitle: "Deploy an NFT item" --- +import { Aside } from '/snippets/aside.jsx'; import { Image } from '/snippets/image.jsx'; -The creator sends a message to the collection contract, which deploys a new non‑fungible token (NFT) item with the specified data: the initial owner and the item‑specific content. The [TEP‑62](https://github.com/ton-blockchain/TEPs/blob/1fbc23cac69723c53251f686ec90d81bf0e83443/text/0062-nft-standard.md) 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](/standard/tokens/nft/cNFT-how-it-works)). +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](/standard/tokens/nft/overview) 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](/standard/tokens/nft/cNFT-how-it-works)). NFT deployment -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](/standard/tokens/nft/reference-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. +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 [NFT reference implementation — Deploy single item](/standard/tokens/nft/reference-implementation#deploy-single-item) 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. -## Deploy with wallet +## Deploy an item with a wallet -To deploy an item from a [wallet](/standard/wallets/how-it-works), send a message from the wallet to the collection contract. The following example uses the @ton/ton stack for TypeScript. These libraries provide interfaces to work with wallet contracts and compose messages. +To deploy an item from a [wallet](/standard/wallets/how-it-works), send a message from the wallet to the collection contract. ### Prerequisites - Node.js 22+ - Packages: `@ton/ton`, `@ton/core`, `@ton/crypto` -- A funded Testnet wallet mnemonic in `MNEMONIC` environment variable +- A funded Testnet wallet mnemonic in the `MNEMONIC` environment variable +- The sending wallet must be the collection owner; otherwise the collection rejects deployments + + + +The following example uses the @ton/ton stack for TypeScript. These libraries provide interfaces to work with wallet contracts and compose messages. ```ts import { Address, beginCell, internal, toNano } from "@ton/core"; @@ -30,31 +38,42 @@ import { TonClient, WalletContractV5R1, SendMode } from "@ton/ton"; import { mnemonicToPrivateKey } from "@ton/crypto"; async function main() { + const collectionAddress = Address.parse(""); + const recipientAddress = Address.parse(""); + + // 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. + + // Compose deploy message const msg = internal({ - to: Address.parse(""), - value: toNano("0.01"), // 0.01 TON for fees + to: collectionAddress, + value: toNano("0.01"), // 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). bounce: true, body: beginCell() .storeUint(1, 32) // deploy opcode .storeUint(0, 64) // query id - .storeUint(0, 64) // item index - .storeCoins(toNano("0.005")) // 0.005 TON will be sent to item contract on deployment + .storeUint(nextItemIndex, 64) + .storeCoins(toNano("0.005")) // Forwarded to the new item as its initial balance. Ensure `value` >= this amount + all fees. .storeRef( beginCell() - .storeAddress(Address.parse("")) + .storeAddress(recipientAddress) .storeRef(beginCell().storeStringTail("").endCell()) // individual content .endCell(), ) .endCell(), }); - // Testnet Toncenter endpoint - const client = new TonClient({ - endpoint: "https://testnet.toncenter.com/api/v2/jsonRPC", - }); - + // Initialize wallet const MNEMONIC = process.env.MNEMONIC; - if (!MNEMONIC) throw new Error("Set MNEMONIC to a test mnemonic (testnet)."); + if (!MNEMONIC) throw new Error("Set MNEMONIC to a test mnemonic (Testnet)."); const keyPair = await mnemonicToPrivateKey(MNEMONIC.split(" ")); const walletContract = client.open( WalletContractV5R1.create({ @@ -63,12 +82,12 @@ async function main() { }), ); - // send the mint message through the wallet + // 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 + // Good practice to use these modes for regular wallet transfers sendMode: SendMode.IGNORE_ERRORS | SendMode.PAY_GAS_SEPARATELY, messages: [msg], }); @@ -79,24 +98,46 @@ void main(); Where -- `` — the collection contract address. -- `` — the initial owner address for the new item. +- `` — the collection contract address. +- `` — the initial owner address for the new item. - `` — 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 (see [NFT reference implementation — Deploy single item](/standard/tokens/nft/reference-implementation#deploy-single-item)). 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](/standard/tokens/nft/reference-implementation#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. + +TL‑B for the reference implementation + +```tlb +deploy_nft#00000001 query_id:uint64 item_index:uint64 amount:Coins content:^Cell = InternalMsgBody; +``` + +In the reference contract, the body for `op=1` consists of `query_id`, `item_index`, `amount`, and a reference to `content`. See the [NFT reference implementation — Deploy single item](/standard/tokens/nft/reference-implementation#deploy-single-item) and [TL‑B overview](/languages/TL-B/overview). + ### Verify -- Confirm the transaction succeeded in a block explorer for ``. -- Call `get_nft_address_by_index(0)` on the collection to fetch the new item address, or open the item link in the explorer. +- In a block explorer, confirm the transaction for `` 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()`](/standard/tokens/nft/reference-implementation#return-item-address-by-index) on the collection with the item index used in the deploy message to obtain the item address, then call [`get_nft_data`](/standard/tokens/nft/reference-implementation#return-item-data) on the item and check that the owner is `` and the content is ``. -It composes a message, creates a `TonClient` object to interact with the chain through [Toncenter](/ecosystem/api/toncenter/overview), initializes a wallet from mnemonic, and send a message. -## Deploy with smart contract +## Deploy an item with a smart contract -To deploy an item from a smart contract, send a message from the contract to the collection contract. The following example provides a minimal example that implements only the item deployment logic. In realistic cases this is integrated into a larger flow. +To deploy an item from a smart contract, send a message from the contract to the collection contract. ### Prerequisites -- Enough Testnet TON to cover fees for contract and item deployments +- A deployed calling contract that can send internal messages +- Enough Testnet funds on the calling contract to cover fees and attached value (for example, ≥ 0.02 TON) +- ``, ``, ``, `` +- The calling contract must be the collection owner; otherwise the collection rejects deployments + + + +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. ```tolk // SnakeString describes a (potentially long) string inside a cell; @@ -105,7 +146,7 @@ To deploy an item from a smart contract, send a message from the contract to the type SnakeString = slice fun SnakeString.unpackFromSlice(mutate s: slice) { - // SnakeString can be only the last: it's "the remainder"; + // 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; @@ -118,7 +159,7 @@ fun SnakeString.packToBuilder(self, mutate b: builder) { } struct NftItemInitAtDeployment { - ownerAddress: address + recipientAddress: address content: Cell } @@ -131,22 +172,22 @@ struct (0x00000001) DeployNft { fun onInternalMessage(in: InMessage) { // The whole logic will be in `onInternalMessage` - // for example purposes. In realistic cases this should - // usually be gated behind some auth and other checks. + // for demonstration purposes. In real deployments this should + // usually be gated behind authorization and other checks. val deploy = DeployNft { queryId: 0, - itemIndex: 0, - attachTonAmount: ton("0.005"), // will be sent to item contract on deployment + itemIndex: , + attachTonAmount: ton("0.005"), // will be sent to the item contract on deployment initParams: NftItemInitAtDeployment { - ownerAddress: address(""), + recipientAddress: address(""), content: ("" as SnakeString).toCell() }.toCell() }; val msg = createMessage({ bounce: true, - dest: address(""), + dest: address(""), value: ton("0.01"), body: deploy }); @@ -157,13 +198,14 @@ fun onInternalMessage(in: InMessage) { Where -- `` — the collection contract address. -- `` — the initial owner address for the new item. +- `` — the collection contract address. +- `` — the initial owner address for the new item. +- `` — item's index. Note that obtaining the actual index on‑chain is [not possible](/from-ethereum#on-chain-get-methods), 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‑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. ### Verify -- Confirm the transaction succeeded in a block explorer for ``. -- Call `get_nft_address_by_index(0)` on the collection to fetch the new item address, or open the item link in the explorer. +- In a block explorer, confirm the transaction from the calling contract to `` 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()`](/standard/tokens/nft/reference-implementation#return-item-address-by-index) on the collection to obtain the item address, then call [`get_nft_data`](/standard/tokens/nft/reference-implementation#return-item-data) on the item and check that the owner is `` and the content is ``. From 6290e4170acaf1d213d673b20af0efa24ac656ca Mon Sep 17 00:00:00 2001 From: Gusarich Date: Mon, 10 Nov 2025 23:56:34 +0300 Subject: [PATCH 04/10] feat: improvements --- standard/tokens/nft/how-to-deploy-item.mdx | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/standard/tokens/nft/how-to-deploy-item.mdx b/standard/tokens/nft/how-to-deploy-item.mdx index 88585758f..8141b5578 100644 --- a/standard/tokens/nft/how-to-deploy-item.mdx +++ b/standard/tokens/nft/how-to-deploy-item.mdx @@ -13,7 +13,7 @@ The creator sends a message to the collection contract, which deploys a new NFT alt="Diagram of an NFT item deployment from a collection contract" /> -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 [NFT reference implementation — Deploy single item](/standard/tokens/nft/reference-implementation#deploy-single-item) 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. +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](/standard/tokens/nft/reference-implementation#deploy-single-item) 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. ## Deploy an item with a wallet @@ -30,7 +30,7 @@ To deploy an item from a [wallet](/standard/wallets/how-it-works), send a messag This 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. +The following example uses the `@ton/ton` stack for TypeScript. These libraries provide interfaces to work with wallet contracts and compose messages. ```ts import { Address, beginCell, internal, toNano } from "@ton/core"; @@ -103,7 +103,7 @@ Where - `` — 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 (see [NFT reference implementation — Deploy single item](/standard/tokens/nft/reference-implementation#deploy-single-item)). TEP‑62 does not specify this opcode, so in custom implementations, this can differ. +- `.storeUint(1, 32)` — operation code `1` selects "deploy single item" in the [reference collection contract](/standard/tokens/nft/reference-implementation#deploy-single-item). 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](/standard/tokens/nft/reference-implementation#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. @@ -114,7 +114,7 @@ TL‑B for the reference implementation deploy_nft#00000001 query_id:uint64 item_index:uint64 amount:Coins content:^Cell = InternalMsgBody; ``` -In the reference contract, the body for `op=1` consists of `query_id`, `item_index`, `amount`, and a reference to `content`. See the [NFT reference implementation — Deploy single item](/standard/tokens/nft/reference-implementation#deploy-single-item) and [TL‑B overview](/languages/TL-B/overview). +In the [reference contract](/standard/tokens/nft/reference-implementation#deploy-single-item), the body for `op=1` consists of `query_id`, `item_index`, `amount`, and a reference to `content`. See the [TL‑B overview](/languages/TL-B/overview). ### Verify @@ -128,7 +128,6 @@ To deploy an item from a smart contract, send a message from the contract to the ### Prerequisites -- A deployed calling contract that can send internal messages - Enough Testnet funds on the calling contract to cover fees and attached value (for example, ≥ 0.02 TON) - ``, ``, ``, `` - The calling contract must be the collection owner; otherwise the collection rejects deployments From e1e2ec17f2c2dec13cd11fb50475b855f0a0bf5f Mon Sep 17 00:00:00 2001 From: Gusarich Date: Mon, 10 Nov 2025 23:57:24 +0300 Subject: [PATCH 05/10] fix: formatting --- standard/tokens/nft/how-to-deploy-item.mdx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/standard/tokens/nft/how-to-deploy-item.mdx b/standard/tokens/nft/how-to-deploy-item.mdx index 8141b5578..6f47a76ec 100644 --- a/standard/tokens/nft/how-to-deploy-item.mdx +++ b/standard/tokens/nft/how-to-deploy-item.mdx @@ -26,7 +26,10 @@ To deploy an item from a [wallet](/standard/wallets/how-it-works), send a messag - A funded Testnet wallet mnemonic in the `MNEMONIC` environment variable - The sending wallet must be the collection owner; otherwise the collection rejects deployments -