110 lines
3.0 KiB
Go
110 lines
3.0 KiB
Go
package tron
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"math/big"
|
|
|
|
"code.luxferre.top/luxferre/kisstron/keccak"
|
|
)
|
|
|
|
// zeroOwner is the owner address used for read-only TRC20 calls, matching
|
|
// tronpy's Contract default (0x41 followed by 20 zero bytes).
|
|
const zeroOwner = "410000000000000000000000000000000000000000"
|
|
|
|
// selector returns the 4-byte ABI function selector (as hex) for a signature.
|
|
func selector(signature string) string {
|
|
h := keccak.Sum256([]byte(signature))
|
|
return hex.EncodeToString(h[:4])
|
|
}
|
|
|
|
var (
|
|
selectorDecimals = selector("decimals()")
|
|
selectorBalanceOf = selector("balanceOf(address)")
|
|
selectorTransfer = selector("transfer(address,uint256)")
|
|
)
|
|
|
|
// Contract is a minimal handle to a TRC20 contract. Only the three TRC20 calls
|
|
// used by kisstron (decimals, balanceOf, transfer) are supported, so no ABI is
|
|
// fetched or parsed.
|
|
type Contract struct {
|
|
Address string
|
|
client *Client
|
|
}
|
|
|
|
// Decimals returns the token's decimal precision.
|
|
func (c *Contract) Decimals() (int, error) {
|
|
res, err := c.client.triggerConstantContract(zeroOwner, c.Address, "decimals()", "")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
v, err := decodeUint256(res)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return int(v.Int64()), nil
|
|
}
|
|
|
|
// BalanceOf returns the raw token balance of addr.
|
|
func (c *Contract) BalanceOf(addr string) (*big.Int, error) {
|
|
param, err := encodeAddressParam(addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res, err := c.client.triggerConstantContract(zeroOwner, c.Address, "balanceOf(address)", param)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return decodeUint256(res)
|
|
}
|
|
|
|
// Transfer builds a signed-ready TRC20 transfer transaction builder.
|
|
func (c *Contract) Transfer(from, to string, amount *big.Int) (*TransactionBuilder, error) {
|
|
fromAddr, err := encodeAddressParam(to)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
fromHex, err := ToHexAddress(from)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
contractHex, err := ToHexAddress(c.Address)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data := selectorTransfer + fromAddr + encodeUint256(amount)
|
|
return c.client.newTransactionBuilder("TriggerSmartContract", TriggerValue{
|
|
OwnerAddress: fromHex,
|
|
ContractAddress: contractHex,
|
|
Data: data,
|
|
}), nil
|
|
}
|
|
|
|
// encodeAddressParam left-pads a TVM address to a 32-byte ABI word.
|
|
func encodeAddressParam(addr string) (string, error) {
|
|
tvm, err := ToTvmAddress(addr)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
word := make([]byte, 32)
|
|
copy(word[32-len(tvm):], tvm)
|
|
return hex.EncodeToString(word), nil
|
|
}
|
|
|
|
// encodeUint256 encodes a non-negative integer as a 32-byte ABI word.
|
|
func encodeUint256(v *big.Int) string {
|
|
word := make([]byte, 32)
|
|
if v != nil {
|
|
v.FillBytes(word)
|
|
}
|
|
return hex.EncodeToString(word)
|
|
}
|
|
|
|
// decodeUint256 decodes a 32-byte ABI word (hex) into a big integer.
|
|
func decodeUint256(hexStr string) (*big.Int, error) {
|
|
b, err := hex.DecodeString(hexStr)
|
|
if err != nil {
|
|
return nil, ErrTvm
|
|
}
|
|
return new(big.Int).SetBytes(b), nil
|
|
}
|