@ixily/activ
Version:
Alpha Capture Trade Idea Verification. Blockchain ownership proven trade ideas and strategies.
373 lines (341 loc) • 10 kB
text/typescript
import {
ILitAction,
LIT_ACTIONS,
LitModule,
generateUUID,
LogModule as log,
loop,
retryFunctionHelper,
CONTRACT_INTERFACES,
} from '../..'
import { ParallelChunksModule } from './parallel-chunks.module'
import { IG_PRE_AUTHENTICATE } from '../../constants/lit-actions'
const CROUPIER_URL = 'https://croupier.ixily.io' // 'https://api.ixily.io/v1/activ/croupier'
const state = {
useProxyUntrusted: false as boolean,
}
const config = (params: { useProxyUntrusted?: boolean }): void => {
if (params.useProxyUntrusted !== undefined) {
state.useProxyUntrusted = params.useProxyUntrusted
}
}
export const extractPricingKeysArrayFromPriceLogs = (
logs: string[],
): [string, string][] => {
const keysArray: [string, string][] = []
for (const logi of logs) {
const loga = logi.split('\n')
// console.log('loga')
// console.log(loga)
const logo = loga[loga.length - 2]
// console.log('logo')
// console.log(logo)
const logp = logo.split(':')
// console.log('logp')
// console.log(logp)
if (logp[1] !== 'myidpass') {
throw new Error('Error Decoding Node Key for a node, for pricing')
}
const nodeId = logp[2]
// console.log('nodeId')
// console.log(nodeId)
const nodeKey = logp[3]
// console.log('nodeKey')
// console.log(nodeKey)
keysArray.push([nodeId, nodeKey])
}
// sort key arrays alphabetically by first entry of the double array
keysArray.sort((a, b) => {
if (a[0] < b[0]) {
return -1
} else if (a[0] > b[0]) {
return 1
} else {
return 0
}
})
return keysArray
}
export const getValidPriceFromSigLogPrice = (litResult: {
signatures: any
response: CONTRACT_INTERFACES.IPrice
logs: string[]
}): CONTRACT_INTERFACES.IValidPrice => {
const keys = extractPricingKeysArrayFromPriceLogs(litResult.logs)
return {
response: litResult.response as unknown as string[],
signature: litResult.signatures.priceSignature,
keys,
}
}
export const getSigLogPriceResponseFromLitResult = (
litResult: any,
): {
signatures: any
response: CONTRACT_INTERFACES.IPrice
logs: string[]
} => {
// console.log('litResult')
// console.log(litResult)
const sigLogResponse = {
signatures: litResult.signatures,
response: litResult.response as CONTRACT_INTERFACES.IPrice,
logs: litResult.debug.allNodeLogs as string[],
}
return sigLogResponse
}
export const getValidPriceFromLitPriceResult = (
litResult: any,
): CONTRACT_INTERFACES.IValidPrice => {
const sigLogResponse = getSigLogPriceResponseFromLitResult(litResult)
const validPrice = getValidPriceFromSigLogPrice(sigLogResponse)
return validPrice
}
const LIT_PRICING_LIT_JS_TASK = 'litpri'
const LIT_PRICING_LIT_JS_TASK_CHUNKS = 5
ParallelChunksModule.defineTaskChunks(
LIT_PRICING_LIT_JS_TASK,
LIT_PRICING_LIT_JS_TASK_CHUNKS,
)
const getPriceDetails = async (
params: CONTRACT_INTERFACES.IProviderRequest,
mockPricing: boolean = false,
customSettings?: {
tracker?: string
attempts?: number | undefined
croupierUrl?: string
},
): Promise<CONTRACT_INTERFACES.IValidPrice> => {
// only for debug/testing
if (mockPricing) {
const price = await testPricingAsset(params)
return {
response: [JSON.stringify(price)],
signature: null,
keys: [],
}
}
// pricing code
// let litAction: ILitActionV2
let litAction: ILitAction
if (params.provider === 'Binance') {
log.dev('set to ask binance')
litAction = LIT_ACTIONS.GET_PRICE_BINANCE_V2
} else if (params.provider === 'IEX') {
log.dev('set to ask iex')
litAction = LIT_ACTIONS.GET_PRICE_IEX_V2
} else if (params.provider === 'IG Group') {
log.dev('set to ask IG')
litAction = LIT_ACTIONS.GET_PRICE_IG_V2
const trueCreds = await IG_PRE_AUTHENTICATE(
params.auth!.key,
params.auth!.secret!,
params.auth!.secret2!,
)
params.auth!.key = trueCreds.key
params.auth!.secret = trueCreds.secret
params.auth!.secret2 = trueCreds.secret2
} else if (params.provider === 'Alpaca') {
log.dev('set to ask alpaca')
litAction = LIT_ACTIONS.GET_PRICE_ALPACA_V2
} else if (params.provider === 'Tradestation') {
log.dev('set to ask tradestation')
litAction = LIT_ACTIONS.GET_PRICE_TRADESTATION_V2
} else if (params.provider === 'CoinMarketCap') {
log.dev('set to ask coinmarketcap')
litAction = LIT_ACTIONS.GET_PRICE_COINMARKETCAP_V2
} else if (params.provider === 'CryptoCompare') {
log.dev('set to ask cryptocompare')
if (params.params.symbol.indexOf('=') === -1) {
throw new Error(
'Pricing Error: For CryptoCompare, please separate the assets with "=". For instance: "BTC=USD"',
)
}
litAction = LIT_ACTIONS.GET_PRICE_CRYPTOCOMPARE_V2
} else {
// throw new Error(
// 'PricingModule: getPriceDetails: Price Provider not supported: ' +
// params.provider,
// )
throw new Error('Unsupported provider')
}
// trying the first time to load this and force
await loop(
async () => {
log.dev(
`The ipfs content to this id "${litAction.ipfsId}" was loaded successfully.`,
)
log.dev(`GET https://ipfs.io/ipfs/${litAction.ipfsId}`)
},
async () => {
let isPassed = false
try {
log.dev(
`Calling the ipfs content to "${params.provider} lit action" with id "${litAction.ipfsId}". loading...`,
)
const result = await fetch(
`https://ipfs.io/ipfs/${litAction.ipfsId}`,
)
if (result.status === 200) {
isPassed = true
}
} catch (er: any) {
log.dev('er:', er)
isPassed = false
}
return isPassed
},
{
loopTimeInMs: 10000,
limitTimeSecond: 300,
},
async (err: any) => {
log.dev('err', err)
},
)
// note: define one interface for this (LitModule.runJS<...>)
// let dt: any = null
// This is no the best place for a retry function as such.
// try {
// dt = await retryFunctionHelper({
// maxRetries: 3,
// retryCallback: async () => {
// console.log(
// 'ACTIV: PricingModule: getPriceDetails: tracker: ',
// customSettings?.tracker,
// )
const litActionParams = {
symbol: params.params.symbol,
apiKey: params.auth?.key || '',
apiSecret: params.auth?.secret || '',
apiSecret2: params.auth?.secret2 || '',
ipfsId: litAction.ipfsId,
uniqueExecutionId: generateUUID(),
croupierUrl: customSettings?.croupierUrl || CROUPIER_URL,
tracker: customSettings?.tracker,
attempts: customSettings?.attempts,
useProxy: state.useProxyUntrusted === true ? true : false,
}
// console.log('[pricing.module] getPriceDetails (litActionParams)', litActionParams)
const dt = await ParallelChunksModule.run(
LIT_PRICING_LIT_JS_TASK,
async (): Promise<any> => {
return LitModule.runJS(
// litAction.fetcher.ipfsId,
litAction.ipfsId,
// litAction.fetcher.publicKey,
litAction.publicKey,
litActionParams,
)
},
)
// },
// notificationCallback: async (errMsg: string, retryInx: number) => {
// log.dev(
// `[${params.provider}] getPriceDetails LitModule.runJS (retry #${retryInx})`,
// errMsg,
// )
// },
// rejectOnMaxRetries: true,
// })
// } catch (err: any) {
// We do prefer to show error for sure and we do prefer the uggly;
// cause the uggly is actually pretty and says everythin we want to know.
// the pretty is a code that tells us nothing.
/*
let errorCode = err.message
let prettyError = null as any
const showCode = false
if (showCode) {
switch (errorCode) {
case 'There was an error getting the signing shares from the nodes':
errorCode = 'LA-001'
break
case 'There was a timeout error executing the Javascript for this action':
errorCode = 'LA-002'
break
case 'LitNodeClient is not ready. Please call await litNodeClient.connect() first.':
errorCode = 'LA-003'
break
// we need catch and define the case for this error
default:
errorCode = 'LA-000'
break
}
prettyError = `Request failed (${errorCode})`
}
throw new Error(prettyError || errorCode)
*/
// We do prefer to show error for sure and we do prefer the uggly;
// cause the uggly is actually pretty and says everythin we want to know.
// the pretty is a code that tells us nothing.
// throw err
// }
// console.log('dt')
// console.log(dt)
return getValidPriceFromLitPriceResult(dt)
}
const testPricingAsset = async (
request: CONTRACT_INTERFACES.IProviderRequest,
): Promise<CONTRACT_INTERFACES.IPrice> => {
try {
switch (request.provider) {
case 'Binance':
const binanceParams = {
symbol: request.params.symbol,
key: request.auth?.key,
secret: request.auth?.secret,
useProxy: undefined as boolean | undefined,
}
if (state.useProxyUntrusted === true) {
binanceParams.useProxy = true
}
return LIT_ACTIONS.GET_PRICE_BINANCE_V2.testPricing(
binanceParams,
)
case 'IEX':
return LIT_ACTIONS.GET_PRICE_IEX_V2.testPricing({
symbol: request.params.symbol,
key: request.auth?.key,
})
case 'IG Group':
return LIT_ACTIONS.GET_PRICE_IG_V2.testPricing({
key: request.auth?.key,
secret: request.auth?.secret,
secret2: request.auth?.secret2,
symbol: request.params.symbol,
})
case 'CoinMarketCap':
return LIT_ACTIONS.GET_PRICE_COINMARKETCAP_V2.testPricing({
symbol: request.params.symbol,
key: request.auth?.key,
})
case 'CryptoCompare':
return LIT_ACTIONS.GET_PRICE_CRYPTOCOMPARE_V2.testPricing({
symbol: request.params.symbol,
key: request.auth?.key,
})
case 'Alpaca':
return LIT_ACTIONS.GET_PRICE_ALPACA_V2.testPricing({
symbol: request.params.symbol,
key: request.auth?.key,
secret: request.auth?.secret,
})
case 'Tradestation':
return LIT_ACTIONS.GET_PRICE_TRADESTATION_V2.testPricing({
symbol: request.params.symbol,
key: request.auth?.key,
})
default:
throw new Error('Provider not supported')
}
} catch (err) {
;(err as any).message = 'PRICING MODULE ERROR: ' + (err as any).message
throw err
}
}
export const PricingModule = {
config,
getPriceDetails,
testPricingAsset,
}