Source

lib/horizon/server.js

  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.SUBMIT_TRANSACTION_TIMEOUT = exports.HorizonServer = void 0;
  6. var _bignumber = _interopRequireDefault(require("bignumber.js"));
  7. var _stellarBase = require("@stellar/stellar-base");
  8. var _urijs = _interopRequireDefault(require("urijs"));
  9. var _call_builder = require("./call_builder");
  10. var _config = require("../config");
  11. var _errors = require("../errors");
  12. var _account_call_builder = require("./account_call_builder");
  13. var _account_response = require("./account_response");
  14. var _assets_call_builder = require("./assets_call_builder");
  15. var _claimable_balances_call_builder = require("./claimable_balances_call_builder");
  16. var _effect_call_builder = require("./effect_call_builder");
  17. var _friendbot_builder = require("./friendbot_builder");
  18. var _ledger_call_builder = require("./ledger_call_builder");
  19. var _liquidity_pool_call_builder = require("./liquidity_pool_call_builder");
  20. var _offer_call_builder = require("./offer_call_builder");
  21. var _operation_call_builder = require("./operation_call_builder");
  22. var _orderbook_call_builder = require("./orderbook_call_builder");
  23. var _payment_call_builder = require("./payment_call_builder");
  24. var _strict_receive_path_call_builder = require("./strict_receive_path_call_builder");
  25. var _strict_send_path_call_builder = require("./strict_send_path_call_builder");
  26. var _trade_aggregation_call_builder = require("./trade_aggregation_call_builder");
  27. var _trades_call_builder = require("./trades_call_builder");
  28. var _transaction_call_builder = require("./transaction_call_builder");
  29. var _horizon_axios_client = _interopRequireWildcard(require("./horizon_axios_client"));
  30. function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
  31. function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
  32. function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
  33. /* tslint:disable:variable-name no-namespace */
  34. // eslint-disable-next-line import/no-named-as-default
  35. /**
  36. * Default transaction submission timeout for Horizon requests, in milliseconds
  37. * @constant {number}
  38. * @default 60000
  39. * @memberof module:Horizon.Server
  40. */
  41. const SUBMIT_TRANSACTION_TIMEOUT = exports.SUBMIT_TRANSACTION_TIMEOUT = 60 * 1000;
  42. const STROOPS_IN_LUMEN = 10000000;
  43. // ACCOUNT_REQUIRES_MEMO is the base64 encoding of "1".
  44. // SEP 29 uses this value to define transaction memo requirements for incoming payments.
  45. const ACCOUNT_REQUIRES_MEMO = "MQ==";
  46. function getAmountInLumens(amt) {
  47. return new _bignumber.default(amt).div(STROOPS_IN_LUMEN).toString();
  48. }
  49. /**
  50. * Server handles the network connection to a [Horizon](https://developers.stellar.org/docs/data/horizon)
  51. * instance and exposes an interface for requests to that instance.
  52. * @class
  53. * @alias module:Horizon.Server
  54. * @memberof module:Horizon
  55. *
  56. * @param {string} serverURL Horizon Server URL (ex. `https://horizon-testnet.stellar.org`).
  57. * @param {module:Horizon.Server.Options} [opts] Options object
  58. */
  59. class HorizonServer {
  60. /**
  61. * Horizon Server URL (ex. `https://horizon-testnet.stellar.org`)
  62. *
  63. * @todo Solve `URI(this.serverURL as any)`.
  64. */
  65. constructor(serverURL, opts = {}) {
  66. this.serverURL = (0, _urijs.default)(serverURL);
  67. const allowHttp = typeof opts.allowHttp === "undefined" ? _config.Config.isAllowHttp() : opts.allowHttp;
  68. const customHeaders = {};
  69. if (opts.appName) {
  70. customHeaders["X-App-Name"] = opts.appName;
  71. }
  72. if (opts.appVersion) {
  73. customHeaders["X-App-Version"] = opts.appVersion;
  74. }
  75. if (opts.authToken) {
  76. customHeaders["X-Auth-Token"] = opts.authToken;
  77. }
  78. if (opts.headers) {
  79. Object.assign(customHeaders, opts.headers);
  80. }
  81. if (Object.keys(customHeaders).length > 0) {
  82. _horizon_axios_client.default.interceptors.request.use(config => {
  83. // merge the custom headers with an existing headers, where customs
  84. // override defaults
  85. config.headers = config.headers || {};
  86. config.headers = Object.assign(config.headers, customHeaders);
  87. return config;
  88. });
  89. }
  90. if (this.serverURL.protocol() !== "https" && !allowHttp) {
  91. throw new Error("Cannot connect to insecure horizon server");
  92. }
  93. }
  94. /**
  95. * Get timebounds for N seconds from now, when you're creating a transaction
  96. * with {@link TransactionBuilder}.
  97. *
  98. * By default, {@link TransactionBuilder} uses the current local time, but
  99. * your machine's local time could be different from Horizon's. This gives you
  100. * more assurance that your timebounds will reflect what you want.
  101. *
  102. * Note that this will generate your timebounds when you **init the transaction**,
  103. * not when you build or submit the transaction! So give yourself enough time to get
  104. * the transaction built and signed before submitting.
  105. *
  106. * @example
  107. * const transaction = new StellarSdk.TransactionBuilder(accountId, {
  108. * fee: await StellarSdk.Server.fetchBaseFee(),
  109. * timebounds: await StellarSdk.Server.fetchTimebounds(100)
  110. * })
  111. * .addOperation(operation)
  112. * // normally we would need to call setTimeout here, but setting timebounds
  113. * // earlier does the trick!
  114. * .build();
  115. *
  116. * @param {number} seconds Number of seconds past the current time to wait.
  117. * @param {boolean} [_isRetry] True if this is a retry. Only set this internally!
  118. * This is to avoid a scenario where Horizon is horking up the wrong date.
  119. * @returns {Promise<Timebounds>} Promise that resolves a `timebounds` object
  120. * (with the shape `{ minTime: 0, maxTime: N }`) that you can set the `timebounds` option to.
  121. */
  122. async fetchTimebounds(seconds, _isRetry = false) {
  123. // AxiosClient instead of this.ledgers so we can get at them headers
  124. const currentTime = (0, _horizon_axios_client.getCurrentServerTime)(this.serverURL.hostname());
  125. if (currentTime) {
  126. return {
  127. minTime: 0,
  128. maxTime: currentTime + seconds
  129. };
  130. }
  131. // if this is a retry, then the retry has failed, so use local time
  132. if (_isRetry) {
  133. return {
  134. minTime: 0,
  135. maxTime: Math.floor(new Date().getTime() / 1000) + seconds
  136. };
  137. }
  138. // otherwise, retry (by calling the root endpoint)
  139. // toString automatically adds the trailing slash
  140. await _horizon_axios_client.default.get((0, _urijs.default)(this.serverURL).toString());
  141. return this.fetchTimebounds(seconds, true);
  142. }
  143. /**
  144. * Fetch the base fee. Since this hits the server, if the server call fails,
  145. * you might get an error. You should be prepared to use a default value if
  146. * that happens!
  147. * @returns {Promise<number>} Promise that resolves to the base fee.
  148. */
  149. async fetchBaseFee() {
  150. const response = await this.feeStats();
  151. return parseInt(response.last_ledger_base_fee, 10) || 100;
  152. }
  153. /**
  154. * Fetch the fee stats endpoint.
  155. * @see {@link https://developers.stellar.org/docs/data/horizon/api-reference/aggregations/fee-stats|Fee Stats}
  156. * @returns {Promise<HorizonApi.FeeStatsResponse>} Promise that resolves to the fee stats returned by Horizon.
  157. */
  158. // eslint-disable-next-line require-await
  159. async feeStats() {
  160. const cb = new _call_builder.CallBuilder((0, _urijs.default)(this.serverURL));
  161. cb.filter.push(["fee_stats"]);
  162. return cb.call();
  163. }
  164. /**
  165. * Fetch the Horizon server's root endpoint.
  166. * @returns {Promise<HorizonApi.RootResponse>} Promise that resolves to the root endpoint returned by Horizon.
  167. */
  168. async root() {
  169. const cb = new _call_builder.CallBuilder((0, _urijs.default)(this.serverURL));
  170. return cb.call();
  171. }
  172. /**
  173. * Submits a transaction to the network.
  174. *
  175. * By default this function calls {@link Horizon.Server#checkMemoRequired}, you can
  176. * skip this check by setting the option `skipMemoRequiredCheck` to `true`.
  177. *
  178. * If you submit any number of `manageOffer` operations, this will add an
  179. * attribute to the response that will help you analyze what happened with
  180. * your offers.
  181. *
  182. * For example, you'll want to examine `offerResults` to add affordances like
  183. * these to your app:
  184. * - If `wasImmediatelyFilled` is true, then no offer was created. So if you
  185. * normally watch the `Server.offers` endpoint for offer updates, you
  186. * instead need to check `Server.trades` to find the result of this filled
  187. * offer.
  188. * - If `wasImmediatelyDeleted` is true, then the offer you submitted was
  189. * deleted without reaching the orderbook or being matched (possibly because
  190. * your amounts were rounded down to zero). So treat the just-submitted
  191. * offer request as if it never happened.
  192. * - If `wasPartiallyFilled` is true, you can tell the user that
  193. * `amountBought` or `amountSold` have already been transferred.
  194. *
  195. * @example
  196. * const res = {
  197. * ...response,
  198. * offerResults: [
  199. * {
  200. * // Exact ordered list of offers that executed, with the exception
  201. * // that the last one may not have executed entirely.
  202. * offersClaimed: [
  203. * sellerId: String,
  204. * offerId: String,
  205. * assetSold: {
  206. * type: 'native|credit_alphanum4|credit_alphanum12',
  207. *
  208. * // these are only present if the asset is not native
  209. * assetCode: String,
  210. * issuer: String,
  211. * },
  212. *
  213. * // same shape as assetSold
  214. * assetBought: {}
  215. * ],
  216. *
  217. * // What effect your manageOffer op had
  218. * effect: "manageOfferCreated|manageOfferUpdated|manageOfferDeleted",
  219. *
  220. * // Whether your offer immediately got matched and filled
  221. * wasImmediatelyFilled: Boolean,
  222. *
  223. * // Whether your offer immediately got deleted, if for example the order was too small
  224. * wasImmediatelyDeleted: Boolean,
  225. *
  226. * // Whether the offer was partially, but not completely, filled
  227. * wasPartiallyFilled: Boolean,
  228. *
  229. * // The full requested amount of the offer is open for matching
  230. * isFullyOpen: Boolean,
  231. *
  232. * // The total amount of tokens bought / sold during transaction execution
  233. * amountBought: Number,
  234. * amountSold: Number,
  235. *
  236. * // if the offer was created, updated, or partially filled, this is
  237. * // the outstanding offer
  238. * currentOffer: {
  239. * offerId: String,
  240. * amount: String,
  241. * price: {
  242. * n: String,
  243. * d: String,
  244. * },
  245. *
  246. * selling: {
  247. * type: 'native|credit_alphanum4|credit_alphanum12',
  248. *
  249. * // these are only present if the asset is not native
  250. * assetCode: String,
  251. * issuer: String,
  252. * },
  253. *
  254. * // same as `selling`
  255. * buying: {},
  256. * },
  257. *
  258. * // the index of this particular operation in the op stack
  259. * operationIndex: Number
  260. * }
  261. * ]
  262. * }
  263. *
  264. * @see {@link https://developers.stellar.org/docs/data/horizon/api-reference/resources/submit-a-transaction|Submit a Transaction}
  265. * @param {Transaction|FeeBumpTransaction} transaction - The transaction to submit.
  266. * @param {object} [opts] Options object
  267. * @param {boolean} [opts.skipMemoRequiredCheck] - Allow skipping memo
  268. * required check, default: `false`. See
  269. * [SEP0029](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0029.md).
  270. * @returns {Promise} Promise that resolves or rejects with response from
  271. * horizon.
  272. */
  273. async submitTransaction(transaction, opts = {
  274. skipMemoRequiredCheck: false
  275. }) {
  276. // only check for memo required if skipMemoRequiredCheck is false and the transaction doesn't include a memo.
  277. if (!opts.skipMemoRequiredCheck) {
  278. await this.checkMemoRequired(transaction);
  279. }
  280. const tx = encodeURIComponent(transaction.toEnvelope().toXDR().toString("base64"));
  281. return _horizon_axios_client.default.post((0, _urijs.default)(this.serverURL).segment("transactions").toString(), `tx=${tx}`, {
  282. timeout: SUBMIT_TRANSACTION_TIMEOUT
  283. }).then(response => {
  284. if (!response.data.result_xdr) {
  285. return response.data;
  286. }
  287. const responseXDR = _stellarBase.xdr.TransactionResult.fromXDR(response.data.result_xdr, "base64");
  288. // TODO: fix stellar-base types.
  289. const results = responseXDR.result().value();
  290. let offerResults;
  291. let hasManageOffer;
  292. if (results.length) {
  293. offerResults = results
  294. // TODO: fix stellar-base types.
  295. .map((result, i) => {
  296. if (result.value().switch().name !== "manageBuyOffer" && result.value().switch().name !== "manageSellOffer") {
  297. return null;
  298. }
  299. hasManageOffer = true;
  300. let amountBought = new _bignumber.default(0);
  301. let amountSold = new _bignumber.default(0);
  302. const offerSuccess = result.value().value().success();
  303. const offersClaimed = offerSuccess.offersClaimed()
  304. // TODO: fix stellar-base types.
  305. .map(offerClaimedAtom => {
  306. const offerClaimed = offerClaimedAtom.value();
  307. let sellerId = "";
  308. switch (offerClaimedAtom.switch()) {
  309. case _stellarBase.xdr.ClaimAtomType.claimAtomTypeV0():
  310. sellerId = _stellarBase.StrKey.encodeEd25519PublicKey(offerClaimed.sellerEd25519());
  311. break;
  312. case _stellarBase.xdr.ClaimAtomType.claimAtomTypeOrderBook():
  313. sellerId = _stellarBase.StrKey.encodeEd25519PublicKey(offerClaimed.sellerId().ed25519());
  314. break;
  315. // It shouldn't be possible for a claimed offer to have type
  316. // claimAtomTypeLiquidityPool:
  317. //
  318. // https://github.com/stellar/stellar-core/blob/c5f6349b240818f716617ca6e0f08d295a6fad9a/src/transactions/TransactionUtils.cpp#L1284
  319. //
  320. // However, you can never be too careful.
  321. default:
  322. throw new Error(`Invalid offer result type: ${offerClaimedAtom.switch()}`);
  323. }
  324. const claimedOfferAmountBought = new _bignumber.default(
  325. // amountBought is a js-xdr hyper
  326. offerClaimed.amountBought().toString());
  327. const claimedOfferAmountSold = new _bignumber.default(
  328. // amountBought is a js-xdr hyper
  329. offerClaimed.amountSold().toString());
  330. // This is an offer that was filled by the one just submitted.
  331. // So this offer has an _opposite_ bought/sold frame of ref
  332. // than from what we just submitted!
  333. // So add this claimed offer's bought to the SOLD count and vice v
  334. amountBought = amountBought.plus(claimedOfferAmountSold);
  335. amountSold = amountSold.plus(claimedOfferAmountBought);
  336. const sold = _stellarBase.Asset.fromOperation(offerClaimed.assetSold());
  337. const bought = _stellarBase.Asset.fromOperation(offerClaimed.assetBought());
  338. const assetSold = {
  339. type: sold.getAssetType(),
  340. assetCode: sold.getCode(),
  341. issuer: sold.getIssuer()
  342. };
  343. const assetBought = {
  344. type: bought.getAssetType(),
  345. assetCode: bought.getCode(),
  346. issuer: bought.getIssuer()
  347. };
  348. return {
  349. sellerId,
  350. offerId: offerClaimed.offerId().toString(),
  351. assetSold,
  352. amountSold: getAmountInLumens(claimedOfferAmountSold),
  353. assetBought,
  354. amountBought: getAmountInLumens(claimedOfferAmountBought)
  355. };
  356. });
  357. const effect = offerSuccess.offer().switch().name;
  358. let currentOffer;
  359. if (typeof offerSuccess.offer().value === "function" && offerSuccess.offer().value()) {
  360. const offerXDR = offerSuccess.offer().value();
  361. currentOffer = {
  362. offerId: offerXDR.offerId().toString(),
  363. selling: {},
  364. buying: {},
  365. amount: getAmountInLumens(offerXDR.amount().toString()),
  366. price: {
  367. n: offerXDR.price().n(),
  368. d: offerXDR.price().d()
  369. }
  370. };
  371. const selling = _stellarBase.Asset.fromOperation(offerXDR.selling());
  372. currentOffer.selling = {
  373. type: selling.getAssetType(),
  374. assetCode: selling.getCode(),
  375. issuer: selling.getIssuer()
  376. };
  377. const buying = _stellarBase.Asset.fromOperation(offerXDR.buying());
  378. currentOffer.buying = {
  379. type: buying.getAssetType(),
  380. assetCode: buying.getCode(),
  381. issuer: buying.getIssuer()
  382. };
  383. }
  384. return {
  385. offersClaimed,
  386. effect,
  387. operationIndex: i,
  388. currentOffer,
  389. // this value is in stroops so divide it out
  390. amountBought: getAmountInLumens(amountBought),
  391. amountSold: getAmountInLumens(amountSold),
  392. isFullyOpen: !offersClaimed.length && effect !== "manageOfferDeleted",
  393. wasPartiallyFilled: !!offersClaimed.length && effect !== "manageOfferDeleted",
  394. wasImmediatelyFilled: !!offersClaimed.length && effect === "manageOfferDeleted",
  395. wasImmediatelyDeleted: !offersClaimed.length && effect === "manageOfferDeleted"
  396. };
  397. })
  398. // TODO: fix stellar-base types.
  399. .filter(result => !!result);
  400. }
  401. return {
  402. ...response.data,
  403. offerResults: hasManageOffer ? offerResults : undefined
  404. };
  405. }).catch(response => {
  406. if (response instanceof Error) {
  407. return Promise.reject(response);
  408. }
  409. return Promise.reject(new _errors.BadResponseError(`Transaction submission failed. Server responded: ${response.status} ${response.statusText}`, response.data));
  410. });
  411. }
  412. /**
  413. * Submits an asynchronous transaction to the network. Unlike the synchronous version, which blocks
  414. * and waits for the transaction to be ingested in Horizon, this endpoint relays the response from
  415. * core directly back to the user.
  416. *
  417. * By default, this function calls {@link HorizonServer#checkMemoRequired}, you can
  418. * skip this check by setting the option `skipMemoRequiredCheck` to `true`.
  419. *
  420. * @see [Submit-Async-Transaction](https://developers.stellar.org/docs/data/horizon/api-reference/resources/submit-async-transaction)
  421. * @param {Transaction|FeeBumpTransaction} transaction - The transaction to submit.
  422. * @param {object} [opts] Options object
  423. * @param {boolean} [opts.skipMemoRequiredCheck] - Allow skipping memo
  424. * required check, default: `false`. See
  425. * [SEP0029](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0029.md).
  426. * @returns {Promise} Promise that resolves or rejects with response from
  427. * horizon.
  428. */
  429. async submitAsyncTransaction(transaction, opts = {
  430. skipMemoRequiredCheck: false
  431. }) {
  432. // only check for memo required if skipMemoRequiredCheck is false and the transaction doesn't include a memo.
  433. if (!opts.skipMemoRequiredCheck) {
  434. await this.checkMemoRequired(transaction);
  435. }
  436. const tx = encodeURIComponent(transaction.toEnvelope().toXDR().toString("base64"));
  437. return _horizon_axios_client.default.post((0, _urijs.default)(this.serverURL).segment("transactions_async").toString(), `tx=${tx}`).then(response => response.data).catch(response => {
  438. if (response instanceof Error) {
  439. return Promise.reject(response);
  440. }
  441. return Promise.reject(new _errors.BadResponseError(`Transaction submission failed. Server responded: ${response.status} ${response.statusText}`, response.data));
  442. });
  443. }
  444. /**
  445. * @returns {AccountCallBuilder} New {@link AccountCallBuilder} object configured by a current Horizon server configuration.
  446. */
  447. accounts() {
  448. return new _account_call_builder.AccountCallBuilder((0, _urijs.default)(this.serverURL));
  449. }
  450. /**
  451. * @returns {ClaimableBalanceCallBuilder} New {@link ClaimableBalanceCallBuilder} object configured by a current Horizon server configuration.
  452. */
  453. claimableBalances() {
  454. return new _claimable_balances_call_builder.ClaimableBalanceCallBuilder((0, _urijs.default)(this.serverURL));
  455. }
  456. /**
  457. * @returns {LedgerCallBuilder} New {@link LedgerCallBuilder} object configured by a current Horizon server configuration.
  458. */
  459. ledgers() {
  460. return new _ledger_call_builder.LedgerCallBuilder((0, _urijs.default)(this.serverURL));
  461. }
  462. /**
  463. * @returns {TransactionCallBuilder} New {@link TransactionCallBuilder} object configured by a current Horizon server configuration.
  464. */
  465. transactions() {
  466. return new _transaction_call_builder.TransactionCallBuilder((0, _urijs.default)(this.serverURL));
  467. }
  468. /**
  469. * People on the Stellar network can make offers to buy or sell assets. This endpoint represents all the offers on the DEX.
  470. *
  471. * You can query all offers for account using the function `.accountId`.
  472. *
  473. * @example
  474. * server.offers()
  475. * .forAccount(accountId).call()
  476. * .then(function(offers) {
  477. * console.log(offers);
  478. * });
  479. *
  480. * @returns {OfferCallBuilder} New {@link OfferCallBuilder} object
  481. */
  482. offers() {
  483. return new _offer_call_builder.OfferCallBuilder((0, _urijs.default)(this.serverURL));
  484. }
  485. /**
  486. * @param {Asset} selling Asset being sold
  487. * @param {Asset} buying Asset being bought
  488. * @returns {OrderbookCallBuilder} New {@link OrderbookCallBuilder} object configured by a current Horizon server configuration.
  489. */
  490. orderbook(selling, buying) {
  491. return new _orderbook_call_builder.OrderbookCallBuilder((0, _urijs.default)(this.serverURL), selling, buying);
  492. }
  493. /**
  494. * Returns
  495. * @returns {TradesCallBuilder} New {@link TradesCallBuilder} object configured by a current Horizon server configuration.
  496. */
  497. trades() {
  498. return new _trades_call_builder.TradesCallBuilder((0, _urijs.default)(this.serverURL));
  499. }
  500. /**
  501. * @returns {OperationCallBuilder} New {@link OperationCallBuilder} object configured by a current Horizon server configuration.
  502. */
  503. operations() {
  504. return new _operation_call_builder.OperationCallBuilder((0, _urijs.default)(this.serverURL));
  505. }
  506. /**
  507. * @returns {LiquidityPoolCallBuilder} New {@link LiquidityPoolCallBuilder}
  508. * object configured to the current Horizon server settings.
  509. */
  510. liquidityPools() {
  511. return new _liquidity_pool_call_builder.LiquidityPoolCallBuilder((0, _urijs.default)(this.serverURL));
  512. }
  513. /**
  514. * The Stellar Network allows payments to be made between assets through path
  515. * payments. A strict receive path payment specifies a series of assets to
  516. * route a payment through, from source asset (the asset debited from the
  517. * payer) to destination asset (the asset credited to the payee).
  518. *
  519. * A strict receive path search is specified using:
  520. *
  521. * * The destination address.
  522. * * The source address or source assets.
  523. * * The asset and amount that the destination account should receive.
  524. *
  525. * As part of the search, horizon will load a list of assets available to the
  526. * source address and will find any payment paths from those source assets to
  527. * the desired destination asset. The search's amount parameter will be used
  528. * to determine if there a given path can satisfy a payment of the desired
  529. * amount.
  530. *
  531. * If a list of assets is passed as the source, horizon will find any payment
  532. * paths from those source assets to the desired destination asset.
  533. *
  534. * @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.
  535. * @param {Asset} destinationAsset The destination asset.
  536. * @param {string} destinationAmount The amount, denominated in the destination asset, that any returned path should be able to satisfy.
  537. * @returns {StrictReceivePathCallBuilder} New {@link StrictReceivePathCallBuilder} object configured with the current Horizon server configuration.
  538. */
  539. strictReceivePaths(source, destinationAsset, destinationAmount) {
  540. return new _strict_receive_path_call_builder.StrictReceivePathCallBuilder((0, _urijs.default)(this.serverURL), source, destinationAsset, destinationAmount);
  541. }
  542. /**
  543. * The Stellar Network allows payments to be made between assets through path payments. A strict send path payment specifies a
  544. * series of assets to route a payment through, from source asset (the asset debited from the payer) to destination
  545. * asset (the asset credited to the payee).
  546. *
  547. * A strict send path search is specified using:
  548. *
  549. * The asset and amount that is being sent.
  550. * The destination account or the destination assets.
  551. *
  552. * @param {Asset} sourceAsset The asset to be sent.
  553. * @param {string} sourceAmount The amount, denominated in the source asset, that any returned path should be able to satisfy.
  554. * @param {string|Asset[]} destination The destination account or the destination assets.
  555. * @returns {StrictSendPathCallBuilder} New {@link StrictSendPathCallBuilder} object configured with the current Horizon server configuration.
  556. */
  557. strictSendPaths(sourceAsset, sourceAmount, destination) {
  558. return new _strict_send_path_call_builder.StrictSendPathCallBuilder((0, _urijs.default)(this.serverURL), sourceAsset, sourceAmount, destination);
  559. }
  560. /**
  561. * @returns {PaymentCallBuilder} New {@link PaymentCallBuilder} instance configured with the current
  562. * Horizon server configuration.
  563. */
  564. payments() {
  565. return new _payment_call_builder.PaymentCallBuilder((0, _urijs.default)(this.serverURL));
  566. }
  567. /**
  568. * @returns {EffectCallBuilder} New {@link EffectCallBuilder} instance configured with the current
  569. * Horizon server configuration
  570. */
  571. effects() {
  572. return new _effect_call_builder.EffectCallBuilder((0, _urijs.default)(this.serverURL));
  573. }
  574. /**
  575. * @param {string} address The Stellar ID that you want Friendbot to send lumens to
  576. * @returns {FriendbotBuilder} New {@link FriendbotBuilder} instance configured with the current
  577. * Horizon server configuration
  578. * @private
  579. */
  580. friendbot(address) {
  581. return new _friendbot_builder.FriendbotBuilder((0, _urijs.default)(this.serverURL), address);
  582. }
  583. /**
  584. * Get a new {@link AssetsCallBuilder} instance configured with the current
  585. * Horizon server configuration.
  586. * @returns {AssetsCallBuilder} New AssetsCallBuilder instance
  587. */
  588. assets() {
  589. return new _assets_call_builder.AssetsCallBuilder((0, _urijs.default)(this.serverURL));
  590. }
  591. /**
  592. * Fetches an account's most current state in the ledger, then creates and
  593. * returns an {@link AccountResponse} object.
  594. *
  595. * @param {string} accountId - The account to load.
  596. *
  597. * @returns {Promise} Returns a promise to the {@link AccountResponse} object
  598. * with populated sequence number.
  599. */
  600. async loadAccount(accountId) {
  601. const res = await this.accounts().accountId(accountId).call();
  602. return new _account_response.AccountResponse(res);
  603. }
  604. /**
  605. *
  606. * @param {Asset} base base asset
  607. * @param {Asset} counter counter asset
  608. * @param {number} start_time lower time boundary represented as millis since epoch
  609. * @param {number} end_time upper time boundary represented as millis since epoch
  610. * @param {number} 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).
  611. * @param {number} 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.
  612. * Returns new {@link TradeAggregationCallBuilder} object configured with the current Horizon server configuration.
  613. * @returns {TradeAggregationCallBuilder} New TradeAggregationCallBuilder instance
  614. */
  615. tradeAggregation(base, counter, start_time, end_time, resolution, offset) {
  616. return new _trade_aggregation_call_builder.TradeAggregationCallBuilder((0, _urijs.default)(this.serverURL), base, counter, start_time, end_time, resolution, offset);
  617. }
  618. /**
  619. * Check if any of the destination accounts requires a memo.
  620. *
  621. * This function implements a memo required check as defined in
  622. * [SEP-29](https://stellar.org/protocol/sep-29). It will load each account
  623. * which is the destination and check if it has the data field
  624. * `config.memo_required` set to `"MQ=="`.
  625. *
  626. * Each account is checked sequentially instead of loading multiple accounts
  627. * at the same time from Horizon.
  628. *
  629. * @see {@link https://stellar.org/protocol/sep-29|SEP-29: Account Memo Requirements}
  630. * @param {Transaction} transaction - The transaction to check.
  631. * @returns {Promise<void, Error>} - If any of the destination account
  632. * requires a memo, the promise will throw {@link AccountRequiresMemoError}.
  633. * @throws {AccountRequiresMemoError}
  634. */
  635. async checkMemoRequired(transaction) {
  636. if (transaction instanceof _stellarBase.FeeBumpTransaction) {
  637. transaction = transaction.innerTransaction;
  638. }
  639. if (transaction.memo.type !== "none") {
  640. return;
  641. }
  642. const destinations = new Set();
  643. /* eslint-disable no-continue */
  644. for (let i = 0; i < transaction.operations.length; i += 1) {
  645. const operation = transaction.operations[i];
  646. switch (operation.type) {
  647. case "payment":
  648. case "pathPaymentStrictReceive":
  649. case "pathPaymentStrictSend":
  650. case "accountMerge":
  651. break;
  652. default:
  653. continue;
  654. }
  655. const destination = operation.destination;
  656. if (destinations.has(destination)) {
  657. continue;
  658. }
  659. destinations.add(destination);
  660. // skip M account checks since it implies a memo
  661. if (destination.startsWith("M")) {
  662. continue;
  663. }
  664. try {
  665. // eslint-disable-next-line no-await-in-loop
  666. const account = await this.loadAccount(destination);
  667. if (account.data_attr["config.memo_required"] === ACCOUNT_REQUIRES_MEMO) {
  668. throw new _errors.AccountRequiresMemoError("account requires memo", destination, i);
  669. }
  670. } catch (e) {
  671. if (e instanceof _errors.AccountRequiresMemoError) {
  672. throw e;
  673. }
  674. // fail if the error is different to account not found
  675. if (!(e instanceof _errors.NotFoundError)) {
  676. throw e;
  677. }
  678. continue;
  679. }
  680. }
  681. /* eslint-enable no-continue */
  682. }
  683. }
  684. /**
  685. * Options for configuring connections to Horizon servers.
  686. * @typedef {object} Options
  687. * @memberof module:Horizon.Server
  688. * @property {boolean} [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.
  689. * @property {string} [appName] Allow set custom header `X-App-Name`, default: `undefined`.
  690. * @property {string} [appVersion] Allow set custom header `X-App-Version`, default: `undefined`.
  691. * @property {string} [authToken] Allow set custom header `X-Auth-Token`, default: `undefined`.
  692. */
  693. exports.HorizonServer = HorizonServer;