UNPKG

15.8 kBPlain TextView Raw
1/**
2 * @prettier
3 */
4import * as crypto from 'crypto';
5import * as bip32 from 'bip32';
6import { BigNumber } from 'bignumber.js';
7import { BaseCoin as AccountLibBasecoin } from '@bitgo/account-lib';
8import * as utxolib from '@bitgo/utxo-lib';
9
10import { BitGo } from '../bitgo';
11import { RequestTracer } from './internal/util';
12import { Wallet, WalletData } from './wallet';
13import { Wallets } from './wallets';
14import { Markets } from './markets';
15import { Webhooks } from './webhooks';
16import { PendingApprovals } from './pendingApprovals';
17import { Keychain, Keychains, KeyIndices } from './keychains';
18import { Enterprises } from './enterprises';
19
20import { InitiateRecoveryOptions } from './recovery/initiate';
21import { signMessage } from '../bip32util';
22import { TssUtils } from './internal/tssUtils';
23
24// re-export account lib transaction types
25export type TransactionType = AccountLibBasecoin.TransactionType;
26
27export interface TransactionRecipient {
28 address: string;
29 amount: string | number;
30 memo?: string;
31}
32
33export interface TransactionFee<TAmount = string> {
34 fee: TAmount;
35 feeRate?: number;
36 size?: number;
37}
38
39export interface TransactionExplanation<TFee = any, TAmount = any> {
40 displayOrder: string[];
41 id: string;
42 outputs: TransactionRecipient[];
43 outputAmount: TAmount;
44 changeOutputs: TransactionRecipient[];
45 changeAmount: TAmount;
46 fee: TFee;
47 proxy?: string;
48 producers?: string[];
49}
50
51export interface KeyPair {
52 pub?: string;
53 prv: string;
54}
55
56export interface BlsKeyPair extends KeyPair {
57 secretShares?: string[];
58}
59
60export interface VerifyAddressOptions {
61 address: string;
62 addressType?: string;
63 keychains?: {
64 pub: string;
65 }[];
66 error?: string;
67 coinSpecific?: AddressCoinSpecific;
68 impliedForwarderVersion?: number;
69}
70
71export interface TransactionParams {
72 recipients?: TransactionRecipient[];
73 walletPassphrase?: string;
74 type?: string;
75}
76
77export interface AddressVerificationData {
78 coinSpecific?: AddressCoinSpecific;
79 chain?: number;
80 index?: number;
81}
82
83export interface VerificationOptions {
84 disableNetworking?: boolean;
85 keychains?: {
86 user?: Keychain;
87 backup?: Keychain;
88 bitgo?: Keychain;
89 };
90 addresses?: { [address: string]: AddressVerificationData };
91 allowPaygoOutput?: boolean;
92 considerMigratedFromAddressInternal?: boolean;
93}
94
95export interface VerifyTransactionOptions {
96 txPrebuild: TransactionPrebuild;
97 txParams: TransactionParams;
98 wallet: Wallet;
99 verification?: VerificationOptions;
100 reqId?: RequestTracer;
101}
102
103export interface SupplementGenerateWalletOptions {
104 label: string;
105 m: number;
106 n: number;
107 enterprise?: string;
108 disableTransactionNotifications?: boolean;
109 gasPrice?: number | string;
110 eip1559?: {
111 maxFeePerGas: number | string;
112 maxPriorityFeePerGas?: number | string;
113 };
114 walletVersion?: number;
115 keys: string[];
116 isCold: boolean;
117 keySignatures?: {
118 backup: string;
119 bitgo: string;
120 };
121 rootPrivateKey?: string;
122 disableKRSEmail?: boolean;
123 multisigType?: 'tss' | 'onchain' | 'blsdkg';
124}
125
126export interface FeeEstimateOptions {
127 numBlocks?: number;
128 hop?: boolean;
129 recipient?: string;
130 data?: string;
131 amount?: string;
132}
133
134// TODO (SDKT-9): reverse engineer and add options
135export interface ExtraPrebuildParamsOptions {
136 [index: string]: unknown;
137}
138
139// TODO (SDKT-9): reverse engineer and add options
140export interface PresignTransactionOptions {
141 txPrebuild?: TransactionPrebuild;
142 walletData: WalletData;
143 tssUtils: TssUtils;
144 [index: string]: unknown;
145}
146
147// TODO (SDKT-9): reverse engineer and add options
148export interface PrecreateBitGoOptions {
149 [index: string]: unknown;
150}
151
152// TODO (SDKT-9): reverse engineer and add options
153export interface VerifyRecoveryTransactionOptions {
154 [index: string]: unknown;
155}
156
157// TODO (SDKT-9): reverse engineer and add options
158export interface ParseTransactionOptions {
159 [index: string]: unknown;
160}
161
162// TODO (SDKT-9): reverse engineer and add options
163export interface ParsedTransaction {
164 [index: string]: unknown;
165}
166
167// TODO (SDKT-9): reverse engineer and add options
168export interface SignTransactionOptions {
169 [index: string]: unknown;
170}
171
172export interface KeychainsTriplet {
173 userKeychain: Keychain;
174 backupKeychain: Keychain;
175 bitgoKeychain: Keychain;
176}
177
178export interface TransactionPrebuild {
179 txBase64?: string;
180 txHex?: string;
181 txInfo?: unknown;
182 wallet?: Wallet;
183 buildParams?: any;
184 consolidateId?: string;
185 txRequestId?: string;
186}
187
188export interface AddressCoinSpecific {
189 outputScript?: string;
190 redeemScript?: string;
191 witnessScript?: string;
192 baseAddress?: string;
193 pendingChainInitialization?: boolean;
194 forwarderVersion?: number;
195}
196
197export interface FullySignedTransaction {
198 txHex: string; // Transaction in any format required by each coin, i.e. in Tron it is a stringifyed JSON
199}
200
201export interface HalfSignedUtxoTransaction {
202 txHex: string;
203}
204
205export interface HalfSignedAccountTransaction {
206 halfSigned?: {
207 txHex?: string; // Transaction in any format required by each coin, i.e. in Tron it is a stringifyed JSON
208 payload?: string;
209 txBase64?: string;
210 };
211}
212
213export interface SignedTransactionRequest {
214 txRequestId: string;
215}
216
217export type SignedTransaction =
218 | HalfSignedAccountTransaction
219 | HalfSignedUtxoTransaction
220 | FullySignedTransaction
221 | SignedTransactionRequest;
222
223export abstract class BaseCoin {
224 protected readonly bitgo: BitGo;
225 protected readonly _url: string;
226 protected readonly _enterprises: Enterprises;
227 protected readonly _wallets: Wallets;
228 protected readonly _keychains: Keychains;
229 protected readonly _webhooks: Webhooks;
230 protected readonly _pendingApprovals: PendingApprovals;
231 protected readonly _markets: Markets;
232 protected static readonly _coinTokenPatternSeparator = ':';
233
234 protected constructor(bitgo: BitGo) {
235 this.bitgo = bitgo;
236 this._url = this.bitgo.url('/', 2);
237 this._wallets = new Wallets(this.bitgo, this);
238 this._keychains = new Keychains(this.bitgo, this);
239 this._webhooks = new Webhooks(this.bitgo, this);
240 this._pendingApprovals = new PendingApprovals(this.bitgo, this);
241 this._enterprises = new Enterprises(this.bitgo, this);
242 this._markets = new Markets(this.bitgo, this);
243 }
244
245 public url(suffix: string): string {
246 return this._url + this.getChain() + suffix;
247 }
248
249 public wallets(): Wallets {
250 return this._wallets;
251 }
252
253 public enterprises(): Enterprises {
254 return this._enterprises;
255 }
256
257 public keychains(): Keychains {
258 return this._keychains;
259 }
260
261 public webhooks(): Webhooks {
262 return this._webhooks;
263 }
264
265 public pendingApprovals(): PendingApprovals {
266 return this._pendingApprovals;
267 }
268
269 public markets(): Markets {
270 return this._markets;
271 }
272
273 public static get coinTokenPatternSeparator(): string {
274 return this._coinTokenPatternSeparator;
275 }
276
277 public get type(): string {
278 return this.getChain();
279 }
280
281 /**
282 * Name of the chain which supports this coin (eg, 'btc', 'eth')
283 */
284 abstract getChain(): string;
285
286 /**
287 * Name of the coin family (eg. for tbtc, this would be btc)
288 */
289 abstract getFamily(): string;
290
291 /**
292 * Human readable full name for the coin
293 */
294 abstract getFullName(): string;
295
296 /**
297 * Flag for sending value of 0.
298 * @returns {boolean} True if okay to send 0 value, false otherwise
299 */
300 valuelessTransferAllowed(): boolean {
301 return false;
302 }
303
304 /**
305 * Use `sendMany()` to perform wallet sweep.
306 * FIXME(BG-39738): add coin.sweepWallet() instead
307 */
308 sweepWithSendMany(): boolean {
309 return false;
310 }
311
312 /**
313 * Flag for sending data along with transactions
314 * @returns {boolean} True if okay to send tx data (ETH), false otherwise
315 */
316 transactionDataAllowed(): boolean {
317 return false;
318 }
319
320 /**
321 * Flag for determining whether this coin supports account consolidations
322 * from its receive addresses to the root address.
323 * @returns {boolean} True if okay to consolidate over this coin; false, otherwise
324 */
325 allowsAccountConsolidations(): boolean {
326 return false;
327 }
328
329 /**
330 * Flag indicating if this coin supports TSS wallets.
331 * @returns {boolean} True if TSS Wallets can be created for this coin
332 */
333 supportsTss(): boolean {
334 return false;
335 }
336
337 /**
338 * Flag indicating if this coin supports BLS-DKG wallets.
339 * @returns {boolean} True if BLS-DKG Wallets can be created for this coin
340 */
341 supportsBlsDkg(): boolean {
342 return false;
343 }
344
345 /**
346 * Returns the factor between the base unit and its smallest subdivison
347 * @return {number}
348 */
349 abstract getBaseFactor(): number | string;
350
351 /**
352 * Convert a currency amount represented in base units (satoshi, wei, atoms, drops, stroops)
353 * to big units (btc, eth, xrp, xlm)
354 */
355 baseUnitsToBigUnits(baseUnits: string | number): string {
356 const dividend = this.getBaseFactor();
357 const bigNumber = new BigNumber(baseUnits).dividedBy(dividend);
358 // set the format so commas aren't added to large coin amounts
359 return bigNumber.toFormat(null as any, null as any, { groupSeparator: '', decimalSeparator: '.' });
360 }
361
362 /**
363 * Convert a currency amount represented in big units (btc, eth, xrp, xlm)
364 * to base units (satoshi, wei, atoms, drops, stroops)
365 * @param bigUnits
366 */
367 bigUnitsToBaseUnits(bigUnits: string | number): string {
368 const multiplier = this.getBaseFactor();
369 const bigNumber = new BigNumber(bigUnits).times(multiplier);
370 if (!bigNumber.isInteger()) {
371 throw new Error(`non-integer output resulted from multiplying ${bigUnits} by ${multiplier}`);
372 }
373 return bigNumber.toFixed(0);
374 }
375
376 /**
377 * Sign message with private key
378 *
379 * @param key
380 * @param message
381 */
382 async signMessage(key: { prv: string }, message: string): Promise<Buffer> {
383 return signMessage(message, bip32.fromBase58(key.prv), utxolib.networks.bitcoin);
384 }
385
386 /**
387 * Decompose a raw transaction into useful information.
388 * @param options - coin-specific
389 */
390 explainTransaction(options: Record<string, any>): Promise<TransactionExplanation<any, string | number> | undefined> {
391 throw new Error(`not implemented`);
392 }
393
394 /**
395 * Verify that a transaction prebuild complies with the original intention
396 */
397 abstract verifyTransaction(params: VerifyTransactionOptions): Promise<boolean>;
398
399 /**
400 * @deprecated use {@see isWalletAddress} instead
401 */
402 verifyAddress(params: VerifyAddressOptions): boolean {
403 return this.isWalletAddress(params);
404 }
405
406 /**
407 * @param params
408 * @return true iff address is a wallet address. Must return false if address is outside wallet.
409 */
410 abstract isWalletAddress(params: VerifyAddressOptions): boolean;
411
412 /**
413 * convert address into desired address format.
414 * @param address
415 * @param format
416 */
417 canonicalAddress(address: string, format?: unknown): string {
418 return address;
419 }
420
421 /**
422 * Check whether a coin supports blockTarget for transactions to be included in
423 * @returns {boolean}
424 */
425 supportsBlockTarget() {
426 return false;
427 }
428
429 /**
430 * Hook to add additional parameters to the wallet generation
431 * @param walletParams
432 * @param keychains
433 * @return {*}
434 */
435 supplementGenerateWallet(walletParams: SupplementGenerateWalletOptions, keychains: KeychainsTriplet): Promise<any> {
436 return Promise.resolve(walletParams);
437 }
438
439 /**
440 * Get extra parameters for prebuilding a tx. Add things like hop transaction params
441 */
442 getExtraPrebuildParams(buildParams: ExtraPrebuildParamsOptions): Promise<Record<string, unknown>> {
443 return Promise.resolve({});
444 }
445
446 /**
447 * Modify prebuild after receiving it from the server. Add things like nlocktime
448 */
449 postProcessPrebuild(prebuildResponse: TransactionPrebuild): Promise<TransactionPrebuild> {
450 return Promise.resolve(prebuildResponse);
451 }
452
453 /**
454 * Coin-specific things done before signing a transaction, i.e. verification
455 */
456 presignTransaction(params: PresignTransactionOptions): Promise<PresignTransactionOptions> {
457 return Promise.resolve(params);
458 }
459
460 /**
461 * Create a new wallet object from a wallet data object
462 * @param walletParams
463 */
464 newWalletObject(walletParams: any): Wallet {
465 return new Wallet(this.bitgo, this, walletParams);
466 }
467
468 /**
469 * Fetch fee estimate information from the server
470 * @param {Object} params The params passed into the function
471 * @param {Integer} params.numBlocks The number of blocks to target for conformation (Only works for btc)
472 * @returns {Object} The info returned from the merchant server
473 */
474 async feeEstimate(params: FeeEstimateOptions): Promise<any> {
475 const query: any = {};
476 if (params && params.numBlocks) {
477 query.numBlocks = params.numBlocks;
478 }
479
480 return this.bitgo.get(this.url('/tx/fee')).query(query).result();
481 }
482
483 /**
484 * The cold wallet tool uses this function to derive an extended key that is based on the passed key and seed
485 * @param key
486 * @param seed
487 * @returns {{key: string, derivationPath: string}}
488 */
489 deriveKeyWithSeed({ key, seed }: { key: string; seed: string }): { key: string; derivationPath: string } {
490 function sha256(input) {
491 return crypto.createHash('sha256').update(input).digest();
492 }
493 const derivationPathInput = sha256(sha256(`${seed}`)).toString('hex');
494 const derivationPathParts = [
495 parseInt(derivationPathInput.slice(0, 7), 16),
496 parseInt(derivationPathInput.slice(7, 14), 16),
497 ];
498 const derivationPath = 'm/999999/' + derivationPathParts.join('/');
499 const keyNode = bip32.fromBase58(key);
500 const derivedKeyNode = keyNode.derivePath(derivationPath);
501 return {
502 key: derivedKeyNode.toBase58(),
503 derivationPath: derivationPath,
504 };
505 }
506
507 /**
508 * Specifies what key we will need for signing - right now we just need the
509 * user key.
510 */
511 keyIdsForSigning(): number[] {
512 return [KeyIndices.USER];
513 }
514
515 /**
516 * Perform additional checks before adding a bitgo key. Base controller
517 * is a no-op, but coin-specific controller may do something
518 * @param params
519 */
520 preCreateBitGo(params: PrecreateBitGoOptions): void {
521 return;
522 }
523
524 /**
525 * @deprecated - use getBip32Keys() in conjunction with isValidAddress instead
526 */
527 initiateRecovery(params: InitiateRecoveryOptions): never {
528 throw new Error('deprecated method');
529 }
530
531 abstract parseTransaction(params: ParseTransactionOptions): Promise<ParsedTransaction>;
532
533 /**
534 * Generate a key pair on the curve used by the coin
535 *
536 * @param seed
537 */
538 abstract generateKeyPair(seed?: Buffer): KeyPair;
539
540 /**
541 * Return boolean indicating whether input is valid public key for the coin.
542 *
543 * @param {String} pub the pub to be checked
544 * @returns {Boolean} is it valid?
545 */
546 abstract isValidPub(pub: string): boolean;
547
548 /**
549 * Return wether the given m of n wallet signers/ key amounts are valid for the coin
550 */
551 isValidMofNSetup({ m, n }: { m?: number; n?: number }): boolean {
552 return m === 2 && n === 3;
553 }
554
555 /**
556 * Check if `address` is a plausibly valid address for the given coin.
557 *
558 * Does not verify that the address belongs to a wallet. For that,
559 * use [[verifyAddress]]
560 * @param address
561 */
562 abstract isValidAddress(address: string): boolean;
563
564 /**
565 * Sign a transaction
566 */
567 abstract signTransaction(params: SignTransactionOptions): Promise<SignedTransaction>;
568
569 /**
570 * Returns the portion of the transaction that needs to be signed in Buffer format.
571 * Only needed for coins that support adding signatures directly (e.g. TSS).
572 *
573 * @param {String} serializedTx - the unsigned transaction in broadcast format
574 * @returns {Promise<Buffer>} - the portion of the transaction that needs to be signed
575 */
576 async getSignablePayload(serializedTx: string): Promise<Buffer> {
577 return Buffer.from(serializedTx);
578 }
579}