# apostle/v1.ts

    import { ethers } from "ethers";
    import { erc20Allowance } from "@evo/api/ethers/erc20";
    import { pad0xBegin } from "@evo/utils/common/utils";
    import { toHexAndPadLeft, padLeft } from "@evo/utils/ethers/utils";
    import {
      getContractAddressAndAbiByContractName,
      triggerContractByContractName,
      viewContractByContractName,
    } from "@evo/utils/ethers/contractHelper";
    import type { ElementType } from "@evo/types";
    import type { CallbackType } from "@evo/utils/ethers/contractHelper";
    import { LandId } from "@evo/config/constants";
    import { TransactionResponse } from "@ethersproject/providers";
    import { getAddressesByLandId } from "@evo/utils/ethers/addressHelper";

    /**
     * Apostle Allowance To Clock Auction
     * @param provider Ethers provider
     * @param from From
     * @param callback Callback
     * @returns Promise<string | undefined>
     */
    export const apostleAllowanceToClockAuction = async (
      landId: LandId,
      provider: ethers.providers.Provider,
      from: string,
      callback?: CallbackType
    ): Promise<string | undefined> => {
      const spender = getContractAddressAndAbiByContractName(landId, provider, "apostleAuction").address;
      if (spender) {
        return erc20Allowance(landId, provider, "ring", from, spender as string);
      } else {
        callback &&
          callback.errorCallback({ error: new Error("failed to call apostleAllowanceToClockAuction. spender undefined") });
      }
    };

    /**
     * Apostle Bid
     * @param signer Ethers signer
     * @param tokenId Apostle token id
     * @param referrer Refer address
     * @param amountMax RING Amount
     * @param callback Callback
     */
    export const apostleBidWithToken = (
      landId: LandId,
      signer: ethers.Signer,
      tokenId: string,
      referrer: string,
      amountMax: string,
      callback?: CallbackType
    ): Promise<TransactionResponse> => {
      const finalReferrer =
        referrer && ethers.utils.isAddress(referrer) ? referrer : "0x0000000000000000000000000000000000000000";

      if (landId === LandId.ETHEREUM || landId === LandId._ETHEREUM) {
        const data = referrer
          ? `${pad0xBegin(tokenId)}${ethers.utils.hexZeroPad(referrer, 32).slice(2)}`
          : pad0xBegin(tokenId);

        const addresses = getAddressesByLandId(landId);
        if (!addresses || !addresses.APOSTLE_CLOCK_AUCTION) {
          throw "address error";
        }

        return triggerContractByContractName(
          landId,
          signer,
          "ring",
          "transfer(address,uint256,bytes)",
          [addresses.APOSTLE_CLOCK_AUCTION, amountMax, data],
          callback
        );
      }

      return triggerContractByContractName(
        landId,
        signer,
        "apostleClockAuctionV3",
        "bidWithToken",
        [pad0xBegin(tokenId), finalReferrer, amountMax],
        callback
      );
    };

    /**
     * Apostle Breed - Apostle reproduction in own
     * @param signer Ethers signer
     * @param tokenId Token id
     * @param targetTokenId Target token id
     * @param amountMax Amount max
     * @param callback Callback
     */
    export const apostleBreed = (
      landId: LandId,
      signer: ethers.Signer,
      tokenId: string,
      targetTokenId: string,
      amountMax: string,
      callback?: CallbackType
    ): Promise<TransactionResponse> => {
      if (landId === LandId.ETHEREUM || landId === LandId._ETHEREUM) {
        const addresses = getAddressesByLandId(landId);
        if (!addresses || !addresses.APOSTLE_BASE) {
          throw "address error";
        }

        return triggerContractByContractName(
          landId,
          signer,
          "ring",
          "transfer(address,uint256,bytes)",
          [
            addresses.APOSTLE_BASE,
            amountMax,
            `${pad0xBegin(tokenId)}${ethers.utils.hexZeroPad(pad0xBegin(targetTokenId), 32).slice(2)}`,
          ],
          callback
        );
      }

      return triggerContractByContractName(
        landId,
        signer,
        "apostleBaseV3",
        "breedWithAuto",
        [pad0xBegin(tokenId), pad0xBegin(targetTokenId), amountMax],
        callback
      );
    };

    /**
     * Apostle Breed Bid - Apostle reproduction
     * @param signer Ethers signer
     * @param tokenId Token id
     * @param targetTokenId Target token id
     * @param amountMax Amount max (bidPrice + autoBirthFee)
     * @param callback Callback
     */
    export const apostleBreedBid = (
      landId: LandId,
      signer: ethers.Signer,
      tokenId: string,
      targetTokenId: string,
      amountMax: string,
      callback?: CallbackType
    ): Promise<TransactionResponse> => {
      if (landId === LandId.ETHEREUM || landId === LandId._ETHEREUM) {
        const addresses = getAddressesByLandId(landId);
        if (!addresses || !addresses.SIRING_CLOCK_AUCTION) {
          throw "address error";
        }

        return triggerContractByContractName(
          landId,
          signer,
          "ring",
          "transfer(address,uint256,bytes)",
          [
            addresses.SIRING_CLOCK_AUCTION,
            amountMax,
            `${pad0xBegin(tokenId)}${ethers.utils.hexZeroPad(pad0xBegin(targetTokenId), 32).slice(2)}`,
          ],
          callback
        );
      }

      return triggerContractByContractName(
        landId,
        signer,
        "apostleSiringAuctionV3",
        "bidWithToken",
        [pad0xBegin(tokenId), pad0xBegin(targetTokenId), amountMax],
        callback
      );
    };

    /**
     * Apostle Hire Bid - Bid apostle on Renting
     * @param signer Ethers signer
     * @param tokenId Apostle token id
     * @param amountMax Amount max (bid price)
     * @param callback Callback
     */
    export const apostleHireBid = (
      landId: LandId,
      signer: ethers.Signer,
      tokenId: string,
      amountMax: string,
      callback?: CallbackType
    ): Promise<TransactionResponse> => {
      if (landId === LandId.ETHEREUM || landId === LandId._ETHEREUM) {
        const addresses = getAddressesByLandId(landId);
        if (!addresses || !addresses.TOKEN_USE) {
          throw "address error";
        }

        return triggerContractByContractName(
          landId,
          signer,
          "ring",
          "transfer(address,uint256,bytes)",
          [addresses.TOKEN_USE, amountMax, pad0xBegin(tokenId)],
          callback
        );
      }

      return triggerContractByContractName(
        landId,
        signer,
        "apostleTokenUseV2",
        "takeTokenUseOffer",
        [pad0xBegin(tokenId), amountMax],
        callback
      );
    };

    /**
     * Apostle Born Without Element
     * @param signer Ethers signer
     * @param motherTokenId Mother token id
     * @param callback Callback
     */
    export const apostleBorn = (
      landId: LandId,
      signer: ethers.Signer,
      motherTokenId: string,
      callback?: CallbackType
    ): Promise<TransactionResponse> => {
      return triggerContractByContractName(
        landId,
        signer,
        "apostleBaseV3",
        "giveBirth",
        [pad0xBegin(motherTokenId), ethers.utils.hexZeroPad("0x", 20), 0, 0],
        callback
      );
    };

    /**
     * Apostle Born And Enhance
     * @param signer Ethers signer
     * @param motherTokenId Mother token id
     * @param element Element
     * @param level Level
     * @param levelUnitPrice Level unit price
     * @param callback Callback
     * @returns Void
     */
    export const apostleBornAndEnhance = async (
      landId: LandId,
      signer: ethers.Signer,
      motherTokenId: string,
      element: ElementType,
      level: number,
      levelUnitPrice: string,
      callback?: CallbackType
    ): Promise<TransactionResponse> => {
      const elementAddress = getContractAddressAndAbiByContractName(landId, signer, element).address;
      const cost = ethers.BigNumber.from(level).mul(ethers.BigNumber.from(levelUnitPrice)).toString();

      if (landId === LandId.ETHEREUM || landId === LandId._ETHEREUM) {
        const addresses = getAddressesByLandId(landId);
        if (!addresses || !addresses.APOSTLE_BASE) {
          throw "address error";
        }

        return triggerContractByContractName(
          landId,
          signer,
          element,
          "transfer(address,uint256,bytes)",
          [
            addresses.APOSTLE_BASE,
            cost,
            `${pad0xBegin(motherTokenId)}${ethers.utils.hexZeroPad(ethers.utils.hexlify(level), 32).slice(2)}`,
          ],
          callback
        );
      }

      return triggerContractByContractName(
        landId,
        signer,
        "apostleBaseV3",
        "giveBirth",
        [pad0xBegin(motherTokenId), elementAddress, level, cost],
        callback
      );
    };

    /**
     * Returns the current price of an auction.
     * @param provider Ethers signer
     * @param tokenId Apostle Token Id
     * @param callback Callback
     * @returns any
     */
    export const apostleGetCurrentPriceByTokenId = async (
      landId: LandId,
      provider: ethers.providers.Provider,
      tokenId: string,
      callback?: CallbackType
    ): Promise<string> => {
      const results = await viewContractByContractName(
        landId,
        provider,
        "apostleClockAuctionV3",
        "getCurrentPriceInToken",
        [pad0xBegin(tokenId)],
        callback
      );
      return (results[0] as ethers.BigNumber).toHexString();
    };

    /**
     * Returns the current siring price of an auction.
     * @param provider Ethers signer
     * @param tokenId Token Id
     * @param callback Callback
     * @returns any
     */
    export const apostleGetCurrentSiringPriceByTokenId = async (
      landId: LandId,
      provider: ethers.providers.Provider,
      tokenId: string,
      callback?: CallbackType
    ): Promise<string> => {
      const results = await viewContractByContractName(
        landId,
        provider,
        "apostleSiringAuctionV3",
        "getCurrentPriceInToken",
        [pad0xBegin(tokenId)],
        callback
      );
      return (results[0] as ethers.BigNumber).toHexString();
    };

    /**
     * Sell Apostle asset
     * @param signer Ethers signer
     * @param from
     * @param tokenId - Apostle tokenId
     * @param start - start price
     * @param end - end price
     * @param duration - bid duration time in second
     * @param callback Callback
     * @returns {Promise<void>}
     */
    export const apostleAskWithToken = async (
      landId: LandId,
      signer: ethers.Signer,
      from: string,
      tokenId: string,
      startPrice: string,
      endPrice: string,
      duration: string,
      callback?: CallbackType
    ): Promise<TransactionResponse> => {
      const _from = padLeft(from.slice(2), 64, "0");
      const _start = toHexAndPadLeft(startPrice).slice(2);
      const _end = toHexAndPadLeft(endPrice).slice(2);
      const _duration = toHexAndPadLeft(duration).slice(2);
      const data = `0x${_start}${_end}${_duration}${_from}`;
      const apostleClockAuctionV3 = getContractAddressAndAbiByContractName(landId, signer, "apostleClockAuctionV3");

      return triggerContractByContractName(
        landId,
        signer,
        "objectOwnership",
        "approveAndCall",
        [apostleClockAuctionV3.address, pad0xBegin(tokenId), data],
        callback
      );
    };

    /**
     * Cancel the auction by apostle token ID
     * @param landId Land Id
     * @param signer Signer
     * @param tokenId apostle token Id
     * @param callback Callback
     * @returns any
     */
    export const apostleCancelAskWithToken = (
      landId: LandId,
      signer: ethers.Signer,
      tokenId: string,
      callback?: CallbackType
    ): Promise<TransactionResponse> => {
      return triggerContractByContractName(
        landId,
        signer,
        "apostleAuction",
        "cancelAuction",
        [pad0xBegin(tokenId)],
        callback
      );
    };

    /**
     * Renting apostles to work
     * @param landId Land Id
     * @param signer Signer
     * @param resourceAddress Resource contract address
     * @param tokenId Apostle tokenId
     * @param price Hire price
     * @param duration Duration in second
     * @param callback Callback
     */
    export const apostleHire = async (
      landId: LandId,
      signer: ethers.Signer,
      resourceAddress: string,
      tokenId: string,
      price: string,
      duration: string,
      callback?: CallbackType
    ): Promise<TransactionResponse> => {
      const _price = toHexAndPadLeft(price).slice(2);
      const _duration = toHexAndPadLeft(duration).slice(2);
      const _resourceAddress = padLeft(resourceAddress.slice(2), 64, "0");
      const data = `0x${_duration}${_price}${_resourceAddress}`;
      const apostleTokenUseV2Contract = getContractAddressAndAbiByContractName(landId, signer, "apostleTokenUseV2");

      return triggerContractByContractName(
        landId,
        signer,
        "objectOwnership",
        "approveAndCall",
        [apostleTokenUseV2Contract.address, pad0xBegin(tokenId), data],
        callback
      );
    };

    /**
     * Cancel an apostle on Renting
     * @param landId Land Id
     * @param signer Signer
     * @param tokenId Apostle tokenId
     * @param callback Callback
     * @returns any
     */
    export const apostleCancelHire = (
      landId: LandId,
      signer: ethers.Signer,
      tokenId: string,
      callback?: CallbackType
    ): Promise<TransactionResponse> => {
      return triggerContractByContractName(
        landId,
        signer,
        "apostleTokenUseV2",
        "cancelTokenUseOffer",
        [pad0xBegin(tokenId)],
        callback
      );
    };

    /**
     * Let apostle go to work
     * @param landId Land Id
     * @param signer Signer
     * @param apostleTokenId Apostle token Id
     * @param landTokenId Land token Id
     * @param elementContractAddress Element contract address
     * @param callback Callback
     * @returns any
     */
    export const apostleWork = (
      landId: LandId,
      signer: ethers.Signer,
      apostleTokenId: string,
      landTokenId: string,
      elementContractAddress: string,
      callback?: CallbackType
    ): Promise<TransactionResponse> => {
      return triggerContractByContractName(
        landId,
        signer,
        "apostleLandResource",
        "startMining",
        [pad0xBegin(apostleTokenId), pad0xBegin(landTokenId), elementContractAddress],
        callback
      );
    };

    /**
     * Stop apostle mining
     * @param landId Land Id
     * @param signer Signer
     * @param tokenId Apostle tokenId
     * @param callback Callback
     * @returns any
     */
    export const apostleStopWork = async (
      landId: LandId,
      signer: ethers.Signer,
      tokenId: string,
      callback?: CallbackType
    ): Promise<TransactionResponse> => {
      return triggerContractByContractName(
        landId,
        signer,
        "apostleLandResource",
        "stopMining",
        [pad0xBegin(tokenId)],
        callback
      );
    };

    /**
     * Get apostle breed fee
     * @param landId Land Id
     * @param provider Provider
     * @param callback Callback
     * @returns fee in string
     */
    export const apostleGetAutoBreedFee = async (
      landId: LandId,
      provider: ethers.providers.Provider,
      callback?: CallbackType
    ): Promise<string> => {
      const results = await viewContractByContractName(
        landId,
        provider,
        "settingsRegistry",
        "uintOf",
        ["0x55494e545f4155544f42495254485f4645450000000000000000000000000000"], // UINT_AUTOBIRTH_FEE
        callback
      );
      return (results[0] as ethers.BigNumber).toHexString();
    };

    /**
     * Receive apostle
     * @param landId Land Id
     * @param signer Singer
     * @param tokenId Apostle tokenId
     * @param callback Callback
     * @returns any
     */
    export const apostleClaim = async (
      landId: LandId,
      signer: ethers.Signer,
      tokenId: string,
      callback?: CallbackType
    ): Promise<TransactionResponse> => {
      return triggerContractByContractName(
        landId,
        signer,
        "apostleAuction",
        "claimApostleAsset",
        [pad0xBegin(tokenId)],
        callback
      );
    };

    /**
     * Apostle Breed Auction
     * @param landId Land Id
     * @param signer Signer
     * @param from
     * @param tokenId Apostle tokenId
     * @param startPrice Start price
     * @param endPrice End price
     * @param duration Auction duration time in second
     * @param callback Callback
     * @returns any
     */
    export const apostleBreedAsk = async (
      landId: LandId,
      signer: ethers.Signer,
      from: string,
      tokenId: string,
      startPrice: string,
      endPrice: string,
      duration: string,
      callback?: CallbackType
    ): Promise<TransactionResponse> => {
      const _from = padLeft(from.slice(2), 64, "0");
      const _start = toHexAndPadLeft(startPrice).slice(2);
      const _end = toHexAndPadLeft(endPrice).slice(2);
      const _duration = toHexAndPadLeft(duration).slice(2);
      const data = `0x${_start}${_end}${_duration}${_from}`;
      const apostleSiringAuctionV3Contract = getContractAddressAndAbiByContractName(
        landId,
        signer,
        "apostleSiringAuctionV3"
      );
      return triggerContractByContractName(
        landId,
        signer,
        "objectOwnership",
        "approveAndCall",
        [apostleSiringAuctionV3Contract.address, pad0xBegin(tokenId), data],
        callback
      );
    };

    /**
     * Cancel Apostle Breed Auction
     * @param landId Land Id
     * @param signer Signer
     * @param tokenId Apostle tokenId
     * @param callback Callback
     * @returns any
     */
    export const apostleCancelBreedAsk = async (
      landId: LandId,
      signer: ethers.Signer,
      tokenId: string,
      callback?: CallbackType
    ): Promise<TransactionResponse> => {
      return triggerContractByContractName(
        landId,
        signer,
        "apostleSiringAuctionV3",
        "cancelAuction",
        [pad0xBegin(tokenId)],
        callback
      );
    };

    /**
     * Transfer Apsotle
     * @param landId Land Id
     * @param signer Signer
     * @param from Apostle owner
     * @param to Recipient
     * @param tokenId Apostle tokenId
     * @param callback Callback
     * @returns any
     */
    export const apostleTransfer = async (
      landId: LandId,
      signer: ethers.Signer,
      from: string,
      to: string,
      tokenId: string,
      callback?: CallbackType
    ): Promise<TransactionResponse> => {
      return triggerContractByContractName(
        landId,
        signer,
        "objectOwnership",
        "transferFrom",
        [from, to, pad0xBegin(tokenId)],
        callback
      );
    };

    /**
     * Claim an apostle that expires at work
     * @param landId Land Id
     * @param signer Signer
     * @param tokenId Apostle tokenId
     * @param callback Callback
     * @returns any
     */
    export const apostleClaimHire = async (
      landId: LandId,
      signer: ethers.Signer,
      tokenId: string,
      callback?: CallbackType
    ): Promise<TransactionResponse> => {
      return triggerContractByContractName(
        landId,
        signer,
        "apostleTokenUseV2",
        "removeTokenUseAndActivity",
        [pad0xBegin(tokenId)],
        callback
      );
    };