Repository: alchemyplatform/alchemy-sdk-js
Pull request #328 (2023-05-18 00:55:50)

Update response types in v3
Building on the previous PR in #327, this PR updates the new response types so that all integration tests are passing:

External changes:
- Added `GetNftMetadataBatchResponse`, `GetContractMetadataBatchResponse`, `IsSpamContractResponse`, `GetSpamContractsResponse`, `ComputeRarityResponse`, and `SearchContractMetadataResponse` objects for v3 that replaced primitive or array return types. All methods in v3 should now return an object to allow for future extensibility without breaking changes.
- Renamed `ownerAddresses` to `owners` for `getOwnersForContract()` return object as part of v3
- updated `totalSupply` fields from `number` to `string` to allow for 1155 overflows in v3.

Internal changes:
- Added new `Raw*` types to fully separate backend types from exported types
- Fixed `getTransfers*` methods to properly use the new `GetNftMetadataBatchResponse` type.
- Various test fixes

thebrianchen: @thebrianchen started a stack merge that includes this pull request via [Graphite](https://app.graphite.dev/github/pr/alchemyplatform/alchemy-sdk-js/328).
thebrianchen: [Graphite](https://app.graphite.dev/github/pr/alchemyplatform/alchemy-sdk-js/328) rebased this pull request as part of a merge.

Patch:
diff --git a/src/api/nft-namespace.ts b/src/api/nft-namespace.ts
index 73042a5e..53fc4a38 100644
--- a/src/api/nft-namespace.ts
+++ b/src/api/nft-namespace.ts
@@ -27,12 +27,15 @@ import {
   verifyNftOwnership
 } from '../internal/nft-api';
 import {
+  ComputeRarityResponse,
   GetBaseNftsForContractOptions,
   GetBaseNftsForOwnerOptions,
+  GetContractMetadataBatchResponse,
   GetContractsForOwnerOptions,
   GetContractsForOwnerResponse,
   GetFloorPriceResponse,
   GetMintedNftsOptions,
+  GetNftMetadataBatchResponse,
   GetNftMetadataOptions,
   GetNftSalesOptions,
   GetNftSalesOptionsByContractAddress,
@@ -45,10 +48,11 @@ import {
   GetOwnersForContractWithTokenBalancesResponse,
   GetOwnersForNftOptions,
   GetOwnersForNftResponse,
+  GetSpamContractsResponse,
   GetTransfersForContractOptions,
   GetTransfersForOwnerOptions,
   GetTransfersForOwnerTransferType,
-  NftAttributeRarity,
+  IsSpamContractResponse,
   NftAttributesResponse,
   NftContractBaseNftsResponse,
   NftContractNftsResponse,
@@ -60,6 +64,7 @@ import {
   OwnedNft,
   OwnedNftsResponse,
   RefreshContractResult,
+  SearchContractMetadataResponse,
   TransfersNftResponse
 } from '../types/types';
 import { AlchemyConfig } from './alchemy-config';
@@ -120,7 +125,7 @@ export class NftNamespace {
   getNftMetadataBatch(
     tokens: Array<NftMetadataBatchToken>,
     options?: NftMetadataBatchOptions
-  ): Promise<Nft[]> {
+  ): Promise<GetNftMetadataBatchResponse> {
     return getNftMetadataBatch(this.config, tokens, options);
   }
 
@@ -141,7 +146,7 @@ export class NftNamespace {
    */
   getContractMetadataBatch(
     contractAddresses: string[]
-  ): Promise<NftContract[]> {
+  ): Promise<GetContractMetadataBatchResponse> {
     return getContractMetadataBatch(this.config, contractAddresses);
   }
 
@@ -437,7 +442,7 @@ export class NftNamespace {
    * @param contractAddress - The contract address to check.
    * @beta
    */
-  isSpamContract(contractAddress: string): Promise<boolean> {
+  isSpamContract(contractAddress: string): Promise<IsSpamContractResponse> {
     return isSpamContract(this.config, contractAddress);
   }
 
@@ -448,7 +453,7 @@ export class NftNamespace {
    *
    * @beta
    */
-  getSpamContracts(): Promise<string[]> {
+  getSpamContracts(): Promise<GetSpamContractsResponse> {
     return getSpamContracts(this.config);
   }
 
@@ -487,7 +492,7 @@ export class NftNamespace {
   computeRarity(
     contractAddress: string,
     tokenId: BigNumberish
-  ): Promise<NftAttributeRarity[]> {
+  ): Promise<ComputeRarityResponse> {
     return computeRarity(this.config, contractAddress, tokenId);
   }
 
@@ -496,7 +501,9 @@ export class NftNamespace {
    *
    * @param query - The search string that you want to search for in contract metadata.
    */
-  searchContractMetadata(query: string): Promise<NftContract[]> {
+  searchContractMetadata(
+    query: string
+  ): Promise<SearchContractMetadataResponse> {
     return searchContractMetadata(this.config, query);
   }
 
diff --git a/src/api/notify-namespace.ts b/src/api/notify-namespace.ts
index 0c5fc472..5eb7ef07 100644
--- a/src/api/notify-namespace.ts
+++ b/src/api/notify-namespace.ts
@@ -634,17 +634,12 @@ const WEBHOOK_NETWORK_TO_NETWORK: { [key: string]: Network } = {
   ETH_MAINNET: Network.ETH_MAINNET,
   ETH_GOERLI: Network.ETH_GOERLI,
   ETH_SEPOLIA: Network.ETH_SEPOLIA,
-  ETH_ROPSTEN: Network.ETH_ROPSTEN,
-  ETH_RINKEBY: Network.ETH_RINKEBY,
-  ETH_KOVAN: Network.ETH_KOVAN,
   MATIC_MAINNET: Network.MATIC_MAINNET,
   MATIC_MUMBAI: Network.MATIC_MUMBAI,
   ARB_MAINNET: Network.ARB_MAINNET,
   ARB_GOERLI: Network.ARB_GOERLI,
-  ARB_RINKEBY: Network.ARB_RINKEBY,
   OPT_MAINNET: Network.OPT_MAINNET,
-  OPT_GOERLI: Network.OPT_GOERLI,
-  OPT_KOVAN: Network.OPT_KOVAN
+  OPT_GOERLI: Network.OPT_GOERLI
 };
 
 /** Mapping of the SDK's network representation the webhook API's network representation. */
diff --git a/src/internal/nft-api.ts b/src/internal/nft-api.ts
index e8d6d957..b307c10b 100644
--- a/src/internal/nft-api.ts
+++ b/src/internal/nft-api.ts
@@ -7,12 +7,15 @@ import {
   AssetTransfersParams,
   AssetTransfersResponse,
   AssetTransfersResult,
+  ComputeRarityResponse,
   GetBaseNftsForContractOptions,
   GetBaseNftsForOwnerOptions,
+  GetContractMetadataBatchResponse,
   GetContractsForOwnerOptions,
   GetContractsForOwnerResponse,
   GetFloorPriceResponse,
   GetMintedNftsOptions,
+  GetNftMetadataBatchResponse,
   GetNftMetadataOptions,
   GetNftSalesOptions,
   GetNftSalesOptionsByContractAddress,
@@ -24,10 +27,11 @@ import {
   GetOwnersForContractWithTokenBalancesOptions,
   GetOwnersForContractWithTokenBalancesResponse,
   GetOwnersForNftResponse,
+  GetSpamContractsResponse,
   GetTransfersForContractOptions,
   GetTransfersForOwnerOptions,
   GetTransfersForOwnerTransferType,
-  NftAttributeRarity,
+  IsSpamContractResponse,
   NftAttributesResponse,
   NftContractBaseNftsResponse,
   NftContractNftsResponse,
@@ -44,6 +48,7 @@ import {
   OwnedNft,
   OwnedNftsResponse,
   RefreshContractResult,
+  SearchContractMetadataResponse,
   SortingOrder,
   TransfersNftResponse
 } from '../types/types';
@@ -60,20 +65,27 @@ import {
 import { getAssetTransfers } from './core-api';
 import { paginateEndpoint, requestHttpWithBackoff } from './dispatch';
 import {
+  RawComputeRarityResponse,
   RawContractBaseNft,
   RawGetBaseNftsForContractResponse,
   RawGetBaseNftsResponse,
+  RawGetContractMetadataBatchResponse,
   RawGetContractsForOwnerResponse,
+  RawGetNftMetadataBatchResponse,
   RawGetNftSalesResponse,
   RawGetNftsForContractResponse,
   RawGetNftsResponse,
   RawGetOwnersForContractResponse,
+  RawGetOwnersForContractWithTokenBalancesResponse,
+  RawGetSpamContractsResponse,
+  RawIsSpamContractResponse,
   RawNft,
-  RawNftAttributeRarity,
+  RawNftAttributesResponse,
   RawNftContractForNft,
   RawOwnedBaseNft,
   RawOwnedNft,
-  RawReingestContractResponse
+  RawReingestContractResponse,
+  RawSearchContractMetadataResponse
 } from './raw-interfaces';
 
 /**
@@ -113,13 +125,16 @@ export async function getNftMetadataBatch(
   config: AlchemyConfig,
   tokens: Array<NftMetadataBatchToken>,
   options?: NftMetadataBatchOptions
-): Promise<Nft[]> {
+): Promise<GetNftMetadataBatchResponse> {
   const data = {
     tokens,
     tokenUriTimeoutInMs: options?.tokenUriTimeoutInMs,
     refreshCache: options?.refreshCache
   };
-  const response = await requestHttpWithBackoff<{}, RawNft[]>(
+  const response = await requestHttpWithBackoff<
+    {},
+    RawGetNftMetadataBatchResponse
+  >(
     config,
     AlchemyApiType.NFT,
     'getNFTMetadataBatch',
@@ -130,7 +145,9 @@ export async function getNftMetadataBatch(
       data
     }
   );
-  return response.map(getNftFromRaw);
+  return {
+    nfts: response.nfts.map(nft => getNftFromRaw(nft))
+  };
 }
 
 export async function getContractMetadata(
@@ -151,8 +168,11 @@ export async function getContractMetadata(
 export async function getContractMetadataBatch(
   config: AlchemyConfig,
   contractAddresses: string[]
-): Promise<NftContract[]> {
-  const response = await requestHttpWithBackoff<{}, RawNftContractForNft[]>(
+): Promise<GetContractMetadataBatchResponse> {
+  const response = await requestHttpWithBackoff<
+    {},
+    RawGetContractMetadataBatchResponse
+  >(
     config,
     AlchemyApiType.NFT,
     'getContractMetadataBatch',
@@ -164,7 +184,9 @@ export async function getContractMetadataBatch(
     }
   );
 
-  return response.map(getNftContractFromRaw);
+  return {
+    contracts: response.contracts.map(getNftContractFromRaw)
+  };
 }
 
 export async function* getNftsForOwnerIterator(
@@ -317,20 +339,26 @@ export async function getOwnersForContract(
   GetOwnersForContractResponse | GetOwnersForContractWithTokenBalancesResponse
 > {
   // Cast to `any` to avoid more type wrangling.
-  const response: any = await requestHttpWithBackoff<
+  const response = await requestHttpWithBackoff<
     GetOwnersForNftContractAlchemyParams,
-    RawGetOwnersForContractResponse
+    | RawGetOwnersForContractResponse
+    | RawGetOwnersForContractWithTokenBalancesResponse
   >(config, AlchemyApiType.NFT, 'getOwnersForContract', srcMethod, {
     ...options,
     contractAddress
   });
 
-  return {
-    owners: response.ownerAddresses,
-
-    // Only include the pageKey in the final response if it's defined
-    ...(response.pageKey !== undefined && { pageKey: response.pageKey })
-  };
+  if (options?.withTokenBalances) {
+    return nullsToUndefined<GetOwnersForContractWithTokenBalancesResponse>({
+      owners: (response as RawGetOwnersForContractWithTokenBalancesResponse)
+        .owners,
+      pageKey: response.pageKey
+    });
+  }
+  return nullsToUndefined<GetOwnersForContractResponse>({
+    owners: response.owners as string[],
+    pageKey: response.pageKey
+  });
 }
 
 export async function getContractsForOwner(
@@ -549,23 +577,20 @@ export async function isSpamContract(
   config: AlchemyConfig,
   contractAddress: string,
   srcMethod = 'isSpamContract'
-): Promise<boolean> {
-  return requestHttpWithBackoff<IsSpamContractParams, boolean>(
-    config,
-    AlchemyApiType.NFT,
-    'isSpamContract',
-    srcMethod,
-    {
-      contractAddress
-    }
-  );
+): Promise<IsSpamContractResponse> {
+  return requestHttpWithBackoff<
+    IsSpamContractParams,
+    RawIsSpamContractResponse
+  >(config, AlchemyApiType.NFT, 'isSpamContract', srcMethod, {
+    contractAddress
+  });
 }
 
 export async function getSpamContracts(
   config: AlchemyConfig,
   srcMethod = 'getSpamContracts'
-): Promise<string[]> {
-  return requestHttpWithBackoff<undefined, string[]>(
+): Promise<GetSpamContractsResponse> {
+  return requestHttpWithBackoff<undefined, RawGetSpamContractsResponse>(
     config,
     AlchemyApiType.NFT,
     'getSpamContracts',
@@ -627,10 +652,10 @@ export async function computeRarity(
   contractAddress: string,
   tokenId: BigNumberish,
   srcMethod = 'computeRarity'
-): Promise<NftAttributeRarity[]> {
+): Promise<ComputeRarityResponse> {
   const response = await requestHttpWithBackoff<
     ComputeRarityParams,
-    RawNftAttributeRarity[]
+    RawComputeRarityResponse
   >(config, AlchemyApiType.NFT, 'computeRarity', srcMethod, {
     contractAddress,
     tokenId: BigNumber.from(tokenId).toString()
@@ -643,16 +668,17 @@ export async function searchContractMetadata(
   config: AlchemyConfig,
   query: string,
   srcMethod = 'searchContractMetadata'
-): Promise<NftContract[]> {
+): Promise<SearchContractMetadataResponse> {
   const response = await requestHttpWithBackoff<
     SearchContractMetadataParams,
-    // TODO(v3): update to response
-    RawNftContractForNft[]
+    RawSearchContractMetadataResponse
   >(config, AlchemyApiType.NFT, 'searchContractMetadata', srcMethod, {
     query
   });
 
-  return response.map(getNftContractFromRaw);
+  return {
+    contracts: response.contracts.map(getNftContractFromRaw)
+  };
 }
 
 export async function summarizeNftAttributes(
@@ -662,8 +688,8 @@ export async function summarizeNftAttributes(
 ): Promise<NftAttributesResponse> {
   return requestHttpWithBackoff<
     SummarizeNftAttributesParams,
-    NftAttributesResponse
-  >(config, AlchemyApiType.NFT, 'summarizeNftAttributes', srcMethod, {
+    RawNftAttributesResponse
+  >(config, AlchemyApiType.NFT, 'summarizeNFTAttributes', srcMethod, {
     contractAddress
   });
 }
@@ -815,21 +841,21 @@ export async function getNftsForTransfers(
       )
     )
   );
-  const nfts = responseBatches.flat();
+  const nfts = responseBatches.map(r => r.nfts).flat();
 
   // The same NFT can be transferred multiple times in the same transfers response.
   // We want to return one NFT for each transfer, so we create a mapping for
   // each NFT to pair with the transfer metadata.
   const nftsByTokenId = new Map<string, Nft>();
   nfts.forEach(nft => {
-    const key = `${nft.contract.address}-${BigNumber.from(
+    const key = `${nft.contract.address.toLowerCase()}-${BigNumber.from(
       nft.tokenId
     ).toString()}`;
     nftsByTokenId.set(key, nft);
   });
 
   const transferredNfts = metadataTransfers.map(t => {
-    const key = `${t.token.contractAddress}-${BigNumber.from(
+    const key = `${t.token.contractAddress.toLowerCase()}-${BigNumber.from(
       t.token.tokenId
     ).toString()}`;
     return {
diff --git a/src/internal/raw-interfaces.ts b/src/internal/raw-interfaces.ts
index 817a064f..8611d957 100644
--- a/src/internal/raw-interfaces.ts
+++ b/src/internal/raw-interfaces.ts
@@ -149,11 +149,12 @@ export interface RawContractBaseNft {
  * @internal
  */
 export interface RawGetOwnersForContractResponse {
-  ownerAddresses: string[];
+  owners: string[];
+  pageKey: string | null;
 }
 
 export interface RawGetOwnersForContractWithTokenBalancesResponse {
-  ownerAddresses: RawOwnerAddress[];
+  owners: RawOwnerAddress[];
   pageKey: string | null;
 }
 
@@ -227,12 +228,26 @@ export interface RawNftFilterParam {
   token_id?: string;
 }
 
+export interface RawSearchContractMetadataResponse {
+  contracts: RawNftContract[];
+}
+
+export interface RawComputeRarityResponse {
+  rarities: RawNftAttributeRarity[];
+}
+
 export interface RawNftAttributeRarity {
   value: string;
   traitType: string;
   prevalence: number;
 }
 
+export interface RawNftAttributesResponse {
+  summary: Record<string, Record<string, number>>;
+  totalSupply: string;
+  contractAddress: string;
+}
+
 export interface RawGetNftSalesResponse {
   nftSales: RawNftSale[];
   pageKey: string | null;
@@ -275,6 +290,22 @@ export interface RawNftContractForOwner
   image: RawNftImage;
 }
 
+export interface RawGetNftMetadataBatchResponse {
+  nfts: Array<RawNft>;
+}
+
+export interface RawGetContractMetadataBatchResponse {
+  contracts: RawNftContract[];
+}
+
+export interface RawIsSpamContractResponse {
+  isSpamContract: boolean;
+}
+
+export interface RawGetSpamContractsResponse {
+  contractAddresses: string[];
+}
+
 export interface RawDisplayNftForContract {
   tokenId: string;
   name: string | null;
diff --git a/src/types/types.ts b/src/types/types.ts
index ab7aa6a6..febd274c 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -75,21 +75,11 @@ export interface AlchemySettings {
  */
 export enum Network {
   ETH_MAINNET = 'eth-mainnet',
-  /** @deprecated - Will be removed in subsequent versions */
-  ETH_ROPSTEN = 'eth-ropsten',
   ETH_GOERLI = 'eth-goerli',
-  /** @deprecated - Will be removed in subsequent versions */
-  ETH_KOVAN = 'eth-kovan',
-  /** @deprecated - Will be removed in subsequent versions */
-  ETH_RINKEBY = 'eth-rinkeby',
   ETH_SEPOLIA = 'eth-sepolia',
   OPT_MAINNET = 'opt-mainnet',
-  /** @deprecated - Will be removed in subsequent versions */
-  OPT_KOVAN = 'opt-kovan',
   OPT_GOERLI = 'opt-goerli',
   ARB_MAINNET = 'arb-mainnet',
-  /** @deprecated - Will be removed in subsequent versions */
-  ARB_RINKEBY = 'arb-rinkeby',
   ARB_GOERLI = 'arb-goerli',
   MATIC_MAINNET = 'polygon-mainnet',
   MATIC_MUMBAI = 'polygon-mumbai',
@@ -377,19 +367,6 @@ export enum AssetTransfersCategory {
   SPECIALNFT = 'specialnft'
 }
 
-/**
- * Enum for the order of the {@link AssetTransfersParams} request object when
- * using {@link CoreNamespace.getAssetTransfers}.
- *
- * @deprecated Use {@link SortingOrder} instead. This enum will be removed in a
- *   future version.
- * @public
- */
-export enum AssetTransfersOrder {
-  ASCENDING = 'asc',
-  DESCENDING = 'desc'
-}
-
 /**
  * An enum for specifying the token type on NFTs.
  *
@@ -547,42 +524,6 @@ export interface NftImage {
   originalUrl?: string;
 }
 
-/**
- * Represents the URI information for the NFT's media assets.
- *
- * @public
- */
-export interface Media {
-  /**
-   * URI for the location of the NFT's original metadata blob for media (ex: the
-   * original IPFS link).
-   */
-  raw: string;
-
-  /** Public gateway URI for the raw URI. Generally offers better performance. */
-  gateway: string;
-
-  /** URL for a resized thumbnail of the NFT media asset. */
-  thumbnail?: string;
-
-  /**
-   * The media format (ex: jpg, gif, png) of the {@link gateway} and
-   * {@link thumbnail} assets.
-   */
-  format?: string;
-
-  /**
-   * DEPRECATED - The size of the media asset in bytes
-   *
-   * @deprecated - Please use {@link bytes} instead. This field will be removed
-   *   in a subsequent release.
-   */
-  size?: number;
-
-  /** The size of the media asset in bytes. */
-  bytes?: number;
-}
-
 /** Potential reasons why an NFT contract was classified as spam. */
 export enum NftSpamClassification {
   Erc721TooManyOwners = 'Erc721TooManyOwners',
@@ -709,23 +650,6 @@ export interface GetBaseNftsForOwnerOptions {
   orderBy?: NftOrdering;
 }
 
-/**
- * Enum of NFT filters that can be applied to a {@link getNftsForOwner} request.
- * NFTs that match one or more of these filters are excluded from the response.
- *
- * @deprecated Use {@link NftFilters} instead. This enum will be removed in a
- *   future version.
- *
- * @beta
- */
-export enum NftExcludeFilters {
-  /** Exclude NFTs that have been classified as spam. */
-  SPAM = 'SPAM',
-
-  /** Exclude NFTs that have been airdropped to a user. */
-  AIRDROPS = 'AIRDROPS'
-}
-
 /**
  * Enum of NFT filters that can be applied to a {@link getNftsForOwner} or a
  * {@link getContractsForOwner} request.
@@ -841,6 +765,8 @@ export interface GetOwnersForNftResponse {
 export interface GetOwnersForContractResponse {
   /** An array of owner addresses for the provided contract address */
   owners: string[];
+  /** Optional page key that is returned when a collection has more than 50,000 owners. */
+  pageKey?: string;
 }
 
 /**
@@ -856,6 +782,15 @@ export interface GetOwnersForContractWithTokenBalancesResponse {
   pageKey?: string;
 }
 
+export interface GetNftMetadataBatchResponse {
+  /** An array of NFT metadata objects. */
+  nfts: Nft[];
+}
+
+export interface GetContractMetadataBatchResponse {
+  contracts: NftContract[];
+}
+
 /**
  * An object representing the owner of an NFT and its corresponding token
  * balances in a {@link GetOwnersForContractWithTokenBalancesResponse} object.
@@ -879,6 +814,14 @@ export interface NftContractTokenBalance {
   balance: string;
 }
 
+export interface IsSpamContractResponse {
+  isSpamContract: boolean;
+}
+
+export interface GetSpamContractsResponse {
+  contractAddresses: string[];
+}
+
 /**
  * Additional options for the {@link NftNamespace.getNftMetadata} method.
  */
@@ -1168,17 +1111,6 @@ export interface GetMintedNftsOptions {
   pageKey?: string;
 }
 
-/**
- * @deprecated Use {@link TransfersNftResponse} instead.
- */
-export interface GetMintedNftsResponse {
-  /** An array of the minted NFTs for the provided owner address. */
-  nfts: Nft[];
-
-  /** Key for pagination to use to fetch results from the next page if available. */
-  pageKey?: string;
-}
-
 /**
  * Optional parameters object for the {@link NftNamespace.getNftSales} method.
  *
@@ -1347,6 +1279,17 @@ export enum NftSaleTakerType {
   SELLER = 'seller'
 }
 
+export interface SearchContractMetadataResponse {
+  contracts: NftContract[];
+}
+
+/**
+ * Response object for the {@link NftNamespace.computeRarity} method.
+ */
+export interface ComputeRarityResponse {
+  rarities: NftAttributeRarity[];
+}
+
 /**
  * Information about the rarity of an NFT's attribute in the specified collection.
  *
@@ -1376,7 +1319,7 @@ export interface NftAttributesResponse {
   contractAddress: string;
 
   /** The specified NFT contract's total supply. */
-  totalSupply: number;
+  totalSupply: string;
 
   /**
    * The attribute prevalence of each trait grouped by the trait type for the
diff --git a/src/util/const.ts b/src/util/const.ts
index d2521350..c8dbed3f 100644
--- a/src/util/const.ts
+++ b/src/util/const.ts
@@ -41,16 +41,11 @@ export enum AlchemyApiType {
  */
 export const EthersNetwork = {
   [Network.ETH_MAINNET]: 'mainnet',
-  [Network.ETH_ROPSTEN]: 'ropsten',
   [Network.ETH_GOERLI]: 'goerli',
-  [Network.ETH_KOVAN]: 'kovan',
-  [Network.ETH_RINKEBY]: 'rinkeby',
   [Network.ETH_SEPOLIA]: 'sepolia',
   [Network.OPT_MAINNET]: 'optimism',
-  [Network.OPT_KOVAN]: 'optimism-kovan',
   [Network.OPT_GOERLI]: 'optimism-goerli',
   [Network.ARB_MAINNET]: 'arbitrum',
-  [Network.ARB_RINKEBY]: 'arbitrum-rinkeby',
   [Network.ARB_GOERLI]: 'arbitrum-goerli',
   [Network.MATIC_MAINNET]: 'matic',
   [Network.MATIC_MUMBAI]: 'maticmum',


Test patch:
diff --git a/test/integration/nft.test.ts b/test/integration/nft.test.ts
index db5905ce..3a084ea7 100644
--- a/test/integration/nft.test.ts
+++ b/test/integration/nft.test.ts
@@ -16,6 +16,8 @@ jest.setTimeout(50000);
 
 // These integration tests check for valid response types and protect against
 // regressions in the backend.
+// TODO(V3): now that types match, add automated tests to check every single field
+// to make sure the schema matches
 describe('E2E integration tests', () => {
   let alchemy: Alchemy;
   const ownerEns = 'vitalik.eth';
@@ -95,9 +97,9 @@ describe('E2E integration tests', () => {
       },
       { contractAddress, tokenId: 13596716 }
     ]);
-    expect(response.length).toEqual(2);
-    expect(response[0].tokenId).toEqual(fromHex('0x8b57f0').toString());
-    expect(response[1].tokenId).toEqual('13596716');
+    expect(response.nfts.length).toEqual(2);
+    expect(response.nfts[0].tokenId).toEqual(fromHex('0x8b57f0').toString());
+    expect(response.nfts[1].tokenId).toEqual('13596716');
   });
 
   it('getContractMetadata()', async () => {
@@ -222,7 +224,7 @@ describe('E2E integration tests', () => {
     expect(response.owners.length).toBeGreaterThan(0);
     expect(response.owners[0].tokenBalances.length).toBeGreaterThan(0);
     expect(typeof response.owners[0].tokenBalances[0].balance).toEqual(
-      'number'
+      'string'
     );
   });
 
@@ -310,15 +312,19 @@ describe('E2E integration tests', () => {
 
   it('getContractMetadataBatch()', async () => {
     const contractAddresses = [
-      '0xe785e82358879f061bc3dcac6f0444462d4b5330',
-      '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d'
+      '0xe785E82358879F061BC3dcAC6f0444462D4b5330',
+      '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D'
     ];
     const response = await alchemy.nft.getContractMetadataBatch(
       contractAddresses
     );
-    expect(response.length).toEqual(2);
-    expect(contractAddresses.includes(response[0].address)).toEqual(true);
-    expect(contractAddresses.includes(response[1].address)).toEqual(true);
+    expect(response.contracts.length).toEqual(2);
+    expect(contractAddresses.includes(response.contracts[0].address)).toEqual(
+      true
+    );
+    expect(contractAddresses.includes(response.contracts[1].address)).toEqual(
+      true
+    );
   });
 
   it('getNftsForOwnerIterator()', async () => {
@@ -399,8 +405,8 @@ describe('E2E integration tests', () => {
 
     // Handles contract address specifying.
     const contractAddresses = [
-      '0xa1eb40c284c5b44419425c4202fa8dabff31006b',
-      '0x8442864d6ab62a9193be2f16580c08e0d7bcda2f'
+      '0xa1eB40c284C5B44419425c4202Fa8DabFF31006b',
+      '0x8442864d6AB62a9193be2F16580c08E0D7BCda2f'
     ];
     const response5 = await alchemy.nft.getMintedNfts('vitalik.eth', {
       contractAddresses
@@ -458,8 +464,8 @@ describe('E2E integration tests', () => {
 
     // Handles contract address specifying.
     const contractAddresses = [
-      '0xa1eb40c284c5b44419425c4202fa8dabff31006b',
-      '0x8442864d6ab62a9193be2f16580c08e0d7bcda2f'
+      '0xa1eB40c284C5B44419425c4202Fa8DabFF31006b',
+      '0x8442864d6AB62a9193be2F16580c08E0D7BCda2f'
     ];
     const response5 = await alchemy.nft.getTransfersForOwner(
       'vitalik.eth',
@@ -554,13 +560,13 @@ describe('E2E integration tests', () => {
 
   it('isSpamContract()', async () => {
     const response = await alchemy.nft.isSpamContract(contractAddress);
-    expect(typeof response).toEqual('boolean');
+    expect(typeof response.isSpamContract).toEqual('boolean');
   });
 
   it('getSpamContracts()', async () => {
     const response = await alchemy.nft.getSpamContracts();
-    expect(response.length).toBeGreaterThan(0);
-    expect(typeof response[0]).toEqual('string');
+    expect(response.contractAddresses.length).toBeGreaterThan(0);
+    expect(typeof response.contractAddresses[0]).toEqual('string');
   });
 
   it('getFloorPrice()', async () => {
@@ -630,10 +636,10 @@ describe('E2E integration tests', () => {
     const response = await alchemy.nft.computeRarity(contractAddress, tokenId);
 
     expect(response).toBeDefined();
-    expect(response.length).toBeGreaterThan(0);
-    expect(response[0].prevalence).toBeDefined();
-    expect(response[0].traitType).toBeDefined();
-    expect(response[0].value).toBeDefined();
+    expect(response.rarities.length).toBeGreaterThan(0);
+    expect(response.rarities[0].prevalence).toBeDefined();
+    expect(response.rarities[0].traitType).toBeDefined();
+    expect(response.rarities[0].value).toBeDefined();
   });
 
   it('searchContractMetadata()', async () => {
@@ -642,14 +648,14 @@ describe('E2E integration tests', () => {
     const response = await alchemy.nft.searchContractMetadata(query);
 
     expect(response).toBeDefined();
-    expect(response.length).toBeGreaterThan(0);
-    expect(response[0].address).toBeDefined();
-    expect(typeof response[0].address).toEqual('string');
-    expect(response[0].tokenType).toBeDefined();
+    expect(response.contracts.length).toBeGreaterThan(0);
+    expect(response.contracts[0].address).toBeDefined();
+    expect(typeof response.contracts[0].address).toEqual('string');
+    expect(response.contracts[0].tokenType).toBeDefined();
   });
 
   it('summarizeNftAttributes()', async () => {
-    const contractAddress = '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d';
+    const contractAddress = '0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D';
 
     const response = await alchemy.nft.summarizeNftAttributes(contractAddress);
 
@@ -657,7 +663,7 @@ describe('E2E integration tests', () => {
     expect(response.contractAddress).toBeDefined();
     expect(response.contractAddress).toEqual(contractAddress);
     expect(response.totalSupply).toBeDefined();
-    expect(typeof response.totalSupply).toEqual('number');
+    expect(typeof response.totalSupply).toEqual('string');
     expect(response.summary).toBeDefined();
   });
 
diff --git a/test/test-util.ts b/test/test-util.ts
index 3823d31d..e6e66687 100644
--- a/test/test-util.ts
+++ b/test/test-util.ts
@@ -1,6 +1,5 @@
 import {
   BaseNft,
-  Media,
   Nft,
   NftContract,
   NftSaleMarketplace,
@@ -237,22 +236,6 @@ export function createRawContractForOwner(
   };
 }
 
-export function createNftMediaData(
-  bytes?: number,
-  format?: string,
-  thumbnail?: string
-): Media[] {
-  return [
-    {
-      raw: 'http://api.nikeape.xyz/ipfs/nickbanc/1.jpg',
-      gateway: 'http://api.nikeape.xyz/ipfs/nickbanc/1.jpg',
-      bytes,
-      format,
-      thumbnail
-    }
-  ];
-}
-
 export function verifyNftContractMetadata(
   actualNftContract: NftContract,
   expectedNftContract: NftContract,
diff --git a/test/unit/alchemy.test.ts b/test/unit/alchemy.test.ts
index 60afee1e..b2303f1c 100644
--- a/test/unit/alchemy.test.ts
+++ b/test/unit/alchemy.test.ts
@@ -24,7 +24,7 @@ describe('Alchemy class', () => {
   it('preserves settings', () => {
     const config: AlchemySettings = {
       apiKey: 'api-key-here',
-      network: Network.OPT_KOVAN,
+      network: Network.OPT_GOERLI,
       maxRetries: 2,
       url: 'invalid-url'
     };
@@ -35,7 +35,7 @@ describe('Alchemy class', () => {
     config.url = 'another-url';
 
     expect(alchemy.config.apiKey).toEqual('api-key-here');
-    expect(alchemy.config.network).toEqual(Network.OPT_KOVAN);
+    expect(alchemy.config.network).toEqual(Network.OPT_GOERLI);
     expect(alchemy.config.maxRetries).toEqual(2);
     expect(alchemy.config.url).toEqual('invalid-url');
   });
@@ -91,7 +91,7 @@ describe('Alchemy class', () => {
   it('uses the config url instead of apiKey/network if one was provided', () => {
     const alchemy = new Alchemy({
       apiKey: 'api-key-here',
-      network: Network.OPT_KOVAN,
+      network: Network.OPT_GOERLI,
       url: 'custom-url'
     });
     expect(alchemy.config._getRequestUrl(AlchemyApiType.NFT)).toEqual(
diff --git a/test/unit/nft-api.test.ts b/test/unit/nft-api.test.ts
index 47f001c9..295d9e9e 100644
--- a/test/unit/nft-api.test.ts
+++ b/test/unit/nft-api.test.ts
@@ -262,11 +262,13 @@ describe('NFT module', () => {
     const timeoutInMs = 50;
     // Special case token ID as an integer string, since that's what the NFT
     // API endpoint returns.
-    const rawNftsResponse = [
-      createRawNft(contractAddress, title, tokenId),
-      createRawNft(contractAddress2, title, tokenId2.toString())
-    ];
-    const expectedNfts = rawNftsResponse.map(getNftFromRaw);
+    const rawNftsResponse = {
+      nfts: [
+        createRawNft(contractAddress, title, tokenId),
+        createRawNft(contractAddress2, title, tokenId2.toString())
+      ]
+    };
+    const expectedNfts = rawNftsResponse.nfts.map(getNftFromRaw);
 
     beforeEach(() => {
       mock.onPost().reply(200, rawNftsResponse);
@@ -285,7 +287,7 @@ describe('NFT module', () => {
         refreshCache: true,
         tokenUriTimeoutInMs: timeoutInMs
       });
-      expect(response).toEqual(expectedNfts);
+      expect(response.nfts).toEqual(expectedNfts);
       expect(mock.history.post.length).toEqual(1);
       const parsedRequest = JSON.parse(mock.history.post[0].data);
       expect(parsedRequest).toHaveProperty('tokens', tokens);
@@ -1010,7 +1012,8 @@ describe('NFT module', () => {
 
     beforeEach(() => {
       mock.onGet().reply(200, {
-        ownerAddresses: owners
+        owners,
+        pageKey: null
       });
     });
 
@@ -1021,12 +1024,12 @@ describe('NFT module', () => {
         'contractAddress',
         contractAddress
       );
-      expect(response).toEqual({ owners });
+      expect(response).toEqual({ owners, pageKey: undefined });
     });
 
     it('handles withTokenBalances=true', async () => {
       const mockResponse: RawGetOwnersForContractWithTokenBalancesResponse = {
-        ownerAddresses: [
+        owners: [
           {
             ownerAddress: '0xABC',
             tokenBalances: [
@@ -1535,7 +1538,7 @@ describe('NFT module', () => {
       symbol,
       totalSupply
     });
-    const templateResponse = [rawNftContractResponse];
+    const templateResponse = { contracts: [rawNftContractResponse] };
     const expectedNftContract = getNftContractFromRaw(rawNftContractResponse);
 
     beforeEach(() => {
@@ -1552,9 +1555,9 @@ describe('NFT module', () => {
     it('returns the api response in the expected format', async () => {
       const response = await alchemy.nft.searchContractMetadata(query);
 
-      expect(response.length).toEqual(1);
+      expect(response.contracts.length).toEqual(1);
       verifyNftContractMetadata(
-        response[0],
+        response.contracts[0],
         expectedNftContract,
         address,
         name,
@@ -1580,7 +1583,7 @@ describe('NFT module', () => {
     const contractAddress = '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d';
     const templateResponse: NftAttributesResponse = {
       contractAddress,
-      totalSupply: 1000,
+      totalSupply: '1000',
       summary: {
         Background: {
           Aquamarine: 3,
@@ -1841,11 +1844,13 @@ describe('NFT module', () => {
     });
 
     it('flattens 1155 transfers', async () => {
-      const nftMetadataBatchResponse = [
-        createRawNft('0xabc', 'NFT1', '1', NftTokenType.ERC721),
-        createRawNft('0xdef', 'NFT2', '2', NftTokenType.ERC1155),
-        createRawNft('0xdef', 'NFT2', '3', NftTokenType.ERC1155)
-      ];
+      const nftMetadataBatchResponse = {
+        nfts: [
+          createRawNft('0xabc', 'NFT1', '1', NftTokenType.ERC721),
+          createRawNft('0xdef', 'NFT2', '2', NftTokenType.ERC1155),
+          createRawNft('0xdef', 'NFT2', '3', NftTokenType.ERC1155)
+        ]
+      };
       mock.onPost().reply(200, nftMetadataBatchResponse);
       const transfers = [
         create721Transfer('0xabc', '0x1'),
@@ -1871,12 +1876,14 @@ describe('NFT module', () => {
     });
 
     it('batches NFT metadata calls', async () => {
-      const nftMetadataBatchResponse1 = Array.from({ length: 100 }, (_, i) =>
-        createRawNft('0xdef', 'NFT', toHex(i), NftTokenType.ERC1155)
-      );
-      const nftMetadataBatchResponse2 = [
-        createRawNft('0xdef', 'NFT', toHex(100), NftTokenType.ERC1155)
-      ];
+      const nftMetadataBatchResponse1 = {
+        nfts: Array.from({ length: 100 }, (_, i) =>
+          createRawNft('0xdef', 'NFT', toHex(i), NftTokenType.ERC1155)
+        )
+      };
+      const nftMetadataBatchResponse2 = {
+        nfts: [createRawNft('0xdef', 'NFT', toHex(100), NftTokenType.ERC1155)]
+      };
       mock
         .onPost()
         .reply(200, nftMetadataBatchResponse1)
@@ -1920,10 +1927,12 @@ describe('NFT module', () => {
     });
 
     it('returns separate NFTs for duplicate transfers', async () => {
-      const nftMetadataBatchResponse = [
-        createRawNft('0xabc', 'NFT1', '0x1', NftTokenType.ERC721),
-        createRawNft('0xdef', 'NFT2', '0x1', NftTokenType.ERC1155)
-      ];
+      const nftMetadataBatchResponse = {
+        nfts: [
+          createRawNft('0xabc', 'NFT1', '0x1', NftTokenType.ERC721),
+          createRawNft('0xdef', 'NFT2', '0x1', NftTokenType.ERC1155)
+        ]
+      };
       mock.onPost().reply(200, nftMetadataBatchResponse);
       const transfers = [
         create721Transfer('0xabc', '0x1'),