/
DCoreClient.ts
132 lines (121 loc) · 5.57 KB
/
DCoreClient.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import Decimal from "decimal.js";
import * as _ from "lodash";
import { Duration } from "moment";
import * as P from "pino";
import { Logger } from "pino";
import { CoreOptions } from "request";
import { Observable, of, throwError, zip } from "rxjs";
import { flatMap, map, tap } from "rxjs/operators";
import { DCoreApi } from "./api/rx/DCoreApi";
import { DCoreConstants } from "./DCoreConstants";
import { Asset } from "./models/Asset";
import { AssetAmount } from "./models/AssetAmount";
import { ChainObject } from "./models/ChainObject";
import { IllegalArgumentError } from "./models/error/IllegalArgumentError";
import { BaseOperation } from "./models/operation/BaseOperation";
import { Transaction } from "./models/Transaction";
import { BaseRequest } from "./net/models/request/BaseRequest";
import { GetChainId } from "./net/models/request/GetChainId";
import { GetDynamicGlobalProps } from "./net/models/request/GetDynamicGlobalProps";
import { GetRequiredFees } from "./net/models/request/GetRequiredFees";
import { WithCallback } from "./net/models/request/WithCallback";
import { RpcService } from "./net/rpc/RpcService";
import { RxWebSocket, WebSocketFactory } from "./net/ws/RxWebSocket";
import { ObjectCheckOf } from "./utils/ObjectCheckOf";
import { assertThrow, info } from "./utils/Utils";
export type NftRef = ChainObject | string;
export type AccountRef = ChainObject | string;
export type AssetRef = ChainObject | string;
export type AssetWithAmount = [Asset, AssetAmount];
export type AssetPrecision = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
export type Fee = ChainObject | AssetAmount;
export declare type Newable<T> = new (...args: any[]) => T;
Decimal.set({
// max amount have precision 16 (satoshi significant places) and we are doubling that for partial result from multiply/division operations
precision: 32,
});
export class DCoreClient {
public static create(options?: CoreOptions, factory?: WebSocketFactory, logger: Logger = DCoreClient.DEFAULT_LOGGER): DCoreApi {
return new DCoreApi(new DCoreClient(
logger,
options ? new RpcService(options, logger) : undefined,
factory ? new RxWebSocket(factory, logger) : undefined,
));
}
private static get DEFAULT_LOGGER() {
return P({ name: "DCORE", base: undefined, enabled: false });
}
private chainId?: string;
private constructor(private logger: Logger, private rpc?: RpcService, private ws?: RxWebSocket) {
assertThrow(rpc != null || ws != null, () => "rpc or webSocket must be set");
}
public requestStream<T>(request: BaseRequest<T> & WithCallback): Observable<T> {
if (this.ws) {
return this.ws.requestStream(request).pipe(info("API_request_callback_" + request.method, this.logger));
} else {
return throwError(new IllegalArgumentError("callbacks not available through HTTP API"));
}
}
public request<T>(request: BaseRequest<T>): Observable<T> {
let result: Observable<T>;
assertThrow(_.isNil(this.ws) || _.isNil(this.rpc), () => "either ws or rpc must be present");
if (this.ws && (_.isNil(this.rpc) || this.ws.isConnected() || ObjectCheckOf<WithCallback>(request, "callbackId"))) {
result = this.ws.request(request);
} else {
if (_.isNil(this.rpc)) {
return throwError(new IllegalArgumentError("callbacks not available through HTTP API"));
}
result = this.rpc.request(request);
}
return result.pipe(info("API_request_" + request.method, this.logger));
}
public set timeout(millis: number) {
if (this.ws) {
this.ws.timeout = millis;
}
}
public disconnect() {
if (this.ws) {
this.ws.disconnect();
}
}
public prepareTransaction(operations: BaseOperation[], transactionExpiration: Duration): Observable<Transaction> {
const [withoutFees, withFees] = operations.reduce((res: [BaseOperation[], BaseOperation[]], el) => {
res[_.isNil(el.fee) ? 0 : 1].push(el);
return res;
}, [[], []]);
let finalOps: Observable<BaseOperation[]>;
if (withoutFees.length > 0) {
const forId = withoutFees.reduce((res: Map<string, BaseOperation[]>, el) => {
const feeAssetId = (el.feeAssetId ? el.feeAssetId : DCoreConstants.DCT_ASSET_ID).objectId;
if (!res.has(feeAssetId)) {
res.set(feeAssetId, []);
}
res.get(feeAssetId)!.push(el);
return res;
}, new Map());
const feeRequests = Array.from(forId.entries()).map(([feeId, ops]) =>
this.request(new GetRequiredFees(ops, ChainObject.parse(feeId))).pipe(
map((fees) => ops.map((op, idx) => {
op.fee = fees[idx];
return op;
})),
));
finalOps = zip(...feeRequests).pipe(map((ops) => _.flatten(ops).concat(withFees)));
} else {
finalOps = of(withFees);
}
let chainId: Observable<string>;
if (_.isNil(this.chainId)) {
chainId = this.request(new GetChainId()).pipe(tap((id) => this.chainId = id));
} else {
chainId = of(this.chainId);
}
return chainId.pipe(flatMap((id) =>
zip(
finalOps,
this.request(new GetDynamicGlobalProps()),
).pipe(map(([ops, props]) => Transaction.create(ops, id, props, transactionExpiration)))),
);
}
}