USDT transaction on Solana network
Tags: solana, web3, blockchain, usdt
Published: 2023-05-12 14:04:59

![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.

```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"
)

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
}

```

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`.

```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
}

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
}
```

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

```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
}
```

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

```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
}
```

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.


```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: <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.

```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"
)

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
}

```

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`.

```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
}

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
}
```

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

```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
}
```

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

```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
}
```

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.


```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: <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.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
}
```

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