Skip to content

Commit 419df34

Browse files
authored
Merge pull request #22 from ora-io/dev
Dev
2 parents dec9cb2 + 76ad898 commit 419df34

26 files changed

+4629
-3554
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: 🐞 Bug report
2+
description: Report an issue that should be fixed
3+
labels: [triage]
4+
body:
5+
- type: markdown
6+
attributes:
7+
value: |
8+
Thank you for submitting a bug report. It helps make our project better.
9+
10+
Please try to include as much information as possible.
11+
- type: input
12+
attributes:
13+
label: What version of the project are you using?
14+
placeholder: 0.0.0
15+
validations:
16+
required: true
17+
- type: dropdown
18+
id: package-manager
19+
attributes:
20+
label: Used Package Manager
21+
description: Select the used package manager
22+
options:
23+
- npm
24+
- yarn
25+
- pnpm
26+
validations:
27+
required: true
28+
- type: textarea
29+
attributes:
30+
label: What steps can reproduce the bug?
31+
description: Explain the bug and provide a code snippet that can reproduce it.
32+
validations:
33+
required: true
34+
- type: textarea
35+
attributes:
36+
label: What is the expected behavior?
37+
- type: textarea
38+
attributes:
39+
label: What do you see instead?
40+
- type: textarea
41+
attributes:
42+
label: Additional information
43+
description: Is there anything else you think we should know?
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: 🚀 Feature Request
2+
description: Suggest an idea, feature, or enhancement
3+
labels: [enhancement]
4+
body:
5+
- type: markdown
6+
attributes:
7+
value: |
8+
Thank you for submitting an idea. It helps make our project better.
9+
10+
- type: textarea
11+
attributes:
12+
label: What is the feature you are proposing?
13+
description: A clear description of what you want to happen.
14+
validations:
15+
required: true
16+
- type: textarea
17+
id: suggested-solution
18+
attributes:
19+
label: Suggested solution
20+
description: 'In module [xy] we could provide following implementation...'
21+
validations:
22+
required: true
23+
- type: textarea
24+
id: alternative
25+
attributes:
26+
label: Alternative
27+
description: Clear and concise description of any alternative solutions or features you've considered.
28+
- type: textarea
29+
id: additional-context
30+
attributes:
31+
label: Additional context
32+
description: Any other context or screenshots about the feature request here.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: ❓ General Question
2+
description: Ask any general questions or inquiries
3+
labels: [question]
4+
body:
5+
- type: markdown
6+
attributes:
7+
value: |
8+
Thank you for reaching out with your question. Please provide as much detail as possible to help us assist you better.
9+
10+
- type: textarea
11+
attributes:
12+
label: What is your question?
13+
description: Please describe your question or inquiry in detail.
14+
validations:
15+
required: true
16+
17+
- type: textarea
18+
attributes:
19+
label: Additional context
20+
description: If applicable, provide any additional context or details that might help us understand your question better.

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
blank_issues_enabled: false

README.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
2+
<p align="center">
3+
<a href="https://www.ora.io" target="_blank" rel="noopener noreferrer">
4+
<img width="180" src="https://raw.githubusercontent.com/ora-io/media-kit/f0595ccc0b2c867614379c009de1fa81db794db5/Logo/logo_type_blue_solid.svg" alt="HyperOracle logo">
5+
</a>
6+
</p>
7+
<br/>
8+
<p align="center">
9+
<a href="https://npmjs.com/package/@ora-io/orap"><img src="https://img.shields.io/npm/v/@ora-io/orap/latest.svg" alt="npm package"></a>
10+
<a href="https://www.npmjs.com/package/@ora-io/orap"><img src="https://img.shields.io/npm/d18m/%40ora-io%2Forap" alt="build status"></a>
11+
<a href="https://www.npmjs.com/package/@ora-io/orap"><img src="https://img.shields.io/npm/l/%40ora-io%2Forap" alt="build status"></a>
12+
</p>
13+
114
# ORA STACK
215

316
## What is ORA STACK
@@ -13,4 +26,4 @@ Comming soon:
1326

1427
## Template License
1528

16-
[MIT](./LICENSE) License © 2022 [ORA Protocol](https://ora.io)
29+
[MIT](./LICENSE) License © 2022 [ORA Protocol](https://ora.io)

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "ora-stack",
3-
"version": "0.2.1",
3+
"version": "0.2.2",
44
"private": true,
55
"packageManager": "[email protected]",
66
"description": "",
@@ -62,6 +62,7 @@
6262
"@rollup/plugin-node-resolve": "^15.2.3",
6363
"@types/fs-extra": "^11.0.4",
6464
"@types/node": "^22.1.0",
65+
"@types/prompts": "^2.4.9",
6566
"bumpp": "^9.4.2",
6667
"consola": "^3.2.3",
6768
"dotenv": "^16.4.5",
@@ -70,8 +71,10 @@
7071
"ethers": "^6.13.2",
7172
"fast-glob": "^3.3.2",
7273
"fs-extra": "^11.2.0",
74+
"kolorist": "^1.8.0",
7375
"lint-staged": "^15.2.8",
7476
"pnpm": "^9.7.0",
77+
"prompts": "^2.4.2",
7578
"rimraf": "^5.0.10",
7679
"rollup": "^4.20.0",
7780
"rollup-plugin-dts": "^6.1.1",

packages/orap/README.md

Lines changed: 196 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ Note the following already includes using Redis as the store to cache tasks, all
6666

6767
```ts
6868
import { ethers } from 'ethers'
69-
import { Orap, StoreManager } from '@orap-io/orap'
69+
import type { NextFunction, TaskRaplized } from '@orap-io/orap'
70+
import { Orap, StoreManager, getMiddlewareContext } from '@orap-io/orap'
7071
import { Logger, redisStore } from '@ora-io/utils'
7172

7273
// new orap
@@ -76,12 +77,18 @@ const orap = new Orap()
7677
const store = redisStore()
7778
const sm = new StoreManager(store)
7879

79-
// use a logger
80-
const logger = new Logger('info', '[orap-raplize-sample]')
81-
82-
const handle1 = (...args: any) => { logger.log('handle task 1', args); return true }
80+
// example event: erc20 transfer
81+
const handle1 = (from: string, to: string, amount: number, event: ContractEventPayload, task: TaskRaplized, next: NextFunction) => {
82+
console.log(`handle task 1: from ${from} to ${to} amount ${amount} task ${task}`)
83+
next()
84+
}
8385

84-
const handle2 = (...args: any) => { logger.log('handle task 2', args); return true }
86+
// when you don't know the specific parameters, you can use `getMiddlewareContext` fn get `next` and `task` object.
87+
const handle2 = (...args: any[]) => {
88+
const { task, next } = getMiddlewareContext(...args)
89+
console.log('handle task 2', args, task)
90+
next()
91+
}
8592

8693
// define event signal with crosscheck, and customized cacheable tasks
8794
// note: use redis as the cache layer
@@ -162,6 +169,7 @@ Each `.task(...)` starts a `Task Flow`
162169
- ~~`return true` to identify handle success, and entering `onSuccess`~~
163170
- ~~`return false` to identify handle failed, and entering `onFailed`~~
164171
- It will automatically detect success and failed and call the `onSuccess` and `onFailed` hooks
172+
- this fn is essentially a middleware, so it has all the features of middleware
165173

166174
**.cache(sm: StoreManager)**
167175
- set the store to cache the tasks
@@ -181,6 +189,7 @@ Each `.task(...)` starts a `Task Flow`
181189

182190
**.key(toKey: ToKeyFn)**
183191
- defines the primary key of a task based on the event values (i.e. log topics)
192+
- `ToKeyFn` will callback with the event values, and should return a string as the key
184193
- default: random hex string
185194

186195
**.success(onSuccess: HandleResultFn)**
@@ -198,16 +207,194 @@ Each `.task(...)` starts a `Task Flow`
198207
- back to the parent `Event Flow`, so that it can add another `.task`
199208
- e.g. `orap.event(...).task().another().task()`
200209

201-
### Middlewares
210+
## Middlewares
211+
212+
The middlewares are used in the `Task Flow` to handle the task processing, it's a chain of functions that can be called in order.
202213

203-
The middlewares are used in the `Task Flow` to handle the task processing, it's a chain of functions that can be called in order.
214+
You can define `use` to add a middleware to the task flow.
204215

205-
#### Features
216+
NOTE: handle is a middleware that can be flexibly placed at any position within the middleware chain, and it will be called in order.
217+
218+
> Middleware is only applicable to task flow, not event flow.
219+
220+
### Features
206221

207222
- the last parameter of the handler is the `next` fn, so you have to call it to continue the next handler.
208223
- the penultimate parameter is the `TaskRaplized` object, which contains the task info.
209224
- you can pass parameters to the next handler by calling `next(param1, param2, ...)`, it will be passed to the next handler as arguments, note: you cannot pass `TaskRaplized` object and `next` fn to the next handler, it will pass the next handler with the `TaskRaplized` object and `next` fn automatically.
210225

226+
### Usage
227+
228+
#### create a middleware for check transaction status
229+
230+
1. create `CheckTransactionStatusMiddleware.js` file
231+
232+
2. define `CheckTransactionStatusMiddleware` function
233+
234+
```js
235+
// Since it is a general middleware, we don't know the specific parameters of the event, so we need to use rest params.
236+
export function CheckTransactionStatusMiddleware(...args) {}
237+
```
238+
239+
3. get middleware context for get `next` fn and `task` object
240+
241+
```js
242+
// Since it is a general middleware, we don't know the specific parameters of the event, so we need to use rest params.
243+
export function CheckTransactionStatusMiddleware(...args) {
244+
// get middleware context for get `next` fn and `task` object
245+
const { next, task } = getMiddlewareContext(...args)
246+
}
247+
```
248+
249+
250+
4. write the check transaction status logic,
251+
252+
since we need to use the provider to check transaction status, we need the user to actively pass in the provider
253+
254+
```js
255+
// Since it is a general middleware, we don't know the specific parameters of the event, so we need to use rest params.
256+
export function CheckTransactionStatusMiddleware(provider) {
257+
return (...args) => {
258+
// get middleware context for get `next` fn and `task` object
259+
const { next, task } = getMiddlewareContext(...args)
260+
// get contract event payload
261+
const contractEventPayload = args.at(-3) as ContractEventPayload
262+
// check transaction status
263+
if (contractEventPayload instanceof ContractEventPayload) {
264+
const tx = await provider.getTransactionReceipt(contractEventPayload.log.transactionHash)
265+
if (!tx || tx?.status === 0) {
266+
// it will be caught by the error handler, and terminate the execution of the entire middlewares chain
267+
throw new Error('Transaction failed')
268+
}
269+
await next()
270+
}
271+
else {
272+
// throw error if the contract event payload is invalid, it will be caught by the error handler, and terminate the execution of the entire middlewares chain
273+
throw new TypeError('Invalid contract event payload')
274+
}
275+
}
276+
}
277+
```
278+
279+
280+
5. use middleware in task flow, take erc20 transfer event as an example
281+
282+
```js
283+
import { Orap, StoreManager, getMiddlewareContext } from '@ora-io/orap'
284+
import ethers from 'ethers'
285+
import { CheckTransactionStatusMiddleware } from './CheckTransactionStatusMiddleware.js'
286+
287+
const orap = new Orap()
288+
289+
const store = redisStore()
290+
const sm = new StoreManager(store)
291+
292+
const MAINNET_USDT_ADDR = '0xdAC17F958D2ee523a2206206994597C13D831ec7'
293+
const TRANSFER_EVENT_NAME = 'Transfer'
294+
295+
const eventSignalParam = {
296+
address: MAINNET_USDT_ADDR,
297+
abi: ['event Transfer(address indexed from, address indexed to, uint256 amount)'],
298+
eventName: TRANSFER_EVENT_NAME,
299+
}
300+
301+
const providers = {
302+
wsProvider: new ethers.WebSocketProvider('wss://127.0.0.1'),
303+
httpProvider: new ethers.JsonRpcProvider('http://127.0.0.1')
304+
}
305+
306+
orap.event(eventSignalParam)
307+
// add a task
308+
.task()
309+
.cache(sm)
310+
.prefix('ora-stack:orap:demo:TransferTask:', 'ora-stack:orap:demo:Done-TransferTask:')
311+
.ttl({ taskTtl: 120000, doneTtl: 60000 })
312+
.use(CheckTransactionStatus(providers.wsProvider))
313+
.handle(handleTask)
314+
315+
// start signal listener
316+
orap.listen(
317+
providers,
318+
() => { logger.log('listening on provider.network') },
319+
)
320+
321+
async function handleTask(from, to, amount, event, task, next) {
322+
logger.log('[+] handleTask: from =', from, 'to =', to, 'amount =', amount)
323+
// do something ...
324+
await next()
325+
}
326+
327+
```
328+
329+
### Execution order
330+
331+
The middleware will be executed in the order you define it.
332+
333+
```js
334+
import { Orap, StoreManager, getMiddlewareContext } from '@ora-io/orap'
335+
import ethers from 'ethers'
336+
337+
const orap = new Orap()
338+
339+
const store = redisStore()
340+
const sm = new StoreManager(store)
341+
342+
const MAINNET_USDT_ADDR = '0xdAC17F958D2ee523a2206206994597C13D831ec7'
343+
const TRANSFER_EVENT_NAME = 'Transfer'
344+
345+
const eventSignalParam = {
346+
address: MAINNET_USDT_ADDR,
347+
abi: ['event Transfer(address indexed from, address indexed to, uint256 amount)'],
348+
eventName: TRANSFER_EVENT_NAME,
349+
}
350+
351+
const providers = {
352+
wsProvider: new ethers.WebSocketProvider('wss://127.0.0.1'),
353+
httpProvider: new ethers.JsonRpcProvider('http://127.0.0.1')
354+
}
355+
356+
orap.event(eventSignalParam)
357+
// add a task
358+
.task()
359+
.cache(sm)
360+
.prefix('ora-stack:orap:demo:TransferTask:', 'ora-stack:orap:demo:Done-TransferTask:')
361+
.ttl({ taskTtl: 120000, doneTtl: 60000 })
362+
.use(async (...args) => {
363+
const { next } = getMiddlewareContext(...args)
364+
console.log(1)
365+
await next()
366+
})
367+
.handle(handleTask)
368+
.use(async (...args) => {
369+
const { next } = getMiddlewareContext(...args)
370+
console.log(3)
371+
await next()
372+
})
373+
.use(async (...args) => {
374+
const { next } = getMiddlewareContext(...args)
375+
console.log(4)
376+
})
377+
.use(async (...args) => {
378+
const { next } = getMiddlewareContext(...args)
379+
console.log(5)
380+
})
381+
// start signal listener
382+
orap.listen(
383+
providers,
384+
() => { logger.log('listening on provider.network') },
385+
)
386+
387+
async function handleTask(from, to, amount, event, task, next) {
388+
console.log(2)
389+
// do something ...
390+
await next()
391+
}
392+
```
393+
394+
```bash
395+
output: 1 2 3 4
396+
```
397+
Since 4th middleware does not call `next`, 5th middleware will not be executed
211398
212399
### OO Style (Basic)
213400

0 commit comments

Comments
 (0)