|
1 | | -import { FlareNetwork, BaseCoin as StaticsBaseCoin, CoinFamily, coins } from '@bitgo/statics'; |
| 1 | +import { BaseCoin as StaticsBaseCoin, CoinFamily } from '@bitgo/statics'; |
2 | 2 | import { |
| 3 | + AuditDecryptedKeyParams, |
3 | 4 | BaseCoin, |
4 | 5 | BitGoBase, |
5 | 6 | KeyPair, |
6 | | - VerifyAddressOptions, |
7 | | - SignedTransaction, |
8 | | - ParseTransactionOptions, |
9 | | - BaseTransaction, |
10 | | - InvalidTransactionError, |
11 | | - SigningError, |
12 | | - TransactionType, |
13 | | - InvalidAddressError, |
14 | | - UnexpectedAddressError, |
15 | | - ITransactionRecipient, |
16 | | - ParsedTransaction, |
17 | 7 | MultisigType, |
18 | 8 | multisigTypes, |
19 | | - AuditDecryptedKeyParams, |
20 | | - MethodNotImplementedError, |
| 9 | + ParsedTransaction, |
| 10 | + ParseTransactionOptions, |
| 11 | + SignedTransaction, |
| 12 | + SignTransactionOptions, |
| 13 | + TssVerifyAddressOptions, |
| 14 | + VerifyAddressOptions, |
| 15 | + VerifyTransactionOptions, |
21 | 16 | } 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'; |
32 | 17 |
|
33 | 18 | export class Flrp extends BaseCoin { |
34 | 19 | protected readonly _staticsCoin: Readonly<StaticsBaseCoin>; |
@@ -65,308 +50,28 @@ export class Flrp extends BaseCoin { |
65 | 50 | return multisigTypes.onchain; |
66 | 51 | } |
67 | 52 |
|
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.'); |
111 | 55 | } |
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.'); |
133 | 58 | } |
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.'); |
193 | 61 | } |
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 | | - */ |
247 | 62 | 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.'); |
259 | 64 | } |
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 | | - */ |
267 | 65 | 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.'); |
274 | 67 | } |
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.'); |
289 | 70 | } |
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.'); |
302 | 73 | } |
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 */ |
368 | 74 | auditDecryptedKey(params: AuditDecryptedKeyParams): void { |
369 | | - /** https://bitgoinc.atlassian.net/browse/COIN-4213 */ |
370 | | - throw new MethodNotImplementedError(); |
| 75 | + throw new Error('Method not implemented.'); |
371 | 76 | } |
372 | 77 | } |
0 commit comments