1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
|
const (
// replace <API-KEY> with alchemy api key provided.
AlchemyPolygonRPCEndpoint = "https://polygon-mumbai.infura.io/v3/<API-KEY>"
// TokenAddress is DERC20 contract address for the DERC20 token on Polygon mumbai
// network. Can be checked in the following polygonscan link:
// https://mumbai.polygonscan.com/address/0xfe4f5145f6e09952a5ba9e956ed0c25e3fa4c7f1
TokenAddress = "0xfe4F5145f6e09952a5ba9e956ED0C25e3Fa4c7F1"
DefaultGasLimit uint64 = 100000
)
// 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
}
// TransferToken make transaction of tokens to the specified address.
// The amount should be provided in 18 decimals.
// Meaning, 1 DERC20 should be represented as 1e18.
// ctx: context
// toAddressStrHex: hexadecimal representation of receiver address(Public Address)
// amount: usdt amount to be sent.
func (c *Client) TransferToken(
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(TokenAddress)
// 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://mumbai.polygonscan.com/address/0xfe4f5145f6e09952a5ba9e956ed0c25e3fa4c7f1#writeContract#F9
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 18 decimals.
// which are the decimals for DERC20.
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
}
func main() {
ctx := context.Background()
privateKey := `<PRIVATE-KEY>`
client, err := NewWithPrivateKey(privateKey)
if err != nil {
panic(err)
}
receiverAddress := `RECEIVER-ADDRESS`
txnHash, err := client.TransferToken(ctx, receiverAddress, 1e18) // Sending 1 DERC20.
if err != nil {
panic(err)
}
fmt.Println("TXN HASH: ", txnHash)
}
|