Introduction
In this small receipt I'm going to show you how to make a USDT transfer on the Solana network.
Requirements
- 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 }
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 }
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 }
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 }
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  ## 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: , 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 :).
Top comments (0)