207 lines
5.7 KiB
Go
207 lines
5.7 KiB
Go
package tron
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"errors"
|
|
"time"
|
|
)
|
|
|
|
// Transaction is a signed or unsigned TRON transaction. Its field order matches
|
|
// tronpy's Transaction.to_json() dict order so the marshalled JSON is
|
|
// byte-compatible with the Python offline output.
|
|
type Transaction struct {
|
|
TxID string `json:"txID"`
|
|
RawData *RawData `json:"raw_data"`
|
|
Signature []string `json:"signature"`
|
|
Permission any `json:"permission"`
|
|
client *Client
|
|
}
|
|
|
|
func currentMillis() int64 {
|
|
return time.Now().UnixNano() / int64(time.Millisecond)
|
|
}
|
|
|
|
// TransactionBuilder assembles a transaction before signing.
|
|
type TransactionBuilder struct {
|
|
client *Client
|
|
rawData *RawData
|
|
}
|
|
|
|
// newTransactionBuilder creates a builder for a single contract.
|
|
func (c *Client) newTransactionBuilder(typ string, value any) *TransactionBuilder {
|
|
now := currentMillis()
|
|
rd := &RawData{
|
|
Contract: []ContractJSON{{
|
|
Parameter: ParameterJSON{
|
|
Value: value,
|
|
TypeURL: "type.googleapis.com/protocol." + typ,
|
|
},
|
|
Type: typ,
|
|
}},
|
|
Timestamp: now,
|
|
Expiration: now + 60000,
|
|
}
|
|
if typ == "TriggerSmartContract" || typ == "CreateSmartContract" {
|
|
rd.FeeLimit = c.feeLimit
|
|
}
|
|
return &TransactionBuilder{client: c, rawData: rd}
|
|
}
|
|
|
|
// Transfer builds a TRX transfer builder. Amount is in SUN.
|
|
func (c *Client) Transfer(from, to string, amount int64) (*TransactionBuilder, error) {
|
|
fromHex, err := ToHexAddress(from)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
toHex, err := ToHexAddress(to)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return c.newTransactionBuilder("TransferContract", TransferValue{
|
|
OwnerAddress: fromHex,
|
|
ToAddress: toHex,
|
|
Amount: amount,
|
|
}), nil
|
|
}
|
|
|
|
// FreezeBalance builds a FreezeBalanceV2 builder for the given resource.
|
|
func (c *Client) FreezeBalance(owner string, amount int64, resource string) (*TransactionBuilder, error) {
|
|
ownerHex, err := ToHexAddress(owner)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return c.newTransactionBuilder("FreezeBalanceV2Contract", FreezeValue{
|
|
OwnerAddress: ownerHex,
|
|
FrozenBalance: amount,
|
|
Resource: resource,
|
|
}), nil
|
|
}
|
|
|
|
// UnfreezeBalance builds an UnfreezeBalanceV2 builder for the given resource.
|
|
func (c *Client) UnfreezeBalance(owner string, amount int64, resource string) (*TransactionBuilder, error) {
|
|
ownerHex, err := ToHexAddress(owner)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return c.newTransactionBuilder("UnfreezeBalanceV2Contract", UnfreezeValue{
|
|
OwnerAddress: ownerHex,
|
|
UnfreezeBalance: amount,
|
|
Resource: resource,
|
|
}), nil
|
|
}
|
|
|
|
// Build fills the reference block fields from the chain and computes the txid.
|
|
func (b *TransactionBuilder) Build() (*Transaction, error) {
|
|
refID, err := b.client.getLatestSolidBlockID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(refID) < 32 {
|
|
return nil, ErrBadHash
|
|
}
|
|
b.rawData.RefBlockBytes = refID[12:16]
|
|
b.rawData.RefBlockHash = refID[16:32]
|
|
txid, err := b.rawData.TxID()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Transaction{
|
|
TxID: txid,
|
|
RawData: b.rawData,
|
|
Signature: []string{},
|
|
client: b.client,
|
|
}, nil
|
|
}
|
|
|
|
// Sign signs the transaction with the given private key.
|
|
func (tx *Transaction) Sign(key *PrivateKey) error {
|
|
if tx.TxID == "" {
|
|
return ErrTransaction
|
|
}
|
|
hash, err := hex.DecodeString(tx.TxID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sig, err := key.Sign(hash)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
tx.Signature = append(tx.Signature, hex.EncodeToString(sig))
|
|
return nil
|
|
}
|
|
|
|
// Broadcast sends the signed transaction and returns the raw response.
|
|
func (tx *Transaction) Broadcast() (map[string]any, error) {
|
|
if tx.client == nil {
|
|
return nil, ErrTransaction
|
|
}
|
|
return tx.client.broadcast(tx)
|
|
}
|
|
|
|
// Wait polls for the transaction receipt, matching TransactionRet.wait().
|
|
func (tx *Transaction) Wait(timeout, interval time.Duration) (map[string]any, error) {
|
|
if tx.client == nil {
|
|
return nil, ErrTransaction
|
|
}
|
|
deadline := time.Now().Add(timeout)
|
|
for time.Now().Before(deadline) {
|
|
info, err := tx.client.getTransactionInfo(tx.TxID)
|
|
if err == nil {
|
|
return info, nil
|
|
}
|
|
if !errors.Is(err, ErrTransactionNotFound) {
|
|
return nil, err
|
|
}
|
|
time.Sleep(interval)
|
|
}
|
|
return nil, ErrTransactionNotFound
|
|
}
|
|
|
|
// JSON returns the compact transaction JSON used for node requests and offline
|
|
// mode output, matching json.dumps(txn.to_json(), separators=(",", ":")).
|
|
func (tx *Transaction) JSON() string {
|
|
b, _ := json.Marshal(tx)
|
|
return string(b)
|
|
}
|
|
|
|
// toJSON returns the transaction as a generic map for node requests.
|
|
func (tx *Transaction) toJSON() map[string]any {
|
|
var m map[string]any
|
|
json.Unmarshal([]byte(tx.JSON()), &m)
|
|
return m
|
|
}
|
|
|
|
// TransactionFromJSON parses a transaction previously exported by JSON().
|
|
func TransactionFromJSON(s string) (*Transaction, error) {
|
|
var decoded struct {
|
|
TxID string `json:"txID"`
|
|
RawData *RawData `json:"raw_data"`
|
|
Signature []string `json:"signature"`
|
|
Permission any `json:"permission"`
|
|
}
|
|
if err := json.Unmarshal([]byte(s), &decoded); err != nil {
|
|
return nil, err
|
|
}
|
|
if decoded.RawData == nil {
|
|
return nil, ErrTransaction
|
|
}
|
|
return &Transaction{
|
|
TxID: decoded.TxID,
|
|
RawData: decoded.RawData,
|
|
Signature: decoded.Signature,
|
|
Permission: decoded.Permission,
|
|
}, nil
|
|
}
|
|
|
|
// ParseTransaction parses exported transaction JSON and attaches the client so
|
|
// the transaction can be broadcast (mirrors Transaction.from_json + _client).
|
|
func (c *Client) ParseTransaction(raw string) (*Transaction, error) {
|
|
tx, err := TransactionFromJSON(raw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tx.client = c
|
|
return tx, nil
|
|
}
|