Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How do I parse event logs? #487

Closed
dblockunity opened this issue Apr 10, 2019 · 16 comments
Closed

How do I parse event logs? #487

dblockunity opened this issue Apr 10, 2019 · 16 comments
Labels
discussion Questions, feedback and general information.

Comments

@dblockunity
Copy link

dblockunity commented Apr 10, 2019

I have a smart contract that emits events whenever the following line of code in the contract is called:

emit Transfer(from, to, value);

The script that is used to list these events is shown below:

var ethers = require('ethers');

var provider = new ethers.providers.InfuraProvider('ropsten');
const metacoinArtifacts = require('../build/contracts/MyToken.json');
const address = metacoinArtifacts.networks[3].address;

var filter = {
    address: address,
    fromBlock: 0
};
var callPromise = provider.getLogs(filter);
callPromise.then(function(events) {
    console.log("Printing array of events:");
    console.log(events);
}).catch(function(err){
    console.log(err);
});

The output is the following:

[ { blockNumber: 5196629,
    blockHash: '0x19b083ee0752347f0bba9cd1cf19c827fd108c0f5d4973e33719d97a60cf14ca',
    transactionIndex: 1,
    removed: false,
    address: '0xF92298d72afE68300EA065c0EdaDbb1A29804faa',
    data: '0x00000000000000000000000000000000000000000000021e19e0c9bab2400000',
    topics: 
     [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
       '0x0000000000000000000000000000000000000000000000000000000000000000',
       '0x000000000000000000000000f58e01ac4134468f9ad846034fb9247c6c131d8c' ],
    transactionHash: '0xdad8bc380240548aafecba06bf6cee898513da850bb37859cfea5f93d8dd25da',
    logIndex: 0 },
  { blockNumber: 5301391,
    blockHash: '0x00b7bfeade71c17fd99bcc7c9b684cedad05fede5201e0dbc64614e14b07b07c',
    transactionIndex: 23,
    removed: false,
    address: '0xF92298d72afE68300EA065c0EdaDbb1A29804faa',
    data: '0x00000000000000000000000000000000000000000000000000038d7ea4c68000',
    topics: 
     [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
       '0x000000000000000000000000f58e01ac4134468f9ad846034fb9247c6c131d8c',
       '0x0000000000000000000000002d92bce8a6cf0fd32cffcabed026a6cb34ee0e9b' ],
    transactionHash: '0xf4f973b90fea5b32bf09b55166ee106b81cffd9d1ca8dd4752541d927f12dc18',
    logIndex: 33 },
  { blockNumber: 5301417,
    blockHash: '0x474fd08cad1d1ea870b8ed60d39505afd8b6e6be40f72c61581602056d7c8d1f',
    transactionIndex: 0,
    removed: false,
    address: '0xF92298d72afE68300EA065c0EdaDbb1A29804faa',
    data: '0x00000000000000000000000000000000000000000000000000005af3107a4000',
    topics: 
     [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
       '0x000000000000000000000000f58e01ac4134468f9ad846034fb9247c6c131d8c',
       '0x0000000000000000000000002d92bce8a6cf0fd32cffcabed026a6cb34ee0e9b' ],
    transactionHash: '0x8fa45694dc0c06b8b447f56221551cdec7987414b127d215d27e992f745d30a8',
    logIndex: 0 },
  { blockNumber: 5321681,
    blockHash: '0x600fad7a526ac89210378b63df96c09b45238a526fc10df42bfd34643d6b3cfa',
    transactionIndex: 24,
    removed: false,
    address: '0xF92298d72afE68300EA065c0EdaDbb1A29804faa',
    data: '0x000000000000000000000000000000000000000000000000000000000000000a',
    topics: 
     [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
       '0x000000000000000000000000f58e01ac4134468f9ad846034fb9247c6c131d8c',
       '0x00000000000000000000000015c72944b325a3e1c7a4dbdc6f883bd5948d3d9f' ],
    transactionHash: '0x3518b448d9091b3afa3822c4f7ed2216879bba875078b7cc3499142eb50be828',
    logIndex: 22 },
  { blockNumber: 5326453,
    blockHash: '0x2d709809aa31acc2e047f5d9f2c87aaff958c823725aa3ba7e5cbf5ff5bd7f7a',
    transactionIndex: 33,
    removed: false,
    address: '0xF92298d72afE68300EA065c0EdaDbb1A29804faa',
    data: '0x000000000000000000000000000000000000000000000000000000000000000a',
    topics: 
     [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
       '0x000000000000000000000000f58e01ac4134468f9ad846034fb9247c6c131d8c',
       '0x00000000000000000000000015c72944b325a3e1c7a4dbdc6f883bd5948d3d9f' ],
    transactionHash: '0x0e594a0cfc3bd89df75e9e178f1ef63441e950b621d145fdc063123af595a2f5',
    logIndex: 66 },
  { blockNumber: 5326455,
    blockHash: '0x85d1bb0651b8064e367b3958133712774d205039cfc8d25a9234e875e48c9615',
    transactionIndex: 16,
    removed: false,
    address: '0xF92298d72afE68300EA065c0EdaDbb1A29804faa',
    data: '0x000000000000000000000000000000000000000000000000000000000000000a',
    topics: 
     [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
       '0x000000000000000000000000f58e01ac4134468f9ad846034fb9247c6c131d8c',
       '0x00000000000000000000000015c72944b325a3e1c7a4dbdc6f883bd5948d3d9f' ],
    transactionHash: '0xf8f2affa3ea09c609dba11b2da4ec0946ae72f269887dd239729c5951f661b21',
    logIndex: 38 },
  { blockNumber: 5326525,
    blockHash: '0xa0b85af06cbed82cfdea7658c21983502c01679efa79142d34470eb58c3ba9a2',
    transactionIndex: 43,
    removed: false,
    address: '0xF92298d72afE68300EA065c0EdaDbb1A29804faa',
    data: '0x000000000000000000000000000000000000000000000000000000000000000a',
    topics: 
     [ '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
       '0x000000000000000000000000f58e01ac4134468f9ad846034fb9247c6c131d8c',
       '0x00000000000000000000000015c72944b325a3e1c7a4dbdc6f883bd5948d3d9f' ],
    transactionHash: '0xc6a8fa37107ed2bd3d9c07be0c86ff8a83002ab590d296cc3af303f09f68b368',
    logIndex: 122 } ]

What I am wondering is how do I decode the output of this script to get from, to, value from the event that was originally emitted?

@ricmoo
Copy link
Member

ricmoo commented Apr 10, 2019

The easiest way to do this, if you are not using the Contract API, is to use the Interface object API:

// You can also pull in your JSON ABI; I'm not sure of the structure inside artifacts
let abi = [ "event Transfer(address indexed from, address indexed to, uint value)" ];
let iface = new ethers.utils.Interface(abi);

var logPromise = provider.getLogs(filter);
logPromise.then(function(logs) {
    console.log("Printing array of events:");
    let events = logs.map((log) => iface.parseLog(log))
    console.log(events);
}).catch(function(err){
    console.log(err);
});

Which will output (given your logs):

[ _LogDescription {
    decode: [Function],
    name: 'Transfer',
    signature: 'Transfer(address,address,uint256)',
    topic:
     '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
    values: {
       '0': '0x0000000000000000000000000000000000000000',
       '1': '0xF58E01Ac4134468F9Ad846034fb9247c6C131d8C',
       '2': [BigNumber],
       from: '0x0000000000000000000000000000000000000000',
       to: '0xF58E01Ac4134468F9Ad846034fb9247c6C131d8C',
       value: [BigNumber],
       length: 3 } },
  _LogDescription {
    decode: [Function],
    name: 'Transfer',
    signature: 'Transfer(address,address,uint256)',
    topic:
     '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
    values:
     Result {
       '0': '0xF58E01Ac4134468F9Ad846034fb9247c6C131d8C',
       '1': '0x2D92bce8a6cf0FD32CFfCABed026a6cb34ee0e9b',
       '2': [BigNumber],
       from: '0xF58E01Ac4134468F9Ad846034fb9247c6C131d8C',
       to: '0x2D92bce8a6cf0FD32CFfCABed026a6cb34ee0e9b',
       value: [BigNumber],
       length: 3 } },
  _LogDescription {
    decode: [Function],
    name: 'Transfer',
    signature: 'Transfer(address,address,uint256)',
    topic:
     '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
    values: {
       '0': '0xF58E01Ac4134468F9Ad846034fb9247c6C131d8C',
       '1': '0x2D92bce8a6cf0FD32CFfCABed026a6cb34ee0e9b',
       '2': [BigNumber],
       from: '0xF58E01Ac4134468F9Ad846034fb9247c6C131d8C',
       to: '0x2D92bce8a6cf0FD32CFfCABed026a6cb34ee0e9b',
       value: [BigNumber],
       length: 3 } },
  _LogDescription {
    decode: [Function],
    name: 'Transfer',
    signature: 'Transfer(address,address,uint256)',
    topic:
     '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
    values: {
       '0': '0xF58E01Ac4134468F9Ad846034fb9247c6C131d8C',
       '1': '0x15c72944b325a3E1c7a4DBdc6F883bD5948d3D9f',
       '2': [BigNumber],
       from: '0xF58E01Ac4134468F9Ad846034fb9247c6C131d8C',
       to: '0x15c72944b325a3E1c7a4DBdc6F883bD5948d3D9f',
       value: [BigNumber],
       length: 3 } },
  _LogDescription {
    decode: [Function],
    name: 'Transfer',
    signature: 'Transfer(address,address,uint256)',
    topic:
     '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
    values: {
       '0': '0xF58E01Ac4134468F9Ad846034fb9247c6C131d8C',
       '1': '0x15c72944b325a3E1c7a4DBdc6F883bD5948d3D9f',
       '2': [BigNumber],
       from: '0xF58E01Ac4134468F9Ad846034fb9247c6C131d8C',
       to: '0x15c72944b325a3E1c7a4DBdc6F883bD5948d3D9f',
       value: [BigNumber],
       length: 3 } },
  _LogDescription {
    decode: [Function],
    name: 'Transfer',
    signature: 'Transfer(address,address,uint256)',
    topic:
     '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
    values: {
       '0': '0xF58E01Ac4134468F9Ad846034fb9247c6C131d8C',
       '1': '0x15c72944b325a3E1c7a4DBdc6F883bD5948d3D9f',
       '2': [BigNumber],
       from: '0xF58E01Ac4134468F9Ad846034fb9247c6C131d8C',
       to: '0x15c72944b325a3E1c7a4DBdc6F883bD5948d3D9f',
       value: [BigNumber],
       length: 3 } },
  _LogDescription {
    decode: [Function],
    name: 'Transfer',
    signature: 'Transfer(address,address,uint256)',
    topic:
     '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
    values: {
       '0': '0xF58E01Ac4134468F9Ad846034fb9247c6C131d8C',
       '1': '0x15c72944b325a3E1c7a4DBdc6F883bD5948d3D9f',
       '2': [BigNumber],
       from: '0xF58E01Ac4134468F9Ad846034fb9247c6C131d8C',
       to: '0x15c72944b325a3E1c7a4DBdc6F883bD5948d3D9f',
       value: [BigNumber],
       length: 3 } } ]

Note the values inside each event object are what you would be interested in.

@ricmoo ricmoo added the discussion Questions, feedback and general information. label Apr 10, 2019
@ricmoo ricmoo changed the title How do I get precise information from emitted events? How do I parse event logs? Apr 10, 2019
@dblockunity
Copy link
Author

I think we are close. I now get the following output:

Printing array of events:
[ undefined,
  undefined,
  undefined,
  undefined,
  undefined,
  undefined,
  undefined ]

Note that this output is produced based on the changes that were made to create the following script:

var ethers = require('ethers');

var provider = new ethers.providers.InfuraProvider('ropsten');
const metacoinArtifacts = require('../build/contracts/MyToken.json');
const address = metacoinArtifacts.networks[3].address;

var abi = [ "event Transfer(address indexed from, address indexed to, uint256 value)" ];
var iface = new ethers.utils.Interface(abi);

var filter = {
    address: address,
    fromBlock: 0
};
var callPromise = provider.getLogs(filter);
callPromise.then(function(events) {
    console.log("Printing array of events:");
    var parsedEvents = events.map(function(log) {iface.parseLog(log)});
    console.log(parsedEvents);
}).catch(function(err){
    console.log(err);
});

What could I be doing wrong here?

@ricmoo
Copy link
Member

ricmoo commented Apr 11, 2019

You forgot the return in the callback to map. :)

(return iface.parseLog(log))

@bohendo
Copy link

bohendo commented Apr 18, 2019

Is there any good way to convert a Log into an Event?

This is my current attempt to fetch Events from the last n blocks:

const getPastEvents = (eventName, fromBlock, args) => {
  const filter = myContract.filters[eventName](...args)
  filter.fromBlock = fromBlock
  filter.toBlock = "latest"

  const logs = await this.provider.getLogs(filter)

  const events = []
  for (let i in logs) {
    events.push(myAbi.parseLog(logs[i]))
  }

  return events
}

But at the end I'm getting a set of LogDescription which is missing the helpful transaction-accessing methods. Ultimately, the args (aka values) is what I need so not a deal breaker but would be nice to have.

@ricmoo
Copy link
Member

ricmoo commented Apr 18, 2019

Oh, you'd like the getBlock(), getTransaction() and getTransactionReceipt() operations on the object returned from parseLog? I can add that easy enough.

I may only add it to the v5 branch though, which should be available for public beta later today. :)

@ricmoo
Copy link
Member

ricmoo commented May 24, 2019

These have been added to v5. If you try it out, let me know how they work for you.

Please feel free to re-open or continue discussion (I monitor closed issues) if you have any problems.

Thanks! :)

@ricmoo ricmoo closed this as completed May 24, 2019
@ricmoo
Copy link
Member

ricmoo commented Jun 12, 2019

Oh, that’s just what console.log does. If you want more context logged, you can use console.dir(result, { depth: null }). :)

@maraoz
Copy link

maraoz commented Sep 15, 2021

In case anyone's looking for how to extract an event from a specific transaction, here's the code I managed to get working based on the suggestions on this thread.

const receipt = await ethers.provider.getTransactionReceipt("0x0c8300a14a08fffe209dfe5961b3027b1321428184365751b89c4ac6056c28e4");
let abi = [ "event Donation(address donor, uint256 value, uint256 tokenID)" ];
let iface = new ethers.utils.Interface(abi);
let log = iface.parseLog(receipt.logs[1]); // here you can add your own logic to find the correct log
const {donor, value, tokenID} = log.args;

@kimborgen
Copy link

kimborgen commented Mar 31, 2022

Somehow, my results from parseLog was split in an args array without name, only numerical index and value, and in eventFragement.inputs with name, but without value.

Context: This is with the Contract API, reading historical logs.

const parseEtherjsLog = (parsed) => {
    let parsedEvent = {}
    for (let i = 0; i < parsed.args.length; i++) {
        const input = parsed.eventFragment.inputs[i]
        const arg = parsed.args[i]
        const newObj = {...input, ...{"value": arg}}
        parsedEvent[input["name"]] = newObj
    }
    return parsedEvent
}

export const getEthersLog = async (contract, filter) => {
    if (contract === undefined || filter === undefined ) return
    const events = await contract.queryFilter(filter)
    if (events.length === 0) return
    let parsedEvents = []
    for (let event of events) {
        const ethersParsed = contract.interface.parseLog(event)
        const customParsed = parseEtherjsLog(ethersParsed)
        parsedEvents.push(customParsed)
    }
    return parsedEvents
}

export default getEthersLog;

That I call like this

    const flt = contract.filters.EventName() 
    const log = await getEthersLog(contract, flt)   

Hope this is useful for those who come from google :)

@zemse
Copy link
Collaborator

zemse commented Apr 1, 2022

Somehow, my results from parseLog was split in an args array without name, only numerical index and value, and in eventFragement.inputs with name, but without value.

@kimborgen That should not happen, the Result object from contract.queryFilter (event.args) should contain param names as well if they exist in ABI, you don't need to parseLog. Can you ensure you're on latest ethers and try it once again?

const filter = contract.filters.EventName() 
const events = await contract.queryFilter(filter)
const {param1, param2} = events[0].args;

@bholagabbar
Copy link

bholagabbar commented Apr 19, 2022

In case anyone's looking for how to extract an event from a specific transaction, here's the code I managed to get working based on the suggestions on this thread.

const receipt = await ethers.provider.getTransactionReceipt("0x0c8300a14a08fffe209dfe5961b3027b1321428184365751b89c4ac6056c28e4");
let abi = [ "event Donation(address donor, uint256 value, uint256 tokenID)" ];
let iface = new ethers.utils.Interface(abi);
let log = iface.parseLog(receipt.logs[1]); // here you can add your own logic to find the correct log
const {donor, value, tokenID} = log.args;

If you're using the contracts API, you can also use the contract's interface itself.

        let contract = new ethers.Contract(address, abi, provider);
        let events = await contract.queryFilter(...);
        let event = events[0];

        const txnReceipt = await event.getTransactionReceipt();
        let eventLog = txnReceipt.logs[1] // could be any index
        let log = contract.interface.parseLog(eventLog); // Use the contracts interface

@cauta
Copy link

cauta commented Jul 2, 2023

I found this simple solution:

const transaction = await contract.transfer(to, value);
const receipt = await transaction.wait(1);
let log = receipt?.logs.find((log) => credsLifeContract.interface.parseLog(log as any)?.name === 'Transfer') as EventLog;
console.log(log);

@kfazil
Copy link

kfazil commented Sep 16, 2023

Somehow, my results from parseLog was split in an args array without name, only numerical index and value, and in eventFragement.inputs with name, but without value.

@kimborgen That should not happen, the Result object from contract.queryFilter (event.args) should contain param names as well if they exist in ABI, you don't need to parseLog. Can you ensure you're on latest ethers and try it once again?

const filter = contract.filters.EventName() 
const events = await contract.queryFilter(filter)
const {param1, param2} = events[0].args;

=> In ethers version - 6.7.1
queryFilter returns Log | EventLog type of object and 'args' property does not exist on type Log or EventLog type of object.

@ricmoo
Copy link
Member

ricmoo commented Sep 16, 2023

@kfazil The EventLog has an args property: https://docs.ethers.org/v6/api/contract/#EventLog

@iamjakkie
Copy link

I got the same problem with 'args' property which I'm trying to get:

EventLog {
  provider: JsonRpcProvider {},
  transactionHash: '0x1f39cb3770efd21d3753ce05351e09aeb5968d7ec0c3d408d486450fe7f726d1',
  blockHash: '0x8aac8d8ae7aecb4c61dd52a6340a702d86d11724f1e24d95d0d791eed6aa59b4',
  blockNumber: 16609101,
  removed: false,
  address: '0xcEBc7b5fA2540e16B40a2A4364E78CEcefB7F368',
  data: '0x00000000000000000000000000000000000000000000000000470de4df82000000000000000000000000000000000000000000000000005b6d54b689114c76b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000019954c0db8e242c32e676',
  topics: [
    '0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822',
    '0x0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d',
    '0x000000000000000000000000accfa8514f7d9d4d9b6a307d17de2a12b8fbac63'
  ],
  index: 136,
  transactionIndex: 15,
  interface: Interface {
    fragments: [ [EventFragment], [EventFragment], [EventFragment] ],
    deploy: ConstructorFragment {
      type: 'constructor',
      inputs: [],
      payable: false,
      gas: null
    },
    fallback: null,
    receive: false
  },
  fragment: EventFragment {
    type: 'event',
    inputs: [
      [ParamType],
      [ParamType],
      [ParamType],
      [ParamType],
      [ParamType],
      [ParamType]
    ],
    name: 'Swap',
    anonymous: false
  },
  args: Result(6) [
    '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
    20000000000000000n,
    1686531833055564035760n,
    0n,
    1933011314853237688034934n,
    '0xaccfa8514F7D9d4d9B6A307D17DE2a12b8fbac63'
  ]
}

error:
Property 'args' does not exist on type 'Log | EventLog'.
Property 'args' does not exist on type 'Log'.

code:

let swaps = await pairContract.queryFilter(filter_swap, startingBlock, startingBlock + 10000);
console.log(swaps[0].args);

ethers@6.7.1

@ricmoo
Copy link
Member

ricmoo commented Sep 16, 2023

Yes, the type args doesn’t exist on Log | EventLog, but it does exist on EventLog. Because it doesn’t exist on Log, it doesn’t exist on the union of them.

It’s a Typing issue. You can use if (“args” in log) { console.log(log.args); }. If you and find with casts and checking nullish, you can also use console.log((<EventLog>log).args). I will add a EventLog.isLog type guard in the next minor bump too. :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Questions, feedback and general information.
Projects
None yet
Development

No branches or pull requests

11 participants