Source

modules/OnchainAssetManager.ts

import { ethers } from "ethers";
import { MyriaClient } from "../clients/MyriaClient";
import { AssetAPI } from "../core/apis/asset.api";
import { AssetMarketpAPI } from "../core/apis/asset.marketp.api";
import { QueryAssetParams } from "../types";
import { APIResponseType } from "../types/APIResponseType";
import {
  AssetByCollectionIdResponse,
  AssetDetailsResponse, AssetDetailsResponseData, AssetHaveOrderResponse, AssetOwnerPublicKeyResponse,
  AssetStarkKeyResponse,
  AssetVaultDetailsResponseAPI, CollectionByIdDetailsParams,
  CollectionDetailsParams, NftAssetEqualMetadataResponse,
  QueryAssetParamsByOwner,
  QueryAssetRequestAPIParams,
  QueryAssetsWithCollectionResponse,
  QueryEqualMetadataNftAssetParams,
  RecrawlBatchNftMetadataParams,
  RecrawlBatchNftMetadataResponse,
  UpdateAssetMetadataParams,
  UpdatedAssetParams
} from "../types/AssetTypes";
import {
  CommonPaginateDataTypes,
  PagingDataParams
} from "../types/CommonTypes";
import { MintedAssetType } from "../types/MintTypes";
import { CommonModule } from "./CommonModule";

/**
 * Create OnchainAssetManager instance object
 * @class OnchainAssetManager
 * @param {MyriaClient} MyriaClient Interface of Myria Client
 * @example <caption>Constructor for OnchainAssetManager</caption>
 * 
 * 
 // Approach #1
  const mClient: IMyriaClient = {
      networkId: Network.SEPOLIA,
      provider: web3Instance.currentProvider,
      web3: web3Instance,
      env: EnvTypes.STAGING,
  };

  const moduleFactory = ModuleFactory.getInstance(mClient);
  const onchainAssetManager = moduleFactory.getOnchainAssetManager();

  // Approach #2
  const mClient: IMyriaClient = {
      networkId: Network.SEPOLIA,
      provider: web3Instance.currentProvider,
      web3: web3Instance,
      env: EnvTypes.STAGING,
  };

  const myriaClient = new MyriaClient(mClient);
  const onchainAssetManager = new OnchainAssetManager(myriaClient);
 */
export class OnchainAssetManager {
  private assetApi: AssetAPI;
  private assetMarketpApi: AssetMarketpAPI;
  private commonModule: CommonModule;
  public client: MyriaClient;


  constructor(client: MyriaClient) {
    this.client = client;
    this.assetApi = new AssetAPI(client.env);
    this.assetMarketpApi = new AssetMarketpAPI(client.env);
    this.commonModule = new CommonModule(client);
  }

  public async getListAssetsByStarkKey(starkKey: string): Promise<any> {
    const assetVaults = await this.assetApi.getAssetVaultsByStarkKey(starkKey);
    return assetVaults;
  }

  public async getAssetVaultDetails(
    starkKey: string,
    assetId: string
  ): Promise<APIResponseType<AssetVaultDetailsResponseAPI> | undefined> {
    const assetVaultDetails = await this.assetApi.getAssetVaultDetails({
      starkKey,
      assetId,
    });
    return assetVaultDetails;
  }

  // /**
  //  * 
  //  * @description Get all of minted NFTs (MINTABLE_ERC721) for specific stark key
  //  * @param {string} starkKey Stark public key of user
  //  * @returns 
  //  */
  public async getNftAssets(
    starkKey: string
  ): Promise<APIResponseType<MintedAssetType[]> | undefined> {
    const nftAssets = await this.assetApi.getNftAssets(starkKey);
    return nftAssets;
  }

/**
  * @description Get list of selling NFTs/assets
  * @param {PagingDataParams=} data requested the list assets/NFTs in paging format
  * @returns {CommonPaginateDataTypes<AssetDetailsResponse[]> | undefined} The response data for list assets details
  * @example <caption>Staging example code for getAssets</caption>
  const mClient: IMyriaClient = {
      networkId: Network.SEPOLIA,
      provider: web3Instance.currentProvider,
      web3: web3Instance,
      env: EnvTypes.STAGING,
  };

  const myriaClient = new MyriaClient(mClient);
  const onchainAssetManager = new OnchainAssetManager(myriaClient);

  const queryAssets: PagingDataParams = {
    limit: 50,
    page: 1
  };

  const assetsData = await onchainAssetManager.getAssets(queryAssets);
 */
  public async getAssets(
    data?: PagingDataParams
  ): Promise<
    APIResponseType<CommonPaginateDataTypes<AssetDetailsResponse[]>> | undefined> {
    const result = await this.assetMarketpApi.requestGetAsset(data);

    return result;
  }

/**
  * @description Get NFTs asset details by ID
  * @param {string} assetId The ID of the asset/NFT 
  * @throws {string} Exception: AssetId is required
  * @returns {APIResponseType<AssetDetailsResponse>} The full information of the NFTs/Assets
  * @example <caption>Staging example code for getAssetById</caption>
  const mClient: IMyriaClient = {
      networkId: Network.SEPOLIA,
      provider: web3Instance.currentProvider,
      web3: web3Instance,
      env: EnvTypes.STAGING,
  };

  const myriaClient = new MyriaClient(mClient);
  const onchainAssetManager = new OnchainAssetManager(myriaClient);

  const requestAssetDetails = {
    assetId: 1, // Asset id number
  }
  const assetsData: AssetDetailsResponse = await onchainAssetManager.getAssetById(requestAssetDetails);
*/
  public async getAssetById(
    assetId: string
  ): Promise<APIResponseType<AssetDetailsResponse> | undefined> {
    if (!assetId) {
      throw new Error("AssetId is required");
    } else {
      const result = await this.assetMarketpApi.requestGetAssetById(assetId);

      return result;
    }
  }

  /**
   * @description Refresh metadata for the NFTs and sync up with latest off-chain data for specific NFTs
   * @param {string} assetId The ID of the asset/NFT 
   * @param {string} starkKey The stark key owner of the asset to request refresh
   * @throws {string} Exception: Asset Id is required
   * @throws {string} Exception: Stark Key is required
   * @returns {APIResponseType<AssetDetailsResponse> | undefined} The full information of the refreshed NFTs/Assets
   * @example <caption>Staging example code for refreshAssetMetadata({})</caption>
  const mClient: IMyriaClient = {
      networkId: Network.SEPOLIA,
      provider: web3Instance.currentProvider,
      web3: web3Instance,
      env: EnvTypes.STAGING,
  };

  const myriaClient = new MyriaClient(mClient);
  const onchainAssetManager = new OnchainAssetManager(myriaClient);

  const assetId = 100;
  const starkKey = '0xfb....'; // Stark key of user;

  const refreshNftResponse: APIResponseType<AssetDetailsResponse> | undefined = await onchainAssetManager.refreshAssetMetadata(assetId, starkKey);
   */
  public async refreshAssetMetadata(
    assetId: string,
    starkKey: string
  ): Promise<APIResponseType<AssetDetailsResponse> | undefined> {
    if (!assetId) {
      throw new Error("Asset Id is required");
    }
    if (!starkKey) {
      throw new Error("Stark Key is required");
    }
    const result = await this.assetMarketpApi.requestSyncAttributeMetadata(assetId, starkKey);
    return result;
  }

  public async getAssetsByOwnerPublicKey(
    ownerPublicKey: string
  ): Promise<APIResponseType<AssetOwnerPublicKeyResponse> | undefined> {
    if (!ownerPublicKey) {
      throw new Error("OwnerPublicKey is required");
    } else {
      const result = await this.assetMarketpApi.requestAssetOwnerPublicKey(
        ownerPublicKey
      );
      return result;
    }
  }

  public async getAssetsByCollectionId(
    payload: CollectionDetailsParams
  ): Promise<
    | APIResponseType<CommonPaginateDataTypes<AssetByCollectionIdResponse>>
    | undefined
  > {
    if (!payload.collectionId) {
      throw new Error("OwnerPublicKey is required");
    }
    const result = await this.assetMarketpApi.getAssetByCollectionId(payload);
    return result;
  }

/**
  * @description Query the assets / NFTs and apply filtering (mostly for NFTs querying based on metadata)
  * @param {CollectionByIdDetailsParams} payload Query the assets / NFTs request with collection IDs and filterField, filterValue
  * @throws {string} Exception: Owner public key is required
  * @returns {APIResponseType<CommonPaginateDataTypes<AssetByCollectionIdResponse>>| undefined} Get list assets of NFTs
  * @example <caption>Staging example code for getAssetsWithFilter</caption>
  * 
  const mClient: IMyriaClient = {
      networkId: Network.SEPOLIA,
      provider: web3Instance.currentProvider,
      web3: web3Instance,
      env: EnvTypes.STAGING,
  };

  const myriaClient = new MyriaClient(mClient);
  const onchainAssetManager = new OnchainAssetManager(myriaClient);

  const queryAssetsPayload: CollectionByIdDetailsParams = {
    sortingField: 'created_at',
    orderBy: AssetOrderBy.DESC, 
    filterField: 'metadata',
    filterValue: '{body: ["Mustard"]}',
    collectionId: 10,
  };

  const listFilteredNfts = await onchainAssetManager.getAssetsWithFilter(queryAssetsPayload);
 */
  public async getAssetsWithFilter(
    payload: CollectionByIdDetailsParams
  ): Promise<
    | APIResponseType<CommonPaginateDataTypes<AssetByCollectionIdResponse>>
    | undefined
  > {
    if (!payload.collectionId) {
      throw new Error("Owner public key is required");
    }
    const result = await this.assetMarketpApi.requestAssetByCollectionId(
      payload
    );
    return result;
  }

  /**
   * @description Get all of NFTs/Assets belonging to the specific users (StarkKey)
   * @param {string} starkKey The stark key owner for the assets NFTs
   * @param {number?} page The index of page (1,2,3,...etc)
   * @param {number?} limit The total number of limited items per page
   * @throws {string} Exception: Stark Key is required
   * @returns {CommonPaginateDataTypes<AssetStarkKeyResponse> | undefined} The full information of NFTs/Assets list (Id, price, order, status...)
   * @example <caption>Staging example code for getAssetByStarkKey()</caption> 
  const mClient: IMyriaClient = {
      networkId: Network.SEPOLIA,
      provider: web3Instance.currentProvider,
      web3: web3Instance,
      env: EnvTypes.STAGING,
  };

  const myriaClient = new MyriaClient(mClient);
  const onchainAssetManager = new OnchainAssetManager(myriaClient);

  const starkKey = '0xfb...'; // User stark key
  const page = 1;
  const limit = 100;

  const listFilteredNfts = await onchainAssetManager.getAssetByStarkKey(starkKey, page, limit); 
  */
  public async getAssetByStarkKey(
    starkKey: string,
    page?: number,
    limit?: number
  ): Promise<APIResponseType<CommonPaginateDataTypes<AssetStarkKeyResponse>> | undefined> {
    if (!starkKey) {
      throw new Error("Stark Key is required");
    } else {
      const pageNumber = page || 1;
      const limitNumber = limit || 10;

      const result = await this.assetMarketpApi.requestAssetStarkKey(
        starkKey,
        pageNumber,
        limitNumber
      );
      return result;
    }
  }

  /**
   * @description Query the assets list by the collection owner
   * @param {QueryAssetParamsByOwner} queryAssetParams 
   * @throws {string} Exception: Collection IDs are required
   * @throws {string} Exception: Owner stark key is required
   * @returns {QueryAssetsWithCollectionResponse}  The assets list data includes collections / NFTs data of all and specific players
  * @example <caption>Staging example code for queryAssetsByCollectionOwner({})</caption> 
  const mClient: IMyriaClient = {
      networkId: Network.SEPOLIA,
      provider: web3Instance.currentProvider,
      web3: web3Instance,
      env: EnvTypes.STAGING,
  };

  const myriaClient = new MyriaClient(mClient);
  const onchainAssetManager = new OnchainAssetManager(myriaClient);

  const queryNftPayload: QueryAssetParamsByOwner = {
    starkKey: '0xfb...',
    ownerWalletAddress: '0xab....', // Owner of the nfts
    collectionIds: [1,2,3,5], // The list of collections ID that partners have in myria system
    walletAddress: '0xa9a......', // Example of user's wallet address - it's optional, if wallet address is not passed, it's default to return all of nft's data for different users wallet address
    page: 1,
    limit: 100,
  };

  const listFilteredNfts = await onchainAssetManager.queryAssetsByCollectionOwner(queryNftPayload); 
   */
  public async queryAssetsByCollectionOwner(
    queryAssetParams: QueryAssetParamsByOwner
  ): Promise<APIResponseType<CommonPaginateDataTypes<QueryAssetsWithCollectionResponse>> | undefined> {

    if (!queryAssetParams.collectionIds || queryAssetParams.collectionIds.length === 0) {
      throw new Error("Collection IDs are required")
    }
    if (!queryAssetParams.starkKey) {
      throw new Error("Owner stark key is required");
    }

    const requestAssetsAPIParams: QueryAssetRequestAPIParams = {
      starkKey: queryAssetParams.starkKey,
      collectionIds: queryAssetParams.collectionIds,
      walletAddress: queryAssetParams.walletAddress,
      limit: queryAssetParams.limit,
      page: queryAssetParams.page
    };
    
    const result = await this.assetMarketpApi.requestAssetsByCollectionOwner(requestAssetsAPIParams);
    
    if (!result) {
      throw new Error("Request assets by collection owner failed");
    }

    return result;
  }

  public async updateAssetById(
    assetId: string,
    data: UpdatedAssetParams
  ): Promise<APIResponseType<UpdatedAssetParams[]> | undefined> {
    let assetIdData;
    if (!assetId) {
      throw new Error("AssetId is required");
    }
    if (!data.starkKey) {
      throw new Error("StarkKey is required");
    }
    if (!data.uri) {
      throw new Error("Uri is required");
    }
    if (!data.assetType) {
      throw new Error("AssetType is required");
    }
    if (!data.tokenId) {
      throw new Error("TokenId is required");
    }
    if (!data.tokenAddress) {
      throw new Error("TokenAddress is required");
    }
    if (!data.collectionId) {
      throw new Error("CollectionId is required");
    }
    if (!data.status) {
      throw new Error("Status is required");
    }
    try {
      const result = await this.assetMarketpApi.requestUpdateAssetId(
        assetId,
        data
      );
      if (result?.status === "success") {
        assetIdData = result;
      }
    } catch (error) {
      throw new Error("UpdateAssetIdMarketp  failure");
    }
    return assetIdData;
  }

  public async updateAssetMetadataByAssetId(
    assetId: number,
    data: UpdateAssetMetadataParams
  ): Promise<APIResponseType<AssetDetailsResponseData[]> | undefined> {
    let assetIdMetadata;
    if (!assetId) {
      throw new Error("AssetId is required");
    }
    if (!data.name) {
      throw new Error("Name is required");
    }
    if (!data.animationUrl) {
      throw new Error("AnimationUrl is required");
    }
    if (!data.animationUrlMimeType) {
      throw new Error("AnimationUrlMimeType is required");
    }
    if (!data.imageUrl) {
      throw new Error("Image is required");
    }
    if (!data.attack) {
      throw new Error("Attack is required");
    }
    if (!data.collectable) {
      throw new Error("Collectable is required");
    }
    if (!data.god) {
      throw new Error("God is required");
    }
    if (!data.element) {
      throw new Error("Element is required");
    }
    if (!data.product) {
      throw new Error("Product is required");
    }
    if (!data.rarity) {
      throw new Error("Rarity is required");
    }
    if (!data.type) {
      throw new Error("Type is required");
    }
    try {
      const result = await this.assetMarketpApi.requestUpdateAssetIdMetadata(
        assetId,
        data
      );
      if (result?.status === "success") {
        assetIdMetadata = result;
      }
    } catch (error) {
      throw new Error("UpdateAssetIdMetadataMarketp  failure");
    }
    return assetIdMetadata;
  }

  /**
   * @description Get NFTs with same metadata
   * @param {QueryEqualMetadataNftAssetParams} payload The payload to request the same assets/NFTs
   * @throws {string} Exception: AssetId is required
   * @returns {CommonPaginateDataTypes<NftAssetEqualMetadataResponse> | undefined} The list of same NFTs which have same metadata for the asset request
   * @example <caption>Staging example code for getAssetEqualMetadataById({})</caption>
  const mClient: IMyriaClient = {
      networkId: Network.SEPOLIA,
      provider: web3Instance.currentProvider,
      web3: web3Instance,
      env: EnvTypes.STAGING,
  };

  const myriaClient = new MyriaClient(mClient);
  const onchainAssetManager = new OnchainAssetManager(myriaClient);

  const assetId = 100;
  const requestEqualMetadata = {
    assetId: 100,
  };

  const sameNftsWithMetadata = await onchainAssetManager.getAssetEqualMetadataById(requestEqualMetadata);
   */
  public async getAssetEqualMetadataById(
    payload: QueryEqualMetadataNftAssetParams
  ): Promise<
    | APIResponseType<CommonPaginateDataTypes<NftAssetEqualMetadataResponse>>| undefined> {
    if (!payload.assetId) {
      throw new Error("AssetId is required");
    } else {
      const result = await this.assetMarketpApi.requestAssetEqualMetadataById(
        payload
      );
      return result;
    }
  }

  /**
   * @description Function is to query the list of NFTs with a given order status along with the option to sort the data by specific fields
   * @param {QueryAssetParams} payload The requested for assets query
   * @throws {string} Exception: Order status is required
   * @returns {CommonPaginateDataTypes<AssetHaveOrderResponse[]> | undefined}
   * @example <caption>Staging example code for getNftAssetsByStatus({})</caption>
  const mClient: IMyriaClient = {
      networkId: Network.SEPOLIA,
      provider: web3Instance.currentProvider,
      web3: web3Instance,
      env: EnvTypes.STAGING,
  };

  const myriaClient = new MyriaClient(mClient);
  const onchainAssetManager = new OnchainAssetManager(myriaClient);

  const queryNftParams: QueryAssetParams = {
    orderType: OrderType.SELL,
    status: OrderStatus.ACTIVE,
    sortingField: "amountSell",
    orderBy: AssetOrderBy.ASC; // increasement by the prices of NFT list
  };

  const sameNftsWithMetadata = await onchainAssetManager.getNftAssetsByStatus(queryNftParams);
   */
  public async getNftAssetsByStatus(
    payload: QueryAssetParams
  ): Promise<
    | APIResponseType<CommonPaginateDataTypes<AssetHaveOrderResponse[]>>
    | undefined
  > {
    if (!payload.status) {
      throw new Error("Order status is required");
    }

    const listNftAssets = await this.assetMarketpApi.getAssetsByStatus(payload);
    return listNftAssets;
  }


  /**
   * @description The function to recrawl the list of NFTs in range with start token id and end token id
   * @param {RecrawlBatchNftMetadataParams} payload The request payload to recraw the list of NFT's metadata
   * @throws {string} Exception: CreatorStarkKey is required
   * @throws {string} Exception: StartTokenId is required
   * @throws {string} Exception: EndTokenId is required
   * @throws {string} Exception: TokenAddress is required
   * @throws {string} Exception: Start tokenId should be less than end tokenId.
   * @throws {string} Exception: Token address should be the valid ethereum address type
   * @throws {string} Http Status 500: Refresh metadata failed: ${INTERNAL_SERVER_ERROR}
   * @returns {APIResponseType<RecrawlBatchNftMetadataResponse> | undefined}
   * @example <caption>Staging example code for recrawlBatchNftMetadata({})</caption>
  const mClient: IMyriaClient = {
      networkId: Network.SEPOLIA,
      provider: web3Instance.currentProvider,
      web3: web3Instance,
      env: EnvTypes.STAGING,
  };

  const myriaClient = new MyriaClient(mClient);
  const onchainAssetManager = new OnchainAssetManager(myriaClient);

  const recrawlNftMetadata: RecrawlBatchNftMetadataParams = {
    creatorStarkKey: '0xfbc.....',
    startTokenId: '1',
    endTokenId: '100',
    tokenAddress: '0xfbc.....'
  };

  const recrawlBatchNfts = await onchainAssetManager.recrawlBatchNftMetadata(recrawlNftMetadata);
   */
  public async recrawlBatchNftMetadata(
    payload: RecrawlBatchNftMetadataParams
  ): Promise<APIResponseType<RecrawlBatchNftMetadataResponse> | undefined> {
    if (!payload.creatorStarkKey) {
      throw new Error("CreatorStarkKey is required");
    }
    if (!payload.startTokenId || !Number(payload.startTokenId?.trim())) {
      throw new Error("StartTokenId is required");
    }
    if (!payload.endTokenId || !Number(payload.endTokenId?.trim())) {
      throw new Error("EndTokenId is required");
    }
    if (!payload.tokenAddress) {
      throw new Error("TokenAddress is required");
    }
    if(payload.startTokenId > payload.endTokenId) {
      throw new Error(`Start tokenId should be less than end tokenId. \n Current value is: \n startTokenId: ${payload.startTokenId}, \n endTokenId: ${payload.endTokenId}`)
    }
    if(!ethers.utils.isAddress(payload.tokenAddress)) {
      throw new Error("Token address should be the valid ethereum address type.");
    }

    try {
      const response = await this.assetMarketpApi.requestRecrawlBatchNftMetadata(payload);
      if (response?.status === 'success') {
        return response
      } else {
        throw new Error(`Refresh metadata failed: ${response}`);
      }
    } catch (err) {
      throw new Error(`Refresh metadata failed: ${err}`);
    }

  }
}