-
Notifications
You must be signed in to change notification settings - Fork 67
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add automation package building blocks, including state machine…
…, listeners, state and transitions
- Loading branch information
1 parent
d7aabfd
commit 327964e
Showing
40 changed files
with
1,641 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"presets": [ | ||
[ | ||
"@nx/web/babel", | ||
{ | ||
"useBuiltIns": "usage" | ||
} | ||
] | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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": {} | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'], | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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": [] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import { StateMachine } from './lib/state-machine'; | ||
|
||
export { StateMachine }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}, | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
81
packages/automation/src/lib/listeners/evm-contract-event.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
); | ||
}); | ||
}); |
Oops, something went wrong.