Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions standard/tokens/jettons/transfer.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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:

<CodeGroup>
```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_WALLET>')) // destination:MsgAddress
.storeAddress(Address.parse('<SENDER_WALLET>')) // 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_JETTON_WALLET>', // sender's jetton wallet address
amount: toNano('0.05').toString(), // for gas fees, excess is returned
Comment on lines +110 to +120
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the jetton wallet needs message_value > jetton_wallet_gas + forward_ton_amount + receiver_gas, but here forward_ton_amount = message value = 0.05 TON so by the time the chain reaches the receiver's jetton wallet there's nothing left to forward as the notification. typical safe values are total ~0.1 TON with forward ~0.02 TON (matches the low-level example below at lines 352/360). this also applies to the bare TS variant on lines 150/160

correct me if i am wrong

Suggested change
.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_JETTON_WALLET>', // sender's jetton wallet address
amount: toNano('0.05').toString(), // for gas fees, excess is returned
.storeUint(0, 1) // custom_payload:(Maybe ^Cell)
.storeCoins(toNano('0.02')) // forward_ton_amount — must be < message value
.storeBit(0) // forward_payload inline, empty
.endCell();
const transaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: '<SENDER_JETTON_WALLET>', // sender's jetton wallet address
amount: toNano('0.1').toString(), // for gas fees, excess is returned

payload: body.toBoc().toString('base64'),
},
],
};

export const TransferJetton = () => {
const [tonConnectUI] = useTonConnectUI();

return (
<button onClick={() => tonConnectUI.sendTransaction(transaction)}>
Send jetton
</button>
);
};
```

```ts title="TypeScript" icon="globe"
import TonConnectUI from '@tonconnect/ui';
import { beginCell, toNano, Address } from '@ton/ton';

const tonConnectUI = new TonConnectUI({ manifestUrl: '<MANIFEST_URL>' });

const body = beginCell()
.storeUint(0x0f8a7ea5, 32)
.storeUint(0, 64)
.storeCoins(toNano('0.001'))
.storeAddress(Address.parse('<DESTINATION_WALLET>'))
.storeAddress(Address.parse('<SENDER_WALLET>'))
.storeUint(0, 1)
.storeCoins(toNano('0.05'))
.storeBit(1)
.storeRef(beginCell().endCell())
.endCell();

const transaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: '<SENDER_JETTON_WALLET>',
amount: toNano('0.05').toString(),
payload: body.toBoc().toString('base64'),
},
],
};

const result = await tonConnectUI.sendTransaction(transaction);
```
Comment thread
coalus marked this conversation as resolved.
</CodeGroup>

Where:

- `<DESTINATION_WALLET>` — the recipient's TON wallet address.
- `<SENDER_WALLET>` — the sender's TON wallet address (receives excess fees).
- `<SENDER_JETTON_WALLET>` — 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.
- `<MANIFEST_URL>` — URL of the dApp manifest. See [manifest](/ecosystem/ton-connect/manifest).

<Aside>
`toNano` assumes 9 decimals. For tokens with different decimals (for example, 6 for USDT), compute the amount manually: `BigInt(value) * 10n ** BigInt(jettonDecimals)`.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tiny: the Aside has no type= so it defaults to a note. that's probably what's intended here, but explicit type makes it consistent with the rest of the file (lines 9, 26, etc)

Suggested change
`toNano` assumes 9 decimals. For tokens with different decimals (for example, 6 for USDT), compute the amount manually: `BigInt(value) * 10n ** BigInt(jettonDecimals)`.
<Aside type="note">
`toNano` assumes 9 decimals. For tokens with different decimals (for example, 6 for USDT), compute the amount manually: `BigInt(value) * 10n ** BigInt(jettonDecimals)`.
</Aside>

</Aside>

### 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('<DESTINATION_WALLET>'))
.storeAddress(Address.parse('<SENDER_WALLET>'))
.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`):

<CodeGroup>
```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('<OWNER_WALLET>')) // response_destination
.storeUint(0, 1) // no custom payload
.endCell();

const transaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: '<OWNER_JETTON_WALLET>', // owner's jetton wallet
amount: toNano('0.05').toString(),
payload: body.toBoc().toString('base64'),
},
],
};

export const BurnJetton = () => {
const [tonConnectUI] = useTonConnectUI();

return (
<button onClick={() => tonConnectUI.sendTransaction(transaction)}>
Burn jetton
</button>
);
};
```

```ts title="TypeScript" icon="globe"
import TonConnectUI from '@tonconnect/ui';
import { beginCell, toNano, Address } from '@ton/ton';

const tonConnectUI = new TonConnectUI({ manifestUrl: '<MANIFEST_URL>' });

const body = beginCell()
.storeUint(0x595f07bc, 32)
.storeUint(0, 64)
.storeCoins(toNano('0.001'))
.storeAddress(Address.parse('<OWNER_WALLET>'))
.storeUint(0, 1)
.endCell();

const transaction = {
validUntil: Math.floor(Date.now() / 1000) + 360,
messages: [
{
address: '<OWNER_JETTON_WALLET>',
amount: toNano('0.05').toString(),
payload: body.toBoc().toString('base64'),
},
],
};

const result = await tonConnectUI.sendTransaction(transaction);
```
</CodeGroup>

Where:

- `<OWNER_WALLET>` — the wallet address of the jetton owner (receives excess fees).
- `<OWNER_JETTON_WALLET>` — 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
Expand Down
Loading