Skip to content

Commit b555379

Browse files
committed
feat(sdk-coin-flrp): refactored transaction builder
Ticket: WIN-7770
1 parent c82d952 commit b555379

File tree

12 files changed

+322
-767
lines changed

12 files changed

+322
-767
lines changed

modules/sdk-coin-flrp/src/lib/atomicInCTransactionBuilder.ts

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import { AtomicTransactionBuilder } from './atomicTransactionBuilder';
22
import { BaseCoin as CoinConfig } from '@bitgo/statics';
33
import utils from './utils';
44
import { BuildTransactionError } from '@bitgo/sdk-core';
5+
import { Transaction } from './transaction';
56

6-
interface FlareChainNetworkMeta {
7-
blockchainID?: string; // P-chain id (external)
8-
cChainBlockchainID?: string; // C-chain id (local)
9-
[k: string]: unknown;
10-
}
7+
// interface FlareChainNetworkMeta {
8+
// blockchainID?: string; // P-chain id (external)
9+
// cChainBlockchainID?: string; // C-chain id (local)
10+
// [k: string]: unknown;
11+
// }
1112

1213
interface FeeShape {
1314
fee?: string; // legacy
@@ -41,12 +42,12 @@ export abstract class AtomicInCTransactionBuilder extends AtomicTransactionBuild
4142
/**
4243
* Recreate builder state from raw tx (hex). Flare C-chain support TBD; for now validate & stash.
4344
*/
44-
protected fromImplementation(rawTransaction: string): { _tx?: unknown } {
45+
protected fromImplementation(rawTransaction: string): Transaction {
4546
// If utils has validateRawTransaction use it; otherwise basic check
4647
if ((utils as unknown as { validateRawTransaction?: (r: string) => void }).validateRawTransaction) {
4748
(utils as unknown as { validateRawTransaction: (r: string) => void }).validateRawTransaction(rawTransaction);
4849
}
49-
this.transaction.setTransaction(rawTransaction);
50+
// this.transaction.setTransaction(rawTransaction);
5051
return this.transaction;
5152
}
5253

@@ -57,13 +58,8 @@ export abstract class AtomicInCTransactionBuilder extends AtomicTransactionBuild
5758
}
5859

5960
private initializeChainIds(): void {
60-
const meta = this.transaction._network as FlareChainNetworkMeta;
61-
if (meta?.blockchainID) {
62-
this._externalChainId = utils.cb58Decode(meta.blockchainID);
63-
}
64-
if (meta?.cChainBlockchainID) {
65-
this.transaction._blockchainID = utils.cb58Decode(meta.cChainBlockchainID);
66-
}
61+
this._externalChainId = utils.cb58Decode(this._network.blockchainID);
62+
this._blockchainID = utils.cb58Decode(this._network.cChainBlockchainID);
6763
}
6864

6965
private setFeeRate(n: bigint): void {

modules/sdk-coin-flrp/src/lib/atomicTransactionBuilder.ts

Lines changed: 100 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
1-
import { BaseCoin as CoinConfig } from '@bitgo/statics';
2-
import { BuildTransactionError, TransactionType, BaseTransaction } from '@bitgo/sdk-core';
1+
import { BaseCoin as CoinConfig, FlareNetwork } from '@bitgo/statics';
2+
import { BuildTransactionError, TransactionType, BaseTransaction, BaseKey } from '@bitgo/sdk-core';
33
import { Credential, Signature, TransferableInput, TransferableOutput } from '@flarenetwork/flarejs';
4-
import { TransactionExplanation, DecodedUtxoObj } from './iface';
4+
import { TransactionExplanation, DecodedUtxoObj, BaseAddress } from './iface';
5+
import { KeyPair } from './keyPair';
6+
import BigNumber from 'bignumber.js';
57
import {
68
ASSET_ID_LENGTH,
79
TRANSACTION_ID_HEX_LENGTH,
810
PRIVATE_KEY_HEX_LENGTH,
911
SECP256K1_SIGNATURE_LENGTH,
1012
TRANSACTION_ID_PREFIX,
11-
DEFAULT_NETWORK_ID,
12-
EMPTY_BUFFER_SIZE,
1313
HEX_PREFIX,
1414
HEX_PREFIX_LENGTH,
1515
DECIMAL_RADIX,
1616
SIGNING_METHOD,
1717
AMOUNT_STRING_ZERO,
18-
DEFAULT_LOCKTIME,
19-
DEFAULT_THRESHOLD,
2018
ZERO_BIGINT,
2119
ZERO_NUMBER,
2220
ERROR_AMOUNT_POSITIVE,
@@ -35,51 +33,41 @@ import {
3533
FLARE_ATOMIC_PARSED_PREFIX,
3634
HEX_ENCODING,
3735
} from './constants';
38-
import { createFlexibleHexRegex } from './utils';
36+
import utils, { createFlexibleHexRegex } from './utils';
37+
import { TransactionBuilder } from './transactionBuilder';
38+
import { Transaction } from './transaction';
3939

4040
/**
4141
* Flare P-chain atomic transaction builder with FlareJS credential support.
4242
* This provides the foundation for building Flare P-chain transactions with proper
4343
* credential handling using FlareJS Credential and Signature classes.
4444
*/
45-
export abstract class AtomicTransactionBuilder {
45+
export abstract class AtomicTransactionBuilder extends TransactionBuilder {
4646
protected readonly _coinConfig: Readonly<CoinConfig>;
4747
// External chain id (destination) for export transactions
4848
protected _externalChainId: Buffer | undefined;
4949

5050
protected _utxos: DecodedUtxoObj[] = [];
51-
52-
protected transaction: {
53-
_network: Record<string, unknown>;
54-
_networkID: number;
55-
_blockchainID: Buffer;
56-
_assetId: Buffer;
57-
_fromAddresses: string[];
58-
_to: string[];
59-
_locktime: bigint;
60-
_threshold: number;
61-
_fee: { fee: string; feeRate?: string; size?: number };
62-
hasCredentials: boolean;
63-
_tx?: unknown;
64-
_signature?: unknown;
65-
setTransaction: (tx: unknown) => void;
66-
} = {
67-
_network: {},
68-
_networkID: DEFAULT_NETWORK_ID,
69-
_blockchainID: Buffer.alloc(EMPTY_BUFFER_SIZE),
70-
_assetId: Buffer.alloc(EMPTY_BUFFER_SIZE),
71-
_fromAddresses: [],
72-
_to: [],
73-
_locktime: DEFAULT_LOCKTIME,
74-
_threshold: DEFAULT_THRESHOLD,
75-
_fee: { fee: AMOUNT_STRING_ZERO },
76-
hasCredentials: false,
77-
setTransaction: function (_tx: unknown) {
78-
this._tx = _tx;
79-
},
80-
};
51+
// TODO check _flrpTransaction type
52+
protected _flrpTransaction: any;
53+
54+
public _network: FlareNetwork;
55+
public _networkID: number;
56+
public _blockchainID: Buffer;
57+
public _assetId: Buffer;
58+
public _fromAddresses: string[];
59+
public _to: string[];
60+
public _locktime: bigint;
61+
public _threshold: number;
62+
public _fee: { fee: string; feeRate?: string; size?: number };
63+
public hasCredentials: boolean;
64+
public _tx?: unknown;
65+
public _signature?: unknown;
66+
public _type: TransactionType;
67+
public _rewardAddresses: Buffer[];
8168

8269
constructor(coinConfig: Readonly<CoinConfig>) {
70+
super(coinConfig);
8371
this._coinConfig = coinConfig;
8472
}
8573

@@ -91,8 +79,8 @@ export abstract class AtomicTransactionBuilder {
9179
*/
9280
protected getAssetId(): Buffer {
9381
// Use the asset ID from transaction if already set
94-
if (this.transaction._assetId && this.transaction._assetId.length > 0) {
95-
return this.transaction._assetId;
82+
if (this._assetId && this._assetId.length > 0) {
83+
return this._assetId;
9684
}
9785

9886
// For native FLR transactions, return zero-filled buffer as placeholder
@@ -218,7 +206,7 @@ export abstract class AtomicTransactionBuilder {
218206
assetID: this.getAssetId(),
219207
output: {
220208
amount: changeAmount,
221-
addresses: this.transaction._fromAddresses,
209+
addresses: this._fromAddresses,
222210
threshold: 1,
223211
locktime: 0n,
224212
},
@@ -326,8 +314,8 @@ export abstract class AtomicTransactionBuilder {
326314
};
327315

328316
// Store signature for FlareJS compatibility
329-
this.transaction._signature = signature;
330-
this.transaction.hasCredentials = true;
317+
this._signature = signature;
318+
this.hasCredentials = true;
331319

332320
return this;
333321
} catch (error) {
@@ -356,7 +344,7 @@ export abstract class AtomicTransactionBuilder {
356344
_type: this.transactionType,
357345
signature: [] as string[],
358346

359-
fromAddresses: this.transaction._fromAddresses,
347+
fromAddresses: this._fromAddresses,
360348
validationErrors: [],
361349

362350
// FlareJS methods with atomic support
@@ -374,7 +362,7 @@ export abstract class AtomicTransactionBuilder {
374362
id: `${FLARE_ATOMIC_PREFIX}${Date.now()}`,
375363
changeOutputs: [],
376364
changeAmount: AMOUNT_STRING_ZERO,
377-
fee: { fee: this.transaction._fee.fee },
365+
fee: { fee: this._fee.fee },
378366
}),
379367

380368
isTransactionForCChain: false,
@@ -383,7 +371,7 @@ export abstract class AtomicTransactionBuilder {
383371
},
384372
inputs: () => [],
385373
outputs: () => [],
386-
fee: () => ({ fee: this.transaction._fee.fee }),
374+
fee: () => ({ fee: this._fee.fee }),
387375
feeRate: () => 0,
388376
id: () => `${FLARE_ATOMIC_PREFIX}${Date.now()}`,
389377
type: this.transactionType,
@@ -412,12 +400,76 @@ export abstract class AtomicTransactionBuilder {
412400
id: `${FLARE_ATOMIC_PARSED_PREFIX}${Date.now()}`,
413401
changeOutputs: [],
414402
changeAmount: AMOUNT_STRING_ZERO,
415-
fee: { fee: this.transaction._fee.fee },
403+
fee: { fee: this._fee.fee },
416404
};
417405
} catch (error) {
418406
throw new BuildTransactionError(
419407
`${ERROR_ENHANCED_PARSE_FAILED}: ${error instanceof Error ? error.message : ERROR_UNKNOWN}`
420408
);
421409
}
422410
}
411+
412+
/** @inheritdoc */
413+
protected signImplementation({ key }: BaseKey): BaseTransaction {
414+
this._signer.push(new KeyPair({ prv: key }));
415+
return this.transaction;
416+
}
417+
418+
/** @inheritdoc */
419+
protected async buildImplementation(): Promise<Transaction> {
420+
this.buildFlareTransaction();
421+
this.transaction.setTransactionType(this.transactionType);
422+
if (this.hasSigner) {
423+
this._signer.forEach((keyPair) => this.transaction.sign(keyPair));
424+
}
425+
return this.transaction;
426+
}
427+
428+
/**
429+
* Builds the avax transaction. transaction field is changed.
430+
*/
431+
protected abstract buildFlareTransaction(): void;
432+
433+
/**
434+
* Getter for know if build should sign
435+
*/
436+
get hasSigner(): boolean {
437+
return this._signer !== undefined && this._signer.length > 0;
438+
}
439+
440+
/** @inheritdoc */
441+
validateKey({ key }: BaseKey): void {
442+
if (!new KeyPair({ prv: key })) {
443+
throw new BuildTransactionError('Invalid key');
444+
}
445+
}
446+
447+
/** @inheritdoc */
448+
validateAddress(address: BaseAddress, addressFormat?: string): void {
449+
if (!utils.isValidAddress(address.address)) {
450+
throw new BuildTransactionError('Invalid address');
451+
}
452+
}
453+
454+
/** @inheritdoc */
455+
validateValue(value: BigNumber): void {
456+
if (value.isLessThan(0)) {
457+
throw new BuildTransactionError('Value cannot be less than zero');
458+
}
459+
}
460+
461+
/**
462+
* Check the raw transaction has a valid format in the blockchain context, throw otherwise.
463+
* It overrides abstract method from BaseTransactionBuilder
464+
*
465+
* @param rawTransaction Transaction in any format
466+
*/
467+
validateRawTransaction(rawTransaction: string): void {
468+
utils.validateRawTransaction(rawTransaction);
469+
}
470+
471+
/** @inheritdoc */
472+
validateTransaction(transaction?: Transaction): void {
473+
// throw new NotImplementedError('validateTransaction not implemented');
474+
}
423475
}

modules/sdk-coin-flrp/src/lib/exportInCTxBuilder.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
NETWORK_ID_PROP,
1616
BLOCKCHAIN_ID_PROP,
1717
} from './constants';
18+
import { Transaction } from './transaction';
1819

1920
// Lightweight interface placeholders replacing Avalanche SDK transaction shapes
2021
interface FlareExportInputShape {
@@ -111,7 +112,7 @@ export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
111112
if (unsigned.networkId !== this.transaction._networkID) {
112113
throw new Error('Network ID mismatch');
113114
}
114-
if (!unsigned.sourceBlockchainId.equals(this.transaction._blockchainID)) {
115+
if (!unsigned.sourceBlockchainId.equals(this._blockchainID)) {
115116
throw new Error('Blockchain ID mismatch');
116117
}
117118
if (unsigned.outputs.length !== 1) {
@@ -122,23 +123,23 @@ export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
122123
}
123124
const out = unsigned.outputs[0];
124125
const inp = unsigned.inputs[0];
125-
if (!out.assetId.equals(this.transaction._assetId) || !inp.assetId.equals(this.transaction._assetId)) {
126+
if (!out.assetId.equals(this._assetId) || !inp.assetId.equals(this._assetId)) {
126127
throw new Error('AssetID mismatch');
127128
}
128-
this.transaction._to = out.addresses;
129+
this._to = out.addresses;
129130
this._amount = out.amount;
130131
const fee = inp.amount - out.amount;
131132
if (fee < 0n) {
132133
throw new BuildTransactionError('Computed fee is negative');
133134
}
134135
const fixed = this.fixedFee;
135-
const feeRate = fee - fixed;
136-
this.transaction._fee.feeRate = feeRate.toString();
136+
const feeRate = Number(fee - fixed);
137+
this.transaction._fee.feeRate = feeRate;
137138
this.transaction._fee.fee = fee.toString();
138139
this.transaction._fee.size = 1;
139140
this.transaction._fromAddresses = [inp.address];
140141
this._nonce = inp.nonce;
141-
this.transaction.setTransaction(raw);
142+
// this.transaction.setTransaction(raw.unsignedTx);
142143
return this;
143144
}
144145

@@ -225,28 +226,30 @@ export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
225226
// Create FlareJS-ready unsigned transaction
226227
const unsigned: FlareUnsignedExportTx = {
227228
networkId: this.transaction._networkID,
228-
sourceBlockchainId: this.transaction._blockchainID,
229+
sourceBlockchainId: this._blockchainID,
229230
destinationBlockchainId: this._externalChainId || Buffer.alloc(ASSET_ID_LENGTH),
230231
inputs: [input],
231232
outputs: [output],
232233
};
233234

235+
console.log('Unsigned Export Tx:', unsigned);
236+
234237
// Create signed transaction structure
235-
const signed: FlareSignedExportTx = { unsignedTx: unsigned, credentials: [] };
238+
// const signed: FlareSignedExportTx = { unsignedTx: unsigned, credentials: [] };
236239

237240
// Update transaction fee information
238241
this.transaction._fee.fee = totalFee.toString();
239242
this.transaction._fee.size = 1;
240243

241244
// Set the enhanced transaction
242-
this.transaction.setTransaction(signed);
245+
// this.transaction.setTransaction(signed);
243246
}
244247

245248
/** @inheritdoc */
246-
protected fromImplementation(raw: string | RawFlareExportTx): { _tx?: unknown } {
249+
protected fromImplementation(raw: string | RawFlareExportTx): Transaction {
247250
if (typeof raw === STRING_TYPE) {
248251
// Future: parse hex or serialized form. For now treat as opaque raw tx.
249-
this.transaction.setTransaction(raw);
252+
// this.transaction.setTransaction(raw);
250253
return this.transaction;
251254
}
252255
return this.initBuilder(raw as RawFlareExportTx).transaction;

modules/sdk-coin-flrp/src/lib/exportInPTxBuilder.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export class ExportInPTxBuilder extends AtomicTransactionBuilder {
126126
],
127127

128128
// Enhanced fee structure for P-chain operations
129-
fee: BigInt(this.transaction._fee.fee) || BigInt(DEFAULT_BASE_FEE), // Default P-chain fee
129+
fee: BigInt(this._fee.fee) || BigInt(DEFAULT_BASE_FEE), // Default P-chain fee
130130

131131
// Credential placeholders ready for FlareJS integration
132132
credentials: this.transaction._fromAddresses.map(() => ({
@@ -139,8 +139,10 @@ export class ExportInPTxBuilder extends AtomicTransactionBuilder {
139139
memo: Buffer.alloc(EMPTY_BUFFER_SIZE),
140140
};
141141

142+
console.log('Enhanced P-chain Export Tx:', enhancedExportTx);
143+
142144
// Store the transaction structure
143-
this.transaction.setTransaction(enhancedExportTx);
145+
// this.transaction.setTransaction(enhancedExportTx);
144146
}
145147

146148
/**

modules/sdk-coin-flrp/src/lib/iface.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,3 +138,7 @@ export interface FlrpSpendOptions {
138138
memo?: Uint8Array; // FlareJS memo format (byte array)
139139
locktime?: bigint;
140140
}
141+
142+
export interface BaseAddress {
143+
address: string;
144+
}

0 commit comments

Comments
 (0)