@ixily/activ
Version:
Alpha Capture Trade Idea Verification. Blockchain ownership proven trade ideas and strategies.
654 lines • 24.3 kB
JavaScript
;
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