This is take-two of articles on creating type inference for Node.js EventEmitters with TypeScript.  This version provides a much cleaner interface for restricting events and handlers to those desired by your custom EventEmitter. So without further ado...

The first step is define an event type that maps the event types to their handler:

export type BlockScannerEvent = {
    receive: (event: BlockScanReceiveEvent) => boolean;
    spend: (event: BlockScanSpendEvent) => boolean;
    start: () => void;
    block: (height: number) => void;
    complete: () => void;
};

Here you can see we define five events and three custom handlers. The event types such as BlockScanReceiveEvent aren't special, they're just a type definition with a few properties. You can make the handler's method signature have whichever properties you need.

Next, you need to define an interface that matches the EventEmitter method signatures.

export interface IBlockScanner {
    // can add domain specific methods here as well...

    // matches EventEmitter.on
    on<U extends keyof BlockScannerEvent>(event: U, listener: BlockScannerEvent[U]): this;
    
    // matches EventEmitter.off
    off<U extends keyof BlockScannerEvent>(event: U, listener: BlockScannerEvent[U]): this;
    
    // matches EventEmitter.emit
    emit<U extends keyof BlockScannerEvent>(
        event: U,
        ...args: Parameters<BlockScannerEvent[U]>
    ): boolean;
}

This essentially redefines EventEmitter methods such as on, off, emit, etc.  But we'll use the keyof TypeScript helper to restrict the event strings to those we previously defined as properties in the BlockScannerEvent.  

One interesting node is that for the emit method we use the Parameters helper. This constructs a tuple type from the types used in the parameters of the supplied function type.  So for us, since U is a key in BlockScannerEvent.  And BlockScannerEvent[U] is a function, we get a tuple with the parameters of that function!

The last step is that we simply define a type that implements this interface to complete the fun.

export class BlockScanner extends EventEmitter implements IBlockScanner {
   // stuff goes here
}

Then you can do fun things like:

scanner.on("receive", (e: BlockScanReceiveEvent) => {
  // do stuff
});