Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 2 additions & 0 deletions modules/sdk-coin-vet/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ export const DELEGATE_CLAUSE_METHOD_ID = '0x08bbb824';
export const ADD_VALIDATION_METHOD_ID = '0xc3c4b138';
export const INCREASE_STAKE_METHOD_ID = '0x43b0de9a';
export const DECREASE_STAKE_METHOD_ID = '0x1a73ba01';
export const SIGNAL_EXIT_METHOD_ID = '0xcb652cef';
export const WITHDRAW_STAKE_METHOD_ID = '0xc23a5cea';
export const EXIT_DELEGATION_METHOD_ID = '0x69e79b7d';
export const BURN_NFT_METHOD_ID = '0x2e17de78';
export const TRANSFER_NFT_METHOD_ID = '0x23b872dd';
Expand Down
4 changes: 4 additions & 0 deletions modules/sdk-coin-vet/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export { NFTTransaction } from './transaction/nftTransaction';
export { ValidatorRegistrationTransaction } from './transaction/validatorRegistrationTransaction';
export { IncreaseStakeTransaction } from './transaction/increaseStakeTransaction';
export { DecreaseStakeTransaction } from './transaction/decreaseStakeTransaction';
export { SignalExitTransaction } from './transaction/signalExitTransaction';
export { WithdrawStakeTransaction } from './transaction/withdrawStakeTransaction';
export { TransactionBuilder } from './transactionBuilder/transactionBuilder';
export { TransferBuilder } from './transactionBuilder/transferBuilder';
export { AddressInitializationBuilder } from './transactionBuilder/addressInitializationBuilder';
Expand All @@ -31,5 +33,7 @@ export { ClaimRewardsBuilder } from './transactionBuilder/claimRewardsBuilder';
export { ValidatorRegistrationBuilder } from './transactionBuilder/validatorRegistrationBuilder';
export { IncreaseStakeBuilder } from './transactionBuilder/increaseStakeBuilder';
export { DecreaseStakeBuilder } from './transactionBuilder/decreaseStakeBuilder';
export { SignalExitBuilder } from './transactionBuilder/signalExitBuilder';
export { WithdrawStakeBuilder } from './transactionBuilder/withdrawStakeBuilder';
export { TransactionBuilderFactory } from './transactionBuilderFactory';
export { Constants, Utils, Interface };
147 changes: 147 additions & 0 deletions modules/sdk-coin-vet/src/lib/transaction/signalExitTransaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { TransactionType, InvalidTransactionError } from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { Transaction as VetTransaction, Secp256k1 } from '@vechain/sdk-core';
import { Transaction } from './transaction';
import { VetTransactionData } from '../iface';
import EthereumAbi from 'ethereumjs-abi';
import utils from '../utils';
import BigNumber from 'bignumber.js';
import { addHexPrefix } from 'ethereumjs-util';
import { ZERO_VALUE_AMOUNT } from '../constants';

export class SignalExitTransaction extends Transaction {
private _stakingContractAddress: string;
private _validator: string;

constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
this._type = TransactionType.StakingUnvote;
}

get validator(): string {
return this._validator;
}

set validator(address: string) {
this._validator = address;
}

get stakingContractAddress(): string {
return this._stakingContractAddress;
}

set stakingContractAddress(address: string) {
this._stakingContractAddress = address;
}

buildClauses(): void {
if (!this.stakingContractAddress) {
throw new Error('Staking contract address is not set');
}

if (!this.validator) {
throw new Error('Validator address is not set');
}

utils.validateContractAddressForValidatorRegistration(this.stakingContractAddress, this._coinConfig);
const signalExitData = this.getSignalExitClauseData(this.validator);
this._transactionData = signalExitData;
this._clauses = [
{
to: this.stakingContractAddress,
value: ZERO_VALUE_AMOUNT,
data: signalExitData,
},
];

this._recipients = [
{
address: this.stakingContractAddress,
amount: ZERO_VALUE_AMOUNT,
},
];
}

getSignalExitClauseData(validator: string): string {
const methodName = 'signalExit';
const types = ['address'];
const params = [validator];

const method = EthereumAbi.methodID(methodName, types);
const args = EthereumAbi.rawEncode(types, params);

return addHexPrefix(Buffer.concat([method, args]).toString('hex'));
}

toJson(): VetTransactionData {
return {
id: this.id,
chainTag: this.chainTag,
blockRef: this.blockRef,
expiration: this.expiration,
gasPriceCoef: this.gasPriceCoef,
gas: this.gas,
dependsOn: this.dependsOn,
nonce: this.nonce,
data: this.transactionData,
value: ZERO_VALUE_AMOUNT,
sender: this.sender,
to: this.stakingContractAddress,
stakingContractAddress: this.stakingContractAddress,
validatorAddress: this.validator,
};
}

fromDeserializedSignedTransaction(signedTx: VetTransaction): void {
try {
if (!signedTx || !signedTx.body) {
throw new InvalidTransactionError('Invalid transaction: missing transaction body');
}

this.rawTransaction = signedTx;

const body = signedTx.body;
this.chainTag = typeof body.chainTag === 'number' ? body.chainTag : 0;
this.blockRef = body.blockRef || '0x0';
this.expiration = typeof body.expiration === 'number' ? body.expiration : 64;
this.clauses = body.clauses || [];
this.gasPriceCoef = typeof body.gasPriceCoef === 'number' ? body.gasPriceCoef : 128;
this.gas = typeof body.gas === 'number' ? body.gas : Number(body.gas) || 0;
this.dependsOn = body.dependsOn || null;
this.nonce = String(body.nonce);

if (body.clauses.length > 0) {
const clause = body.clauses[0];
if (clause.to) {
this.stakingContractAddress = clause.to;
}

if (clause.data) {
this.transactionData = clause.data;
const decoded = utils.decodeSignalExitData(clause.data);
this.validator = decoded.validator;
}
}

this.recipients = body.clauses.map((clause) => ({
address: (clause.to || '0x0').toString().toLowerCase(),
amount: new BigNumber(clause.value || 0).toString(),
}));
this.loadInputsAndOutputs();

if (signedTx.signature && signedTx.origin) {
this.sender = signedTx.origin.toString().toLowerCase();
}

if (signedTx.signature) {
this.senderSignature = Buffer.from(signedTx.signature.slice(0, Secp256k1.SIGNATURE_LENGTH));

if (signedTx.signature.length > Secp256k1.SIGNATURE_LENGTH) {
this.feePayerSignature = Buffer.from(signedTx.signature.slice(Secp256k1.SIGNATURE_LENGTH));
}
}
} catch (e) {
throw new InvalidTransactionError(`Failed to deserialize transaction: ${e.message}`);
}
}
}
4 changes: 3 additions & 1 deletion modules/sdk-coin-vet/src/lib/transaction/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,9 @@ export class Transaction extends BaseTransaction {
this.type === TransactionType.StakingClaim ||
this.type === TransactionType.StakingLock ||
this.type === TransactionType.StakingAdd ||
this.type === TransactionType.StakingDeactivate
this.type === TransactionType.StakingDeactivate ||
this.type === TransactionType.StakingUnvote ||
this.type === TransactionType.StakingPledge
) {
transactionBody.reserved = {
features: 1, // mark transaction as delegated i.e. will use gas payer
Expand Down
147 changes: 147 additions & 0 deletions modules/sdk-coin-vet/src/lib/transaction/withdrawStakeTransaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { TransactionType, InvalidTransactionError } from '@bitgo/sdk-core';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
import { Transaction as VetTransaction, Secp256k1 } from '@vechain/sdk-core';
import { Transaction } from './transaction';
import { VetTransactionData } from '../iface';
import EthereumAbi from 'ethereumjs-abi';
import utils from '../utils';
import BigNumber from 'bignumber.js';
import { addHexPrefix } from 'ethereumjs-util';
import { ZERO_VALUE_AMOUNT } from '../constants';

export class WithdrawStakeTransaction extends Transaction {
private _stakingContractAddress: string;
private _validator: string;

constructor(_coinConfig: Readonly<CoinConfig>) {
super(_coinConfig);
this._type = TransactionType.StakingPledge;
}

get validator(): string {
return this._validator;
}

set validator(address: string) {
this._validator = address;
}

get stakingContractAddress(): string {
return this._stakingContractAddress;
}

set stakingContractAddress(address: string) {
this._stakingContractAddress = address;
}

buildClauses(): void {
if (!this.stakingContractAddress) {
throw new Error('Staking contract address is not set');
}

if (!this.validator) {
throw new Error('Validator address is not set');
}

utils.validateContractAddressForValidatorRegistration(this.stakingContractAddress, this._coinConfig);
const withdrawStakeData = this.getWithdrawStakeClauseData(this.validator);
this._transactionData = withdrawStakeData;
this._clauses = [
{
to: this.stakingContractAddress,
value: ZERO_VALUE_AMOUNT,
data: withdrawStakeData,
},
];

this._recipients = [
{
address: this.stakingContractAddress,
amount: ZERO_VALUE_AMOUNT,
},
];
}

getWithdrawStakeClauseData(validator: string): string {
const methodName = 'withdrawStake';
const types = ['address'];
const params = [validator];

const method = EthereumAbi.methodID(methodName, types);
const args = EthereumAbi.rawEncode(types, params);

return addHexPrefix(Buffer.concat([method, args]).toString('hex'));
}

toJson(): VetTransactionData {
return {
id: this.id,
chainTag: this.chainTag,
blockRef: this.blockRef,
expiration: this.expiration,
gasPriceCoef: this.gasPriceCoef,
gas: this.gas,
dependsOn: this.dependsOn,
nonce: this.nonce,
data: this.transactionData,
value: ZERO_VALUE_AMOUNT,
sender: this.sender,
to: this.stakingContractAddress,
stakingContractAddress: this.stakingContractAddress,
validatorAddress: this.validator,
};
}

fromDeserializedSignedTransaction(signedTx: VetTransaction): void {
try {
if (!signedTx || !signedTx.body) {
throw new InvalidTransactionError('Invalid transaction: missing transaction body');
}

this.rawTransaction = signedTx;

const body = signedTx.body;
this.chainTag = typeof body.chainTag === 'number' ? body.chainTag : 0;
this.blockRef = body.blockRef || '0x0';
this.expiration = typeof body.expiration === 'number' ? body.expiration : 64;
this.clauses = body.clauses || [];
this.gasPriceCoef = typeof body.gasPriceCoef === 'number' ? body.gasPriceCoef : 128;
this.gas = typeof body.gas === 'number' ? body.gas : Number(body.gas) || 0;
this.dependsOn = body.dependsOn || null;
this.nonce = String(body.nonce);

if (body.clauses.length > 0) {
const clause = body.clauses[0];
if (clause.to) {
this.stakingContractAddress = clause.to;
}

if (clause.data) {
this.transactionData = clause.data;
const decoded = utils.decodeWithdrawStakeData(clause.data);
this.validator = decoded.validator;
}
}

this.recipients = body.clauses.map((clause) => ({
address: (clause.to || '0x0').toString().toLowerCase(),
amount: new BigNumber(clause.value || 0).toString(),
}));
this.loadInputsAndOutputs();

if (signedTx.signature && signedTx.origin) {
this.sender = signedTx.origin.toString().toLowerCase();
}

if (signedTx.signature) {
this.senderSignature = Buffer.from(signedTx.signature.slice(0, Secp256k1.SIGNATURE_LENGTH));

if (signedTx.signature.length > Secp256k1.SIGNATURE_LENGTH) {
this.feePayerSignature = Buffer.from(signedTx.signature.slice(Secp256k1.SIGNATURE_LENGTH));
}
}
} catch (e) {
throw new InvalidTransactionError(`Failed to deserialize transaction: ${e.message}`);
}
}
}
Loading
Loading