UNPKG

@ixily/activ

Version:

Alpha Capture Trade Idea Verification. Blockchain ownership proven trade ideas and strategies.

654 lines 24.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProvableModule = void 0; const CryptoJS = __importStar(require("crypto-js")); const __1 = require("../.."); const state = { mockValidation: false, mockLitPricing: false, }; const config = (_config) => { if (_config.mockValidation !== undefined) { state.mockValidation = _config.mockValidation; } if (_config.mockLitPricing !== undefined) { state.mockLitPricing = _config.mockLitPricing; } }; /* const checkValidNewIdeaFromChronological = async ( idea: ITradeIdea, chronological: IOpenSeaMetadata[], ): Promise<{ validation: string | IRuleValidResult chronological: IOpenSeaMetadata[] }> => { const chronologicalIdeas = chronological.map((each) => each.idea!) chronologicalIdeas.push(idea) // console.log('chronologicalIdeas') // console.log(chronologicalIdeas) return { validation: await rules(chronologicalIdeas, undefined, true), chronological, } } */ /* * Uses crypto-js to decrypt the price part * response is the encrypted price part as string * key is the key part as string * cryptography used function PBKDF2 with SHA-256 * response is concatenation of, in order: salt, iv and ciphertext. */ const decryptPricePart = (response, password) => { const encrypted = CryptoJS.enc.Base64.parse(response); const salt_len = 16; const iv_len = 16; const salt = CryptoJS.lib.WordArray.create(encrypted.words.slice(0, salt_len / 4)); const iv = CryptoJS.lib.WordArray.create(encrypted.words.slice(0 + salt_len / 4, (salt_len + iv_len) / 4)); const key = CryptoJS.PBKDF2(password, salt, { keySize: 256 / 32, iterations: 10000, hasher: CryptoJS.algo.SHA256, }); const decrypted = CryptoJS.AES.decrypt(CryptoJS.lib.WordArray.create(encrypted.words.slice((salt_len + iv_len) / 4)).toString(CryptoJS.enc.Base64), key, { iv: iv }); return JSON.parse(decrypted.toString(CryptoJS.enc.Utf8)); }; const restorePricePartsFromKeys = (responses, keys) => { const priceParts = []; for (let i = 0; i < responses.length; i++) { const response = responses[i]; const key = keys[i]; const pricePart = decryptPricePart(response, key[1]); // log.dev('pricePart') // log.dev(pricePart) priceParts.push(pricePart); } return priceParts; }; const MAXIMUM_TIMESTAMP_DISTANCE = 400; // 200 ms const MAXIMUM_PRICE_DISTANCE = 0.04; // 4% const testForInvalidNoFetchNodes = (priceParts, keys) => { for (const pricePartIndex in priceParts) { const pricePart = priceParts[pricePartIndex]; // validate the skipped parts if (pricePart.noFetch === true) { const skipped = pricePart; if (skipped.ckWd === undefined) { throw new Error('TradeIdeaPricingValidationError: noFetch ckWd undefined'); } else { if (skipped.ckWd !== keys[pricePartIndex][0]) { throw new Error('TradeIdeaPricingValidationError: noFetch ckWd does not match'); } } } } }; const testForDealtErrors = (priceParts) => { let listOfKnownErrors = []; let atLeastOneUnknownError = undefined; for (const pricePartIndex in priceParts) { const pricePart = priceParts[pricePartIndex]; // validate the skipped parts if (pricePart.fetchError === true) { const err = pricePart; if (err.unknownError !== undefined) { atLeastOneUnknownError = err.unknownError; } else { if (err.knownError === undefined) { throw new Error('TradeIdeaPricingValidationError: fetchError knownError undefined when not expected'); } if (listOfKnownErrors.find((one) => one === err.knownError) === undefined) { listOfKnownErrors.push(err.knownError); } } } } if (atLeastOneUnknownError !== undefined) { const pricingError = { fetchError: true, unknownError: atLeastOneUnknownError, }; return pricingError; } else { if (listOfKnownErrors.length === 0) { return undefined; } else if (listOfKnownErrors.length === 1) { const pricingError = { fetchError: true, knownError: listOfKnownErrors[0], }; return pricingError; } else { const pricingError = { fetchError: true, unknownError: listOfKnownErrors.join(', '), }; return pricingError; } } }; const getValidPriceFromPartsAndKeys = (priceParts, keys) => { // console.log('priceParts') // console.log(priceParts) // console.log('keys') // console.log(keys) testForInvalidNoFetchNodes(priceParts, keys); const pricingError = testForDealtErrors(priceParts); // console.log('pricingError') // console.log(pricingError) if (pricingError !== undefined) { return pricingError; } let provider; // required let symbol; // required let company; const globalPrices = []; // required let globalPrice = 0; // required let asks = undefined; let ask = undefined; let bids = undefined; let bid = undefined; const timestamps = []; // required let timestamp = 0; // required let fromProxy = undefined; for (const pricePartIndex in priceParts) { const pricePart = priceParts[pricePartIndex]; if (!(pricePart.noFetch === true)) { const _price = pricePart; // validate provider if (_price.provider === undefined) { throw new Error('TradeIdeaPricingValidationError: provider undefined'); } if (typeof _price.provider !== 'string') { throw new Error('TradeIdeaPricingValidationError: provider not string'); } if (provider === undefined) { provider = _price.provider; } else { if (provider !== _price.provider) { throw new Error('TradeIdeaPricingValidationError: provider does not match'); } } // validate symbol if (_price.symbol === undefined) { throw new Error('TradeIdeaPricingValidationError: symbol undefined'); } if (typeof _price.symbol !== 'string') { throw new Error('TradeIdeaPricingValidationError: symbol not string'); } if (symbol === undefined) { symbol = _price.symbol; } else { if (symbol !== _price.symbol) { throw new Error('TradeIdeaPricingValidationError: symbol does not match'); } } // validate company if (_price.company !== undefined) { if (typeof _price.company !== 'string') { throw new Error('TradeIdeaPricingValidationError: company not string'); } if (company === undefined) { company = _price.company; } else { if (company !== _price.company) { throw new Error('TradeIdeaPricingValidationError: company does not match'); } } } else { if (company !== undefined) { throw new Error('TradeIdeaPricingValidationError: company does not match'); } } // validate globalPrice if (_price.globalPrice === undefined) { throw new Error('TradeIdeaPricingValidationError: globalPrice undefined'); } if (typeof _price.globalPrice !== 'number') { throw new Error('TradeIdeaPricingValidationError: globalPrice not number'); } globalPrices.push(_price.globalPrice); // validate ask if (_price.ask !== undefined) { if (typeof _price.ask !== 'number') { throw new Error('TradeIdeaPricingValidationError: ask not number'); } if (asks === undefined) { asks = []; } asks.push(_price.ask); } // validate bid if (_price.bid !== undefined) { if (typeof _price.bid !== 'number') { throw new Error('TradeIdeaPricingValidationError: bid not number'); } if (bids === undefined) { bids = []; } bids.push(_price.bid); } // validate timestamp if (_price.timestamp === undefined) { throw new Error('TradeIdeaPricingValidationError: timestamp undefined'); } if (typeof _price.timestamp !== 'number') { throw new Error('TradeIdeaPricingValidationError: timestamp not number'); } timestamps.push(_price.timestamp); // validate origin if (_price.fromProxy !== undefined) { if (fromProxy === undefined) { fromProxy = _price.fromProxy; } else { if (fromProxy !== _price.fromProxy) { throw new Error('TradeIdeaPricingValidationError: fromProxy does not match'); } } } } } // calculate globalPrice // console.log('globalPrices') // console.log(globalPrices) let maxDecimalsGlobalPrice = 0; for (const _globalPrice of globalPrices) { const preDecimals = _globalPrice.toString().split('.'); const decimals = preDecimals.length > 1 ? preDecimals[1].length : 0; if (decimals > maxDecimalsGlobalPrice) { maxDecimalsGlobalPrice = decimals; } globalPrice += _globalPrice; } globalPrice = globalPrice / globalPrices.length; // restore globalPrice to its maximum decimals globalPrice = parseFloat(globalPrice.toFixed(maxDecimalsGlobalPrice)); // console.log('globalPrice') // console.log(globalPrice) // validate that globalPrices are not too far apart in a distance of constant // MAXIMUM_PRICE_DISTANCE, in percentage difference from average for (const _globalPrice of globalPrices) { const difference = Math.abs(_globalPrice - globalPrice); const percentageDifference = difference / globalPrice; if (percentageDifference > MAXIMUM_PRICE_DISTANCE) { throw new Error('TradeIdeaPricingValidationError: globalPrice too far apart'); } } // calculate average timestamp for (const _timestamp of timestamps) { timestamp += _timestamp; } timestamp = Math.floor(timestamp / timestamps.length); // validate that timestamps are not too far apart in a distance of constant // MAXIMUM_TIMESTAMP_DISTANCE, in milliseconds for (const _timestamp of timestamps) { if (Math.abs(_timestamp - timestamp) > MAXIMUM_TIMESTAMP_DISTANCE) { throw new Error('TradeIdeaPricingValidationError: timestamp too far apart'); } } // if applicable, calculate average ask and bid if (asks !== undefined) { ask = 0; let maxDecimalsAsk = 0; for (const _ask of asks) { const preDecimals = _ask.toString().split('.'); const decimals = preDecimals.length > 1 ? preDecimals[1].length : 0; if (decimals > maxDecimalsAsk) { maxDecimalsAsk = decimals; } ask += _ask; } ask = ask / asks.length; ask = parseFloat(ask.toFixed(maxDecimalsAsk)); // validate that ask prices are not too far apart in a distance of constant // MAXIMUM_PRICE_DISTANCE, in percentage difference from average for (const _ask of asks) { const difference = Math.abs(_ask - ask); const percentageDifference = difference / ask; if (percentageDifference > MAXIMUM_PRICE_DISTANCE) { throw new Error('TradeIdeaPricingValidationError: ask too far apart'); } } } if (bids !== undefined) { bid = 0; let maxDecimalsBid = 0; for (const _bid of bids) { const preDecimals = _bid.toString().split('.'); const decimals = preDecimals.length > 1 ? preDecimals[1].length : 0; if (decimals > maxDecimalsBid) { maxDecimalsBid = decimals; } bid += _bid; } bid = bid / bids.length; bid = parseFloat(bid.toFixed(maxDecimalsBid)); // validate that bid prices are not too far apart in a distance of constant // MAXIMUM_PRICE_DISTANCE, in percentage difference from average for (const _bid of bids) { const difference = Math.abs(_bid - bid); const percentageDifference = difference / bid; if (percentageDifference > MAXIMUM_PRICE_DISTANCE) { throw new Error('TradeIdeaPricingValidationError: bid too far apart'); } } } if (provider === undefined) { throw new Error('TradeIdeaPricingValidationError: provider undefined'); } if (symbol === undefined) { throw new Error('TradeIdeaPricingValidationError: symbol undefined'); } if (timestamp === 0) { throw new Error('TradeIdeaPricingValidationError: timestamp undefined'); } if (globalPrice === 0) { throw new Error('TradeIdeaPricingValidationError: globalPrice undefined'); } return { provider: provider, symbol: symbol, company, globalPrice, ask, bid, fromProxy, timestamp, }; }; const checkIfPriceSignedResponseIsValid = async (pricingDetails) => { if (state.mockValidation) { return { valid: true, price: pricingDetails, }; } const hashedMessage = (0, __1.getHashedMessage)(__1.CONTRACT_TOOLS.deterministicStringify(pricingDetails.response)); // log.dev('pricingDetails.signature.signature:') // log.dev(pricingDetails.signature.signature) const signature = pricingDetails.signature.signature; const splitedSignature = (0, __1.splitSignature)(signature); const hashedSignedMessage = { ...hashedMessage, signature, ...splitedSignature, }; if (pricingDetails.price === undefined) { return { valid: false, price: pricingDetails, }; } let expectedAddress = undefined; if (pricingDetails.price.provider === 'Binance') { expectedAddress = __1.LIT_ACTIONS.GET_PRICE_BINANCE_V2.keyEthAddress; } else if (pricingDetails.price.provider === 'IEX') { expectedAddress = __1.LIT_ACTIONS.GET_PRICE_IEX_V2.keyEthAddress; } else if (pricingDetails.price.provider === 'IG Group') { expectedAddress = __1.LIT_ACTIONS.GET_PRICE_IG_V2.keyEthAddress; } else if (pricingDetails.price.provider === 'Alpaca') { expectedAddress = __1.LIT_ACTIONS.GET_PRICE_ALPACA_V2.keyEthAddress; } else if (pricingDetails.price.provider === 'Tradestation') { expectedAddress = __1.LIT_ACTIONS.GET_PRICE_TRADESTATION_V2.keyEthAddress; } else if (pricingDetails.price.provider === 'CoinMarketCap') { expectedAddress = __1.LIT_ACTIONS.GET_PRICE_COINMARKETCAP_V2.keyEthAddress; } else if (pricingDetails.price.provider === 'CryptoCompare') { expectedAddress = __1.LIT_ACTIONS.GET_PRICE_CRYPTOCOMPARE_V2.keyEthAddress; } else { throw new Error('TradeIdeaPricingValidationError: Invalid pricing provider'); } const verification = await (0, __1.validateHashedSignedMessage)(hashedSignedMessage); __1.LogModule.dev('verification:', verification); // we let off while not debugged if (verification !== expectedAddress) { return { valid: false, price: pricingDetails, }; } else { return { valid: true, price: pricingDetails, }; } }; const verifyPriceDetails = async (pricingDetails, symbol) => { if (state.mockValidation) { return { valid: true, price: { ...pricingDetails, priceParts: pricingDetails.response, price: JSON.parse(pricingDetails.response[0]), }, }; } // First we restore the price parts from key parts let priceParts = []; try { priceParts = restorePricePartsFromKeys(pricingDetails.response, pricingDetails.keys); } catch (e) { __1.LogModule.prod('Error Validating Prices: ' + symbol + ' :'); __1.LogModule.prod(e); return { valid: false, price: pricingDetails, }; } pricingDetails.priceParts = priceParts; // Now we validate the got prices and reduce to one price let price; try { price = getValidPriceFromPartsAndKeys(pricingDetails.priceParts, pricingDetails.keys); } catch (e) { __1.LogModule.prod('Error Validating Prices: ' + symbol + ' :'); __1.LogModule.prod(e); return { valid: false, price: pricingDetails, }; } __1.LogModule.dev('symbol:'); __1.LogModule.dev(symbol); throwPricingError(price); pricingDetails.price = price; // console.log('price:') // console.log(price) // For last, we validate the original responseArray signature return checkIfPriceSignedResponseIsValid(pricingDetails); }; const getVerifiedPriceDetails = async (symbol, provider, auth, customSettings) => { if (customSettings?.tracker === undefined) { customSettings = { ...customSettings, tracker: (0, __1.randomKeyWithLength)(7), }; __1.LogModule.dev('ACTIV: getVerifiedPriceDetails: The tracker was undefined, changing it to string:', customSettings?.tracker); } const maxAttempts = 3; let attempts = 0; // to avoid mutating the original auth object (IG especially) let authCopy = auth !== undefined ? JSON.stringify(auth) : 'undefined'; while (attempts < maxAttempts) { auth = authCopy !== 'undefined' ? JSON.parse(authCopy) : undefined; const pricingDetails = await __1.PricingModule.getPriceDetails({ params: { symbol, }, provider, auth, }, state.mockLitPricing, { attempts, tracker: customSettings?.tracker, croupierUrl: customSettings?.croupierUrl, }); if (state.mockLitPricing) { return { ...pricingDetails, priceParts: [JSON.parse(pricingDetails.response[0])], price: JSON.parse(pricingDetails.response[0]), }; } const verification = await verifyPriceDetails(pricingDetails, symbol); __1.LogModule.dev('IG_PRE_AUTHENTICATE (verification --> ¿is valid?)', verification.valid); __1.LogModule.dev('IG_PRE_AUTHENTICATE (verification --> price)', verification.price.price); if (verification.valid) { // console.log('verification') // console.log(verification) return { ...pricingDetails, priceParts: verification.price.priceParts, price: verification.price.price, }; } else { __1.LogModule.prod('Invalid signatures'); } attempts++; // await rest(200) } throw new Error('TradeIdeaPricingValidationError: Could not get valid price details'); }; const throwPricingError = (price) => { if (price.fetchError === true) { const err = price; if (err.knownError !== undefined) { throw new Error('Pricing Error: ' + err.knownError); } else { throw new Error('Unknown Pricing Error: ' + err.unknownError); } } }; const validatePricingSignature = async (idea) => { const ideaIdea = idea.idea; const pricingDetails = ideaIdea.priceInfo; // console.log('pricingDetails:') // console.log(pricingDetails) if (state.mockValidation) { let priceIn; try { priceIn = JSON.parse(pricingDetails.response[0]); } catch (e) { priceIn = { globalPrice: 1, provider: 'Binance', symbol: 'BTCUSDT', timestamp: 1, }; } return { valid: true, price: { ...pricingDetails, priceParts: pricingDetails.response, price: priceIn, }, }; } if (ideaIdea.priceInfo === undefined) { __1.LogModule.prod('TradeIdeaPricingValidationError: priceInfo undefined'); return { valid: false, price: pricingDetails, }; } // First we restore the price parts from key parts let priceParts = []; try { priceParts = restorePricePartsFromKeys(pricingDetails.response, pricingDetails.keys); } catch (e) { __1.LogModule.prod('Error Validating Prices:'); __1.LogModule.prod(e); return { valid: false, price: pricingDetails, }; } if (__1.CONTRACT_TOOLS.deterministicStringify(priceParts) !== __1.CONTRACT_TOOLS.deterministicStringify(pricingDetails.priceParts)) { __1.LogModule.prod('TradeIdeaPricingValidationError: priceParts do not match'); return { valid: false, price: pricingDetails, }; } // Now we validate the got prices and reduce to one price let price; try { price = getValidPriceFromPartsAndKeys(pricingDetails.priceParts, pricingDetails.keys); } catch (e) { __1.LogModule.prod('Error Validating Prices:'); __1.LogModule.prod(e); return { valid: false, price: pricingDetails, }; } throwPricingError(price); if (__1.CONTRACT_TOOLS.deterministicStringify(price) !== __1.CONTRACT_TOOLS.deterministicStringify(pricingDetails.price)) { __1.LogModule.prod('TradeIdeaPricingValidationError: price do not match'); return { valid: false, price: pricingDetails, }; } // For last, we validate the original responseArray signature return checkIfPriceSignedResponseIsValid(pricingDetails); }; exports.ProvableModule = { config, // checkValidNewIdeaFromChronological, getVerifiedPriceDetails, validatePricingSignature, }; //# sourceMappingURL=provable.module.js.map