Skip to content

Commit a5ae88e

Browse files
committed
feat(sdk-coin-tempo): add transaction serialization
TICKET: WIN-8479
1 parent ab6caf8 commit a5ae88e

3 files changed

Lines changed: 107 additions & 8 deletions

File tree

modules/sdk-coin-tempo/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"@bitgo/sdk-core": "^36.25.0",
4545
"@bitgo/secp256k1": "^1.8.0",
4646
"@bitgo/statics": "^58.19.0",
47+
"@ethereumjs/common": "^2.6.5",
4748
"ethers": "^5.7.2"
4849
},
4950
"devDependencies": {

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

Lines changed: 99 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77

88
import { BaseTransaction, ParseTransactionError, TransactionType } from '@bitgo/sdk-core';
99
import { BaseCoin as CoinConfig } from '@bitgo/statics';
10+
import { ethers } from 'ethers';
1011
import { Address, Hex, Tip20Operation } from './types';
12+
import { AA_TRANSACTION_TYPE } from './constants';
1113

1214
/**
1315
* TIP-20 Transaction Request Structure
@@ -54,8 +56,103 @@ export class Tip20Transaction extends BaseTransaction {
5456
}
5557

5658
async serialize(signature?: { r: Hex; s: Hex; yParity: number }): Promise<Hex> {
57-
// TODO: Implement EIP-7702 transaction serialization with ethers.js
58-
throw new ParseTransactionError('Transaction serialization not yet implemented');
59+
const sig = signature || this._signature;
60+
61+
// For unsigned transactions, return the transaction hash that needs to be signed
62+
if (!sig) {
63+
// Return the unsigned transaction serialization
64+
return this.serializeUnsigned();
65+
}
66+
67+
// Serialize signed transaction for broadcasting
68+
return this.serializeSigned(sig);
69+
}
70+
71+
/**
72+
* Serialize unsigned transaction for signing
73+
* @returns Hex string of unsigned transaction
74+
* @private
75+
*/
76+
private serializeUnsigned(): Hex {
77+
// EIP-7702 transaction structure for signing
78+
// This creates the transaction hash that will be signed
79+
const txData = {
80+
type: parseInt(AA_TRANSACTION_TYPE, 16),
81+
chainId: this.txRequest.chainId,
82+
nonce: this.txRequest.nonce,
83+
maxFeePerGas: ethers.BigNumber.from(this.txRequest.maxFeePerGas.toString()),
84+
maxPriorityFeePerGas: ethers.BigNumber.from(this.txRequest.maxPriorityFeePerGas.toString()),
85+
gasLimit: ethers.BigNumber.from(this.txRequest.gas.toString()),
86+
to: this.txRequest.calls.length > 0 ? this.txRequest.calls[0].to : ethers.constants.AddressZero,
87+
value: this.txRequest.calls.length > 0 ? this.txRequest.calls[0].value : 0,
88+
data: this.txRequest.calls.length > 0 ? this.txRequest.calls[0].data : '0x',
89+
};
90+
91+
// For multi-call transactions, encode all calls
92+
if (this.txRequest.calls.length > 1) {
93+
// Encode multiple calls as batch data
94+
txData.data = this.encodeBatchCalls();
95+
}
96+
97+
// Serialize transaction for signing
98+
try {
99+
return ethers.utils.serializeTransaction(txData) as Hex;
100+
} catch (error) {
101+
throw new ParseTransactionError(`Failed to serialize unsigned transaction: ${error}`);
102+
}
103+
}
104+
105+
/**
106+
* Serialize signed transaction for broadcasting
107+
* @param signature Transaction signature
108+
* @returns Hex string of signed transaction
109+
* @private
110+
*/
111+
private serializeSigned(signature: { r: Hex; s: Hex; yParity: number }): Hex {
112+
const txData = {
113+
type: parseInt(AA_TRANSACTION_TYPE, 16),
114+
chainId: this.txRequest.chainId,
115+
nonce: this.txRequest.nonce,
116+
maxFeePerGas: ethers.BigNumber.from(this.txRequest.maxFeePerGas.toString()),
117+
maxPriorityFeePerGas: ethers.BigNumber.from(this.txRequest.maxPriorityFeePerGas.toString()),
118+
gasLimit: ethers.BigNumber.from(this.txRequest.gas.toString()),
119+
to: this.txRequest.calls.length > 0 ? this.txRequest.calls[0].to : ethers.constants.AddressZero,
120+
value: this.txRequest.calls.length > 0 ? this.txRequest.calls[0].value : 0,
121+
data: this.txRequest.calls.length > 0 ? this.txRequest.calls[0].data : '0x',
122+
// Add signature fields
123+
r: signature.r,
124+
s: signature.s,
125+
v: signature.yParity,
126+
};
127+
128+
// For multi-call transactions, encode all calls
129+
if (this.txRequest.calls.length > 1) {
130+
txData.data = this.encodeBatchCalls();
131+
}
132+
133+
try {
134+
return ethers.utils.serializeTransaction(txData, {
135+
r: signature.r,
136+
s: signature.s,
137+
v: signature.yParity,
138+
}) as Hex;
139+
} catch (error) {
140+
throw new ParseTransactionError(`Failed to serialize signed transaction: ${error}`);
141+
}
142+
}
143+
144+
/**
145+
* Encode multiple calls into batch transaction data
146+
* @returns Hex-encoded batch call data
147+
* @private
148+
*/
149+
private encodeBatchCalls(): Hex {
150+
// For now, encode the first call
151+
// TODO: Implement proper batch encoding when multi-call ABI is available
152+
if (this.txRequest.calls.length > 0) {
153+
return this.txRequest.calls[0].data;
154+
}
155+
return '0x';
59156
}
60157

61158
getOperations(): Tip20Operation[] {

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import {
88
UnsignedSweepTxMPCv2,
99
TransactionBuilder,
1010
} from '@bitgo/abstract-eth';
11+
import type * as EthLikeCommon from '@ethereumjs/common';
1112
import { BaseCoin, BitGoBase, MPCAlgorithm } from '@bitgo/sdk-core';
12-
import { BaseCoin as StaticsBaseCoin } from '@bitgo/statics';
13+
import { BaseCoin as StaticsBaseCoin, coins } from '@bitgo/statics';
14+
import { Tip20TransactionBuilder } from './lib';
1315

1416
export class Tempo extends AbstractEthLikeNewCoins {
1517
protected constructor(bitgo: BitGoBase, staticsCoin?: Readonly<StaticsBaseCoin>) {
@@ -105,12 +107,11 @@ export class Tempo extends AbstractEthLikeNewCoins {
105107

106108
/**
107109
* Get transaction builder for Tempo
108-
* TODO: Implement TransactionBuilder for Tempo
110+
* Returns a TIP-20 transaction builder for Tempo-specific operations
111+
* @param common - Optional common chain configuration
109112
* @protected
110113
*/
111-
protected getTransactionBuilder(): TransactionBuilder {
112-
// TODO: Create and return TransactionBuilder instance
113-
// Return undefined cast as TransactionBuilder to prevent downstream services from breaking
114-
return undefined as unknown as TransactionBuilder;
114+
protected getTransactionBuilder(common?: EthLikeCommon.default): TransactionBuilder {
115+
return new Tip20TransactionBuilder(coins.get(this.getBaseChain())) as unknown as TransactionBuilder;
115116
}
116117
}

0 commit comments

Comments
 (0)