Files
zeekayandHanzo Dev 62d5f28c22 A chain address is built in one place
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]>
2026-08-30 15:05:42 -07:00

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
}
}