Displaying my NFTs on ETH and Polygon chains using Moralis API and PHP

After tinkering with automating crypto buys with the Coinbase API, I wanted to bring in my NFTs as part of my aggregate crypto portfolio (even though my NFTs are more personal interest than investment). I opted to explore Moralis as I’d never worked with their platform before and it seemed to have a good community around it. Overall, I’m pleased with the result but a few things of note.

First, the API is slow (but it is free). A call for a wallet’s NFTs across ETH and Polygon chains with corresponding URI fetches takes about 20 seconds for my ~50 NFTs.

Second, the Moralis cache of NFT data doesn’t always refresh against their stated SLA and I found a couple of my NFTs simply refuse to refresh. The Moralis documentation and community forum suggest they’re working on improving this but it’s quite frequent that users have to escalate to support when API calls for metadata refresh simply don’t work. I’m unsure if this is a Moralis problem or not but it feels like it is as platforms like OpenSea have no problem updating their NFT cache.

Here’s the end result (enlarge):

 

Here’s a walkthrough of my script:

require_once('/home/vendor/autoload.php');

//Using Guzzle over cURL
$client = new \GuzzleHttp\Client();

//I want to check both ETH and polygon chains
$nft_networks =array("eth","polygon");

//Start a loop for each of the chains I want to query.
foreach($nft_networks as $nft_nftwork){
  $response = $client->request('GET', "https://deep-index.moralis.io/api/v2/<your_wallet_address>/nft?chain=$nft_nftwork&format=hex&limit=100", [
    'headers' => [
      'X-API-Key' => '<your_api_key>',
      'accept' => 'application/json',
    ],
  ]);

  $json = $response->getBody();
  $data =  json_decode($response->getBody(),true);

  foreach($data['result'] as $nft) {
    $token_address = $nft['token_address'];
    $token_id = $nft['token_id'];
    $owner_of = $nft['owner_of'];
    $block_number = $nft['block_number'];
    $block_number_minted = $nft['block_number_minted'];
    $token_hash = $nft['token_hash'];
    $amount = $nft['amount'];
    $contract_type = $nft['contract_type'];
    $name = $nft['name'];
    $symbol = $nft['symbol'];
    $token_uri = $nft['token_uri'];
    $metadata = $nft['metadata'];
    $metadata_description = json_decode($nft['metadata'],true);
    $last_token_uri_sync = $nft['last_token_uri_sync'];
    $last_metadata_sync = $nft['last_metadata_sync'];

 

NFTs are surprisingly non-standard. Metadata lacks standard structure and syntax and URI data can contain images hosted on IPFS, a web2 protocol, or varying forms of video formats. I cleaned this up for the various NFTs I own but I’m sure there’s a better standard to follow.

//Try to get the metadata from the token URI but if that doesn't work, try from the original API call's metadata. The original metadata is usually less reliable and less detailed.
if(!file_get_contents($token_uri)){
  //Set image/video url
  if(isset($metadata_description['image'])){
    $image=$metadata_description['image'];
  } else {
    $image=$metadata_description['image_url'];
  }

  //Set NFT attributes array
  $attributes=$metadata_description['attributes'];

  //Set description
  if(empty($metadata_description['description'])){$description="";} else {$description=$metadata_description['description'];}
} else {
  //Set image/video url
  $token_uri_details = json_decode(file_get_contents($token_uri),true);
  $image=$token_uri_details['image'];

  //Set NFT attributes array
  $attributes=$token_uri_details['attributes'];

  //Set description
  $description=$token_uri_details['description'];

  //Set name
  $name=$token_uri_details['name'];
}

//The metadata and URI can be very non-standard. This is a weak attempt to standardize for easy display but is not a catch-all.
if(mb_substr($image, 0,4)=="http" && empty($metadata_description['animation_url']) && substr($image, -3)<>"mp4"){
  echo "<img alt='logicA' class=\"card-img-top\" src=\"$image\">";
} elseif(mb_substr($image, 0,12)=="ipfs://ipfs/" && empty($metadata_description['animation_url']) && substr($image, -3)<>"mp4"){
  echo "<img alt='logicB' class=\"card-img-top\" src=\"https://ipfs.io/ipfs/".str_replace("ipfs://ipfs/", "",$metadata_description['image'])." \">";
} elseif(mb_substr($image, 0,4)=="ipfs" && empty($metadata_description['animation_url']) && substr($image, -3)<>"mp4"){
  echo "<img alt='logicC' class=\"card-img-top\" src=\"https://ipfs.io/ipfs/".str_replace("ipfs://", "",$metadata_description['image'])." \">";
} elseif(!empty($metadata_description['animation_url'])){ 
  echo "<video class=\"card-img-top\" autoplay controls loop>
    <source src=\"https://ipfs.io/ipfs/".str_replace("ipfs://", "",$metadata_description['animation_url'])."\" type=\"video/mp4\">
  </video>";
} elseif(empty($metadata_description['animation_url']) && substr($image, -3)=="mp4"){ 
  echo "<video  class=\"card-img-top\" autoplay controls loop>
    <source src=\"$image\" type=\"video/mp4\">
  </video>";
}

 

Lastly, we’ll display the meta data, NFT attributes, and transaction details.

    echo "<h5 class=\"card-title\">$name | ".$metadata_description['name']." <a target=_blank href=\"$token_uri\">&#x2197;</a></h5>";
    echo "<p class=\"card-text\">$description</p>";
    echo "<p class=\"card-text\"><small class=\"\">";
    echo "
    <details>
      <summary>Raw Meta</summary><pre>";
      print_r($metadata_description);
      echo "</pre>
    </details>";

    echo "<strong>Attributes:</strong><br />";
    foreach ($attributes as $attribute){
      echo "<span class=\"badge bg-light text-white\">".$attribute['trait_type'].": ".$attribute['value']."</span> ";
    }
    $attributes=array();

    echo "<br /><strong>Metadata:</strong><br />Token Address: $token_address; Token ID: $token_id; Block #: $block_number; Minted: $block_number_minted; Contract Type: $contract_type; Token Hash: $token_hash; Symbol: $symbol; Last URI Sync: $last_token_uri_sync; Last Metadata Sync: $last_metadata_sync
    </small></p>";
  }
}//end network loop
?>

 

Here’s the full script:

<?php
require_once('/home/vendor/autoload.php');

$client = new \GuzzleHttp\Client();

//I want to check both ETH and polygon chains
$nft_networks =array("eth","polygon");

foreach($nft_networks as $nft_nftwork){
  $response = $client->request('GET', "https://deep-index.moralis.io/api/v2/<your_wallet_address>/nft?chain=$nft_nftwork&format=hex&limit=100", [
    'headers' => [
      'X-API-Key' => '<your_api_key>',
      'accept' => 'application/json',
    ],
  ]);

  $json = $response->getBody();
  $data =  json_decode($response->getBody(),true);

  foreach($data['result'] as $nft) {
    $token_address = $nft['token_address'];
    $token_id = $nft['token_id'];
    $owner_of = $nft['owner_of'];
    $block_number = $nft['block_number'];
    $block_number_minted = $nft['block_number_minted'];
    $token_hash = $nft['token_hash'];
    $amount = $nft['amount'];
    $contract_type = $nft['contract_type'];
    $name = $nft['name'];
    $symbol = $nft['symbol'];
    $token_uri = $nft['token_uri'];
    $metadata = $nft['metadata'];
    $metadata_description = json_decode($nft['metadata'],true);
    $last_token_uri_sync = $nft['last_token_uri_sync'];
    $last_metadata_sync = $nft['last_metadata_sync'];


    //Try to get the metadata from the token URI but if that doesn't work, try from the original API call's metadata. The original metadata is usually less reliable and less detailed.
    if(!file_get_contents($token_uri)){
      //Set image/video url
      if(isset($metadata_description['image'])){
        $image=$metadata_description['image'];
      } else {
        $image=$metadata_description['image_url'];
      }

      //Set NFT attributes array
      $attributes=$metadata_description['attributes'];

      //Set description
      if(empty($metadata_description['description'])){$description="";} else {$description=$metadata_description['description'];}
    } else {

      //Set image/video url
      $token_uri_details = json_decode(file_get_contents($token_uri),true);
      $image=$token_uri_details['image'];

      //Set NFT attributes array
      $attributes=$token_uri_details['attributes'];

      //Set description
      $description=$token_uri_details['description'];

      //Set name
      $name=$token_uri_details['name'];
    }

    //The metadata and URI can be very non-standard. This is a weak attempt to standardize for easy display but is not a catch-all.
    if(mb_substr($image, 0,4)=="http" && empty($metadata_description['animation_url']) && substr($image, -3)<>"mp4"){
      echo "<img alt='logicA' class=\"card-img-top\" src=\"$image\">";
    } elseif(mb_substr($image, 0,12)=="ipfs://ipfs/" && empty($metadata_description['animation_url']) && substr($image, -3)<>"mp4"){
      echo "<img alt='logicB' class=\"card-img-top\" src=\"https://ipfs.io/ipfs/".str_replace("ipfs://ipfs/", "",$metadata_description['image'])." \">";
    } elseif(mb_substr($image, 0,4)=="ipfs" && empty($metadata_description['animation_url']) && substr($image, -3)<>"mp4"){
      echo "<img alt='logicC' class=\"card-img-top\" src=\"https://ipfs.io/ipfs/".str_replace("ipfs://", "",$metadata_description['image'])." \">";
    } elseif(!empty($metadata_description['animation_url'])){ 
      echo "<video class=\"card-img-top\" autoplay controls loop>
        <source src=\"https://ipfs.io/ipfs/".str_replace("ipfs://", "",$metadata_description['animation_url'])."\" type=\"video/mp4\">
      </video>";
    } elseif(empty($metadata_description['animation_url']) && substr($image, -3)=="mp4"){ 
      echo "<video  class=\"card-img-top\" autoplay controls loop>
        <source src=\"$image\" type=\"video/mp4\">
      </video>";
    }

    echo "
                  <h5 class=\"card-title\">$name | ".$metadata_description['name']." <a target=_blank href=\"$token_uri\">&#x2197;</a></h5>";
    echo "<p class=\"card-text\">$description</p>";

    echo "<p class=\"card-text\"><small class=\"\">";
    echo "
    <details>
      <summary>Raw Meta</summary><pre>";
      print_r($metadata_description);
      echo "</pre>
    </details>";

    echo "<strong>Attributes:</strong><br />";
    foreach ($attributes as $attribute){
      echo "<span class=\"badge bg-light text-white\">".$attribute['trait_type'].": ".$attribute['value']."</span> ";
    }
    $attributes=array();

    echo "<br /><strong>Metadata:</strong><br />Token Address: $token_address; Token ID: $token_id; Block #: $block_number; Minted: $block_number_minted; Contract Type: $contract_type; Token Hash: $token_hash; Symbol: $symbol; Last URI Sync: $last_token_uri_sync; Last Metadata Sync: $last_metadata_sync
    </small></p>";
  }
}//end network loop
?>

 

Leave a Reply