Skip to content

Commit 2fd393e

Browse files
committed
feat(sdk-coin-tempo): add comprehensive tests for TIP-20 transaction builder
Ticket: CECHO-122
1 parent ba9875b commit 2fd393e

File tree

5 files changed

+546
-258
lines changed

5 files changed

+546
-258
lines changed

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,18 +82,25 @@ export class Tip20Transaction extends BaseTransaction {
8282
}
8383

8484
/**
85-
* Build base RLP data array per Tempo EIP-7702 specification
86-
* @param callsTuples Encoded calls
87-
* @param accessTuples Encoded access list
88-
* @returns RLP-ready array of transaction fields
85+
* Convert bigint to hex string for RLP encoding
86+
* @param value bigint value to convert
87+
* @returns Hex string
8988
* @private
9089
*/
90+
private bigintToHex(value: bigint): string {
91+
if (value === 0n) {
92+
return '0x';
93+
}
94+
const hex = value.toString(16);
95+
return '0x' + (hex.length % 2 ? '0' : '') + hex;
96+
}
97+
9198
private buildBaseRlpData(callsTuples: any[], accessTuples: any[]): any[] {
9299
return [
93100
ethers.utils.hexlify(this.txRequest.chainId),
94-
this.txRequest.maxPriorityFeePerGas ? ethers.utils.hexlify(this.txRequest.maxPriorityFeePerGas.toString()) : '0x',
95-
ethers.utils.hexlify(this.txRequest.maxFeePerGas.toString()),
96-
ethers.utils.hexlify(this.txRequest.gas.toString()),
101+
this.txRequest.maxPriorityFeePerGas ? this.bigintToHex(this.txRequest.maxPriorityFeePerGas) : '0x',
102+
this.bigintToHex(this.txRequest.maxFeePerGas),
103+
this.bigintToHex(this.txRequest.gas),
97104
callsTuples,
98105
accessTuples,
99106
'0x', // nonceKey (reserved for 2D nonce system)

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

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,13 @@ export class Tip20TransactionBuilder extends AbstractTransactionBuilder {
4646
}
4747

4848
/**
49-
* Build the transaction from configured TIP-20 operations and transaction parameters
49+
* Validate the transaction has all required fields for Tempo AA transactions.
50+
* Overrides parent class validation since AA transactions use a different model
51+
* (operations-based rather than single contract address).
52+
*
53+
* @throws BuildTransactionError if validation fails
5054
*/
51-
protected async buildImplementation(): Promise<BaseTransaction> {
55+
validateTransaction(): void {
5256
if (this.operations.length === 0) {
5357
throw new BuildTransactionError('At least one operation is required to build a transaction');
5458
}
@@ -68,6 +72,24 @@ export class Tip20TransactionBuilder extends AbstractTransactionBuilder {
6872
if (this._maxPriorityFeePerGas === undefined) {
6973
throw new BuildTransactionError('maxPriorityFeePerGas is required to build a transaction');
7074
}
75+
}
76+
77+
/**
78+
* Build the transaction from configured TIP-20 operations and transaction parameters.
79+
* Validation is performed by validateTransaction() which is called by build() before this method.
80+
*/
81+
protected async buildImplementation(): Promise<BaseTransaction> {
82+
// These checks satisfy TypeScript's type narrowing.
83+
// validateTransaction() already ensures these are defined, but TypeScript
84+
// doesn't track that across method boundaries.
85+
if (
86+
this._nonce === undefined ||
87+
this._gas === undefined ||
88+
this._maxFeePerGas === undefined ||
89+
this._maxPriorityFeePerGas === undefined
90+
) {
91+
throw new BuildTransactionError('Transaction validation failed: missing required fields');
92+
}
7193

7294
const calls = this.operations.map((op) => this.operationToCall(op));
7395

@@ -140,6 +162,7 @@ export class Tip20TransactionBuilder extends AbstractTransactionBuilder {
140162
throw new BuildTransactionError(`Invalid gas limit: ${gas}`);
141163
}
142164
this._gas = gasValue;
165+
this.updateEip1559Fee();
143166
return this;
144167
}
145168

@@ -155,6 +178,7 @@ export class Tip20TransactionBuilder extends AbstractTransactionBuilder {
155178
throw new BuildTransactionError(`Invalid maxFeePerGas: ${maxFeePerGas}`);
156179
}
157180
this._maxFeePerGas = feeValue;
181+
this.updateEip1559Fee();
158182
return this;
159183
}
160184

@@ -170,9 +194,27 @@ export class Tip20TransactionBuilder extends AbstractTransactionBuilder {
170194
throw new BuildTransactionError(`Invalid maxPriorityFeePerGas: ${maxPriorityFeePerGas}`);
171195
}
172196
this._maxPriorityFeePerGas = feeValue;
197+
this.updateEip1559Fee();
173198
return this;
174199
}
175200

201+
/**
202+
* Update the parent class fee structure with EIP-1559 parameters
203+
* @private
204+
*/
205+
private updateEip1559Fee(): void {
206+
if (this._maxFeePerGas !== undefined && this._maxPriorityFeePerGas !== undefined && this._gas !== undefined) {
207+
this.fee({
208+
fee: this._maxFeePerGas.toString(),
209+
gasLimit: this._gas.toString(),
210+
eip1559: {
211+
maxFeePerGas: this._maxFeePerGas.toString(),
212+
maxPriorityFeePerGas: this._maxPriorityFeePerGas.toString(),
213+
},
214+
});
215+
}
216+
}
217+
176218
/**
177219
* Get all operations in this transaction
178220
* @returns Array of TIP-20 operations

0 commit comments

Comments
 (0)