Skip to content

Commit 53d5509

Browse files
committed
refactor(sdk-coin-flrp): update flrp txn builders to use etna support
Ticket: WIN-8498
1 parent 9423f1d commit 53d5509

27 files changed

+1399
-2214
lines changed

modules/sdk-coin-flrp/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"@bitgo/sdk-test": "^9.1.20"
4848
},
4949
"dependencies": {
50+
"@bitgo/public-types": "5.59.0",
5051
"@bitgo/sdk-core": "^36.25.0",
5152
"@bitgo/secp256k1": "^1.8.0",
5253
"@bitgo/statics": "^58.19.0",

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

Lines changed: 36 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -5,47 +5,30 @@ import {
55
evmSerial,
66
UnsignedTx,
77
Credential,
8-
BigIntPr,
9-
Int,
10-
Id,
11-
TransferableOutput,
128
Address,
139
TransferOutput,
14-
OutputOwners,
1510
utils as FlareUtils,
11+
evm,
1612
} from '@flarenetwork/flarejs';
1713
import utils from './utils';
18-
import { DecodedUtxoObj, Tx, FlareTransactionType } from './iface';
14+
import { Tx, FlareTransactionType, ExportEVMOptions } from './iface';
1915

2016
export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
21-
private _amount: bigint;
2217
private _nonce: bigint;
2318

2419
constructor(_coinConfig: Readonly<CoinConfig>) {
2520
super(_coinConfig);
2621
}
2722

2823
/**
29-
* Utxos are not required in Export Tx in C-Chain.
30-
* Override utxos to prevent used by throwing a error.
24+
* UTXOs are not required for Export Tx from C-Chain (uses EVM balance instead).
25+
* Override to prevent usage by throwing an error.
3126
*
32-
* @param {DecodedUtxoObj[]} value ignored
27+
* @param {string[]} _utxoHexStrings - ignored, UTXOs not used for C-chain exports
28+
* @throws {BuildTransactionError} always throws as UTXOs are not applicable
3329
*/
34-
utxos(value: DecodedUtxoObj[]): this {
35-
throw new BuildTransactionError('utxos are not required in Export Tx in C-Chain');
36-
}
37-
38-
/**
39-
* Amount is a bigint that specifies the quantity of the asset that this output owns. Must be positive.
40-
* The transaction output amount add a fixed fee that will be paid upon import.
41-
*
42-
* @param {bigint | string} amount The withdrawal amount
43-
*/
44-
amount(amount: bigint | string): this {
45-
const amountBigInt = typeof amount === 'string' ? BigInt(amount) : amount;
46-
this.validateAmount(amountBigInt);
47-
this._amount = amountBigInt;
48-
return this;
30+
utxos(_utxoHexStrings: string[]): this {
31+
throw new BuildTransactionError('UTXOs are not required for Export Tx from C-Chain');
4932
}
5033

5134
/**
@@ -81,8 +64,6 @@ export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
8164
throw new NotSupported('Transaction cannot be parsed or has an unsupported transaction type');
8265
}
8366

84-
// The outputs is a multisign P-Chain address result.
85-
// It's expected to have only one output to the destination P-Chain address.
8667
const outputs = baseTx.exportedOutputs;
8768
if (outputs.length !== 1) {
8869
throw new BuildTransactionError('Transaction can have one output');
@@ -93,8 +74,6 @@ export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
9374
throw new BuildTransactionError('AssetID mismatch');
9475
}
9576

96-
// The inputs is not an utxo.
97-
// It's expected to have only one input from C-Chain address.
9877
const inputs = baseTx.ins;
9978
if (inputs.length !== 1) {
10079
throw new BuildTransactionError('Transaction can have one input');
@@ -107,27 +86,17 @@ export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
10786
const inputAmount = input.amount.value();
10887
const outputAmount = transferOutput.amount();
10988
const fee = inputAmount - outputAmount;
110-
this._amount = outputAmount;
111-
// Subtract fixedFee from total fee to get the gas-based feeRate
112-
// buildFlareTransaction will add fixedFee back when building the transaction
113-
this.transaction._fee.feeRate = Number(fee) - Number(this.fixedFee);
89+
this.transaction._amount = outputAmount;
11490
this.transaction._fee.fee = fee.toString();
115-
this.transaction._fee.size = 1;
11691
this.transaction._fromAddresses = [Buffer.from(input.address.toBytes())];
11792
this.transaction._locktime = transferOutput.getLocktime();
118-
11993
this._nonce = input.nonce.value();
120-
121-
// Use credentials passed from TransactionBuilderFactory (properly extracted using codec)
12294
const credentials = parsedCredentials || [];
12395
const hasCredentials = credentials.length > 0;
124-
125-
// If it's a signed transaction, store the original raw bytes to preserve exact format
12696
if (hasCredentials && rawBytes) {
12797
this.transaction._rawSignedBytes = rawBytes;
12898
}
12999

130-
// Create proper UnsignedTx wrapper with credentials
131100
const fromAddress = new Address(this.transaction._fromAddresses[0]);
132101
const addressMap = new FlareUtils.AddressMap([
133102
[fromAddress, 0],
@@ -160,80 +129,52 @@ export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
160129
*/
161130
protected buildFlareTransaction(): void {
162131
if (this.transaction.hasCredentials) return;
163-
if (this._amount === undefined) {
164-
throw new Error('amount is required');
132+
if (this.transaction._amount === undefined) {
133+
throw new BuildTransactionError('amount is required');
165134
}
166135
if (this.transaction._fromAddresses.length !== 1) {
167-
throw new Error('sender is one and required');
136+
throw new BuildTransactionError('sender is one and required');
168137
}
169138
if (this.transaction._to.length === 0) {
170-
throw new Error('to is required');
139+
throw new BuildTransactionError('to is required');
171140
}
172-
if (!this.transaction._fee.feeRate) {
173-
throw new Error('fee rate is required');
141+
if (!this.transaction._fee.fee) {
142+
throw new BuildTransactionError('fee rate is required');
174143
}
175144
if (this._nonce === undefined) {
176-
throw new Error('nonce is required');
145+
throw new BuildTransactionError('nonce is required');
146+
}
147+
if (!this.transaction._context) {
148+
throw new BuildTransactionError('context is required');
177149
}
178150

179-
// For EVM exports, total fee = feeRate (gas-based fee) + fixedFee (P-chain import fee)
180-
// This matches the AVAX implementation where fixedFee covers the import cost
181-
const txFee = BigInt(this.fixedFee);
182-
const fee = BigInt(this.transaction._fee.feeRate) + txFee;
183-
this.transaction._fee.fee = fee.toString();
184-
this.transaction._fee.size = 1;
185-
151+
const fee = BigInt(this.transaction._fee.fee);
186152
const fromAddressBytes = this.transaction._fromAddresses[0];
187-
const fromAddress = new Address(fromAddressBytes);
188-
const assetId = utils.flareIdString(this.transaction._assetId);
189-
const amount = new BigIntPr(this._amount + fee);
190-
const nonce = new BigIntPr(this._nonce);
191-
const input = new evmSerial.Input(fromAddress, amount, assetId, nonce);
192-
// Map all destination P-chain addresses for multisig support
193-
// Sort addresses alphabetically by hex representation (required by Avalanche/Flare protocol)
194153
const sortedToAddresses = [...this.transaction._to].sort((a, b) => {
195154
const aHex = Buffer.from(a).toString('hex');
196155
const bHex = Buffer.from(b).toString('hex');
197156
return aHex.localeCompare(bHex);
198157
});
199158
const toAddresses = sortedToAddresses.map((addr) => new Address(addr));
200159

201-
const exportTx = new evmSerial.ExportTx(
202-
new Int(this.transaction._networkID),
203-
utils.flareIdString(this.transaction._blockchainID),
204-
new Id(new Uint8Array(this._externalChainId)),
205-
[input],
206-
[
207-
new TransferableOutput(
208-
assetId,
209-
new TransferOutput(
210-
new BigIntPr(this._amount),
211-
new OutputOwners(
212-
new BigIntPr(this.transaction._locktime),
213-
new Int(this.transaction._threshold),
214-
toAddresses
215-
)
216-
)
217-
),
218-
]
219-
);
220-
221-
// Create address maps with proper EVM address format
222-
const addressMap = new FlareUtils.AddressMap([
223-
[fromAddress, 0],
224-
[fromAddress, 1], // Map the same address to both indices since it's used in both places
225-
]);
226-
const addressMaps = new FlareUtils.AddressMaps([addressMap]); // Single map is sufficient
227-
228-
// Create unsigned transaction with proper address mapping
229-
const unsignedTx = new UnsignedTx(
230-
exportTx,
231-
[], // Empty UTXOs array, will be filled during processing
232-
addressMaps,
233-
[new Credential([utils.createNewSig('')])] // Empty credential for signing
160+
const exportEVMOptions: ExportEVMOptions = {
161+
threshold: this.transaction._threshold,
162+
locktime: this.transaction._locktime,
163+
};
164+
165+
const exportTx = evm.newExportTxFromBaseFee(
166+
this.transaction._context,
167+
fee / BigInt(1e9),
168+
this.transaction._amount,
169+
this.transaction._context.pBlockchainID,
170+
fromAddressBytes,
171+
toAddresses.map((addr) => Buffer.from(addr.toBytes())),
172+
BigInt(this._nonce),
173+
utils.flareIdString(this.transaction._assetId).toString(),
174+
exportEVMOptions
234175
);
235176

236-
this.transaction.setTransaction(unsignedTx);
177+
this.transaction.setTransaction(exportTx);
237178
}
238179

239180
/**

0 commit comments

Comments
 (0)