'use strict';

var index = require('./index-37554fc9.cjs.dev.js');
var ethers = require('ethers');
var chains = require('@thirdweb-dev/chains');
var invariant = require('tiny-invariant');
var generatedAbis = require('@thirdweb-dev/generated-abis');
var zod = require('zod');
var setErc20Allowance = require('./setErc20Allowance-01f35ca3.cjs.dev.js');
var thirdweb = require('thirdweb');
var pay = require('thirdweb/pay');
var utils = require('thirdweb/utils');

function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }

var invariant__default = /*#__PURE__*/_interopDefault(invariant);

/**
 * @internal
 * @param contractWrapper - The contract wrapper to check
 * @param abi - The abi to check
 * @returns
 */
function matchesPrebuiltAbi(contractWrapper, abi) {
  return index.hasMatchingAbi(index.AbiSchema.parse(contractWrapper.abi || []), [abi]);
}

async function compatibleExtensions(coreBytecode, extensionBytecodes, chainId) {
  const chain = await chains.getChainByChainIdAsync(chainId);
  const rpcUrl = chains.getChainRPC(chain);
  const jsonRpcProvider = new ethers.providers.JsonRpcProvider(rpcUrl);
  const addr = "0x0000000000000000000000000000000000000124"; // arbitrary address

  /**
   *  Here we use state override with eth_call.
   *
   *  This lets us call functions on a non-deployed contract, by using its deployed bytecode
   *  to override code of an arbitrary address in eth_call.
   *
   *  Encode function calldata, and perform eth_call with bytecode override.
   */

  const coreIface = new ethers.utils.Interface(index.coreContractAbi);
  const coreCalldata = coreIface.encodeFunctionData("getSupportedCallbackFunctions", []);
  if (!coreBytecode.startsWith("0x6080604052")) {
    const index = coreBytecode.indexOf("6080604052");
    coreBytecode = `0x${coreBytecode.substring(index)}`;
  } else if (coreBytecode.lastIndexOf("6080604052") > 0) {
    const index = coreBytecode.lastIndexOf("6080604052");
    coreBytecode = `0x${coreBytecode.substring(index)}`;
  }
  const core = await jsonRpcProvider.send("eth_call", [{
    to: addr,
    data: coreCalldata
  }, "latest", {
    [addr]: {
      code: coreBytecode
    }
  } // eth_call with bytecode override
  ]);
  const extensionIface = new ethers.utils.Interface(index.extensionContractAbi);
  const extensionCalldata = extensionIface.encodeFunctionData("getExtensionConfig", []);
  const extensions = await Promise.all(extensionBytecodes.map(b => {
    // TODO: Upload deployed bytecode on publish metadata

    if (!b.startsWith("0x6080604052")) {
      const index = b.indexOf("6080604052");
      b = `0x${b.substring(index)}`;
    } else if (b.lastIndexOf("6080604052") > 0) {
      const index = b.lastIndexOf("6080604052");
      b = `0x${b.substring(index)}`;
    }
    return jsonRpcProvider.send("eth_call", [{
      to: addr,
      data: extensionCalldata
    }, "latest", {
      [addr]: {
        code: b
      }
    } // eth_call with bytecode override
    ]);
  }));
  const decodedCore = coreIface.decodeFunctionResult("getSupportedCallbackFunctions", core);
  const coreCallbackSelectors = decodedCore.flat().map(c => c.selector);

  // extract callback/fallback selectors and required interfaces from extension config
  const requiredInterfaces = [];
  const extensionFallbackSelectors = [];
  const extensionCallbackSelectors = [];
  const selectors = [];
  extensions.forEach(e => {
    const decodedExtensionConfig = extensionIface.decodeFunctionResult("getExtensionConfig", e);
    requiredInterfaces.push(...decodedExtensionConfig[0].requiredInterfaces);
    const fallbackSelectors = decodedExtensionConfig[0].fallbackFunctions.map(a => a.selector);
    const callbackSelectors = decodedExtensionConfig[0].callbackFunctions.map(a => a.selector);
    extensionFallbackSelectors.push(...fallbackSelectors);
    extensionCallbackSelectors.push(...callbackSelectors);
  });

  // check if callback selectors are supported
  for (const callback of extensionCallbackSelectors) {
    if (!coreCallbackSelectors.includes(callback)) {
      return false;
    }
  }

  // check if the core contract supports required interfaces by extensions above
  if (requiredInterfaces.length > 0) {
    const supportsInterfaceResult = await Promise.all(requiredInterfaces.map(r => {
      const supportsInterfaceCalldata = coreIface.encodeFunctionData("supportsInterface", [r]);
      return jsonRpcProvider.send("eth_call", [{
        to: addr,
        data: supportsInterfaceCalldata
      }, "latest", {
        [addr]: {
          code: coreBytecode
        }
      }]);
    }));
    const supportsInterfaceDecoded = supportsInterfaceResult.map(r => {
      return coreIface.decodeFunctionResult("supportsInterface", r);
    });
    if (supportsInterfaceDecoded.flat().some(element => element === false)) {
      return false;
    }
  }

  // check duplicate callback/fallback signatures
  selectors.push(...extensionFallbackSelectors, ...extensionCallbackSelectors);
  return !index.hasDuplicates(selectors.flat(), (a, b) => a === b);
}

function toUnits(amount, decimals) {
  return ethers.utils.parseUnits(index.AmountSchema.parse(amount), decimals);
}

function toDisplayValue(amount, decimals) {
  return ethers.utils.formatUnits(amount, decimals);
}

/**
 *
 * @internal
 * @param provider - The provider to use
 * @param storage - The storage to use
 * @param create2Factory - The create2 factory to use
 */
async function computeNativeTokenAddress(provider, storage, create2Factory, clientId, secretKey) {
  if (!create2Factory || create2Factory === "") {
    create2Factory = await index.getCreate2FactoryAddress(provider);
  }
  return (await index.computeDeploymentInfo("infra", provider, storage, create2Factory, {
    contractName: "WETH9"
  }, clientId, secretKey)).transaction.predictedAddress;
}

/**
 * Deploy a contract at a deterministic address, using Create2 method
 * Address depends on the Create2 factory address.
 *
 * @internal
 *
 * @param signer - The signer to use
 * @param bytecode - The bytecode to deploy
 * @param encodedArgs - The encoded constructor args to use
 * @param create2FactoryAddress - The create2 factory address to use
 */
async function deployContractDeterministicRaw(signer, bytecode, encodedArgs, create2FactoryAddress, options, predictedAddress) {
  let gasLimit = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : 7000000;
  // Check if the implementation contract is already deployed
  invariant__default["default"](signer.provider, "Provider required");
  const contractDeployed = predictedAddress ? await index.isContractDeployed(predictedAddress, signer.provider) : false;
  if (!contractDeployed) {
    console.debug(`deploying contract via create2 factory at: ${predictedAddress}`);
    const initBytecodeWithSalt = index.getInitBytecodeWithSalt(bytecode, encodedArgs);
    const tx = {
      to: create2FactoryAddress,
      data: initBytecodeWithSalt
    };
    try {
      await signer.estimateGas(tx);
    } catch (e) {
      console.debug("error estimating gas while deploying prebuilt: ", e);
      tx.gasLimit = ethers.BigNumber.from(gasLimit);
    }
    await (await signer.sendTransaction(tx)).wait();
  }
}

/**
 * @internal
 */
function getCachedAbiForContract(address) {
  if (address in generatedAbis.GENERATED_ABI) {
    return generatedAbis.GENERATED_ABI[address];
  }
  return undefined;
}

let ProposalState = /*#__PURE__*/function (ProposalState) {
  ProposalState[ProposalState["Pending"] = 0] = "Pending";
  ProposalState[ProposalState["Active"] = 1] = "Active";
  ProposalState[ProposalState["Canceled"] = 2] = "Canceled";
  ProposalState[ProposalState["Defeated"] = 3] = "Defeated";
  ProposalState[ProposalState["Succeeded"] = 4] = "Succeeded";
  ProposalState[ProposalState["Queued"] = 5] = "Queued";
  ProposalState[ProposalState["Expired"] = 6] = "Expired";
  ProposalState[ProposalState["Executed"] = 7] = "Executed";
  return ProposalState;
}({});

const PropertiesInput = /* @__PURE__ */(() => zod.z.object({}).catchall(zod.z.union([index.BigNumberTransformSchema, zod.z.unknown()])))();

/**
 * @internal
 */
const OptionalPropertiesInput = /* @__PURE__ */(() => zod.z.union([zod.z.array(PropertiesInput), PropertiesInput]).optional())();

/**
 * @internal
 */
const EditionMetadataOutputSchema = /* @__PURE__ */(() => zod.z.object({
  supply: index.BigNumberSchema,
  metadata: setErc20Allowance.CommonNFTOutput
}))();

/**
 * @internal
 */
const EditionMetadataWithOwnerOutputSchema = /* @__PURE__ */(() => EditionMetadataOutputSchema.extend({
  owner: zod.z.string(),
  quantityOwned: index.BigNumberSchema
}))();

/**
 * @internal
 */
const EditionMetadataInputSchema = /* @__PURE__ */(() => zod.z.object({
  supply: index.BigNumberishSchema,
  metadata: setErc20Allowance.CommonNFTInput
}))();

/**
 * @internal
 */
const EditionMetadataInputOrUriSchema = /* @__PURE__ */(() => zod.z.object({
  supply: index.BigNumberishSchema,
  metadata: setErc20Allowance.NFTInputOrUriSchema
}))();

/**
 * @public
 */

/**
 * @public
 */

/**
 * @internal
 */
const TokenMintInputSchema = /* @__PURE__ */(() => zod.z.object({
  toAddress: index.AddressOrEnsSchema,
  amount: index.AmountSchema
}))();

/**
 * @public
 */

/**
 * The parameters for [`getBuyWithCryptoHistory`](https://portal.thirdweb.com/references/typescript/v4/getBuyWithCryptoHistory) function
 * It takes the wallet history address and optional cursor and page size. for paginated results.
 */

/**
 * Gets the History of purchases for a given wallet address
 * @param params - Object of type [`BuyWithCryptoHistoryParams`](https://portal.thirdweb.com/references/typescript/v4/BuyWithCryptoHistoryParams)
 * @example
 *
 * ```ts
//  * import { BuyWithCryptoHistoryData } from "@thirdweb-dev/sdk";
 *
 * const walletAddress = "0x...";
 * const params = {
 *  clientId: "YOUR_CLIENT_ID",
 *  walletAddress,
 * };
 *
 * // grabs the history of purchase transactions for the wallet address
 * const status = await getBuyWithCryptoHistory(params)
 * ```
 * @returns Object of type [`BuyWithCryptoHistoryData`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoHistoryData)
 */
async function getBuyWithCryptoHistory(params) {
  let client;
  if (params.secretKey) {
    client = thirdweb.createThirdwebClient({
      secretKey: params.secretKey
    });
  }
  if (params.clientId) {
    client = thirdweb.createThirdwebClient({
      clientId: params.clientId
    });
  }
  if (!client) {
    throw new Error("You must provide either a `clientId` or a `secretKey` to get a quote");
  }
  return pay.getBuyWithCryptoHistory({
    ...params,
    client: client
  });
}

/**
 * The parameters for [`getBuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v4/getBuyWithCryptoQuote) function
 * It includes information about which tokens to swap, the amount of tokens to swap, slippage, etc.
 */

/**
 * Get a quote of type [`BuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoQuote) to buy any given token with crypto.
 * This quote contains the information about the swap such as token amounts, processing fees, estimated time etc.
 *
 * Once you have the quote, you can use `prepareTransaction` and prepare the transaction for submission.
 * @param params - object of type [`GetBuyWithCryptoQuoteParams`](https://portal.thirdweb.com/references/typescript/v5/GetBuyWithCryptoQuoteParams)
 * @returns Object of type [`BuyWithCryptoQuote`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoQuote) which contains the information about the quote such as processing fees, estimated time, converted token amounts, etc.
 * @example
 *
 * ```ts
 * import { getBuyWithCryptoQuote } from "@thirdweb-dev/sdk";
 *
 * const quote = await getBuyWithCryptoQuote({
 *  clientId: "...",
 *  fromAddress: "0x...", // wallet address
 *  fromChainId: 137, // chain id of the source token
 *  fromTokenAddress: "0x...", // token address of the source token
 *  fromAmount: "10", // amount of source token to swap
 *  // optionally, you can use `toAmount` instead if you only want a certain amount of destination token
 *  toChainId: 10, // chain id of the destination token
 *  toTokenAddress: "0x...", // token address of the destination token
 *  toAddress: "0x...", // optional: send the tokens to a different address
 *  maxSlippageBPS: 50, // optional: max 0.5% slippage
 * });
 * ```
 */
async function getBuyWithCryptoQuote(params) {
  let client;
  if (params.secretKey) {
    client = thirdweb.createThirdwebClient({
      secretKey: params.secretKey
    });
  }
  if (params.clientId) {
    client = thirdweb.createThirdwebClient({
      clientId: params.clientId
    });
  }
  if (!client) {
    throw new Error("You must provide either a `clientId` or a `secretKey` to get a quote");
  }
  const data = await thirdweb.getBuyWithCryptoQuote({
    ...params,
    client
  });
  const sendTxn = thirdweb.prepareTransaction(data.transactionRequest);
  const approvalTxn = data.approval ? thirdweb.prepareTransaction(data.approval) : undefined;
  const [sendTxnTo, sendTxnData, sendTxnGasPrice, sendTxnGasLimit, sendTxnNonce, sendTxnValue, approvalTxnTo, approvalTxnData, approvalTxnGasLimit, approvalTxnGasPrice, approvalTxnNonce, approvalTxnValue] = await Promise.all([utils.resolvePromisedValue(sendTxn.to), utils.resolvePromisedValue(sendTxn.data), utils.resolvePromisedValue(sendTxn.gasPrice), utils.resolvePromisedValue(sendTxn.gas), utils.resolvePromisedValue(sendTxn.nonce), utils.resolvePromisedValue(sendTxn.value), utils.resolvePromisedValue(approvalTxn?.to), utils.resolvePromisedValue(approvalTxn?.data), utils.resolvePromisedValue(approvalTxn?.gas), utils.resolvePromisedValue(approvalTxn?.gasPrice), utils.resolvePromisedValue(approvalTxn?.nonce), utils.resolvePromisedValue(approvalTxn?.value)]);
  const swapRoute = {
    transactionRequest: {
      to: sendTxnTo,
      data: sendTxnData,
      gasPrice: sendTxnGasPrice,
      gasLimit: sendTxnGasLimit,
      nonce: sendTxnNonce,
      value: sendTxnValue,
      chainId: sendTxn.chain.id
    },
    approval: approvalTxn ? {
      to: approvalTxnTo,
      data: approvalTxnData,
      gasLimit: approvalTxnGasLimit,
      gasPrice: approvalTxnGasPrice,
      nonce: approvalTxnNonce,
      value: approvalTxnValue,
      chainId: approvalTxn.chain.id
    } : undefined,
    swapDetails: data.swapDetails,
    paymentTokens: data.paymentTokens,
    processingFees: data.processingFees,
    client
  };
  return swapRoute;
}

/**
 * Gets the status of a buy with crypto transaction
 * @param buyWithCryptoTransaction - Object of type [`BuyWithCryptoTransaction`](https://portal.thirdweb.com/references/typescript/v5/BuyWithCryptoTransaction)
 * @example
 *
 * ```ts
 * import { getBuyWithCryptoStatus, getBuyWithCryptoQuote } @thirdweb-dev/sdk";
 *
 * // get a quote between two tokens
 * const quote = await getBuyWithCryptoQuote(quoteParams);
 * // if approval is required, send the approval transaction
 * if (quote.approval) {
 *   const response = await signer.sendTransaction(quote.approval);
 * }
 * // send the quoted transaction
 * const transactionResult = await signer.sendTransaction(
 *   quote.transactionRequest,
 * );
 * // keep polling the status of the quoted transaction until it * returns a success or failure status
 * const status = await getBuyWithCryptoStatus({
 *   clientId: "YOUR_CLIENT_ID",
 *   transactionHash: transactionResult.hash,
 * });
 * ```
 * @returns Object of type [`BuyWithCryptoStatus`](https://portal.thirdweb.com/references/typescript/v4/BuyWithCryptoStatus)
 */
async function getBuyWithCryptoStatus(buyWithCryptoTransaction) {
  return pay.getBuyWithCryptoStatus({
    ...buyWithCryptoTransaction,
    client: thirdweb.createThirdwebClient({
      clientId: buyWithCryptoTransaction.clientId
    })
  });
}

exports.EditionMetadataInputOrUriSchema = EditionMetadataInputOrUriSchema;
exports.EditionMetadataInputSchema = EditionMetadataInputSchema;
exports.EditionMetadataOutputSchema = EditionMetadataOutputSchema;
exports.EditionMetadataWithOwnerOutputSchema = EditionMetadataWithOwnerOutputSchema;
exports.OptionalPropertiesInput = OptionalPropertiesInput;
exports.ProposalState = ProposalState;
exports.TokenMintInputSchema = TokenMintInputSchema;
exports.compatibleExtensions = compatibleExtensions;
exports.computeNativeTokenAddress = computeNativeTokenAddress;
exports.deployContractDeterministicRaw = deployContractDeterministicRaw;
exports.getBuyWithCryptoHistory = getBuyWithCryptoHistory;
exports.getBuyWithCryptoQuote = getBuyWithCryptoQuote;
exports.getBuyWithCryptoStatus = getBuyWithCryptoStatus;
exports.getCachedAbiForContract = getCachedAbiForContract;
exports.matchesPrebuiltAbi = matchesPrebuiltAbi;
exports.toDisplayValue = toDisplayValue;
exports.toUnits = toUnits;
