Skip to content

Commit ea85be9

Browse files
Merge pull request #7694 from BitGo/WIN-8153
feat(sdk-coin-iota): refactor tx builders for better readability
2 parents 0ff8926 + 7391438 commit ea85be9

File tree

10 files changed

+1432
-1757
lines changed

10 files changed

+1432
-1757
lines changed

modules/sdk-coin-iota/src/lib/transaction.ts

Lines changed: 230 additions & 97 deletions
Large diffs are not rendered by default.

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

Lines changed: 100 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import utils from './utils';
1515
import { TransactionObjectInput, GasData } from './iface';
1616
import { toBase64 } from '@iota/iota-sdk/utils';
1717

18+
/**
19+
* Base class for IOTA transaction builders.
20+
* Provides common functionality for building and validating IOTA transactions.
21+
*/
1822
export abstract class TransactionBuilder extends BaseTransactionBuilder {
1923
protected _transaction: Transaction;
2024

@@ -23,12 +27,18 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
2327
}
2428

2529
/**
26-
* Initialize the transaction builder fields using the decoded transaction data
27-
*
28-
* @param {Transaction} tx the transaction data
30+
* Initializes the transaction builder with data from an existing transaction.
31+
* Copies sender, gas data, and gas sponsor information.
2932
*/
3033
initBuilder(tx: Transaction): void {
3134
this.validateTransaction(tx);
35+
this.copyTransactionData(tx);
36+
}
37+
38+
/**
39+
* Copies transaction data from the source transaction to the builder's transaction.
40+
*/
41+
private copyTransactionData(tx: Transaction): void {
3242
this.transaction.sender = tx.sender;
3343
this.transaction.gasPrice = tx.gasPrice;
3444
this.transaction.gasBudget = tx.gasBudget;
@@ -40,25 +50,18 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
4050
return this.transaction.type;
4151
}
4252

43-
/**
44-
* @inheritdoc
45-
* */
4653
get transaction(): Transaction {
4754
return this._transaction;
4855
}
4956

50-
/**
51-
* @inheritdoc
52-
* */
5357
protected set transaction(transaction: Transaction) {
5458
this._transaction = transaction;
5559
}
5660

5761
/**
58-
* Sets the sender of this transaction.
59-
*
60-
* @param {string} senderAddress the account that is sending this transaction
61-
* @returns {TransactionBuilder} This transaction builder
62+
* Sets the sender address for this transaction.
63+
* @param senderAddress - The IOTA address that is sending this transaction
64+
* @returns This transaction builder for method chaining
6265
*/
6366
sender(senderAddress: string): this {
6467
this.validateAddress({ address: senderAddress });
@@ -67,70 +70,99 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
6770
}
6871

6972
/**
70-
* Sets the gasData for this transaction.
71-
*
72-
* @param {string} gasData the gas details for this transaction
73-
* @returns {TransactionBuilder} This transaction builder
73+
* Sets the gas data for this transaction (budget, price, and payment objects).
74+
* @param gasData - The gas configuration including budget, price, and payment objects
75+
* @returns This transaction builder for method chaining
7476
*/
7577
gasData(gasData: GasData): this {
7678
this.validateGasData(gasData);
79+
this.setGasDataOnTransaction(gasData);
80+
return this;
81+
}
82+
83+
/**
84+
* Sets gas data fields on the transaction.
85+
*/
86+
private setGasDataOnTransaction(gasData: GasData): void {
7787
this.transaction.gasPrice = gasData.gasPrice;
7888
this.transaction.gasBudget = gasData.gasBudget;
7989
this.transaction.gasPaymentObjects = gasData.gasPaymentObjects as TransactionObjectInput[];
80-
return this;
8190
}
8291

8392
/**
84-
* Sets the gasSponsor of this transaction.
85-
*
86-
* @param {string} sponsorAddress the account that is sponsoring this transaction's gas
87-
* @returns {TransactionBuilder} This transaction builder
93+
* Sets the gas sponsor for this transaction.
94+
* The gas sponsor pays for transaction fees instead of the sender.
95+
* @param sponsorAddress - The IOTA address sponsoring this transaction's gas fees
96+
* @returns This transaction builder for method chaining
8897
*/
8998
gasSponsor(sponsorAddress: string): this {
9099
this.validateAddress({ address: sponsorAddress });
91100
this.transaction.gasSponsor = sponsorAddress;
92101
return this;
93102
}
94103

104+
/**
105+
* Adds a signature from the transaction sender.
106+
* @param publicKey - The sender's public key
107+
* @param signature - The signature bytes
108+
* @throws BuildTransactionError if the signature or public key is invalid
109+
*/
95110
addSignature(publicKey: PublicKey, signature: Buffer): void {
96-
if (!utils.isValidPublicKey(publicKey.pub) || !utils.isValidSignature(toBase64(signature))) {
97-
throw new BuildTransactionError('Invalid transaction signature');
98-
}
111+
this.validateSignatureData(publicKey, signature);
99112
this.transaction.addSignature(publicKey, signature);
100113
}
101114

115+
/**
116+
* Adds a signature from the gas sponsor.
117+
* @param publicKey - The gas sponsor's public key
118+
* @param signature - The signature bytes
119+
* @throws BuildTransactionError if the signature or public key is invalid
120+
*/
102121
addGasSponsorSignature(publicKey: PublicKey, signature: Buffer): void {
122+
this.validateSignatureData(publicKey, signature);
123+
this.transaction.addGasSponsorSignature(publicKey, signature);
124+
}
125+
126+
/**
127+
* Validates that the signature and public key are in valid formats.
128+
*/
129+
private validateSignatureData(publicKey: PublicKey, signature: Buffer): void {
103130
if (!utils.isValidPublicKey(publicKey.pub) || !utils.isValidSignature(toBase64(signature))) {
104131
throw new BuildTransactionError('Invalid transaction signature');
105132
}
106-
this.transaction.addGasSponsorSignature(publicKey, signature);
107133
}
108134

109135
validateKey(key: BaseKey): void {
110136
throw new Error('Method not implemented.');
111137
}
112138

113139
/**
114-
* @inheritdoc
115-
* */
140+
* Validates an IOTA address format.
141+
* @throws BuildTransactionError if address is invalid
142+
*/
116143
validateAddress(address: BaseAddress, addressFormat?: string): void {
117144
if (!utils.isValidAddress(address.address)) {
118145
throw new BuildTransactionError('Invalid address ' + address.address);
119146
}
120147
}
121148

122149
/**
123-
* @inheritdoc
124-
* */
150+
* Validates that a numeric value is valid (not NaN and not negative).
151+
* @throws BuildTransactionError if value is invalid
152+
*/
125153
validateValue(value: BigNumber): void {
126154
if (value.isNaN()) {
127155
throw new BuildTransactionError('Invalid amount format');
128-
} else if (value.isLessThan(0)) {
156+
}
157+
if (value.isLessThan(0)) {
129158
throw new BuildTransactionError('Value cannot be less than zero');
130159
}
131160
}
132161

133-
/** @inheritdoc */
162+
/**
163+
* Validates that a raw transaction string is properly formatted.
164+
* @throws ParseTransactionError if raw transaction is invalid
165+
*/
134166
validateRawTransaction(rawTransaction: string): void {
135167
if (!rawTransaction) {
136168
throw new ParseTransactionError('Invalid raw transaction: Undefined');
@@ -141,26 +173,32 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
141173
}
142174

143175
/**
144-
* @inheritdoc
145-
* */
176+
* Validates a transaction object has all required fields.
177+
* @throws Error if transaction is undefined or has invalid data
178+
*/
146179
validateTransaction(transaction?: Transaction): void {
147180
if (!transaction) {
148181
throw new Error('transaction not defined');
149182
}
183+
150184
this.validateAddress({ address: transaction.sender });
151185
this.validateGasData({
152186
gasBudget: transaction.gasBudget,
153187
gasPrice: transaction.gasPrice,
154188
gasPaymentObjects: transaction.gasPaymentObjects,
155189
});
190+
156191
if (transaction.gasSponsor) {
157192
this.validateAddress({ address: transaction.gasSponsor });
158193
}
159194
}
160195

161196
/**
162-
* @inheritdoc
163-
* */
197+
* Creates a transaction object from a raw transaction string or bytes.
198+
* @param rawTransaction - Raw transaction in base64 string or Uint8Array format
199+
* @returns The parsed transaction object
200+
* @throws BuildTransactionError if raw transaction is invalid
201+
*/
164202
fromImplementation(rawTransaction: string | Uint8Array): Transaction {
165203
if (!utils.isValidRawTransaction(rawTransaction)) {
166204
throw new BuildTransactionError('Invalid transaction');
@@ -170,32 +208,50 @@ export abstract class TransactionBuilder extends BaseTransactionBuilder {
170208
}
171209

172210
/**
173-
* @inheritdoc
174-
* */
211+
* Sign implementation - not supported for IOTA transactions.
212+
* IOTA transactions must be signed externally.
213+
*/
175214
protected signImplementation(key: BaseKey): BaseTransaction {
176215
throw new Error('Method not implemented.');
177216
}
178217

179218
/**
180-
* @inheritdoc
181-
* */
219+
* Builds the transaction and prepares it for broadcast.
220+
* Automatically switches from simulate to real transaction mode if gas data is present.
221+
*/
182222
protected async buildImplementation(): Promise<Transaction> {
183-
// If gas data is provided, this is not a simulate transaction
184-
if (this.transaction.gasPrice && this.transaction.gasBudget && this.transaction.gasPaymentObjects) {
185-
this.transaction.isSimulateTx = false;
186-
}
223+
this.updateTransactionMode();
187224
await this.transaction.build();
188225
this.transaction.addInputsAndOutputs();
189226
return this.transaction;
190227
}
191228

229+
/**
230+
* Updates the transaction mode based on gas data availability.
231+
* Switches to real transaction mode if all gas data is provided.
232+
*/
233+
private updateTransactionMode(): void {
234+
const hasCompleteGasData =
235+
this.transaction.gasPrice && this.transaction.gasBudget && this.transaction.gasPaymentObjects;
236+
237+
if (hasCompleteGasData) {
238+
this.transaction.isSimulateTx = false;
239+
}
240+
}
241+
242+
/**
243+
* Validates gas data values and presence.
244+
* @throws BuildTransactionError if gas data is invalid
245+
*/
192246
private validateGasData(gasData: GasData): void {
193247
if (gasData.gasBudget) {
194248
this.validateValue(new BigNumber(gasData.gasBudget));
195249
}
250+
196251
if (gasData.gasPrice) {
197252
this.validateValue(new BigNumber(gasData.gasPrice));
198253
}
254+
199255
if (gasData.gasPaymentObjects && gasData.gasPaymentObjects.length === 0) {
200256
throw new BuildTransactionError('Gas input objects list is empty');
201257
}

0 commit comments

Comments
 (0)