diff --git a/standard/tokens/jettons/transfer.mdx b/standard/tokens/jettons/transfer.mdx
index 8c003a9ee..c95d6d266 100644
--- a/standard/tokens/jettons/transfer.mdx
+++ b/standard/tokens/jettons/transfer.mdx
@@ -86,6 +86,201 @@ async function main() {
void main();
```
+## Transfer via TON Connect
+
+To send jettons from a dApp through [TON Connect](/ecosystem/ton-connect/overview), build the [TEP-74](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md#1-transfer) transfer body and submit it to the sender's jetton wallet contract:
+
+
+ ```tsx title="React" icon="react"
+ import { useTonConnectUI } from '@tonconnect/ui-react';
+ import { beginCell, toNano, Address } from '@ton/ton';
+
+ // transfer#0f8a7ea5 query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress
+ // response_destination:MsgAddress custom_payload:(Maybe ^Cell)
+ // forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell)
+ // = InternalMsgBody;
+
+ const body = beginCell()
+ .storeUint(0x0f8a7ea5, 32) // jetton transfer op code
+ .storeUint(0, 64) // query_id:uint64
+ .storeCoins(toNano('0.001')) // amount — decimals vary per token (6 for USDT, 9 default)
+ .storeAddress(Address.parse('')) // destination:MsgAddress
+ .storeAddress(Address.parse('')) // response_destination:MsgAddress
+ .storeUint(0, 1) // custom_payload:(Maybe ^Cell)
+ .storeCoins(toNano('0.05')) // forward_ton_amount — if >0, sends notification
+ .storeBit(1) // forward_payload as reference
+ .storeRef(beginCell().endCell()) // empty payload reference
+ .endCell();
+
+ const transaction = {
+ validUntil: Math.floor(Date.now() / 1000) + 360,
+ messages: [
+ {
+ address: '', // sender's jetton wallet address
+ amount: toNano('0.05').toString(), // for gas fees, excess is returned
+ payload: body.toBoc().toString('base64'),
+ },
+ ],
+ };
+
+ export const TransferJetton = () => {
+ const [tonConnectUI] = useTonConnectUI();
+
+ return (
+
+ );
+ };
+ ```
+
+ ```ts title="TypeScript" icon="globe"
+ import TonConnectUI from '@tonconnect/ui';
+ import { beginCell, toNano, Address } from '@ton/ton';
+
+ const tonConnectUI = new TonConnectUI({ manifestUrl: '' });
+
+ const body = beginCell()
+ .storeUint(0x0f8a7ea5, 32)
+ .storeUint(0, 64)
+ .storeCoins(toNano('0.001'))
+ .storeAddress(Address.parse(''))
+ .storeAddress(Address.parse(''))
+ .storeUint(0, 1)
+ .storeCoins(toNano('0.05'))
+ .storeBit(1)
+ .storeRef(beginCell().endCell())
+ .endCell();
+
+ const transaction = {
+ validUntil: Math.floor(Date.now() / 1000) + 360,
+ messages: [
+ {
+ address: '',
+ amount: toNano('0.05').toString(),
+ payload: body.toBoc().toString('base64'),
+ },
+ ],
+ };
+
+ const result = await tonConnectUI.sendTransaction(transaction);
+ ```
+
+
+Where:
+
+- `` — the recipient's TON wallet address.
+- `` — the sender's TON wallet address (receives excess fees).
+- `` — the sender's jetton wallet contract address. To find it, call the `get_wallet_address` get method on the jetton master contract, passing the sender's wallet address. See the [low-level example](#transfer-using-a-wallet-contract) below for the full derivation.
+- `` — URL of the dApp manifest. See [manifest](/ecosystem/ton-connect/manifest).
+
+
+
+### Transfer with a comment
+
+To attach a text comment, serialize it in the `forward_payload` field with opcode `0` (text comment). This snippet builds only the body — send it the same way as a [regular transfer](#transfer-via-ton-connect) above:
+
+```ts
+import { beginCell, toNano, Address } from '@ton/ton';
+
+const forwardPayload = beginCell()
+ .storeUint(0, 32) // 0 opcode = text comment
+ .storeStringTail('Hello, TON!')
+ .endCell();
+
+const body = beginCell()
+ .storeUint(0x0f8a7ea5, 32)
+ .storeUint(0, 64)
+ .storeCoins(toNano('5')) // jetton amount (decimals vary)
+ .storeAddress(Address.parse(''))
+ .storeAddress(Address.parse(''))
+ .storeBit(0) // no custom payload
+ .storeCoins(toNano('0.02')) // forward amount
+ .storeBit(1) // forward_payload as reference
+ .storeRef(forwardPayload)
+ .endCell();
+```
+
+### Burn via TON Connect
+
+The burn body follows [TEP-74 burn](https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md#2-burn) (`burn#595f07bc`):
+
+
+ ```tsx title="React" icon="react"
+ import { useTonConnectUI } from '@tonconnect/ui-react';
+ import { beginCell, toNano, Address } from '@ton/ton';
+
+ // burn#595f07bc query_id:uint64 amount:(VarUInteger 16)
+ // response_destination:MsgAddress custom_payload:(Maybe ^Cell)
+
+ const body = beginCell()
+ .storeUint(0x595f07bc, 32) // jetton burn op code
+ .storeUint(0, 64) // query_id
+ .storeCoins(toNano('0.001')) // burn amount (decimals vary)
+ .storeAddress(Address.parse('')) // response_destination
+ .storeUint(0, 1) // no custom payload
+ .endCell();
+
+ const transaction = {
+ validUntil: Math.floor(Date.now() / 1000) + 360,
+ messages: [
+ {
+ address: '', // owner's jetton wallet
+ amount: toNano('0.05').toString(),
+ payload: body.toBoc().toString('base64'),
+ },
+ ],
+ };
+
+ export const BurnJetton = () => {
+ const [tonConnectUI] = useTonConnectUI();
+
+ return (
+
+ );
+ };
+ ```
+
+ ```ts title="TypeScript" icon="globe"
+ import TonConnectUI from '@tonconnect/ui';
+ import { beginCell, toNano, Address } from '@ton/ton';
+
+ const tonConnectUI = new TonConnectUI({ manifestUrl: '' });
+
+ const body = beginCell()
+ .storeUint(0x595f07bc, 32)
+ .storeUint(0, 64)
+ .storeCoins(toNano('0.001'))
+ .storeAddress(Address.parse(''))
+ .storeUint(0, 1)
+ .endCell();
+
+ const transaction = {
+ validUntil: Math.floor(Date.now() / 1000) + 360,
+ messages: [
+ {
+ address: '',
+ amount: toNano('0.05').toString(),
+ payload: body.toBoc().toString('base64'),
+ },
+ ],
+ };
+
+ const result = await tonConnectUI.sendTransaction(transaction);
+ ```
+
+
+Where:
+
+- `` — the wallet address of the jetton owner (receives excess fees).
+- `` — the owner's jetton wallet contract address.
+
+## Transfer using a wallet contract
+
For reference, here's a low-level example of the process, where message serialization is done manually.
```ts expandable
diff --git a/standard/tokens/nft/transfer.mdx b/standard/tokens/nft/transfer.mdx
index c48500d64..9775bcb95 100644
--- a/standard/tokens/nft/transfer.mdx
+++ b/standard/tokens/nft/transfer.mdx
@@ -73,3 +73,354 @@ where:
- `` — the address of the NFT item to be transferred.
- `` — the address of the recipient.
+
+## Transfer via TON Connect
+
+To send an NFT from a dApp through [TON Connect](/ecosystem/ton-connect/overview), build the transfer body and submit it to the NFT item contract:
+
+
+ ```tsx title="React" icon="react"
+ import { useTonConnectUI } from '@tonconnect/ui-react';
+ import { beginCell, toNano, Address } from '@ton/ton';
+
+ // transfer#5fcc3d14 query_id:uint64 new_owner:MsgAddress response_destination:MsgAddress
+ // custom_payload:(Maybe ^Cell) forward_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell)
+
+ const body = beginCell()
+ .storeUint(0x5fcc3d14, 32) // NFT transfer op code
+ .storeUint(0, 64) // query_id
+ .storeAddress(Address.parse('')) // new_owner
+ .storeAddress(Address.parse('')) // response_destination (excess fees)
+ .storeUint(0, 1) // no custom_payload
+ .storeCoins(toNano('0.0001')) // forward_amount
+ .storeUint(0, 1) // no forward_payload
+ .endCell();
+
+ const transaction = {
+ validUntil: Math.floor(Date.now() / 1000) + 360,
+ messages: [
+ {
+ address: '', // NFT item contract address
+ amount: toNano('0.05').toString(), // for gas fees, excess is returned
+ payload: body.toBoc().toString('base64'),
+ },
+ ],
+ };
+
+ export const TransferNft = () => {
+ const [tonConnectUI] = useTonConnectUI();
+
+ return (
+
+ );
+ };
+ ```
+
+ ```ts title="TypeScript" icon="globe"
+ import TonConnectUI from '@tonconnect/ui';
+ import { beginCell, toNano, Address } from '@ton/ton';
+
+ const tonConnectUI = new TonConnectUI({ manifestUrl: '' });
+
+ const body = beginCell()
+ .storeUint(0x5fcc3d14, 32)
+ .storeUint(0, 64)
+ .storeAddress(Address.parse(''))
+ .storeAddress(Address.parse(''))
+ .storeUint(0, 1)
+ .storeCoins(toNano('0.0001'))
+ .storeUint(0, 1)
+ .endCell();
+
+ const transaction = {
+ validUntil: Math.floor(Date.now() / 1000) + 360,
+ messages: [
+ {
+ address: '',
+ amount: toNano('0.05').toString(),
+ payload: body.toBoc().toString('base64'),
+ },
+ ],
+ };
+
+ const result = await tonConnectUI.sendTransaction(transaction);
+ ```
+
+
+Where:
+
+- `` — the TON wallet address of the new NFT owner.
+- `` — the current owner's wallet address (receives excess fees).
+- `` — the address of the NFT item contract to transfer.
+- `` — URL of the dApp manifest. See [manifest](/ecosystem/ton-connect/manifest).
+
+### List an NFT for sale (GetGems)
+
+Listing on [GetGems](https://getgems.io) goes through the GetGems deployer: the seller transfers the NFT to the deployer with a `do_sale` forward payload, and the deployer atomically deploys a per-listing sale contract (`nft-fixprice-sale-v4r1`) and moves the NFT into it.
+
+```ts expandable
+import {
+ Address,
+ beginCell,
+ Cell,
+ contractAddress,
+ internal,
+ SendMode,
+ storeStateInit,
+ toNano,
+} from '@ton/ton';
+
+const GETGEMS_DEPLOYER = Address.parse('EQA51jCD5I9GRS_4oEzQ03M6kMi-KZqllRtidBdZssF-DjDh');
+const GETGEMS_MARKETPLACE = Address.parse('EQBYTuYbLf8INxFtD8tQeNk5ZLy-nAX9ahQbG_yl1qQ-GEMS');
+const GETGEMS_FEE_ADDRESS = Address.parse('UQC-56PXuMBvlVIDLDiApWp0twKtiyJPQr-xrBwzvXGQgKvJ');
+
+// nft-fixprice-sale-v4r1 code BOC;
+const SALE_V4R1_CODE_BOC =
+ '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';
+const SALE_V4R1_CODE = Cell.fromBoc(Buffer.from(SALE_V4R1_CODE_BOC, 'base64'))[0];
+
+interface ListingParams {
+ seller: Address;
+ nftAddress: Address;
+ royaltyAddress: Address;
+ royaltyPercent: number;
+ // Sale price in nanoTON
+ fullPrice: bigint;
+}
+
+const GETGEMS_FEE_PERCENT = 5000;
+
+function buildListingMessage(p: ListingParams) {
+ const staticData = beginCell()
+ .storeAddress(GETGEMS_FEE_ADDRESS) // fee_address
+ .storeAddress(p.royaltyAddress) // royalty_address
+ .storeUint(GETGEMS_FEE_PERCENT, 17) // fee_percent
+ .storeUint(p.royaltyPercent, 17) // royalty_percent
+ .storeAddress(p.nftAddress) // nft_address
+ .storeUint(Math.floor(Date.now() / 1000), 32) // created_at
+ .endCell();
+
+ const saleData = beginCell()
+ .storeUint(0, 1) // is_complete
+ .storeAddress(GETGEMS_MARKETPLACE) // marketplace_address
+ .storeAddress(p.seller) // nft_owner_address (receives sale proceeds)
+ .storeCoins(p.fullPrice) // full_price
+ .storeUint(0, 32) // sold_at
+ .storeUint(0, 64) // sold_query_id
+ .storeRef(staticData)
+ .storeBit(0) // jetton_price_dict (Maybe = empty)
+ .endCell();
+
+ const stateInit = { code: SALE_V4R1_CODE, data: saleData };
+ const saleAddress = contractAddress(0, stateInit);
+ const stateInitCell = beginCell().store(storeStateInit(stateInit)).endCell();
+
+ // deploy_blank — accepts coins on deploy without further processing, no signature required
+ const saleBody = beginCell()
+ .storeUint(0x664c0905, 32) // op: deploy_blank
+ .storeUint(0, 64) // query_id
+ .endCell();
+
+ const forwardPayload = beginCell()
+ .storeUint(0x0fe0ede, 32) // op: do_sale
+ .storeRef(stateInitCell)
+ .storeRef(saleBody)
+ .storeCoins(toNano('0.02')) // fwd_amount
+ .endCell();
+
+ const transferBody = beginCell()
+ .storeUint(0x5fcc3d14, 32) // op: transfer
+ .storeUint(0, 64) // query_id
+ .storeAddress(GETGEMS_DEPLOYER) // new_owner
+ .storeAddress(p.seller) // response_destination (excess TON)
+ .storeBit(0) // no custom_payload
+ .storeCoins(toNano('0.2')) // forward_amount to deployer
+ .storeBit(1) // forward_payload is a ref
+ .storeRef(forwardPayload) // ref(forward_payload)
+ .endCell();
+
+ // value covers deployer gas, sale-contract deploy, and excess return to seller
+ return {
+ saleAddress,
+ message: internal({
+ to: p.nftAddress,
+ value: toNano('0.5'),
+ bounce: true,
+ body: transferBody,
+ }),
+ };
+}
+```
+
+When the deployer receives the NFT, it validates the sale contract code hash against its allowlist, deploys the contract from the supplied `stateInit`, and forwards the NFT to the new sale address. Confirm the listing went through by checking that the NFT's `owner_address` equals the computed `saleAddress`.
+
+### Buy an NFT (GetGems)
+
+GetGems sale contracts come in two active versions: [`nft-fixprice-sale-v3r3`](https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/sources/nft-fixprice-sale-v3r3.fc) and [`nft-fixprice-sale-v4r1`](https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/sources/nft-fixprice-sale-v4r1.fc). Both are bought the same way — send a regular transfer **without** payload, with `amount = full_price + 0.1 TON`. The marketplace fee and royalty are taken out of `full_price` itself; the extra `0.1 TON` matches the v4r1 contract's `min_gas_amount` (see [`nft-fixprice-sale-v4r1.fc` line 9](https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/sources/nft-fixprice-sale-v4r1.fc#L9)), and any unspent gas is returned to the buyer with the NFT.
+
+
+
+#### Verify the sale contract
+
+Confirm three things before sending TON: the deployed code matches one of the canonical hashes, the sale getter describes an active sale for the expected NFT and price, and the NFT is held by the sale contract.
+
+```ts expandable
+import { Cell, TonClient, Address } from '@ton/ton';
+
+// Code hashes from the GetGems nft-contracts repo
+const NFT_FIXPRICE_SALE_V3R3_HASH = Buffer.from(
+ '24221fa571e542e055c77bedfdbf527c7af460cfdc7f344c450787b4cfa1eb4d',
+ 'hex',
+);
+const NFT_FIXPRICE_SALE_V4R1_HASH = Buffer.from(
+ '6b95a6418b9c9d2359045d1e7559b8d549ae0e506f24caab58fa30c8fb1feb86',
+ 'hex',
+);
+
+async function verifySaleContract(
+ client: TonClient,
+ saleAddress: Address,
+ expectedNftAddress: Address,
+ expectedPrice: bigint,
+): Promise {
+ // 1. Deployed code must match a canonical GetGems sale contract
+ const state = await client.getContractState(saleAddress);
+ if (!state.code) throw new Error('Sale contract is not deployed');
+ const codeHash = Cell.fromBoc(state.code)[0].hash();
+ const isV3 = codeHash.equals(NFT_FIXPRICE_SALE_V3R3_HASH);
+ const isV4 = codeHash.equals(NFT_FIXPRICE_SALE_V4R1_HASH);
+ if (!isV3 && !isV4) {
+ throw new Error('Code hash matches neither v3r3 nor v4r1 — possible lookalike contract');
+ }
+
+ // 2. The sale getter must describe an active sale for the expected NFT and price.
+ let nftAddress: Address;
+ let fullPrice: bigint;
+ if (isV3) {
+ // v3r3 get_sale_data: magic, is_complete, created_at, marketplace_address,
+ // nft_address, nft_owner_address, full_price, ...
+ const sale = await client.runMethod(saleAddress, 'get_sale_data');
+ if (sale.stack.readNumber() !== 0x46495850) throw new Error('Wrong sale magic constant');
+ if (sale.stack.readNumber() !== 0) throw new Error('Sale already complete');
+ // Skip created_at and marketplace_address
+ sale.stack.readNumber();
+ sale.stack.readAddress();
+ nftAddress = sale.stack.readAddress();
+ // Skip nft_owner_address
+ sale.stack.readAddress();
+ fullPrice = sale.stack.readBigNumber();
+ } else {
+ // v4r1 get_fix_price_data_v4 stack: is_complete, created_at, marketplace_address,
+ // nft_address, nft_owner_address, full_price, fee_address, fee_percent,
+ // royalty_address, royalty_percent, sold_at, sold_query_id, jetton_price_dict.
+ const sale = await client.runMethod(saleAddress, 'get_fix_price_data_v4');
+ if (sale.stack.readBoolean()) throw new Error('Sale already complete');
+ // Skip created_at and marketplace_address
+ sale.stack.readBigNumber();
+ sale.stack.readAddress();
+ nftAddress = sale.stack.readAddress();
+ // Skip nft_owner_address
+ sale.stack.readAddress();
+ fullPrice = sale.stack.readBigNumber();
+ // Skip fee_address, fee_percent, royalty_address, royalty_percent, sold_at, sold_query_id
+ sale.stack.readAddress();
+ sale.stack.readBigNumber();
+ sale.stack.readAddress();
+ sale.stack.readBigNumber();
+ sale.stack.readBigNumber();
+ sale.stack.readBigNumber();
+ // Reject mixed TON/jetton sales - this verifier handles TON-only
+ if (sale.stack.readCellOpt() !== null) {
+ throw new Error('Sale also accepts jettons; this verifier handles TON-only');
+ }
+ }
+ if (!nftAddress.equals(expectedNftAddress)) throw new Error('Sale points to a different NFT');
+ if (fullPrice !== expectedPrice) throw new Error('Price does not match');
+
+ // 3. The NFT must be held by the sale contract.
+ // get_nft_data stack order: init?, index, collection_address, owner_address, individual_content
+ const nftData = await client.runMethod(nftAddress, 'get_nft_data');
+ // Skip init?, index, collection_address
+ nftData.stack.readBigNumber();
+ nftData.stack.readBigNumber();
+ nftData.stack.readAddress();
+ const owner = nftData.stack.readAddress();
+ if (!owner.equals(saleAddress)) {
+ throw new Error('NFT is not held by the sale contract');
+ }
+}
+```
+
+The canonical BoCs and reference verifier ([`isNftFixPriceSaleV3Contract`](https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/nft-fixprice-sale-v3/isNftFixPriceSaleV3Contract.ts)) live in the GetGems nft-contracts repo, alongside the v4 source ([`NftFixPriceSaleV4.source.ts`](https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/nft-fixprice-sale-v4/NftFixPriceSaleV4.source.ts)).
+
+#### Send the buy transaction
+
+Once verification passes, send a payload-free transfer:
+
+
+ ```tsx title="React" icon="react"
+ import { useTonConnectUI } from '@tonconnect/ui-react';
+ import { toNano } from '@ton/ton';
+
+ // NFT price in TON; total value is price + 0.1 TON for network gas, with excess returned
+ const nftPrice = 5.0;
+ const buyAmount = nftPrice + 0.1;
+
+ const transaction = {
+ validUntil: Math.floor(Date.now() / 1000) + 360,
+ messages: [
+ {
+ address: '',
+ amount: toNano(buyAmount).toString(),
+ },
+ ],
+ };
+
+ export const BuyNft = () => {
+ const [tonConnectUI] = useTonConnectUI();
+
+ return (
+
+ );
+ };
+ ```
+
+ ```ts title="TypeScript" icon="globe"
+ import TonConnectUI from '@tonconnect/ui';
+ import { toNano } from '@ton/ton';
+
+ const tonConnectUI = new TonConnectUI({ manifestUrl: '' });
+
+ const nftPrice = 5.0;
+ const buyAmount = nftPrice + 0.1;
+
+ const transaction = {
+ validUntil: Math.floor(Date.now() / 1000) + 360,
+ messages: [
+ {
+ address: '',
+ amount: toNano(buyAmount).toString(),
+ },
+ ],
+ };
+
+ const result = await tonConnectUI.sendTransaction(transaction);
+ ```
+
+
+Where `` is the address of the verified GetGems sale contract.
+
+## See also
+
+- [dApp integration with TON Connect](/ecosystem/ton-connect/dapp)
+- [Jetton transfer](/standard/tokens/jettons/transfer)
+- [NFT standard overview](/standard/tokens/nft/how-it-works)