How to check NFT supply with AWS Lambda?

Non-Fungible Tokens, or short NFTs, are all the rage right now. Everyone and their pets are starting an NFT project. Some people got rich from using NFTs; others did not. Some say it’s the savior that will rip the power away from big corporations and give it back to the creators; others say it’s just a giant pyramid scheme.

I don’t know how things will play out, if blockchains will be the next big thing, fueled by the NFT hype, or if it will fizzle out like so many other technologies before it. But a question I got asked lately was, how does this precisely fit in with AWS and serverless technology?

That’s why I thought, let’s ride the hype train and write an article about NFTs and serverless technology!

My first thought when thinking about AWS and blockchain was, of course, the AWS service with blockchain in its name: Amazon Managed Blockchain (AMB).

With AMB, you get Ethereum nodes hosted on an EC2 machine of your choice, managed by AWS. You can think of it as RDS, but with blockchains instead of SQL databases.

You will need such a service if you don’t want to rely on nodes of third parties, like Infura or Alchemy. These nodes can be seen as the bridge between on-chain services and off-chain services. 

ABM is neither serverless nor cheap; you pay per hour and can end up with a bill that’s over $300, no problem. So you should use it to make it safe that your nodes don’t go down when the third party gets bankrupt or if you managed to get exorbitant bills from a third party service.

Another related service is Amazon Quantum Ledger Database (QLDB), a managed immutable blockchain/ledger. It’s used if you need the immutability of a blockchain, but without everything decentralized, that comes with it. It has on-demand payment and is quite a bit cheaper than AMB, so if you just need an immutable database, go for QLDB.

Node or Client?

The nodes that make up the decentralized system Ethereum can be seen as servers in a classical setup or the databases and functions in a serverless design. They mine new blocks or validate transactions; they also execute the software that is known as a smart contract. This means you need to connect to a node to access the Ethereum blockchain.

In this article we aren’t focusing on nodes, so AMB doesn’t interest us here. We want to implement a client that asks a node for data that’s stored on the blockchain. So, our example will be located off-chain.

What Will We Build?

We will build a serverless system with the AWS CDK. It will consist of a Lambda function called every hour and an S3 bucket to store data off-chain. We will use JavaScript and the Ethers.js library to connect from AWS Lambda to Ethereum.

The data we will check is the supply of NFTs a smart contract has issued. Since many of the smart contracts on the Ethererum blockchain have well-defined interfaces, we can write a function that just needs a contract address to do its work.

Usually, AWS is seen as a competition to Ethereum because “Ethereum is just another way to build backends,” but I think they can work together. Transactions on the blockchain are expensive, so some data and calculations could be outsourced to an off-chain system like AWS Lambda.

How to Interact with Ethereum?

To connect a non-Ethereum (off-chain) system with an Etherum smart contract (on-chain), it must connect to a node. We already learned that AWS offers rather expensive nodes we could use, but there are many services out there that can be used for free. These services have harsh limits, but for this example, they should suffice.

Usually, transactions on the blockchain cost gas, but we will only call a function marked as view. This way, the node we connect to can simply read the data from its local blockchain copy, no transactions, and in turn, neither a wallet nor gas is needed.

The Infrastructure

Let’s look at the example CDK stack that defines our infrastructure.

const nftChecks = new s3.Bucket(this, "nftChecks");

const nftChecker = new lambda.Function(this, "nftChecker", {
  runtime: lambda.Runtime.NODEJS_14_X,
  handler: "index.handler",
  code: lambda.Code.fromAsset(
    path.join(__dirname, "functions", "nftChecker")
  ),
  environment: {
    BUCKET_NAME: nftChecks.bucketName,
    CONTRACT_ADDRESS: "0x25ed58c027921e14d86380ea2646e3a1b5c55a8b",
  },
});

nftChecks.grantWrite(nftChecker);

const rule = new events.Rule(this, "nftCheckRule", {
  schedule: events.Schedule.rate(cdk.Duration.hours(1)),
});
rule.addTarget(new eventsTargets.LambdaFunction(nftChecker));

A bucket and a Lambda function. The bucket name and the address of the smart contract are passed to the Lambda function via environment variables. 

In this example, I used the NFT smart contract of the Developer DAO because its code is open source, so I know what interfaces they implemented.

At the end of the stack, the schedule is set up with CloudWatch events.

The Lambda Function

Now, let us look at the code that will interact with Ethereum.

const contractAbi = ["function totalSupply() external view returns (uint256)"];

const { BUCKET_NAME, CONTRACT_ADDRESS } = process.env;

exports.handler = async function () {
  const provider = new ethers.providers.getDefaultProvider();
  const contract = new ethers.Contract(CONTRACT_ADDRESS, contractAbi, provider);
  const mintedTokens = await contract.totalSupply();

  await s3
    .putObject({
      Bucket: BUCKET_NAME,
      Key: new Date().toISOString() + ".json",
      Body: mintedTokens + "",
    })
    .promise();
};

The first line defines the Application Binary Interface of the smart contract we want to call. It’s just an array of strings that make up the method signatures of that contract. I didn’t define all the contract methods because I will just call one of them anyway.

Next, I get the environment variables to know where to get the data from and where to save it.

In the function body, I get the default provider from Ethers.js. In this example, the function is only executed once an hour, so it won’t hit the limits. Still, in a production system that might access a node provider more often, you should sign up for an Ethereum gateway service like Infura or Alchemy.

Then I set up the contract with the correct ABI, address, and provider. The provider makes sure we are connected to the proper chain (there are test chains for Ethereum, we don’t want to communicate to those). The ABI tells Ethers.js which methods the contract provides. And finally, the contract’s address so Ethers.js knows where to find the contract on the chain.

Finally, we call the method that gives us the number of NFTs already minted and save it to our S3 bucket as a JSON file. The file name is the current date and time as ISO-string; we will know how many NFTs were minted every hour.

Monitoring with Dashbird

If you moved some of your calculations off-chain and into AWS Lambda, you can monitor them with Dashbird like every other serverless system you build on AWS. No additional setup is needed. We can set up alarms for our NFT checker and get notified if things fail. 

In Figure 1, we see the general information Dashbird gives us about a Lambda function. This just resembles a few test invocations, but we can already see some interesting things.

Figure 1: Dashbird Lambda function details

The executions were all free of charge and they fit snuggly into the smallest memory configuration. That’s good to know. But if we look at the Duration tab in Figure 2, we see that our function has varying runtimes.

Figure 2: Dashbird Lambda duration tab

The function took anything from under 1 second to up to 3 seconds to do its work. With the default Lambda invocation timeout of 3,000 milliseconds, our function is at high risk to be shut down prematurely. Especially, since it’s only called once every hour, it will have a cold-start every time.

We can use the insight gathered by Dashbird to update our Lambda function definition with a more generous timeout, so it won’t accidentally crash in the future.

const nftChecker = new lambda.Function(this, "nftChecker", {
  runtime: lambda.Runtime.NODEJS_14_X,
  handler: "index.handler",
  timeout: cdk.Duration.seconds(30),
  code: lambda.Code.fromAsset(
    path.join(__dirname, "functions", "nftChecker")
  ),
  environment: {
    BUCKET_NAME: nftChecks.bucketName,
    CONTRACT_ADDRESS: "0x25ed58c027921e14d86380ea2646e3a1b5c55a8b",
  },
});

Conclusion

This small example application outlined how serverless technology can be used in tandem with blockchains. Lambda functions are cheaper and faster than blockchain transactions, so they lend themselves for non-critical calculations that don’t have to be tracked by a blockchain.

This example was straightforward and somewhat contrived, but doing automated work that depends on the state of a blockchain is an actual use case. The Lambda function could render statistics on an HTML site or notify the NFT creator of changes to their supply.

With Dashbird you can keep track of your NFT checker as you would with any other Lambda function and use the insights to optimize the execution even more. You can find the complete example on GitHub.


Further reading:

How we built a serverless “stonks” checker API for Wall Street Bets

How to save hundreds of hours on Lambda debugging?

Bulletproofing serverless applications with failure and threat detection

Read our blog

ANNOUNCEMENT: new pricing and the end of free tier

Today we are announcing a new, updated pricing model and the end of free tier for Dashbird.

4 Tips for AWS Lambda Performance Optimization

In this article, we’re covering 4 tips for AWS Lambda optimization for production. Covering error handling, memory provisioning, monitoring, performance, and more.

AWS Lambda Free Tier: Where Are The Limits?

In this article we’ll go through the ins and outs of AWS Lambda pricing model, how it works, what additional charges you might be looking at and what’s in the fine print.

More articles

Made by developers for developers

Dashbird was born out of our own need for an enhanced serverless debugging and monitoring tool, and we take pride in being developers.

What our customers say

Dashbird gives us a simple and easy to use tool to have peace of mind and know that all of our Serverless functions are running correctly. We are instantly aware now if there’s a problem. We love the fact that we have enough information in the Slack notification itself to take appropriate action immediately and know exactly where the issue occurred.

Thanks to Dashbird the time to discover the occurrence of an issue reduced from 2-4 hours to a matter of seconds or minutes. It also means that hundreds of dollars are saved every month.

Great onboarding: it takes just a couple of minutes to connect an AWS account to an organization in Dashbird. The UI is clean and gives a good overview of what is happening with the Lambdas and API Gateways in the account.

I mean, it is just extremely time-saving. It’s so efficient! I don’t think it’s an exaggeration or dramatic to say that Dashbird has been a lifesaver for us.

Dashbird provides an easier interface to monitor and debug problems with our Lambdas. Relevant logs are simple to find and view. Dashbird’s support has been good, and they take product suggestions with grace.

Great UI. Easy to navigate through CloudWatch logs. Simple setup.

Dashbird helped us refine the size of our Lambdas, resulting in significantly reduced costs. We have Dashbird alert us in seconds via email when any of our functions behaves abnormally. Their app immediately makes the cause and severity of errors obvious.