denotify-client 1.1.3 → 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 +4617 -7
- 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/index.mjs
CHANGED
@@ -9,6 +9,7 @@ import followRedirects from 'follow-redirects';
|
|
9
9
|
import zlib from 'zlib';
|
10
10
|
import stream, { Readable } from 'stream';
|
11
11
|
import EventEmitter from 'events';
|
12
|
+
import * as yup from 'yup';
|
12
13
|
import { ethers } from 'ethers';
|
13
14
|
|
14
15
|
function bind(fn, thisArg) {
|
@@ -4061,6 +4062,16 @@ class NotifyDiscordWebhook {
|
|
4061
4062
|
notify: config
|
4062
4063
|
};
|
4063
4064
|
}
|
4065
|
+
static validateCreate(options) {
|
4066
|
+
const urlRegex = /^(https?|ftp):\/\/(-\.)?([^\s/?\.#]+\.?)+([^\s\.?#]+)?(\?\S*)?$/;
|
4067
|
+
const schema = yup.object({
|
4068
|
+
url: yup.string().matches(urlRegex, 'url is not a valid url').required(),
|
4069
|
+
username: yup.string(),
|
4070
|
+
avatar_url: yup.string().matches(urlRegex, 'url is not a valid url'),
|
4071
|
+
message: yup.string().required()
|
4072
|
+
});
|
4073
|
+
return schema.validate(options);
|
4074
|
+
}
|
4064
4075
|
}
|
4065
4076
|
|
4066
4077
|
class Notification {
|
@@ -4082,6 +4093,19 @@ class HandlerFunctionCall {
|
|
4082
4093
|
handler: config
|
4083
4094
|
};
|
4084
4095
|
}
|
4096
|
+
static validateCreate(options) {
|
4097
|
+
const requiredWhenConditional = ([condition], schema) => condition === 'true' ? schema.notRequired() : schema.required();
|
4098
|
+
const schema = yup.object({
|
4099
|
+
address: yup.string().required(),
|
4100
|
+
abi: yup.array().required(),
|
4101
|
+
nBlocks: yup.number().min(10),
|
4102
|
+
condition: yup.string().oneOf(['>', '>=', '<', '<=', '=', 'true']),
|
4103
|
+
constant: yup.number().min(0).when('condition', requiredWhenConditional),
|
4104
|
+
responseArgIndex: yup.number().min(0).when('condition', requiredWhenConditional),
|
4105
|
+
responseArgDecimals: yup.number().min(0).when('condition', requiredWhenConditional),
|
4106
|
+
});
|
4107
|
+
return schema.validate(options);
|
4108
|
+
}
|
4085
4109
|
}
|
4086
4110
|
|
4087
4111
|
const HANDLER_ONCHAIN_EVENT_V1_RAW_ID = 'handler_onchain_event';
|
@@ -4095,6 +4119,219 @@ class HandlerOnchainEvent {
|
|
4095
4119
|
handler: config
|
4096
4120
|
};
|
4097
4121
|
}
|
4122
|
+
static validateCreate(options) {
|
4123
|
+
const requiredWhenConditional = ([condition], schema) => condition === 'true' ? schema.notRequired() : schema.required();
|
4124
|
+
const onchainEventSchema = yup.object({
|
4125
|
+
address: yup.string().required(),
|
4126
|
+
event: yup.string().required(),
|
4127
|
+
abi: yup.array().required(),
|
4128
|
+
condition: yup.string().oneOf(['>', '>=', '<', '<=', '=', 'true']),
|
4129
|
+
constant: yup.number().min(0).when('condition', requiredWhenConditional),
|
4130
|
+
paramsIndex: yup.number().min(0).when('condition', requiredWhenConditional),
|
4131
|
+
paramsDecimals: yup.number().min(0).when('condition', requiredWhenConditional),
|
4132
|
+
});
|
4133
|
+
return onchainEventSchema.validate(options);
|
4134
|
+
}
|
4135
|
+
static validateUpdate(options) {
|
4136
|
+
}
|
4137
|
+
}
|
4138
|
+
|
4139
|
+
class FunctionBuilder {
|
4140
|
+
api;
|
4141
|
+
data = [];
|
4142
|
+
constructor(api) {
|
4143
|
+
this.api = api;
|
4144
|
+
}
|
4145
|
+
static create(api) {
|
4146
|
+
return new FunctionBuilder(api);
|
4147
|
+
}
|
4148
|
+
getAbiHash(abi) {
|
4149
|
+
if (this.api === undefined)
|
4150
|
+
throw new Error('FunctionBuilder must be initialised with api');
|
4151
|
+
return this.api.getAbiHash(abi);
|
4152
|
+
}
|
4153
|
+
async addFunction(address, func, args, abi) {
|
4154
|
+
const contract = new ethers.Contract(address, abi);
|
4155
|
+
contract.interface.encodeFunctionData(func, args);
|
4156
|
+
this.data.push({
|
4157
|
+
address,
|
4158
|
+
bytecode: contract.interface.encodeFunctionData(func, args),
|
4159
|
+
abiHash: await this.getAbiHash(abi),
|
4160
|
+
function: func,
|
4161
|
+
});
|
4162
|
+
return this;
|
4163
|
+
}
|
4164
|
+
get() {
|
4165
|
+
return this.data;
|
4166
|
+
}
|
4167
|
+
static schema() {
|
4168
|
+
return yup.array().of(yup.object({
|
4169
|
+
address: yup.string().required(),
|
4170
|
+
bytecode: yup.string().matches(/^(0x)?([0-9a-fA-F]{2})*$/).required(),
|
4171
|
+
abiHash: yup.string().matches(/^[A-Fa-f0-9]{64}/).required(),
|
4172
|
+
function: yup.string().required()
|
4173
|
+
}));
|
4174
|
+
}
|
4175
|
+
}
|
4176
|
+
|
4177
|
+
class Filter {
|
4178
|
+
static version() {
|
4179
|
+
return 'v1';
|
4180
|
+
}
|
4181
|
+
static execute(record, groupsIn, version = 'v1') {
|
4182
|
+
// Deep copy to so we can edit the object during recursion
|
4183
|
+
const groups = JSON.parse(JSON.stringify(groupsIn));
|
4184
|
+
let lhs = true;
|
4185
|
+
for (const group of groups) {
|
4186
|
+
const rhs = Filter.process(record, group.conditions);
|
4187
|
+
lhs = Filter.execLogic(lhs, group.logic, rhs);
|
4188
|
+
}
|
4189
|
+
return lhs;
|
4190
|
+
}
|
4191
|
+
static process(record, conditions, lhs) {
|
4192
|
+
const condition = conditions.shift();
|
4193
|
+
if (!condition) {
|
4194
|
+
if (lhs === undefined)
|
4195
|
+
return true;
|
4196
|
+
return lhs;
|
4197
|
+
}
|
4198
|
+
// lhs <logic> rhs
|
4199
|
+
const rhs = Filter.execCondition(record, condition);
|
4200
|
+
if (lhs === undefined || condition.logic === 'WHERE')
|
4201
|
+
return Filter.process(record, conditions, rhs);
|
4202
|
+
if (condition.logic === undefined)
|
4203
|
+
throw new Error('Invalid filter. Condition must have logic value');
|
4204
|
+
const next = Filter.execLogic(lhs, condition.logic, rhs);
|
4205
|
+
return Filter.process(record, conditions, next);
|
4206
|
+
}
|
4207
|
+
static execLogic(lhs, logic, rhs) {
|
4208
|
+
switch (logic) {
|
4209
|
+
case 'AND': return lhs && rhs;
|
4210
|
+
case 'OR': return lhs || rhs;
|
4211
|
+
case 'XOR': return (lhs || rhs) && !(lhs && rhs);
|
4212
|
+
case 'NAND': return !(lhs && rhs);
|
4213
|
+
case 'NOR': return !(lhs && rhs);
|
4214
|
+
case 'WHERE': return rhs;
|
4215
|
+
default: throw new Error(`Invalid Filter. Bad logic value: ${logic}`);
|
4216
|
+
}
|
4217
|
+
}
|
4218
|
+
static execCondition(record, condition) {
|
4219
|
+
const data = record[condition.key];
|
4220
|
+
if (condition.type === 'Number') {
|
4221
|
+
if (typeof condition.constant !== 'number' || typeof data !== 'number')
|
4222
|
+
throw new Error(`Invalid filter. Type miss-match. Type: ${condition.type}, Variable: ${condition.constant}, Data: ${data}`);
|
4223
|
+
const n = data;
|
4224
|
+
const constant = condition.constant;
|
4225
|
+
switch (condition.operation) {
|
4226
|
+
case 'eq': return n === constant;
|
4227
|
+
case '!eq': return n !== constant;
|
4228
|
+
case 'gt': return n > constant;
|
4229
|
+
case 'gte': return n >= constant;
|
4230
|
+
case 'lt': return n < constant;
|
4231
|
+
case 'lte': return n <= constant;
|
4232
|
+
default: throw new Error('Invalid Filter. Operation does not match type');
|
4233
|
+
}
|
4234
|
+
}
|
4235
|
+
if (condition.type === 'String') {
|
4236
|
+
if (typeof condition.constant !== 'string' || typeof data !== 'string')
|
4237
|
+
throw new Error(`Invalid filter. Type miss-match. Type: ${condition.type}, Variable: ${condition.constant}, Data: ${data}`);
|
4238
|
+
const str = data;
|
4239
|
+
const constant = condition.constant;
|
4240
|
+
switch (condition.operation) {
|
4241
|
+
case 'contains': return str.includes(constant);
|
4242
|
+
case '!contains': return !str.includes(constant);
|
4243
|
+
case 'is': return str === constant;
|
4244
|
+
case '!is': return str !== constant;
|
4245
|
+
case 'isEmpty': return str === '';
|
4246
|
+
case '!isEmpty': return str !== '';
|
4247
|
+
default: throw new Error('Invalid Filter. Operation does not match type');
|
4248
|
+
}
|
4249
|
+
}
|
4250
|
+
if (condition.type === 'Address') {
|
4251
|
+
if (typeof condition.constant !== 'string' || typeof data !== 'string')
|
4252
|
+
throw new Error(`Invalid filter. Type miss-match. Type: ${condition.type}, Variable: ${condition.constant}, Data: ${data}`);
|
4253
|
+
const str = data;
|
4254
|
+
const constant = condition.constant;
|
4255
|
+
switch (condition.operation) {
|
4256
|
+
case 'contains': return str.toLowerCase().includes(constant.toLowerCase());
|
4257
|
+
case '!contains': return !str.toLowerCase().includes(constant.toLowerCase());
|
4258
|
+
case 'is': return str.toLowerCase() === constant.toLowerCase();
|
4259
|
+
case '!is': return str.toLowerCase() !== constant.toLowerCase();
|
4260
|
+
case 'isEmpty': return str === '';
|
4261
|
+
case '!isEmpty': return str !== '';
|
4262
|
+
default: throw new Error('Invalid Filter. Operation does not match type');
|
4263
|
+
}
|
4264
|
+
}
|
4265
|
+
throw new Error('Invalid Filter. Unknown Type');
|
4266
|
+
}
|
4267
|
+
}
|
4268
|
+
class FilterBuilder {
|
4269
|
+
groups = [];
|
4270
|
+
constructor() {
|
4271
|
+
this.newGroup('WHERE');
|
4272
|
+
}
|
4273
|
+
static version() {
|
4274
|
+
return Filter.version();
|
4275
|
+
}
|
4276
|
+
static new() {
|
4277
|
+
return new FilterBuilder();
|
4278
|
+
}
|
4279
|
+
newGroup(logic) {
|
4280
|
+
if (this.groups.length !== 0 && logic === 'WHERE')
|
4281
|
+
throw new Error('Only the first groups can start with "WHERE"');
|
4282
|
+
this.groups.push({ logic: 'WHERE', conditions: [] });
|
4283
|
+
return this;
|
4284
|
+
}
|
4285
|
+
addCondition(logic, key, type, operation, constant) {
|
4286
|
+
const group = this.groups[this.groups.length - 1];
|
4287
|
+
if (group.conditions.length === 0 && logic !== 'WHERE')
|
4288
|
+
throw new Error('Logic for the first condition of a group must be "WHERE"');
|
4289
|
+
if (group.conditions.length > 0 && logic === 'WHERE')
|
4290
|
+
throw new Error('Only the first condition of a group can be "WHERE"');
|
4291
|
+
group.conditions.push({
|
4292
|
+
logic,
|
4293
|
+
key,
|
4294
|
+
type,
|
4295
|
+
operation,
|
4296
|
+
constant
|
4297
|
+
});
|
4298
|
+
return this;
|
4299
|
+
}
|
4300
|
+
finalise() {
|
4301
|
+
for (const group of this.groups) {
|
4302
|
+
if (group.conditions.length === 0)
|
4303
|
+
throw new Error('Bad Filter. All Groups must have atleast one condition');
|
4304
|
+
}
|
4305
|
+
return this.groups;
|
4306
|
+
}
|
4307
|
+
static schema() {
|
4308
|
+
const logic = yup.string().oneOf(['AND', 'OR', 'XOR', 'NAND', 'NOR', 'WHERE']).required();
|
4309
|
+
const condition = yup.object({
|
4310
|
+
logic,
|
4311
|
+
key: yup.string().required(),
|
4312
|
+
type: yup.string().oneOf(['String', 'Address', 'Number']).required(),
|
4313
|
+
operation: yup.string().when('type', ([type], schema) => {
|
4314
|
+
switch (type) {
|
4315
|
+
case 'String': return schema.oneOf(['contains', '!contains', 'is', '!is', 'isEmpty', '!isEmpty']);
|
4316
|
+
case 'Address': return schema.oneOf(['is', '!is', 'isEmpty', '!isEmpty']);
|
4317
|
+
case 'Number': return schema.oneOf(['String', 'Address', 'Number']);
|
4318
|
+
default: throw new Error('Invalid Filter Data Type');
|
4319
|
+
}
|
4320
|
+
}),
|
4321
|
+
constant: yup.mixed().when('type', ([type]) => {
|
4322
|
+
switch (type) {
|
4323
|
+
case 'String': return yup.string();
|
4324
|
+
case 'Address': return yup.string();
|
4325
|
+
case 'Number': return yup.number();
|
4326
|
+
default: throw new Error('Invalid Filter Data Type');
|
4327
|
+
}
|
4328
|
+
}),
|
4329
|
+
});
|
4330
|
+
return yup.array().of(yup.object({
|
4331
|
+
logic,
|
4332
|
+
conditions: yup.array().of(condition)
|
4333
|
+
}));
|
4334
|
+
}
|
4098
4335
|
}
|
4099
4336
|
|
4100
4337
|
const HANDLER_FUNCTION_CALL_V2_RAW_ID = 'handler_function_call_v2';
|
@@ -4108,6 +4345,32 @@ class HandlerFunctionCallV2 {
|
|
4108
4345
|
handler: config
|
4109
4346
|
};
|
4110
4347
|
}
|
4348
|
+
static validateCreate(options) {
|
4349
|
+
const timePeriodRegex = /^(\d+)([SMHD])$/i;
|
4350
|
+
const onchainEventSchema = yup.object({
|
4351
|
+
timeBase: yup.string().oneOf(['blocks', 'time']).required(),
|
4352
|
+
// Blocks config
|
4353
|
+
nBlocks: yup.number()
|
4354
|
+
.min(10)
|
4355
|
+
.when('timeBase', ([timeBase], schema) => timeBase === 'blocks' ? schema.required() : schema),
|
4356
|
+
// Or Time config
|
4357
|
+
timePeriod: yup.string()
|
4358
|
+
.matches(timePeriodRegex, 'timePeriod must be of the format n[s|m|h|d], eg 10s for 10 seconds')
|
4359
|
+
.when('timeBase', ([timeBase], schema) => timeBase === 'time' ? schema.required() : schema),
|
4360
|
+
startTime: yup.number().default(0),
|
4361
|
+
// Debouncing. Default is 0
|
4362
|
+
debounceCount: yup.number(),
|
4363
|
+
// Functions
|
4364
|
+
functions: FunctionBuilder.schema(),
|
4365
|
+
// Trigger
|
4366
|
+
triggerOn: yup.string().oneOf(['always', 'filter']).default('always'),
|
4367
|
+
latch: yup.boolean().default(false),
|
4368
|
+
// Filter
|
4369
|
+
filterVersion: yup.string().when('triggerOn', ([triggerOn], schema) => triggerOn === 'filter' ? schema.required() : schema),
|
4370
|
+
filter: FilterBuilder.schema().when('triggerOn', ([triggerOn], schema) => triggerOn === 'filter' ? schema.required() : schema),
|
4371
|
+
});
|
4372
|
+
return onchainEventSchema.validate(options);
|
4373
|
+
}
|
4111
4374
|
}
|
4112
4375
|
|
4113
4376
|
class Trigger {
|
@@ -4115,7 +4378,7 @@ class Trigger {
|
|
4115
4378
|
switch (id) {
|
4116
4379
|
case 'PollFunctionV2': return HandlerFunctionCallV2.SimpleToRaw(name, network, config);
|
4117
4380
|
case 'PollFunctionV1': return HandlerFunctionCall.SimpleToRaw(name, network, config);
|
4118
|
-
case '
|
4381
|
+
case 'OnchainEventV1': return HandlerOnchainEvent.SimpleToRaw(name, network, config);
|
4119
4382
|
default:
|
4120
4383
|
throw new Error('Invalid Trigger ID');
|
4121
4384
|
}
|
@@ -4251,6 +4514,13 @@ class AlertBuilder {
|
|
4251
4514
|
this.network = network;
|
4252
4515
|
return this;
|
4253
4516
|
}
|
4517
|
+
/**
|
4518
|
+
* Call withTrigger with one of the TriggerConfig types:
|
4519
|
+
* PollFunctionV2 | PollFunctionV1 | OnchainEventV1
|
4520
|
+
* @param id Simple ID
|
4521
|
+
* @param options Desired trigger configuration
|
4522
|
+
* @returns self for piping
|
4523
|
+
*/
|
4254
4524
|
withTrigger(id, options) {
|
4255
4525
|
this.triggerId = id;
|
4256
4526
|
this.trigger = options;
|
@@ -4261,13 +4531,25 @@ class AlertBuilder {
|
|
4261
4531
|
this.notification = options;
|
4262
4532
|
return this;
|
4263
4533
|
}
|
4264
|
-
|
4534
|
+
async validate() {
|
4535
|
+
// Validate trigger
|
4536
|
+
switch (this.triggerId) {
|
4537
|
+
case 'OnchainEventV1': return HandlerOnchainEvent.validateCreate(this.trigger);
|
4538
|
+
case 'PollFunctionV1': return HandlerFunctionCall.validateCreate(this.trigger);
|
4539
|
+
case 'PollFunctionV2': return HandlerFunctionCallV2.validateCreate(this.trigger);
|
4540
|
+
}
|
4541
|
+
switch (this.notificationId) {
|
4542
|
+
case 'Discord': return NotifyDiscordWebhook.validateCreate(this.notification);
|
4543
|
+
}
|
4544
|
+
}
|
4545
|
+
async config() {
|
4265
4546
|
if (this.trigger === undefined || this.triggerId === undefined)
|
4266
4547
|
throw new Error('Trigger not configured');
|
4267
4548
|
if (this.notification === undefined || this.notificationId === undefined)
|
4268
4549
|
throw new Error('Notification not configured');
|
4269
4550
|
if (this.network === undefined)
|
4270
4551
|
throw new Error('Network not configured');
|
4552
|
+
await this.validate();
|
4271
4553
|
return {
|
4272
4554
|
name: this.name,
|
4273
4555
|
network: this.network,
|
@@ -4279,168 +4561,6 @@ class AlertBuilder {
|
|
4279
4561
|
}
|
4280
4562
|
}
|
4281
4563
|
|
4282
|
-
class FunctionBuilder {
|
4283
|
-
api;
|
4284
|
-
data = [];
|
4285
|
-
constructor(api) {
|
4286
|
-
this.api = api;
|
4287
|
-
}
|
4288
|
-
static create(api) {
|
4289
|
-
return new FunctionBuilder(api);
|
4290
|
-
}
|
4291
|
-
getAbiHash(abi) {
|
4292
|
-
if (this.api === undefined)
|
4293
|
-
throw new Error('FunctionBuilder must be initialised with api');
|
4294
|
-
return this.api.getAbiHash(abi);
|
4295
|
-
}
|
4296
|
-
async addFunction(address, func, args, abi) {
|
4297
|
-
const contract = new ethers.Contract(address, abi);
|
4298
|
-
contract.interface.encodeFunctionData(func, args);
|
4299
|
-
this.data.push({
|
4300
|
-
address,
|
4301
|
-
bytecode: contract.interface.encodeFunctionData(func, args),
|
4302
|
-
abiHash: await this.getAbiHash(abi),
|
4303
|
-
function: func,
|
4304
|
-
});
|
4305
|
-
return this;
|
4306
|
-
}
|
4307
|
-
get() {
|
4308
|
-
return this.data;
|
4309
|
-
}
|
4310
|
-
}
|
4311
|
-
|
4312
|
-
class Filter {
|
4313
|
-
static version() {
|
4314
|
-
return 'v1';
|
4315
|
-
}
|
4316
|
-
static execute(record, groupsIn, version = 'v1') {
|
4317
|
-
// Deep copy to so we can edit the object during recursion
|
4318
|
-
const groups = JSON.parse(JSON.stringify(groupsIn));
|
4319
|
-
let lhs = true;
|
4320
|
-
for (const group of groups) {
|
4321
|
-
const rhs = Filter.process(record, group.conditions);
|
4322
|
-
lhs = Filter.execLogic(lhs, group.logic, rhs);
|
4323
|
-
}
|
4324
|
-
return lhs;
|
4325
|
-
}
|
4326
|
-
static process(record, conditions, lhs) {
|
4327
|
-
const condition = conditions.shift();
|
4328
|
-
if (!condition) {
|
4329
|
-
if (lhs === undefined)
|
4330
|
-
return true;
|
4331
|
-
return lhs;
|
4332
|
-
}
|
4333
|
-
// lhs <logic> rhs
|
4334
|
-
const rhs = Filter.execCondition(record, condition);
|
4335
|
-
if (lhs === undefined || condition.logic === 'WHERE')
|
4336
|
-
return Filter.process(record, conditions, rhs);
|
4337
|
-
if (condition.logic === undefined)
|
4338
|
-
throw new Error('Invalid filter. Condition must have logic value');
|
4339
|
-
const next = Filter.execLogic(lhs, condition.logic, rhs);
|
4340
|
-
return Filter.process(record, conditions, next);
|
4341
|
-
}
|
4342
|
-
static execLogic(lhs, logic, rhs) {
|
4343
|
-
switch (logic) {
|
4344
|
-
case 'AND': return lhs && rhs;
|
4345
|
-
case 'OR': return lhs || rhs;
|
4346
|
-
case 'XOR': return (lhs || rhs) && !(lhs && rhs);
|
4347
|
-
case 'NAND': return !(lhs && rhs);
|
4348
|
-
case 'NOR': return !(lhs && rhs);
|
4349
|
-
case 'WHERE': return rhs;
|
4350
|
-
default: throw new Error(`Invalid Filter. Bad logic value: ${logic}`);
|
4351
|
-
}
|
4352
|
-
}
|
4353
|
-
static execCondition(record, condition) {
|
4354
|
-
const data = record[condition.key];
|
4355
|
-
if (condition.type === 'Number') {
|
4356
|
-
if (typeof condition.constant !== 'number' || typeof data !== 'number')
|
4357
|
-
throw new Error(`Invalid filter. Type miss-match. Type: ${condition.type}, Variable: ${condition.constant}, Data: ${data}`);
|
4358
|
-
const n = data;
|
4359
|
-
const constant = condition.constant;
|
4360
|
-
switch (condition.operation) {
|
4361
|
-
case 'eq': return n === constant;
|
4362
|
-
case '!eq': return n !== constant;
|
4363
|
-
case 'gt': return n > constant;
|
4364
|
-
case 'gte': return n >= constant;
|
4365
|
-
case 'lt': return n < constant;
|
4366
|
-
case 'lte': return n <= constant;
|
4367
|
-
default: throw new Error('Invalid Filter. Operation does not match type');
|
4368
|
-
}
|
4369
|
-
}
|
4370
|
-
if (condition.type === 'String') {
|
4371
|
-
if (typeof condition.constant !== 'string' || typeof data !== 'string')
|
4372
|
-
throw new Error(`Invalid filter. Type miss-match. Type: ${condition.type}, Variable: ${condition.constant}, Data: ${data}`);
|
4373
|
-
const str = data;
|
4374
|
-
const constant = condition.constant;
|
4375
|
-
switch (condition.operation) {
|
4376
|
-
case 'contains': return str.includes(constant);
|
4377
|
-
case '!contains': return !str.includes(constant);
|
4378
|
-
case 'is': return str === constant;
|
4379
|
-
case '!is': return str !== constant;
|
4380
|
-
case 'isEmpty': return str === '';
|
4381
|
-
case '!isEmpty': return str !== '';
|
4382
|
-
default: throw new Error('Invalid Filter. Operation does not match type');
|
4383
|
-
}
|
4384
|
-
}
|
4385
|
-
if (condition.type === 'Address') {
|
4386
|
-
if (typeof condition.constant !== 'string' || typeof data !== 'string')
|
4387
|
-
throw new Error(`Invalid filter. Type miss-match. Type: ${condition.type}, Variable: ${condition.constant}, Data: ${data}`);
|
4388
|
-
const str = data;
|
4389
|
-
const constant = condition.constant;
|
4390
|
-
switch (condition.operation) {
|
4391
|
-
case 'contains': return str.toLowerCase().includes(constant.toLowerCase());
|
4392
|
-
case '!contains': return !str.toLowerCase().includes(constant.toLowerCase());
|
4393
|
-
case 'is': return str.toLowerCase() === constant.toLowerCase();
|
4394
|
-
case '!is': return str.toLowerCase() !== constant.toLowerCase();
|
4395
|
-
case 'isEmpty': return str === '';
|
4396
|
-
case '!isEmpty': return str !== '';
|
4397
|
-
default: throw new Error('Invalid Filter. Operation does not match type');
|
4398
|
-
}
|
4399
|
-
}
|
4400
|
-
throw new Error('Invalid Filter. Unknown Type');
|
4401
|
-
}
|
4402
|
-
}
|
4403
|
-
class FilterBuilder {
|
4404
|
-
groups = [];
|
4405
|
-
constructor() {
|
4406
|
-
this.newGroup('WHERE');
|
4407
|
-
}
|
4408
|
-
static version() {
|
4409
|
-
return Filter.version();
|
4410
|
-
}
|
4411
|
-
static new() {
|
4412
|
-
return new FilterBuilder();
|
4413
|
-
}
|
4414
|
-
newGroup(logic) {
|
4415
|
-
if (this.groups.length !== 0 && logic === 'WHERE')
|
4416
|
-
throw new Error('Only the first groups can start with "WHERE"');
|
4417
|
-
this.groups.push({ logic: 'WHERE', conditions: [] });
|
4418
|
-
return this;
|
4419
|
-
}
|
4420
|
-
addCondition(logic, key, type, operation, constant) {
|
4421
|
-
const group = this.groups[this.groups.length - 1];
|
4422
|
-
if (group.conditions.length === 0 && logic !== 'WHERE')
|
4423
|
-
throw new Error('Logic for the first condition of a group must be "WHERE"');
|
4424
|
-
if (group.conditions.length > 0 && logic === 'WHERE')
|
4425
|
-
throw new Error('Only the first condition of a group can be "WHERE"');
|
4426
|
-
group.conditions.push({
|
4427
|
-
logic,
|
4428
|
-
key,
|
4429
|
-
type,
|
4430
|
-
operation,
|
4431
|
-
constant
|
4432
|
-
});
|
4433
|
-
return this;
|
4434
|
-
}
|
4435
|
-
finalise() {
|
4436
|
-
for (const group of this.groups) {
|
4437
|
-
if (group.conditions.length === 0)
|
4438
|
-
throw new Error('Bad Filter. All Groups must have atleast one condition');
|
4439
|
-
}
|
4440
|
-
return this.groups;
|
4441
|
-
}
|
4442
|
-
}
|
4443
|
-
|
4444
4564
|
var index$1 = /*#__PURE__*/Object.freeze({
|
4445
4565
|
__proto__: null,
|
4446
4566
|
Trigger: Trigger,
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import * as yup from 'yup';
|
1
2
|
export const NOTIFY_DISCORD_WEBHOOK_RAW_ID = 'notify_discord_webhook';
|
2
3
|
export class NotifyDiscordWebhook {
|
3
4
|
static SimpleToRaw(config) {
|
@@ -7,4 +8,14 @@ export class NotifyDiscordWebhook {
|
|
7
8
|
notify: config
|
8
9
|
};
|
9
10
|
}
|
11
|
+
static validateCreate(options) {
|
12
|
+
const urlRegex = /^(https?|ftp):\/\/(-\.)?([^\s/?\.#]+\.?)+([^\s\.?#]+)?(\?\S*)?$/;
|
13
|
+
const schema = yup.object({
|
14
|
+
url: yup.string().matches(urlRegex, 'url is not a valid url').required(),
|
15
|
+
username: yup.string(),
|
16
|
+
avatar_url: yup.string().matches(urlRegex, 'url is not a valid url'),
|
17
|
+
message: yup.string().required()
|
18
|
+
});
|
19
|
+
return schema.validate(options);
|
20
|
+
}
|
10
21
|
}
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import * as yup from 'yup';
|
1
2
|
const HANDLER_FUNCTION_CALL_V1_RAW_ID = 'handler_function_call';
|
2
3
|
export class HandlerFunctionCall {
|
3
4
|
static SimpleToRaw(name, network, config) {
|
@@ -9,4 +10,17 @@ export class HandlerFunctionCall {
|
|
9
10
|
handler: config
|
10
11
|
};
|
11
12
|
}
|
13
|
+
static validateCreate(options) {
|
14
|
+
const requiredWhenConditional = ([condition], schema) => condition === 'true' ? schema.notRequired() : schema.required();
|
15
|
+
const schema = yup.object({
|
16
|
+
address: yup.string().required(),
|
17
|
+
abi: yup.array().required(),
|
18
|
+
nBlocks: yup.number().min(10),
|
19
|
+
condition: yup.string().oneOf(['>', '>=', '<', '<=', '=', 'true']),
|
20
|
+
constant: yup.number().min(0).when('condition', requiredWhenConditional),
|
21
|
+
responseArgIndex: yup.number().min(0).when('condition', requiredWhenConditional),
|
22
|
+
responseArgDecimals: yup.number().min(0).when('condition', requiredWhenConditional),
|
23
|
+
});
|
24
|
+
return schema.validate(options);
|
25
|
+
}
|
12
26
|
}
|
@@ -1,3 +1,6 @@
|
|
1
|
+
import { FunctionBuilder } from "../functionbuilder.js";
|
2
|
+
import { FilterBuilder } from "../util/filter.js";
|
3
|
+
import * as yup from 'yup';
|
1
4
|
const HANDLER_FUNCTION_CALL_V2_RAW_ID = 'handler_function_call_v2';
|
2
5
|
export class HandlerFunctionCallV2 {
|
3
6
|
static SimpleToRaw(name, network, config) {
|
@@ -9,4 +12,30 @@ export class HandlerFunctionCallV2 {
|
|
9
12
|
handler: config
|
10
13
|
};
|
11
14
|
}
|
15
|
+
static validateCreate(options) {
|
16
|
+
const timePeriodRegex = /^(\d+)([SMHD])$/i;
|
17
|
+
const onchainEventSchema = yup.object({
|
18
|
+
timeBase: yup.string().oneOf(['blocks', 'time']).required(),
|
19
|
+
// Blocks config
|
20
|
+
nBlocks: yup.number()
|
21
|
+
.min(10)
|
22
|
+
.when('timeBase', ([timeBase], schema) => timeBase === 'blocks' ? schema.required() : schema),
|
23
|
+
// Or Time config
|
24
|
+
timePeriod: yup.string()
|
25
|
+
.matches(timePeriodRegex, 'timePeriod must be of the format n[s|m|h|d], eg 10s for 10 seconds')
|
26
|
+
.when('timeBase', ([timeBase], schema) => timeBase === 'time' ? schema.required() : schema),
|
27
|
+
startTime: yup.number().default(0),
|
28
|
+
// Debouncing. Default is 0
|
29
|
+
debounceCount: yup.number(),
|
30
|
+
// Functions
|
31
|
+
functions: FunctionBuilder.schema(),
|
32
|
+
// Trigger
|
33
|
+
triggerOn: yup.string().oneOf(['always', 'filter']).default('always'),
|
34
|
+
latch: yup.boolean().default(false),
|
35
|
+
// Filter
|
36
|
+
filterVersion: yup.string().when('triggerOn', ([triggerOn], schema) => triggerOn === 'filter' ? schema.required() : schema),
|
37
|
+
filter: FilterBuilder.schema().when('triggerOn', ([triggerOn], schema) => triggerOn === 'filter' ? schema.required() : schema),
|
38
|
+
});
|
39
|
+
return onchainEventSchema.validate(options);
|
40
|
+
}
|
12
41
|
}
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import * as yup from 'yup';
|
1
2
|
const HANDLER_ONCHAIN_EVENT_V1_RAW_ID = 'handler_onchain_event';
|
2
3
|
export class HandlerOnchainEvent {
|
3
4
|
static SimpleToRaw(name, network, config) {
|
@@ -9,4 +10,19 @@ export class HandlerOnchainEvent {
|
|
9
10
|
handler: config
|
10
11
|
};
|
11
12
|
}
|
13
|
+
static validateCreate(options) {
|
14
|
+
const requiredWhenConditional = ([condition], schema) => condition === 'true' ? schema.notRequired() : schema.required();
|
15
|
+
const onchainEventSchema = yup.object({
|
16
|
+
address: yup.string().required(),
|
17
|
+
event: yup.string().required(),
|
18
|
+
abi: yup.array().required(),
|
19
|
+
condition: yup.string().oneOf(['>', '>=', '<', '<=', '=', 'true']),
|
20
|
+
constant: yup.number().min(0).when('condition', requiredWhenConditional),
|
21
|
+
paramsIndex: yup.number().min(0).when('condition', requiredWhenConditional),
|
22
|
+
paramsDecimals: yup.number().min(0).when('condition', requiredWhenConditional),
|
23
|
+
});
|
24
|
+
return onchainEventSchema.validate(options);
|
25
|
+
}
|
26
|
+
static validateUpdate(options) {
|
27
|
+
}
|
12
28
|
}
|
package/dist/triggers/trigger.js
CHANGED
@@ -6,7 +6,7 @@ export class Trigger {
|
|
6
6
|
switch (id) {
|
7
7
|
case 'PollFunctionV2': return HandlerFunctionCallV2.SimpleToRaw(name, network, config);
|
8
8
|
case 'PollFunctionV1': return HandlerFunctionCall.SimpleToRaw(name, network, config);
|
9
|
-
case '
|
9
|
+
case 'OnchainEventV1': return HandlerOnchainEvent.SimpleToRaw(name, network, config);
|
10
10
|
default:
|
11
11
|
throw new Error('Invalid Trigger ID');
|
12
12
|
}
|
package/dist/util/filter.js
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import * as yup from 'yup';
|
1
2
|
export class Filter {
|
2
3
|
static version() {
|
3
4
|
return 'v1';
|
@@ -128,4 +129,32 @@ export class FilterBuilder {
|
|
128
129
|
}
|
129
130
|
return this.groups;
|
130
131
|
}
|
132
|
+
static schema() {
|
133
|
+
const logic = yup.string().oneOf(['AND', 'OR', 'XOR', 'NAND', 'NOR', 'WHERE']).required();
|
134
|
+
const condition = yup.object({
|
135
|
+
logic,
|
136
|
+
key: yup.string().required(),
|
137
|
+
type: yup.string().oneOf(['String', 'Address', 'Number']).required(),
|
138
|
+
operation: yup.string().when('type', ([type], schema) => {
|
139
|
+
switch (type) {
|
140
|
+
case 'String': return schema.oneOf(['contains', '!contains', 'is', '!is', 'isEmpty', '!isEmpty']);
|
141
|
+
case 'Address': return schema.oneOf(['is', '!is', 'isEmpty', '!isEmpty']);
|
142
|
+
case 'Number': return schema.oneOf(['String', 'Address', 'Number']);
|
143
|
+
default: throw new Error('Invalid Filter Data Type');
|
144
|
+
}
|
145
|
+
}),
|
146
|
+
constant: yup.mixed().when('type', ([type]) => {
|
147
|
+
switch (type) {
|
148
|
+
case 'String': return yup.string();
|
149
|
+
case 'Address': return yup.string();
|
150
|
+
case 'Number': return yup.number();
|
151
|
+
default: throw new Error('Invalid Filter Data Type');
|
152
|
+
}
|
153
|
+
}),
|
154
|
+
});
|
155
|
+
return yup.array().of(yup.object({
|
156
|
+
logic,
|
157
|
+
conditions: yup.array().of(condition)
|
158
|
+
}));
|
159
|
+
}
|
131
160
|
}
|