Skip to content

Commit c73a237

Browse files
committed
feat(sdk-coin-flrp): refactored and implemented export C to P builder with test cases\
Ticket: WIN-7770
1 parent 2959566 commit c73a237

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1777
-8316
lines changed

modules/sdk-coin-flrp/package.json

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,16 @@
4242
".ts"
4343
]
4444
},
45-
"devDependencies": {
46-
"@bitgo/sdk-api": "^1.71.7",
47-
"@bitgo/sdk-test": "^9.1.15"
48-
},
4945
"dependencies": {
5046
"@bitgo/sdk-core": "^36.22.0",
51-
"@bitgo/secp256k1": "^1.7.0",
5247
"@bitgo/statics": "^58.15.0",
53-
"@flarenetwork/flarejs": "4.1.0-rc0",
48+
"@bitgo/secp256k1": "^1.7.0",
49+
"@flarenetwork/flarejs": "4.1.1",
5450
"bech32": "^2.0.0",
5551
"bignumber.js": "9.0.0",
56-
"bs58": "^6.0.0"
52+
"bs58": "^6.0.0",
53+
"create-hash": "^1.2.0",
54+
"safe-buffer": "^5.2.1"
5755
},
5856
"gitHead": "18e460ddf02de2dbf13c2aa243478188fb539f0c",
5957
"files": [

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

Lines changed: 22 additions & 317 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,19 @@
1-
import { FlareNetwork, BaseCoin as StaticsBaseCoin, CoinFamily, coins } from '@bitgo/statics';
1+
import { BaseCoin as StaticsBaseCoin, CoinFamily } from '@bitgo/statics';
22
import {
3+
AuditDecryptedKeyParams,
34
BaseCoin,
45
BitGoBase,
56
KeyPair,
6-
VerifyAddressOptions,
7-
SignedTransaction,
8-
ParseTransactionOptions,
9-
BaseTransaction,
10-
InvalidTransactionError,
11-
SigningError,
12-
TransactionType,
13-
InvalidAddressError,
14-
UnexpectedAddressError,
15-
ITransactionRecipient,
16-
ParsedTransaction,
177
MultisigType,
188
multisigTypes,
19-
AuditDecryptedKeyParams,
20-
MethodNotImplementedError,
9+
ParsedTransaction,
10+
ParseTransactionOptions,
11+
SignedTransaction,
12+
SignTransactionOptions,
13+
TssVerifyAddressOptions,
14+
VerifyAddressOptions,
15+
VerifyTransactionOptions,
2116
} from '@bitgo/sdk-core';
22-
import * as FlrpLib from './lib';
23-
import {
24-
FlrpSignTransactionOptions,
25-
ExplainTransactionOptions,
26-
FlrpVerifyTransactionOptions,
27-
FlrpTransactionStakingOptions,
28-
FlrpTransactionParams,
29-
} from './lib/iface';
30-
import utils from './lib/utils';
31-
import BigNumber from 'bignumber.js';
3217

3318
export class Flrp extends BaseCoin {
3419
protected readonly _staticsCoin: Readonly<StaticsBaseCoin>;
@@ -65,308 +50,28 @@ export class Flrp extends BaseCoin {
6550
return multisigTypes.onchain;
6651
}
6752

68-
/**
69-
* Check if staking txn is valid, based on expected tx params.
70-
*
71-
* @param {FlrpTransactionStakingOptions} stakingOptions expected staking params to check against
72-
* @param {FlrpLib.TransactionExplanation} explainedTx explained staking transaction
73-
*/
74-
validateStakingTx(stakingOptions: FlrpTransactionStakingOptions, explainedTx: FlrpLib.TransactionExplanation): void {
75-
const filteredRecipients = [{ address: stakingOptions.nodeID, amount: stakingOptions.amount }];
76-
const filteredOutputs = explainedTx.outputs.map((output) => utils.pick(output, ['address', 'amount']));
77-
78-
if (!utils.isEqual(filteredOutputs, filteredRecipients)) {
79-
throw new Error('Tx outputs does not match with expected txParams');
80-
}
81-
if (stakingOptions?.amount !== explainedTx.outputAmount) {
82-
throw new Error('Tx total amount does not match with expected total amount field');
83-
}
84-
}
85-
86-
/**
87-
* Check if export txn is valid, based on expected tx params.
88-
*
89-
* @param {ITransactionRecipient[]} recipients expected recipients and info
90-
* @param {FlrpLib.TransactionExplanation} explainedTx explained export transaction
91-
*/
92-
validateExportTx(recipients: ITransactionRecipient[], explainedTx: FlrpLib.TransactionExplanation): void {
93-
if (recipients.length !== 1 || explainedTx.outputs.length !== 1) {
94-
throw new Error('Export Tx requires one recipient');
95-
}
96-
97-
const maxImportFee = (this._staticsCoin.network as FlareNetwork).maxImportFee || '0';
98-
const recipientAmount = new BigNumber(recipients[0].amount);
99-
if (
100-
recipientAmount.isGreaterThan(explainedTx.outputAmount) ||
101-
recipientAmount.plus(maxImportFee).isLessThan(explainedTx.outputAmount)
102-
) {
103-
throw new Error(
104-
`Tx total amount ${explainedTx.outputAmount} does not match with expected total amount field ${recipientAmount} and max import fee ${maxImportFee}`
105-
);
106-
}
107-
108-
if (explainedTx.outputs && !utils.isValidAddress(explainedTx.outputs[0].address)) {
109-
throw new Error(`Invalid P-chain address ${explainedTx.outputs[0].address}`);
110-
}
53+
verifyTransaction(params: VerifyTransactionOptions): Promise<boolean> {
54+
throw new Error('Method not implemented.');
11155
}
112-
113-
/**
114-
* Check if import txn into P is valid, based on expected tx params.
115-
*
116-
* @param {FlrpLib.FlrpEntry[]} explainedTxInputs tx inputs (unspents to be imported)
117-
* @param {FlrpTransactionParams} txParams expected tx info to check against
118-
*/
119-
validateImportTx(explainedTxInputs: FlrpLib.FlrpEntry[], txParams: FlrpTransactionParams): void {
120-
if (txParams.unspents) {
121-
if (explainedTxInputs.length !== txParams.unspents.length) {
122-
throw new Error(`Expected ${txParams.unspents.length} UTXOs, transaction had ${explainedTxInputs.length}`);
123-
}
124-
125-
const unspents = new Set(txParams.unspents);
126-
127-
for (const unspent of explainedTxInputs) {
128-
if (!unspents.has(unspent.id)) {
129-
throw new Error(`Transaction should not contain the UTXO: ${unspent.id}`);
130-
}
131-
}
132-
}
56+
isWalletAddress(params: VerifyAddressOptions | TssVerifyAddressOptions): Promise<boolean> {
57+
throw new Error('Method not implemented.');
13358
}
134-
135-
async verifyTransaction(params: FlrpVerifyTransactionOptions): Promise<boolean> {
136-
const txHex = params.txPrebuild && params.txPrebuild.txHex;
137-
if (!txHex) {
138-
throw new Error('missing required tx prebuild property txHex');
139-
}
140-
let tx;
141-
try {
142-
const txBuilder = this.getBuilder().from(txHex);
143-
tx = await txBuilder.build();
144-
} catch (error) {
145-
throw new Error('Invalid transaction');
146-
}
147-
const explainedTx = tx.explainTransaction();
148-
149-
const { type, stakingOptions } = params.txParams;
150-
// TODO(BG-62112): change ImportToC type to Import
151-
if (!type || (type !== 'ImportToC' && explainedTx.type !== TransactionType[type])) {
152-
throw new Error('Tx type does not match with expected txParams type');
153-
}
154-
155-
switch (explainedTx.type) {
156-
// @deprecated
157-
case TransactionType.AddDelegator:
158-
case TransactionType.AddValidator:
159-
case TransactionType.AddPermissionlessDelegator:
160-
case TransactionType.AddPermissionlessValidator:
161-
if (stakingOptions) {
162-
this.validateStakingTx(stakingOptions, explainedTx);
163-
}
164-
break;
165-
case TransactionType.Export:
166-
if (!params.txParams.recipients || params.txParams.recipients?.length !== 1) {
167-
throw new Error('Export Tx requires a recipient');
168-
} else {
169-
this.validateExportTx(params.txParams.recipients, explainedTx);
170-
}
171-
break;
172-
case TransactionType.Import:
173-
if (tx.isTransactionForCChain) {
174-
// Import to C-chain
175-
if (explainedTx.outputs.length !== 1) {
176-
throw new Error('Expected 1 output in import transaction');
177-
}
178-
if (!params.txParams.recipients || params.txParams.recipients.length !== 1) {
179-
throw new Error('Expected 1 recipient in import transaction');
180-
}
181-
} else {
182-
// Import to P-chain
183-
if (explainedTx.outputs.length !== 1) {
184-
throw new Error('Expected 1 output in import transaction');
185-
}
186-
this.validateImportTx(explainedTx.inputs, params.txParams);
187-
}
188-
break;
189-
default:
190-
throw new Error('Tx type is not supported yet');
191-
}
192-
return true;
59+
parseTransaction(params: ParseTransactionOptions): Promise<ParsedTransaction> {
60+
throw new Error('Method not implemented.');
19361
}
194-
195-
/**
196-
* Check if address is valid, then make sure it matches the root address.
197-
*
198-
* @param params.address address to validate
199-
* @param params.keychains public keys to generate the wallet
200-
*/
201-
async isWalletAddress(params: VerifyAddressOptions): Promise<boolean> {
202-
const { address, keychains } = params;
203-
204-
if (!this.isValidAddress(address)) {
205-
throw new InvalidAddressError(`invalid address: ${address}`);
206-
}
207-
if (!keychains || keychains.length !== 3) {
208-
throw new Error('Invalid keychains');
209-
}
210-
211-
// multisig addresses are separated by ~
212-
const splitAddresses = address.split('~');
213-
214-
// derive addresses from keychain
215-
const unlockAddresses = keychains.map((keychain) =>
216-
new FlrpLib.KeyPair({ pub: keychain.pub }).getAddress(this._staticsCoin.network.type)
217-
);
218-
219-
if (splitAddresses.length !== unlockAddresses.length) {
220-
throw new UnexpectedAddressError(`address validation failure: multisig address length does not match`);
221-
}
222-
223-
if (!this.adressesArraysMatch(splitAddresses, unlockAddresses)) {
224-
throw new UnexpectedAddressError(`address validation failure: ${address} is not of this wallet`);
225-
}
226-
227-
return true;
228-
}
229-
230-
/**
231-
* Validate that two multisig address arrays have the same elements, order doesnt matter
232-
* @param addressArray1
233-
* @param addressArray2
234-
* @returns true if address arrays have the same addresses
235-
* @private
236-
*/
237-
private adressesArraysMatch(addressArray1: string[], addressArray2: string[]) {
238-
return JSON.stringify(addressArray1.sort()) === JSON.stringify(addressArray2.sort());
239-
}
240-
241-
/**
242-
* Generate Flrp key pair
243-
*
244-
* @param {Buffer} seed - Seed from which the new keypair should be generated, otherwise a random seed is used
245-
* @returns {Object} object with generated pub and prv
246-
*/
24762
generateKeyPair(seed?: Buffer): KeyPair {
248-
const keyPair = seed ? new FlrpLib.KeyPair({ seed }) : new FlrpLib.KeyPair();
249-
const keys = keyPair.getKeys();
250-
251-
if (!keys.prv) {
252-
throw new Error('Missing prv in key generation.');
253-
}
254-
255-
return {
256-
pub: keys.pub,
257-
prv: keys.prv,
258-
};
63+
throw new Error('Method not implemented.');
25964
}
260-
261-
/**
262-
* Return boolean indicating whether input is valid public key for the coin
263-
*
264-
* @param {string} pub the prv to be checked
265-
* @returns is it valid?
266-
*/
26765
isValidPub(pub: string): boolean {
268-
try {
269-
new FlrpLib.KeyPair({ pub });
270-
return true;
271-
} catch (e) {
272-
return false;
273-
}
66+
throw new Error('Method not implemented.');
27467
}
275-
276-
/**
277-
* Return boolean indicating whether input is valid private key for the coin
278-
*
279-
* @param {string} prv the prv to be checked
280-
* @returns is it valid?
281-
*/
282-
isValidPrv(prv: string): boolean {
283-
try {
284-
new FlrpLib.KeyPair({ prv });
285-
return true;
286-
} catch (e) {
287-
return false;
288-
}
68+
isValidAddress(address: string): boolean {
69+
throw new Error('Method not implemented.');
28970
}
290-
291-
isValidAddress(address: string | string[]): boolean {
292-
if (address === undefined) {
293-
return false;
294-
}
295-
296-
// validate eth address for cross-chain txs to c-chain
297-
if (typeof address === 'string' && utils.isValidEthereumAddress(address)) {
298-
return true;
299-
}
300-
301-
return FlrpLib.Utils.isValidAddress(address);
71+
signTransaction(params: SignTransactionOptions): Promise<SignedTransaction> {
72+
throw new Error('Method not implemented.');
30273
}
303-
304-
/**
305-
* Signs Flrp transaction
306-
*/
307-
async signTransaction(params: FlrpSignTransactionOptions): Promise<SignedTransaction> {
308-
// deserialize raw transaction (note: fromAddress has onchain order)
309-
const txBuilder = this.getBuilder().from(params.txPrebuild.txHex);
310-
const key = params.prv;
311-
312-
// push the keypair to signer array
313-
txBuilder.sign({ key });
314-
315-
// build the transaction
316-
const transaction: BaseTransaction = await txBuilder.build();
317-
if (!transaction) {
318-
throw new InvalidTransactionError('Error while trying to build transaction');
319-
}
320-
return transaction.signature.length >= 2
321-
? { txHex: transaction.toBroadcastFormat() }
322-
: { halfSigned: { txHex: transaction.toBroadcastFormat() } };
323-
}
324-
325-
async parseTransaction(params: ParseTransactionOptions): Promise<ParsedTransaction> {
326-
return {};
327-
}
328-
329-
/**
330-
* Explain a Flrp transaction from txHex
331-
* @param params
332-
* @param callback
333-
*/
334-
async explainTransaction(params: ExplainTransactionOptions): Promise<FlrpLib.TransactionExplanation> {
335-
const txHex = params.txHex ?? params?.halfSigned?.txHex;
336-
if (!txHex) {
337-
throw new Error('missing transaction hex');
338-
}
339-
try {
340-
const txBuilder = this.getBuilder().from(txHex);
341-
const tx = await txBuilder.build();
342-
return tx.explainTransaction();
343-
} catch (e) {
344-
throw new Error(`Invalid transaction: ${e.message}`);
345-
}
346-
}
347-
348-
recoverySignature(message: Buffer, signature: Buffer): Buffer {
349-
return FlrpLib.Utils.recoverySignature(this._staticsCoin.network as FlareNetwork, message, signature);
350-
}
351-
352-
async signMessage(key: KeyPair, message: string | Buffer): Promise<Buffer> {
353-
const prv = new FlrpLib.KeyPair(key).getPrivateKey();
354-
if (!prv) {
355-
throw new SigningError('Invalid key pair options');
356-
}
357-
if (typeof message === 'string') {
358-
message = Buffer.from(message, 'hex');
359-
}
360-
return FlrpLib.Utils.createSignature(this._staticsCoin.network as FlareNetwork, message, prv);
361-
}
362-
363-
private getBuilder(): FlrpLib.TransactionBuilderFactory {
364-
return new FlrpLib.TransactionBuilderFactory(coins.get(this.getChain()));
365-
}
366-
367-
/** @inheritDoc */
36874
auditDecryptedKey(params: AuditDecryptedKeyParams): void {
369-
/** https://bitgoinc.atlassian.net/browse/COIN-4213 */
370-
throw new MethodNotImplementedError();
75+
throw new Error('Method not implemented.');
37176
}
37277
}

0 commit comments

Comments
 (0)