DEV Community

Gealber Morales
Gealber Morales

Posted on • Originally published at gealber.com

USDT transaction on Solana network

Solana USDT

Introduction

In this small receipt I'm going to show you how to make a USDT transfer on the Solana network.

Requirements

  1. I'm going to use Golang as the language.

Code with comments

Let's first create a client for the wallet and import the required libraries.

package main


import (
    "context"
    "errors"

    "github.com/portto/solana-go-sdk/client"
    "github.com/portto/solana-go-sdk/common"
    "github.com/portto/solana-go-sdk/program/associated_token_account"
    "github.com/portto/solana-go-sdk/program/memo"
    "github.com/portto/solana-go-sdk/program/token"
    "github.com/portto/solana-go-sdk/rpc"
    "github.com/portto/solana-go-sdk/types"
)

var (
    ErrInsuficientBalance = errors.New("insufficient balance")
)

const (
    USDTTokenPublicAddress               = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
    USDTTokenDecimals              uint8 = 6
)

type Client struct {
    client *client.Client
    wallet types.Account
}

// NewClient ...
func NewClient(ctx context.Context, privateKey string) (*Client, error) {
    c := client.NewClient(rpc.DevnetRPCEndpoint)

    wallet, err := types.AccountFromBase58(privateKey)
    if err != nil {
        return nil, err
    }

    return &Client{
        client: c,
        wallet: wallet,
    }, nil
}

Enter fullscreen mode Exit fullscreen mode

I'll create two auxiliar methods for retrieving the USDT account associated with a public key.
And retrieving the USDT PublicKey for our wallet. Is important to notice that is not the same the PublicKey, than USDT PublicKey.
Let's call them GetUSDTAccount and GetUSDTPublic.

func (c *Client) GetUSDTAccount(ctx context.Context) (token.TokenAccount, error) {
    publicAddress := c.wallet.PublicKey.ToBase58()
    mapAccs, err := c.client.GetTokenAccountsByOwner(ctx, publicAddress)
    if err != nil {
        return token.TokenAccount{}, err
    }

    for _, acc := range mapAccs {
        if acc.Mint.ToBase58() == USDTTokenPublicAddress && acc.Owner.ToBase58() == publicAddress {
            return acc, nil
        }
    }

    return token.TokenAccount{}, nil
}

func (c *Client) GetUSDTPublic(ctx context.Context) (common.PublicKey, error) {
    publicAddress := c.wallet.PublicKey.ToBase58()
    mapAccs, err := c.client.GetTokenAccountsByOwner(ctx, publicAddress)
    if err != nil {
        return common.PublicKey{}, err
    }

    for key, acc := range mapAccs {
        if acc.Mint.ToBase58() == USDTTokenPublicAddress && acc.Owner.ToBase58() == publicAddress {
            return key, nil
        }
    }

    return common.PublicKey{}, nil
}
Enter fullscreen mode Exit fullscreen mode

I'll also will need to check the available balance in USDT, expressed as 1e6 units of Lamports.

func (c *Client) GetUSDTBalanceLamports(ctx context.Context) (uint64, error) {
    usdtPublicAddress, err := c.GetUSDTPublic(ctx)
    if err != nil {
        return 0, err
    }

    lamports, _, err := c.client.GetTokenAccountBalance(ctx, usdtPublicAddress.ToBase58())

    return lamports, err
}
Enter fullscreen mode Exit fullscreen mode

I'll also need to get the associated token address for the receiver wallet. So let's implement a method
for GetAssociatedTokenAddress.

func (c *Client) GetAssociatedTokenAddress(ctx context.Context, address string) (common.PublicKey, error) {
    pubAddress := common.PublicKeyFromString(address)
    mintAddress := common.PublicKeyFromString(USDTTokenPublicAddress)
    ata, _, err := common.FindAssociatedTokenAddress(pubAddress, mintAddress)

    return ata, err
}
Enter fullscreen mode Exit fullscreen mode

Now let's implement a method for making the transaction of USDTs. It's important to keep in mind that USDT on Solana, is just a SPL token.

// TransferUSDT make transaction of usdt to solana wallet specified.
// walletAddress: the public address where you want make the usdt transaction.
// amount: amount of USDT to be transfered. The ammount are expressed in 1e6, meaning that 1 USDT is expressed as 1e6.
// memoStr: in case we want to send a message on the transaction, Solana network allow you to do that. This argument
// is for that case.
// return: <string>, <error> we will return the transaction ID to later check it on Solana scanner.
func (c *Client) TransferUSDT(
    ctx context.Context,
    walletAddress string,
    amount uint64,
    memoStr string,
) (string, error) {
    // we need to get the latest blockhash.
    res, err := c.client.GetLatestBlockhash(ctx)
    if err != nil {
        return "", err
    }

    usdtTokenAccount, err := c.GetUSDTAccount(ctx)
    if err != nil {
        return "", err
    }

    usdtBalance, err := c.GetUSDTBalanceLamports(ctx)
    if err != nil {
        return "", err
    }

    // check if our available balance in USDT is enough to make the transaction.
    if usdtBalance <= amount {
        return "", ErrInsuficientBalance
    }

    // our usdt public address.
    usdtPubAddress, err := c.GetUSDTPublic(ctx)
    if err != nil {
        return "", err
    }

    // the token address of the receiver.
    receiverAddress, err := c.GetAssociatedTokenAddress(ctx, walletAddress)
    if err != nil {
        return "", err
    }

    // let's create the intructions to be executed.
    // for a more detailed explanation feel free to check the official doc in this link
    // https://docs.solana.com/es/developing/programming-model/transactions#overview-of-a-transaction
    instructions := make([]types.Instruction, 0)

    // could be the case that the account we are trying to send the usdt
    // doesn't have a token account. In this intruction I specified, that if that's the case
    // I'll pay for the creation of this account, which is cheap, but the owner will be the other
    // part. In our case the receiver.
    _, err = c.client.GetTokenAccount(ctx, receiverAddress.ToBase58())
    if err != nil {
        // add intruction for creating token account.
        instructions = append(instructions, associated_token_account.CreateAssociatedTokenAccount(associated_token_account.CreateAssociatedTokenAccountParam{
            Funder:                 c.wallet.PublicKey,
            Owner:                  common.PublicKeyFromString(walletAddress),
            Mint:                   usdtTokenAccount.Mint,
            AssociatedTokenAccount: receiverAddress,
        }))
    }

    //  intruction associated with the transaction, where we specify everything needed.
    instructions = append(instructions, token.TransferChecked(token.TransferCheckedParam{
        From:     usdtPubAddress,         // from (should be a token account)
        To:       receiverAddress,        // from (should be a token account)
        Mint:     usdtTokenAccount.Mint,  // mint
        Auth:     usdtTokenAccount.Owner, // from's owner
        Signers:  []common.PublicKey{},
        Amount:   amount,
        Decimals: USDTTokenDecimals, // in our case usdt decimals is 6.
    }))

    // if you pass an empty string we won't include
    // the intruction associated with the comment.
    if memoStr != "" {
        instructions = append(instructions, memo.BuildMemo(memo.BuildMemoParam{
            SignerPubkeys: []common.PublicKey{c.wallet.PublicKey},
            Memo:          []byte(memoStr),
        }))
    }

    tx, err := types.NewTransaction(types.NewTransactionParam{
        Message: types.NewMessage(types.NewMessageParam{
            FeePayer:        c.wallet.PublicKey,
            RecentBlockhash: res.Blockhash, // here we use the recent blockhash.
            Instructions:    instructions,  // including our previously constructed intructions.
        }),
        Signers: []types.Account{
            c.wallet,
            c.https://solana.com/# USDT transaction on Solana network

![Solana USDT](https://res.cloudinary.com/djjd0wi5i/image/upload/v1682960840/solana-ether_vxq6o6.png)

## Introduction

In this small receipt I'm going to show you how to make a USDT transfer on the [Solana](https://solana.com/) network.


### Requirements

1. I'm going to use Golang as the language.


## Code with comments

Let's first create a client for the wallet and import the required libraries.

Enter fullscreen mode Exit fullscreen mode


go
package main

import (
"context"
"errors"

"github.com/portto/solana-go-sdk/client"
"github.com/portto/solana-go-sdk/common"
"github.com/portto/solana-go-sdk/program/associated_token_account"
"github.com/portto/solana-go-sdk/program/memo"
"github.com/portto/solana-go-sdk/program/token"
"github.com/portto/solana-go-sdk/rpc"
"github.com/portto/solana-go-sdk/types"
Enter fullscreen mode Exit fullscreen mode

)

var (
ErrInsuficientBalance = errors.New("insufficient balance")
)

const (
USDTTokenPublicAddress = "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB"
USDTTokenDecimals uint8 = 6
)

type Client struct {
client *client.Client
wallet types.Account
}

// NewClient ...
func NewClient(ctx context.Context, privateKey string) (*Client, error) {
c := client.NewClient(rpc.DevnetRPCEndpoint)

wallet, err := types.AccountFromBase58(privateKey)
if err != nil {
    return nil, err
}

return &Client{
    client: c,
    wallet: wallet,
}, nil
Enter fullscreen mode Exit fullscreen mode

}


I'll create two auxiliar methods for retrieving the USDT account associated with a public key. 
And retrieving the USDT PublicKey for our wallet. Is important to notice that is not the same the PublicKey, than USDT PublicKey.
Let's call them `GetUSDTAccount` and `GetUSDTPublic`.

Enter fullscreen mode Exit fullscreen mode


go
func (c *Client) GetUSDTAccount(ctx context.Context) (token.TokenAccount, error) {
publicAddress := c.wallet.PublicKey.ToBase58()
mapAccs, err := c.client.GetTokenAccountsByOwner(ctx, publicAddress)
if err != nil {
return token.TokenAccount{}, err
}

for _, acc := range mapAccs {
    if acc.Mint.ToBase58() == USDTTokenPublicAddress && acc.Owner.ToBase58() == publicAddress {
        return acc, nil
    }
}

return token.TokenAccount{}, nil
Enter fullscreen mode Exit fullscreen mode

}

func (c *Client) GetUSDTPublic(ctx context.Context) (common.PublicKey, error) {
publicAddress := c.wallet.PublicKey.ToBase58()
mapAccs, err := c.client.GetTokenAccountsByOwner(ctx, publicAddress)
if err != nil {
return common.PublicKey{}, err
}

for key, acc := range mapAccs {
    if acc.Mint.ToBase58() == USDTTokenPublicAddress && acc.Owner.ToBase58() == publicAddress {
        return key, nil
    }
}

return common.PublicKey{}, nil
Enter fullscreen mode Exit fullscreen mode

}


I'll also will need to check the available balance in USDT, expressed as 1e6 units of Lamports.

Enter fullscreen mode Exit fullscreen mode


go
func (c *Client) GetUSDTBalanceLamports(ctx context.Context) (uint64, error) {
usdtPublicAddress, err := c.GetUSDTPublic(ctx)
if err != nil {
return 0, err
}

lamports, _, err := c.client.GetTokenAccountBalance(ctx, usdtPublicAddress.ToBase58())

return lamports, err
Enter fullscreen mode Exit fullscreen mode

}


I'll also need to get the associated token address for the receiver wallet. So let's implement a method
for `GetAssociatedTokenAddress`.

Enter fullscreen mode Exit fullscreen mode


go
func (c *Client) GetAssociatedTokenAddress(ctx context.Context, address string) (common.PublicKey, error) {
pubAddress := common.PublicKeyFromString(address)
mintAddress := common.PublicKeyFromString(USDTTokenPublicAddress)
ata, _, err := common.FindAssociatedTokenAddress(pubAddress, mintAddress)

return ata, err
Enter fullscreen mode Exit fullscreen mode

}


Now let's implement a method for making the transaction of USDTs. It's important to keep in mind that USDT on Solana, is just a SPL token.


Enter fullscreen mode Exit fullscreen mode


go
// TransferUSDT make transaction of usdt to solana wallet specified.
// walletAddress: the public address where you want make the usdt transaction.
// amount: amount of USDT to be transfered. The ammount are expressed in 1e6, meaning that 1 USDT is expressed as 1e6.
// memoStr: in case we want to send a message on the transaction, Solana network allow you to do that. This argument
// is for that case.
// return: , we will return the transaction ID to later check it on Solana scanner.
func (c *Client) TransferUSDT(
ctx context.Context,
walletAddress string,
amount uint64,
memoStr string,
) (string, error) {
// we need to get the latest blockhash.
res, err := c.client.GetLatestBlockhash(ctx)
if err != nil {
return "", err
}

usdtTokenAccount, err := c.GetUSDTAccount(ctx)
if err != nil {
    return "", err
}

usdtBalance, err := c.GetUSDTBalanceLamports(ctx)
if err != nil {
    return "", err
}

// check if our available balance in USDT is enough to make the transaction.
if usdtBalance <= amount {
    return "", ErrInsuficientBalance
}

// our usdt public address.
usdtPubAddress, err := c.GetUSDTPublic(ctx)
if err != nil {
    return "", err
}

// the token address of the receiver.
receiverAddress, err := c.GetAssociatedTokenAddress(ctx, walletAddress)
if err != nil {
    return "", err
}

// let's create the intructions to be executed.
// for a more detailed explanation feel free to check the official doc in this link
// https://docs.solana.com/es/developing/programming-model/transactions#overview-of-a-transaction
instructions := make([]types.Instruction, 0)

// could be the case that the account we are trying to send the usdt
// doesn't have a token account. In this intruction I specified, that if that's the case
// I'll pay for the creation of this account, which is cheap, but the owner will be the other
// part. In our case the receiver.
_, err = c.client.GetTokenAccount(ctx, receiverAddress.ToBase58())
if err != nil {
    // add intruction for creating token account.
    instructions = append(instructions, associated_token_account.CreateAssociatedTokenAccount(associated_token_account.CreateAssociatedTokenAccountParam{
        Funder:                 c.wallet.PublicKey,
        Owner:                  common.PublicKeyFromString(walletAddress),
        Mint:                   usdtTokenAccount.Mint,
        AssociatedTokenAccount: receiverAddress,
    }))
}

//  intruction associated with the transaction, where we specify everything needed.
instructions = append(instructions, token.TransferChecked(token.TransferCheckedParam{
    From:     usdtPubAddress,         // from (should be a token account)
    To:       receiverAddress,        // from (should be a token account)
    Mint:     usdtTokenAccount.Mint,  // mint
    Auth:     usdtTokenAccount.Owner, // from's owner
    Signers:  []common.PublicKey{},
    Amount:   amount,
    Decimals: USDTTokenDecimals, // in our case usdt decimals is 6.
}))

// if you pass an empty string we won't include
// the intruction associated with the comment.
if memoStr != "" {
    instructions = append(instructions, memo.BuildMemo(memo.BuildMemoParam{
        SignerPubkeys: []common.PublicKey{c.wallet.PublicKey},
        Memo:          []byte(memoStr),
    }))
}

tx, err := types.NewTransaction(types.NewTransactionParam{
    Message: types.NewMessage(types.NewMessageParam{
        FeePayer:        c.wallet.PublicKey,
        RecentBlockhash: res.Blockhash, // here we use the recent blockhash.
        Instructions:    instructions,  // including our previously constructed intructions.
    }),
    Signers: []types.Account{
        c.wallet,
        c.wallet,
    },
})
if err != nil {
    return "", err
}

// send the transaction.
txnHash, err := c.client.SendTransaction(ctx, tx)
if err != nil {
    return "", err
}

// transaction hash to check it on https://explorer.solana.com/
return txnHash, nil
Enter fullscreen mode Exit fullscreen mode

}




That's all, feel free to join all the pieces yourself :).

Enter fullscreen mode Exit fullscreen mode

Top comments (0)