Repository: alchemyplatform/alchemy-sdk-js
Pull request #238 (2023-01-10 23:11:04)

Include contract deployer and block number in NftContract


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cb0a07e1..7ce54a94 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,7 @@
 - Fixed a bug where using `AlchemySubscriptions.PENDING_TRANSACTIONS` with a string array input would throw an error (#222).
 - Added support for the `refreshCache` option in `NftNamespace.getNftMetadata()`. This option is now available when using the `options` overload. The original method without the `options` overload is now deprecated.
 - Added support for the `requestTimeout` option in the `AlchemySettings` object to configure a timeout for `NftNamespace` and `NotifyNamespace` methods. Thanks @Abbaskt!
+- Added the `contractDeployer` and `deployedBlockNumber` into the `NftContract` object. Methods that return NFTs such as `NftNamespace.getNftsForOwner()` and `NftNamespace.getContractsForOwner()` should now include this information under the `contract` field.
 
 ## 2.2.5
 
diff --git a/src/api/nft-namespace.ts b/src/api/nft-namespace.ts
index 87e88554..6b184353 100644
--- a/src/api/nft-namespace.ts
+++ b/src/api/nft-namespace.ts
@@ -87,6 +87,15 @@ export class NftNamespace {
     tokenType?: NftTokenType,
     tokenUriTimeoutInMs?: number
   ): Promise<Nft>;
+
+  /**
+   * Get the NFT metadata associated with the provided parameters.
+   *
+   * @param contractAddress - The contract address of the NFT.
+   * @param tokenId - Token id of the NFT.
+   * @param options - Options for the request.
+   * @public
+   */
   getNftMetadata(
     contractAddress: string,
     tokenId: BigNumberish,
diff --git a/src/api/nft.ts b/src/api/nft.ts
index 22cee761..c8db79e2 100644
--- a/src/api/nft.ts
+++ b/src/api/nft.ts
@@ -44,6 +44,10 @@ export interface NftContract extends BaseNftContract {
   totalSupply?: string;
   /** OpenSea's metadata for the contract. */
   openSea?: OpenSeaCollectionMetadata;
+  /** The address that deployed the NFT contract. */
+  contractDeployer?: string;
+  /** The block number the NFT contract deployed in. */
+  deployedBlockNumber?: number;
 }
 
 /**
diff --git a/src/internal/raw-interfaces.ts b/src/internal/raw-interfaces.ts
index 71606b85..15dac89a 100644
--- a/src/internal/raw-interfaces.ts
+++ b/src/internal/raw-interfaces.ts
@@ -83,6 +83,8 @@ export interface RawNftContractMetadata {
   totalSupply?: string;
   tokenType?: NftTokenType;
   openSea?: RawOpenSeaCollectionMetadata;
+  contractDeployer?: string;
+  deployedBlockNumber?: number;
 }
 
 /** OpenSea's metadata for an NFT collection. */
diff --git a/src/util/util.ts b/src/util/util.ts
index 33e8c49d..63d04cce 100644
--- a/src/util/util.ts
+++ b/src/util/util.ts
@@ -51,7 +51,9 @@ export function getNftContractFromRaw(
     symbol: rawNftContract.contractMetadata.symbol,
     totalSupply: rawNftContract.contractMetadata.totalSupply,
     tokenType: parseNftTokenType(rawNftContract.contractMetadata.tokenType),
-    openSea: parseOpenSeaMetadata(rawNftContract.contractMetadata.openSea)
+    openSea: parseOpenSeaMetadata(rawNftContract.contractMetadata.openSea),
+    contractDeployer: rawNftContract.contractMetadata.contractDeployer,
+    deployedBlockNumber: rawNftContract.contractMetadata.deployedBlockNumber
   };
 }
 
@@ -85,7 +87,9 @@ export function getNftFromRaw(rawNft: RawNft): Nft {
         symbol: rawNft.contractMetadata?.symbol,
         totalSupply: rawNft.contractMetadata?.totalSupply,
         tokenType,
-        openSea: parseOpenSeaMetadata(rawNft.contractMetadata?.openSea)
+        openSea: parseOpenSeaMetadata(rawNft.contractMetadata?.openSea),
+        contractDeployer: rawNft.contractMetadata?.contractDeployer,
+        deployedBlockNumber: rawNft.contractMetadata?.deployedBlockNumber
       },
       tokenId: parseNftTokenId(rawNft.id.tokenId),
       tokenType,
@@ -180,7 +184,9 @@ export function getContractsForOwnerFromRaw(
         name: contract.name,
         openSea: parseOpenSeaMetadata(contract?.opensea),
         symbol: contract?.symbol,
-        tokenType: parseNftTokenType(contract?.tokenType)
+        tokenType: parseNftTokenType(contract?.tokenType),
+        contractDeployer: contract.contractDeployer,
+        deployedBlockNumber: contract.deployedBlockNumber
       };
     })
   };


Test patch:
diff --git a/test/integration/nft.test.ts b/test/integration/nft.test.ts
index 17286876..22860835 100644
--- a/test/integration/nft.test.ts
+++ b/test/integration/nft.test.ts
@@ -1,5 +1,6 @@
 import {
   Alchemy,
+  NftContract,
   NftFilters,
   NftSaleMarketplace,
   NftTokenType,
@@ -28,15 +29,34 @@ describe('E2E integration tests', () => {
     jest.setTimeout(50000);
   });
 
+  function verifyNftContractMetadata(metadata: NftContract): void {
+    expect(typeof metadata.totalSupply).toEqual('string');
+    expect(typeof metadata.symbol).toEqual('string');
+    expect(metadata.tokenType).toEqual(NftTokenType.ERC721);
+    expect(typeof metadata.name).toEqual('string');
+    expect(metadata.openSea).toBeDefined();
+    expect(metadata.openSea?.safelistRequestStatus).toBeDefined();
+    expect(
+      Object.values(OpenSeaSafelistRequestStatus).includes(
+        metadata.openSea?.safelistRequestStatus!
+      )
+    ).toEqual(true);
+    expect(typeof metadata.contractDeployer).toEqual('string');
+    expect(typeof metadata.deployedBlockNumber).toEqual('number');
+  }
+
   it('getNftMetadata()', async () => {
     const contractAddress = '0x0510745d2ca36729bed35c818527c4485912d99e';
     const tokenId = 403;
     const response = await alchemy.nft.getNftMetadata(
       contractAddress,
       tokenId,
-      NftTokenType.UNKNOWN
+      {
+        tokenType: NftTokenType.UNKNOWN
+      }
     );
     expect(response.media).toBeDefined();
+    verifyNftContractMetadata(response.contract);
   });
 
   it('getNftMetadataBatch()', async () => {
@@ -55,18 +75,7 @@ describe('E2E integration tests', () => {
 
   it('getContractMetadata()', async () => {
     const response = await alchemy.nft.getContractMetadata(contractAddress);
-    expect(typeof response.totalSupply).toEqual('string');
-    expect(typeof response.symbol).toEqual('string');
-    expect(response.tokenType).toEqual(NftTokenType.ERC721);
-    expect(response.address).toEqual(contractAddress);
-    expect(typeof response.name).toEqual('string');
-    expect(response.openSea).toBeDefined();
-    expect(response.openSea?.safelistRequestStatus).toBeDefined();
-    expect(
-      Object.values(OpenSeaSafelistRequestStatus).includes(
-        response.openSea?.safelistRequestStatus!
-      )
-    ).toEqual(true);
+    verifyNftContractMetadata(response);
   });
 
   it('getOwnersForNft()', async () => {
@@ -160,19 +169,7 @@ describe('E2E integration tests', () => {
     const response = await alchemy.nft.getContractsForOwner(ownerAddress);
 
     expect(response.contracts.length).toBeGreaterThan(0);
-    expect(response.contracts[0].address).toBeDefined();
-    expect(typeof response.contracts[0].address).toEqual('string');
-    expect(response.contracts[0].isSpam).toBeDefined();
-    expect(typeof response.contracts[0].isSpam).toEqual('boolean');
-    expect(response.contracts[0].media).toBeDefined();
-    expect(response.contracts[0].numDistinctTokensOwned).toBeDefined();
-    expect(typeof response.contracts[0].numDistinctTokensOwned).toEqual(
-      'number'
-    );
-    expect(response.contracts[0].totalBalance).toBeDefined();
-    expect(typeof response.contracts[0].totalBalance).toEqual('number');
-    expect(response.contracts[0].tokenId).toBeDefined();
-    expect(typeof response.contracts[0].tokenId).toEqual('string');
+    verifyNftContractMetadata(response.contracts[0]);
   });
 
   it('getContractsForOwner() with pageKey', async () => {
diff --git a/test/test-util.ts b/test/test-util.ts
index 0b6ab95a..af49fdd9 100644
--- a/test/test-util.ts
+++ b/test/test-util.ts
@@ -37,20 +37,12 @@ export const TEST_WALLET_PUBLIC_ADDRESS =
 /** Creates a dummy response for the `getContractMetadata` endpoint. */
 export function createRawNftContract(
   address: string,
-  tokenType: NftTokenType,
-  name?: string,
-  symbol?: string,
-  totalSupply?: string,
-  openSea?: RawOpenSeaCollectionMetadata
+  metadata: RawNftContractMetadata
 ): RawNftContract {
   return {
     address,
     contractMetadata: {
-      name,
-      symbol,
-      totalSupply,
-      tokenType,
-      openSea
+      ...metadata
     }
   };
 }
@@ -248,7 +240,9 @@ export function createRawContractForOwner(
   tokenType?: NftTokenType,
   symbol?: string,
   totalSupply?: string,
-  opensea?: RawOpenSeaCollectionMetadata
+  opensea?: RawOpenSeaCollectionMetadata,
+  contractDeployer?: string,
+  deployedBlockNumber?: number
 ): RawContractForOwner {
   return {
     address,
@@ -261,7 +255,9 @@ export function createRawContractForOwner(
     totalSupply,
     opensea,
     symbol,
-    tokenType
+    tokenType,
+    contractDeployer,
+    deployedBlockNumber
   };
 }
 
diff --git a/test/unit/nft-api.test.ts b/test/unit/nft-api.test.ts
index a750d87b..808c2965 100644
--- a/test/unit/nft-api.test.ts
+++ b/test/unit/nft-api.test.ts
@@ -83,16 +83,19 @@ describe('NFT module', () => {
     const symbol = 'NCN';
     const totalSupply = '9999';
     const tokenType = NftTokenType.ERC721;
+    const contractDeployer = '0xDEF';
+    const deployedBlockNumber = 424242;
     const openSea = createRawOpenSeaCollectionMetadata();
 
-    const rawNftContractResponse = createRawNftContract(
-      address,
+    const rawNftContractResponse = createRawNftContract(address, {
       tokenType,
       name,
       symbol,
       totalSupply,
-      openSea
-    );
+      openSea,
+      contractDeployer,
+      deployedBlockNumber
+    });
     const expectedNftContract = getNftContractFromRaw(rawNftContractResponse);
 
     beforeEach(() => {
@@ -131,12 +134,22 @@ describe('NFT module', () => {
     const title = 'NFT Title';
     const tokenId = '42';
     const timeoutInMs = 50;
+    const contractMetadata = {
+      name: 'NFT Title',
+      symbol: 'NCN',
+      totalSupply: '9999',
+      tokenType: NftTokenType.ERC721,
+      contractDeployer: '0xDEF',
+      deployedBlockNumber: 424242
+    };
     // Special case token ID as an integer string, since that's what the NFT
     // API endpoint returns.
     const rawNftResponse = createRawNft(
       contractAddress,
       title,
-      tokenId.toString()
+      tokenId.toString(),
+      NftTokenType.UNKNOWN,
+      { contractMetadata }
     );
     const expectedNft = getNftFromRaw(rawNftResponse);
 
@@ -1137,6 +1150,8 @@ describe('NFT module', () => {
     const totalContractCount = 3;
     const totalSupply = '1492';
     const nftMediaData = createNftMediaData();
+    const contractDeployer = '0xABC';
+    const deployedBlockNumber = 424242;
     const completeNftMediaData = createNftMediaData(
       128,
       'jpg',
@@ -1169,7 +1184,9 @@ describe('NFT module', () => {
           NftTokenType.ERC1155,
           symbol,
           totalSupply,
-          rawOpenSeaContractMetadata
+          rawOpenSeaContractMetadata,
+          contractDeployer,
+          deployedBlockNumber
         )
       ]
     };
@@ -1532,13 +1549,12 @@ describe('NFT module', () => {
     const totalSupply = '1155';
     const tokenType = NftTokenType.ERC721;
 
-    const rawNftContractResponse = createRawNftContract(
-      address,
+    const rawNftContractResponse = createRawNftContract(address, {
       tokenType,
       name,
       symbol,
       totalSupply
-    );
+    });
     const templateResponse = [rawNftContractResponse];
     const expectedNftContract = getNftContractFromRaw(rawNftContractResponse);