# erc721/index.ts

    import { ethers } from "ethers";
    import { getAbiObjectOwnership, getAbiERC721, getAbiERC721Collection } from "@evo/utils/ethers/abiHelper";
    import type { CallbackType } from "@evo/utils/ethers/contractHelper";
    import { pad0xBegin } from "@evo/utils/common/utils";
    import { triggerContract, viewContract, getContract } from "@evo/utils/ethers/contractHelper";
    import { TransactionResponse } from "@ethersproject/providers";

    /**
     * ERC721 Owner of
     * @param provider Provider
     * @param tokenAddress ERC721 contract address
     * @param tokenId Token Id
     * @param callback Callback
     * @returns The address of owner
     */
    export const erc721OwnerOf = async (
      provider: ethers.providers.Provider,
      tokenAddress: string,
      tokenId: string,
      callback?: CallbackType
    ): Promise<string> => {
      const contract = await getContract(provider, tokenAddress, await getAbiObjectOwnership());

      const results = await viewContract(contract, "ownerOf", [tokenId], callback);
      return results.length ? results[0] : "";
    };

    /**
     * Query if an address is an authorized operator for another address
     * @param provider Provider
     * @param owner The address that owns the NFTs
     * @param spender The address that acts on behalf of the owner
     * @param tokenContractAddress ERC721 contract address
     * @param callback Callback
     * @returns any
     */
    export const erc721IsApprovedForAll = async (
      provider: ethers.providers.Provider,
      owner: string,
      spender: string,
      tokenContractAddress: string,
      callback?: CallbackType
    ): Promise<boolean> => {
      const contract = await getContract(provider, tokenContractAddress, await getAbiERC721Collection());
      const results = await viewContract(contract, "isApprovedForAll", [owner, spender]);
      return results.length ? results[0] : false;
    };

    /**
     * Returns whether `spender` is allowed to manage `tokenId`.
     * @param provider Provider
     * @param spender The address that acts on behalf of the owner
     * @param tokenContractAddress The factory of tokenId.
     * @param tokenId ERC721 token Id;
     * @param callback Callback
     * @returns any
     */
    export const erc721IsApprovedOrOwner = async (
      provider: ethers.providers.Provider,
      spender: string,
      tokenContractAddress: string,
      tokenId: string,
      callback?: CallbackType
    ): Promise<boolean> => {
      const contract = await getContract(provider, tokenContractAddress, await getAbiERC721());

      const owner = await viewContract(contract, "ownerOf", [pad0xBegin(tokenId)]);

      if (!owner._owner) {
        throw new Error("erc721IsApprovedOrOwner::Token Id does not exist!");
      }

      const approvedAddress = await viewContract(contract, "getApproved", [pad0xBegin(tokenId)]);
      const isApprovedForAll = await erc721IsApprovedForAll(provider, owner._owner, spender, tokenContractAddress);

      return (
        owner._owner?.toLowerCase() === spender.toLowerCase() ||
        approvedAddress._approved?.toLowerCase() === spender.toLowerCase() ||
        isApprovedForAll
      );
    };

    /**
     * Change or reaffirm the approved address for all of NFT
     * @param signer Signer
     * @param contractAddress The factory of tokenId.
     * @param spender The address that acts on behalf of the owner
     * @param approved Approved or Cancel Approved
     * @param callback Callback
     * @returns any
     */
    export const erc721SetApprovalForAll = async (
      signer: ethers.Signer,
      contractAddress: string,
      spender: string,
      approved: boolean,
      callback?: CallbackType
    ): Promise<TransactionResponse> => {
      const contract = await getContract(signer, contractAddress, await getAbiERC721Collection());
      return triggerContract(contract, "setApprovalForAll", [spender, approved], callback);
    };