@stellar/stellar-sdk
Version:
A library for working with the Stellar network, including communication with the Horizon and Soroban RPC servers.
738 lines (735 loc) • 29.6 kB
JavaScript
import BigNumber from '../base/util/bignumber.js';
import '../node_modules/.pnpm/@stellar_js-xdr@4.0.0/node_modules/@stellar/js-xdr/src/int.js';
import '../node_modules/.pnpm/@stellar_js-xdr@4.0.0/node_modules/@stellar/js-xdr/src/hyper.js';
import '../node_modules/.pnpm/@stellar_js-xdr@4.0.0/node_modules/@stellar/js-xdr/src/unsigned-int.js';
import '../node_modules/.pnpm/@stellar_js-xdr@4.0.0/node_modules/@stellar/js-xdr/src/unsigned-hyper.js';
import '../node_modules/.pnpm/@stellar_js-xdr@4.0.0/node_modules/@stellar/js-xdr/src/xdr-type.js';
import 'buffer';
import types from '../base/generated/curr_generated.js';
import '@noble/hashes/sha2.js';
import '../base/signing.js';
import { Asset } from '../base/asset.js';
import '../base/keypair.js';
import { StrKey } from '../base/strkey.js';
import '../base/util/continued_fraction.js';
import { FeeBumpTransaction } from '../base/fee_bump_transaction.js';
import '../base/transaction_builder.js';
import '../base/muxed_account.js';
import '../base/scval.js';
import '../base/numbers/uint128.js';
import '../base/numbers/uint256.js';
import '../base/numbers/int128.js';
import '../base/numbers/int256.js';
import { CallBuilder } from './call_builder.js';
import { Config } from '../config.js';
import { NotFoundError } from '../errors/not_found.js';
import { BadResponseError } from '../errors/bad_response.js';
import { TransactionFailedError } from '../errors/transaction_failed.js';
import { AccountRequiresMemoError } from '../errors/account_requires_memo.js';
import { wrapHttpError } from '../errors/wrap_http_error.js';
import { AccountCallBuilder } from './account_call_builder.js';
import { AccountResponse } from './account_response.js';
import { AssetsCallBuilder } from './assets_call_builder.js';
import { ClaimableBalanceCallBuilder } from './claimable_balances_call_builder.js';
import { EffectCallBuilder } from './effect_call_builder.js';
import { FriendbotBuilder } from './friendbot_builder.js';
import { LedgerCallBuilder } from './ledger_call_builder.js';
import { LiquidityPoolCallBuilder } from './liquidity_pool_call_builder.js';
import { OfferCallBuilder } from './offer_call_builder.js';
import { OperationCallBuilder } from './operation_call_builder.js';
import { OrderbookCallBuilder } from './orderbook_call_builder.js';
import { PaymentCallBuilder } from './payment_call_builder.js';
import { StrictReceivePathCallBuilder } from './strict_receive_path_call_builder.js';
import { StrictSendPathCallBuilder } from './strict_send_path_call_builder.js';
import { TradeAggregationCallBuilder } from './trade_aggregation_call_builder.js';
import { TradesCallBuilder } from './trades_call_builder.js';
import { TransactionCallBuilder } from './transaction_call_builder.js';
import { createHttpClient, getCurrentServerTime } from './horizon_axios_client.js';
const SUBMIT_TRANSACTION_TIMEOUT = 60 * 1e3;
const STROOPS_IN_LUMEN = 1e7;
const ACCOUNT_REQUIRES_MEMO = "MQ==";
function getAmountInLumens(amt) {
return new BigNumber(amt).div(STROOPS_IN_LUMEN).toString();
}
function toSubmissionError(error) {
return wrapHttpError(error, (details) => {
const message = `Transaction submission failed. Server responded: ${details.status} ${details.statusText}`;
return details.data?.extras?.result_codes ? new TransactionFailedError(message, details) : new BadResponseError(message, details);
});
}
class HorizonServer {
/**
* Horizon Server URL (ex. `https://horizon-testnet.stellar.org`)
*
* TODO: Solve `this.serverURL`.
*/
serverURL;
/**
* HTTP client instance for making requests to Horizon.
* Exposes interceptors, defaults, and other configuration options.
*
* @example
* ```ts
* // Add authentication header
* server.httpClient.defaults.headers['Authorization'] = 'Bearer token';
*
* // Add request interceptor
* server.httpClient.interceptors.request.use((config) => {
* console.log('Request:', config.url);
* return config;
* });
* ```
*/
httpClient;
constructor(serverURL, opts = {}) {
this.serverURL = new URL(serverURL);
const allowHttp = typeof opts.allowHttp === "undefined" ? Config.isAllowHttp() : opts.allowHttp;
const customHeaders = {};
if (opts.appName) {
customHeaders["X-App-Name"] = opts.appName;
}
if (opts.appVersion) {
customHeaders["X-App-Version"] = opts.appVersion;
}
if (opts.authToken) {
customHeaders["X-Auth-Token"] = opts.authToken;
}
if (opts.headers) {
Object.assign(customHeaders, opts.headers);
}
this.httpClient = createHttpClient(customHeaders);
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
* ```ts
* 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();
* ```
*
* @param seconds - Number of seconds past the current time to wait.
* @param _isRetry - (optional) 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 that resolves a `Timebounds` object
* (with the shape `{ minTime: 0, maxTime: N }`) that you can set the `timebounds` option to.
*/
async fetchTimebounds(seconds, _isRetry = false) {
const serverPort = this.serverURL.port;
const serverKey = serverPort ? `${this.serverURL.hostname}:${serverPort}` : this.serverURL.hostname;
const currentTime = getCurrentServerTime(serverKey);
if (currentTime) {
return {
minTime: 0,
maxTime: currentTime + seconds
};
}
if (_isRetry) {
return {
minTime: 0,
maxTime: Math.floor((/* @__PURE__ */ new Date()).getTime() / 1e3) + seconds
};
}
await this.httpClient.get(this.serverURL.toString());
return 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 that resolves to the base fee.
*/
async fetchBaseFee() {
const response = await this.feeStats();
return parseInt(response.last_ledger_base_fee, 10) || 100;
}
/**
* Fetch the fee stats endpoint.
* @see {@link https://developers.stellar.org/docs/data/horizon/api-reference/aggregations/fee-stats | Fee Stats}
* @returns Promise that resolves to the fee stats returned by Horizon.
*/
async feeStats() {
const cb = new CallBuilder(
this.serverURL,
this.httpClient
);
cb.filter.push(["fee_stats"]);
return cb.call();
}
/**
* Fetch the Horizon server's root endpoint.
* @returns Promise that resolves to the root endpoint returned by Horizon.
*/
async root() {
const cb = new CallBuilder(
this.serverURL,
this.httpClient
);
return cb.call();
}
/**
* Submits a transaction to the network.
*
* By default this function calls {@link Horizon.Server.checkMemoRequired}, you can
* skip this check by setting the option `skipMemoRequiredCheck` to `true`.
*
* 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.
*
* 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.
*
* @example
* ```ts
* 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
* }
* ]
* }
* ```
*
* @see {@link https://developers.stellar.org/docs/data/horizon/api-reference/resources/submit-a-transaction | Submit a Transaction}
* @param transaction - The transaction to submit.
* @param opts - (optional) Options object
* - `skipMemoRequiredCheck` (optional): Allow skipping memo
* required check, default: `false`. See
* [SEP0029](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0029.md).
* @returns Promise that resolves with the response from Horizon. Rejects
* with a {@link TransactionFailedError} when Horizon reports transaction
* result codes, a {@link BadResponseError} for any other HTTP error
* response (the underlying client error is preserved as `cause` on both),
* or the original error for network-level failures.
*/
async submitTransaction(transaction, opts = {
skipMemoRequiredCheck: false
}) {
if (!opts.skipMemoRequiredCheck) {
await this.checkMemoRequired(transaction);
}
const tx = encodeURIComponent(
transaction.toEnvelope().toXDR().toString("base64")
);
const url = new URL(this.serverURL);
url.pathname = url.pathname.split("/").concat(["transactions"]).filter((value) => value.length > 0).join("/");
return this.httpClient.post(url.toString(), `tx=${tx}`, {
timeout: SUBMIT_TRANSACTION_TIMEOUT,
headers: { "Content-Type": "application/x-www-form-urlencoded" }
}).then((response) => {
if (!response.data.result_xdr) {
return response.data;
}
const responseXDR = types.TransactionResult.fromXDR(
response.data.result_xdr,
"base64"
);
const results = responseXDR.result().value();
let offerResults;
let hasManageOffer;
if (results.length) {
offerResults = results.map((result, i) => {
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().map((offerClaimedAtom) => {
const offerClaimed = offerClaimedAtom.value();
let sellerId = "";
switch (offerClaimedAtom.switch()) {
case types.ClaimAtomType.claimAtomTypeV0():
sellerId = StrKey.encodeEd25519PublicKey(
offerClaimed.sellerEd25519()
);
break;
case types.ClaimAtomType.claimAtomTypeOrderBook():
sellerId = StrKey.encodeEd25519PublicKey(
offerClaimed.sellerId().ed25519()
);
break;
// It shouldn't be possible for a claimed offer to have type
// claimAtomTypeLiquidityPool:
//
// https://github.com/stellar/stellar-core/blob/c5f6349b240818f716617ca6e0f08d295a6fad9a/src/transactions/TransactionUtils.cpp#L1284
//
// However, you can never be too careful.
default:
throw new Error(
`Invalid offer result type: ${offerClaimedAtom.switch()}`
);
}
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()
);
amountBought = amountBought.plus(claimedOfferAmountSold);
amountSold = amountSold.plus(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,
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"
};
}).filter((result) => !!result);
}
return {
...response.data,
offerResults: hasManageOffer ? offerResults : void 0
};
}).catch((error) => Promise.reject(toSubmissionError(error)));
}
/**
* Submits an asynchronous transaction to the network. Unlike the synchronous version, which blocks
* and waits for the transaction to be ingested in Horizon, this endpoint relays the response from
* core directly back to the user.
*
* By default, this function calls {@link HorizonServer.checkMemoRequired}, you can
* skip this check by setting the option `skipMemoRequiredCheck` to `true`.
*
* @see [Submit-Async-Transaction](https://developers.stellar.org/docs/data/horizon/api-reference/resources/submit-async-transaction)
* @param transaction - The transaction to submit.
* @param opts - (optional) Options object
* - `skipMemoRequiredCheck` (optional): Allow skipping memo
* required check, default: `false`. See
* [SEP0029](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0029.md).
* @returns Promise that resolves with the response from Horizon. Rejects
* with a {@link TransactionFailedError} when Horizon reports transaction
* result codes, a {@link BadResponseError} for any other HTTP error
* response (the underlying client error is preserved as `cause` on both),
* or the original error for network-level failures.
*/
async submitAsyncTransaction(transaction, opts = {
skipMemoRequiredCheck: false
}) {
if (!opts.skipMemoRequiredCheck) {
await this.checkMemoRequired(transaction);
}
const tx = encodeURIComponent(
transaction.toEnvelope().toXDR().toString("base64")
);
const url = new URL(this.serverURL);
url.pathname = url.pathname.split("/").concat(["transactions_async"]).filter((value) => value.length > 0).join("/");
return this.httpClient.post(url.toString(), `tx=${tx}`, {
headers: { "Content-Type": "application/x-www-form-urlencoded" }
}).then((response) => response.data).catch((error) => Promise.reject(toSubmissionError(error)));
}
/**
* @returns New {@link AccountCallBuilder} object configured by a current Horizon server configuration.
*/
accounts() {
return new AccountCallBuilder(this.serverURL, this.httpClient);
}
/**
* @returns New {@link ClaimableBalanceCallBuilder} object configured by a current Horizon server configuration.
*/
claimableBalances() {
return new ClaimableBalanceCallBuilder(this.serverURL, this.httpClient);
}
/**
* @returns New {@link LedgerCallBuilder} object configured by a current Horizon server configuration.
*/
ledgers() {
return new LedgerCallBuilder(this.serverURL, this.httpClient);
}
/**
* @returns New {@link TransactionCallBuilder} object configured by a current Horizon server configuration.
*/
transactions() {
return new TransactionCallBuilder(this.serverURL, this.httpClient);
}
/**
* 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`.
*
* @example
* ```ts
* server.offers()
* .forAccount(accountId).call()
* .then(function(offers) {
* console.log(offers);
* });
* ```
*
* @returns New {@link OfferCallBuilder} object
*/
offers() {
return new OfferCallBuilder(this.serverURL, this.httpClient);
}
/**
* @param selling - Asset being sold
* @param buying - Asset being bought
* @returns New {@link OrderbookCallBuilder} object configured by a current Horizon server configuration.
*/
orderbook(selling, buying) {
return new OrderbookCallBuilder(
this.serverURL,
this.httpClient,
selling,
buying
);
}
/**
* Returns
* @returns New {@link TradesCallBuilder} object configured by a current Horizon server configuration.
*/
trades() {
return new TradesCallBuilder(this.serverURL, this.httpClient);
}
/**
* @returns New {@link OperationCallBuilder} object configured by a current Horizon server configuration.
*/
operations() {
return new OperationCallBuilder(this.serverURL, this.httpClient);
}
/**
* @returns New {@link LiquidityPoolCallBuilder}
* object configured to the current Horizon server settings.
*/
liquidityPools() {
return new LiquidityPoolCallBuilder(this.serverURL, this.httpClient);
}
/**
* 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 source - The sender's account ID or a list of assets. Any returned path will use a source that the sender can hold.
* @param destinationAsset - The destination asset.
* @param destinationAmount - The amount, denominated in the destination asset, that any returned path should be able to satisfy.
* @returns New {@link StrictReceivePathCallBuilder} object configured with the current Horizon server configuration.
*/
strictReceivePaths(source, destinationAsset, destinationAmount) {
return new StrictReceivePathCallBuilder(
this.serverURL,
this.httpClient,
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 sourceAsset - The asset to be sent.
* @param sourceAmount - The amount, denominated in the source asset, that any returned path should be able to satisfy.
* @param destination - The destination account or the destination assets.
* @returns New {@link StrictSendPathCallBuilder} object configured with the current Horizon server configuration.
*/
strictSendPaths(sourceAsset, sourceAmount, destination) {
return new StrictSendPathCallBuilder(
this.serverURL,
this.httpClient,
sourceAsset,
sourceAmount,
destination
);
}
/**
* @returns New {@link PaymentCallBuilder} instance configured with the current
* Horizon server configuration.
*/
payments() {
return new PaymentCallBuilder(this.serverURL, this.httpClient);
}
/**
* @returns New {@link EffectCallBuilder} instance configured with the current
* Horizon server configuration
*/
effects() {
return new EffectCallBuilder(this.serverURL, this.httpClient);
}
/**
* @param address - The Stellar ID that you want Friendbot to send lumens to
* @returns New {@link FriendbotBuilder} instance configured with the current
* Horizon server configuration
*/
friendbot(address) {
return new FriendbotBuilder(this.serverURL, this.httpClient, address);
}
/**
* Get a new {@link AssetsCallBuilder} instance configured with the current
* Horizon server configuration.
* @returns New AssetsCallBuilder instance
*/
assets() {
return new AssetsCallBuilder(this.serverURL, this.httpClient);
}
/**
* Fetches an account's most current state in the ledger, then creates and
* returns an {@link AccountResponse} object.
*
* @param accountId - The account to load.
*
* @returns Returns a promise to the {@link AccountResponse} object
* with populated sequence number.
*/
async loadAccount(accountId) {
const res = await this.accounts().accountId(accountId).call();
return new AccountResponse(res);
}
/**
*
* @param base - base asset
* @param counter - counter asset
* @param start_time - lower time boundary represented as millis since epoch
* @param end_time - upper time boundary represented as millis since epoch
* @param 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 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 New TradeAggregationCallBuilder instance
*/
tradeAggregation(base, counter, start_time, end_time, resolution, offset) {
return new TradeAggregationCallBuilder(
this.serverURL,
this.httpClient,
base,
counter,
start_time,
end_time,
resolution,
offset
);
}
/**
* Check if any of the destination accounts requires a memo.
*
* This function implements a memo required check as defined in
* [SEP-29](https://stellar.org/protocol/sep-29). It will load each account
* which is the destination and check if it has the data field
* `config.memo_required` set to `"MQ=="`.
*
* Each account is checked sequentially instead of loading multiple accounts
* at the same time from Horizon.
*
* @see {@link https://stellar.org/protocol/sep-29 | SEP-29: Account Memo Requirements}
* @param transaction - The transaction to check.
* @returns - If any of the destination account
* requires a memo, the promise will throw {@link AccountRequiresMemoError}.
* @throws */
async checkMemoRequired(transaction) {
if (transaction instanceof FeeBumpTransaction) {
transaction = transaction.innerTransaction;
}
if (transaction.memo.type !== "none") {
return;
}
const destinations = /* @__PURE__ */ new Set();
for (let i = 0; i < transaction.operations.length; i += 1) {
const operation = transaction.operations[i];
switch (operation.type) {
case "payment":
case "pathPaymentStrictReceive":
case "pathPaymentStrictSend":
case "accountMerge":
break;
default:
continue;
}
const destination = operation.destination;
if (destinations.has(destination)) {
continue;
}
destinations.add(destination);
if (destination.startsWith("M")) {
continue;
}
try {
const account = await this.loadAccount(destination);
if (account.data_attr["config.memo_required"] === ACCOUNT_REQUIRES_MEMO) {
throw new AccountRequiresMemoError(
"account requires memo",
destination,
i
);
}
} catch (e) {
if (e instanceof AccountRequiresMemoError) {
throw e;
}
if (!(e instanceof NotFoundError)) {
throw e;
}
continue;
}
}
}
}
export { HorizonServer, SUBMIT_TRANSACTION_TIMEOUT };
//# sourceMappingURL=server.js.map