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
13 changes: 8 additions & 5 deletions modules/sdk-coin-vet/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ export const VET_ADDRESS_LENGTH = 40;
export const VET_BLOCK_ID_LENGTH = 64;

export const TRANSFER_TOKEN_METHOD_ID = '0xa9059cbb';
export const STAKING_METHOD_ID = '0xa694fc3a';
export const EXIT_DELEGATION_METHOD_ID = '0x3fb7a871';
export const BURN_NFT_METHOD_ID = '0x42966c68';
export const STAKING_METHOD_ID = '0xd8da3bbf';
export const EXIT_DELEGATION_METHOD_ID = '0x69e79b7d';
export const BURN_NFT_METHOD_ID = '0x2e17de78';
export const TRANSFER_NFT_METHOD_ID = '0x23b872dd';
export const CLAIM_BASE_REWARDS_METHOD_ID = '0x037402d3';
export const CLAIM_STAKING_REWARDS_METHOD_ID = '0xeb2767fa';
export const CLAIM_BASE_REWARDS_METHOD_ID = '0x858d50e8'; // claimVetGeneratedVtho(uint256)
export const CLAIM_STAKING_REWARDS_METHOD_ID = '0x0962ef79'; // claimRewards(uint256)

export const STARGATE_NFT_ADDRESS = '0x1856c533ac2d94340aaa8544d35a5c1d4a21dee7';
export const STARGATE_DELEGATION_ADDRESS = '0x4cb1c9ef05b529c093371264fab2c93cc6cddb0e';

export const STARGATE_NFT_ADDRESS_TESTNET = '0x1ec1d168574603ec35b9d229843b7c2b44bcb770';
export const STARGATE_DELEGATION_ADDRESS_TESTNET = '0x7240e3bc0d26431512d5b67dbd26d199205bffe8';
2 changes: 2 additions & 0 deletions modules/sdk-coin-vet/src/lib/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export interface VetTransactionData {
tokenId?: string; // Added for unstaking and burn NFT transactions
stakingContractAddress?: string;
amountToStake?: string;
nftTokenId?: number; // Used as tier level (levelId) for stakeAndDelegate method (not the actual NFT token ID)
autorenew?: boolean; // Autorenew flag for stakeAndDelegate method
nftCollectionId?: string;
claimRewardsData?: ClaimRewardsData;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { VetTransactionData } from '../iface';
import { BURN_NFT_METHOD_ID } from '../constants';
import EthereumAbi from 'ethereumjs-abi';
import { addHexPrefix } from 'ethereumjs-util';
import utils from '../utils';

export class BurnNftTransaction extends Transaction {
private _tokenId: string;
Expand Down Expand Up @@ -37,6 +38,8 @@ export class BurnNftTransaction extends Transaction {
throw new InvalidTransactionError('Missing required burn NFT parameters');
}

utils.validateStakingContractAddress(this._contract, this._coinConfig);

this._clauses = [
{
to: this._contract,
Expand All @@ -53,7 +56,7 @@ export class BurnNftTransaction extends Transaction {
* @returns {string} The encoded transaction data as a hex string
*/
private getBurnNftData(): string {
const methodName = 'burn';
const methodName = 'unstake';
const types = ['uint256'];
const params = [this._tokenId];

Expand Down Expand Up @@ -110,9 +113,7 @@ export class BurnNftTransaction extends Transaction {

// Extract tokenId from transaction data
if (this.transactionData.startsWith(BURN_NFT_METHOD_ID)) {
const tokenIdHex = this.transactionData.slice(BURN_NFT_METHOD_ID.length);
// Convert hex to decimal
this.tokenId = parseInt(tokenIdHex, 16).toString();
this.tokenId = utils.decodeBurnNftData(this.transactionData);
}

// Set sender address
Expand Down
117 changes: 33 additions & 84 deletions modules/sdk-coin-vet/src/lib/transaction/claimRewards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,8 @@ import { Transaction as VetTransaction, Secp256k1, TransactionClause } from '@ve
import { Transaction } from './transaction';
import { VetTransactionData } from '../iface';
import { ClaimRewardsData } from '../types';
import {
CLAIM_BASE_REWARDS_METHOD_ID,
CLAIM_STAKING_REWARDS_METHOD_ID,
STARGATE_DELEGATION_ADDRESS,
} from '../constants';
import { CLAIM_BASE_REWARDS_METHOD_ID, CLAIM_STAKING_REWARDS_METHOD_ID } from '../constants';
import utils from '../utils';

export class ClaimRewardsTransaction extends Transaction {
private _claimRewardsData: ClaimRewardsData;
Expand Down Expand Up @@ -83,42 +80,26 @@ export class ClaimRewardsTransaction extends Transaction {
}

/**
* Get the delegation contract address to use for claims
* Uses the address from claimRewardsData if provided, otherwise falls back to default
*/
private getDelegationAddress(): string {
return this._claimRewardsData.delegationContractAddress || STARGATE_DELEGATION_ADDRESS;
}

/**
* Build clause for claiming base rewards
* Build clause for claiming base rewards (claimVetGeneratedVtho)
*/
private buildClaimBaseRewardsClause(): TransactionClause {
const methodData = this.encodeClaimRewardsMethod(
CLAIM_BASE_REWARDS_METHOD_ID,
this._claimRewardsData.validatorAddress,
this._claimRewardsData.delegatorAddress
);
const methodData = this.encodeClaimRewardsMethod(CLAIM_BASE_REWARDS_METHOD_ID, this._claimRewardsData.tokenId);

return {
to: this.getDelegationAddress(),
to: utils.getDefaultStakingAddress(this._coinConfig),
value: '0x0',
data: methodData,
};
}

/**
* Build clause for claiming staking rewards
* Build clause for claiming staking rewards (claimRewards)
*/
private buildClaimStakingRewardsClause(): TransactionClause {
const methodData = this.encodeClaimRewardsMethod(
CLAIM_STAKING_REWARDS_METHOD_ID,
this._claimRewardsData.validatorAddress,
this._claimRewardsData.delegatorAddress
);
const methodData = this.encodeClaimRewardsMethod(CLAIM_STAKING_REWARDS_METHOD_ID, this._claimRewardsData.tokenId);

return {
to: this.getDelegationAddress(),
to: utils.getDefaultDelegationAddress(this._coinConfig),
value: '0x0',
data: methodData,
};
Expand All @@ -127,10 +108,10 @@ export class ClaimRewardsTransaction extends Transaction {
/**
* Encode the claim rewards method call data
*/
private encodeClaimRewardsMethod(methodId: string, validatorAddress: string, delegatorAddress: string): string {
const methodName = methodId === CLAIM_BASE_REWARDS_METHOD_ID ? 'claimBaseRewards' : 'claimStakingRewards';
const types = ['address', 'address'];
const params = [validatorAddress, delegatorAddress];
private encodeClaimRewardsMethod(methodId: string, tokenId: string): string {
const methodName = methodId === CLAIM_BASE_REWARDS_METHOD_ID ? 'claimVetGeneratedVtho' : 'claimRewards';
const types = ['uint256'];
const params = [tokenId];

const method = EthereumAbi.methodID(methodName, types);
const args = EthereumAbi.rawEncode(types, params);
Expand Down Expand Up @@ -215,35 +196,29 @@ export class ClaimRewardsTransaction extends Transaction {

let claimBaseRewards = false;
let claimStakingRewards = false;
let validatorAddress = '';
let delegatorAddress = '';
let tokenId = '';
let delegationContractAddress = '';
let stargateNftAddress = '';

for (const clause of clauses) {
// Check if this is a claim rewards clause by looking at the method ID in data
if (
clause.data &&
(clause.data.startsWith(CLAIM_BASE_REWARDS_METHOD_ID) ||
clause.data.startsWith(CLAIM_STAKING_REWARDS_METHOD_ID))
) {
// Store the contract address (could be different from default)
if (!delegationContractAddress) {
delegationContractAddress = clause.to || '';
}

if (clause.data) {
if (clause.data.startsWith(CLAIM_BASE_REWARDS_METHOD_ID)) {
// claimVetGeneratedVtho should go to STARGATE_NFT_ADDRESS
claimBaseRewards = true;
if (!validatorAddress || !delegatorAddress) {
const addresses = this.parseAddressesFromClaimData(clause.data);
validatorAddress = addresses.validator;
delegatorAddress = addresses.delegator;
if (!tokenId) {
tokenId = utils.decodeClaimRewardsData(clause.data);
}
if (!stargateNftAddress && clause.to) {
stargateNftAddress = clause.to;
}
} else if (clause.data.startsWith(CLAIM_STAKING_REWARDS_METHOD_ID)) {
// claimRewards should go to STARGATE_DELEGATION_ADDRESS
claimStakingRewards = true;
if (!validatorAddress || !delegatorAddress) {
const addresses = this.parseAddressesFromClaimData(clause.data);
validatorAddress = addresses.validator;
delegatorAddress = addresses.delegator;
if (!tokenId) {
tokenId = utils.decodeClaimRewardsData(clause.data);
}
if (!delegationContractAddress && clause.to) {
delegationContractAddress = clause.to;
}
}
}
Expand All @@ -254,41 +229,15 @@ export class ClaimRewardsTransaction extends Transaction {
}

this._claimRewardsData = {
validatorAddress,
delegatorAddress,
tokenId,
delegationContractAddress:
delegationContractAddress !== STARGATE_DELEGATION_ADDRESS ? delegationContractAddress : undefined,
delegationContractAddress && !utils.isDelegationContractAddress(delegationContractAddress)
? delegationContractAddress
: undefined,
stargateNftAddress:
stargateNftAddress && !utils.isNftContractAddress(stargateNftAddress) ? stargateNftAddress : undefined,
claimBaseRewards,
claimStakingRewards,
};
}

/**
* Parse validator and delegator addresses from claim rewards method data.
*
* The method data follows Ethereum ABI encoding where each parameter occupies 32 bytes (64 hex chars).
* After the 4-byte method ID, the parameters are laid out as:
* - Bytes 0-31 (chars 0-63): First address parameter (validator) - right-padded, actual address in bytes 12-31
* - Bytes 32-63 (chars 64-127): Second address parameter (delegator) - right-padded, actual address in bytes 44-63
*
* @param data The encoded method call data including method ID and parameters
* @returns Object containing the extracted validator and delegator addresses
*/
private parseAddressesFromClaimData(data: string): { validator: string; delegator: string } {
// Remove method ID (first 10 characters: '0x' + 4-byte method ID)
const methodData = data.slice(10);

// Extract validator address from first parameter (bytes 12-31 of first 32-byte slot)
// Slice 24-64: Skip first 12 bytes of padding (24 hex chars), take next 20 bytes (40 hex chars)
const validatorAddress = '0x' + methodData.slice(24, 64);

// Extract delegator address from second parameter (bytes 44-63 of second 32-byte slot)
// Slice 88-128: Skip to second slot + 12 bytes padding (88 hex chars), take next 20 bytes (40 hex chars)
const delegatorAddress = '0x' + methodData.slice(88, 128);

return {
validator: validatorAddress,
delegator: delegatorAddress,
};
}
}
9 changes: 5 additions & 4 deletions modules/sdk-coin-vet/src/lib/transaction/exitDelegation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { VetTransactionData } from '../iface';
import { EXIT_DELEGATION_METHOD_ID } from '../constants';
import EthereumAbi from 'ethereumjs-abi';
import { addHexPrefix } from 'ethereumjs-util';
import utils from '../utils';

export class ExitDelegationTransaction extends Transaction {
private _tokenId: string;
Expand Down Expand Up @@ -37,6 +38,8 @@ export class ExitDelegationTransaction extends Transaction {
throw new InvalidTransactionError('Missing required unstaking parameters');
}

utils.validateDelegationContractAddress(this._contract, this._coinConfig);

this._clauses = [
{
to: this._contract,
Expand All @@ -53,7 +56,7 @@ export class ExitDelegationTransaction extends Transaction {
* @returns {string} The encoded transaction data as a hex string
*/
private getExitDelegationData(): string {
const methodName = 'exitDelegation';
const methodName = 'requestDelegationExit';
const types = ['uint256'];
const params = [this._tokenId];

Expand Down Expand Up @@ -110,9 +113,7 @@ export class ExitDelegationTransaction extends Transaction {

// Extract tokenId from transaction data
if (this.transactionData.startsWith(EXIT_DELEGATION_METHOD_ID)) {
const tokenIdHex = this.transactionData.slice(EXIT_DELEGATION_METHOD_ID.length);
// Convert hex to decimal
this.tokenId = parseInt(tokenIdHex, 16).toString();
this.tokenId = utils.decodeExitDelegationData(this.transactionData);
}

// Set sender address
Expand Down
51 changes: 49 additions & 2 deletions modules/sdk-coin-vet/src/lib/transaction/stakingTransaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ import { VetTransactionData } from '../iface';
import EthereumAbi from 'ethereumjs-abi';
import utils from '../utils';
import BigNumber from 'bignumber.js';
import { addHexPrefix } from 'ethereumjs-util';

export class StakingTransaction extends Transaction {
private _stakingContractAddress: string;
private _levelId: number;
private _autorenew = true;
private _amountToStake: string;
private _stakingContractABI: EthereumAbi;

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

get stakingContractAddress(): string {
Expand All @@ -25,6 +29,22 @@ export class StakingTransaction extends Transaction {
this._stakingContractAddress = address;
}

get levelId(): number {
return this._levelId;
}

set levelId(levelId: number) {
this._levelId = levelId;
}

get autorenew(): boolean {
return this._autorenew;
}

set autorenew(autorenew: boolean) {
this._autorenew = autorenew;
}

get amountToStake(): string {
return this._amountToStake;
}
Expand All @@ -46,12 +66,17 @@ export class StakingTransaction extends Transaction {
throw new Error('Staking contract address is not set');
}

utils.validateStakingContractAddress(this.stakingContractAddress, this._coinConfig);

if (this.levelId === undefined || this.levelId === null) {
throw new Error('Level ID is not set');
}

if (!this.amountToStake) {
throw new Error('Amount to stake is not set');
}

// Generate transaction data using ethereumjs-abi
const data = utils.getStakingData(this.amountToStake);
const data = this.getStakingData(this.levelId, this.autorenew);
this._transactionData = data;

// Create the clause for staking
Expand All @@ -71,6 +96,23 @@ export class StakingTransaction extends Transaction {
},
];
}
/**
* Encodes staking transaction data using ethereumjs-abi for stakeAndDelegate method
*
* @param {number} levelId - The level ID for staking
* @param {boolean} autorenew - Whether to enable autorenew
* @returns {string} - The encoded transaction data
*/
getStakingData(levelId: number, autorenew = true): string {
const methodName = 'stakeAndDelegate';
const types = ['uint8', 'bool'];
const params = [levelId, autorenew];

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

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

toJson(): VetTransactionData {
const json: VetTransactionData = {
Expand All @@ -88,6 +130,8 @@ export class StakingTransaction extends Transaction {
to: this.stakingContractAddress,
stakingContractAddress: this.stakingContractAddress,
amountToStake: this.amountToStake,
nftTokenId: this.levelId,
autorenew: this.autorenew,
};

return json;
Expand Down Expand Up @@ -124,6 +168,9 @@ export class StakingTransaction extends Transaction {
}
if (clause.data) {
this.transactionData = clause.data;
const decoded = utils.decodeStakingData(clause.data);
this.levelId = decoded.levelId;
this.autorenew = decoded.autorenew;
}
}

Expand Down
Loading