mirror of
https://github.com/luxfi/cli.git
synced 2026-10-30 17:01:20 +00:00
The node moved every chain route from /v1/bc/<alias> to /v1/chain/<alias> and deleted the old prefix, so every chain call answered 404. The address was spelled out at 112 sites across 34 files and constants.ChainAliasPrefix was used at none of them, which is why nothing failed to compile when the segment moved. route.Chain(uri, alias) composes it now, and route.Alias reads one back, replacing three hand-rolled parsers ā one of which took the alias by position in the split path and only worked because the new segment happens to leave the component count unchanged. TestChainAddressBuiltOnlyHere walks every .go file in the module and fails on a chain address in a string literal, so the segment cannot drift back; prose is free to name a route. TestGuardCatchesEachShape plants each forbidden shape and requires the guard to find it. The endpoint box and the eleven-chain table were the same list written twice; there is one list, rendered once, measured rather than hand-padded. The generated CLI reference and the faucet manifest compose through the same function. Co-authored-by: Hanzo Dev <[email protected]>
273 lines
8.6 KiB
Go
273 lines
8.6 KiB
Go
// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package l1cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/luxfi/cli/pkg/chain"
|
|
"github.com/luxfi/cli/pkg/localnet"
|
|
"github.com/luxfi/cli/pkg/route"
|
|
"github.com/luxfi/cli/pkg/ux"
|
|
"github.com/luxfi/sdk/models"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
deployLocal bool
|
|
deployTestnet bool
|
|
deployMainnet bool
|
|
useExisting bool
|
|
protocol string
|
|
)
|
|
|
|
func newDeployCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "deploy [l1Name]",
|
|
Short: "Deploy a sovereign L1 blockchain",
|
|
Long: `Deploy a sovereign L1 blockchain to local, testnet, or mainnet.
|
|
|
|
L1s are independent blockchains with their own:
|
|
- Validator set
|
|
- Native token
|
|
- Consensus mechanism
|
|
- L2/L3 support
|
|
|
|
A deployed L1 can also connect to other protocols (Lux, OP Stack, etc.)
|
|
for cross-chain interoperability.`,
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: deployL1,
|
|
}
|
|
|
|
cmd.Flags().BoolVarP(&deployLocal, "local", "l", false, "Deploy to local network")
|
|
cmd.Flags().BoolVarP(&deployTestnet, "testnet", "t", false, "Deploy to testnet")
|
|
cmd.Flags().BoolVarP(&deployMainnet, "mainnet", "m", false, "Deploy to mainnet")
|
|
cmd.Flags().BoolVar(&useExisting, "use-existing", false, "Use existing blockchain data")
|
|
cmd.Flags().StringVar(&protocol, "protocol", "lux", "Protocol to use (lux, lux-compat)")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func deployL1(_ *cobra.Command, args []string) error {
|
|
l1Name := args[0]
|
|
|
|
// Determine deployment target
|
|
network := ""
|
|
switch {
|
|
case deployLocal:
|
|
network = NetworkLocal
|
|
case deployTestnet:
|
|
network = NetworkTestnet
|
|
case deployMainnet:
|
|
network = NetworkMainnet
|
|
default:
|
|
// Interactive selection
|
|
networks := []string{"Local Network", "Testnet", "Mainnet"}
|
|
choice, err := app.Prompt.CaptureList("Choose deployment network", networks)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch choice {
|
|
case "Local Network":
|
|
network = NetworkLocal
|
|
case "Testnet":
|
|
network = NetworkTestnet
|
|
case "Mainnet":
|
|
network = NetworkMainnet
|
|
}
|
|
}
|
|
|
|
ux.Logger.PrintToUser("Deploying L1 %s to %s network...", l1Name, network)
|
|
|
|
// Load L1 configuration
|
|
sc, err := app.LoadSidecar(l1Name)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load L1 configuration: %w", err)
|
|
}
|
|
|
|
// Show deployment info
|
|
ux.Logger.PrintToUser("\nš L1 Configuration:")
|
|
ux.Logger.PrintToUser(" Name: %s", l1Name)
|
|
ux.Logger.PrintToUser(" Chain ID: %s", sc.ChainID)
|
|
ux.Logger.PrintToUser(" Token: %s (%s)", sc.TokenInfo.Name, sc.TokenInfo.Symbol)
|
|
ux.Logger.PrintToUser(" Validator Management: %s", sc.ValidatorManagement)
|
|
ux.Logger.PrintToUser(" Protocol: %s", protocol)
|
|
|
|
// Check for existing blockchain data
|
|
if useExisting && sc.BlockchainID.String() != "" {
|
|
ux.Logger.PrintToUser("\nš Using existing blockchain data:")
|
|
ux.Logger.PrintToUser(" Blockchain ID: %s", sc.BlockchainID)
|
|
ux.Logger.PrintToUser(" Chain ID: %s", sc.ChainID)
|
|
}
|
|
|
|
// Deploy based on network
|
|
switch network {
|
|
case NetworkLocal:
|
|
return deployL1Local(l1Name, &sc)
|
|
case NetworkTestnet:
|
|
return deployL1Testnet(l1Name, &sc)
|
|
case NetworkMainnet:
|
|
return deployL1Mainnet(l1Name, &sc)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func deployL1Local(l1Name string, sc *models.Sidecar) error {
|
|
ux.Logger.PrintToUser("\nš Deploying to local network...")
|
|
|
|
// Check if local network is running
|
|
if !app.IsLocalNetworkRunning() {
|
|
ux.Logger.PrintToUser("Local network not running. Starting it now...")
|
|
// Start local network
|
|
if err := startLocalNetwork(); err != nil {
|
|
return fmt.Errorf("failed to start local network: %w", err)
|
|
}
|
|
// Wait for network with timeout
|
|
networkReadyTimeout := 30 * time.Second
|
|
if err := waitForLocalNetworkReady(networkReadyTimeout); err != nil {
|
|
return fmt.Errorf("local network failed to start: %w", err)
|
|
}
|
|
}
|
|
|
|
// Deploy L1
|
|
ux.Logger.PrintToUser("Creating L1 blockchain...")
|
|
|
|
// If using existing data, restore it
|
|
if useExisting && sc.BlockchainID.String() != "" {
|
|
ux.Logger.PrintToUser("Restoring blockchain state from existing data...")
|
|
// Restore blockchain data from saved state
|
|
statePath := app.GetSidecarPath(l1Name)
|
|
if _, err := os.Stat(statePath); err == nil {
|
|
ux.Logger.PrintToUser("Found existing state at %s", statePath)
|
|
}
|
|
}
|
|
|
|
// Initialize validator manager
|
|
if sc.ValidatorManagement == ValidatorManagementPoA {
|
|
ux.Logger.PrintToUser("Initializing PoA validator manager...")
|
|
// Deploy PoA validator manager contract using the SDK
|
|
sc.ValidatorManagerAddress = "0x0000000000000000000000000000000000001000" // Precompiled address
|
|
ux.Logger.PrintToUser("PoA validator manager deployed at: %s", sc.ValidatorManagerAddress)
|
|
} else {
|
|
ux.Logger.PrintToUser("Initializing PoS validator manager...")
|
|
// Deploy PoS validator manager contract using the SDK
|
|
sc.ValidatorManagerAddress = "0x0000000000000000000000000000000000001001" // Precompiled address
|
|
ux.Logger.PrintToUser("PoS validator manager deployed at: %s", sc.ValidatorManagerAddress)
|
|
}
|
|
|
|
// Set up cross-protocol support if needed
|
|
if protocol == "lux-compat" {
|
|
ux.Logger.PrintToUser("Enabling Lux compatibility mode...")
|
|
// Enable Lux chain compatibility by setting appropriate configurations
|
|
sc.ProtocolCompatibility = "lux,ethereum"
|
|
ux.Logger.PrintToUser("Cross-protocol support enabled")
|
|
}
|
|
|
|
ux.Logger.PrintToUser("\nā
L1 deployed successfully!")
|
|
ux.Logger.PrintToUser("\nš L1 Information:")
|
|
ux.Logger.PrintToUser(" RPC Endpoint: %s", route.Chain("http://localhost:9630", sc.BlockchainID.String())+"/rpc")
|
|
ux.Logger.PrintToUser(" Chain ID: %s", sc.ChainID)
|
|
ux.Logger.PrintToUser(" Explorer: http://localhost:4000")
|
|
|
|
ux.Logger.PrintToUser("\nš” Next steps:")
|
|
ux.Logger.PrintToUser(" Add validator: lux l1 validator add %s --node-id <NODE_ID>", l1Name)
|
|
ux.Logger.PrintToUser(" Deploy L2: lux l2 create %s-l2 --l1 %s", l1Name, l1Name)
|
|
ux.Logger.PrintToUser(" Enable cross-chain: lux bridge enable %s", l1Name)
|
|
|
|
return nil
|
|
}
|
|
|
|
func deployL1Testnet(l1Name string, _ *models.Sidecar) error {
|
|
ux.Logger.PrintToUser("\nš Deploying to testnet...")
|
|
|
|
// Use the blockchain deployment logic from blockchaincmd
|
|
deployer := chain.NewLocalDeployer(app, "", "")
|
|
|
|
// Deploy to testnet
|
|
genesis, err := app.LoadRawGenesis(l1Name)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load genesis: %w", err)
|
|
}
|
|
chainID, blockchainID, err := deployer.DeployBlockchain(l1Name, genesis)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to deploy L1 to testnet: %w", err)
|
|
}
|
|
|
|
ux.Logger.PrintToUser("L1 deployed to testnet!")
|
|
ux.Logger.PrintToUser("Chain ID: %s", chainID)
|
|
ux.Logger.PrintToUser("Blockchain ID: %s", blockchainID)
|
|
|
|
return nil
|
|
}
|
|
|
|
func deployL1Mainnet(l1Name string, _ *models.Sidecar) error {
|
|
ux.Logger.PrintToUser("\nš Deploying to mainnet...")
|
|
|
|
// Mainnet deployment requires additional security checks
|
|
ux.Logger.PrintToUser("ā ļø Mainnet deployment requires:")
|
|
ux.Logger.PrintToUser(" - Sufficient LUX balance for deployment")
|
|
ux.Logger.PrintToUser(" - Validator nodes ready")
|
|
ux.Logger.PrintToUser(" - Security audit completed")
|
|
|
|
// Use the blockchain deployment logic from blockchaincmd
|
|
deployer := chain.NewLocalDeployer(app, "", "")
|
|
|
|
// Deploy to mainnet with additional confirmations
|
|
genesis, err := app.LoadRawGenesis(l1Name)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load genesis: %w", err)
|
|
}
|
|
chainID, blockchainID, err := deployer.DeployBlockchain(l1Name, genesis)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to deploy L1 to mainnet: %w", err)
|
|
}
|
|
|
|
ux.Logger.PrintToUser("L1 deployed to mainnet!")
|
|
ux.Logger.PrintToUser("Chain ID: %s", chainID)
|
|
ux.Logger.PrintToUser("Blockchain ID: %s", blockchainID)
|
|
|
|
return nil
|
|
}
|
|
|
|
func startLocalNetwork() error {
|
|
// Start local network with optimal L1 settings
|
|
ux.Logger.PrintToUser("Starting local network...")
|
|
|
|
// Check if local network is already running
|
|
if isRunning, err := localnet.LocalNetworkIsRunning(app); err != nil {
|
|
return fmt.Errorf("failed to check network status: %w", err)
|
|
} else if isRunning {
|
|
ux.Logger.PrintToUser("Local network is already running")
|
|
return nil
|
|
}
|
|
|
|
// Start the local network
|
|
if err := localnet.StartLocalNetwork(app, "local-network", "latest"); err != nil {
|
|
return fmt.Errorf("failed to start local network: %w", err)
|
|
}
|
|
|
|
ux.Logger.PrintToUser("Local network started successfully")
|
|
return nil
|
|
}
|
|
|
|
// waitForLocalNetworkReady waits for the local network to be ready with a timeout
|
|
func waitForLocalNetworkReady(timeout time.Duration) error {
|
|
deadline := time.Now().Add(timeout)
|
|
ticker := time.NewTicker(1 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
if time.Now().After(deadline) {
|
|
return fmt.Errorf("timeout after %s waiting for local network to become ready", timeout)
|
|
}
|
|
if app.IsLocalNetworkRunning() {
|
|
return nil
|
|
}
|
|
<-ticker.C
|
|
}
|
|
}
|