'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var Signer = require('../../types/Signer.cjs');
var RpcError = require('../../errors/RpcError.cjs');
var Amount = require('../../types/Amount.cjs');
var Program = require('../../types/Program.cjs');
var ProgramError = require('../../errors/ProgramError.cjs');
var common = require('../../utils/common.cjs');

/**
 * @group Modules
 */
class RpcClient {
  constructor(metaplex) {
    this.metaplex = metaplex;
  }
  async prepareTransaction(transaction, signers) {
    let blockhashWithExpiryBlockHeight;
    if (!('records' in transaction) && transaction.recentBlockhash && transaction.lastValidBlockHeight) {
      blockhashWithExpiryBlockHeight = {
        blockhash: transaction.recentBlockhash,
        lastValidBlockHeight: transaction.lastValidBlockHeight
      };
    } else {
      blockhashWithExpiryBlockHeight = await this.getLatestBlockhash();
    }
    if ('records' in transaction) {
      signers = [...transaction.getSigners(), ...signers];
      transaction = transaction.toTransaction(blockhashWithExpiryBlockHeight);
    }
    return {
      transaction,
      signers,
      blockhashWithExpiryBlockHeight
    };
  }
  async signTransaction(transaction, signers) {
    const {
      keypairs,
      identities
    } = Signer.getSignerHistogram(signers);

    // Keypair signers.
    if (keypairs.length > 0) {
      transaction.partialSign(...keypairs);
    }

    // Identity signers.
    for (let i = 0; i < identities.length; i++) {
      await identities[i].signTransaction(transaction);
    }
    return transaction;
  }
  async sendTransaction(transaction, sendOptions = {}, signers = []) {
    const prepared = await this.prepareTransaction(transaction, signers);
    transaction = prepared.transaction;
    signers = prepared.signers;
    const defaultFeePayer = this.getDefaultFeePayer();
    if (!transaction.feePayer && defaultFeePayer) {
      transaction.feePayer = defaultFeePayer.publicKey;
      signers = [defaultFeePayer, ...signers];
    }
    transaction = await this.signTransaction(transaction, signers);
    const rawTransaction = transaction.serialize();
    try {
      return await this.metaplex.connection.sendRawTransaction(rawTransaction, sendOptions);
    } catch (error) {
      throw this.parseProgramError(error, transaction);
    }
  }
  async confirmTransaction(signature, blockhashWithExpiryBlockHeight, commitment) {
    let rpcResponse;
    try {
      rpcResponse = await this.metaplex.connection.confirmTransaction({
        signature,
        ...blockhashWithExpiryBlockHeight
      }, commitment);
    } catch (error) {
      throw new RpcError.FailedToConfirmTransactionError(error);
    }
    if (rpcResponse.value.err) {
      throw new RpcError.FailedToConfirmTransactionWithResponseError(rpcResponse);
    }
    return rpcResponse;
  }
  async sendAndConfirmTransaction(transaction, confirmOptions, signers = []) {
    const prepared = await this.prepareTransaction(transaction, signers);
    const {
      blockhashWithExpiryBlockHeight
    } = prepared;
    transaction = prepared.transaction;
    signers = prepared.signers;
    const signature = await this.sendTransaction(transaction, confirmOptions, signers);
    const confirmResponse = await this.confirmTransaction(signature, blockhashWithExpiryBlockHeight, confirmOptions?.commitment);
    return {
      signature,
      confirmResponse,
      ...blockhashWithExpiryBlockHeight
    };
  }
  async getAccount(publicKey, commitment) {
    const accountInfo = await this.metaplex.connection.getAccountInfo(publicKey, commitment);
    return this.getUnparsedMaybeAccount(publicKey, accountInfo);
  }
  async accountExists(publicKey, commitment) {
    const balance = await this.metaplex.connection.getBalance(publicKey, commitment);
    return balance > 0;
  }
  async getMultipleAccounts(publicKeys, commitment) {
    const accountInfos = await this.metaplex.connection.getMultipleAccountsInfo(publicKeys, commitment);
    return common.zipMap(publicKeys, accountInfos, (publicKey, accountInfo) => {
      return this.getUnparsedMaybeAccount(publicKey, accountInfo);
    });
  }
  async getProgramAccounts(programId, configOrCommitment) {
    const accounts = await this.metaplex.connection.getProgramAccounts(programId, configOrCommitment);
    return accounts.map(({
      pubkey,
      account
    }) => ({
      ...account,
      publicKey: pubkey,
      lamports: Amount.lamports(account.lamports)
    }));
  }
  async airdrop(publicKey, amount, commitment) {
    Amount.assertSol(amount);
    const signature = await this.metaplex.connection.requestAirdrop(publicKey, amount.basisPoints.toNumber());
    const blockhashWithExpiryBlockHeight = await this.getLatestBlockhash();
    const confirmResponse = await this.confirmTransaction(signature, blockhashWithExpiryBlockHeight, commitment);
    return {
      signature,
      confirmResponse,
      ...blockhashWithExpiryBlockHeight
    };
  }
  async getBalance(publicKey, commitment) {
    const balance = await this.metaplex.connection.getBalance(publicKey, commitment);
    return Amount.lamports(balance);
  }
  async getRent(bytes, commitment) {
    const rent = await this.metaplex.connection.getMinimumBalanceForRentExemption(bytes, commitment);
    return Amount.lamports(rent);
  }
  async getLatestBlockhash(commitmentOrConfig = 'finalized') {
    return this.metaplex.connection.getLatestBlockhash(commitmentOrConfig);
  }
  getSolanaExporerUrl(signature) {
    let clusterParam = '';
    switch (this.metaplex.cluster) {
      case 'devnet':
        clusterParam = '?cluster=devnet';
        break;
      case 'testnet':
        clusterParam = '?cluster=testnet';
        break;
      case 'localnet':
      case 'custom':
        const url = encodeURIComponent(this.metaplex.connection.rpcEndpoint);
        clusterParam = `?cluster=custom&customUrl=${url}`;
        break;
    }
    return `https://explorer.solana.com/tx/${signature}${clusterParam}`;
  }
  setDefaultFeePayer(payer) {
    this.defaultFeePayer = payer;
    return this;
  }
  getDefaultFeePayer() {
    return this.defaultFeePayer ? this.defaultFeePayer : this.metaplex.identity();
  }
  getUnparsedMaybeAccount(publicKey, accountInfo) {
    if (!accountInfo) {
      return {
        publicKey,
        exists: false
      };
    }
    return {
      ...accountInfo,
      publicKey,
      exists: true,
      lamports: Amount.lamports(accountInfo.lamports)
    };
  }
  parseProgramError(error, transaction) {
    // Ensure the error as logs.
    if (!Program.isErrorWithLogs(error)) {
      return new RpcError.FailedToSendTransactionError(error);
    }

    // Parse the instruction number.
    const regex = /Error processing Instruction (\d+):/;
    const instruction = error.message.match(regex)?.[1] ?? null;

    // Ensure there is an instruction number given to find the program.
    if (!instruction) {
      return new RpcError.FailedToSendTransactionError(error);
    }

    // Get the program ID from the instruction in the transaction.
    const instructionNumber = parseInt(instruction, 10);
    const programId = transaction.instructions?.[instructionNumber]?.programId ?? null;

    // Ensure we were able to find a program ID for the instruction.
    if (!programId) {
      return new RpcError.FailedToSendTransactionError(error);
    }

    // Find a registered program if any.
    let program;
    try {
      program = this.metaplex.programs().get(programId);
    } catch (_programNotFoundError) {
      return new RpcError.FailedToSendTransactionError(error);
    }

    // Ensure an error resolver exists on the program.
    if (!program.errorResolver) {
      return new ProgramError.UnknownProgramError(program, error);
    }

    // Finally, resolve the error.
    const resolvedError = program.errorResolver(error);
    return resolvedError ? new ProgramError.ParsedProgramError(program, resolvedError) : new ProgramError.UnknownProgramError(program, error);
  }
}

exports.RpcClient = RpcClient;
//# sourceMappingURL=RpcClient.cjs.map
