
Product
Introducing Webhook Events for Pull Request Scans
Add real-time Socket webhook events to your workflows to automatically receive pull request scan results and security alerts in real time.
@ora-io/orap
Advanced tools
ORAP is a declarative framework for building oracle services, handy to use out of the box.
Show me you
Flow
s, I'll help youassemble
toVerse
s, which compose into aOrap
.
drop
theBeat
s, let'splay
!
Orap
provides 2 styles of usage:
Orap
handles most of the common part, e.g. signal handle, task defining, task caching, task fetch and processing, multitasks processing, etc., while it may sacrifice flexibility in some way.Back in the scene, there are 2 internal layers in Orap
:
Signal
, StoreManager
, and Task
, where the concepts are self-explained in engineering context.Flow
, Verse
, and Beat
, where the concepts are introduced by Orap
only.About Multi-chain: Currently Each
Orap
listens to only 1 blockchain network by design, similar to http servers. Create multipleOrap
instances to implement multi-chain listener.Suggest to include network in task
prefix()
, to avoid key collision in cache store
It comes with rich features like customized task cache, multitasks handling etc.
Note the following already includes using Redis as the store to cache tasks, allowing continuation after service restart, it's robust even when the service restarts.
import { ethers } from 'ethers'
import type { NextFunction, TaskRaplized } from '@orap-io/orap'
import { Orap, StoreManager, getMiddlewareContext } from '@orap-io/orap'
import { Logger, redisStore } from '@ora-io/utils'
// new orap
const orap = new Orap()
// use redis
const store = redisStore()
const sm = new StoreManager(store)
// example event: erc20 transfer
const handle1 = (from: string, to: string, amount: number, event: ContractEventPayload, next: NextFunction, task: TaskRaplized) => {
console.log(`handle task 1: from ${from} to ${to} amount ${amount} task ${task}`)
next()
}
// when you don't know the specific parameters, you can use `getMiddlewareContext` fn get `next` and `task` object.
const handle2 = (...args: any[]) => {
const { task, next } = getMiddlewareContext(...args)
console.log('handle task 2', args, task)
next()
}
// define event signal with crosscheck, and customized cacheable tasks
// note: use redis as the cache layer
orap.event('contract address', 'contract abi', 'event name')
.crosscheck(ccOptions)
// add a task
.task()
.cache(sm)
.prefix('ora-stack:orap:raplizeSample:Task-1:', 'ora-stack:orap:raplizeSample:Done-Task-1:')
.ttl({ taskTtl: 120000, doneTtl: 60000 })
.handle(handle1)
// add another task
.another()
.task()
.cache(sm)
.prefix('ora-stack:orap:raplizeSample:Task-2:', 'ora-stack:orap:raplizeSample:Done-Task-2:')
.ttl({ taskTtl: 120000, doneTtl: 60000 })
.handle(handle2)
// start signal listeners
orap.listen(
{
wsProvider: new ethers.WebSocketProvider('wss://127.0.0.1'),
httpProvider: new ethers.JsonRpcProvider('http://127.0.0.1')
},
() => { console.log('listening on provider.network') }
)
Each new Orap()
starts a Orap Flow
.event(address, abi, eventName, handleFn)
address
: defines the contract addressabi
: defines the contract abieventName
: defines the event namehandleFn
: customized hook on new event received.
return true
to continue the rest of processesreturn false
to hijack the rest of processes.listen(options, onListenFn?)
options
:
onListenFn
: customized hook when listener started.Each .event(...)
starts an Event Flow
.crosscheck(...)
getLogs
..task()
Task Flow
.handle(handleFn)
.event(.., handleFn)
handleFn
: customized hook on new event received.
return true
to continue the rest of processesreturn false
to hijack the rest of processes.another()
Orap Flow
, so that it can add another .event
orap.event(...).another().event(...)
Each .task(...)
starts a Task Flow
.use(handler: HandleFn)
.handle(handler: HandleFn)
return true
to identify handle success, and entering onSuccess
return false
to identify handle failed, and entering onFailed
onSuccess
and onFailed
hooks.cache(sm: StoreManager)
.prefix(taskPrefix: Prefix, donePrefix: Prefix)
donePrefix
: prefix of 'todo' tasks recordsdonePrefix
: prefix of 'done' tasks records.ttl({ taskTtl, doneTtl }: { taskTtl: number; doneTtl: number })
donePrefix
: ttl of 'todo' tasks recordsdonePrefix
: ttl of 'done' tasks records.key(toKey: ToKeyFn)
ToKeyFn
will callback with the event values, and should return a string as the key.success(onSuccess: HandleResultFn)
.fail(onFail: HandleResultFn)
.context(ctx: Context)
.another()
Event Flow
, so that it can add another .task
orap.event(...).task().another().task()
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.
You can define use
to add a middleware to the task flow.
NOTE: handle is a middleware that can be flexibly placed at any position within the middleware chain, and it will be called in order.
Middleware is only applicable to task flow, not event flow.
next
fn, so you have to call it to continue the next handler.TaskRaplized
object, which contains the task info.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.create CheckTransactionStatusMiddleware.js
file
define CheckTransactionStatusMiddleware
function
// Since it is a general middleware, we don't know the specific parameters of the event, so we need to use rest params.
export function CheckTransactionStatusMiddleware(...args) {}
get middleware context for get next
fn and task
object
// Since it is a general middleware, we don't know the specific parameters of the event, so we need to use rest params.
export function CheckTransactionStatusMiddleware(...args) {
// get middleware context for get `next` fn and `task` object
const { next, task } = getMiddlewareContext(...args)
}
write the check transaction status logic,
since we need to use the provider to check transaction status, we need the user to actively pass in the provider
// Since it is a general middleware, we don't know the specific parameters of the event, so we need to use rest params.
export function CheckTransactionStatusMiddleware(provider) {
return (...args) => {
// get middleware context for get `next` fn and `task` object
const { next, task } = getMiddlewareContext(...args)
// get contract event payload
const contractEventPayload = args.at(-3) as ContractEventPayload
// check transaction status
if (contractEventPayload instanceof ContractEventPayload) {
const tx = await provider.getTransactionReceipt(contractEventPayload.log.transactionHash)
if (!tx || tx?.status === 0) {
// it will be caught by the error handler, and terminate the execution of the entire middlewares chain
throw new Error('Transaction failed')
}
await next()
}
else {
// 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
throw new TypeError('Invalid contract event payload')
}
}
}
use middleware in task flow, take erc20 transfer event as an example
import { Orap, StoreManager, getMiddlewareContext } from '@ora-io/orap'
import ethers from 'ethers'
import { CheckTransactionStatusMiddleware } from './CheckTransactionStatusMiddleware.js'
const orap = new Orap()
const store = redisStore()
const sm = new StoreManager(store)
const MAINNET_USDT_ADDR = '0xdAC17F958D2ee523a2206206994597C13D831ec7'
const TRANSFER_EVENT_NAME = 'Transfer'
const eventSignalParam = {
address: MAINNET_USDT_ADDR,
abi: ['event Transfer(address indexed from, address indexed to, uint256 amount)'],
eventName: TRANSFER_EVENT_NAME,
}
const providers = {
wsProvider: new ethers.WebSocketProvider('wss://127.0.0.1'),
httpProvider: new ethers.JsonRpcProvider('http://127.0.0.1')
}
orap.event(eventSignalParam.address, eventSignalParam.abi, eventSignalParam.eventName)
// add a task
.task()
.cache(sm)
.prefix('ora-stack:orap:demo:TransferTask:', 'ora-stack:orap:demo:Done-TransferTask:')
.ttl({ taskTtl: 120000, doneTtl: 60000 })
.use(CheckTransactionStatus(providers.wsProvider))
.handle(handleTask)
// start signal listener
orap.listen(
providers,
() => { logger.log('listening on provider.network') },
)
async function handleTask(from, to, amount, event, task, next) {
logger.log('[+] handleTask: from =', from, 'to =', to, 'amount =', amount)
// do something ...
await next()
}
The middleware will be executed in the order you define it.
import { Orap, StoreManager, getMiddlewareContext } from '@ora-io/orap'
import ethers from 'ethers'
const orap = new Orap()
const store = redisStore()
const sm = new StoreManager(store)
const MAINNET_USDT_ADDR = '0xdAC17F958D2ee523a2206206994597C13D831ec7'
const TRANSFER_EVENT_NAME = 'Transfer'
const eventSignalParam = {
address: MAINNET_USDT_ADDR,
abi: ['event Transfer(address indexed from, address indexed to, uint256 amount)'],
eventName: TRANSFER_EVENT_NAME,
}
const providers = {
wsProvider: new ethers.WebSocketProvider('wss://127.0.0.1'),
httpProvider: new ethers.JsonRpcProvider('http://127.0.0.1')
}
orap.event(eventSignalParam.address, eventSignalParam.abi, eventSignalParam.eventName)
// add a task
.task()
.cache(sm)
.prefix('ora-stack:orap:demo:TransferTask:', 'ora-stack:orap:demo:Done-TransferTask:')
.ttl({ taskTtl: 120000, doneTtl: 60000 })
.use(async (...args) => {
const { next } = getMiddlewareContext(...args)
console.log(1)
await next()
})
.handle(handleTask)
.use(async (...args) => {
const { next } = getMiddlewareContext(...args)
console.log(3)
await next()
})
.use(async (...args) => {
const { next } = getMiddlewareContext(...args)
console.log(4)
})
.use(async (...args) => {
const { next } = getMiddlewareContext(...args)
console.log(5)
})
// add a task
.task()
.cache(sm)
.prefix('ora-stack:orap:demo:TransferTask:', 'ora-stack:orap:demo:Done-TransferTask:')
.ttl({ taskTtl: 120000, doneTtl: 60000 })
.use(async (...args) => {
const { next } = getMiddlewareContext(...args)
console.log(1)
await next()
})
.handle(handleTask)
.use(async (...args) => {
const { next } = getMiddlewareContext(...args)
console.log(3)
await next()
})
.use(async (...args) => {
const { next } = getMiddlewareContext(...args)
console.log(4)
})
.use(async (...args) => {
const { next } = getMiddlewareContext(...args)
console.log(5)
})
// start signal listener
orap.listen(
providers,
() => { logger.log('listening on provider.network') },
)
async function handleTask(from, to, amount, event, task, next) {
console.log(2)
// do something ...
await next()
}
output: 1 2 3 4
Since 4th middleware does not call next
, 5th middleware will not be executed
Note the following doesn't include task cache, it only calls handle
every time it receives an event. So this service is only for demo, don't use it for production, otherwise it may miss events when service down.
import { ethers } from 'ethers'
import { Orap } from '@orap-io/orap'
const orap = new Orap()
const eventSignalParam = {
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
abi: { anonymous: false, inputs: [{ indexed: true, name: 'from', type: 'address' }, { indexed: true, name: 'to', type: 'address' }, { indexed: false, name: 'value', type: 'uint256' }], name: 'Transfer', type: 'event' },
eventName: 'Transfer',
}
const handle = (...args: any) => { console.log('handle', args) }
orap.event(eventSignalParam.address, eventSignalParam.abi, eventSignalParam.eventName, handle)
.crosscheck({ pollingInterval: 1000, batchBlocksCount: 1, blockInterval: 12000 })
orap.listen(
{
wsProvider: new ethers.WebSocketProvider('wss://127.0.0.1'),
httpProvider: new ethers.JsonRpcProvider('http://127.0.0.1')
},
() => { console.log('listening on provider.network') }
)
The following terminology is internally, can be transparent to users.
Orap
compromises multiple Verses
as the processors;Verses
includes Beat
s, which define the pace and the incoming signals that triggering task handling in Orap.Orap
: only need to define Flow
s intuitively, the Owl Rapper will take care of all the rest things.Terminology
Flow
:
new Orap().event(..).crosscheck()
.handle(..)
.task(..).key(..).prefix(..).ttl(..)
.handle(..)
.another()
.task(..).key(..).prefix(..).ttl(..)
.handle(..)
Flow.assemble()
:
Flow
definition and build a Verse
based on it.Verse
:
Flow
.Verse.play()
:
Beat
:
Signal
, which defines the incoming triggers that initiate the runtime process flow
EventBeat
defines the event listenerBeat
wraps Signal
into a uniformed class with only the constructor
and drop()
, easy for Verse
to handleBeat.drop()
:
Signal
listener process.Basic Layer currently consists of 3 parts:
Signal
defines the incoming trigger typesTask
defines the task types that handles signalsStorageManager
defines the cache interface, allowing tasks to be cachedAll events that arrive the oracle service and trigger following actions are defined as Signal
, including:
EventSignal
BlockSignal
EventSignal
orap.event(address:"0x", abi:"", eventName: "Transfer", handleSignal)
crosschecker
features from @ora-io/reku, available config please check out AutoCrossCheckParam
in reku
callback
: the user provided handle function to handle the new signals.TaskBase
toString
, fromString
, stringify
TaskStorable
toKey()
(required): define the primary key that identifies each task, doesn't include taskPrefix
taskPrefix
(recommend): set the prefix of all tasks, also is used when load
tasktaskPrefixDone
(recommend): set the prefix of finished tasks, only used in done
; no need to set if you don't use "task.done(sm)"store
: the store entity, currently provides 2 options: use memory or redis, checkout orap/store
queryDelay
: when doing retry-able operations, e.g. get all keys with the given prefix, this defines the interval between retries.FAQs
Unknown package
We found that @ora-io/orap demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Add real-time Socket webhook events to your workflows to automatically receive pull request scan results and security alerts in real time.
Research
The Socket Threat Research Team uncovered malicious NuGet packages typosquatting the popular Nethereum project to steal wallet keys.
Product
A single platform for static analysis, secrets detection, container scanning, and CVE checks—built on trusted open source tools, ready to run out of the box.