mirror of
https://github.com/luxfi/sdk.git
synced 2026-10-30 15:19:23 +00:00
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]>
486 lines
14 KiB
Go
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
|
|
}
|