denotify-client 1.1.4 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/alertbuilder.js +24 -1
- package/dist/examples/balance-monitor.js +47 -0
- package/dist/functionbuilder.js +9 -0
- package/dist/index.js +303 -164
- package/dist/index.min.js +1 -1
- package/dist/index.mjs +284 -164
- package/dist/notifications/notify_discord_webhook.js +11 -0
- package/dist/triggers/handler_function_call.js +14 -0
- package/dist/triggers/handler_function_call_v2.js +29 -0
- package/dist/triggers/handler_onchain_event.js +16 -0
- package/dist/triggers/trigger.js +1 -1
- package/dist/util/filter.js +29 -0
- package/package.json +6 -4
- package/types/alertbuilder.d.ts +57 -3
- package/types/examples/aave-healthcheck.d.ts +1 -1
- package/types/examples/balance-monitor.d.ts +1 -0
- package/types/functionbuilder.d.ts +7 -0
- package/types/notifications/notify_discord_webhook.d.ts +6 -0
- package/types/triggers/handler_function_call.d.ts +9 -0
- package/types/triggers/handler_function_call_v2.d.ts +26 -0
- package/types/triggers/handler_onchain_event.d.ts +11 -1
- package/types/triggers/trigger.d.ts +1 -1
- package/types/util/filter.d.ts +11 -0
package/dist/alertbuilder.js
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
import { NotifyDiscordWebhook } from "./notifications/notify_discord_webhook.js";
|
2
|
+
import { HandlerFunctionCall } from "./triggers/handler_function_call.js";
|
3
|
+
import { HandlerFunctionCallV2 } from "./triggers/handler_function_call_v2.js";
|
4
|
+
import { HandlerOnchainEvent } from "./triggers/handler_onchain_event.js";
|
1
5
|
export class AlertBuilder {
|
2
6
|
name;
|
3
7
|
network;
|
@@ -15,6 +19,13 @@ export class AlertBuilder {
|
|
15
19
|
this.network = network;
|
16
20
|
return this;
|
17
21
|
}
|
22
|
+
/**
|
23
|
+
* Call withTrigger with one of the TriggerConfig types:
|
24
|
+
* PollFunctionV2 | PollFunctionV1 | OnchainEventV1
|
25
|
+
* @param id Simple ID
|
26
|
+
* @param options Desired trigger configuration
|
27
|
+
* @returns self for piping
|
28
|
+
*/
|
18
29
|
withTrigger(id, options) {
|
19
30
|
this.triggerId = id;
|
20
31
|
this.trigger = options;
|
@@ -25,13 +36,25 @@ export class AlertBuilder {
|
|
25
36
|
this.notification = options;
|
26
37
|
return this;
|
27
38
|
}
|
28
|
-
|
39
|
+
async validate() {
|
40
|
+
// Validate trigger
|
41
|
+
switch (this.triggerId) {
|
42
|
+
case 'OnchainEventV1': return HandlerOnchainEvent.validateCreate(this.trigger);
|
43
|
+
case 'PollFunctionV1': return HandlerFunctionCall.validateCreate(this.trigger);
|
44
|
+
case 'PollFunctionV2': return HandlerFunctionCallV2.validateCreate(this.trigger);
|
45
|
+
}
|
46
|
+
switch (this.notificationId) {
|
47
|
+
case 'Discord': return NotifyDiscordWebhook.validateCreate(this.notification);
|
48
|
+
}
|
49
|
+
}
|
50
|
+
async config() {
|
29
51
|
if (this.trigger === undefined || this.triggerId === undefined)
|
30
52
|
throw new Error('Trigger not configured');
|
31
53
|
if (this.notification === undefined || this.notificationId === undefined)
|
32
54
|
throw new Error('Notification not configured');
|
33
55
|
if (this.network === undefined)
|
34
56
|
throw new Error('Network not configured');
|
57
|
+
await this.validate();
|
35
58
|
return {
|
36
59
|
name: this.name,
|
37
60
|
network: this.network,
|
@@ -0,0 +1,47 @@
|
|
1
|
+
import { AlertBuilder } from "../alertbuilder.js";
|
2
|
+
import { DeNotifyClient } from "../denotifyclient.js";
|
3
|
+
import { FunctionBuilder } from "../functionbuilder.js";
|
4
|
+
import { FilterBuilder } from "../util/filter.js";
|
5
|
+
// Simple App to demonstrate usage. Created a balance monitoring alert, updates it and deletes it
|
6
|
+
async function main() {
|
7
|
+
const api = await DeNotifyClient.create({
|
8
|
+
email: process.env.EMAIL,
|
9
|
+
password: process.env.PASSWORD,
|
10
|
+
});
|
11
|
+
const network = 'avalanche';
|
12
|
+
const address = '0x26985888d5b7019ff2A7444fB567D8F386c3b538';
|
13
|
+
const myAddress = '0x7601630eC802952ba1ED2B6e4db16F699A0a5A87';
|
14
|
+
const { abi } = await api.getAbi(network, address);
|
15
|
+
const webhook = process.env.DISCORD_WEBHOOK;
|
16
|
+
const builder = FunctionBuilder.create(api);
|
17
|
+
await builder.addFunction(address, 'getBalance', [myAddress], abi);
|
18
|
+
// Create the Balance Monitor alert
|
19
|
+
const alert = await AlertBuilder.create('Test Alert')
|
20
|
+
.onNetwork('avalanche')
|
21
|
+
.withTrigger('PollFunctionV2', {
|
22
|
+
timeBase: 'time',
|
23
|
+
timePeriod: '100s',
|
24
|
+
functions: builder.get(),
|
25
|
+
triggerOn: 'always',
|
26
|
+
})
|
27
|
+
.withNotification('Discord', {
|
28
|
+
url: webhook,
|
29
|
+
message: `Your avax balance is [{func_0_ret_0 / 1e18}](https://snowtrace.io/address/${myAddress})`,
|
30
|
+
})
|
31
|
+
.config();
|
32
|
+
// Create the alert with the API
|
33
|
+
const triggerId = await api.createAlert(alert);
|
34
|
+
console.log(triggerId);
|
35
|
+
// Update the period to every 10s
|
36
|
+
await api.updateTrigger(triggerId, { timePeriod: '10s' });
|
37
|
+
// Update the Filter using the filter builder
|
38
|
+
const filter = FilterBuilder.new()
|
39
|
+
.addCondition('WHERE', 'func_0_ret_0', 'Number', 'gt', 3)
|
40
|
+
.finalise();
|
41
|
+
await api.updateTrigger(13, { triggerOn: 'filter', filter, filterVersion: FilterBuilder.version() });
|
42
|
+
// Delete the filter in 10s
|
43
|
+
setTimeout(async () => {
|
44
|
+
await api.deleteAlert(triggerId);
|
45
|
+
}, 10 * 1000);
|
46
|
+
}
|
47
|
+
main();
|
package/dist/functionbuilder.js
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import { ethers } from "ethers";
|
2
|
+
import * as yup from 'yup';
|
2
3
|
export class FunctionBuilder {
|
3
4
|
api;
|
4
5
|
data = [];
|
@@ -27,4 +28,12 @@ export class FunctionBuilder {
|
|
27
28
|
get() {
|
28
29
|
return this.data;
|
29
30
|
}
|
31
|
+
static schema() {
|
32
|
+
return yup.array().of(yup.object({
|
33
|
+
address: yup.string().required(),
|
34
|
+
bytecode: yup.string().matches(/^(0x)?([0-9a-fA-F]{2})*$/).required(),
|
35
|
+
abiHash: yup.string().matches(/^[A-Fa-f0-9]{64}/).required(),
|
36
|
+
function: yup.string().required()
|
37
|
+
}));
|
38
|
+
}
|
30
39
|
}
|
package/dist/index.js
CHANGED
@@ -11,10 +11,29 @@ var followRedirects = require('follow-redirects');
|
|
11
11
|
var zlib = require('zlib');
|
12
12
|
var stream = require('stream');
|
13
13
|
var EventEmitter = require('events');
|
14
|
+
var yup = require('yup');
|
14
15
|
var ethers = require('ethers');
|
15
16
|
|
16
17
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
17
18
|
|
19
|
+
function _interopNamespace(e) {
|
20
|
+
if (e && e.__esModule) return e;
|
21
|
+
var n = Object.create(null);
|
22
|
+
if (e) {
|
23
|
+
Object.keys(e).forEach(function (k) {
|
24
|
+
if (k !== 'default') {
|
25
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
26
|
+
Object.defineProperty(n, k, d.get ? d : {
|
27
|
+
enumerable: true,
|
28
|
+
get: function () { return e[k]; }
|
29
|
+
});
|
30
|
+
}
|
31
|
+
});
|
32
|
+
}
|
33
|
+
n["default"] = e;
|
34
|
+
return Object.freeze(n);
|
35
|
+
}
|
36
|
+
|
18
37
|
var FormData__default = /*#__PURE__*/_interopDefaultLegacy(FormData$1);
|
19
38
|
var url__default = /*#__PURE__*/_interopDefaultLegacy(url);
|
20
39
|
var http__default = /*#__PURE__*/_interopDefaultLegacy(http);
|
@@ -24,6 +43,7 @@ var followRedirects__default = /*#__PURE__*/_interopDefaultLegacy(followRedirect
|
|
24
43
|
var zlib__default = /*#__PURE__*/_interopDefaultLegacy(zlib);
|
25
44
|
var stream__default = /*#__PURE__*/_interopDefaultLegacy(stream);
|
26
45
|
var EventEmitter__default = /*#__PURE__*/_interopDefaultLegacy(EventEmitter);
|
46
|
+
var yup__namespace = /*#__PURE__*/_interopNamespace(yup);
|
27
47
|
|
28
48
|
function bind(fn, thisArg) {
|
29
49
|
return function wrap() {
|
@@ -4075,6 +4095,16 @@ class NotifyDiscordWebhook {
|
|
4075
4095
|
notify: config
|
4076
4096
|
};
|
4077
4097
|
}
|
4098
|
+
static validateCreate(options) {
|
4099
|
+
const urlRegex = /^(https?|ftp):\/\/(-\.)?([^\s/?\.#]+\.?)+([^\s\.?#]+)?(\?\S*)?$/;
|
4100
|
+
const schema = yup__namespace.object({
|
4101
|
+
url: yup__namespace.string().matches(urlRegex, 'url is not a valid url').required(),
|
4102
|
+
username: yup__namespace.string(),
|
4103
|
+
avatar_url: yup__namespace.string().matches(urlRegex, 'url is not a valid url'),
|
4104
|
+
message: yup__namespace.string().required()
|
4105
|
+
});
|
4106
|
+
return schema.validate(options);
|
4107
|
+
}
|
4078
4108
|
}
|
4079
4109
|
|
4080
4110
|
class Notification {
|
@@ -4096,6 +4126,19 @@ class HandlerFunctionCall {
|
|
4096
4126
|
handler: config
|
4097
4127
|
};
|
4098
4128
|
}
|
4129
|
+
static validateCreate(options) {
|
4130
|
+
const requiredWhenConditional = ([condition], schema) => condition === 'true' ? schema.notRequired() : schema.required();
|
4131
|
+
const schema = yup__namespace.object({
|
4132
|
+
address: yup__namespace.string().required(),
|
4133
|
+
abi: yup__namespace.array().required(),
|
4134
|
+
nBlocks: yup__namespace.number().min(10),
|
4135
|
+
condition: yup__namespace.string().oneOf(['>', '>=', '<', '<=', '=', 'true']),
|
4136
|
+
constant: yup__namespace.number().min(0).when('condition', requiredWhenConditional),
|
4137
|
+
responseArgIndex: yup__namespace.number().min(0).when('condition', requiredWhenConditional),
|
4138
|
+
responseArgDecimals: yup__namespace.number().min(0).when('condition', requiredWhenConditional),
|
4139
|
+
});
|
4140
|
+
return schema.validate(options);
|
4141
|
+
}
|
4099
4142
|
}
|
4100
4143
|
|
4101
4144
|
const HANDLER_ONCHAIN_EVENT_V1_RAW_ID = 'handler_onchain_event';
|
@@ -4109,6 +4152,219 @@ class HandlerOnchainEvent {
|
|
4109
4152
|
handler: config
|
4110
4153
|
};
|
4111
4154
|
}
|
4155
|
+
static validateCreate(options) {
|
4156
|
+
const requiredWhenConditional = ([condition], schema) => condition === 'true' ? schema.notRequired() : schema.required();
|
4157
|
+
const onchainEventSchema = yup__namespace.object({
|
4158
|
+
address: yup__namespace.string().required(),
|
4159
|
+
event: yup__namespace.string().required(),
|
4160
|
+
abi: yup__namespace.array().required(),
|
4161
|
+
condition: yup__namespace.string().oneOf(['>', '>=', '<', '<=', '=', 'true']),
|
4162
|
+
constant: yup__namespace.number().min(0).when('condition', requiredWhenConditional),
|
4163
|
+
paramsIndex: yup__namespace.number().min(0).when('condition', requiredWhenConditional),
|
4164
|
+
paramsDecimals: yup__namespace.number().min(0).when('condition', requiredWhenConditional),
|
4165
|
+
});
|
4166
|
+
return onchainEventSchema.validate(options);
|
4167
|
+
}
|
4168
|
+
static validateUpdate(options) {
|
4169
|
+
}
|
4170
|
+
}
|
4171
|
+
|
4172
|
+
class FunctionBuilder {
|
4173
|
+
api;
|
4174
|
+
data = [];
|
4175
|
+
constructor(api) {
|
4176
|
+
this.api = api;
|
4177
|
+
}
|
4178
|
+
static create(api) {
|
4179
|
+
return new FunctionBuilder(api);
|
4180
|
+
}
|
4181
|
+
getAbiHash(abi) {
|
4182
|
+
if (this.api === undefined)
|
4183
|
+
throw new Error('FunctionBuilder must be initialised with api');
|
4184
|
+
return this.api.getAbiHash(abi);
|
4185
|
+
}
|
4186
|
+
async addFunction(address, func, args, abi) {
|
4187
|
+
const contract = new ethers.ethers.Contract(address, abi);
|
4188
|
+
contract.interface.encodeFunctionData(func, args);
|
4189
|
+
this.data.push({
|
4190
|
+
address,
|
4191
|
+
bytecode: contract.interface.encodeFunctionData(func, args),
|
4192
|
+
abiHash: await this.getAbiHash(abi),
|
4193
|
+
function: func,
|
4194
|
+
});
|
4195
|
+
return this;
|
4196
|
+
}
|
4197
|
+
get() {
|
4198
|
+
return this.data;
|
4199
|
+
}
|
4200
|
+
static schema() {
|
4201
|
+
return yup__namespace.array().of(yup__namespace.object({
|
4202
|
+
address: yup__namespace.string().required(),
|
4203
|
+
bytecode: yup__namespace.string().matches(/^(0x)?([0-9a-fA-F]{2})*$/).required(),
|
4204
|
+
abiHash: yup__namespace.string().matches(/^[A-Fa-f0-9]{64}/).required(),
|
4205
|
+
function: yup__namespace.string().required()
|
4206
|
+
}));
|
4207
|
+
}
|
4208
|
+
}
|
4209
|
+
|
4210
|
+
class Filter {
|
4211
|
+
static version() {
|
4212
|
+
return 'v1';
|
4213
|
+
}
|
4214
|
+
static execute(record, groupsIn, version = 'v1') {
|
4215
|
+
// Deep copy to so we can edit the object during recursion
|
4216
|
+
const groups = JSON.parse(JSON.stringify(groupsIn));
|
4217
|
+
let lhs = true;
|
4218
|
+
for (const group of groups) {
|
4219
|
+
const rhs = Filter.process(record, group.conditions);
|
4220
|
+
lhs = Filter.execLogic(lhs, group.logic, rhs);
|
4221
|
+
}
|
4222
|
+
return lhs;
|
4223
|
+
}
|
4224
|
+
static process(record, conditions, lhs) {
|
4225
|
+
const condition = conditions.shift();
|
4226
|
+
if (!condition) {
|
4227
|
+
if (lhs === undefined)
|
4228
|
+
return true;
|
4229
|
+
return lhs;
|
4230
|
+
}
|
4231
|
+
// lhs <logic> rhs
|
4232
|
+
const rhs = Filter.execCondition(record, condition);
|
4233
|
+
if (lhs === undefined || condition.logic === 'WHERE')
|
4234
|
+
return Filter.process(record, conditions, rhs);
|
4235
|
+
if (condition.logic === undefined)
|
4236
|
+
throw new Error('Invalid filter. Condition must have logic value');
|
4237
|
+
const next = Filter.execLogic(lhs, condition.logic, rhs);
|
4238
|
+
return Filter.process(record, conditions, next);
|
4239
|
+
}
|
4240
|
+
static execLogic(lhs, logic, rhs) {
|
4241
|
+
switch (logic) {
|
4242
|
+
case 'AND': return lhs && rhs;
|
4243
|
+
case 'OR': return lhs || rhs;
|
4244
|
+
case 'XOR': return (lhs || rhs) && !(lhs && rhs);
|
4245
|
+
case 'NAND': return !(lhs && rhs);
|
4246
|
+
case 'NOR': return !(lhs && rhs);
|
4247
|
+
case 'WHERE': return rhs;
|
4248
|
+
default: throw new Error(`Invalid Filter. Bad logic value: ${logic}`);
|
4249
|
+
}
|
4250
|
+
}
|
4251
|
+
static execCondition(record, condition) {
|
4252
|
+
const data = record[condition.key];
|
4253
|
+
if (condition.type === 'Number') {
|
4254
|
+
if (typeof condition.constant !== 'number' || typeof data !== 'number')
|
4255
|
+
throw new Error(`Invalid filter. Type miss-match. Type: ${condition.type}, Variable: ${condition.constant}, Data: ${data}`);
|
4256
|
+
const n = data;
|
4257
|
+
const constant = condition.constant;
|
4258
|
+
switch (condition.operation) {
|
4259
|
+
case 'eq': return n === constant;
|
4260
|
+
case '!eq': return n !== constant;
|
4261
|
+
case 'gt': return n > constant;
|
4262
|
+
case 'gte': return n >= constant;
|
4263
|
+
case 'lt': return n < constant;
|
4264
|
+
case 'lte': return n <= constant;
|
4265
|
+
default: throw new Error('Invalid Filter. Operation does not match type');
|
4266
|
+
}
|
4267
|
+
}
|
4268
|
+
if (condition.type === 'String') {
|
4269
|
+
if (typeof condition.constant !== 'string' || typeof data !== 'string')
|
4270
|
+
throw new Error(`Invalid filter. Type miss-match. Type: ${condition.type}, Variable: ${condition.constant}, Data: ${data}`);
|
4271
|
+
const str = data;
|
4272
|
+
const constant = condition.constant;
|
4273
|
+
switch (condition.operation) {
|
4274
|
+
case 'contains': return str.includes(constant);
|
4275
|
+
case '!contains': return !str.includes(constant);
|
4276
|
+
case 'is': return str === constant;
|
4277
|
+
case '!is': return str !== constant;
|
4278
|
+
case 'isEmpty': return str === '';
|
4279
|
+
case '!isEmpty': return str !== '';
|
4280
|
+
default: throw new Error('Invalid Filter. Operation does not match type');
|
4281
|
+
}
|
4282
|
+
}
|
4283
|
+
if (condition.type === 'Address') {
|
4284
|
+
if (typeof condition.constant !== 'string' || typeof data !== 'string')
|
4285
|
+
throw new Error(`Invalid filter. Type miss-match. Type: ${condition.type}, Variable: ${condition.constant}, Data: ${data}`);
|
4286
|
+
const str = data;
|
4287
|
+
const constant = condition.constant;
|
4288
|
+
switch (condition.operation) {
|
4289
|
+
case 'contains': return str.toLowerCase().includes(constant.toLowerCase());
|
4290
|
+
case '!contains': return !str.toLowerCase().includes(constant.toLowerCase());
|
4291
|
+
case 'is': return str.toLowerCase() === constant.toLowerCase();
|
4292
|
+
case '!is': return str.toLowerCase() !== constant.toLowerCase();
|
4293
|
+
case 'isEmpty': return str === '';
|
4294
|
+
case '!isEmpty': return str !== '';
|
4295
|
+
default: throw new Error('Invalid Filter. Operation does not match type');
|
4296
|
+
}
|
4297
|
+
}
|
4298
|
+
throw new Error('Invalid Filter. Unknown Type');
|
4299
|
+
}
|
4300
|
+
}
|
4301
|
+
class FilterBuilder {
|
4302
|
+
groups = [];
|
4303
|
+
constructor() {
|
4304
|
+
this.newGroup('WHERE');
|
4305
|
+
}
|
4306
|
+
static version() {
|
4307
|
+
return Filter.version();
|
4308
|
+
}
|
4309
|
+
static new() {
|
4310
|
+
return new FilterBuilder();
|
4311
|
+
}
|
4312
|
+
newGroup(logic) {
|
4313
|
+
if (this.groups.length !== 0 && logic === 'WHERE')
|
4314
|
+
throw new Error('Only the first groups can start with "WHERE"');
|
4315
|
+
this.groups.push({ logic: 'WHERE', conditions: [] });
|
4316
|
+
return this;
|
4317
|
+
}
|
4318
|
+
addCondition(logic, key, type, operation, constant) {
|
4319
|
+
const group = this.groups[this.groups.length - 1];
|
4320
|
+
if (group.conditions.length === 0 && logic !== 'WHERE')
|
4321
|
+
throw new Error('Logic for the first condition of a group must be "WHERE"');
|
4322
|
+
if (group.conditions.length > 0 && logic === 'WHERE')
|
4323
|
+
throw new Error('Only the first condition of a group can be "WHERE"');
|
4324
|
+
group.conditions.push({
|
4325
|
+
logic,
|
4326
|
+
key,
|
4327
|
+
type,
|
4328
|
+
operation,
|
4329
|
+
constant
|
4330
|
+
});
|
4331
|
+
return this;
|
4332
|
+
}
|
4333
|
+
finalise() {
|
4334
|
+
for (const group of this.groups) {
|
4335
|
+
if (group.conditions.length === 0)
|
4336
|
+
throw new Error('Bad Filter. All Groups must have atleast one condition');
|
4337
|
+
}
|
4338
|
+
return this.groups;
|
4339
|
+
}
|
4340
|
+
static schema() {
|
4341
|
+
const logic = yup__namespace.string().oneOf(['AND', 'OR', 'XOR', 'NAND', 'NOR', 'WHERE']).required();
|
4342
|
+
const condition = yup__namespace.object({
|
4343
|
+
logic,
|
4344
|
+
key: yup__namespace.string().required(),
|
4345
|
+
type: yup__namespace.string().oneOf(['String', 'Address', 'Number']).required(),
|
4346
|
+
operation: yup__namespace.string().when('type', ([type], schema) => {
|
4347
|
+
switch (type) {
|
4348
|
+
case 'String': return schema.oneOf(['contains', '!contains', 'is', '!is', 'isEmpty', '!isEmpty']);
|
4349
|
+
case 'Address': return schema.oneOf(['is', '!is', 'isEmpty', '!isEmpty']);
|
4350
|
+
case 'Number': return schema.oneOf(['String', 'Address', 'Number']);
|
4351
|
+
default: throw new Error('Invalid Filter Data Type');
|
4352
|
+
}
|
4353
|
+
}),
|
4354
|
+
constant: yup__namespace.mixed().when('type', ([type]) => {
|
4355
|
+
switch (type) {
|
4356
|
+
case 'String': return yup__namespace.string();
|
4357
|
+
case 'Address': return yup__namespace.string();
|
4358
|
+
case 'Number': return yup__namespace.number();
|
4359
|
+
default: throw new Error('Invalid Filter Data Type');
|
4360
|
+
}
|
4361
|
+
}),
|
4362
|
+
});
|
4363
|
+
return yup__namespace.array().of(yup__namespace.object({
|
4364
|
+
logic,
|
4365
|
+
conditions: yup__namespace.array().of(condition)
|
4366
|
+
}));
|
4367
|
+
}
|
4112
4368
|
}
|
4113
4369
|
|
4114
4370
|
const HANDLER_FUNCTION_CALL_V2_RAW_ID = 'handler_function_call_v2';
|
@@ -4122,6 +4378,32 @@ class HandlerFunctionCallV2 {
|
|
4122
4378
|
handler: config
|
4123
4379
|
};
|
4124
4380
|
}
|
4381
|
+
static validateCreate(options) {
|
4382
|
+
const timePeriodRegex = /^(\d+)([SMHD])$/i;
|
4383
|
+
const onchainEventSchema = yup__namespace.object({
|
4384
|
+
timeBase: yup__namespace.string().oneOf(['blocks', 'time']).required(),
|
4385
|
+
// Blocks config
|
4386
|
+
nBlocks: yup__namespace.number()
|
4387
|
+
.min(10)
|
4388
|
+
.when('timeBase', ([timeBase], schema) => timeBase === 'blocks' ? schema.required() : schema),
|
4389
|
+
// Or Time config
|
4390
|
+
timePeriod: yup__namespace.string()
|
4391
|
+
.matches(timePeriodRegex, 'timePeriod must be of the format n[s|m|h|d], eg 10s for 10 seconds')
|
4392
|
+
.when('timeBase', ([timeBase], schema) => timeBase === 'time' ? schema.required() : schema),
|
4393
|
+
startTime: yup__namespace.number().default(0),
|
4394
|
+
// Debouncing. Default is 0
|
4395
|
+
debounceCount: yup__namespace.number(),
|
4396
|
+
// Functions
|
4397
|
+
functions: FunctionBuilder.schema(),
|
4398
|
+
// Trigger
|
4399
|
+
triggerOn: yup__namespace.string().oneOf(['always', 'filter']).default('always'),
|
4400
|
+
latch: yup__namespace.boolean().default(false),
|
4401
|
+
// Filter
|
4402
|
+
filterVersion: yup__namespace.string().when('triggerOn', ([triggerOn], schema) => triggerOn === 'filter' ? schema.required() : schema),
|
4403
|
+
filter: FilterBuilder.schema().when('triggerOn', ([triggerOn], schema) => triggerOn === 'filter' ? schema.required() : schema),
|
4404
|
+
});
|
4405
|
+
return onchainEventSchema.validate(options);
|
4406
|
+
}
|
4125
4407
|
}
|
4126
4408
|
|
4127
4409
|
class Trigger {
|
@@ -4129,7 +4411,7 @@ class Trigger {
|
|
4129
4411
|
switch (id) {
|
4130
4412
|
case 'PollFunctionV2': return HandlerFunctionCallV2.SimpleToRaw(name, network, config);
|
4131
4413
|
case 'PollFunctionV1': return HandlerFunctionCall.SimpleToRaw(name, network, config);
|
4132
|
-
case '
|
4414
|
+
case 'OnchainEventV1': return HandlerOnchainEvent.SimpleToRaw(name, network, config);
|
4133
4415
|
default:
|
4134
4416
|
throw new Error('Invalid Trigger ID');
|
4135
4417
|
}
|
@@ -4265,6 +4547,13 @@ class AlertBuilder {
|
|
4265
4547
|
this.network = network;
|
4266
4548
|
return this;
|
4267
4549
|
}
|
4550
|
+
/**
|
4551
|
+
* Call withTrigger with one of the TriggerConfig types:
|
4552
|
+
* PollFunctionV2 | PollFunctionV1 | OnchainEventV1
|
4553
|
+
* @param id Simple ID
|
4554
|
+
* @param options Desired trigger configuration
|
4555
|
+
* @returns self for piping
|
4556
|
+
*/
|
4268
4557
|
withTrigger(id, options) {
|
4269
4558
|
this.triggerId = id;
|
4270
4559
|
this.trigger = options;
|
@@ -4275,13 +4564,25 @@ class AlertBuilder {
|
|
4275
4564
|
this.notification = options;
|
4276
4565
|
return this;
|
4277
4566
|
}
|
4278
|
-
|
4567
|
+
async validate() {
|
4568
|
+
// Validate trigger
|
4569
|
+
switch (this.triggerId) {
|
4570
|
+
case 'OnchainEventV1': return HandlerOnchainEvent.validateCreate(this.trigger);
|
4571
|
+
case 'PollFunctionV1': return HandlerFunctionCall.validateCreate(this.trigger);
|
4572
|
+
case 'PollFunctionV2': return HandlerFunctionCallV2.validateCreate(this.trigger);
|
4573
|
+
}
|
4574
|
+
switch (this.notificationId) {
|
4575
|
+
case 'Discord': return NotifyDiscordWebhook.validateCreate(this.notification);
|
4576
|
+
}
|
4577
|
+
}
|
4578
|
+
async config() {
|
4279
4579
|
if (this.trigger === undefined || this.triggerId === undefined)
|
4280
4580
|
throw new Error('Trigger not configured');
|
4281
4581
|
if (this.notification === undefined || this.notificationId === undefined)
|
4282
4582
|
throw new Error('Notification not configured');
|
4283
4583
|
if (this.network === undefined)
|
4284
4584
|
throw new Error('Network not configured');
|
4585
|
+
await this.validate();
|
4285
4586
|
return {
|
4286
4587
|
name: this.name,
|
4287
4588
|
network: this.network,
|
@@ -4293,168 +4594,6 @@ class AlertBuilder {
|
|
4293
4594
|
}
|
4294
4595
|
}
|
4295
4596
|
|
4296
|
-
class FunctionBuilder {
|
4297
|
-
api;
|
4298
|
-
data = [];
|
4299
|
-
constructor(api) {
|
4300
|
-
this.api = api;
|
4301
|
-
}
|
4302
|
-
static create(api) {
|
4303
|
-
return new FunctionBuilder(api);
|
4304
|
-
}
|
4305
|
-
getAbiHash(abi) {
|
4306
|
-
if (this.api === undefined)
|
4307
|
-
throw new Error('FunctionBuilder must be initialised with api');
|
4308
|
-
return this.api.getAbiHash(abi);
|
4309
|
-
}
|
4310
|
-
async addFunction(address, func, args, abi) {
|
4311
|
-
const contract = new ethers.ethers.Contract(address, abi);
|
4312
|
-
contract.interface.encodeFunctionData(func, args);
|
4313
|
-
this.data.push({
|
4314
|
-
address,
|
4315
|
-
bytecode: contract.interface.encodeFunctionData(func, args),
|
4316
|
-
abiHash: await this.getAbiHash(abi),
|
4317
|
-
function: func,
|
4318
|
-
});
|
4319
|
-
return this;
|
4320
|
-
}
|
4321
|
-
get() {
|
4322
|
-
return this.data;
|
4323
|
-
}
|
4324
|
-
}
|
4325
|
-
|
4326
|
-
class Filter {
|
4327
|
-
static version() {
|
4328
|
-
return 'v1';
|
4329
|
-
}
|
4330
|
-
static execute(record, groupsIn, version = 'v1') {
|
4331
|
-
// Deep copy to so we can edit the object during recursion
|
4332
|
-
const groups = JSON.parse(JSON.stringify(groupsIn));
|
4333
|
-
let lhs = true;
|
4334
|
-
for (const group of groups) {
|
4335
|
-
const rhs = Filter.process(record, group.conditions);
|
4336
|
-
lhs = Filter.execLogic(lhs, group.logic, rhs);
|
4337
|
-
}
|
4338
|
-
return lhs;
|
4339
|
-
}
|
4340
|
-
static process(record, conditions, lhs) {
|
4341
|
-
const condition = conditions.shift();
|
4342
|
-
if (!condition) {
|
4343
|
-
if (lhs === undefined)
|
4344
|
-
return true;
|
4345
|
-
return lhs;
|
4346
|
-
}
|
4347
|
-
// lhs <logic> rhs
|
4348
|
-
const rhs = Filter.execCondition(record, condition);
|
4349
|
-
if (lhs === undefined || condition.logic === 'WHERE')
|
4350
|
-
return Filter.process(record, conditions, rhs);
|
4351
|
-
if (condition.logic === undefined)
|
4352
|
-
throw new Error('Invalid filter. Condition must have logic value');
|
4353
|
-
const next = Filter.execLogic(lhs, condition.logic, rhs);
|
4354
|
-
return Filter.process(record, conditions, next);
|
4355
|
-
}
|
4356
|
-
static execLogic(lhs, logic, rhs) {
|
4357
|
-
switch (logic) {
|
4358
|
-
case 'AND': return lhs && rhs;
|
4359
|
-
case 'OR': return lhs || rhs;
|
4360
|
-
case 'XOR': return (lhs || rhs) && !(lhs && rhs);
|
4361
|
-
case 'NAND': return !(lhs && rhs);
|
4362
|
-
case 'NOR': return !(lhs && rhs);
|
4363
|
-
case 'WHERE': return rhs;
|
4364
|
-
default: throw new Error(`Invalid Filter. Bad logic value: ${logic}`);
|
4365
|
-
}
|
4366
|
-
}
|
4367
|
-
static execCondition(record, condition) {
|
4368
|
-
const data = record[condition.key];
|
4369
|
-
if (condition.type === 'Number') {
|
4370
|
-
if (typeof condition.constant !== 'number' || typeof data !== 'number')
|
4371
|
-
throw new Error(`Invalid filter. Type miss-match. Type: ${condition.type}, Variable: ${condition.constant}, Data: ${data}`);
|
4372
|
-
const n = data;
|
4373
|
-
const constant = condition.constant;
|
4374
|
-
switch (condition.operation) {
|
4375
|
-
case 'eq': return n === constant;
|
4376
|
-
case '!eq': return n !== constant;
|
4377
|
-
case 'gt': return n > constant;
|
4378
|
-
case 'gte': return n >= constant;
|
4379
|
-
case 'lt': return n < constant;
|
4380
|
-
case 'lte': return n <= constant;
|
4381
|
-
default: throw new Error('Invalid Filter. Operation does not match type');
|
4382
|
-
}
|
4383
|
-
}
|
4384
|
-
if (condition.type === 'String') {
|
4385
|
-
if (typeof condition.constant !== 'string' || typeof data !== 'string')
|
4386
|
-
throw new Error(`Invalid filter. Type miss-match. Type: ${condition.type}, Variable: ${condition.constant}, Data: ${data}`);
|
4387
|
-
const str = data;
|
4388
|
-
const constant = condition.constant;
|
4389
|
-
switch (condition.operation) {
|
4390
|
-
case 'contains': return str.includes(constant);
|
4391
|
-
case '!contains': return !str.includes(constant);
|
4392
|
-
case 'is': return str === constant;
|
4393
|
-
case '!is': return str !== constant;
|
4394
|
-
case 'isEmpty': return str === '';
|
4395
|
-
case '!isEmpty': return str !== '';
|
4396
|
-
default: throw new Error('Invalid Filter. Operation does not match type');
|
4397
|
-
}
|
4398
|
-
}
|
4399
|
-
if (condition.type === 'Address') {
|
4400
|
-
if (typeof condition.constant !== 'string' || typeof data !== 'string')
|
4401
|
-
throw new Error(`Invalid filter. Type miss-match. Type: ${condition.type}, Variable: ${condition.constant}, Data: ${data}`);
|
4402
|
-
const str = data;
|
4403
|
-
const constant = condition.constant;
|
4404
|
-
switch (condition.operation) {
|
4405
|
-
case 'contains': return str.toLowerCase().includes(constant.toLowerCase());
|
4406
|
-
case '!contains': return !str.toLowerCase().includes(constant.toLowerCase());
|
4407
|
-
case 'is': return str.toLowerCase() === constant.toLowerCase();
|
4408
|
-
case '!is': return str.toLowerCase() !== constant.toLowerCase();
|
4409
|
-
case 'isEmpty': return str === '';
|
4410
|
-
case '!isEmpty': return str !== '';
|
4411
|
-
default: throw new Error('Invalid Filter. Operation does not match type');
|
4412
|
-
}
|
4413
|
-
}
|
4414
|
-
throw new Error('Invalid Filter. Unknown Type');
|
4415
|
-
}
|
4416
|
-
}
|
4417
|
-
class FilterBuilder {
|
4418
|
-
groups = [];
|
4419
|
-
constructor() {
|
4420
|
-
this.newGroup('WHERE');
|
4421
|
-
}
|
4422
|
-
static version() {
|
4423
|
-
return Filter.version();
|
4424
|
-
}
|
4425
|
-
static new() {
|
4426
|
-
return new FilterBuilder();
|
4427
|
-
}
|
4428
|
-
newGroup(logic) {
|
4429
|
-
if (this.groups.length !== 0 && logic === 'WHERE')
|
4430
|
-
throw new Error('Only the first groups can start with "WHERE"');
|
4431
|
-
this.groups.push({ logic: 'WHERE', conditions: [] });
|
4432
|
-
return this;
|
4433
|
-
}
|
4434
|
-
addCondition(logic, key, type, operation, constant) {
|
4435
|
-
const group = this.groups[this.groups.length - 1];
|
4436
|
-
if (group.conditions.length === 0 && logic !== 'WHERE')
|
4437
|
-
throw new Error('Logic for the first condition of a group must be "WHERE"');
|
4438
|
-
if (group.conditions.length > 0 && logic === 'WHERE')
|
4439
|
-
throw new Error('Only the first condition of a group can be "WHERE"');
|
4440
|
-
group.conditions.push({
|
4441
|
-
logic,
|
4442
|
-
key,
|
4443
|
-
type,
|
4444
|
-
operation,
|
4445
|
-
constant
|
4446
|
-
});
|
4447
|
-
return this;
|
4448
|
-
}
|
4449
|
-
finalise() {
|
4450
|
-
for (const group of this.groups) {
|
4451
|
-
if (group.conditions.length === 0)
|
4452
|
-
throw new Error('Bad Filter. All Groups must have atleast one condition');
|
4453
|
-
}
|
4454
|
-
return this.groups;
|
4455
|
-
}
|
4456
|
-
}
|
4457
|
-
|
4458
4597
|
var index$1 = /*#__PURE__*/Object.freeze({
|
4459
4598
|
__proto__: null,
|
4460
4599
|
Trigger: Trigger,
|