lib/soroban/server.js

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.Server = exports.SUBMIT_TRANSACTION_TIMEOUT = exports.Durability = void 0;
var _urijs = _interopRequireDefault(require("urijs"));
var _stellarBase = require("@stellar/stellar-base");
var _axios = _interopRequireDefault(require("./axios"));
var jsonrpc = _interopRequireWildcard(require("./jsonrpc"));
var _api = require("./api");
var _transaction = require("./transaction");
var _parsers = require("./parsers");
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); }
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; }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/* tslint:disable:variable-name no-namespace */

const SUBMIT_TRANSACTION_TIMEOUT = exports.SUBMIT_TRANSACTION_TIMEOUT = 60 * 1000;

/** Specifies the durability namespace of contract-related ledger entries. */
let Durability = exports.Durability = /*#__PURE__*/function (Durability) {
  Durability["Temporary"] = "temporary";
  Durability["Persistent"] = "persistent";
  return Durability;
}({});
/**
 * Handles the network connection to a Soroban RPC instance, exposing an
 * interface for requests to that instance.
 *
 * @constructor
 *
 * @param {string} serverURL Soroban-RPC Server URL (ex.
 *    `http://localhost:8000/soroban/rpc`).
 * @param {object} [opts] Options object
 * @param {boolean} [opts.allowHttp]  allows connecting to insecure 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 {Record<string, string>} [opts.headers] allows setting custom headers
 *
 * @see https://soroban.stellar.org/api/methods
 */
class Server {
  /** Soroban RPC Server URL (ex. `http://localhost:8000/soroban/rpc`). */

  constructor(serverURL, opts = {}) {
    this.serverURL = (0, _urijs.default)(serverURL);
    if (opts.headers && Object.keys(opts.headers).length === 0) {
      _axios.default.interceptors.request.use(config => {
        // merge the custom headers into any existing headers
        config.headers = Object.assign(config.headers, opts.headers);
        return config;
      });
    }
    if (this.serverURL.protocol() !== 'https' && !opts.allowHttp) {
      throw new Error("Cannot connect to insecure Soroban RPC server if `allowHttp` isn't set");
    }
  }

  /**
   * Fetch a minimal set of current info about a Stellar account.
   *
   * Needed to get the current sequence number for the account so you can build
   * a successful transaction with {@link TransactionBuilder}.
   *
   * @param {string} address - The public address of the account to load.
   *
   * @returns {Promise<Account>}  a promise to the {@link Account} object with
   *    a populated sequence number
   *
   * @see https://soroban.stellar.org/api/methods/getLedgerEntries
   * @example
   * const accountId = "GBZC6Y2Y7Q3ZQ2Y4QZJ2XZ3Z5YXZ6Z7Z2Y4QZJ2XZ3Z5YXZ6Z7Z2Y4";
   * server.getAccount(accountId).then((account) => {
   *   console.log("sequence:", account.sequence);
   * });
   */
  async getAccount(address) {
    const ledgerKey = _stellarBase.xdr.LedgerKey.account(new _stellarBase.xdr.LedgerKeyAccount({
      accountId: _stellarBase.Keypair.fromPublicKey(address).xdrPublicKey()
    }));
    const resp = await this.getLedgerEntries(ledgerKey);
    if (resp.entries.length === 0) {
      return Promise.reject({
        code: 404,
        message: `Account not found: ${address}`
      });
    }
    const accountEntry = resp.entries[0].val.account();
    return new _stellarBase.Account(address, accountEntry.seqNum().toString());
  }

  /**
   * General node health check.
   *
   * @returns {Promise<Api.GetHealthResponse>}   a promise to the
   *    {@link Api.GetHealthResponse} object with the status of the
   *    server (e.g. "healthy").
   *
   * @see https://soroban.stellar.org/api/methods/getHealth
   * @example
   * server.getHealth().then((health) => {
   *   console.log("status:", health.status);
   * });
   */
  async getHealth() {
    return jsonrpc.postObject(this.serverURL.toString(), 'getHealth');
  }

  /**
   * Reads the current value of contract data ledger entries directly.
   *
   * Allows you to directly inspect the current state of a contract. This is a
   * backup way to access your contract data which may not be available via
   * events or {@link Server.simulateTransaction}.
   *
   * @param {string|Address|Contract} contract    the contract ID containing the
   *    data to load as a strkey (`C...` form), a {@link Contract}, or an
   *    {@link Address} instance
   * @param {xdr.ScVal} key   the key of the contract data to load
   * @param {Durability} [durability=Durability.Persistent]   the "durability
   *    keyspace" that this ledger key belongs to, which is either 'temporary'
   *    or 'persistent' (the default), see {@link Durability}.
   *
   * @returns {Promise<Api.LedgerEntryResult>}   the current data value
   *
   * @warning If the data entry in question is a 'temporary' entry, it's
   *    entirely possible that it has expired out of existence.
   *
   * @see https://soroban.stellar.org/api/methods/getLedgerEntries
   * @example
   * const contractId = "CCJZ5DGASBWQXR5MPFCJXMBI333XE5U3FSJTNQU7RIKE3P5GN2K2WYD5";
   * const key = xdr.ScVal.scvSymbol("counter");
   * server.getContractData(contractId, key, Durability.Temporary).then(data => {
   *   console.log("value:", data.val);
   *   console.log("liveUntilLedgerSeq:", data.liveUntilLedgerSeq);
   *   console.log("lastModified:", data.lastModifiedLedgerSeq);
   *   console.log("latestLedger:", data.latestLedger);
   * });
   */
  async getContractData(contract, key, durability = Durability.Persistent) {
    // coalesce `contract` param variants to an ScAddress
    let scAddress;
    if (typeof contract === 'string') {
      scAddress = new _stellarBase.Contract(contract).address().toScAddress();
    } else if (contract instanceof _stellarBase.Address) {
      scAddress = contract.toScAddress();
    } else if (contract instanceof _stellarBase.Contract) {
      scAddress = contract.address().toScAddress();
    } else {
      throw new TypeError(`unknown contract type: ${contract}`);
    }
    let xdrDurability;
    switch (durability) {
      case Durability.Temporary:
        xdrDurability = _stellarBase.xdr.ContractDataDurability.temporary();
        break;
      case Durability.Persistent:
        xdrDurability = _stellarBase.xdr.ContractDataDurability.persistent();
        break;
      default:
        throw new TypeError(`invalid durability: ${durability}`);
    }
    let contractKey = _stellarBase.xdr.LedgerKey.contractData(new _stellarBase.xdr.LedgerKeyContractData({
      key,
      contract: scAddress,
      durability: xdrDurability
    }));
    return this.getLedgerEntries(contractKey).then(r => {
      if (r.entries.length === 0) {
        return Promise.reject({
          code: 404,
          message: `Contract data not found. Contract: ${_stellarBase.Address.fromScAddress(scAddress).toString()}, Key: ${key.toXDR('base64')}, Durability: ${durability}`
        });
      }
      return r.entries[0];
    });
  }

  /**
   * Reads the current value of arbitrary ledger entries directly.
   *
   * Allows you to directly inspect the current state of contracts, contract's
   * code, accounts, or any other ledger entries.
   *
   * To fetch a contract's WASM byte-code, built the appropriate
   * {@link xdr.LedgerKeyContractCode} ledger entry key (or see
   * {@link Contract.getFootprint}).
   *
   * @param {xdr.ScVal[]} keys  one or more ledger entry keys to load
   *
   * @returns {Promise<Api.GetLedgerEntriesResponse>}  the current
   *    on-chain values for the given ledger keys
   *
   * @see Server._getLedgerEntries
   * @see https://soroban.stellar.org/api/methods/getLedgerEntries
   * @example
   * const contractId = "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM";
   * const key = xdr.LedgerKey.contractData(new xdr.LedgerKeyContractData({
   *   contractId: StrKey.decodeContract(contractId),
   *   key: xdr.ScVal.scvSymbol("counter"),
   * }));
   *
   * server.getLedgerEntries([key]).then(response => {
   *   const ledgerData = response.entries[0];
   *   console.log("key:", ledgerData.key);
   *   console.log("value:", ledgerData.val);
   *   console.log("liveUntilLedgerSeq:", ledgerData.liveUntilLedgerSeq);
   *   console.log("lastModified:", ledgerData.lastModifiedLedgerSeq);
   *   console.log("latestLedger:", response.latestLedger);
   * });
   */
  async getLedgerEntries(...keys) {
    return this._getLedgerEntries(...keys).then(_parsers.parseRawLedgerEntries);
  }
  async _getLedgerEntries(...keys) {
    return jsonrpc.postObject(this.serverURL.toString(), 'getLedgerEntries', {
      keys: keys.map(k => k.toXDR('base64'))
    });
  }

  /**
   * Fetch the details of a submitted transaction.
   *
   * After submitting a transaction, clients should poll this to tell when the
   * transaction has completed.
   *
   * @param {string} hash   hex-encoded hash of the transaction to check
   *
   * @returns {Promise<Api.GetTransactionResponse>}  the status,
   *    result, and other details about the transaction
   *
   * @see https://soroban.stellar.org/api/methods/getTransaction
   * @example
   * const transactionHash = "c4515e3bdc0897f21cc5dbec8c82cf0a936d4741cb74a8e158eb51b9fb00411a";
   * server.getTransaction(transactionHash).then((tx) => {
   *   console.log("status:", tx.status);
   *   console.log("envelopeXdr:", tx.envelopeXdr);
   *   console.log("resultMetaXdr:", tx.resultMetaXdr);
   *   console.log("resultXdr:", tx.resultXdr);
   * });
   */
  async getTransaction(hash) {
    return this._getTransaction(hash).then(raw => {
      let foundInfo = {};
      if (raw.status !== _api.Api.GetTransactionStatus.NOT_FOUND) {
        const meta = _stellarBase.xdr.TransactionMeta.fromXDR(raw.resultMetaXdr, 'base64');
        foundInfo = {
          ledger: raw.ledger,
          createdAt: raw.createdAt,
          applicationOrder: raw.applicationOrder,
          feeBump: raw.feeBump,
          envelopeXdr: _stellarBase.xdr.TransactionEnvelope.fromXDR(raw.envelopeXdr, 'base64'),
          resultXdr: _stellarBase.xdr.TransactionResult.fromXDR(raw.resultXdr, 'base64'),
          resultMetaXdr: meta,
          ...(meta.switch() === 3 && meta.v3().sorobanMeta() !== null && raw.status === _api.Api.GetTransactionStatus.SUCCESS && {
            returnValue: meta.v3().sorobanMeta()?.returnValue()
          })
        };
      }
      const result = {
        status: raw.status,
        latestLedger: raw.latestLedger,
        latestLedgerCloseTime: raw.latestLedgerCloseTime,
        oldestLedger: raw.oldestLedger,
        oldestLedgerCloseTime: raw.oldestLedgerCloseTime,
        ...foundInfo
      };
      return result;
    });
  }
  async _getTransaction(hash) {
    return jsonrpc.postObject(this.serverURL.toString(), 'getTransaction', {
      hash
    });
  }

  /**
   * Fetch all events that match a given set of filters.
   *
   * The given filters (see {@link Api.EventFilter} for detailed fields)
   * are combined only in a logical OR fashion, and all of the fields in each
   * filter are optional.
   *
   * To page through events, use the `pagingToken` field on the relevant
   * {@link Api.EventResponse} object to set the `cursor` parameter.
   *
   * @param {Server.GetEventsRequest} request   event filters
   * @returns {Promise<Api.GetEventsResponse>}   a paginatable set of the
   *    events matching the given event filters
   *
   * @see https://soroban.stellar.org/api/methods/getEvents
   * @example
   * server.getEvents({
   *    startLedger: 1000,
   *    filters: [
   *     {
   *      type: "contract",
   *      contractIds: [ "deadb33f..." ],
   *      topics: [[ "AAAABQAAAAh0cmFuc2Zlcg==", "AAAAAQB6Mcc=", "*" ]]
   *     }, {
   *      type: "system",
   *      contractIds: [ "...c4f3b4b3..." ],
   *      topics: [[ "*" ], [ "*", "AAAAAQB6Mcc=" ]]
   *     }, {
   *      contractIds: [ "...c4f3b4b3..." ],
   *      topics: [[ "AAAABQAAAAh0cmFuc2Zlcg==" ]]
   *     }, {
   *      type: "diagnostic",
   *      topics: [[ "AAAAAQB6Mcc=" ]]
   *     }
   *    ],
   *    limit: 10,
   * });
   */
  async getEvents(request) {
    return this._getEvents(request).then(_parsers.parseRawEvents);
  }
  async _getEvents(request) {
    return jsonrpc.postObject(this.serverURL.toString(), 'getEvents', {
      filters: request.filters ?? [],
      pagination: {
        ...(request.cursor && {
          cursor: request.cursor
        }),
        // add if defined
        ...(request.limit && {
          limit: request.limit
        })
      },
      ...(request.startLedger && {
        startLedger: request.startLedger
      })
    });
  }

  /**
   * Fetch metadata about the network this Soroban RPC server is connected to.
   *
   * @returns {Promise<Api.GetNetworkResponse>}  metadata about the
   *    current network this RPC server is connected to
   *
   * @see https://soroban.stellar.org/api/methods/getNetwork
   * @example
   * server.getNetwork().then((network) => {
   *   console.log("friendbotUrl:", network.friendbotUrl);
   *   console.log("passphrase:", network.passphrase);
   *   console.log("protocolVersion:", network.protocolVersion);
   * });
   */
  async getNetwork() {
    return await jsonrpc.postObject(this.serverURL.toString(), 'getNetwork');
  }

  /**
   * Fetch the latest ledger meta info from network which this Soroban RPC
   * server is connected to.
   *
   * @returns {Promise<Api.GetLatestLedgerResponse>}   metadata about the
   *    latest ledger on the network that this RPC server is connected to
   *
   * @see https://soroban.stellar.org/api/methods/getLatestLedger
   * @example
   * server.getLatestLedger().then((response) => {
   *   console.log("hash:", response.id);
   *   console.log("sequence:", response.sequence);
   *   console.log("protocolVersion:", response.protocolVersion);
   * });
   */
  async getLatestLedger() {
    return jsonrpc.postObject(this.serverURL.toString(), 'getLatestLedger');
  }

  /**
   * Submit a trial contract invocation to get back return values, expected
   * ledger footprint, expected authorizations, and expected costs.
   *
   * @param {Transaction | FeeBumpTransaction} transaction  the transaction to
   *    simulate, which should include exactly one operation (one of
   *    {@link xdr.InvokeHostFunctionOp}, {@link xdr.ExtendFootprintTTLOp}, or
   *    {@link xdr.RestoreFootprintOp}). Any provided footprint or auth
   *    information will be ignored.
   *
   * @returns {Promise<Api.SimulateTransactionResponse>}   an object with the
   *    cost, footprint, result/auth requirements (if applicable), and error of
   *    the transaction
   *
   * @see https://developers.stellar.org/docs/glossary/transactions/
   * @see https://soroban.stellar.org/api/methods/simulateTransaction
   * @see Server.prepareTransaction
   * @see assembleTransaction
   *
   * @example
   * const contractId = 'CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE';
   * const contract = new StellarSdk.Contract(contractId);
   *
   * // Right now, this is just the default fee for this example.
   * const fee = StellarSdk.BASE_FEE;
   * const transaction = new StellarSdk.TransactionBuilder(account, { fee })
   *   // Uncomment the following line to build transactions for the live network. Be
   *   // sure to also change the horizon hostname.
   *   //.setNetworkPassphrase(StellarSdk.Networks.PUBLIC)
   *   .setNetworkPassphrase(StellarSdk.Networks.FUTURENET)
   *   .setTimeout(30) // valid for the next 30s
   *   // Add an operation to call increment() on the contract
   *   .addOperation(contract.call("increment"))
   *   .build();
   *
   * server.simulateTransaction(transaction).then((sim) => {
   *   console.log("cost:", sim.cost);
   *   console.log("result:", sim.result);
   *   console.log("error:", sim.error);
   *   console.log("latestLedger:", sim.latestLedger);
   * });
   */
  async simulateTransaction(tx, addlResources) {
    return this._simulateTransaction(tx, addlResources).then(_parsers.parseRawSimulation);
  }
  async _simulateTransaction(transaction, addlResources) {
    return jsonrpc.postObject(this.serverURL.toString(), 'simulateTransaction', {
      transaction: transaction.toXDR(),
      ...(addlResources !== undefined && {
        resourceConfig: {
          instructionLeeway: addlResources.cpuInstructions
        }
      })
    });
  }

  /**
   * Submit a trial contract invocation, first run a simulation of the contract
   * invocation as defined on the incoming transaction, and apply the results to
   * a new copy of the transaction which is then returned. Setting the ledger
   * footprint and authorization, so the resulting transaction is ready for
   * signing & sending.
   *
   * The returned transaction will also have an updated fee that is the sum of
   * fee set on incoming transaction with the contract resource fees estimated
   * from simulation. It is adviseable to check the fee on returned transaction
   * and validate or take appropriate measures for interaction with user to
   * confirm it is acceptable.
   *
   * You can call the {@link Server.simulateTransaction} method directly first
   * if you want to inspect estimated fees for a given transaction in detail
   * first, then re-assemble it manually or via {@link assembleTransaction}.
   *
   * @param {Transaction | FeeBumpTransaction} transaction  the transaction to
   *    prepare. It should include exactly one operation, which must be one of
   *    {@link xdr.InvokeHostFunctionOp}, {@link xdr.ExtendFootprintTTLOp},
   *    or {@link xdr.RestoreFootprintOp}.
   *
   *    Any provided footprint will be overwritten. However, if your operation
   *    has existing auth entries, they will be preferred over ALL auth entries
   *    from the simulation. In other words, if you include auth entries, you
   *    don't care about the auth returned from the simulation. Other fields
   *    (footprint, etc.) will be filled as normal.
   *
   * @returns {Promise<Transaction | FeeBumpTransaction>}   a copy of the
   *    transaction with the expected authorizations (in the case of
   *    invocation), resources, and ledger footprints added. The transaction fee
   *    will also automatically be padded with the contract's minimum resource
   *    fees discovered from the simulation.
   *
   * @see assembleTransaction
   * @see https://soroban.stellar.org/api/methods/simulateTransaction
   * @throws {jsonrpc.Error<any>|Error|Api.SimulateTransactionErrorResponse}
   *    if simulation fails
   * @example
   * const contractId = 'CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE';
   * const contract = new StellarSdk.Contract(contractId);
   *
   * // Right now, this is just the default fee for this example.
   * const fee = StellarSdk.BASE_FEE;
   * const transaction = new StellarSdk.TransactionBuilder(account, { fee })
   *   // Uncomment the following line to build transactions for the live network. Be
   *   // sure to also change the horizon hostname.
   *   //.setNetworkPassphrase(StellarSdk.Networks.PUBLIC)
   *   .setNetworkPassphrase(StellarSdk.Networks.FUTURENET)
   *   .setTimeout(30) // valid for the next 30s
   *   // Add an operation to call increment() on the contract
   *   .addOperation(contract.call("increment"))
   *   .build();
   *
   * const preparedTransaction = await server.prepareTransaction(transaction);
   *
   * // Sign this transaction with the secret key
   * // NOTE: signing is transaction is network specific. Test network transactions
   * // won't work in the public network. To switch networks, use the Network object
   * // as explained above (look for StellarSdk.Network).
   * const sourceKeypair = StellarSdk.Keypair.fromSecret(sourceSecretKey);
   * preparedTransaction.sign(sourceKeypair);
   *
   * server.sendTransaction(transaction).then(result => {
   *   console.log("hash:", result.hash);
   *   console.log("status:", result.status);
   *   console.log("errorResultXdr:", result.errorResultXdr);
   * });
   */
  async prepareTransaction(tx) {
    const simResponse = await this.simulateTransaction(tx);
    if (_api.Api.isSimulationError(simResponse)) {
      throw simResponse.error;
    }
    return (0, _transaction.assembleTransaction)(tx, simResponse).build();
  }

  /**
   * Submit a real transaction to the Stellar network.
   *
   * Unlike Horizon, Soroban RPC does not wait for transaction completion. It
   * simply validates the transaction and enqueues it. Clients should call
   * {@link Server.getTransactionStatus} to learn about transaction
   * success/failure.
   *
   * @param {Transaction | FeeBumpTransaction} transaction  to submit
   * @returns {Promise<Api.SendTransactionResponse>}   the
   *    transaction id, status, and any error if available
   *
   * @see https://developers.stellar.org/docs/glossary/transactions/
   * @see https://soroban.stellar.org/api/methods/sendTransaction
   * @example
   * const contractId = 'CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE';
   * const contract = new StellarSdk.Contract(contractId);
   *
   * // Right now, this is just the default fee for this example.
   * const fee = StellarSdk.BASE_FEE;
   * const transaction = new StellarSdk.TransactionBuilder(account, { fee })
   *   // Uncomment the following line to build transactions for the live network. Be
   *   // sure to also change the horizon hostname.
   *   //.setNetworkPassphrase(StellarSdk.Networks.PUBLIC)
   *   .setNetworkPassphrase(StellarSdk.Networks.FUTURENET)
   *   .setTimeout(30) // valid for the next 30s
   *   // Add an operation to call increment() on the contract
   *   .addOperation(contract.call("increment"))
   *   .build();
   *
   * // Sign this transaction with the secret key
   * // NOTE: signing is transaction is network specific. Test network transactions
   * // won't work in the public network. To switch networks, use the Network object
   * // as explained above (look for StellarSdk.Network).
   * const sourceKeypair = StellarSdk.Keypair.fromSecret(sourceSecretKey);
   * transaction.sign(sourceKeypair);
   *
   * server.sendTransaction(transaction).then((result) => {
   *   console.log("hash:", result.hash);
   *   console.log("status:", result.status);
   *   console.log("errorResultXdr:", result.errorResultXdr);
   * });
   */
  async sendTransaction(transaction) {
    return this._sendTransaction(transaction).then(_parsers.parseRawSendTransaction);
  }
  async _sendTransaction(transaction) {
    return jsonrpc.postObject(this.serverURL.toString(), 'sendTransaction', {
      transaction: transaction.toXDR()
    });
  }

  /**
   * Fund a new account using the network's friendbot faucet, if any.
   *
   * @param {string | Account} address  the address or account instance that we
   *    want to create and fund with friendbot
   * @param {string} [friendbotUrl]     optionally, an explicit address for
   *    friendbot (by default: this calls the Soroban RPC
   *    {@link Server.getNetwork} method to try to discover this network's
   *    Friendbot url).
   *
   * @returns {Promise<Account>}  an {@link Account} object for the created
   *    account, or the existing account if it's already funded with the
   *    populated sequence number (note that the account will not be "topped
   *    off" if it already exists)
   *
   * @throws if Friendbot is not configured on this network or request failure
   *
   * @see
   * https://developers.stellar.org/docs/fundamentals-and-concepts/testnet-and-pubnet#friendbot
   * @see Friendbot.Response
   * @example
   * server
   *    .requestAirdrop("GBZC6Y2Y7Q3ZQ2Y4QZJ2XZ3Z5YXZ6Z7Z2Y4QZJ2XZ3Z5YXZ6Z7Z2Y4")
   *    .then((accountCreated) => {
   *      console.log("accountCreated:", accountCreated);
   *    }).catch((error) => {
   *      console.error("error:", error);
   *    });
   */
  async requestAirdrop(address, friendbotUrl) {
    const account = typeof address === 'string' ? address : address.accountId();
    friendbotUrl = friendbotUrl || (await this.getNetwork()).friendbotUrl;
    if (!friendbotUrl) {
      throw new Error('No friendbot URL configured for current network');
    }
    try {
      const response = await _axios.default.post(`${friendbotUrl}?addr=${encodeURIComponent(account)}`);
      const meta = _stellarBase.xdr.TransactionMeta.fromXDR(response.data.result_meta_xdr, 'base64');
      const sequence = findCreatedAccountSequenceInTransactionMeta(meta);
      return new _stellarBase.Account(account, sequence);
    } catch (error) {
      if (error.response?.status === 400) {
        if (error.response.detail?.includes('createAccountAlreadyExist')) {
          // Account already exists, load the sequence number
          return this.getAccount(account);
        }
      }
      throw error;
    }
  }
}
exports.Server = Server;
function findCreatedAccountSequenceInTransactionMeta(meta) {
  let operations = [];
  switch (meta.switch()) {
    case 0:
      operations = meta.operations();
      break;
    case 1:
    case 2:
    case 3:
      // all three have the same interface
      operations = meta.value().operations();
      break;
    default:
      throw new Error('Unexpected transaction meta switch value');
  }
  for (const op of operations) {
    for (const c of op.changes()) {
      if (c.switch() !== _stellarBase.xdr.LedgerEntryChangeType.ledgerEntryCreated()) {
        continue;
      }
      const data = c.created().data();
      if (data.switch() !== _stellarBase.xdr.LedgerEntryType.account()) {
        continue;
      }
      return data.account().seqNum().toString();
    }
  }
  throw new Error('No account created in transaction');
}