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

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

Open
sbahman opened this issue Sep 2, 2023 · 4 comments · May be fixed by #884
Open

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

sbahman opened this issue Sep 2, 2023 · 4 comments · May be fixed by #884

Comments

@sbahman
Copy link

sbahman commented Sep 2, 2023

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

@kliyer-ai
Copy link

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.

@sbahman
Copy link
Author

sbahman commented Sep 7, 2023

@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?

@pocin
Copy link

pocin commented Oct 3, 2023

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 pocin linked a pull request Nov 2, 2023 that will close this issue
@sowedoO
Copy link

sowedoO commented Apr 9, 2024

this is still not fixed, right?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants