346 lines
9.4 KiB
TypeScript
346 lines
9.4 KiB
TypeScript
import { Command, option } from "commander"
|
|
|
|
import fs from "fs"
|
|
import path from "path"
|
|
|
|
import {
|
|
BPFLoader, PublicKey, Wallet, NetworkName,
|
|
solana, Deployer, SPLToken, ProgramAccount
|
|
} from "solray"
|
|
|
|
import dotenv from "dotenv"
|
|
|
|
import FluxAggregator, { AggregatorLayout, OracleLayout } from "./FluxAggregator"
|
|
|
|
import {
|
|
decodeAggregatorInfo,
|
|
walletFromEnv,
|
|
openDeployer,
|
|
sleep,
|
|
} from "./utils"
|
|
|
|
import * as feed from "./feed"
|
|
|
|
dotenv.config()
|
|
|
|
const cli = new Command()
|
|
|
|
const FLUX_AGGREGATOR_SO = path.resolve(__dirname, "../build/flux_aggregator.so")
|
|
const network = (process.env.NETWORK || "local") as NetworkName
|
|
const conn = solana.connect(network)
|
|
|
|
class AppContext {
|
|
|
|
static readonly AGGREGATOR_PROGRAM = "aggregatorProgram"
|
|
|
|
static async forAdmin() {
|
|
const deployer = await openDeployer()
|
|
const admin = await walletFromEnv("ADMIN_MNEMONIC", conn)
|
|
|
|
return new AppContext(deployer, admin)
|
|
}
|
|
|
|
static async forOracle() {
|
|
const deployer = await openDeployer()
|
|
const wallet = await walletFromEnv("ORACLE_MNEMONIC", conn)
|
|
|
|
return new AppContext(deployer, wallet)
|
|
}
|
|
|
|
constructor(public deployer: Deployer, public wallet: Wallet) { }
|
|
|
|
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) {
|
|
throw new Error(`flux aggregator program is not yet deployed`)
|
|
}
|
|
|
|
return program
|
|
}
|
|
}
|
|
|
|
function color(s, c = "black", b = false): string {
|
|
// 30m Black, 31m Red, 32m Green, 33m Yellow, 34m Blue, 35m Magenta, 36m Cyanic, 37m White
|
|
const cArr = ["black", "red", "green", "yellow", "blue", "megenta", "cyanic", "white"]
|
|
|
|
let cIdx = cArr.indexOf(c)
|
|
let bold = b ? "\x1b[1m" : ""
|
|
|
|
return `\x1b[${30 + (cIdx > -1 ? cIdx : 0)}m${bold}${s}\x1b[0m`
|
|
}
|
|
|
|
function error(message: string) {
|
|
console.log("\n")
|
|
console.error(color(message, "red"))
|
|
console.log("\n")
|
|
process.exit()
|
|
}
|
|
|
|
function log(message: any) {
|
|
console.log(message)
|
|
}
|
|
|
|
cli
|
|
.command("generate-wallet").action(async () => {
|
|
const mnemonic = Wallet.generateMnemonic()
|
|
const wallet = await Wallet.fromMnemonic(mnemonic, conn)
|
|
|
|
log(`address: ${wallet.address}`)
|
|
log(`mnemonic: ${mnemonic}`)
|
|
})
|
|
|
|
cli
|
|
.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)
|
|
|
|
const { amount } = opts
|
|
|
|
log(`requesting 10 sol airdrop to: ${address}`)
|
|
await conn.requestAirdrop(dest, amount * 1e9)
|
|
log("airdrop success")
|
|
})
|
|
|
|
cli
|
|
.command("deploy-program")
|
|
.description("deploy the aggregator program")
|
|
.action(async () => {
|
|
const { wallet, deployer } = await AppContext.forAdmin()
|
|
|
|
const programAccount = await deployer.ensure(AppContext.AGGREGATOR_PROGRAM, async () => {
|
|
const programBinary = fs.readFileSync(FLUX_AGGREGATOR_SO)
|
|
|
|
log(`deploying ${FLUX_AGGREGATOR_SO}...`)
|
|
const bpfLoader = new BPFLoader(wallet)
|
|
|
|
return bpfLoader.load(programBinary)
|
|
})
|
|
|
|
log(`deployed aggregator program. program id: ${color(programAccount.publicKey.toBase58(), "blue")}`)
|
|
})
|
|
|
|
cli
|
|
.command("add-aggregator")
|
|
.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, wallet, aggregatorProgramAccount: aggregatorProgram } = await AppContext.forAdmin()
|
|
|
|
const { feedName, submitInterval, minSubmissionValue, maxSubmissionValue } = opts
|
|
|
|
const aggregator = new FluxAggregator(wallet, aggregatorProgram.publicKey)
|
|
|
|
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: wallet.account
|
|
})
|
|
})
|
|
|
|
log(`feed initialized, pubkey: ${color(feed.publicKey.toBase58(), "blue")}`)
|
|
})
|
|
|
|
// cli
|
|
// .command("aggregators")
|
|
// .description("show all aggregators")
|
|
// .action(() => {
|
|
// // show current network
|
|
// showNetwork()
|
|
|
|
// if (!fs.existsSync(deployedPath)) {
|
|
// error("program haven't deployed yet")
|
|
// }
|
|
|
|
// 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")
|
|
// }
|
|
|
|
// log(deployed.pairs)
|
|
// })
|
|
|
|
cli
|
|
.command("add-oracle")
|
|
.description("add an oracle to aggregator")
|
|
.option("--index <number>", "add to index (0-20)")
|
|
.option("--feedAddress <string>", "feed address")
|
|
.option("--oracleName <string>", "oracle name")
|
|
.option("--oracleOwner <string>", "oracle owner address")
|
|
.action(async (opts) => {
|
|
const { wallet, aggregator, deployer } = await AppContext.forAdmin()
|
|
|
|
const { index, oracleName, oracleOwner, feedAddress } = opts
|
|
|
|
if (!index || index < 0 || index > 21) {
|
|
error("invalid index. requires (0-20)")
|
|
}
|
|
|
|
log("add oracle...")
|
|
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.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
|
|
// .command("oracles")
|
|
// .description("show all oracles")
|
|
// .action(() => {
|
|
// // show current network
|
|
// showNetwork()
|
|
|
|
// if (!fs.existsSync(deployedPath)) {
|
|
// error("program haven't deployed yet")
|
|
// }
|
|
|
|
// 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")
|
|
// }
|
|
|
|
// log(deployed.oracles)
|
|
// })
|
|
|
|
cli
|
|
.command("feed-poll")
|
|
.description("poll current feed value")
|
|
.option("--feedAddress <string>", "feed address to submit values to")
|
|
.action(async (opts) => {
|
|
const { feedAddress } = opts
|
|
|
|
while (true) {
|
|
const feedInfo = await conn.getAccountInfo(new PublicKey(feedAddress))
|
|
log(decodeAggregatorInfo(feedInfo))
|
|
|
|
await sleep(1000)
|
|
}
|
|
})
|
|
|
|
cli
|
|
.command("feed")
|
|
.description("oracle feeds to aggregator")
|
|
.option("--feedAddress <string>", "feed address to submit values to")
|
|
.option("--oracleAddress <string>", "feed address to submit values to")
|
|
.action(async (opts) => {
|
|
|
|
const { wallet, aggregatorProgramAccount: aggregatorProgram } = await AppContext.forOracle()
|
|
|
|
const { feedAddress, oracleAddress } = opts
|
|
|
|
feed.start({
|
|
oracle: new PublicKey(oracleAddress),
|
|
oracleOwner: wallet.account,
|
|
feed: new PublicKey(feedAddress),
|
|
pairSymbol: "BTC-USD",
|
|
payerWallet: wallet,
|
|
programId: aggregatorProgram.publicKey,
|
|
})
|
|
})
|
|
|
|
cli
|
|
.command("testToken")
|
|
.description("create test token")
|
|
.option("--amount <number>", "amount of the test token")
|
|
.action(async (opts) => {
|
|
const { wallet, aggregatorProgramAccount: aggregatorProgram, deployer } = await AppContext.forAdmin()
|
|
|
|
const { amount } = opts
|
|
|
|
if (!amount || amount < 0) {
|
|
error("invalid amount")
|
|
}
|
|
|
|
const spltoken = new SPLToken(wallet)
|
|
|
|
log(`create test token...`)
|
|
// 1. create token
|
|
const token = await spltoken.initializeMint({
|
|
mintAuthority: wallet.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: wallet.account,
|
|
})
|
|
|
|
log({
|
|
token: token.publicKey.toBase58(),
|
|
tokenAccount: tokenAccount.publicKey.toBase58(),
|
|
tokenOwner: {
|
|
address: tokenOwner.address,
|
|
seed: tokenOwner.noncedSeed.toString("hex"),
|
|
nonce: tokenOwner.nonce,
|
|
}
|
|
})
|
|
|
|
})
|
|
|
|
|
|
cli.parse(process.argv) |