refactor cli
This commit is contained in:
parent
7749dff9ea
commit
faa0f8e8c6
5
.env
5
.env
|
@ -1 +1,4 @@
|
|||
NETWORK=local
|
||||
NETWORK=dev
|
||||
DEPLOY_FILE=deploy.json
|
||||
ADMIN_MNEMONIC="wine vault fancy enhance trade dolphin hard traffic social butter client pave"
|
||||
ORACLE_MNEMONIC="amount smoke bar coil current trial toward minimum model pass moral liberty"
|
92
README.md
92
README.md
|
@ -1,44 +1,96 @@
|
|||
# solana-flux-aggregator
|
||||
|
||||
Solnana Flux Aggregator
|
||||
|
||||
## Install
|
||||
|
||||
`npm install`
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
`sudo npm run build:program`
|
||||
## Admin Wallet Setup
|
||||
|
||||
## Create Payer
|
||||
Setup a wallet for the flux aggregator admin:
|
||||
|
||||
`npm run create:payer`
|
||||
```
|
||||
yarn generate-wallet
|
||||
|
||||
request some airdrop:
|
||||
address: 7YMUUCzZir7AAuoy4CtZih9JFBqYwtQiCxjA5dtqwRxU
|
||||
mnemonic: wine vault fancy enhance trade dolphin hard traffic social butter client pave
|
||||
```
|
||||
|
||||
`npm run airdrop:payer`
|
||||
```
|
||||
yarn solink airdrop 7YMUUCzZir7AAuoy4CtZih9JFBqYwtQiCxjA5dtqwRxU
|
||||
```
|
||||
|
||||
## Deploy
|
||||
Create `.env` configuration file for the deploy script.
|
||||
|
||||
`npm run deploy`
|
||||
```
|
||||
NETWORK=dev
|
||||
DEPLOY_FILE=deploy.json
|
||||
ADMIN_MNEMONIC="wine vault fancy enhance trade dolphin hard traffic social butter client pave"
|
||||
```
|
||||
|
||||
## Create Aggregator Owner
|
||||
## Aggregator Setup
|
||||
|
||||
`npm run create:aggregatorOwner`
|
||||
Build and deploy the flux aggregator:
|
||||
|
||||
## Add An Aggregator
|
||||
```
|
||||
yarn build:program
|
||||
```
|
||||
|
||||
`npm run add:aggregator`
|
||||
```
|
||||
yarn solink deploy-program
|
||||
|
||||
## Create Oracle Owner
|
||||
deployed aggregator program. program id: 9KXbVqUrMgtti7Jx4rrV1NqXjQNxWaKgtYCEwJ8AESS5
|
||||
```
|
||||
|
||||
`npm run create:oracleOwner`
|
||||
Create the `btc:usd` feed (that accepts max and min u64 as valid submission values):
|
||||
|
||||
## Add An Oracle
|
||||
```
|
||||
yarn solink add-aggregator \
|
||||
--feedName btc:usd \
|
||||
--submitInterval 6 \
|
||||
--minSubmissionValue 0 \
|
||||
--maxSubmissionValue 18446744073709551615
|
||||
|
||||
`npm run add:oracle`
|
||||
feed initialized, pubkey: AUK9X6QLgauAUvEA3Ajc91fZytb9ccA7qVR72ErDFNg2
|
||||
```
|
||||
|
||||
## Show Aggregators/Oracles
|
||||
## Adding an oracle
|
||||
|
||||
`npm run show:aggregators|oracles`
|
||||
Next, we create a separate wallet to control oracles:
|
||||
|
||||
## Start to feed
|
||||
```
|
||||
yarn generate-wallet
|
||||
|
||||
`npm run feed`
|
||||
address: FosLwbttPgkEDv36VJLU3wwXcBSSoUGkh7dyZPsXNtT4
|
||||
mnemonic: amount smoke bar coil current trial toward minimum model pass moral liberty
|
||||
```
|
||||
|
||||
```
|
||||
yarn solink airdrop FosLwbttPgkEDv36VJLU3wwXcBSSoUGkh7dyZPsXNtT4
|
||||
```
|
||||
|
||||
Add this wallet to `.env`:
|
||||
|
||||
```
|
||||
ORACLE_MNEMONIC="amount smoke bar coil current trial toward minimum model pass moral liberty"
|
||||
```
|
||||
|
||||
Next we create a new oracle to the feed we've created previously, and set its owner to be the new oracle wallet that we've generated:
|
||||
|
||||
```
|
||||
yarn solink add-oracle \
|
||||
--feedAddress AUK9X6QLgauAUvEA3Ajc91fZytb9ccA7qVR72ErDFNg2 \
|
||||
--oracleName solink-test \
|
||||
--oracleOwner FosLwbttPgkEDv36VJLU3wwXcBSSoUGkh7dyZPsXNtT4
|
||||
|
||||
added oracle. pubkey: 4vH5L2jSNXGfcCx42N4sqPiMzEbp1PaQjQ6XngDBu8zR
|
||||
```
|
||||
|
||||
```
|
||||
yarn solink feed \
|
||||
--feedAddress AUK9X6QLgauAUvEA3Ajc91fZytb9ccA7qVR72ErDFNg2 \
|
||||
--oracleAddress 4vH5L2jSNXGfcCx42N4sqPiMzEbp1PaQjQ6XngDBu8zR
|
||||
```
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"aggregatorProgram": {
|
||||
"pubkey": "9KXbVqUrMgtti7Jx4rrV1NqXjQNxWaKgtYCEwJ8AESS5",
|
||||
"secret": "a2b92fa9514ac18a40c2bc895db0f97cabc1870205a74d84a0fb4a8167e6cba37b9c8b296cb261a7f3b17b294c72c4b6d427ab499b4ad6323b844289bcfebf6a"
|
||||
},
|
||||
"btc:usd": {
|
||||
"pubkey": "AUK9X6QLgauAUvEA3Ajc91fZytb9ccA7qVR72ErDFNg2",
|
||||
"secret": "233f1f8df9bf0e98c054f4b012eba361ec5050d93f7b06cdb0b2d0242cdf9a2e8cb862071d89cbf390570fa521c0daff5ca0e47f767dc32940801b449e50ad1b"
|
||||
}
|
||||
}
|
30
package.json
30
package.json
|
@ -5,39 +5,17 @@
|
|||
"main": "index.js",
|
||||
"testnetDefaultChannel": "v1.4.8",
|
||||
"scripts": {
|
||||
"deploy": "cd src && ts-node cli.ts deploy",
|
||||
"show:roles": "cd src && ts-node cli.ts roleinfo",
|
||||
"show:aggregators": "cd src && ts-node cli.ts aggregators",
|
||||
"show:aggregatorInfo": "cd src && ts-node cli.ts aggregatorInfo",
|
||||
"show:oracles": "cd src && ts-node cli.ts oracles",
|
||||
"create:payer": "cd src && ts-node cli.ts create payer",
|
||||
"create:aggregatorOwner": "cd src && ts-node cli.ts create aggregatorOwner",
|
||||
"create:oracleOwner": "cd src && ts-node cli.ts create oracleOwner",
|
||||
"remove:payer": "cd src && ts-node cli.ts remove payer",
|
||||
"remove:aggregatorOwner": "cd src && ts-node cli.ts remove aggregatorOwner",
|
||||
"remove:oracleOwner": "cd src && ts-node cli.ts remove oracleOwner",
|
||||
"airdrop:payer": "cd src && ts-node cli.ts airdrop payer -m 10000000000",
|
||||
"airdrop:aggregatorOwner": "cd src && ts-node cli.ts airdrop aggregatorOwner",
|
||||
"airdrop:oracleOwner": "cd src && ts-node cli.ts airdrop oracleOwner",
|
||||
"add:aggregator": "cd src && ts-node cli.ts add-aggregator",
|
||||
"add:oracle": "cd src && ts-node cli.ts add-oracle",
|
||||
"feed": "cd src && ts-node cli.ts feed",
|
||||
"localnet:update": "solana-localnet update",
|
||||
"localnet:up": "set -x; solana-localnet down; set -e; solana-localnet up",
|
||||
"localnet:down": "solana-localnet down",
|
||||
"clean:roles": "rm -rf src/wallets/*",
|
||||
"clean:deployed": "rm -rf src/deployed.json",
|
||||
"clean": "npm run clean:deployed && npm run clean:roles",
|
||||
"solink": "ts-node src/cli.ts",
|
||||
"build:program": "solray build program"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@solana/web3.js": "^0.87.1",
|
||||
"buffer-layout": "^1.2.0",
|
||||
"commander": "^6.2.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"inquirer": "^7.3.3",
|
||||
"solray": "git+https://github.com/czl1378/solray.git"
|
||||
"dotenv": "^8.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/recommended": "^1.0.1",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
PublicKey, BaseProgram, Account,
|
||||
Wallet, System, SPLToken,
|
||||
Wallet, System, SPLToken
|
||||
} from "solray";
|
||||
|
||||
import {
|
||||
|
@ -8,10 +8,14 @@ import {
|
|||
TransactionInstruction, SystemProgram
|
||||
} from "@solana/web3.js";
|
||||
|
||||
import { publicKey, u64LEBuffer, uint64 } from "solray/lib/util/encoding";
|
||||
import { publicKey, u64LEBuffer, uint64, BufferLayout } from "solray/lib/util/encoding";
|
||||
|
||||
import {
|
||||
decodeOracleInfo
|
||||
} from "./utils"
|
||||
|
||||
// @ts-ignore
|
||||
import BufferLayout from "buffer-layout";
|
||||
// import BufferLayout from "buffer-layout";
|
||||
|
||||
export const AggregatorLayout = BufferLayout.struct([
|
||||
BufferLayout.blob(4, "submitInterval"),
|
||||
|
@ -109,8 +113,9 @@ export default class FluxAggregator extends BaseProgram {
|
|||
this.sys = new System(this.wallet);
|
||||
}
|
||||
|
||||
public async initialize(params: InitializeParams): Promise<PublicKey> {
|
||||
public async initialize(params: InitializeParams): Promise<Account> {
|
||||
const account = new Account();
|
||||
|
||||
await this.sendTx([
|
||||
await this.sys.createRentFreeAccountInstruction({
|
||||
newPubicKey: account.publicKey,
|
||||
|
@ -123,11 +128,11 @@ export default class FluxAggregator extends BaseProgram {
|
|||
})
|
||||
], [this.account, account, params.owner]);
|
||||
|
||||
return account.publicKey;
|
||||
return account;
|
||||
}
|
||||
|
||||
private initializeInstruction(params: InitializeInstructionParams): TransactionInstruction {
|
||||
const {
|
||||
let {
|
||||
aggregator,
|
||||
description,
|
||||
submitInterval,
|
||||
|
@ -136,6 +141,9 @@ export default class FluxAggregator extends BaseProgram {
|
|||
owner,
|
||||
} = params;
|
||||
|
||||
// FIXME: hmm... should this throw error or what?
|
||||
description = description.substr(0, 32).toUpperCase().padEnd(32)
|
||||
|
||||
const layout = BufferLayout.struct([
|
||||
BufferLayout.u8("instruction"),
|
||||
BufferLayout.blob(4, "submitInterval"),
|
||||
|
@ -178,6 +186,11 @@ export default class FluxAggregator extends BaseProgram {
|
|||
return account.publicKey;
|
||||
}
|
||||
|
||||
public async oracleInfo(pubkey: PublicKey) {
|
||||
const info = await this.conn.getAccountInfo(pubkey)
|
||||
return decodeOracleInfo(info)
|
||||
}
|
||||
|
||||
private addOracleInstruction(params: AddOracleInstructionParams): TransactionInstruction {
|
||||
const {
|
||||
oracle,
|
||||
|
|
623
src/cli.ts
623
src/cli.ts
|
@ -1,17 +1,19 @@
|
|||
import { Command, option } from "commander"
|
||||
import inquirer from "inquirer"
|
||||
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
|
||||
import { Connection, PublicKey } from "@solana/web3.js"
|
||||
import { BPFLoader, PublicKey, Wallet, NetworkName, solana, Deployer } from "solray"
|
||||
|
||||
import { BPFLoader, Wallet, NetworkName} from "solray"
|
||||
import dotenv from "dotenv"
|
||||
|
||||
import FluxAggregator, { AggregatorLayout, OracleLayout } from "./FluxAggregator"
|
||||
|
||||
import { newWallet, calculatePayfees, connectTo, sleep, decodeAggregatorInfo } from "./utils"
|
||||
import {
|
||||
decodeAggregatorInfo,
|
||||
walletFromEnv,
|
||||
openDeployer,
|
||||
} from "./utils"
|
||||
|
||||
import * as feed from "./feed"
|
||||
|
||||
|
@ -19,31 +21,60 @@ dotenv.config()
|
|||
|
||||
const cli = new Command()
|
||||
|
||||
const roles = ["payer", "aggregatorOwner", "oracleOwner"]
|
||||
const FLUX_AGGREGATOR_SO = path.resolve(__dirname, "../build/flux_aggregator.so")
|
||||
const network = (process.env.NETWORK || "local") as NetworkName
|
||||
const conn = solana.connect(network)
|
||||
|
||||
const sofilePath = path.resolve(__dirname, "../build/flux_aggregator.so")
|
||||
class AdminContext {
|
||||
|
||||
const deployedPath = path.resolve(__dirname, "./deployed.json")
|
||||
static readonly AGGREGATOR_PROGRAM = "aggregatorProgram"
|
||||
|
||||
const { NETWORK } = process.env
|
||||
static async load() {
|
||||
const deployer = await openDeployer()
|
||||
const admin = await walletFromEnv("ADMIN_MNEMONIC", conn)
|
||||
|
||||
const network = (NETWORK || "local") as NetworkName
|
||||
|
||||
function checkRole(role) {
|
||||
if (roles.indexOf(role) < 0) {
|
||||
error("invalid role")
|
||||
return new AdminContext(deployer, admin)
|
||||
}
|
||||
|
||||
const walletPath = path.resolve(`./wallets/${role}.json`)
|
||||
constructor(public deployer: Deployer, public admin: Wallet) {}
|
||||
|
||||
return {
|
||||
exist: fs.existsSync(walletPath),
|
||||
walletPath
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
constructor(public deployer: Deployer, public wallet: 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
|
||||
}
|
||||
}
|
||||
|
||||
// 30m Black, 31m Red, 32m Green, 33m Yellow, 34m Blue, 35m Magenta, 36m Cyanic, 37m White
|
||||
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)
|
||||
|
@ -52,10 +83,6 @@ function color(s, c="black", b=false): string {
|
|||
return `\x1b[${30 + (cIdx > -1 ? cIdx : 0)}m${bold}${s}\x1b[0m`
|
||||
}
|
||||
|
||||
function showNetwork() {
|
||||
process.stdout.write(`${color(`Network: ${color(network, "blue")}`, "black", true)} \n\n`)
|
||||
}
|
||||
|
||||
function error(message: string) {
|
||||
console.log("\n")
|
||||
console.error(color(message, "red"))
|
||||
|
@ -67,500 +94,202 @@ function log(message: any) {
|
|||
console.log(message)
|
||||
}
|
||||
|
||||
async function showRoleInfo(role, conn: Connection): Promise<void> {
|
||||
|
||||
const res = checkRole(role)
|
||||
if (!res) return
|
||||
|
||||
if (!res.exist) {
|
||||
log(`role ${color(role, "red")} not created.`)
|
||||
return
|
||||
}
|
||||
|
||||
const fileData = fs.readFileSync(res.walletPath)
|
||||
const wallet = JSON.parse(fileData.toString())
|
||||
|
||||
log(color(`[${role}]`, "cyanic", true))
|
||||
log(`${color("public key: ", "blue")} ${wallet.pubkey}`)
|
||||
log(`${color("mnemonic: ", "blue")} ${wallet.mnemonic}`)
|
||||
process.stdout.write(`${color("balance: ", "blue")}...`)
|
||||
|
||||
const balance = await conn.getBalance(new PublicKey(wallet.pubkey))
|
||||
process.stdout.clearLine(-1)
|
||||
process.stdout.cursorTo(0)
|
||||
process.stdout.write(`${color("balance: ", "blue")}${balance} \n\n`)
|
||||
|
||||
}
|
||||
|
||||
cli
|
||||
.command("create <role>")
|
||||
.description(`create role account, roles: ${roles.join("|")}`)
|
||||
.action(async (role) => {
|
||||
const res = checkRole(role)
|
||||
if (!res) return
|
||||
.command("generate-wallet").action(async () => {
|
||||
const mnemonic = Wallet.generateMnemonic()
|
||||
const wallet = await Wallet.fromMnemonic(mnemonic, conn)
|
||||
|
||||
if (res.exist) {
|
||||
let fileData = fs.readFileSync(res.walletPath)
|
||||
let wallet = JSON.parse(fileData.toString())
|
||||
error(`role ${color(role, "red")} already created, public key: ${color(wallet.pubkey, "blue")}`)
|
||||
} else {
|
||||
const wallet = await newWallet()
|
||||
fs.writeFileSync(res.walletPath, JSON.stringify({
|
||||
pubkey: wallet.account.publicKey.toBase58(),
|
||||
secretKey: "["+wallet.account.secretKey.toString()+"]",
|
||||
mnemonic: wallet.mnemonic,
|
||||
}))
|
||||
|
||||
log(`create role ${color(role, "blue)")} success!`)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
cli
|
||||
.command("remove <role>")
|
||||
.description(`remove role account, roles: ${roles.join("|")}`)
|
||||
.action((role) => {
|
||||
const res = checkRole(role)
|
||||
if (!res) return
|
||||
|
||||
if (!res.exist) {
|
||||
error(`role [${role}] not created.`)
|
||||
}
|
||||
|
||||
fs.unlinkSync(res.walletPath)
|
||||
log(`remove role ${color(role, "blue")} success!`)
|
||||
})
|
||||
|
||||
|
||||
cli
|
||||
.command("roleinfo [role]")
|
||||
.description(`show role info, or all if no role supplied`)
|
||||
.action(async (role, opts) => {
|
||||
|
||||
// show current network
|
||||
showNetwork()
|
||||
const conn = await connectTo(network)
|
||||
|
||||
if (role) {
|
||||
showRoleInfo(role, conn)
|
||||
} else {
|
||||
for (let i = 0; i < roles.length; i++) {
|
||||
await showRoleInfo(roles[i], conn)
|
||||
}
|
||||
}
|
||||
log(`address: ${wallet.address}`)
|
||||
log(`mnemonic: ${mnemonic}`)
|
||||
})
|
||||
|
||||
cli
|
||||
.command("airdrop <role>")
|
||||
.description(`request airdrop to the role account, roles: ${roles.join("|")}`)
|
||||
.option("-m, --amount <amount>", "request amount, default is 10e8", "100000000")
|
||||
.action(async (role, opts) => {
|
||||
|
||||
// show current network
|
||||
showNetwork()
|
||||
|
||||
const res = checkRole(role)
|
||||
if (!res) return
|
||||
|
||||
if (!res.exist) {
|
||||
error(`role [${role}] not created.`)
|
||||
}
|
||||
|
||||
const fileData = fs.readFileSync(res.walletPath)
|
||||
const wallet = JSON.parse(fileData.toString())
|
||||
log(`payer public key: ${color(wallet.pubkey, "blue")}, request airdop...`)
|
||||
.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
|
||||
const conn = await connectTo(network)
|
||||
|
||||
await conn.requestAirdrop(new PublicKey(wallet.pubkey), amount*1)
|
||||
await sleep(1000)
|
||||
const balance = await conn.getBalance(new PublicKey(wallet.pubkey))
|
||||
|
||||
log(`airdop success, balance: ${color(balance, "blue")}`)
|
||||
log(`requesting 10 sol airdrop to: ${address}`)
|
||||
await conn.requestAirdrop(dest, amount * 1e9)
|
||||
log("airdrop success")
|
||||
})
|
||||
|
||||
|
||||
cli
|
||||
.command("deploy")
|
||||
.description("deploy the program")
|
||||
.action(async (opts) => {
|
||||
.command("deploy-program")
|
||||
.description("deploy the aggregator program")
|
||||
.action(async () => {
|
||||
const { admin, deployer } = await AdminContext.load()
|
||||
|
||||
// show current network
|
||||
showNetwork()
|
||||
const programAccount = await deployer.ensure(AdminContext.AGGREGATOR_PROGRAM, async () => {
|
||||
const programBinary = fs.readFileSync(FLUX_AGGREGATOR_SO)
|
||||
|
||||
if (fs.existsSync(deployedPath)) {
|
||||
const deployed = JSON.parse(fs.readFileSync(deployedPath).toString())
|
||||
log(`already deployed, program id: ${color(deployed.programId, "blue")}`)
|
||||
error("if you want to deployed again, try `npm run clean:deployed`")
|
||||
}
|
||||
log(`deploying ${FLUX_AGGREGATOR_SO}...`)
|
||||
const bpfLoader = new BPFLoader(admin)
|
||||
|
||||
const res = checkRole("payer")
|
||||
if (!res || !res.exist) {
|
||||
error(`role [payer] not created`)
|
||||
}
|
||||
return bpfLoader.load(programBinary)
|
||||
})
|
||||
|
||||
const fileData = fs.readFileSync(res.walletPath)
|
||||
const payer = JSON.parse(fileData.toString())
|
||||
|
||||
if (!fs.existsSync(sofilePath)) {
|
||||
error("program file not exists")
|
||||
}
|
||||
|
||||
const programBinary = fs.readFileSync(sofilePath)
|
||||
|
||||
const conn = await connectTo(network)
|
||||
|
||||
const fees = await calculatePayfees(programBinary.length, conn)
|
||||
let balance = await conn.getBalance(new PublicKey(payer.pubkey))
|
||||
|
||||
log(`payer wallet: ${color(payer.pubkey, "blue")}, balance: ${color(balance, "blue")}`)
|
||||
log(`deploy payfees: ${color(fees, "blue")}`)
|
||||
|
||||
if (balance < fees) {
|
||||
error("insufficient balance to pay fees")
|
||||
}
|
||||
|
||||
log("deploying...")
|
||||
const wallet = await Wallet.fromMnemonic(payer.mnemonic, conn)
|
||||
const bpfLoader = new BPFLoader(wallet)
|
||||
|
||||
const programAccount = await bpfLoader.load(programBinary)
|
||||
|
||||
log(`deploy success, program id: ${color(programAccount.publicKey.toBase58(), "blue")}`)
|
||||
fs.writeFileSync(deployedPath, JSON.stringify({
|
||||
network,
|
||||
programId: programAccount.publicKey.toBase58()
|
||||
}))
|
||||
log(`deployed aggregator program. program id: ${color(programAccount.publicKey.toBase58(), "blue")}`)
|
||||
})
|
||||
|
||||
cli
|
||||
.command("add-aggregator")
|
||||
.description("add an aggregator")
|
||||
.action(async () => {
|
||||
// show current network
|
||||
showNetwork()
|
||||
.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()
|
||||
|
||||
if (!fs.existsSync(deployedPath)) {
|
||||
error("program haven't deployed yet")
|
||||
}
|
||||
const { feedName, submitInterval, minSubmissionValue, maxSubmissionValue } = opts
|
||||
|
||||
const deployed = JSON.parse(fs.readFileSync(deployedPath).toString())
|
||||
const aggregator = new FluxAggregator(admin, aggregatorProgram.publicKey)
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
let res = checkRole("payer")
|
||||
if (!res || !res.exist) {
|
||||
error(`role ${color("payer", "blue")} not created`)
|
||||
}
|
||||
const payer = JSON.parse(fs.readFileSync(res.walletPath).toString())
|
||||
|
||||
res = checkRole("aggregatorOwner")
|
||||
|
||||
if (!res || !res.exist) {
|
||||
error(`role ${color("aggregatorOwner", "blue")} not created, please create the role first`)
|
||||
}
|
||||
const aggregatorOwner = JSON.parse(fs.readFileSync(res.walletPath).toString())
|
||||
|
||||
const inputs = await inquirer
|
||||
.prompt([
|
||||
{ message: "Pair name (eg. ETH/USD)", type: "input", name: "pairName", validate: (input) => {
|
||||
if (!input) {
|
||||
return "pair name cannot be empty"
|
||||
}
|
||||
if (deployed.pairs && deployed.pairs.some((p) => p.pairName == input)) {
|
||||
return "pair name exist"
|
||||
}
|
||||
return true
|
||||
}, transformer: (input) => {
|
||||
return input.substr(0, 32).toUpperCase()
|
||||
} },
|
||||
{ message: "Submit interval", type: "number", name: "submitInterval", default: 6 },
|
||||
{ message: "Min submission value", type: "number", name: "minSubmissionValue", default: 100 },
|
||||
{ message: "Max submission value", type: "number", name: "maxSubmissionValue", default: 10e9 },
|
||||
])
|
||||
|
||||
const { pairName, submitInterval, minSubmissionValue, maxSubmissionValue } = inputs
|
||||
|
||||
const conn = await connectTo(network)
|
||||
|
||||
const payerWallet = await Wallet.fromMnemonic(payer.mnemonic, conn)
|
||||
const aggregatorOwnerWallet = await Wallet.fromMnemonic(aggregatorOwner.mnemonic, conn)
|
||||
|
||||
const payerWalletBalance = await conn.getBalance(payerWallet.pubkey)
|
||||
const fees = await calculatePayfees(AggregatorLayout.span, conn)
|
||||
|
||||
if (payerWalletBalance < fees) {
|
||||
error("insufficient balance to pay fees")
|
||||
}
|
||||
|
||||
const program = new FluxAggregator(payerWallet, new PublicKey(deployed.programId))
|
||||
|
||||
let description = pairName.substr(0, 32).toUpperCase().padEnd(32)
|
||||
const aggregator = await program.initialize({
|
||||
submitInterval: submitInterval as number,
|
||||
const feed = await deployer.ensure(feedName, async () => {
|
||||
return aggregator.initialize({
|
||||
submitInterval: parseInt(submitInterval),
|
||||
minSubmissionValue: BigInt(minSubmissionValue),
|
||||
maxSubmissionValue: BigInt(maxSubmissionValue),
|
||||
description,
|
||||
owner: aggregatorOwnerWallet.account
|
||||
description: feedName.substr(0, 32).padEnd(32),
|
||||
owner: admin.account
|
||||
})
|
||||
})
|
||||
|
||||
log(`aggregator initialized, pubkey: ${color(aggregator.toBase58(), "blue")}, owner: ${color(aggregatorOwner.pubkey, "blue")}`)
|
||||
fs.writeFileSync(deployedPath, JSON.stringify({
|
||||
...deployed,
|
||||
pairs: (deployed.pairs || []).concat([{
|
||||
pairName: description.trim(),
|
||||
aggregator: aggregator.toBase58()
|
||||
}])
|
||||
}))
|
||||
|
||||
log(`feed initialized, pubkey: ${color(feed.publicKey.toBase58(), "blue")}`)
|
||||
})
|
||||
|
||||
cli
|
||||
.command("aggregators")
|
||||
.description("show all aggregators")
|
||||
.action(() => {
|
||||
// show current network
|
||||
showNetwork()
|
||||
// cli
|
||||
// .command("aggregators")
|
||||
// .description("show all aggregators")
|
||||
// .action(() => {
|
||||
// // show current network
|
||||
// showNetwork()
|
||||
|
||||
if (!fs.existsSync(deployedPath)) {
|
||||
error("program haven't deployed yet")
|
||||
}
|
||||
// if (!fs.existsSync(deployedPath)) {
|
||||
// error("program haven't deployed yet")
|
||||
// }
|
||||
|
||||
const deployed = JSON.parse(fs.readFileSync(deployedPath).toString())
|
||||
// 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.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")
|
||||
}
|
||||
// if (!deployed.programId) {
|
||||
// error("program haven't deployed yet")
|
||||
// }
|
||||
|
||||
log(deployed.pairs)
|
||||
})
|
||||
// log(deployed.pairs)
|
||||
// })
|
||||
|
||||
cli
|
||||
.command("add-oracle")
|
||||
.description("add an oracle to aggregator")
|
||||
.action(async () => {
|
||||
// show current network
|
||||
showNetwork()
|
||||
.option("--feedAddress <string>", "feed address")
|
||||
.option("--oracleName <string>", "oracle name")
|
||||
.option("--oracleOwner <string>", "oracle owner address")
|
||||
.action(async (opts) => {
|
||||
const { deployer, admin, aggregatorProgram } = await AdminContext.load()
|
||||
|
||||
if (!fs.existsSync(deployedPath)) {
|
||||
error("program haven't deployed yet")
|
||||
}
|
||||
const { oracleName, oracleOwner, feedAddress } = opts
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
if (!deployed.pairs) {
|
||||
error("no aggregators")
|
||||
}
|
||||
|
||||
let res = checkRole("payer")
|
||||
if (!res || !res.exist) {
|
||||
error(`role ${color("payer", "blue")} not created`)
|
||||
}
|
||||
const payer = JSON.parse(fs.readFileSync(res.walletPath).toString())
|
||||
|
||||
res = checkRole("aggregatorOwner")
|
||||
if (!res || !res.exist) {
|
||||
error(`role ${color("aggregatorOwner", "blue")} not created, please create the role first`)
|
||||
}
|
||||
const aggregatorOwner = JSON.parse(fs.readFileSync(res.walletPath).toString())
|
||||
|
||||
res = checkRole("oracleOwner")
|
||||
if (!res || !res.exist) {
|
||||
error(`role ${color("oracleOwner", "blue")} not created, please create the role first`)
|
||||
}
|
||||
const oracleOwner = JSON.parse(fs.readFileSync(res.walletPath).toString())
|
||||
|
||||
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 }))
|
||||
}},
|
||||
{ message: "Oracle name (eg. Solink)", type: "input", name: "oracleName", validate: (input) => {
|
||||
if (!input) {
|
||||
return "oracle name cannot be empty"
|
||||
}
|
||||
return true
|
||||
} }
|
||||
])
|
||||
|
||||
const { oracleName, aggregator } = inputs
|
||||
|
||||
const conn = await connectTo(network)
|
||||
|
||||
const payerWallet = await Wallet.fromMnemonic(payer.mnemonic, conn)
|
||||
const aggregatorOwnerWallet = await Wallet.fromMnemonic(aggregatorOwner.mnemonic, conn)
|
||||
|
||||
const payerWalletBalance = await conn.getBalance(payerWallet.pubkey)
|
||||
const fees = await calculatePayfees(OracleLayout.span, conn)
|
||||
|
||||
if (payerWalletBalance < fees) {
|
||||
error("insufficient balance to pay fees")
|
||||
}
|
||||
|
||||
const program = new FluxAggregator(payerWallet, new PublicKey(deployed.programId))
|
||||
const program = new FluxAggregator(admin, aggregatorProgram.publicKey)
|
||||
|
||||
log("add oracle...")
|
||||
const oracle = await program.addOracle({
|
||||
owner: new PublicKey(oracleOwner.pubkey),
|
||||
owner: new PublicKey(oracleOwner),
|
||||
description: oracleName.substr(0, 32).padEnd(32),
|
||||
aggregator: new PublicKey(aggregator),
|
||||
aggregatorOwner: aggregatorOwnerWallet.account,
|
||||
aggregator: new PublicKey(feedAddress),
|
||||
aggregatorOwner: admin.account,
|
||||
})
|
||||
|
||||
log(`add oracle success, pubkey: ${color(oracle.toBase58(), "blue")}, owner: ${color(oracleOwner.pubkey, "blue")}`)
|
||||
fs.writeFileSync(deployedPath, JSON.stringify({
|
||||
...deployed,
|
||||
oracles: (deployed.oracles || []).concat([{
|
||||
name: oracleName,
|
||||
aggregator,
|
||||
pubkey: oracle.toBase58()
|
||||
}])
|
||||
}))
|
||||
log(`added oracle. pubkey: ${color(oracle.toBase58(), "blue")}`)
|
||||
})
|
||||
|
||||
cli
|
||||
.command("oracles")
|
||||
.description("show all oracles")
|
||||
.action(() => {
|
||||
// show current network
|
||||
showNetwork()
|
||||
// cli
|
||||
// .command("oracles")
|
||||
// .description("show all oracles")
|
||||
// .action(() => {
|
||||
// // show current network
|
||||
// showNetwork()
|
||||
|
||||
if (!fs.existsSync(deployedPath)) {
|
||||
error("program haven't deployed yet")
|
||||
}
|
||||
// if (!fs.existsSync(deployedPath)) {
|
||||
// error("program haven't deployed yet")
|
||||
// }
|
||||
|
||||
const deployed = JSON.parse(fs.readFileSync(deployedPath).toString())
|
||||
// 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.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")
|
||||
}
|
||||
// if (!deployed.programId) {
|
||||
// error("program haven't deployed yet")
|
||||
// }
|
||||
|
||||
log(deployed.oracles)
|
||||
})
|
||||
// log(deployed.oracles)
|
||||
// })
|
||||
|
||||
cli
|
||||
.command("aggregatorInfo")
|
||||
.description("show aggregatorInfo")
|
||||
.action(async () => {
|
||||
// show current network
|
||||
showNetwork()
|
||||
// cli
|
||||
// .command("aggregatorInfo")
|
||||
// .description("show aggregatorInfo")
|
||||
// .action(async () => {
|
||||
// // show current network
|
||||
// showNetwork()
|
||||
|
||||
if (!fs.existsSync(deployedPath)) {
|
||||
error("program haven't deployed yet")
|
||||
}
|
||||
// if (!fs.existsSync(deployedPath)) {
|
||||
// error("program haven't deployed yet")
|
||||
// }
|
||||
|
||||
const deployed = JSON.parse(fs.readFileSync(deployedPath).toString())
|
||||
// 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.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")
|
||||
}
|
||||
// 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 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 { aggregator } = inputs
|
||||
// const conn = await connectTo(network)
|
||||
|
||||
const accountInfo = await conn.getAccountInfo(new PublicKey(aggregator))
|
||||
// const accountInfo = await conn.getAccountInfo(new PublicKey(aggregator))
|
||||
|
||||
log(decodeAggregatorInfo(accountInfo))
|
||||
})
|
||||
// log(decodeAggregatorInfo(accountInfo))
|
||||
// })
|
||||
|
||||
cli
|
||||
.command("feed")
|
||||
.description("oracle feeds to aggregator")
|
||||
.action(async () => {
|
||||
// show current network
|
||||
showNetwork()
|
||||
.option("--feedAddress <string>", "feed address to submit values to")
|
||||
.option("--oracleAddress <string>", "feed address to submit values to")
|
||||
.action(async (opts) => {
|
||||
|
||||
if (!fs.existsSync(deployedPath)) {
|
||||
error("program haven't deployed yet")
|
||||
}
|
||||
const { wallet, aggregatorProgram } = await OracleContext.load()
|
||||
|
||||
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 oracle", type: "list", name: "oracle", choices: () => {
|
||||
return deployed.oracles.map(p => ({ name: p.name+ ` [${p.pubkey}]`, value: `${p.pubkey}|${p.aggregator}` }))
|
||||
}},
|
||||
])
|
||||
|
||||
const tmpArr = inputs.oracle.split("|")
|
||||
|
||||
let res = checkRole("payer")
|
||||
if (!res || !res.exist) {
|
||||
error(`role ${color("payer", "blue")} not created`)
|
||||
}
|
||||
const payer = JSON.parse(fs.readFileSync(res.walletPath).toString())
|
||||
|
||||
res = checkRole("oracleOwner")
|
||||
if (!res || !res.exist) {
|
||||
error(`role ${color("oracleOwner", "blue")} not created, please create the role first`)
|
||||
}
|
||||
const oracleOwner = JSON.parse(fs.readFileSync(res.walletPath).toString())
|
||||
|
||||
let oracle = tmpArr[0], aggregator = tmpArr[1]
|
||||
|
||||
let pair = ""
|
||||
deployed.pairs.map((p) => {
|
||||
if (p.aggregator == aggregator) {
|
||||
pair = p.pairName
|
||||
}
|
||||
})
|
||||
|
||||
const conn = await connectTo(network)
|
||||
|
||||
const payerWallet = await Wallet.fromMnemonic(payer.mnemonic, conn)
|
||||
const oracleOwnerWallet = await Wallet.fromMnemonic(oracleOwner.mnemonic, conn)
|
||||
const { feedAddress, oracleAddress } = opts
|
||||
|
||||
feed.start({
|
||||
oracle: new PublicKey(oracle),
|
||||
oracleOwner: oracleOwnerWallet.account,
|
||||
aggregator: new PublicKey(aggregator),
|
||||
pair,
|
||||
payerWallet,
|
||||
programId: new PublicKey(deployed.programId)
|
||||
oracle: new PublicKey(oracleAddress),
|
||||
oracleOwner: wallet.account,
|
||||
feed: new PublicKey(feedAddress),
|
||||
pairSymbol: "BTC-USD",
|
||||
payerWallet: wallet,
|
||||
programId: aggregatorProgram.publicKey,
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
{"network":"local","programId":"95mRqquh7vGi31i87g2tnozV9ngap9GtnkpsHp31kFt1","pairs":[{"pairName":"ETH/USD","aggregator":"GNUKT4Ug3574s6tuWXtPhC9u1VPWgx4XXVCcD8usXHGW"}],"oracles":[{"name":"Solink","aggregator":"GNUKT4Ug3574s6tuWXtPhC9u1VPWgx4XXVCcD8usXHGW","pubkey":"5fs1WPxopQfgRWujfShfh6qLuPGkHjyMYbgxtzZyNtrp"}]}
|
76
src/feed.ts
76
src/feed.ts
|
@ -1,19 +1,17 @@
|
|||
import { PublicKey, Account, Wallet } from "solray"
|
||||
import WebSocket from "ws"
|
||||
|
||||
import { decodeOracleInfo } from "./utils"
|
||||
import { decodeOracleInfo, sleep } from "./utils"
|
||||
|
||||
import FluxAggregator from "./FluxAggregator"
|
||||
|
||||
let nextSubmitTime = new Date().getTime()
|
||||
let submiting = false
|
||||
const submitInterval = 10 * 1000
|
||||
|
||||
interface StartParams {
|
||||
oracle: PublicKey;
|
||||
oracleOwner: Account;
|
||||
aggregator: PublicKey;
|
||||
pair: string;
|
||||
feed: PublicKey;
|
||||
pairSymbol: string;
|
||||
payerWallet: Wallet;
|
||||
programId: PublicKey;
|
||||
}
|
||||
|
@ -22,23 +20,21 @@ export async function start(params: StartParams) {
|
|||
const {
|
||||
oracle,
|
||||
oracleOwner,
|
||||
aggregator,
|
||||
pair,
|
||||
feed,
|
||||
pairSymbol,
|
||||
payerWallet,
|
||||
programId,
|
||||
} = params
|
||||
|
||||
console.log("ready to feeds...")
|
||||
console.log("connecting to wss://ws-feed.pro.coinbase.com ()")
|
||||
const ws = new WebSocket("wss://ws-feed.pro.coinbase.com")
|
||||
|
||||
const program = new FluxAggregator(payerWallet, programId)
|
||||
|
||||
ws.on("open", () => {
|
||||
console.log(`${pair} price feed connected`)
|
||||
console.log(`${pairSymbol} price feed connected`)
|
||||
ws.send(JSON.stringify({
|
||||
"type": "subscribe",
|
||||
"product_ids": [
|
||||
pair.replace("/", "-").toUpperCase(),
|
||||
pairSymbol.replace("/", "-").toUpperCase(),
|
||||
],
|
||||
"channels": [
|
||||
"ticker"
|
||||
|
@ -46,45 +42,49 @@ export async function start(params: StartParams) {
|
|||
}))
|
||||
})
|
||||
|
||||
ws.on("message", (data) => {
|
||||
// in penny
|
||||
let curPriceCent = 0
|
||||
|
||||
ws.on("message", async (data) => {
|
||||
const json = JSON.parse(data)
|
||||
if (!json || !json.price) {
|
||||
return console.log(data)
|
||||
}
|
||||
|
||||
if (submiting) return false
|
||||
curPriceCent = Math.floor(json.price * 100)
|
||||
|
||||
console.log("new price:", json.price)
|
||||
let now = new Date().getTime()
|
||||
if (now < nextSubmitTime) {
|
||||
console.log("submit cooling...")
|
||||
return false
|
||||
console.log("current price:", json.price)
|
||||
})
|
||||
|
||||
ws.on("close", (err) => {
|
||||
console.error(`websocket closed: ${err}`)
|
||||
process.exit(1)
|
||||
})
|
||||
|
||||
const program = new FluxAggregator(payerWallet, programId)
|
||||
|
||||
console.log(await program.oracleInfo(oracle))
|
||||
console.log({ owner: oracleOwner.publicKey.toString() })
|
||||
|
||||
while (true) {
|
||||
if (curPriceCent == 0) {
|
||||
await sleep(1000)
|
||||
}
|
||||
|
||||
submiting = true
|
||||
|
||||
program.submit({
|
||||
aggregator,
|
||||
await program.submit({
|
||||
aggregator: feed,
|
||||
oracle,
|
||||
submission: BigInt(parseInt((json.price * 100) as any)),
|
||||
submission: BigInt(curPriceCent),
|
||||
owner: oracleOwner,
|
||||
}).then(() => {
|
||||
})
|
||||
|
||||
console.log("submit success!")
|
||||
nextSubmitTime = now + submitInterval
|
||||
|
||||
payerWallet.conn.getAccountInfo(oracle).then((accountInfo) => {
|
||||
console.log("oracle info:", decodeOracleInfo(accountInfo))
|
||||
})
|
||||
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
}).finally(() => {
|
||||
submiting = false
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
|
||||
ws.on("close", (error) => {
|
||||
console.error(error)
|
||||
})
|
||||
console.log("wait for cooldown success!")
|
||||
await sleep(submitInterval)
|
||||
}
|
||||
}
|
46
src/utils.ts
46
src/utils.ts
|
@ -1,25 +1,8 @@
|
|||
import { Connection, BpfLoader, PublicKey } from "@solana/web3.js"
|
||||
import { Connection, PublicKey } from "@solana/web3.js"
|
||||
|
||||
import { AggregatorLayout, SubmissionLayout, OracleLayout } from "./FluxAggregator"
|
||||
|
||||
import { solana, Wallet, NetworkName } from "solray"
|
||||
|
||||
export async function calculatePayfees(dataLength: number, conn: Connection): Promise<number> {
|
||||
|
||||
let fees = 0
|
||||
const { feeCalculator } = await conn.getRecentBlockhash()
|
||||
|
||||
const NUM_RETRIES = 500
|
||||
fees +=
|
||||
feeCalculator.lamportsPerSignature *
|
||||
(BpfLoader.getMinNumSignatures(dataLength) + NUM_RETRIES) +
|
||||
(await conn.getMinimumBalanceForRentExemption(dataLength))
|
||||
|
||||
// Calculate the cost of sending the transactions
|
||||
fees += feeCalculator.lamportsPerSignature * 100
|
||||
|
||||
return fees
|
||||
}
|
||||
import { solana, Wallet, NetworkName, Deployer } from "solray"
|
||||
|
||||
export function getSubmissionValue(submissions: []): number {
|
||||
const values = submissions
|
||||
|
@ -108,18 +91,21 @@ export function decodeOracleInfo(accountInfo) {
|
|||
return oracle
|
||||
}
|
||||
|
||||
export async function connectTo(network: NetworkName): Promise<Connection> {
|
||||
const conn = solana.connect(network as NetworkName)
|
||||
return conn
|
||||
export async function walletFromEnv(key: string, conn: Connection): Promise<Wallet> {
|
||||
const mnemonic = process.env[key]
|
||||
if (!mnemonic) {
|
||||
throw new Error(`Set ${key} in .env to be a mnemonic`)
|
||||
}
|
||||
|
||||
export async function newWallet(): Promise<any> {
|
||||
const mnemonic = Wallet.generateMnemonic()
|
||||
|
||||
const wallet = await Wallet.fromMnemonic(mnemonic, null as any)
|
||||
|
||||
return {
|
||||
mnemonic,
|
||||
account: wallet.account
|
||||
return Wallet.fromMnemonic(mnemonic, conn)
|
||||
}
|
||||
|
||||
export async function openDeployer(): Promise<Deployer> {
|
||||
const deployFile = process.env.DEPLOY_FILE
|
||||
|
||||
if (!deployFile) {
|
||||
throw new Error(`Set DEPLOY_FILE in .env`)
|
||||
}
|
||||
|
||||
return Deployer.open(deployFile)
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
{"pubkey":"2ATirbVBRtEGGY3jeEcEsTGLBVSJEvk6WLb1K7droYp2","secretKey":"[183,247,179,232,164,36,17,253,149,205,222,132,4,4,154,4,206,110,37,117,129,66,18,146,95,45,172,29,62,133,203,242,17,72,32,226,225,18,116,158,33,176,7,145,15,65,108,116,53,15,28,112,25,66,150,7,240,20,112,110,21,92,250,67]","mnemonic":"forget mushroom capable trim chapter rally long congress humor maximum title citizen"}
|
|
@ -1 +0,0 @@
|
|||
{"pubkey":"FUZDeYACNRvDYD9A1cmrEqfZ5GiE5rBEfc2rcXW2sXxw","secretKey":"[18,53,121,118,64,233,227,172,4,14,12,103,202,93,26,114,190,103,143,81,125,144,144,156,74,251,138,251,131,128,135,13,215,18,186,86,171,14,169,75,62,159,168,103,242,239,62,120,134,113,144,77,53,136,194,105,57,172,193,138,105,231,244,228]","mnemonic":"subway wine design chuckle tooth helmet solution butter tooth slab leopard useful"}
|
|
@ -1 +0,0 @@
|
|||
{"pubkey":"79AnPHtN7P4e36CDrP9CzuVRbwRUZdVMQ7weFiktYjPv","secretKey":"[224,93,108,163,202,117,226,167,224,216,219,4,51,192,130,204,111,22,71,199,97,113,139,183,236,194,229,135,214,12,231,108,91,61,212,65,148,88,37,162,244,108,41,171,74,32,204,29,135,215,103,88,134,32,115,81,222,40,175,222,205,208,152,65]","mnemonic":"dove ribbon nut beef trouble language unfair quiz sand wash copper result"}
|
Loading…
Reference in New Issue