How to transfer custom token by '@solana/web3.js'
Asked Answered
W

3

20

I want to send my deployed token other than sol using solana web3.js, but I don't know how. I've been looking for the official documentation for a long time, but I can't find it. Could you please let me know if you have any information on this? Thanks

Wylen answered 3/7, 2021 at 12:20 Comment(0)
S
18

You need to make sure to install the npm bindings for the token program as you can see from imports below

import * as web3 from "@solana/web3.js";
import * as splToken from "@solana/spl-token";

// Address: 9vpsmXhZYMpvhCKiVoX5U8b1iKpfwJaFpPEEXF7hRm9N
const DEMO_WALLET_SECRET_KEY = new Uint8Array([
  37, 21, 197, 185, 105, 201, 212, 148, 164, 108, 251, 159, 174, 252, 43, 246,
  225, 156, 38, 203, 99, 42, 244, 73, 252, 143, 34, 239, 15, 222, 217, 91, 132,
  167, 105, 60, 17, 211, 120, 243, 197, 99, 113, 34, 76, 127, 190, 18, 91, 246,
  121, 93, 189, 55, 165, 129, 196, 104, 25, 157, 209, 168, 165, 149,
]);
(async () => {
  // Connect to cluster
  var connection = new web3.Connection(web3.clusterApiUrl("devnet"));
  // Construct wallet keypairs
  var fromWallet = web3.Keypair.fromSecretKey(DEMO_WALLET_SECRET_KEY);
  var toWallet = web3.Keypair.generate();
  // Construct my token class
  var myMint = new web3.PublicKey("My Mint Public Address");
  var myToken = new splToken.Token(
    connection,
    myMint,
    splToken.TOKEN_PROGRAM_ID,
    fromWallet
  );
  // Create associated token accounts for my token if they don't exist yet
  var fromTokenAccount = await myToken.getOrCreateAssociatedAccountInfo(
    fromWallet.publicKey
  )
  var toTokenAccount = await myToken.getOrCreateAssociatedAccountInfo(
    toWallet.publicKey
  )
  // Add token transfer instructions to transaction
  var transaction = new web3.Transaction()
    .add(
      splToken.Token.createTransferInstruction(
        splToken.TOKEN_PROGRAM_ID,
        fromTokenAccount.address,
        toTokenAccount.address,
        fromWallet.publicKey,
        [],
        0
      )
    );
  // Sign transaction, broadcast, and confirm
  var signature = await web3.sendAndConfirmTransaction(
    connection,
    transaction,
    [fromWallet]
  );
  console.log("SIGNATURE", signature);
  console.log("SUCCESS");
})();
String answered 3/7, 2021 at 15:32 Comment(6)
Seems like a bad assumption that we would have access to the secret key in code.Dahlberg
This snippet uses: import * as web3 from "@solana/web3.js"; which is the official Solana JS bindingsThud
Agree with @Ozymandias. How can we transfer spl token by using phantom wallet, rather than using keypair in the code?Garfieldgarfinkel
To everyone concerned about hardcoding sensitive data - you can always save these into .env and access via env variables feature of JS ...or you did plan to copy-paste the snippet and hardcode own secret key?Thud
This solution fails on await myToken.getOrCreateAssociatedAccountInfo(toWallet.publicKey) when the publicKey does not have the token address already in place. (Flag if was fixed by Solana.)Thud
Module '"@solana/spl-token"' has no exported member 'Token'Cypro
D
29

The problem with the existing answers is they only show you how to first create a new custom token then perform a transfer from one wallet to another. Here I will show how to do this with an existing custom token.

import { Token, TOKEN_PROGRAM_ID } from "@solana/spl-token";
import { web3, Wallet } from "@project-serum/anchor";

async function transfer(tokenMintAddress: string, wallet: Wallet, to: string, connection: web3.Connection, amount: number) {
  const mintPublicKey = new web3.PublicKey(tokenMintAddress);    
  const mintToken = new Token(
    connection,
    mintPublicKey,
    TOKEN_PROGRAM_ID,
    wallet.payer // the wallet owner will pay to transfer and to create recipients associated token account if it does not yet exist.
  );
        
  const fromTokenAccount = await mintToken.getOrCreateAssociatedAccountInfo(
    wallet.publicKey
  );

  const destPublicKey = new web3.PublicKey(to);

  // Get the derived address of the destination wallet which will hold the custom token
  const associatedDestinationTokenAddr = await Token.getAssociatedTokenAddress(
    mintToken.associatedProgramId,
    mintToken.programId,
    mintPublicKey,
    destPublicKey
  );

  const receiverAccount = await connection.getAccountInfo(associatedDestinationTokenAddr);
        
  const instructions: web3.TransactionInstruction[] = [];  

  if (receiverAccount === null) {

    instructions.push(
      Token.createAssociatedTokenAccountInstruction(
        mintToken.associatedProgramId,
        mintToken.programId,
        mintPublicKey,
        associatedDestinationTokenAddr,
        destPublicKey,
        wallet.publicKey
      )
    )

  }
  
  instructions.push(
    Token.createTransferInstruction(
      TOKEN_PROGRAM_ID,
      fromTokenAccount.address,
      associatedDestinationTokenAddr,
      wallet.publicKey,
      [],
      amount
    )
  );

  const transaction = new web3.Transaction().add(...instructions);
  transaction.feePayer = wallet.publicKey;
  transaction.recentBlockhash = (await connection.getRecentBlockhash()).blockhash;
  
  const transactionSignature = await connection.sendRawTransaction(
    transaction.serialize(),
    { skipPreflight: true }
  );

  await connection.confirmTransaction(transactionSignature);
}

notice how we add an instruction for creating the recipient's custom token account if they don't have one.

Dahlberg answered 11/10, 2021 at 7:2 Comment(3)
This snippet uses: import { web3, Wallet } from "@project-serum/anchor"; rather than import * as web3 from "@solana/web3.js"; which is the official Solana JS bindingsThud
Token not exists at @solana/spl-tokenVersicular
@JuanEnriqueGarcíaSancho Correct, have made that updation in my answer below.Titian
S
18

You need to make sure to install the npm bindings for the token program as you can see from imports below

import * as web3 from "@solana/web3.js";
import * as splToken from "@solana/spl-token";

// Address: 9vpsmXhZYMpvhCKiVoX5U8b1iKpfwJaFpPEEXF7hRm9N
const DEMO_WALLET_SECRET_KEY = new Uint8Array([
  37, 21, 197, 185, 105, 201, 212, 148, 164, 108, 251, 159, 174, 252, 43, 246,
  225, 156, 38, 203, 99, 42, 244, 73, 252, 143, 34, 239, 15, 222, 217, 91, 132,
  167, 105, 60, 17, 211, 120, 243, 197, 99, 113, 34, 76, 127, 190, 18, 91, 246,
  121, 93, 189, 55, 165, 129, 196, 104, 25, 157, 209, 168, 165, 149,
]);
(async () => {
  // Connect to cluster
  var connection = new web3.Connection(web3.clusterApiUrl("devnet"));
  // Construct wallet keypairs
  var fromWallet = web3.Keypair.fromSecretKey(DEMO_WALLET_SECRET_KEY);
  var toWallet = web3.Keypair.generate();
  // Construct my token class
  var myMint = new web3.PublicKey("My Mint Public Address");
  var myToken = new splToken.Token(
    connection,
    myMint,
    splToken.TOKEN_PROGRAM_ID,
    fromWallet
  );
  // Create associated token accounts for my token if they don't exist yet
  var fromTokenAccount = await myToken.getOrCreateAssociatedAccountInfo(
    fromWallet.publicKey
  )
  var toTokenAccount = await myToken.getOrCreateAssociatedAccountInfo(
    toWallet.publicKey
  )
  // Add token transfer instructions to transaction
  var transaction = new web3.Transaction()
    .add(
      splToken.Token.createTransferInstruction(
        splToken.TOKEN_PROGRAM_ID,
        fromTokenAccount.address,
        toTokenAccount.address,
        fromWallet.publicKey,
        [],
        0
      )
    );
  // Sign transaction, broadcast, and confirm
  var signature = await web3.sendAndConfirmTransaction(
    connection,
    transaction,
    [fromWallet]
  );
  console.log("SIGNATURE", signature);
  console.log("SUCCESS");
})();
String answered 3/7, 2021 at 15:32 Comment(6)
Seems like a bad assumption that we would have access to the secret key in code.Dahlberg
This snippet uses: import * as web3 from "@solana/web3.js"; which is the official Solana JS bindingsThud
Agree with @Ozymandias. How can we transfer spl token by using phantom wallet, rather than using keypair in the code?Garfieldgarfinkel
To everyone concerned about hardcoding sensitive data - you can always save these into .env and access via env variables feature of JS ...or you did plan to copy-paste the snippet and hardcode own secret key?Thud
This solution fails on await myToken.getOrCreateAssociatedAccountInfo(toWallet.publicKey) when the publicKey does not have the token address already in place. (Flag if was fixed by Solana.)Thud
Module '"@solana/spl-token"' has no exported member 'Token'Cypro
T
14

Small update on @Ozymandias answer, there seems to be few changes in the spl-token library. Firstly Token dosen't exist anymore. And few functions such as Token.getAssociatedTokenAddress also seem to be removed. Here is the updated code:



import * as splToken from "@solana/spl-token";
import { web3, Wallet } from "@project-serum/anchor";

async function transfer(tokenMintAddress: string, wallet: Wallet, to: string, connection: web3.Connection, amount: number) {

  const mintPublicKey = new web3.PublicKey(tokenMintAddress);  
  const {TOKEN_PROGRAM_ID} = splToken

  const fromTokenAccount = await splToken.getOrCreateAssociatedTokenAccount(
    connection,
    wallet.payer,
    mintPublicKey,
    wallet.publicKey
  );

  const destPublicKey = new web3.PublicKey(to);

  // Get the derived address of the destination wallet which will hold the custom token
  const associatedDestinationTokenAddr = await splToken.getOrCreateAssociatedTokenAccount(
    connection,
    wallet.payer,
    mintPublicKey,
    destPublicKey
  );
  

  const receiverAccount = await connection.getAccountInfo(associatedDestinationTokenAddr.address);
        
  const instructions: web3.TransactionInstruction[] = [];  

  
  instructions.push(
    splToken.createTransferInstruction(
      fromTokenAccount.address,
      associatedDestinationTokenAddr.address,
      wallet.publicKey,
      amount,
      [],
      TOKEN_PROGRAM_ID
    )
  );

  const transaction = new web3.Transaction().add(...instructions);
  transaction.feePayer = wallet.publicKey;
  transaction.recentBlockhash = (await connection.getRecentBlockhash()).blockhash;
  
  const transactionSignature = await connection.sendRawTransaction(
    transaction.serialize(),
    { skipPreflight: true }
  );

  await connection.confirmTransaction(transactionSignature);
}
Titian answered 16/3, 2022 at 11:56 Comment(2)
See official Solana documentation hereThud
What is the meaning of TOKEN_PROGRAM_ID ? How you are even able to assign it with splToken library? Didn't you need to set it manually ?Usia

© 2022 - 2024 — McMap. All rights reserved.