TypeChain icon indicating copy to clipboard operation
TypeChain copied to clipboard

ethers-v6: wrong type generated for listening to contract events

Open sbahman opened this issue 1 year ago • 5 comments

I've generated the types for an ERC20 contract with ethers-v6. I would like to listen to the transfer events, to that end I call

token.on(token.filters.Transfer(), async (...args) => {
    const lastArg = args[args.length - 1];
    console.log(lastArg)
    // do something with it
}

The generated types claim lastArg to be of type:

lastArg: TypedEventLog<TypedContractEvent<TransferEvent.InputTuple, TransferEvent.OutputTuple, TransferEvent.OutputObject>>  | undefined | string | bigint

However, the console.log gives me the following output:

ContractEventPayload {
  filter: PreparedTopicFilter {
    fragment: EventFragment {
      type: 'event',
      inputs: [Array],
      name: 'Transfer',
      anonymous: false
    }
  },
  emitter: Contract {
    target: '0x...',
    interface: Interface {
      fragments: [Array],
      deploy: [ConstructorFragment],
      fallback: null,
      receive: false
    },
    runner: Wallet {
      provider: [InfuraProvider],
      address: '0x...'
    },
    filters: {},
    fallback: null,
    [Symbol(_ethersInternal_contract)]: {}
  },
  log: EventLog {
    provider: InfuraProvider {
      projectId: '...',
      projectSecret: ...
    },
    transactionHash: '0x...',
    blockHash: '0x...',
    blockNumber: ...,
    removed: false,
    address: '0x...',
    data: '0x...,
    topics: [
      '0x...',
      '0x...',
      '0x...'
    ],
    index: 12,
    transactionIndex: 6,
    interface: Interface {
      fragments: [Array],
      deploy: [ConstructorFragment],
      fallback: null,
      receive: false
    },
    fragment: EventFragment {
      type: 'event',
      inputs: [Array],
      name: 'Transfer',
      anonymous: false
    },
    args: Result(3) [
      '0x...',
      '0x...',
      10000n
    ]
  },
  args: Result(3) [
    '0x...',
    '0x...',
    10000n
  ],
  fragment: EventFragment {
    type: 'event',
    inputs: [ [ParamType], [ParamType], [ParamType] ],
    name: 'Transfer',
    anonymous: false
  }
}

The TypedEventLog<... object is actually found under lastArg.log instead of lastArg itself being of type TypedEventLog<...

Checking the ethers documentation under "Listening to Events" it says that

There is always one additional parameter passed to a listener, which is an EventPayload, which includes more information about the event including the filter and a method to remove that listener.

Which seems to line up with the behaviour I am observing

sbahman avatar Sep 02 '23 10:09 sbahman

Yes, I can confirm that the types are incorrect. The ...args array actually just contains a single element, which is the one you have logged.

kliyer-ai avatar Sep 07 '23 08:09 kliyer-ai

@kliyer-ai Why are the types incorrect though? Is it a bug in typechain? Or did I make some sort of mistake in using it?

sbahman avatar Sep 07 '23 11:09 sbahman

I think this is a bug in typechain.

When i was constructing my listeners without typechain using

  const VAULT_EVENTS = [
    'event Deposit(address account, uint256 amount, uint256 amountStaked)',
    'event Withdraw(address account, uint256 amount)',
  ];

  console.log(`Setting up ${name}: ${address} listener`);
  const contract = new Contract(address, VAULT_EVENTS, provider);
  contract.on(
    'Deposit',
    async (
      account: string,
      amount: number,
      amountStaked: number,
      event: ContractEventPayload
    ) => {
      const template = `**${name}**: \`Deposit\` ${formatUnits(
        amount,
        18
      )} $FOO into \`${account}\` for (${formatUnits(
        amountStaked,
        18
      )} total`;
      await notify({
        text: template,
        txid: event.log.transactionHash,
      });
    }
  );

The listener last arg is ethers.ContractEventPayload so i can access event.log.transactionHash.

When using generated Contract__factory().on(contract.filters.Deposit, ...) I can confirm that

  • (account, amount, amountStaked) is parsed correctly
  • the last arg event is (incorrectly) typed as you describe, so when you access event.transactionHash it is null.

Not sure how exactly to fix, but imho the generated typechain/common.ts

export type TypedListener<TCEvent extends TypedContractEvent> = (
  ...listenerArg: [
    ...__TypechainAOutputTuple<TCEvent>,
    TypedEventLog<TCEvent>
    ...undefined[]
  ]
) => void;

should just type the last arg as

import {  ContractEventPayload } from 'ethers';

export type TypedListener<TCEvent extends TypedContractEvent> = (
  ...listenerArg: [
    ...__TypechainAOutputTuple<TCEvent>,
    ContractEventPayload, // this
    ...undefined[]
  ]
) => void;

pocin avatar Oct 03 '23 09:10 pocin

this is still not fixed, right?

sowedoO avatar Apr 09 '24 09:04 sowedoO

Please, can the PR that fix this issue be merged?

amilcarrey avatar May 22 '24 22:05 amilcarrey