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
24 changes: 24 additions & 0 deletions blockchain/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
package blockchain

import (
"encoding/json"

"github.com/ava-labs/avalanchego/vms/platformvm"
"github.com/ava-labs/avalanchego/vms/platformvm/signer"

"github.com/ava-labs/avalanche-tooling-sdk-go/network"
"github.com/ava-labs/avalanche-tooling-sdk-go/utils"
Expand All @@ -19,3 +22,24 @@ func GetSubnet(subnetID ids.ID, network network.Network) (platformvm.GetSubnetCl
defer cancel()
return pClient.GetSubnet(ctx, subnetID)
}

func ConvertToBLSProofOfPossession(publicKey, proofOfPossesion string) (signer.ProofOfPossession, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Probably the name should be NewSignerProofOfPossession.
Besides this, I don't think it belongs to blockchain/. It most probably belongs to utils/, or pchain/utils/

type jsonProofOfPossession struct {
PublicKey string
ProofOfPossession string
}
jsonPop := jsonProofOfPossession{
PublicKey: publicKey,
ProofOfPossession: proofOfPossesion,
}
popBytes, err := json.Marshal(jsonPop)
if err != nil {
return signer.ProofOfPossession{}, err
}
pop := &signer.ProofOfPossession{}
err = pop.UnmarshalJSON(popBytes)
if err != nil {
return signer.ProofOfPossession{}, err
}
return *pop, nil
}
11 changes: 11 additions & 0 deletions multisig/multisig.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const (
PChainTransformSubnetTx
PChainAddPermissionlessValidatorTx
PChainTransferSubnetOwnershipTx
PChainConvertSubnetToL1Tx
)

type Multisig struct {
Expand Down Expand Up @@ -176,6 +177,8 @@ func (ms *Multisig) GetAuthSigners() ([]ids.ShortID, error) {
subnetAuth = unsignedTx.SubnetAuth
case *txs.TransferSubnetOwnershipTx:
subnetAuth = unsignedTx.SubnetAuth
case *txs.ConvertSubnetToL1Tx:
subnetAuth = unsignedTx.SubnetAuth
default:
return nil, fmt.Errorf("unexpected unsigned tx type %T", unsignedTx)
}
Expand Down Expand Up @@ -215,6 +218,8 @@ func (ms *Multisig) GetTxKind() (TxKind, error) {
return PChainAddPermissionlessValidatorTx, nil
case *txs.TransferSubnetOwnershipTx:
return PChainTransferSubnetOwnershipTx, nil
case *txs.ConvertSubnetToL1Tx:
return PChainConvertSubnetToL1Tx, nil
default:
return Undefined, fmt.Errorf("unexpected unsigned tx type %T", unsignedTx)
}
Expand All @@ -240,6 +245,8 @@ func (ms *Multisig) GetNetworkID() (uint32, error) {
networkID = unsignedTx.NetworkID
case *txs.TransferSubnetOwnershipTx:
networkID = unsignedTx.NetworkID
case *txs.ConvertSubnetToL1Tx:
networkID = unsignedTx.NetworkID
default:
return 0, fmt.Errorf("unexpected unsigned tx type %T", unsignedTx)
}
Expand Down Expand Up @@ -281,6 +288,8 @@ func (ms *Multisig) GetBlockchainID() (ids.ID, error) {
blockchainID = unsignedTx.BlockchainID
case *txs.TransferSubnetOwnershipTx:
blockchainID = unsignedTx.BlockchainID
case *txs.ConvertSubnetToL1Tx:
blockchainID = unsignedTx.BlockchainID
default:
return ids.Empty, fmt.Errorf("unexpected unsigned tx type %T", unsignedTx)
}
Expand All @@ -307,6 +316,8 @@ func (ms *Multisig) GetSubnetID() (ids.ID, error) {
subnetID = unsignedTx.Subnet
case *txs.TransferSubnetOwnershipTx:
subnetID = unsignedTx.Subnet
case *txs.ConvertSubnetToL1Tx:
subnetID = unsignedTx.Subnet
default:
return ids.Empty, fmt.Errorf("unexpected unsigned tx type %T", unsignedTx)
}
Expand Down
148 changes: 148 additions & 0 deletions transactions/examples/convertSubnetToL1Tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// Copyright (C) 2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package main

import (
"context"
"fmt"
"os"

"github.com/ava-labs/avalanche-tooling-sdk-go/blockchain"
"github.com/ava-labs/avalanche-tooling-sdk-go/keychain"
"github.com/ava-labs/avalanche-tooling-sdk-go/network"
"github.com/ava-labs/avalanche-tooling-sdk-go/transactions/p-chain/txs"
"github.com/ava-labs/avalanche-tooling-sdk-go/wallet"
"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/formatting/address"
avagoTxs "github.com/ava-labs/avalanchego/vms/platformvm/txs"
"github.com/ava-labs/avalanchego/vms/platformvm/warp/message"
"github.com/ava-labs/avalanchego/wallet/subnet/primary"
"github.com/ethereum/go-ethereum/common"
)

func ConvertL1() error {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Collaborator

Choose a reason for hiding this comment

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

this way you will check what is being used and the separate parts: chain, account, wallet, functions

Copy link
Collaborator

Choose a reason for hiding this comment

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

you can also try to refer to my example with is pretty much based on https://github.com/javiertc/avalanche-sdk-test/blob/main/createSubnetAndChain.ts

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

will refactor to be only usign wallet, need to create PR for that

// Configuration - Replace these with your actual values
const (
// Your private key file path
privateKeyFilePath = ""

// Subnet and Chain IDs
subnetIDStr = ""
chainIDStr = ""

// Validator information
nodeIDStr = ""
BLSPublicKey = "0x..." // Replace with actual BLS public key
BLSProofOfPossession = "0x..." // Replace with actual BLS proof
ChangeOwnerAddr = "P-fujixxx" // Address to receive remaining balance
Weight = 100 // Validator weight
Balance = 1000000000 // Validator balance in nAVAX

// Validator manager contract address
validatorManagerAddr = "0x0FEEDC0DE0000000000000000000000000000000" // Replace with actual contract address
)

// Subnet auth keys (addresses that can sign the conversion tx)
subnetAuthKeysStrs := []string{
"P-fujixxx", // Replace with actual addresses
}

network := network.FujiNetwork()
Copy link
Collaborator

Choose a reason for hiding this comment

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

can you take the network endpoint from env var as in my example

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

why do we want to use env var as opposed to just declaring it in example?

keychain, err := keychain.NewKeychain(network, privateKeyFilePath, nil)
Copy link
Collaborator

Choose a reason for hiding this comment

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

lets create keychain from string private key in env var

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

same here switching to wallet

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

// Parse IDs
subnetID, err := ids.FromString(subnetIDStr)
if err != nil {
return fmt.Errorf("failed to parse subnet ID: %w", err)
}

chainID, err := ids.FromString(chainIDStr)
if err != nil {
return fmt.Errorf("failed to parse chain ID: %w", err)
}

deployer := txs.NewPublicDeployer(keychain, network)

wallet, err := wallet.New(
Copy link
Collaborator

Choose a reason for hiding this comment

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

probably my new wallet interface is better as it does directly receives SDK objects

context.Background(),
network.Endpoint,
keychain.Keychain,
primary.WalletConfig{
SubnetIDs: []ids.ID{subnetID},
},
)
if err != nil {
return fmt.Errorf("failed to create wallet: %w", err)
}

subnetAuthKeys, err := address.ParseToIDs(subnetAuthKeysStrs)
if err != nil {
return fmt.Errorf("failure parsing auth keys: %w", err)
}

bootstrapValidators := []*avagoTxs.ConvertSubnetToL1Validator{}
nodeID, err := ids.NodeIDFromString(nodeIDStr)
if err != nil {
return fmt.Errorf("failed to parse node ID: %w", err)
}

blsInfo, err := blockchain.ConvertToBLSProofOfPossession(BLSPublicKey, BLSProofOfPossession)
if err != nil {
return fmt.Errorf("failure parsing BLS info: %w", err)
}

addrs, err := address.ParseToIDs([]string{ChangeOwnerAddr})
if err != nil {
return fmt.Errorf("failure parsing change owner address: %w", err)
}

bootstrapValidator := &avagoTxs.ConvertSubnetToL1Validator{
NodeID: nodeID[:],
Weight: Weight,
Balance: Balance,
Signer: blsInfo,
RemainingBalanceOwner: message.PChainOwner{
Threshold: 1,
Addresses: addrs,
},
}
bootstrapValidators = append(bootstrapValidators, bootstrapValidator)

convertSubnetParams := txs.ConvertSubnetToL1TxParams{
// SubnetAuthKeys are the keys used to sign `ConvertSubnetToL1Tx`
SubnetAuthKeys: subnetAuthKeys,
// SubnetID is Subnet ID of the subnet to convert to an L1.
SubnetID: subnetID,
// ChainID is Blockchain ID of the L1 where the validator manager contract is deployed.
ChainID: chainID,
// Address is address of the validator manager contract.
Address: common.HexToAddress(validatorManagerAddr).Bytes(),
// Validators are the initial set of L1 validators after the conversion.
Validators: bootstrapValidators,
// Wallet is the wallet used to sign `ConvertSubnetToL1Tx`
Wallet: &wallet, // Use the wallet wrapper
}

tx, err := deployer.NewConvertSubnetToL1Tx(convertSubnetParams)
if err != nil {
return fmt.Errorf("failed to create convert subnet tx: %w", err)
}

// Since it has the required signatures, we will now commit the transaction on chain
txID, err := deployer.Commit(*tx, wallet, true)
if err != nil {
return fmt.Errorf("failed to commit transaction: %w", err)
}
fmt.Printf("Convert subnet to L1 transaction submitted successfully! TX ID: %s\n", txID.String())
return nil
}

func main() {
if err := ConvertL1(); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

nice

fmt.Println(err)
os.Exit(1)
}
}
49 changes: 49 additions & 0 deletions transactions/p-chain/txs/baseTx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (C) 2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs

Copy link
Collaborator

Choose a reason for hiding this comment

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

why is this called baseTx.go?

import (
"errors"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/set"
"github.com/ava-labs/avalanchego/vms/secp256k1fx"
"github.com/ava-labs/avalanchego/wallet/subnet/primary/common"

"github.com/ava-labs/avalanche-tooling-sdk-go/keychain"
"github.com/ava-labs/avalanche-tooling-sdk-go/network"
"github.com/ava-labs/avalanchego/wallet/subnet/primary"
)

var ErrNoSubnetAuthKeysInWallet = errors.New("auth wallet does not contain auth keys")

type PublicDeployer struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

lets not have this object at all, make all function to receive the wallet

Copy link
Collaborator

Choose a reason for hiding this comment

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

or just a pchain helper

kc *keychain.Keychain
network network.Network
wallet *primary.Wallet
}

func NewPublicDeployer(kc *keychain.Keychain, network network.Network) *PublicDeployer {
return &PublicDeployer{
kc: kc,
network: network,
}
}

func (d *PublicDeployer) getMultisigTxOptions(subnetAuthKeys []ids.ShortID) []common.Option {
Copy link
Collaborator

Choose a reason for hiding this comment

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

lets move this function as a helper into wallet

options := []common.Option{}
walletAddrs := d.kc.Addresses().List()
changeAddr := walletAddrs[0]
// addrs to use for signing
customAddrsSet := set.Set[ids.ShortID]{}
customAddrsSet.Add(walletAddrs...)
customAddrsSet.Add(subnetAuthKeys...)
options = append(options, common.WithCustomAddresses(customAddrsSet))
// set change to go to wallet addr (instead of any other subnet auth key)
changeOwner := &secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{changeAddr},
}
options = append(options, common.WithChangeOwner(changeOwner))
return options
}
49 changes: 49 additions & 0 deletions transactions/p-chain/txs/convertSubnetToL1Tx.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (C) 2025, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package txs

import (
"context"
"fmt"

"github.com/ava-labs/avalanche-tooling-sdk-go/multisig"
"github.com/ava-labs/avalanche-tooling-sdk-go/wallet"

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/vms/platformvm/txs"
)

// ConvertSubnetToL1TxParams contains all parameters needed to create a ConvertSubnetToL1Tx
Copy link
Collaborator

Choose a reason for hiding this comment

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

ok I agree with this structs to have more compat with TS, will also do in my PR

type ConvertSubnetToL1TxParams struct {
// SubnetAuthKeys are the keys used to sign `ConvertSubnetToL1Tx`
SubnetAuthKeys []ids.ShortID
Copy link
Collaborator

Choose a reason for hiding this comment

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

if you agree, we can incorporate the simplifications of strings instead of ids.ID, etc, so we are more
similar to TS, and maybe it may be easier for users

// SubnetID is Subnet ID of the subnet to convert to an L1.
SubnetID ids.ID
// ChainID is Blockchain ID of the L1 where the validator manager contract is deployed.
ChainID ids.ID
// Address is address of the validator manager contract.
Copy link
Collaborator

Choose a reason for hiding this comment

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

also this as string

Address []byte
// Validators are the initial set of L1 validators after the conversion.
Validators []*txs.ConvertSubnetToL1Validator
Copy link
Collaborator

Choose a reason for hiding this comment

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

this also will need replacement into a new struct that uses strings, etc

// Wallet is the wallet used to sign `ConvertSubnetToL1Tx`
Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe wallet should like outside of this struct, and be a separate argument, as in my PR, so NewConvertSubnetToL1Tx will take the wallet/client, and the params.
With this be have more similarity to TS and I believe it is best organization of the input as the wallet serves
other function, is not part of the tx data

Wallet *wallet.Wallet
}

func (d *PublicDeployer) NewConvertSubnetToL1Tx(params ConvertSubnetToL1TxParams) (*multisig.Multisig, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't believe we want to be this a member method of a PublicDeployer. lets make this function not
belong to any object, as in TS. I do agree with the different function name, but lets try to keep the same
function signature (or mostly similar)

options := d.getMultisigTxOptions(params.SubnetAuthKeys)
Copy link
Collaborator

Choose a reason for hiding this comment

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

lets have this as a helper function of the wallet, but that does not modify the wallet (we already have that in our wallet but I believe it is best to not modify it)

unsignedTx, err := params.Wallet.P().Builder().NewConvertSubnetToL1Tx(
params.SubnetID,
params.ChainID,
params.Address,
params.Validators,
options...,
)
if err != nil {
return nil, fmt.Errorf("error building tx: %w", err)
}
tx := txs.Tx{Unsigned: unsignedTx}
if err := params.Wallet.P().Signer().Sign(context.Background(), &tx); err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

lets avoid signing here, just return unsigned multisig

return nil, fmt.Errorf("error signing tx: %w", err)
}
return multisig.New(&tx), nil
}
Loading
Loading