Skip to content

Commit 0f1863d

Browse files
authored
Merge pull request #122 from BootNodeDev/feat/scaffold
feat(solvers): scritp to add and remove
2 parents 625e4f6 + fdcb4be commit 0f1863d

File tree

19 files changed

+788
-98
lines changed

19 files changed

+788
-98
lines changed

typescript/solver/README.md

Lines changed: 82 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ The solver directory contains the implementation of the Intent Solver, a TypeScr
44

55
## Table of Contents
66

7-
- Directory Structure
8-
- Installation
9-
- Usage
10-
- Adding a New Solver
7+
- [Directory Structure](#directory-structure)
8+
- [Installation](#installation)
9+
- [Usage](#usage)
10+
- [Managing Solvers](#managing-solvers)
11+
- [Intent Filtering](#intent-filtering)
12+
- [Logging](#logging)
1113

1214
## Directory Structure
1315

@@ -46,13 +48,9 @@ solver/
4648
### Description of Key Files and Directories
4749

4850
- **solver/index.ts**: The main entry point of the solver application. It initializes and starts the listeners and fillers for different solvers.
49-
5051
- **logger.ts**: Contains the Logger class used for logging messages with various formats and levels.
51-
5252
- **NonceKeeperWallet.ts**: A class that extends ethers Wallet and prevents nonces race conditions when the solver needs to fill different intents (from different solutions) in the same network.
53-
5453
- **patch-bigint-buffer-warn.js**: A script to suppress specific warnings related to BigInt and Buffer, ensuring cleaner console output.
55-
5654
- **solvers/**: Contains implementations of different solvers and common utilities.
5755
- **BaseListener.ts**: An abstract base class that provides common functionality for event listeners. It handles setting up contract connections and defines the interface for parsing event arguments.
5856
- **BaseFiller.ts**: An abstract base class that provides common functionality for fillers. It handles the solver's lifecycle `prepareIntent`, `fill`, and `settle`.
@@ -96,33 +94,96 @@ solver/
9694

9795
### Running the Solver Application
9896

99-
To start the solver application, execute:
97+
Start the solver application:
10098

10199
```sh
102100
yarn solver
103101
```
104102

105-
This will run the compiled JavaScript code from the `dist` directory, starting all the listeners defined in index.ts.
103+
This will run the compiled JavaScript code from the `dist` directory, initializing and starting all enabled solvers as defined in `config/solvers.json`. Each solver's status (enabled/disabled) can be configured in this JSON file.
106104

107105
### Development Mode
108106

109-
For development, you can run the application in watch mode to automatically restart on code changes:
107+
Run in watch mode for development:
110108

111109
```sh
112110
yarn dev
113111
```
114112

115-
### Intent filtering
113+
## Managing Solvers
114+
115+
### Adding a New Solver
116+
117+
You can add a new solver in two ways:
118+
119+
```sh
120+
# Interactive mode - will prompt for solver name and options
121+
yarn solver:add
122+
123+
# Direct mode - specify the solver name as an argument
124+
yarn solver:add mySolver
125+
```
126+
127+
This will:
128+
129+
1. Validate the solver name
130+
2. Create the solver directory structure
131+
3. Generate necessary files with boilerplate code
132+
4. Update the solvers index
133+
5. Add solver configuration
134+
6. Set up allow/block lists
135+
136+
The script creates the following structure:
137+
138+
```
139+
solvers/
140+
└── yourSolver/
141+
├── index.ts
142+
├── listener.ts
143+
├── filler.ts
144+
├── types.ts
145+
├── contracts/
146+
├── rules/
147+
│ └── index.ts
148+
└── config/
149+
├── index.ts
150+
├── metadata.ts
151+
└── allowBlockLists.ts
152+
```
153+
154+
After creation:
155+
156+
1. Add your contract ABI to: `solvers/yourSolver/contracts/`
157+
2. Run `yarn contracts:typegen` to generate TypeScript types
158+
3. Update the listener and filler implementations
159+
4. Configure your solver options in `config/solvers.ts`
160+
5. Update metadata in `solvers/yourSolver/config/metadata.ts`
161+
162+
### Removing Solvers
163+
164+
To remove existing solvers:
165+
166+
```sh
167+
yarn solver:remove
168+
```
169+
170+
This will:
116171

117-
Configure which intent to fill , and which to ignore
172+
1. Show a list of existing solvers (use space to select multiple, enter to confirm)
173+
2. Ask for confirmation before proceeding
174+
3. Remove the selected solver directories
175+
4. Update the solvers index
176+
5. Remove solver configurations
177+
6. Clean up generated typechain files
118178

119-
By default, the solver will attempt to fill intents sent from its origin chain to any destination chains.
179+
You can cancel the removal operation at any time by pressing 'q'.
120180

121-
Solvers may want to further filter the intents they attempt to fill. For example, fill intents coming from a specific address or going to a subset of chains.
181+
## Intent Filtering
122182

123-
In order to do this, you can configure a block-list or an allow-list at a global level within the [allowBlockList.ts file](./config/allowBlockLists.ts) or at a specific solver level like e.g. [solvers/eco/config/allowBlockList.ts file](./solvers/eco/config/allowBlockLists.ts).
183+
Configure which intents to fill or ignore using allow/block lists. Configure at:
124184

125-
Such configs should be written in the `allowBlockLists` variable
185+
- Global level: `config/allowBlockLists.ts`
186+
- Solver level: `solvers/<solver>/config/allowBlockLists.ts`
126187

127188
```typescript
128189
const allowBlockLists: AllowBlockLists = {
@@ -131,7 +192,7 @@ const allowBlockLists: AllowBlockLists = {
131192
};
132193
```
133194

134-
as object of the following format:
195+
Format:
135196

136197
```typescript
137198
type Wildcard = "*";
@@ -152,54 +213,8 @@ Both the allow-list and block-lists have "any" semantics. In other words, the So
152213
153214
The block-list supersedes the allow-list, i.e. if a message matches both the allow-list and the block-list, it will not be delivered.
154215
155-
### Logging
156-
157-
The application utilizes a custom Logger class for logging. You can adjust the log level and format by modifying the Logger instantiation in index.ts. By default, it will log to `stdout` in a human-readable format using the `INFO` level.
158-
159-
You can customize the logging destination by using a pino transport of your choosing. There's an example for logging to a Syslog server running on `localhost` commented in [logger.ts](logger.ts). Check out the [pino transports docs](https://github.com/pinojs/pino/blob/main/docs/transports.md) for other available transports.
160-
161-
## Adding a New Solver
162-
163-
To integrate a new solver into the application, follow these steps:
164-
165-
1. **Create a New Solver Directory**: Inside solvers/, create a new directory for your solver:
166-
167-
```
168-
solvers/
169-
└── yourSolver/
170-
├── listener.ts
171-
├── filler.ts
172-
└── contracts/
173-
└── YourContract.json
174-
```
175-
176-
2. **Implement the Listener**: In listener.ts, extend the `BaseListener` class and implement the required methods to handle your specific event.
177-
178-
3. **Implement the Filler**: In filler.ts, extend the `BaseFiller` class and implement the required logic to process events captured by your listener.
179-
180-
4. **Add Contract Definitions**: Place your contract ABI and type definitions in the `contracts/` directory.
181-
182-
5. **Generate Types from Contract Definitions**: Run the following command:
183-
184-
```sh
185-
yarn contracts:typegen
186-
```
187-
188-
6. **Update the Index**: Export your new solver in index.ts:
216+
## Logging
189217
190-
```typescript
191-
export * as yourSolver from "./yourSolver/index.js";
192-
```
193-
194-
7. **Initialize in Main Application**: In index.ts, import and initialize your solver:
195-
196-
```typescript
197-
import * as solvers from './solvers/index.js';
218+
The application uses a custom Logger class. Default: `stdout` with `INFO` level.
198219
199-
// ... existing code ...
200-
201-
const yourSolverListener = solvers['yourSolver'].listener.create();
202-
const yourSolverFiller = solvers['yourSolver'].filler.create(multiProvider);
203-
204-
yourSolverListener(yourSolverFiller);
205-
```
220+
Customize using pino transports. See [pino transports docs](https://github.com/pinojs/pino/blob/main/docs/transports.md). There's an example for logging to a Syslog server running on `localhost` commented in [logger.ts](logger.ts).
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"eco": {
3+
"enabled": true
4+
},
5+
"hyperlane7683": {
6+
"enabled": true
7+
}
8+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import fs from "fs";
2+
import path from "path";
3+
import { fileURLToPath } from "url";
4+
import { z } from "zod";
5+
6+
const __filename = fileURLToPath(import.meta.url);
7+
const __dirname = path.dirname(__filename);
8+
9+
export const SolverConfigSchema = z.object({
10+
enabled: z.boolean(),
11+
});
12+
13+
export type SolverConfig = z.infer<typeof SolverConfigSchema>;
14+
15+
// Read and parse the JSON file
16+
const solversConfigPath = path.join(__dirname, "solvers.json");
17+
export const solversConfig: Record<string, SolverConfig> = JSON.parse(
18+
fs.readFileSync(solversConfigPath, "utf-8"),
19+
);
20+
21+
// Validate config
22+
Object.entries(solversConfig).forEach(([name, config]) => {
23+
SolverConfigSchema.parse(config);
24+
});
25+
26+
export type SolverName = keyof typeof solversConfig;

typescript/solver/index.ts

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,34 +4,39 @@ import "./patch-bigint-buffer-warn.js";
44

55
import { chainMetadata } from "./config/chainMetadata.js";
66
import { log } from "./logger.js";
7-
import * as solvers from "./solvers/index.js";
7+
import { SolverManager } from "./solvers/SolverManager.js";
88
import { getMultiProvider } from "./solvers/utils.js";
99

1010
const main = async () => {
1111
const multiProvider = await getMultiProvider(chainMetadata).catch(
12-
(error) => (log.error(error.reason ?? error.message), process.exit(1)),
12+
(error) => (log.error(error.reason ?? error.message), process.exit(1))
1313
);
1414

1515
log.info("🙍 Intent Solver 📝");
1616
log.info("Starting...");
1717

18-
// TODO: implement a way to choose different listeners and fillers
19-
const ecoListener = solvers["eco"].listener.create();
20-
const ecoFiller = solvers["eco"].filler.create(
21-
multiProvider,
22-
solvers["eco"].rules,
23-
);
24-
25-
ecoListener(ecoFiller);
26-
27-
const hyperlane7683Listener =
28-
await solvers["hyperlane7683"].listener.create();
29-
const hyperlane7683Filler = solvers["hyperlane7683"].filler.create(
30-
multiProvider,
31-
solvers["hyperlane7683"].rules,
32-
);
33-
34-
hyperlane7683Listener(hyperlane7683Filler);
18+
const solverManager = new SolverManager(multiProvider, log);
19+
20+
// Handle shutdown gracefully
21+
process.on("SIGINT", () => {
22+
log.debug("Received SIGINT signal");
23+
solverManager.shutdown();
24+
process.exit(0);
25+
});
26+
27+
process.on("SIGTERM", () => {
28+
log.debug("Received SIGTERM signal");
29+
solverManager.shutdown();
30+
process.exit(0);
31+
});
32+
33+
try {
34+
await solverManager.initializeSolvers();
35+
log.info("All solvers initialized successfully");
36+
} catch (error) {
37+
log.error("Failed to initialize solvers:", error);
38+
process.exit(1);
39+
}
3540
};
3641

3742
await main();

typescript/solver/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
"prettier:write": "prettier . --write",
1212
"contracts:typegen": "typechain --node16-modules --target ethers-v5 --out-dir typechain **/contracts/*.json",
1313
"postinstall": "yarn contracts:typegen",
14-
"test": "vitest run"
14+
"test": "vitest run",
15+
"solver:add": "tsx scripts/solver-add.ts",
16+
"solver:remove": "tsx scripts/solver-remove.ts"
1517
},
1618
"repository": {
1719
"type": "git",

0 commit comments

Comments
 (0)