NFTs are so hot right now! I don’t own any myself, but there is a lot of hype around them at the moment within the web3 space.

What is web3 anyway? To be honest, I’m not really sure, but I have done some looking into how to sell NFTs, specifically on some of the NFT marketplaces like OpenSea and Rarible. In all my research and playing, I also helped my 12 year old son create his first NFT. Unfortunately, nobody has purchased it yet for $16 million 🙂

Can FooGallery Sell NFTs?

The other day, I started to think “How would someone use FooGallery to sell NFTs?”. Well, right now, you can sell images using FooGallery and WooCommerce very easily. But NFTs are bought and sold using cryptocurrencies, so how would that work with WooCommerce?

After a short google, I did find some blog posts on this subject. But it was too much work for me. (I am a bit lazy.) And it would certainly be too much work for our customers. (Most people are lazy too.)

So I figured there must be a middle ground. After all, I don’t mind keeping my NFTs hosted on an already established marketplace like Opensea. So my next thought was “Why not just create an Opensea datasource for FooGallery!”

Opensea has an API : https://docs.opensea.io/reference/api-keys

And after playing around with their API, I realised that I can easily integrate with the API and get this all working. And that is what I am going to show you today, step by step. I am actually going to code this feature while writing this blog post (something I have never done before). The end result should hopefully be an easy to use feature that you can use to sell NFTs with WordPress.

FooGallery Datasources FTW

FooGallery has a nifty feature where you can build galleries from other sources. By default, it pulls images directly from the media library. But it can also pull from other places, including pulling images from a directory on your server, or pulling in all images with a specific tag. There are a bunch of them:

FooGallery Datasources

And our latest datasource pulls in products from WooCommerce. This really starts to become powerful, because now, besides the normal stuff like the image and caption, you can start using all the product metadata, like price, if the product’s on sale, etc. This can result in some really cool (and useful) product galleries, like this:

gallery with products from woocommerce

Adding A New Datasource To FooGallery

FooGallery has a great datasource architecture, so the first step to being able to sell NFTs is to add a new datasource which does nothing. We can do the actual integration with OpenSea later. To do this I created a new PHP file which has a class:

<?php
/**
* The Gallery Datasource which pulls data from Opensea.
*
* @since 2.1.34
*/
if ( ! class_exists( 'FooGallery_Pro_Datasource_Opensea' ) ) {

  class FooGallery_Pro_Datasource_Opensea {
  }
}Code language: HTML, XML (xml)

Obviously this file will need to be included and something needs to be instantiated, because it’s a class, but I am not going to show that code here.

There is a useful class that does all the heavy lifting for you. All you need to do is extend FooGallery_Datasource_Base. This is an abstract class with a couple of abstract methods. All abstract methods have to be implemented and we will do that later. But for now, let’s extend the class and call the parent class’ constructor with some necessary info:

class FooGallery_Pro_Datasource_Opensea extends FooGallery_Datasource_Base {

  public function __construct() {
     parent::__construct(
        'opensea',
        __('OpenSea', 'foogallery'),
        FOOGALLERY_PRO_URL . 'js/foogallery.admin.datasources.opensea.js'
     );
  }Code language: PHP (php)

Some basic stuff here. We are giving the datasource a key and a name. We also want to include some javascript to help when capturing some data about the datasource, so we also include the path to that javascript file (which will be created later).

After implementing the 3 abstract methods (which do nothing for now), this is what it looks like in the frontend:

opensea datasource added to FooGallery

When you click on the new Opensea datasource, nothing shows. We need to output some HTML in the render_datasource_modal_content method:

/**
* Render some modal content for the OpenSea datasource.
*
* @param int $foogallery_id The ID of the current FooGallery.
* @param array $datasource_value The stored value for the datasource.
*
* @return void
*/
public function render_datasource_modal_content( $foogallery_id, $datasource_value ) {
  $opensea_link = '<a href="https://opensea.io/" target="_blank">' . __( 'OpenSea', 'foogallery' ) . '</a>';
  $opensea_logo = 'https://opensea.io/static/images/logos/opensea.svg';
  ?>
  <h2>
     <img src="<?php echo esc_url( $opensea_logo ); ?>" width="40" height="40" />
     <?php _e( 'OpenSea', 'foogallery' ); ?>
  </h2>
  <p>
     <?php echo sprintf( __('The gallery will be dynamically populated with NFT\'s from %s.', 'foogallery' ), $opensea_link ); ?>
  </p>
  <?php
}Code language: PHP (php)

Now it looks like this:

opensea  datasource in FooGallery

Looking again at the OpenSea assets endpoint, it has a number of parameters that we can use to filter the results, including owner, token_ids, order_by etc. We want to add a few of these to a form in the modal. The HTML for adding owner looks like:

<form action="" method="post" name="opensea_gallery_form">
  <table class="form-table">
     <tbody>
     <tr>
        <th scope="row"><?php _e( 'Owner', 'foogallery' ) ?></th>
        <td>
           <input
              class="regular-text foogallery_opensea_input"
              name="owner"
              id="foogallery_opensea_owner"
              value="<?php echo isset( $datasource_value['owner'] ) ? $datasource_value['owner'] : '' ?>"
           />
           <p class="description"><?php _e( 'The address of the owner of the assets.', 'foogallery' ) ?></p>
        </td>
     </tr>
     </tbody>
  </table>
</form>Code language: HTML, XML (xml)

I added a few other fields to the HTML form, and it now looks like this:

settings for opensea datasource

That should be good enough for now. But nothing is actually being saved from this form, and it does not change what we see when the modal is closed. We need to hook that all up with some javascript.

The built-in FooGallery datasource javascript will trigger an event when the modal is closed. The trigger used for our new datasource key is foogallery-datasource-changed-opensea.

Remember in the class constructor, we passed in a path to a javascript file foogallery.admin.datasources.opensea.js? We can now create that file, which will automatically be enqueued for us by the base class when needed. It contains the following code:

FooGallery.utils.ready(function ($) {

   // Hook into the opensea datasource change event
   $(document).on('foogallery-datasource-changed-opensea', function () {
       //build up the datasource_value from our form.
       var value = {
           "owner": $('#foogallery_opensea_owner').val(),
           "token_ids": $('#foogallery_opensea_token_ids').val(),
           "order_direction": $(".foogallery_opensea_input.order_direction:checked").val(),
           "order_by": $(".foogallery_opensea_input.order_by:checked").val()
       };

       //save the datasource_value to a special hidden input.
       $('#_foogallery_datasource_value').val(JSON.stringify(value));

       //tidy up : show the correct stuff within the Gallery Items metabox
       FOOGALLERY.showHiddenAreas(false);

       //tidy up : hide the attachment list
       $('.foogallery-attachments-list-container').addClass('hidden');

       //force the gallery to refresh when next previewed
       $('.foogallery_preview_container').addClass('foogallery-preview-force-refresh');
   });
});Code language: JavaScript (javascript)

This code hooks into the trigger and saves the values of the form we created earlier. This datasource value is saved as postmeta against the gallery you are currently editing. And then it does a bit of cleanup to make sure the right stuff is shown on the gallery edit page.

The second method in the base class which needs to be implemented is render_datasource_state. This method outputs HTML to represent the saved state of our datasource. This might be hard to understand, so let me show an example from another datasource. The Post Query datasource allows you to populate a gallery from posts or pages. Once you have selected all the options for your post query, this is shown to you:

Post Query Datasource

It’s a nice representation of all your selected options for the state of the datasource, and we need to do something similar for the OpenSea datasource.

Here is the code:

/**
* Render the state of the datasource in the admin.
*
* @param FooGallery $gallery
*/
function render_datasource_state( $gallery ) {
  // Only show the container if that is the selected datasource for the gallery.
  $show_container = isset( $gallery->datasource_name ) && 'opensea' === $gallery->datasource_name;

  // Extract all the values we care about from the datasource value.
  $owner = $this->get_datasource_value( 'owner', '' );
  $token_ids = $this->get_datasource_value('token_ids', '' );
  $order_by = $this->get_datasource_value( 'order_by', '' );
  $order_direction = $this->get_datasource_value( 'order_direction', '' );

  ?>
  <div <?php echo $show_container ? '' : 'style="display:none" '; ?>class="foogallery-datasource-item foogallery-datasource-opensea">
     <h3>
        <?php _e( 'Datasource : OpenSea', 'foogallery' ); ?>
     </h3>
     <p>
        <?php _e( 'This gallery will be dynamically populated with NFT\'s from OpenSea with the following parameters:', 'foogallery' ); ?>
     </p>
     <div class="foogallery-items-html">
        <?php echo __('Owner : ', 'foogallery'); ?><span id="foogallery-datasource-opensea-owner"><?php echo $owner; ?></span><br />
        <?php echo __('Token ID\'s: ', 'foogallery'); ?><span id="foogallery-datasource-opensea-token-ids"><?php echo $token_ids; ?></span><br />
        <?php echo __('Order By : ', 'foogallery'); ?><span id="foogallery-datasource-opensea-order-by"><?php echo $order_by; ?></span><br />
        <?php echo __('Order Direction : ', 'foogallery'); ?><span id="foogallery-datasource-opensea-order-direction"><?php echo $order_direction; ?></span><br />
     </div>
     <br/>
     <button type="button" class="button edit">
        <?php _e( 'Change', 'foogallery' ); ?>
     </button>
     <button type="button" class="button remove">
        <?php _e( 'Remove', 'foogallery' ); ?>
     </button>
  </div>
  <?php
}Code language: JavaScript (javascript)

Some pretty simple HTML that renders the values we have saved for the OpenSea datasource. I also added some javascript to our previous file to show this info after the modal is closed. And I hooked up the buttons to allow you to change or remove the datasource. That is about it, when it comes to saving and displaying the data needed for the new OpenSea datasource. So now when the modal closes, this info is shown:

opensea as foogallery datasource

But Something Smells

When I got to this point in the post, I read what I had written and looked at the code. Something smelled. The code had a distinct smell to it. All developers know this feeling. I had written a LOT of code, and I realised that a lot of the code was copied from a previous datasource. This duplication had to go, so a major refactor was in order. This is when I extracted most of the code I had written and created the base class FooGallery_Datasource_Base. It never existed before this blog post! 

I deleted all the duplicate code and I changed my class to rather extend the new base class. The resulting code was about 40% of the original! And this also meant new and existing datasources could reuse all this logic. I had to go back and edit/delete a LOT of content from this post. About 2 hours later, the smell was gone, the post was shorter and it was easier to follow, so win-win! Moving on…

Hooking Up the OpenSea API

Now that the UI is done, we can dive into the most interesting part : working with the OpenSea API. We can now implement the 3rd and final method in the base class, called build_attachments_from_datasource. This method must return an array of FooGalleryAttachment objects. Check out the FooGalleryAttachment class to see what it does. The function will have this signature:

/**
* Returns an array of FooGalleryAttachments from OpenSea
*
* @param FooGallery $foogallery
*
* @return array(FooGalleryAttachment)
*/
public function get_gallery_attachments( $foogallery ) {
  return array();
}Code language: PHP (php)

The OpenSea API is very simple and all we need to do is build up a URL with our parameters. So let’s build up the URL first:

$base_url = 'https://api.opensea.io/api/v1/assets';
$params = array();

if ( ! empty( $foogallery->datasource_value['owner'] ) ) {
  $params['owner'] = $foogallery->datasource_value['owner'];
}
if ( ! empty( $foogallery->datasource_value['token_ids'] ) ) {
  $params['token_ids'] = explode( ',', $foogallery->datasource_value['token_ids'] );
}
if ( ! empty( $foogallery->datasource_value['order_by'] ) ) {
  $params['order_by'] = $foogallery->datasource_value['order_by'];
}
if ( ! empty( $foogallery->datasource_value['order_direction'] ) ) {
  $params['order_direction'] = $foogallery->datasource_value['order_direction'];
}

$url = add_query_arg( $params, $base_url );Code language: PHP (php)

I am using the built-in WordPress function add_query_arg to do the hard work for me. It will build up a nicely formatted URL that we can now pass onto OpenSea.

To call the URL, we use another built-in WordPress function wp_remote_get. We do some checks to make sure we get a successful response and then we convert the response to an array:

// Make the call to the API.
$response = wp_remote_get( $url );

// Check for errors.
if ( ! is_wp_error( $response ) ) {

  // Check we got a successful response.
  if ( wp_remote_retrieve_response_code( $response ) == 200 ) {

     // Decode the json body to an array, which we can work with.
     $assets = @json_decode( wp_remote_retrieve_body( $response ), true );

  }
}Code language: PHP (php)

If all went well, we now have an $assets variable which holds an array of all the data we need. And there is a lot of data:

Hooking Up the OpenSea API

So now, we can loop through the assets and create new instances of FooGalleryAttachment objects:

foreach ( $assets['assets'] as $asset ) {
  $attachment = new FooGalleryAttachment();

  $attachment->ID            = $asset['token_id'];
  $attachment->title         = $asset['name'];
  $attachment->description   = $asset['description'];
  $attachment->custom_url    = $asset['permalink'];
  $attachment->custom_target = '_blank';
  $attachment->sort          = '';
  $attachment->url           = $asset['image_url'];
  $attachment->asset         = $asset;

  $attachment    = apply_filters( 'foogallery_datasource_opensea_build_attachment', $attachment, $asset );
  $attachments[] = $attachment;
}Code language: PHP (php)

And that is pretty much all the hard work done. And this is how it looks:

opensea datasource to sell nfts

Areas For Improvement

I noticed a few things which can be improved. They are:

  • There is no control over the number of NFT’s you get back. There is a limit parameter in the OpenSea assets API, which we can and should use.
  • Token_Id is a parameter on the API, but is not something you can actually find from the OpenSea frontend, so it is pretty useless.
  • We do not pull any price info, which will be useful to show if you want to sell NFTs. Thinking about this more, we should also show a Buy Now button which takes you directly to OpenSea. And perhaps show a ribbon if there is a current auction for the item.
  • We are only handling images. What about other types of NFT’s like videos?
  • The OpenSea API is rate limited, and with all my testing I eventually got blocked from calling it. You can overcome this by requesting an API key, and passing the API key in the headers when making the calls. (showstopper)
  • If there are any errors when calling the API, they are not being shown to the user, which can be very frustrating. Perhaps this can be shown together with the datasource state? (showstopper)
  • The datasource state could also contain other info, like number of calls made to the API, or time of last data refresh. Maybe not so interesting to the user, but useful for debugging.

When Will This Be Available in FooGallery?

I am not sure TBH. I think the above areas of improvement need to be addressed first. Or at the very least, the “showstoppers” in the list need to be sorted. This will likely also be part of the PRO Commerce plan, so the feature has to be solid before people start paying for it.

That made me think of experimental features. It would be awesome to enable experimental features in the plugin, and then you get to play around with the new stuff. But then it probably cannot be paid if it was experimental. Or can it? 

Do You Want To Sell NFTs With WordPress?

Are you looking to do something like this? Would you use the OpenSea datasource to list or sell NFTs? Should we release it? Would you pay for it?

A Short Summary

So what did I actually learn from writing this post? A number of things:

  • The datasource architecture in FooGallery is really great and super-easy to extend. And it is even better now with my new base class that can be extended.
  • Refactoring is a never-ending task. Writing the code and this post at the same time made me question my existing code. That is a good thing.
  • The OpenSea API is actually very easy to integrate with. But it comes with some caveats (listed above in the areas of improvements). Writing software is seldom as easy as it seems. 
  • The 80/20 rule is always a thing! It’s very easy to get 80% of the way through something. But it’s the last 20% that will take 80% of your overall time.
  • It has been ages since I wrote and I really enjoyed writing this post. “Building in public” as they say. I could even continue with part 2 and finish the feature by addressing all my areas of improvement. I want to write more content like this.
  • There are always so many things to consider with software. Perhaps I should stop overthinking every angle and scenario, but it is hard to rewire the way I think.

I hope you learn (from reading) as much as I did from writing this post. Let me know what you think about this #buildinpublic post and whether you’re looking for a way to sell NFTs with WordPress.

Comments are closed.