Files
zeekayandHanzo Dev 75cf1d0486 Depend on the renamed utility package
github.com/luxfi/utils is now github.com/luxfi/util. Import paths and the
module requirement follow the rename. The json types cross the platformvm and
exchangevm client boundary, so this also takes luxfi/vm v1.3.18, where those
API structs already speak the renamed types.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-06 04:21:55 -07:00

245 lines
7.0 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package exchangevm
import (
"context"
"errors"
"time"
"github.com/luxfi/address"
"github.com/luxfi/consensus/core/choices"
"github.com/luxfi/formatting"
"github.com/luxfi/ids"
"github.com/luxfi/rpc"
"github.com/luxfi/sdk/constants"
"github.com/luxfi/util/json"
"github.com/luxfi/vm/api"
)
var ErrRejected = errors.New("rejected")
// Client for interacting with the Exchange VM (X-Chain)
type Client struct {
Requester rpc.EndpointRequester
}
// NewClient returns an Exchange VM client for interacting with the X-Chain
func NewClient(uri, chain string) *Client {
return &Client{Requester: rpc.NewEndpointRequester(constants.Chain(uri, chain))}
}
// GetBlock returns the block with the given id.
func (c *Client) GetBlock(ctx context.Context, blkID ids.ID, options ...rpc.Option) ([]byte, error) {
res := &api.FormattedBlock{}
err := c.Requester.SendRequest(ctx, "xvm.getBlock", &api.GetBlockArgs{
BlockID: blkID,
Encoding: formatting.HexNC,
}, res, options...)
if err != nil {
return nil, err
}
return formatting.Decode(res.Encoding, res.Block)
}
// GetBlockByHeight returns the block at the given height.
func (c *Client) GetBlockByHeight(ctx context.Context, height uint64, options ...rpc.Option) ([]byte, error) {
res := &api.FormattedBlock{}
err := c.Requester.SendRequest(ctx, "xvm.getBlockByHeight", &api.GetBlockByHeightArgs{
Height: json.Uint64(height),
Encoding: formatting.HexNC,
}, res, options...)
if err != nil {
return nil, err
}
return formatting.Decode(res.Encoding, res.Block)
}
// GetHeight returns the height of the last accepted block.
func (c *Client) GetHeight(ctx context.Context, options ...rpc.Option) (uint64, error) {
res := &api.GetHeightResponse{}
err := c.Requester.SendRequest(ctx, "xvm.getHeight", struct{}{}, res, options...)
return uint64(res.Height), err
}
// IssueTx issues a transaction to a node and returns the TxID
func (c *Client) IssueTx(ctx context.Context, txBytes []byte, options ...rpc.Option) (ids.ID, error) {
txStr, err := formatting.Encode(formatting.Hex, txBytes)
if err != nil {
return ids.Empty, err
}
res := &api.JSONTxID{}
err = c.Requester.SendRequest(ctx, "xvm.issueTx", &api.FormattedTx{
Tx: txStr,
Encoding: formatting.Hex,
}, res, options...)
return res.TxID, err
}
// GetTxStatus returns the status of [txID]
//
// Deprecated: GetTxStatus only returns Accepted or Unknown, GetTx should be
// used instead to determine if the tx was accepted.
func (c *Client) GetTxStatus(ctx context.Context, txID ids.ID, options ...rpc.Option) (choices.Status, error) {
res := &GetTxStatusReply{}
err := c.Requester.SendRequest(ctx, "xvm.getTxStatus", &api.JSONTxID{
TxID: txID,
}, res, options...)
return res.Status, err
}
// GetTx returns the byte representation of txID.
func (c *Client) GetTx(ctx context.Context, txID ids.ID, options ...rpc.Option) ([]byte, error) {
res := &api.FormattedTx{}
err := c.Requester.SendRequest(ctx, "xvm.getTx", &api.GetTxArgs{
TxID: txID,
Encoding: formatting.Hex,
}, res, options...)
if err != nil {
return nil, err
}
return formatting.Decode(res.Encoding, res.Tx)
}
// GetUTXOs returns the byte representation of the UTXOs controlled by addrs.
func (c *Client) GetUTXOs(
ctx context.Context,
addrs []ids.ShortID,
limit uint32,
startAddress ids.ShortID,
startUTXOID ids.ID,
options ...rpc.Option,
) ([][]byte, ids.ShortID, ids.ID, error) {
return c.GetAtomicUTXOs(ctx, addrs, "", limit, startAddress, startUTXOID, options...)
}
// GetAtomicUTXOs returns the byte representation of the atomic UTXOs controlled
// by addrs from sourceChain.
func (c *Client) GetAtomicUTXOs(
ctx context.Context,
addrs []ids.ShortID,
sourceChain string,
limit uint32,
startAddress ids.ShortID,
startUTXOID ids.ID,
options ...rpc.Option,
) ([][]byte, ids.ShortID, ids.ID, error) {
res := &api.GetUTXOsReply{}
err := c.Requester.SendRequest(ctx, "xvm.getUTXOs", &api.GetUTXOsArgs{
Addresses: ids.ShortIDsToStrings(addrs),
SourceChain: sourceChain,
Limit: json.Uint32(limit),
StartIndex: api.Index{
Address: startAddress.String(),
UTXO: startUTXOID.String(),
},
Encoding: formatting.Hex,
}, res, options...)
if err != nil {
return nil, ids.ShortID{}, ids.Empty, err
}
utxos := make([][]byte, len(res.UTXOs))
for i, utxo := range res.UTXOs {
utxoBytes, err := formatting.Decode(res.Encoding, utxo)
if err != nil {
return nil, ids.ShortID{}, ids.Empty, err
}
utxos[i] = utxoBytes
}
endAddr, err := address.ParseToID(res.EndIndex.Address)
if err != nil {
return nil, ids.ShortID{}, ids.Empty, err
}
endUTXOID, err := ids.FromString(res.EndIndex.UTXO)
return utxos, endAddr, endUTXOID, err
}
// GetAssetDescription returns a description of assetID.
func (c *Client) GetAssetDescription(ctx context.Context, assetID string, options ...rpc.Option) (*GetAssetDescriptionReply, error) {
res := &GetAssetDescriptionReply{}
err := c.Requester.SendRequest(ctx, "xvm.getAssetDescription", &GetAssetDescriptionArgs{
AssetID: assetID,
}, res, options...)
return res, err
}
// GetBalance returns the balance of assetID held by addr.
//
// If includePartial is set, balance includes partial owned (i.e. in a multisig)
// funds.
//
// Deprecated: GetUTXOs should be used instead.
func (c *Client) GetBalance(
ctx context.Context,
addr ids.ShortID,
assetID string,
includePartial bool,
options ...rpc.Option,
) (*GetBalanceReply, error) {
res := &GetBalanceReply{}
err := c.Requester.SendRequest(ctx, "xvm.getBalance", &GetBalanceArgs{
Address: addr.String(),
AssetID: assetID,
IncludePartial: includePartial,
}, res, options...)
return res, err
}
// GetAllBalances returns all asset balances for addr.
//
// Deprecated: GetUTXOs should be used instead.
func (c *Client) GetAllBalances(
ctx context.Context,
addr ids.ShortID,
includePartial bool,
options ...rpc.Option,
) ([]Balance, error) {
res := &GetAllBalancesReply{}
err := c.Requester.SendRequest(ctx, "xvm.getAllBalances", &GetAllBalancesArgs{
JSONAddress: api.JSONAddress{Address: addr.String()},
IncludePartial: includePartial,
}, res, options...)
return res.Balances, err
}
// GetTxFee returns the cost to issue certain transactions.
func (c *Client) GetTxFee(ctx context.Context, options ...rpc.Option) (uint64, uint64, error) {
res := &GetTxFeeReply{}
err := c.Requester.SendRequest(ctx, "xvm.getTxFee", struct{}{}, res, options...)
return uint64(res.TxFee), uint64(res.CreateAssetTxFee), err
}
// AwaitTxAccepted waits for a transaction to be accepted
func AwaitTxAccepted(
c *Client,
ctx context.Context,
txID ids.ID,
freq time.Duration,
options ...rpc.Option,
) error {
ticker := time.NewTicker(freq)
defer ticker.Stop()
for {
status, err := c.GetTxStatus(ctx, txID, options...)
if err != nil {
return err
}
switch status {
case choices.Accepted:
return nil
case choices.Rejected:
return ErrRejected
}
select {
case <-ticker.C:
case <-ctx.Done():
return ctx.Err()
}
}
}