mirror of
https://github.com/luxfi/node.git
synced 2026-07-27 03:39:39 +00:00
Route registration, client URL builders, and log lines all move to /v1/ (wallet primary api, chains/rpc handler_manager+chain_integration, xvm client+wallet_client, xsvm api client, multi-network example, manager logs). Zero /ext/ literals remain. One endpoint namespace, no transition alias. Co-authored-by: Hanzo Dev <dev@hanzo.ai>
253 lines
7.2 KiB
Go
253 lines
7.2 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package xvm
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/luxfi/address"
|
|
"github.com/luxfi/consensus/core/choices"
|
|
"github.com/luxfi/constants"
|
|
"github.com/luxfi/formatting"
|
|
"github.com/luxfi/ids"
|
|
apitypes "github.com/luxfi/api/types"
|
|
"github.com/luxfi/rpc"
|
|
)
|
|
|
|
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 {
|
|
path := fmt.Sprintf(
|
|
"%s/v1/%s/%s",
|
|
uri,
|
|
constants.ChainAliasPrefix,
|
|
chain,
|
|
)
|
|
return &Client{
|
|
Requester: rpc.NewEndpointRequester(path),
|
|
}
|
|
}
|
|
|
|
// GetBlock returns the block with the given id.
|
|
func (c *Client) GetBlock(ctx context.Context, blkID ids.ID, options ...rpc.Option) ([]byte, error) {
|
|
res := &apitypes.FormattedBlock{}
|
|
err := c.Requester.SendRequest(ctx, "exchangevm.getBlock", &apitypes.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 := &apitypes.FormattedBlock{}
|
|
err := c.Requester.SendRequest(ctx, "exchangevm.getBlockByHeight", &apitypes.GetBlockByHeightArgs{
|
|
Height: apitypes.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 := &apitypes.GetHeightResponse{}
|
|
err := c.Requester.SendRequest(ctx, "exchangevm.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 := &apitypes.JSONTxID{}
|
|
err = c.Requester.SendRequest(ctx, "exchangevm.issueTx", &apitypes.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, "exchangevm.getTxStatus", &apitypes.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 := &apitypes.FormattedTx{}
|
|
err := c.Requester.SendRequest(ctx, "exchangevm.getTx", &apitypes.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 := &apitypes.GetUTXOsReply{}
|
|
err := c.Requester.SendRequest(ctx, "exchangevm.getUTXOs", &apitypes.GetUTXOsArgs{
|
|
Addresses: ids.ShortIDsToStrings(addrs),
|
|
SourceChain: sourceChain,
|
|
Limit: apitypes.Uint32(limit),
|
|
StartIndex: apitypes.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, "exchangevm.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, "exchangevm.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, "exchangevm.getAllBalances", &GetAllBalancesArgs{
|
|
JSONAddress: apitypes.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, "exchangevm.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()
|
|
}
|
|
}
|
|
}
|