@avalabs/avalanchejs
Version:
Avalanche Platform JS Library
1,829 lines (1,674 loc) • 43.1 kB
text/typescript
/**
* @module
*
* This module contains builder functions which are responsible for building
* PVM transactions post e-upgrade (etna), which uses dynamic fees based on transaction complexity.
*/
import {
PlatformChainID,
PrimaryNetworkID,
} from '../../../constants/networkIDs';
import type { TransferOutput } from '../../../serializable';
import {
BigIntPr,
BlsSignature,
Input,
NodeId,
OutputOwners,
Stringpr,
TransferInput,
} from '../../../serializable';
import {
Bytes,
Id,
Int,
TransferableInput,
TransferableOutput,
} from '../../../serializable';
import { BaseTx as AvaxBaseTx } from '../../../serializable/avax';
import type { Utxo } from '../../../serializable/avax/utxo';
import { ID_LEN } from '../../../serializable/fxs/common/id';
import {
AddPermissionlessDelegatorTx,
AddPermissionlessValidatorTx,
AddSubnetValidatorTx,
BaseTx,
CreateChainTx,
CreateSubnetTx,
ExportTx,
ImportTx,
RemoveSubnetValidatorTx,
SubnetValidator,
TransferSubnetOwnershipTx,
ConvertSubnetToL1Tx,
IncreaseL1ValidatorBalanceTx,
DisableL1ValidatorTx,
SetL1ValidatorWeightTx,
RegisterL1ValidatorTx,
} from '../../../serializable/pvm';
import { createSignerOrSignerEmptyFromStrings } from '../../../serializable/pvm/signer';
import {
AddressMaps,
addressesFromBytes,
bytesCompare,
isTransferOut,
matchOwners,
} from '../../../utils';
import { compareTransferableOutputs } from '../../../utils/sort';
import { baseTxUnsafePvm, UnsignedTx } from '../../common';
import { addDimensions, createDimensions } from '../../common/fees/dimensions';
import type { Context } from '../../context';
import type { FeeState } from '../models';
import {
INTRINSIC_ADD_PERMISSIONLESS_DELEGATOR_TX_COMPLEXITIES,
INTRINSIC_ADD_PERMISSIONLESS_VALIDATOR_TX_COMPLEXITIES,
INTRINSIC_ADD_SUBNET_VALIDATOR_TX_COMPLEXITIES,
INTRINSIC_BASE_TX_COMPLEXITIES,
INTRINSIC_CONVERT_SUBNET_TO_L1_TX_COMPLEXITIES,
INTRINSIC_CREATE_CHAIN_TX_COMPLEXITIES,
INTRINSIC_CREATE_SUBNET_TX_COMPLEXITIES,
INTRINSIC_DISABLE_L1_VALIDATOR_TX_COMPLEXITIES,
INTRINSIC_EXPORT_TX_COMPLEXITIES,
INTRINSIC_IMPORT_TX_COMPLEXITIES,
INTRINSIC_INCREASE_L1_VALIDATOR_BALANCE_TX_COMPLEXITIES,
INTRINSIC_REGISTER_L1_VALIDATOR_TX_COMPLEXITIES,
INTRINSIC_REMOVE_SUBNET_VALIDATOR_TX_COMPLEXITIES,
INTRINSIC_SET_L1_VALIDATOR_WEIGHT_TX_COMPLEXITIES,
INTRINSIC_TRANSFER_SUBNET_OWNERSHIP_TX_COMPLEXITIES,
getAuthComplexity,
getInputComplexity,
getOutputComplexity,
getOwnerComplexity,
getSignerComplexity,
getBytesComplexity,
getL1ValidatorsComplexity,
} from '../txs/fee';
import { spend } from './spend';
import { useSpendableLockedUTXOs, useUnlockedUTXOs } from './spend-reducers';
import type { L1Validator } from '../../../serializable/fxs/pvm/L1Validator';
import { getWarpComplexity } from '../txs/fee/complexity';
/**
* Creates OutputOwners used for change outputs with the specified
* `changeAddressBytes` if provided, otherwise uses the `fromAddressesBytes`.
*/
const getChangeOutputOwners = ({
fromAddressesBytes,
changeAddressesBytes,
}: {
fromAddressesBytes: readonly Uint8Array[];
changeAddressesBytes?: readonly Uint8Array[];
}): OutputOwners => {
return OutputOwners.fromNative(
changeAddressesBytes ?? fromAddressesBytes,
0n,
1,
);
};
const getAddressMaps = ({
inputs,
inputUTXOs,
minIssuanceTime,
fromAddressesBytes,
}: {
inputs: readonly TransferableInput[];
inputUTXOs: readonly Utxo[];
minIssuanceTime: bigint;
fromAddressesBytes: readonly Uint8Array[];
}): AddressMaps => {
return AddressMaps.fromTransferableInputs(
inputs,
inputUTXOs,
minIssuanceTime,
fromAddressesBytes,
);
};
const getDefaultMinIssuanceTime = (): bigint => {
return BigInt(Math.floor(new Date().getTime() / 1000));
};
/**
* Common properties used in all PVM transaction builder functions.
*/
type CommonTxProps = Readonly<{
/**
* List of addresses that are used for change outputs.
*
* Defaults to the addresses provided in `fromAddressesBytes`.
*/
changeAddressesBytes?: readonly Uint8Array[];
/**
* The current fee state returned from `PVMApi.getFeeState()`.
*/
feeState: FeeState;
/**
* List of addresses that are used for selecting which UTXOs are signable.
*/
fromAddressesBytes: readonly Uint8Array[];
/**
* Contains arbitrary bytes (up to 256 bytes).
*
* Defaults to an empty byte array.
*/
memo?: Uint8Array;
/**
* Minimum time in Unix seconds.
*
* Defaults to the current time in Unix seconds.
*/
minIssuanceTime?: bigint;
/**
* List of UTXOs that are available to be spent.
*/
utxos: readonly Utxo[];
}>;
type TxProps<T extends Record<string, unknown>> = CommonTxProps & Readonly<T>;
type TxBuilderFn<T extends TxProps<Record<string, unknown>>> = (
props: T,
context: Context,
) => UnsignedTx;
export type NewBaseTxProps = TxProps<{
/**
* The desired output (change outputs will be added to them automatically).
*/
outputs: readonly TransferableOutput[];
}>;
/**
* Creates a new unsigned PVM base transaction (`BaseTx`) using calculated dynamic fees.
*
* @param props
* @param context
* @returns An UnsignedTx.
*/
export const newBaseTx: TxBuilderFn<NewBaseTxProps> = (
{
changeAddressesBytes,
feeState,
fromAddressesBytes,
memo = new Uint8Array(),
minIssuanceTime = getDefaultMinIssuanceTime(),
outputs,
utxos,
},
context,
) => {
const fromAddresses = addressesFromBytes(fromAddressesBytes);
const toBurn = new Map<string, bigint>();
outputs.forEach((out) => {
const assetId = out.assetId.value();
const amountToBurn = (toBurn.get(assetId) ?? 0n) + out.amount();
toBurn.set(assetId, amountToBurn);
});
const memoComplexity = getBytesComplexity(memo);
const outputComplexity = getOutputComplexity(outputs);
const complexity = addDimensions(
INTRINSIC_BASE_TX_COMPLEXITIES,
memoComplexity,
outputComplexity,
);
const spendResults = spend(
{
changeOutputOwners: getChangeOutputOwners({
changeAddressesBytes,
fromAddressesBytes,
}),
excessAVAX: 0n,
feeState,
fromAddresses,
initialComplexity: complexity,
minIssuanceTime,
shouldConsolidateOutputs: true,
toBurn,
utxos,
},
[useUnlockedUTXOs],
context,
);
const { changeOutputs, inputs, inputUTXOs } = spendResults;
const addressMaps = getAddressMaps({
inputs,
inputUTXOs,
minIssuanceTime,
fromAddressesBytes,
});
const allOutputs = [...outputs, ...changeOutputs].sort(
compareTransferableOutputs,
);
return new UnsignedTx(
new BaseTx(baseTxUnsafePvm(context, allOutputs, inputs, memo)),
inputUTXOs,
addressMaps,
);
};
export type NewImportTxProps = Omit<
TxProps<{
/**
* The locktime to write onto the UTXO.
*/
locktime?: bigint;
/**
* Base58 string of the source chain ID.
*/
sourceChainId: string;
/**
* The threshold to write on the UTXO.
*/
threshold?: number;
/**
* List of addresses to import into.
*/
toAddressesBytes: readonly Uint8Array[];
}>,
'changeAddressesBytes'
>;
/**
* Creates a new unsigned PVM import transaction (`ImportTx`) using calculated dynamic fees.
*
* @param props
* @param context
* @returns An UnsignedTx.
*/
export const newImportTx: TxBuilderFn<NewImportTxProps> = (
{
feeState,
fromAddressesBytes,
locktime,
memo = new Uint8Array(),
minIssuanceTime = getDefaultMinIssuanceTime(),
sourceChainId,
threshold,
toAddressesBytes,
utxos,
},
context,
) => {
const fromAddresses = addressesFromBytes(fromAddressesBytes);
const filteredUtxos = utxos.filter(
(utxo): utxo is Utxo<TransferOutput> =>
isTransferOut(utxo.output) &&
// Currently - only AVAX is allowed to be imported to the P-Chain
utxo.assetId.toString() === context.avaxAssetID,
);
const { importedInputs, importedAmounts, inputUtxos } = filteredUtxos.reduce<{
importedInputs: TransferableInput[];
importedAmounts: Record<string, bigint>;
inputUtxos: Utxo[];
}>(
(acc, utxo) => {
const { sigIndicies: inputSigIndices } =
matchOwners(utxo.getOutputOwners(), fromAddresses, minIssuanceTime) ||
{};
if (inputSigIndices === undefined) {
// We couldn't spend this UTXO, so we skip to the next one.
return acc;
}
const assetId = utxo.getAssetId();
return {
importedInputs: [
...acc.importedInputs,
new TransferableInput(
utxo.utxoId,
utxo.assetId,
new TransferInput(
utxo.output.amt,
new Input(inputSigIndices.map((value) => new Int(value))),
),
),
],
importedAmounts: {
...acc.importedAmounts,
[assetId]:
(acc.importedAmounts[assetId] ?? 0n) + utxo.output.amount(),
},
inputUtxos: [...acc.inputUtxos, utxo],
};
},
{ importedInputs: [], importedAmounts: {}, inputUtxos: [] },
);
if (importedInputs.length === 0) {
throw new Error('no UTXOs available to import');
}
const importedAvax = importedAmounts[context.avaxAssetID];
const addressMaps = AddressMaps.fromTransferableInputs(
importedInputs,
filteredUtxos,
minIssuanceTime,
fromAddressesBytes,
);
const outputs: TransferableOutput[] = Object.entries(importedAmounts)
.filter(([assetID]) => assetID !== context.avaxAssetID)
.map(([assetID, amount]) =>
TransferableOutput.fromNative(assetID, amount, toAddressesBytes),
);
const memoComplexity = getBytesComplexity(memo);
const inputComplexity = getInputComplexity(importedInputs);
const outputComplexity = getOutputComplexity(outputs);
const complexity = addDimensions(
INTRINSIC_IMPORT_TX_COMPLEXITIES,
memoComplexity,
inputComplexity,
outputComplexity,
);
const spendResults = spend(
{
changeOutputOwners: OutputOwners.fromNative(
toAddressesBytes,
locktime,
threshold,
),
excessAVAX: importedAvax,
feeState,
fromAddresses,
initialComplexity: complexity,
minIssuanceTime,
utxos: filteredUtxos,
},
[useUnlockedUTXOs],
context,
);
// Note: We don't use the `inputUTXOs` from `spendResults`
// for the `UnsignedTx` because we want to use the original
// UTXOs that were imported.
const { changeOutputs, inputs } = spendResults;
return new UnsignedTx(
new ImportTx(
new AvaxBaseTx(
new Int(context.networkID),
PlatformChainID,
[...outputs, ...changeOutputs].sort(compareTransferableOutputs),
inputs,
new Bytes(memo),
),
Id.fromString(sourceChainId),
importedInputs.sort(TransferableInput.compare),
),
inputUtxos,
addressMaps,
);
};
export type NewExportTxProps = TxProps<{
/**
* Base58 string of the destination chain ID.
*/
destinationChainId: string;
/**
* List of outputs to create.
*/
outputs: readonly TransferableOutput[];
}>;
/**
* Creates a new unsigned PVM export transaction (`ExportTx`) using calculated dynamic fees.
*
* @param props
* @param context
* @returns An UnsignedTx.
*/
export const newExportTx: TxBuilderFn<NewExportTxProps> = (
{
changeAddressesBytes,
destinationChainId,
feeState,
fromAddressesBytes,
memo = new Uint8Array(),
minIssuanceTime = getDefaultMinIssuanceTime(),
outputs,
utxos,
},
context,
) => {
const fromAddresses = addressesFromBytes(fromAddressesBytes);
const toBurn = new Map<string, bigint>();
outputs.forEach((output) => {
const assetId = output.assetId.value();
toBurn.set(assetId, (toBurn.get(assetId) ?? 0n) + output.output.amount());
});
const memoComplexity = getBytesComplexity(memo);
const outputComplexity = getOutputComplexity(outputs);
const complexity = addDimensions(
INTRINSIC_EXPORT_TX_COMPLEXITIES,
memoComplexity,
outputComplexity,
);
const spendResults = spend(
{
changeOutputOwners: getChangeOutputOwners({
changeAddressesBytes,
fromAddressesBytes,
}),
excessAVAX: 0n,
feeState,
fromAddresses,
initialComplexity: complexity,
minIssuanceTime,
toBurn,
utxos,
},
[useUnlockedUTXOs],
context,
);
const { changeOutputs, inputs, inputUTXOs } = spendResults;
const addressMaps = getAddressMaps({
inputs,
inputUTXOs,
minIssuanceTime,
fromAddressesBytes,
});
return new UnsignedTx(
new ExportTx(
new AvaxBaseTx(
new Int(context.networkID),
PlatformChainID,
changeOutputs,
inputs,
new Bytes(memo),
),
Id.fromString(destinationChainId),
[...outputs].sort(compareTransferableOutputs),
),
inputUTXOs,
addressMaps,
);
};
export type NewCreateSubnetTxProps = TxProps<{
/**
* The locktime to write onto the UTXO.
*/
locktime?: bigint;
subnetOwners: readonly Uint8Array[];
/**
* The threshold to write on the UTXO.
*/
threshold?: number;
}>;
/**
* Creates a new unsigned PVM create subnet transaction (`CreateSubnetTx`) using calculated dynamic fees.
*
* @param props
* @param context
* @returns An UnsignedTx.
*/
export const newCreateSubnetTx: TxBuilderFn<NewCreateSubnetTxProps> = (
{
changeAddressesBytes,
fromAddressesBytes,
feeState,
locktime,
memo = new Uint8Array(),
minIssuanceTime = getDefaultMinIssuanceTime(),
subnetOwners,
threshold,
utxos,
},
context,
) => {
const memoComplexity = getBytesComplexity(memo);
const ownerComplexity = getOwnerComplexity(
OutputOwners.fromNative(subnetOwners, locktime, threshold),
);
const complexity = addDimensions(
INTRINSIC_CREATE_SUBNET_TX_COMPLEXITIES,
memoComplexity,
ownerComplexity,
);
const spendResults = spend(
{
changeOutputOwners: getChangeOutputOwners({
changeAddressesBytes,
fromAddressesBytes,
}),
excessAVAX: 0n,
feeState,
fromAddresses: addressesFromBytes(fromAddressesBytes),
initialComplexity: complexity,
minIssuanceTime,
utxos,
},
[useUnlockedUTXOs],
context,
);
const { changeOutputs, inputs, inputUTXOs } = spendResults;
const addressMaps = getAddressMaps({
inputs,
inputUTXOs,
minIssuanceTime,
fromAddressesBytes,
});
const createSubnetTx = new CreateSubnetTx(
AvaxBaseTx.fromNative(
context.networkID,
context.pBlockchainID,
changeOutputs,
inputs,
memo,
),
OutputOwners.fromNative(subnetOwners, locktime, threshold),
);
return new UnsignedTx(createSubnetTx, inputUTXOs, addressMaps);
};
export type NewCreateChainTxProps = TxProps<{
/**
* A human readable name for the chain.
*/
chainName: string;
/**
* IDs of the feature extensions running on the new chain.
*/
fxIds: readonly string[];
/**
* JSON config for the genesis data.
*/
genesisData: Record<string, unknown>;
/**
* Indices of subnet owners.
*/
subnetAuth: readonly number[];
/**
* ID of the subnet (Avalanche L1) that validates this chain.
*/
subnetId: string;
/**
* ID of the VM running on the new chain.
*/
vmId: string;
}>;
/**
* Creates a new unsigned PVM create chain transaction (`CreateChainTx`) using calculated dynamic fees.
*
* @param props
* @param context
* @returns An UnsignedTx.
*/
export const newCreateChainTx: TxBuilderFn<NewCreateChainTxProps> = (
{
changeAddressesBytes,
chainName,
feeState,
fromAddressesBytes,
fxIds,
genesisData,
memo = new Uint8Array(),
minIssuanceTime = getDefaultMinIssuanceTime(),
subnetAuth,
subnetId,
utxos,
vmId,
},
context,
) => {
const genesisBytes = new Bytes(
new TextEncoder().encode(JSON.stringify(genesisData)),
);
const subnetAuthInput = Input.fromNative(subnetAuth);
const dynamicComplexity = createDimensions({
bandwidth:
fxIds.length * ID_LEN +
chainName.length +
genesisBytes.length +
memo.length,
dbRead: 0,
dbWrite: 0,
compute: 0,
});
const authComplexity = getAuthComplexity(subnetAuthInput);
const complexity = addDimensions(
INTRINSIC_CREATE_CHAIN_TX_COMPLEXITIES,
dynamicComplexity,
authComplexity,
);
const spendResults = spend(
{
changeOutputOwners: getChangeOutputOwners({
changeAddressesBytes,
fromAddressesBytes,
}),
excessAVAX: 0n,
feeState,
fromAddresses: addressesFromBytes(fromAddressesBytes),
initialComplexity: complexity,
minIssuanceTime,
utxos,
},
[useUnlockedUTXOs],
context,
);
const { changeOutputs, inputs, inputUTXOs } = spendResults;
const addressMaps = getAddressMaps({
inputs,
inputUTXOs,
minIssuanceTime,
fromAddressesBytes,
});
const createChainTx = new CreateChainTx(
AvaxBaseTx.fromNative(
context.networkID,
context.pBlockchainID,
changeOutputs,
inputs,
memo,
),
Id.fromString(subnetId),
new Stringpr(chainName),
Id.fromString(vmId),
fxIds.map(Id.fromString.bind(Id)),
genesisBytes,
subnetAuthInput,
);
return new UnsignedTx(createChainTx, inputUTXOs, addressMaps);
};
export type NewAddSubnetValidatorTxProps = TxProps<{
end: bigint;
nodeId: string;
start: bigint;
/**
* Indices of subnet owners.
*/
subnetAuth: readonly number[];
/**
* ID of the subnet (Avalanche L1) that validates this chain.
*/
subnetId: string;
weight: bigint;
}>;
/**
* Creates a new unsigned PVM add subnet validator transaction
* (`AddSubnetValidatorTx`) using calculated dynamic fees.
*
* @param props
* @param context
* @returns An UnsignedTx.
*/
export const newAddSubnetValidatorTx: TxBuilderFn<
NewAddSubnetValidatorTxProps
> = (
{
changeAddressesBytes,
end,
feeState,
fromAddressesBytes,
nodeId,
memo = new Uint8Array(),
minIssuanceTime = getDefaultMinIssuanceTime(),
start,
subnetAuth,
subnetId,
utxos,
weight,
},
context,
) => {
const memoComplexity = getBytesComplexity(memo);
const authComplexity = getAuthComplexity(Input.fromNative(subnetAuth));
const complexity = addDimensions(
INTRINSIC_ADD_SUBNET_VALIDATOR_TX_COMPLEXITIES,
memoComplexity,
authComplexity,
);
const spendResults = spend(
{
changeOutputOwners: getChangeOutputOwners({
changeAddressesBytes,
fromAddressesBytes,
}),
excessAVAX: 0n,
feeState,
fromAddresses: addressesFromBytes(fromAddressesBytes),
initialComplexity: complexity,
minIssuanceTime,
utxos,
},
[useUnlockedUTXOs],
context,
);
const { changeOutputs, inputs, inputUTXOs } = spendResults;
const addressMaps = getAddressMaps({
inputs,
inputUTXOs,
minIssuanceTime,
fromAddressesBytes,
});
const addSubnetValidatorTx = new AddSubnetValidatorTx(
AvaxBaseTx.fromNative(
context.networkID,
context.pBlockchainID,
changeOutputs,
inputs,
memo,
),
SubnetValidator.fromNative(
nodeId,
start,
end,
weight,
Id.fromString(subnetId),
),
Input.fromNative(subnetAuth),
);
return new UnsignedTx(addSubnetValidatorTx, inputUTXOs, addressMaps);
};
export type NewRemoveSubnetValidatorTxProps = TxProps<{
nodeId: string;
/**
* Indices of subnet owners.
*/
subnetAuth: readonly number[];
/**
* ID of the subnet (Avalanche L1) that validates this chain.
*/
subnetId: string;
}>;
/**
* Creates a new unsigned PVM remove subnet validator transaction
* (`RemoveSubnetValidatorTx`) using calculated dynamic fees.
*
* @param props
* @param context
* @returns An UnsignedTx.
*/
export const newRemoveSubnetValidatorTx: TxBuilderFn<
NewRemoveSubnetValidatorTxProps
> = (
{
changeAddressesBytes,
fromAddressesBytes,
feeState,
nodeId,
memo = new Uint8Array(),
minIssuanceTime = getDefaultMinIssuanceTime(),
subnetAuth,
subnetId,
utxos,
},
context,
) => {
const memoComplexity = getBytesComplexity(memo);
const authComplexity = getAuthComplexity(Input.fromNative(subnetAuth));
const complexity = addDimensions(
INTRINSIC_REMOVE_SUBNET_VALIDATOR_TX_COMPLEXITIES,
memoComplexity,
authComplexity,
);
const spendResults = spend(
{
changeOutputOwners: getChangeOutputOwners({
changeAddressesBytes,
fromAddressesBytes,
}),
excessAVAX: 0n,
feeState,
fromAddresses: addressesFromBytes(fromAddressesBytes),
initialComplexity: complexity,
minIssuanceTime,
utxos,
},
[useUnlockedUTXOs],
context,
);
const { changeOutputs, inputs, inputUTXOs } = spendResults;
const addressMaps = getAddressMaps({
inputs,
inputUTXOs,
minIssuanceTime,
fromAddressesBytes,
});
const removeSubnetValidatorTx = new RemoveSubnetValidatorTx(
AvaxBaseTx.fromNative(
context.networkID,
context.pBlockchainID,
changeOutputs,
inputs,
memo,
),
NodeId.fromString(nodeId),
Id.fromString(subnetId),
Input.fromNative(subnetAuth),
);
return new UnsignedTx(removeSubnetValidatorTx, inputUTXOs, addressMaps);
};
export type NewAddPermissionlessValidatorTxProps = TxProps<{
delegatorRewardsOwner: readonly Uint8Array[];
/**
* The Unix time based on p-chain timestamp when the validator
* stops validating the Primary Network (and staked AVAX is returned).
*/
end: bigint;
/**
* Optional. The number locktime field created in the resulting reward outputs.
* @default 0n
*/
locktime?: bigint;
/**
* The node ID of the validator being added.
*/
nodeId: string;
/**
* The BLS public key.
*/
publicKey?: Uint8Array;
/**
* The addresses which will receive the rewards from the delegated stake.
* Given addresses will share the reward UTXO.
*/
rewardAddresses: readonly Uint8Array[];
/**
* A number for the percentage times 10,000 of reward to be given to the
* validator when someone delegates to them.
*/
shares: number;
/**
* The BLS signature.
*/
signature?: Uint8Array;
/**
* Which asset to stake. Defaults to AVAX.
*/
stakingAssetId?: string;
/**
* The Unix time based on p-chain timestamp when the validator
* starts validating the Primary Network.
*/
start: bigint;
/**
* ID of the subnet (Avalanche L1) that validates this chain.
*/
subnetId: string;
/**
* Optional. The number of signatures required to spend the funds in the
* resultant reward UTXO.
*
* @default 1
*/
threshold?: number;
/**
* The amount being locked for validation in nAVAX.
*/
weight: bigint;
}>;
/**
* Creates a new unsigned PVM add permissionless validator transaction
* (`AddPermissionlessValidatorTx`) using calculated dynamic fees.
*
* @param props
* @param context
* @returns An UnsignedTx.
*/
export const newAddPermissionlessValidatorTx: TxBuilderFn<
NewAddPermissionlessValidatorTxProps
> = (
{
changeAddressesBytes,
delegatorRewardsOwner,
end,
feeState,
fromAddressesBytes,
locktime = 0n,
nodeId,
memo = new Uint8Array(),
minIssuanceTime = getDefaultMinIssuanceTime(),
publicKey,
rewardAddresses,
shares,
signature,
stakingAssetId,
start,
subnetId,
threshold = 1,
utxos,
weight,
},
context,
) => {
const isPrimaryNetwork = subnetId === PrimaryNetworkID.toString();
const assetId = stakingAssetId ?? context.avaxAssetID;
// Check if we use correct asset if on primary network
if (isPrimaryNetwork && assetId !== context.avaxAssetID)
throw new Error('Staking asset ID must be AVAX for the primary network.');
const toStake = new Map<string, bigint>([[assetId, weight]]);
const signer = createSignerOrSignerEmptyFromStrings(publicKey, signature);
const validatorOutputOwners = OutputOwners.fromNative(
rewardAddresses,
locktime,
threshold,
);
const delegatorOutputOwners = OutputOwners.fromNative(
delegatorRewardsOwner,
0n,
);
const memoComplexity = getBytesComplexity(memo);
const signerComplexity = getSignerComplexity(signer);
const validatorOwnerComplexity = getOwnerComplexity(validatorOutputOwners);
const delegatorOwnerComplexity = getOwnerComplexity(delegatorOutputOwners);
const complexity = addDimensions(
INTRINSIC_ADD_PERMISSIONLESS_VALIDATOR_TX_COMPLEXITIES,
memoComplexity,
signerComplexity,
validatorOwnerComplexity,
delegatorOwnerComplexity,
);
const spendResults = spend(
{
changeOutputOwners: getChangeOutputOwners({
changeAddressesBytes,
fromAddressesBytes,
}),
excessAVAX: 0n,
feeState,
fromAddresses: addressesFromBytes(fromAddressesBytes),
initialComplexity: complexity,
minIssuanceTime,
shouldConsolidateOutputs: true,
toStake,
utxos,
},
[useSpendableLockedUTXOs, useUnlockedUTXOs],
context,
);
const { changeOutputs, inputs, inputUTXOs, stakeOutputs } = spendResults;
const addressMaps = getAddressMaps({
inputs,
inputUTXOs,
minIssuanceTime,
fromAddressesBytes,
});
const validatorTx = new AddPermissionlessValidatorTx(
AvaxBaseTx.fromNative(
context.networkID,
context.pBlockchainID,
changeOutputs,
inputs,
memo,
),
SubnetValidator.fromNative(
nodeId,
start,
end,
weight,
Id.fromString(subnetId),
),
signer,
stakeOutputs,
validatorOutputOwners,
delegatorOutputOwners,
new Int(shares),
);
return new UnsignedTx(validatorTx, inputUTXOs, addressMaps);
};
export type NewAddPermissionlessDelegatorTxProps = TxProps<{
/**
* The Unix time based on p-chain timestamp when the delegation stops
* (and staked AVAX is returned).
*/
end: bigint;
/**
* Optional. The number locktime field created in the resulting reward outputs.
* @default 0n
*/
locktime?: bigint;
/**
* The node ID of the validator being delegated to.
*/
nodeId: string;
/**
* The addresses which will receive the rewards from the delegated stake.
* Given addresses will share the reward UTXO.
*/
rewardAddresses: readonly Uint8Array[];
/**
* Which asset to stake. Defaults to AVAX.
*/
stakingAssetId?: string;
/**
* The Unix time based on p-chain timestamp when the delegation starts.
*/
start: bigint;
/**
* ID of the subnet (Avalanche L1) being delegated to.
*/
subnetId: string;
/**
* Optional. The number of signatures required to spend the funds in the
* resultant reward UTXO.
*
* @default 1
*/
threshold?: number;
/**
* The amount being delegated in nAVAX.
*/
weight: bigint;
}>;
/**
* Creates a new unsigned PVM add permissionless delegator transaction
* (`AddPermissionlessDelegatorTx`) using calculated dynamic fees.
*
* @param props
* @param context
* @returns An UnsignedTx.
*/
export const newAddPermissionlessDelegatorTx: TxBuilderFn<
NewAddPermissionlessDelegatorTxProps
> = (
{
changeAddressesBytes,
end,
feeState,
fromAddressesBytes,
locktime = 0n,
nodeId,
memo = new Uint8Array(),
minIssuanceTime = getDefaultMinIssuanceTime(),
rewardAddresses,
stakingAssetId,
start,
subnetId,
threshold = 1,
utxos,
weight,
},
context,
) => {
const isPrimaryNetwork = subnetId === PrimaryNetworkID.toString();
const assetId = stakingAssetId ?? context.avaxAssetID;
// Check if we use correct asset if on primary network
if (isPrimaryNetwork && assetId !== context.avaxAssetID)
throw new Error('Staking asset ID must be AVAX for the primary network.');
const toStake = new Map<string, bigint>([[assetId, weight]]);
const delegatorRewardsOwner = OutputOwners.fromNative(
rewardAddresses,
locktime,
threshold,
);
const memoComplexity = getBytesComplexity(memo);
const ownerComplexity = getOwnerComplexity(delegatorRewardsOwner);
const complexity = addDimensions(
INTRINSIC_ADD_PERMISSIONLESS_DELEGATOR_TX_COMPLEXITIES,
memoComplexity,
ownerComplexity,
);
const spendResults = spend(
{
changeOutputOwners: getChangeOutputOwners({
changeAddressesBytes,
fromAddressesBytes,
}),
excessAVAX: 0n,
feeState,
fromAddresses: addressesFromBytes(fromAddressesBytes),
initialComplexity: complexity,
minIssuanceTime,
shouldConsolidateOutputs: true,
toStake,
utxos,
},
[useSpendableLockedUTXOs, useUnlockedUTXOs],
context,
);
const { changeOutputs, inputs, inputUTXOs, stakeOutputs } = spendResults;
const addressMaps = getAddressMaps({
inputs,
inputUTXOs,
minIssuanceTime,
fromAddressesBytes,
});
const delegatorTx = new AddPermissionlessDelegatorTx(
AvaxBaseTx.fromNative(
context.networkID,
context.pBlockchainID,
changeOutputs,
inputs,
memo,
),
SubnetValidator.fromNative(
nodeId,
start,
end,
weight,
Id.fromString(subnetId),
),
stakeOutputs,
delegatorRewardsOwner,
);
return new UnsignedTx(delegatorTx, inputUTXOs, addressMaps);
};
export type NewTransferSubnetOwnershipTxProps = TxProps<{
/**
* Optional. The number locktime field created in the resulting reward outputs.
* @default 0n
*/
locktime?: bigint;
/**
* Indices of existing subnet owners.
*/
subnetAuth: readonly number[];
/**
* ID of the subnet (Avalanche L1).
*/
subnetId: string;
/**
* The new owner(s) addresses.
*/
subnetOwners: readonly Uint8Array[];
/**
* Optional. The number of signatures required to spend the funds in the
* resultant reward UTXO.
*
* @default 1
*/
threshold?: number;
}>;
/**
* Creates a new unsigned PVM transfer subnet ownership transaction
* (`TransferSubnetOwnershipTx`) using calculated dynamic fees.
*
* @param props
* @param context
* @returns An UnsignedTx.
*/
export const newTransferSubnetOwnershipTx: TxBuilderFn<
NewTransferSubnetOwnershipTxProps
> = (
{
changeAddressesBytes,
fromAddressesBytes,
feeState,
locktime = 0n,
memo = new Uint8Array(),
minIssuanceTime = getDefaultMinIssuanceTime(),
subnetAuth,
subnetId,
subnetOwners,
threshold = 1,
utxos,
},
context,
) => {
const memoComplexity = getBytesComplexity(memo);
const authComplexity = getAuthComplexity(Input.fromNative(subnetAuth));
const ownerComplexity = getOwnerComplexity(
OutputOwners.fromNative(subnetOwners, locktime, threshold),
);
const complexity = addDimensions(
INTRINSIC_TRANSFER_SUBNET_OWNERSHIP_TX_COMPLEXITIES,
memoComplexity,
authComplexity,
ownerComplexity,
);
const spendResults = spend(
{
changeOutputOwners: getChangeOutputOwners({
changeAddressesBytes,
fromAddressesBytes,
}),
excessAVAX: 0n,
feeState,
fromAddresses: addressesFromBytes(fromAddressesBytes),
initialComplexity: complexity,
minIssuanceTime,
utxos,
},
[useUnlockedUTXOs],
context,
);
const { changeOutputs, inputs, inputUTXOs } = spendResults;
const addressMaps = getAddressMaps({
inputs,
inputUTXOs,
minIssuanceTime,
fromAddressesBytes,
});
return new UnsignedTx(
new TransferSubnetOwnershipTx(
AvaxBaseTx.fromNative(
context.networkID,
context.pBlockchainID,
changeOutputs,
inputs,
memo,
),
Id.fromString(subnetId),
Input.fromNative(subnetAuth),
OutputOwners.fromNative(subnetOwners, locktime, threshold),
),
inputUTXOs,
addressMaps,
);
};
export type NewConvertSubnetToL1TxProps = TxProps<{
/**
* Specifies which chain the manager is deployed on
*/
chainId: string;
/**
* Specifies the subnet to be converted
*/
subnetId: string;
/**
* Specifies the address of the validator manager
*/
address: Uint8Array;
/**
* Initial continuous-fee-paying validators for the L1
*/
validators: L1Validator[];
/**
* Indices of existing subnet owners.
*/
subnetAuth: readonly number[];
}>;
/**
* Creates a new unsigned PVM convert subnet to L1 transaction
* (`ConvertSubnetToL1Tx`) using calculated dynamic fees.
*
* @param props
* @param context
* @returns An UnsignedTx.
*/
export const newConvertSubnetToL1Tx: TxBuilderFn<
NewConvertSubnetToL1TxProps
> = (
{
address,
chainId,
changeAddressesBytes,
feeState,
fromAddressesBytes,
memo = new Uint8Array(),
minIssuanceTime = BigInt(Math.floor(new Date().getTime() / 1000)),
subnetAuth,
subnetId,
utxos,
validators,
},
context,
) => {
if (validators.find((validator) => validator.getWeight() <= 0n)) {
throw new Error('Validator weight must be greater than 0');
}
const bytesComplexity = getBytesComplexity(memo, address);
const authComplexity = getAuthComplexity(Input.fromNative(subnetAuth));
const validatorComplexity = getL1ValidatorsComplexity(validators);
const sortedValidators = validators.sort((a, b) =>
bytesCompare(a.nodeId.toBytes(), b.nodeId.toBytes()),
);
const toBurn = new Map<string, bigint>();
for (const validator of sortedValidators) {
toBurn.set(
context.avaxAssetID,
(toBurn.get(context.avaxAssetID) ?? 0n) + validator.getBalance().value(),
);
}
const complexity = addDimensions(
INTRINSIC_CONVERT_SUBNET_TO_L1_TX_COMPLEXITIES,
bytesComplexity,
validatorComplexity,
authComplexity,
);
const spendResults = spend(
{
changeOutputOwners: getChangeOutputOwners({
changeAddressesBytes,
fromAddressesBytes,
}),
excessAVAX: 0n,
feeState,
fromAddresses: addressesFromBytes(fromAddressesBytes),
initialComplexity: complexity,
minIssuanceTime,
toBurn,
utxos,
},
[useUnlockedUTXOs],
context,
);
const { changeOutputs, inputs, inputUTXOs } = spendResults;
const addressMaps = getAddressMaps({
inputs,
inputUTXOs,
minIssuanceTime,
fromAddressesBytes,
});
return new UnsignedTx(
new ConvertSubnetToL1Tx(
AvaxBaseTx.fromNative(
context.networkID,
context.pBlockchainID,
changeOutputs,
inputs,
memo,
),
Id.fromString(subnetId),
Id.fromString(chainId),
new Bytes(address),
sortedValidators,
Input.fromNative(subnetAuth),
),
inputUTXOs,
addressMaps,
);
};
export type NewRegisterL1ValidatorTx = TxProps<{
/**
* Must be less than or equal to the sum of the AVAX inputs minus
* the sum of the AVAX outputs minus the fee.
*/
balance: bigint;
/**
* A BLS signature proving ownership of the BLS public key specified in the
* `message` for this validator.
*/
blsSignature: Uint8Array;
/**
* Warp message bytes.
*
* Expected to be a signed Warp message containing an AddressedCall payload
* with the RegisterL1ValidatorMessage.
*/
message: Uint8Array;
}>;
/**
* Creates a new unsigned PVM register L1 validator transaction
* (`RegisterL1ValidatorTx`) using calculated dynamic fees.
*
* @param props
* @param context
* @returns An UnsignedTx.
*/
export const newRegisterL1ValidatorTx: TxBuilderFn<NewRegisterL1ValidatorTx> = (
{
balance,
blsSignature,
changeAddressesBytes,
feeState,
fromAddressesBytes,
memo = new Uint8Array(),
message,
minIssuanceTime = BigInt(Math.floor(new Date().getTime() / 1000)),
utxos,
},
context,
) => {
const messageBytes = new Bytes(message);
const toBurn = new Map<string, bigint>([[context.avaxAssetID, balance]]);
const bytesComplexity = getBytesComplexity(memo);
const warpComplexity = getWarpComplexity(messageBytes);
const complexity = addDimensions(
INTRINSIC_REGISTER_L1_VALIDATOR_TX_COMPLEXITIES,
bytesComplexity,
warpComplexity,
);
const spendResults = spend(
{
changeOutputOwners: getChangeOutputOwners({
changeAddressesBytes,
fromAddressesBytes,
}),
excessAVAX: 0n,
feeState,
fromAddresses: addressesFromBytes(fromAddressesBytes),
initialComplexity: complexity,
minIssuanceTime,
toBurn,
utxos,
},
[useUnlockedUTXOs],
context,
);
const { changeOutputs, inputs, inputUTXOs } = spendResults;
const addressMaps = getAddressMaps({
inputs,
inputUTXOs,
minIssuanceTime,
fromAddressesBytes,
});
return new UnsignedTx(
new RegisterL1ValidatorTx(
AvaxBaseTx.fromNative(
context.networkID,
context.pBlockchainID,
changeOutputs,
inputs,
memo,
),
new BigIntPr(balance),
BlsSignature.fromSignatureBytes(blsSignature),
messageBytes,
),
inputUTXOs,
addressMaps,
);
};
export type SetL1ValidatorWeightTxProps = TxProps<{
/**
* Warp message bytes.
*
* A L1ValidatorWeightMessage payload.
*/
message: Uint8Array;
}>;
/**
* Creates a new unsigned PVM set L1 validator weight transaction
* (`SetL1ValidatorWeightTx`) using calculated dynamic fees.
*
* @param props
* @param context
* @returns An UnsignedTx.
*/
export const newSetL1ValidatorWeightTx: TxBuilderFn<
SetL1ValidatorWeightTxProps
> = (
{
changeAddressesBytes,
feeState,
fromAddressesBytes,
memo = new Uint8Array(),
message,
minIssuanceTime = BigInt(Math.floor(new Date().getTime() / 1000)),
utxos,
},
context,
) => {
const messageBytes = new Bytes(message);
const memoComplexity = getBytesComplexity(memo);
const warpComplexity = getWarpComplexity(messageBytes);
const complexity = addDimensions(
INTRINSIC_SET_L1_VALIDATOR_WEIGHT_TX_COMPLEXITIES,
memoComplexity,
warpComplexity,
);
const spendResults = spend(
{
changeOutputOwners: getChangeOutputOwners({
changeAddressesBytes,
fromAddressesBytes,
}),
excessAVAX: 0n,
feeState,
fromAddresses: addressesFromBytes(fromAddressesBytes),
initialComplexity: complexity,
minIssuanceTime,
utxos,
},
[useUnlockedUTXOs],
context,
);
const { changeOutputs, inputs, inputUTXOs } = spendResults;
const addressMaps = getAddressMaps({
inputs,
inputUTXOs,
minIssuanceTime,
fromAddressesBytes,
});
return new UnsignedTx(
new SetL1ValidatorWeightTx(
AvaxBaseTx.fromNative(
context.networkID,
context.pBlockchainID,
changeOutputs,
inputs,
memo,
),
messageBytes,
),
inputUTXOs,
addressMaps,
);
};
export type IncreaseL1ValidatorBalanceTxProps = TxProps<{
/**
* Amount to increase the balance by.
*
* Must be greater than 0.
*/
balance: bigint;
/**
* ID corresponding to the validator
*/
validationId: string;
}>;
/**
* Creates a new unsigned PVM increase L1 validator balance transaction
* (`IncreaseL1ValidatorBalanceTx`) using calculated dynamic fees.
*
* @param props
* @param context
* @returns An UnsignedTx.
*/
export const newIncreaseL1ValidatorBalanceTx: TxBuilderFn<
IncreaseL1ValidatorBalanceTxProps
> = (
{
balance,
changeAddressesBytes,
feeState,
fromAddressesBytes,
memo = new Uint8Array(),
minIssuanceTime = BigInt(Math.floor(new Date().getTime() / 1000)),
utxos,
validationId,
},
context,
) => {
if (balance <= 0n) {
throw new Error('Balance must be greater than 0');
}
const toBurn = new Map<string, bigint>([[context.avaxAssetID, balance]]);
const bytesComplexity = getBytesComplexity(memo);
const complexity = addDimensions(
INTRINSIC_INCREASE_L1_VALIDATOR_BALANCE_TX_COMPLEXITIES,
bytesComplexity,
);
const spendResults = spend(
{
changeOutputOwners: getChangeOutputOwners({
changeAddressesBytes,
fromAddressesBytes,
}),
excessAVAX: 0n,
feeState,
fromAddresses: addressesFromBytes(fromAddressesBytes),
initialComplexity: complexity,
minIssuanceTime,
toBurn,
utxos,
},
[useUnlockedUTXOs],
context,
);
const { changeOutputs, inputs, inputUTXOs } = spendResults;
const addressMaps = getAddressMaps({
inputs,
inputUTXOs,
minIssuanceTime,
fromAddressesBytes,
});
return new UnsignedTx(
new IncreaseL1ValidatorBalanceTx(
AvaxBaseTx.fromNative(
context.networkID,
context.pBlockchainID,
changeOutputs,
inputs,
memo,
),
Id.fromString(validationId),
new BigIntPr(balance),
),
inputUTXOs,
addressMaps,
);
};
export type DisableL1ValidatorTxProps = TxProps<{
/**
* Indices of owners.
*/
disableAuth: readonly number[];
/**
* ID corresponding to the validator
*/
validationId: string;
}>;
/**
* Creates a new unsigned PVM disable subnet validator transaction
* (`DisableL1ValidatorTx`) using calculated dynamic fees.
*
* @param props
* @param context
* @returns An UnsignedTx.
*/
export const newDisableL1ValidatorTx: TxBuilderFn<DisableL1ValidatorTxProps> = (
{
changeAddressesBytes,
disableAuth,
feeState,
fromAddressesBytes,
memo = new Uint8Array(),
minIssuanceTime = BigInt(Math.floor(new Date().getTime() / 1000)),
utxos,
validationId,
},
context,
) => {
const disableAuthInput = Input.fromNative(disableAuth);
const bytesComplexity = getBytesComplexity(memo);
const authComplexity = getAuthComplexity(disableAuthInput);
const complexity = addDimensions(
INTRINSIC_DISABLE_L1_VALIDATOR_TX_COMPLEXITIES,
bytesComplexity,
authComplexity,
);
const spendResults = spend(
{
changeOutputOwners: getChangeOutputOwners({
changeAddressesBytes,
fromAddressesBytes,
}),
excessAVAX: 0n,
feeState,
fromAddresses: addressesFromBytes(fromAddressesBytes),
initialComplexity: complexity,
minIssuanceTime,
utxos,
},
[useUnlockedUTXOs],
context,
);
const { changeOutputs, inputs, inputUTXOs } = spendResults;
const addressMaps = getAddressMaps({
inputs,
inputUTXOs,
minIssuanceTime,
fromAddressesBytes,
});
return new UnsignedTx(
new DisableL1ValidatorTx(
AvaxBaseTx.fromNative(
context.networkID,
context.pBlockchainID,
changeOutputs,
inputs,
memo,
),
Id.fromString(validationId),
disableAuthInput,
),
inputUTXOs,
addressMaps,
);
};