USDT transaction on Polygon(MATIC) network

Polygon USDT

Introduction

This article is similar to our previous one, USDT transaction on Solana network, but in this case we are going to transfer USDT on Polygon(previously called MATIC) network.

Requirements

  1. Go installed, given that we are going to keep using Go for this job.

Code with comments

Ok so let’s start, first let’s import the libraries, mostly will be using go-ethereum.


package main

import (
	"context"
	"crypto/ecdsa"
	"errors"
	"fmt"
	"math/big"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/crypto"
	"github.com/ethereum/go-ethereum/ethclient"
	"golang.org/x/crypto/sha3"
)

In this small receipt we’ll use Alchemy RPC provider. Alchemy support several blockchains check it out in case you will need it.

Alchemy Dashboard

When you create a application in Alchemy they will provide you with an API-KEY, which will be needed for connecting to the RPC endpoint. Let’s define some constants that we’ll need.


// After imports....

const (
	// replace <API-KEY> with alchemy api key provided.
	AlchemyPolygonRPCEndpoint = "https://polygon-mainnet.g.alchemy.com/v2/<API-KEY>"

	// USDTTokenAddress is USDT contract address for the USDT token on Polygon
	// network. Can be checked in the following polygonscan link:
	// https://polygonscan.com/token/0xc2132d05d31c914a87c6611c10748aeb04b58e8f
	USDTTokenAddress = "0xc2132D05D31c914a87C6611C10748AEb04B58e8F"

	DefaultGasLimit uint64 = 100000
)

Let’s define our client and its constructor.


// Client for making transaction.
type Client struct {	
	client      *ethclient.Client
	publickKey  common.Address
	privateKey  *ecdsa.PrivateKey
}

// NewWithPrivateKey creates a new Client with the private key
// provided.
func NewWithPrivateKey(pKeyStr string) (*Client, error) {
	client, err := ethclient.Dial(AlchemyPolygonRPCEndpoint)
	if err != nil {
		return nil, err
	}

	privateKey, err := crypto.HexToECDSA(pKeyStr)
	if err != nil {
		return nil, err
	}

	publicKey := privateKey.Public()
	publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey)
	if !ok {
		return nil, errors.New("unable to convert publicKey to *ecdsa.PublicKey type")
	}

	// extracting public address of the wallet with the supplied private key.
	pubAddrs := crypto.PubkeyToAddress(*publicKeyECDSA)

	return &Client{		
		client:      client,
		publickKey:  pubAddrs,
		privateKey:  privateKey,
	}, nil
}

The main method will be called TransferUSDT. One main difference in this method and the one from Solana article, is that we cannot send comment in the transaction.


// TransferUSDT make transaction of usdts to the specified address.
// The amount should be provided in 6 decimals.
// Meaning, 1 USDT should be represented as 1e6.
// ctx: context
// toAddressStrHex: hexadecimal representation of receiver address(Public Address)
// amount: usdt amount to be sent.
func (c *Client) TransferUSDT(
	ctx context.Context,
	toAddressStrHex string,
	amount uint64,
) (string, error) {
	// Retrieving pending nonce. The nonce, according to
	// ethereum glossary is a:
	// "An account nonce is a transaction counter in each account,
	// which is used to prevent replay attacks."
	nonce, err := c.client.PendingNonceAt(ctx, c.publickKey)
	if err != nil {
		return "", err
	}

	// given that we are going to transfer
	// usdts we don't need eths wei (0 eth).
	value := big.NewInt(0)

	// receiver address.
	toAddress := common.HexToAddress(toAddressStrHex)
	// usdt token address.
	tokenAddress := common.HexToAddress(USDTTokenAddress)

	// we will use the transfer method
	// on the smart contract associated with usdt token
	// in order to use this method, we need to provide the method id
	// this is how we get that number.
	// You could also check it here in this link
	// https://polygonscan.com/token/0xc2132d05d31c914a87c6611c10748aeb04b58e8f#writeProxyContract#F11
	transferFnSignature := []byte("transfer(address,uint256)")
	hash := sha3.NewLegacyKeccak256()
	hash.Write(transferFnSignature)
	methodID := hash.Sum(nil)[:4]

	// we need to add 32 bytes of zeros to our address.
	paddedAddress := common.LeftPadBytes(toAddress.Bytes(), 32)

	// we need to add 32 bytes of zeros to our amount of tokens.
	// we are assuming this amount of tokens is expressed in 6 decimals.
	// which are the decimals for usdt.
	amountBigInt := new(big.Int)
	amountBigInt.SetUint64(amount)
	paddedAmount := common.LeftPadBytes(amountBigInt.Bytes(), 32)

	// now let's put this three parts into
	// the data we are going to pass in the transaction
	// part one: methodID
	// part two: receiver address padded 32 bytes
	// part three: padded amount to be sent
	var data []byte
	data = append(data, methodID...)
	data = append(data, paddedAddress...)
	data = append(data, paddedAmount...)

	// retrieving suggested gas fees and gas price.
	tipCap, err := c.client.SuggestGasTipCap(ctx)
	if err != nil {
		return "", err
	}

	feeCap, err := c.client.SuggestGasPrice(ctx)
	if err != nil {
		return "", err
	}

	//  network ID for this client.
	chainID, err := c.client.NetworkID(ctx)
	if err != nil {
		return "", err
	}

	// creating our transaction, in this case we are going to use
	// dynamic fees txns instead of the legacy system.
	tx := types.NewTx(&types.DynamicFeeTx{
		ChainID:   chainID,
		Nonce:     nonce,
		GasTipCap: tipCap,
		GasFeeCap: feeCap,
		Gas:       DefaultGasLimit,
		To:        &tokenAddress,
		Value:     value,
		Data:      data,
	})

	// sign the transaction with our private key.
	signedTx, err := types.SignTx(tx, types.LatestSignerForChainID(chainID), c.privateKey)
	if err != nil {
		return "", err
	}

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

	// return the hexadecimal representation of the txnHash.
	return signedTx.Hash().Hex(), nil
}

Now writing the main function we got:


func main() {
	ctx := context.Background()
	client, err := NewWithPrivateKey("<PRIVATE-KEY>")
	if err != nil {
		panic(err)
	}

	txnHash, err := client.TransferUSDT(ctx, "<RECEIVER ADDRESS>", 1e6) // Sending 1 usdt.
	if err != nil {
		panic(err)
	}

	fmt.Println("TXN HASH: ", txnHash)
}

That’s all, feel free to join the parts :).