solana-flux-aggregator/src/cli.ts

361 lines
9.8 KiB
TypeScript
Raw Normal View History

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")
.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) => {
const { admin, aggregatorProgram } = await AdminContext.load()
2020-12-08 23:52:28 -08:00
const { index, oracleName, oracleOwner, feedAddress } = opts
2020-12-08 23:52:28 -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({
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)