Files
zeekayandHanzo Dev da299df77d the corporate name is Lux Industries Inc
Lux Partners Limited is not the entity behind this work. Every place the
old name stood — copyright lines, site footers, brand config, legal and
compliance text — now names the company that actually holds it.

lux.fund is the exception and is not renamed to match: that site belongs
to Lux Assets Management LLC, a different company, and it says so.

Two kinds of line were left for a person rather than rewritten. A dated
news item recording that Lux Partners Limited was established in the Isle
of Man is a historical fact, and renaming it would falsify the record. And
a sentence asserting that the company is domiciled in the Isle of Man is a
claim about a jurisdiction, not a name — an Inc or an LLC is not an Isle
of Man company, so swapping the name there would assert something untrue.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-28 13:05:15 -07:00

486 lines
14 KiB
Go

// Copyright (C) 2025, Lux Industries Inc All rights reserved.
// See the file LICENSE for licensing terms.
package contract
import (
"context"
"fmt"
"strings"
"github.com/luxfi/constants"
"github.com/luxfi/ids"
"github.com/luxfi/sdk/application"
"github.com/luxfi/sdk/flags"
sdkinfo "github.com/luxfi/sdk/info"
"github.com/luxfi/sdk/models"
"github.com/luxfi/sdk/prompts"
"github.com/spf13/cobra"
)
// GetBlockchainIDFromAlias retrieves a blockchain ID from its alias using the info API.
func GetBlockchainIDFromAlias(endpoint string, alias string) (ids.ID, error) {
infoClient := sdkinfo.NewClient(endpoint)
ctx := context.Background()
return infoClient.GetBlockchainID(ctx, alias)
}
type ChainSpec struct {
BlockchainName string
blockchainNameFlagEnabled bool
blockchainNameFlagName string
CChain bool
cChainFlagEnabled bool
cChainFlagName string
PChain bool
pChainFlagEnabled bool
pChainFlagName string
XChain bool
xChainFlagEnabled bool
xChainFlagName string
BlockchainID string
blockchainIDFlagEnabled bool
blockchainIDFlagName string
OnlySOV bool
}
const (
defaultBlockchainNameFlagName = "blockchain"
defaultCChainFlagName = "c-chain"
defaultPChainFlagName = "p-chain"
defaultXChainFlagName = "x-chain"
defaultBlockchainIDFlagName = "blockchain-id"
)
func (cs *ChainSpec) CheckMutuallyExclusiveFields() error {
if !flags.EnsureMutuallyExclusive([]bool{
cs.BlockchainName != "",
cs.BlockchainID != "",
cs.CChain,
cs.PChain,
cs.XChain,
}) {
flags := []string{}
if cs.blockchainNameFlagEnabled {
flags = append(flags, cs.blockchainNameFlagName)
}
if cs.cChainFlagEnabled {
flags = append(flags, cs.cChainFlagName)
}
if cs.pChainFlagEnabled {
flags = append(flags, cs.pChainFlagName)
}
if cs.xChainFlagEnabled {
flags = append(flags, cs.xChainFlagName)
}
if cs.blockchainIDFlagEnabled {
flags = append(flags, cs.blockchainIDFlagName)
}
return fmt.Errorf("%s are mutually exclusive flags",
strings.Join(flags, ", "),
)
}
return nil
}
func (cs *ChainSpec) Defined() bool {
if cs.blockchainNameFlagEnabled && cs.BlockchainName != "" {
return true
}
if cs.cChainFlagEnabled && cs.CChain {
return true
}
if cs.pChainFlagEnabled && cs.PChain {
return true
}
if cs.xChainFlagEnabled && cs.XChain {
return true
}
if cs.blockchainIDFlagEnabled && cs.BlockchainID != "" {
return true
}
return false
}
func (cs *ChainSpec) fillDefaults() {
if cs.blockchainNameFlagName == "" {
cs.blockchainIDFlagEnabled = true
cs.blockchainNameFlagName = defaultBlockchainNameFlagName
}
if cs.cChainFlagName == "" {
cs.cChainFlagEnabled = true
cs.cChainFlagName = defaultCChainFlagName
}
if cs.pChainFlagName == "" {
cs.pChainFlagName = defaultPChainFlagName
}
if cs.xChainFlagName == "" {
cs.xChainFlagName = defaultXChainFlagName
}
if cs.blockchainIDFlagName == "" {
cs.blockchainIDFlagName = defaultBlockchainIDFlagName
}
}
func (cs *ChainSpec) SetFlagNames(
blockchainNameFlagName string,
cChainFlagName string,
pChainFlagName string,
xChainFlagName string,
blockchainIDFlagName string,
) {
cs.blockchainNameFlagName = blockchainNameFlagName
cs.cChainFlagName = cChainFlagName
cs.pChainFlagName = pChainFlagName
cs.xChainFlagName = xChainFlagName
cs.blockchainIDFlagName = blockchainIDFlagName
cs.SetEnabled(
cs.blockchainNameFlagName != "",
cs.cChainFlagName != "",
cs.pChainFlagName != "",
cs.xChainFlagName != "",
cs.blockchainIDFlagName != "",
)
}
func (cs *ChainSpec) SetEnabled(
blockchainNameFlagEnabled bool,
cChainFlagEnabled bool,
pChainFlagEnabled bool,
xChainFlagEnabled bool,
blockchainIDFlagEnabled bool,
) {
cs.blockchainNameFlagEnabled = blockchainNameFlagEnabled
cs.cChainFlagEnabled = cChainFlagEnabled
cs.pChainFlagEnabled = pChainFlagEnabled
cs.xChainFlagEnabled = xChainFlagEnabled
cs.blockchainIDFlagEnabled = blockchainIDFlagEnabled
}
func (cs *ChainSpec) AddToCmd(
cmd *cobra.Command,
goalFmt string,
) {
cs.fillDefaults()
if cs.blockchainNameFlagEnabled {
cmd.Flags().StringVar(&cs.BlockchainName, cs.blockchainNameFlagName, "", fmt.Sprintf(goalFmt, "the given CLI blockchain"))
}
if cs.cChainFlagEnabled {
cmd.Flags().BoolVar(&cs.CChain, cs.cChainFlagName, false, fmt.Sprintf(goalFmt, "C-Chain"))
}
if cs.pChainFlagEnabled {
cmd.Flags().BoolVar(&cs.PChain, cs.pChainFlagName, false, fmt.Sprintf(goalFmt, "P-Chain"))
}
if cs.xChainFlagEnabled {
cmd.Flags().BoolVar(&cs.XChain, cs.xChainFlagName, false, fmt.Sprintf(goalFmt, "X-Chain"))
}
if cs.blockchainIDFlagEnabled {
cmd.Flags().StringVar(&cs.BlockchainID, cs.blockchainIDFlagName, "", fmt.Sprintf(goalFmt, "the given blockchain ID/Alias"))
}
}
func GetBlockchainEndpoints(
app *application.Lux,
network models.Network,
chainSpec ChainSpec,
promptForRPCEndpoint bool,
promptForWSEndpoint bool,
) (string, string, error) {
var (
rpcEndpoint string
wsEndpoint string
)
if chainSpec.BlockchainName != "" {
sc, err := app.LoadSidecar(chainSpec.BlockchainName)
if err != nil {
return "", "", fmt.Errorf("failed to load sidecar: %w", err)
}
networkName := network.Name()
if sc.Networks[networkName].BlockchainID == ids.Empty {
// look into the cluster deploys
for k := range sc.Networks {
sidecarNetwork, err := app.GetNetworkFromSidecarNetworkName(k)
if err == nil {
if sidecarNetwork.Equals(network) {
networkName = sidecarNetwork.Name()
}
}
}
}
if len(sc.Networks[networkName].RPCEndpoints) > 0 {
rpcEndpoint = sc.Networks[networkName].RPCEndpoints[0]
}
if len(sc.Networks[networkName].WSEndpoints) > 0 {
wsEndpoint = sc.Networks[networkName].WSEndpoints[0]
}
}
if rpcEndpoint == "" && chainSpec.CChain {
rpcEndpoint = network.CChainEndpoint()
wsEndpoint = network.CChainWSEndpoint()
}
blockchainDesc, err := GetBlockchainDesc(chainSpec)
if err != nil {
return "", "", err
}
if rpcEndpoint == "" && promptForRPCEndpoint {
rpcEndpoint, err = app.Prompt.CaptureURL("What is the RPC endpoint for " + blockchainDesc)
if err != nil {
return "", "", err
}
}
if wsEndpoint == "" && promptForWSEndpoint {
wsEndpoint, err = app.Prompt.CaptureURL("What is the WS endpoint for " + blockchainDesc)
if err != nil {
return "", "", err
}
}
return rpcEndpoint, wsEndpoint, nil
}
func GetBlockchainID(
app *application.Lux,
network models.Network,
chainSpec ChainSpec,
) (ids.ID, error) {
var blockchainID ids.ID
switch {
case chainSpec.BlockchainID != "":
var err error
blockchainID, err = ids.FromString(chainSpec.BlockchainID)
if err != nil {
// it should be an alias at this point
blockchainID, err = GetBlockchainIDFromAlias(network.Endpoint(), chainSpec.BlockchainID)
if err != nil {
return ids.Empty, err
}
}
case chainSpec.CChain:
var err error
blockchainID, err = GetBlockchainIDFromAlias(network.Endpoint(), "C")
if err != nil {
return ids.Empty, err
}
case chainSpec.BlockchainName != "":
sc, err := app.LoadSidecar(chainSpec.BlockchainName)
if err != nil {
return ids.Empty, fmt.Errorf("failed to load sidecar: %w", err)
}
if sc.Networks[network.Name()].BlockchainID == ids.Empty {
return ids.Empty, fmt.Errorf("blockchain has not been deployed to %s", network.Name())
}
blockchainID = sc.Networks[network.Name()].BlockchainID
default:
return ids.Empty, fmt.Errorf("blockchain is not defined")
}
return blockchainID, nil
}
func GetNetworkID(
app *application.Lux,
network models.Network,
chainSpec ChainSpec,
) (ids.ID, error) {
var networkID ids.ID
switch {
case chainSpec.CChain:
networkID = ids.Empty
case chainSpec.BlockchainName != "":
sc, err := app.LoadSidecar(chainSpec.BlockchainName)
if err != nil {
return ids.Empty, fmt.Errorf("failed to load sidecar: %w", err)
}
if sc.Networks[network.Name()].BlockchainID == ids.Empty {
return ids.Empty, fmt.Errorf("blockchain has not been deployed to %s", network.Name())
}
networkID = sc.Networks[network.Name()].ChainID
case chainSpec.BlockchainID != "":
blockchainID, err := ids.FromString(chainSpec.BlockchainID)
if err != nil {
return ids.Empty, fmt.Errorf("failure parsing %s as id: %w", chainSpec.BlockchainID, err)
}
// Retrieve the blockchain creation transaction to get the network ID
createChainTx, err := GetBlockchainTx(network.Endpoint(), blockchainID)
if err != nil {
return ids.Empty, fmt.Errorf("failed to get blockchain tx: %w", err)
}
networkID = createChainTx.ValidateNetworkID
default:
return ids.Empty, fmt.Errorf("blockchain is not defined")
}
return networkID, nil
}
func GetBlockchainDesc(
chainSpec ChainSpec,
) (string, error) {
blockchainDesc := ""
switch {
case chainSpec.BlockchainName != "":
blockchainDesc = chainSpec.BlockchainName
case chainSpec.CChain:
blockchainDesc = "C-Chain"
case chainSpec.PChain:
blockchainDesc = "P-Chain"
case chainSpec.XChain:
blockchainDesc = "X-Chain"
case chainSpec.BlockchainID != "":
blockchainDesc = chainSpec.BlockchainID
default:
return "", fmt.Errorf("blockchain is not defined")
}
return blockchainDesc, nil
}
func GetWarpInfo(
app *application.Lux,
network models.Network,
chainSpec ChainSpec,
promptForRegistry bool,
promptForMessenger bool,
defaultToLatestReleasedMessenger bool,
) (string, string, error) {
registryAddress := ""
messengerAddress := ""
switch {
case chainSpec.CChain:
var err error
registryAddress, messengerAddress, err = GetCChainWarpInfo(app, network)
if err != nil {
return "", "", err
}
case chainSpec.BlockchainID != "":
case chainSpec.BlockchainName != "":
sc, err := app.LoadSidecar(chainSpec.BlockchainName)
if err != nil {
return "", "", fmt.Errorf("failed to load sidecar: %w", err)
}
if sc.Networks[network.Name()].BlockchainID == ids.Empty {
return "", "", fmt.Errorf("blockchain has not been deployed to %s", network.Name())
}
registryAddress = sc.Networks[network.Name()].TeleporterRegistryAddress
messengerAddress = sc.Networks[network.Name()].TeleporterMessengerAddress
default:
return "", "", fmt.Errorf("blockchain is not defined")
}
blockchainDesc, err := GetBlockchainDesc(chainSpec)
if err != nil {
return "", "", err
}
if registryAddress == "" && promptForRegistry {
addr, err := app.Prompt.CaptureAddress("Which is the Warp Registry address for " + blockchainDesc)
if err != nil {
return "", "", err
}
registryAddress = addr.Hex()
}
if messengerAddress == "" {
if promptForMessenger {
addr, err := app.Prompt.CaptureAddress("Which is the Warp Messenger address for " + blockchainDesc)
if err != nil {
return "", "", err
}
messengerAddress = addr.Hex()
} else if defaultToLatestReleasedMessenger {
messengerAddress = constants.DefaultWarpMessengerAddress
}
}
return registryAddress, messengerAddress, nil
}
func PromptChain(
app *application.Lux,
network models.Network,
prompt string,
blockchainNameToAvoid string,
chainSpec *ChainSpec,
) (bool, error) {
var (
err error
blockchainNames []string
)
if chainSpec.blockchainNameFlagEnabled {
blockchainNames, err = app.GetBlockchainNamesOnNetwork(network, chainSpec.OnlySOV)
if err != nil {
return false, err
}
}
cancel, pChain, xChain, cChain, blockchainName, blockchainID, err := prompts.PromptChain(
app.Prompt,
prompt,
blockchainNames,
chainSpec.pChainFlagEnabled,
chainSpec.xChainFlagEnabled,
chainSpec.cChainFlagEnabled,
blockchainNameToAvoid,
chainSpec.blockchainIDFlagEnabled,
)
if err != nil || cancel {
return cancel, err
}
if blockchainID != "" {
chainID, err := ids.FromString(blockchainID)
if err != nil {
// map from alias to blockchain ID (or identity)
// For L1 blockchains, we don't have a chain ID in the traditional sense
// This would need to be retrieved from the blockchain's configuration
// For now, return an error
return false, fmt.Errorf("getting chain ID for blockchain %s not yet implemented", blockchainID)
}
blockchainID = chainID.String()
}
chainSpec.BlockchainName = blockchainName
chainSpec.CChain = cChain
chainSpec.PChain = pChain
chainSpec.XChain = xChain
chainSpec.BlockchainID = blockchainID
return false, nil
}
func GetCChainWarpInfo(
app *application.Lux,
network models.Network,
) (string, string, error) {
messengerAddress := ""
registryAddress := ""
switch {
case network.Kind() == models.Local:
// TODO: Add localnet support
// b, extraLocalNetworkData, err := localnet.GetExtraLocalNetworkData(app, "")
b := false
var extraLocalNetworkData interface{} = nil
var err error = nil
if err != nil {
return "", "", err
}
if !b || extraLocalNetworkData == nil {
return "", "", fmt.Errorf("no extra local network data available")
}
// TODO: Implement extraction of teleporter addresses from extraLocalNetworkData
// For now, return empty addresses
messengerAddress = ""
registryAddress = ""
case network.ClusterName() != "":
clusterConfig, err := app.GetClusterConfig(network.ClusterName())
if err != nil {
return "", "", err
}
// Type assertion for ExtraNetworkData field
extraData, ok := clusterConfig["extraNetworkData"].(map[string]interface{})
if ok {
if msg, ok := extraData["CChainTeleporterMessengerAddress"].(string); ok {
messengerAddress = msg
}
if reg, ok := extraData["CChainTeleporterRegistryAddress"].(string); ok {
registryAddress = reg
}
}
case network.Kind() == models.Testnet:
messengerAddress = constants.DefaultWarpMessengerAddress
registryAddress = "" // TODO: Add TestnetCChainWarpRegistryAddress when available
case network.Kind() == models.Mainnet:
messengerAddress = constants.DefaultWarpMessengerAddress
registryAddress = constants.MainnetCChainWarpRegistryAddress
}
return registryAddress, messengerAddress, nil
}