Files
zeekayandHanzo Dev 27979a225c The wallet has no C-Chain, so stop asking it for one
The SDK's primary wallet no longer builds a C-Chain wallet, and every
caller here was carrying scaffolding for the one it used to build: an
emptyEVMKeychain that answers nothing, passed as the EVMKeychain of a
wallet that never consults it, and in one case a nil.

rpc transfer went further and reached around the wallet, building
C-Chain atomic import and export transactions by hand against
lux.issueTx and polling lux.getAtomicTxStatus. Value on the C-Chain
moves by an ordinary EVM transaction against /v1/bc/C/rpc; an atomic
export from an EVM chain is a different act with a different settlement
path, and this command was not the place for it. transfer now moves
value between P and X, names an unsupported chain in its error, and
reports both the export and the import transaction rather than one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-06 12:19:00 -07:00

797 lines
24 KiB
Go

// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package chain provides chain deployment and management utilities.
package chain
import (
"context"
"crypto/tls"
"errors"
"fmt"
"net"
"net/http"
"os"
"time"
"github.com/luxfi/proto/p/signer"
"github.com/luxfi/proto/p/txs"
"github.com/luxfi/sdk/platformvm"
lux "github.com/luxfi/utxo"
"github.com/luxfi/vm/components/verify"
"github.com/luxfi/address"
"github.com/luxfi/cli/pkg/application"
keychainwrapper "github.com/luxfi/cli/pkg/keychain"
climodels "github.com/luxfi/cli/pkg/models"
"github.com/luxfi/cli/pkg/txutils"
"github.com/luxfi/cli/pkg/ux"
"github.com/luxfi/constants"
ethcommon "github.com/luxfi/geth/common"
"github.com/luxfi/ids"
"github.com/luxfi/keychain"
"github.com/luxfi/math/set"
"github.com/luxfi/netrunner/utils"
"github.com/luxfi/sdk/models"
"github.com/luxfi/sdk/wallet/primary"
"github.com/luxfi/sdk/wallet/primary/common"
"github.com/luxfi/utxo/secp256k1fx"
)
// ErrNoChainAuthKeysInWallet indicates the wallet doesn't contain required chain auth keys.
var ErrNoChainAuthKeysInWallet = errors.New("auth wallet does not contain chain auth keys")
// PublicDeployer handles chain deployment to public networks.
type PublicDeployer struct {
LocalDeployer
usingLedger bool
kc keychain.Keychain
network models.Network
app *application.Lux
}
// NewPublicDeployer creates a new PublicDeployer instance.
func NewPublicDeployer(app *application.Lux, usingLedger bool, kc keychain.Keychain, network models.Network) *PublicDeployer {
return &PublicDeployer{
LocalDeployer: *NewLocalDeployer(app, "", ""),
app: app,
usingLedger: usingLedger,
kc: kc,
network: network,
}
}
// AddValidator registers a validator on the primary network by
// AddPermissionlessValidatorTx. Under the "validators validate networks,
// chains live on networks" model a sovereign L1 IS a primary network at its
// own networkID, so this one path serves mainnet, testnet, devnet and any L1.
//
// pop is the node's BLS proof of possession; the primary network refuses a
// registration without one. end is when the validator leaves the set — the
// chain reads the start from its own timestamp, so there is nothing to
// schedule. rewardsOwner names where stake and delegation rewards land; nil
// falls back to the first wallet address. delegationShares is the fraction
// out of 1,000,000 the validator keeps from delegation rewards.
func (d *PublicDeployer) AddValidator(
nodeID ids.NodeID,
pop *signer.ProofOfPossession,
stake uint64,
end time.Time,
rewardsOwner *secp256k1fx.OutputOwners,
delegationShares uint32,
) (ids.ID, error) {
wallet, err := d.loadWallet()
if err != nil {
return ids.Empty, err
}
if rewardsOwner == nil {
walletAddrs := d.kc.Addresses().List()
if len(walletAddrs) == 0 {
return ids.Empty, errors.New("wallet has no address to own the rewards")
}
rewardsOwner = &secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{walletAddrs[0]},
}
}
if d.usingLedger {
ux.Logger.PrintToUser("*** Please sign the AddValidator transaction on the ledger device *** ")
}
tx, err := wallet.P().IssueAddPermissionlessValidatorTx(
&txs.ChainValidator{
Validator: txs.Validator{
NodeID: nodeID,
End: uint64(end.Unix()), //nolint:gosec // G115: Unix time is positive
Wght: stake,
},
Chain: constants.PrimaryNetworkID,
},
pop,
wallet.P().Builder().Context().UTXOAssetID,
rewardsOwner,
rewardsOwner,
delegationShares,
)
if err != nil {
return ids.Empty, err
}
return tx.ID(), nil
}
// CreateAssetTx creates a new asset on the X-Chain.
func (d *PublicDeployer) CreateAssetTx(
chainID ids.ID,
tokenName string,
tokenSymbol string,
denomination byte,
initialState map[uint32][]verify.State,
) (ids.ID, error) {
wallet, err := d.loadWallet(chainID)
if err != nil {
return ids.Empty, err
}
if d.usingLedger {
ux.Logger.PrintToUser("*** Please sign Create Asset Transaction hash on the ledger device *** ")
}
tx, err := wallet.X().IssueCreateAssetTx(tokenName, tokenSymbol, denomination, initialState)
if err != nil {
return ids.Empty, err
}
ux.Logger.PrintToUser("Create Asset Transaction successful, transaction ID: %s", tx.ID())
ux.Logger.PrintToUser("Now exporting asset to P-Chain ...")
return tx.ID(), err
}
// ExportToPChainTx exports assets from X-Chain to P-Chain.
func (d *PublicDeployer) ExportToPChainTx(
chainID ids.ID,
chainAssetID ids.ID,
owner *secp256k1fx.OutputOwners,
assetAmount uint64,
) (ids.ID, error) {
wallet, err := d.loadWallet(chainID)
if err != nil {
return ids.Empty, err
}
if d.usingLedger {
ux.Logger.PrintToUser("*** Please sign X -> P Chain Export Transaction hash on the ledger device *** ")
}
tx, err := wallet.X().IssueExportTx(ids.Empty,
[]*lux.TransferableOutput{
{
Asset: lux.Asset{
ID: chainAssetID,
},
Out: &secp256k1fx.TransferOutput{
Amt: assetAmount,
OutputOwners: *owner,
},
},
})
if err == nil {
ux.Logger.PrintToUser("Export to P-Chain Transaction successful, transaction ID: %s", tx.ID())
ux.Logger.PrintToUser("Now importing asset from X-Chain ...")
}
return tx.ID(), err
}
// ImportFromXChain imports assets from X-Chain to P-Chain.
func (d *PublicDeployer) ImportFromXChain(
chainID ids.ID,
owner *secp256k1fx.OutputOwners,
) (ids.ID, error) {
wallet, err := d.loadWallet(chainID)
if err != nil {
return ids.Empty, err
}
if d.usingLedger {
ux.Logger.PrintToUser("*** Please sign X -> P Chain Import Transaction hash on the ledger device *** ")
}
xChainID := ids.FromStringOrPanic("2oYMBNV4eNHyqk2fjjV5nVQLDbtmNJzq5s3qs3Lo6ftnC6FByM") // X-Chain ID
tx, err := wallet.P().IssueImportTx(xChainID, owner)
if err == nil {
ux.Logger.PrintToUser("Import from X Chain Transaction successful, transaction ID: %s", tx.ID())
ux.Logger.PrintToUser("Now transforming net into elastic net ...")
}
if err != nil {
return ids.Empty, err
}
return tx.ID(), err
}
// TransformChainTx transforms a chain to a permissionless elastic chain.
func (d *PublicDeployer) TransformChainTx(
controlKeys []string,
chainAuthKeysStrs []string,
elasticChainConfig climodels.ElasticChainConfig,
chainID ids.ID,
chainAssetID ids.ID,
) (bool, ids.ID, *txs.Tx, []string, error) {
wallet, err := d.loadWallet(chainID)
if err != nil {
return false, ids.Empty, nil, nil, err
}
chainAuthKeys, err := address.ParseToIDs(chainAuthKeysStrs)
if err != nil {
return false, ids.Empty, nil, nil, fmt.Errorf("failure parsing chain auth keys: %w", err)
}
if d.usingLedger {
ux.Logger.PrintToUser("*** Please sign Transform Net hash on the ledger device *** ")
}
tx, err := d.createTransformChainTX(chainAuthKeys, elasticChainConfig, wallet, chainAssetID)
if err != nil {
return false, ids.Empty, nil, nil, err
}
_, remainingChainAuthKeys, err := txutils.GetRemainingSigners(tx, controlKeys)
if err != nil {
return false, ids.Empty, nil, nil, err
}
isFullySigned := len(remainingChainAuthKeys) == 0
if isFullySigned {
txID, err := d.Commit(tx)
if err != nil {
return false, ids.Empty, nil, nil, err
}
ux.Logger.PrintToUser("Transaction successful, transaction ID: %s", txID)
return true, txID, nil, nil, nil
}
ux.Logger.PrintToUser("Partial tx created")
return false, ids.Empty, tx, remainingChainAuthKeys, nil
}
// RemoveValidator removes a validator from the network identified by chainID
// (still expressed as a chainID here because RemoveChainValidatorTx is kept
// for one release cycle while the sovereign-L1 model phases in).
// It verifies that the wallet is one of the chain auth keys (so as to sign the tx).
// If operation is multisig (len(chainAuthKeysStrs) > 1), it creates a remove-
// validator tx and sets the change output owner to be a wallet address.
func (d *PublicDeployer) RemoveValidator(
controlKeys []string,
chainAuthKeysStrs []string,
chainID ids.ID,
nodeID ids.NodeID,
) (bool, *txs.Tx, []string, error) {
wallet, err := d.loadWallet(chainID)
if err != nil {
return false, nil, nil, err
}
chainAuthKeys, err := address.ParseToIDs(chainAuthKeysStrs)
if err != nil {
return false, nil, nil, fmt.Errorf("failure parsing chain auth keys: %w", err)
}
if d.usingLedger {
ux.Logger.PrintToUser("*** Please sign tx hash on the ledger device *** ")
}
tx, err := d.createRemoveValidatorTX(chainAuthKeys, nodeID, chainID, wallet)
if err != nil {
return false, nil, nil, err
}
_, remainingChainAuthKeys, err := txutils.GetRemainingSigners(tx, controlKeys)
if err != nil {
return false, nil, nil, err
}
isFullySigned := len(remainingChainAuthKeys) == 0
if isFullySigned {
id, err := d.Commit(tx)
if err != nil {
return false, nil, nil, err
}
ux.Logger.PrintToUser("Transaction successful, transaction ID: %s", id)
return true, nil, nil, nil
}
ux.Logger.PrintToUser("Partial tx created")
return false, tx, remainingChainAuthKeys, nil
}
// DeployChain creates a chain using the given control keys and threshold.
func (d *PublicDeployer) DeployChain(
controlKeys []string,
threshold uint32,
) (ids.ID, error) {
ux.Logger.PrintToUser("DeployNet: starting...")
wallet, err := d.loadWallet()
if err != nil {
return ids.Empty, err
}
ux.Logger.PrintToUser("DeployNet: calling createNetTx...")
chainID, err := d.createNetworkTx(controlKeys, threshold, wallet)
if err != nil {
ux.Logger.PrintToUser("DeployNet: createNetTx error: %v", err)
return ids.Empty, err
}
ux.Logger.PrintToUser("Net has been created with ID: %s", chainID.String())
time.Sleep(2 * time.Second)
return chainID, nil
}
// DeployBlockchain creates a blockchain for the given chain.
// It creates a create blockchain tx and sets the change output owner
// to be a wallet address (if not, it may go to any other chain auth address).
// chain is the display name stored in the CreateChainTx.
// vmName, if non-empty, overrides the string used for VMID derivation; when
// empty it falls back to chain (preserving legacy behaviour for custom VMs).
func (d *PublicDeployer) DeployBlockchain(
controlKeys []string,
chainAuthKeysStrs []string,
chainID ids.ID,
chain string,
genesis []byte,
vmName ...string, // optional: VM name for VMID; defaults to chain
) (bool, ids.ID, *txs.Tx, []string, error) {
ux.Logger.PrintToUser("Now creating blockchain...")
wallet, err := d.loadWallet(chainID)
if err != nil {
return false, ids.Empty, nil, nil, err
}
// Use explicit vmName when provided (EVM chains: "Lux EVM" ≠ chain name).
resolvedVMName := chain
if len(vmName) > 0 && vmName[0] != "" {
resolvedVMName = vmName[0]
}
vmID, err := utils.VMID(resolvedVMName)
if err != nil {
return false, ids.Empty, nil, nil, fmt.Errorf("failed to create VM ID from %s: %w", resolvedVMName, err)
}
chainAuthKeys, err := address.ParseToIDs(chainAuthKeysStrs)
if err != nil {
return false, ids.Empty, nil, nil, fmt.Errorf("failure parsing chain auth keys: %w", err)
}
if d.usingLedger {
ux.Logger.PrintToUser("*** Please sign CreateNetwork transaction on the ledger device *** ")
}
tx, err := d.createBlockchainTx(chainAuthKeys, chain, vmID, chainID, genesis, wallet)
if err != nil {
return false, ids.Empty, nil, nil, err
}
_, remainingChainAuthKeys, err := txutils.GetRemainingSigners(tx, controlKeys)
if err != nil {
return false, ids.Empty, nil, nil, err
}
isFullySigned := len(remainingChainAuthKeys) == 0
id := ids.Empty
if isFullySigned {
id, err = d.Commit(tx)
if err != nil {
return false, ids.Empty, nil, nil, err
}
}
return isFullySigned, id, tx, remainingChainAuthKeys, nil
}
// Commit issues a fully signed transaction to the network.
func (d *PublicDeployer) Commit(
tx *txs.Tx,
) (ids.ID, error) {
wallet, err := d.loadWallet()
if err != nil {
return ids.Empty, err
}
err = wallet.P().IssueTx(tx)
if err != nil {
return ids.Empty, err
}
return tx.ID(), nil
}
// Sign signs a transaction with the wallet's keys.
func (d *PublicDeployer) Sign(
tx *txs.Tx,
chainAuthKeysStrs []string,
chain ids.ID,
) error {
wallet, err := d.loadWallet(chain)
if err != nil {
return err
}
chainAuthKeys, err := address.ParseToIDs(chainAuthKeysStrs)
if err != nil {
return fmt.Errorf("failure parsing chain auth keys: %w", err)
}
if ok := d.checkWalletHasChainAuthAddresses(chainAuthKeys); !ok {
return ErrNoChainAuthKeysInWallet
}
if d.usingLedger {
txName := txutils.GetLedgerDisplayName(tx)
if len(txName) == 0 {
ux.Logger.PrintToUser("*** Please sign tx hash on the ledger device *** ")
} else {
ux.Logger.PrintToUser("*** Please sign %s transaction on the ledger device *** ", txName)
}
}
if err := d.signTx(tx, wallet); err != nil {
return err
}
return nil
}
func (d *PublicDeployer) loadWallet(preloadTxs ...ids.ID) (primary.Wallet, error) {
ctx := context.Background()
ux.Logger.PrintToUser("loadWallet: starting...")
api := os.Getenv("NODE_ENDPOINT")
if api == "" {
switch d.network {
case models.Testnet:
api = constants.TestnetAPIEndpoint
case models.Mainnet:
api = constants.MainnetAPIEndpoint
case models.Devnet:
api = constants.DevnetAPIEndpoint
case models.Local:
// used for E2E testing of public related paths
api = constants.LocalAPIEndpoint
default:
return nil, fmt.Errorf("unsupported public network")
}
}
ux.Logger.PrintToUser("loadWallet: using API endpoint %s", api)
// Build the set of P-Chain transactions to fetch (e.g., chain creation txs)
// This is needed so the wallet knows about chain owners when creating blockchain txs
pChainTxsToFetch := set.Set[ids.ID]{}
for _, txID := range preloadTxs {
pChainTxsToFetch.Add(txID)
}
// Set short dial timeouts on the default HTTP transport to handle DNS
// entries with mixed live/dead IPs. Without this, the Go HTTP client
// waits the full timeout on each dead IP before trying the next one.
if t, ok := http.DefaultTransport.(*http.Transport); ok {
t.DialContext = (&net.Dialer{Timeout: 5 * time.Second}).DialContext
t.TLSHandshakeTimeout = 10 * time.Second
t.TLSClientConfig = &tls.Config{InsecureSkipVerify: false} // Keep TLS verification
}
ux.Logger.PrintToUser("loadWallet: creating P-Chain wallet...")
// Use P-Chain only wallet since our X-Chain uses exchangevm which doesn't
// support standard XVM API methods.
wallet, err := primary.MakePChainWallet(ctx, &primary.WalletConfig{
URI: api,
LUXKeychain: keychainwrapper.WrapCryptoKeychain(d.kc),
PChainTxsToFetch: pChainTxsToFetch,
})
if err != nil {
ux.Logger.PrintToUser("loadWallet: error creating wallet: %v", err)
return nil, err
}
ux.Logger.PrintToUser("loadWallet: wallet created successfully")
return wallet, nil
}
func (d *PublicDeployer) getMultisigTxOptions(chainAuthKeys []ids.ShortID) []common.Option {
options := []common.Option{}
walletAddr := d.kc.Addresses().List()[0]
// addrs to use for signing
customAddrsSet := set.Set[ids.ShortID]{}
customAddrsSet.Add(walletAddr)
customAddrsSet.Add(chainAuthKeys...)
options = append(options, common.WithCustomAddresses(customAddrsSet))
// set change to go to wallet addr (instead of any other chain auth key)
changeOwner := &secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{walletAddr},
}
options = append(options, common.WithChangeOwner(changeOwner))
return options
}
func (d *PublicDeployer) createBlockchainTx(
chainAuthKeys []ids.ShortID,
chainName string,
vmID,
chainID ids.ID,
genesis []byte,
wallet primary.Wallet,
) (*txs.Tx, error) {
fxIDs := make([]ids.ID, 0)
options := d.getMultisigTxOptions(chainAuthKeys)
// create tx
unsignedTx, err := wallet.P().Builder().NewCreateChainTx(
chainID,
genesis,
vmID,
fxIDs,
chainName,
options...,
)
if err != nil {
return nil, err
}
tx := txs.Tx{Unsigned: unsignedTx}
// sign with current wallet
if err := wallet.P().Signer().Sign(context.Background(), &tx); err != nil {
return nil, err
}
return &tx, nil
}
func (d *PublicDeployer) createRemoveValidatorTX(
chainAuthKeys []ids.ShortID,
nodeID ids.NodeID,
chainID ids.ID,
wallet primary.Wallet,
) (*txs.Tx, error) {
options := d.getMultisigTxOptions(chainAuthKeys)
// create tx
unsignedTx, err := wallet.P().Builder().NewRemoveChainValidatorTx(nodeID, chainID, options...)
if err != nil {
return nil, err
}
tx := txs.Tx{Unsigned: unsignedTx}
// sign with current wallet
if err := wallet.P().Signer().Sign(context.Background(), &tx); err != nil {
return nil, err
}
return &tx, nil
}
func (d *PublicDeployer) createTransformChainTX(
chainAuthKeys []ids.ShortID,
elasticChainConfig climodels.ElasticChainConfig,
wallet primary.Wallet,
assetID ids.ID,
) (*txs.Tx, error) {
options := d.getMultisigTxOptions(chainAuthKeys)
// create tx
unsignedTx, err := wallet.P().Builder().NewTransformChainTx(elasticChainConfig.ChainID, assetID,
elasticChainConfig.InitialSupply, elasticChainConfig.MaxSupply, elasticChainConfig.MinConsumptionRate,
elasticChainConfig.MaxConsumptionRate, elasticChainConfig.MinValidatorStake, elasticChainConfig.MaxValidatorStake,
elasticChainConfig.MinStakeDuration, elasticChainConfig.MaxStakeDuration, elasticChainConfig.MinDelegationFee,
elasticChainConfig.MinDelegatorStake, elasticChainConfig.MaxValidatorWeightFactor, elasticChainConfig.UptimeRequirement, options...)
if err != nil {
return nil, err
}
tx := txs.Tx{Unsigned: unsignedTx}
// sign with current wallet
if err := wallet.P().Signer().Sign(context.Background(), &tx); err != nil {
return nil, err
}
return &tx, nil
}
// ConvertL1 converts a chain to an L1 (LP99)
func (d *PublicDeployer) ConvertL1(
controlKeys []string,
chainAuthKeysStrs []string,
chainID ids.ID,
blockchainID ids.ID,
managerAddress ethcommon.Address,
validators []interface{}, // []*txs.ConvertNetworkToL1Validator
) (bool, ids.ID, *txs.Tx, []string, error) {
ux.Logger.PrintToUser("Now calling ConvertNetworkToL1Tx...")
// Get wallet
wallet, err := d.loadWallet(chainID)
if err != nil {
return false, ids.Empty, nil, nil, err
}
chainAuthKeys, err := address.ParseToIDs(chainAuthKeysStrs)
if err != nil {
return false, ids.Empty, nil, nil, fmt.Errorf("failure parsing auth keys: %w", err)
}
// Convert []interface{} to []*txs.ConvertNetworkToL1Validator
convertValidators := make([]*txs.ConvertNetworkToL1Validator, 0, len(validators))
for _, v := range validators {
validator, ok := v.(*txs.ConvertNetworkToL1Validator)
if !ok {
return false, ids.Empty, nil, nil, fmt.Errorf("invalid validator type: expected *txs.ConvertNetworkToL1Validator, got %T", v)
}
convertValidators = append(convertValidators, validator)
}
// Build ConvertNetworkToL1Tx using the wallet builder
options := d.getMultisigTxOptions(chainAuthKeys)
unsignedTx, err := wallet.P().Builder().NewConvertNetworkToL1Tx(
chainID,
blockchainID,
managerAddress.Bytes(),
convertValidators,
options...,
)
if err != nil {
return false, ids.Empty, nil, nil, fmt.Errorf("error building ConvertNetworkToL1Tx: %w", err)
}
tx := txs.Tx{Unsigned: unsignedTx}
ctx, cancel := context.WithTimeout(context.Background(), constants.RequestTimeout)
defer cancel()
if err := wallet.P().Signer().Sign(ctx, &tx); err != nil {
return false, ids.Empty, nil, nil, fmt.Errorf("error signing tx: %w", err)
}
_, remainingChainAuthKeys, err := txutils.GetRemainingSigners(&tx, controlKeys)
if err != nil {
return false, ids.Empty, nil, nil, err
}
if len(remainingChainAuthKeys) == 0 {
// Commit the transaction
txID, err := d.Commit(&tx)
if err != nil {
return false, ids.Empty, nil, nil, err
}
return true, txID, &tx, remainingChainAuthKeys, nil
}
return false, ids.Empty, &tx, remainingChainAuthKeys, nil
}
func (*PublicDeployer) signTx(
tx *txs.Tx,
wallet primary.Wallet,
) error {
if err := wallet.P().Signer().Sign(context.Background(), tx); err != nil {
return err
}
return nil
}
func (d *PublicDeployer) createNetworkTx(controlKeys []string, threshold uint32, wallet primary.Wallet) (ids.ID, error) {
ux.Logger.PrintToUser("createNetworkTx: starting with control keys: %v", controlKeys)
addrs, err := address.ParseToIDs(controlKeys)
if err != nil {
return ids.Empty, fmt.Errorf("failure parsing control keys: %w", err)
}
ux.Logger.PrintToUser("createNetworkTx: parsed addresses: %v", addrs)
owners := &secp256k1fx.OutputOwners{
Addrs: addrs,
Threshold: threshold,
Locktime: 0,
}
opts := []common.Option{}
if d.usingLedger {
ux.Logger.PrintToUser("*** Please sign CreateNetwork transaction on the ledger device *** ")
}
ux.Logger.PrintToUser("createNetworkTx: calling IssueCreateNetworkTx...")
tx, err := wallet.P().IssueCreateNetworkTx(owners, opts...)
if err != nil {
ux.Logger.PrintToUser("createNetworkTx: IssueCreateNetworkTx error: %v", err)
return ids.Empty, err
}
ux.Logger.PrintToUser("createNetworkTx: tx issued successfully with ID: %s", tx.ID().String())
return tx.ID(), nil
}
func (d *PublicDeployer) getChainAuthAddressesInWallet(chainAuth []ids.ShortID) []ids.ShortID {
walletAddrs := d.kc.Addresses().List()
chainAuthInWallet := []ids.ShortID{}
for _, walletAddr := range walletAddrs {
for _, addr := range chainAuth {
if addr == walletAddr {
chainAuthInWallet = append(chainAuthInWallet, addr)
}
}
}
return chainAuthInWallet
}
// check that the wallet at least contain one chain auth address
func (d *PublicDeployer) checkWalletHasChainAuthAddresses(chainAuth []ids.ShortID) bool {
addrs := d.getChainAuthAddressesInWallet(chainAuth)
return len(addrs) != 0
}
// IsChainValidator checks if a node is a validator for the given chain.
func IsChainValidator(chainID ids.ID, nodeID ids.NodeID, network models.Network) (bool, error) {
var apiURL string
switch network {
case models.Mainnet:
apiURL = constants.MainnetAPIEndpoint
case models.Testnet:
apiURL = constants.TestnetAPIEndpoint
default:
return false, fmt.Errorf("invalid network: %s", network)
}
pClient := platformvm.NewClient(apiURL)
ctx, cancel := context.WithTimeout(context.Background(), constants.E2ERequestTimeout)
defer cancel()
vals, err := pClient.GetCurrentValidators(ctx, chainID, []ids.NodeID{nodeID})
if err != nil {
return false, fmt.Errorf("failed to get current validators")
}
return len(vals) != 0, nil
}
// GetPublicChainValidators returns the validators for a chain on a public network.
func GetPublicChainValidators(chainID ids.ID, network models.Network) ([]platformvm.ClientPermissionlessValidator, error) {
var apiURL string
switch network {
case models.Mainnet:
apiURL = constants.MainnetAPIEndpoint
case models.Testnet:
apiURL = constants.TestnetAPIEndpoint
default:
return nil, fmt.Errorf("invalid network: %s", network)
}
pClient := platformvm.NewClient(apiURL)
ctx, cancel := context.WithTimeout(context.Background(), constants.E2ERequestTimeout)
defer cancel()
vals, err := pClient.GetCurrentValidators(ctx, chainID, []ids.NodeID{})
if err != nil {
return nil, fmt.Errorf("failed to get current validators")
}
return vals, nil
}
// ValidateChainNameAndGetChains validates a chain name and returns chain information
func ValidateChainNameAndGetChains(chainName string) error {
// Basic validation - can be expanded later
if chainName == "" {
return fmt.Errorf("chain name cannot be empty")
}
return nil
}
// IncreaseValidatorPChainBalance increases a validator's balance on P-chain.
func (d *PublicDeployer) IncreaseValidatorPChainBalance(
_ ids.ID, // validationID reserved for future use
balance uint64,
) error {
wallet, err := d.loadWallet()
if err != nil {
return err
}
// Create a base transaction to transfer funds to increase validator balance
// Use the network's native asset ID (LUX)
luxAssetID := ids.Empty
if d.network.ID() == constants.MainnetID || d.network.ID() == constants.TestnetID {
luxAssetID = ids.Empty // Native asset on mainnet/testnet
}
outputs := []*lux.TransferableOutput{
{
Asset: lux.Asset{ID: luxAssetID},
Out: &secp256k1fx.TransferOutput{
Amt: balance,
OutputOwners: secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{},
},
},
},
}
tx, err := wallet.P().IssueBaseTx(outputs)
if err != nil {
return fmt.Errorf("failed to create balance increase transaction: %w", err)
}
ux.Logger.PrintToUser("Increased validator balance by %d nLUX", balance)
ux.Logger.PrintToUser("Transaction ID: %s", tx.ID())
return nil
}
// GetDefaultChainAirdropKeyInfo returns the default airdrop key information for a chain.
func GetDefaultChainAirdropKeyInfo(_ *application.Lux, _ string) (string, string, string, error) {
// Return empty values for now - this would typically read from sidecar
return "", "", "", nil
}