Skip to content

Commit 7c79047

Browse files
Merge pull request #7486 from BitGo/SC-3926
fix(sdk-coin-vet): stake, delegate SDK changes for hayabusa upgrade
2 parents 994d837 + 42adabf commit 7c79047

File tree

14 files changed

+152
-125
lines changed

14 files changed

+152
-125
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const ZERO_VALUE_AMOUNT = '0';
66
export const TRANSFER_TOKEN_METHOD_ID = '0xa9059cbb';
77
export const STAKING_METHOD_ID = '0xd8da3bbf';
88
export const STAKE_CLAUSE_METHOD_ID = '0x604f2177';
9-
export const DELEGATE_CLAUSE_METHOD_ID = '0x3207555d';
9+
export const DELEGATE_CLAUSE_METHOD_ID = '0x08bbb824';
1010
export const EXIT_DELEGATION_METHOD_ID = '0x69e79b7d';
1111
export const BURN_NFT_METHOD_ID = '0x2e17de78';
1212
export const TRANSFER_NFT_METHOD_ID = '0x23b872dd';
@@ -15,9 +15,10 @@ export const CLAIM_STAKING_REWARDS_METHOD_ID = '0x0962ef79'; // claimRewards(uin
1515

1616
export const STARGATE_NFT_ADDRESS = '0x1856c533ac2d94340aaa8544d35a5c1d4a21dee7';
1717
export const STARGATE_DELEGATION_ADDRESS = '0x4cb1c9ef05b529c093371264fab2c93cc6cddb0e';
18+
export const STARGATE_DELEGATION_ADDRESS_TESTNET = '0x7240e3bc0d26431512d5b67dbd26d199205bffe8';
1819

1920
export const STARGATE_NFT_ADDRESS_TESTNET = '0x1ec1d168574603ec35b9d229843b7c2b44bcb770';
20-
export const STARGATE_DELEGATION_ADDRESS_TESTNET = '0x7240e3bc0d26431512d5b67dbd26d199205bffe8';
21+
export const STARGATE_CONTRACT_ADDRESS_TESTNET = '0x1e02b2953adefec225cf0ec49805b1146a4429c1';
2122

2223
export const AVG_GAS_UNITS = '21000';
2324
export const EXPIRATION = 400;

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

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

3739
export interface VetTransactionExplanation extends BaseTransactionExplanation {

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

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,27 @@ 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';
9+
import { addHexPrefix, BN } from 'ethereumjs-util';
1010
import { ZERO_VALUE_AMOUNT } from '../constants';
1111

1212
export class DelegateClauseTransaction extends Transaction {
1313
private _stakingContractAddress: string;
14-
private _tokenId: number;
15-
private _delegateForever = true;
14+
private _tokenId: string;
15+
private _validator: string;
1616

1717
constructor(_coinConfig: Readonly<CoinConfig>) {
1818
super(_coinConfig);
1919
this._type = TransactionType.StakingDelegate;
2020
}
2121

22+
get validator(): string {
23+
return this._validator;
24+
}
25+
26+
set validator(address: string) {
27+
this._validator = address;
28+
}
29+
2230
get stakingContractAddress(): string {
2331
return this._stakingContractAddress;
2432
}
@@ -27,22 +35,14 @@ export class DelegateClauseTransaction extends Transaction {
2735
this._stakingContractAddress = address;
2836
}
2937

30-
get tokenId(): number {
38+
get tokenId(): string {
3139
return this._tokenId;
3240
}
3341

34-
set tokenId(tokenId: number) {
42+
set tokenId(tokenId: string) {
3543
this._tokenId = tokenId;
3644
}
3745

38-
get delegateForever(): boolean {
39-
return this._delegateForever;
40-
}
41-
42-
set delegateForever(delegateForever: boolean) {
43-
this._delegateForever = delegateForever;
44-
}
45-
4646
buildClauses(): void {
4747
if (!this.stakingContractAddress) {
4848
throw new Error('Staking contract address is not set');
@@ -54,7 +54,11 @@ export class DelegateClauseTransaction extends Transaction {
5454
throw new Error('Token ID is not set');
5555
}
5656

57-
const data = this.getDelegateData(this.tokenId, this.delegateForever);
57+
if (this.validator === undefined || this.validator === null) {
58+
throw new Error('Validator address is not set');
59+
}
60+
61+
const data = this.getDelegateData(this.tokenId, this.validator);
5862
this._transactionData = data;
5963

6064
// Create the clause for delegation
@@ -80,10 +84,10 @@ export class DelegateClauseTransaction extends Transaction {
8084
* @param {number} tokenId - The Token ID for delegation
8185
* @returns {string} - The encoded transaction data
8286
*/
83-
getDelegateData(levelId: number, delegateForever = true): string {
87+
getDelegateData(tokenId: string, validatorAddress: string): string {
8488
const methodName = 'delegate';
85-
const types = ['uint256', 'bool'];
86-
const params = [levelId, delegateForever];
89+
const types = ['uint256', 'address'];
90+
const params = [new BN(tokenId), validatorAddress];
8791

8892
const method = EthereumAbi.methodID(methodName, types);
8993
const args = EthereumAbi.rawEncode(types, params);
@@ -107,8 +111,8 @@ export class DelegateClauseTransaction extends Transaction {
107111
to: this.stakingContractAddress,
108112
stakingContractAddress: this.stakingContractAddress,
109113
amountToStake: ZERO_VALUE_AMOUNT,
110-
nftTokenId: this.tokenId,
111-
autorenew: this.delegateForever,
114+
tokenId: this.tokenId,
115+
validatorAddress: this.validator,
112116
};
113117

114118
return json;
@@ -144,7 +148,7 @@ export class DelegateClauseTransaction extends Transaction {
144148
this.transactionData = clause.data;
145149
const decoded = utils.decodeDelegateClauseData(clause.data);
146150
this.tokenId = decoded.tokenId;
147-
this.delegateForever = decoded.delegateForever;
151+
this.validator = decoded.validator;
148152
}
149153
}
150154

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export class StakeClauseTransaction extends Transaction {
110110
to: this.stakingContractAddress,
111111
stakingContractAddress: this.stakingContractAddress,
112112
amountToStake: this.amountToStake,
113-
nftTokenId: this.levelId,
113+
levelId: this.levelId,
114114
};
115115

116116
return json;
@@ -143,7 +143,7 @@ export class StakeClauseTransaction extends Transaction {
143143
this.stakingContractAddress = clause.to;
144144
}
145145
if (clause.value) {
146-
this.amountToStake = String(clause.value);
146+
this.amountToStake = new BigNumber(clause.value).toFixed();
147147
}
148148
if (clause.data) {
149149
this.transactionData = clause.data;

modules/sdk-coin-vet/src/lib/transactionBuilder/delegateTxnBuilder.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,28 @@ export class DelegateTxnBuilder extends TransactionBuilder {
8888
/**
8989
* Sets the token ID for this delegate tx.
9090
*
91-
* @param {number} levelId - The level ID for staking
91+
* @param {number} levelId - The NFT token ID
9292
* @returns {DelegateTxnBuilder} This transaction builder
9393
*/
94-
tokenId(tokenId: number): this {
94+
tokenId(tokenId: string): this {
9595
this.delegateTransaction.tokenId = tokenId;
9696
return this;
9797
}
9898

99+
/**
100+
* Sets the validator address for this delegate tx.
101+
* @param {string} address - The validator address
102+
* @returns {DelegateTxnBuilder} This transaction builder
103+
*/
104+
validator(address: string): this {
105+
if (!address) {
106+
throw new Error('Validator address is required');
107+
}
108+
this.validateAddress({ address });
109+
this.delegateTransaction.validator = address;
110+
return this;
111+
}
112+
99113
/**
100114
* Sets the transaction data for this delegate tx.
101115
*
@@ -115,7 +129,7 @@ export class DelegateTxnBuilder extends TransactionBuilder {
115129
assert(transaction.stakingContractAddress, 'Staking contract address is required');
116130

117131
assert(transaction.tokenId, 'Token ID is required');
118-
assert(transaction.delegateForever, 'delegate forever flag is required');
132+
assert(transaction.validator, 'Validator address is required');
119133
this.validateAddress({ address: transaction.stakingContractAddress });
120134
}
121135

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

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ import {
2323
STARGATE_NFT_ADDRESS,
2424
STARGATE_NFT_ADDRESS_TESTNET,
2525
STARGATE_DELEGATION_ADDRESS,
26-
STARGATE_DELEGATION_ADDRESS_TESTNET,
2726
DELEGATE_CLAUSE_METHOD_ID,
27+
STARGATE_CONTRACT_ADDRESS_TESTNET,
28+
STARGATE_DELEGATION_ADDRESS_TESTNET,
2829
} from './constants';
2930
import { KeyPair } from './keyPair';
3031
import { BaseCoin as CoinConfig } from '@bitgo/statics';
@@ -179,21 +180,21 @@ export class Utils implements BaseUtils {
179180
}
180181

181182
/**
182-
* Decodes delegate transaction data to extract tokenId and delegateForever
183+
* Decodes delegate transaction data to extract tokenId and validatorAddress
183184
*
184185
* @param {string} data - The encoded transaction data
185-
* @returns {object} - Object containing tokenId and delegateForever
186+
* @returns {object} - Object containing levelId and validator address
186187
*/
187-
decodeDelegateClauseData(data: string): { tokenId: number; delegateForever: boolean } {
188+
decodeDelegateClauseData(data: string): { tokenId: string; validator: string } {
188189
try {
189190
const parameters = data.slice(10);
190191

191192
// Decode using ethereumjs-abi directly
192-
const decoded = EthereumAbi.rawDecode(['uint256', 'bool'], Buffer.from(parameters, 'hex'));
193+
const decoded = EthereumAbi.rawDecode(['uint256', 'address'], Buffer.from(parameters, 'hex'));
193194

194195
return {
195-
tokenId: Number(decoded[0]),
196-
delegateForever: Boolean(decoded[1]),
196+
tokenId: String(decoded[0]),
197+
validator: String(decoded[1]),
197198
};
198199
} catch (error) {
199200
throw new Error(`Failed to decode delegation data: ${error.message}`);
@@ -298,13 +299,13 @@ export class Utils implements BaseUtils {
298299
}
299300

300301
/**
301-
* Get the network-appropriate delegation contract address
302+
* Get the network-appropriate stargate contract address
302303
* @param {CoinConfig} coinConfig - The coin configuration object
303304
* @returns {string} The delegation contract address for the network
304305
*/
305306
getDefaultDelegationAddress(coinConfig: Readonly<CoinConfig>): string {
306307
const isTestnet = coinConfig.network.type === 'testnet';
307-
return isTestnet ? STARGATE_DELEGATION_ADDRESS_TESTNET : STARGATE_DELEGATION_ADDRESS;
308+
return isTestnet ? STARGATE_CONTRACT_ADDRESS_TESTNET : STARGATE_DELEGATION_ADDRESS;
308309
}
309310

310311
/**
@@ -314,7 +315,7 @@ export class Utils implements BaseUtils {
314315
*/
315316
getDefaultStakingAddress(coinConfig: Readonly<CoinConfig>): string {
316317
const isTestnet = coinConfig.network.type === 'testnet';
317-
return isTestnet ? STARGATE_NFT_ADDRESS_TESTNET : STARGATE_NFT_ADDRESS;
318+
return isTestnet ? STARGATE_CONTRACT_ADDRESS_TESTNET : STARGATE_NFT_ADDRESS;
318319
}
319320

320321
/**
@@ -343,10 +344,10 @@ export class Utils implements BaseUtils {
343344
}
344345

345346
/**
346-
* Validate that a contract address matches the expected NFT/staking contract for the network
347+
* Validate that a contract address matches the expected stargate address for the network
347348
* @param {string} address - The contract address to validate
348349
* @param {CoinConfig} coinConfig - The coin configuration object
349-
* @throws {Error} If the address doesn't match the expected NFT contract address
350+
* @throws {Error} If the address doesn't match the expected contract address
350351
*/
351352
validateStakingContractAddress(address: string, coinConfig: Readonly<CoinConfig>): void {
352353
const expectedAddress = this.getDefaultStakingAddress(coinConfig);
@@ -358,7 +359,7 @@ export class Utils implements BaseUtils {
358359
}
359360

360361
/**
361-
* Validate that a contract address matches the expected delegation contract for the network
362+
* Validate that a contract address matches the expected stargate contract for the network
362363
* @param {string} address - The contract address to validate
363364
* @param {CoinConfig} coinConfig - The coin configuration object
364365
* @throws {Error} If the address doesn't match the expected delegation contract address

modules/sdk-coin-vet/test/resources/vet.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ export const STAKING_TRANSACTION =
1515
'0xf901032788015d55fcf2457e7c40f866f864941856c533ac2d94340aaa8544d35a5c1d4a21dee7880de0b6b3a7640000b844d8da3bbf0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000181808265848083094c53c101b882efcb9ea88e908d1a142db96c1b44dd056ea194f1ad45670c100a8c52348cc7b20387741260ebe7fe9b7594f96693c88662fa60edba5992332728222b0bdd8a30008535368bd901319eb4513d16bebc428dc8454d32a19eeb76372849a6134ebbba79f1eeceea1f6546574b945c05489222cb451f5b0e2901b0c687b750e833aeb800';
1616

1717
export const STAKE_CLAUSE_TRANSACTION =
18-
'0xf8e3278801618b7b1354c4ca40f845f843941ec1d168574603ec35b9d229843b7c2b44bcb770880de0b6b3a7640000a4604f2177000000000000000000000000000000000000000000000000000000000000000881808305fd5a808307b278c101b882b380970580d957b8e7989aa9aa9281e57245fa3835cb2aaae6475b4062bb4f1c2dd2ca694df6503d5dfd654579130b3484bee75420247cf8f6b6b4b76b7939f101db6e6cef5a27375274741f3c0aba4be13a9e086337c3290866afe049efcdaa2d3227c9e12b52627c4d71f5b667821f9d33adcc4c97fdc28b93c34013d32e242300';
18+
'0xf8e5278801638298c53767ac40f847f845941e02b2953adefec225cf0ec49805b1146a4429c18a021e19e0c9bab2400000a4604f2177000000000000000000000000000000000000000000000000000000000000000881808306338a80830f340bc101b882b4970b0c160552162de719b9ed1fa6268bbfe9b36fd4e4a5c13e956cf539e60478f69179e8db4a3106fdbe775e3d923510b16f48da56c66076d1f66ffd822abf01ebc040c795b1e17cb0ca5ba747e3a181b4eefea7db5378f64f82361bdb7745da45aba2ace3db9822b675474552f13849052987c431cd4867813c2cf635302b1101';
1919

2020
export const DELEGATION_TRANSACTION =
21-
'0xf8fb278801618aa3e0a55fc940f85ef85c947240e3bc0d26431512d5b67dbd26d199205bffe880b8443207555d00000000000000000000000000000000000000000000000000000000000187690000000000000000000000000000000000000000000000000000000000000001818082e43a808306af07c101b882fb8030f6e2ef6563ff3b0e7e2a2292c1db5fc41c7ab9f598bad370c5cfd3dc32286ae8d709e941c0312c8cd33a3505156b44d1639c73980ffa66bc72f37820f2001c0e6b6e76a6a4d806c377a0a279053eb6ea4356bd235f4396585bb071d70f992c639d45c53431a3c1493a52a136203905e42c671dd384ee5f5ead0a70cb607001';
21+
'0xf8fc278801640639091a26ce40f85ef85c941e02b2953adefec225cf0ec49805b1146a4429c180b84408bbb8240000000000000000000000000000000000000000000000000000000000003d2e00000000000000000000000000563ec3cafbbe7e60b04b3190e6eca66579706d8180830464b080830d8b05c101b8821a3cca8e8339456c6055ef796e5d716dda00de45f4cd9431bedf2119ae5de01b1f0a7268690784ba8f5c22b3043d0530ece5303a813ffdd9c0a5ae0ae85deee400b04543d6874f30eca88b3efb927c44934e9eb64a6f2327cce44a0a94faaca13615d153e804ba3fdd02bf5f8e1b6bc8e0f6149a1c7694803ed4fbb549bb79066101';
2222

2323
export const STAKING_LEVEL_ID = 8;
2424
export const STAKING_AUTORENEW = true;
25-
export const STAKING_CONTRACT_ADDRESS = '0x1ec1d168574603ec35b9d229843b7c2b44bcb770';
25+
export const STAKING_CONTRACT_ADDRESS = '0x1e02b2953adefec225cf0ec49805b1146a4429c1';
2626

2727
export const VALID_TOKEN_SIGNABLE_PAYLOAD =
2828
'f8762788014ead140e77bbc140f85ef85c940000000000000000000000000000456e6572677980b844a9059cbb000000000000000000000000e59f1cea4e0fef511e3d0f4eec44adf19c4cbeec000000000000000000000000000000000000000000000000016345785d8a000081808252088082faf8c101';

modules/sdk-coin-vet/test/transactionBuilder/burnNftBuilder.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { coins } from '@bitgo/statics';
44
import { TransactionBuilderFactory } from '../../src';
55
import { BurnNftTransaction } from '../../src/lib/transaction/burnNftTransaction';
66
import * as testData from '../resources/vet';
7-
import { BURN_NFT_METHOD_ID, STARGATE_NFT_ADDRESS_TESTNET } from '../../src/lib/constants';
7+
import { BURN_NFT_METHOD_ID, STARGATE_CONTRACT_ADDRESS_TESTNET } from '../../src/lib/constants';
88

99
describe('Vet Burn NFT Transaction', () => {
1010
const factory = new TransactionBuilderFactory(coins.get('tvet'));
@@ -16,7 +16,7 @@ describe('Vet Burn NFT Transaction', () => {
1616

1717
txBuilder.sender(testData.addresses.validAddresses[0]);
1818
txBuilder.tokenId(tokenId);
19-
txBuilder.nftContract(STARGATE_NFT_ADDRESS_TESTNET);
19+
txBuilder.nftContract(STARGATE_CONTRACT_ADDRESS_TESTNET);
2020
txBuilder.gas(21000);
2121
txBuilder.nonce('64248');
2222
txBuilder.blockRef('0x014ead140e77bbc1');
@@ -27,7 +27,7 @@ describe('Vet Burn NFT Transaction', () => {
2727

2828
should.equal(tx.sender, testData.addresses.validAddresses[0]);
2929
should.equal(tx.tokenId, tokenId);
30-
should.equal(tx.contract, STARGATE_NFT_ADDRESS_TESTNET);
30+
should.equal(tx.contract, STARGATE_CONTRACT_ADDRESS_TESTNET);
3131
should.equal(tx.gas, 21000);
3232
should.equal(tx.nonce, '64248');
3333
should.equal(tx.expiration, 64);
@@ -40,7 +40,7 @@ describe('Vet Burn NFT Transaction', () => {
4040
tx.clauses.length.should.equal(1);
4141
should.exist(tx.clauses[0]);
4242
should.exist(tx.clauses[0].to);
43-
tx.clauses[0]?.to?.should.equal(STARGATE_NFT_ADDRESS_TESTNET);
43+
tx.clauses[0]?.to?.should.equal(STARGATE_CONTRACT_ADDRESS_TESTNET);
4444
should.exist(tx.clauses[0].value);
4545
tx.clauses[0].value.should.equal('0x0');
4646

@@ -51,14 +51,14 @@ describe('Vet Burn NFT Transaction', () => {
5151
should.equal(tx.inputs[0].value, '0');
5252
should.equal(tx.inputs[0].coin, 'tvet');
5353

54-
should.equal(tx.outputs[0].address, STARGATE_NFT_ADDRESS_TESTNET);
54+
should.equal(tx.outputs[0].address, STARGATE_CONTRACT_ADDRESS_TESTNET);
5555
should.equal(tx.outputs[0].value, '0');
5656
should.equal(tx.outputs[0].coin, 'tvet');
5757
});
5858

5959
it('should build a burn NFT transaction with custom contract address', async function () {
6060
const tokenId = '123456';
61-
const customContractAddress = STARGATE_NFT_ADDRESS_TESTNET; // Use the valid testnet NFT address
61+
const customContractAddress = STARGATE_CONTRACT_ADDRESS_TESTNET; // Use the valid testnet NFT address
6262
const txBuilder = factory.getBurnNftBuilder();
6363

6464
txBuilder.sender(testData.addresses.validAddresses[0]);
@@ -85,7 +85,7 @@ describe('Vet Burn NFT Transaction', () => {
8585

8686
txBuilder.sender(testData.addresses.validAddresses[0]);
8787
txBuilder.tokenId(tokenId);
88-
txBuilder.nftContract(STARGATE_NFT_ADDRESS_TESTNET);
88+
txBuilder.nftContract(STARGATE_CONTRACT_ADDRESS_TESTNET);
8989
txBuilder.gas(21000);
9090
txBuilder.nonce('64248');
9191
txBuilder.blockRef('0x014ead140e77bbc1');
@@ -101,7 +101,7 @@ describe('Vet Burn NFT Transaction', () => {
101101

102102
should.equal(deserializedTx.type, TransactionType.StakingWithdraw);
103103
should.equal(deserializedTx.tokenId, tokenId);
104-
should.equal(deserializedTx.contract, STARGATE_NFT_ADDRESS_TESTNET);
104+
should.equal(deserializedTx.contract, STARGATE_CONTRACT_ADDRESS_TESTNET);
105105
});
106106

107107
it('should validate the transaction data structure', async function () {
@@ -111,7 +111,7 @@ describe('Vet Burn NFT Transaction', () => {
111111
await should(txBuilder.build()).be.rejectedWith('NFT contract address is required');
112112

113113
txBuilder.sender(testData.addresses.validAddresses[0]);
114-
txBuilder.nftContract(STARGATE_NFT_ADDRESS_TESTNET);
114+
txBuilder.nftContract(STARGATE_CONTRACT_ADDRESS_TESTNET);
115115
await should(txBuilder.build()).be.rejectedWith('Token ID is required');
116116

117117
// Now add the token ID and it should build successfully
@@ -134,7 +134,7 @@ describe('Vet Burn NFT Transaction', () => {
134134
it('should fail with invalid token ID', async function () {
135135
const txBuilder = factory.getBurnNftBuilder();
136136
txBuilder.sender(testData.addresses.validAddresses[0]);
137-
txBuilder.nftContract(STARGATE_NFT_ADDRESS_TESTNET);
137+
txBuilder.nftContract(STARGATE_CONTRACT_ADDRESS_TESTNET);
138138
txBuilder.tokenId('');
139139

140140
await should(txBuilder.build()).be.rejectedWith('Token ID is required');

0 commit comments

Comments
 (0)