Source

lib/rpc/transaction.js

"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.assembleTransaction = assembleTransaction;
var _stellarBase = require("@stellar/stellar-base");
var _api = require("./api");
var _parsers = require("./parsers");
function isSorobanTransaction(tx) {
  if (tx.operations.length !== 1) {
    return false;
  }
  switch (tx.operations[0].type) {
    case 'invokeHostFunction':
    case 'extendFootprintTtl':
    case 'restoreFootprint':
      return true;
    default:
      return false;
  }
}

/**
 * Combines the given raw transaction alongside the simulation results.
 * If the given transaction already has authorization entries in a host
 * function invocation (see {@link Operation.invokeHostFunction}), **the
 * simulation entries are ignored**.
 *
 * If the given transaction already has authorization entries in a host function
 * invocation (see {@link Operation.invokeHostFunction}), **the simulation
 * entries are ignored**.
 *
 * @param {Transaction|FeeBumpTransaction} raw the initial transaction, w/o simulation applied
 * @param {Api.SimulateTransactionResponse|Api.RawSimulateTransactionResponse} simulation the Soroban RPC simulation result (see {@link module:rpc.Server#simulateTransaction})
 * @returns {TransactionBuilder} a new, cloned transaction with the proper auth and resource (fee, footprint) simulation data applied
 *
 * @memberof module:rpc
 * @see {@link module:rpc.Server#simulateTransaction}
 * @see {@link module:rpc.Server#prepareTransaction}
 */
function assembleTransaction(raw, simulation) {
  if ('innerTransaction' in raw) {
    // TODO: Handle feebump transactions
    return assembleTransaction(raw.innerTransaction, simulation);
  }
  if (!isSorobanTransaction(raw)) {
    throw new TypeError('unsupported transaction: must contain exactly one ' + 'invokeHostFunction, extendFootprintTtl, or restoreFootprint ' + 'operation');
  }
  const success = (0, _parsers.parseRawSimulation)(simulation);
  if (!_api.Api.isSimulationSuccess(success)) {
    throw new Error(`simulation incorrect: ${JSON.stringify(success)}`);
  }

  /* eslint-disable radix */
  const classicFeeNum = parseInt(raw.fee) || 0;
  const minResourceFeeNum = parseInt(success.minResourceFee) || 0;
  const txnBuilder = _stellarBase.TransactionBuilder.cloneFrom(raw, {
    // automatically update the tx fee that will be set on the resulting tx to
    // the sum of 'classic' fee provided from incoming tx.fee and minResourceFee
    // provided by simulation.
    //
    // 'classic' tx fees are measured as the product of tx.fee * 'number of
    // operations', In soroban contract tx, there can only be single operation
    // in the tx, so can make simplification of total classic fees for the
    // soroban transaction will be equal to incoming tx.fee + minResourceFee.
    fee: (classicFeeNum + minResourceFeeNum).toString(),
    // apply the pre-built Soroban Tx Data from simulation onto the Tx
    sorobanData: success.transactionData.build(),
    networkPassphrase: raw.networkPassphrase
  });
  if (raw.operations[0].type === 'invokeHostFunction') {
    // In this case, we don't want to clone the operation, so we drop it.
    txnBuilder.clearOperations();
    const invokeOp = raw.operations[0];
    const existingAuth = invokeOp.auth ?? [];
    txnBuilder.addOperation(_stellarBase.Operation.invokeHostFunction({
      source: invokeOp.source,
      func: invokeOp.func,
      // if auth entries are already present, we consider this "advanced
      // usage" and disregard ALL auth entries from the simulation
      //
      // the intuition is "if auth exists, this tx has probably been
      // simulated before"
      auth: existingAuth.length > 0 ? existingAuth : success.result.auth
    }));
  }
  return txnBuilder;
}