Skip to content
26 changes: 22 additions & 4 deletions blockchain/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"fmt"
"math/big"
"os"
"time"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/logging"
Expand All @@ -20,6 +21,7 @@ import (
"github.com/ava-labs/subnet-evm/commontype"
"github.com/ava-labs/subnet-evm/params"
"github.com/ava-labs/subnet-evm/params/extras"
"github.com/ava-labs/subnet-evm/utils"
"go.uber.org/zap"

"github.com/ava-labs/avalanche-tooling-sdk-go/evm"
Expand Down Expand Up @@ -214,14 +216,14 @@ func New(subnetParams *SubnetParams) (*Subnet, error) {
case subnetParams.GenesisFilePath != "":
genesisBytes, err = os.ReadFile(subnetParams.GenesisFilePath)
case subnetParams.SubnetEVM != nil:
genesisBytes, err = createEvmGenesis(subnetParams.SubnetEVM)
genesisBytes, err = CreateEvmGenesis(subnetParams.SubnetEVM)
default:
}
if err != nil {
return nil, err
}

vmID, err := vmID(subnetParams.Name)
vmID, err := VMID(subnetParams.Name)
if err != nil {
return nil, fmt.Errorf("failed to create VM ID from %s: %w", subnetParams.Name, err)
}
Expand All @@ -237,7 +239,7 @@ func (c *Subnet) SetSubnetID(subnetID ids.ID) {
c.SubnetID = subnetID
}

func createEvmGenesis(
func CreateEvmGenesis(
subnetEVMParams *SubnetEVMParams,
) ([]byte, error) {
genesis := core.Genesis{}
Expand Down Expand Up @@ -291,7 +293,23 @@ func createEvmGenesis(
return prettyJSON.Bytes(), nil
}

func vmID(vmName string) (ids.ID, error) {
func GetDefaultSubnetEVMGenesis(initialAllocationAddress string) SubnetEVMParams {
genesisBlock0Timestamp := utils.TimeToNewUint64(time.Now())
allocation := core.GenesisAlloc{}
defaultAmount, _ := new(big.Int).SetString(vm.DefaultEvmAirdropAmount, 10)
allocation[common.HexToAddress(initialAllocationAddress)] = core.GenesisAccount{
Balance: defaultAmount,
}
return SubnetEVMParams{
ChainID: big.NewInt(123456),
FeeConfig: vm.StarterFeeConfig,
Allocation: allocation,
Precompiles: extras.Precompiles{},
Timestamp: genesisBlock0Timestamp,
}
}

func VMID(vmName string) (ids.ID, error) {
if len(vmName) > 32 {
return ids.Empty, fmt.Errorf("VM name must be <= 32 bytes, found %d", len(vmName))
}
Expand Down
5 changes: 5 additions & 0 deletions constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ const (
UserOnlyWriteReadExecPerms = 0o700
WriteReadUserOnlyPerms = 0o600
)

// Chain type constants
const (
ChainTypePChain = "P-Chain"
)
108 changes: 108 additions & 0 deletions example/create_chain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//go:build create_chain
// +build create_chain

// Copyright (C) 2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package main

import (
"fmt"
"os"
"time"

"github.com/ava-labs/avalanche-tooling-sdk-go/blockchain"
"github.com/ava-labs/avalanche-tooling-sdk-go/network"
"github.com/ava-labs/avalanche-tooling-sdk-go/utils"
"github.com/ava-labs/avalanche-tooling-sdk-go/wallet/local"
"github.com/ava-labs/avalanche-tooling-sdk-go/wallet/types"

pchainTxs "github.com/ava-labs/avalanche-tooling-sdk-go/wallet/txs/p-chain"
avagoTxs "github.com/ava-labs/avalanchego/vms/platformvm/txs"
)

func CreateChain(subnetID string) error {
ctx, cancel := utils.GetTimedContext(120 * time.Second)
defer cancel()
network := network.FujiNetwork()

localWallet, err := local.NewLocalWallet()
if err != nil {
return fmt.Errorf("failed to create wallet: %w", err)
}

existingAccount, err := localWallet.ImportAccount("EXISTING_KEY_PATH")
if err != nil {
return fmt.Errorf("failed to ImportAccount: %w", err)
}

evmGenesisParams := blockchain.GetDefaultSubnetEVMGenesis("EVM_ADDRESS")
evmGenesisBytes, _ := blockchain.CreateEvmGenesis(&evmGenesisParams)
blockchainName := "TestBlockchain"
vmID, err := blockchain.VMID(blockchainName)
if err != nil {
return fmt.Errorf("failed to get vmid: %w", err)
}

createChainParams := &pchainTxs.CreateChainTxParams{
SubnetAuthKeys: []string{"P-fujixxxxx"},
SubnetID: subnetID,
VMID: vmID.String(),
ChainName: blockchainName,
Genesis: evmGenesisBytes,
}
buildTxParams := types.BuildTxParams{
BaseParams: types.BaseParams{
Account: *existingAccount,
Network: network,
},
BuildTxInput: createChainParams,
}
buildTxResult, err := localWallet.BuildTx(ctx, buildTxParams)
if err != nil {
return fmt.Errorf("failed to BuildTx: %w", err)
}

signTxParams := types.SignTxParams{
BaseParams: types.BaseParams{
Account: *existingAccount,
Network: network,
},
BuildTxResult: &buildTxResult,
}

signTxResult, err := localWallet.SignTx(ctx, signTxParams)
if err != nil {
return fmt.Errorf("failed to signTx: %w", err)
}

sendTxParams := types.SendTxParams{
BaseParams: types.BaseParams{
Account: *existingAccount,
Network: network,
},
SignTxResult: &signTxResult,
}
sendTxResult, err := localWallet.SendTx(ctx, sendTxParams)
if err != nil {
return fmt.Errorf("failed to sendTx: %w", err)
}

if tx := sendTxResult.GetTx(); tx != nil {
if pChainTx, ok := tx.(*avagoTxs.Tx); ok {
fmt.Printf("sendTxResult %s \n", pChainTx.ID())
} else {
fmt.Printf("sendTxResult %s transaction \n", sendTxResult.GetChainType())
}
}
return nil
}

func main() {
// Use a hardcoded subnet ID for this example
// In a real scenario, you would get this from creating a subnet first
subnetID := "SUBNET_ID"
if err := CreateChain(subnetID); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
26 changes: 15 additions & 11 deletions example/wallet_example.go → example/create_subnet.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
//go:build create_subnet
// +build create_subnet

// Copyright (C) 2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package main
Expand All @@ -7,6 +10,8 @@ import (
"os"
"time"

"github.com/ava-labs/avalanchego/ids"

"github.com/ava-labs/avalanche-tooling-sdk-go/network"
"github.com/ava-labs/avalanche-tooling-sdk-go/utils"
"github.com/ava-labs/avalanche-tooling-sdk-go/wallet/local"
Expand All @@ -16,23 +21,23 @@ import (
avagoTxs "github.com/ava-labs/avalanchego/vms/platformvm/txs"
)

func CreateSubnet() error {
func CreateSubnet() (ids.ID, error) {
ctx, cancel := utils.GetTimedContext(120 * time.Second)
defer cancel()
network := network.FujiNetwork()

localWallet, err := local.NewLocalWallet()
if err != nil {
return fmt.Errorf("failed to create wallet: %w", err)
return ids.Empty, fmt.Errorf("failed to create wallet: %w", err)
}

existingAccount, err := localWallet.ImportAccount("EXISTING_KEY_PATH")
if err != nil {
return fmt.Errorf("failed to ImportAccount: %w", err)
return ids.Empty, fmt.Errorf("failed to ImportAccount: %w", err)
}

createSubnetParams := &pchainTxs.CreateSubnetTxParams{
ControlKeys: []string{"P-fuji1377nx80rx3pzneup5qywgdgdsmzntql7trcqlg"},
ControlKeys: []string{"P-fujixxxxx"},
Threshold: 1,
}
buildTxParams := types.BuildTxParams{
Expand All @@ -42,7 +47,7 @@ func CreateSubnet() error {
}
buildTxResult, err := localWallet.BuildTx(ctx, buildTxParams)
if err != nil {
return fmt.Errorf("failed to BuildTx: %w", err)
return ids.Empty, fmt.Errorf("failed to BuildTx: %w", err)
}

signTxParams := types.SignTxParams{
Expand All @@ -52,7 +57,7 @@ func CreateSubnet() error {
}
signTxResult, err := localWallet.SignTx(ctx, signTxParams)
if err != nil {
return fmt.Errorf("failed to signTx: %w", err)
return ids.Empty, fmt.Errorf("failed to signTx: %w", err)
}

sendTxParams := types.SendTxParams{
Expand All @@ -62,20 +67,19 @@ func CreateSubnet() error {
}
sendTxResult, err := localWallet.SendTx(ctx, sendTxParams)
if err != nil {
return fmt.Errorf("failed to sendTx: %w", err)
return ids.Empty, fmt.Errorf("failed to sendTx: %w", err)
}
if tx := sendTxResult.GetTx(); tx != nil {
if pChainTx, ok := tx.(*avagoTxs.Tx); ok {
fmt.Printf("sendTxResult %s \n", pChainTx.ID())
} else {
fmt.Printf("sendTxResult %s transaction \n", sendTxResult.GetChainType())
return pChainTx.ID(), nil
}
}
return nil
return ids.Empty, fmt.Errorf("unable to get tx id")
}

func main() {
if err := CreateSubnet(); err != nil {
if _, err := CreateSubnet(); err != nil {
fmt.Println(err)
os.Exit(1)
}
Expand Down
92 changes: 74 additions & 18 deletions wallet/chains/pchain/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,31 +24,15 @@ func BuildTx(wallet *primary.Wallet, account account.Account, params types.Build
switch txType := params.BuildTxInput.(type) {
case *pchainTxs.CreateSubnetTxParams:
return buildCreateSubnetTx(wallet, txType)
case *pchainTxs.CreateChainTxParams:
return buildCreateChainTx(wallet, account, txType)
case *pchainTxs.ConvertSubnetToL1TxParams:
return buildConvertSubnetToL1Tx(wallet, account, txType)
default:
return types.BuildTxResult{}, fmt.Errorf("unsupported P-Chain transaction type: %T", params.BuildTxInput)
}
}

// buildConvertSubnetToL1Tx provides a default implementation that can be used by any wallet
func buildConvertSubnetToL1Tx(wallet *primary.Wallet, account account.Account, params *pchainTxs.ConvertSubnetToL1TxParams) (types.BuildTxResult, error) {
options := getMultisigTxOptions(account, params.SubnetAuthKeys)
unsignedTx, err := wallet.P().Builder().NewConvertSubnetToL1Tx(
params.SubnetID,
params.ChainID,
params.Address,
params.Validators,
options...,
)
if err != nil {
return types.BuildTxResult{}, fmt.Errorf("error building tx: %w", err)
}
builtTx := avagoTxs.Tx{Unsigned: unsignedTx}
pChainResult := types.NewPChainBuildTxResult(&builtTx)
return types.BuildTxResult{BuildTxOutput: pChainResult}, nil
}

// buildCreateSubnetTx provides a default implementation that can be used by any wallet
func buildCreateSubnetTx(wallet *primary.Wallet, params *pchainTxs.CreateSubnetTxParams) (types.BuildTxResult, error) {
addrs, err := address.ParseToIDs(params.ControlKeys)
Expand All @@ -71,6 +55,69 @@ func buildCreateSubnetTx(wallet *primary.Wallet, params *pchainTxs.CreateSubnetT
return types.BuildTxResult{BuildTxOutput: pChainResult}, nil
}

// buildCreateChainTx provides a default implementation that can be used by any wallet
func buildCreateChainTx(wallet *primary.Wallet, account account.Account, params *pchainTxs.CreateChainTxParams) (types.BuildTxResult, error) {
subnetAuthKeys, err := convertSubnetAuthKeys(params.SubnetAuthKeys)
if err != nil {
return types.BuildTxResult{}, fmt.Errorf("failed to convert subnet auth keys: %w", err)
}
options := getMultisigTxOptions(account, subnetAuthKeys)
fxIDs := make([]ids.ID, 0)
subnetID, err := ids.FromString(params.SubnetID)
if err != nil {
return types.BuildTxResult{}, fmt.Errorf("failed to parse subnet ID: %w", err)
}
vmID, err := ids.FromString(params.VMID)
if err != nil {
return types.BuildTxResult{}, fmt.Errorf("failed to parse VM ID: %w", err)
}
unsignedTx, err := wallet.P().Builder().NewCreateChainTx(
subnetID,
params.Genesis,
vmID,
fxIDs,
params.ChainName,
options...,
)
if err != nil {
return types.BuildTxResult{}, fmt.Errorf("error building tx: %w", err)
}
builtTx := avagoTxs.Tx{Unsigned: unsignedTx}
pChainResult := types.NewPChainBuildTxResult(&builtTx)
return types.BuildTxResult{BuildTxOutput: pChainResult}, nil
}

// buildConvertSubnetToL1Tx provides a default implementation that can be used by any wallet
func buildConvertSubnetToL1Tx(wallet *primary.Wallet, account account.Account, params *pchainTxs.ConvertSubnetToL1TxParams) (types.BuildTxResult, error) {
subnetAuthKeys, err := convertSubnetAuthKeys(params.SubnetAuthKeys)
if err != nil {
return types.BuildTxResult{}, fmt.Errorf("failed to convert subnet auth keys: %w", err)
}
subnetID, err := ids.FromString(params.SubnetID)
if err != nil {
return types.BuildTxResult{}, fmt.Errorf("failed to parse subnet ID: %w", err)
}
chainID, err := ids.FromString(params.ChainID)
if err != nil {
return types.BuildTxResult{}, fmt.Errorf("failed to parse chain ID: %w", err)
}
addressBytes := []byte(params.Address)
options := getMultisigTxOptions(account, subnetAuthKeys)
unsignedTx, err := wallet.P().Builder().NewConvertSubnetToL1Tx(
subnetID,
chainID,
addressBytes,
params.Validators,
options...,
)
if err != nil {
return types.BuildTxResult{}, fmt.Errorf("error building tx: %w", err)
}
builtTx := avagoTxs.Tx{Unsigned: unsignedTx}
pChainResult := types.NewPChainBuildTxResult(&builtTx)
return types.BuildTxResult{BuildTxOutput: pChainResult}, nil
}

// getMultisigTxOptions is a helper function that can be shared
func getMultisigTxOptions(account account.Account, subnetAuthKeys []ids.ShortID) []common.Option {
options := []common.Option{}
Expand All @@ -94,3 +141,12 @@ func getMultisigTxOptions(account account.Account, subnetAuthKeys []ids.ShortID)
options = append(options, common.WithChangeOwner(changeOwner))
return options
}

// convertSubnetAuthKeys converts a slice of string addresses to a slice of ShortIDs
func convertSubnetAuthKeys(subnetAuthKeys []string) ([]ids.ShortID, error) {
subnetAuthKeyIDs, err := address.ParseToIDs(subnetAuthKeys)
if err != nil {
return nil, fmt.Errorf("failed to convert subnet auth key %s to ShortID: %w", subnetAuthKeys, err)
}
return subnetAuthKeyIDs, nil
}
Loading
Loading