Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
run: go test -race -coverprofile=coverage.out ./...

- name: Run CLI e2e smoke tests
run: go test -tags=clie2e -v ./e2e/... -run "Help|Params|MissingArgs"
run: go test -tags=clie2e -v ./e2e/... -run "Help|Params|MissingArgs|RemovedOldNames|UnknownSubcommand"

- name: Compile integration tests
run: go test -tags=integration -run '^$' ./pkg/pchain/...
Expand Down
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ cmd/ - Cobra CLI commands (user-facing interface)
├── keys.go - Key management: generate, import, export, delete, default
├── wallet.go - Wallet info: address, balance
├── transfer.go - Transfers: send, p-to-c, c-to-p, export, import
├── validator.go - Staking: add validator, delegate
├── subnet.go - Subnets: create, transfer-ownership, convert-l1
├── l1.go - L1 validators: register, set-weight, add-balance, disable
├── validator.go - Staking: add-permissionless, add-permissionless-delegator
├── subnet.go - Subnets: create, transfer-ownership, convert-to-l1, add-validator
├── l1.go - L1 validators: register-validator, set-validator-weight, increase-validator-balance, disable-validator
├── chain.go - Chains: create chain on subnet
└── node.go - Node utilities: info

Expand Down
3 changes: 2 additions & 1 deletion cmd/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ var chainCmd = &cobra.Command{
Use: "chain",
Short: "Chain management",
Long: `Create and manage chains on subnets.`,
RunE: requireSubcommand,
}

var chainCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a new chain",
Short: "Create a new chain (CreateChainTx)",
Long: `Create a new blockchain on a subnet.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := getOperationContext()
Expand Down
1 change: 1 addition & 0 deletions cmd/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Subcommands:
list List all stored keys
export Export a key (show private key)
delete Remove a stored key`,
RunE: requireSubcommand,
}

var keysImportCmd = &cobra.Command{
Expand Down
13 changes: 7 additions & 6 deletions cmd/l1.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ var l1Cmd = &cobra.Command{
Use: "l1",
Short: "L1 validator operations",
Long: `Manage validators on Avalanche L1 blockchains.`,
RunE: requireSubcommand,
}

var l1RegisterValidatorCmd = &cobra.Command{
Use: "register-validator",
Short: "Register a new L1 validator",
Short: "Register a new L1 validator (RegisterL1ValidatorTx)",
Long: `Register a new validator on an L1 blockchain.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := getOperationContext()
Expand Down Expand Up @@ -80,8 +81,8 @@ var l1RegisterValidatorCmd = &cobra.Command{
}

var l1SetWeightCmd = &cobra.Command{
Use: "set-weight",
Short: "Set L1 validator weight",
Use: "set-validator-weight",
Short: "Set L1 validator weight (SetL1ValidatorWeightTx)",
Long: `Set the weight of a validator on an L1 blockchain.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := getOperationContext()
Expand Down Expand Up @@ -118,8 +119,8 @@ var l1SetWeightCmd = &cobra.Command{
}

var l1AddBalanceCmd = &cobra.Command{
Use: "add-balance",
Short: "Increase L1 validator balance",
Use: "increase-validator-balance",
Short: "Increase L1 validator balance (IncreaseL1ValidatorBalanceTx)",
Long: `Increase the balance of a validator on an L1 blockchain for continuous fees.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := getOperationContext()
Expand Down Expand Up @@ -165,7 +166,7 @@ var l1AddBalanceCmd = &cobra.Command{

var l1DisableValidatorCmd = &cobra.Command{
Use: "disable-validator",
Short: "Disable an L1 validator",
Short: "Disable an L1 validator (DisableL1ValidatorTx)",
Long: `Disable a validator on an L1 blockchain and return remaining funds.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := getOperationContext()
Expand Down
1 change: 1 addition & 0 deletions cmd/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ var nodeCmd = &cobra.Command{
Use: "node",
Short: "Node information",
Long: `Node information operations including getting node ID and BLS key.`,
RunE: requireSubcommand,
}

var nodeIP string
Expand Down
13 changes: 12 additions & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@ var rootCmd = &cobra.Command{
Short: "Avalanche P-Chain CLI",
SilenceErrors: true,
SilenceUsage: true,
RunE: requireSubcommand,
Long: `Avalanche P-Chain operations: staking, subnets, transfers, and L1 validators.

Example usage:
platform wallet balance --key-name mykey
platform validator add --node-id NodeID-... --stake 2000
platform validator add-permissionless --node-id NodeID-... --stake 2000
platform transfer p-to-c --amount 10 --key-name mykey
platform subnet create --network fuji --key-name mykey

Expand Down Expand Up @@ -81,6 +82,16 @@ func init() {
})
}

// requireSubcommand is the RunE for command groups: it prints help when the
// group is invoked bare, and rejects any unknown subcommand with an error so
// removed command names fail loudly instead of silently printing help.
func requireSubcommand(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
return fmt.Errorf("unknown command %q for %q", args[0], cmd.CommandPath())
}
return cmd.Help()
}

// avaxToNAVAX converts AVAX amount to nAVAX with validation.
// Returns error if amount is negative or would overflow.
func avaxToNAVAX(avax float64) (uint64, error) {
Expand Down
95 changes: 91 additions & 4 deletions cmd/subnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,23 @@ var (
subnetValBalance float64
subnetMockVal bool
subnetValidatorWeights string

subnetValNodeID string
subnetValWeight uint64
subnetValStartTime string
subnetValDuration string
)

var subnetCmd = &cobra.Command{
Use: "subnet",
Short: "Subnet management",
Long: `Create and manage subnets on the Avalanche P-Chain.`,
RunE: requireSubcommand,
}

var subnetCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a new subnet",
Short: "Create a new subnet (CreateSubnetTx)",
Long: `Create a new subnet on the P-Chain.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := getOperationContext()
Expand Down Expand Up @@ -67,7 +73,7 @@ var subnetCreateCmd = &cobra.Command{

var subnetTransferOwnershipCmd = &cobra.Command{
Use: "transfer-ownership",
Short: "Transfer subnet ownership",
Short: "Transfer subnet ownership (TransferSubnetOwnershipTx)",
Long: `Transfer ownership of a subnet to a new address.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := getOperationContext()
Expand Down Expand Up @@ -112,8 +118,8 @@ var subnetTransferOwnershipCmd = &cobra.Command{
}

var subnetConvertL1Cmd = &cobra.Command{
Use: "convert-l1",
Short: "Convert subnet to L1",
Use: "convert-to-l1",
Short: "Convert subnet to L1 (ConvertSubnetToL1Tx)",
Long: `Convert a permissioned subnet to an L1 blockchain.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := getOperationContext()
Expand Down Expand Up @@ -237,12 +243,86 @@ var subnetConvertL1Cmd = &cobra.Command{
},
}

var subnetAddValidatorCmd = &cobra.Command{
Use: "add-validator",
Short: "Add a validator to a permissioned subnet (AddSubnetValidatorTx)",
Long: `Add a validator to a permissioned subnet (AddSubnetValidatorTx).

The node must already be a primary network validator, and the validation period
must fall within its primary network validation window. The subnet owner key
authorizes the transaction, so load the owner key via --key-name or --ledger.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := getOperationContext()
defer cancel()

if subnetID == "" {
return fmt.Errorf("--subnet-id is required")
}
if subnetValNodeID == "" {
return fmt.Errorf("--node-id is required")
}
if subnetValWeight == 0 {
return fmt.Errorf("--weight is required and must be positive")
}

sid, err := ids.FromString(subnetID)
if err != nil {
return fmt.Errorf("invalid subnet ID: %w", err)
}

nodeID, err := ids.NodeIDFromString(subnetValNodeID)
if err != nil {
return fmt.Errorf("invalid node ID: %w", err)
}

start, end, err := parseTimeRange(subnetValStartTime, subnetValDuration)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question on --start: avalanchego is pinned at v1.14.1 (post-Durango), where I believe the P-chain ignores the tx Start for subnet/permissionless validators — they begin at acceptance. If that is right, parseTimeRange returning now+30s and feeding it into the tx has no on-chain effect, so --start is effectively a no-op and might deserve a note in the flag help. Not specific to this PR (the existing add-permissionless commands do the same) — just flagging it since this new flag advertises a start time.

if err != nil {
return err
}

netConfig, err := getNetworkConfig(ctx)
if err != nil {
return fmt.Errorf("failed to get network config: %w", err)
}
if end.Sub(start) < netConfig.MinStakeDuration {
return fmt.Errorf("duration too short for %s: minimum is %s", netConfig.Name, netConfig.MinStakeDuration)
}

w, cleanup, err := loadPChainWalletWithSubnet(ctx, netConfig, sid)
if err != nil {
return fmt.Errorf("failed to create wallet: %w", err)
}
defer cleanup()

fmt.Printf("Adding validator %s to subnet %s...\n", nodeID, sid)
fmt.Printf(" Weight: %d\n", subnetValWeight)
fmt.Printf(" Start: %s\n", start.UTC().Format("2006-01-02 15:04:05 MST"))
fmt.Printf(" End: %s\n", end.UTC().Format("2006-01-02 15:04:05 MST"))
fmt.Println("Submitting transaction...")

txID, err := pchain.AddSubnetValidator(ctx, w, pchain.AddSubnetValidatorConfig{
SubnetID: sid,
NodeID: nodeID,
Start: start,
End: end,
Weight: subnetValWeight,
})
if err != nil {
return err
}

fmt.Printf("TX ID: %s\n", txID)
return nil
},
}

func init() {
rootCmd.AddCommand(subnetCmd)

subnetCmd.AddCommand(subnetCreateCmd)
subnetCmd.AddCommand(subnetTransferOwnershipCmd)
subnetCmd.AddCommand(subnetConvertL1Cmd)
subnetCmd.AddCommand(subnetAddValidatorCmd)

// Transfer ownership flags
subnetTransferOwnershipCmd.Flags().StringVar(&subnetID, "subnet-id", "", "Subnet ID")
Expand All @@ -260,4 +340,11 @@ func init() {
subnetConvertL1Cmd.Flags().Float64Var(&subnetValBalance, "validator-balance", 1.0, "Balance per validator in AVAX")
subnetConvertL1Cmd.Flags().StringVar(&subnetValidatorWeights, "validator-weights", "", "Comma-separated validator weights (uint64). Must match validator count. Defaults to 100 per validator if omitted.")
subnetConvertL1Cmd.Flags().BoolVar(&subnetMockVal, "mock-validator", false, "Use a mock validator (for testing)")

// Add validator flags
subnetAddValidatorCmd.Flags().StringVar(&subnetID, "subnet-id", "", "Subnet ID")
subnetAddValidatorCmd.Flags().StringVar(&subnetValNodeID, "node-id", "", "Validator node ID (must already validate the primary network)")
subnetAddValidatorCmd.Flags().Uint64Var(&subnetValWeight, "weight", 0, "Validator sampling weight on the subnet")
subnetAddValidatorCmd.Flags().StringVar(&subnetValStartTime, "start", "now", "Start time (RFC3339 or 'now'). Post-Durango networks ignore this; validation begins at tx acceptance")
subnetAddValidatorCmd.Flags().StringVar(&subnetValDuration, "duration", "336h", "Validation duration (must fall within the node's primary network validation period)")
}
1 change: 1 addition & 0 deletions cmd/transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Amount Precision:

Warning: Float amounts may lose precision for values > 9007199254740992 nAVAX (~9M AVAX).
For large transfers, use --amount-navax for guaranteed precision.`,
RunE: requireSubcommand,
}

// getTransferAmountNAVAX returns the transfer amount in nAVAX.
Expand Down
15 changes: 8 additions & 7 deletions cmd/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ var validatorCmd = &cobra.Command{
Use: "validator",
Short: "Primary network staking",
Long: `Add validators and delegators to the Avalanche primary network.`,
RunE: requireSubcommand,
}

var validatorAddCmd = &cobra.Command{
Use: "add",
Short: "Add a primary network validator",
Long: `Add a validator to the Avalanche primary network.`,
Use: "add-permissionless",
Short: "Add a primary network validator (AddPermissionlessValidatorTx)",
Long: `Add a permissionless validator to the Avalanche primary network.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := getOperationContext()
defer cancel()
Expand Down Expand Up @@ -126,8 +127,8 @@ var validatorAddCmd = &cobra.Command{
}

var validatorDelegateCmd = &cobra.Command{
Use: "delegate",
Short: "Delegate to a primary network validator",
Use: "add-permissionless-delegator",
Short: "Delegate to a primary network validator (AddPermissionlessDelegatorTx)",
Long: `Delegate stake to an existing primary network validator.`,
RunE: func(cmd *cobra.Command, args []string) error {
ctx, cancel := getOperationContext()
Expand Down Expand Up @@ -280,15 +281,15 @@ func init() {
validatorAddCmd.Flags().StringVar(&valBLSPublicKey, "bls-public-key", "", "Validator BLS public key (hex, recommended/manual mode)")
validatorAddCmd.Flags().StringVar(&valBLSPoP, "bls-pop", "", "Validator BLS proof of possession signature (hex, recommended/manual mode)")
validatorAddCmd.Flags().Float64Var(&valStakeAmount, "stake", 0, "Stake amount in AVAX (min 2000)")
validatorAddCmd.Flags().StringVar(&valStartTime, "start", "now", "Start time (RFC3339 or 'now')")
validatorAddCmd.Flags().StringVar(&valStartTime, "start", "now", "Start time (RFC3339 or 'now'). Post-Durango networks ignore this; validation begins at tx acceptance")
validatorAddCmd.Flags().StringVar(&valDuration, "duration", "336h", "Validation duration (min 14 days)")
validatorAddCmd.Flags().Float64Var(&valDelegationFee, "delegation-fee", 0.02, "Delegation fee (0.02 = 2%)")
validatorAddCmd.Flags().StringVar(&valRewardAddr, "reward-address", "", "Reward address (default: own address)")

// Delegate flags
validatorDelegateCmd.Flags().StringVar(&valNodeID, "node-id", "", "Node ID to delegate to")
validatorDelegateCmd.Flags().Float64Var(&valStakeAmount, "stake", 0, "Stake amount in AVAX (min 25)")
validatorDelegateCmd.Flags().StringVar(&valStartTime, "start", "now", "Start time (RFC3339 or 'now')")
validatorDelegateCmd.Flags().StringVar(&valStartTime, "start", "now", "Start time (RFC3339 or 'now'). Post-Durango networks ignore this; validation begins at tx acceptance")
validatorDelegateCmd.Flags().StringVar(&valDuration, "duration", "336h", "Delegation duration (min 14 days)")
validatorDelegateCmd.Flags().StringVar(&valRewardAddr, "reward-address", "", "Reward address (default: own address)")
}
1 change: 1 addition & 0 deletions cmd/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ var walletCmd = &cobra.Command{
Use: "wallet",
Short: "Wallet operations",
Long: `Wallet operations including balance check and address display.`,
RunE: requireSubcommand,
}

var balanceCmd = &cobra.Command{
Expand Down
35 changes: 20 additions & 15 deletions docs/pchain-operations.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
# P-Chain Operations Reference

| Operation | Command | SDK Method |
|-----------|---------|------------|
| Send AVAX | `transfer send` | `IssueBaseTx` |
| Export | `transfer export` | `IssueExportTx` |
| Import | `transfer import` | `IssueImportTx` |
| Add Validator | `validator add` | `IssueAddPermissionlessValidatorTx` |
| Add Delegator | `validator delegate` | `IssueAddPermissionlessDelegatorTx` |
| Create Subnet | `subnet create` | `IssueCreateSubnetTx` |
| Transfer Subnet Ownership | `subnet transfer-ownership` | `IssueTransferSubnetOwnershipTx` |
| Convert to L1 | `subnet convert-l1` | `IssueConvertSubnetToL1Tx` |
| Register L1 Validator | `l1 register-validator` | `IssueRegisterL1ValidatorTx` |
| Set L1 Validator Weight | `l1 set-weight` | `IssueSetL1ValidatorWeightTx` |
| Increase L1 Balance | `l1 add-balance` | `IssueIncreaseL1ValidatorBalanceTx` |
| Disable L1 Validator | `l1 disable-validator` | `IssueDisableL1ValidatorTx` |
| Create Chain | `chain create` | `IssueCreateChainTx` |
Command names mirror the avalanchego transaction type each one issues. The
"Previous name" column lists names that were **removed in v2.0.0** (no aliases) —
use it to migrate existing scripts.

| Tx Type | Command | SDK Method | Previous name (removed in v2.0.0) |
|---------|---------|------------|-----------------------------------|
| `BaseTx` | `transfer send` | `IssueBaseTx` | — |
| `ExportTx` | `transfer export` | `IssueExportTx` | — |
| `ImportTx` | `transfer import` | `IssueImportTx` | — |
| `AddPermissionlessValidatorTx` | `validator add-permissionless` | `IssueAddPermissionlessValidatorTx` | `validator add` |
| `AddPermissionlessDelegatorTx` | `validator add-permissionless-delegator` | `IssueAddPermissionlessDelegatorTx` | `validator delegate` |
| `CreateSubnetTx` | `subnet create` | `IssueCreateSubnetTx` | — |
| `TransferSubnetOwnershipTx` | `subnet transfer-ownership` | `IssueTransferSubnetOwnershipTx` | — |
| `ConvertSubnetToL1Tx` | `subnet convert-to-l1` | `IssueConvertSubnetToL1Tx` | `subnet convert-l1` |
| `AddSubnetValidatorTx` | `subnet add-validator` | `IssueAddSubnetValidatorTx` | — |
| `RegisterL1ValidatorTx` | `l1 register-validator` | `IssueRegisterL1ValidatorTx` | — |
| `SetL1ValidatorWeightTx` | `l1 set-validator-weight` | `IssueSetL1ValidatorWeightTx` | `l1 set-weight` |
| `IncreaseL1ValidatorBalanceTx` | `l1 increase-validator-balance` | `IssueIncreaseL1ValidatorBalanceTx` | `l1 add-balance` |
| `DisableL1ValidatorTx` | `l1 disable-validator` | `IssueDisableL1ValidatorTx` | — |
| `CreateChainTx` | `chain create` | `IssueCreateChainTx` | — |
Loading
Loading