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
5 changes: 3 additions & 2 deletions modules/sdk-coin-vet/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export const ZERO_VALUE_AMOUNT = '0';
export const TRANSFER_TOKEN_METHOD_ID = '0xa9059cbb';
export const STAKING_METHOD_ID = '0xd8da3bbf';
export const STAKE_CLAUSE_METHOD_ID = '0x604f2177';
export const DELEGATE_CLAUSE_METHOD_ID = '0x3207555d';
export const DELEGATE_CLAUSE_METHOD_ID = '0x08bbb824';
export const EXIT_DELEGATION_METHOD_ID = '0x69e79b7d';
export const BURN_NFT_METHOD_ID = '0x2e17de78';
export const TRANSFER_NFT_METHOD_ID = '0x23b872dd';
Expand All @@ -15,9 +15,10 @@ export const CLAIM_STAKING_REWARDS_METHOD_ID = '0x0962ef79'; // claimRewards(uin

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

export const STARGATE_NFT_ADDRESS_TESTNET = '0x1ec1d168574603ec35b9d229843b7c2b44bcb770';
export const STARGATE_DELEGATION_ADDRESS_TESTNET = '0x7240e3bc0d26431512d5b67dbd26d199205bffe8';
export const STARGATE_CONTRACT_ADDRESS_TESTNET = '0x1e02b2953adefec225cf0ec49805b1146a4429c1';

export const AVG_GAS_UNITS = '21000';
export const EXPIRATION = 400;
Expand Down
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,10 +28,12 @@ export interface VetTransactionData {
tokenId?: string; // Added for unstaking and burn NFT transactions
stakingContractAddress?: string;
amountToStake?: string;
levelId?: number; // NFT tier level
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;
validatorAddress?: string;
}

export interface VetTransactionExplanation extends BaseTransactionExplanation {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,27 @@ import { VetTransactionData } from '../iface';
import EthereumAbi from 'ethereumjs-abi';
import utils from '../utils';
import BigNumber from 'bignumber.js';
import { addHexPrefix } from 'ethereumjs-util';
import { addHexPrefix, BN } from 'ethereumjs-util';
import { ZERO_VALUE_AMOUNT } from '../constants';

export class DelegateClauseTransaction extends Transaction {
private _stakingContractAddress: string;
private _tokenId: number;
private _delegateForever = true;
private _tokenId: string;
private _validator: string;

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

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

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

get stakingContractAddress(): string {
return this._stakingContractAddress;
}
Expand All @@ -27,22 +35,14 @@ export class DelegateClauseTransaction extends Transaction {
this._stakingContractAddress = address;
}

get tokenId(): number {
get tokenId(): string {
return this._tokenId;
}

set tokenId(tokenId: number) {
set tokenId(tokenId: string) {
this._tokenId = tokenId;
}

get delegateForever(): boolean {
return this._delegateForever;
}

set delegateForever(delegateForever: boolean) {
this._delegateForever = delegateForever;
}

buildClauses(): void {
if (!this.stakingContractAddress) {
throw new Error('Staking contract address is not set');
Expand All @@ -54,7 +54,11 @@ export class DelegateClauseTransaction extends Transaction {
throw new Error('Token ID is not set');
}

const data = this.getDelegateData(this.tokenId, this.delegateForever);
if (this.validator === undefined || this.validator === null) {
throw new Error('Validator address is not set');
}

const data = this.getDelegateData(this.tokenId, this.validator);
this._transactionData = data;

// Create the clause for delegation
Expand All @@ -80,10 +84,10 @@ export class DelegateClauseTransaction extends Transaction {
* @param {number} tokenId - The Token ID for delegation
* @returns {string} - The encoded transaction data
*/
getDelegateData(levelId: number, delegateForever = true): string {
getDelegateData(tokenId: string, validatorAddress: string): string {
const methodName = 'delegate';
const types = ['uint256', 'bool'];
const params = [levelId, delegateForever];
const types = ['uint256', 'address'];
const params = [new BN(tokenId), validatorAddress];

const method = EthereumAbi.methodID(methodName, types);
const args = EthereumAbi.rawEncode(types, params);
Expand All @@ -107,8 +111,8 @@ export class DelegateClauseTransaction extends Transaction {
to: this.stakingContractAddress,
stakingContractAddress: this.stakingContractAddress,
amountToStake: ZERO_VALUE_AMOUNT,
nftTokenId: this.tokenId,
autorenew: this.delegateForever,
tokenId: this.tokenId,
validatorAddress: this.validator,
};

return json;
Expand Down Expand Up @@ -144,7 +148,7 @@ export class DelegateClauseTransaction extends Transaction {
this.transactionData = clause.data;
const decoded = utils.decodeDelegateClauseData(clause.data);
this.tokenId = decoded.tokenId;
this.delegateForever = decoded.delegateForever;
this.validator = decoded.validator;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export class StakeClauseTransaction extends Transaction {
to: this.stakingContractAddress,
stakingContractAddress: this.stakingContractAddress,
amountToStake: this.amountToStake,
nftTokenId: this.levelId,
levelId: this.levelId,
};

return json;
Expand Down Expand Up @@ -143,7 +143,7 @@ export class StakeClauseTransaction extends Transaction {
this.stakingContractAddress = clause.to;
}
if (clause.value) {
this.amountToStake = String(clause.value);
this.amountToStake = new BigNumber(clause.value).toFixed();
}
if (clause.data) {
this.transactionData = clause.data;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,28 @@ export class DelegateTxnBuilder extends TransactionBuilder {
/**
* Sets the token ID for this delegate tx.
*
* @param {number} levelId - The level ID for staking
* @param {number} levelId - The NFT token ID
* @returns {DelegateTxnBuilder} This transaction builder
*/
tokenId(tokenId: number): this {
tokenId(tokenId: string): this {
this.delegateTransaction.tokenId = tokenId;
return this;
}

/**
* Sets the validator address for this delegate tx.
* @param {string} address - The validator address
* @returns {DelegateTxnBuilder} This transaction builder
*/
validator(address: string): this {
if (!address) {
throw new Error('Validator address is required');
}
this.validateAddress({ address });
this.delegateTransaction.validator = address;
return this;
}

/**
* Sets the transaction data for this delegate tx.
*
Expand All @@ -115,7 +129,7 @@ export class DelegateTxnBuilder extends TransactionBuilder {
assert(transaction.stakingContractAddress, 'Staking contract address is required');

assert(transaction.tokenId, 'Token ID is required');
assert(transaction.delegateForever, 'delegate forever flag is required');
assert(transaction.validator, 'Validator address is required');
this.validateAddress({ address: transaction.stakingContractAddress });
}

Expand Down
27 changes: 14 additions & 13 deletions modules/sdk-coin-vet/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ import {
STARGATE_NFT_ADDRESS,
STARGATE_NFT_ADDRESS_TESTNET,
STARGATE_DELEGATION_ADDRESS,
STARGATE_DELEGATION_ADDRESS_TESTNET,
DELEGATE_CLAUSE_METHOD_ID,
STARGATE_CONTRACT_ADDRESS_TESTNET,
STARGATE_DELEGATION_ADDRESS_TESTNET,
} from './constants';
import { KeyPair } from './keyPair';
import { BaseCoin as CoinConfig } from '@bitgo/statics';
Expand Down Expand Up @@ -179,21 +180,21 @@ export class Utils implements BaseUtils {
}

/**
* Decodes delegate transaction data to extract tokenId and delegateForever
* Decodes delegate transaction data to extract tokenId and validatorAddress
*
* @param {string} data - The encoded transaction data
* @returns {object} - Object containing tokenId and delegateForever
* @returns {object} - Object containing levelId and validator address
*/
decodeDelegateClauseData(data: string): { tokenId: number; delegateForever: boolean } {
decodeDelegateClauseData(data: string): { tokenId: string; validator: string } {
try {
const parameters = data.slice(10);

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

return {
tokenId: Number(decoded[0]),
delegateForever: Boolean(decoded[1]),
tokenId: String(decoded[0]),
validator: String(decoded[1]),
};
} catch (error) {
throw new Error(`Failed to decode delegation data: ${error.message}`);
Expand Down Expand Up @@ -298,13 +299,13 @@ export class Utils implements BaseUtils {
}

/**
* Get the network-appropriate delegation contract address
* Get the network-appropriate stargate contract address
* @param {CoinConfig} coinConfig - The coin configuration object
* @returns {string} The delegation contract address for the network
*/
getDefaultDelegationAddress(coinConfig: Readonly<CoinConfig>): string {
const isTestnet = coinConfig.network.type === 'testnet';
return isTestnet ? STARGATE_DELEGATION_ADDRESS_TESTNET : STARGATE_DELEGATION_ADDRESS;
return isTestnet ? STARGATE_CONTRACT_ADDRESS_TESTNET : STARGATE_DELEGATION_ADDRESS;
}

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

/**
Expand Down Expand Up @@ -343,10 +344,10 @@ export class Utils implements BaseUtils {
}

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

/**
* Validate that a contract address matches the expected delegation contract for the network
* Validate that a contract address matches the expected stargate contract for the network
* @param {string} address - The contract address to validate
* @param {CoinConfig} coinConfig - The coin configuration object
* @throws {Error} If the address doesn't match the expected delegation contract address
Expand Down
6 changes: 3 additions & 3 deletions modules/sdk-coin-vet/test/resources/vet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ export const STAKING_TRANSACTION =
'0xf901032788015d55fcf2457e7c40f866f864941856c533ac2d94340aaa8544d35a5c1d4a21dee7880de0b6b3a7640000b844d8da3bbf0000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000181808265848083094c53c101b882efcb9ea88e908d1a142db96c1b44dd056ea194f1ad45670c100a8c52348cc7b20387741260ebe7fe9b7594f96693c88662fa60edba5992332728222b0bdd8a30008535368bd901319eb4513d16bebc428dc8454d32a19eeb76372849a6134ebbba79f1eeceea1f6546574b945c05489222cb451f5b0e2901b0c687b750e833aeb800';

export const STAKE_CLAUSE_TRANSACTION =
'0xf8e3278801618b7b1354c4ca40f845f843941ec1d168574603ec35b9d229843b7c2b44bcb770880de0b6b3a7640000a4604f2177000000000000000000000000000000000000000000000000000000000000000881808305fd5a808307b278c101b882b380970580d957b8e7989aa9aa9281e57245fa3835cb2aaae6475b4062bb4f1c2dd2ca694df6503d5dfd654579130b3484bee75420247cf8f6b6b4b76b7939f101db6e6cef5a27375274741f3c0aba4be13a9e086337c3290866afe049efcdaa2d3227c9e12b52627c4d71f5b667821f9d33adcc4c97fdc28b93c34013d32e242300';
'0xf8e5278801638298c53767ac40f847f845941e02b2953adefec225cf0ec49805b1146a4429c18a021e19e0c9bab2400000a4604f2177000000000000000000000000000000000000000000000000000000000000000881808306338a80830f340bc101b882b4970b0c160552162de719b9ed1fa6268bbfe9b36fd4e4a5c13e956cf539e60478f69179e8db4a3106fdbe775e3d923510b16f48da56c66076d1f66ffd822abf01ebc040c795b1e17cb0ca5ba747e3a181b4eefea7db5378f64f82361bdb7745da45aba2ace3db9822b675474552f13849052987c431cd4867813c2cf635302b1101';

export const DELEGATION_TRANSACTION =
'0xf8fb278801618aa3e0a55fc940f85ef85c947240e3bc0d26431512d5b67dbd26d199205bffe880b8443207555d00000000000000000000000000000000000000000000000000000000000187690000000000000000000000000000000000000000000000000000000000000001818082e43a808306af07c101b882fb8030f6e2ef6563ff3b0e7e2a2292c1db5fc41c7ab9f598bad370c5cfd3dc32286ae8d709e941c0312c8cd33a3505156b44d1639c73980ffa66bc72f37820f2001c0e6b6e76a6a4d806c377a0a279053eb6ea4356bd235f4396585bb071d70f992c639d45c53431a3c1493a52a136203905e42c671dd384ee5f5ead0a70cb607001';
'0xf8fc278801640639091a26ce40f85ef85c941e02b2953adefec225cf0ec49805b1146a4429c180b84408bbb8240000000000000000000000000000000000000000000000000000000000003d2e00000000000000000000000000563ec3cafbbe7e60b04b3190e6eca66579706d8180830464b080830d8b05c101b8821a3cca8e8339456c6055ef796e5d716dda00de45f4cd9431bedf2119ae5de01b1f0a7268690784ba8f5c22b3043d0530ece5303a813ffdd9c0a5ae0ae85deee400b04543d6874f30eca88b3efb927c44934e9eb64a6f2327cce44a0a94faaca13615d153e804ba3fdd02bf5f8e1b6bc8e0f6149a1c7694803ed4fbb549bb79066101';

export const STAKING_LEVEL_ID = 8;
export const STAKING_AUTORENEW = true;
export const STAKING_CONTRACT_ADDRESS = '0x1ec1d168574603ec35b9d229843b7c2b44bcb770';
export const STAKING_CONTRACT_ADDRESS = '0x1e02b2953adefec225cf0ec49805b1146a4429c1';

export const VALID_TOKEN_SIGNABLE_PAYLOAD =
'f8762788014ead140e77bbc140f85ef85c940000000000000000000000000000456e6572677980b844a9059cbb000000000000000000000000e59f1cea4e0fef511e3d0f4eec44adf19c4cbeec000000000000000000000000000000000000000000000000016345785d8a000081808252088082faf8c101';
Expand Down
20 changes: 10 additions & 10 deletions modules/sdk-coin-vet/test/transactionBuilder/burnNftBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { coins } from '@bitgo/statics';
import { TransactionBuilderFactory } from '../../src';
import { BurnNftTransaction } from '../../src/lib/transaction/burnNftTransaction';
import * as testData from '../resources/vet';
import { BURN_NFT_METHOD_ID, STARGATE_NFT_ADDRESS_TESTNET } from '../../src/lib/constants';
import { BURN_NFT_METHOD_ID, STARGATE_CONTRACT_ADDRESS_TESTNET } from '../../src/lib/constants';

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

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

should.equal(tx.sender, testData.addresses.validAddresses[0]);
should.equal(tx.tokenId, tokenId);
should.equal(tx.contract, STARGATE_NFT_ADDRESS_TESTNET);
should.equal(tx.contract, STARGATE_CONTRACT_ADDRESS_TESTNET);
should.equal(tx.gas, 21000);
should.equal(tx.nonce, '64248');
should.equal(tx.expiration, 64);
Expand All @@ -40,7 +40,7 @@ describe('Vet Burn NFT Transaction', () => {
tx.clauses.length.should.equal(1);
should.exist(tx.clauses[0]);
should.exist(tx.clauses[0].to);
tx.clauses[0]?.to?.should.equal(STARGATE_NFT_ADDRESS_TESTNET);
tx.clauses[0]?.to?.should.equal(STARGATE_CONTRACT_ADDRESS_TESTNET);
should.exist(tx.clauses[0].value);
tx.clauses[0].value.should.equal('0x0');

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

should.equal(tx.outputs[0].address, STARGATE_NFT_ADDRESS_TESTNET);
should.equal(tx.outputs[0].address, STARGATE_CONTRACT_ADDRESS_TESTNET);
should.equal(tx.outputs[0].value, '0');
should.equal(tx.outputs[0].coin, 'tvet');
});

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

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

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

should.equal(deserializedTx.type, TransactionType.StakingWithdraw);
should.equal(deserializedTx.tokenId, tokenId);
should.equal(deserializedTx.contract, STARGATE_NFT_ADDRESS_TESTNET);
should.equal(deserializedTx.contract, STARGATE_CONTRACT_ADDRESS_TESTNET);
});

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

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

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

await should(txBuilder.build()).be.rejectedWith('Token ID is required');
Expand Down
Loading