From f48c1a5c5dc63c20d6c7b7e3492d305f06043d13 Mon Sep 17 00:00:00 2001 From: danielailie Date: Mon, 15 Sep 2025 15:07:02 +0300 Subject: [PATCH 01/17] Add validatorPem and extend validatorSigner --- src/wallet/index.ts | 1 + src/wallet/validatorPem.ts | 52 +++++++++++++++++++++++++++++++++++ src/wallet/validatorSigner.ts | 29 ++++++++++++++++++- 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/wallet/validatorPem.ts diff --git a/src/wallet/index.ts b/src/wallet/index.ts index a0181b16..da2cc8a5 100644 --- a/src/wallet/index.ts +++ b/src/wallet/index.ts @@ -8,4 +8,5 @@ export * from "./userSigner"; export * from "./userVerifier"; export * from "./userWallet"; export * from "./validatorKeys"; +export * from "./validatorPem"; export * from "./validatorSigner"; diff --git a/src/wallet/validatorPem.ts b/src/wallet/validatorPem.ts new file mode 100644 index 00000000..335a7c1a --- /dev/null +++ b/src/wallet/validatorPem.ts @@ -0,0 +1,52 @@ +import { readFileSync, writeFileSync } from "fs"; +import { resolve } from "path"; +import { PemEntry } from "../wallet/pemEntry"; +import { BLS, ValidatorSecretKey } from "../wallet/validatorKeys"; + +export class ValidatorPEM { + label: string; + secretKey: ValidatorSecretKey; + + constructor(label: string, secretKey: ValidatorSecretKey) { + this.label = label; + this.secretKey = secretKey; + } + + static async fromFile(path: string, index = 0): Promise { + return (await this.fromFileAll(path))[index]; + } + + static async fromFileAll(path: string): Promise { + const absPath = resolve(path); + const text = readFileSync(absPath, "utf-8"); + return await this.fromTextAll(text); + } + + static async fromText(text: string, index = 0): Promise { + return (await ValidatorPEM.fromTextAll(text))[index]; + } + + static async fromTextAll(text: string): Promise { + const entries = PemEntry.fromTextAll(text); + const resultItems: ValidatorPEM[] = []; + + for (const entry of entries) { + await BLS.initIfNecessary(); + const secretKey = new ValidatorSecretKey(entry.message); + const item = new ValidatorPEM(entry.label, secretKey); + resultItems.push(item); + } + + return resultItems; + } + + save(path: string): void { + const absPath = resolve(path); + writeFileSync(absPath, this.toText(), "utf-8"); + } + + toText(): string { + const message = this.secretKey.valueOf(); + return new PemEntry(this.label, message).toText(); + } +} diff --git a/src/wallet/validatorSigner.ts b/src/wallet/validatorSigner.ts index 41122980..fa490bb5 100644 --- a/src/wallet/validatorSigner.ts +++ b/src/wallet/validatorSigner.ts @@ -1,10 +1,16 @@ import { ErrSignerCannotSign } from "../core/errors"; -import { BLS, ValidatorSecretKey } from "./validatorKeys"; +import { BLS, ValidatorPublicKey, ValidatorSecretKey } from "./validatorKeys"; +import { ValidatorPEM } from "./validatorPem"; /** * Validator signer (BLS signer) */ export class ValidatorSigner { + private secretKey: ValidatorSecretKey; + + constructor(secretKey: ValidatorSecretKey) { + this.secretKey = secretKey; + } /** * Signs a message. */ @@ -18,4 +24,25 @@ export class ValidatorSigner { throw new ErrSignerCannotSign(err); } } + + static async fromPemFile(path: string, index = 0): Promise { + const secretKey = (await ValidatorPEM.fromFile(path, index)).secretKey; + return new ValidatorSigner(secretKey); + } + + sign(data: Uint8Array): Uint8Array { + try { + return this.trySign(data); + } catch (err) { + throw new ErrSignerCannotSign(err as Error); + } + } + + private trySign(data: Uint8Array): Uint8Array { + return this.secretKey.sign(data); + } + + getPubkey(): ValidatorPublicKey { + return this.secretKey.generatePublicKey(); + } } From 5b44df2a82d2cf969bd7e240339039cbd37237a7 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 16 Sep 2025 11:56:55 +0300 Subject: [PATCH 02/17] Add validator pem tests --- src/testdata/testwallets/validators.pem | 8 ++++ src/wallet/validatorPem.spec.ts | 60 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 src/testdata/testwallets/validators.pem create mode 100644 src/wallet/validatorPem.spec.ts diff --git a/src/testdata/testwallets/validators.pem b/src/testdata/testwallets/validators.pem new file mode 100644 index 00000000..14bc776d --- /dev/null +++ b/src/testdata/testwallets/validators.pem @@ -0,0 +1,8 @@ +-----BEGIN PRIVATE KEY for f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d----- +N2MxOWJmM2EwYzU3Y2RkMWZiMDhlNDYwN2NlYmFhMzY0N2Q2YjkyNjFiNDY5M2Y2 +MWU5NmU1NGIyMThkNDQyYQ== +-----END PRIVATE KEY for f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d----- +-----BEGIN PRIVATE KEY for 1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d----- +MzAzNGIxZDU4NjI4YTg0Mjk4NGRhMGM3MGRhMGI1YTI1MWViYjJhZWJmNTFhZmM1 +YjU4NmUyODM5YjVlNTI2Mw== +-----END PRIVATE KEY for 1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d----- diff --git a/src/wallet/validatorPem.spec.ts b/src/wallet/validatorPem.spec.ts new file mode 100644 index 00000000..140c0c93 --- /dev/null +++ b/src/wallet/validatorPem.spec.ts @@ -0,0 +1,60 @@ +import { assert } from "chai"; +import * as fs from "fs"; +import path from "path"; +import { getTestWalletsPath } from "../testutils"; +import { ValidatorPEM } from "./validatorPem"; + +describe("test ValidatorPEMs", () => { + const walletsPath = path.join("src", "testdata", "testwallets"); + const pemPath = `${getTestWalletsPath()}/validatorKey00.pem`; + const savedPath = pemPath.replace(".pem", "-saved.pem"); + + afterEach(() => { + if (fs.existsSync(savedPath)) { + fs.unlinkSync(savedPath); // cleanup + } + }); + + it("should save pem from file", async function () { + const contentExpected = fs.readFileSync(pemPath, "utf-8").trim(); + + // fromFile is async → await + const pem = await ValidatorPEM.fromFile(pemPath); + pem.save(savedPath); + + const contentActual = fs.readFileSync(savedPath, "utf-8").trim(); + assert.deepEqual(contentActual, contentExpected); + }); + + it("should create from text all", async function () { + let text = fs.readFileSync(path.join(walletsPath, "validatorKey00.pem"), "utf-8"); + + let entries = await ValidatorPEM.fromTextAll(text); + let entry = entries[0]; + + assert.lengthOf(entries, 1); + assert.equal( + entry.label, + "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208", + ); + assert.equal(entry.secretKey.hex(), "7cff99bd671502db7d15bc8abc0c9a804fb925406fbdd50f1e4c17a4cd774247"); + + text = fs.readFileSync(path.join(walletsPath, "validators.pem"), "utf-8"); + entries = await ValidatorPEM.fromTextAll(text); + entry = entries[0]; + + assert.lengthOf(entries, 2); + assert.equal( + entry.label, + "f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d", + ); + assert.equal(entry.secretKey.hex(), "7c19bf3a0c57cdd1fb08e4607cebaa3647d6b9261b4693f61e96e54b218d442a"); + + entry = entries[1]; + assert.equal( + entry.label, + "1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d", + ); + assert.equal(entry.secretKey.hex(), "3034b1d58628a842984da0c70da0b5a251ebb2aebf51afc5b586e2839b5e5263"); + }); +}); From 15406441c5934aa96d8d4cd8a3fed25146dfdf7e Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 16 Sep 2025 12:13:53 +0300 Subject: [PATCH 03/17] Add validators transaction factory and tests --- src/core/constants.ts | 1 + src/core/transactionsFactoryConfig.ts | 34 ++ src/validators/index.ts | 2 + src/validators/resources.ts | 15 + src/validators/validatorsSigner.ts | 27 ++ .../validatorsTransactionsFactory.spec.ts | 348 ++++++++++++++++ .../validatorsTransactionsFactory.ts | 389 ++++++++++++++++++ 7 files changed, 816 insertions(+) create mode 100644 src/validators/index.ts create mode 100644 src/validators/resources.ts create mode 100644 src/validators/validatorsSigner.ts create mode 100644 src/validators/validatorsTransactionsFactory.spec.ts create mode 100644 src/validators/validatorsTransactionsFactory.ts diff --git a/src/core/constants.ts b/src/core/constants.ts index b9dbfa59..a398c7ef 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -15,6 +15,7 @@ export const CONTRACT_DEPLOY_ADDRESS_HEX = "000000000000000000000000000000000000 export const DELEGATION_MANAGER_SC_ADDRESS_HEX = "000000000000000000010000000000000000000000000000000000000004ffff"; export const ESDT_CONTRACT_ADDRESS_HEX = "000000000000000000010000000000000000000000000000000000000002ffff"; export const GOVERNANCE_CONTRACT_ADDRESS_HEX = "000000000000000000010000000000000000000000000000000000000003ffff"; +export const STAKING_SMART_CONTRACT_ADDRESS_HEX = "000000000000000000010000000000000000000000000000000000000001ffff"; export const DEFAULT_MESSAGE_VERSION = 1; export const MESSAGE_PREFIX = "\x17Elrond Signed Message:\n"; diff --git a/src/core/transactionsFactoryConfig.ts b/src/core/transactionsFactoryConfig.ts index a7b38372..53c7cc74 100644 --- a/src/core/transactionsFactoryConfig.ts +++ b/src/core/transactionsFactoryConfig.ts @@ -50,6 +50,22 @@ export class TransactionsFactoryConfig { gasLimitForClearProposals: bigint; gasLimitForChangeConfig: bigint; gasLimitForClaimAccumulatedFees: bigint; + gasLimitForStaking: bigint; + gasLimitForToppingUp: bigint; + gasLimitForUnstaking: bigint; + gasLimitForUnjailing: bigint; + gasLimitForUnbonding: bigint; + gasLimitForChangingRewardsAddress: bigint; + gasLimitForClaiming: bigint; + gasLimitForUnstakingNodes: bigint; + gasLimitForUnstakingTokens: bigint; + gasLimitForUnbondingNodes: bigint; + gasLimitForUnbondingTokens: bigint; + gasLimitForCleaningRegisteredData: bigint; + gasLimitForRestakingUnstakedTokens: bigint; + gasLimitForCreatingDelegationContractFromValidator: bigint; + gasLimitForWhitelistForMerge: bigint; + gasLimitForMergingValidatorToDelegation: bigint; constructor(options: { chainID: string }) { // General-purpose configuration @@ -114,5 +130,23 @@ export class TransactionsFactoryConfig { this.gasLimitForClearProposals = 50_000_000n; this.gasLimitForChangeConfig = 50_000_000n; this.gasLimitForClaimAccumulatedFees = 1_000_000n; + + // Configuration for staking operations + this.gasLimitForStaking = 5_000_000n; + this.gasLimitForToppingUp = 5_000_000n; + this.gasLimitForUnstaking = 5_000_000n; + this.gasLimitForUnjailing = 5_000_000n; + this.gasLimitForUnbonding = 5_000_000n; + this.gasLimitForChangingRewardsAddress = 5_000_000n; + this.gasLimitForClaiming = 5_000_000n; + this.gasLimitForUnstakingNodes = 5_000_000n; + this.gasLimitForUnstakingTokens = 5_000_000n; + this.gasLimitForUnbondingNodes = 5_000_000n; + this.gasLimitForUnbondingTokens = 5_000_000n; + this.gasLimitForCleaningRegisteredData = 5_000_000n; + this.gasLimitForRestakingUnstakedTokens = 5_000_000n; + this.gasLimitForCreatingDelegationContractFromValidator = 51_000_000n; + this.gasLimitForWhitelistForMerge = 5_000_000n; + this.gasLimitForMergingValidatorToDelegation = 50_000_000n; } } diff --git a/src/validators/index.ts b/src/validators/index.ts new file mode 100644 index 00000000..4fe67884 --- /dev/null +++ b/src/validators/index.ts @@ -0,0 +1,2 @@ +export * from "./resources"; +export * from "./validatorsTransactionsFactory"; diff --git a/src/validators/resources.ts b/src/validators/resources.ts new file mode 100644 index 00000000..6f46271d --- /dev/null +++ b/src/validators/resources.ts @@ -0,0 +1,15 @@ +import { Address } from "../core/address"; +import { ValidatorPublicKey } from "../wallet"; +import { ValidatorsSigners } from "./validatorsSigner"; + +export type StakingInput = { validatorsFile: ValidatorsSigners | string; amount: bigint; rewardsAddress?: Address }; +export type ChangeRewardsAddressInput = { rewardsAddress: Address }; +export type ToppingUpInput = { amount: bigint }; +export type UnstakingTokensInput = { amount: bigint }; +export type UnstakingInput = { publicKeys: ValidatorPublicKey[] }; +export type RestakingInput = { publicKeys: ValidatorPublicKey[] }; +export type UnboundInput = { publicKeys: ValidatorPublicKey[] }; +export type UnboundTokensInput = { amount: bigint }; +export type UnjailingInput = { publicKeys: ValidatorPublicKey[]; amount: bigint }; +export type NewDelegationContractInput = { maxCap: bigint; fee: bigint }; +export type MergeValidatorToDelegationInput = { delegationAddress: Address }; diff --git a/src/validators/validatorsSigner.ts b/src/validators/validatorsSigner.ts new file mode 100644 index 00000000..ca0ccb32 --- /dev/null +++ b/src/validators/validatorsSigner.ts @@ -0,0 +1,27 @@ +import { ValidatorPEM, ValidatorPublicKey, ValidatorSigner } from "../wallet"; + +export class ValidatorsSigners { + private signers: ValidatorSigner[]; + + constructor(validatorSigners: ValidatorSigner[]) { + this.signers = validatorSigners; + } + + static async newFromPem(filePath: string): Promise { + const validatorPemFiles = await ValidatorPEM.fromFileAll(filePath); + const signers = validatorPemFiles.map((pem) => new ValidatorSigner(pem.secretKey)); + return new ValidatorsSigners(signers); + } + + getNumOfNodes(): number { + return this.signers.length; + } + + getSigners(): ValidatorSigner[] { + return this.signers; + } + + getPublicKeys(): ValidatorPublicKey[] { + return this.signers.map((signer) => signer.getPubkey()); + } +} diff --git a/src/validators/validatorsTransactionsFactory.spec.ts b/src/validators/validatorsTransactionsFactory.spec.ts new file mode 100644 index 00000000..49eb17b7 --- /dev/null +++ b/src/validators/validatorsTransactionsFactory.spec.ts @@ -0,0 +1,348 @@ +import { assert } from "chai"; +import { TransactionsFactoryConfig } from "../core"; +import { Address } from "../core/address"; +import { DELEGATION_MANAGER_SC_ADDRESS_HEX, STAKING_SMART_CONTRACT_ADDRESS_HEX } from "../core/constants"; +import { getTestWalletsPath } from "../testutils"; +import { ValidatorPublicKey } from "../wallet"; +import { ValidatorsSigners } from "./validatorsSigner"; +import { ValidatorsTransactionsFactory } from "./validatorsTransactionsFactory"; + +describe("test delegation transactions factory", function () { + const config = new TransactionsFactoryConfig({ chainID: "D" }); + const validatorsFactory = new ValidatorsTransactionsFactory({ config: config }); + const validatorsPath = `${getTestWalletsPath()}/validators.pem`; + const alice = Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + const rewardAddress = Address.newFromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"); + const validatorPubkey = new ValidatorPublicKey( + Buffer.from( + "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208", + "hex", + ), + ); + + it("should create 'Transaction' for staking from file path", async function () { + const transaction = await validatorsFactory.createTransactionForStaking(alice, { + validatorsFile: validatorsPath, + amount: 2500000000000000000000n, + rewardsAddress: rewardAddress, + }); + + assert.deepEqual( + transaction.sender, + Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"), + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 2500000000000000000000n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.version, 2); + assert.equal(transaction.gasLimit, 11029500n); + assert.equal(transaction.options, 0); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "stake@02@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1865870f7f69162a2dfefd33fe232a9ca984c6f22d1ee3f6a5b34a8eb8c9f7319001f29d5a2eed85c1500aca19fa4189@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d@12b309791213aac8ad9f34f0d912261e30f9ab060859e4d515e020a98b91d82a7cd334e4b504bb93d6b75347cccd6318@b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba", + ); + }); + + it("should create 'Transaction' for staking using validators file", async function () { + const validatorsFile = await ValidatorsSigners.newFromPem(validatorsPath); + const transaction = await validatorsFactory.createTransactionForStaking(alice, { + validatorsFile: validatorsFile, + amount: 2500000000000000000000n, + rewardsAddress: rewardAddress, + }); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 2500000000000000000000n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.version, 2); + assert.equal(transaction.gasLimit, 11029500n); + assert.equal(transaction.options, 0); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "stake@02@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1865870f7f69162a2dfefd33fe232a9ca984c6f22d1ee3f6a5b34a8eb8c9f7319001f29d5a2eed85c1500aca19fa4189@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d@12b309791213aac8ad9f34f0d912261e30f9ab060859e4d515e020a98b91d82a7cd334e4b504bb93d6b75347cccd6318@b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba", + ); + }); + + it("should create 'Transaction' for topping up", async function () { + const transaction = await validatorsFactory.createTransactionForToppingUp(alice, { + amount: 2500000000000000000000n, + }); + + assert.equal(transaction.sender.toBech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 2500000000000000000000n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.gasLimit, 5057500n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual(Buffer.from(transaction.data).toString(), "stake"); + }); + + it("should create 'Transaction' for unstake", async function () { + const transaction = await validatorsFactory.createTransactionForUnstaking(alice, { + publicKeys: [validatorPubkey], + }); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.gasLimit, 5350000n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "unStake@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208", + ); + }); + + it("should create 'Transaction' for unjail", async function () { + const transaction = await validatorsFactory.createTransactionForUnjailing(alice, { + publicKeys: [validatorPubkey], + amount: 2500000000000000000000n, + }); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.gasLimit, 5348500n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "unJail@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208", + ); + }); + + it("should create 'Transaction' for changing rewards address", async function () { + const transaction = await validatorsFactory.createTransactionForChangingRewardsAddress(alice, { + rewardsAddress: rewardAddress, + }); + + assert.deepEqual( + transaction.sender, + Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"), + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.isDefined(transaction.data); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "changeRewardAddress@b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba", + ); + assert.equal(transaction.value, 0n); + }); + + it("should create 'Transaction' for claiming", async function () { + const transaction = await validatorsFactory.createTransactionForClaiming(alice); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.gasLimit, 5057500n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual(Buffer.from(transaction.data).toString(), "claim"); + }); + + it("should create 'Transaction' for unstaking nodes", async function () { + const transaction = await validatorsFactory.createTransactionForUnstakingNodes(alice, { + publicKeys: [validatorPubkey], + }); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.gasLimit, 5357500n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "unStakeNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208", + ); + }); + + it("should create 'Transaction' for unstaking tokens", async function () { + const transaction = await validatorsFactory.createTransactionFoUnstakingTokens(alice, { + amount: 11000000000000000000n, + }); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.gasLimit, 5095000n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual(Buffer.from(transaction.data).toString(), "unStakeTokens@98a7d9b8314c0000"); + }); + + it("should create 'Transaction' for unbounding nodes", async function () { + const transaction = await validatorsFactory.createTransactionForUnboundingNodes(alice, { + publicKeys: [validatorPubkey], + }); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.gasLimit, 5356000n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "unBondNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208", + ); + }); + + it("should create 'Transaction' for unbounding tokens", async function () { + const transaction = await validatorsFactory.createTransactionForUnboundingTokens(alice, { + amount: 20000000000000000000n, + }); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.gasLimit, 5096500n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual(Buffer.from(transaction.data).toString(), "unBondTokens@01158e460913d00000"); + }); + + it("should create 'Transaction' for cleaning registered data", async function () { + const transaction = await validatorsFactory.createTransactionForCleaningRegisteredData(alice); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.gasLimit, 5078500n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual(Buffer.from(transaction.data).toString(), "cleanRegisteredData"); + }); + + it("should create 'Transaction' for restaking unstaked nodes", async function () { + const transaction = await validatorsFactory.createTransactionForRestakingUnstakedNodes(alice, { + publicKeys: [validatorPubkey], + }); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.gasLimit, 5369500n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "reStakeUnStakedNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208", + ); + }); + + it("should create 'Transaction' for new delegation contract from validator", async function () { + const transaction = await validatorsFactory.createTransactionForNewDelegationContractFromValidatorData(alice, { + maxCap: 0n, + fee: 3745n, + }); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.gasLimit, 51107000n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual(Buffer.from(transaction.data).toString(), "makeNewContractFromValidatorData@@0ea1"); + }); + + it("should create 'Transaction' for merging validator to delegation whitelisting", async function () { + const delegationContract = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc", + ); + + const transaction = await validatorsFactory.createTransactionForMergingValidatorToDelegationWithWhitelist( + alice, + { + delegationAddress: delegationContract, + }, + ); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.gasLimit, 5206000n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "mergeValidatorToDelegationWithWhitelist@000000000000000000010000000000000000000000000000000000002fffffff", + ); + }); + + it("should create 'Transaction' for merging validator to delegation same owner", async function () { + const delegationContract = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc", + ); + + const transaction = await validatorsFactory.createTransactionForMergingValidatorToDelegationSameOwner(alice, { + delegationAddress: delegationContract, + }); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.gasLimit, 50200000n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "mergeValidatorToDelegationSameOwner@000000000000000000010000000000000000000000000000000000002fffffff", + ); + }); +}); diff --git a/src/validators/validatorsTransactionsFactory.ts b/src/validators/validatorsTransactionsFactory.ts new file mode 100644 index 00000000..85d2b073 --- /dev/null +++ b/src/validators/validatorsTransactionsFactory.ts @@ -0,0 +1,389 @@ +import { AddressValue, ArgSerializer, BigUIntValue, BytesValue, U32Value } from "../abi"; +import { IGasLimitEstimator, TransactionsFactoryConfig } from "../core"; +import { Address } from "../core/address"; +import { BaseFactory } from "../core/baseFactory"; +import { DELEGATION_MANAGER_SC_ADDRESS_HEX, STAKING_SMART_CONTRACT_ADDRESS_HEX } from "../core/constants"; +import { Transaction } from "../core/transaction"; +import * as resources from "./resources"; +import { ValidatorsSigners } from "./validatorsSigner"; + +/** + * Use this class to create validators related transactions like creating transaction for staking or adding nodes. + */ +export class ValidatorsTransactionsFactory extends BaseFactory { + private readonly config: TransactionsFactoryConfig; + private readonly argSerializer: ArgSerializer; + + constructor(options: { config: TransactionsFactoryConfig; gasLimitEstimator?: IGasLimitEstimator }) { + super({ config: options.config, gasLimitEstimator: options.gasLimitEstimator }); + this.config = options.config; + this.argSerializer = new ArgSerializer(); + } + + async createTransactionForStaking(sender: Address, options: resources.StakingInput): Promise { + let validators: ValidatorsSigners; + if (typeof options.validatorsFile === "string") { + validators = await ValidatorsSigners.newFromPem(options.validatorsFile); + } else { + validators = options.validatorsFile; + } + + const dataParts = this.prepareDataPartsForStaking({ + nodeOperator: sender, + validatorsFile: validators, + rewardsAddress: options.rewardsAddress, + }); + + const transaction = new Transaction({ + sender, + receiver: Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX, this.config.addressHrp), + chainID: this.config.chainID, + gasLimit: 0n, + value: options.amount, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit( + transaction, + undefined, + this.config.gasLimitForStaking * BigInt(validators.getNumOfNodes()), + ); + + return transaction; + } + + async createTransactionForToppingUp(sender: Address, options: resources.ToppingUpInput): Promise { + const data = ["stake"]; + + const transaction = new Transaction({ + sender, + receiver: Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX, this.config.addressHrp), + chainID: this.config.chainID, + gasLimit: 0n, + value: options.amount, + }); + + this.setTransactionPayload(transaction, data); + await this.setGasLimit(transaction, undefined, this.config.gasLimitForToppingUp); + + return transaction; + } + + async createTransactionForUnstaking(sender: Address, options: resources.UnstakingInput): Promise { + const dataParts = ["unStake"]; + + for (const key of options.publicKeys) { + dataParts.push(key.hex()); + } + + const transaction = new Transaction({ + sender, + receiver: Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX, this.config.addressHrp), + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit( + transaction, + undefined, + this.config.gasLimitForUnstaking * BigInt(options.publicKeys.length), + ); + + return transaction; + } + + async createTransactionForUnjailing(sender: Address, options: resources.UnjailingInput): Promise { + let dataParts = ["unJail"]; + + for (const key of options.publicKeys) { + dataParts = dataParts.concat(key.hex()); + } + + const transaction = new Transaction({ + sender, + receiver: Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX, this.config.addressHrp), + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit( + transaction, + undefined, + this.config.gasLimitForUnjailing * BigInt(options.publicKeys.length), + ); + + return transaction; + } + + async createTransactionForUnbounding(sender: Address, options: resources.UnboundInput): Promise { + let dataParts = ["unBond"]; + + for (const key of options.publicKeys) { + dataParts = dataParts.concat(key.hex()); + } + + const transaction = new Transaction({ + sender, + receiver: Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX, this.config.addressHrp), + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit( + transaction, + undefined, + this.config.gasLimitForUnbonding * BigInt(options.publicKeys.length), + ); + + return transaction; + } + + async createTransactionForChangingRewardsAddress( + sender: Address, + options: resources.ChangeRewardsAddressInput, + ): Promise { + let dataParts = ["changeRewardAddress", options.rewardsAddress.toHex()]; + + const transaction = new Transaction({ + sender, + receiver: Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX, this.config.addressHrp), + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitForChangingRewardsAddress); + + return transaction; + } + + async createTransactionForClaiming(sender: Address): Promise { + const dataParts = ["claim"]; + + const transaction = new Transaction({ + sender, + receiver: Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX, this.config.addressHrp), + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitForClaiming); + + return transaction; + } + + async createTransactionForUnstakingNodes(sender: Address, options: resources.UnstakingInput): Promise { + let dataParts = ["unStakeNodes"]; + for (const key of options.publicKeys) { + dataParts = dataParts.concat(key.hex()); + } + + const transaction = new Transaction({ + sender, + receiver: Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX, this.config.addressHrp), + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit( + transaction, + undefined, + this.config.gasLimitForUnstakingNodes * BigInt(options.publicKeys.length), + ); + + return transaction; + } + + async createTransactionFoUnstakingTokens( + sender: Address, + options: resources.UnstakingTokensInput, + ): Promise { + const dataParts = ["unStakeTokens", this.argSerializer.valuesToStrings([new BigUIntValue(options.amount)])[0]]; + + const transaction = new Transaction({ + sender, + receiver: Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX, this.config.addressHrp), + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitForRestakingUnstakedTokens); + + return transaction; + } + + async createTransactionForUnboundingNodes(sender: Address, options: resources.UnboundInput): Promise { + let dataParts = ["unBondNodes"]; + for (const key of options.publicKeys) { + dataParts = dataParts.concat(key.hex()); + } + const transaction = new Transaction({ + sender, + receiver: Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX, this.config.addressHrp), + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit( + transaction, + undefined, + this.config.gasLimitForUnbondingNodes * BigInt(options.publicKeys.length), + ); + + return transaction; + } + + async createTransactionForUnboundingTokens( + sender: Address, + options: resources.UnboundTokensInput, + ): Promise { + const dataParts = ["unBondTokens", this.argSerializer.valuesToStrings([new BigUIntValue(options.amount)])[0]]; + + const transaction = new Transaction({ + sender, + receiver: Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX, this.config.addressHrp), + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitForUnbondingTokens); + + return transaction; + } + + async createTransactionForCleaningRegisteredData(sender: Address): Promise { + const dataParts = ["cleanRegisteredData"]; + + const transaction = new Transaction({ + sender, + receiver: Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX, this.config.addressHrp), + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitForCleaningRegisteredData); + + return transaction; + } + + async createTransactionForRestakingUnstakedNodes( + sender: Address, + options: resources.RestakingInput, + ): Promise { + let dataParts = ["reStakeUnStakedNodes"]; + for (const key of options.publicKeys) { + dataParts = dataParts.concat(key.hex()); + } + const transaction = new Transaction({ + sender, + receiver: Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX, this.config.addressHrp), + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit( + transaction, + undefined, + this.config.gasLimitForUnstakingNodes * BigInt(options.publicKeys.length), + ); + + return transaction; + } + + async createTransactionForNewDelegationContractFromValidatorData( + sender: Address, + options: resources.NewDelegationContractInput, + ): Promise { + let dataParts = [ + "makeNewContractFromValidatorData", + ...this.argSerializer.valuesToStrings([new BigUIntValue(options.maxCap), new BigUIntValue(options.fee)]), + ]; + + const transaction = new Transaction({ + sender, + receiver: Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX, this.config.addressHrp), + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitForCreatingDelegationContractFromValidator); + + return transaction; + } + + async createTransactionForMergingValidatorToDelegationWithWhitelist( + sender: Address, + options: resources.MergeValidatorToDelegationInput, + ): Promise { + let dataParts = [ + "mergeValidatorToDelegationWithWhitelist", + this.argSerializer.valuesToStrings([new AddressValue(options.delegationAddress)])[0], + ]; + + const transaction = new Transaction({ + sender, + receiver: Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX, this.config.addressHrp), + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitForWhitelistForMerge); + + return transaction; + } + + async createTransactionForMergingValidatorToDelegationSameOwner( + sender: Address, + options: resources.MergeValidatorToDelegationInput, + ): Promise { + let dataParts = [ + "mergeValidatorToDelegationSameOwner", + this.argSerializer.valuesToStrings([new AddressValue(options.delegationAddress)])[0], + ]; + + const transaction = new Transaction({ + sender, + receiver: Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX, this.config.addressHrp), + chainID: this.config.chainID, + gasLimit: 0n, + }); + + this.setTransactionPayload(transaction, dataParts); + await this.setGasLimit(transaction, undefined, this.config.gasLimitForMergingValidatorToDelegation); + + return transaction; + } + + private prepareDataPartsForStaking(options: { + nodeOperator: Address; + validatorsFile: import("./validatorsSigner").ValidatorsSigners; + rewardsAddress: Address | undefined; + }) { + const dataParts = ["stake"]; + const numOfNodes = options.validatorsFile.getNumOfNodes(); + const callArguments = []; + callArguments.push(new U32Value(numOfNodes)); + for (const signer of options.validatorsFile.getSigners()) { + const signedMessages = signer.sign(options.nodeOperator.getPublicKey()); + callArguments.push(new BytesValue(Buffer.from(signer.getPubkey()))); + callArguments.push(new BytesValue(Buffer.from(signedMessages))); + } + if (options.rewardsAddress) { + callArguments.push(new AddressValue(options.rewardsAddress)); + } + const args = this.argSerializer.valuesToStrings(callArguments); + return dataParts.concat(args); + } +} From 411f27377cbd66af0a1ef37a246eada05f2c2737 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 16 Sep 2025 12:50:53 +0300 Subject: [PATCH 04/17] Add Validators Controller --- src/validators/validatorsController.ts | 309 +++++++++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 src/validators/validatorsController.ts diff --git a/src/validators/validatorsController.ts b/src/validators/validatorsController.ts new file mode 100644 index 00000000..9b77324d --- /dev/null +++ b/src/validators/validatorsController.ts @@ -0,0 +1,309 @@ +import { + Address, + BaseController, + BaseControllerInput, + IAccount, + IGasLimitEstimator, + Transaction, + TransactionsFactoryConfig, +} from "../core"; +import { INetworkProvider } from "../networkProviders"; +import * as resources from "./resources"; +import { ValidatorsTransactionsFactory } from "./validatorsTransactionsFactory"; + +export class ValidatorsController extends BaseController { + private factory: ValidatorsTransactionsFactory; + + constructor(options: { + chainID: string; + networkProvider: INetworkProvider; + gasLimitEstimator?: IGasLimitEstimator; + }) { + super(); + this.factory = new ValidatorsTransactionsFactory({ + config: new TransactionsFactoryConfig({ chainID: options.chainID }), + gasLimitEstimator: options.gasLimitEstimator, + }); + } + + async createTransactionForStaking( + sender: IAccount, + nonce: bigint, + options: resources.StakingInput & BaseControllerInput, + ): Promise { + const transaction = await this.factory.createTransactionForStaking(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + async createTransactionForToppingUp( + sender: IAccount, + nonce: bigint, + options: resources.ToppingUpInput & BaseControllerInput, + ): Promise { + const transaction = await this.factory.createTransactionForToppingUp(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + async createTransactionForUnstaking( + sender: IAccount, + nonce: bigint, + options: resources.UnstakingInput & BaseControllerInput, + ): Promise { + const transaction = await this.factory.createTransactionForUnstaking(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + async createTransactionForUnjailing( + sender: IAccount, + nonce: bigint, + options: resources.UnjailingInput & BaseControllerInput, + ): Promise { + const transaction = await this.factory.createTransactionForUnjailing(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + async createTransactionForUnbounding( + sender: IAccount, + nonce: bigint, + options: resources.UnboundInput & BaseControllerInput, + ): Promise { + const transaction = await this.factory.createTransactionForUnbounding(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + async createTransactionForChangingRewardsAddress( + sender: IAccount, + nonce: bigint, + options: resources.ChangeRewardsAddressInput & BaseControllerInput, + ): Promise { + const transaction = await this.factory.createTransactionForChangingRewardsAddress(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + async createTransactionForClaiming( + sender: IAccount, + nonce: bigint, + options: BaseControllerInput, + ): Promise { + const transaction = await this.factory.createTransactionForClaiming(sender.address); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + async createTransactionForUnstakingNodes( + sender: IAccount, + nonce: bigint, + options: resources.UnstakingInput & BaseControllerInput, + ): Promise { + const transaction = await this.factory.createTransactionForUnstakingNodes(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + async createTransactionFoUnstakingTokens( + sender: IAccount, + nonce: bigint, + options: resources.UnstakingTokensInput & BaseControllerInput, + ): Promise { + const transaction = await this.factory.createTransactionFoUnstakingTokens(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + async createTransactionForUnboundingNodes( + sender: IAccount, + nonce: bigint, + options: resources.UnboundInput & BaseControllerInput, + ): Promise { + const transaction = await this.factory.createTransactionForUnboundingNodes(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + async createTransactionForUnboundingTokens( + sender: IAccount, + nonce: bigint, + options: resources.UnboundTokensInput & BaseControllerInput, + ): Promise { + const transaction = await this.factory.createTransactionForUnboundingTokens(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + async createTransactionForCleaningRegisteredData( + sender: IAccount, + nonce: bigint, + options: BaseControllerInput, + ): Promise { + const transaction = await this.factory.createTransactionForCleaningRegisteredData(sender.address); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + async createTransactionForRestakingUnstakedNodes( + sender: IAccount, + nonce: bigint, + options: resources.RestakingInput & BaseControllerInput, + ): Promise { + const transaction = await this.factory.createTransactionForRestakingUnstakedNodes(sender.address, options); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + async createTransactionForNewDelegationContractFromValidatorData( + sender: IAccount, + nonce: bigint, + options: resources.NewDelegationContractInput & BaseControllerInput, + ): Promise { + const transaction = await this.factory.createTransactionForNewDelegationContractFromValidatorData( + sender.address, + options, + ); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + async createTransactionForMergingValidatorToDelegationWithWhitelist( + sender: IAccount, + nonce: bigint, + options: resources.MergeValidatorToDelegationInput & BaseControllerInput, + ): Promise { + const transaction = await this.factory.createTransactionForMergingValidatorToDelegationWithWhitelist( + sender.address, + options, + ); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } + + async createTransactionForMergingValidatorToDelegationSameOwner( + sender: IAccount, + nonce: bigint, + options: resources.MergeValidatorToDelegationInput & BaseControllerInput, + ): Promise { + const transaction = await this.factory.createTransactionForMergingValidatorToDelegationSameOwner( + sender.address, + options, + ); + + transaction.guardian = options.guardian ?? Address.empty(); + transaction.relayer = options.relayer ?? Address.empty(); + transaction.nonce = nonce; + this.setTransactionGasOptions(transaction, options); + this.setVersionAndOptionsForGuardian(transaction); + transaction.signature = await sender.signTransaction(transaction); + + return transaction; + } +} From 3a6839beec40c6c5c5be1b00d4ec0c93869c6483 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 16 Sep 2025 13:54:28 +0300 Subject: [PATCH 05/17] add toText tests and update private fields --- src/wallet/validatorPem.spec.ts | 11 +++++++++++ src/wallet/validatorPem.ts | 6 +++--- src/wallet/validatorSigner.ts | 8 ++++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/wallet/validatorPem.spec.ts b/src/wallet/validatorPem.spec.ts index 140c0c93..746ef963 100644 --- a/src/wallet/validatorPem.spec.ts +++ b/src/wallet/validatorPem.spec.ts @@ -57,4 +57,15 @@ describe("test ValidatorPEMs", () => { ); assert.equal(entry.secretKey.hex(), "3034b1d58628a842984da0c70da0b5a251ebb2aebf51afc5b586e2839b5e5263"); }); + + it("should convert to text", async function () { + let text = fs.readFileSync(path.join(walletsPath, "validatorKey00.pem"), "utf-8").trim(); + const entry = await ValidatorPEM.fromTextAll(text); + assert.deepEqual(entry[0].toText(), text); + + text = fs.readFileSync(path.join(walletsPath, "multipleValidatorKeys.pem"), "utf-8").trim(); + const entries = await ValidatorPEM.fromTextAll(text); + const actualText = entries.map((entry) => entry.toText()).join("\n"); + assert.deepEqual(actualText, text); + }); }); diff --git a/src/wallet/validatorPem.ts b/src/wallet/validatorPem.ts index 335a7c1a..b7e07f56 100644 --- a/src/wallet/validatorPem.ts +++ b/src/wallet/validatorPem.ts @@ -4,8 +4,8 @@ import { PemEntry } from "../wallet/pemEntry"; import { BLS, ValidatorSecretKey } from "../wallet/validatorKeys"; export class ValidatorPEM { - label: string; - secretKey: ValidatorSecretKey; + readonly label: string; + readonly secretKey: ValidatorSecretKey; constructor(label: string, secretKey: ValidatorSecretKey) { this.label = label; @@ -30,8 +30,8 @@ export class ValidatorPEM { const entries = PemEntry.fromTextAll(text); const resultItems: ValidatorPEM[] = []; + await BLS.initIfNecessary(); for (const entry of entries) { - await BLS.initIfNecessary(); const secretKey = new ValidatorSecretKey(entry.message); const item = new ValidatorPEM(entry.label, secretKey); resultItems.push(item); diff --git a/src/wallet/validatorSigner.ts b/src/wallet/validatorSigner.ts index fa490bb5..df98eb4a 100644 --- a/src/wallet/validatorSigner.ts +++ b/src/wallet/validatorSigner.ts @@ -6,7 +6,7 @@ import { ValidatorPEM } from "./validatorPem"; * Validator signer (BLS signer) */ export class ValidatorSigner { - private secretKey: ValidatorSecretKey; + private readonly secretKey: ValidatorSecretKey; constructor(secretKey: ValidatorSecretKey) { this.secretKey = secretKey; @@ -14,7 +14,11 @@ export class ValidatorSigner { /** * Signs a message. */ - async signUsingPem(pemText: string, pemIndex: number = 0, signable: Buffer | Uint8Array): Promise { + static async signUsingPem( + pemText: string, + pemIndex: number = 0, + signable: Buffer | Uint8Array, + ): Promise { await BLS.initIfNecessary(); try { From 34ba2696f531918cc66f382514880aa60de03139 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 16 Sep 2025 14:07:15 +0300 Subject: [PATCH 06/17] Fix Typos and some formatting errors --- .../validatorsTransactionsFactory.spec.ts | 2 +- .../validatorsTransactionsFactory.ts | 56 ++++++++++--------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/validators/validatorsTransactionsFactory.spec.ts b/src/validators/validatorsTransactionsFactory.spec.ts index 49eb17b7..d129d5cc 100644 --- a/src/validators/validatorsTransactionsFactory.spec.ts +++ b/src/validators/validatorsTransactionsFactory.spec.ts @@ -181,7 +181,7 @@ describe("test delegation transactions factory", function () { }); it("should create 'Transaction' for unstaking tokens", async function () { - const transaction = await validatorsFactory.createTransactionFoUnstakingTokens(alice, { + const transaction = await validatorsFactory.createTransactionForUnstakingTokens(alice, { amount: 11000000000000000000n, }); diff --git a/src/validators/validatorsTransactionsFactory.ts b/src/validators/validatorsTransactionsFactory.ts index 85d2b073..d19212a4 100644 --- a/src/validators/validatorsTransactionsFactory.ts +++ b/src/validators/validatorsTransactionsFactory.ts @@ -52,6 +52,29 @@ export class ValidatorsTransactionsFactory extends BaseFactory { return transaction; } + private prepareDataPartsForStaking(options: { + nodeOperator: Address; + validatorsFile: ValidatorsSigners; + rewardsAddress: Address | undefined; + }) { + const dataParts = ["stake"]; + const numOfNodes = options.validatorsFile.getNumOfNodes(); + + const callArguments = []; + callArguments.push(new U32Value(numOfNodes)); + + for (const signer of options.validatorsFile.getSigners()) { + const signedMessages = signer.sign(options.nodeOperator.getPublicKey()); + callArguments.push(new BytesValue(Buffer.from(signer.getPubkey()))); + callArguments.push(new BytesValue(Buffer.from(signedMessages))); + } + if (options.rewardsAddress) { + callArguments.push(new AddressValue(options.rewardsAddress)); + } + const args = this.argSerializer.valuesToStrings(callArguments); + return dataParts.concat(args); + } + async createTransactionForToppingUp(sender: Address, options: resources.ToppingUpInput): Promise { const data = ["stake"]; @@ -97,7 +120,7 @@ export class ValidatorsTransactionsFactory extends BaseFactory { let dataParts = ["unJail"]; for (const key of options.publicKeys) { - dataParts = dataParts.concat(key.hex()); + dataParts.push(key.hex()); } const transaction = new Transaction({ @@ -121,7 +144,7 @@ export class ValidatorsTransactionsFactory extends BaseFactory { let dataParts = ["unBond"]; for (const key of options.publicKeys) { - dataParts = dataParts.concat(key.hex()); + dataParts.push(key.hex()); } const transaction = new Transaction({ @@ -179,7 +202,7 @@ export class ValidatorsTransactionsFactory extends BaseFactory { async createTransactionForUnstakingNodes(sender: Address, options: resources.UnstakingInput): Promise { let dataParts = ["unStakeNodes"]; for (const key of options.publicKeys) { - dataParts = dataParts.concat(key.hex()); + dataParts.push(key.hex()); } const transaction = new Transaction({ @@ -199,7 +222,7 @@ export class ValidatorsTransactionsFactory extends BaseFactory { return transaction; } - async createTransactionFoUnstakingTokens( + async createTransactionForUnstakingTokens( sender: Address, options: resources.UnstakingTokensInput, ): Promise { @@ -221,7 +244,7 @@ export class ValidatorsTransactionsFactory extends BaseFactory { async createTransactionForUnboundingNodes(sender: Address, options: resources.UnboundInput): Promise { let dataParts = ["unBondNodes"]; for (const key of options.publicKeys) { - dataParts = dataParts.concat(key.hex()); + dataParts.push(key.hex()); } const transaction = new Transaction({ sender, @@ -281,7 +304,7 @@ export class ValidatorsTransactionsFactory extends BaseFactory { ): Promise { let dataParts = ["reStakeUnStakedNodes"]; for (const key of options.publicKeys) { - dataParts = dataParts.concat(key.hex()); + dataParts.push(key.hex()); } const transaction = new Transaction({ sender, @@ -365,25 +388,4 @@ export class ValidatorsTransactionsFactory extends BaseFactory { return transaction; } - - private prepareDataPartsForStaking(options: { - nodeOperator: Address; - validatorsFile: import("./validatorsSigner").ValidatorsSigners; - rewardsAddress: Address | undefined; - }) { - const dataParts = ["stake"]; - const numOfNodes = options.validatorsFile.getNumOfNodes(); - const callArguments = []; - callArguments.push(new U32Value(numOfNodes)); - for (const signer of options.validatorsFile.getSigners()) { - const signedMessages = signer.sign(options.nodeOperator.getPublicKey()); - callArguments.push(new BytesValue(Buffer.from(signer.getPubkey()))); - callArguments.push(new BytesValue(Buffer.from(signedMessages))); - } - if (options.rewardsAddress) { - callArguments.push(new AddressValue(options.rewardsAddress)); - } - const args = this.argSerializer.valuesToStrings(callArguments); - return dataParts.concat(args); - } } From fd7510af8933613f9f4935eb15fd0499e9322f2f Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 16 Sep 2025 14:15:17 +0300 Subject: [PATCH 07/17] Fix naming --- src/validators/validatorsTransactionsFactory.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/validators/validatorsTransactionsFactory.spec.ts b/src/validators/validatorsTransactionsFactory.spec.ts index d129d5cc..e9616e96 100644 --- a/src/validators/validatorsTransactionsFactory.spec.ts +++ b/src/validators/validatorsTransactionsFactory.spec.ts @@ -7,7 +7,7 @@ import { ValidatorPublicKey } from "../wallet"; import { ValidatorsSigners } from "./validatorsSigner"; import { ValidatorsTransactionsFactory } from "./validatorsTransactionsFactory"; -describe("test delegation transactions factory", function () { +describe("test validator transactions factory", function () { const config = new TransactionsFactoryConfig({ chainID: "D" }); const validatorsFactory = new ValidatorsTransactionsFactory({ config: config }); const validatorsPath = `${getTestWalletsPath()}/validators.pem`; From d80d3c9b3ba459019f9b2761fdace14b32d3b9ca Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 16 Sep 2025 16:07:44 +0300 Subject: [PATCH 08/17] Mark method with deprecated --- src/wallet/validatorSigner.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wallet/validatorSigner.ts b/src/wallet/validatorSigner.ts index df98eb4a..ff684796 100644 --- a/src/wallet/validatorSigner.ts +++ b/src/wallet/validatorSigner.ts @@ -11,7 +11,9 @@ export class ValidatorSigner { constructor(secretKey: ValidatorSecretKey) { this.secretKey = secretKey; } + /** + * * @deprecated This method will be deprecated! Use the sign method directly. * Signs a message. */ static async signUsingPem( From 521faad257f8dd471bf98a1e5c834d38558853d1 Mon Sep 17 00:00:00 2001 From: danielailie Date: Wed, 17 Sep 2025 10:52:25 +0300 Subject: [PATCH 09/17] code review follow up --- src/validators/resources.ts | 6 ++-- .../validatorsTransactionsFactory.ts | 30 ++++++++++--------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/validators/resources.ts b/src/validators/resources.ts index 6f46271d..0f08b8b5 100644 --- a/src/validators/resources.ts +++ b/src/validators/resources.ts @@ -3,13 +3,13 @@ import { ValidatorPublicKey } from "../wallet"; import { ValidatorsSigners } from "./validatorsSigner"; export type StakingInput = { validatorsFile: ValidatorsSigners | string; amount: bigint; rewardsAddress?: Address }; -export type ChangeRewardsAddressInput = { rewardsAddress: Address }; +export type ChangingRewardsAddressInput = { rewardsAddress: Address }; export type ToppingUpInput = { amount: bigint }; export type UnstakingTokensInput = { amount: bigint }; export type UnstakingInput = { publicKeys: ValidatorPublicKey[] }; export type RestakingInput = { publicKeys: ValidatorPublicKey[] }; -export type UnboundInput = { publicKeys: ValidatorPublicKey[] }; -export type UnboundTokensInput = { amount: bigint }; +export type UnbondingInput = { publicKeys: ValidatorPublicKey[] }; +export type UnbondingTokensInput = { amount: bigint }; export type UnjailingInput = { publicKeys: ValidatorPublicKey[]; amount: bigint }; export type NewDelegationContractInput = { maxCap: bigint; fee: bigint }; export type MergeValidatorToDelegationInput = { delegationAddress: Address }; diff --git a/src/validators/validatorsTransactionsFactory.ts b/src/validators/validatorsTransactionsFactory.ts index d19212a4..f3b5950e 100644 --- a/src/validators/validatorsTransactionsFactory.ts +++ b/src/validators/validatorsTransactionsFactory.ts @@ -68,9 +68,11 @@ export class ValidatorsTransactionsFactory extends BaseFactory { callArguments.push(new BytesValue(Buffer.from(signer.getPubkey()))); callArguments.push(new BytesValue(Buffer.from(signedMessages))); } + if (options.rewardsAddress) { callArguments.push(new AddressValue(options.rewardsAddress)); } + const args = this.argSerializer.valuesToStrings(callArguments); return dataParts.concat(args); } @@ -117,7 +119,7 @@ export class ValidatorsTransactionsFactory extends BaseFactory { } async createTransactionForUnjailing(sender: Address, options: resources.UnjailingInput): Promise { - let dataParts = ["unJail"]; + const dataParts = ["unJail"]; for (const key of options.publicKeys) { dataParts.push(key.hex()); @@ -140,8 +142,8 @@ export class ValidatorsTransactionsFactory extends BaseFactory { return transaction; } - async createTransactionForUnbounding(sender: Address, options: resources.UnboundInput): Promise { - let dataParts = ["unBond"]; + async createTransactionForUnbonding(sender: Address, options: resources.UnbondingInput): Promise { + const dataParts = ["unBond"]; for (const key of options.publicKeys) { dataParts.push(key.hex()); @@ -166,9 +168,9 @@ export class ValidatorsTransactionsFactory extends BaseFactory { async createTransactionForChangingRewardsAddress( sender: Address, - options: resources.ChangeRewardsAddressInput, + options: resources.ChangingRewardsAddressInput, ): Promise { - let dataParts = ["changeRewardAddress", options.rewardsAddress.toHex()]; + const dataParts = ["changeRewardAddress", options.rewardsAddress.toHex()]; const transaction = new Transaction({ sender, @@ -200,7 +202,7 @@ export class ValidatorsTransactionsFactory extends BaseFactory { } async createTransactionForUnstakingNodes(sender: Address, options: resources.UnstakingInput): Promise { - let dataParts = ["unStakeNodes"]; + const dataParts = ["unStakeNodes"]; for (const key of options.publicKeys) { dataParts.push(key.hex()); } @@ -236,13 +238,13 @@ export class ValidatorsTransactionsFactory extends BaseFactory { }); this.setTransactionPayload(transaction, dataParts); - await this.setGasLimit(transaction, undefined, this.config.gasLimitForRestakingUnstakedTokens); + await this.setGasLimit(transaction, undefined, this.config.gasLimitForUnstakingTokens); return transaction; } - async createTransactionForUnboundingNodes(sender: Address, options: resources.UnboundInput): Promise { - let dataParts = ["unBondNodes"]; + async createTransactionForUnbondingNodes(sender: Address, options: resources.UnbondingInput): Promise { + const dataParts = ["unBondNodes"]; for (const key of options.publicKeys) { dataParts.push(key.hex()); } @@ -265,7 +267,7 @@ export class ValidatorsTransactionsFactory extends BaseFactory { async createTransactionForUnboundingTokens( sender: Address, - options: resources.UnboundTokensInput, + options: resources.UnbondingTokensInput, ): Promise { const dataParts = ["unBondTokens", this.argSerializer.valuesToStrings([new BigUIntValue(options.amount)])[0]]; @@ -302,7 +304,7 @@ export class ValidatorsTransactionsFactory extends BaseFactory { sender: Address, options: resources.RestakingInput, ): Promise { - let dataParts = ["reStakeUnStakedNodes"]; + const dataParts = ["reStakeUnStakedNodes"]; for (const key of options.publicKeys) { dataParts.push(key.hex()); } @@ -327,7 +329,7 @@ export class ValidatorsTransactionsFactory extends BaseFactory { sender: Address, options: resources.NewDelegationContractInput, ): Promise { - let dataParts = [ + const dataParts = [ "makeNewContractFromValidatorData", ...this.argSerializer.valuesToStrings([new BigUIntValue(options.maxCap), new BigUIntValue(options.fee)]), ]; @@ -349,7 +351,7 @@ export class ValidatorsTransactionsFactory extends BaseFactory { sender: Address, options: resources.MergeValidatorToDelegationInput, ): Promise { - let dataParts = [ + const dataParts = [ "mergeValidatorToDelegationWithWhitelist", this.argSerializer.valuesToStrings([new AddressValue(options.delegationAddress)])[0], ]; @@ -371,7 +373,7 @@ export class ValidatorsTransactionsFactory extends BaseFactory { sender: Address, options: resources.MergeValidatorToDelegationInput, ): Promise { - let dataParts = [ + const dataParts = [ "mergeValidatorToDelegationSameOwner", this.argSerializer.valuesToStrings([new AddressValue(options.delegationAddress)])[0], ]; From 5e8250ff432f2ee8e61ea4301c73a89caea3d643 Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 16 Sep 2025 13:54:28 +0300 Subject: [PATCH 10/17] add toText tests and update private fields --- src/wallet/validatorPem.spec.ts | 11 +++++++++++ src/wallet/validatorPem.ts | 6 +++--- src/wallet/validatorSigner.ts | 8 ++++++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/wallet/validatorPem.spec.ts b/src/wallet/validatorPem.spec.ts index 140c0c93..746ef963 100644 --- a/src/wallet/validatorPem.spec.ts +++ b/src/wallet/validatorPem.spec.ts @@ -57,4 +57,15 @@ describe("test ValidatorPEMs", () => { ); assert.equal(entry.secretKey.hex(), "3034b1d58628a842984da0c70da0b5a251ebb2aebf51afc5b586e2839b5e5263"); }); + + it("should convert to text", async function () { + let text = fs.readFileSync(path.join(walletsPath, "validatorKey00.pem"), "utf-8").trim(); + const entry = await ValidatorPEM.fromTextAll(text); + assert.deepEqual(entry[0].toText(), text); + + text = fs.readFileSync(path.join(walletsPath, "multipleValidatorKeys.pem"), "utf-8").trim(); + const entries = await ValidatorPEM.fromTextAll(text); + const actualText = entries.map((entry) => entry.toText()).join("\n"); + assert.deepEqual(actualText, text); + }); }); diff --git a/src/wallet/validatorPem.ts b/src/wallet/validatorPem.ts index 335a7c1a..b7e07f56 100644 --- a/src/wallet/validatorPem.ts +++ b/src/wallet/validatorPem.ts @@ -4,8 +4,8 @@ import { PemEntry } from "../wallet/pemEntry"; import { BLS, ValidatorSecretKey } from "../wallet/validatorKeys"; export class ValidatorPEM { - label: string; - secretKey: ValidatorSecretKey; + readonly label: string; + readonly secretKey: ValidatorSecretKey; constructor(label: string, secretKey: ValidatorSecretKey) { this.label = label; @@ -30,8 +30,8 @@ export class ValidatorPEM { const entries = PemEntry.fromTextAll(text); const resultItems: ValidatorPEM[] = []; + await BLS.initIfNecessary(); for (const entry of entries) { - await BLS.initIfNecessary(); const secretKey = new ValidatorSecretKey(entry.message); const item = new ValidatorPEM(entry.label, secretKey); resultItems.push(item); diff --git a/src/wallet/validatorSigner.ts b/src/wallet/validatorSigner.ts index fa490bb5..df98eb4a 100644 --- a/src/wallet/validatorSigner.ts +++ b/src/wallet/validatorSigner.ts @@ -6,7 +6,7 @@ import { ValidatorPEM } from "./validatorPem"; * Validator signer (BLS signer) */ export class ValidatorSigner { - private secretKey: ValidatorSecretKey; + private readonly secretKey: ValidatorSecretKey; constructor(secretKey: ValidatorSecretKey) { this.secretKey = secretKey; @@ -14,7 +14,11 @@ export class ValidatorSigner { /** * Signs a message. */ - async signUsingPem(pemText: string, pemIndex: number = 0, signable: Buffer | Uint8Array): Promise { + static async signUsingPem( + pemText: string, + pemIndex: number = 0, + signable: Buffer | Uint8Array, + ): Promise { await BLS.initIfNecessary(); try { From ab986a836c9a1dd347e89561761328e48a238edd Mon Sep 17 00:00:00 2001 From: danielailie Date: Tue, 16 Sep 2025 16:07:44 +0300 Subject: [PATCH 11/17] Mark method with deprecated --- src/wallet/validatorSigner.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wallet/validatorSigner.ts b/src/wallet/validatorSigner.ts index df98eb4a..ff684796 100644 --- a/src/wallet/validatorSigner.ts +++ b/src/wallet/validatorSigner.ts @@ -11,7 +11,9 @@ export class ValidatorSigner { constructor(secretKey: ValidatorSecretKey) { this.secretKey = secretKey; } + /** + * * @deprecated This method will be deprecated! Use the sign method directly. * Signs a message. */ static async signUsingPem( From 6c839f8bffe391770e5c7ee19089c76ce89024c8 Mon Sep 17 00:00:00 2001 From: danielailie Date: Wed, 17 Sep 2025 11:19:04 +0300 Subject: [PATCH 12/17] Fix naming --- src/validators/validatorsTransactionsFactory.spec.ts | 8 ++++---- src/validators/validatorsTransactionsFactory.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/validators/validatorsTransactionsFactory.spec.ts b/src/validators/validatorsTransactionsFactory.spec.ts index e9616e96..58c89cc1 100644 --- a/src/validators/validatorsTransactionsFactory.spec.ts +++ b/src/validators/validatorsTransactionsFactory.spec.ts @@ -198,8 +198,8 @@ describe("test validator transactions factory", function () { assert.deepEqual(Buffer.from(transaction.data).toString(), "unStakeTokens@98a7d9b8314c0000"); }); - it("should create 'Transaction' for unbounding nodes", async function () { - const transaction = await validatorsFactory.createTransactionForUnboundingNodes(alice, { + it("should create 'Transaction' for unbonding nodes", async function () { + const transaction = await validatorsFactory.createTransactionForUnbondingNodes(alice, { publicKeys: [validatorPubkey], }); @@ -219,8 +219,8 @@ describe("test validator transactions factory", function () { ); }); - it("should create 'Transaction' for unbounding tokens", async function () { - const transaction = await validatorsFactory.createTransactionForUnboundingTokens(alice, { + it("should create 'Transaction' for unbonding tokens", async function () { + const transaction = await validatorsFactory.createTransactionForUnbondingTokens(alice, { amount: 20000000000000000000n, }); diff --git a/src/validators/validatorsTransactionsFactory.ts b/src/validators/validatorsTransactionsFactory.ts index f3b5950e..5532c590 100644 --- a/src/validators/validatorsTransactionsFactory.ts +++ b/src/validators/validatorsTransactionsFactory.ts @@ -265,7 +265,7 @@ export class ValidatorsTransactionsFactory extends BaseFactory { return transaction; } - async createTransactionForUnboundingTokens( + async createTransactionForUnbondingTokens( sender: Address, options: resources.UnbondingTokensInput, ): Promise { From 0f7bc856f8c8ef34c2dbe01470cc040a5b026cec Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 18 Sep 2025 10:04:16 +0300 Subject: [PATCH 13/17] Fix build --- src/validators/validatorsController.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/validators/validatorsController.ts b/src/validators/validatorsController.ts index 9b77324d..8f67dc78 100644 --- a/src/validators/validatorsController.ts +++ b/src/validators/validatorsController.ts @@ -97,9 +97,9 @@ export class ValidatorsController extends BaseController { async createTransactionForUnbounding( sender: IAccount, nonce: bigint, - options: resources.UnboundInput & BaseControllerInput, + options: resources.UnbondingInput & BaseControllerInput, ): Promise { - const transaction = await this.factory.createTransactionForUnbounding(sender.address, options); + const transaction = await this.factory.createTransactionForUnbonding(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -114,7 +114,7 @@ export class ValidatorsController extends BaseController { async createTransactionForChangingRewardsAddress( sender: IAccount, nonce: bigint, - options: resources.ChangeRewardsAddressInput & BaseControllerInput, + options: resources.ChangingRewardsAddressInput & BaseControllerInput, ): Promise { const transaction = await this.factory.createTransactionForChangingRewardsAddress(sender.address, options); @@ -162,12 +162,12 @@ export class ValidatorsController extends BaseController { return transaction; } - async createTransactionFoUnstakingTokens( + async createTransactionForUnstakingTokens( sender: IAccount, nonce: bigint, options: resources.UnstakingTokensInput & BaseControllerInput, ): Promise { - const transaction = await this.factory.createTransactionFoUnstakingTokens(sender.address, options); + const transaction = await this.factory.createTransactionForUnstakingTokens(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -182,9 +182,9 @@ export class ValidatorsController extends BaseController { async createTransactionForUnboundingNodes( sender: IAccount, nonce: bigint, - options: resources.UnboundInput & BaseControllerInput, + options: resources.UnbondingInput & BaseControllerInput, ): Promise { - const transaction = await this.factory.createTransactionForUnboundingNodes(sender.address, options); + const transaction = await this.factory.createTransactionForUnbondingNodes(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); @@ -196,12 +196,12 @@ export class ValidatorsController extends BaseController { return transaction; } - async createTransactionForUnboundingTokens( + async createTransactionForUnbondingTokens( sender: IAccount, nonce: bigint, - options: resources.UnboundTokensInput & BaseControllerInput, + options: resources.UnbondingTokensInput & BaseControllerInput, ): Promise { - const transaction = await this.factory.createTransactionForUnboundingTokens(sender.address, options); + const transaction = await this.factory.createTransactionForUnbondingTokens(sender.address, options); transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); From 078ac0ba3d23e010459fed94ce2b13e106ab94ec Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 18 Sep 2025 10:42:44 +0300 Subject: [PATCH 14/17] Add tests and add methods to export validator via entrypoint --- src/entrypoints/entrypoints.ts | 17 + src/validators/validatorsController.spec.ts | 458 ++++++++++++++++++++ src/validators/validatorsController.ts | 4 +- 3 files changed, 477 insertions(+), 2 deletions(-) create mode 100644 src/validators/validatorsController.spec.ts diff --git a/src/entrypoints/entrypoints.ts b/src/entrypoints/entrypoints.ts index e230d168..ff15d013 100644 --- a/src/entrypoints/entrypoints.ts +++ b/src/entrypoints/entrypoints.ts @@ -22,6 +22,8 @@ import { SmartContractTransactionsFactory } from "../smartContracts"; import { SmartContractController } from "../smartContracts/smartContractController"; import { TokenManagementController, TokenManagementTransactionsFactory } from "../tokenManagement"; import { TransfersController, TransferTransactionsFactory } from "../transfers"; +import { ValidatorsTransactionsFactory } from "../validators"; +import { ValidatorsController } from "../validators/validatorsController"; import { UserSecretKey } from "../wallet"; import { DevnetEntrypointConfig, MainnetEntrypointConfig, TestnetEntrypointConfig } from "./config"; @@ -260,6 +262,21 @@ export class NetworkEntrypoint { gasLimitEstimator: this.withGasLimitEstimator ? this.createGasLimitEstimator() : undefined, }); } + + createValidatorsController(): ValidatorsController { + return new ValidatorsController({ + chainID: this.chainId, + networkProvider: this.networkProvider, + gasLimitEstimator: this.withGasLimitEstimator ? this.createGasLimitEstimator() : undefined, + }); + } + + createValidatorsTransactionsFactory(): ValidatorsTransactionsFactory { + return new ValidatorsTransactionsFactory({ + config: new TransactionsFactoryConfig({ chainID: this.chainId }), + gasLimitEstimator: this.withGasLimitEstimator ? this.createGasLimitEstimator() : undefined, + }); + } } export class TestnetEntrypoint extends NetworkEntrypoint { diff --git a/src/validators/validatorsController.spec.ts b/src/validators/validatorsController.spec.ts new file mode 100644 index 00000000..c855cffe --- /dev/null +++ b/src/validators/validatorsController.spec.ts @@ -0,0 +1,458 @@ +import { assert } from "chai"; +import path from "path"; +import { Account } from "../accounts"; +import { Address } from "../core"; +import { DELEGATION_MANAGER_SC_ADDRESS_HEX, STAKING_SMART_CONTRACT_ADDRESS_HEX } from "../core/constants"; +import { DevnetEntrypoint } from "../entrypoints/entrypoints"; +import { ValidatorPublicKey } from "../wallet"; +import { ValidatorsSigners } from "./validatorsSigner"; + +describe("test validator controller", function () { + const walletsPath = path.join("src", "testdata", "testwallets"); + const validatorsPath = path.join(walletsPath, "validators.pem"); + const entrypoint = new DevnetEntrypoint(); + + const rewardAddress = Address.newFromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"); + const validatorPubkey = new ValidatorPublicKey( + Buffer.from( + "e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208", + "hex", + ), + ); + + const validatorController = entrypoint.createValidatorsController(); + + it("should create 'Transaction' for staking from file path", async function () { + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + alice.nonce = 77777n; + const transaction = await validatorController.createTransactionForStaking( + alice, + BigInt(alice.getNonceThenIncrement().valueOf()), + { + validatorsFile: validatorsPath, + amount: 2500000000000000000000n, + rewardsAddress: rewardAddress, + }, + ); + + assert.deepEqual( + transaction.sender, + Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"), + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 2500000000000000000000n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.nonce, 77777n); + assert.equal(transaction.version, 2); + assert.equal(transaction.gasLimit, 11029500n); + assert.equal(transaction.options, 0); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "stake@02@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1865870f7f69162a2dfefd33fe232a9ca984c6f22d1ee3f6a5b34a8eb8c9f7319001f29d5a2eed85c1500aca19fa4189@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d@12b309791213aac8ad9f34f0d912261e30f9ab060859e4d515e020a98b91d82a7cd334e4b504bb93d6b75347cccd6318@b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba", + ); + }); + + it("should create 'Transaction' for staking using validators file", async function () { + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + alice.nonce = 77777n; + const validatorsFile = await ValidatorsSigners.newFromPem(validatorsPath); + const transaction = await validatorController.createTransactionForStaking( + alice, + BigInt(alice.getNonceThenIncrement().valueOf()), + { + validatorsFile: validatorsFile, + amount: 2500000000000000000000n, + rewardsAddress: rewardAddress, + }, + ); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 2500000000000000000000n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.nonce, 77777n); + assert.equal(transaction.version, 2); + assert.equal(transaction.gasLimit, 11029500n); + assert.equal(transaction.options, 0); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "stake@02@f8910e47cf9464777c912e6390758bb39715fffcb861b184017920e4a807b42553f2f21e7f3914b81bcf58b66a72ab16d97013ae1cff807cefc977ef8cbf116258534b9e46d19528042d16ef8374404a89b184e0a4ee18c77c49e454d04eae8d@1865870f7f69162a2dfefd33fe232a9ca984c6f22d1ee3f6a5b34a8eb8c9f7319001f29d5a2eed85c1500aca19fa4189@1b4e60e6d100cdf234d3427494dac55fbac49856cadc86bcb13a01b9bb05a0d9143e86c186c948e7ae9e52427c9523102efe9019a2a9c06db02993f2e3e6756576ae5a3ec7c235d548bc79de1a6990e1120ae435cb48f7fc436c9f9098b92a0d@12b309791213aac8ad9f34f0d912261e30f9ab060859e4d515e020a98b91d82a7cd334e4b504bb93d6b75347cccd6318@b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba", + ); + }); + + it("should create 'Transaction' for topping up", async function () { + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + alice.nonce = 77777n; + const transaction = await validatorController.createTransactionForToppingUp( + alice, + BigInt(alice.getNonceThenIncrement().valueOf()), + { + amount: 2500000000000000000000n, + }, + ); + + assert.equal(transaction.sender.toBech32(), "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 2500000000000000000000n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.nonce, 77777n); + assert.equal(transaction.gasLimit, 5057500n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual(Buffer.from(transaction.data).toString(), "stake"); + }); + + it("should create 'Transaction' for unstake", async function () { + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + alice.nonce = 77777n; + const transaction = await validatorController.createTransactionForUnstaking( + alice, + BigInt(alice.getNonceThenIncrement().valueOf()), + { + publicKeys: [validatorPubkey], + }, + ); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.nonce, 77777n); + assert.equal(transaction.gasLimit, 5350000n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "unStake@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208", + ); + }); + + it("should create 'Transaction' for unjail", async function () { + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + alice.nonce = 77777n; + const transaction = await validatorController.createTransactionForUnjailing( + alice, + BigInt(alice.getNonceThenIncrement().valueOf()), + { + publicKeys: [validatorPubkey], + amount: 2500000000000000000000n, + }, + ); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.nonce, 77777n); + assert.equal(transaction.gasLimit, 5348500n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "unJail@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208", + ); + }); + + it("should create 'Transaction' for changing rewards address", async function () { + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + alice.nonce = 77777n; + const transaction = await validatorController.createTransactionForChangingRewardsAddress( + alice, + BigInt(alice.getNonceThenIncrement().valueOf()), + { + rewardsAddress: rewardAddress, + }, + ); + + assert.deepEqual( + transaction.sender, + Address.newFromBech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"), + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.isDefined(transaction.data); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "changeRewardAddress@b2a11555ce521e4944e09ab17549d85b487dcd26c84b5017a39e31a3670889ba", + ); + assert.equal(transaction.value, 0n); + }); + + it("should create 'Transaction' for claiming", async function () { + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + alice.nonce = 77777n; + const transaction = await validatorController.createTransactionForClaiming( + alice, + BigInt(alice.getNonceThenIncrement().valueOf()), + {}, + ); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.nonce, 77777n); + assert.equal(transaction.gasLimit, 5057500n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual(Buffer.from(transaction.data).toString(), "claim"); + }); + + it("should create 'Transaction' for unstaking nodes", async function () { + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + alice.nonce = 77777n; + const transaction = await validatorController.createTransactionForUnstakingNodes( + alice, + BigInt(alice.getNonceThenIncrement().valueOf()), + { + publicKeys: [validatorPubkey], + }, + ); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.nonce, 77777n); + assert.equal(transaction.gasLimit, 5357500n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "unStakeNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208", + ); + }); + + it("should create 'Transaction' for unstaking tokens", async function () { + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + alice.nonce = 77777n; + const transaction = await validatorController.createTransactionForUnstakingTokens( + alice, + BigInt(alice.getNonceThenIncrement().valueOf()), + { + amount: 11000000000000000000n, + }, + ); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.nonce, 77777n); + assert.equal(transaction.gasLimit, 5095000n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual(Buffer.from(transaction.data).toString(), "unStakeTokens@98a7d9b8314c0000"); + }); + + it("should create 'Transaction' for unbonding nodes", async function () { + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + alice.nonce = 77777n; + const transaction = await validatorController.createTransactionForUnbondingNodes( + alice, + BigInt(alice.getNonceThenIncrement().valueOf()), + { + publicKeys: [validatorPubkey], + }, + ); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.nonce, 77777n); + assert.equal(transaction.gasLimit, 5356000n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "unBondNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208", + ); + }); + + it("should create 'Transaction' for unbonding tokens", async function () { + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + alice.nonce = 77777n; + const transaction = await validatorController.createTransactionForUnbondingTokens( + alice, + BigInt(alice.getNonceThenIncrement().valueOf()), + { + amount: 20000000000000000000n, + }, + ); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.nonce, 77777n); + assert.equal(transaction.gasLimit, 5096500n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual(Buffer.from(transaction.data).toString(), "unBondTokens@01158e460913d00000"); + }); + + it("should create 'Transaction' for cleaning registered data", async function () { + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + alice.nonce = 77777n; + const transaction = await validatorController.createTransactionForCleaningRegisteredData( + alice, + BigInt(alice.getNonceThenIncrement().valueOf()), + {}, + ); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.nonce, 77777n); + assert.equal(transaction.gasLimit, 5078500n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual(Buffer.from(transaction.data).toString(), "cleanRegisteredData"); + }); + + it("should create 'Transaction' for restaking unstaked nodes", async function () { + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + alice.nonce = 77777n; + const transaction = await validatorController.createTransactionForRestakingUnstakedNodes( + alice, + BigInt(alice.getNonceThenIncrement().valueOf()), + { + publicKeys: [validatorPubkey], + }, + ); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(STAKING_SMART_CONTRACT_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.nonce, 77777n); + assert.equal(transaction.gasLimit, 5369500n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "reStakeUnStakedNodes@e7beaa95b3877f47348df4dd1cb578a4f7cabf7a20bfeefe5cdd263878ff132b765e04fef6f40c93512b666c47ed7719b8902f6c922c04247989b7137e837cc81a62e54712471c97a2ddab75aa9c2f58f813ed4c0fa722bde0ab718bff382208", + ); + }); + + it("should create 'Transaction' for new delegation contract from validator", async function () { + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + alice.nonce = 77777n; + const transaction = await validatorController.createTransactionForNewDelegationContractFromValidatorData( + alice, + BigInt(alice.getNonceThenIncrement().valueOf()), + { + maxCap: 0n, + fee: 3745n, + }, + ); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.nonce, 77777n); + assert.equal(transaction.gasLimit, 51107000n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual(Buffer.from(transaction.data).toString(), "makeNewContractFromValidatorData@@0ea1"); + }); + + it("should create 'Transaction' for merging validator to delegation whitelisting", async function () { + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + alice.nonce = 77777n; + const delegationContract = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc", + ); + + const transaction = await validatorController.createTransactionForMergingValidatorToDelegationWithWhitelist( + alice, + BigInt(alice.getNonceThenIncrement().valueOf()), + { + delegationAddress: delegationContract, + }, + ); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.nonce, 77777n); + assert.equal(transaction.gasLimit, 5206000n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "mergeValidatorToDelegationWithWhitelist@000000000000000000010000000000000000000000000000000000002fffffff", + ); + }); + + it("should create 'Transaction' for merging validator to delegation same owner", async function () { + const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + alice.nonce = 77777n; + const delegationContract = Address.newFromBech32( + "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc", + ); + + const transaction = await validatorController.createTransactionForMergingValidatorToDelegationSameOwner( + alice, + BigInt(alice.getNonceThenIncrement().valueOf()), + { + delegationAddress: delegationContract, + }, + ); + + assert.deepEqual( + transaction.sender.toBech32(), + "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", + ); + assert.deepEqual(transaction.receiver, Address.newFromHex(DELEGATION_MANAGER_SC_ADDRESS_HEX)); + assert.equal(transaction.value, 0n); + assert.equal(transaction.chainID, "D"); + assert.equal(transaction.nonce, 77777n); + assert.equal(transaction.gasLimit, 50200000n); + assert.equal(transaction.version, 2); + assert.equal(transaction.options, 0); + assert.deepEqual( + Buffer.from(transaction.data).toString(), + "mergeValidatorToDelegationSameOwner@000000000000000000010000000000000000000000000000000000002fffffff", + ); + }); +}); diff --git a/src/validators/validatorsController.ts b/src/validators/validatorsController.ts index 8f67dc78..b6d88901 100644 --- a/src/validators/validatorsController.ts +++ b/src/validators/validatorsController.ts @@ -94,7 +94,7 @@ export class ValidatorsController extends BaseController { return transaction; } - async createTransactionForUnbounding( + async createTransactionForUnbonding( sender: IAccount, nonce: bigint, options: resources.UnbondingInput & BaseControllerInput, @@ -179,7 +179,7 @@ export class ValidatorsController extends BaseController { return transaction; } - async createTransactionForUnboundingNodes( + async createTransactionForUnbondingNodes( sender: IAccount, nonce: bigint, options: resources.UnbondingInput & BaseControllerInput, From 7fd18a92eb768528d293a653d4de7da56d7ac74f Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 18 Sep 2025 11:57:35 +0300 Subject: [PATCH 15/17] Refactor validator controller --- src/validators/validatorsController.ts | 118 ++++++------------------- 1 file changed, 26 insertions(+), 92 deletions(-) diff --git a/src/validators/validatorsController.ts b/src/validators/validatorsController.ts index b6d88901..57615d59 100644 --- a/src/validators/validatorsController.ts +++ b/src/validators/validatorsController.ts @@ -33,12 +33,7 @@ export class ValidatorsController extends BaseController { ): Promise { const transaction = await this.factory.createTransactionForStaking(sender.address, options); - transaction.guardian = options.guardian ?? Address.empty(); - transaction.relayer = options.relayer ?? Address.empty(); - transaction.nonce = nonce; - this.setTransactionGasOptions(transaction, options); - this.setVersionAndOptionsForGuardian(transaction); - transaction.signature = await sender.signTransaction(transaction); + await this.setupAndSignTransaction(transaction, options, nonce, sender); return transaction; } @@ -50,12 +45,7 @@ export class ValidatorsController extends BaseController { ): Promise { const transaction = await this.factory.createTransactionForToppingUp(sender.address, options); - transaction.guardian = options.guardian ?? Address.empty(); - transaction.relayer = options.relayer ?? Address.empty(); - transaction.nonce = nonce; - this.setTransactionGasOptions(transaction, options); - this.setVersionAndOptionsForGuardian(transaction); - transaction.signature = await sender.signTransaction(transaction); + await this.setupAndSignTransaction(transaction, options, nonce, sender); return transaction; } @@ -67,12 +57,7 @@ export class ValidatorsController extends BaseController { ): Promise { const transaction = await this.factory.createTransactionForUnstaking(sender.address, options); - transaction.guardian = options.guardian ?? Address.empty(); - transaction.relayer = options.relayer ?? Address.empty(); - transaction.nonce = nonce; - this.setTransactionGasOptions(transaction, options); - this.setVersionAndOptionsForGuardian(transaction); - transaction.signature = await sender.signTransaction(transaction); + await this.setupAndSignTransaction(transaction, options, nonce, sender); return transaction; } @@ -84,12 +69,7 @@ export class ValidatorsController extends BaseController { ): Promise { const transaction = await this.factory.createTransactionForUnjailing(sender.address, options); - transaction.guardian = options.guardian ?? Address.empty(); - transaction.relayer = options.relayer ?? Address.empty(); - transaction.nonce = nonce; - this.setTransactionGasOptions(transaction, options); - this.setVersionAndOptionsForGuardian(transaction); - transaction.signature = await sender.signTransaction(transaction); + await this.setupAndSignTransaction(transaction, options, nonce, sender); return transaction; } @@ -101,12 +81,7 @@ export class ValidatorsController extends BaseController { ): Promise { const transaction = await this.factory.createTransactionForUnbonding(sender.address, options); - transaction.guardian = options.guardian ?? Address.empty(); - transaction.relayer = options.relayer ?? Address.empty(); - transaction.nonce = nonce; - this.setTransactionGasOptions(transaction, options); - this.setVersionAndOptionsForGuardian(transaction); - transaction.signature = await sender.signTransaction(transaction); + await this.setupAndSignTransaction(transaction, options, nonce, sender); return transaction; } @@ -118,12 +93,7 @@ export class ValidatorsController extends BaseController { ): Promise { const transaction = await this.factory.createTransactionForChangingRewardsAddress(sender.address, options); - transaction.guardian = options.guardian ?? Address.empty(); - transaction.relayer = options.relayer ?? Address.empty(); - transaction.nonce = nonce; - this.setTransactionGasOptions(transaction, options); - this.setVersionAndOptionsForGuardian(transaction); - transaction.signature = await sender.signTransaction(transaction); + await this.setupAndSignTransaction(transaction, options, nonce, sender); return transaction; } @@ -135,12 +105,7 @@ export class ValidatorsController extends BaseController { ): Promise { const transaction = await this.factory.createTransactionForClaiming(sender.address); - transaction.guardian = options.guardian ?? Address.empty(); - transaction.relayer = options.relayer ?? Address.empty(); - transaction.nonce = nonce; - this.setTransactionGasOptions(transaction, options); - this.setVersionAndOptionsForGuardian(transaction); - transaction.signature = await sender.signTransaction(transaction); + await this.setupAndSignTransaction(transaction, options, nonce, sender); return transaction; } @@ -152,12 +117,7 @@ export class ValidatorsController extends BaseController { ): Promise { const transaction = await this.factory.createTransactionForUnstakingNodes(sender.address, options); - transaction.guardian = options.guardian ?? Address.empty(); - transaction.relayer = options.relayer ?? Address.empty(); - transaction.nonce = nonce; - this.setTransactionGasOptions(transaction, options); - this.setVersionAndOptionsForGuardian(transaction); - transaction.signature = await sender.signTransaction(transaction); + await this.setupAndSignTransaction(transaction, options, nonce, sender); return transaction; } @@ -169,12 +129,7 @@ export class ValidatorsController extends BaseController { ): Promise { const transaction = await this.factory.createTransactionForUnstakingTokens(sender.address, options); - transaction.guardian = options.guardian ?? Address.empty(); - transaction.relayer = options.relayer ?? Address.empty(); - transaction.nonce = nonce; - this.setTransactionGasOptions(transaction, options); - this.setVersionAndOptionsForGuardian(transaction); - transaction.signature = await sender.signTransaction(transaction); + await this.setupAndSignTransaction(transaction, options, nonce, sender); return transaction; } @@ -186,12 +141,7 @@ export class ValidatorsController extends BaseController { ): Promise { const transaction = await this.factory.createTransactionForUnbondingNodes(sender.address, options); - transaction.guardian = options.guardian ?? Address.empty(); - transaction.relayer = options.relayer ?? Address.empty(); - transaction.nonce = nonce; - this.setTransactionGasOptions(transaction, options); - this.setVersionAndOptionsForGuardian(transaction); - transaction.signature = await sender.signTransaction(transaction); + await this.setupAndSignTransaction(transaction, options, nonce, sender); return transaction; } @@ -203,12 +153,7 @@ export class ValidatorsController extends BaseController { ): Promise { const transaction = await this.factory.createTransactionForUnbondingTokens(sender.address, options); - transaction.guardian = options.guardian ?? Address.empty(); - transaction.relayer = options.relayer ?? Address.empty(); - transaction.nonce = nonce; - this.setTransactionGasOptions(transaction, options); - this.setVersionAndOptionsForGuardian(transaction); - transaction.signature = await sender.signTransaction(transaction); + await this.setupAndSignTransaction(transaction, options, nonce, sender); return transaction; } @@ -220,12 +165,7 @@ export class ValidatorsController extends BaseController { ): Promise { const transaction = await this.factory.createTransactionForCleaningRegisteredData(sender.address); - transaction.guardian = options.guardian ?? Address.empty(); - transaction.relayer = options.relayer ?? Address.empty(); - transaction.nonce = nonce; - this.setTransactionGasOptions(transaction, options); - this.setVersionAndOptionsForGuardian(transaction); - transaction.signature = await sender.signTransaction(transaction); + await this.setupAndSignTransaction(transaction, options, nonce, sender); return transaction; } @@ -237,12 +177,7 @@ export class ValidatorsController extends BaseController { ): Promise { const transaction = await this.factory.createTransactionForRestakingUnstakedNodes(sender.address, options); - transaction.guardian = options.guardian ?? Address.empty(); - transaction.relayer = options.relayer ?? Address.empty(); - transaction.nonce = nonce; - this.setTransactionGasOptions(transaction, options); - this.setVersionAndOptionsForGuardian(transaction); - transaction.signature = await sender.signTransaction(transaction); + await this.setupAndSignTransaction(transaction, options, nonce, sender); return transaction; } @@ -257,12 +192,7 @@ export class ValidatorsController extends BaseController { options, ); - transaction.guardian = options.guardian ?? Address.empty(); - transaction.relayer = options.relayer ?? Address.empty(); - transaction.nonce = nonce; - this.setTransactionGasOptions(transaction, options); - this.setVersionAndOptionsForGuardian(transaction); - transaction.signature = await sender.signTransaction(transaction); + await this.setupAndSignTransaction(transaction, options, nonce, sender); return transaction; } @@ -277,12 +207,7 @@ export class ValidatorsController extends BaseController { options, ); - transaction.guardian = options.guardian ?? Address.empty(); - transaction.relayer = options.relayer ?? Address.empty(); - transaction.nonce = nonce; - this.setTransactionGasOptions(transaction, options); - this.setVersionAndOptionsForGuardian(transaction); - transaction.signature = await sender.signTransaction(transaction); + await this.setupAndSignTransaction(transaction, options, nonce, sender); return transaction; } @@ -297,13 +222,22 @@ export class ValidatorsController extends BaseController { options, ); + await this.setupAndSignTransaction(transaction, options, nonce, sender); + + return transaction; + } + + private async setupAndSignTransaction( + transaction: Transaction, + options: BaseControllerInput, + nonce: bigint, + sender: IAccount, + ) { transaction.guardian = options.guardian ?? Address.empty(); transaction.relayer = options.relayer ?? Address.empty(); transaction.nonce = nonce; this.setTransactionGasOptions(transaction, options); this.setVersionAndOptionsForGuardian(transaction); transaction.signature = await sender.signTransaction(transaction); - - return transaction; } } From c12469c92535703e122e8d2d55ac8774177e9577 Mon Sep 17 00:00:00 2001 From: danielailie Date: Thu, 18 Sep 2025 13:18:43 +0300 Subject: [PATCH 16/17] Update tests --- src/validators/validatorsController.spec.ts | 22 +++++---------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/src/validators/validatorsController.spec.ts b/src/validators/validatorsController.spec.ts index c855cffe..bff3ab79 100644 --- a/src/validators/validatorsController.spec.ts +++ b/src/validators/validatorsController.spec.ts @@ -11,6 +11,7 @@ describe("test validator controller", function () { const walletsPath = path.join("src", "testdata", "testwallets"); const validatorsPath = path.join(walletsPath, "validators.pem"); const entrypoint = new DevnetEntrypoint(); + let alice: Account; const rewardAddress = Address.newFromBech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8"); const validatorPubkey = new ValidatorPublicKey( @@ -19,11 +20,13 @@ describe("test validator controller", function () { "hex", ), ); - const validatorController = entrypoint.createValidatorsController(); + beforeEach(async function () { + alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); + }); + it("should create 'Transaction' for staking from file path", async function () { - const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); alice.nonce = 77777n; const transaction = await validatorController.createTransactionForStaking( alice, @@ -53,7 +56,6 @@ describe("test validator controller", function () { }); it("should create 'Transaction' for staking using validators file", async function () { - const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); alice.nonce = 77777n; const validatorsFile = await ValidatorsSigners.newFromPem(validatorsPath); const transaction = await validatorController.createTransactionForStaking( @@ -84,7 +86,6 @@ describe("test validator controller", function () { }); it("should create 'Transaction' for topping up", async function () { - const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); alice.nonce = 77777n; const transaction = await validatorController.createTransactionForToppingUp( alice, @@ -106,7 +107,6 @@ describe("test validator controller", function () { }); it("should create 'Transaction' for unstake", async function () { - const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); alice.nonce = 77777n; const transaction = await validatorController.createTransactionForUnstaking( alice, @@ -134,7 +134,6 @@ describe("test validator controller", function () { }); it("should create 'Transaction' for unjail", async function () { - const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); alice.nonce = 77777n; const transaction = await validatorController.createTransactionForUnjailing( alice, @@ -163,7 +162,6 @@ describe("test validator controller", function () { }); it("should create 'Transaction' for changing rewards address", async function () { - const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); alice.nonce = 77777n; const transaction = await validatorController.createTransactionForChangingRewardsAddress( alice, @@ -187,7 +185,6 @@ describe("test validator controller", function () { }); it("should create 'Transaction' for claiming", async function () { - const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); alice.nonce = 77777n; const transaction = await validatorController.createTransactionForClaiming( alice, @@ -210,7 +207,6 @@ describe("test validator controller", function () { }); it("should create 'Transaction' for unstaking nodes", async function () { - const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); alice.nonce = 77777n; const transaction = await validatorController.createTransactionForUnstakingNodes( alice, @@ -238,7 +234,6 @@ describe("test validator controller", function () { }); it("should create 'Transaction' for unstaking tokens", async function () { - const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); alice.nonce = 77777n; const transaction = await validatorController.createTransactionForUnstakingTokens( alice, @@ -263,7 +258,6 @@ describe("test validator controller", function () { }); it("should create 'Transaction' for unbonding nodes", async function () { - const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); alice.nonce = 77777n; const transaction = await validatorController.createTransactionForUnbondingNodes( alice, @@ -291,7 +285,6 @@ describe("test validator controller", function () { }); it("should create 'Transaction' for unbonding tokens", async function () { - const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); alice.nonce = 77777n; const transaction = await validatorController.createTransactionForUnbondingTokens( alice, @@ -316,7 +309,6 @@ describe("test validator controller", function () { }); it("should create 'Transaction' for cleaning registered data", async function () { - const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); alice.nonce = 77777n; const transaction = await validatorController.createTransactionForCleaningRegisteredData( alice, @@ -339,7 +331,6 @@ describe("test validator controller", function () { }); it("should create 'Transaction' for restaking unstaked nodes", async function () { - const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); alice.nonce = 77777n; const transaction = await validatorController.createTransactionForRestakingUnstakedNodes( alice, @@ -367,7 +358,6 @@ describe("test validator controller", function () { }); it("should create 'Transaction' for new delegation contract from validator", async function () { - const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); alice.nonce = 77777n; const transaction = await validatorController.createTransactionForNewDelegationContractFromValidatorData( alice, @@ -393,7 +383,6 @@ describe("test validator controller", function () { }); it("should create 'Transaction' for merging validator to delegation whitelisting", async function () { - const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); alice.nonce = 77777n; const delegationContract = Address.newFromBech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc", @@ -425,7 +414,6 @@ describe("test validator controller", function () { }); it("should create 'Transaction' for merging validator to delegation same owner", async function () { - const alice = await Account.newFromPem(path.join(walletsPath, "alice.pem")); alice.nonce = 77777n; const delegationContract = Address.newFromBech32( "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqtllllls002zgc", From bcee878b63ad117eac5abac3fae2e20050c83820 Mon Sep 17 00:00:00 2001 From: danielailie Date: Mon, 22 Sep 2025 13:43:06 +0300 Subject: [PATCH 17/17] Bump version --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index cfcecb3f..b78a6879 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@multiversx/sdk-core", - "version": "15.1.1", + "version": "15.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@multiversx/sdk-core", - "version": "15.1.1", + "version": "15.2.0", "license": "MIT", "dependencies": { "@multiversx/sdk-transaction-decoder": "1.0.2", diff --git a/package.json b/package.json index de492a4b..63a17ec8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@multiversx/sdk-core", - "version": "15.1.1", + "version": "15.2.0", "description": "MultiversX SDK for JavaScript and TypeScript", "author": "MultiversX", "homepage": "https://multiversx.com",