Skip to content
5 changes: 5 additions & 0 deletions .changeset/good-starfishes-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@typechain/ethers-v5': minor
---

Export typed events
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@ interface ERC20Interface extends ethers.utils.Interface {
getEvent(nameOrSignatureOrTopic: "Transfer"): EventFragment;
}

export type ApprovalEvent = TypedEvent<
[string, string, BigNumber] & {
owner: string;
spender: string;
value: BigNumber;
}
>;

export type TransferEvent = TypedEvent<
[string, string, BigNumber] & { from: string; to: string; value: BigNumber }
>;

export class ERC20 extends BaseContract {
connect(signerOrProvider: Signer | Provider | string): this;
attach(addressOrName: string): this;
Expand Down Expand Up @@ -289,6 +301,15 @@ export class ERC20 extends BaseContract {
};

filters: {
"Approval(address,address,uint256)"(
owner?: string | null,
spender?: string | null,
value?: null
): TypedEventFilter<
[string, string, BigNumber],
{ owner: string; spender: string; value: BigNumber }
>;

Approval(
owner?: string | null,
spender?: string | null,
Expand All @@ -298,6 +319,15 @@ export class ERC20 extends BaseContract {
{ owner: string; spender: string; value: BigNumber }
>;

"Transfer(address,address,uint256)"(
from?: string | null,
to?: string | null,
value?: null
): TypedEventFilter<
[string, string, BigNumber],
{ from: string; to: string; value: BigNumber }
>;

Transfer(
from?: string | null,
to?: string | null,
Expand Down
38 changes: 37 additions & 1 deletion packages/target-ethers-v5-test/test/Events.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { BigNumber, ethers } from 'ethers'
import { typedAssert } from 'test-utils'

import { Events } from '../types/Events'
import { Event1Event, Events } from '../types/Events'
import { createNewBlockchain, deployContract } from './common'

describe('Events', () => {
Expand Down Expand Up @@ -84,4 +84,40 @@ describe('Events', () => {
await contract.emit_event1()
await new Promise((r) => setTimeout(r, 1000))
})

it('typed event import', async () => {
const filter = contract.filters.Event1(null, null)
const results = (await contract.queryFilter(filter)) as any

const results2 = results as Event1Event[]
results2.map((r) => {
typedAssert(r.args.value1, BigNumber.from(1))
typedAssert(r.args.value2, BigNumber.from(2))
typedAssert(r.args[0], BigNumber.from(1))
typedAssert(r.args[1], BigNumber.from(2))
})
})

it('queryFilter overloaded event', async () => {
await contract.emit_event3_overloaded()

{
const filterA = contract.filters['Event3(bool,uint256)']()
const results = await contract.queryFilter(filterA)
results.map((r) => {
typedAssert(r.args.value1, true)
typedAssert(r.args.value2, BigNumber.from(2))
typedAssert(r.args[0], true)
typedAssert(r.args[1], BigNumber.from(2))
})
}
{
const filterB = contract.filters['Event3(uint256)']()
const results = await contract.queryFilter(filterB)
results.map((r) => {
typedAssert(r.args.value1, BigNumber.from(1))
typedAssert(r.args[0], BigNumber.from(1))
})
}
})
})
55 changes: 54 additions & 1 deletion packages/target-ethers-v5-test/types/Events.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,30 @@ interface EventsInterface extends ethers.utils.Interface {
getEvent(nameOrSignatureOrTopic: "NoArgsEvent"): EventFragment;
}

export type AnonEvent1Event = TypedEvent<[BigNumber] & { value1: BigNumber }>;

export type Event1Event = TypedEvent<
[BigNumber, BigNumber] & { value1: BigNumber; value2: BigNumber }
>;

export type Event2Event = TypedEvent<[BigNumber] & { arg0: BigNumber }>;

export type Event3_bool_uint256_Event = TypedEvent<
[boolean, BigNumber] & { value1: boolean; value2: BigNumber }
>;

export type Event3_uint256_Event = TypedEvent<
[BigNumber] & { value1: BigNumber }
>;

export type Event4Event = TypedEvent<
[[BigNumber, string] & { index: BigNumber; name: string }] & {
data: [BigNumber, string] & { index: BigNumber; name: string };
}
>;

export type NoArgsEventEvent = TypedEvent<[] & {}>;

export class Events extends BaseContract {
connect(signerOrProvider: Signer | Provider | string): this;
attach(addressOrName: string): this;
Expand Down Expand Up @@ -201,10 +225,22 @@ export class Events extends BaseContract {
};

filters: {
"AnonEvent1(uint256)"(
value1?: BigNumberish | null
): TypedEventFilter<[BigNumber], { value1: BigNumber }>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here it would be perfect to reuse the already generated type ie:
TypedEventFilter<AnonEvent1Event>; but I think it's not that simple so we can ship initial implementation without this and later improve.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree we should, that may involve some refactor to the TypedEventFilter generics. Maybe we can clean up the code in another PR.


AnonEvent1(
value1?: BigNumberish | null
): TypedEventFilter<[BigNumber], { value1: BigNumber }>;

"Event1(uint256,uint256)"(
value1?: BigNumberish | null,
value2?: null
): TypedEventFilter<
[BigNumber, BigNumber],
{ value1: BigNumber; value2: BigNumber }
>;

Event1(
value1?: BigNumberish | null,
value2?: null
Expand All @@ -213,25 +249,42 @@ export class Events extends BaseContract {
{ value1: BigNumber; value2: BigNumber }
>;

"Event2(uint256)"(
undefined?: null
): TypedEventFilter<[BigNumber], { arg0: BigNumber }>;

Event2(
undefined?: null
): TypedEventFilter<[BigNumber], { arg0: BigNumber }>;

Event3(
"Event3(bool,uint256)"(
value1?: boolean | null,
value2?: null
): TypedEventFilter<
[boolean, BigNumber],
{ value1: boolean; value2: BigNumber }
>;

"Event3(uint256)"(
value1?: BigNumberish | null
): TypedEventFilter<[BigNumber], { value1: BigNumber }>;

"Event4(tuple)"(
data?: null
): TypedEventFilter<
[[BigNumber, string] & { index: BigNumber; name: string }],
{ data: [BigNumber, string] & { index: BigNumber; name: string } }
>;

Event4(
data?: null
): TypedEventFilter<
[[BigNumber, string] & { index: BigNumber; name: string }],
{ data: [BigNumber, string] & { index: BigNumber; name: string } }
>;

"NoArgsEvent()"(): TypedEventFilter<[], {}>;

NoArgsEvent(): TypedEventFilter<[], {}>;
};

Expand Down
47 changes: 41 additions & 6 deletions packages/target-ethers-v5/src/codegen/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ export function codegenContractTypings(contract: Contract, codegenConfig: Codege
.join('\n')}
}

${values(contract.events).map(generateEventTypeExports).join('\n')}

export class ${contract.name} extends BaseContract {
connect(signerOrProvider: Signer | Provider | string): this;
attach(addressOrName: string): this;
Expand Down Expand Up @@ -117,10 +119,7 @@ export function codegenContractTypings(contract: Contract, codegenConfig: Codege
};

filters: {
${values(contract.events)
.map((v) => v[0])
.map(generateEvents)
.join('\n')}
${values(contract.events).map(generateEventFilters).join('\n')}
};

estimateGas: {
Expand Down Expand Up @@ -301,13 +300,49 @@ function generateParamNames(params: Array<AbiParameter | EventArgDeclaration>):
return params.map((param, index) => param.name || `arg${index}`).join(', ')
}

function generateEvents(event: EventDeclaration) {
function generateEventFilters(events: EventDeclaration[]) {
if (events.length === 1) {
return generateEventFilter(events[0], true)
} else {
return events.map((e) => generateEventFilter(e, false)).join('\n')
}
}

function generateEventFilter(event: EventDeclaration, includeNameFilter: boolean) {
const components = event.inputs.map((input, i) => ({ name: input.name ?? `arg${i.toString()}`, type: input.type }))
const arrayOutput = generateOutputComplexTypeAsArray(components)
const objectOutput = generateOutputComplexTypesAsObject(components) || '{}'

let filter = `
'${generateEventSignature(event)}'(${generateEventTypes(
event.inputs,
)}): TypedEventFilter<${arrayOutput}, ${objectOutput}>;
`

if (includeNameFilter) {
filter += `
${event.name}(${generateEventTypes(event.inputs)}): TypedEventFilter<${arrayOutput}, ${objectOutput}>;
`
}
return filter
}

function generateEventTypeExports(events: EventDeclaration[]) {
if (events.length === 1) {
return generateEventTypeExport(events[0], false)
} else {
return events.map((e) => generateEventTypeExport(e, true)).join('\n')
}
}
function generateEventTypeExport(event: EventDeclaration, includeArgTypes: boolean) {
const components = event.inputs.map((input, i) => ({ name: input.name ?? `arg${i.toString()}`, type: input.type }))
const arrayOutput = generateOutputComplexTypeAsArray(components)
const objectOutput = generateOutputComplexTypesAsObject(components) || '{}'

return `
${event.name}(${generateEventTypes(event.inputs)}): TypedEventFilter<${arrayOutput}, ${objectOutput}>;
export type ${event.name}${
includeArgTypes ? event.inputs.map((input) => '_' + input.type.originalType).join('') + '_Event' : 'Event'
} = TypedEvent<${arrayOutput} & ${objectOutput}>;
`
}

Expand Down