mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
182 lines
4.9 KiB
Go
182 lines
4.9 KiB
Go
package client
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"bytes"
|
||
|
|
"context"
|
||
|
|
"encoding/json"
|
||
|
|
"fmt"
|
||
|
|
"net/http"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||
|
|
"github.com/cosmos/cosmos-sdk/types/tx"
|
||
|
|
)
|
||
|
|
|
||
|
|
// TxResponse represents transaction broadcast response
|
||
|
|
type TxResponse struct {
|
||
|
|
TxHash string `json:"txhash"`
|
||
|
|
Code uint32 `json:"code"`
|
||
|
|
RawLog string `json:"raw_log"`
|
||
|
|
GasUsed string `json:"gas_used"`
|
||
|
|
GasWanted string `json:"gas_wanted"`
|
||
|
|
Height string `json:"height"`
|
||
|
|
Events []struct {
|
||
|
|
Type string `json:"type"`
|
||
|
|
Attributes []struct {
|
||
|
|
Key string `json:"key"`
|
||
|
|
Value string `json:"value"`
|
||
|
|
} `json:"attributes"`
|
||
|
|
} `json:"events"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// BroadcastTxRequest represents transaction broadcast request
|
||
|
|
type BroadcastTxRequest struct {
|
||
|
|
TxBytes []byte `json:"tx_bytes"`
|
||
|
|
Mode BroadcastMode `json:"mode"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// BroadcastMode represents different broadcast modes
|
||
|
|
type BroadcastMode string
|
||
|
|
|
||
|
|
const (
|
||
|
|
BroadcastModeSync BroadcastMode = "BROADCAST_MODE_SYNC"
|
||
|
|
BroadcastModeAsync BroadcastMode = "BROADCAST_MODE_ASYNC"
|
||
|
|
BroadcastModeBlock BroadcastMode = "BROADCAST_MODE_BLOCK"
|
||
|
|
)
|
||
|
|
|
||
|
|
// BroadcastTx broadcasts a transaction to the network
|
||
|
|
func (c *StarshipClient) BroadcastTx(ctx context.Context, txBytes []byte, mode BroadcastMode) (*TxResponse, error) {
|
||
|
|
url := fmt.Sprintf("%s/cosmos/tx/v1beta1/txs", c.baseURL)
|
||
|
|
|
||
|
|
reqBody := BroadcastTxRequest{
|
||
|
|
TxBytes: txBytes,
|
||
|
|
Mode: mode,
|
||
|
|
}
|
||
|
|
|
||
|
|
jsonBody, err := json.Marshal(reqBody)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonBody))
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("failed to create request: %w", err)
|
||
|
|
}
|
||
|
|
req.Header.Set("Content-Type", "application/json")
|
||
|
|
|
||
|
|
resp, err := c.httpClient.Do(req)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("failed to broadcast transaction: %w", err)
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
|
||
|
|
if resp.StatusCode != http.StatusOK {
|
||
|
|
return nil, fmt.Errorf("broadcast failed with status %d", resp.StatusCode)
|
||
|
|
}
|
||
|
|
|
||
|
|
var broadcastResp struct {
|
||
|
|
TxResponse TxResponse `json:"tx_response"`
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := json.NewDecoder(resp.Body).Decode(&broadcastResp); err != nil {
|
||
|
|
return nil, fmt.Errorf("failed to decode broadcast response: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
return &broadcastResp.TxResponse, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetTxResponse represents get transaction response
|
||
|
|
type GetTxResponse struct {
|
||
|
|
Tx tx.Tx `json:"tx"`
|
||
|
|
TxResponse TxResponse `json:"tx_response"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetTx queries a transaction by hash
|
||
|
|
func (c *StarshipClient) GetTx(ctx context.Context, txHash string) (*GetTxResponse, error) {
|
||
|
|
url := fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/%s", c.baseURL, txHash)
|
||
|
|
|
||
|
|
var txResp GetTxResponse
|
||
|
|
if err := c.doRequest(ctx, url, &txResp); err != nil {
|
||
|
|
return nil, fmt.Errorf("failed to query transaction: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
return &txResp, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// SimulateRequest represents transaction simulation request
|
||
|
|
type SimulateRequest struct {
|
||
|
|
TxBytes []byte `json:"tx_bytes"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// SimulateResponse represents transaction simulation response
|
||
|
|
type SimulateResponse struct {
|
||
|
|
GasInfo struct {
|
||
|
|
GasWanted string `json:"gas_wanted"`
|
||
|
|
GasUsed string `json:"gas_used"`
|
||
|
|
} `json:"gas_info"`
|
||
|
|
Result struct {
|
||
|
|
Data string `json:"data"`
|
||
|
|
Log string `json:"log"`
|
||
|
|
Events []sdk.StringEvent `json:"events"`
|
||
|
|
} `json:"result"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// SimulateTx simulates a transaction to estimate gas
|
||
|
|
func (c *StarshipClient) SimulateTx(ctx context.Context, txBytes []byte) (*SimulateResponse, error) {
|
||
|
|
url := fmt.Sprintf("%s/cosmos/tx/v1beta1/simulate", c.baseURL)
|
||
|
|
|
||
|
|
reqBody := SimulateRequest{
|
||
|
|
TxBytes: txBytes,
|
||
|
|
}
|
||
|
|
|
||
|
|
jsonBody, err := json.Marshal(reqBody)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("failed to marshal simulate request: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonBody))
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("failed to create simulate request: %w", err)
|
||
|
|
}
|
||
|
|
req.Header.Set("Content-Type", "application/json")
|
||
|
|
|
||
|
|
resp, err := c.httpClient.Do(req)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("failed to simulate transaction: %w", err)
|
||
|
|
}
|
||
|
|
defer resp.Body.Close()
|
||
|
|
|
||
|
|
if resp.StatusCode != http.StatusOK {
|
||
|
|
return nil, fmt.Errorf("simulation failed with status %d", resp.StatusCode)
|
||
|
|
}
|
||
|
|
|
||
|
|
var simResp SimulateResponse
|
||
|
|
if err := json.NewDecoder(resp.Body).Decode(&simResp); err != nil {
|
||
|
|
return nil, fmt.Errorf("failed to decode simulation response: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
return &simResp, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// WaitForTx waits for a transaction to be included in a block
|
||
|
|
func (c *StarshipClient) WaitForTx(ctx context.Context, txHash string, timeout time.Duration) (*GetTxResponse, error) {
|
||
|
|
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||
|
|
defer cancel()
|
||
|
|
|
||
|
|
ticker := time.NewTicker(2 * time.Second)
|
||
|
|
defer ticker.Stop()
|
||
|
|
|
||
|
|
for {
|
||
|
|
select {
|
||
|
|
case <-ctx.Done():
|
||
|
|
return nil, fmt.Errorf("timeout waiting for transaction %s", txHash)
|
||
|
|
case <-ticker.C:
|
||
|
|
tx, err := c.GetTx(ctx, txHash)
|
||
|
|
if err == nil && tx.TxResponse.Code == 0 {
|
||
|
|
return tx, nil
|
||
|
|
}
|
||
|
|
// Continue waiting if transaction not found or failed
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|