diff --git a/README.md b/README.md index d17998e..e9aead1 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,23 @@ not use this key for production! NOTE 2: You might get error messages if somebody else is also running the oracle. +### Manual price feed +Alternatively, you can submit prices to the oracle manually by instructing the oracle to watch for changes to a local file. +This can be useful for testing specific behaviour (such as triggering liquidations). + +Run the oracle: +``` +yarn solink oracle-file {pair} {filepath} +``` + +{pair} must be one of the aggregator pairs in deploy.sandbox.json + +Then you can update prices by running +``` +echo {price} > {filepath} +``` + + # Observe The Aggregators With the oracle running, you can subscribe to price changes. In another diff --git a/config/setup.mainnet.json b/config/setup.mainnet.json new file mode 100644 index 0000000..fa9d722 --- /dev/null +++ b/config/setup.mainnet.json @@ -0,0 +1,67 @@ +{ + "aggregators": { + "btc:usd": { + "decimals": 2, + "minSubmissions": 1, + "maxSubmissions": 3, + "restartDelay": 0, + "rewardAmount": 10000, + "rewardTokenAccount": "3oLHHTaRqNsuTMjsTtkVy8bock6Bx8gCmDxku4TurVj1", + "oracles": [ + "blockworks", + "bartosz", + "defacto" + ] + }, + "eth:usd": { + "decimals": 2, + "minSubmissions": 1, + "maxSubmissions": 3, + "restartDelay": 0, + "rewardAmount": 10000, + "rewardTokenAccount": "3oLHHTaRqNsuTMjsTtkVy8bock6Bx8gCmDxku4TurVj1", + "oracles": [ + "blockworks", + "bartosz", + "defacto" + ] + }, + "sol:usd": { + "decimals": 2, + "minSubmissions": 1, + "maxSubmissions": 3, + "restartDelay": 0, + "rewardAmount": 10000, + "rewardTokenAccount": "3oLHHTaRqNsuTMjsTtkVy8bock6Bx8gCmDxku4TurVj1", + "oracles": [ + "blockworks", + "bartosz", + "defacto" + ] + }, + "srm:usd": { + "decimals": 2, + "minSubmissions": 1, + "maxSubmissions": 3, + "restartDelay": 0, + "rewardAmount": 10000, + "rewardTokenAccount": "3oLHHTaRqNsuTMjsTtkVy8bock6Bx8gCmDxku4TurVj1", + "oracles": [ + "blockworks", + "bartosz", + "defacto" + ] + } + }, + "oracles": { + "blockworks": { + "owner": "GW6bnJN6cMNzVnoVfV9hW6vx6XxTfP8k3HA1PgWCumxV" + }, + "bartosz": { + "owner": "3cGZFMZavPHzciggMLWzT74qdaakNAosMwQokHTEKmbx" + }, + "defacto": { + "owner": "6KwCmCtjmaQg3jbX7dejnB29RqMByx32KryX3Lyuqmq8" + } + } +} diff --git a/src/cli.ts b/src/cli.ts index cbf64b6..1b77c90 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -10,6 +10,8 @@ import { PriceFeeder } from "./PriceFeeder" import { sleep, walletFromEnv } from "./utils" import { PublicKey, Wallet } from "solray" import { log } from "./log" +import { Submitter } from "./Submitter" +import { file } from "./feeds" const cli = new Command() @@ -86,4 +88,51 @@ cli.command("observe").action(async (name?: string) => { } }) + +cli.command("oracle-file ").action(async (pair, path) => { + + console.log(pair); + console.log(path); + + let deployInfo = loadJSONFile(process.env.DEPLOY_FILE!) + + // validate pair arg here + if (!(pair in deployInfo.aggregators)) { + throw 'Invalid pair ' + pair; + } + + let slot = await conn.getSlot() + conn.onSlotChange((slotInfo) => { + slot = slotInfo.slot + }) + + const wallet = await walletFromEnv("ORACLE_MNEMONIC", conn) + // await maybeRequestAirdrop(wallet.pubkey) + + let aggregatorInfo = deployInfo.aggregators[pair]; + + const oracleInfo = Object.values(aggregatorInfo.oracles).find( + (oracleInfo) => { + return oracleInfo.owner.equals(wallet.pubkey) + } + ) + + let minValueChangeForNewRound = 0 + + const submitter = new Submitter( + deployInfo.programID, + aggregatorInfo.pubkey, + oracleInfo!.pubkey, + wallet, + file(pair, path), + { + minValueChangeForNewRound, + }, + () => slot + ) + + submitter.start(); + +}) + cli.parse(process.argv) diff --git a/src/feeds.ts b/src/feeds.ts index b89ea66..e161dcd 100644 --- a/src/feeds.ts +++ b/src/feeds.ts @@ -6,7 +6,7 @@ import { eventsIter, median, notify } from "./utils" import { log } from "./log" import winston from "winston" - +import fs from "fs"; const SECONDS = 1000 export const UPDATE = "UPDATE" @@ -292,6 +292,7 @@ export class Binance extends PriceFeed { const quoteCurrency = payload.s.slice(3).toLowerCase(); const pair = `${baseCurrency}:${quoteCurrency == 'busd' ? 'usd' : quoteCurrency}`; + const price: IPrice = { source: Binance.name, pair, @@ -511,3 +512,32 @@ export function coinbase(pair: string): IPriceFeed { return eventsIter(emitter, UPDATE) } + +export function file(pair: string, filepath: string): IPriceFeed { + const emitter = new EventEmitter() + + try { + fs.accessSync(filepath); + } catch { + fs.writeFileSync(filepath, ''); + console.log('feed file created at ' + filepath) + } + + console.log('started file feed for ' + pair) + + fs.watch(filepath, (event) => { + if (event === 'change') { + const data = fs.readFileSync(filepath, 'utf8') + const price: IPrice = { + source: "file", + pair, + decimals: 2, + value: parseFloat(data) + } + console.log('price update: ', price) + emitter.emit(UPDATE, price) + } + }); + + return eventsIter(emitter, UPDATE) +} \ No newline at end of file