Files
kisstron/tron/contract.go
T
2026-09-19 18:26:07 +03:00

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
}