Skip to content

Commit c8e0b97

Browse files
committed
feat(sdk-coin-vet): add flush token tx builder
COIN-4742 TICKET: COIN-4742
1 parent 1c42dab commit c8e0b97

7 files changed

Lines changed: 96 additions & 62 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export class AddressInitializationTransaction extends Transaction {
131131
this.gas = typeof body.gas === 'number' ? body.gas : Number(body.gas) || 0;
132132
this.dependsOn = body.dependsOn || null;
133133
this.nonce = typeof body.nonce === 'number' ? body.nonce : Number(body.nonce) || 0;
134-
// Set recipients from clauses
134+
// Set data from clauses
135135
this.contract = body.clauses[0]?.to || '0x0';
136136
this.transactionData = body.clauses[0]?.data || '0x0';
137137

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

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
1-
import { TransactionType } from '@bitgo/sdk-core';
1+
import { TransactionType, InvalidTransactionError } from '@bitgo/sdk-core';
22
import { BaseCoin as CoinConfig } from '@bitgo/statics';
3+
import { Transaction as VetTransaction, Secp256k1 } from '@vechain/sdk-core';
34

45
import { Transaction } from './transaction';
56
import { VetTransactionData } from '../iface';
67

78
export class FlushTokenTransaction extends Transaction {
8-
private _forwarderAddress: string;
9+
private _forwarderVersion: number;
910
private _tokenAddress: string;
1011

1112
constructor(_coinConfig: Readonly<CoinConfig>) {
1213
super(_coinConfig);
1314
this._type = TransactionType.FlushTokens;
1415
}
1516

16-
get forwarderAddress(): string {
17-
return this._forwarderAddress;
17+
get forwarderVersion(): number {
18+
return this._forwarderVersion;
1819
}
1920

20-
set forwarderAddress(address: string) {
21-
this._forwarderAddress = address;
21+
set forwarderVersion(forwarderVersion: number) {
22+
this._forwarderVersion = forwarderVersion;
2223
}
2324

2425
get tokenAddress(): string {
@@ -59,4 +60,48 @@ export class FlushTokenTransaction extends Transaction {
5960
};
6061
return json;
6162
}
63+
64+
/** @inheritdoc */
65+
fromDeserializedSignedTransaction(signedTx: VetTransaction): void {
66+
try {
67+
if (!signedTx || !signedTx.body) {
68+
throw new InvalidTransactionError('Invalid transaction: missing transaction body');
69+
}
70+
71+
// Store the raw transaction
72+
this.rawTransaction = signedTx;
73+
74+
// Set transaction body properties
75+
const body = signedTx.body;
76+
this.chainTag = typeof body.chainTag === 'number' ? body.chainTag : 0;
77+
this.blockRef = body.blockRef || '0x0';
78+
this.expiration = typeof body.expiration === 'number' ? body.expiration : 64;
79+
this.clauses = body.clauses || [];
80+
this.gasPriceCoef = typeof body.gasPriceCoef === 'number' ? body.gasPriceCoef : 128;
81+
this.gas = typeof body.gas === 'number' ? body.gas : Number(body.gas) || 0;
82+
this.dependsOn = body.dependsOn || null;
83+
this.nonce = typeof body.nonce === 'number' ? body.nonce : Number(body.nonce) || 0;
84+
// Set data from clauses
85+
this.contract = body.clauses[0]?.to || '0x0';
86+
this.transactionData = body.clauses[0]?.data || '0x0';
87+
88+
// Set sender address
89+
if (signedTx.origin) {
90+
this.sender = signedTx.origin.toString().toLowerCase();
91+
}
92+
93+
// Set signatures if present
94+
if (signedTx.signature) {
95+
// First signature is sender's signature
96+
this.senderSignature = Buffer.from(signedTx.signature.slice(0, Secp256k1.SIGNATURE_LENGTH));
97+
98+
// If there's additional signature data, it's the fee payer's signature
99+
if (signedTx.signature.length > Secp256k1.SIGNATURE_LENGTH) {
100+
this.feePayerSignature = Buffer.from(signedTx.signature.slice(Secp256k1.SIGNATURE_LENGTH));
101+
}
102+
}
103+
} catch (e) {
104+
throw new InvalidTransactionError(`Failed to deserialize transaction: ${e.message}`);
105+
}
106+
}
62107
}

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

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -175,15 +175,4 @@ export class AddressInitializationBuilder extends TransactionBuilder {
175175
const args = EthereumAbi.rawEncode(createForwarderTypes, createForwarderParams);
176176
return addHexPrefix(Buffer.concat([method, args]).toString('hex'));
177177
}
178-
179-
/** @inheritdoc */
180-
protected fromImplementation(rawTransaction: string): Transaction {
181-
const tx = new AddressInitializationTransaction(this._coinConfig);
182-
this.validateRawTransaction(rawTransaction);
183-
184-
tx.fromRawTransaction(rawTransaction);
185-
this.initBuilder(tx);
186-
this.validateTransaction(tx);
187-
return this.transaction;
188-
}
189178
}
Lines changed: 24 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import assert from 'assert';
12
import { BaseCoin as CoinConfig } from '@bitgo/statics';
23
import { TransactionType } from '@bitgo/sdk-core';
3-
import { decodeFlushTokensData } from '@bitgo/abstract-eth';
4+
import { decodeFlushTokensData, flushTokensData } from '@bitgo/abstract-eth';
45
import { TransactionClause } from '@vechain/sdk-core';
56

67
import { TransactionBuilder } from './transactionBuilder';
8+
import { Transaction } from '../transaction/transaction';
79
import { FlushTokenTransaction } from '../transaction/flushTokenTransaction';
810
import utils from '../utils';
911

@@ -27,16 +29,16 @@ export class FlushTokenTransactionBuilder extends TransactionBuilder {
2729
}
2830

2931
/**
30-
* Gets the address initialization transaction instance.
32+
* Gets the flush token transaction instance.
3133
*
32-
* @returns {FlushTokenTransaction} The address initialization transaction
34+
* @returns {FlushTokenTransaction} The flush token transaction
3335
*/
3436
get flushTokenTransaction(): FlushTokenTransaction {
3537
return this._transaction as FlushTokenTransaction;
3638
}
3739

3840
/**
39-
* Gets the transaction type for address initialization.
41+
* Gets the transaction type for flush token.
4042
*
4143
* @returns {TransactionType} The transaction type
4244
*/
@@ -45,7 +47,7 @@ export class FlushTokenTransactionBuilder extends TransactionBuilder {
4547
}
4648

4749
/**
48-
* Validates the transaction clauses for address initialization.
50+
* Validates the transaction clauses for flush token transaction.
4951
* @param {TransactionClause[]} clauses - The transaction clauses to validate.
5052
* @returns {boolean} - Returns true if the clauses are valid, false otherwise.
5153
*/
@@ -61,7 +63,7 @@ export class FlushTokenTransactionBuilder extends TransactionBuilder {
6163
return false;
6264
}
6365

64-
// For address init transactions, value must be exactly '0x0'
66+
// For address init transactions, value must be exactly 0
6567
if (clause.value !== 0) {
6668
return false;
6769
}
@@ -79,10 +81,10 @@ export class FlushTokenTransactionBuilder extends TransactionBuilder {
7981
}
8082

8183
/**
82-
* Sets the base address for this forwarder init tx.
84+
* Sets the token address for this token flush tx.
8385
*
84-
* @param {string} address - The base address to be set for the forwarder address
85-
* @returns {AddressInitializationBuilder} This transaction builder
86+
* @param {string} address - The token address to be set for the token flush transaction
87+
* @returns {FlushTokenTransaction} This transaction builder
8688
*/
8789
tokenAddress(address: string): this {
8890
this.validateAddress({ address });
@@ -91,59 +93,38 @@ export class FlushTokenTransactionBuilder extends TransactionBuilder {
9193
}
9294

9395
/** @inheritdoc */
94-
validateTransaction(transaction?: AddressInitializationTransaction): void {
96+
validateTransaction(transaction?: FlushTokenTransaction): void {
9597
if (!transaction) {
9698
throw new Error('transaction not defined');
9799
}
98100
assert(transaction.contract, 'Contract address is required');
99-
assert(transaction.baseAddress, 'Base address is required');
100-
assert(transaction.feeAddress, 'Fee address is required');
101-
assert(transaction.salt, 'Salt is required');
102-
assert(transaction.initCode, 'Init code is required');
101+
assert(transaction.tokenAddress, 'Token address is required');
103102

104103
this.validateAddress({ address: transaction.contract });
105-
this.validateAddress({ address: transaction.baseAddress });
106-
this.validateAddress({ address: transaction.feeAddress });
107-
if (!utils.isValidHex(transaction.salt, 64)) {
108-
throw new Error('Invalid salt');
109-
}
104+
this.validateAddress({ address: transaction.tokenAddress });
110105
}
111106

112107
/** @inheritdoc */
113108
protected async buildImplementation(): Promise<Transaction> {
114-
const transactionData = this.getAddressInitializationData();
109+
const transactionData = this.getFlushTokenTransactionData();
115110
this.transaction.type = this.transactionType;
116-
this.addressInitializationTransaction.transactionData = transactionData;
117-
await this.addressInitializationTransaction.build();
111+
this.flushTokenTransaction.transactionData = transactionData;
112+
await this.flushTokenTransaction.build();
118113
return this.transaction;
119114
}
120115

121116
/**
122-
* Generates the transaction data for address initialization by encoding the createForwarder method call.
117+
* Generates the transaction data for flush token transaction
123118
*
124119
* @private
125120
* @returns {string} The encoded transaction data as a hex string
126121
*/
127-
private getAddressInitializationData(): string {
128-
const saltBuffer = setLengthLeft(toBuffer(this.addressInitializationTransaction.salt), 32);
129-
const { createForwarderParams, createForwarderTypes } = getCreateForwarderParamsAndTypes(
130-
this.addressInitializationTransaction.baseAddress,
131-
saltBuffer,
132-
this.addressInitializationTransaction.feeAddress
122+
private getFlushTokenTransactionData(): string {
123+
const flushTokenData = flushTokensData(
124+
this.flushTokenTransaction.contract,
125+
this.flushTokenTransaction.tokenAddress,
126+
this.flushTokenTransaction.forwarderVersion
133127
);
134-
const method = EthereumAbi.methodID('createForwarder', createForwarderTypes);
135-
const args = EthereumAbi.rawEncode(createForwarderTypes, createForwarderParams);
136-
return addHexPrefix(Buffer.concat([method, args]).toString('hex'));
137-
}
138-
139-
/** @inheritdoc */
140-
protected fromImplementation(rawTransaction: string): Transaction {
141-
const tx = new AddressInitializationTransaction(this._coinConfig);
142-
this.validateRawTransaction(rawTransaction);
143-
144-
tx.fromRawTransaction(rawTransaction);
145-
this.initBuilder(tx);
146-
this.validateTransaction(tx);
147-
return this.transaction;
128+
return flushTokenData;
148129
}
149130
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,11 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
180180
validateKey(key: BaseKey): void {
181181
throw new Error('Method not implemented.');
182182
}
183+
184+
/** @inheritdoc */
185+
protected fromImplementation(rawTransaction: string): Transaction {
186+
this.validateRawTransaction(rawTransaction);
187+
this.transaction.fromRawTransaction(rawTransaction);
188+
return this.transaction;
189+
}
183190
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import { BaseCoin as CoinConfig } from '@bitgo/statics';
44
import { TransactionBuilder } from './transactionBuilder/transactionBuilder';
55
import { TransferBuilder } from './transactionBuilder/transferBuilder';
66
import { AddressInitializationBuilder } from './transactionBuilder/addressInitializationBuilder';
7+
import { FlushTokenTransactionBuilder } from './transactionBuilder/flushTokenTransactionBuilder';
78
import { Transaction } from './transaction/transaction';
89
import utils from './utils';
910
import { AddressInitializationTransaction } from './transaction/addressInitializationTransaction';
11+
import { FlushTokenTransaction } from './transaction/flushTokenTransaction';
1012

1113
export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
1214
constructor(_coinConfig: Readonly<CoinConfig>) {
@@ -27,6 +29,10 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
2729
const addressInitializationTx = new AddressInitializationTransaction(this._coinConfig);
2830
addressInitializationTx.fromDeserializedSignedTransaction(signedTx);
2931
return this.getAddressInitialisationBuilder(addressInitializationTx);
32+
case TransactionType.FlushTokens:
33+
const flushTokenTx = new FlushTokenTransaction(this._coinConfig);
34+
flushTokenTx.fromDeserializedSignedTransaction(signedTx);
35+
return this.getFlushTokenTransactionBuilder(flushTokenTx);
3036
default:
3137
throw new InvalidTransactionError('Invalid transaction type');
3238
}
@@ -44,6 +50,10 @@ export class TransactionBuilderFactory extends BaseTransactionBuilderFactory {
4450
return this.initializeBuilder(tx, new AddressInitializationBuilder(this._coinConfig));
4551
}
4652

53+
getFlushTokenTransactionBuilder(tx?: FlushTokenTransaction): FlushTokenTransactionBuilder {
54+
return this.initializeBuilder(tx, new FlushTokenTransactionBuilder(this._coinConfig));
55+
}
56+
4757
/** @inheritdoc */
4858
getWalletInitializationBuilder(): void {
4959
throw new Error('Method not implemented.');

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BaseUtils, TransactionType } from '@bitgo/sdk-core';
2-
import { v4CreateForwarderMethodId } from '@bitgo/abstract-eth';
2+
import { v4CreateForwarderMethodId, flushForwarderTokensMethodIdV4 } from '@bitgo/abstract-eth';
33
import { VET_ADDRESS_LENGTH, VET_BLOCK_ID_LENGTH, VET_TRANSACTION_ID_LENGTH } from './constants';
44
import { KeyPair } from './keyPair';
55
import { HexUInt, Transaction, TransactionClause } from '@vechain/sdk-core';
@@ -62,6 +62,8 @@ export class Utils implements BaseUtils {
6262
return TransactionType.Send;
6363
} else if (clauses[0].data.startsWith(v4CreateForwarderMethodId)) {
6464
return TransactionType.AddressInitialization;
65+
} else if (clauses[0].data.startsWith(flushForwarderTokensMethodIdV4)) {
66+
return TransactionType.FlushTokens;
6567
} else {
6668
return TransactionType.SendToken;
6769
}

0 commit comments

Comments
 (0)