github.com/MetalBlockchain/metalgo@v1.11.9/wallet/chain/p/builder/builder.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package builder
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"time"
    11  
    12  	"github.com/MetalBlockchain/metalgo/ids"
    13  	"github.com/MetalBlockchain/metalgo/utils"
    14  	"github.com/MetalBlockchain/metalgo/utils/constants"
    15  	"github.com/MetalBlockchain/metalgo/utils/math"
    16  	"github.com/MetalBlockchain/metalgo/utils/set"
    17  	"github.com/MetalBlockchain/metalgo/vms/components/avax"
    18  	"github.com/MetalBlockchain/metalgo/vms/platformvm/fx"
    19  	"github.com/MetalBlockchain/metalgo/vms/platformvm/signer"
    20  	"github.com/MetalBlockchain/metalgo/vms/platformvm/stakeable"
    21  	"github.com/MetalBlockchain/metalgo/vms/platformvm/txs"
    22  	"github.com/MetalBlockchain/metalgo/vms/secp256k1fx"
    23  	"github.com/MetalBlockchain/metalgo/wallet/subnet/primary/common"
    24  )
    25  
    26  var (
    27  	ErrNoChangeAddress           = errors.New("no possible change address")
    28  	ErrUnknownOutputType         = errors.New("unknown output type")
    29  	ErrUnknownOwnerType          = errors.New("unknown owner type")
    30  	ErrInsufficientAuthorization = errors.New("insufficient authorization")
    31  	ErrInsufficientFunds         = errors.New("insufficient funds")
    32  
    33  	_ Builder = (*builder)(nil)
    34  )
    35  
    36  // Builder provides a convenient interface for building unsigned P-chain
    37  // transactions.
    38  type Builder interface {
    39  	// Context returns the configuration of the chain that this builder uses to
    40  	// create transactions.
    41  	Context() *Context
    42  
    43  	// GetBalance calculates the amount of each asset that this builder has
    44  	// control over.
    45  	GetBalance(
    46  		options ...common.Option,
    47  	) (map[ids.ID]uint64, error)
    48  
    49  	// GetImportableBalance calculates the amount of each asset that this
    50  	// builder could import from the provided chain.
    51  	//
    52  	// - [chainID] specifies the chain the funds are from.
    53  	GetImportableBalance(
    54  		chainID ids.ID,
    55  		options ...common.Option,
    56  	) (map[ids.ID]uint64, error)
    57  
    58  	// NewBaseTx creates a new simple value transfer.
    59  	//
    60  	// - [outputs] specifies all the recipients and amounts that should be sent
    61  	//   from this transaction.
    62  	NewBaseTx(
    63  		outputs []*avax.TransferableOutput,
    64  		options ...common.Option,
    65  	) (*txs.BaseTx, error)
    66  
    67  	// NewAddValidatorTx creates a new validator of the primary network.
    68  	//
    69  	// - [vdr] specifies all the details of the validation period such as the
    70  	//   startTime, endTime, stake weight, and nodeID.
    71  	// - [rewardsOwner] specifies the owner of all the rewards this validator
    72  	//   may accrue during its validation period.
    73  	// - [shares] specifies the fraction (out of 1,000,000) that this validator
    74  	//   will take from delegation rewards. If 1,000,000 is provided, 100% of
    75  	//   the delegation reward will be sent to the validator's [rewardsOwner].
    76  	NewAddValidatorTx(
    77  		vdr *txs.Validator,
    78  		rewardsOwner *secp256k1fx.OutputOwners,
    79  		shares uint32,
    80  		options ...common.Option,
    81  	) (*txs.AddValidatorTx, error)
    82  
    83  	// NewAddSubnetValidatorTx creates a new validator of a subnet.
    84  	//
    85  	// - [vdr] specifies all the details of the validation period such as the
    86  	//   startTime, endTime, sampling weight, nodeID, and subnetID.
    87  	NewAddSubnetValidatorTx(
    88  		vdr *txs.SubnetValidator,
    89  		options ...common.Option,
    90  	) (*txs.AddSubnetValidatorTx, error)
    91  
    92  	// NewRemoveSubnetValidatorTx removes [nodeID] from the validator
    93  	// set [subnetID].
    94  	NewRemoveSubnetValidatorTx(
    95  		nodeID ids.NodeID,
    96  		subnetID ids.ID,
    97  		options ...common.Option,
    98  	) (*txs.RemoveSubnetValidatorTx, error)
    99  
   100  	// NewAddDelegatorTx creates a new delegator to a validator on the primary
   101  	// network.
   102  	//
   103  	// - [vdr] specifies all the details of the delegation period such as the
   104  	//   startTime, endTime, stake weight, and validator's nodeID.
   105  	// - [rewardsOwner] specifies the owner of all the rewards this delegator
   106  	//   may accrue at the end of its delegation period.
   107  	NewAddDelegatorTx(
   108  		vdr *txs.Validator,
   109  		rewardsOwner *secp256k1fx.OutputOwners,
   110  		options ...common.Option,
   111  	) (*txs.AddDelegatorTx, error)
   112  
   113  	// NewCreateChainTx creates a new chain in the named subnet.
   114  	//
   115  	// - [subnetID] specifies the subnet to launch the chain in.
   116  	// - [genesis] specifies the initial state of the new chain.
   117  	// - [vmID] specifies the vm that the new chain will run.
   118  	// - [fxIDs] specifies all the feature extensions that the vm should be
   119  	//   running with.
   120  	// - [chainName] specifies a human readable name for the chain.
   121  	NewCreateChainTx(
   122  		subnetID ids.ID,
   123  		genesis []byte,
   124  		vmID ids.ID,
   125  		fxIDs []ids.ID,
   126  		chainName string,
   127  		options ...common.Option,
   128  	) (*txs.CreateChainTx, error)
   129  
   130  	// NewCreateSubnetTx creates a new subnet with the specified owner.
   131  	//
   132  	// - [owner] specifies who has the ability to create new chains and add new
   133  	//   validators to the subnet.
   134  	NewCreateSubnetTx(
   135  		owner *secp256k1fx.OutputOwners,
   136  		options ...common.Option,
   137  	) (*txs.CreateSubnetTx, error)
   138  
   139  	// NewTransferSubnetOwnershipTx changes the owner of the named subnet.
   140  	//
   141  	// - [subnetID] specifies the subnet to be modified
   142  	// - [owner] specifies who has the ability to create new chains and add new
   143  	//   validators to the subnet.
   144  	NewTransferSubnetOwnershipTx(
   145  		subnetID ids.ID,
   146  		owner *secp256k1fx.OutputOwners,
   147  		options ...common.Option,
   148  	) (*txs.TransferSubnetOwnershipTx, error)
   149  
   150  	// NewImportTx creates an import transaction that attempts to consume all
   151  	// the available UTXOs and import the funds to [to].
   152  	//
   153  	// - [chainID] specifies the chain to be importing funds from.
   154  	// - [to] specifies where to send the imported funds to.
   155  	NewImportTx(
   156  		chainID ids.ID,
   157  		to *secp256k1fx.OutputOwners,
   158  		options ...common.Option,
   159  	) (*txs.ImportTx, error)
   160  
   161  	// NewExportTx creates an export transaction that attempts to send all the
   162  	// provided [outputs] to the requested [chainID].
   163  	//
   164  	// - [chainID] specifies the chain to be exporting the funds to.
   165  	// - [outputs] specifies the outputs to send to the [chainID].
   166  	NewExportTx(
   167  		chainID ids.ID,
   168  		outputs []*avax.TransferableOutput,
   169  		options ...common.Option,
   170  	) (*txs.ExportTx, error)
   171  
   172  	// NewTransformSubnetTx creates a transform subnet transaction that attempts
   173  	// to convert the provided [subnetID] from a permissioned subnet to a
   174  	// permissionless subnet. This transaction will convert
   175  	// [maxSupply] - [initialSupply] of [assetID] to staking rewards.
   176  	//
   177  	// - [subnetID] specifies the subnet to transform.
   178  	// - [assetID] specifies the asset to use to reward stakers on the subnet.
   179  	// - [initialSupply] is the amount of [assetID] that will be in circulation
   180  	//   after this transaction is accepted.
   181  	// - [maxSupply] is the maximum total amount of [assetID] that should ever
   182  	//   exist.
   183  	// - [minConsumptionRate] is the rate that a staker will receive rewards
   184  	//   if they stake with a duration of 0.
   185  	// - [maxConsumptionRate] is the maximum rate that staking rewards should be
   186  	//   consumed from the reward pool per year.
   187  	// - [minValidatorStake] is the minimum amount of funds required to become a
   188  	//   validator.
   189  	// - [maxValidatorStake] is the maximum amount of funds a single validator
   190  	//   can be allocated, including delegated funds.
   191  	// - [minStakeDuration] is the minimum number of seconds a staker can stake
   192  	//   for.
   193  	// - [maxStakeDuration] is the maximum number of seconds a staker can stake
   194  	//   for.
   195  	// - [minValidatorStake] is the minimum amount of funds required to become a
   196  	//   delegator.
   197  	// - [maxValidatorWeightFactor] is the factor which calculates the maximum
   198  	//   amount of delegation a validator can receive. A value of 1 effectively
   199  	//   disables delegation.
   200  	// - [uptimeRequirement] is the minimum percentage a validator must be
   201  	//   online and responsive to receive a reward.
   202  	NewTransformSubnetTx(
   203  		subnetID ids.ID,
   204  		assetID ids.ID,
   205  		initialSupply uint64,
   206  		maxSupply uint64,
   207  		minConsumptionRate uint64,
   208  		maxConsumptionRate uint64,
   209  		minValidatorStake uint64,
   210  		maxValidatorStake uint64,
   211  		minStakeDuration time.Duration,
   212  		maxStakeDuration time.Duration,
   213  		minDelegationFee uint32,
   214  		minDelegatorStake uint64,
   215  		maxValidatorWeightFactor byte,
   216  		uptimeRequirement uint32,
   217  		options ...common.Option,
   218  	) (*txs.TransformSubnetTx, error)
   219  
   220  	// NewAddPermissionlessValidatorTx creates a new validator of the specified
   221  	// subnet.
   222  	//
   223  	// - [vdr] specifies all the details of the validation period such as the
   224  	//   subnetID, startTime, endTime, stake weight, and nodeID.
   225  	// - [signer] if the subnetID is the primary network, this is the BLS key
   226  	//   for this validator. Otherwise, this value should be the empty signer.
   227  	// - [assetID] specifies the asset to stake.
   228  	// - [validationRewardsOwner] specifies the owner of all the rewards this
   229  	//   validator earns for its validation period.
   230  	// - [delegationRewardsOwner] specifies the owner of all the rewards this
   231  	//   validator earns for delegations during its validation period.
   232  	// - [shares] specifies the fraction (out of 1,000,000) that this validator
   233  	//   will take from delegation rewards. If 1,000,000 is provided, 100% of
   234  	//   the delegation reward will be sent to the validator's [rewardsOwner].
   235  	NewAddPermissionlessValidatorTx(
   236  		vdr *txs.SubnetValidator,
   237  		signer signer.Signer,
   238  		assetID ids.ID,
   239  		validationRewardsOwner *secp256k1fx.OutputOwners,
   240  		delegationRewardsOwner *secp256k1fx.OutputOwners,
   241  		shares uint32,
   242  		options ...common.Option,
   243  	) (*txs.AddPermissionlessValidatorTx, error)
   244  
   245  	// NewAddPermissionlessDelegatorTx creates a new delegator of the specified
   246  	// subnet on the specified nodeID.
   247  	//
   248  	// - [vdr] specifies all the details of the delegation period such as the
   249  	//   subnetID, startTime, endTime, stake weight, and nodeID.
   250  	// - [assetID] specifies the asset to stake.
   251  	// - [rewardsOwner] specifies the owner of all the rewards this delegator
   252  	//   earns during its delegation period.
   253  	NewAddPermissionlessDelegatorTx(
   254  		vdr *txs.SubnetValidator,
   255  		assetID ids.ID,
   256  		rewardsOwner *secp256k1fx.OutputOwners,
   257  		options ...common.Option,
   258  	) (*txs.AddPermissionlessDelegatorTx, error)
   259  }
   260  
   261  type Backend interface {
   262  	UTXOs(ctx context.Context, sourceChainID ids.ID) ([]*avax.UTXO, error)
   263  	GetSubnetOwner(ctx context.Context, subnetID ids.ID) (fx.Owner, error)
   264  }
   265  
   266  type builder struct {
   267  	addrs   set.Set[ids.ShortID]
   268  	context *Context
   269  	backend Backend
   270  }
   271  
   272  // New returns a new transaction builder.
   273  //
   274  //   - [addrs] is the set of addresses that the builder assumes can be used when
   275  //     signing the transactions in the future.
   276  //   - [context] provides the chain's configuration.
   277  //   - [backend] provides the chain's state.
   278  func New(
   279  	addrs set.Set[ids.ShortID],
   280  	context *Context,
   281  	backend Backend,
   282  ) Builder {
   283  	return &builder{
   284  		addrs:   addrs,
   285  		context: context,
   286  		backend: backend,
   287  	}
   288  }
   289  
   290  func (b *builder) Context() *Context {
   291  	return b.context
   292  }
   293  
   294  func (b *builder) GetBalance(
   295  	options ...common.Option,
   296  ) (map[ids.ID]uint64, error) {
   297  	ops := common.NewOptions(options)
   298  	return b.getBalance(constants.PlatformChainID, ops)
   299  }
   300  
   301  func (b *builder) GetImportableBalance(
   302  	chainID ids.ID,
   303  	options ...common.Option,
   304  ) (map[ids.ID]uint64, error) {
   305  	ops := common.NewOptions(options)
   306  	return b.getBalance(chainID, ops)
   307  }
   308  
   309  func (b *builder) NewBaseTx(
   310  	outputs []*avax.TransferableOutput,
   311  	options ...common.Option,
   312  ) (*txs.BaseTx, error) {
   313  	toBurn := map[ids.ID]uint64{
   314  		b.context.AVAXAssetID: b.context.BaseTxFee,
   315  	}
   316  	for _, out := range outputs {
   317  		assetID := out.AssetID()
   318  		amountToBurn, err := math.Add64(toBurn[assetID], out.Out.Amount())
   319  		if err != nil {
   320  			return nil, err
   321  		}
   322  		toBurn[assetID] = amountToBurn
   323  	}
   324  	toStake := map[ids.ID]uint64{}
   325  
   326  	ops := common.NewOptions(options)
   327  	inputs, changeOutputs, _, err := b.spend(toBurn, toStake, ops)
   328  	if err != nil {
   329  		return nil, err
   330  	}
   331  	outputs = append(outputs, changeOutputs...)
   332  	avax.SortTransferableOutputs(outputs, txs.Codec) // sort the outputs
   333  
   334  	tx := &txs.BaseTx{BaseTx: avax.BaseTx{
   335  		NetworkID:    b.context.NetworkID,
   336  		BlockchainID: constants.PlatformChainID,
   337  		Ins:          inputs,
   338  		Outs:         outputs,
   339  		Memo:         ops.Memo(),
   340  	}}
   341  	return tx, b.initCtx(tx)
   342  }
   343  
   344  func (b *builder) NewAddValidatorTx(
   345  	vdr *txs.Validator,
   346  	rewardsOwner *secp256k1fx.OutputOwners,
   347  	shares uint32,
   348  	options ...common.Option,
   349  ) (*txs.AddValidatorTx, error) {
   350  	avaxAssetID := b.context.AVAXAssetID
   351  	toBurn := map[ids.ID]uint64{
   352  		avaxAssetID: b.context.AddPrimaryNetworkValidatorFee,
   353  	}
   354  	toStake := map[ids.ID]uint64{
   355  		avaxAssetID: vdr.Wght,
   356  	}
   357  	ops := common.NewOptions(options)
   358  	inputs, baseOutputs, stakeOutputs, err := b.spend(toBurn, toStake, ops)
   359  	if err != nil {
   360  		return nil, err
   361  	}
   362  
   363  	utils.Sort(rewardsOwner.Addrs)
   364  	tx := &txs.AddValidatorTx{
   365  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
   366  			NetworkID:    b.context.NetworkID,
   367  			BlockchainID: constants.PlatformChainID,
   368  			Ins:          inputs,
   369  			Outs:         baseOutputs,
   370  			Memo:         ops.Memo(),
   371  		}},
   372  		Validator:        *vdr,
   373  		StakeOuts:        stakeOutputs,
   374  		RewardsOwner:     rewardsOwner,
   375  		DelegationShares: shares,
   376  	}
   377  	return tx, b.initCtx(tx)
   378  }
   379  
   380  func (b *builder) NewAddSubnetValidatorTx(
   381  	vdr *txs.SubnetValidator,
   382  	options ...common.Option,
   383  ) (*txs.AddSubnetValidatorTx, error) {
   384  	toBurn := map[ids.ID]uint64{
   385  		b.context.AVAXAssetID: b.context.AddSubnetValidatorFee,
   386  	}
   387  	toStake := map[ids.ID]uint64{}
   388  	ops := common.NewOptions(options)
   389  	inputs, outputs, _, err := b.spend(toBurn, toStake, ops)
   390  	if err != nil {
   391  		return nil, err
   392  	}
   393  
   394  	subnetAuth, err := b.authorizeSubnet(vdr.Subnet, ops)
   395  	if err != nil {
   396  		return nil, err
   397  	}
   398  
   399  	tx := &txs.AddSubnetValidatorTx{
   400  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
   401  			NetworkID:    b.context.NetworkID,
   402  			BlockchainID: constants.PlatformChainID,
   403  			Ins:          inputs,
   404  			Outs:         outputs,
   405  			Memo:         ops.Memo(),
   406  		}},
   407  		SubnetValidator: *vdr,
   408  		SubnetAuth:      subnetAuth,
   409  	}
   410  	return tx, b.initCtx(tx)
   411  }
   412  
   413  func (b *builder) NewRemoveSubnetValidatorTx(
   414  	nodeID ids.NodeID,
   415  	subnetID ids.ID,
   416  	options ...common.Option,
   417  ) (*txs.RemoveSubnetValidatorTx, error) {
   418  	toBurn := map[ids.ID]uint64{
   419  		b.context.AVAXAssetID: b.context.BaseTxFee,
   420  	}
   421  	toStake := map[ids.ID]uint64{}
   422  	ops := common.NewOptions(options)
   423  	inputs, outputs, _, err := b.spend(toBurn, toStake, ops)
   424  	if err != nil {
   425  		return nil, err
   426  	}
   427  
   428  	subnetAuth, err := b.authorizeSubnet(subnetID, ops)
   429  	if err != nil {
   430  		return nil, err
   431  	}
   432  
   433  	tx := &txs.RemoveSubnetValidatorTx{
   434  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
   435  			NetworkID:    b.context.NetworkID,
   436  			BlockchainID: constants.PlatformChainID,
   437  			Ins:          inputs,
   438  			Outs:         outputs,
   439  			Memo:         ops.Memo(),
   440  		}},
   441  		Subnet:     subnetID,
   442  		NodeID:     nodeID,
   443  		SubnetAuth: subnetAuth,
   444  	}
   445  	return tx, b.initCtx(tx)
   446  }
   447  
   448  func (b *builder) NewAddDelegatorTx(
   449  	vdr *txs.Validator,
   450  	rewardsOwner *secp256k1fx.OutputOwners,
   451  	options ...common.Option,
   452  ) (*txs.AddDelegatorTx, error) {
   453  	avaxAssetID := b.context.AVAXAssetID
   454  	toBurn := map[ids.ID]uint64{
   455  		avaxAssetID: b.context.AddPrimaryNetworkDelegatorFee,
   456  	}
   457  	toStake := map[ids.ID]uint64{
   458  		avaxAssetID: vdr.Wght,
   459  	}
   460  	ops := common.NewOptions(options)
   461  	inputs, baseOutputs, stakeOutputs, err := b.spend(toBurn, toStake, ops)
   462  	if err != nil {
   463  		return nil, err
   464  	}
   465  
   466  	utils.Sort(rewardsOwner.Addrs)
   467  	tx := &txs.AddDelegatorTx{
   468  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
   469  			NetworkID:    b.context.NetworkID,
   470  			BlockchainID: constants.PlatformChainID,
   471  			Ins:          inputs,
   472  			Outs:         baseOutputs,
   473  			Memo:         ops.Memo(),
   474  		}},
   475  		Validator:              *vdr,
   476  		StakeOuts:              stakeOutputs,
   477  		DelegationRewardsOwner: rewardsOwner,
   478  	}
   479  	return tx, b.initCtx(tx)
   480  }
   481  
   482  func (b *builder) NewCreateChainTx(
   483  	subnetID ids.ID,
   484  	genesis []byte,
   485  	vmID ids.ID,
   486  	fxIDs []ids.ID,
   487  	chainName string,
   488  	options ...common.Option,
   489  ) (*txs.CreateChainTx, error) {
   490  	toBurn := map[ids.ID]uint64{
   491  		b.context.AVAXAssetID: b.context.CreateBlockchainTxFee,
   492  	}
   493  	toStake := map[ids.ID]uint64{}
   494  	ops := common.NewOptions(options)
   495  	inputs, outputs, _, err := b.spend(toBurn, toStake, ops)
   496  	if err != nil {
   497  		return nil, err
   498  	}
   499  
   500  	subnetAuth, err := b.authorizeSubnet(subnetID, ops)
   501  	if err != nil {
   502  		return nil, err
   503  	}
   504  
   505  	utils.Sort(fxIDs)
   506  	tx := &txs.CreateChainTx{
   507  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
   508  			NetworkID:    b.context.NetworkID,
   509  			BlockchainID: constants.PlatformChainID,
   510  			Ins:          inputs,
   511  			Outs:         outputs,
   512  			Memo:         ops.Memo(),
   513  		}},
   514  		SubnetID:    subnetID,
   515  		ChainName:   chainName,
   516  		VMID:        vmID,
   517  		FxIDs:       fxIDs,
   518  		GenesisData: genesis,
   519  		SubnetAuth:  subnetAuth,
   520  	}
   521  	return tx, b.initCtx(tx)
   522  }
   523  
   524  func (b *builder) NewCreateSubnetTx(
   525  	owner *secp256k1fx.OutputOwners,
   526  	options ...common.Option,
   527  ) (*txs.CreateSubnetTx, error) {
   528  	toBurn := map[ids.ID]uint64{
   529  		b.context.AVAXAssetID: b.context.CreateSubnetTxFee,
   530  	}
   531  	toStake := map[ids.ID]uint64{}
   532  	ops := common.NewOptions(options)
   533  	inputs, outputs, _, err := b.spend(toBurn, toStake, ops)
   534  	if err != nil {
   535  		return nil, err
   536  	}
   537  
   538  	utils.Sort(owner.Addrs)
   539  	tx := &txs.CreateSubnetTx{
   540  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
   541  			NetworkID:    b.context.NetworkID,
   542  			BlockchainID: constants.PlatformChainID,
   543  			Ins:          inputs,
   544  			Outs:         outputs,
   545  			Memo:         ops.Memo(),
   546  		}},
   547  		Owner: owner,
   548  	}
   549  	return tx, b.initCtx(tx)
   550  }
   551  
   552  func (b *builder) NewTransferSubnetOwnershipTx(
   553  	subnetID ids.ID,
   554  	owner *secp256k1fx.OutputOwners,
   555  	options ...common.Option,
   556  ) (*txs.TransferSubnetOwnershipTx, error) {
   557  	toBurn := map[ids.ID]uint64{
   558  		b.context.AVAXAssetID: b.context.BaseTxFee,
   559  	}
   560  	toStake := map[ids.ID]uint64{}
   561  	ops := common.NewOptions(options)
   562  	inputs, outputs, _, err := b.spend(toBurn, toStake, ops)
   563  	if err != nil {
   564  		return nil, err
   565  	}
   566  
   567  	subnetAuth, err := b.authorizeSubnet(subnetID, ops)
   568  	if err != nil {
   569  		return nil, err
   570  	}
   571  
   572  	utils.Sort(owner.Addrs)
   573  	tx := &txs.TransferSubnetOwnershipTx{
   574  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
   575  			NetworkID:    b.context.NetworkID,
   576  			BlockchainID: constants.PlatformChainID,
   577  			Ins:          inputs,
   578  			Outs:         outputs,
   579  			Memo:         ops.Memo(),
   580  		}},
   581  		Subnet:     subnetID,
   582  		Owner:      owner,
   583  		SubnetAuth: subnetAuth,
   584  	}
   585  	return tx, b.initCtx(tx)
   586  }
   587  
   588  func (b *builder) NewImportTx(
   589  	sourceChainID ids.ID,
   590  	to *secp256k1fx.OutputOwners,
   591  	options ...common.Option,
   592  ) (*txs.ImportTx, error) {
   593  	ops := common.NewOptions(options)
   594  	utxos, err := b.backend.UTXOs(ops.Context(), sourceChainID)
   595  	if err != nil {
   596  		return nil, err
   597  	}
   598  
   599  	var (
   600  		addrs           = ops.Addresses(b.addrs)
   601  		minIssuanceTime = ops.MinIssuanceTime()
   602  		avaxAssetID     = b.context.AVAXAssetID
   603  		txFee           = b.context.BaseTxFee
   604  
   605  		importedInputs  = make([]*avax.TransferableInput, 0, len(utxos))
   606  		importedAmounts = make(map[ids.ID]uint64)
   607  	)
   608  	// Iterate over the unlocked UTXOs
   609  	for _, utxo := range utxos {
   610  		out, ok := utxo.Out.(*secp256k1fx.TransferOutput)
   611  		if !ok {
   612  			continue
   613  		}
   614  
   615  		inputSigIndices, ok := common.MatchOwners(&out.OutputOwners, addrs, minIssuanceTime)
   616  		if !ok {
   617  			// We couldn't spend this UTXO, so we skip to the next one
   618  			continue
   619  		}
   620  
   621  		importedInputs = append(importedInputs, &avax.TransferableInput{
   622  			UTXOID: utxo.UTXOID,
   623  			Asset:  utxo.Asset,
   624  			In: &secp256k1fx.TransferInput{
   625  				Amt: out.Amt,
   626  				Input: secp256k1fx.Input{
   627  					SigIndices: inputSigIndices,
   628  				},
   629  			},
   630  		})
   631  
   632  		assetID := utxo.AssetID()
   633  		newImportedAmount, err := math.Add64(importedAmounts[assetID], out.Amt)
   634  		if err != nil {
   635  			return nil, err
   636  		}
   637  		importedAmounts[assetID] = newImportedAmount
   638  	}
   639  	utils.Sort(importedInputs) // sort imported inputs
   640  
   641  	if len(importedInputs) == 0 {
   642  		return nil, fmt.Errorf(
   643  			"%w: no UTXOs available to import",
   644  			ErrInsufficientFunds,
   645  		)
   646  	}
   647  
   648  	var (
   649  		inputs       []*avax.TransferableInput
   650  		outputs      = make([]*avax.TransferableOutput, 0, len(importedAmounts))
   651  		importedAVAX = importedAmounts[avaxAssetID]
   652  	)
   653  	if importedAVAX > txFee {
   654  		importedAmounts[avaxAssetID] -= txFee
   655  	} else {
   656  		if importedAVAX < txFee { // imported amount goes toward paying tx fee
   657  			toBurn := map[ids.ID]uint64{
   658  				avaxAssetID: txFee - importedAVAX,
   659  			}
   660  			toStake := map[ids.ID]uint64{}
   661  			var err error
   662  			inputs, outputs, _, err = b.spend(toBurn, toStake, ops)
   663  			if err != nil {
   664  				return nil, fmt.Errorf("couldn't generate tx inputs/outputs: %w", err)
   665  			}
   666  		}
   667  		delete(importedAmounts, avaxAssetID)
   668  	}
   669  
   670  	for assetID, amount := range importedAmounts {
   671  		outputs = append(outputs, &avax.TransferableOutput{
   672  			Asset: avax.Asset{ID: assetID},
   673  			Out: &secp256k1fx.TransferOutput{
   674  				Amt:          amount,
   675  				OutputOwners: *to,
   676  			},
   677  		})
   678  	}
   679  
   680  	avax.SortTransferableOutputs(outputs, txs.Codec) // sort imported outputs
   681  	tx := &txs.ImportTx{
   682  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
   683  			NetworkID:    b.context.NetworkID,
   684  			BlockchainID: constants.PlatformChainID,
   685  			Ins:          inputs,
   686  			Outs:         outputs,
   687  			Memo:         ops.Memo(),
   688  		}},
   689  		SourceChain:    sourceChainID,
   690  		ImportedInputs: importedInputs,
   691  	}
   692  	return tx, b.initCtx(tx)
   693  }
   694  
   695  func (b *builder) NewExportTx(
   696  	chainID ids.ID,
   697  	outputs []*avax.TransferableOutput,
   698  	options ...common.Option,
   699  ) (*txs.ExportTx, error) {
   700  	toBurn := map[ids.ID]uint64{
   701  		b.context.AVAXAssetID: b.context.BaseTxFee,
   702  	}
   703  	for _, out := range outputs {
   704  		assetID := out.AssetID()
   705  		amountToBurn, err := math.Add64(toBurn[assetID], out.Out.Amount())
   706  		if err != nil {
   707  			return nil, err
   708  		}
   709  		toBurn[assetID] = amountToBurn
   710  	}
   711  
   712  	toStake := map[ids.ID]uint64{}
   713  	ops := common.NewOptions(options)
   714  	inputs, changeOutputs, _, err := b.spend(toBurn, toStake, ops)
   715  	if err != nil {
   716  		return nil, err
   717  	}
   718  
   719  	avax.SortTransferableOutputs(outputs, txs.Codec) // sort exported outputs
   720  	tx := &txs.ExportTx{
   721  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
   722  			NetworkID:    b.context.NetworkID,
   723  			BlockchainID: constants.PlatformChainID,
   724  			Ins:          inputs,
   725  			Outs:         changeOutputs,
   726  			Memo:         ops.Memo(),
   727  		}},
   728  		DestinationChain: chainID,
   729  		ExportedOutputs:  outputs,
   730  	}
   731  	return tx, b.initCtx(tx)
   732  }
   733  
   734  func (b *builder) NewTransformSubnetTx(
   735  	subnetID ids.ID,
   736  	assetID ids.ID,
   737  	initialSupply uint64,
   738  	maxSupply uint64,
   739  	minConsumptionRate uint64,
   740  	maxConsumptionRate uint64,
   741  	minValidatorStake uint64,
   742  	maxValidatorStake uint64,
   743  	minStakeDuration time.Duration,
   744  	maxStakeDuration time.Duration,
   745  	minDelegationFee uint32,
   746  	minDelegatorStake uint64,
   747  	maxValidatorWeightFactor byte,
   748  	uptimeRequirement uint32,
   749  	options ...common.Option,
   750  ) (*txs.TransformSubnetTx, error) {
   751  	toBurn := map[ids.ID]uint64{
   752  		b.context.AVAXAssetID: b.context.TransformSubnetTxFee,
   753  		assetID:               maxSupply - initialSupply,
   754  	}
   755  	toStake := map[ids.ID]uint64{}
   756  	ops := common.NewOptions(options)
   757  	inputs, outputs, _, err := b.spend(toBurn, toStake, ops)
   758  	if err != nil {
   759  		return nil, err
   760  	}
   761  
   762  	subnetAuth, err := b.authorizeSubnet(subnetID, ops)
   763  	if err != nil {
   764  		return nil, err
   765  	}
   766  
   767  	tx := &txs.TransformSubnetTx{
   768  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
   769  			NetworkID:    b.context.NetworkID,
   770  			BlockchainID: constants.PlatformChainID,
   771  			Ins:          inputs,
   772  			Outs:         outputs,
   773  			Memo:         ops.Memo(),
   774  		}},
   775  		Subnet:                   subnetID,
   776  		AssetID:                  assetID,
   777  		InitialSupply:            initialSupply,
   778  		MaximumSupply:            maxSupply,
   779  		MinConsumptionRate:       minConsumptionRate,
   780  		MaxConsumptionRate:       maxConsumptionRate,
   781  		MinValidatorStake:        minValidatorStake,
   782  		MaxValidatorStake:        maxValidatorStake,
   783  		MinStakeDuration:         uint32(minStakeDuration / time.Second),
   784  		MaxStakeDuration:         uint32(maxStakeDuration / time.Second),
   785  		MinDelegationFee:         minDelegationFee,
   786  		MinDelegatorStake:        minDelegatorStake,
   787  		MaxValidatorWeightFactor: maxValidatorWeightFactor,
   788  		UptimeRequirement:        uptimeRequirement,
   789  		SubnetAuth:               subnetAuth,
   790  	}
   791  	return tx, b.initCtx(tx)
   792  }
   793  
   794  func (b *builder) NewAddPermissionlessValidatorTx(
   795  	vdr *txs.SubnetValidator,
   796  	signer signer.Signer,
   797  	assetID ids.ID,
   798  	validationRewardsOwner *secp256k1fx.OutputOwners,
   799  	delegationRewardsOwner *secp256k1fx.OutputOwners,
   800  	shares uint32,
   801  	options ...common.Option,
   802  ) (*txs.AddPermissionlessValidatorTx, error) {
   803  	avaxAssetID := b.context.AVAXAssetID
   804  	toBurn := map[ids.ID]uint64{}
   805  	if vdr.Subnet == constants.PrimaryNetworkID {
   806  		toBurn[avaxAssetID] = b.context.AddPrimaryNetworkValidatorFee
   807  	} else {
   808  		toBurn[avaxAssetID] = b.context.AddSubnetValidatorFee
   809  	}
   810  	toStake := map[ids.ID]uint64{
   811  		assetID: vdr.Wght,
   812  	}
   813  	ops := common.NewOptions(options)
   814  	inputs, baseOutputs, stakeOutputs, err := b.spend(toBurn, toStake, ops)
   815  	if err != nil {
   816  		return nil, err
   817  	}
   818  
   819  	utils.Sort(validationRewardsOwner.Addrs)
   820  	utils.Sort(delegationRewardsOwner.Addrs)
   821  	tx := &txs.AddPermissionlessValidatorTx{
   822  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
   823  			NetworkID:    b.context.NetworkID,
   824  			BlockchainID: constants.PlatformChainID,
   825  			Ins:          inputs,
   826  			Outs:         baseOutputs,
   827  			Memo:         ops.Memo(),
   828  		}},
   829  		Validator:             vdr.Validator,
   830  		Subnet:                vdr.Subnet,
   831  		Signer:                signer,
   832  		StakeOuts:             stakeOutputs,
   833  		ValidatorRewardsOwner: validationRewardsOwner,
   834  		DelegatorRewardsOwner: delegationRewardsOwner,
   835  		DelegationShares:      shares,
   836  	}
   837  	return tx, b.initCtx(tx)
   838  }
   839  
   840  func (b *builder) NewAddPermissionlessDelegatorTx(
   841  	vdr *txs.SubnetValidator,
   842  	assetID ids.ID,
   843  	rewardsOwner *secp256k1fx.OutputOwners,
   844  	options ...common.Option,
   845  ) (*txs.AddPermissionlessDelegatorTx, error) {
   846  	avaxAssetID := b.context.AVAXAssetID
   847  	toBurn := map[ids.ID]uint64{}
   848  	if vdr.Subnet == constants.PrimaryNetworkID {
   849  		toBurn[avaxAssetID] = b.context.AddPrimaryNetworkDelegatorFee
   850  	} else {
   851  		toBurn[avaxAssetID] = b.context.AddSubnetDelegatorFee
   852  	}
   853  	toStake := map[ids.ID]uint64{
   854  		assetID: vdr.Wght,
   855  	}
   856  	ops := common.NewOptions(options)
   857  	inputs, baseOutputs, stakeOutputs, err := b.spend(toBurn, toStake, ops)
   858  	if err != nil {
   859  		return nil, err
   860  	}
   861  
   862  	utils.Sort(rewardsOwner.Addrs)
   863  	tx := &txs.AddPermissionlessDelegatorTx{
   864  		BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{
   865  			NetworkID:    b.context.NetworkID,
   866  			BlockchainID: constants.PlatformChainID,
   867  			Ins:          inputs,
   868  			Outs:         baseOutputs,
   869  			Memo:         ops.Memo(),
   870  		}},
   871  		Validator:              vdr.Validator,
   872  		Subnet:                 vdr.Subnet,
   873  		StakeOuts:              stakeOutputs,
   874  		DelegationRewardsOwner: rewardsOwner,
   875  	}
   876  	return tx, b.initCtx(tx)
   877  }
   878  
   879  func (b *builder) getBalance(
   880  	chainID ids.ID,
   881  	options *common.Options,
   882  ) (
   883  	balance map[ids.ID]uint64,
   884  	err error,
   885  ) {
   886  	utxos, err := b.backend.UTXOs(options.Context(), chainID)
   887  	if err != nil {
   888  		return nil, err
   889  	}
   890  
   891  	addrs := options.Addresses(b.addrs)
   892  	minIssuanceTime := options.MinIssuanceTime()
   893  	balance = make(map[ids.ID]uint64)
   894  
   895  	// Iterate over the UTXOs
   896  	for _, utxo := range utxos {
   897  		outIntf := utxo.Out
   898  		if lockedOut, ok := outIntf.(*stakeable.LockOut); ok {
   899  			if !options.AllowStakeableLocked() && lockedOut.Locktime > minIssuanceTime {
   900  				// This output is currently locked, so this output can't be
   901  				// burned.
   902  				continue
   903  			}
   904  			outIntf = lockedOut.TransferableOut
   905  		}
   906  
   907  		out, ok := outIntf.(*secp256k1fx.TransferOutput)
   908  		if !ok {
   909  			return nil, ErrUnknownOutputType
   910  		}
   911  
   912  		_, ok = common.MatchOwners(&out.OutputOwners, addrs, minIssuanceTime)
   913  		if !ok {
   914  			// We couldn't spend this UTXO, so we skip to the next one
   915  			continue
   916  		}
   917  
   918  		assetID := utxo.AssetID()
   919  		balance[assetID], err = math.Add64(balance[assetID], out.Amt)
   920  		if err != nil {
   921  			return nil, err
   922  		}
   923  	}
   924  	return balance, nil
   925  }
   926  
   927  // spend takes in the requested burn amounts and the requested stake amounts.
   928  //
   929  //   - [amountsToBurn] maps assetID to the amount of the asset to spend without
   930  //     producing an output. This is typically used for fees. However, it can
   931  //     also be used to consume some of an asset that will be produced in
   932  //     separate outputs, such as ExportedOutputs. Only unlocked UTXOs are able
   933  //     to be burned here.
   934  //   - [amountsToStake] maps assetID to the amount of the asset to spend and
   935  //     place into the staked outputs. First locked UTXOs are attempted to be
   936  //     used for these funds, and then unlocked UTXOs will be attempted to be
   937  //     used. There is no preferential ordering on the unlock times.
   938  func (b *builder) spend(
   939  	amountsToBurn map[ids.ID]uint64,
   940  	amountsToStake map[ids.ID]uint64,
   941  	options *common.Options,
   942  ) (
   943  	inputs []*avax.TransferableInput,
   944  	changeOutputs []*avax.TransferableOutput,
   945  	stakeOutputs []*avax.TransferableOutput,
   946  	err error,
   947  ) {
   948  	utxos, err := b.backend.UTXOs(options.Context(), constants.PlatformChainID)
   949  	if err != nil {
   950  		return nil, nil, nil, err
   951  	}
   952  
   953  	addrs := options.Addresses(b.addrs)
   954  	minIssuanceTime := options.MinIssuanceTime()
   955  
   956  	addr, ok := addrs.Peek()
   957  	if !ok {
   958  		return nil, nil, nil, ErrNoChangeAddress
   959  	}
   960  	changeOwner := options.ChangeOwner(&secp256k1fx.OutputOwners{
   961  		Threshold: 1,
   962  		Addrs:     []ids.ShortID{addr},
   963  	})
   964  
   965  	// Initialize the return values with empty slices to preserve backward
   966  	// compatibility of the json representation of transactions with no
   967  	// inputs or outputs.
   968  	inputs = make([]*avax.TransferableInput, 0)
   969  	changeOutputs = make([]*avax.TransferableOutput, 0)
   970  	stakeOutputs = make([]*avax.TransferableOutput, 0)
   971  
   972  	// Iterate over the locked UTXOs
   973  	for _, utxo := range utxos {
   974  		assetID := utxo.AssetID()
   975  		remainingAmountToStake := amountsToStake[assetID]
   976  
   977  		// If we have staked enough of the asset, then we have no need burn
   978  		// more.
   979  		if remainingAmountToStake == 0 {
   980  			continue
   981  		}
   982  
   983  		outIntf := utxo.Out
   984  		lockedOut, ok := outIntf.(*stakeable.LockOut)
   985  		if !ok {
   986  			// This output isn't locked, so it will be handled during the next
   987  			// iteration of the UTXO set
   988  			continue
   989  		}
   990  		if minIssuanceTime >= lockedOut.Locktime {
   991  			// This output isn't locked, so it will be handled during the next
   992  			// iteration of the UTXO set
   993  			continue
   994  		}
   995  
   996  		out, ok := lockedOut.TransferableOut.(*secp256k1fx.TransferOutput)
   997  		if !ok {
   998  			return nil, nil, nil, ErrUnknownOutputType
   999  		}
  1000  
  1001  		inputSigIndices, ok := common.MatchOwners(&out.OutputOwners, addrs, minIssuanceTime)
  1002  		if !ok {
  1003  			// We couldn't spend this UTXO, so we skip to the next one
  1004  			continue
  1005  		}
  1006  
  1007  		inputs = append(inputs, &avax.TransferableInput{
  1008  			UTXOID: utxo.UTXOID,
  1009  			Asset:  utxo.Asset,
  1010  			In: &stakeable.LockIn{
  1011  				Locktime: lockedOut.Locktime,
  1012  				TransferableIn: &secp256k1fx.TransferInput{
  1013  					Amt: out.Amt,
  1014  					Input: secp256k1fx.Input{
  1015  						SigIndices: inputSigIndices,
  1016  					},
  1017  				},
  1018  			},
  1019  		})
  1020  
  1021  		// Stake any value that should be staked
  1022  		amountToStake := min(
  1023  			remainingAmountToStake, // Amount we still need to stake
  1024  			out.Amt,                // Amount available to stake
  1025  		)
  1026  
  1027  		// Add the output to the staked outputs
  1028  		stakeOutputs = append(stakeOutputs, &avax.TransferableOutput{
  1029  			Asset: utxo.Asset,
  1030  			Out: &stakeable.LockOut{
  1031  				Locktime: lockedOut.Locktime,
  1032  				TransferableOut: &secp256k1fx.TransferOutput{
  1033  					Amt:          amountToStake,
  1034  					OutputOwners: out.OutputOwners,
  1035  				},
  1036  			},
  1037  		})
  1038  
  1039  		amountsToStake[assetID] -= amountToStake
  1040  		if remainingAmount := out.Amt - amountToStake; remainingAmount > 0 {
  1041  			// This input had extra value, so some of it must be returned
  1042  			changeOutputs = append(changeOutputs, &avax.TransferableOutput{
  1043  				Asset: utxo.Asset,
  1044  				Out: &stakeable.LockOut{
  1045  					Locktime: lockedOut.Locktime,
  1046  					TransferableOut: &secp256k1fx.TransferOutput{
  1047  						Amt:          remainingAmount,
  1048  						OutputOwners: out.OutputOwners,
  1049  					},
  1050  				},
  1051  			})
  1052  		}
  1053  	}
  1054  
  1055  	// Iterate over the unlocked UTXOs
  1056  	for _, utxo := range utxos {
  1057  		assetID := utxo.AssetID()
  1058  		remainingAmountToStake := amountsToStake[assetID]
  1059  		remainingAmountToBurn := amountsToBurn[assetID]
  1060  
  1061  		// If we have consumed enough of the asset, then we have no need burn
  1062  		// more.
  1063  		if remainingAmountToStake == 0 && remainingAmountToBurn == 0 {
  1064  			continue
  1065  		}
  1066  
  1067  		outIntf := utxo.Out
  1068  		if lockedOut, ok := outIntf.(*stakeable.LockOut); ok {
  1069  			if lockedOut.Locktime > minIssuanceTime {
  1070  				// This output is currently locked, so this output can't be
  1071  				// burned.
  1072  				continue
  1073  			}
  1074  			outIntf = lockedOut.TransferableOut
  1075  		}
  1076  
  1077  		out, ok := outIntf.(*secp256k1fx.TransferOutput)
  1078  		if !ok {
  1079  			return nil, nil, nil, ErrUnknownOutputType
  1080  		}
  1081  
  1082  		inputSigIndices, ok := common.MatchOwners(&out.OutputOwners, addrs, minIssuanceTime)
  1083  		if !ok {
  1084  			// We couldn't spend this UTXO, so we skip to the next one
  1085  			continue
  1086  		}
  1087  
  1088  		inputs = append(inputs, &avax.TransferableInput{
  1089  			UTXOID: utxo.UTXOID,
  1090  			Asset:  utxo.Asset,
  1091  			In: &secp256k1fx.TransferInput{
  1092  				Amt: out.Amt,
  1093  				Input: secp256k1fx.Input{
  1094  					SigIndices: inputSigIndices,
  1095  				},
  1096  			},
  1097  		})
  1098  
  1099  		// Burn any value that should be burned
  1100  		amountToBurn := min(
  1101  			remainingAmountToBurn, // Amount we still need to burn
  1102  			out.Amt,               // Amount available to burn
  1103  		)
  1104  		amountsToBurn[assetID] -= amountToBurn
  1105  
  1106  		amountAvalibleToStake := out.Amt - amountToBurn
  1107  		// Burn any value that should be burned
  1108  		amountToStake := min(
  1109  			remainingAmountToStake, // Amount we still need to stake
  1110  			amountAvalibleToStake,  // Amount available to stake
  1111  		)
  1112  		amountsToStake[assetID] -= amountToStake
  1113  		if amountToStake > 0 {
  1114  			// Some of this input was put for staking
  1115  			stakeOutputs = append(stakeOutputs, &avax.TransferableOutput{
  1116  				Asset: utxo.Asset,
  1117  				Out: &secp256k1fx.TransferOutput{
  1118  					Amt:          amountToStake,
  1119  					OutputOwners: *changeOwner,
  1120  				},
  1121  			})
  1122  		}
  1123  		if remainingAmount := amountAvalibleToStake - amountToStake; remainingAmount > 0 {
  1124  			// This input had extra value, so some of it must be returned
  1125  			changeOutputs = append(changeOutputs, &avax.TransferableOutput{
  1126  				Asset: utxo.Asset,
  1127  				Out: &secp256k1fx.TransferOutput{
  1128  					Amt:          remainingAmount,
  1129  					OutputOwners: *changeOwner,
  1130  				},
  1131  			})
  1132  		}
  1133  	}
  1134  
  1135  	for assetID, amount := range amountsToStake {
  1136  		if amount != 0 {
  1137  			return nil, nil, nil, fmt.Errorf(
  1138  				"%w: provided UTXOs need %d more units of asset %q to stake",
  1139  				ErrInsufficientFunds,
  1140  				amount,
  1141  				assetID,
  1142  			)
  1143  		}
  1144  	}
  1145  	for assetID, amount := range amountsToBurn {
  1146  		if amount != 0 {
  1147  			return nil, nil, nil, fmt.Errorf(
  1148  				"%w: provided UTXOs need %d more units of asset %q",
  1149  				ErrInsufficientFunds,
  1150  				amount,
  1151  				assetID,
  1152  			)
  1153  		}
  1154  	}
  1155  
  1156  	utils.Sort(inputs)                                     // sort inputs
  1157  	avax.SortTransferableOutputs(changeOutputs, txs.Codec) // sort the change outputs
  1158  	avax.SortTransferableOutputs(stakeOutputs, txs.Codec)  // sort stake outputs
  1159  	return inputs, changeOutputs, stakeOutputs, nil
  1160  }
  1161  
  1162  func (b *builder) authorizeSubnet(subnetID ids.ID, options *common.Options) (*secp256k1fx.Input, error) {
  1163  	ownerIntf, err := b.backend.GetSubnetOwner(options.Context(), subnetID)
  1164  	if err != nil {
  1165  		return nil, fmt.Errorf(
  1166  			"failed to fetch subnet owner for %q: %w",
  1167  			subnetID,
  1168  			err,
  1169  		)
  1170  	}
  1171  	owner, ok := ownerIntf.(*secp256k1fx.OutputOwners)
  1172  	if !ok {
  1173  		return nil, ErrUnknownOwnerType
  1174  	}
  1175  
  1176  	addrs := options.Addresses(b.addrs)
  1177  	minIssuanceTime := options.MinIssuanceTime()
  1178  	inputSigIndices, ok := common.MatchOwners(owner, addrs, minIssuanceTime)
  1179  	if !ok {
  1180  		// We can't authorize the subnet
  1181  		return nil, ErrInsufficientAuthorization
  1182  	}
  1183  	return &secp256k1fx.Input{
  1184  		SigIndices: inputSigIndices,
  1185  	}, nil
  1186  }
  1187  
  1188  func (b *builder) initCtx(tx txs.UnsignedTx) error {
  1189  	ctx, err := NewSnowContext(b.context.NetworkID, b.context.AVAXAssetID)
  1190  	if err != nil {
  1191  		return err
  1192  	}
  1193  
  1194  	tx.InitCtx(ctx)
  1195  	return nil
  1196  }