import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
import { makeSignDoc as makeSignDocAmino, pubkeyType } from "@cosmjs/amino";
import { fromBase64, toBase64 } from "@cosmjs/encoding";
import { Int53, Uint53 } from "@cosmjs/math";
import { encodePubkey, isOfflineDirectSigner, makeAuthInfoBytes, makeSignDoc, Registry } from "@cosmjs/proto-signing";
import { calculateFee, accountFromAny, StargateClient } from "@cosmjs/stargate";
import { connectComet } from "@cosmjs/tendermint-rpc";
import { assert, assertDefined } from "@cosmjs/utils";
import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing";
import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx";
import { createRegistry, defaultRegistryTypes } from "./customRegistries";
import { getSignerData } from "./store";
function createDefaultRegistry() {
  return new Registry(defaultRegistryTypes);
}
export class SigningStargateClient extends StargateClient {
  static async connectWithSigner(endpoint, signer, options = {}, ignoreGetSequence, localStoreFunctions) {
    const cometClient = await connectComet(endpoint);
    return new SigningStargateClient(cometClient, signer, options, ignoreGetSequence, localStoreFunctions);
  }

  /**
   * Creates an instance from a manually created Comet client.
   * Use this to use `Comet38Client` or `Tendermint37Client` instead of `Tendermint34Client`.
   */
  static async createWithSigner(cometClient, signer, options = {}) {
    return new SigningStargateClient(cometClient, signer, options);
  }

  /**
   * Creates a client in offline mode.
   *
   * This should only be used in niche cases where you know exactly what you're doing,
   * e.g. when building an offline signing application.
   *
   * When you try to use online functionality with such a signer, an
   * exception will be raised.
   */
  static async offline(signer, options = {}, ignoreGetSequence, localStoreFunctions) {
    return new SigningStargateClient(undefined, signer, options, ignoreGetSequence, localStoreFunctions);
  }
  constructor(cometClient, signer, options, ignoreGetSequence = false, localStoreFunctions) {
    super(cometClient, options);
    _defineProperty(this, "registry", void 0);
    _defineProperty(this, "broadcastTimeoutMs", void 0);
    _defineProperty(this, "broadcastPollIntervalMs", void 0);
    _defineProperty(this, "signer", void 0);
    _defineProperty(this, "aminoTypes", void 0);
    _defineProperty(this, "gasPrice", void 0);
    _defineProperty(this, "ignoreGetSequence", void 0);
    // Starting with Cosmos SDK 0.47, we see many cases in which 1.3 is not enough anymore
    // E.g. https://github.com/cosmos/cosmos-sdk/issues/16020
    _defineProperty(this, "defaultGasMultiplier", 1.4);
    // sequence store functions for consecutive transactions
    _defineProperty(this, "localStoreFunctions", void 0);
    // public cometClient for convenience
    _defineProperty(this, "publicCometClient", void 0);
    const {
      registry = createDefaultRegistry(),
      // aminoTypes = new AminoTypes(createDefaultTypes(prefix)),
      aminoTypes
    } = options;
    this.publicCometClient = cometClient;
    this.registry = registry;
    this.aminoTypes = aminoTypes;
    this.signer = signer;
    this.broadcastTimeoutMs = options.broadcastTimeoutMs;
    this.broadcastPollIntervalMs = options.broadcastPollIntervalMs;
    this.gasPrice = options.gasPrice;
    this.ignoreGetSequence = ignoreGetSequence;
    this.localStoreFunctions = localStoreFunctions;
  }
  async simulate(signerAddress, messages, memo, txBodyBytes, explicitSignerData) {
    // const txBody = txBodyBytes ? this.registry.decodeTxBody(txBodyBytes) : null;
    // console.log("txBody", txBody);

    const anyMsgs = messages.map(m => this.registry.encodeAsAny(m));
    const accountFromSigner = (await this.signer.getAccounts()).find(account => account.address === signerAddress);
    if (!accountFromSigner) {
      throw new Error("Failed to retrieve account from signer");
    }
    const pubkey = {
      type: accountFromSigner.algo === "secp256k1" ? pubkeyType.secp256k1 : pubkeyType.ed25519,
      value: toBase64(accountFromSigner.pubkey)
    };

    // Use explicit sequence if provided, otherwise fetch from chain
    let sequence;
    if ((explicitSignerData === null || explicitSignerData === void 0 ? void 0 : explicitSignerData.sequence) !== undefined) {
      sequence = explicitSignerData.sequence;
    } else if (this.ignoreGetSequence) {
      sequence = 0;
    } else {
      const seqResult = await this.getSequence(signerAddress);
      sequence = seqResult.sequence;
    }
    const {
      gasInfo
    } = await this.forceGetQueryClient().tx.simulate(anyMsgs, memo, pubkey, sequence);
    assertDefined(gasInfo);
    return Uint53.fromString(gasInfo.gasUsed.toString()).toNumber();
  }
  async signAndBroadcast(signerAddress, messages, fee, memo = "", explicitSignerData, txBodyBytes, timeoutHeight) {
    // Get signer data FIRST before getUsedFee, so simulate uses correct sequence
    let signerData = explicitSignerData;
    if (!signerData && !this.ignoreGetSequence && this.localStoreFunctions) {
      signerData = await getSignerData(this, this.signer, this.localStoreFunctions);
    }
    const usedFee = await this.getUsedFee(signerAddress, messages, fee, memo, txBodyBytes, signerData);
    const txRaw = await this.sign(signerAddress, messages, usedFee, memo, signerData, txBodyBytes, timeoutHeight);
    const txBytes = TxRaw.encode(txRaw).finish();
    return this.broadcastTx(txBytes, this.broadcastTimeoutMs, this.broadcastPollIntervalMs);
  }

  /**
   * This method is useful if you want to send a transaction in broadcast,
   * without waiting for it to be placed inside a block, because for example
   * I would like to receive the hash to later track the transaction with another tool.
   * @returns Returns the hash of the transaction
   */
  async signAndBroadcastSync(signerAddress, messages, fee, memo = "", explicitSignerData, txBodyBytes, timeoutHeight) {
    // Get signer data FIRST before getUsedFee, so simulate uses correct sequence
    let signerData = explicitSignerData;
    if (!signerData && !this.ignoreGetSequence && this.localStoreFunctions) {
      signerData = await getSignerData(this, this.signer, this.localStoreFunctions);
    }
    const usedFee = await this.getUsedFee(signerAddress, messages, fee, memo, txBodyBytes, signerData);
    const txRaw = await this.sign(signerAddress, messages, usedFee, memo, signerData, txBodyBytes, timeoutHeight);
    const txBytes = TxRaw.encode(txRaw).finish();
    return this.broadcastTxSync(txBytes);
  }

  /**
   * Gets account number and sequence from the API, creates a sign doc,
   * creates a single signature and assembles the signed transaction.
   *
   * The sign mode (SIGN_MODE_DIRECT or SIGN_MODE_LEGACY_AMINO_JSON) is determined by this client's signer.
   *
   * You can pass signer data (account number, sequence and chain ID) explicitly instead of querying them
   * from the chain. This is needed when signing for a multisig account, but it also allows for offline signing
   * (See the SigningStargateClient.offline constructor).
   */
  async sign(signerAddress, messages, fee, memo, explicitSignerData, txBodyBytes, timeoutHeight) {
    let signerData;
    if (explicitSignerData) {
      signerData = explicitSignerData;
    } else if (!this.ignoreGetSequence && this.localStoreFunctions) {
      // if dont skip sequence and want to use local sequence checker
      signerData = await getSignerData(this, this.signer, this.localStoreFunctions);
    } else {
      const {
        accountNumber,
        sequence
      } = this.ignoreGetSequence ? {
        sequence: 0,
        accountNumber: 0
      } : await this.getSequence(signerAddress);
      const chainId = await this.getChainId();
      signerData = {
        accountNumber: accountNumber,
        sequence: sequence,
        chainId: chainId
      };
    }
    return isOfflineDirectSigner(this.signer) ? this.signDirect(signerAddress, messages, fee, memo, signerData, txBodyBytes, timeoutHeight) : this.signAmino(signerAddress, messages, fee, memo, signerData, timeoutHeight);
  }
  async getUsedFee(signerAddress, messages, fee, memo = "", txBodyBytes, explicitSignerData) {
    if (fee == "auto" || typeof fee === "number") {
      assertDefined(this.gasPrice, "Gas price must be set in the client options when auto gas is used.");
      const gasEstimation = await this.simulate(signerAddress, messages, memo, txBodyBytes, explicitSignerData);
      const multiplier = typeof fee === "number" ? fee : this.defaultGasMultiplier;
      return calculateFee(Math.round(gasEstimation * multiplier), this.gasPrice);
    } else {
      return fee;
    }
  }
  async signAmino(signerAddress, messages, fee, memo, {
    accountNumber,
    sequence,
    chainId
  }, timeoutHeight) {
    assert(!isOfflineDirectSigner(this.signer));
    const accountFromSigner = (await this.signer.getAccounts()).find(account => account.address === signerAddress);
    if (!accountFromSigner) {
      throw new Error("Failed to retrieve account from signer");
    }
    const pubkey = encodePubkey({
      type: accountFromSigner.algo === "secp256k1" ? pubkeyType.secp256k1 : pubkeyType.ed25519,
      value: toBase64(accountFromSigner.pubkey)
    });
    const signMode = SignMode.SIGN_MODE_LEGACY_AMINO_JSON;
    const msgs = messages.map(msg => this.aminoTypes.toAmino(msg));
    const signDoc = makeSignDocAmino(msgs, fee, chainId, memo, accountNumber, sequence, timeoutHeight);
    const {
      signature,
      signed
    } = await this.signer.signAmino(signerAddress, signDoc);
    const signedTxBody = {
      // messages: messages,
      messages: signed.msgs.map(msg => this.aminoTypes.fromAmino(msg)),
      memo: signed.memo,
      timeoutHeight: timeoutHeight
    };
    const signedTxBodyEncodeObject = {
      typeUrl: "/cosmos.tx.v1beta1.TxBody",
      value: signedTxBody
    };
    const signedTxBodyBytes = this.registry.encode(signedTxBodyEncodeObject);
    const signedGasLimit = Int53.fromString(signed.fee.gas).toNumber();
    const signedSequence = Int53.fromString(signed.sequence).toNumber();
    const signedAuthInfoBytes = makeAuthInfoBytes([{
      pubkey,
      sequence: signedSequence
    }], signed.fee.amount, signedGasLimit, signed.fee.granter, signed.fee.payer, signMode);
    return TxRaw.fromPartial({
      bodyBytes: signedTxBodyBytes,
      authInfoBytes: signedAuthInfoBytes,
      signatures: [fromBase64(signature.signature)]
    });
  }
  async signDirect(signerAddress, messages, fee, memo, {
    accountNumber,
    sequence,
    chainId
  }, txBodyBytes, timeoutHeight) {
    assert(isOfflineDirectSigner(this.signer));
    const accountFromSigner = (await this.signer.getAccounts()).find(account => account.address === signerAddress);
    if (!accountFromSigner) {
      throw new Error("Failed to retrieve account from signer");
    }
    const pubkey = encodePubkey({
      type: accountFromSigner.algo === "secp256k1" ? pubkeyType.secp256k1 : pubkeyType.ed25519,
      value: toBase64(accountFromSigner.pubkey)
    });
    const txBodyBytes1 = txBodyBytes || this.registry.encodeTxBody({
      messages,
      memo,
      timeoutHeight
    });
    const gasLimit = Int53.fromString(fee.gas).toNumber();
    const authInfoBytes = makeAuthInfoBytes([{
      pubkey,
      sequence
    }], fee.amount, gasLimit, fee.granter, fee.payer);
    const signDoc = makeSignDoc(txBodyBytes1, authInfoBytes, chainId, accountNumber);
    const {
      signature,
      signed
    } = await this.signer.signDirect(signerAddress, signDoc);
    return TxRaw.fromPartial({
      bodyBytes: signed.bodyBytes,
      authInfoBytes: signed.authInfoBytes,
      signatures: [fromBase64(signature.signature)]
    });
  }
}

/**
 * Creates a new SigningStargateClient with the given signer.
 * @param rpcEndpoint - The RPC endpoint of the chain.
 * @param offlineWallet - The wallet to sign transactions.
 * @param ignoreGetSequence - If true, the client will not query the chain for the account sequence.
 * @param options - The client options.
 * @param localStoreFunctions - The local storage getter and setter to use save sequence locally.
 */
export const createSigningClient = async (rpcEndpoint, offlineWallet, ignoreGetSequence, options, localStoreFunctions) => {
  return await SigningStargateClient.connectWithSigner(rpcEndpoint, offlineWallet, _objectSpread({
    // registry: new Registry([...defaultRegistryTypes, ...ixoProtoRegistry]),
    registry: createRegistry(),
    accountParser: accountFromAny
  }, options), ignoreGetSequence, localStoreFunctions);
};