From 56e498bd382cb828c637d07c4d6e0a0a5f162a19 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Fri, 10 Mar 2023 15:23:17 +0100 Subject: [PATCH 01/17] feed: refactoring, move out schema related logic to separate class Signed-off-by: dzdidi --- src/Feeds.js | 112 ++----------------------- src/SlashtagsSchema.js | 109 ++++++++++++++++++++++++ test/Feeds.test.js | 155 +++++------------------------------ test/SlashtagsSchema.test.js | 107 ++++++++++++++++++++++++ 4 files changed, 243 insertions(+), 240 deletions(-) create mode 100644 src/SlashtagsSchema.js create mode 100644 test/SlashtagsSchema.test.js diff --git a/src/Feeds.js b/src/Feeds.js index e48fca1..b1f09e9 100644 --- a/src/Feeds.js +++ b/src/Feeds.js @@ -1,11 +1,10 @@ -const fs = require('fs') const b4a = require('b4a') const z32 = require('z32') -const path = require('path') const Feeds = require('@synonymdev/feeds') const FeedDb = require('./FeedDb.js') +const SlashtagsSchema = require('./SlashtagsSchema.js') const Log = require('./Log.js') const customErr = require('./CustomError.js') @@ -17,13 +16,8 @@ const _err = { dbFailedStart: 'FAILED_TO_START_DB', feedIdMissing: 'FEED_ID_NOT_PASSED', failedCreateDrive: 'FAILED_TO_CREATE_FEED_FEED', - failedCreateDriveArgs: 'FAILED_TO_CREATE_FEED_INVALID_RESPONSE', - failedBalanceCheck: 'FAILED_BALANCE_CHECK', badConfig: 'BAD_CONSTRUCTOR_CONFIG', badSchemaSetup: 'FEED_SCHEMA_FAILED', - badFeedDataType: 'BAD_FEED_DATA_TYPE', - invalidSchema: 'INVALID_FEED_SCHEMA', - badUpdateParam: 'BAD_UPDATE_PARAM', updateFeedFailed: 'FAILED_TO_UPDATE_FEED', idNoFeed: 'FEED_ID_HAS_NO_FEED', failedDeleteFeed: 'FAILED_FEED_DELETE', @@ -35,111 +29,17 @@ const _err = { processAlreadyRunning: 'PROCESS_ALREADY_RUNNING', feedNotFound: 'FEED_FEED_NOT_FOUND', - missingFeedName: 'MISSING_FEED_NAME', - missingFeedDescription: 'MISSING_FEED_DESCRIPTION', - missingFeedIcons: 'MISSING_FEED_ICONS', - missingFeedFields: 'MISSING_FEED_FIELDS', - invalidFeedIcon: 'INVALID_FEED_ICON', - missingFields: 'MISSING_FIELDS', invalidFeedFields: 'INVALID_FEED_FIELDS', missingFieldName: 'MISSING_FIELD_NAME', - missingFieldDescription: 'MISSING_FIELD_DESCRIPTION', - missingFieldUnits: 'MISSING_FIELD_UNITS', - badFieldType: 'UNSUPPORTED_FIELD_TYPE', missingFieldValue: 'MISSING_FIELD_VALUE', unknownField: 'UKNOWN_FIELD', - invalidFieldValue: 'INVALID_FIELD_VALUE' } module.exports = class SlashtagsFeeds { static err = _err static Error = Err - static DEFAULT_SCHEMA_PATH = './schemas/slashfeed.json' - static DEFAULT_TYPES = [ - 'number', - 'utf-8' - ] - - static MEASURED_TYPES = [ - 'currency', - 'delta' - ] - - static VALID_TYPES = [ - ...this.DEFAULT_TYPES, - ...this.MEASURED_TYPES - ] - - static validateSchemaConfig (schemaConfig) { - if (!schemaConfig.name) throw new SlashtagsFeeds.Error(SlashtagsFeeds.err.missingFeedName) - if (!schemaConfig.description) throw new SlashtagsFeeds.Error(SlashtagsFeeds.err.missingFeedDescription) - if (!schemaConfig.icons) throw new SlashtagsFeeds.Error(SlashtagsFeeds.err.missingFeedIcons) - if (!schemaConfig.fields) throw new SlashtagsFeeds.Error(SlashtagsFeeds.err.missingFeedFields) - if (!Array.isArray(schemaConfig.fields)) throw new SlashtagsFeeds.Error(SlashtagsFeeds.err.invalidFeedFields) - - const imageRX = /^data:image\/((svg\+xml)|(png));base64,.+$/ - for (const size in schemaConfig.icons) { - const icon = schemaConfig.icons[size] - - if (typeof icon !== 'string') throw new SlashtagsFeeds.Error(SlashtagsFeeds.err.invalidFeedIcon) - if (!imageRX.test(icon)) throw new SlashtagsFeeds.Error(SlashtagsFeeds.err.invalidFeedIcon) - } - - schemaConfig.fields.forEach((field) => { - if (!field.name) throw new SlashtagsFeeds.Error(SlashtagsFeeds.err.missingFieldName) - if (!field.description) throw new SlashtagsFeeds.Error(SlashtagsFeeds.err.missingFieldDescription) - if (field.type && (field.type !== '') && !SlashtagsFeeds.VALID_TYPES.includes(field.type)) { - throw new SlashtagsFeeds.Error(SlashtagsFeeds.err.badFieldType) - } - - if (this.MEASURED_TYPES.includes(field.type)) { - if (!field.units) throw new SlashtagsFeeds.Error(SlashtagsFeeds.err.missingFieldUnits) - } - }) - } - - static generateSchema (config) { - const { schemaConfig } = config - SlashtagsFeeds.validateSchemaConfig(schemaConfig) - - const schema = { - name: schemaConfig.name, - description: schemaConfig.description, - type: 'account_feed', - version: '0.0.1', - icons: {} - } - - for (const size in schemaConfig.icons) { - schema.icons[size] = schemaConfig.icons[size] - } - - schema.fields = schemaConfig.fields.map((field) => { - return { - name: field.name, - description: field.description, - main: path.join(Feeds.FEED_PREFIX, SlashtagsFeeds.getFileName(field)), - type: field.type || 'utf-8', - units: field.units - } - }) - - return schema - } - - static persistSchema (schema) { - fs.writeFileSync(this.DEFAULT_SCHEMA_PATH, Buffer.from(JSON.stringify(schema, undefined, 2)), 'utf-8') - } - - static getFileName (field) { - const regex = /[^a-z0-9]+/gi - const trailing = /-+$/ - - return `/${field.name.toLowerCase().trim().replace(regex, '-').replace(trailing, '')}/` - } - /** * @param {String} config.db.name Database name * @param {String} config.db.path Database path location @@ -153,12 +53,12 @@ module.exports = class SlashtagsFeeds { let feedSchema if (config.schemaConfig) { - feedSchema = SlashtagsFeeds.generateSchema(config) - SlashtagsFeeds.persistSchema(feedSchema) + feedSchema = SlashtagsSchema.generateSchema(config.schemaConfig) + SlashtagsSchema.persistSchema(feedSchema) } else if (config.feed_schema) { feedSchema = config.feed_schema } - SlashtagsFeeds.validateSchemaConfig(feedSchema) + SlashtagsSchema.validateSchemaConfig(feedSchema) this.config = config this.db = new FeedDb(config.db) @@ -203,7 +103,7 @@ module.exports = class SlashtagsFeeds { try { // NOTE: consider storing balance on db as well for (const field of update.fields) { - await this._slashfeeds.update(update.feed_id, SlashtagsFeeds.getFileName(field), field.value) + await this._slashfeeds.update(update.feed_id, SlashtagsSchema.getFileName(field), field.value) } return { updated: true } } catch (err) { @@ -312,7 +212,7 @@ module.exports = class SlashtagsFeeds { async (field) => { await this._slashfeeds.update( args.feed_id, - SlashtagsFeeds.getFileName(field), + SlashtagsSchema.getFileName(field), args.init_data || null ) } diff --git a/src/SlashtagsSchema.js b/src/SlashtagsSchema.js new file mode 100644 index 0000000..9d7f94b --- /dev/null +++ b/src/SlashtagsSchema.js @@ -0,0 +1,109 @@ +const path = require('path') +const fs = require('fs') +const Feeds = require('@synonymdev/feeds') + +const customErr = require('./CustomError.js') +const Err = customErr({ errName: 'Slashtags', fileName: __filename }) + +const _err = { + missingFeedName: 'MISSING_FEED_NAME', + missingFeedDescription: 'MISSING_FEED_DESCRIPTION', + missingFeedIcons: 'MISSING_FEED_ICONS', + missingFeedFields: 'MISSING_FEED_FIELDS', + invalidFeedIcon: 'INVALID_FEED_ICON', + + invalidFeedFields: 'INVALID_FEED_FIELDS', + missingFieldName: 'MISSING_FIELD_NAME', + missingFieldDescription: 'MISSING_FIELD_DESCRIPTION', + missingFieldUnits: 'MISSING_FIELD_UNITS', + badFieldType: 'UNSUPPORTED_FIELD_TYPE', +} + +module.exports = class SlashtagsSchema { + static err = _err + static Error = Err + + static DEFAULT_SCHEMA_PATH = './schemas/slashfeed.json' + + static DEFAULT_TYPES = [ + 'number', + 'utf-8' + ] + + static MEASURED_TYPES = [ + 'currency', + 'delta' + ] + + static VALID_TYPES = [ + ...this.DEFAULT_TYPES, + ...this.MEASURED_TYPES + ] + + static validateSchemaConfig (schemaConfig) { + if (!schemaConfig.name) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFeedName) + if (!schemaConfig.description) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFeedDescription) + if (!schemaConfig.icons) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFeedIcons) + if (!schemaConfig.fields) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFeedFields) + if (!Array.isArray(schemaConfig.fields)) throw new SlashtagsSchema.Error(SlashtagsSchema.err.invalidFeedFields) + + const imageRX = /^data:image\/((svg\+xml)|(png));base64,.+$/ + for (const size in schemaConfig.icons) { + const icon = schemaConfig.icons[size] + + if (typeof icon !== 'string') throw new SlashtagsSchema.Error(SlashtagsSchema.err.invalidFeedIcon) + if (!imageRX.test(icon)) throw new SlashtagsSchema.Error(SlashtagsSchema.err.invalidFeedIcon) + } + + schemaConfig.fields.forEach((field) => { + if (!field.name) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFieldName) + if (!field.description) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFieldDescription) + if (field.type && (field.type !== '') && !SlashtagsSchema.VALID_TYPES.includes(field.type)) { + throw new SlashtagsSchema.Error(SlashtagsSchema.err.badFieldType) + } + + if (this.MEASURED_TYPES.includes(field.type)) { + if (!field.units) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFieldUnits) + } + }) + } + + static generateSchema (schemaConfig) { + SlashtagsSchema.validateSchemaConfig(schemaConfig) + + const schema = { + name: schemaConfig.name, + description: schemaConfig.description, + type: 'account_feed', + version: '0.0.1', + icons: {} + } + + for (const size in schemaConfig.icons) { + schema.icons[size] = schemaConfig.icons[size] + } + + schema.fields = schemaConfig.fields.map((field) => { + return { + name: field.name, + description: field.description, + main: path.join(Feeds.FEED_PREFIX, SlashtagsSchema.getFileName(field)), + type: field.type || 'utf-8', + units: field.units + } + }) + + return schema + } + + static persistSchema (schema) { + fs.writeFileSync(this.DEFAULT_SCHEMA_PATH, Buffer.from(JSON.stringify(schema, undefined, 2)), 'utf-8') + } + + static getFileName (field) { + const regex = /[^a-z0-9]+/gi + const trailing = /-+$/ + + return `/${field.name.toLowerCase().trim().replace(regex, '-').replace(trailing, '')}/` + } +} diff --git a/test/Feeds.test.js b/test/Feeds.test.js index c0eab10..07f62f9 100644 --- a/test/Feeds.test.js +++ b/test/Feeds.test.js @@ -1,5 +1,6 @@ const { strict: assert } = require('node:assert') const SlashtagsFeeds = require('../src/Feeds.js') +const SlashtagsSchema = require('../src/SlashtagsSchema.js') const FeedDb = require('../src/FeedDb.js') const path = require('path') const fs = require('fs') @@ -20,7 +21,16 @@ describe('SlashtagsFeeds', () => { describe('Constructor', () => { describe('Valid config', () => { let feed - before(() => feed = new SlashtagsFeeds(validConfig)) + let conf + before(() => { + feed = new SlashtagsFeeds(validConfig) + conf = { + name: Schema.name, + description: Schema.description, + icons: JSON.parse(JSON.stringify(Schema.icons)), + fields: Schema.fields.map(f => JSON.parse(JSON.stringify(f))) + } + }) it('has config', () => assert.deepStrictEqual(feed.config, validConfig)) it('has db', () => assert.deepStrictEqual(feed.db, new FeedDb(validConfig.db))) @@ -28,120 +38,14 @@ describe('SlashtagsFeeds', () => { it('has lock', () => assert.deepStrictEqual(feed.lock, new Map())) it('has ready flag', () => assert.equal(feed.ready, false)) it('has slashtags property', () => assert.equal(feed._slashfeeds, null)) - - describe('it generates and overwrites slashfeed based on config', () => { - let conf - beforeEach(() => { - conf = { - ...JSON.parse(JSON.stringify(validConfig)), - schemaConfig: { - name: Schema.name, - description: Schema.description, - icons: JSON.parse(JSON.stringify(Schema.icons)), - fields: Schema.fields.map(f => JSON.parse(JSON.stringify(f))) - } - } - }) - - describe('invalid schamaConfig', () => { - describe('missing name', () => { - beforeEach(() => { - delete conf.schemaConfig.name - error.message = SlashtagsFeeds.err.missingFeedName - }) - - it('throws an error', () => assert.throws(() => new SlashtagsFeeds(conf), error)) - }) - - describe('missing description', () => { - beforeEach(() => { - delete conf.schemaConfig.description - error.message = SlashtagsFeeds.err.missingFeedDescription - }) - - it('throws an error', () => assert.throws(() => new SlashtagsFeeds(conf), error)) - }) - - describe('missing icons', () => { - beforeEach(() => { - delete conf.schemaConfig.icons - error.message = SlashtagsFeeds.err.missingFeedIcons - }) - - it('throws an error', () => assert.throws(() => new SlashtagsFeeds(conf), error)) - }) - - describe('missing fields', () => { - beforeEach(() => { - delete conf.schemaConfig.fields - error.message = SlashtagsFeeds.err.missingFeedFields - }) - - it('throws an error', () => assert.throws(() => new SlashtagsFeeds(conf), error)) - }) - - describe('fields are not array', () => { - beforeEach(() => { - conf.schemaConfig.fields = 'fields' - error.message = SlashtagsFeeds.err.invalidFeedFields - }) - - it('throws an error', () => assert.throws(() => new SlashtagsFeeds(conf), error)) - }) - - describe('invalid icon', () => { - beforeEach(() => { - conf.schemaConfig.icons['48'] = 'not an image' - error.message = SlashtagsFeeds.err.invalidFeedIcon - }) - - it('throws an error', () => assert.throws(() => new SlashtagsFeeds(conf), error)) - }) - - describe('invalid field', () => { - describe('missing name', () => { - beforeEach(() => { - delete conf.schemaConfig.fields[0].name - error.message = SlashtagsFeeds.err.missingFieldName - }) - - it('throws an error', () => assert.throws(() => new SlashtagsFeeds(conf), error)) - }) - - describe('missing description', () => { - beforeEach(() => { - delete conf.schemaConfig.fields[1].description - error.message = SlashtagsFeeds.err.missingFieldDescription - }) - - it('throws an error', () => assert.throws(() => new SlashtagsFeeds(conf), error)) - }) - - describe('invalid type', () => { - beforeEach(() => { - conf.schemaConfig.fields[1].type = 'unsupported type' - error.message = SlashtagsFeeds.err.badFieldType - }) - - it('throws an error', () => assert.throws(() => new SlashtagsFeeds(conf), error)) - }) - }) - }) - - describe('valid config', () => { - let instance - beforeEach(() => instance = new SlashtagsFeeds(conf)) - - it('uses new schema', () => assert.deepStrictEqual( - instance.feed_schema, - SlashtagsFeeds.generateSchema(conf) - )) - it('persists generated schema', () => assert.deepStrictEqual( - instance.feed_schema, - JSON.parse(fs.readFileSync(SlashtagsFeeds.DEFAULT_SCHEMA_PATH).toString('utf8')) - )) - }) - }) + it('uses new schema', () => assert.deepStrictEqual( + feed.feed_schema, + SlashtagsSchema.generateSchema(conf) + )) + it('persists generated schema', () => assert.deepStrictEqual( + feed.feed_schema, + JSON.parse(fs.readFileSync(SlashtagsSchema.DEFAULT_SCHEMA_PATH).toString('utf8')) + )) }) describe('Invalid config', () => { @@ -154,23 +58,6 @@ describe('SlashtagsFeeds', () => { slashtags: path.resolve('./test-data/storage'), } - // TODO: -// describe('Invalid feed schema', () => { -// before(() => error.message = SlashtagsFeeds.err.invalidSchema) -// -// const keys = [ 'image', 'name', 'feed_type', 'version' ] -// keys.forEach((k) => { -// let tmp -// beforeEach(() => { -// tmp = invalidConfig.feed_schema[k] -// invalidConfig.feed_schema[k] = null -// }) -// afterEach(() => invalidConfig.feed_schema[k] = tmp) -// -// it(`fails without ${k}`, () => assert.throws(() => new SlashtagsFeeds(invalidConfig), error)) -// }) -// }) - describe('Missing slashtags', () => { before(() => error.message = SlashtagsFeeds.err.badConfig) @@ -704,8 +591,8 @@ describe('SlashtagsFeeds', () => { before(async () => { await feed.stop() feedReader = new Feeds(validConfig.slashtags, validConfig.feed_schema) - balance = await feedReader.get(update.feed_id, SlashtagsFeeds.getFileName(update.fields[0])) - balanceChange = await feedReader.get(update.feed_id, SlashtagsFeeds.getFileName(update.fields[1])) + balance = await feedReader.get(update.feed_id, SlashtagsSchema.getFileName(update.fields[0])) + balanceChange = await feedReader.get(update.feed_id, SlashtagsSchema.getFileName(update.fields[1])) }) after(async () => { diff --git a/test/SlashtagsSchema.test.js b/test/SlashtagsSchema.test.js new file mode 100644 index 0000000..8a4b0b2 --- /dev/null +++ b/test/SlashtagsSchema.test.js @@ -0,0 +1,107 @@ +const { strict: assert } = require('node:assert') +const SlashtagsSchema = require('../src/SlashtagsSchema.js') +const fs = require('fs') +const Schema = require('../schemas/slashfeed.json') + +describe('SlashtagsSchema', () => { + const error = { name: 'Slashtags' } + + describe('generateSchema', () => { + describe('it generates and overwrites slashfeed based on config', () => { + let conf + beforeEach(() => { + conf = { + name: Schema.name, + description: Schema.description, + icons: JSON.parse(JSON.stringify(Schema.icons)), + fields: Schema.fields.map(f => JSON.parse(JSON.stringify(f))) + } + }) + + describe('invalid schamaConfig', () => { + describe('missing name', () => { + beforeEach(() => { + delete conf.name + error.message = SlashtagsSchema.err.missingFeedName + }) + + it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) + }) + + describe('missing description', () => { + beforeEach(() => { + delete conf.description + error.message = SlashtagsSchema.err.missingFeedDescription + }) + + it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) + }) + + describe('missing icons', () => { + beforeEach(() => { + delete conf.icons + error.message = SlashtagsSchema.err.missingFeedIcons + }) + + it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) + }) + + describe('missing fields', () => { + beforeEach(() => { + delete conf.fields + error.message = SlashtagsSchema.err.missingFeedFields + }) + + it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) + }) + + describe('fields are not array', () => { + beforeEach(() => { + conf.fields = 'fields' + error.message = SlashtagsSchema.err.invalidFeedFields + }) + + it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) + }) + + describe('invalid icon', () => { + beforeEach(() => { + conf.icons['48'] = 'not an image' + error.message = SlashtagsSchema.err.invalidFeedIcon + }) + + it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) + }) + + describe('invalid field', () => { + describe('missing name', () => { + beforeEach(() => { + delete conf.fields[0].name + error.message = SlashtagsSchema.err.missingFieldName + }) + + it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) + }) + + describe('missing description', () => { + beforeEach(() => { + delete conf.fields[1].description + error.message = SlashtagsSchema.err.missingFieldDescription + }) + + it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) + }) + + describe('invalid type', () => { + beforeEach(() => { + conf.fields[1].type = 'unsupported type' + error.message = SlashtagsSchema.err.badFieldType + }) + + it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) + }) + }) + }) + }) + }) +}) From 5f718c86596b411bdd0ea577d98c02865444e4f4 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Fri, 10 Mar 2023 19:09:19 +0100 Subject: [PATCH 02/17] feed: exchange account type validation and generation Signed-off-by: dzdidi --- src/SlashtagsSchema.js | 6 +- src/schemaTypes/ExchangeAccountFeed.js | 128 +++++++++++++++ test/SlashtagsSchema.test.js | 1 - test/schemaTypes/ExchangeAccountFeed.test.js | 156 +++++++++++++++++++ 4 files changed, 289 insertions(+), 2 deletions(-) create mode 100644 src/schemaTypes/ExchangeAccountFeed.js create mode 100644 test/schemaTypes/ExchangeAccountFeed.test.js diff --git a/src/SlashtagsSchema.js b/src/SlashtagsSchema.js index 9d7f94b..e71dad7 100644 --- a/src/SlashtagsSchema.js +++ b/src/SlashtagsSchema.js @@ -45,6 +45,8 @@ module.exports = class SlashtagsSchema { if (!schemaConfig.description) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFeedDescription) if (!schemaConfig.icons) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFeedIcons) if (!schemaConfig.fields) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFeedFields) + + // FIXME: this is type dependent if (!Array.isArray(schemaConfig.fields)) throw new SlashtagsSchema.Error(SlashtagsSchema.err.invalidFeedFields) const imageRX = /^data:image\/((svg\+xml)|(png));base64,.+$/ @@ -55,6 +57,7 @@ module.exports = class SlashtagsSchema { if (!imageRX.test(icon)) throw new SlashtagsSchema.Error(SlashtagsSchema.err.invalidFeedIcon) } + // FIXME: this is type dependent schemaConfig.fields.forEach((field) => { if (!field.name) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFieldName) if (!field.description) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFieldDescription) @@ -74,7 +77,7 @@ module.exports = class SlashtagsSchema { const schema = { name: schemaConfig.name, description: schemaConfig.description, - type: 'account_feed', + type: 'account_feed', // this must be passed? version: '0.0.1', icons: {} } @@ -83,6 +86,7 @@ module.exports = class SlashtagsSchema { schema.icons[size] = schemaConfig.icons[size] } + // FIXME: this is type dependent schema.fields = schemaConfig.fields.map((field) => { return { name: field.name, diff --git a/src/schemaTypes/ExchangeAccountFeed.js b/src/schemaTypes/ExchangeAccountFeed.js new file mode 100644 index 0000000..4c80235 --- /dev/null +++ b/src/schemaTypes/ExchangeAccountFeed.js @@ -0,0 +1,128 @@ +const Feeds = require('@synonymdev/feeds') +const path = require('path') + +module.exports = class ExchangeAccountFeed { + static REQUIRED_FIELDS = [ + 'balance', + 'pnl', + 'pnl_and_balance' + ] + + static REQUIRED_PROPS_FOR_FIELDS = { + balance: [ + 'label', + 'denomination_type', + 'denomination_ratio', + 'units', + ], + + pnl: [ + 'label', + 'units', + ], + + pnl_and_balance:[ + 'label', + 'denomination_type', + 'denomination_ratio', + 'units', + ] + } + + static generateSchemaFields(schemaFields) { + return { + balance: this._generateBalanceFields(schemaFields.balance), + pnl: this._generatePNLFields(schemaFields.pnl), + pnl_and_balance: this._generatePNLandBalanceFields(schemaFields.pnl_and_balance), + } + } + + + static validateFields(fields) { + this.REQUIRED_FIELDS.forEach((field) => { + if (!fields[field]) throw new Error(`missing ${field}`) + }) + + for (let fieldType in this.REQUIRED_PROPS_FOR_FIELDS) { + for (let fieldName in fields[fieldType]) { + for (let fieldProp of this.REQUIRED_PROPS_FOR_FIELDS[fieldType]) { + if (!fields[fieldType][fieldName][fieldProp]) + throw new Error(`${fieldType} for ${fieldName} is missing ${fieldProp}`) + } + } + } + } + + static validateValues(fields) { + this._validateBalanceValues(fields) + this._validatePNLandBalanceValues(fields) + } + + static _validateBalanceValues(fields) { + for (let fieldName in fields.balance) { + if (!['main', 'base'].includes(fields.balance[fieldName].denomination_type)) + throw new Error('balance denomination_type must be "main" or "base"') + if (!/[1-9]+/.test(fields.balance[fieldName].denomination_ratio.toString())) + throw new Error('balance denomination_ratio must be natural number more or equal 1') + } + } + + static _validatePNLandBalanceValues(fields) { + for (let fieldName in fields.pnl_and_balance) { + if (!['main', 'base'].includes(fields.pnl_and_balance[fieldName].denomination_type)) + throw new Error('pnl_and_balance denomination_type must be "main" or "base"') + if (!/[1-9]+/.test(fields.pnl_and_balance[fieldName].denomination_ratio.toString())) + throw new Error('pnl_and_balance denomination_ratio must be natural number more or equal 1') + } + } + + static _generateBalanceFields(balanceFields) { + let res = {} + for (let balanceName in balanceFields) { + res[balanceName] = { + label: balanceFields[balanceName].label, + denomination_type: balanceFields[balanceName].denomination_type, + denomination_ratio: balanceFields[balanceName].denomination_ratio, + units: balanceFields[balanceName].units, + main: path.join(Feeds.FEED_PREFIX, this._getFileName(balanceName)), + } + } + + return res + } + + static _generatePNLFields(pnlFields) { + let res = {} + for (let pnlName in pnlFields) { + res[pnlName] = { + label: pnlFields[pnlName].label, + units: pnlFields[pnlName].units, + main: path.join(Feeds.FEED_PREFIX, this._getFileName(pnlName)), + } + } + + return res + } + + static _generatePNLandBalanceFields(pnlBalanceFields) { + let res = {} + for (let pnlBalanceName in pnlBalanceFields) { + res[pnlBalanceName] = { + label: pnlBalanceFields[pnlBalanceName].label, + denomination_type: pnlBalanceFields[pnlBalanceName].denomination_type, + denomination_ratio: pnlBalanceFields[pnlBalanceName].denomination_ratio, + units: pnlBalanceFields[pnlBalanceName].units, + main: path.join(Feeds.FEED_PREFIX, this._getFileName(pnlBalanceName)), + } + } + + return res + } + + static _getFileName (fieldName) { + const regex = /[^a-z0-9]+/gi + const trailing = /-+$/ + + return `/${fieldName.toLowerCase().trim().replace(regex, '-').replace(trailing, '')}/` + } +} diff --git a/test/SlashtagsSchema.test.js b/test/SlashtagsSchema.test.js index 8a4b0b2..a72f228 100644 --- a/test/SlashtagsSchema.test.js +++ b/test/SlashtagsSchema.test.js @@ -1,6 +1,5 @@ const { strict: assert } = require('node:assert') const SlashtagsSchema = require('../src/SlashtagsSchema.js') -const fs = require('fs') const Schema = require('../schemas/slashfeed.json') describe('SlashtagsSchema', () => { diff --git a/test/schemaTypes/ExchangeAccountFeed.test.js b/test/schemaTypes/ExchangeAccountFeed.test.js new file mode 100644 index 0000000..8d512dd --- /dev/null +++ b/test/schemaTypes/ExchangeAccountFeed.test.js @@ -0,0 +1,156 @@ +const { strict: assert } = require('node:assert') +const ExchangeAccountFeed = require('../../src/schemaTypes/ExchangeAccountFeed.js') + +describe('ExchangeAccountFeed', () => { + const validExchangeAccountSchemaFields = { + "balance": { + "btc balance": { + "label": "description or label", + "denomination_type": "main", + "denomination_ratio": 8, + "main": "path to value in slashdrive", + "units": "sign to be shown next to value", + }, + }, + "pnl": { + "spot pnl": { + "main": "path to value on slashdrive, the value example is { absolute: 75, relative: 12 }", + "label": "description or label", + "units": "sign to be shown next to absolute value, relative value always shown with % sign", + }, + }, + "pnl_and_balance": { + "spot pnl and balance": { + "label": "description or label", + "main": "path to value on slashdrive, the value example is { balance: 100, absolute_pnl: 75, relative_pnl: 300 }", + "denomination_type": "base", + "denomination_ratio": 8, + "units": "sign to be shown next to absolute value, relative value always shown with % sign", + }, + } + } + + describe('Invalid fields', () => { + let invalidFields + beforeEach(() => invalidFields = JSON.parse(JSON.stringify(validExchangeAccountSchemaFields))) + + for (let fieldType in ExchangeAccountFeed.REQUIRED_PROPS_FOR_FIELDS) { + for (let fieldName in validExchangeAccountSchemaFields[fieldType]) { + for (let fieldProp of ExchangeAccountFeed.REQUIRED_PROPS_FOR_FIELDS[fieldType]) { + const message = `${fieldType} for ${fieldName} is missing ${fieldProp}` + describe(message, () => { + beforeEach(() => delete invalidFields[fieldType][fieldName][fieldProp]) + it('fails', () => assert.throws(() => ExchangeAccountFeed.validateFields(invalidFields), { message })) + }) + } + } + } + }) + + describe('Invalid field values', () => { + describe('Invalid denomination_type', () => { + let invalidFields + beforeEach(() => { + invalidFields = JSON.parse(JSON.stringify(validExchangeAccountSchemaFields)) + invalidFields.balance['btc balance'].denomination_type = 'wrong' + }) + it('fails', () => assert.throws(() => ExchangeAccountFeed.validateValues(invalidFields), { + message: 'balance denomination_type must be "main" or "base"' + })) + }) + + describe('Invalid denomination_ratio', () => { + let invalidFields + beforeEach(() => { + invalidFields = JSON.parse(JSON.stringify(validExchangeAccountSchemaFields)) + invalidFields.pnl_and_balance['spot pnl and balance'].denomination_ratio = 'wrong' + }) + it('fails', () => { + assert.throws(() => ExchangeAccountFeed.validateValues(invalidFields), { + message: 'pnl_and_balance denomination_ratio must be natural number more or equal 1' + }) + }) + }) + }) + + describe('Generated scheam', () => { + let res + before(() => res = ExchangeAccountFeed.generateSchemaFields(validExchangeAccountSchemaFields)) + + it('has balance', () => assert(res.balance)) + describe('balance', () => { + it('has specified blance property', () => assert(res.balance['btc balance'])) + it('has contains label', () => assert.equal( + res.balance['btc balance'].label, + validExchangeAccountSchemaFields.balance['btc balance'].label + )) + + it('has contains denomination_type', () => assert.equal( + res.balance['btc balance'].denomination_type, + validExchangeAccountSchemaFields.balance['btc balance'].denomination_type + )) + + it('has contains denomination_ratio', () => assert.equal( + res.balance['btc balance'].denomination_ratio, + validExchangeAccountSchemaFields.balance['btc balance'].denomination_ratio + )) + + it('has contains main', () => assert.equal( + res.balance['btc balance'].main, + '/feed/btc-balance/' + )) + + it('has contains units', () => assert.equal( + res.balance['btc balance'].units, + validExchangeAccountSchemaFields.balance['btc balance'].units + )) + }) + + describe('pnl', () => { + it('has specified blance property', () => assert(res.pnl['spot pnl'])) + it('has contains label', () => assert.equal( + res.pnl['spot pnl'].label, + validExchangeAccountSchemaFields.pnl['spot pnl'].label + )) + + it('has contains main', () => assert.equal( + res.pnl['spot pnl'].main, + '/feed/spot-pnl/' + )) + + it('has contains units', () => assert.equal( + res.pnl['spot pnl'].units, + validExchangeAccountSchemaFields.pnl['spot pnl'].units + )) + }) + + it('has pnl_and_balance', () => assert(res.pnl_and_balance)) + describe('pnl_and_balance', () => { + it('has specified blance property', () => assert(res.pnl_and_balance['spot pnl and balance'])) + it('has contains label', () => assert.equal( + res.pnl_and_balance['spot pnl and balance'].label, + validExchangeAccountSchemaFields.pnl_and_balance['spot pnl and balance'].label + )) + + it('has contains denomination_type', () => assert.equal( + res.pnl_and_balance['spot pnl and balance'].denomination_type, + validExchangeAccountSchemaFields.pnl_and_balance['spot pnl and balance'].denomination_type + )) + + it('has contains denomination_ratio', () => assert.equal( + res.pnl_and_balance['spot pnl and balance'].denomination_ratio, + validExchangeAccountSchemaFields.pnl_and_balance['spot pnl and balance'].denomination_ratio + )) + + it('has contains main', () => assert.equal( + res.pnl_and_balance['spot pnl and balance'].main, + '/feed/spot-pnl-and-balance/' + )) + + it('has contains units', () => assert.equal( + res.pnl_and_balance['spot pnl and balance'].units, + validExchangeAccountSchemaFields.pnl_and_balance['spot pnl and balance'].units + )) + }) + }) +}) From fc1915adfe1a1bfcff16e2df3f63866c5459e1fd Mon Sep 17 00:00:00 2001 From: dzdidi Date: Fri, 10 Mar 2023 21:41:45 +0100 Subject: [PATCH 03/17] slashfeed: new format Signed-off-by: dzdidi --- schemas/slashfeed.json | 62 +++++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/schemas/slashfeed.json b/schemas/slashfeed.json index 3bb3dd4..49031e8 100644 --- a/schemas/slashfeed.json +++ b/schemas/slashfeed.json @@ -1,25 +1,55 @@ { "name": "Bitfinex", "description": "Bitfinex account feed", - "type": "account_feed", + "type": "exchange_account_feed", "version": "0.0.1", "icons": { "48": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAyNHB4IiBoZWlnaHQ9IjEwMjRweCIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgPGNpcmNsZSBjeD0iNTEyIiBjeT0iNTEyIiByPSI1MTIiIHN0eWxlPSJmaWxsOiMwM2NhOWIiLz4KICAgPHBhdGggZD0iTTczMi44MSAyNzVjLTEuNjctLjczLTE3My41MS0yNC42LTM0My4zOSA4Ni44OEMyODQgNDMxLjIyIDI3MCA1MzIuNjggMjc0LjgxIDYwMC4zNmMyNDcuMDgtMjcuODkgNDUyLjQxLTMxNy4yOSA0NTgtMzI1LjM2ek0yOTEuNTcgNjc1LjUzYzIxLjI0IDM4LjE0IDE4MS43NyAxMjQuNiAzMjMuODcgNS4zNVM3NDUuNjIgMzQ0LjY2IDczMi44MSAyNzVjLTQuNDcgMTAuMTEtMTU5LjYyIDM1Ni44OS00NDEuMjQgNDAwLjUxIiBzdHlsZT0iZmlsbDojZmZmIi8+Cjwvc3ZnPgo=" }, - "fields": [ - { - "name": "Bitcoin", - "description": "Bitcoin balance", - "main": "/feed/bitcoin/", - "type": "currency", - "units": "btc" + "fields": { + "balance": { + "bitcoin futures balance": { + "label": "bitcoin futures balance", + "denomination_type": "base", + "denomination_ratio": "8", + "main": "/feed/bitcoin-futures-balance/", + "units": "sats" + }, + "bitcoin spot balance": { + "label": "bitcoin spot balance", + "denomination_type": "base", + "denomination_ratio": "8", + "main": "/feed/bitcoin-spot-balance/", + "units": "sats" + } }, - { - "name": "Bitcoin P/L", - "description": "Bitcoin relative balance change", - "main": "/feed/bitcoin-p-l/", - "type": "delta", - "units": "%" + "pnl": { + "bitcoin futures pnl": { + "label": "bitcoin futures pnl", + "main": "/feed/bitcoin-futures-pnl/", + "units": "sats" + }, + "bitcoin spot pnl": { + "label": "bitcoin spot pnl", + "main": "/feed/bitcoin-spot-pnl/", + "units": "sats" + } + }, + "pnl_and_balance": { + "bitcoin futures pnl and balance": { + "label": "bitcoin futures pnl and balance", + "denomination_type": "base", + "denomination_ratio": "8", + "main": "/feed/bitcoin-futures-pnl-and-balance/", + "units": "sats" + }, + "bitcoin spot pnl and balance": { + "label": "bitcoin spot pnl and balance", + "denomination_type": "base", + "denomination_ratio": "8", + "main": "/feed/bitcoin-spot-pnl-and-balance/", + "units": "sats" + } } - ] -} \ No newline at end of file + } +} From 16bbb08009bd5eb45d455f768b32c4cb6750adfe Mon Sep 17 00:00:00 2001 From: dzdidi Date: Fri, 10 Mar 2023 21:42:37 +0100 Subject: [PATCH 04/17] Feeds: rename update method, adjust logic to new schema Signed-off-by: dzdidi --- src/Feeds.js | 46 ++++++++++++++++++++++------------------ test/Feeds.test.js | 53 +++++++++++++++++++++++++++++----------------- 2 files changed, 59 insertions(+), 40 deletions(-) diff --git a/src/Feeds.js b/src/Feeds.js index b1f09e9..533710f 100644 --- a/src/Feeds.js +++ b/src/Feeds.js @@ -92,7 +92,7 @@ module.exports = class SlashtagsFeeds { * @param {Object} updates[].wallet_name feed id to update * @param {Object} updates[].amount amount */ - async updateFeedBalance (update) { + async updateFeed (update) { if (!this.ready) throw new Err(_err.notReady) this.validateUpdate(update) @@ -103,7 +103,7 @@ module.exports = class SlashtagsFeeds { try { // NOTE: consider storing balance on db as well for (const field of update.fields) { - await this._slashfeeds.update(update.feed_id, SlashtagsSchema.getFileName(field), field.value) + await this._slashfeeds.update(update.feed_id, SlashtagsSchema.getFileName(field.name), field.value) } return { updated: true } } catch (err) { @@ -207,17 +207,15 @@ module.exports = class SlashtagsFeeds { * @param {String} feedId */ async _initFeed (args) { - return Promise.all( - this.feed_schema.fields.map( - async (field) => { - await this._slashfeeds.update( - args.feed_id, - SlashtagsSchema.getFileName(field), - args.init_data || null - ) - } - ) - ) + for (let field in this.feed_schema.fields) { + for (let fieldName in this.feed_schema.fields[field]) { + await this._slashfeeds.update( + args.feed_id, + SlashtagsSchema.getFileName(fieldName), + args.init_data || null + ) + } + } } async createFeed (args) { @@ -322,16 +320,22 @@ module.exports = class SlashtagsFeeds { if (!Array.isArray(update.fields)) throw new Err(_err.invalidFeedFields) if (update.fields.length === 0) throw new Err(_err.invalidFeedFields) - for (const field of update.fields) { - this.validateFieldUpdate(field) + const { validateFieldsValues } = require( + `${__dirname}/schemaTypes/${this.snakeToCamel(this._slashfeeds.type || 'exchange_account_feed')}.js` + ) + + for (let field of update.fields) { + if (!field.name) throw new Err(_err.missingFieldName) + } + + for (let field of update.fields) { + if (!field.value) throw new Err(_err.missingFieldValue) } - } - validateFieldUpdate (field) { - if (!field.name) throw new Err(_err.missingFieldName) - if (!field.value) throw new Err(_err.missingFieldValue) + validateFieldsValues(update.fields, this.feed_schema.fields) + } - const schemaField = this.feed_schema.fields.find((sF) => sF.name === field.name) - if (!schemaField) throw new Err(_err.unknownField) + snakeToCamel (str) { + return str.toLowerCase().replace(/([-_][a-z])/g, group => group.toUpperCase().replace('-', '').replace('_', '')) } } diff --git a/test/Feeds.test.js b/test/Feeds.test.js index 07f62f9..e8adfe8 100644 --- a/test/Feeds.test.js +++ b/test/Feeds.test.js @@ -28,7 +28,7 @@ describe('SlashtagsFeeds', () => { name: Schema.name, description: Schema.description, icons: JSON.parse(JSON.stringify(Schema.icons)), - fields: Schema.fields.map(f => JSON.parse(JSON.stringify(f))) + fields: JSON.parse(JSON.stringify(Schema.fields)) } }) @@ -215,7 +215,6 @@ describe('SlashtagsFeeds', () => { after(() => feed.db.insert = insertFeed) it('fails with no feed error', async function () { - this.timeout(5000) await assert.rejects(async () => feed.createFeed(input), error) }) }) @@ -452,18 +451,34 @@ describe('SlashtagsFeeds', () => { }) }) - describe('updateFeedBalance', () => { + describe('updateFeed', () => { const update = { feed_id: 'testUpdateFeed', fields: [ { - name: 'Bitcoin', + name: 'bitcoin futures balance', + value: 11, + }, + { + name: 'bitcoin options balance', value: 12, }, { - name: 'Bitcoin P/L', - value: 1, - } + name: 'bitcoin futures pnl', + value: { absolute: 1, relative: 10 }, + }, + { + name: 'bitcoin options pnl', + value: { absolute: 2, relative: 20 }, + }, + { + name: 'bitcoin futures pnl and balance', + value: { balance: 10, absolute_pnl: 1, relative_pnl: 10 }, + }, + { + name: 'bitcoin options pnl and balance', + value: { balance: 10, absolute_pnl: 1, relative_pnl: 10 }, + }, ] } @@ -484,7 +499,7 @@ describe('SlashtagsFeeds', () => { }) after(() => feed.ready = true) - it('fails if slahstags is not ready', async () => assert.rejects(async () => feed.updateFeedBalance(update), error)) + it('fails if slahstags is not ready', async () => assert.rejects(async () => feed.updateFeed(update), error)) }) describe('Input handling', () => { @@ -495,7 +510,7 @@ describe('SlashtagsFeeds', () => { input = { ...update, feed_id: undefined } error.message = SlashtagsFeeds.err.feedIdMissing }) - it('throws an error', async () => assert.rejects(async () => feed.updateFeedBalance(input), error)) + it('throws an error', async () => assert.rejects(async () => feed.updateFeed(input), error)) }) describe('fields is missing', () => { @@ -503,7 +518,7 @@ describe('SlashtagsFeeds', () => { input = { ...update, fields: undefined } error.message = SlashtagsFeeds.err.missingFields }) - it('throws an error', async () => assert.rejects(async () => feed.updateFeedBalance(input), error)) + it('throws an error', async () => assert.rejects(async () => feed.updateFeed(input), error)) }) describe('fields is not an array', () => { @@ -511,7 +526,7 @@ describe('SlashtagsFeeds', () => { input = { ...update, fields: 'fields' } error.message = SlashtagsFeeds.err.invalidFeedFields }) - it('throws an error', async () => assert.rejects(async () => feed.updateFeedBalance(input), error)) + it('throws an error', async () => assert.rejects(async () => feed.updateFeed(input), error)) }) describe('fields is empty array', () => { @@ -519,7 +534,7 @@ describe('SlashtagsFeeds', () => { input = { ...update, fields: [] } error.message = SlashtagsFeeds.err.invalidFeedFields }) - it('throws an error', async () => assert.rejects(async () => feed.updateFeedBalance(input), error)) + it('throws an error', async () => assert.rejects(async () => feed.updateFeed(input), error)) }) describe('field is missing name', () => { @@ -527,7 +542,7 @@ describe('SlashtagsFeeds', () => { input = { ...update, fields: [{ value: 1 }]} error.message = SlashtagsFeeds.err.missingFieldName }) - it('throws an error', async () => assert.rejects(async () => feed.updateFeedBalance(input), error)) + it('throws an error', async () => assert.rejects(async () => feed.updateFeed(input), error)) }) describe('field is missing value', () => { @@ -535,7 +550,7 @@ describe('SlashtagsFeeds', () => { input = { ...update, fields: [{ name: 1 }]} error.message = SlashtagsFeeds.err.missingFieldValue }) - it('throws an error', async () => assert.rejects(async () => feed.updateFeedBalance(input), error)) + it('throws an error', async () => assert.rejects(async () => feed.updateFeed(input), error)) }) }) @@ -546,7 +561,7 @@ describe('SlashtagsFeeds', () => { }) it('throws an error', async () => assert.rejects( - async () => feed.updateFeedBalance({...update, feed_id: 'do_not_exist' }), + async () => feed.updateFeed({...update, feed_id: 'do_not_exist' }), error )) }) @@ -567,7 +582,7 @@ describe('SlashtagsFeeds', () => { }) it('throws an error', async () => assert.rejects( - async () => feed.updateFeedBalance({...update, feed_id: 'exist' }), + async () => feed.updateFeed({...update, feed_id: 'exist' }), error )) }) @@ -580,7 +595,7 @@ describe('SlashtagsFeeds', () => { await feed.deleteFeed({ feed_id: update.feed_id }) await feed.createFeed({ feed_id: update.feed_id }) - res = await feed.updateFeedBalance(update) + res = await feed.updateFeed(update) }) it('returns true', () => assert.deepStrictEqual(res, { updated: true })) @@ -591,8 +606,8 @@ describe('SlashtagsFeeds', () => { before(async () => { await feed.stop() feedReader = new Feeds(validConfig.slashtags, validConfig.feed_schema) - balance = await feedReader.get(update.feed_id, SlashtagsSchema.getFileName(update.fields[0])) - balanceChange = await feedReader.get(update.feed_id, SlashtagsSchema.getFileName(update.fields[1])) + balance = await feedReader.get(update.feed_id, SlashtagsSchema.getFileName(update.fields[0].name)) + balanceChange = await feedReader.get(update.feed_id, SlashtagsSchema.getFileName(update.fields[1].name)) }) after(async () => { From 13ededd7d2e701d0e38d2305edbf8b55e2cc276d Mon Sep 17 00:00:00 2001 From: dzdidi Date: Fri, 10 Mar 2023 21:43:25 +0100 Subject: [PATCH 05/17] SlashtagsSchema: update generation to new schema Signed-off-by: dzdidi --- src/SlashtagsSchema.js | 66 +++++++---------------- test/SlashtagsSchema.test.js | 102 +++++++++++------------------------ 2 files changed, 51 insertions(+), 117 deletions(-) diff --git a/src/SlashtagsSchema.js b/src/SlashtagsSchema.js index e71dad7..23c5140 100644 --- a/src/SlashtagsSchema.js +++ b/src/SlashtagsSchema.js @@ -1,6 +1,4 @@ -const path = require('path') const fs = require('fs') -const Feeds = require('@synonymdev/feeds') const customErr = require('./CustomError.js') const Err = customErr({ errName: 'Slashtags', fileName: __filename }) @@ -17,6 +15,9 @@ const _err = { missingFieldDescription: 'MISSING_FIELD_DESCRIPTION', missingFieldUnits: 'MISSING_FIELD_UNITS', badFieldType: 'UNSUPPORTED_FIELD_TYPE', + + invalidField: 'INVALID_FIELD', + invalidFieldValue: 'INVALID_FIELD_VALUE' } module.exports = class SlashtagsSchema { @@ -25,30 +26,12 @@ module.exports = class SlashtagsSchema { static DEFAULT_SCHEMA_PATH = './schemas/slashfeed.json' - static DEFAULT_TYPES = [ - 'number', - 'utf-8' - ] - - static MEASURED_TYPES = [ - 'currency', - 'delta' - ] - - static VALID_TYPES = [ - ...this.DEFAULT_TYPES, - ...this.MEASURED_TYPES - ] - static validateSchemaConfig (schemaConfig) { if (!schemaConfig.name) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFeedName) if (!schemaConfig.description) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFeedDescription) if (!schemaConfig.icons) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFeedIcons) if (!schemaConfig.fields) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFeedFields) - // FIXME: this is type dependent - if (!Array.isArray(schemaConfig.fields)) throw new SlashtagsSchema.Error(SlashtagsSchema.err.invalidFeedFields) - const imageRX = /^data:image\/((svg\+xml)|(png));base64,.+$/ for (const size in schemaConfig.icons) { const icon = schemaConfig.icons[size] @@ -57,28 +40,25 @@ module.exports = class SlashtagsSchema { if (!imageRX.test(icon)) throw new SlashtagsSchema.Error(SlashtagsSchema.err.invalidFeedIcon) } - // FIXME: this is type dependent - schemaConfig.fields.forEach((field) => { - if (!field.name) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFieldName) - if (!field.description) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFieldDescription) - if (field.type && (field.type !== '') && !SlashtagsSchema.VALID_TYPES.includes(field.type)) { - throw new SlashtagsSchema.Error(SlashtagsSchema.err.badFieldType) - } - - if (this.MEASURED_TYPES.includes(field.type)) { - if (!field.units) throw new SlashtagsSchema.Error(SlashtagsSchema.err.missingFieldUnits) - } - }) + const { validateSchemaFields, validateSchemaValues } = require( + `${__dirname}/schemaTypes/${this.snakeToCamel(schemaConfig.type || 'exchange_account_feed')}.js` + ) + validateSchemaFields(schemaConfig.fields, SlashtagsSchema.err.invalidField) + validateSchemaValues(schemaConfig.fields, SlashtagsSchema.err.invalidFieldValue) } static generateSchema (schemaConfig) { SlashtagsSchema.validateSchemaConfig(schemaConfig) + const { generateSchemaFields } = require( + `${__dirname}/schemaTypes/${this.snakeToCamel(schemaConfig.type || 'exchange_account_feed')}.js` + ) + const schema = { name: schemaConfig.name, description: schemaConfig.description, - type: 'account_feed', // this must be passed? - version: '0.0.1', + type: schemaConfig.type || 'exchange_account_feed', + version: schemaConfig.version || '0.0.1', icons: {} } @@ -86,16 +66,7 @@ module.exports = class SlashtagsSchema { schema.icons[size] = schemaConfig.icons[size] } - // FIXME: this is type dependent - schema.fields = schemaConfig.fields.map((field) => { - return { - name: field.name, - description: field.description, - main: path.join(Feeds.FEED_PREFIX, SlashtagsSchema.getFileName(field)), - type: field.type || 'utf-8', - units: field.units - } - }) + schema.fields = generateSchemaFields(schemaConfig.fields) return schema } @@ -104,10 +75,13 @@ module.exports = class SlashtagsSchema { fs.writeFileSync(this.DEFAULT_SCHEMA_PATH, Buffer.from(JSON.stringify(schema, undefined, 2)), 'utf-8') } - static getFileName (field) { + static getFileName (fieldName) { const regex = /[^a-z0-9]+/gi const trailing = /-+$/ - return `/${field.name.toLowerCase().trim().replace(regex, '-').replace(trailing, '')}/` + return `/${fieldName.toLowerCase().trim().replace(regex, '-').replace(trailing, '')}/` + } + static snakeToCamel (str) { + return str.toLowerCase().replace(/([-_][a-z])/g, group => group.toUpperCase().replace('-', '').replace('_', '')) } } diff --git a/test/SlashtagsSchema.test.js b/test/SlashtagsSchema.test.js index a72f228..c4d7deb 100644 --- a/test/SlashtagsSchema.test.js +++ b/test/SlashtagsSchema.test.js @@ -6,100 +6,60 @@ describe('SlashtagsSchema', () => { const error = { name: 'Slashtags' } describe('generateSchema', () => { - describe('it generates and overwrites slashfeed based on config', () => { + describe('schemaConfig validation', () => { let conf beforeEach(() => { conf = { name: Schema.name, description: Schema.description, icons: JSON.parse(JSON.stringify(Schema.icons)), - fields: Schema.fields.map(f => JSON.parse(JSON.stringify(f))) + fields: JSON.parse(JSON.stringify(Schema.fields)) } }) - describe('invalid schamaConfig', () => { - describe('missing name', () => { - beforeEach(() => { - delete conf.name - error.message = SlashtagsSchema.err.missingFeedName - }) - - it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) - }) - - describe('missing description', () => { - beforeEach(() => { - delete conf.description - error.message = SlashtagsSchema.err.missingFeedDescription - }) - - it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) + describe('missing name', () => { + beforeEach(() => { + delete conf.name + error.message = SlashtagsSchema.err.missingFeedName }) - describe('missing icons', () => { - beforeEach(() => { - delete conf.icons - error.message = SlashtagsSchema.err.missingFeedIcons - }) + it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) + }) - it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) + describe('missing description', () => { + beforeEach(() => { + delete conf.description + error.message = SlashtagsSchema.err.missingFeedDescription }) - describe('missing fields', () => { - beforeEach(() => { - delete conf.fields - error.message = SlashtagsSchema.err.missingFeedFields - }) + it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) + }) - it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) + describe('missing icons', () => { + beforeEach(() => { + delete conf.icons + error.message = SlashtagsSchema.err.missingFeedIcons }) - describe('fields are not array', () => { - beforeEach(() => { - conf.fields = 'fields' - error.message = SlashtagsSchema.err.invalidFeedFields - }) + it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) + }) - it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) + describe('missing fields', () => { + beforeEach(() => { + delete conf.fields + error.message = SlashtagsSchema.err.missingFeedFields }) - describe('invalid icon', () => { - beforeEach(() => { - conf.icons['48'] = 'not an image' - error.message = SlashtagsSchema.err.invalidFeedIcon - }) + it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) + }) - it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) + describe('invalid icon', () => { + beforeEach(() => { + conf.icons['48'] = 'not an image' + error.message = SlashtagsSchema.err.invalidFeedIcon }) - describe('invalid field', () => { - describe('missing name', () => { - beforeEach(() => { - delete conf.fields[0].name - error.message = SlashtagsSchema.err.missingFieldName - }) - - it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) - }) - - describe('missing description', () => { - beforeEach(() => { - delete conf.fields[1].description - error.message = SlashtagsSchema.err.missingFieldDescription - }) - - it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) - }) - - describe('invalid type', () => { - beforeEach(() => { - conf.fields[1].type = 'unsupported type' - error.message = SlashtagsSchema.err.badFieldType - }) - - it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) - }) - }) + it('throws an error', () => assert.throws(() => SlashtagsSchema.generateSchema(conf), error)) }) }) }) From 1abafd5e9d77c5861585f8a52ad851cee3f1b0b5 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Fri, 10 Mar 2023 21:43:58 +0100 Subject: [PATCH 06/17] ExchangeAccountFeed: generation; validation Signed-off-by: dzdidi --- src/schemaTypes/ExchangeAccountFeed.js | 79 ++++++++++++++------ test/schemaTypes/ExchangeAccountFeed.test.js | 4 +- 2 files changed, 60 insertions(+), 23 deletions(-) diff --git a/src/schemaTypes/ExchangeAccountFeed.js b/src/schemaTypes/ExchangeAccountFeed.js index 4c80235..e5dccd0 100644 --- a/src/schemaTypes/ExchangeAccountFeed.js +++ b/src/schemaTypes/ExchangeAccountFeed.js @@ -31,48 +31,85 @@ module.exports = class ExchangeAccountFeed { static generateSchemaFields(schemaFields) { return { - balance: this._generateBalanceFields(schemaFields.balance), - pnl: this._generatePNLFields(schemaFields.pnl), - pnl_and_balance: this._generatePNLandBalanceFields(schemaFields.pnl_and_balance), + balance: ExchangeAccountFeed._generateBalanceFields(schemaFields.balance), + pnl: ExchangeAccountFeed._generatePNLFields(schemaFields.pnl), + pnl_and_balance: ExchangeAccountFeed._generatePNLandBalanceFields(schemaFields.pnl_and_balance), } } - static validateFields(fields) { - this.REQUIRED_FIELDS.forEach((field) => { - if (!fields[field]) throw new Error(`missing ${field}`) + static validateSchemaFields(fields, err) { + ExchangeAccountFeed.REQUIRED_FIELDS.forEach((field) => { + if (!fields[field]) throw err || new Error(`missing ${field}`) }) - for (let fieldType in this.REQUIRED_PROPS_FOR_FIELDS) { + for (let fieldType in ExchangeAccountFeed.REQUIRED_PROPS_FOR_FIELDS) { for (let fieldName in fields[fieldType]) { - for (let fieldProp of this.REQUIRED_PROPS_FOR_FIELDS[fieldType]) { + for (let fieldProp of ExchangeAccountFeed.REQUIRED_PROPS_FOR_FIELDS[fieldType]) { if (!fields[fieldType][fieldName][fieldProp]) - throw new Error(`${fieldType} for ${fieldName} is missing ${fieldProp}`) + throw err || new Error(`${fieldType} for ${fieldName} is missing ${fieldProp}`) } } } } - static validateValues(fields) { - this._validateBalanceValues(fields) - this._validatePNLandBalanceValues(fields) + static validateSchemaValues(fields, err) { + ExchangeAccountFeed._validateSchemaBalanceValues(fields, err) + ExchangeAccountFeed._validateSchemaPNLandBalanceValues(fields, err) } - static _validateBalanceValues(fields) { + static validateFieldsValues(updates, fields) { + for (let balanceField in fields.balance) { + updates.forEach((field) => { + if (field.name === balanceField) ExchangeAccountFeed.validateBalanceValue(field.value) + }) + } + + for (let pnlField in fields.pnl) { + updates.forEach((field) => { + if (field.name === pnlField) ExchangeAccountFeed.validatePNLValue(field.value) + }) + } + + for (let pnlAndBalanceField in fields.pnl_and_balance) { + updates.forEach((field) => { + if (field.name === pnlAndBalanceField) ExchangeAccountFeed.validatePNLandBalanceValue(field.value) + }) + } + } + + static validateBalanceValue(value) { + if (isNaN(parseFloat(value))) throw new Error('invalid balance') + } + + static validatePNLValue(value) { + const { absolute, relative } = value + if (isNaN(parseFloat(absolute))) throw new Error('invalid absolute') + if (isNaN(parseFloat(relative))) throw new Error('invalid relative') + } + + static validatePNLandBalanceValue(value) { + const { absolute_pnl, relative_pnl, balance } = value + if (isNaN(parseFloat(balance))) throw new Error('invalid balance') + if (isNaN(parseFloat(absolute_pnl))) throw new Error('invalid absolute') + if (isNaN(parseFloat(relative_pnl))) throw new Error('invalid relative') + } + + static _validateSchemaBalanceValues(fields, err) { for (let fieldName in fields.balance) { if (!['main', 'base'].includes(fields.balance[fieldName].denomination_type)) - throw new Error('balance denomination_type must be "main" or "base"') + throw err || new Error('balance denomination_type must be "main" or "base"') if (!/[1-9]+/.test(fields.balance[fieldName].denomination_ratio.toString())) - throw new Error('balance denomination_ratio must be natural number more or equal 1') + throw err || new Error('balance denomination_ratio must be natural number more or equal 1') } } - static _validatePNLandBalanceValues(fields) { + static _validateSchemaPNLandBalanceValues(fields, err) { for (let fieldName in fields.pnl_and_balance) { if (!['main', 'base'].includes(fields.pnl_and_balance[fieldName].denomination_type)) - throw new Error('pnl_and_balance denomination_type must be "main" or "base"') + throw err || new Error('pnl_and_balance denomination_type must be "main" or "base"') if (!/[1-9]+/.test(fields.pnl_and_balance[fieldName].denomination_ratio.toString())) - throw new Error('pnl_and_balance denomination_ratio must be natural number more or equal 1') + throw err || new Error('pnl_and_balance denomination_ratio must be natural number more or equal 1') } } @@ -84,7 +121,7 @@ module.exports = class ExchangeAccountFeed { denomination_type: balanceFields[balanceName].denomination_type, denomination_ratio: balanceFields[balanceName].denomination_ratio, units: balanceFields[balanceName].units, - main: path.join(Feeds.FEED_PREFIX, this._getFileName(balanceName)), + main: path.join(Feeds.FEED_PREFIX, ExchangeAccountFeed._getFileName(balanceName)), } } @@ -97,7 +134,7 @@ module.exports = class ExchangeAccountFeed { res[pnlName] = { label: pnlFields[pnlName].label, units: pnlFields[pnlName].units, - main: path.join(Feeds.FEED_PREFIX, this._getFileName(pnlName)), + main: path.join(Feeds.FEED_PREFIX, ExchangeAccountFeed._getFileName(pnlName)), } } @@ -112,7 +149,7 @@ module.exports = class ExchangeAccountFeed { denomination_type: pnlBalanceFields[pnlBalanceName].denomination_type, denomination_ratio: pnlBalanceFields[pnlBalanceName].denomination_ratio, units: pnlBalanceFields[pnlBalanceName].units, - main: path.join(Feeds.FEED_PREFIX, this._getFileName(pnlBalanceName)), + main: path.join(Feeds.FEED_PREFIX, ExchangeAccountFeed._getFileName(pnlBalanceName)), } } diff --git a/test/schemaTypes/ExchangeAccountFeed.test.js b/test/schemaTypes/ExchangeAccountFeed.test.js index 8d512dd..3d70a7f 100644 --- a/test/schemaTypes/ExchangeAccountFeed.test.js +++ b/test/schemaTypes/ExchangeAccountFeed.test.js @@ -54,7 +54,7 @@ describe('ExchangeAccountFeed', () => { invalidFields = JSON.parse(JSON.stringify(validExchangeAccountSchemaFields)) invalidFields.balance['btc balance'].denomination_type = 'wrong' }) - it('fails', () => assert.throws(() => ExchangeAccountFeed.validateValues(invalidFields), { + it('fails', () => assert.throws(() => ExchangeAccountFeed.validateSchemaValues(invalidFields), { message: 'balance denomination_type must be "main" or "base"' })) }) @@ -66,7 +66,7 @@ describe('ExchangeAccountFeed', () => { invalidFields.pnl_and_balance['spot pnl and balance'].denomination_ratio = 'wrong' }) it('fails', () => { - assert.throws(() => ExchangeAccountFeed.validateValues(invalidFields), { + assert.throws(() => ExchangeAccountFeed.validateSchemaValues(invalidFields), { message: 'pnl_and_balance denomination_ratio must be natural number more or equal 1' }) }) From 5afc712fdfd3b51d5e5c3f24d430ba2702f89a1e Mon Sep 17 00:00:00 2001 From: dzdidi Date: Sat, 11 Mar 2023 07:08:06 +0100 Subject: [PATCH 07/17] RPC: update method renaming Signed-off-by: dzdidi --- src/RPC.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RPC.js b/src/RPC.js index 470a3d4..74d04a6 100644 --- a/src/RPC.js +++ b/src/RPC.js @@ -91,7 +91,7 @@ module.exports = function (config) { { name: 'updateFeed', description: 'Update feed feed', - svc: 'feeds.updateFeedBalance' + svc: 'feeds.updateFeed' }, { name: 'getFeed', From eb2b1df5ef48d12f7ae1b2c94839898d2602bf4e Mon Sep 17 00:00:00 2001 From: dzdidi Date: Sat, 11 Mar 2023 07:08:49 +0100 Subject: [PATCH 08/17] Feeds: return objects alignment Signed-off-by: dzdidi --- src/Feeds.js | 15 ++++++++------- test/Feeds.test.js | 15 ++++++--------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/Feeds.js b/src/Feeds.js index 533710f..a4f8035 100644 --- a/src/Feeds.js +++ b/src/Feeds.js @@ -157,8 +157,8 @@ module.exports = class SlashtagsFeeds { } return { // XXX should it be hex or base32 - key: feed.key.toString('hex'), - encryption_key: feed.encryptionKey.toString('hex') + feed_key: feed.key.toString('hex'), + encrypt_key: feed.encryptionKey.toString('hex') } } @@ -263,8 +263,8 @@ module.exports = class SlashtagsFeeds { try { await this.db.insert({ feed_id: args.feed_id, - feed_key: feed.key, - encrypt_key: feed.encryption_key, + feed_key: feed.feed_key, + encrypt_key: feed.encrypt_key, meta: {} }) } catch (err) { @@ -276,16 +276,17 @@ module.exports = class SlashtagsFeeds { const { format } = await import('@synonymdev/slashtags-url') const url = format( - b4a.from(feed.key, 'hex'), + b4a.from(feed.feed_key, 'hex'), { protocol: 'slashfeed:', - fragment: { encryptionKey: z32.encode(b4a.from(feed.encryption_key, 'hex')) } + fragment: { encryptionKey: z32.encode(b4a.from(feed.encrypt_key, 'hex')) } } ) return { url, - slashdrive: feed + feed_key: feed.feed_key, + encrypt_key: feed.encrypt_key } } diff --git a/test/Feeds.test.js b/test/Feeds.test.js index e8adfe8..8201dc3 100644 --- a/test/Feeds.test.js +++ b/test/Feeds.test.js @@ -232,12 +232,9 @@ describe('SlashtagsFeeds', () => { await feed.deleteFeed(input) }) - it('has slashdrive property', () => assert(res.slashdrive)) - describe('slashdrive property', () => { - it('has key', () => assert(res.slashdrive.key)) - it('has encryption_key', () => assert(res.slashdrive.encryption_key)) - it('has url', () => assert(res.url)) - }) + it('has feed_key', () => assert(res.feed_key)) + it('has encrypt', () => assert(res.encrypt_key)) + it('has url', () => assert(res.url)) }) }) }) @@ -432,7 +429,7 @@ describe('SlashtagsFeeds', () => { let readResult let createResult before(async function () { - this.timeout(5000) + this.timeout(10000) createResult = await feed.createFeed(input) readResult = await feed.getFeed(input) @@ -441,12 +438,12 @@ describe('SlashtagsFeeds', () => { describe('feed_key', () => { it('has `feed_key`', () => assert(readResult.feed_key)) - it('is correct', () => assert.strictEqual(createResult.slashdrive.key, readResult.feed_key)) + it('is correct', () => assert.strictEqual(createResult.feed_key, readResult.feed_key)) }) describe('encrypt_key', () => { it('has `encrypt_key`', () => assert(readResult.encrypt_key)) - it('is correct', () => assert.strictEqual(createResult.slashdrive.encryption_key, readResult.encrypt_key)) + it('is correct', () => assert.strictEqual(createResult.encrypt_key, readResult.encrypt_key)) }) }) }) From c7382648f09ea4e3585e62709fdf68a6ee154455 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Sat, 11 Mar 2023 07:09:19 +0100 Subject: [PATCH 09/17] Readme: return objects alignement, method renaming, update fieelds adjustment to new schema Signed-off-by: dzdidi --- README.md | 58 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index b34b28b..3d64360 100644 --- a/README.md +++ b/README.md @@ -78,14 +78,13 @@ curl --location --request POST 'http://localhost:8787/v0.1/rpc' \ Response ``` json { - "jsonrpc": "2.0", - "id": "925b10c27f4ad350ab3ef7e027605fd83388c999a890cfdd8e6061656b5a5513", - "result": { - "slashdrive": { - "key": "", - "encryption_key": "" - } - } + "jsonrpc": "2.0", + "id": "", + "result": { + "url": ":", + "feed_key": "", + "encrypt_key": "" + } } ``` @@ -95,14 +94,34 @@ Update feed request curl --location --request POST 'http://localhost:8787/v0.1/rpc' \ --header 'Content-Type: application/json' \ --data-raw '{ - "method":"updateFeedBalance", + "method":"updateFeed", "params": { "feed_id":"satoshi123", "fields": [ - { - "name": "Bitcoin", - "value": 1.442 - } + { + "name": "bitcoin futures balance", + "value": 11 + }, + { + "name": "bitcoin options balance", + "value": 12 + }, + { + "name": "bitcoin futures pnl", + "value": { "absolute": 1, "relative": 10 } + }, + { + "name": "bitcoin options pnl", + "value": { "absolute": 2, "relative": 20 } + }, + { + "name": "bitcoin futures pnl and balance", + "value": { "balance": 10, "absolute_pnl": 1, "relative_pnl": 10 } + }, + { + "name": "bitcoin options pnl and balance", + "value": { "balance": 10, "absolute_pnl": 1, "relative_pnl": 10 } + } ] } }' @@ -111,8 +130,10 @@ Response ``` json { "jsonrpc": "2.0", - "id": "4fefad839fa440cc2a85d8178d1d895fa1044460080b8fe1a26b4942aa86c07f", - "result": true + "id": "", + "result": { + "updated": true + } } ``` @@ -129,10 +150,11 @@ Response ``` json { "jsonrpc": "2.0", - "id": "c6ccd88f842330ab60153b5fb512101d2ab76824189eee5690f9070ebe18cb87", + "id": "call id", "result": { - "feed_key": "", - "encrypt_key": "" + "url": ":", + "feed_key": "", + "encrypt_key": "" } } ``` From 843b1155a44f1ee51722b56766a7ccbafd5ee923 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Sat, 11 Mar 2023 07:45:12 +0100 Subject: [PATCH 10/17] Postman: adjust update method params Signed-off-by: dzdidi --- Feeds Daemon.postman_collection.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Feeds Daemon.postman_collection.json b/Feeds Daemon.postman_collection.json index 981d694..f1b9a14 100644 --- a/Feeds Daemon.postman_collection.json +++ b/Feeds Daemon.postman_collection.json @@ -100,7 +100,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"method\":\"updateFeed\",\n \"params\": {\n \"feed_id\":\"satoshi\",\n \"fields\": [\n {\n \"name\": \"Bitcoin\",\n \"value\": 1.243\n }\n ]\n }\n}", + "raw": "{\n \"method\":\"updateFeed\",\n \"params\": {\n \"feed_id\":\"satoshi\",\n \"fields\": [\n {\n \"name\": \"bitcoin futures balance\",\n \"value\": 11\n },\n {\n \"name\": \"bitcoin options balance\",\n \"value\": 12\n },\n {\n \"name\": \"bitcoin futures pnl\",\n \"value\": { \"absolute\": 1, \"relative\": 10 }\n },\n {\n \"name\": \"bitcoin options pnl\",\n \"value\": { \"absolute\": 2, \"relative\": 20 }\n },\n {\n \"name\": \"bitcoin futures pnl and balance\",\n \"value\": { \"balance\": 10, \"absolute_pnl\": 1, \"relative_pnl\": 10 }\n },\n {\n \"name\": \"bitcoin options pnl and balance\",\n \"value\": { \"balance\": 10, \"absolute_pnl\": 1, \"relative_pnl\": 10 }\n }\n ]\n }\n}", "options": { "raw": { "language": "json" From 94cc317486246c5f0feb2ae497f363e0137c6d26 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Sat, 11 Mar 2023 07:56:16 +0100 Subject: [PATCH 11/17] Example: adjust to new schema Signed-off-by: dzdidi --- README.md | 2 +- example/README.md | 4 +- ...{accountFeed.js => exchangeAccountFeed.js} | 40 ++++++++++++++++--- package.json | 2 +- 4 files changed, 39 insertions(+), 9 deletions(-) rename example/{accountFeed.js => exchangeAccountFeed.js} (64%) diff --git a/README.md b/README.md index 3d64360..f400868 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ node start.js ## Test & Development There are tests for all methods located in `./test` dir. You can run them by `npm run test` -There examples in `./examples` folder. They require running daemon. Run `npm run start` to start daemon and in separate terminal run `npm run example:account` to start generating account feed. See [examples readme](./example/README.md) for more details +There examples in `./examples` folder. They require running daemon. Run `npm run start` to start daemon and in separate terminal run `npm run example:exchange:account` to start generating account feed. See [examples readme](./example/README.md) for more details ## RPC API diff --git a/example/README.md b/example/README.md index cc0b44c..2c74016 100644 --- a/example/README.md +++ b/example/README.md @@ -1,4 +1,4 @@ -

Account feeds example

+

Exchange account feeds example

## Start account feeds daemon @@ -45,7 +45,7 @@ $ npm run start ## Feed generation -Open a separate terminal window and execute `npm run example:account` from the root directory. This will create three hyperdrives. Each hyperdrive feeds the data for a particular customer account. The drive contents are accessible by knowing the discovery key and the encryption key. This information can be shared by [slashfeed URL](https://github.com/synonymdev/slashtags/tree/master/packages/url), which has the following format: `slashfeed:#encryptionKey=`. +Open a separate terminal window and execute `npm run example:exchange:account` from the root directory. This will create three hyperdrives. Each hyperdrive feeds the data for a particular customer account. The drive contents are accessible by knowing the discovery key and the encryption key. This information can be shared by [slashfeed URL](https://github.com/synonymdev/slashtags/tree/master/packages/url), which has the following format: `slashfeed:#encryptionKey=`. ```sh example Starting account feed +0ms diff --git a/example/accountFeed.js b/example/exchangeAccountFeed.js similarity index 64% rename from example/accountFeed.js rename to example/exchangeAccountFeed.js index 594fa9a..41bb3a1 100644 --- a/example/accountFeed.js +++ b/example/exchangeAccountFeed.js @@ -42,13 +42,43 @@ async function updateAccounts (accountIds) { for (const accountId of accountIds) { const update = [ { - name: 'Bitcoin', - value: faker.finance.amount(5, 10, 2) + name: 'bitcoin futures balance', + value: faker.finance.amount(-10000000, 10000000, 8), }, { - name: 'Bitcoin P/L', - value: faker.finance.amount(-100, 100, 2) - } + name: 'bitcoin options balance', + value: faker.finance.amount(-10000000, 10000000, 8), + }, + { + name: 'bitcoin futures pnl', + value: { + absolute: faker.finance.amount(-10000000, 10000000, 8), + relative: faker.finance.amount(-100, 100, 2) + }, + }, + { + name: 'bitcoin options pnl', + value: { + absolute: faker.finance.amount(-10000000, 10000000, 8), + relative: faker.finance.amount(-100, 100, 2) + }, + }, + { + name: 'bitcoin futures pnl and balance', + value: { + balance: faker.finance.amount(-10000000, 10000000, 8), + absolute_pnl: faker.finance.amount(-10000000, 10000000, 8), + relative_pnl: faker.finance.amount(-100, 100, 2) + }, + }, + { + name: 'bitcoin options pnl and balance', + value: { + balance: faker.finance.amount(-10000000, 10000000, 8), + absolute_pnl: faker.finance.amount(-10000000, 10000000, 8), + relative_pnl: faker.finance.amount(-100, 100, 2) + }, + }, ] await updateFeed(accountId, update) accountLogger(accountId)('Updated feed:', update.map(u => `${u.name}: ${JSON.stringify(u.value)}`)) diff --git a/package.json b/package.json index d1c1807..dd9fe30 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "scripts": { "test": "mocha ./test/", "start": "DEBUG=stfeed* node start.js", - "example:account": "DEBUG=example* node example/accountFeed.js" + "example:exchange:account": "DEBUG=example* node example/exchangeAccountFeed.js" }, "repository": { "type": "git", From 5150ab7b56ab3b85c72e20afd6a692283551b86b Mon Sep 17 00:00:00 2001 From: dzdidi Date: Sat, 11 Mar 2023 08:09:31 +0100 Subject: [PATCH 12/17] utils: move helpers to utils Signed-off-by: dzdidi --- src/BaseUtil.js | 11 ++++++++++- src/Feeds.js | 11 ++++------- src/SlashtagsSchema.js | 15 +++------------ src/schemaTypes/ExchangeAccountFeed.js | 14 ++++---------- src/util.js | 11 +++++++++++ test/Feeds.test.js | 5 +++-- 6 files changed, 35 insertions(+), 32 deletions(-) diff --git a/src/BaseUtil.js b/src/BaseUtil.js index 6ff9d2c..f31d263 100644 --- a/src/BaseUtil.js +++ b/src/BaseUtil.js @@ -11,6 +11,15 @@ module.exports = (name, fileName) => { errName: `${name}_ERROR`, fileName }), - log: Log(name) + log: Log(name), + getFileName: (fieldName) => { + const regex = /[^a-z0-9]+/gi + const trailing = /-+$/ + + return `/${fieldName.toLowerCase().trim().replace(regex, '-').replace(trailing, '')}/` + }, + snakeToCamel: (str) => { + return str.toLowerCase().replace(/([-_][a-z])/g, group => group.toUpperCase().replace('-', '').replace('_', '')) + } } } diff --git a/src/Feeds.js b/src/Feeds.js index a4f8035..2943735 100644 --- a/src/Feeds.js +++ b/src/Feeds.js @@ -5,6 +5,7 @@ const Feeds = require('@synonymdev/feeds') const FeedDb = require('./FeedDb.js') const SlashtagsSchema = require('./SlashtagsSchema.js') +const { snakeToCamel, getFileName } = require('./util.js') const Log = require('./Log.js') const customErr = require('./CustomError.js') @@ -103,7 +104,7 @@ module.exports = class SlashtagsFeeds { try { // NOTE: consider storing balance on db as well for (const field of update.fields) { - await this._slashfeeds.update(update.feed_id, SlashtagsSchema.getFileName(field.name), field.value) + await this._slashfeeds.update(update.feed_id, getFileName(field.name), field.value) } return { updated: true } } catch (err) { @@ -211,7 +212,7 @@ module.exports = class SlashtagsFeeds { for (let fieldName in this.feed_schema.fields[field]) { await this._slashfeeds.update( args.feed_id, - SlashtagsSchema.getFileName(fieldName), + getFileName(fieldName), args.init_data || null ) } @@ -322,7 +323,7 @@ module.exports = class SlashtagsFeeds { if (update.fields.length === 0) throw new Err(_err.invalidFeedFields) const { validateFieldsValues } = require( - `${__dirname}/schemaTypes/${this.snakeToCamel(this._slashfeeds.type || 'exchange_account_feed')}.js` + `${__dirname}/schemaTypes/${snakeToCamel(this._slashfeeds.type || 'exchange_account_feed')}.js` ) for (let field of update.fields) { @@ -335,8 +336,4 @@ module.exports = class SlashtagsFeeds { validateFieldsValues(update.fields, this.feed_schema.fields) } - - snakeToCamel (str) { - return str.toLowerCase().replace(/([-_][a-z])/g, group => group.toUpperCase().replace('-', '').replace('_', '')) - } } diff --git a/src/SlashtagsSchema.js b/src/SlashtagsSchema.js index 23c5140..f9f96df 100644 --- a/src/SlashtagsSchema.js +++ b/src/SlashtagsSchema.js @@ -1,6 +1,7 @@ const fs = require('fs') const customErr = require('./CustomError.js') +const { snakeToCamel } = require('./util.js') const Err = customErr({ errName: 'Slashtags', fileName: __filename }) const _err = { @@ -41,7 +42,7 @@ module.exports = class SlashtagsSchema { } const { validateSchemaFields, validateSchemaValues } = require( - `${__dirname}/schemaTypes/${this.snakeToCamel(schemaConfig.type || 'exchange_account_feed')}.js` + `${__dirname}/schemaTypes/${snakeToCamel(schemaConfig.type || 'exchange_account_feed')}.js` ) validateSchemaFields(schemaConfig.fields, SlashtagsSchema.err.invalidField) validateSchemaValues(schemaConfig.fields, SlashtagsSchema.err.invalidFieldValue) @@ -51,7 +52,7 @@ module.exports = class SlashtagsSchema { SlashtagsSchema.validateSchemaConfig(schemaConfig) const { generateSchemaFields } = require( - `${__dirname}/schemaTypes/${this.snakeToCamel(schemaConfig.type || 'exchange_account_feed')}.js` + `${__dirname}/schemaTypes/${snakeToCamel(schemaConfig.type || 'exchange_account_feed')}.js` ) const schema = { @@ -74,14 +75,4 @@ module.exports = class SlashtagsSchema { static persistSchema (schema) { fs.writeFileSync(this.DEFAULT_SCHEMA_PATH, Buffer.from(JSON.stringify(schema, undefined, 2)), 'utf-8') } - - static getFileName (fieldName) { - const regex = /[^a-z0-9]+/gi - const trailing = /-+$/ - - return `/${fieldName.toLowerCase().trim().replace(regex, '-').replace(trailing, '')}/` - } - static snakeToCamel (str) { - return str.toLowerCase().replace(/([-_][a-z])/g, group => group.toUpperCase().replace('-', '').replace('_', '')) - } } diff --git a/src/schemaTypes/ExchangeAccountFeed.js b/src/schemaTypes/ExchangeAccountFeed.js index e5dccd0..09fa847 100644 --- a/src/schemaTypes/ExchangeAccountFeed.js +++ b/src/schemaTypes/ExchangeAccountFeed.js @@ -1,5 +1,6 @@ const Feeds = require('@synonymdev/feeds') const path = require('path') +const { getFileName } = require('../util.js') module.exports = class ExchangeAccountFeed { static REQUIRED_FIELDS = [ @@ -121,7 +122,7 @@ module.exports = class ExchangeAccountFeed { denomination_type: balanceFields[balanceName].denomination_type, denomination_ratio: balanceFields[balanceName].denomination_ratio, units: balanceFields[balanceName].units, - main: path.join(Feeds.FEED_PREFIX, ExchangeAccountFeed._getFileName(balanceName)), + main: path.join(Feeds.FEED_PREFIX, getFileName(balanceName)), } } @@ -134,7 +135,7 @@ module.exports = class ExchangeAccountFeed { res[pnlName] = { label: pnlFields[pnlName].label, units: pnlFields[pnlName].units, - main: path.join(Feeds.FEED_PREFIX, ExchangeAccountFeed._getFileName(pnlName)), + main: path.join(Feeds.FEED_PREFIX, getFileName(pnlName)), } } @@ -149,17 +150,10 @@ module.exports = class ExchangeAccountFeed { denomination_type: pnlBalanceFields[pnlBalanceName].denomination_type, denomination_ratio: pnlBalanceFields[pnlBalanceName].denomination_ratio, units: pnlBalanceFields[pnlBalanceName].units, - main: path.join(Feeds.FEED_PREFIX, ExchangeAccountFeed._getFileName(pnlBalanceName)), + main: path.join(Feeds.FEED_PREFIX, getFileName(pnlBalanceName)), } } return res } - - static _getFileName (fieldName) { - const regex = /[^a-z0-9]+/gi - const trailing = /-+$/ - - return `/${fieldName.toLowerCase().trim().replace(regex, '-').replace(trailing, '')}/` - } } diff --git a/src/util.js b/src/util.js index 4b8c4fc..e222c45 100644 --- a/src/util.js +++ b/src/util.js @@ -19,5 +19,16 @@ module.exports = { rnd: function () { return randomBytes(32).toString('hex') + }, + + getFileName: function (fieldName) { + const regex = /[^a-z0-9]+/gi + const trailing = /-+$/ + + return `/${fieldName.toLowerCase().trim().replace(regex, '-').replace(trailing, '')}/` + }, + + snakeToCamel: function (str) { + return str.toLowerCase().replace(/([-_][a-z])/g, group => group.toUpperCase().replace('-', '').replace('_', '')) } } diff --git a/test/Feeds.test.js b/test/Feeds.test.js index 8201dc3..174fe10 100644 --- a/test/Feeds.test.js +++ b/test/Feeds.test.js @@ -1,5 +1,6 @@ const { strict: assert } = require('node:assert') const SlashtagsFeeds = require('../src/Feeds.js') +const { getFileName } = require('../src/util.js') const SlashtagsSchema = require('../src/SlashtagsSchema.js') const FeedDb = require('../src/FeedDb.js') const path = require('path') @@ -603,8 +604,8 @@ describe('SlashtagsFeeds', () => { before(async () => { await feed.stop() feedReader = new Feeds(validConfig.slashtags, validConfig.feed_schema) - balance = await feedReader.get(update.feed_id, SlashtagsSchema.getFileName(update.fields[0].name)) - balanceChange = await feedReader.get(update.feed_id, SlashtagsSchema.getFileName(update.fields[1].name)) + balance = await feedReader.get(update.feed_id, getFileName(update.fields[0].name)) + balanceChange = await feedReader.get(update.feed_id, getFileName(update.fields[1].name)) }) after(async () => { From b3ff05e6399d9b636d0968710676edb1ca91c12b Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 13 Mar 2023 11:13:39 +0100 Subject: [PATCH 13/17] schema: fields as an array Signed-off-by: dzdidi --- example/README.md | 41 ++++- example/exchangeAccountFeed.js | 37 +++-- schemas/slashfeed.json | 132 +++++++++------ src/schemaTypes/ExchangeAccountFeed.js | 104 +++--------- test/schemaTypes/ExchangeAccountFeed.test.js | 161 +++++++++---------- 5 files changed, 250 insertions(+), 225 deletions(-) diff --git a/example/README.md b/example/README.md index 2c74016..090d020 100644 --- a/example/README.md +++ b/example/README.md @@ -63,10 +63,43 @@ example:synonymxyz Created feed: slashfeed:7fgcnreg8cmqom8yhjeqkhbyybdi5jyjf3f7o The data will update periodically. ```sh -example Updating account feeds +5s -example:abcde123 Updated feed: [ 'Bitcoin: "7.40"', 'Bitcoin P/L: "-99.27"' ] +0ms -example:satoshi9191 Updated feed: [ 'Bitcoin: "9.73"', 'Bitcoin P/L: "-51.02"' ] +0ms -example:synonymxyz Updated feed: [ 'Bitcoin: "8.52"', 'Bitcoin P/L: "54.86"' ] +0ms + example Updating account feeds +5s + example:abcde123 Updated feed: [ + 'total net value: "-540041.33678973"', + 'btc balance: "-8856777.19488740"', + 'usd balance: "-658412.61"', + 'margin used: "-3144281.20851517"', + 'pnl: {"absolute":"-6144114.17860538","relative":"60.91"}', + 'bitcoin futures pnl: {"absolute":"-8351435.58215350","relative":"-20.92"}', + 'bitcoin futures balance: "-6480275.58345348"', + 'bitcoin options pnl: {"absolute":"7494553.67680640","relative":"28.39"}', + 'bitcoin options balance: "6678712.46114374"', + 'bitcoin pnl and balance: {"value":"1671238.87222260","absolute_pnl":"-3901669.66244578","relative_pnl":"10.82"}' +] +0ms + example:satoshi9191 Updated feed: [ + 'total net value: "3082602.53630579"', + 'btc balance: "-4622066.03493542"', + 'usd balance: "4061698.67"', + 'margin used: "-728434.17525291"', + 'pnl: {"absolute":"-826597.13923931","relative":"90.69"}', + 'bitcoin futures pnl: {"absolute":"-5438805.26907742","relative":"58.63"}', + 'bitcoin futures balance: "-458402.74076909"', + 'bitcoin options pnl: {"absolute":"-2772833.66769552","relative":"38.64"}', + 'bitcoin options balance: "9011384.84477997"', + 'bitcoin pnl and balance: {"value":"4379805.63379825","absolute_pnl":"7014897.92205394","relative_pnl":"-90.72"}' +] +0ms + example:synonymxyz Updated feed: [ + 'total net value: "-5312686.59047783"', + 'btc balance: "5488227.16344148"', + 'usd balance: "5969599.78"', + 'margin used: "7940113.55843396"', + 'pnl: {"absolute":"9615660.69163383","relative":"-71.20"}', + 'bitcoin futures pnl: {"absolute":"-8700234.18217898","relative":"59.86"}', + 'bitcoin futures balance: "6294792.03838855"', + 'bitcoin options pnl: {"absolute":"1224181.76382780","relative":"57.59"}', + 'bitcoin options balance: "-8294838.71348203"', + 'bitcoin pnl and balance: {"value":"-7941445.28917969","absolute_pnl":"8669050.75032265","relative_pnl":"-6.00"}' +] +0ms ... ``` diff --git a/example/exchangeAccountFeed.js b/example/exchangeAccountFeed.js index 41bb3a1..1d68819 100644 --- a/example/exchangeAccountFeed.js +++ b/example/exchangeAccountFeed.js @@ -42,43 +42,60 @@ async function updateAccounts (accountIds) { for (const accountId of accountIds) { const update = [ { - name: 'bitcoin futures balance', + name: 'total net value', value: faker.finance.amount(-10000000, 10000000, 8), }, { - name: 'bitcoin options balance', + name: "btc balance", value: faker.finance.amount(-10000000, 10000000, 8), }, { - name: 'bitcoin futures pnl', + name: "usd balance", + value: faker.finance.amount(-10000000, 10000000, 2), + }, + { + name: "margin used", + value: faker.finance.amount(-10000000, 10000000, 8), + }, + { + name: "pnl", value: { absolute: faker.finance.amount(-10000000, 10000000, 8), relative: faker.finance.amount(-100, 100, 2) }, }, { - name: 'bitcoin options pnl', + name: "bitcoin futures pnl", value: { absolute: faker.finance.amount(-10000000, 10000000, 8), relative: faker.finance.amount(-100, 100, 2) }, }, { - name: 'bitcoin futures pnl and balance', + name: "bitcoin futures balance", + value: faker.finance.amount(-10000000, 10000000, 8), + }, + { + name: "bitcoin options pnl", value: { - balance: faker.finance.amount(-10000000, 10000000, 8), - absolute_pnl: faker.finance.amount(-10000000, 10000000, 8), - relative_pnl: faker.finance.amount(-100, 100, 2) + absolute: faker.finance.amount(-10000000, 10000000, 8), + relative: faker.finance.amount(-100, 100, 2) }, }, { - name: 'bitcoin options pnl and balance', + name: "bitcoin options balance", + value: faker.finance.amount(-10000000, 10000000, 8), + }, + { + name: "bitcoin pnl and balance", value: { - balance: faker.finance.amount(-10000000, 10000000, 8), + value: faker.finance.amount(-10000000, 10000000, 8), absolute_pnl: faker.finance.amount(-10000000, 10000000, 8), relative_pnl: faker.finance.amount(-100, 100, 2) }, }, + + ] await updateFeed(accountId, update) accountLogger(accountId)('Updated feed:', update.map(u => `${u.name}: ${JSON.stringify(u.value)}`)) diff --git a/schemas/slashfeed.json b/schemas/slashfeed.json index 49031e8..4964d0d 100644 --- a/schemas/slashfeed.json +++ b/schemas/slashfeed.json @@ -6,50 +6,92 @@ "icons": { "48": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAyNHB4IiBoZWlnaHQ9IjEwMjRweCIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgPGNpcmNsZSBjeD0iNTEyIiBjeT0iNTEyIiByPSI1MTIiIHN0eWxlPSJmaWxsOiMwM2NhOWIiLz4KICAgPHBhdGggZD0iTTczMi44MSAyNzVjLTEuNjctLjczLTE3My41MS0yNC42LTM0My4zOSA4Ni44OEMyODQgNDMxLjIyIDI3MCA1MzIuNjggMjc0LjgxIDYwMC4zNmMyNDcuMDgtMjcuODkgNDUyLjQxLTMxNy4yOSA0NTgtMzI1LjM2ek0yOTEuNTcgNjc1LjUzYzIxLjI0IDM4LjE0IDE4MS43NyAxMjQuNiAzMjMuODcgNS4zNVM3NDUuNjIgMzQ0LjY2IDczMi44MSAyNzVjLTQuNDcgMTAuMTEtMTU5LjYyIDM1Ni44OS00NDEuMjQgNDAwLjUxIiBzdHlsZT0iZmlsbDojZmZmIi8+Cjwvc3ZnPgo=" }, - "fields": { - "balance": { - "bitcoin futures balance": { - "label": "bitcoin futures balance", - "denomination_type": "base", - "denomination_ratio": "8", - "main": "/feed/bitcoin-futures-balance/", - "units": "sats" - }, - "bitcoin spot balance": { - "label": "bitcoin spot balance", - "denomination_type": "base", - "denomination_ratio": "8", - "main": "/feed/bitcoin-spot-balance/", - "units": "sats" - } - }, - "pnl": { - "bitcoin futures pnl": { - "label": "bitcoin futures pnl", - "main": "/feed/bitcoin-futures-pnl/", - "units": "sats" - }, - "bitcoin spot pnl": { - "label": "bitcoin spot pnl", - "main": "/feed/bitcoin-spot-pnl/", - "units": "sats" - } - }, - "pnl_and_balance": { - "bitcoin futures pnl and balance": { - "label": "bitcoin futures pnl and balance", - "denomination_type": "base", - "denomination_ratio": "8", - "main": "/feed/bitcoin-futures-pnl-and-balance/", - "units": "sats" - }, - "bitcoin spot pnl and balance": { - "label": "bitcoin spot pnl and balance", - "denomination_type": "base", - "denomination_ratio": "8", - "main": "/feed/bitcoin-spot-pnl-and-balance/", - "units": "sats" - } + "fields": [ + { + "type": "balance", + "name": "total net value", + "label": "Total net value", + "main": "/feed/total-net-value/", + "denomination_type": "base", + "denomination_ratio": "8", + "units": "sats" + }, + { + "type": "balance", + "name": "btc balance", + "label": "btc balance", + "main": "/feed/btc-balance/", + "denomination_type": "base", + "denomination_ratio": "8", + "units": "sats" + }, + { + "type": "balance", + "name": "usd balance", + "label": "usd balance", + "main": "/feed/usd-balance/", + "denomination_type": "base", + "denomination_ratio": "2", + "units": "sats" + }, + { + "type": "balance", + "name": "margin used", + "label": "total used margin", + "main": "/feed/margin-used/", + "denomination_type": "main", + "denomination_ratio": "2", + "units": "usd" + }, + { + "type": "pnl", + "name": "pnl", + "label": "total profit loss", + "main": "/feed/pnl/", + "denomination_type": "base", + "denomination_ratio": "2", + "units": "sats" + }, + { + "type": "pnl", + "name": "bitcoin futures pnl", + "label": "bitcoin futures pnl", + "main": "/feed/bitcoin-futures-pnl/", + "units": "sats" + }, + { + "type": "balance", + "name": "bitcoin futures balance", + "label": "bitcoin futures balance", + "main": "/feed/bitcoin-futures-balance/", + "denomination_type": "main", + "denomination_ratio": "2", + "units": "sats" + }, + { + "type": "pnl", + "name": "bitcoin options pnl", + "label": "bitcoin options pnl", + "main": "/feed/bitcoin-options-pnl/", + "units": "sats" + }, + { + "type": "balance", + "name": "bitcoin options balance", + "label": "bitcoin options balance", + "main": "/feed/bitcoin-options-balance/", + "denomination_type": "main", + "denomination_ratio": "2", + "units": "sats" + }, + { + "type": "pnl_and_balance", + "name": "bitcoin pnl and balance", + "label": "bitcoin total pnl and balance", + "main": "/feed/bitcoin-pnl-and-balance/", + "denomination_type": "base", + "denomination_ratio": "8", + "units": "sats" } - } + ] } diff --git a/src/schemaTypes/ExchangeAccountFeed.js b/src/schemaTypes/ExchangeAccountFeed.js index 09fa847..dbcde04 100644 --- a/src/schemaTypes/ExchangeAccountFeed.js +++ b/src/schemaTypes/ExchangeAccountFeed.js @@ -3,13 +3,13 @@ const path = require('path') const { getFileName } = require('../util.js') module.exports = class ExchangeAccountFeed { - static REQUIRED_FIELDS = [ + static SUPPORTED_TYPES = [ 'balance', 'pnl', 'pnl_and_balance' ] - static REQUIRED_PROPS_FOR_FIELDS = { + static REQUIRED_PROPS_FOR_TYPE = { balance: [ 'label', 'denomination_type', @@ -31,32 +31,33 @@ module.exports = class ExchangeAccountFeed { } static generateSchemaFields(schemaFields) { - return { - balance: ExchangeAccountFeed._generateBalanceFields(schemaFields.balance), - pnl: ExchangeAccountFeed._generatePNLFields(schemaFields.pnl), - pnl_and_balance: ExchangeAccountFeed._generatePNLandBalanceFields(schemaFields.pnl_and_balance), - } + return schemaFields.map((field) => { + return { + ...field, + main: path.join(Feeds.FEED_PREFIX, getFileName(field.name)), + } + }) } - static validateSchemaFields(fields, err) { - ExchangeAccountFeed.REQUIRED_FIELDS.forEach((field) => { - if (!fields[field]) throw err || new Error(`missing ${field}`) - }) + fields.forEach((field) => { + if (!ExchangeAccountFeed.SUPPORTED_TYPES.includes(field.type)) throw err || new Error(`Wrong type ${field.type}`) - for (let fieldType in ExchangeAccountFeed.REQUIRED_PROPS_FOR_FIELDS) { - for (let fieldName in fields[fieldType]) { - for (let fieldProp of ExchangeAccountFeed.REQUIRED_PROPS_FOR_FIELDS[fieldType]) { - if (!fields[fieldType][fieldName][fieldProp]) - throw err || new Error(`${fieldType} for ${fieldName} is missing ${fieldProp}`) - } - } - } + ExchangeAccountFeed.REQUIRED_PROPS_FOR_TYPE[field.type].forEach((prop) => { + if(!field[prop]) throw err || new Error(`${field.type} for ${field.name} is missing ${prop}`) + }) + }) } static validateSchemaValues(fields, err) { - ExchangeAccountFeed._validateSchemaBalanceValues(fields, err) - ExchangeAccountFeed._validateSchemaPNLandBalanceValues(fields, err) + fields.forEach((field) => { + if (['balance', 'pnl_and_balance'].includes(field.type)) { + if (!['main', 'base'].includes(field.denomination_type)) + throw err || new Error(`${field.type} denomination_type must be "main" or "base"`) + if (!/[1-9]+/.test(field.denomination_ratio.toString())) + throw err || new Error(`${field.type} denomination_ratio must be natural number more or equal 1`) + } + }) } static validateFieldsValues(updates, fields) { @@ -95,65 +96,4 @@ module.exports = class ExchangeAccountFeed { if (isNaN(parseFloat(absolute_pnl))) throw new Error('invalid absolute') if (isNaN(parseFloat(relative_pnl))) throw new Error('invalid relative') } - - static _validateSchemaBalanceValues(fields, err) { - for (let fieldName in fields.balance) { - if (!['main', 'base'].includes(fields.balance[fieldName].denomination_type)) - throw err || new Error('balance denomination_type must be "main" or "base"') - if (!/[1-9]+/.test(fields.balance[fieldName].denomination_ratio.toString())) - throw err || new Error('balance denomination_ratio must be natural number more or equal 1') - } - } - - static _validateSchemaPNLandBalanceValues(fields, err) { - for (let fieldName in fields.pnl_and_balance) { - if (!['main', 'base'].includes(fields.pnl_and_balance[fieldName].denomination_type)) - throw err || new Error('pnl_and_balance denomination_type must be "main" or "base"') - if (!/[1-9]+/.test(fields.pnl_and_balance[fieldName].denomination_ratio.toString())) - throw err || new Error('pnl_and_balance denomination_ratio must be natural number more or equal 1') - } - } - - static _generateBalanceFields(balanceFields) { - let res = {} - for (let balanceName in balanceFields) { - res[balanceName] = { - label: balanceFields[balanceName].label, - denomination_type: balanceFields[balanceName].denomination_type, - denomination_ratio: balanceFields[balanceName].denomination_ratio, - units: balanceFields[balanceName].units, - main: path.join(Feeds.FEED_PREFIX, getFileName(balanceName)), - } - } - - return res - } - - static _generatePNLFields(pnlFields) { - let res = {} - for (let pnlName in pnlFields) { - res[pnlName] = { - label: pnlFields[pnlName].label, - units: pnlFields[pnlName].units, - main: path.join(Feeds.FEED_PREFIX, getFileName(pnlName)), - } - } - - return res - } - - static _generatePNLandBalanceFields(pnlBalanceFields) { - let res = {} - for (let pnlBalanceName in pnlBalanceFields) { - res[pnlBalanceName] = { - label: pnlBalanceFields[pnlBalanceName].label, - denomination_type: pnlBalanceFields[pnlBalanceName].denomination_type, - denomination_ratio: pnlBalanceFields[pnlBalanceName].denomination_ratio, - units: pnlBalanceFields[pnlBalanceName].units, - main: path.join(Feeds.FEED_PREFIX, getFileName(pnlBalanceName)), - } - } - - return res - } } diff --git a/test/schemaTypes/ExchangeAccountFeed.test.js b/test/schemaTypes/ExchangeAccountFeed.test.js index 3d70a7f..2c55980 100644 --- a/test/schemaTypes/ExchangeAccountFeed.test.js +++ b/test/schemaTypes/ExchangeAccountFeed.test.js @@ -2,57 +2,55 @@ const { strict: assert } = require('node:assert') const ExchangeAccountFeed = require('../../src/schemaTypes/ExchangeAccountFeed.js') describe('ExchangeAccountFeed', () => { - const validExchangeAccountSchemaFields = { - "balance": { - "btc balance": { - "label": "description or label", - "denomination_type": "main", - "denomination_ratio": 8, - "main": "path to value in slashdrive", - "units": "sign to be shown next to value", - }, + const validExchangeAccountSchemaFields = [ + { + "type": "balance", + "name": "btc balance", + "label": "description or label", + "denomination_type": "main", + "denomination_ratio": 8, + "main": "path to value in slashdrive", + "units": "sign to be shown next to value", }, - "pnl": { - "spot pnl": { - "main": "path to value on slashdrive, the value example is { absolute: 75, relative: 12 }", - "label": "description or label", - "units": "sign to be shown next to absolute value, relative value always shown with % sign", - }, + { + "type": "pnl", + "name": "spot pnl", + "main": "path to value on slashdrive, the value example is { absolute: 75, relative: 12 }", + "label": "description or label", + "units": "sign to be shown next to absolute value, relative value always shown with % sign", }, - "pnl_and_balance": { - "spot pnl and balance": { - "label": "description or label", - "main": "path to value on slashdrive, the value example is { balance: 100, absolute_pnl: 75, relative_pnl: 300 }", - "denomination_type": "base", - "denomination_ratio": 8, - "units": "sign to be shown next to absolute value, relative value always shown with % sign", - }, + { + "type": "pnl_and_balance", + "name": "spot pnl and balance", + "label": "description or label", + "main": "path to value on slashdrive, the value example is { balance: 100, absolute_pnl: 75, relative_pnl: 300 }", + "denomination_type": "base", + "denomination_ratio": 8, + "units": "sign to be shown next to absolute value, relative value always shown with % sign", } - } - - describe('Invalid fields', () => { - let invalidFields - beforeEach(() => invalidFields = JSON.parse(JSON.stringify(validExchangeAccountSchemaFields))) - - for (let fieldType in ExchangeAccountFeed.REQUIRED_PROPS_FOR_FIELDS) { - for (let fieldName in validExchangeAccountSchemaFields[fieldType]) { - for (let fieldProp of ExchangeAccountFeed.REQUIRED_PROPS_FOR_FIELDS[fieldType]) { - const message = `${fieldType} for ${fieldName} is missing ${fieldProp}` - describe(message, () => { - beforeEach(() => delete invalidFields[fieldType][fieldName][fieldProp]) - it('fails', () => assert.throws(() => ExchangeAccountFeed.validateFields(invalidFields), { message })) - }) - } - } - } - }) + ] + +// describe('Invalid fields', () => { +// let invalidFields +// beforeEach(() => invalidFields = JSON.parse(JSON.stringify(validExchangeAccountSchemaFields))) +// +// for (let field of validExchangeAccountSchemaFields) { +// for (let prop of ExchangeAccountFeed.REQUIRED_PROPS_FOR_TYPE[field.type]) { +// const message = `${field.type} for ${field.name} is missing ${prop}` +// describe(message, () => { +// beforeEach(() => delete field[prop]) +// it('fails', () => assert.throws(() => ExchangeAccountFeed.validateSchemaFields(invalidFields), { message })) +// }) +// } +// } +// }) describe('Invalid field values', () => { describe('Invalid denomination_type', () => { let invalidFields beforeEach(() => { invalidFields = JSON.parse(JSON.stringify(validExchangeAccountSchemaFields)) - invalidFields.balance['btc balance'].denomination_type = 'wrong' + invalidFields[0].denomination_type = 'wrong' }) it('fails', () => assert.throws(() => ExchangeAccountFeed.validateSchemaValues(invalidFields), { message: 'balance denomination_type must be "main" or "base"' @@ -63,7 +61,7 @@ describe('ExchangeAccountFeed', () => { let invalidFields beforeEach(() => { invalidFields = JSON.parse(JSON.stringify(validExchangeAccountSchemaFields)) - invalidFields.pnl_and_balance['spot pnl and balance'].denomination_ratio = 'wrong' + invalidFields[2].denomination_ratio = 'wrong' }) it('fails', () => { assert.throws(() => ExchangeAccountFeed.validateSchemaValues(invalidFields), { @@ -77,79 +75,74 @@ describe('ExchangeAccountFeed', () => { let res before(() => res = ExchangeAccountFeed.generateSchemaFields(validExchangeAccountSchemaFields)) - it('has balance', () => assert(res.balance)) describe('balance', () => { - it('has specified blance property', () => assert(res.balance['btc balance'])) - it('has contains label', () => assert.equal( - res.balance['btc balance'].label, - validExchangeAccountSchemaFields.balance['btc balance'].label + it('contains label', () => assert.equal( + res[0].label, + validExchangeAccountSchemaFields[0].label )) - it('has contains denomination_type', () => assert.equal( - res.balance['btc balance'].denomination_type, - validExchangeAccountSchemaFields.balance['btc balance'].denomination_type + it('contains denomination_type', () => assert.equal( + res[0].denomination_type, + validExchangeAccountSchemaFields[0].denomination_type )) - it('has contains denomination_ratio', () => assert.equal( - res.balance['btc balance'].denomination_ratio, - validExchangeAccountSchemaFields.balance['btc balance'].denomination_ratio + it('contains denomination_ratio', () => assert.equal( + res[0].denomination_ratio, + validExchangeAccountSchemaFields[0].denomination_ratio )) - it('has contains main', () => assert.equal( - res.balance['btc balance'].main, + it('contains main', () => assert.equal( + res[0].main, '/feed/btc-balance/' )) - it('has contains units', () => assert.equal( - res.balance['btc balance'].units, - validExchangeAccountSchemaFields.balance['btc balance'].units + it('contains units', () => assert.equal( + res[0].units, + validExchangeAccountSchemaFields[0].units )) }) describe('pnl', () => { - it('has specified blance property', () => assert(res.pnl['spot pnl'])) - it('has contains label', () => assert.equal( - res.pnl['spot pnl'].label, - validExchangeAccountSchemaFields.pnl['spot pnl'].label + it('contains label', () => assert.equal( + res[1].label, + validExchangeAccountSchemaFields[1].label )) - it('has contains main', () => assert.equal( - res.pnl['spot pnl'].main, + it('contains main', () => assert.equal( + res[1].main, '/feed/spot-pnl/' )) - it('has contains units', () => assert.equal( - res.pnl['spot pnl'].units, - validExchangeAccountSchemaFields.pnl['spot pnl'].units + it('contains units', () => assert.equal( + res[1].units, + validExchangeAccountSchemaFields[1].units )) }) - it('has pnl_and_balance', () => assert(res.pnl_and_balance)) describe('pnl_and_balance', () => { - it('has specified blance property', () => assert(res.pnl_and_balance['spot pnl and balance'])) - it('has contains label', () => assert.equal( - res.pnl_and_balance['spot pnl and balance'].label, - validExchangeAccountSchemaFields.pnl_and_balance['spot pnl and balance'].label + it('contains label', () => assert.equal( + res[2].label, + validExchangeAccountSchemaFields[2].label )) - it('has contains denomination_type', () => assert.equal( - res.pnl_and_balance['spot pnl and balance'].denomination_type, - validExchangeAccountSchemaFields.pnl_and_balance['spot pnl and balance'].denomination_type + it('contains denomination_type', () => assert.equal( + res[2].denomination_type, + validExchangeAccountSchemaFields[2].denomination_type )) - it('has contains denomination_ratio', () => assert.equal( - res.pnl_and_balance['spot pnl and balance'].denomination_ratio, - validExchangeAccountSchemaFields.pnl_and_balance['spot pnl and balance'].denomination_ratio + it('contains denomination_ratio', () => assert.equal( + res[2].denomination_ratio, + validExchangeAccountSchemaFields[2].denomination_ratio )) - it('has contains main', () => assert.equal( - res.pnl_and_balance['spot pnl and balance'].main, + it('contains main', () => assert.equal( + res[2].main, '/feed/spot-pnl-and-balance/' )) - it('has contains units', () => assert.equal( - res.pnl_and_balance['spot pnl and balance'].units, - validExchangeAccountSchemaFields.pnl_and_balance['spot pnl and balance'].units + it('contains units', () => assert.equal( + res[2].units, + validExchangeAccountSchemaFields[2].units )) }) }) From ba596a786fbc9ca2a02151cb179953943d115f9a Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 13 Mar 2023 13:07:28 +0100 Subject: [PATCH 14/17] schema: basic fields Signed-off-by: dzdidi --- example/README.md | 47 ++++++-------------- example/exchangeAccountFeed.js | 40 ++--------------- schemas/slashfeed.json | 79 +++++----------------------------- 3 files changed, 26 insertions(+), 140 deletions(-) diff --git a/example/README.md b/example/README.md index 090d020..3350772 100644 --- a/example/README.md +++ b/example/README.md @@ -63,42 +63,21 @@ example:synonymxyz Created feed: slashfeed:7fgcnreg8cmqom8yhjeqkhbyybdi5jyjf3f7o The data will update periodically. ```sh - example Updating account feeds +5s - example:abcde123 Updated feed: [ - 'total net value: "-540041.33678973"', - 'btc balance: "-8856777.19488740"', - 'usd balance: "-658412.61"', - 'margin used: "-3144281.20851517"', - 'pnl: {"absolute":"-6144114.17860538","relative":"60.91"}', - 'bitcoin futures pnl: {"absolute":"-8351435.58215350","relative":"-20.92"}', - 'bitcoin futures balance: "-6480275.58345348"', - 'bitcoin options pnl: {"absolute":"7494553.67680640","relative":"28.39"}', - 'bitcoin options balance: "6678712.46114374"', - 'bitcoin pnl and balance: {"value":"1671238.87222260","absolute_pnl":"-3901669.66244578","relative_pnl":"10.82"}' +example Updating account feeds +5s +example:abcde123 Updated feed: [ + 'total balance: "-5443472.07061947"', + 'total open pnl: {"absolute":"-9039929.42534387","relative":"11.30"}', + 'total open pnl and total balance: {"value":"8112571.67253644","absolute_pnl":"2471980.91819883","relative_pnl":"-26.32"}' ] +0ms - example:satoshi9191 Updated feed: [ - 'total net value: "3082602.53630579"', - 'btc balance: "-4622066.03493542"', - 'usd balance: "4061698.67"', - 'margin used: "-728434.17525291"', - 'pnl: {"absolute":"-826597.13923931","relative":"90.69"}', - 'bitcoin futures pnl: {"absolute":"-5438805.26907742","relative":"58.63"}', - 'bitcoin futures balance: "-458402.74076909"', - 'bitcoin options pnl: {"absolute":"-2772833.66769552","relative":"38.64"}', - 'bitcoin options balance: "9011384.84477997"', - 'bitcoin pnl and balance: {"value":"4379805.63379825","absolute_pnl":"7014897.92205394","relative_pnl":"-90.72"}' +example:satoshi9191 Updated feed: [ + 'total balance: "-719298.06843400"', + 'total open pnl: {"absolute":"-331530.44059873","relative":"-61.52"}', + 'total open pnl and total balance: {"value":"-7741750.18537790","absolute_pnl":"-9805918.76897961","relative_pnl":"-17.91"}' ] +0ms - example:synonymxyz Updated feed: [ - 'total net value: "-5312686.59047783"', - 'btc balance: "5488227.16344148"', - 'usd balance: "5969599.78"', - 'margin used: "7940113.55843396"', - 'pnl: {"absolute":"9615660.69163383","relative":"-71.20"}', - 'bitcoin futures pnl: {"absolute":"-8700234.18217898","relative":"59.86"}', - 'bitcoin futures balance: "6294792.03838855"', - 'bitcoin options pnl: {"absolute":"1224181.76382780","relative":"57.59"}', - 'bitcoin options balance: "-8294838.71348203"', - 'bitcoin pnl and balance: {"value":"-7941445.28917969","absolute_pnl":"8669050.75032265","relative_pnl":"-6.00"}' +example:synonymxyz Updated feed: [ + 'total balance: "9146804.61864919"', + 'total open pnl: {"absolute":"1281327.62853056","relative":"-70.31"}', + 'total open pnl and total balance: {"value":"-797822.55459577","absolute_pnl":"6331277.87150443","relative_pnl":"10.34"}' ] +0ms ... ``` diff --git a/example/exchangeAccountFeed.js b/example/exchangeAccountFeed.js index 1d68819..dd7a382 100644 --- a/example/exchangeAccountFeed.js +++ b/example/exchangeAccountFeed.js @@ -42,52 +42,18 @@ async function updateAccounts (accountIds) { for (const accountId of accountIds) { const update = [ { - name: 'total net value', + name: 'total balance', value: faker.finance.amount(-10000000, 10000000, 8), }, { - name: "btc balance", - value: faker.finance.amount(-10000000, 10000000, 8), - }, - { - name: "usd balance", - value: faker.finance.amount(-10000000, 10000000, 2), - }, - { - name: "margin used", - value: faker.finance.amount(-10000000, 10000000, 8), - }, - { - name: "pnl", - value: { - absolute: faker.finance.amount(-10000000, 10000000, 8), - relative: faker.finance.amount(-100, 100, 2) - }, - }, - { - name: "bitcoin futures pnl", + name: "total open pnl", value: { absolute: faker.finance.amount(-10000000, 10000000, 8), relative: faker.finance.amount(-100, 100, 2) }, }, { - name: "bitcoin futures balance", - value: faker.finance.amount(-10000000, 10000000, 8), - }, - { - name: "bitcoin options pnl", - value: { - absolute: faker.finance.amount(-10000000, 10000000, 8), - relative: faker.finance.amount(-100, 100, 2) - }, - }, - { - name: "bitcoin options balance", - value: faker.finance.amount(-10000000, 10000000, 8), - }, - { - name: "bitcoin pnl and balance", + name: "total open pnl and total balance", value: { value: faker.finance.amount(-10000000, 10000000, 8), absolute_pnl: faker.finance.amount(-10000000, 10000000, 8), diff --git a/schemas/slashfeed.json b/schemas/slashfeed.json index 4964d0d..9f5291b 100644 --- a/schemas/slashfeed.json +++ b/schemas/slashfeed.json @@ -9,86 +9,27 @@ "fields": [ { "type": "balance", - "name": "total net value", - "label": "Total net value", - "main": "/feed/total-net-value/", + "name": "total balance", + "label": "Total balance", + "main": "/feed/total-balance/", "denomination_type": "base", "denomination_ratio": "8", "units": "sats" }, - { - "type": "balance", - "name": "btc balance", - "label": "btc balance", - "main": "/feed/btc-balance/", - "denomination_type": "base", - "denomination_ratio": "8", - "units": "sats" - }, - { - "type": "balance", - "name": "usd balance", - "label": "usd balance", - "main": "/feed/usd-balance/", - "denomination_type": "base", - "denomination_ratio": "2", - "units": "sats" - }, - { - "type": "balance", - "name": "margin used", - "label": "total used margin", - "main": "/feed/margin-used/", - "denomination_type": "main", - "denomination_ratio": "2", - "units": "usd" - }, { "type": "pnl", - "name": "pnl", - "label": "total profit loss", - "main": "/feed/pnl/", + "name": "total open pnl", + "label": "Total open Profit and Loss", + "main": "/feed/total-open-pnl/", "denomination_type": "base", - "denomination_ratio": "2", - "units": "sats" - }, - { - "type": "pnl", - "name": "bitcoin futures pnl", - "label": "bitcoin futures pnl", - "main": "/feed/bitcoin-futures-pnl/", - "units": "sats" - }, - { - "type": "balance", - "name": "bitcoin futures balance", - "label": "bitcoin futures balance", - "main": "/feed/bitcoin-futures-balance/", - "denomination_type": "main", - "denomination_ratio": "2", - "units": "sats" - }, - { - "type": "pnl", - "name": "bitcoin options pnl", - "label": "bitcoin options pnl", - "main": "/feed/bitcoin-options-pnl/", - "units": "sats" - }, - { - "type": "balance", - "name": "bitcoin options balance", - "label": "bitcoin options balance", - "main": "/feed/bitcoin-options-balance/", - "denomination_type": "main", - "denomination_ratio": "2", + "denomination_ratio": "8", "units": "sats" }, { "type": "pnl_and_balance", - "name": "bitcoin pnl and balance", - "label": "bitcoin total pnl and balance", - "main": "/feed/bitcoin-pnl-and-balance/", + "name": "total open pnl and total balance", + "label": "Bitcoin open Profit and Loss and Total balance", + "main": "/feed/total-open-pnl-and-total-balance/", "denomination_type": "base", "denomination_ratio": "8", "units": "sats" From 477dcfc88e74e8c094268d192eea5fdabf3e9015 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 13 Mar 2023 13:10:22 +0100 Subject: [PATCH 15/17] schema: basic fields readme and postman Signed-off-by: dzdidi --- Feeds Daemon.postman_collection.json | 2 +- README.md | 18 +++--------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/Feeds Daemon.postman_collection.json b/Feeds Daemon.postman_collection.json index f1b9a14..adb435f 100644 --- a/Feeds Daemon.postman_collection.json +++ b/Feeds Daemon.postman_collection.json @@ -100,7 +100,7 @@ "header": [], "body": { "mode": "raw", - "raw": "{\n \"method\":\"updateFeed\",\n \"params\": {\n \"feed_id\":\"satoshi\",\n \"fields\": [\n {\n \"name\": \"bitcoin futures balance\",\n \"value\": 11\n },\n {\n \"name\": \"bitcoin options balance\",\n \"value\": 12\n },\n {\n \"name\": \"bitcoin futures pnl\",\n \"value\": { \"absolute\": 1, \"relative\": 10 }\n },\n {\n \"name\": \"bitcoin options pnl\",\n \"value\": { \"absolute\": 2, \"relative\": 20 }\n },\n {\n \"name\": \"bitcoin futures pnl and balance\",\n \"value\": { \"balance\": 10, \"absolute_pnl\": 1, \"relative_pnl\": 10 }\n },\n {\n \"name\": \"bitcoin options pnl and balance\",\n \"value\": { \"balance\": 10, \"absolute_pnl\": 1, \"relative_pnl\": 10 }\n }\n ]\n }\n}", + "raw": "{\n \"method\":\"updateFeed\",\n \"params\": {\n \"feed_id\":\"satoshi\",\n \"fields\": [\n {\n \"name\": \"total balance\",\n \"value\": 11\n },\n {\n \"name\": \"total open pnl\",\n \"value\": 12\n },\n {\n \"name\": \"total open pnl and total balance\",\n \"value\": { \"balance\": 10, \"absolute_pnl\": 1, \"relative_pnl\": 10 }\n }\n ]\n }\n}", "options": { "raw": { "language": "json" diff --git a/README.md b/README.md index f400868..8419d53 100644 --- a/README.md +++ b/README.md @@ -99,27 +99,15 @@ curl --location --request POST 'http://localhost:8787/v0.1/rpc' \ "feed_id":"satoshi123", "fields": [ { - "name": "bitcoin futures balance", + "name": "total balance", "value": 11 }, { - "name": "bitcoin options balance", + "name": "total open pnl", "value": 12 }, { - "name": "bitcoin futures pnl", - "value": { "absolute": 1, "relative": 10 } - }, - { - "name": "bitcoin options pnl", - "value": { "absolute": 2, "relative": 20 } - }, - { - "name": "bitcoin futures pnl and balance", - "value": { "balance": 10, "absolute_pnl": 1, "relative_pnl": 10 } - }, - { - "name": "bitcoin options pnl and balance", + "name": "total open pnl and total balance", "value": { "balance": 10, "absolute_pnl": 1, "relative_pnl": 10 } } ] From e7a98bcf554fabb40793952fe94499a2737395ec Mon Sep 17 00:00:00 2001 From: dzdidi Date: Mon, 13 Mar 2023 15:25:54 +0100 Subject: [PATCH 16/17] schema rename label to description Signed-off-by: dzdidi --- schemas/slashfeed.json | 6 ++--- src/schemaTypes/ExchangeAccountFeed.js | 6 ++--- test/schemaTypes/ExchangeAccountFeed.test.js | 24 ++++++++++---------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/schemas/slashfeed.json b/schemas/slashfeed.json index 9f5291b..d1c2251 100644 --- a/schemas/slashfeed.json +++ b/schemas/slashfeed.json @@ -10,7 +10,7 @@ { "type": "balance", "name": "total balance", - "label": "Total balance", + "description": "Total balance", "main": "/feed/total-balance/", "denomination_type": "base", "denomination_ratio": "8", @@ -19,7 +19,7 @@ { "type": "pnl", "name": "total open pnl", - "label": "Total open Profit and Loss", + "description": "Total open Profit and Loss", "main": "/feed/total-open-pnl/", "denomination_type": "base", "denomination_ratio": "8", @@ -28,7 +28,7 @@ { "type": "pnl_and_balance", "name": "total open pnl and total balance", - "label": "Bitcoin open Profit and Loss and Total balance", + "description": "Bitcoin open Profit and Loss and Total balance", "main": "/feed/total-open-pnl-and-total-balance/", "denomination_type": "base", "denomination_ratio": "8", diff --git a/src/schemaTypes/ExchangeAccountFeed.js b/src/schemaTypes/ExchangeAccountFeed.js index dbcde04..585839c 100644 --- a/src/schemaTypes/ExchangeAccountFeed.js +++ b/src/schemaTypes/ExchangeAccountFeed.js @@ -11,19 +11,19 @@ module.exports = class ExchangeAccountFeed { static REQUIRED_PROPS_FOR_TYPE = { balance: [ - 'label', + 'description', 'denomination_type', 'denomination_ratio', 'units', ], pnl: [ - 'label', + 'description', 'units', ], pnl_and_balance:[ - 'label', + 'description', 'denomination_type', 'denomination_ratio', 'units', diff --git a/test/schemaTypes/ExchangeAccountFeed.test.js b/test/schemaTypes/ExchangeAccountFeed.test.js index 2c55980..988dea5 100644 --- a/test/schemaTypes/ExchangeAccountFeed.test.js +++ b/test/schemaTypes/ExchangeAccountFeed.test.js @@ -6,7 +6,7 @@ describe('ExchangeAccountFeed', () => { { "type": "balance", "name": "btc balance", - "label": "description or label", + "description": "description", "denomination_type": "main", "denomination_ratio": 8, "main": "path to value in slashdrive", @@ -16,13 +16,13 @@ describe('ExchangeAccountFeed', () => { "type": "pnl", "name": "spot pnl", "main": "path to value on slashdrive, the value example is { absolute: 75, relative: 12 }", - "label": "description or label", + "description": "description", "units": "sign to be shown next to absolute value, relative value always shown with % sign", }, { "type": "pnl_and_balance", "name": "spot pnl and balance", - "label": "description or label", + "description": "description", "main": "path to value on slashdrive, the value example is { balance: 100, absolute_pnl: 75, relative_pnl: 300 }", "denomination_type": "base", "denomination_ratio": 8, @@ -76,9 +76,9 @@ describe('ExchangeAccountFeed', () => { before(() => res = ExchangeAccountFeed.generateSchemaFields(validExchangeAccountSchemaFields)) describe('balance', () => { - it('contains label', () => assert.equal( - res[0].label, - validExchangeAccountSchemaFields[0].label + it('contains description', () => assert.equal( + res[0].description, + validExchangeAccountSchemaFields[0].description )) it('contains denomination_type', () => assert.equal( @@ -103,9 +103,9 @@ describe('ExchangeAccountFeed', () => { }) describe('pnl', () => { - it('contains label', () => assert.equal( - res[1].label, - validExchangeAccountSchemaFields[1].label + it('contains description', () => assert.equal( + res[1].description, + validExchangeAccountSchemaFields[1].description )) it('contains main', () => assert.equal( @@ -120,9 +120,9 @@ describe('ExchangeAccountFeed', () => { }) describe('pnl_and_balance', () => { - it('contains label', () => assert.equal( - res[2].label, - validExchangeAccountSchemaFields[2].label + it('contains description', () => assert.equal( + res[2].description, + validExchangeAccountSchemaFields[2].description )) it('contains denomination_type', () => assert.equal( From 6009c9fac32cb88c11d25c0b41531b09d8419069 Mon Sep 17 00:00:00 2001 From: dzdidi Date: Tue, 14 Mar 2023 12:36:58 +0100 Subject: [PATCH 17/17] ExchangeAccountSchema: remove denomination Signed-off-by: dzdidi --- schemas/slashfeed.json | 6 --- src/schemaTypes/ExchangeAccountFeed.js | 13 +---- test/schemaTypes/ExchangeAccountFeed.test.js | 50 -------------------- 3 files changed, 1 insertion(+), 68 deletions(-) diff --git a/schemas/slashfeed.json b/schemas/slashfeed.json index d1c2251..90f21e6 100644 --- a/schemas/slashfeed.json +++ b/schemas/slashfeed.json @@ -12,8 +12,6 @@ "name": "total balance", "description": "Total balance", "main": "/feed/total-balance/", - "denomination_type": "base", - "denomination_ratio": "8", "units": "sats" }, { @@ -21,8 +19,6 @@ "name": "total open pnl", "description": "Total open Profit and Loss", "main": "/feed/total-open-pnl/", - "denomination_type": "base", - "denomination_ratio": "8", "units": "sats" }, { @@ -30,8 +26,6 @@ "name": "total open pnl and total balance", "description": "Bitcoin open Profit and Loss and Total balance", "main": "/feed/total-open-pnl-and-total-balance/", - "denomination_type": "base", - "denomination_ratio": "8", "units": "sats" } ] diff --git a/src/schemaTypes/ExchangeAccountFeed.js b/src/schemaTypes/ExchangeAccountFeed.js index 585839c..310ae1a 100644 --- a/src/schemaTypes/ExchangeAccountFeed.js +++ b/src/schemaTypes/ExchangeAccountFeed.js @@ -12,8 +12,6 @@ module.exports = class ExchangeAccountFeed { static REQUIRED_PROPS_FOR_TYPE = { balance: [ 'description', - 'denomination_type', - 'denomination_ratio', 'units', ], @@ -24,8 +22,6 @@ module.exports = class ExchangeAccountFeed { pnl_and_balance:[ 'description', - 'denomination_type', - 'denomination_ratio', 'units', ] } @@ -50,14 +46,7 @@ module.exports = class ExchangeAccountFeed { } static validateSchemaValues(fields, err) { - fields.forEach((field) => { - if (['balance', 'pnl_and_balance'].includes(field.type)) { - if (!['main', 'base'].includes(field.denomination_type)) - throw err || new Error(`${field.type} denomination_type must be "main" or "base"`) - if (!/[1-9]+/.test(field.denomination_ratio.toString())) - throw err || new Error(`${field.type} denomination_ratio must be natural number more or equal 1`) - } - }) + return } static validateFieldsValues(updates, fields) { diff --git a/test/schemaTypes/ExchangeAccountFeed.test.js b/test/schemaTypes/ExchangeAccountFeed.test.js index 988dea5..6c9faf9 100644 --- a/test/schemaTypes/ExchangeAccountFeed.test.js +++ b/test/schemaTypes/ExchangeAccountFeed.test.js @@ -7,8 +7,6 @@ describe('ExchangeAccountFeed', () => { "type": "balance", "name": "btc balance", "description": "description", - "denomination_type": "main", - "denomination_ratio": 8, "main": "path to value in slashdrive", "units": "sign to be shown next to value", }, @@ -24,8 +22,6 @@ describe('ExchangeAccountFeed', () => { "name": "spot pnl and balance", "description": "description", "main": "path to value on slashdrive, the value example is { balance: 100, absolute_pnl: 75, relative_pnl: 300 }", - "denomination_type": "base", - "denomination_ratio": 8, "units": "sign to be shown next to absolute value, relative value always shown with % sign", } ] @@ -45,32 +41,6 @@ describe('ExchangeAccountFeed', () => { // } // }) - describe('Invalid field values', () => { - describe('Invalid denomination_type', () => { - let invalidFields - beforeEach(() => { - invalidFields = JSON.parse(JSON.stringify(validExchangeAccountSchemaFields)) - invalidFields[0].denomination_type = 'wrong' - }) - it('fails', () => assert.throws(() => ExchangeAccountFeed.validateSchemaValues(invalidFields), { - message: 'balance denomination_type must be "main" or "base"' - })) - }) - - describe('Invalid denomination_ratio', () => { - let invalidFields - beforeEach(() => { - invalidFields = JSON.parse(JSON.stringify(validExchangeAccountSchemaFields)) - invalidFields[2].denomination_ratio = 'wrong' - }) - it('fails', () => { - assert.throws(() => ExchangeAccountFeed.validateSchemaValues(invalidFields), { - message: 'pnl_and_balance denomination_ratio must be natural number more or equal 1' - }) - }) - }) - }) - describe('Generated scheam', () => { let res before(() => res = ExchangeAccountFeed.generateSchemaFields(validExchangeAccountSchemaFields)) @@ -81,16 +51,6 @@ describe('ExchangeAccountFeed', () => { validExchangeAccountSchemaFields[0].description )) - it('contains denomination_type', () => assert.equal( - res[0].denomination_type, - validExchangeAccountSchemaFields[0].denomination_type - )) - - it('contains denomination_ratio', () => assert.equal( - res[0].denomination_ratio, - validExchangeAccountSchemaFields[0].denomination_ratio - )) - it('contains main', () => assert.equal( res[0].main, '/feed/btc-balance/' @@ -125,16 +85,6 @@ describe('ExchangeAccountFeed', () => { validExchangeAccountSchemaFields[2].description )) - it('contains denomination_type', () => assert.equal( - res[2].denomination_type, - validExchangeAccountSchemaFields[2].denomination_type - )) - - it('contains denomination_ratio', () => assert.equal( - res[2].denomination_ratio, - validExchangeAccountSchemaFields[2].denomination_ratio - )) - it('contains main', () => assert.equal( res[2].main, '/feed/spot-pnl-and-balance/'