Skip to content

Commit 68f2143

Browse files
committed
refactor(sdk-coin-flrp): update flrp txn builders to use etna support
Ticket: WIN-8498
1 parent f6a77e9 commit 68f2143

28 files changed

+1590
-2205
lines changed

modules/sdk-coin-flr/test/unit/flr.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -606,9 +606,9 @@ describe('flr', function () {
606606
const hopDestinationAddress =
607607
'P-costwo15msvr27szvhhpmah0c38gcml7vm29xjh7tcek8~P-costwo1cwrdtrgf4xh80ncu7palrjw7gn4mpj0n4dxghh~P-costwo1zt9n96hey4fsvnde35n3k4kt5pu7c784dzewzd';
608608
const hopAddress = '0x28A05933dC76e4e6c25f35D5c9b2A58769700E76';
609-
const importTxFee = 1261000; // Updated to match FlarePTestnet txFee
610-
// Adjusted amount to work backwards from hop amount (50000000): 50000000 - 1261000 = 48739000 nanoFLR
611-
const amount = 48739000000000000;
609+
const importTxFee = 200000; // Match FlarePTestnet txFee from networks.ts
610+
// Adjusted amount to work backwards from hop amount (50000000): 50000000 - 200000 = 49800000 nanoFLR
611+
const amount = 49800000000000000;
612612
const txParams = {
613613
recipients: [{ amount, address: hopDestinationAddress }],
614614
wallet: wallet,
@@ -826,7 +826,7 @@ describe('flr', function () {
826826
recipients: [
827827
{
828828
address: 'P-costwo1different~P-costwo1address~P-costwo1here',
829-
amount: '48739000000000000',
829+
amount: '49800000000000000', // 50000000 - 200000 (txFee) = 49800000 nanoFLR = 49800000000000000 wei
830830
},
831831
],
832832
};

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.61.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: 35 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -5,47 +5,29 @@ 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, DecodedUtxoObj } 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+
* @throws {BuildTransactionError} always throws as UTXOs are not applicable
3328
*/
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;
29+
decodedUtxos(_decodedUtxos: DecodedUtxoObj[]): this {
30+
throw new BuildTransactionError('UTXOs are not required for Export Tx from C-Chain');
4931
}
5032

5133
/**
@@ -81,8 +63,6 @@ export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
8163
throw new NotSupported('Transaction cannot be parsed or has an unsupported transaction type');
8264
}
8365

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.
8666
const outputs = baseTx.exportedOutputs;
8767
if (outputs.length !== 1) {
8868
throw new BuildTransactionError('Transaction can have one output');
@@ -93,8 +73,6 @@ export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
9373
throw new BuildTransactionError('AssetID mismatch');
9474
}
9575

96-
// The inputs is not an utxo.
97-
// It's expected to have only one input from C-Chain address.
9876
const inputs = baseTx.ins;
9977
if (inputs.length !== 1) {
10078
throw new BuildTransactionError('Transaction can have one input');
@@ -107,27 +85,17 @@ export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
10785
const inputAmount = input.amount.value();
10886
const outputAmount = transferOutput.amount();
10987
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);
88+
this.transaction._amount = outputAmount;
11489
this.transaction._fee.fee = fee.toString();
115-
this.transaction._fee.size = 1;
11690
this.transaction._fromAddresses = [Buffer.from(input.address.toBytes())];
11791
this.transaction._locktime = transferOutput.getLocktime();
118-
11992
this._nonce = input.nonce.value();
120-
121-
// Use credentials passed from TransactionBuilderFactory (properly extracted using codec)
12293
const credentials = parsedCredentials || [];
12394
const hasCredentials = credentials.length > 0;
124-
125-
// If it's a signed transaction, store the original raw bytes to preserve exact format
12695
if (hasCredentials && rawBytes) {
12796
this.transaction._rawSignedBytes = rawBytes;
12897
}
12998

130-
// Create proper UnsignedTx wrapper with credentials
13199
const fromAddress = new Address(this.transaction._fromAddresses[0]);
132100
const addressMap = new FlareUtils.AddressMap([
133101
[fromAddress, 0],
@@ -160,80 +128,52 @@ export class ExportInCTxBuilder extends AtomicInCTransactionBuilder {
160128
*/
161129
protected buildFlareTransaction(): void {
162130
if (this.transaction.hasCredentials) return;
163-
if (this._amount === undefined) {
164-
throw new Error('amount is required');
131+
if (this.transaction._amount === undefined) {
132+
throw new BuildTransactionError('amount is required');
165133
}
166134
if (this.transaction._fromAddresses.length !== 1) {
167-
throw new Error('sender is one and required');
135+
throw new BuildTransactionError('sender is one and required');
168136
}
169137
if (this.transaction._to.length === 0) {
170-
throw new Error('to is required');
138+
throw new BuildTransactionError('to is required');
171139
}
172-
if (!this.transaction._fee.feeRate) {
173-
throw new Error('fee rate is required');
140+
if (!this.transaction._fee.fee) {
141+
throw new BuildTransactionError('fee rate is required');
174142
}
175143
if (this._nonce === undefined) {
176-
throw new Error('nonce is required');
144+
throw new BuildTransactionError('nonce is required');
145+
}
146+
if (!this.transaction._context) {
147+
throw new BuildTransactionError('context is required');
177148
}
178149

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-
150+
const fee = BigInt(this.transaction._fee.fee);
186151
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)
194152
const sortedToAddresses = [...this.transaction._to].sort((a, b) => {
195153
const aHex = Buffer.from(a).toString('hex');
196154
const bHex = Buffer.from(b).toString('hex');
197155
return aHex.localeCompare(bHex);
198156
});
199157
const toAddresses = sortedToAddresses.map((addr) => new Address(addr));
200158

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
159+
const exportEVMOptions: ExportEVMOptions = {
160+
threshold: this.transaction._threshold,
161+
locktime: this.transaction._locktime,
162+
};
163+
164+
const exportTx = evm.newExportTxFromBaseFee(
165+
this.transaction._context,
166+
fee,
167+
this.transaction._amount,
168+
this.transaction._context.pBlockchainID,
169+
fromAddressBytes,
170+
toAddresses.map((addr) => Buffer.from(addr.toBytes())),
171+
BigInt(this._nonce),
172+
utils.flareIdString(this.transaction._assetId).toString(),
173+
exportEVMOptions
234174
);
235175

236-
this.transaction.setTransaction(unsignedTx);
176+
this.transaction.setTransaction(exportTx);
237177
}
238178

239179
/**

0 commit comments

Comments
 (0)