ton-org / ton

Most popular TON Typescript Library
MIT License
88 stars 24 forks source link

How to create jetton transfer? #44

Open mahnunchik opened 1 week ago

mahnunchik commented 1 week ago

Could you please provide sample code (in the Readme?) how to create jetton/token transfer transactio?

vserpokryl commented 1 week ago

@mahnunchik

I think this is useful for you. Links:

import { beginCell, Address, TonClient, WalletContractV4, internal, external, storeMessage, toNano } from '@ton/ton';
import nacl from 'tweetnacl';

const apiKey = '...';
const client = new TonClient({ endpoint: 'https://toncenter.com/api/v2/jsonRPC', apiKey });

const usdtTokenContractAddress = 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs';
const toAddress = 'UQBcGtGIHLIQJuUHRRfWLhQxUJF4p49ywJyBdDr7UKTK60p9';

async function getUserJettonWalletAddress(userAddress: string, jettonMasterAddress: string) {
  const userAddressCell = beginCell().storeAddress(Address.parse(userAddress)).endCell();

  const response = await client.runMethod(Address.parse(jettonMasterAddress), 'get_wallet_address', [
    { type: 'slice', cell: userAddressCell },
  ]);

  return response.stack.readAddress();
}

(async () => {
  // Generate keyPair from mnemonic/secret key
  const keyPair = nacl.sign.keyPair.fromSecretKey(Buffer.from('SecretKey', 'hex'));
  const secretKey = Buffer.from(keyPair.secretKey);
  const publicKey = Buffer.from(keyPair.publicKey);

  const workchain = 0; // Usually you need a workchain 0
  const wallet = WalletContractV4.create({ workchain, publicKey });
  const address = wallet.address.toString({ urlSafe: true, bounceable: false, testOnly: false });
  const contract = client.open(wallet);

  const balance = await contract.getBalance();
  console.log({ address, balance });

  const seqno = await contract.getSeqno();
  console.log({ address, seqno });

  const { init } = contract;
  const contractDeployed = await client.isContractDeployed(Address.parse(address));
  let neededInit: null | typeof init = null;

  if (init && !contractDeployed) {
    neededInit = init;
  }

  const jettonWalletAddress = await getUserJettonWalletAddress(address, usdtTokenContractAddress);

  // Comment payload
  // const forwardPayload = beginCell()
  //   .storeUint(0, 32) // 0 opcode means we have a comment
  //   .storeStringTail('Hello, TON!')
  //   .endCell();

  const messageBody = beginCell()
    .storeUint(0x0f8a7ea5, 32) // opcode for jetton transfer
    .storeUint(0, 64) // query id
    .storeCoins(5001) // jetton amount, amount * 10^9
    .storeAddress(toAddress)
    .storeAddress(toAddress) // response destination
    .storeBit(0) // no custom payload
    .storeCoins(0) // forward amount - if > 0, will send notification message
    .storeBit(0) // we store forwardPayload as a reference, set 1 and uncomment next line for have a comment
    // .storeRef(forwardPayload)
    .endCell();

  const internalMessage = internal({
    to: jettonWalletAddress,
    value: toNano('0.1'),
    bounce: true,
    body: messageBody,
  });

  const body = wallet.createTransfer({
    seqno,
    secretKey,
    messages: [internalMessage],
  });

  const externalMessage = external({
    to: address,
    init: neededInit,
    body,
  });

  const externalMessageCell = beginCell().store(storeMessage(externalMessage)).endCell();

  const signedTransaction = externalMessageCell.toBoc();
  const hash = externalMessageCell.hash().toString('hex');

  console.log('hash:', hash);

  await client.sendFile(signedTransaction);
})();
vserpokryl commented 1 week ago

If you need sync method of getUserJettonWalletAddress look: https://docs.ton.org/develop/dapps/cookbook#how-to-calculate-users-jetton-wallet-address-offline