diff --git a/modules/sdk-coin-avaxp/src/avaxp.ts b/modules/sdk-coin-avaxp/src/avaxp.ts index 88b85820ee..7d357b0e42 100644 --- a/modules/sdk-coin-avaxp/src/avaxp.ts +++ b/modules/sdk-coin-avaxp/src/avaxp.ts @@ -151,10 +151,15 @@ export class AvaxP extends BaseCoin { } switch (explainedTx.type) { + // @deprecated case TransactionType.AddDelegator: case TransactionType.AddValidator: this.validateStakingTx(stakingOptions, explainedTx); break; + case TransactionType.AddPermissionlessDelegator: + case TransactionType.AddPermissionlessValidator: + this.validateStakingTx(stakingOptions, explainedTx); + break; case TransactionType.Export: if (!params.txParams.recipients || params.txParams.recipients?.length !== 1) { throw new Error('Export Tx requires a recipient'); diff --git a/modules/sdk-coin-avaxp/src/lib/delegatorTxBuilder.ts b/modules/sdk-coin-avaxp/src/lib/delegatorTxBuilder.ts index 8b17349cf2..8faf3c0792 100644 --- a/modules/sdk-coin-avaxp/src/lib/delegatorTxBuilder.ts +++ b/modules/sdk-coin-avaxp/src/lib/delegatorTxBuilder.ts @@ -16,7 +16,7 @@ import { UnsignedTx, } from 'avalanche/dist/apis/platformvm'; import { BinTools, BN } from 'avalanche'; -import { SECP256K1_Transfer_Output, Tx, BaseTx } from './iface'; +import { SECP256K1_Transfer_Output, DeprecatedTx, BaseTx } from './iface'; import utils from './utils'; import { Credential } from 'avalanche/dist/common'; import { recoverUtxos } from './utxoEngine'; @@ -149,7 +149,7 @@ export class DelegatorTxBuilder extends TransactionBuilder { // endregion /** @inheritdoc */ - initBuilder(tx: Tx): this { + initBuilder(tx: DeprecatedTx): this { super.initBuilder(tx); const baseTx: BaseTx = tx.getUnsignedTx().getTransaction(); if (!this.verifyTxType(baseTx)) { diff --git a/modules/sdk-coin-avaxp/src/lib/exportInCTxBuilder.ts b/modules/sdk-coin-avaxp/src/lib/exportInCTxBuilder.ts index 0dfa57548b..039d4a00e4 100644 --- a/modules/sdk-coin-avaxp/src/lib/exportInCTxBuilder.ts +++ b/modules/sdk-coin-avaxp/src/lib/exportInCTxBuilder.ts @@ -14,7 +14,7 @@ import { import utils from './utils'; import { BN, Buffer as BufferAvax } from 'avalanche'; import { Transaction } from './transaction'; -import { Tx, BaseTx, DecodedUtxoObj } from './iface'; +import { DeprecatedTx, BaseTx, DecodedUtxoObj } from './iface'; import { AtomicInCTransactionBuilder } from './atomicInCTransactionBuilder'; export class ExportInCTxBuilder extends AtomicInCTransactionBuilder { @@ -75,7 +75,7 @@ export class ExportInCTxBuilder extends AtomicInCTransactionBuilder { return TransactionType.Export; } - initBuilder(tx: Tx): this { + initBuilder(tx: DeprecatedTx): this { const baseTx: BaseTx = tx.getUnsignedTx().getTransaction(); if ( baseTx.getNetworkID() !== this.transaction._networkID || diff --git a/modules/sdk-coin-avaxp/src/lib/exportTxBuilder.ts b/modules/sdk-coin-avaxp/src/lib/exportTxBuilder.ts index d098d5deac..24d4288706 100644 --- a/modules/sdk-coin-avaxp/src/lib/exportTxBuilder.ts +++ b/modules/sdk-coin-avaxp/src/lib/exportTxBuilder.ts @@ -13,7 +13,7 @@ import { BN } from 'avalanche'; import { AmountOutput } from 'avalanche/dist/apis/evm/outputs'; import utils from './utils'; import { recoverUtxos } from './utxoEngine'; -import { Tx, BaseTx } from './iface'; +import { DeprecatedTx, BaseTx } from './iface'; export class ExportTxBuilder extends AtomicTransactionBuilder { private _amount: BN; @@ -40,7 +40,7 @@ export class ExportTxBuilder extends AtomicTransactionBuilder { } /** @inheritdoc */ - initBuilder(tx: Tx): this { + initBuilder(tx: DeprecatedTx): this { super.initBuilder(tx); const baseTx: BaseTx = tx.getUnsignedTx().getTransaction(); if (!this.verifyTxType(baseTx)) { diff --git a/modules/sdk-coin-avaxp/src/lib/iface.ts b/modules/sdk-coin-avaxp/src/lib/iface.ts index 13acee3176..816c8601cd 100644 --- a/modules/sdk-coin-avaxp/src/lib/iface.ts +++ b/modules/sdk-coin-avaxp/src/lib/iface.ts @@ -1,6 +1,7 @@ import { Entry, TransactionExplanation as BaseTransactionExplanation, TransactionType } from '@bitgo/sdk-core'; import { BaseTx as PMVBaseTx, TransferableOutput, Tx as PMVTx } from 'avalanche/dist/apis/platformvm'; import { EVMBaseTx, EVMOutput, Tx as EMVTx } from 'avalanche/dist/apis/evm'; +import { AddPermissionlessValidatorTx } from 'bitgo-aaron-avalanchejs/dist/serializable/pvm/addPermissionlessValidatorTx'; export interface AvaxpEntry extends Entry { id: string; @@ -63,6 +64,7 @@ export const SECP256K1_Transfer_Output = 7; export const ADDRESS_SEPARATOR = '~'; export const INPUT_SEPARATOR = ':'; -export type Tx = PMVTx | EMVTx; +export type DeprecatedTx = PMVTx | EMVTx; +export type Tx = AddPermissionlessValidatorTx; export type BaseTx = PMVBaseTx | EVMBaseTx; export type Output = TransferableOutput | EVMOutput; diff --git a/modules/sdk-coin-avaxp/src/lib/importInCTxBuilder.ts b/modules/sdk-coin-avaxp/src/lib/importInCTxBuilder.ts index a0e684aba1..515cd0c335 100644 --- a/modules/sdk-coin-avaxp/src/lib/importInCTxBuilder.ts +++ b/modules/sdk-coin-avaxp/src/lib/importInCTxBuilder.ts @@ -15,7 +15,7 @@ import { costImportTx } from 'avalanche/dist/utils'; import { BN } from 'avalanche'; import { Credential } from 'avalanche/dist/common'; import { recoverUtxos, utxoToInput } from './utxoEngine'; -import { BaseTx, Tx } from './iface'; +import { BaseTx, DeprecatedTx } from './iface'; import { AtomicInCTransactionBuilder } from './atomicInCTransactionBuilder'; import utils from './utils'; @@ -39,7 +39,7 @@ export class ImportInCTxBuilder extends AtomicInCTransactionBuilder { } /** @inheritdoc */ - initBuilder(tx: Tx): this { + initBuilder(tx: DeprecatedTx): this { const baseTx: BaseTx = tx.getUnsignedTx().getTransaction(); if ( baseTx.getNetworkID() !== this.transaction._networkID || diff --git a/modules/sdk-coin-avaxp/src/lib/importTxBuilder.ts b/modules/sdk-coin-avaxp/src/lib/importTxBuilder.ts index 82fac40eed..595ad485a9 100644 --- a/modules/sdk-coin-avaxp/src/lib/importTxBuilder.ts +++ b/modules/sdk-coin-avaxp/src/lib/importTxBuilder.ts @@ -5,7 +5,7 @@ import { ImportTx, PlatformVMConstants, Tx as PVMTx, UnsignedTx } from 'avalanch import utils from './utils'; import { BN } from 'avalanche'; import { recoverUtxos } from './utxoEngine'; -import { Tx, BaseTx } from './iface'; +import { DeprecatedTx, BaseTx } from './iface'; export class ImportTxBuilder extends AtomicTransactionBuilder { constructor(_coinConfig: Readonly) { @@ -17,7 +17,7 @@ export class ImportTxBuilder extends AtomicTransactionBuilder { return TransactionType.Import; } - initBuilder(tx: Tx): this { + initBuilder(tx: DeprecatedTx): this { super.initBuilder(tx); const baseTx: BaseTx = tx.getUnsignedTx().getTransaction(); if (!this.verifyTxType(baseTx)) { diff --git a/modules/sdk-coin-avaxp/src/lib/permissionlessValidatorTxBuilder.ts b/modules/sdk-coin-avaxp/src/lib/permissionlessValidatorTxBuilder.ts new file mode 100644 index 0000000000..742c539ddf --- /dev/null +++ b/modules/sdk-coin-avaxp/src/lib/permissionlessValidatorTxBuilder.ts @@ -0,0 +1,248 @@ +import { + BaseAddress, + BaseKey, + BaseTransaction, + BaseTransactionBuilder, + BuildTransactionError, + TransactionType, +} from '@bitgo/sdk-core'; +import { AvalancheNetwork, BaseCoin as CoinConfig } from '@bitgo/statics'; +import { BinTools, BN } from 'avalanche'; +import { AddPermissionlessValidatorTx } from 'bitgo-aaron-avalanchejs/dist/serializable/pvm'; +import { Tx } from './iface'; +import { Transaction } from './transaction'; +import { KeyPair } from './keyPair'; +import utils from './utils'; +import BigNumber from 'bignumber.js'; + +export class PermissionlessValidatorTxBuilder extends BaseTransactionBuilder { + private _transaction: BaseTransaction; + protected _nodeID: string; + protected _blsPublicKey: string; + protected _blsSignature: string; + protected _startTime: BN; + protected _endTime: BN; + protected _stakeAmount: BN; + + /** + * + * @param coinConfig + */ + constructor(coinConfig: Readonly) { + super(coinConfig); + const network = coinConfig.network as AvalancheNetwork; + this._stakeAmount = new BN(network.minStake); + } + + /** + * get transaction type + * @protected + */ + protected get transactionType(): TransactionType { + return TransactionType.AddPermissionlessValidator; + } + + /** + * Addresses where reward should be deposit + * @param {string | string[]} address - single address or array of addresses to receive rewards + */ + rewardAddresses(address: string | string[]): this { + // TODO Implement + return this; + } + + /** + * + * @param nodeID + */ + nodeID(nodeID: string): this { + this.validateNodeID(nodeID); + this._nodeID = nodeID; + return this; + } + + /** + * + * @param blsPublicKey + */ + blsPublicKey(blsPublicKey: string): this { + // TODO add + // this.validateBlsKey(blsPublicKey); + this._blsPublicKey = blsPublicKey; + return this; + } + + /** + * + * @param blsSignature + */ + blsSignature(blsSignature: string): this { + // TODO add + // this.validateBlsSignature(blsSignature); + this._blsSignature = blsSignature; + return this; + } + + /** + * start time of staking period + * @param value + */ + startTime(value: string | number): this { + this._startTime = new BN(value); + return this; + } + + /** + * end time of staking period + * @param value + */ + endTime(value: string | number): this { + this._endTime = new BN(value); + return this; + } + + /** + * + * @param value + */ + stakeAmount(value: BN | string): this { + const valueBN = BN.isBN(value) ? value : new BN(value); + this.validateStakeAmount(valueBN); + this._stakeAmount = valueBN; + return this; + } + + // region Validators + /** + * validates a correct NodeID is used + * @param nodeID + */ + validateNodeID(nodeID: string): void { + if (!nodeID) { + throw new BuildTransactionError('Invalid transaction: missing nodeID'); + } + if (nodeID.slice(0, 6) !== 'NodeID') { + throw new BuildTransactionError('Invalid transaction: invalid NodeID tag'); + } + const bintools = BinTools.getInstance(); + if (!(bintools.b58ToBuffer(nodeID.slice(7)).length === 24)) { + throw new BuildTransactionError('Invalid transaction: NodeID is not in cb58 format'); + } + } + + /** + * + * protected _startTime: Date; + * protected _endTime: Date; + * 2 weeks = 1209600 + * 1 year = 31556926 + * unix time stamp based off seconds + */ + validateStakeDuration(startTime: BN, endTime: BN): void { + const oneDayLater = new BN(Date.now()).add(new BN(86400)); + if (!startTime.gt(oneDayLater)) { + throw new BuildTransactionError('Start time needs to be one day greater than current time'); + } + if (endTime < startTime) { + throw new BuildTransactionError('End date cannot be less than start date'); + } + // TODO implement checks for start/end time + } + + /** + * + * @param amount + */ + validateStakeAmount(amount: BN): void { + // TODO implement + return; + } + + // endregion + + /** @inheritdoc */ + initBuilder(tx: Tx): this { + // super.initBuilder(tx); + return this; + } + + // TODO Implement + static verifyTxType(tx: Tx): tx is AddPermissionlessValidatorTx { + return true; + } + + verifyTxType(tx: Tx): tx is AddPermissionlessValidatorTx { + return PermissionlessValidatorTxBuilder.verifyTxType(tx); + } + + /** + * + * @protected + */ + protected buildAvaxTransaction(): void { + // TODO Implement + } + + /** @inheritdoc */ + // protected async buildImplementation(): Promise { + protected async buildImplementation(): Promise { + // TODO Implement + return this.transaction; + } + + /** @inheritdoc */ + protected fromImplementation(rawTransaction: string): BaseTransaction { + // TODO Implement + return this.transaction; + } + + /** @inheritdoc */ + protected signImplementation({ key }: BaseKey): BaseTransaction { + // TODO Implement + return this.transaction; + } + + /** @inheritdoc */ + validateAddress(address: BaseAddress, addressFormat?: string): void { + if (!utils.isValidAddress(address.address)) { + throw new BuildTransactionError('Invalid address'); + } + } + + /** @inheritdoc */ + protected get transaction(): BaseTransaction { + return this._transaction; + } + + protected set transaction(transaction: BaseTransaction) { + this._transaction = transaction; + } + + /** @inheritdoc */ + validateKey({ key }: BaseKey): void { + if (!new KeyPair({ prv: key })) { + throw new BuildTransactionError('Invalid key'); + } + } + + /** + * Check the raw transaction has a valid format in the blockchain context, throw otherwise. + * + * @param rawTransaction Transaction in any format + */ + validateRawTransaction(rawTransaction: string): void { + utils.validateRawTransaction(rawTransaction); + } + + /** @inheritdoc */ + validateTransaction(transaction?: Transaction): void { + // throw new NotImplementedError('validateTransaction not implemented'); + } + + /** @inheritdoc */ + validateValue(value: BigNumber): void { + if (value.isLessThan(0)) { + throw new BuildTransactionError('Value cannot be less than zero'); + } + } +} diff --git a/modules/sdk-coin-avaxp/src/lib/transaction.ts b/modules/sdk-coin-avaxp/src/lib/transaction.ts index 44af35e31e..f479805ea0 100644 --- a/modules/sdk-coin-avaxp/src/lib/transaction.ts +++ b/modules/sdk-coin-avaxp/src/lib/transaction.ts @@ -13,7 +13,7 @@ import { BaseTx, DecodedUtxoObj, TransactionExplanation, - Tx, + DeprecatedTx, TxData, INPUT_SEPARATOR, ADDRESS_SEPARATOR, @@ -67,7 +67,7 @@ function generateSelectorSignature(signatures: signatureSerialized[]): CheckSign // end region utils for sign export class Transaction extends BaseTransaction { - protected _avaxTransaction: Tx; + protected _avaxTransaction: DeprecatedTx; public _type: TransactionType; public _network: AvalancheNetwork; public _networkID: number; @@ -187,7 +187,7 @@ export class Transaction extends BaseTransaction { }; } - setTransaction(tx: Tx): void { + setTransaction(tx: DeprecatedTx): void { this._avaxTransaction = tx; } diff --git a/modules/sdk-coin-avaxp/src/lib/transactionBuilder.ts b/modules/sdk-coin-avaxp/src/lib/transactionBuilder.ts index 7d6cb61ce4..4c1eb7696b 100644 --- a/modules/sdk-coin-avaxp/src/lib/transactionBuilder.ts +++ b/modules/sdk-coin-avaxp/src/lib/transactionBuilder.ts @@ -12,7 +12,7 @@ import { Transaction } from './transaction'; import { KeyPair } from './keyPair'; import { BN, Buffer as BufferAvax } from 'avalanche'; import utils from './utils'; -import { DecodedUtxoObj, Tx } from './iface'; +import { DecodedUtxoObj, DeprecatedTx } from './iface'; import { Tx as PVMTx } from 'avalanche/dist/apis/platformvm'; export abstract class TransactionBuilder extends BaseTransactionBuilder { @@ -31,7 +31,7 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder { * @param {Transaction} tx the transaction data * @returns itself */ - initBuilder(tx: Tx): this { + initBuilder(tx: DeprecatedTx): this { const baseTx = tx.getUnsignedTx().getTransaction(); if ( baseTx.getNetworkID() !== this._transaction._networkID || diff --git a/modules/sdk-coin-avaxp/src/lib/transactionBuilderFactory.ts b/modules/sdk-coin-avaxp/src/lib/transactionBuilderFactory.ts index d1e7c72c6f..a39bb14aa1 100644 --- a/modules/sdk-coin-avaxp/src/lib/transactionBuilderFactory.ts +++ b/modules/sdk-coin-avaxp/src/lib/transactionBuilderFactory.ts @@ -10,6 +10,7 @@ import { ExportTxBuilder } from './exportTxBuilder'; import { ImportTxBuilder } from './importTxBuilder'; import { ImportInCTxBuilder } from './importInCTxBuilder'; import { ExportInCTxBuilder } from './exportInCTxBuilder'; +import { PermissionlessValidatorTxBuilder } from './permissionlessValidatorTxBuilder'; export class TransactionBuilderFactory extends BaseTransactionBuilderFactory { protected recoverSigner = false; @@ -78,6 +79,15 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory { return new ValidatorTxBuilder(this._coinConfig); } + /** + * Initialize Permissionless Validator builder + * + * @returns {PermissionlessValidatorTxBuilder} the builder initialized + */ + getPermissionlessValidatorTxBuilder(): PermissionlessValidatorTxBuilder { + return new PermissionlessValidatorTxBuilder(this._coinConfig); + } + /** * Export Cross chain transfer * diff --git a/modules/sdk-coin-avaxp/src/lib/utils.ts b/modules/sdk-coin-avaxp/src/lib/utils.ts index a4117b93c5..674bbbbc0b 100644 --- a/modules/sdk-coin-avaxp/src/lib/utils.ts +++ b/modules/sdk-coin-avaxp/src/lib/utils.ts @@ -17,7 +17,7 @@ import { AvalancheNetwork } from '@bitgo/statics'; import { Signature } from 'avalanche/dist/common'; import * as createHash from 'create-hash'; import { EVMOutput } from 'avalanche/dist/apis/evm'; -import { ADDRESS_SEPARATOR, Output, Tx } from './iface'; +import { ADDRESS_SEPARATOR, Output, DeprecatedTx } from './iface'; export class Utils implements BaseUtils { private binTools = BinTools.getInstance(); @@ -267,11 +267,11 @@ export class Utils implements BaseUtils { /** * Check if tx is for the blockchainId * - * @param {Tx} tx + * @param {DeprecatedTx} tx * @param {string} blockchainId * @returns true if tx is for blockchainId */ - isTransactionOf(tx: Tx, blockchainId: string): boolean { + isTransactionOf(tx: DeprecatedTx, blockchainId: string): boolean { return utils.cb58Encode(tx.getUnsignedTx().getTransaction().getBlockchainID()) === blockchainId; } diff --git a/modules/sdk-coin-avaxp/src/lib/validatorTxBuilder.ts b/modules/sdk-coin-avaxp/src/lib/validatorTxBuilder.ts index 681cd72ddd..da7b1b0586 100644 --- a/modules/sdk-coin-avaxp/src/lib/validatorTxBuilder.ts +++ b/modules/sdk-coin-avaxp/src/lib/validatorTxBuilder.ts @@ -2,7 +2,7 @@ import { DelegatorTxBuilder } from './delegatorTxBuilder'; import { BaseCoin } from '@bitgo/statics'; import { AddValidatorTx, PlatformVMConstants, UnsignedTx, Tx as PVMTx } from 'avalanche/dist/apis/platformvm'; import { BuildTransactionError, NotSupported, TransactionType } from '@bitgo/sdk-core'; -import { Tx, BaseTx } from './iface'; +import { DeprecatedTx, BaseTx } from './iface'; import utils from './utils'; export class ValidatorTxBuilder extends DelegatorTxBuilder { @@ -46,7 +46,7 @@ export class ValidatorTxBuilder extends DelegatorTxBuilder { } /** @inheritdoc */ - initBuilder(tx: Tx): this { + initBuilder(tx: DeprecatedTx): this { super.initBuilder(tx); const baseTx: BaseTx = tx.getUnsignedTx().getTransaction(); if (!this.verifyTxType(baseTx)) { diff --git a/modules/sdk-core/src/account-lib/baseCoin/enum.ts b/modules/sdk-core/src/account-lib/baseCoin/enum.ts index 256297e49d..1a6733bad2 100644 --- a/modules/sdk-core/src/account-lib/baseCoin/enum.ts +++ b/modules/sdk-core/src/account-lib/baseCoin/enum.ts @@ -63,6 +63,8 @@ export enum TransactionType { // Custom transaction (e.g. SUI) CustomTx, StakingRedelegate, + AddPermissionlessDelegator, + AddPermissionlessValidator, } /**