DEV Community

Cover image for What the heck is Event and Topic?
Ezekiel Ilori
Ezekiel Ilori

Posted on • Updated on

What the heck is Event and Topic?

I am very new to blockchain development and I had a small project I needed to finish last week. Understanding these concepts was the biggest blocker I had as Web3js documentation wasn't helpful.

Writing a smart contract in Solidity uses a design pattern called Publish-Subscribe(PubSub). In simple terms, when a particular event happens, it is broadcasted publicly across the blockchain. Anyone interested in the information can carefully listen for a particular event broadcast and use the information for whatever they need it for.

Was that confusing? Let me tell you a very short story.

In a small village, whenever a new child is given birth into a Family, the town crier must announce this information across the whole village. The census organization records the new addition to the village. Other willing families in the village send their congratulations

Blockchain: The village
SmartContract: The Family,
Event: new child is given birth,
Publisher: The town crier
Subscribers: census organization, Other willing families

Now, lets read the story one more time

In an ethereum blockchain, when an event is emitted in the smart contract, the event is stored in transaction logs and logs are stored on the blockchain. Anyone who is interested in the event can search for past events recorded or subscribe to new ones.

This is also known as EventDriven PubSub Design

Now that you understand what EventDriven PubSub Design is, let's move forward to how this is implemented in solidity and how it helps us understand events and topics.

Using the above analogy, a family smart contract will look like this...

contract Family {
    // ... other family logic

  event NewChild(person indexed father, person indexed mother, person child);

    function giveBirth() public {
       //...give Bith Logic
       emit NewChild(father, mother, child);
    };
}

A NewChild event is declared and emitted using the emit keyword
Now anybody that wants can listen for NewChild event can subscribe to the particular event across the blockchain.

But How?

Using web3, this is a simple code snippet used to subscribe to all event in the blockchain logs


const subscription = web3.eth.subscribe(
  'logs',
  {
   address: '0x123456..',
   from: 0,
  },
  (error, result) => {
    if (error) return;
    // do something with the data
    console.log(result);    
   }
);

The above code literally listens to all events in the blockchain and that's a lot, tons of data we probably don't need. What we want is to only subscribe to a specific event which is NewChild

Web3 got us covered, web3.eth.subscribe's second argument is an object with the following keys: fromBlock, address, and topics. My focus will be on topics because I feel the other two are self-explanatory and I don't want to deviate too much from this article's subject.

So what the Hecks are Topics?

Topics were my worst nightmare because I couldn't find a documentation/article to explain it to me in a way a beginner like me would understand.

According to Web3 docs:

  • Topics - Array: An array of values which must each appear in the log entries. The order is important, if you want to leave topics out use null, e.g. [null, '0x00...']. You can also pass another array for each topic with options for that topic e.g. [null, ['option1', 'option2']]

Translation:

  • Topics are 32-byte (256 bit) β€œwords” that are used to describe what’s going on in an event.

  • Topics are what helps us filter the events in the logs. With the right topics, we can subscribe only to a particular event instead of all events.

Events emitted are translated to topics.

Example: Event Transfer emitted is equal to the topics below


contract SomeErc20Contract {
  // ...some smartcontract logic
  event Transfer(address indexed from, address indexed to, uint256 tokens);

  function transfer(address to, uint256 tokens) public returns (bool success) {
   // some subtraction and addition logic
   emit Transfer(
     '0x4dc8f417d4eb731d179a0f08b1feaf25216cefd0'
     '0xd2b2fb39b10cd50cab7aa8e834879069ab1a8d4',
     200
   );
  }
}
const topics = [ 
  '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
  '0x0000000000000000000000004dc8f417d4eb731d179a0f08b1feaf25216cefd0',
  '0x0000000000000000000000000d2b2fb39b10cd50cab7aa8e834879069ab1a8d4'
]

Let me explain how topics are generated from Event.

1. Event signature (topics[0]): The first element in the topics array is the hash of the event signature. The event hash is generated by stripping off keywords and arguments from the event declaration and then hashed using keccak256(or sha3).

Example:

  • Event declaration
 event Transfer(address indexed from, address indexed to, uint256 tokens);
  • Strip off keywords and arguments
 'Transfer(address,address,uint256)'
  • Hash it using keccak256(or sha3)
 keccak256('Transfer(address,address,uint256)');

and we get our first topic(hash of the event signature ) 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef


2. Other topics (topics[1 and above]): are the indexed arguments of the event. In our event declaration, only the first two arguments are indexed.

Does that mean 0x4dc8f417d4eb731d179a0f08b1feaf25216cefd0 and 0xd2b2fb39b10cd50cab7aa8e834879069ab1a8d4 are topic[1] and topic[2] respectively? No!

Why? An ethereum address is 20 byte which is 44 characters long but a topic is 32 byte which is 62 characters long.
To convert a 20 byte string to 32 byte, I wrote a line javascript statement that pads the 44 characters to 62 characters appropriately

  const my32byteString = `${web3.utils.padLeft('0x', 24)}${walletAddress.split('0x')[1]}`;

Hence, our topic[1] and topic[2] are
0x0000000000000000000000004dc8f417d4eb731d179a0f08b1feaf25216cefd0 and 0x0000000000000000000000000d2b2fb39b10cd50cab7aa8e834879069ab1a8d4 respectively.

One more Example

We have a smart contract for investment that emits Invested event whenever an investment has been made

contract Business {
    // ... other business logic

  event Invested(address indexed who, address indexed into, unit256 amount);

  function makeInvestment(address into, uint256 amount) public  {
     //...some investment logic
     emit Invested(msg.sender, into, amount);
  };
}



1. Generate the first event signature(topic[0]) of Invested event

Solution:

const eventSignature = web3.utils.sha3('Invested(address,address,uint256)');



2. Generate the topics of an Invested event made from/by 0x9d1a9d53bbccf2b2c1a4d3612dbe8b8b9bb4a558

Solution:

const fromAddress = '0x9d1a9d53bbccf2b2c1a4d3612dbe8b8b9bb4a558';
const eventSignature = web3.utils.sha3('Invested(address,address,uint256)');

const topics = [
  eventSignature,
  `${web3.utils.padLeft('0x', 24)}${fromAddress.split('0x')[1]}`,
];



3. Generate the topics of an Invested event made to 0xE701CD3329057AeA9D54300DdD05e41b8D74727A

Solution:

const toAddress = '0xE701CD3329057AeA9D54300DdD05e41b8D74727A';
const eventSignature = web3.utils.sha3('Invested(address,address,uint256)');

const topics = [
  eventSignature,
  null, // the order is important, this belongs to the first index argument and should be set to null if you want to leave it out
  `${web3.utils.padLeft('0x', 24)}${toAddress.split('0x')[1]}`,
];



4. Generate the topics of an Invested event made from/by 0x9d1a9d53bbccf2b2c1a4d3612dbe8b8b9bb4a558 to 0xE701CD3329057AeA9D54300DdD05e41b8D74727A

Solution:

const fromAddress = '0x9d1a9d53bbccf2b2c1a4d3612dbe8b8b9bb4a558';
const toAddress = '0xE701CD3329057AeA9D54300DdD05e41b8D74727A';
const eventSignature = web3.utils.sha3('Invested(address,address,uint256)');

const topics = [
  eventSignature,
  `${web3.utils.padLeft('0x', 24)}${fromAddress.split('0x'),
  `${web3.utils.padLeft('0x', 24)}${toAddress.split('0x')[1]}`,
];

Top comments (4)

Collapse
 
trutheonion profile image
truTheOnion

Oh my god! Thanks for existing to cover this topic my dude

Collapse
 
kevinmaarek profile image
Maarek

Hey could you explain the difference between eth.subscribe and myContract.getPastEvents ?

I know getPastEvents is to a 10,000 blocks range, is that also the case on subscribe ?
Thanks.

Collapse
 
coccoinomane profile image
Guido Walter Pettinari

Thank you so much!
I could not find a clearer explanation in all of Stack Exchange πŸ˜…

Collapse
 
congminh090800 profile image
congminh090800

Finally a greate answer, thank you man