feed polling and oracle removal
This commit is contained in:
parent
26f529d304
commit
3e097081c4
2
.env
2
.env
|
@ -1,4 +1,4 @@
|
|||
NETWORK=dev
|
||||
DEPLOY_FILE=deploy.json
|
||||
ADMIN_MNEMONIC="summer fuel twin history item learn flip marble ginger knee mix ten"
|
||||
ORACLE_MNEMONIC="pet retreat peasant wing search rug dwarf high city pill giggle dinner"
|
||||
ORACLE_MNEMONIC="amount smoke bar coil current trial toward minimum model pass moral liberty"
|
|
@ -1,4 +1,7 @@
|
|||
/node_modules
|
||||
/program/target
|
||||
/integration-example/target
|
||||
yarn.lock
|
||||
node_modules
|
||||
target
|
||||
target
|
||||
yarn.lock
|
||||
build
|
||||
.yarn
|
||||
dist
|
31
README.md
31
README.md
|
@ -42,7 +42,7 @@ yarn build:program
|
|||
```
|
||||
yarn solink deploy-program
|
||||
|
||||
deployed aggregator program. program id: 9KXbVqUrMgtti7Jx4rrV1NqXjQNxWaKgtYCEwJ8AESS5
|
||||
deployed aggregator program. program id: HFHbe2uckzz9Xh633mbJPYcukzpyJRVcwL87fUrVddiq
|
||||
```
|
||||
|
||||
Create the `btc:usd` feed (that accepts max and min u64 as valid submission values):
|
||||
|
@ -54,7 +54,7 @@ yarn solink add-aggregator \
|
|||
--minSubmissionValue 0 \
|
||||
--maxSubmissionValue 18446744073709551615
|
||||
|
||||
feed initialized, pubkey: AUK9X6QLgauAUvEA3Ajc91fZytb9ccA7qVR72ErDFNg2
|
||||
feed initialized, pubkey: 2jReuMRoYi3pKTF8YLnZEvT2bXcw56SdBxvssrVzu41v
|
||||
```
|
||||
|
||||
## Adding an oracle
|
||||
|
@ -83,17 +83,36 @@ Next we create a new oracle to the feed we've created previously, and set its ow
|
|||
```
|
||||
yarn solink add-oracle \
|
||||
--index 0 \
|
||||
--feedAddress AUK9X6QLgauAUvEA3Ajc91fZytb9ccA7qVR72ErDFNg2 \
|
||||
--feedAddress 2jReuMRoYi3pKTF8YLnZEvT2bXcw56SdBxvssrVzu41v \
|
||||
--oracleName solink-test \
|
||||
--oracleOwner FosLwbttPgkEDv36VJLU3wwXcBSSoUGkh7dyZPsXNtT4
|
||||
|
||||
added oracle. pubkey: 4vH5L2jSNXGfcCx42N4sqPiMzEbp1PaQjQ6XngDBu8zR
|
||||
added oracle. pubkey: 4jWLbd2Vm98RrqunVvaSXZuP1AFbgQSM2hAHMvZSdNCu
|
||||
```
|
||||
|
||||
Start submitting data from a price feed (e.g. coinbase BTC-USDT):
|
||||
|
||||
```
|
||||
yarn solink feed \
|
||||
--feedAddress AUK9X6QLgauAUvEA3Ajc91fZytb9ccA7qVR72ErDFNg2 \
|
||||
--oracleAddress 4vH5L2jSNXGfcCx42N4sqPiMzEbp1PaQjQ6XngDBu8zR
|
||||
--feedAddress 2jReuMRoYi3pKTF8YLnZEvT2bXcw56SdBxvssrVzu41v \
|
||||
--oracleAddress 4jWLbd2Vm98RrqunVvaSXZuP1AFbgQSM2hAHMvZSdNCu
|
||||
```
|
||||
|
||||
## Read price
|
||||
|
||||
Poll the latest aggregated (median) value from a feed:
|
||||
|
||||
```
|
||||
yarn solink feed-poll \
|
||||
--feedAddress 2jReuMRoYi3pKTF8YLnZEvT2bXcw56SdBxvssrVzu41v
|
||||
```
|
||||
|
||||
## Remove oracle
|
||||
|
||||
```
|
||||
yarn solink remove-oracle \
|
||||
--index 0 \
|
||||
--feedAddress 2jReuMRoYi3pKTF8YLnZEvT2bXcw56SdBxvssrVzu41v
|
||||
```
|
||||
|
||||
## Test Token
|
||||
|
|
Binary file not shown.
|
@ -14,5 +14,9 @@
|
|||
"tokenAccount": {
|
||||
"pubkey": "A4xJLiJrtVigYjJ2NRUrcFza7eB7Y8XeGZh5KmkoxDs1",
|
||||
"secret": "19c19ac59d379f3d669b6b5957bbf99060b2b15dd4d266b3f76e164f665c512986bc739598f0e5f0252b3e2fbd0c54a9a08b295a4c5c2901b91c6e3d1e8ace5c"
|
||||
},
|
||||
"oracle[0]": {
|
||||
"pubkey": "4jWLbd2Vm98RrqunVvaSXZuP1AFbgQSM2hAHMvZSdNCu",
|
||||
"secret": "83369edeffc2f5b870da6595cd7a916723566a1307e722f29e7db6bc8fd01b643776a09078c98230dd23a3aeab3e870e7fb6651ddae3cbf772afa4eba4735e16"
|
||||
}
|
||||
}
|
|
@ -16,11 +16,12 @@
|
|||
"buffer-layout": "^1.2.0",
|
||||
"commander": "^6.2.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"solray": "git+https://github.com/defactojob/solray.git"
|
||||
"solray": "git+https://github.com/defactojob/solray.git",
|
||||
"ws": "^7.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/recommended": "^1.0.1",
|
||||
"@types/node": "^14.14.10",
|
||||
"@types/node": "^14.14.12",
|
||||
"ts-node": "^9.1.1",
|
||||
"typescript": "^4.1.2"
|
||||
}
|
||||
|
|
|
@ -73,10 +73,8 @@ interface RemoveOracleParams {
|
|||
// oracle index
|
||||
index: number;
|
||||
aggregator: PublicKey;
|
||||
// The oracle key
|
||||
oracle: PublicKey;
|
||||
// To prove you are the aggregator owner
|
||||
authority: Account;
|
||||
authority?: Account;
|
||||
}
|
||||
|
||||
interface RemoveOracleInstructionParams extends RemoveOracleParams {
|
||||
|
@ -172,7 +170,7 @@ export default class FluxAggregator extends BaseProgram {
|
|||
]);
|
||||
}
|
||||
|
||||
public async addOracle(params: AddOracleParams): Promise<PublicKey> {
|
||||
public async addOracle(params: AddOracleParams): Promise<Account> {
|
||||
const account = new Account();
|
||||
|
||||
await this.sendTx([
|
||||
|
@ -187,7 +185,7 @@ export default class FluxAggregator extends BaseProgram {
|
|||
})
|
||||
], [this.account, account, params.aggregatorOwner]);
|
||||
|
||||
return account.publicKey;
|
||||
return account;
|
||||
}
|
||||
|
||||
public async oracleInfo(pubkey: PublicKey) {
|
||||
|
@ -227,14 +225,14 @@ export default class FluxAggregator extends BaseProgram {
|
|||
public async removeOracle(params: RemoveOracleParams): Promise<void> {
|
||||
await this.sendTx([
|
||||
this.removeOracleInstruction(params)
|
||||
], [this.account, params.authority]);
|
||||
|
||||
], [this.account, params.authority || this.wallet.account]);
|
||||
}
|
||||
|
||||
private removeOracleInstruction(params: RemoveOracleInstructionParams): TransactionInstruction {
|
||||
const {
|
||||
index,
|
||||
authority,
|
||||
aggregator,
|
||||
} = params;
|
||||
|
||||
const layout = BufferLayout.struct([
|
||||
|
@ -246,7 +244,9 @@ export default class FluxAggregator extends BaseProgram {
|
|||
instruction: 2, // remove oracle instruction
|
||||
index,
|
||||
}, [
|
||||
{ write: authority },
|
||||
//
|
||||
{ write: aggregator },
|
||||
authority || this.wallet.account,
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
116
src/cli.ts
116
src/cli.ts
|
@ -3,9 +3,9 @@ import { Command, option } from "commander"
|
|||
import fs from "fs"
|
||||
import path from "path"
|
||||
|
||||
import {
|
||||
BPFLoader, PublicKey, Wallet, NetworkName,
|
||||
solana, Deployer, SPLToken, ProgramAccount
|
||||
import {
|
||||
BPFLoader, PublicKey, Wallet, NetworkName,
|
||||
solana, Deployer, SPLToken, ProgramAccount
|
||||
} from "solray"
|
||||
|
||||
import dotenv from "dotenv"
|
||||
|
@ -16,6 +16,7 @@ import {
|
|||
decodeAggregatorInfo,
|
||||
walletFromEnv,
|
||||
openDeployer,
|
||||
sleep,
|
||||
} from "./utils"
|
||||
|
||||
import * as feed from "./feed"
|
||||
|
@ -48,7 +49,15 @@ class AppContext {
|
|||
|
||||
constructor(public deployer: Deployer, public wallet: Wallet) { }
|
||||
|
||||
get aggregatorProgram() {
|
||||
get aggregatorProgramID() {
|
||||
return this.aggregatorProgramAccount.publicKey
|
||||
}
|
||||
|
||||
get aggregator() {
|
||||
return new FluxAggregator(this.wallet, this.aggregatorProgramID)
|
||||
}
|
||||
|
||||
get aggregatorProgramAccount() {
|
||||
const program = this.deployer.account(AppContext.AGGREGATOR_PROGRAM)
|
||||
|
||||
if (program == null) {
|
||||
|
@ -129,7 +138,7 @@ cli
|
|||
.option("--minSubmissionValue <number>", "minSubmissionValue", "0")
|
||||
.option("--maxSubmissionValue <number>", "maxSubmissionValue", "18446744073709551615")
|
||||
.action(async (opts) => {
|
||||
const { deployer, wallet, aggregatorProgram } = await AppContext.forAdmin()
|
||||
const { deployer, wallet, aggregatorProgramAccount: aggregatorProgram } = await AppContext.forAdmin()
|
||||
|
||||
const { feedName, submitInterval, minSubmissionValue, maxSubmissionValue } = opts
|
||||
|
||||
|
@ -180,25 +189,41 @@ cli
|
|||
.option("--oracleName <string>", "oracle name")
|
||||
.option("--oracleOwner <string>", "oracle owner address")
|
||||
.action(async (opts) => {
|
||||
const { wallet, aggregatorProgram } = await AppContext.forAdmin()
|
||||
const { wallet, aggregator, deployer } = await AppContext.forAdmin()
|
||||
|
||||
const { index, oracleName, oracleOwner, feedAddress } = opts
|
||||
|
||||
if (!index || index < 0 || index > 21) {
|
||||
error("invalid index (0-20)")
|
||||
error("invalid index. requires (0-20)")
|
||||
}
|
||||
const program = new FluxAggregator(wallet, aggregatorProgram.publicKey)
|
||||
|
||||
log("add oracle...")
|
||||
const oracle = await program.addOracle({
|
||||
index,
|
||||
owner: new PublicKey(oracleOwner),
|
||||
description: oracleName.substr(0, 32).padEnd(32),
|
||||
aggregator: new PublicKey(feedAddress),
|
||||
aggregatorOwner: wallet.account,
|
||||
const oracle = await deployer.ensure(`oracle[${index}]`, async () => {
|
||||
return aggregator.addOracle({
|
||||
index,
|
||||
owner: new PublicKey(oracleOwner),
|
||||
description: oracleName.substr(0, 32).padEnd(32),
|
||||
aggregator: new PublicKey(feedAddress),
|
||||
aggregatorOwner: wallet.account,
|
||||
})
|
||||
})
|
||||
|
||||
log(`added oracle. pubkey: ${color(oracle.toBase58(), "blue")}`)
|
||||
log(`added oracle. pubkey: ${color(oracle.publicKey.toBase58(), "blue")}`)
|
||||
})
|
||||
|
||||
cli
|
||||
.command("remove-oracle")
|
||||
.option("--index <number>", "remove oracle from index (0-20)")
|
||||
.option("--feedAddress <string>", "feed to remove oracle from")
|
||||
.action(async (opts) => {
|
||||
const { index, feedAddress } = opts
|
||||
|
||||
const { aggregator } = await AppContext.forAdmin()
|
||||
|
||||
await aggregator.removeOracle({
|
||||
aggregator: new PublicKey(feedAddress),
|
||||
index,
|
||||
})
|
||||
})
|
||||
|
||||
// cli
|
||||
|
@ -225,43 +250,20 @@ cli
|
|||
// log(deployed.oracles)
|
||||
// })
|
||||
|
||||
// cli
|
||||
// .command("aggregatorInfo")
|
||||
// .description("show aggregatorInfo")
|
||||
// .action(async () => {
|
||||
// // show current network
|
||||
// showNetwork()
|
||||
cli
|
||||
.command("feed-poll")
|
||||
.description("poll current feed value")
|
||||
.option("--feedAddress <string>", "feed address to submit values to")
|
||||
.action(async (opts) => {
|
||||
const { feedAddress } = opts
|
||||
|
||||
// if (!fs.existsSync(deployedPath)) {
|
||||
// error("program haven't deployed yet")
|
||||
// }
|
||||
while (true) {
|
||||
const feedInfo = await conn.getAccountInfo(new PublicKey(feedAddress))
|
||||
log(decodeAggregatorInfo(feedInfo))
|
||||
|
||||
// const deployed = JSON.parse(fs.readFileSync(deployedPath).toString())
|
||||
|
||||
// if (deployed.network != network) {
|
||||
// error("deployed network not match, please try `npm run clean:deployed`, and deploy again")
|
||||
// }
|
||||
|
||||
// if (!deployed.programId) {
|
||||
// error("program haven't deployed yet")
|
||||
// }
|
||||
|
||||
// const inputs = await inquirer
|
||||
// .prompt([
|
||||
// {
|
||||
// message: "Choose an aggregator", type: "list", name: "aggregator", choices: () => {
|
||||
// return deployed.pairs.map(p => ({ name: p.pairName.trim() + ` [${p.aggregator}]`, value: p.aggregator }))
|
||||
// }
|
||||
// },
|
||||
// ])
|
||||
|
||||
// const { aggregator } = inputs
|
||||
// const conn = await connectTo(network)
|
||||
|
||||
// const accountInfo = await conn.getAccountInfo(new PublicKey(aggregator))
|
||||
|
||||
// log(decodeAggregatorInfo(accountInfo))
|
||||
// })
|
||||
await sleep(1000)
|
||||
}
|
||||
})
|
||||
|
||||
cli
|
||||
.command("feed")
|
||||
|
@ -270,7 +272,7 @@ cli
|
|||
.option("--oracleAddress <string>", "feed address to submit values to")
|
||||
.action(async (opts) => {
|
||||
|
||||
const { wallet, aggregatorProgram } = await AppContext.forOracle()
|
||||
const { wallet, aggregatorProgramAccount: aggregatorProgram } = await AppContext.forOracle()
|
||||
|
||||
const { feedAddress, oracleAddress } = opts
|
||||
|
||||
|
@ -289,7 +291,7 @@ cli
|
|||
.description("create test token")
|
||||
.option("--amount <number>", "amount of the test token")
|
||||
.action(async (opts) => {
|
||||
const { admin, aggregatorProgram, deployer } = await AdminContext.load()
|
||||
const { wallet, aggregatorProgramAccount: aggregatorProgram, deployer } = await AppContext.forAdmin()
|
||||
|
||||
const { amount } = opts
|
||||
|
||||
|
@ -297,18 +299,18 @@ cli
|
|||
error("invalid amount")
|
||||
}
|
||||
|
||||
const spltoken = new SPLToken(admin)
|
||||
|
||||
const spltoken = new SPLToken(wallet)
|
||||
|
||||
log(`create test token...`)
|
||||
// 1. create token
|
||||
const token = await spltoken.initializeMint({
|
||||
mintAuthority: admin.account.publicKey,
|
||||
mintAuthority: wallet.account.publicKey,
|
||||
decimals: 8,
|
||||
})
|
||||
|
||||
// 2. create tokenOwner (program account)
|
||||
const tokenOwner = await ProgramAccount.forSeed(
|
||||
Buffer.from(token.publicKey.toBuffer()).slice(0, 30),
|
||||
Buffer.from(token.publicKey.toBuffer()).slice(0, 30),
|
||||
aggregatorProgram.publicKey
|
||||
)
|
||||
|
||||
|
@ -325,7 +327,7 @@ cli
|
|||
token: token.publicKey,
|
||||
to: tokenAccount.publicKey,
|
||||
amount: BigInt(amount),
|
||||
authority: admin.account,
|
||||
authority: wallet.account,
|
||||
})
|
||||
|
||||
log({
|
||||
|
|
36
src/utils.ts
36
src/utils.ts
|
@ -4,7 +4,7 @@ import { AggregatorLayout, SubmissionLayout, OracleLayout } from "./FluxAggregat
|
|||
|
||||
import { solana, Wallet, NetworkName, Deployer } from "solray"
|
||||
|
||||
export function getSubmissionValue(submissions: []): number {
|
||||
export function getMedian(submissions: number[]): number {
|
||||
const values = submissions
|
||||
.filter((s: any) => s.value != 0)
|
||||
.map((s: any) => s.value)
|
||||
|
@ -31,47 +31,45 @@ export function sleep(ms: number): Promise<void> {
|
|||
}
|
||||
|
||||
export function decodeAggregatorInfo(accountInfo) {
|
||||
|
||||
const data = Buffer.from(accountInfo.data)
|
||||
const aggregator = AggregatorLayout.decode(data)
|
||||
|
||||
const minSubmissionValue = aggregator.minSubmissionValue.readBigUInt64LE().toString()
|
||||
const maxSubmissionValue = aggregator.maxSubmissionValue.readBigUInt64LE().toString()
|
||||
const minSubmissionValue = aggregator.minSubmissionValue.readBigUInt64LE()
|
||||
const maxSubmissionValue = aggregator.maxSubmissionValue.readBigUInt64LE()
|
||||
const submitInterval = aggregator.submitInterval.readInt32LE()
|
||||
const description = aggregator.description.toString()
|
||||
const description = (aggregator.description.toString() as String).trim()
|
||||
|
||||
// decode oracles
|
||||
let submissions: [] = []
|
||||
let oracles: [] = []
|
||||
let submissions: any[] = []
|
||||
let submissionSpace = SubmissionLayout.span
|
||||
let updateTime = '0'
|
||||
let latestUpdateTime = BigInt(0)
|
||||
|
||||
for (let i = 0; i < aggregator.submissions.length / submissionSpace; i++) {
|
||||
let submission = SubmissionLayout.decode(
|
||||
aggregator.submissions.slice(i*submissionSpace, (i+1)*submissionSpace)
|
||||
)
|
||||
|
||||
submission.oracle = new PublicKey(submission.oracle)
|
||||
submission.time = submission.time.readBigInt64LE()
|
||||
submission.value = submission.value.readBigInt64LE()
|
||||
|
||||
submission.time = submission.time.readBigInt64LE().toString()
|
||||
submission.value = submission.value.readBigInt64LE().toString()*1
|
||||
if (!submission.oracle.equals(new PublicKey(0))) {
|
||||
submissions.push(submission as never)
|
||||
oracles.push(submission.oracle.toBase58() as never)
|
||||
|
||||
submissions.push(submission)
|
||||
}
|
||||
if (submission.time > updateTime) {
|
||||
updateTime = submission.time
|
||||
|
||||
if (submission.time > latestUpdateTime) {
|
||||
latestUpdateTime = submission.time
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
minSubmissionValue: minSubmissionValue,
|
||||
maxSubmissionValue: maxSubmissionValue,
|
||||
submissionValue: getSubmissionValue(submissions),
|
||||
submissionValue: getMedian(submissions),
|
||||
submitInterval,
|
||||
description,
|
||||
oracles,
|
||||
updateTime,
|
||||
oracles: submissions.map(s => s.oracle.toString()),
|
||||
latestUpdateTime: new Date(Number(latestUpdateTime)*1000),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,7 +100,7 @@ export async function walletFromEnv(key: string, conn: Connection): Promise<Wall
|
|||
|
||||
export async function openDeployer(): Promise<Deployer> {
|
||||
const deployFile = process.env.DEPLOY_FILE
|
||||
|
||||
|
||||
if (!deployFile) {
|
||||
throw new Error(`Set DEPLOY_FILE in .env`)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"incremental": true, /* Enable incremental compilation */
|
||||
"target": "ES2019", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
|
@ -67,8 +67,5 @@
|
|||
/* Advanced Options */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue