Guide 4: How to get the logs of a contract event using the Alchemy SDK

Use the Alchemy SDK to get all the event logs that happened on a contract, per block.

The SDK Developer Challenge: What are we here for?

The SDK Developer Challenge is an opportunity to build and learn together, using daily guides to inspire and motivate you to supercharge your app with the SDK.

The SDK will be your secret weapon to help simplify your code so you can build faster.

As you build, submit your project to win prizes.

What could you win? Epic never-before seen Alchemy swag, and $500 in monthly Alchemy credit.

For all of the challenges, you will need to set up a free Alchemy account. This will give you access to the SDK, as well as a HUGE selection of web3 development tools.

Ok, let's go!

Guide 4: How to get logs of a contract event using the Alchemy SDK

In this guide, we will assume that you already know how to:

  • Set up an Alchemy SDK instance in your local project and initialize it with your Alchemy API Key
  • Perform basic navigation of the command line
  • Make an API call to Alchemy
  • Show the results in your terminal

If you don't, head on back to Guide 2, How to get started on the Alchemy SDK.

Let's start!

To bring this tutorial to life, we will take a real life example: How to get all Nouns DAO proposals; this is just one instance of fetching the logs of a contract event using the Alchemy SDK.

1200

NounsDAO auctions one Noun, everyday!

We will use the mainnet address and contract ABI of the Nouns DAO smart contract. This smart contract is built using the OpenZeppelin Governor standard.

In the script below, we will show you how to retrieve all the proposals ever created on the Nouns DAO Governor smart contract. That's awesome!

Using the Alchemy SDK, we make an API call to eth_getLogs. This call will respond with every ProposalCreated event emitted by the Nouns DAO smart contract. Let's go!

Step 1: Import Code Snippet into Local Project

Assuming you have a local environment set up and an Alchemy API key ready, copy-paste the code snippet below:

require('dotenv').config();
const { Alchemy, Network, Utils } = require('alchemy-sdk');

// Optional Config object, but defaults to demo api-key and eth-mainnet.
const settings = {
  apiKey: process.env.ALCHEMY_API_KEY,
  network: Network.ETH_MAINNET,
};

const alchemy = new Alchemy(settings);

const NOUNS_DAO_CONTRACT_ADDRESS = '0x6f3E6272A167e8AcCb32072d08E0957F9c79223d';
const nounsDaoAbiString = `[{"inputs":[{"internalType":"address","name":"timelock_","type":"address"},{"internalType":"address","name":"nouns_","type":"address"},{"internalType":"address","name":"vetoer_","type":"address"},{"internalType":"address","name":"admin_","type":"address"},{"internalType":"address","name":"implementation_","type":"address"},{"internalType":"uint256","name":"votingPeriod_","type":"uint256"},{"internalType":"uint256","name":"votingDelay_","type":"uint256"},{"internalType":"uint256","name":"proposalThresholdBPS_","type":"uint256"},{"internalType":"uint256","name":"quorumVotesBPS_","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"NewAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldImplementation","type":"address"},{"indexed":false,"internalType":"address","name":"newImplementation","type":"address"}],"name":"NewImplementation","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldPendingAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newPendingAdmin","type":"address"}],"name":"NewPendingAdmin","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"oldVetoer","type":"address"},{"indexed":false,"internalType":"address","name":"newVetoer","type":"address"}],"name":"NewVetoer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"}],"name":"ProposalCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"address","name":"proposer","type":"address"},{"indexed":false,"internalType":"address[]","name":"targets","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"values","type":"uint256[]"},{"indexed":false,"internalType":"string[]","name":"signatures","type":"string[]"},{"indexed":false,"internalType":"bytes[]","name":"calldatas","type":"bytes[]"},{"indexed":false,"internalType":"uint256","name":"startBlock","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"endBlock","type":"uint256"},{"indexed":false,"internalType":"string","name":"description","type":"string"}],"name":"ProposalCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"address","name":"proposer","type":"address"},{"indexed":false,"internalType":"address[]","name":"targets","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"values","type":"uint256[]"},{"indexed":false,"internalType":"string[]","name":"signatures","type":"string[]"},{"indexed":false,"internalType":"bytes[]","name":"calldatas","type":"bytes[]"},{"indexed":false,"internalType":"uint256","name":"startBlock","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"endBlock","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"proposalThreshold","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"quorumVotes","type":"uint256"},{"indexed":false,"internalType":"string","name":"description","type":"string"}],"name":"ProposalCreatedWithRequirements","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"}],"name":"ProposalExecuted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"eta","type":"uint256"}],"name":"ProposalQueued","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldProposalThresholdBPS","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newProposalThresholdBPS","type":"uint256"}],"name":"ProposalThresholdBPSSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"id","type":"uint256"}],"name":"ProposalVetoed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldQuorumVotesBPS","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newQuorumVotesBPS","type":"uint256"}],"name":"QuorumVotesBPSSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"voter","type":"address"},{"indexed":false,"internalType":"uint256","name":"proposalId","type":"uint256"},{"indexed":false,"internalType":"uint8","name":"support","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"votes","type":"uint256"},{"indexed":false,"internalType":"string","name":"reason","type":"string"}],"name":"VoteCast","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldVotingDelay","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newVotingDelay","type":"uint256"}],"name":"VotingDelaySet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldVotingPeriod","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newVotingPeriod","type":"uint256"}],"name":"VotingPeriodSet","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[{"internalType":"address","name":"implementation_","type":"address"}],"name":"_setImplementation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingAdmin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]`;

const NOUNS_DAO_INTERFACE = new Utils.Interface(nounsDaoAbiString);
const NOUNS_DAO_PROPOSAL_CREATED_TOPICS =
  NOUNS_DAO_INTERFACE.encodeFilterTopics('ProposalCreated', []);

async function getAllNounsDaoProposalsEver() {
  const logs = await alchemy.core.getLogs({
    fromBlock: '0x0',
    toBlock: 'latest',
    address: NOUNS_DAO_CONTRACT_ADDRESS,
    topics: NOUNS_DAO_PROPOSAL_CREATED_TOPICS,
  });
  console.log(logs);
}

getAllNounsDaoProposalsEver();

This code snippet does a couple of things:

  1. Uses dotenv package to import and use Alchemy API Key: (import is on line 1 and variable is used by accessing via process.env on line 6)
  2. Initializes a settings variable with your Alchemy API key and sets the network to ETH mainnet using Network (line 5)
  3. Declares all the needed variables for the getLogs API call to work:
  • Nouns DAO mainnet address: 0x6f3E6272A167e8AcCb32072d08E0957F9c79223d on line 12
  • Governor contract ABI (this was simply fetched from Etherscan!) on line 13
  • The desired event you want to get logs of on Line 16 which is ProposalCreated ⬇️

Step 2: Run Your Script!

Once you have everything set up, simply run node getAllNounsDaoProposalsEver.js and you will see your terminal instantly populate with all the raw data of every proposal ever created on the Nouns DAO smart contract!

Want a cleaner view of what this query's response looks like once it's better packaged into a UI? Check out the Nouns DAO proposals page!

Why Is This Useful? 🤔

Technically, the Alchemy SDK is the ONLY way to fetch historical event logs, of any kind, via JSON-RPC. Otherwise, if you ever needed to look at who created a proposal, you'd need to brute-force search the entire blockchain and specifically look for the ProposalCreated event in every single transaction ever. The Alchemy SDK helps you avoid that!

Step 3: Customize Your Script!

The script you just set up via the Alchemy SDK is extremely powerful! The Nouns DAO ProposalCreated is just an example! You can use the Alchemy SDK to get the event logs of any event, including custom events. This is very useful not just for dApp owners, but for dApp indexers like Etherscan that require this data to power their front-ends.

Go ahead and try to carry an event in another contract! You can use all the steps above, but you'll need to change the variable names, import a new address and ABI and change the event topic name you want to query. You got this!