0.10.0 • Published 1 month ago

@mountainpath9/overlord v0.10.0

Weekly downloads
-
License
AGPL-3.0-or-later
Repository
-
Last release
1 month ago

What is it?

A typescript library to support contract automation tasks in the overlord framework.

Usage

A instance of the TaskRunner class manages a suite of tasks. It's expected that it runs under nodejs. The structure is typically:

import { createTaskRunner } from "@mountainpath9/overlord";

async function main() {
  const runner = createTaskRunner();

  // specify tasks here
  
  runner.main();
}

main();

The above program will fetch configuration from the OVL_TASK_RUNNER_CONFIG environment variable and then manage the specified tasks.

Scheduled tasks

Scheduled tasks run according to a cron style type schedule. An example:

  runner.addPeriodicTask({
    id: 'heatbeat',
    cronSchedule: '*/10 * * * * *',
    action: async (ctx, date) => {
      ctx.logger.info(`tick at ${date.toISOString()}...`);
      return taskSuccess();
    },
  });

The cron syntax is as per the cron-parser package. The ctx parameter provides task specific context, including a winston implemented logger as above. The date parameter provides the scheduled time of the task.

.addPeriodicTask permits a allowConcurrent parameter. By default, subsequent runs of a task will be skipped when a previous run is still in progress. If allowConcurrent is present and true, then subsequent runs will run concurrently with any previous ones.

Webhook tasks

Webhook tasks are triggered via an HTTP post request. An example:

  runner.addWebhookTask({
    id: 'some-webhook',
    action: async (ctx, koaCtx) => {
      ctx.logger.info(`webhook from ${koaCtx.ip}`);
      return taskSuccess();
    },
  });

The koaCtx provides details of the web request. It can be used for authorisation, and also to obtain the request body for request specific parameters.

On Boot task

"On boot" tasks are run a single time when the task runner starts up:

  runner.addOnBootTask({
    id: 'onBootTask1',
    action: async (ctx) => {
      ctx.logger.info("onBoot task 1");
      return taskSuccess();
    },
  });

Chain Event tasks

Chain event tasks are triggered whenever an event on an EVM blockchain is generated that matches a provided filter:

import { Contract } from "ethers";
...
  const chainId = 1;
  const daiContractAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; 
  const provider = await runner.getProvider(chainId);
  let abi = [ 
    "event Transfer(address indexed from, address indexed to, uint value)" 
  ];
  let contract = new Contract(daiContractAddress, abi, provider);
  const filter = contract.filters.Transfer();

  runner.addChainEventTask({
    id:'alert_dai_token_transfer',
    chainId,
    filters: [filter],
    action: async (ctx, evmlog) => {
      const args = contract.interface.parseLog(evmlog).args;
      ctx.logger.info(`dai transfer: from=${args.from} to=${args.to} value=${args.value}`);
      return taskSuccess();
    }
  });

The above example uses ethersjs untyped event decoding. It's possible to use TypeChain to improve static typing rigor.

Task context

As mentioned above, a variety of contextual information is available to tasks via the ctx parameter. This value has type

export interface TaskContext {
  logger: Logger;

  getProvider(chainId: number): Promise<Provider>;
  getSigner(provider: Provider, signerId: string): Promise<Signer>;

  kvGet(key: string): Promise<Json | undefined>;
  kvSet(key: string, value: Json): Promise<void>;
  kvClear(key: string): Promise<void>;
  config: ConfigAccess;
};

export interface ConfigAccess {
  get(configId: string): Promise<Json | undefined>;

  getString(configId: string): Promise<string | undefined>;
  getNumber(configId: string): Promise<number | undefined>;
  getBoolean(configId: string): Promise<boolean | undefined>;

  requireString(configId: string): Promise<string>;
  requireNumber(configId: string): Promise<number>;
  requireBoolean(configId: string): Promise<boolean>;
}

The KV store has string keys, and the values are arbitrary json. Here is a stateful task:

  runner.addPeriodicTask({
    id: 'heatbeat',
    cronSchedule: '*/10 * * * * *',
    action: async (ctx) => {
      const toggle = !!await ctx.kvGet('toggle');
      ctx.logger.info(toggle ? "tick..." : `tock...`);
      await ctx.kvSet('toggle', !toggle);
      return taskSuccess();
    },
  });

When running under the overlord framework, the kv store is persistent, with keys shared between task runners configured in the same tenant.

The overlord framework can manage secrets on behalf of tasks. These must be preconfigured, and are accessible via the getSecret() method.

Finally, when configured, tasks have access to EVM providers and signers.

Task Results

A succesfull task should return taskSuccess() which will cause it to be recorded in the overlord dashboard. If it returns taskSuccessSilent() then a dashboard record will not be created.

The framework will catch and log all exceptions thrown by tasks. For global custom notifications, a specific handler may be provided

  runner.setTaskExceptionHandler( async (ctx: TaskContext, te:TaskException) => {
    // perform existing overlord logging
    await logTaskException(ctx, te);
    
    // then custom notifications code ...
  });

Development

The overlord framework expects tasks runners to be packaged as nodejs docker containers. But for local testing, it is convenient to run task runners directly. This can be done by compiling the typescript, setting the OVL_TASK_RUNNER_CONFIG environment variable, and running the generated javascript. With an appropriate package.json this can look like:

export OVL_TASK_RUNNER_CONFIG='{
  "label":"demo_tasks",
  "http_port":8111,
  "manager":{
    "local" :{
        "providers":[
          {
            "chain_id":80001,
            "rpc_url":"https://rpc.ankr.com/polygon_mumbai"
          }
        ],
        "vars": {
          "test_secret1":"XXX",
          "test_secret2":"true"
        },
        "signers":[
          {
            "chain_id":80001,
            "signer_id": "demo_tasks_signer",
            "kind": {
                "private_key": "TESTPRIVATEKEY"
            }
          }
        ]
    }
  }
}'
yarn
yarn tsc
node dist/main.js

If desired one can run against a local network fork, by using the appropriate rpc_url.

When run locally, all of the task types will run as requested. To manually trigger tasks it's useful to have webhook tasks setup, and then trigger them with curl:

curl  -X POST -D '{}' http://localhost:8111/webhooks/demo-test-webhook

To build and publish the overlord library itself:

pnpm install
pnpm build
(cd dist; npm publish --access=public)
0.10.0

1 month ago

0.9.0

2 months ago

0.8.5

4 months ago

0.8.4

4 months ago

0.8.3

4 months ago

0.9.0-beta.1

5 months ago

0.9.0-beta.0

5 months ago

0.9.0-beta.3

5 months ago

0.9.0-beta.2

5 months ago

0.9.0-beta.4

5 months ago

0.9.0-alpha.1

5 months ago

0.3.9

11 months ago

0.3.12

10 months ago

0.3.11

10 months ago

0.3.10

11 months ago

0.8.1

8 months ago

0.5.4

8 months ago

0.8.0

8 months ago

0.5.3

8 months ago

0.3.8

11 months ago

0.8.2

8 months ago

0.5.0

9 months ago

0.4.1

9 months ago

0.4.0

9 months ago

0.7.0

8 months ago

0.6.1

8 months ago

0.5.2

8 months ago

0.6.0

8 months ago

0.5.1

9 months ago

0.3.6

11 months ago

0.3.5

11 months ago

0.3.7

11 months ago

0.0.15

1 year ago

0.0.16

1 year ago

0.0.17

12 months ago

0.0.18

12 months ago

0.0.19

11 months ago

0.0.11

1 year ago

0.0.12

1 year ago

0.0.13

1 year ago

0.0.14

1 year ago

0.1.0

11 months ago

0.2.1

11 months ago

0.2.0

11 months ago

0.1.0-alpha.1

11 months ago

0.3.4

11 months ago

0.0.10

1 year ago

0.0.9

1 year ago

0.0.8

1 year ago

0.0.6

1 year ago

0.0.5

1 year ago

0.0.4

1 year ago

0.0.3

1 year ago

0.0.2

1 year ago

0.0.1

1 year ago