Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion modules/sdk-coin-vet/src/lib/transaction/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,8 @@ export class Transaction extends BaseTransaction {
this.type === TransactionType.StakingDelegate ||
this.type === TransactionType.StakingUnlock ||
this.type === TransactionType.StakingWithdraw ||
this.type === TransactionType.StakingClaim
this.type === TransactionType.StakingClaim ||
this.type === TransactionType.StakingLock
) {
transactionBody.reserved = {
features: 1, // mark transaction as delegated i.e. will use gas payer
Expand Down
1 change: 1 addition & 0 deletions modules/sdk-coin-vet/test/resources/vet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const CLAIM_REWARDS_TRANSACTION =
export const STAKING_LEVEL_ID = 8;
export const STAKING_AUTORENEW = true;
export const STAKING_CONTRACT_ADDRESS = '0x1e02b2953adefec225cf0ec49805b1146a4429c1';
export const BUILT_IN_STAKER_CONTRACT_ADDRESS = '0x00000000000000000000000000005374616b6572';

export const VALID_TOKEN_SIGNABLE_PAYLOAD =
'f8762788014ead140e77bbc140f85ef85c940000000000000000000000000000456e6572677980b844a9059cbb000000000000000000000000e59f1cea4e0fef511e3d0f4eec44adf19c4cbeec000000000000000000000000000000000000000000000000016345785d8a000081808252088082faf8c101';
Expand Down
83 changes: 82 additions & 1 deletion modules/sdk-coin-vet/test/unit/stakingFlowE2E.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import should from 'should';
import { coins } from '@bitgo/statics';
import { TransactionType } from '@bitgo/sdk-core';
import { TransactionBuilderFactory, StakeClauseTransaction } from '../../src/lib';
import { TransactionBuilderFactory, StakeClauseTransaction, ValidatorRegistrationTransaction } from '../../src/lib';
import * as testData from '../resources/vet';

describe('VET Staking Flow - End-to-End Test', function () {
Expand All @@ -10,7 +10,10 @@ describe('VET Staking Flow - End-to-End Test', function () {

// Test data
const stakingContractAddress = testData.STAKING_CONTRACT_ADDRESS;
const builtInStakerContractAddress = testData.BUILT_IN_STAKER_CONTRACT_ADDRESS;
const amountToStake = '1000000000000000000'; // 1 VET in wei
const stakingPeriod = 60480;
const validatorAddress = '0x9a7afcacc88c106f3bbd6b213cd0821d9224d945';
const levelId = testData.STAKING_LEVEL_ID;
const senderAddress = '0x9378c12BD7502A11F770a5C1F223c959B2805dA9';
const feePayerAddress = '0xdc9fef0b84a0ccf3f1bd4b84e41743e3e051a083';
Expand Down Expand Up @@ -99,6 +102,84 @@ describe('VET Staking Flow - End-to-End Test', function () {
jsonOutput.should.have.property('levelId', levelId);
});

it('should build, sign, and serialize a complete validator registration transaction with fee delegation', async function () {
// Step 1: Build the staking transaction
const validatorRegistrationBuilder = factory.getValidatorRegistrationBuilder();

validatorRegistrationBuilder
.stakingContractAddress(builtInStakerContractAddress)
.stakingPeriod(60480)
.validator(validatorAddress)
.sender(senderAddress)
.chainTag(0x27) // Testnet chain tag
.blockRef('0x014ead140e77bbc1')
.expiration(64)
.gas(100000)
.gasPriceCoef(128)
.nonce('12345');

validatorRegistrationBuilder.addFeePayerAddress(feePayerAddress);

const unsignedTx = await validatorRegistrationBuilder.build();
should.exist(unsignedTx);
unsignedTx.should.be.instanceof(ValidatorRegistrationTransaction);

const validatorRegistrationTx = unsignedTx as ValidatorRegistrationTransaction;

// Verify transaction structure
validatorRegistrationTx.type.should.equal(TransactionType.StakingLock);
validatorRegistrationTx.stakingContractAddress.should.equal(builtInStakerContractAddress);
validatorRegistrationTx.stakingPeriod.should.equal(stakingPeriod);
validatorRegistrationTx.validator.should.equal(validatorAddress);

should.exist(validatorRegistrationTx.rawTransaction);
should.exist(validatorRegistrationTx.rawTransaction.body);

// This is the critical test - ensure reserved.features = 1
should.exist(validatorRegistrationTx.rawTransaction.body.reserved);
validatorRegistrationTx.rawTransaction.body.reserved!.should.have.property('features', 1);

// Step 3: Add sender signature
validatorRegistrationTx.addSenderSignature(mockSenderSignature);
should.exist(validatorRegistrationTx.senderSignature);
Buffer.from(validatorRegistrationTx.senderSignature!).should.eql(mockSenderSignature);

// Step 4: Add fee payer signature
validatorRegistrationTx.addFeePayerSignature(mockFeePayerSignature);
should.exist(validatorRegistrationTx.feePayerSignature);
Buffer.from(validatorRegistrationTx.feePayerSignature!).should.eql(mockFeePayerSignature);

// Step 5: Generate transaction ID

// This should NOT throw "not signed transaction: id unavailable" error anymore
const transactionId = validatorRegistrationTx.id;
should.exist(transactionId);
transactionId.should.not.equal('UNAVAILABLE');

// Step 6: Serialize the fully signed transaction
const serializedTx = validatorRegistrationTx.toBroadcastFormat();
should.exist(serializedTx);
serializedTx.should.be.type('string');
serializedTx.should.startWith('0x');

// Step 7: Verify transaction can be deserialized
const deserializedBuilder = factory.from(serializedTx);
const deserializedTx = deserializedBuilder.transaction as ValidatorRegistrationTransaction;

deserializedTx.should.be.instanceof(ValidatorRegistrationTransaction);
deserializedTx.stakingContractAddress.should.equal(builtInStakerContractAddress);
deserializedTx.stakingPeriod.should.equal(stakingPeriod);
deserializedTx.validator.should.equal(validatorAddress);

// Step 8: Verify toJson output
const jsonOutput = validatorRegistrationTx.toJson();
should.exist(jsonOutput);
jsonOutput.should.have.property('id', transactionId);
jsonOutput.should.have.property('stakingContractAddress', builtInStakerContractAddress);
jsonOutput.should.have.property('stakingPeriod', stakingPeriod);
jsonOutput.should.have.property('validatorAddress', validatorAddress);
});

it('should handle signature combination in the correct order', async function () {
// This test specifically validates the signature combination flow that was failing
const stakingBuilder = factory.getStakingActivateBuilder();
Expand Down