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 }