-
Notifications
You must be signed in to change notification settings - Fork 320
/
Copy pathserver.ts
715 lines (648 loc) · 26 KB
/
server.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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
/* tslint:disable:variable-name no-namespace */
import BigNumber from "bignumber.js";
import isEmpty from "lodash/isEmpty";
import merge from "lodash/merge";
import { Asset, StrKey, Transaction, xdr } from "stellar-base";
import URI from "urijs";
import { CallBuilder } from "./call_builder";
import { Config } from "./config";
import { BadResponseError } from "./errors";
import { AccountCallBuilder } from "./account_call_builder";
import { AccountResponse } from "./account_response";
import { AssetsCallBuilder } from "./assets_call_builder";
import { EffectCallBuilder } from "./effect_call_builder";
import { FriendbotBuilder } from "./friendbot_builder";
import { Horizon } from "./horizon_api";
import { LedgerCallBuilder } from "./ledger_call_builder";
import { OfferCallBuilder } from "./offer_call_builder";
import { OperationCallBuilder } from "./operation_call_builder";
import { OrderbookCallBuilder } from "./orderbook_call_builder";
import { PathCallBuilder } from "./path_call_builder";
import { PaymentCallBuilder } from "./payment_call_builder";
import { StrictReceivePathCallBuilder } from "./strict_receive_path_call_builder";
import { StrictSendPathCallBuilder } from "./strict_send_path_call_builder";
import { TradeAggregationCallBuilder } from "./trade_aggregation_call_builder";
import { TradesCallBuilder } from "./trades_call_builder";
import { TransactionCallBuilder } from "./transaction_call_builder";
import HorizonAxiosClient, {
getCurrentServerTime,
} from "./horizon_axios_client";
export const SUBMIT_TRANSACTION_TIMEOUT = 60 * 1000;
const STROOPS_IN_LUMEN = 10000000;
function _getAmountInLumens(amt: BigNumber) {
return new BigNumber(amt).div(STROOPS_IN_LUMEN).toString();
}
/**
* Server handles the network connection to a [Horizon](https://www.stellar.org/developers/horizon/reference/)
* instance and exposes an interface for requests to that instance.
* @constructor
* @param {string} serverURL Horizon Server URL (ex. `https://horizon-testnet.stellar.org`).
* @param {object} [opts] Options object
* @param {boolean} [opts.allowHttp] - Allow connecting to http servers, default: `false`. This must be set to false in production deployments! You can also use {@link Config} class to set this globally.
* @param {string} [opts.appName] - Allow set custom header `X-App-Name`, default: `undefined`.
* @param {string} [opts.appVersion] - Allow set custom header `X-App-Version`, default: `undefined`.
*/
export class Server {
/**
* serverURL Horizon Server URL (ex. `https://horizon-testnet.stellar.org`).
*
* TODO: Solve `URI(this.serverURL as any)`.
*/
public readonly serverURL: uri.URI;
constructor(serverURL: string, opts: Server.Options = {}) {
this.serverURL = URI(serverURL);
const allowHttp =
typeof opts.allowHttp === "undefined"
? Config.isAllowHttp()
: opts.allowHttp;
const customHeaders: any = {};
if (opts.appName) {
customHeaders["X-App-Name"] = opts.appName;
}
if (opts.appVersion) {
customHeaders["X-App-Version"] = opts.appVersion;
}
if (!isEmpty(customHeaders)) {
HorizonAxiosClient.interceptors.request.use((config) => {
// merge the custom headers with an existing headers
config.headers = merge(customHeaders, config.headers);
return config;
});
}
if (this.serverURL.protocol() !== "https" && !allowHttp) {
throw new Error("Cannot connect to insecure horizon server");
}
}
/**
* Get timebounds for N seconds from now, when you're creating a transaction
* with {@link TransactionBuilder}.
*
* By default, {@link TransactionBuilder} uses the current local time, but
* your machine's local time could be different from Horizon's. This gives you
* more assurance that your timebounds will reflect what you want.
*
* Note that this will generate your timebounds when you **init the transaction**,
* not when you build or submit the transaction! So give yourself enough time to get
* the transaction built and signed before submitting.
*
* Example:
*
* ```javascript
* const transaction = new StellarSdk.TransactionBuilder(accountId, {
* fee: await StellarSdk.Server.fetchBaseFee(),
* timebounds: await StellarSdk.Server.fetchTimebounds(100)
* })
* .addOperation(operation)
* // normally we would need to call setTimeout here, but setting timebounds
* // earlier does the trick!
* .build();
* ```
* @argument {number} seconds Number of seconds past the current time to wait.
* @argument {bool} [_isRetry=false] True if this is a retry. Only set this internally!
* This is to avoid a scenario where Horizon is horking up the wrong date.
* @returns {Promise<Timebounds>} Promise that resolves a `timebounds` object
* (with the shape `{ minTime: 0, maxTime: N }`) that you can set the `timebounds` option to.
*/
public async fetchTimebounds(
seconds: number,
_isRetry: boolean = false,
): Promise<Server.Timebounds> {
// HorizonAxiosClient instead of this.ledgers so we can get at them headers
const currentTime = getCurrentServerTime(this.serverURL.hostname());
if (currentTime) {
return {
minTime: 0,
maxTime: currentTime + seconds,
};
}
// if this is a retry, then the retry has failed, so use local time
if (_isRetry) {
return {
minTime: 0,
maxTime: Math.floor(new Date().getTime() / 1000) + seconds,
};
}
// otherwise, retry (by calling the root endpoint)
// toString automatically adds the trailing slash
await HorizonAxiosClient.get(URI(this.serverURL as any).toString());
return await this.fetchTimebounds(seconds, true);
}
/**
* Fetch the base fee. Since this hits the server, if the server call fails,
* you might get an error. You should be prepared to use a default value if
* that happens!
* @returns {Promise<number>} Promise that resolves to the base fee.
*/
public async fetchBaseFee(): Promise<number> {
const response = await this.feeStats();
return parseInt(response.last_ledger_base_fee, 10) || 100;
}
/**
* Fetch the fee stats endpoint.
* @see [Fee Stats](https://www.stellar.org/developers/horizon/reference/endpoints/fee-stats.html)
* @returns {Promise<Horizon.FeeStatsResponse>} Promise that resolves to the fee stats returned by Horizon.
*/
public async feeStats(): Promise<Horizon.FeeStatsResponse> {
const cb = new CallBuilder<Horizon.FeeStatsResponse>(
URI(this.serverURL as any),
);
cb.filter.push(["fee_stats"]);
return cb.call();
}
/**
* Submits a transaction to the network.
*
* If you submit any number of `manageOffer` operations, this will add
* an attribute to the response that will help you analyze what happened
* with your offers.
*
* Ex:
* ```javascript
* const res = {
* ...response,
* offerResults: [
* {
* // Exact ordered list of offers that executed, with the exception
* // that the last one may not have executed entirely.
* offersClaimed: [
* sellerId: String,
* offerId: String,
* assetSold: {
* type: 'native|credit_alphanum4|credit_alphanum12',
*
* // these are only present if the asset is not native
* assetCode: String,
* issuer: String,
* },
*
* // same shape as assetSold
* assetBought: {}
* ],
*
* // What effect your manageOffer op had
* effect: "manageOfferCreated|manageOfferUpdated|manageOfferDeleted",
*
* // Whether your offer immediately got matched and filled
* wasImmediatelyFilled: Boolean,
*
* // Whether your offer immediately got deleted, if for example the order was too small
* wasImmediatelyDeleted: Boolean,
*
* // Whether the offer was partially, but not completely, filled
* wasPartiallyFilled: Boolean,
*
* // The full requested amount of the offer is open for matching
* isFullyOpen: Boolean,
*
* // The total amount of tokens bought / sold during transaction execution
* amountBought: Number,
* amountSold: Number,
*
* // if the offer was created, updated, or partially filled, this is
* // the outstanding offer
* currentOffer: {
* offerId: String,
* amount: String,
* price: {
* n: String,
* d: String,
* },
*
* selling: {
* type: 'native|credit_alphanum4|credit_alphanum12',
*
* // these are only present if the asset is not native
* assetCode: String,
* issuer: String,
* },
*
* // same as `selling`
* buying: {},
* },
*
* // the index of this particular operation in the op stack
* operationIndex: Number
* }
* ]
* }
* ```
*
* For example, you'll want to examine `offerResults` to add affordances
* like these to your app:
* * If `wasImmediatelyFilled` is true, then no offer was created. So if you
* normally watch the `Server.offers` endpoint for offer updates, you instead
* need to check `Server.trades` to find the result of this filled offer.
* * If `wasImmediatelyDeleted` is true, then the offer you submitted was
* deleted without reaching the orderbook or being matched (possibly because
* your amounts were rounded down to zero). So treat the just-submitted offer
* request as if it never happened.
* * If `wasPartiallyFilled` is true, you can tell the user that `amountBought`
* or `amountSold` have already been transferred.
*
* @see [Post Transaction](https://www.stellar.org/developers/horizon/reference/endpoints/transactions-create.html)
* @param {Transaction} transaction - The transaction to submit.
* @returns {Promise} Promise that resolves or rejects with response from horizon.
*/
public async submitTransaction(
transaction: Transaction,
): Promise<Horizon.SubmitTransactionResponse> {
const tx = encodeURIComponent(
transaction
.toEnvelope()
.toXDR()
.toString("base64"),
);
return HorizonAxiosClient.post(
URI(this.serverURL as any)
.segment("transactions")
.toString(),
`tx=${tx}`,
{ timeout: SUBMIT_TRANSACTION_TIMEOUT },
)
.then((response) => {
if (!response.data.result_xdr) {
return response.data;
}
// TODO: fix stellar-base types.
const responseXDR: xdr.TransactionResult = (xdr.TransactionResult
.fromXDR as any)(response.data.result_xdr, "base64");
// TODO: fix stellar-base types.
const results = (responseXDR as any).result().value();
let offerResults;
let hasManageOffer;
if (results.length) {
offerResults = results
// TODO: fix stellar-base types.
.map((result: any, i: number) => {
if (
result.value().switch().name !== "manageBuyOffer" &&
result.value().switch().name !== "manageSellOffer"
) {
return null;
}
hasManageOffer = true;
let amountBought = new BigNumber(0);
let amountSold = new BigNumber(0);
const offerSuccess = result
.value()
.value()
.success();
const offersClaimed = offerSuccess
.offersClaimed()
// TODO: fix stellar-base types.
.map((offerClaimed: any) => {
const claimedOfferAmountBought = new BigNumber(
// amountBought is a js-xdr hyper
offerClaimed.amountBought().toString(),
);
const claimedOfferAmountSold = new BigNumber(
// amountBought is a js-xdr hyper
offerClaimed.amountSold().toString(),
);
// This is an offer that was filled by the one just submitted.
// So this offer has an _opposite_ bought/sold frame of ref
// than from what we just submitted!
// So add this claimed offer's bought to the SOLD count and vice v
amountBought = amountBought.add(claimedOfferAmountSold);
amountSold = amountSold.add(claimedOfferAmountBought);
const sold = Asset.fromOperation(offerClaimed.assetSold());
const bought = Asset.fromOperation(
offerClaimed.assetBought(),
);
const assetSold = {
type: sold.getAssetType(),
assetCode: sold.getCode(),
issuer: sold.getIssuer(),
};
const assetBought = {
type: bought.getAssetType(),
assetCode: bought.getCode(),
issuer: bought.getIssuer(),
};
return {
sellerId: StrKey.encodeEd25519PublicKey(
offerClaimed.sellerId().ed25519(),
),
offerId: offerClaimed.offerId().toString(),
assetSold,
amountSold: _getAmountInLumens(claimedOfferAmountSold),
assetBought,
amountBought: _getAmountInLumens(claimedOfferAmountBought),
};
});
const effect = offerSuccess.offer().switch().name;
let currentOffer;
if (
typeof offerSuccess.offer().value === "function" &&
offerSuccess.offer().value()
) {
const offerXDR = offerSuccess.offer().value();
currentOffer = {
offerId: offerXDR.offerId().toString(),
selling: {},
buying: {},
amount: _getAmountInLumens(offerXDR.amount().toString()),
price: {
n: offerXDR.price().n(),
d: offerXDR.price().d(),
},
};
const selling = Asset.fromOperation(offerXDR.selling());
currentOffer.selling = {
type: selling.getAssetType(),
assetCode: selling.getCode(),
issuer: selling.getIssuer(),
};
const buying = Asset.fromOperation(offerXDR.buying());
currentOffer.buying = {
type: buying.getAssetType(),
assetCode: buying.getCode(),
issuer: buying.getIssuer(),
};
}
return {
offersClaimed,
effect,
operationIndex: i,
currentOffer,
// this value is in stroops so divide it out
amountBought: _getAmountInLumens(amountBought),
amountSold: _getAmountInLumens(amountSold),
isFullyOpen:
!offersClaimed.length && effect !== "manageOfferDeleted",
wasPartiallyFilled:
!!offersClaimed.length && effect !== "manageOfferDeleted",
wasImmediatelyFilled:
!!offersClaimed.length && effect === "manageOfferDeleted",
wasImmediatelyDeleted:
!offersClaimed.length && effect === "manageOfferDeleted",
};
})
// TODO: fix stellar-base types.
.filter((result: any) => !!result);
}
return Object.assign({}, response.data, {
offerResults: hasManageOffer ? offerResults : undefined,
});
})
.catch((response) => {
if (response instanceof Error) {
return Promise.reject(response);
}
return Promise.reject(
new BadResponseError(
`Transaction submission failed. Server responded: ${response.status} ${response.statusText}`,
response.data,
),
);
});
}
/**
* @returns {AccountCallBuilder} New {@link AccountCallBuilder} object configured by a current Horizon server configuration.
*/
public accounts(): AccountCallBuilder {
return new AccountCallBuilder(URI(this.serverURL as any));
}
/**
* @returns {LedgerCallBuilder} New {@link LedgerCallBuilder} object configured by a current Horizon server configuration.
*/
public ledgers(): LedgerCallBuilder {
return new LedgerCallBuilder(URI(this.serverURL as any));
}
/**
* @returns {TransactionCallBuilder} New {@link TransactionCallBuilder} object configured by a current Horizon server configuration.
*/
public transactions(): TransactionCallBuilder {
return new TransactionCallBuilder(URI(this.serverURL as any));
}
/**
* People on the Stellar network can make offers to buy or sell assets. This endpoint represents all the offers on the DEX.
*
* You can query all offers for account using the function `.accountId`:
*
* ```
* server.offers()
* .forAccount(accountId).call()
* .then(function(offers) {
* console.log(offers);
* });
* ```
* @returns {OfferCallBuilder} New {@link OfferCallBuilder} object
*/
public offers(): OfferCallBuilder {
return new OfferCallBuilder(URI(this.serverURL as any));
}
/**
* @param {Asset} selling Asset being sold
* @param {Asset} buying Asset being bought
* @returns {OrderbookCallBuilder} New {@link OrderbookCallBuilder} object configured by a current Horizon server configuration.
*/
public orderbook(selling: Asset, buying: Asset): OrderbookCallBuilder {
return new OrderbookCallBuilder(
URI(this.serverURL as any),
selling,
buying,
);
}
/**
* Returns
* @returns {TradesCallBuilder} New {@link TradesCallBuilder} object configured by a current Horizon server configuration.
*/
public trades(): TradesCallBuilder {
return new TradesCallBuilder(URI(this.serverURL as any));
}
/**
* @returns {OperationCallBuilder} New {@link OperationCallBuilder} object configured by a current Horizon server configuration.
*/
public operations(): OperationCallBuilder {
return new OperationCallBuilder(URI(this.serverURL as any));
}
/**
*
* The Stellar Network allows payments to be made between assets through path payments. A path payment specifies a
* series of assets to route a payment through, from source asset (the asset debited from the payer) to destination
* asset (the asset credited to the payee).
*
* A path search is specified using:
*
* * The destination address
* * The source address
* * The asset and amount that the destination account should receive
*
* As part of the search, horizon will load a list of assets available to the source address and will find any
* payment paths from those source assets to the desired destination asset. The search's amount parameter will be
* used to determine if there a given path can satisfy a payment of the desired amount.
*
* @param {string} source The sender's account ID. Any returned path will use a source that the sender can hold.
* @param {string} destination The destination account ID that any returned path should use.
* @param {Asset} destinationAsset The destination asset.
* @param {string} destinationAmount The amount, denominated in the destination asset, that any returned path should be able to satisfy.
* @returns {PathCallBuilder} New {@link PathCallBuilder} object configured with the current Horizon server configuration.
*
* @deprecated Use {@link Server#strictReceivePaths}
*/
public paths(
source: string,
destination: string,
destinationAsset: Asset,
destinationAmount: string,
): PathCallBuilder {
console.warn(
"`Server#paths` is deprecated. Please use `Server#strictReceivePaths`.",
);
return new PathCallBuilder(
URI(this.serverURL as any),
source,
destination,
destinationAsset,
destinationAmount,
);
}
/**
* The Stellar Network allows payments to be made between assets through path
* payments. A strict receive path payment specifies a series of assets to
* route a payment through, from source asset (the asset debited from the
* payer) to destination asset (the asset credited to the payee).
*
* A strict receive path search is specified using:
*
* * The destination address.
* * The source address or source assets.
* * The asset and amount that the destination account should receive.
*
* As part of the search, horizon will load a list of assets available to the
* source address and will find any payment paths from those source assets to
* the desired destination asset. The search's amount parameter will be used
* to determine if there a given path can satisfy a payment of the desired
* amount.
*
* If a list of assets is passed as the source, horizon will find any payment
* paths from those source assets to the desired destination asset.
*
* @param {string|Asset[]} source The sender's account ID or a list of assets. Any returned path will use a source that the sender can hold.
* @param {Asset} destinationAsset The destination asset.
* @param {string} destinationAmount The amount, denominated in the destination asset, that any returned path should be able to satisfy.
* @returns {StrictReceivePathCallBuilder} New {@link StrictReceivePathCallBuilder} object configured with the current Horizon server configuration.
*/
public strictReceivePaths(
source: string | Asset[],
destinationAsset: Asset,
destinationAmount: string,
): PathCallBuilder {
return new StrictReceivePathCallBuilder(
URI(this.serverURL as any),
source,
destinationAsset,
destinationAmount,
);
}
/**
* The Stellar Network allows payments to be made between assets through path payments. A strict send path payment specifies a
* series of assets to route a payment through, from source asset (the asset debited from the payer) to destination
* asset (the asset credited to the payee).
*
* A strict send path search is specified using:
*
* The asset and amount that is being sent.
* The destination account or the destination assets.
*
* @param {Asset} sourceAsset The asset to be sent.
* @param {string} sourceAmount The amount, denominated in the source asset, that any returned path should be able to satisfy.
* @param {string|Asset[]} destination The destination account or the destination assets.
* @returns {StrictSendPathCallBuilder} New {@link StrictSendPathCallBuilder} object configured with the current Horizon server configuration.
*/
public strictSendPaths(
sourceAsset: Asset,
sourceAmount: string,
destination: string | Asset[],
): PathCallBuilder {
return new StrictSendPathCallBuilder(
URI(this.serverURL as any),
sourceAsset,
sourceAmount,
destination,
);
}
/**
* @returns {PaymentCallBuilder} New {@link PaymentCallBuilder} instance configured with the current
* Horizon server configuration.
*/
public payments(): PaymentCallBuilder {
return new PaymentCallBuilder(URI(this.serverURL as any) as any);
}
/**
* @returns {EffectCallBuilder} New {@link EffectCallBuilder} instance configured with the current
* Horizon server configuration
*/
public effects(): EffectCallBuilder {
return new EffectCallBuilder(URI(this.serverURL as any) as any);
}
/**
* @param {string} address The Stellar ID that you want Friendbot to send lumens to
* @returns {FriendbotBuilder} New {@link FriendbotBuilder} instance configured with the current
* Horizon server configuration
* @private
*/
public friendbot(address: string): FriendbotBuilder {
return new FriendbotBuilder(URI(this.serverURL as any), address);
}
/**
* Get a new {@link AssetsCallBuilder} instance configured with the current
* Horizon server configuration.
* @returns {AssetsCallBuilder} New AssetsCallBuilder instance
*/
public assets(): AssetsCallBuilder {
return new AssetsCallBuilder(URI(this.serverURL as any));
}
/**
* Fetches an account's most current state in the ledger and then creates and returns an {@link Account} object.
* @param {string} accountId - The account to load.
* @returns {Promise} Returns a promise to the {@link AccountResponse} object with populated sequence number.
*/
public async loadAccount(accountId: string): Promise<AccountResponse> {
const res = await this.accounts()
.accountId(accountId)
.call();
return new AccountResponse(res);
}
/**
*
* @param {Asset} base base asset
* @param {Asset} counter counter asset
* @param {long} start_time lower time boundary represented as millis since epoch
* @param {long} end_time upper time boundary represented as millis since epoch
* @param {long} resolution segment duration as millis since epoch. *Supported values are 5 minutes (300000), 15 minutes (900000), 1 hour (3600000), 1 day (86400000) and 1 week (604800000).
* @param {long} offset segments can be offset using this parameter. Expressed in milliseconds. *Can only be used if the resolution is greater than 1 hour. Value must be in whole hours, less than the provided resolution, and less than 24 hours.
* Returns new {@link TradeAggregationCallBuilder} object configured with the current Horizon server configuration.
* @returns {TradeAggregationCallBuilder} New TradeAggregationCallBuilder instance
*/
public tradeAggregation(
base: Asset,
counter: Asset,
start_time: number,
end_time: number,
resolution: number,
offset: number,
): TradeAggregationCallBuilder {
return new TradeAggregationCallBuilder(
URI(this.serverURL as any),
base,
counter,
start_time,
end_time,
resolution,
offset,
);
}
}
export namespace Server {
export interface Options {
allowHttp?: boolean;
appName?: string;
appVersion?: string;
}
export interface Timebounds {
minTime: number;
maxTime: number;
}
}