DEV Community

Cover image for How to Transfer Solana Tokens with Solidity
Shivam Soni
Shivam Soni

Posted on

How to Transfer Solana Tokens with Solidity

Transfer token via cpi(solang-series part-3)

Introduction

Greetings to the fourth instalment of our series! here, we're continuing our exploration of making solana programs. We're still using the solidity language, which is compiled by the solang compiler.

In the previous articles, we laid the foundation by understanding how solana's high-performance blockchain merges with the familiar solidity syntax. we learned how to build smart contracts and make cross program invocation easily.

In this article, we will explore the intriguing concept of cross program invocation. our focus will be on developing a program responsible for transferring tokens between different accounts through cross program invocation.

By the end of this tutorial, you will gain insights into the concept of composability in solana programming. We will discuss the process of transferring tokens between accounts.

Prerequisite

Prior to proceeding, it would be beneficial if you have reviewed the previous articles in this series. You can find them at this link: solang-series.

The code for this project is located here.
You can clone it(instruction provide in readme file) or use same repo spl-token-minter program.

Setup development environment using this link

Here's what you're gonna learn:

Table Of Content

  1. Recap Of Previous Articles
  2. Transfer spl tokens
  3. Associated token accounts in solana
  4. Upgrading spl-token-minter program by implementing transfer gold token to another account via cpi
    • Solidity code
    • Typescript test
  5. Building, deploying and testing
  6. Conclusion
  7. Further resources

Recap Of Previous Articles

In our earlier articles, we've discussed different aspects of Solana programs. We explained how Solana programs work, talked about using Invoke CPI to make switch power programs, and explored the idea of composability in Solana programs. We believe these articles have helped you understand how to develop Solana programs better.

In this article, we will take a deeper look at transferring our gold token to another associated token account by utilizing the Invoke CPI method.

Transfer spl tokens

First understand the analozy

I am Shivam,

And I want to send some of the gold tokens we minted (in spl-token-minter program) to my friend, Justin.

To get started, we need token accounts linked to the same mint, specifically the gold token mint. The actual token transfer happens from my account to Justin's. To make this transfer smoother, we need to create a brand-new associated token account for Justin. This step ensures that the transfer takes place seamlessly from my account to Justin's.

But here's the twist - this isn't your typical wallet-to-wallet transfer. It stands apart from the usual Sol transfer process. Why? Because in this case, the system program takes charge of the transaction, eliminating the need to create another associated token account to hold the Sol that's being transferred.

You can see this analogy in below image

Image description

We will now proceed to interact with the Associated Token Account.

Associated token accounts in solana

Associated Token Accounts (ATAs) provide a simple and reliable way to find the token account associated with a specific public key and token mint. This makes it easier to manage token transfers and interactions within programs.

Managing multiple token accounts under the same mint can be complex and confusing.

Token program offers a way to generate token account keys based on a user's main System account address and the token mint address. This makes it easier for users to create a primary token account for each token they own, for simplifying management.

These specialized accounts are called Associated Token Accounts (ATAs).

This program establishes a standard and provides a mechanism to map a user's wallet address with their associated token accounts.

See below image

Image description

In this image, we're creating two linked token accounts for the Gold Token. We do this by using The Gold Token Mint and the owners' addresses. This process is essentially about generating a Program Derived Address (PDA) from the user's wallet and the Gold Token Mint.

We will learn about pda in next article

Some key take-away for ata

Each Associated Token Account (ATA) is also a Program Derived Address (PDA).

Every Associated Token Account (ATA) is also a Token Account.

We can create associate token account for shivam in typescript using getOrCreateAssociatedTokenAccount method provide by spl-token module

const tokenAccount = await getOrCreateAssociatedTokenAccount(
    connection,// web3 connection
    wallet.payer, // payer for the account
    mintKeypair.publicKey, // mint for the gold token
    wallet.publicKey // owner(shivam)
  );
Enter fullscreen mode Exit fullscreen mode

getOrCreateAssociatedTokenAccount use to get the Token Account associated with a given address or create it if it doesn't exist and return a public key for gold token account

Same for justin to hold some gold token we need to change owner to justin and create brand new ata for him

like this

const receipient = anchor.web3.Keypair.generate(); // Random key generator

const receipientTokenAccount = await getOrCreateAssociatedTokenAccount(
    connection,   // web3 connection
    wallet.payer, // payer for the account
    mintKeypair.publicKey, // mint for the gold token
    receipient.publicKey // owner(justin)
  );
Enter fullscreen mode Exit fullscreen mode

Upgrading spl-token-minter program by implementing transfer gold token to another account via cpi

In our spl-token-minter program we mint some gold token and transfer to our ata

Now we want to transfer some gold token to another account(justin’s ata).

We're using the same code for our project. It's the same code for minting, and we've added the transfer function in both the Solidity program and the TypeScript test file.

Now open our code directory in vscode and move to solidity folder

In transfergold.sol file we are adding a transfer function under mintTo function

// Transfer tokens from one token account to another via Cross Program Invocation to Token Program
function transferTokens(
address from, // token account to transfer from
address to, // token account to transfer to
uint64 amount // amount of gold to transfer
) public {
SplToken.TokenAccountData from_data = SplToken.get_token_account_data(from);
SplToken.transfer(from, to, from_data.owner, amount);
}
Enter fullscreen mode Exit fullscreen mode

let’s break it down

We've made a transferTokens function that needs three things to work:

  1. The sender's token account with Gold Tokens.
  2. The recipient's account with the Gold Token Mint.
  3. The amount of Gold Tokens to transfer.

Now on first line of function body we retrive the shivam’s token account data by make a call to SplToken.get_token_account_data(from).

On the next line

SplToken.transfer function performs the actual gold token transfer between the specified ATAs.

This function requires the following parameters:

  • from: The account from which we're transferring tokens.
  • to: The account to which we're transferring tokens.
  • owner: The public key of the owner of the from account.
  • amount: The amount of tokens to transfer.

It executes a cross-program invocation to the SPL Token Library using the transfer CPI (Cross-Program Invocation).

Now see in below image

Image description

We transferred the amount of Gold Tokens from Shivam's Associated Token Account (ATA) to Justin's ATA using CPI (Cross-Program Invocation).

Now, we're going to write the test for transferring Gold Tokens.

Open the transfersol.ts test file and add this test right after the mint tokens to our wallet test.

// transfer token to another wallet via cpi

it("Transfer some tokens to another wallet!", async () => {
  // Wallet's associated token account address for mint
  const tokenAccount = await getOrCreateAssociatedTokenAccount(
    connection,
    wallet.payer, // payer
    mintKeypair.publicKey, // mint
    wallet.publicKey // owner(shivam)
  );

  const receipient = anchor.web3.Keypair.generate();
  const receipientTokenAccount = await getOrCreateAssociatedTokenAccount(
    connection,
    wallet.payer, // payer
    mintKeypair.publicKey, // mint account
    receipient.publicKey // owner(justin) account
  );

  console.log("receipientTokenAccount",receipientTokenAccount)

  const tx = await program.methods
    .transferTokens(
      tokenAccount.address,
      receipientTokenAccount.address,
      new anchor.BN(54000000000)
    )
    .accounts({ dataAccount: dataAccount.publicKey })
    .remainingAccounts([
      {
        pubkey: wallet.publicKey,
        isWritable: true,
        isSigner: true,
      },
      {
        pubkey: mintKeypair.publicKey,
        isWritable: false,
        isSigner: false,
      },
      {
        pubkey: tokenAccount.address,
        isWritable: true,
        isSigner: false,
      },
      {
        pubkey: receipientTokenAccount.address,
        isWritable: true,
        isSigner: false,
      },
    ])
    .rpc();
  console.log("Your transaction signature", tx);

 const recepienttokenAmount = (await getAccount(connection, receipientTokenAccount.address)).amount;
  console.log("recipienttokenAmount", recepienttokenAmount);
  let tokens = Number(recepienttokenAmount);

  assert.equal(tokens / LAMPORTS_PER_SOL, 54);
});
Enter fullscreen mode Exit fullscreen mode

let’s break it down

First, we create Shivam's Associated Token Account (ATA) using this.

const tokenAccount = await getOrCreateAssociatedTokenAccount(
    connection,
    wallet.payer, // payer
    mintKeypair.publicKey, // mint
    wallet.publicKey // owner(shivam)
  );
Enter fullscreen mode Exit fullscreen mode

We use the getOrCreateAssociatedTokenAccount method from the SPL Token library to create an associated token account.

We're using the same Gold Token Mint for this, and the owner is our wallet.

Next, we create a new keypair for the recipient, which is Justin in our case.

const receipient = anchor.web3.Keypair.generate();
Enter fullscreen mode Exit fullscreen mode

After we create justin’s ata using this

const receipientTokenAccount = await getOrCreateAssociatedTokenAccount(
    connection,
    wallet.payer, // payer
    mintKeypair.publicKey, // mint account
    receipient.publicKey // owner(justin) account
  );
Enter fullscreen mode Exit fullscreen mode

By using same gold token mint and set owner as receipient

Next, we start a transaction using the transferTokens method provided by the Solidity program. We give it the necessary token accounts and specify the amount of Gold Tokens we want to transfer.

Then, we provide the data account and other required accounts for these cross-program invocation (CPI) calls. Finally, we confirm our test by ensuring that Justin's ATA contains 54 Gold Tokens.

Next we move to building, deploying and testing our program

Building

Open the terminal in the root directory of your project and enter the following command.

anchor build
Enter fullscreen mode Exit fullscreen mode

This command will generate a target folder in the project root and generate idl and types of our solana program to interact with the client side using rpc methods.

Now open a new terminal and check our configuration By writing this command

solana config get
Enter fullscreen mode Exit fullscreen mode

After running this you will see something like this

Config File: ~/.config/solana/cli/config.yml
RPC URL: https://api.devnet.solana.com
WebSocket URL: wss://api.devnet.solana.com/ (computed)
Keypair Path: ~/.config/solana/id.json
Commitment: confirmed
Enter fullscreen mode Exit fullscreen mode

Devnet is the RPC URL, and my wallet's default location is at keypairpath. Make sure you Generate a wallet keypair for deploying the program.

You can set devnet for cluster

By writing this command

solana config set --url https://api.devnet.solana.com
Enter fullscreen mode Exit fullscreen mode

By writing this command you can generate a new keypair

solana-keygen new
Enter fullscreen mode Exit fullscreen mode

Now you have a wallet

You can check the address(pubkey) and balance of this account using these commands

solana address
solana balance
Enter fullscreen mode Exit fullscreen mode

If you have some devnet sol then it is okay for deployment if it is not then Get some devnet airdrop by writing this command

solana airdrop 4
Enter fullscreen mode Exit fullscreen mode

Deploying

Deploying the program by opening terminal in a new tab and starting network cluster by writing this command

solana-test-validator
Enter fullscreen mode Exit fullscreen mode

This will start a devnet cluster in the system next deploy program using

anchor deploy
Enter fullscreen mode Exit fullscreen mode

you will get something like this

Deploying cluster: https://api.devnet.solana.com
Upgrade authority: ~/.config/solana/id.json
Deploying program "spl_token_minter"...
Program path: /Users/shivamsoni/Desktop/composability/transfergold/transfergold/target/deploy/spl_token_minter.so...
Program Id: EhJE9AUd8ybeRoTz79QHrxG6j3Z2hWX22g4SCSCKmGwE

Deploy success
Enter fullscreen mode Exit fullscreen mode

Now our both program is on chain (devnet)

We get program id of our deployed program on the chain

Now grab this program id and change it in our program (solidity file) as well as anchor.toml file After pasting the new program id to both files

Build the program again with the command

anchor build
Enter fullscreen mode Exit fullscreen mode

Testing Program

Before testing check dependencies and run:

yarn install
npm install
Enter fullscreen mode Exit fullscreen mode

Now we run the test using this command

anchor test
Enter fullscreen mode Exit fullscreen mode

After this, you will see something like this

spl-token-minter
Your transaction signature 5eMMtzhkGq8sZFNkLir3i7KFCua6b9itjym7mn1Gm2KBTPDjUL6p27qMba8GynZwVfvEBynF2rc57qBrtR3XZdiJ
    ✔ Is initialized! (2311ms)
Your transaction signature 38opFwF4DgQyXmtUwKgx2NDoTGsCTaWJzLqcRk49BS4x1a3hj8MyHSAn89cRvEaf6p62DVKX1u9tBzXtxSK861u9
    ✔ Create an SPL Token! (1540ms)
tokenAmount 199000000000n
    ✔ Mint some tokens to your wallet! (1794ms)
Your transaction signature 36aRV86YKuSab4iMcrBJbgcF2QskVshnFC5fQGDdoXoJm7qnpwAKJ7coXTM9ia2JNDPnr15FTvALtHT9DzvhCksQ
recipienttokenAmount 54000000000n
    ✔ Transfer some tokens to another wallet! (2814ms)


  4 passing (8s)

✨  Done in 12.26s.
Enter fullscreen mode Exit fullscreen mode

And that's it! We've successfully made a CPI call to transfer gold token from our account to jsutin’s account.

Here is the solana explorer demo

Image description

Conclusion

To wrap up our article on How to Transfer Solana Tokens with Solidity, we've expanded on our previous discussion of cross-program invocation. This time, we've dived into the details of Solana token transfers and explained how Associated Token Accounts play a crucial role in making these transfers happen on the Solana network.

In the last part, we put this knowledge into action by performing a transfer using the CPI method for the Gold Tokens that we minted in our SPL Token program.

In next article we will learn about program derived address

Further resources

helius spl token transfer
associated token account
sol dev
solana cookbook
solana token program

Top comments (0)