1 |
|
2 |
|
3 |
|
4 | import * as crypto from 'crypto';
|
5 | import * as bip32 from 'bip32';
|
6 | import { BigNumber } from 'bignumber.js';
|
7 | import { BaseCoin as AccountLibBasecoin } from '@bitgo/account-lib';
|
8 | import * as utxolib from '@bitgo/utxo-lib';
|
9 |
|
10 | import { BitGo } from '../bitgo';
|
11 | import { RequestTracer } from './internal/util';
|
12 | import { Wallet, WalletData } from './wallet';
|
13 | import { Wallets } from './wallets';
|
14 | import { Markets } from './markets';
|
15 | import { Webhooks } from './webhooks';
|
16 | import { PendingApprovals } from './pendingApprovals';
|
17 | import { Keychain, Keychains, KeyIndices } from './keychains';
|
18 | import { Enterprises } from './enterprises';
|
19 |
|
20 | import { InitiateRecoveryOptions } from './recovery/initiate';
|
21 | import { signMessage } from '../bip32util';
|
22 | import { TssUtils } from './internal/tssUtils';
|
23 |
|
24 |
|
25 | export type TransactionType = AccountLibBasecoin.TransactionType;
|
26 |
|
27 | export interface TransactionRecipient {
|
28 | address: string;
|
29 | amount: string | number;
|
30 | memo?: string;
|
31 | }
|
32 |
|
33 | export interface TransactionFee<TAmount = string> {
|
34 | fee: TAmount;
|
35 | feeRate?: number;
|
36 | size?: number;
|
37 | }
|
38 |
|
39 | export 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 |
|
51 | export interface KeyPair {
|
52 | pub?: string;
|
53 | prv: string;
|
54 | }
|
55 |
|
56 | export interface BlsKeyPair extends KeyPair {
|
57 | secretShares?: string[];
|
58 | }
|
59 |
|
60 | export 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 |
|
71 | export interface TransactionParams {
|
72 | recipients?: TransactionRecipient[];
|
73 | walletPassphrase?: string;
|
74 | type?: string;
|
75 | }
|
76 |
|
77 | export interface AddressVerificationData {
|
78 | coinSpecific?: AddressCoinSpecific;
|
79 | chain?: number;
|
80 | index?: number;
|
81 | }
|
82 |
|
83 | export 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 |
|
95 | export interface VerifyTransactionOptions {
|
96 | txPrebuild: TransactionPrebuild;
|
97 | txParams: TransactionParams;
|
98 | wallet: Wallet;
|
99 | verification?: VerificationOptions;
|
100 | reqId?: RequestTracer;
|
101 | }
|
102 |
|
103 | export 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 |
|
126 | export interface FeeEstimateOptions {
|
127 | numBlocks?: number;
|
128 | hop?: boolean;
|
129 | recipient?: string;
|
130 | data?: string;
|
131 | amount?: string;
|
132 | }
|
133 |
|
134 |
|
135 | export interface ExtraPrebuildParamsOptions {
|
136 | [index: string]: unknown;
|
137 | }
|
138 |
|
139 |
|
140 | export interface PresignTransactionOptions {
|
141 | txPrebuild?: TransactionPrebuild;
|
142 | walletData: WalletData;
|
143 | tssUtils: TssUtils;
|
144 | [index: string]: unknown;
|
145 | }
|
146 |
|
147 |
|
148 | export interface PrecreateBitGoOptions {
|
149 | [index: string]: unknown;
|
150 | }
|
151 |
|
152 |
|
153 | export interface VerifyRecoveryTransactionOptions {
|
154 | [index: string]: unknown;
|
155 | }
|
156 |
|
157 |
|
158 | export interface ParseTransactionOptions {
|
159 | [index: string]: unknown;
|
160 | }
|
161 |
|
162 |
|
163 | export interface ParsedTransaction {
|
164 | [index: string]: unknown;
|
165 | }
|
166 |
|
167 |
|
168 | export interface SignTransactionOptions {
|
169 | [index: string]: unknown;
|
170 | }
|
171 |
|
172 | export interface KeychainsTriplet {
|
173 | userKeychain: Keychain;
|
174 | backupKeychain: Keychain;
|
175 | bitgoKeychain: Keychain;
|
176 | }
|
177 |
|
178 | export 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 |
|
188 | export interface AddressCoinSpecific {
|
189 | outputScript?: string;
|
190 | redeemScript?: string;
|
191 | witnessScript?: string;
|
192 | baseAddress?: string;
|
193 | pendingChainInitialization?: boolean;
|
194 | forwarderVersion?: number;
|
195 | }
|
196 |
|
197 | export interface FullySignedTransaction {
|
198 | txHex: string;
|
199 | }
|
200 |
|
201 | export interface HalfSignedUtxoTransaction {
|
202 | txHex: string;
|
203 | }
|
204 |
|
205 | export interface HalfSignedAccountTransaction {
|
206 | halfSigned?: {
|
207 | txHex?: string;
|
208 | payload?: string;
|
209 | txBase64?: string;
|
210 | };
|
211 | }
|
212 |
|
213 | export interface SignedTransactionRequest {
|
214 | txRequestId: string;
|
215 | }
|
216 |
|
217 | export type SignedTransaction =
|
218 | | HalfSignedAccountTransaction
|
219 | | HalfSignedUtxoTransaction
|
220 | | FullySignedTransaction
|
221 | | SignedTransactionRequest;
|
222 |
|
223 | export 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 |
|
283 |
|
284 | abstract getChain(): string;
|
285 |
|
286 | |
287 |
|
288 |
|
289 | abstract getFamily(): string;
|
290 |
|
291 | |
292 |
|
293 |
|
294 | abstract getFullName(): string;
|
295 |
|
296 | |
297 |
|
298 |
|
299 |
|
300 | valuelessTransferAllowed(): boolean {
|
301 | return false;
|
302 | }
|
303 |
|
304 | |
305 |
|
306 |
|
307 |
|
308 | sweepWithSendMany(): boolean {
|
309 | return false;
|
310 | }
|
311 |
|
312 | |
313 |
|
314 |
|
315 |
|
316 | transactionDataAllowed(): boolean {
|
317 | return false;
|
318 | }
|
319 |
|
320 | |
321 |
|
322 |
|
323 |
|
324 |
|
325 | allowsAccountConsolidations(): boolean {
|
326 | return false;
|
327 | }
|
328 |
|
329 | |
330 |
|
331 |
|
332 |
|
333 | supportsTss(): boolean {
|
334 | return false;
|
335 | }
|
336 |
|
337 | |
338 |
|
339 |
|
340 |
|
341 | supportsBlsDkg(): boolean {
|
342 | return false;
|
343 | }
|
344 |
|
345 | |
346 |
|
347 |
|
348 |
|
349 | abstract getBaseFactor(): number | string;
|
350 |
|
351 | |
352 |
|
353 |
|
354 |
|
355 | baseUnitsToBigUnits(baseUnits: string | number): string {
|
356 | const dividend = this.getBaseFactor();
|
357 | const bigNumber = new BigNumber(baseUnits).dividedBy(dividend);
|
358 |
|
359 | return bigNumber.toFormat(null as any, null as any, { groupSeparator: '', decimalSeparator: '.' });
|
360 | }
|
361 |
|
362 | |
363 |
|
364 |
|
365 |
|
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 |
|
378 |
|
379 |
|
380 |
|
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 |
|
388 |
|
389 |
|
390 | explainTransaction(options: Record<string, any>): Promise<TransactionExplanation<any, string | number> | undefined> {
|
391 | throw new Error(`not implemented`);
|
392 | }
|
393 |
|
394 | |
395 |
|
396 |
|
397 | abstract verifyTransaction(params: VerifyTransactionOptions): Promise<boolean>;
|
398 |
|
399 | |
400 |
|
401 |
|
402 | verifyAddress(params: VerifyAddressOptions): boolean {
|
403 | return this.isWalletAddress(params);
|
404 | }
|
405 |
|
406 | |
407 |
|
408 |
|
409 |
|
410 | abstract isWalletAddress(params: VerifyAddressOptions): boolean;
|
411 |
|
412 | |
413 |
|
414 |
|
415 |
|
416 |
|
417 | canonicalAddress(address: string, format?: unknown): string {
|
418 | return address;
|
419 | }
|
420 |
|
421 | |
422 |
|
423 |
|
424 |
|
425 | supportsBlockTarget() {
|
426 | return false;
|
427 | }
|
428 |
|
429 | |
430 |
|
431 |
|
432 |
|
433 |
|
434 |
|
435 | supplementGenerateWallet(walletParams: SupplementGenerateWalletOptions, keychains: KeychainsTriplet): Promise<any> {
|
436 | return Promise.resolve(walletParams);
|
437 | }
|
438 |
|
439 | |
440 |
|
441 |
|
442 | getExtraPrebuildParams(buildParams: ExtraPrebuildParamsOptions): Promise<Record<string, unknown>> {
|
443 | return Promise.resolve({});
|
444 | }
|
445 |
|
446 | |
447 |
|
448 |
|
449 | postProcessPrebuild(prebuildResponse: TransactionPrebuild): Promise<TransactionPrebuild> {
|
450 | return Promise.resolve(prebuildResponse);
|
451 | }
|
452 |
|
453 | |
454 |
|
455 |
|
456 | presignTransaction(params: PresignTransactionOptions): Promise<PresignTransactionOptions> {
|
457 | return Promise.resolve(params);
|
458 | }
|
459 |
|
460 | |
461 |
|
462 |
|
463 |
|
464 | newWalletObject(walletParams: any): Wallet {
|
465 | return new Wallet(this.bitgo, this, walletParams);
|
466 | }
|
467 |
|
468 | |
469 |
|
470 |
|
471 |
|
472 |
|
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 |
|
485 |
|
486 |
|
487 |
|
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 |
|
509 |
|
510 |
|
511 | keyIdsForSigning(): number[] {
|
512 | return [KeyIndices.USER];
|
513 | }
|
514 |
|
515 | |
516 |
|
517 |
|
518 |
|
519 |
|
520 | preCreateBitGo(params: PrecreateBitGoOptions): void {
|
521 | return;
|
522 | }
|
523 |
|
524 | |
525 |
|
526 |
|
527 | initiateRecovery(params: InitiateRecoveryOptions): never {
|
528 | throw new Error('deprecated method');
|
529 | }
|
530 |
|
531 | abstract parseTransaction(params: ParseTransactionOptions): Promise<ParsedTransaction>;
|
532 |
|
533 | |
534 |
|
535 |
|
536 |
|
537 |
|
538 | abstract generateKeyPair(seed?: Buffer): KeyPair;
|
539 |
|
540 | |
541 |
|
542 |
|
543 |
|
544 |
|
545 |
|
546 | abstract isValidPub(pub: string): boolean;
|
547 |
|
548 | |
549 |
|
550 |
|
551 | isValidMofNSetup({ m, n }: { m?: number; n?: number }): boolean {
|
552 | return m === 2 && n === 3;
|
553 | }
|
554 |
|
555 | |
556 |
|
557 |
|
558 |
|
559 |
|
560 |
|
561 |
|
562 | abstract isValidAddress(address: string): boolean;
|
563 |
|
564 | |
565 |
|
566 |
|
567 | abstract signTransaction(params: SignTransactionOptions): Promise<SignedTransaction>;
|
568 |
|
569 | |
570 |
|
571 |
|
572 |
|
573 |
|
574 |
|
575 |
|
576 | async getSignablePayload(serializedTx: string): Promise<Buffer> {
|
577 | return Buffer.from(serializedTx);
|
578 | }
|
579 | }
|