Skip to content

Commit 7efc966

Browse files
authored
Merge pull request #7111 from BitGo/SC-3296
fix: correct methods and abi for vechain
2 parents 657a2ee + 6125807 commit 7efc966

File tree

17 files changed

+463
-297
lines changed

17 files changed

+463
-297
lines changed

modules/sdk-coin-vet/src/lib/constants.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@ export const VET_ADDRESS_LENGTH = 40;
33
export const VET_BLOCK_ID_LENGTH = 64;
44

55
export const TRANSFER_TOKEN_METHOD_ID = '0xa9059cbb';
6-
export const STAKING_METHOD_ID = '0xa694fc3a';
7-
export const EXIT_DELEGATION_METHOD_ID = '0x3fb7a871';
8-
export const BURN_NFT_METHOD_ID = '0x42966c68';
6+
export const STAKING_METHOD_ID = '0xd8da3bbf';
7+
export const EXIT_DELEGATION_METHOD_ID = '0x69e79b7d';
8+
export const BURN_NFT_METHOD_ID = '0x2e17de78';
99
export const TRANSFER_NFT_METHOD_ID = '0x23b872dd';
10-
export const CLAIM_BASE_REWARDS_METHOD_ID = '0x037402d3';
11-
export const CLAIM_STAKING_REWARDS_METHOD_ID = '0xeb2767fa';
10+
export const CLAIM_BASE_REWARDS_METHOD_ID = '0x858d50e8'; // claimVetGeneratedVtho(uint256)
11+
export const CLAIM_STAKING_REWARDS_METHOD_ID = '0x0962ef79'; // claimRewards(uint256)
1212

1313
export const STARGATE_NFT_ADDRESS = '0x1856c533ac2d94340aaa8544d35a5c1d4a21dee7';
1414
export const STARGATE_DELEGATION_ADDRESS = '0x4cb1c9ef05b529c093371264fab2c93cc6cddb0e';
15+
16+
export const STARGATE_NFT_ADDRESS_TESTNET = '0x1ec1d168574603ec35b9d229843b7c2b44bcb770';
17+
export const STARGATE_DELEGATION_ADDRESS_TESTNET = '0x7240e3bc0d26431512d5b67dbd26d199205bffe8';

modules/sdk-coin-vet/src/lib/iface.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ export interface VetTransactionData {
2828
tokenId?: string; // Added for unstaking and burn NFT transactions
2929
stakingContractAddress?: string;
3030
amountToStake?: string;
31+
nftTokenId?: number; // Used as tier level (levelId) for stakeAndDelegate method (not the actual NFT token ID)
32+
autorenew?: boolean; // Autorenew flag for stakeAndDelegate method
3133
nftCollectionId?: string;
3234
claimRewardsData?: ClaimRewardsData;
3335
}

modules/sdk-coin-vet/src/lib/transaction/burnNftTransaction.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { VetTransactionData } from '../iface';
66
import { BURN_NFT_METHOD_ID } from '../constants';
77
import EthereumAbi from 'ethereumjs-abi';
88
import { addHexPrefix } from 'ethereumjs-util';
9+
import utils from '../utils';
910

1011
export class BurnNftTransaction extends Transaction {
1112
private _tokenId: string;
@@ -37,6 +38,8 @@ export class BurnNftTransaction extends Transaction {
3738
throw new InvalidTransactionError('Missing required burn NFT parameters');
3839
}
3940

41+
utils.validateStakingContractAddress(this._contract, this._coinConfig);
42+
4043
this._clauses = [
4144
{
4245
to: this._contract,
@@ -53,7 +56,7 @@ export class BurnNftTransaction extends Transaction {
5356
* @returns {string} The encoded transaction data as a hex string
5457
*/
5558
private getBurnNftData(): string {
56-
const methodName = 'burn';
59+
const methodName = 'unstake';
5760
const types = ['uint256'];
5861
const params = [this._tokenId];
5962

@@ -110,9 +113,7 @@ export class BurnNftTransaction extends Transaction {
110113

111114
// Extract tokenId from transaction data
112115
if (this.transactionData.startsWith(BURN_NFT_METHOD_ID)) {
113-
const tokenIdHex = this.transactionData.slice(BURN_NFT_METHOD_ID.length);
114-
// Convert hex to decimal
115-
this.tokenId = parseInt(tokenIdHex, 16).toString();
116+
this.tokenId = utils.decodeBurnNftData(this.transactionData);
116117
}
117118

118119
// Set sender address

modules/sdk-coin-vet/src/lib/transaction/claimRewards.ts

Lines changed: 33 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,8 @@ import { Transaction as VetTransaction, Secp256k1, TransactionClause } from '@ve
66
import { Transaction } from './transaction';
77
import { VetTransactionData } from '../iface';
88
import { ClaimRewardsData } from '../types';
9-
import {
10-
CLAIM_BASE_REWARDS_METHOD_ID,
11-
CLAIM_STAKING_REWARDS_METHOD_ID,
12-
STARGATE_DELEGATION_ADDRESS,
13-
} from '../constants';
9+
import { CLAIM_BASE_REWARDS_METHOD_ID, CLAIM_STAKING_REWARDS_METHOD_ID } from '../constants';
10+
import utils from '../utils';
1411

1512
export class ClaimRewardsTransaction extends Transaction {
1613
private _claimRewardsData: ClaimRewardsData;
@@ -83,42 +80,26 @@ export class ClaimRewardsTransaction extends Transaction {
8380
}
8481

8582
/**
86-
* Get the delegation contract address to use for claims
87-
* Uses the address from claimRewardsData if provided, otherwise falls back to default
88-
*/
89-
private getDelegationAddress(): string {
90-
return this._claimRewardsData.delegationContractAddress || STARGATE_DELEGATION_ADDRESS;
91-
}
92-
93-
/**
94-
* Build clause for claiming base rewards
83+
* Build clause for claiming base rewards (claimVetGeneratedVtho)
9584
*/
9685
private buildClaimBaseRewardsClause(): TransactionClause {
97-
const methodData = this.encodeClaimRewardsMethod(
98-
CLAIM_BASE_REWARDS_METHOD_ID,
99-
this._claimRewardsData.validatorAddress,
100-
this._claimRewardsData.delegatorAddress
101-
);
86+
const methodData = this.encodeClaimRewardsMethod(CLAIM_BASE_REWARDS_METHOD_ID, this._claimRewardsData.tokenId);
10287

10388
return {
104-
to: this.getDelegationAddress(),
89+
to: utils.getDefaultStakingAddress(this._coinConfig),
10590
value: '0x0',
10691
data: methodData,
10792
};
10893
}
10994

11095
/**
111-
* Build clause for claiming staking rewards
96+
* Build clause for claiming staking rewards (claimRewards)
11297
*/
11398
private buildClaimStakingRewardsClause(): TransactionClause {
114-
const methodData = this.encodeClaimRewardsMethod(
115-
CLAIM_STAKING_REWARDS_METHOD_ID,
116-
this._claimRewardsData.validatorAddress,
117-
this._claimRewardsData.delegatorAddress
118-
);
99+
const methodData = this.encodeClaimRewardsMethod(CLAIM_STAKING_REWARDS_METHOD_ID, this._claimRewardsData.tokenId);
119100

120101
return {
121-
to: this.getDelegationAddress(),
102+
to: utils.getDefaultDelegationAddress(this._coinConfig),
122103
value: '0x0',
123104
data: methodData,
124105
};
@@ -127,10 +108,10 @@ export class ClaimRewardsTransaction extends Transaction {
127108
/**
128109
* Encode the claim rewards method call data
129110
*/
130-
private encodeClaimRewardsMethod(methodId: string, validatorAddress: string, delegatorAddress: string): string {
131-
const methodName = methodId === CLAIM_BASE_REWARDS_METHOD_ID ? 'claimBaseRewards' : 'claimStakingRewards';
132-
const types = ['address', 'address'];
133-
const params = [validatorAddress, delegatorAddress];
111+
private encodeClaimRewardsMethod(methodId: string, tokenId: string): string {
112+
const methodName = methodId === CLAIM_BASE_REWARDS_METHOD_ID ? 'claimVetGeneratedVtho' : 'claimRewards';
113+
const types = ['uint256'];
114+
const params = [tokenId];
134115

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

216197
let claimBaseRewards = false;
217198
let claimStakingRewards = false;
218-
let validatorAddress = '';
219-
let delegatorAddress = '';
199+
let tokenId = '';
220200
let delegationContractAddress = '';
201+
let stargateNftAddress = '';
221202

222203
for (const clause of clauses) {
223-
// Check if this is a claim rewards clause by looking at the method ID in data
224-
if (
225-
clause.data &&
226-
(clause.data.startsWith(CLAIM_BASE_REWARDS_METHOD_ID) ||
227-
clause.data.startsWith(CLAIM_STAKING_REWARDS_METHOD_ID))
228-
) {
229-
// Store the contract address (could be different from default)
230-
if (!delegationContractAddress) {
231-
delegationContractAddress = clause.to || '';
232-
}
233-
204+
if (clause.data) {
234205
if (clause.data.startsWith(CLAIM_BASE_REWARDS_METHOD_ID)) {
206+
// claimVetGeneratedVtho should go to STARGATE_NFT_ADDRESS
235207
claimBaseRewards = true;
236-
if (!validatorAddress || !delegatorAddress) {
237-
const addresses = this.parseAddressesFromClaimData(clause.data);
238-
validatorAddress = addresses.validator;
239-
delegatorAddress = addresses.delegator;
208+
if (!tokenId) {
209+
tokenId = utils.decodeClaimRewardsData(clause.data);
210+
}
211+
if (!stargateNftAddress && clause.to) {
212+
stargateNftAddress = clause.to;
240213
}
241214
} else if (clause.data.startsWith(CLAIM_STAKING_REWARDS_METHOD_ID)) {
215+
// claimRewards should go to STARGATE_DELEGATION_ADDRESS
242216
claimStakingRewards = true;
243-
if (!validatorAddress || !delegatorAddress) {
244-
const addresses = this.parseAddressesFromClaimData(clause.data);
245-
validatorAddress = addresses.validator;
246-
delegatorAddress = addresses.delegator;
217+
if (!tokenId) {
218+
tokenId = utils.decodeClaimRewardsData(clause.data);
219+
}
220+
if (!delegationContractAddress && clause.to) {
221+
delegationContractAddress = clause.to;
247222
}
248223
}
249224
}
@@ -254,41 +229,15 @@ export class ClaimRewardsTransaction extends Transaction {
254229
}
255230

256231
this._claimRewardsData = {
257-
validatorAddress,
258-
delegatorAddress,
232+
tokenId,
259233
delegationContractAddress:
260-
delegationContractAddress !== STARGATE_DELEGATION_ADDRESS ? delegationContractAddress : undefined,
234+
delegationContractAddress && !utils.isDelegationContractAddress(delegationContractAddress)
235+
? delegationContractAddress
236+
: undefined,
237+
stargateNftAddress:
238+
stargateNftAddress && !utils.isNftContractAddress(stargateNftAddress) ? stargateNftAddress : undefined,
261239
claimBaseRewards,
262240
claimStakingRewards,
263241
};
264242
}
265-
266-
/**
267-
* Parse validator and delegator addresses from claim rewards method data.
268-
*
269-
* The method data follows Ethereum ABI encoding where each parameter occupies 32 bytes (64 hex chars).
270-
* After the 4-byte method ID, the parameters are laid out as:
271-
* - Bytes 0-31 (chars 0-63): First address parameter (validator) - right-padded, actual address in bytes 12-31
272-
* - Bytes 32-63 (chars 64-127): Second address parameter (delegator) - right-padded, actual address in bytes 44-63
273-
*
274-
* @param data The encoded method call data including method ID and parameters
275-
* @returns Object containing the extracted validator and delegator addresses
276-
*/
277-
private parseAddressesFromClaimData(data: string): { validator: string; delegator: string } {
278-
// Remove method ID (first 10 characters: '0x' + 4-byte method ID)
279-
const methodData = data.slice(10);
280-
281-
// Extract validator address from first parameter (bytes 12-31 of first 32-byte slot)
282-
// Slice 24-64: Skip first 12 bytes of padding (24 hex chars), take next 20 bytes (40 hex chars)
283-
const validatorAddress = '0x' + methodData.slice(24, 64);
284-
285-
// Extract delegator address from second parameter (bytes 44-63 of second 32-byte slot)
286-
// Slice 88-128: Skip to second slot + 12 bytes padding (88 hex chars), take next 20 bytes (40 hex chars)
287-
const delegatorAddress = '0x' + methodData.slice(88, 128);
288-
289-
return {
290-
validator: validatorAddress,
291-
delegator: delegatorAddress,
292-
};
293-
}
294243
}

modules/sdk-coin-vet/src/lib/transaction/exitDelegation.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { VetTransactionData } from '../iface';
66
import { EXIT_DELEGATION_METHOD_ID } from '../constants';
77
import EthereumAbi from 'ethereumjs-abi';
88
import { addHexPrefix } from 'ethereumjs-util';
9+
import utils from '../utils';
910

1011
export class ExitDelegationTransaction extends Transaction {
1112
private _tokenId: string;
@@ -37,6 +38,8 @@ export class ExitDelegationTransaction extends Transaction {
3738
throw new InvalidTransactionError('Missing required unstaking parameters');
3839
}
3940

41+
utils.validateDelegationContractAddress(this._contract, this._coinConfig);
42+
4043
this._clauses = [
4144
{
4245
to: this._contract,
@@ -53,7 +56,7 @@ export class ExitDelegationTransaction extends Transaction {
5356
* @returns {string} The encoded transaction data as a hex string
5457
*/
5558
private getExitDelegationData(): string {
56-
const methodName = 'exitDelegation';
59+
const methodName = 'requestDelegationExit';
5760
const types = ['uint256'];
5861
const params = [this._tokenId];
5962

@@ -110,9 +113,7 @@ export class ExitDelegationTransaction extends Transaction {
110113

111114
// Extract tokenId from transaction data
112115
if (this.transactionData.startsWith(EXIT_DELEGATION_METHOD_ID)) {
113-
const tokenIdHex = this.transactionData.slice(EXIT_DELEGATION_METHOD_ID.length);
114-
// Convert hex to decimal
115-
this.tokenId = parseInt(tokenIdHex, 16).toString();
116+
this.tokenId = utils.decodeExitDelegationData(this.transactionData);
116117
}
117118

118119
// Set sender address

modules/sdk-coin-vet/src/lib/transaction/stakingTransaction.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,19 @@ import { VetTransactionData } from '../iface';
66
import EthereumAbi from 'ethereumjs-abi';
77
import utils from '../utils';
88
import BigNumber from 'bignumber.js';
9+
import { addHexPrefix } from 'ethereumjs-util';
910

1011
export class StakingTransaction extends Transaction {
1112
private _stakingContractAddress: string;
13+
private _levelId: number;
14+
private _autorenew = true;
1215
private _amountToStake: string;
1316
private _stakingContractABI: EthereumAbi;
1417

1518
constructor(_coinConfig: Readonly<CoinConfig>) {
1619
super(_coinConfig);
1720
this._type = TransactionType.ContractCall;
21+
this._autorenew = true;
1822
}
1923

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

32+
get levelId(): number {
33+
return this._levelId;
34+
}
35+
36+
set levelId(levelId: number) {
37+
this._levelId = levelId;
38+
}
39+
40+
get autorenew(): boolean {
41+
return this._autorenew;
42+
}
43+
44+
set autorenew(autorenew: boolean) {
45+
this._autorenew = autorenew;
46+
}
47+
2848
get amountToStake(): string {
2949
return this._amountToStake;
3050
}
@@ -46,12 +66,17 @@ export class StakingTransaction extends Transaction {
4666
throw new Error('Staking contract address is not set');
4767
}
4868

69+
utils.validateStakingContractAddress(this.stakingContractAddress, this._coinConfig);
70+
71+
if (this.levelId === undefined || this.levelId === null) {
72+
throw new Error('Level ID is not set');
73+
}
74+
4975
if (!this.amountToStake) {
5076
throw new Error('Amount to stake is not set');
5177
}
5278

53-
// Generate transaction data using ethereumjs-abi
54-
const data = utils.getStakingData(this.amountToStake);
79+
const data = this.getStakingData(this.levelId, this.autorenew);
5580
this._transactionData = data;
5681

5782
// Create the clause for staking
@@ -71,6 +96,23 @@ export class StakingTransaction extends Transaction {
7196
},
7297
];
7398
}
99+
/**
100+
* Encodes staking transaction data using ethereumjs-abi for stakeAndDelegate method
101+
*
102+
* @param {number} levelId - The level ID for staking
103+
* @param {boolean} autorenew - Whether to enable autorenew
104+
* @returns {string} - The encoded transaction data
105+
*/
106+
getStakingData(levelId: number, autorenew = true): string {
107+
const methodName = 'stakeAndDelegate';
108+
const types = ['uint8', 'bool'];
109+
const params = [levelId, autorenew];
110+
111+
const method = EthereumAbi.methodID(methodName, types);
112+
const args = EthereumAbi.rawEncode(types, params);
113+
114+
return addHexPrefix(Buffer.concat([method, args]).toString('hex'));
115+
}
74116

75117
toJson(): VetTransactionData {
76118
const json: VetTransactionData = {
@@ -88,6 +130,8 @@ export class StakingTransaction extends Transaction {
88130
to: this.stakingContractAddress,
89131
stakingContractAddress: this.stakingContractAddress,
90132
amountToStake: this.amountToStake,
133+
nftTokenId: this.levelId,
134+
autorenew: this.autorenew,
91135
};
92136

93137
return json;
@@ -124,6 +168,9 @@ export class StakingTransaction extends Transaction {
124168
}
125169
if (clause.data) {
126170
this.transactionData = clause.data;
171+
const decoded = utils.decodeStakingData(clause.data);
172+
this.levelId = decoded.levelId;
173+
this.autorenew = decoded.autorenew;
127174
}
128175
}
129176

0 commit comments

Comments
 (0)