2020-12-08 18:39:39 -08:00
|
|
|
import { Command, option } from "commander"
|
|
|
|
|
|
|
|
import fs from "fs"
|
|
|
|
import path from "path"
|
|
|
|
|
2020-12-10 01:20:07 -08:00
|
|
|
import {
|
|
|
|
BPFLoader, PublicKey, Wallet, NetworkName,
|
|
|
|
solana, Deployer, SPLToken, ProgramAccount
|
|
|
|
} from "solray"
|
2020-12-08 23:52:28 -08:00
|
|
|
|
|
|
|
import dotenv from "dotenv"
|
|
|
|
|
|
|
|
import FluxAggregator, { AggregatorLayout, OracleLayout } from "./FluxAggregator"
|
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
import {
|
|
|
|
decodeAggregatorInfo,
|
|
|
|
walletFromEnv,
|
|
|
|
openDeployer,
|
|
|
|
} from "./utils"
|
2020-12-08 23:52:28 -08:00
|
|
|
|
|
|
|
import * as feed from "./feed"
|
|
|
|
|
|
|
|
dotenv.config()
|
|
|
|
|
2020-12-08 18:39:39 -08:00
|
|
|
const cli = new Command()
|
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
const FLUX_AGGREGATOR_SO = path.resolve(__dirname, "../build/flux_aggregator.so")
|
|
|
|
const network = (process.env.NETWORK || "local") as NetworkName
|
|
|
|
const conn = solana.connect(network)
|
2020-12-08 18:39:39 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
class AdminContext {
|
2020-12-08 18:39:39 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
static readonly AGGREGATOR_PROGRAM = "aggregatorProgram"
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
static async load() {
|
|
|
|
const deployer = await openDeployer()
|
|
|
|
const admin = await walletFromEnv("ADMIN_MNEMONIC", conn)
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
return new AdminContext(deployer, admin)
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor(public deployer: Deployer, public admin: Wallet) {}
|
|
|
|
|
|
|
|
get aggregatorProgram() {
|
|
|
|
const program = this.deployer.account(AdminContext.AGGREGATOR_PROGRAM)
|
|
|
|
|
|
|
|
if (program == null) {
|
|
|
|
throw new Error(`flux aggregator program is not yet deployed`)
|
|
|
|
}
|
|
|
|
|
|
|
|
return program
|
|
|
|
}
|
|
|
|
}
|
2020-12-08 18:39:39 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
class OracleContext {
|
|
|
|
|
|
|
|
static readonly AGGREGATOR_PROGRAM = "aggregatorProgram"
|
|
|
|
|
|
|
|
static async load() {
|
|
|
|
const deployer = await openDeployer()
|
|
|
|
const wallet = await walletFromEnv("ORACLE_MNEMONIC", conn)
|
|
|
|
|
|
|
|
return new OracleContext(deployer, wallet)
|
2020-12-08 18:39:39 -08:00
|
|
|
}
|
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
constructor(public deployer: Deployer, public wallet: Wallet) {}
|
|
|
|
|
|
|
|
get aggregatorProgram() {
|
|
|
|
const program = this.deployer.account(AdminContext.AGGREGATOR_PROGRAM)
|
2020-12-08 18:39:39 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
if (program == null) {
|
|
|
|
throw new Error(`flux aggregator program is not yet deployed`)
|
|
|
|
}
|
|
|
|
|
|
|
|
return program
|
2020-12-08 18:39:39 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
function color(s, c = "black", b = false): string {
|
|
|
|
// 30m Black, 31m Red, 32m Green, 33m Yellow, 34m Blue, 35m Magenta, 36m Cyanic, 37m White
|
2020-12-08 18:39:39 -08:00
|
|
|
const cArr = ["black", "red", "green", "yellow", "blue", "megenta", "cyanic", "white"]
|
2020-12-09 03:41:15 -08:00
|
|
|
|
2020-12-08 18:39:39 -08:00
|
|
|
let cIdx = cArr.indexOf(c)
|
|
|
|
let bold = b ? "\x1b[1m" : ""
|
|
|
|
|
|
|
|
return `\x1b[${30 + (cIdx > -1 ? cIdx : 0)}m${bold}${s}\x1b[0m`
|
|
|
|
}
|
|
|
|
|
2020-12-08 23:52:28 -08:00
|
|
|
function error(message: string) {
|
|
|
|
console.log("\n")
|
|
|
|
console.error(color(message, "red"))
|
|
|
|
console.log("\n")
|
|
|
|
process.exit()
|
|
|
|
}
|
|
|
|
|
|
|
|
function log(message: any) {
|
|
|
|
console.log(message)
|
|
|
|
}
|
|
|
|
|
2020-12-08 18:39:39 -08:00
|
|
|
cli
|
2020-12-09 03:41:15 -08:00
|
|
|
.command("generate-wallet").action(async () => {
|
|
|
|
const mnemonic = Wallet.generateMnemonic()
|
|
|
|
const wallet = await Wallet.fromMnemonic(mnemonic, conn)
|
2020-12-08 18:39:39 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
log(`address: ${wallet.address}`)
|
|
|
|
log(`mnemonic: ${mnemonic}`)
|
2020-12-08 18:39:39 -08:00
|
|
|
})
|
|
|
|
|
|
|
|
cli
|
2020-12-09 03:41:15 -08:00
|
|
|
.command("airdrop <address>")
|
|
|
|
.description(`request airdrop to the address`)
|
|
|
|
.option("-m, --amount <amount>", "request amount in sol (10e9)", "10")
|
|
|
|
.action(async (address, opts) => {
|
|
|
|
const dest = new PublicKey(address)
|
2020-12-08 18:39:39 -08:00
|
|
|
|
2020-12-08 23:52:28 -08:00
|
|
|
const { amount } = opts
|
2020-12-08 18:39:39 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
log(`requesting 10 sol airdrop to: ${address}`)
|
|
|
|
await conn.requestAirdrop(dest, amount * 1e9)
|
|
|
|
log("airdrop success")
|
2020-12-08 18:39:39 -08:00
|
|
|
})
|
|
|
|
|
|
|
|
cli
|
2020-12-09 03:41:15 -08:00
|
|
|
.command("deploy-program")
|
|
|
|
.description("deploy the aggregator program")
|
|
|
|
.action(async () => {
|
|
|
|
const { admin, deployer } = await AdminContext.load()
|
2020-12-08 18:39:39 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
const programAccount = await deployer.ensure(AdminContext.AGGREGATOR_PROGRAM, async () => {
|
|
|
|
const programBinary = fs.readFileSync(FLUX_AGGREGATOR_SO)
|
2020-12-08 18:39:39 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
log(`deploying ${FLUX_AGGREGATOR_SO}...`)
|
|
|
|
const bpfLoader = new BPFLoader(admin)
|
2020-12-08 18:39:39 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
return bpfLoader.load(programBinary)
|
|
|
|
})
|
2020-12-08 18:39:39 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
log(`deployed aggregator program. program id: ${color(programAccount.publicKey.toBase58(), "blue")}`)
|
2020-12-08 18:39:39 -08:00
|
|
|
})
|
|
|
|
|
|
|
|
cli
|
2020-12-08 23:52:28 -08:00
|
|
|
.command("add-aggregator")
|
2020-12-09 03:41:15 -08:00
|
|
|
.description("create an aggregator")
|
|
|
|
.option("--feedName <string>", "feed pair name")
|
|
|
|
.option("--submitInterval <number>", "min wait time between submissions", "6")
|
|
|
|
.option("--minSubmissionValue <number>", "minSubmissionValue", "0")
|
|
|
|
.option("--maxSubmissionValue <number>", "maxSubmissionValue", "18446744073709551615")
|
|
|
|
.action(async (opts) => {
|
|
|
|
const { deployer, admin, aggregatorProgram } = await AdminContext.load()
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
const { feedName, submitInterval, minSubmissionValue, maxSubmissionValue } = opts
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
const aggregator = new FluxAggregator(admin, aggregatorProgram.publicKey)
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
const feed = await deployer.ensure(feedName, async () => {
|
|
|
|
return aggregator.initialize({
|
|
|
|
submitInterval: parseInt(submitInterval),
|
|
|
|
minSubmissionValue: BigInt(minSubmissionValue),
|
|
|
|
maxSubmissionValue: BigInt(maxSubmissionValue),
|
|
|
|
description: feedName.substr(0, 32).padEnd(32),
|
|
|
|
owner: admin.account
|
|
|
|
})
|
2020-12-08 23:52:28 -08:00
|
|
|
})
|
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
log(`feed initialized, pubkey: ${color(feed.publicKey.toBase58(), "blue")}`)
|
2020-12-08 23:52:28 -08:00
|
|
|
})
|
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
// cli
|
|
|
|
// .command("aggregators")
|
|
|
|
// .description("show all aggregators")
|
|
|
|
// .action(() => {
|
|
|
|
// // show current network
|
|
|
|
// showNetwork()
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
// if (!fs.existsSync(deployedPath)) {
|
|
|
|
// error("program haven't deployed yet")
|
|
|
|
// }
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
// const deployed = JSON.parse(fs.readFileSync(deployedPath).toString())
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
// if (deployed.network != network) {
|
|
|
|
// error("deployed network not match, please try `npm run clean:deployed`, and deploy again")
|
|
|
|
// }
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
// if (!deployed.programId) {
|
|
|
|
// error("program haven't deployed yet")
|
|
|
|
// }
|
|
|
|
|
|
|
|
// log(deployed.pairs)
|
|
|
|
// })
|
2020-12-08 23:52:28 -08:00
|
|
|
|
|
|
|
cli
|
|
|
|
.command("add-oracle")
|
|
|
|
.description("add an oracle to aggregator")
|
2020-12-09 07:38:31 -08:00
|
|
|
.option("--index <number>", "add to index (0-20)")
|
2020-12-09 03:41:15 -08:00
|
|
|
.option("--feedAddress <string>", "feed address")
|
|
|
|
.option("--oracleName <string>", "oracle name")
|
|
|
|
.option("--oracleOwner <string>", "oracle owner address")
|
|
|
|
.action(async (opts) => {
|
2020-12-09 07:38:31 -08:00
|
|
|
const { admin, aggregatorProgram } = await AdminContext.load()
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 07:38:31 -08:00
|
|
|
const { index, oracleName, oracleOwner, feedAddress } = opts
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 07:38:31 -08:00
|
|
|
if (!index || index < 0 || index > 21) {
|
|
|
|
error("invalid index (0-20)")
|
|
|
|
}
|
2020-12-09 03:41:15 -08:00
|
|
|
const program = new FluxAggregator(admin, aggregatorProgram.publicKey)
|
2020-12-08 23:52:28 -08:00
|
|
|
|
|
|
|
log("add oracle...")
|
|
|
|
const oracle = await program.addOracle({
|
2020-12-09 07:38:31 -08:00
|
|
|
index,
|
2020-12-09 03:41:15 -08:00
|
|
|
owner: new PublicKey(oracleOwner),
|
|
|
|
description: oracleName.substr(0, 32).padEnd(32),
|
|
|
|
aggregator: new PublicKey(feedAddress),
|
|
|
|
aggregatorOwner: admin.account,
|
2020-12-08 23:52:28 -08:00
|
|
|
})
|
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
log(`added oracle. pubkey: ${color(oracle.toBase58(), "blue")}`)
|
2020-12-08 23:52:28 -08:00
|
|
|
})
|
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
// cli
|
|
|
|
// .command("oracles")
|
|
|
|
// .description("show all oracles")
|
|
|
|
// .action(() => {
|
|
|
|
// // show current network
|
|
|
|
// showNetwork()
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
// if (!fs.existsSync(deployedPath)) {
|
|
|
|
// error("program haven't deployed yet")
|
|
|
|
// }
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
// const deployed = JSON.parse(fs.readFileSync(deployedPath).toString())
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
// if (deployed.network != network) {
|
|
|
|
// error("deployed network not match, please try `npm run clean:deployed`, and deploy again")
|
|
|
|
// }
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
// if (!deployed.programId) {
|
|
|
|
// error("program haven't deployed yet")
|
|
|
|
// }
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
// log(deployed.oracles)
|
|
|
|
// })
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
// cli
|
|
|
|
// .command("aggregatorInfo")
|
|
|
|
// .description("show aggregatorInfo")
|
|
|
|
// .action(async () => {
|
|
|
|
// // show current network
|
|
|
|
// showNetwork()
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
// if (!fs.existsSync(deployedPath)) {
|
|
|
|
// error("program haven't deployed yet")
|
|
|
|
// }
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
// const deployed = JSON.parse(fs.readFileSync(deployedPath).toString())
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
// if (deployed.network != network) {
|
|
|
|
// error("deployed network not match, please try `npm run clean:deployed`, and deploy again")
|
|
|
|
// }
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
// if (!deployed.programId) {
|
|
|
|
// error("program haven't deployed yet")
|
|
|
|
// }
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
// 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 }))
|
|
|
|
// }
|
|
|
|
// },
|
|
|
|
// ])
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
// const { aggregator } = inputs
|
|
|
|
// const conn = await connectTo(network)
|
|
|
|
|
|
|
|
// const accountInfo = await conn.getAccountInfo(new PublicKey(aggregator))
|
|
|
|
|
|
|
|
// log(decodeAggregatorInfo(accountInfo))
|
|
|
|
// })
|
2020-12-08 23:52:28 -08:00
|
|
|
|
|
|
|
cli
|
|
|
|
.command("feed")
|
|
|
|
.description("oracle feeds to aggregator")
|
2020-12-09 03:41:15 -08:00
|
|
|
.option("--feedAddress <string>", "feed address to submit values to")
|
|
|
|
.option("--oracleAddress <string>", "feed address to submit values to")
|
|
|
|
.action(async (opts) => {
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
const { wallet, aggregatorProgram } = await OracleContext.load()
|
2020-12-08 23:52:28 -08:00
|
|
|
|
2020-12-09 03:41:15 -08:00
|
|
|
const { feedAddress, oracleAddress } = opts
|
2020-12-08 23:52:28 -08:00
|
|
|
|
|
|
|
feed.start({
|
2020-12-09 03:41:15 -08:00
|
|
|
oracle: new PublicKey(oracleAddress),
|
|
|
|
oracleOwner: wallet.account,
|
|
|
|
feed: new PublicKey(feedAddress),
|
|
|
|
pairSymbol: "BTC-USD",
|
|
|
|
payerWallet: wallet,
|
|
|
|
programId: aggregatorProgram.publicKey,
|
2020-12-08 23:52:28 -08:00
|
|
|
})
|
2020-12-08 18:39:39 -08:00
|
|
|
})
|
|
|
|
|
2020-12-10 01:20:07 -08:00
|
|
|
cli
|
|
|
|
.command("testToken")
|
|
|
|
.description("create test token")
|
|
|
|
.option("--amount <number>", "amount of the test token")
|
|
|
|
.action(async (opts) => {
|
|
|
|
const { admin, aggregatorProgram, deployer } = await AdminContext.load()
|
|
|
|
|
|
|
|
const { amount } = opts
|
|
|
|
|
|
|
|
if (!amount || amount < 0) {
|
|
|
|
error("invalid amount")
|
|
|
|
}
|
|
|
|
|
|
|
|
const spltoken = new SPLToken(admin)
|
|
|
|
|
|
|
|
log(`create test token...`)
|
|
|
|
// 1. create token
|
|
|
|
const token = await spltoken.initializeMint({
|
|
|
|
mintAuthority: admin.account.publicKey,
|
|
|
|
decimals: 8,
|
|
|
|
})
|
|
|
|
|
|
|
|
// 2. create tokenOwner (program account)
|
|
|
|
const tokenOwner = await ProgramAccount.forSeed(
|
|
|
|
Buffer.from(token.publicKey.toBuffer()).slice(0, 30),
|
|
|
|
aggregatorProgram.publicKey
|
|
|
|
)
|
|
|
|
|
|
|
|
log(`create token acount...`)
|
|
|
|
// 3. create token account
|
|
|
|
const tokenAccount = await spltoken.initializeAccount({
|
|
|
|
token: token.publicKey,
|
|
|
|
owner: tokenOwner.pubkey
|
|
|
|
})
|
|
|
|
|
|
|
|
log(`mint ${amount} token to token account...`)
|
|
|
|
// 4. and then, mint tokens to that account
|
|
|
|
await spltoken.mintTo({
|
|
|
|
token: token.publicKey,
|
|
|
|
to: tokenAccount.publicKey,
|
|
|
|
amount: BigInt(amount),
|
|
|
|
authority: admin.account,
|
|
|
|
})
|
|
|
|
|
|
|
|
log({
|
|
|
|
token: token.publicKey.toBase58(),
|
|
|
|
tokenAccount: tokenAccount.publicKey.toBase58(),
|
|
|
|
tokenOwner: {
|
|
|
|
address: tokenOwner.address,
|
|
|
|
seed: tokenOwner.noncedSeed.toString("hex"),
|
|
|
|
nonce: tokenOwner.nonce,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
2020-12-08 18:39:39 -08:00
|
|
|
cli.parse(process.argv)
|