Repository: alchemyplatform/alchemy-sdk-js
Pull request #333 (2023-05-31 18:45:52)

Add `validAt` to `getNftSales()` and `getNftsForOwner()` responses
Changes:
- `getNftSales()` now includes `validAt` in the response.
-  [BREAKING] `getNftsForOwner()` (with and without metadata) include `validAt` in the response instead of `blockHash`. `blockHash` is now no longer returned.


Patch:
diff --git a/src/internal/nft-api.ts b/src/internal/nft-api.ts
index 294b6df7..8095da5d 100644
--- a/src/internal/nft-api.ts
+++ b/src/internal/nft-api.ts
@@ -74,7 +74,7 @@ import {
   RawGetNftMetadataBatchResponse,
   RawGetNftSalesResponse,
   RawGetNftsForContractResponse,
-  RawGetNftsResponse,
+  RawGetNftsForOwnerResponse,
   RawGetOwnersForContractResponse,
   RawGetOwnersForContractWithTokenBalancesResponse,
   RawGetSpamContractsResponse,
@@ -234,7 +234,7 @@ export async function getNftsForOwner(
   const withMetadata = omitMetadataToWithMetadata(options?.omitMetadata);
   const response = await requestHttpWithBackoff<
     GetNftsAlchemyParams,
-    RawGetBaseNftsResponse | RawGetNftsResponse
+    RawGetBaseNftsResponse | RawGetNftsForOwnerResponse
   >(config, AlchemyApiType.NFT, 'getNFTsForOwner', srcMethod, {
     contractAddresses: options?.contractAddresses,
     pageKey: options?.pageKey,
@@ -254,7 +254,7 @@ export async function getNftsForOwner(
       })),
       pageKey: response.pageKey,
       totalCount: response.totalCount,
-      blockHash: response.blockHash
+      validAt: response.validAt
     });
   }
 
@@ -265,7 +265,7 @@ export async function getNftsForOwner(
     })),
     pageKey: response.pageKey,
     totalCount: response.totalCount,
-    blockHash: response.blockHash
+    validAt: response.validAt
   });
 }
 
diff --git a/src/internal/raw-interfaces.ts b/src/internal/raw-interfaces.ts
index 8611d957..25197a5e 100644
--- a/src/internal/raw-interfaces.ts
+++ b/src/internal/raw-interfaces.ts
@@ -71,7 +71,7 @@ export interface RawOpenSeaCollectionMetadata {
 export interface RawGetBaseNftsResponse {
   ownedNfts: RawOwnedBaseNft[];
   totalCount: number;
-  blockHash: string;
+  validAt: RawNftsForOwnerValidAt;
   pageKey: string | null;
 }
 
@@ -80,10 +80,10 @@ export interface RawGetBaseNftsResponse {
  *
  * @internal
  */
-export interface RawGetNftsResponse {
+export interface RawGetNftsForOwnerResponse {
   ownedNfts: RawOwnedNft[];
   totalCount: number;
-  blockHash: string;
+  validAt: RawNftsForOwnerValidAt;
   pageKey: string | null;
 }
 
@@ -109,6 +109,11 @@ export interface RawOwnedNft extends RawNft {
   balance: string;
 }
 
+export interface RawNftsForOwnerValidAt {
+  blockNumber: number | null;
+  blockHash: string;
+  blockTimestamp: string | null;
+}
 /**
  * Represents Alchemy's HTTP response for `getNftsForNftContract` without metadata.
  *
@@ -250,6 +255,7 @@ export interface RawNftAttributesResponse {
 
 export interface RawGetNftSalesResponse {
   nftSales: RawNftSale[];
+  validAt: RawNftSaleValidAt;
   pageKey: string | null;
 }
 
@@ -270,6 +276,12 @@ export interface RawNftSale {
   transactionHash: string;
 }
 
+export interface RawNftSaleValidAt {
+  blockNumber: number;
+  blockHash: string | null;
+  blockTimestamp: string | null;
+}
+
 export interface RawNftSaleFeeData {
   amount: string | null;
   tokenAddress: string | null;
diff --git a/src/types/types.ts b/src/types/types.ts
index febd274c..7e85fd38 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -695,8 +695,10 @@ export interface OwnedNftsResponse {
   /** The total count of NFTs owned by the provided address. */
   totalCount: number;
 
-  /** The canonical head block hash of when your request was received. */
-  blockHash: string;
+  /**
+   * Block Information of the block as of which the corresponding data is valid
+   */
+  validAt: OwnedNftsValidAt;
 }
 
 /**
@@ -720,8 +722,10 @@ export interface OwnedBaseNftsResponse {
   /** The total count of NFTs owned by the provided address. */
   totalCount: number;
 
-  /** The canonical head block hash of when your request was received. */
-  blockHash: string;
+  /**
+   * Block Information of the block as of which the corresponding data is valid
+   */
+  validAt: OwnedNftsValidAt;
 }
 
 /**
@@ -744,6 +748,16 @@ export interface OwnedBaseNft extends BaseNft {
   balance: string;
 }
 
+/** The block information at which the NFT sale information is valid at. */
+export interface OwnedNftsValidAt {
+  /** The block number the sale information is valid at. */
+  blockNumber?: number;
+  /** The block hash. Used to detect reorgs. */
+  blockHash: string;
+  /** The timestamp for the block. */
+  blockTimestamp?: string;
+}
+
 /**
  * The response object for the {@link getOwnersForNft}.
  *
@@ -1173,11 +1187,16 @@ export interface GetNftSalesOptionsByContractAddress
  * @public
  */
 export interface GetNftSalesResponse {
-  /** The page key to use to fetch the next page if more results are available. */
-  pageKey?: string;
-
   /** List of NFT sales that match the query */
   nftSales: NftSale[];
+  /**
+   * Block Information of the block as of which the corresponding data is valid.
+   */
+  validAt: NftSaleValidAt;
+  /**
+   * The page key to use to fetch the next page if more results are available.
+   */
+  pageKey?: string;
 }
 
 /** Represents a single NFT sale data in the {@link GetNftSalesResponse}. */
@@ -1225,6 +1244,16 @@ export interface NftSale {
   transactionHash: string;
 }
 
+/** The block information at which the NFT sale information is valid at. */
+export interface NftSaleValidAt {
+  /** The block number the sale information is valid at. */
+  blockNumber: number;
+  /** The block hash. Used to detect reorgs. */
+  blockHash?: string;
+  /** The timestamp for the block. */
+  blockTimestamp?: string;
+}
+
 /**
  * Fee detail for an NFT sale.
  *
diff --git a/src/util/util.ts b/src/util/util.ts
index 96abd03d..04989552 100644
--- a/src/util/util.ts
+++ b/src/util/util.ts
@@ -113,6 +113,7 @@ export function getNftSalesFromRaw(
       marketplace: parseNftSaleMarketplace(rawNftSale.marketplace),
       taker: parseNftTaker(rawNftSale.taker)
     })),
+    validAt: rawNftSales.validAt,
     pageKey: rawNftSales.pageKey
   });
 }


Test patch:
diff --git a/test/integration/nft.test.ts b/test/integration/nft.test.ts
index 3a084ea7..e8f43c6f 100644
--- a/test/integration/nft.test.ts
+++ b/test/integration/nft.test.ts
@@ -159,7 +159,7 @@ describe('E2E integration tests', () => {
       ).length
     ).toBeGreaterThan(0);
     expect(response.ownedNfts.length).toEqual(51);
-    expect(typeof response.blockHash).toEqual('string');
+    expect(response.validAt).toBeDefined();
   });
 
   it('getOwnersForNft() from NFT', async () => {
diff --git a/test/integration/notify.test.ts b/test/integration/notify.test.ts
index cab7bcf2..22828eaf 100644
--- a/test/integration/notify.test.ts
+++ b/test/integration/notify.test.ts
@@ -75,11 +75,6 @@ describe('E2E integration tests', () => {
 
   describe('has valid network mappings', () => {
     const UNSUPPORTED_NETWORKS = [
-      Network.ETH_ROPSTEN,
-      Network.ETH_KOVAN,
-      Network.ETH_RINKEBY,
-      Network.OPT_KOVAN,
-      Network.ARB_RINKEBY,
       Network.ASTAR_MAINNET,
       Network.POLYGONZKEVM_MAINNET,
       Network.POLYGONZKEVM_TESTNET
diff --git a/test/unit/nft-api.test.ts b/test/unit/nft-api.test.ts
index 295d9e9e..4d2096b4 100644
--- a/test/unit/nft-api.test.ts
+++ b/test/unit/nft-api.test.ts
@@ -40,7 +40,7 @@ import {
   RawGetContractsForOwnerResponse,
   RawGetNftSalesResponse,
   RawGetNftsForContractResponse,
-  RawGetNftsResponse,
+  RawGetNftsForOwnerResponse,
   RawGetOwnersForContractWithTokenBalancesResponse,
   RawNftAttributeRarity,
   RawNftImage
@@ -318,6 +318,11 @@ describe('NFT module', () => {
       tokenUriTimeoutInMs: 50,
       orderBy: NftOrdering.TRANSFERTIME
     };
+    const rawValidAt = {
+      blockHash: '0x123abc',
+      blockNumber: 123,
+      blockTimestamp: null
+    };
     const baseNftResponse: RawGetBaseNftsResponse = {
       ownedNfts: [
         createRawOwnedBaseNft('0xCA1', '0x1', '1'),
@@ -325,17 +330,17 @@ describe('NFT module', () => {
       ],
       pageKey: 'page-key1',
       totalCount: 3,
-      blockHash: '0x123abc'
+      validAt: rawValidAt
     };
 
-    const nftResponse: RawGetNftsResponse = {
+    const nftResponse: RawGetNftsForOwnerResponse = {
       ownedNfts: [
         createRawOwnedNft('a', '0xCA1', '0x1', '1'),
         createRawOwnedNft('b', '0xCA2', '0x2', '2', NftTokenType.ERC1155)
       ],
       pageKey: 'page-key1',
       totalCount: 3,
-      blockHash: '0x123abc'
+      validAt: rawValidAt
     };
 
     const paramCases = [
@@ -388,7 +393,11 @@ describe('NFT module', () => {
       ],
       pageKey: 'page-key1',
       totalCount: 3,
-      blockHash: '0x123abc'
+      validAt: {
+        blockHash: '0x123abc',
+        blockNumber: 123,
+        blockTimestamp: undefined
+      }
     };
     const nftExpected: OwnedNftsResponse = {
       ownedNfts: [
@@ -397,12 +406,16 @@ describe('NFT module', () => {
       ],
       pageKey: 'page-key1',
       totalCount: 3,
-      blockHash: '0x123abc'
+      validAt: {
+        blockHash: '0x123abc',
+        blockNumber: 123,
+        blockTimestamp: undefined
+      }
     };
     const responseCases: Array<
       [
         boolean,
-        RawGetBaseNftsResponse | RawGetNftsResponse,
+        RawGetBaseNftsResponse | RawGetNftsForOwnerResponse,
         OwnedBaseNftsResponse | OwnedNftsResponse
       ]
     > = [
@@ -444,6 +457,11 @@ describe('NFT module', () => {
     const contractAddresses = ['0xCA1', '0xCA2'];
     const excludeFilters = [NftFilters.SPAM];
     const expectedFilters = ['SPAM'];
+    const rawValidAt = {
+      blockHash: '0x123abc',
+      blockNumber: 123,
+      blockTimestamp: null
+    };
     const baseResponses: RawGetBaseNftsResponse[] = [
       {
         ownedNfts: [
@@ -452,16 +470,16 @@ describe('NFT module', () => {
         ],
         pageKey: 'page-key1',
         totalCount: 3,
-        blockHash: '0x123abc'
+        validAt: rawValidAt
       },
       {
         ownedNfts: [createRawOwnedBaseNft('0xCA2', '0x3', '1')],
         totalCount: 3,
-        blockHash: '0x123abc',
+        validAt: rawValidAt,
         pageKey: null
       }
     ];
-    const nftResponses: RawGetNftsResponse[] = [
+    const nftResponses: RawGetNftsForOwnerResponse[] = [
       {
         ownedNfts: [
           createRawOwnedNft('a', '0xCA1', '0x1', '1'),
@@ -469,20 +487,20 @@ describe('NFT module', () => {
         ],
         pageKey: 'page-key1',
         totalCount: 3,
-        blockHash: '0x123abc'
+        validAt: rawValidAt
       },
       {
         ownedNfts: [
           createRawOwnedNft('c', '0xCA2', '0x3', '1', NftTokenType.ERC1155)
         ],
         totalCount: 3,
-        blockHash: '0x123abc',
+        validAt: rawValidAt,
         pageKey: null
       }
     ];
 
     function setupMock(
-      mockResponses: RawGetBaseNftsResponse[] | RawGetNftsResponse[]
+      mockResponses: RawGetBaseNftsResponse[] | RawGetNftsForOwnerResponse[]
     ): void {
       mock
         .onGet()
@@ -493,7 +511,7 @@ describe('NFT module', () => {
 
     const paramCases: Array<
       [
-        RawGetBaseNftsResponse[] | RawGetNftsResponse[],
+        RawGetBaseNftsResponse[] | RawGetNftsForOwnerResponse[],
         boolean | undefined,
         boolean
       ]
@@ -595,7 +613,7 @@ describe('NFT module', () => {
     const responseCases: Array<
       [
         boolean,
-        RawGetBaseNftsResponse[] | RawGetNftsResponse[],
+        RawGetBaseNftsResponse[] | RawGetNftsForOwnerResponse[],
         OwnedBaseNft[] | OwnedNft[]
       ]
     > = [
@@ -1226,16 +1244,21 @@ describe('NFT module', () => {
   describe('verifyNftOwnership()', () => {
     const owner = '0xABC';
     const addresses = ['0xCA1', '0xCA2'];
+    const rawValidAt = {
+      blockHash: '0x123abc',
+      blockNumber: 123,
+      blockTimestamp: null
+    };
     const emptyResponse: RawGetBaseNftsResponse = {
       ownedNfts: [],
       totalCount: 0,
-      blockHash: '0x123abc',
+      validAt: rawValidAt,
       pageKey: null
     };
     const partialResponse: RawGetBaseNftsResponse = {
       ownedNfts: [createRawOwnedBaseNft('0xCA2', '0x2', '2')],
       totalCount: 1,
-      blockHash: '0x123abc',
+      validAt: rawValidAt,
       pageKey: null
     };
     const nftResponse: RawGetBaseNftsResponse = {
@@ -1244,7 +1267,7 @@ describe('NFT module', () => {
         createRawOwnedBaseNft('0xCA2', '0x2', '2')
       ],
       totalCount: 2,
-      blockHash: '0x123abc',
+      validAt: rawValidAt,
       pageKey: null
     };
 
@@ -1404,6 +1427,11 @@ describe('NFT module', () => {
           sellerAddress
         )
       ],
+      validAt: {
+        blockNumber: 1337,
+        blockTimestamp: null,
+        blockHash: null
+      },
       pageKey: null
     };