Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to create jetton transfer? #44

Open
mahnunchik opened this issue Jun 21, 2024 · 22 comments
Open

How to create jetton transfer? #44

mahnunchik opened this issue Jun 21, 2024 · 22 comments

Comments

@mahnunchik
Copy link

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

@vserpokryl
Copy link

vserpokryl commented Jun 27, 2024

@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
Copy link

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

@Jobians
Copy link

Jobians commented Jul 29, 2024

@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);
})();

Error

'LITE_SERVER_UNKNOWN: cannot apply external message to current state : External message was not accepted

@Aero25x
Copy link

Aero25x commented Aug 26, 2024

If you still cant transfer jettons, here is project which do it.
https://github.com/dry-com/kozel

@LI-YONG-QI
Copy link

@vserpokryl Hello, I have a question about Jetton transfer code, why do I need to use wallet.createTransfer before external ? And where can I find more details about this ?

@vserpokryl
Copy link

@LI-YONG-QI wallet.createTransfer is body for external message in this case. You can just put createTransfer into external message body, like this:

const externalMessage = external({
  to: address,
  init: neededInit,
  body: wallet.createTransfer({
    seqno,
    secretKey,
    messages: [internalMessage],
  }),
});

@LI-YONG-QI
Copy link

LI-YONG-QI commented Aug 28, 2024

@vserpokryl Thanks for your response

Here is the source code of createWalletTransferV4 (from createTransfer)

export function createWalletTransferV4<T extends Wallet4SendArgsSignable | Wallet4SendArgsSigned>(
    args: T & { sendMode: number, walletId: number }
) {

    // Check number of messages
    if (args.messages.length > 4) {
        throw Error("Maximum number of messages in a single transfer is 4");
    }

    let signingMessage = beginCell()
        .storeUint(args.walletId, 32);
    if (args.seqno === 0) {
        for (let i = 0; i < 32; i++) {
            signingMessage.storeBit(1);
        }
    } else {
        signingMessage.storeUint(args.timeout || Math.floor(Date.now() / 1e3) + 60, 32); // Default timeout: 60 seconds
    }
    signingMessage.storeUint(args.seqno, 32);
    signingMessage.storeUint(0, 8); // Simple order
    for (let m of args.messages) {
        signingMessage.storeUint(args.sendMode, 8);
        signingMessage.storeRef(beginCell().store(storeMessageRelaxed(m)));
    }
 
    return signPayload(
        args,
        signingMessage,
        packSignatureToFront,
    ) as T extends Wallet4SendArgsSignable ? Promise<Cell> : Cell;
}

I knew the Cell was serialized by TL-B scheme in TON, but I don't know what is TL-B scheme of this Cell in createWalletTransferV4

@immujahidkhan
Copy link

import "@stdlib/deploy";
import "./google";
message MyTransfer {
amount: Int as coins;
}

contract InitiateJettonTransfer with Deployable {
masterAddress: Address;
recipientAddress: Address;
// Initialize with the master contract address, the recipient, and the amount of Jettons
init(master: Address, recipient: Address){
self.masterAddress = master;
self.recipientAddress = recipient;
}

fun getJettonWalletAddress(user: Address): Address {
    let walletInit: StateInit = initOf JettonDefaultWallet(self.masterAddress, user);
    return contractAddress(walletInit);
}

// Method to initiate the transfer of Jettons

receive(msg: MyTransfer){
    let senderWallet: Address = self.getJettonWalletAddress(sender());
    // Construct the TokenTransfer message using the provided message type
    let transferMessage: TokenTransfer = TokenTransfer{
        queryId: 0, // Set query ID to 0
        amount: msg.amount, // The amount of Jettons to transfer
        destination: myAddress(), // Destination address for Jettons
        response_destination: myAddress(), // Response destination (optional)
        custom_payload: null, // No custom payload
        forward_ton_amount: 0, // Forward TON amount (optional, set to 0)
        forward_payload: emptySlice() // Empty forward payload (optional)
    };
    // Send the transfer message to the sender's wallet
    send(SendParameters{
            to: senderWallet,
            value: ton("0.05"), // Gas fee
            bounce: true,
            body: transferMessage.toCell() // Convert the message to a Cell and send it
        }
    );
}

get fun contractBalance(): Int {
    // This should ideally return the balance of Jettons this contract has received
    return myBalance();
}

}

I tried this but my transaction still failed how to fix?
https://testnet.tonviewer.com/kQDmdvV_XTLbQWFJTJShJE36zPlbJHxlURoPXImn0xyyN6hw

@Omokami
Copy link

Omokami commented Oct 25, 2024

When you run the above code, the following error occurs.

contractDeployed false;
POST https://toncenter.com/api/v2/jsonRPC 500 (Internal Server Error)

Can you help me?
image

@ilaziness
Copy link

@Omokami
Copy link

Omokami commented Nov 6, 2024

@vserpokryl

I solved the problem

Thank you

@Tvenus
Copy link

Tvenus commented Nov 8, 2024

@Omokami

You are great!!!

@Omokami
Copy link

Omokami commented Nov 8, 2024

@vserpokryl

Hello

I need urgent help on how to import my Jetton transaction history

@vserpokryl
Copy link

@Omokami, hello! You can use https://tonapi.io for this. For example getAccountJettonsHistory - https://tonapi.io/api-v2#operations-Accounts-getAccountJettonsHistory

@Omokami
Copy link

Omokami commented Nov 8, 2024

@vserpokryl

Thank you

but, I would appreciate it if you could explain how to get the account ID and jetton ID.

@vserpokryl
Copy link

@Omokami
account_id - your ton address
jetton_id - contract address (jetton master address) of token (Jetton) (for example USDT - EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs)

If you need all history of jetton transfers for all tokens use getAccountJettonsHistory (/v2/accounts/{account_id}/jettons/history)
If you need history for specific Jetton (USDT for example) use getAccountJettonHistoryByID (/v2/accounts/{account_id}/jettons/{jetton_id}/history)

@Omokami
Copy link

Omokami commented Nov 8, 2024

@vserpokryl
Copy link

@Omokami
Check the specifications here https://tonapi.io/api-v2#operations-Accounts-getAccountJettonsHistory (expand the route and check details)
https://tonapi.io/v2/accounts/EQAZ3iBMMvTmQmBSJWtQW6ISeu9Qmiiqs2cq_aionprmhXfw/jettons/history?limit=100

@Omokami
Copy link

Omokami commented Nov 8, 2024

@vserpokryl

Thank you very much

@Omokami
Copy link

Omokami commented Nov 19, 2024

@vserpokryl

Hi
How are you doing?

Is there a fee of 0.12 when transferring Jetton?
To send TON is only 0.01 TON for fees.
I would appreciate it if you could tell me how the fees are determined.

I 'll wait for you reply

@vserpokryl
Copy link

@Omokami
Hi!
I don’t have a simple answer for you. The fee calculation in the TON network is quite complex, and we also face challenges when estimating fees. I recommend checking the documentation on this topic:

@Omokami
Copy link

Omokami commented Nov 19, 2024

@vserpokryl
Hello
What is the standard for distinguishing between what was sent and what was received in transaction data obtained using the API?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

9 participants