go port
This commit is contained in:
+206
@@ -0,0 +1,206 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user