Skip to content

Commit

Permalink
feat: add automation package building blocks, including state machine…
Browse files Browse the repository at this point in the history
…, listeners, state and transitions
  • Loading branch information
FedericoAmura committed Nov 27, 2024
1 parent d7aabfd commit 327964e
Show file tree
Hide file tree
Showing 40 changed files with 1,641 additions and 10 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"@nx/web": "17.3.0",
"@solana/web3.js": "^1.95.3",
"@types/depd": "^1.1.36",
"@types/events": "^3.0.3",
"@types/jest": "27.4.1",
"@types/node": "18.19.18",
"@types/secp256k1": "^4.0.6",
Expand Down
10 changes: 10 additions & 0 deletions packages/automation/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"presets": [
[
"@nx/web/babel",
{
"useBuiltIns": "usage"
}
]
]
}
18 changes: 18 additions & 0 deletions packages/automation/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
3 changes: 3 additions & 0 deletions packages/automation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Quick Start

This submodule is used to automate different actions using the Lit Protocol network or other useful events providing listening and responding abilities based on state machines.
16 changes: 16 additions & 0 deletions packages/automation/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/* eslint-disable */
export default {
displayName: 'types',
preset: '../../jest.preset.js',
globals: {
'ts-jest': {
tsconfig: '<rootDir>/tsconfig.spec.json',
},
},
transform: {
'^.+\\.[t]s$': 'ts-jest',
},
moduleFileExtensions: ['ts', 'js', 'html'],
coverageDirectory: '../../coverage/packages/automation',
setupFilesAfterEnv: ['../../jest.setup.js'],
};
32 changes: 32 additions & 0 deletions packages/automation/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "@lit-protocol/automation",
"type": "commonjs",
"license": "MIT",
"homepage": "https://github.com/Lit-Protocol/js-sdk",
"repository": {
"type": "git",
"url": "https://github.com/LIT-Protocol/js-sdk"
},
"keywords": [
"library"
],
"bugs": {
"url": "https://github.com/LIT-Protocol/js-sdk/issues"
},
"publishConfig": {
"access": "public",
"directory": "../../dist/packages/automation"
},
"tags": [
"universal"
],
"buildOptions": {
"genReact": false
},
"scripts": {
"generate-lit-actions": "yarn node ./esbuild.config.js"
},
"version": "7.0.0",
"main": "./dist/src/index.js",
"typings": "./dist/src/index.d.ts"
}
37 changes: 37 additions & 0 deletions packages/automation/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "automation",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/automation/src",
"projectType": "library",
"targets": {
"build": {
"cache": false,
"executor": "@nx/js:tsc",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/packages/automation",
"main": "packages/automation/src/index.ts",
"tsConfig": "packages/automation/tsconfig.lib.json",
"assets": ["packages/automation/*.md"],
"updateBuildableProjectDepsInPackageJson": true
},
"dependsOn": ["^build"]
},
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["packages/automation/**/*.ts"]
}
},
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/packages/automation"],
"options": {
"jestConfig": "packages/automation/jest.config.ts",
"passWithNoTests": true
}
}
},
"tags": []
}
3 changes: 3 additions & 0 deletions packages/automation/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { StateMachine } from './lib/state-machine';

export { StateMachine };
36 changes: 36 additions & 0 deletions packages/automation/src/lib/listeners/constant.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ConstantListener } from './constant';

describe('ConstantListener', () => {
let constantListener: ConstantListener<number>;
const valueToEmit = 42;

beforeEach(() => {
constantListener = new ConstantListener(valueToEmit);
});

it('should emit the constant value immediately when started', async () => {
const callback = jest.fn();
constantListener.onStateChange(callback);

await constantListener.start();

// Advance event loop
await new Promise((resolve) => setTimeout(resolve, 0));

expect(callback).toHaveBeenCalledWith(valueToEmit);
});

it('should not emit any value after being stopped', async () => {
const callback = jest.fn();
constantListener.onStateChange(callback);

await constantListener.start();
await constantListener.stop();

// Advance event loop
await new Promise((resolve) => setTimeout(resolve, 0));

// Ensure no additional calls were made after stop
expect(callback).toHaveBeenCalledTimes(1);
});
});
17 changes: 17 additions & 0 deletions packages/automation/src/lib/listeners/constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Listener } from './listener';

/**
* A simple listener that emits a constant value immediately when started
*/
export class ConstantListener<T> extends Listener<T> {
constructor(private value: T) {
super({
start: async () => {
// Emit value on next tick simulating a state change and respecting event architecture
setTimeout(() => {
this.emit(this.value);
}, 0);
},
});
}
}
56 changes: 56 additions & 0 deletions packages/automation/src/lib/listeners/evm-block.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { ethers } from 'ethers';

import { EVMBlockListener } from './evm-block';

jest.mock('ethers');

describe('EVMBlockListener', () => {
let evmBlockListener: EVMBlockListener;
let providerMock: jest.Mocked<ethers.providers.JsonRpcProvider>;

beforeEach(() => {
providerMock = {
on: jest.fn(),
removeAllListeners: jest.fn(),
getBlock: jest.fn().mockResolvedValue({ number: 123, hash: '0xabc' }),
} as unknown as jest.Mocked<ethers.providers.JsonRpcProvider>;

(
ethers.providers.JsonRpcProvider as unknown as jest.Mock
).mockImplementation(() => providerMock);

evmBlockListener = new EVMBlockListener('http://example-rpc-url.com');
});

afterEach(async () => {
await evmBlockListener.stop();
jest.clearAllMocks();
});

it('should start listening to block events', async () => {
await evmBlockListener.start();

expect(providerMock.on).toHaveBeenCalledWith('block', expect.any(Function));
});

it('should emit block data on block event', async () => {
const callback = jest.fn();
evmBlockListener.onStateChange(callback);

await evmBlockListener.start();

// Simulate block event
const blockEventCallback = providerMock.on.mock.calls[0][1];
await blockEventCallback(123);

expect(providerMock.getBlock).toHaveBeenCalledWith(123);
expect(callback).toHaveBeenCalledWith({ number: 123, hash: '0xabc' });
});

it('should stop listening to block events', async () => {
await evmBlockListener.start();
await evmBlockListener.stop();

expect(providerMock.removeAllListeners).toHaveBeenCalledWith('block');
});
});
27 changes: 27 additions & 0 deletions packages/automation/src/lib/listeners/evm-block.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ethers } from 'ethers';

import { LIT_EVM_CHAINS } from '@lit-protocol/constants';

import { Listener } from './listener';

export type BlockData = ethers.providers.Block;

export class EVMBlockListener extends Listener<BlockData> {
constructor(rpcUrl: string = LIT_EVM_CHAINS['ethereum'].rpcUrls[0]) {
const provider = new ethers.providers.JsonRpcProvider(rpcUrl);

super({
start: async () => {
provider.on('block', async (blockNumber) => {
const block = await provider.getBlock(blockNumber);
if (block) {
this.emit(block);
}
});
},
stop: async () => {
provider.removeAllListeners('block');
},
});
}
}
81 changes: 81 additions & 0 deletions packages/automation/src/lib/listeners/evm-contract-event.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { ethers } from 'ethers';

import {
EVMContractEventListener,
ContractInfo,
EventInfo,
} from './evm-contract-event';

jest.mock('ethers');

describe('EVMContractEventListener', () => {
let evmContractEventListener: EVMContractEventListener;
let contractMock: jest.Mocked<ethers.Contract>;
const rpcUrl = 'http://example-rpc-url.com';
const contractInfo: ContractInfo = {
address: '0x123',
abi: [],
};
const eventInfo: EventInfo = {
name: 'TestEvent',
};

beforeEach(() => {
contractMock = {
on: jest.fn(),
removeAllListeners: jest.fn(),
filters: {
TestEvent: jest.fn().mockReturnValue({}),
},
} as unknown as jest.Mocked<ethers.Contract>;

(ethers.Contract as unknown as jest.Mock).mockImplementation(
() => contractMock
);

evmContractEventListener = new EVMContractEventListener(
rpcUrl,
contractInfo,
eventInfo
);
});

afterEach(async () => {
await evmContractEventListener.stop();
jest.clearAllMocks();
});

it('should start listening to contract events', async () => {
await evmContractEventListener.start();

expect(contractMock.on).toHaveBeenCalledWith({}, expect.any(Function));
});

it('should emit event data on contract event', async () => {
const callback = jest.fn();
evmContractEventListener.onStateChange(callback);

await evmContractEventListener.start();

// Simulate contract event
const eventCallback = contractMock.on.mock.calls[0][1];
const mockEvent = { blockNumber: 123, transactionHash: '0xabc' };
eventCallback('arg1', 'arg2', mockEvent);

expect(callback).toHaveBeenCalledWith({
event: mockEvent,
args: ['arg1', 'arg2'],
blockNumber: 123,
transactionHash: '0xabc',
});
});

it('should stop listening to contract events', async () => {
await evmContractEventListener.start();
await evmContractEventListener.stop();

expect(contractMock.removeAllListeners).toHaveBeenCalledWith(
eventInfo.name
);
});
});
Loading

0 comments on commit 327964e

Please sign in to comment.