Division of main code in differentiated engines for old Pricekeeper and Wormhole client.
This commit is contained in:
parent
88132b3a19
commit
9e7b26c89b
|
@ -1,4 +1,13 @@
|
|||
/**
|
||||
* Pricecaster Service.
|
||||
*
|
||||
* Fetcher backend component.
|
||||
*
|
||||
* (c) 2021 Randlabs, Inc.
|
||||
*/
|
||||
|
||||
export interface IAppSettings extends Record<string, unknown> {
|
||||
mode: string,
|
||||
algo: {
|
||||
token: string,
|
||||
api: string,
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Pricecaster Service.
|
||||
*
|
||||
* Fetcher backend component.
|
||||
*
|
||||
* (c) 2021 Randlabs, Inc.
|
||||
*/
|
||||
|
||||
export function sleep (ms: number) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms)
|
||||
})
|
||||
}
|
|
@ -1,3 +1,11 @@
|
|||
/**
|
||||
* Pricecaster Service.
|
||||
*
|
||||
* Fetcher backend component.
|
||||
*
|
||||
* (c) 2021 Randlabs, Inc.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
export enum StatusCode {
|
||||
OK,
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* Pricecaster Service.
|
||||
*
|
||||
* Fetcher backend component.
|
||||
*
|
||||
* (c) 2021 Randlabs, Inc.
|
||||
*/
|
||||
|
||||
export interface IEngine {
|
||||
start(): Promise<void>
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* Pricecaster Service.
|
||||
*
|
||||
* Fetcher backend component.
|
||||
*
|
||||
* (c) 2021 Randlabs, Inc.
|
||||
*/
|
||||
|
||||
import { IEngine } from './IEngine'
|
||||
import { PythPriceFetcher } from '../fetcher/PythPriceFetcher'
|
||||
import { StdAlgoPublisher } from '../publisher/stdAlgoPublisher'
|
||||
import { StrategyLastPrice } from '../strategy/strategyLastPrice'
|
||||
import { IAppSettings } from '../common/settings'
|
||||
import { IPriceFetcher } from '../fetcher/IPriceFetcher'
|
||||
import { IPublisher, PublishInfo } from '../publisher/IPublisher'
|
||||
import { PriceTicker } from '../common/priceTicker'
|
||||
import { StatusCode } from '../common/statusCodes'
|
||||
import { sleep } from '../common/sleep'
|
||||
const algosdk = require('algosdk')
|
||||
const charm = require('charm')()
|
||||
|
||||
type WorkerRoutineStatus = {
|
||||
status: StatusCode,
|
||||
reason?: string,
|
||||
tick?: PriceTicker,
|
||||
pub?: PublishInfo
|
||||
}
|
||||
|
||||
async function workerRoutine (fetcher: IPriceFetcher, publisher: IPublisher): Promise<WorkerRoutineStatus> {
|
||||
const tick = fetcher.queryTicker()
|
||||
if (tick === undefined) {
|
||||
return { status: StatusCode.NO_TICKER }
|
||||
}
|
||||
const pub = await publisher.publish(tick)
|
||||
return { status: pub.status, reason: pub.reason, tick, pub }
|
||||
}
|
||||
|
||||
export class PriceKeeperEngine implements IEngine {
|
||||
private settings: IAppSettings
|
||||
|
||||
constructor (settings: IAppSettings) {
|
||||
this.settings = settings
|
||||
}
|
||||
|
||||
async start () {
|
||||
charm.write('Setting up for')
|
||||
.foreground('yellow')
|
||||
.write(` ${this.settings.params.symbol} `)
|
||||
.foreground('white')
|
||||
.write('for PriceKeeper App')
|
||||
.foreground('yellow')
|
||||
.write(` ${this.settings.params.priceKeeperAppId} `)
|
||||
.foreground('white')
|
||||
.write(`interval ${this.settings.params.publishIntervalSecs} secs\n`)
|
||||
|
||||
const publisher = new StdAlgoPublisher(this.settings.params.symbol,
|
||||
this.settings.params.priceKeeperAppId,
|
||||
this.settings.params.validator,
|
||||
(algosdk.mnemonicToSecretKey(this.settings.params.mnemo)).sk,
|
||||
this.settings.algo.token,
|
||||
this.settings.algo.api,
|
||||
this.settings.algo.port
|
||||
)
|
||||
|
||||
const fetcher = new PythPriceFetcher(this.settings.params.symbol,
|
||||
new StrategyLastPrice(this.settings.params.bufferSize), this.settings.pyth?.solanaClusterName!)
|
||||
await fetcher.start()
|
||||
|
||||
console.log('Waiting for fetcher to boot...')
|
||||
while (!fetcher.hasData()) {
|
||||
await sleep(250)
|
||||
}
|
||||
console.log('Waiting for publisher to boot...')
|
||||
await publisher.start()
|
||||
console.log('Starting worker.')
|
||||
|
||||
let active = true
|
||||
charm.removeAllListeners('^C')
|
||||
charm.on('^C', () => {
|
||||
console.log('CTRL+C: Aborted by user.')
|
||||
active = false
|
||||
})
|
||||
let pubCount = 0
|
||||
// eslint-disable-next-line no-unmodified-loop-condition
|
||||
while (active) {
|
||||
const wrs = await workerRoutine(fetcher, publisher)
|
||||
switch (wrs.status) {
|
||||
case StatusCode.OK: {
|
||||
console.log(`[PUB ${pubCount++}] ${wrs.tick!.price}±${wrs.tick!.confidence} exp:${wrs.tick!.exponent} t:${wrs.tick!.networkTime} TXID:${wrs.pub!.txid}`)
|
||||
break
|
||||
}
|
||||
case StatusCode.NO_TICKER:
|
||||
console.log('No ticker available from fetcher data source')
|
||||
break
|
||||
default:
|
||||
console.log('Error. Reason: ' + wrs.reason)
|
||||
}
|
||||
await sleep(this.settings.params.publishIntervalSecs * 1000)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* Pricecaster Service.
|
||||
*
|
||||
* Fetcher backend component.
|
||||
*
|
||||
* (c) 2021 Randlabs, Inc.
|
||||
*/
|
||||
|
||||
import { IEngine } from './IEngine'
|
||||
import { PythPriceFetcher } from '../fetcher/PythPriceFetcher'
|
||||
import { StdAlgoPublisher } from '../publisher/stdAlgoPublisher'
|
||||
import { StrategyLastPrice } from '../strategy/strategyLastPrice'
|
||||
import { IAppSettings } from '../common/settings'
|
||||
import { IPriceFetcher } from '../fetcher/IPriceFetcher'
|
||||
import { IPublisher, PublishInfo } from '../publisher/IPublisher'
|
||||
import { PriceTicker } from '../common/priceTicker'
|
||||
import { StatusCode } from '../common/statusCodes'
|
||||
import { sleep } from '../common/sleep'
|
||||
|
||||
const algosdk = require('algosdk')
|
||||
const charm = require('charm')()
|
||||
|
||||
type WorkerRoutineStatus = {
|
||||
status: StatusCode,
|
||||
reason?: string,
|
||||
tick?: PriceTicker,
|
||||
pub?: PublishInfo
|
||||
}
|
||||
|
||||
async function workerRoutine (fetcher: IPriceFetcher, publisher: IPublisher): Promise<WorkerRoutineStatus> {
|
||||
const tick = fetcher.queryTicker()
|
||||
if (tick === undefined) {
|
||||
return { status: StatusCode.NO_TICKER }
|
||||
}
|
||||
const pub = await publisher.publish(tick)
|
||||
return { status: pub.status, reason: pub.reason, tick, pub }
|
||||
}
|
||||
|
||||
export class WormholeClientEngine implements IEngine {
|
||||
private settings: IAppSettings
|
||||
|
||||
constructor (settings: IAppSettings) {
|
||||
this.settings = settings
|
||||
}
|
||||
|
||||
async start () {
|
||||
charm.write('Setting up for')
|
||||
.foreground('yellow')
|
||||
.write(` ${this.settings.params.symbol} `)
|
||||
.foreground('white')
|
||||
.write('for PriceKeeper App')
|
||||
.foreground('yellow')
|
||||
.write(` ${this.settings.params.priceKeeperAppId} `)
|
||||
.foreground('white')
|
||||
.write(`interval ${this.settings.params.publishIntervalSecs} secs\n`)
|
||||
|
||||
const publisher = new StdAlgoPublisher(this.settings.params.symbol,
|
||||
this.settings.params.priceKeeperAppId,
|
||||
this.settings.params.validator,
|
||||
(algosdk.mnemonicToSecretKey(this.settings.params.mnemo)).sk,
|
||||
this.settings.algo.token,
|
||||
this.settings.algo.api,
|
||||
this.settings.algo.port
|
||||
)
|
||||
|
||||
const fetcher = new PythPriceFetcher(this.settings.params.symbol,
|
||||
new StrategyLastPrice(this.settings.params.bufferSize), this.settings.pyth?.solanaClusterName!)
|
||||
await fetcher.start()
|
||||
|
||||
console.log('Waiting for fetcher to boot...')
|
||||
while (!fetcher.hasData()) {
|
||||
await sleep(250)
|
||||
}
|
||||
console.log('Waiting for publisher to boot...')
|
||||
await publisher.start()
|
||||
console.log('Starting worker.')
|
||||
|
||||
let active = true
|
||||
charm.removeAllListeners('^C')
|
||||
charm.on('^C', () => {
|
||||
console.log('CTRL+C: Aborted by user.')
|
||||
active = false
|
||||
})
|
||||
let pubCount = 0
|
||||
// eslint-disable-next-line no-unmodified-loop-condition
|
||||
while (active) {
|
||||
const wrs = await workerRoutine(fetcher, publisher)
|
||||
switch (wrs.status) {
|
||||
case StatusCode.OK: {
|
||||
console.log(`[PUB ${pubCount++}] ${wrs.tick!.price}±${wrs.tick!.confidence} exp:${wrs.tick!.exponent} t:${wrs.tick!.networkTime} TXID:${wrs.pub!.txid}`)
|
||||
break
|
||||
}
|
||||
case StatusCode.NO_TICKER:
|
||||
console.log('No ticker available from fetcher data source')
|
||||
break
|
||||
default:
|
||||
console.log('Error. Reason: ' + wrs.reason)
|
||||
}
|
||||
await sleep(this.settings.params.publishIntervalSecs * 1000)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,66 +1,66 @@
|
|||
/**
|
||||
* Pricecaster Service.
|
||||
*
|
||||
* Fetcher backend component.
|
||||
*
|
||||
* (c) 2021 Randlabs, Inc.
|
||||
*/
|
||||
// /**
|
||||
// * Pricecaster Service.
|
||||
// *
|
||||
// * Fetcher backend component.
|
||||
// *
|
||||
// * (c) 2021 Randlabs, Inc.
|
||||
// */
|
||||
|
||||
import { IPriceFetcher } from './IPriceFetcher'
|
||||
import { IStrategy } from '../strategy/strategy'
|
||||
import { getPythProgramKeyForCluster, PriceData, Product, PythConnection } from '@pythnetwork/client'
|
||||
import { Cluster, clusterApiUrl, Connection } from '@solana/web3.js'
|
||||
import { PriceTicker } from '../common/priceTicker'
|
||||
import { getEmitterAddressEth, getSignedVAA } from '@certusone/wormhole-sdk'
|
||||
// import { IPriceFetcher } from './IPriceFetcher'
|
||||
// import { IStrategy } from '../strategy/strategy'
|
||||
// import { getPythProgramKeyForCluster, PriceData, Product, PythConnection } from '@pythnetwork/client'
|
||||
// import { Cluster, clusterApiUrl, Connection } from '@solana/web3.js'
|
||||
// import { PriceTicker } from '../common/priceTicker'
|
||||
// import { getEmitterAddressEth, getSignedVAA } from '@certusone/wormhole-sdk'
|
||||
|
||||
export class WormholePythPriceFetcher implements IPriceFetcher {
|
||||
private strategy: IStrategy
|
||||
private symbol: string
|
||||
private pythConnection: PythConnection
|
||||
// export class WormholePythPriceFetcher implements IPriceFetcher {
|
||||
// private strategy: IStrategy
|
||||
// private symbol: string
|
||||
// private pythConnection: PythConnection
|
||||
|
||||
constructor (symbol: string, strategy: IStrategy, solanaClusterName: string) {
|
||||
const SOLANA_CLUSTER_NAME: Cluster = solanaClusterName as Cluster
|
||||
const connection = new Connection(clusterApiUrl(SOLANA_CLUSTER_NAME))
|
||||
const pythPublicKey = getPythProgramKeyForCluster(SOLANA_CLUSTER_NAME)
|
||||
this.pythConnection = new PythConnection(connection, pythPublicKey)
|
||||
this.strategy = strategy
|
||||
this.symbol = symbol
|
||||
}
|
||||
// constructor (symbol: string, strategy: IStrategy, solanaClusterName: string) {
|
||||
// const SOLANA_CLUSTER_NAME: Cluster = solanaClusterName as Cluster
|
||||
// const connection = new Connection(clusterApiUrl(SOLANA_CLUSTER_NAME))
|
||||
// const pythPublicKey = getPythProgramKeyForCluster(SOLANA_CLUSTER_NAME)
|
||||
// this.pythConnection = new PythConnection(connection, pythPublicKey)
|
||||
// this.strategy = strategy
|
||||
// this.symbol = symbol
|
||||
// }
|
||||
|
||||
async start () {
|
||||
await this.pythConnection.start()
|
||||
this.pythConnection.onPriceChange((product: Product, price: PriceData) => {
|
||||
if (product.symbol === this.symbol) {
|
||||
this.onPriceChange(price)
|
||||
}
|
||||
})
|
||||
}
|
||||
// async start () {
|
||||
// await this.pythConnection.start()
|
||||
// this.pythConnection.onPriceChange((product: Product, price: PriceData) => {
|
||||
// if (product.symbol === this.symbol) {
|
||||
// this.onPriceChange(price)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
stop (): void {
|
||||
this.pythConnection.stop()
|
||||
}
|
||||
// stop (): void {
|
||||
// this.pythConnection.stop()
|
||||
// }
|
||||
|
||||
setStrategy (s: IStrategy) {
|
||||
this.strategy = s
|
||||
}
|
||||
// setStrategy (s: IStrategy) {
|
||||
// this.strategy = s
|
||||
// }
|
||||
|
||||
hasData (): boolean {
|
||||
return this.strategy.bufferCount() > 0
|
||||
}
|
||||
// hasData (): boolean {
|
||||
// return this.strategy.bufferCount() > 0
|
||||
// }
|
||||
|
||||
queryTicker (): PriceTicker | undefined {
|
||||
getEmitterAddressEth()
|
||||
// queryTicker (): PriceTicker | undefined {
|
||||
// getEmitterAddressEth()
|
||||
|
||||
await getSignedVAA("https://wormhole-v2-testnet-api.certus.one", )
|
||||
//return this.strategy.getPrice()
|
||||
}
|
||||
// await getSignedVAA("https://wormhole-v2-testnet-api.certus.one", )
|
||||
// //return this.strategy.getPrice()
|
||||
// }
|
||||
|
||||
private onPriceChange (price: PriceData) {
|
||||
GrpcWebImpl
|
||||
PublicRPCServiceClientImpl
|
||||
getSignedVAA()
|
||||
const pt: PriceTicker = new PriceTicker(price.priceComponent,
|
||||
price.confidenceComponent, price.exponent, price.publishSlot)
|
||||
this.strategy.put(pt)
|
||||
}
|
||||
}
|
||||
// private onPriceChange (price: PriceData) {
|
||||
// GrpcWebImpl
|
||||
// PublicRPCServiceClientImpl
|
||||
// getSignedVAA()
|
||||
// const pt: PriceTicker = new PriceTicker(price.priceComponent,
|
||||
// price.confidenceComponent, price.exponent, price.publishSlot)
|
||||
// this.strategy.put(pt)
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -8,45 +8,17 @@
|
|||
*/
|
||||
|
||||
import * as Config from '@randlabs/js-config-reader'
|
||||
import { PythPriceFetcher } from './fetcher/PythPriceFetcher'
|
||||
import { StdAlgoPublisher } from './publisher/stdAlgoPublisher'
|
||||
import { StrategyLastPrice } from './strategy/strategyLastPrice'
|
||||
import { IPriceFetcher } from './fetcher/IPriceFetcher'
|
||||
import { IPublisher, PublishInfo } from './publisher/IPublisher'
|
||||
import { PriceTicker } from './common/priceTicker'
|
||||
import { StatusCode } from './common/statusCodes'
|
||||
import { IAppSettings } from './common/settings'
|
||||
import { exit } from 'process'
|
||||
const algosdk = require('algosdk')
|
||||
const charm = require('charm')()
|
||||
|
||||
function sleep (ms: number) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms)
|
||||
})
|
||||
}
|
||||
|
||||
type WorkerRoutineStatus = {
|
||||
status: StatusCode,
|
||||
reason?: string,
|
||||
tick?: PriceTicker,
|
||||
pub?: PublishInfo
|
||||
}
|
||||
|
||||
async function workerRoutine (fetcher: IPriceFetcher, publisher: IPublisher): Promise<WorkerRoutineStatus> {
|
||||
const tick = fetcher.queryTicker()
|
||||
if (tick === undefined) {
|
||||
return { status: StatusCode.NO_TICKER }
|
||||
}
|
||||
const pub = await publisher.publish(tick)
|
||||
return { status: pub.status, reason: pub.reason, tick, pub }
|
||||
}
|
||||
import { PriceKeeperEngine } from './engine/PriceKeeperEngine'
|
||||
import { WormholeClientEngine } from './engine/WormholeEngine'
|
||||
const charm = require('charm')();
|
||||
|
||||
(async () => {
|
||||
charm.pipe(process.stdout)
|
||||
charm.reset()
|
||||
charm.foreground('cyan').display('bright')
|
||||
console.log('Pricecaster Service Fetcher -- (c) 2021 Randlabs.io\n')
|
||||
console.log('Pricecaster Service Backend -- (c) 2021 Randlabs, Inc.\n')
|
||||
charm.foreground('white')
|
||||
|
||||
let settings: IAppSettings
|
||||
|
@ -58,56 +30,20 @@ async function workerRoutine (fetcher: IPriceFetcher, publisher: IPublisher): Pr
|
|||
exit(1)
|
||||
}
|
||||
|
||||
charm.write('Setting up for')
|
||||
.foreground('yellow')
|
||||
.write(` ${settings.params.symbol} `)
|
||||
.foreground('white')
|
||||
.write('for PriceKeeper App')
|
||||
.foreground('yellow')
|
||||
.write(` ${settings.params.priceKeeperAppId} `)
|
||||
.foreground('white')
|
||||
.write(`interval ${settings.params.publishIntervalSecs} secs\n`)
|
||||
let engine
|
||||
switch (settings.mode) {
|
||||
case 'pkeeper':
|
||||
engine = new PriceKeeperEngine(settings)
|
||||
break
|
||||
|
||||
const publisher = new StdAlgoPublisher(settings.params.symbol,
|
||||
settings.params.priceKeeperAppId,
|
||||
settings.params.validator,
|
||||
(algosdk.mnemonicToSecretKey(settings.params.mnemo)).sk,
|
||||
settings.algo.token,
|
||||
settings.algo.api,
|
||||
settings.algo.port
|
||||
)
|
||||
const fetcher = new PythPriceFetcher(settings.params.symbol, new StrategyLastPrice(settings.params.bufferSize), settings.pyth?.solanaClusterName!)
|
||||
await fetcher.start()
|
||||
case 'wormhole-client':
|
||||
engine = new WormholeClientEngine(settings)
|
||||
break
|
||||
|
||||
console.log('Waiting for fetcher to boot...')
|
||||
while (!fetcher.hasData()) {
|
||||
await sleep(250)
|
||||
default:
|
||||
console.error('Invalid specified mode in settings')
|
||||
exit(2)
|
||||
}
|
||||
console.log('Waiting for publisher to boot...')
|
||||
await publisher.start()
|
||||
console.log('Starting worker.')
|
||||
|
||||
let active = true
|
||||
charm.removeAllListeners('^C')
|
||||
charm.on('^C', () => {
|
||||
console.log('CTRL+C: Aborted by user.')
|
||||
active = false
|
||||
})
|
||||
let pubCount = 0
|
||||
// eslint-disable-next-line no-unmodified-loop-condition
|
||||
while (active) {
|
||||
const wrs = await workerRoutine(fetcher, publisher)
|
||||
switch (wrs.status) {
|
||||
case StatusCode.OK: {
|
||||
console.log(`[PUB ${pubCount++}] ${wrs.tick!.price}±${wrs.tick!.confidence} exp:${wrs.tick!.exponent} t:${wrs.tick!.networkTime} TXID:${wrs.pub!.txid}`)
|
||||
break
|
||||
}
|
||||
case StatusCode.NO_TICKER:
|
||||
console.log('No ticker available from fetcher data source')
|
||||
break
|
||||
default:
|
||||
console.log('Error. Reason: ' + wrs.reason)
|
||||
}
|
||||
await sleep(settings.params.publishIntervalSecs * 1000)
|
||||
}
|
||||
engine.start()
|
||||
})()
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
module.exports = {
|
||||
mode: 'pkeeper',
|
||||
algo: {
|
||||
token: '',
|
||||
api: 'https://api.testnet.algoexplorer.io',
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
module.exports = {
|
||||
mode: 'pkeeper',
|
||||
algo: {
|
||||
token: '',
|
||||
api: 'https://api.testnet.algoexplorer.io',
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
module.exports = {
|
||||
mode: 'pkeeper',
|
||||
algo: {
|
||||
token: '',
|
||||
api: 'https://api.testnet.algoexplorer.io',
|
||||
|
|
|
@ -114,7 +114,7 @@ describe('VAA Processor Smart-contract Tests', function () {
|
|||
const txid = await pclib.setVAAVerifyProgramHash(OWNER_ADDR, compiledProgram.hash, signCallback)
|
||||
await pclib.waitForTransactionResponse(txid)
|
||||
const vphstate = await tools.readAppGlobalStateByKey(algodClient, appId, OWNER_ADDR, 'vphash')
|
||||
expect(algosdk.encodeAddress(Buffer.from(vphstate, 'base64'))).to.equal(compiledProgram.hash)
|
||||
expect(vphstate).to.equal(compiledProgram.hash)
|
||||
})
|
||||
it('Must disallow setting stateless logic hash from non-owner', async function () {
|
||||
|
||||
|
|
Loading…
Reference in New Issue