support feeds to an aggregator

This commit is contained in:
czl1378 2020-12-09 15:52:28 +08:00
parent 4b320fff2f
commit be55afce37
13 changed files with 564 additions and 204 deletions

View File

@ -19,18 +19,26 @@ request some airdrop:
`npm run deploy`
## Create Aggregator
## Create Aggregator Owner
`npm run create:aggregator`
`npm run create:aggregatorOwner`
request some airdrop:
## Add An Aggregator
`npm run airdrop:aggregator`
`npm run add:aggregator`
## Create Oracle
## Create Oracle Owner
`npm run create:oracle`
`npm run create:oracleOwner`
request some airdrop:
## Add An Oracle
`npm run airdrop:oracle`
`npm run add:oracle`
## Show Aggregators/Oracles
`npm run show:aggregators|oracles`
## Start to feed
`npm run feed`

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="de"><head><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"><meta content="/logos/doodles/2020/december-holidays-days-2-30-6753651837108830.3-law.gif" itemprop="image"><meta content="Eine schöne Dezemberzeit! " property="twitter:title"><meta content="Eine schöne Dezemberzeit! #GoogleDoodle" property="twitter:description"><meta content="Eine schöne Dezemberzeit! #GoogleDoodle" property="og:description"><meta content="summary_large_image" property="twitter:card"><meta content="@GoogleDoodles" property="twitter:site"><meta content="https://www.google.com/logos/doodles/2020/december-holidays-days-2-30-6753651837108830.3-2xa.gif" property="twitter:image"><meta content="https://www.google.com/logos/doodles/2020/december-holidays-days-2-30-6753651837108830.3-2xa.gif" property="og:image"><meta content="1100" property="og:image:width"><meta content="440" property="og:image:height"><meta content="https://www.google.com/logos/doodles/2020/december-holidays-days-2-30-6753651837108830.3-2xa.gif" property="og:url"><meta content="video.other" property="og:type"><title>Google</title><script nonce="cn7ENG0wOphTAOnNCV0wrQ==">(function(){window.google={kEI:'-W3QX-m4IaXVgwfuy6rgDQ',kEXPI:'0,202162,1157247,730,224,5105,206,3204,10,1226,364,1499,817,383,246,5,1354,648,3766,3,65,768,217,1263,1527,2514,7,1116988,1197780,503,7,328977,13677,4855,32691,16115,17444,1954,9286,9188,8384,4858,1362,9290,3026,4742,8000,4841,4020,978,7931,5297,2054,920,873,10622,14527,4518,2777,919,2277,8,85,2711,1593,1279,2212,530,149,1103,840,517,1466,56,4258,109,203,1137,2,2669,2023,1777,520,1946,2230,93,329,1283,2001,942,2246,3600,3227,419,1571,855,7,12354,4455,641,598,7279,2037,2999,3407,908,2,940,2615,2397,6335,1133,3277,1,2,576,970,865,6,4341,277,149,3027,2965,6322,1661,4,1252,276,2304,1236,271,561,313,405,42,1818,2393,1791,2429,17,447,459,1555,4067,1036,1315,3,3280,1426,2185,107,392,2811,1409,344,2658,4164,597,912,564,681,437,32,1303,1408,1143,24,114,2440,655,875,115,52,3033,252,2214,1397,200,708,638,1494,130,475,2,567,321,4,1202,1274,2,258,1025,7,319,992,756,613,55,745,212,2052,119,1457,773,2,123,144,933,230,587,11,686,45,64,214,387,337,513,2,841,452,377,3245,2090,45,407,48,479,514,640,1262,59,777,7,334,20,2,18,18,241,33,676,2153,5,100,808,3,202,1325,2014,1,336,613,87,2,625,342,606,1294,122,29,17,979,965,982,704,788,132,247,221,3,161,37,385,12,756,141,293,225,349,436,462,288,1619,452,857,262,68,513,202,179,54,488,896,2832,768,5721587,3869,8798046,549,333,444,1,2,80,1,900,896,1,9,2,2551,1,748,141,59,736,563,1,4265,1,1,2,1017,9,305,3299,248,595,1,1574,610,48,10,2,1,8,113,23,1,2,5,12,16,7,1,23957828',kBL:'rp-W'};google.sn='webhp';google.kHL='de';})();(function(){
google.lc=[];google.li=0;google.getEI=function(a){for(var b;a&&(!a.getAttribute||!(b=a.getAttribute("eid")));)a=a.parentNode;return b||google.kEI};google.getLEI=function(a){for(var b=null;a&&(!a.getAttribute||!(b=a.getAttribute("leid")));)a=a.parentNode;return b};google.ml=function(){return null};google.time=function(){return Date.now()};google.log=function(a,b,c,d,g){if(c=google.logUrl(a,b,c,d,g)){a=new Image;var e=google.lc,f=google.li;e[f]=a;a.onerror=a.onload=a.onabort=function(){delete e[f]};a.src=c;google.li=f+1}};google.logUrl=function(a,b,c,d,g){var e="",f=google.ls||"";c||-1!=b.search("&ei=")||(e="&ei="+google.getEI(d),-1==b.search("&lei=")&&(d=google.getLEI(d))&&(e+="&lei="+d));d="";!c&&google.cshid&&-1==b.search("&cshid=")&&"slh"!=a&&(d="&cshid="+google.cshid);c=c||"/"+(g||"gen_204")+"?atyp=i&ct="+a+"&cad="+b+e+f+"&zx="+google.time()+d;/^http:/i.test(c)&&"https:"==window.location.protocol&&(google.ml(Error("a"),!1,{src:c,glmm:1}),c="");return c};}).call(this);(function(){google.y={};google.x=function(a,b){if(a)var c=a.id;else{do c=Math.random();while(google.y[c])}google.y[c]=[a,b];return!1};google.lm=[];google.plm=function(a){google.lm.push.apply(google.lm,a)};google.lq=[];google.load=function(a,b,c){google.lq.push([[a],b,c])};google.loadAll=function(a,b){google.lq.push([a,b])};}).call(this);google.f={};(function(){
document.documentElement.addEventListener("submit",function(b){var a;if(a=b.target){var c=a.getAttribute("data-submitfalse");a="1"==c||"q"==c&&!a.elements.q.value?!0:!1}else a=!1;a&&(b.preventDefault(),b.stopPropagation())},!0);document.documentElement.addEventListener("click",function(b){var a;a:{for(a=b.target;a&&a!=document.documentElement;a=a.parentElement)if("A"==a.tagName){a="1"==a.getAttribute("data-nohref");break a}a=!1}a&&b.preventDefault()},!0);}).call(this);
var a=window.location,b=a.href.indexOf("#");if(0<=b){var c=a.href.substring(b+1);/(^|&)q=/.test(c)&&-1==c.indexOf("#")&&a.replace("/search?"+c.replace(/(^|&)fp=[^&]*/g,"")+"&cad=h")};</script><style>#gbar,#guser{font-size:13px;padding-top:1px !important;}#gbar{height:22px}#guser{padding-bottom:7px !important;text-align:right}.gbh,.gbd{border-top:1px solid #c9d7f1;font-size:1px}.gbh{height:0;position:absolute;top:24px;width:100%}@media all{.gb1{height:22px;margin-right:.5em;vertical-align:top}#gbar{float:left}}a.gb1,a.gb4{text-decoration:underline !important}a.gb1,a.gb4{color:#00c !important}.gbi .gb4{color:#dd8e27 !important}.gbf .gb4{color:#900 !important}
</style><style>body,td,a,p,.h{font-family:arial,sans-serif}body{margin:0;overflow-y:scroll}#gog{padding:3px 8px 0}td{line-height:.8em}.gac_m td{line-height:17px}form{margin-bottom:20px}.h{color:#1558d6}em{font-weight:bold;font-style:normal}.lst{height:25px;width:496px}.gsfi,.lst{font:18px arial,sans-serif}.gsfs{font:17px arial,sans-serif}.ds{display:inline-box;display:inline-block;margin:3px 0 4px;margin-left:4px}input{font-family:inherit}body{background:#fff;color:#000}a{color:#4b11a8;text-decoration:none}a:hover,a:active{text-decoration:underline}.fl a{color:#1558d6}a:visited{color:#4b11a8}.sblc{padding-top:5px}.sblc a{display:block;margin:2px 0;margin-left:13px;font-size:11px}.lsbb{background:#f8f9fa;border:solid 1px;border-color:#dadce0 #70757a #70757a #dadce0;height:30px}.lsbb{display:block}#WqQANb a{display:inline-block;margin:0 12px}.lsb{background:url(/images/nav_logo229.png) 0 -261px repeat-x;border:none;color:#000;cursor:pointer;height:30px;margin:0;outline:0;font:15px arial,sans-serif;vertical-align:top}.lsb:active{background:#dadce0}.lst:focus{outline:none}</style><script nonce="cn7ENG0wOphTAOnNCV0wrQ=="></script></head><body bgcolor="#fff"><script nonce="cn7ENG0wOphTAOnNCV0wrQ==">(function(){var src='/images/nav_logo229.png';var iesg=false;document.body.onload = function(){window.n && window.n();if (document.images){new Image().src=src;}
if (!iesg){document.f&&document.f.q.focus();document.gbqf&&document.gbqf.q.focus();}
}
})();</script><div id="mngb"><div id=gbar><nobr><b class=gb1>Suche</b> <a class=gb1 href="https://www.google.de/imghp?hl=de&tab=wi">Bilder</a> <a class=gb1 href="https://maps.google.de/maps?hl=de&tab=wl">Maps</a> <a class=gb1 href="https://play.google.com/?hl=de&tab=w8">Play</a> <a class=gb1 href="https://www.youtube.com/?gl=DE&tab=w1">YouTube</a> <a class=gb1 href="https://news.google.com/?tab=wn">News</a> <a class=gb1 href="https://mail.google.com/mail/?tab=wm">Gmail</a> <a class=gb1 href="https://drive.google.com/?tab=wo">Drive</a> <a class=gb1 style="text-decoration:none" href="https://www.google.de/intl/de/about/products?tab=wh"><u>Mehr</u> &raquo;</a></nobr></div><div id=guser width=100%><nobr><span id=gbn class=gbi></span><span id=gbf class=gbf></span><span id=gbe></span><a href="http://www.google.de/history/optout?hl=de" class=gb4>Webprotokoll</a> | <a href="/preferences?hl=de" class=gb4>Einstellungen</a> | <a target=_top id=gb_70 href="https://accounts.google.com/ServiceLogin?hl=de&passive=true&continue=https://www.google.com/&ec=GAZAAQ" class=gb4>Anmelden</a></nobr></div><div class=gbh style=left:0></div><div class=gbh style=right:0></div></div><center><br clear="all" id="lgpd"><div id="lga"><a href="/search?ie=UTF-8&amp;q=+Feiertage+Dezember&amp;oi=ddle&amp;ct=173710363&amp;hl=de&amp;sa=X&amp;ved=0ahUKEwipmdSQosDtAhWl6uAKHe6lCtwQPQgD"><img alt="Eine schöne Dezemberzeit! " border="0" height="180" src="/logos/doodles/2020/december-holidays-days-2-30-6753651837108830.3-law.gif" title="Eine schöne Dezemberzeit! " width="500" id="hplogo"><br></a><br></div><form action="/search" name="f"><table cellpadding="0" cellspacing="0"><tr valign="top"><td width="25%">&nbsp;</td><td align="center" nowrap=""><input name="ie" value="ISO-8859-1" type="hidden"><input value="de" name="hl" type="hidden"><input name="source" type="hidden" value="hp"><input name="biw" type="hidden"><input name="bih" type="hidden"><div class="ds" style="height:32px;margin:4px 0"><input class="lst" style="margin:0;padding:5px 8px 0 6px;vertical-align:top;color:#000" autocomplete="off" value="" title="Google Suche" maxlength="2048" name="q" size="57"></div><br style="line-height:0"><span class="ds"><span class="lsbb"><input class="lsb" value="Google Suche" name="btnG" type="submit"></span></span><span class="ds"><span class="lsbb"><input class="lsb" id="tsuid1" value="Auf gut Glück!" name="btnI" type="submit"><script nonce="cn7ENG0wOphTAOnNCV0wrQ==">(function(){var id='tsuid1';document.getElementById(id).onclick = function(){if (this.form.q.value){this.checked = 1;if (this.form.iflsig)this.form.iflsig.disabled = false;}
else top.location='/doodles/';};})();</script><input value="AINFCbYAAAAAX9B8Cd7eJVkXi-j9B_Xk979qLeuRAn3_" name="iflsig" type="hidden"></span></span></td><td class="fl sblc" align="left" nowrap="" width="25%"><a href="/advanced_search?hl=de&amp;authuser=0">Erweiterte Suche</a></td></tr></table><input id="gbv" name="gbv" type="hidden" value="1"><script nonce="cn7ENG0wOphTAOnNCV0wrQ==">(function(){var a,b="1";if(document&&document.getElementById)if("undefined"!=typeof XMLHttpRequest)b="2";else if("undefined"!=typeof ActiveXObject){var c,d,e=["MSXML2.XMLHTTP.6.0","MSXML2.XMLHTTP.3.0","MSXML2.XMLHTTP","Microsoft.XMLHTTP"];for(c=0;d=e[c++];)try{new ActiveXObject(d),b="2"}catch(h){}}a=b;if("2"==a&&-1==location.search.indexOf("&gbv=2")){var f=google.gbvu,g=document.getElementById("gbv");g&&(g.value=a);f&&window.setTimeout(function(){location.href=f},0)};}).call(this);</script></form><div id="gac_scont"></div><div style="font-size:83%;min-height:3.5em"><br></div><span id="footer"><div style="font-size:10pt"><div style="margin:19px auto;text-align:center" id="WqQANb"><a href="/intl/de/ads/">Werben mit Google</a><a href="/services/">Unternehmensangebote</a><a href="/intl/de/about.html">Über Google</a><a href="https://www.google.com/setprefdomain?prefdom=DE&amp;prev=https://www.google.de/&amp;sig=K_9T17-zAXuU4D1biY7Y7O_jO6a2U%3D">Google.de</a></div></div><p style="font-size:8pt;color:#70757a">&copy; 2020 - <a href="/intl/de/policies/privacy/">Datenschutzerklärung</a> - <a href="/intl/de/policies/terms/">Nutzungsbedingungen</a></p></span></center><script nonce="cn7ENG0wOphTAOnNCV0wrQ==">(function(){window.google.cdo={height:0,width:0};(function(){var a=window.innerWidth,b=window.innerHeight;if(!a||!b){var c=window.document,d="CSS1Compat"==c.compatMode?c.documentElement:c.body;a=d.clientWidth;b=d.clientHeight}a&&b&&(a!=google.cdo.width||b!=google.cdo.height)&&google.log("","","/client_204?&atyp=i&biw="+a+"&bih="+b+"&ei="+google.kEI);}).call(this);})();(function(){var u='/xjs/_/js/k\x3dxjs.hp.en.EX3sRme5MYM.O/m\x3dsb_he,d/am\x3dAHiCOA/d\x3d1/rs\x3dACT90oEFEecr6o5r91Xg9VB85C0s-YlfBA';
var c=this||self,e=/^[\w+/_-]+[=]{0,2}$/,f=null,g=function(a){return(a=a.querySelector&&a.querySelector("script[nonce]"))&&(a=a.nonce||a.getAttribute("nonce"))&&e.test(a)?a:""},h=function(a){return a};var l;var n=function(a,b){this.g=b===m?a:""},m={};setTimeout(function(){var a=document;var b="SCRIPT";"application/xhtml+xml"===a.contentType&&(b=b.toLowerCase());b=a.createElement(b);a=u;if(void 0===l){var d=null;var k=c.trustedTypes;if(k&&k.createPolicy){try{d=k.createPolicy("goog#html",{createHTML:h,createScript:h,createScriptURL:h})}catch(p){c.console&&c.console.error(p.message)}l=d}else l=d}a=(d=l)?d.createScriptURL(a):a;a=new n(a,m);b.src=a instanceof n&&a.constructor===n?a.g:"type_error:TrustedResourceUrl";(a=b.ownerDocument&&b.ownerDocument.defaultView)&&
a!=c?a=g(a.document):(null===f&&(f=g(c.document)),a=f);a&&b.setAttribute("nonce",a);google.timers&&google.timers.load&&google.tick&&google.tick("load","xjsls");document.body.appendChild(b)},0);})();(function(){window.google.xjsu='/xjs/_/js/k\x3dxjs.hp.en.EX3sRme5MYM.O/m\x3dsb_he,d/am\x3dAHiCOA/d\x3d1/rs\x3dACT90oEFEecr6o5r91Xg9VB85C0s-YlfBA';})();function _DumpException(e){throw e;}
function _F_installCss(c){}
(function(){google.jl={dw:false,em:[],emw:false,lls:'default',pdt:0,snet:true,uwp:true};})();(function(){var pmc='{\x22d\x22:{},\x22sb_he\x22:{\x22agen\x22:true,\x22cgen\x22:true,\x22client\x22:\x22heirloom-hp\x22,\x22dh\x22:true,\x22dhqt\x22:true,\x22ds\x22:\x22\x22,\x22ffql\x22:\x22de\x22,\x22fl\x22:true,\x22host\x22:\x22google.com\x22,\x22isbh\x22:28,\x22jsonp\x22:true,\x22lm\x22:true,\x22msgs\x22:{\x22cibl\x22:\x22Suche löschen\x22,\x22dym\x22:\x22Meintest du:\x22,\x22lcky\x22:\x22Auf gut Glück!\x22,\x22lml\x22:\x22Weitere Informationen\x22,\x22oskt\x22:\x22Eingabetools\x22,\x22psrc\x22:\x22Diese Suchanfrage wurde aus deinem \\u003Ca href\x3d\\\x22/history\\\x22\\u003EWebprotokoll\\u003C/a\\u003E entfernt.\x22,\x22psrl\x22:\x22Entfernen\x22,\x22sbit\x22:\x22Bildersuche\x22,\x22srch\x22:\x22Google Suche\x22},\x22nrft\x22:false,\x22ovr\x22:{},\x22pq\x22:\x22\x22,\x22refpd\x22:true,\x22rfs\x22:[],\x22sbas\x22:\x220 3px 8px 0 rgba(0,0,0,0.2),0 0 0 1px rgba(0,0,0,0.08)\x22,\x22sbpl\x22:16,\x22sbpr\x22:16,\x22scd\x22:10,\x22stok\x22:\x22GN3OqQHwh9gc0L6Uy6HU9Up3lh0\x22,\x22uhde\x22:false}}';google.pmc=JSON.parse(pmc);})();</script> </body></html>

View File

@ -6,20 +6,28 @@
"testnetDefaultChannel": "v1.4.8",
"scripts": {
"deploy": "cd src && ts-node cli.ts deploy",
"role-info": "cd src && ts-node cli.ts role-info",
"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:aggregator": "cd src && ts-node cli.ts create aggregator",
"create:oracle": "cd src && ts-node cli.ts create oracle",
"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:aggregator": "cd src && ts-node cli.ts remove aggregator",
"remove:oracle": "cd src && ts-node cli.ts remove oracle",
"airdrop:payer": "cd src && ts-node cli.ts airdrop payer",
"airdrop:aggregator": "cd src && ts-node cli.ts airdrop aggregator",
"airdrop:oracle": "cd src && ts-node cli.ts airdrop oracle",
"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": "rm -rf src/deployed.md && rm -rf src/wallets/*",
"clean:roles": "rm -rf src/wallets/*",
"clean:deployed": "rm -rf src/deployed.json",
"clean": "npm run clean:deployed && npm run clean:roles",
"build:program": "solray build program"
},
"keywords": [],
@ -28,6 +36,7 @@
"dependencies": {
"commander": "^6.2.0",
"dotenv": "^8.2.0",
"inquirer": "^7.3.3",
"solray": "git+https://github.com/czl1378/solray.git"
},
"devDependencies": {

1
src/.env Normal file
View File

@ -0,0 +1 @@
NETWORK=local

View File

@ -1,27 +1,37 @@
import { Command, option } from "commander"
import { PublicKey, Connection } from "@solana/web3.js"
import { BPFLoader, Wallet } from "solray"
import {
newWallet, calculatePayfees, connectTo, sleep
} from "./utils"
import inquirer from "inquirer"
import fs from "fs"
import path from "path"
import { Connection, PublicKey } from "@solana/web3.js"
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 * as feed from "./feed"
dotenv.config()
const cli = new Command()
const roles = ["payer", "aggregator", "oracle"]
const roles = ["payer", "aggregatorOwner", "oracleOwner"]
const sofilePath = path.resolve(__dirname, "../build/flux_aggregator.so")
const deployedPath = path.resolve(__dirname, "./deployed.md")
const deployedPath = path.resolve(__dirname, "./deployed.json")
const { NETWORK } = process.env
const network = (NETWORK || "local") as NetworkName
function checkRole(role) {
if (roles.indexOf(role) < 0) {
console.error("invalid role")
return false
error("invalid role")
}
const walletPath = path.resolve(`./wallets/${role}.json`)
@ -42,24 +52,40 @@ 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"))
console.log("\n")
process.exit()
}
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) {
return console.error(`role ${color(role, "red")} not created.`)
log(`role ${color(role, "red")} not created.`)
return
}
const fileData = fs.readFileSync(res.walletPath)
const wallet = JSON.parse(fileData.toString())
console.log(color(`[${role}]`, "cyanic", true))
console.log(color("public key: ", "blue"), `${wallet.publicKey}`)
console.log(color("mnemonic: ", "blue"), `${wallet.mnemonic}`)
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.publicKey))
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`)
@ -76,17 +102,16 @@ cli
if (res.exist) {
let fileData = fs.readFileSync(res.walletPath)
let wallet = JSON.parse(fileData.toString())
console.error(`role ${color(role, "red")} already created, public key: ${color(wallet.publicKey, "blue")}`)
return
error(`role ${color(role, "red")} already created, public key: ${color(wallet.pubkey, "blue")}`)
} else {
const wallet = await newWallet()
fs.writeFileSync(res.walletPath, JSON.stringify({
publicKey: wallet.account.publicKey.toBase58(),
pubkey: wallet.account.publicKey.toBase58(),
secretKey: "["+wallet.account.secretKey.toString()+"]",
mnemonic: wallet.mnemonic,
}))
console.log(`create role ${color(role, "blue)")} success!`)
log(`create role ${color(role, "blue)")} success!`)
}
})
@ -100,24 +125,21 @@ cli
if (!res) return
if (!res.exist) {
return console.error(`role ${color(role, "red")} not created.`)
error(`role [${role}] not created.`)
}
fs.unlinkSync(res.walletPath)
console.log(`remove role ${color(role, "blue")} success!`)
log(`remove role ${color(role, "blue")} success!`)
})
cli
.command("role-info [role]")
.command("roleinfo [role]")
.description(`show role info, or all if no role supplied`)
.option(
"-n, --network <network_name>",
"deploy on which network (local|devnet|mainnet), default is localnet",
"local"
)
.action(async (role, opts) => {
const { network } = opts
// show current network
showNetwork()
const conn = await connectTo(network)
if (role) {
@ -132,63 +154,58 @@ cli
cli
.command("airdrop <role>")
.description(`request airdrop to the role account, roles: ${roles.join("|")}`)
.option(
"-n, --network <network_name>",
"deploy on which network (local|devnet|mainnet), default is localnet",
"local"
)
.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) {
return console.error(`role ${color(role, "red")} not created.`)
error(`role [${role}] not created.`)
}
const fileData = fs.readFileSync(res.walletPath)
const wallet = JSON.parse(fileData.toString())
console.log(`payer public key: ${color(wallet.publicKey, "blue")}, request airdop...`)
log(`payer public key: ${color(wallet.pubkey, "blue")}, request airdop...`)
const { network, amount } = opts
const { amount } = opts
const conn = await connectTo(network)
await conn.requestAirdrop(new PublicKey(wallet.publicKey), amount*1)
await conn.requestAirdrop(new PublicKey(wallet.pubkey), amount*1)
await sleep(1000)
const balance = await conn.getBalance(new PublicKey(wallet.publicKey))
const balance = await conn.getBalance(new PublicKey(wallet.pubkey))
console.log(`airdop success, balance: ${color(balance, "blue")}`)
log(`airdop success, balance: ${color(balance, "blue")}`)
})
cli
.command("deploy")
.description("deploy the aggregator program")
.option(
"-n,--network <network_name>",
"deploy on which network (local|devnet|mainnet), default is localnet",
"local"
)
.description("deploy the program")
.action(async (opts) => {
// show current network
showNetwork()
if (fs.existsSync(deployedPath)) {
const programId = fs.readFileSync(deployedPath).toString()
console.log("already deployed, program id: ", color(programId, "blue"))
console.log(color("if you want to deployed agian, try `npm run clean`", "red", true))
return
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`")
}
const { network } = opts
const res = checkRole("payer")
if (!res || !res.exist) {
return console.error(`role ${color("payer", "blue")} not created`)
error(`role [payer] not created`)
}
const fileData = fs.readFileSync(res.walletPath)
const payer = JSON.parse(fileData.toString())
if (!fs.existsSync(sofilePath)) {
return console.error(`${color("program file not exists", "red")}`)
error("program file not exists")
}
const programBinary = fs.readFileSync(sofilePath)
@ -196,39 +213,355 @@ cli
const conn = await connectTo(network)
const fees = await calculatePayfees(programBinary.length, conn)
let balance = await conn.getBalance(new PublicKey(payer.publicKey))
let balance = await conn.getBalance(new PublicKey(payer.pubkey))
console.log(`payer wallet: ${color(payer.publicKey, "blue")}, balance: ${color(balance, "blue")}`)
console.log(`deploy payfees: ${color(fees, "blue")}`)
log(`payer wallet: ${color(payer.pubkey, "blue")}, balance: ${color(balance, "blue")}`)
log(`deploy payfees: ${color(fees, "blue")}`)
if (balance < fees) {
return console.log(color("insufficient balance to pay fees", "red"))
error("insufficient balance to pay fees")
}
console.log("deploying...")
log("deploying...")
const wallet = await Wallet.fromMnemonic(payer.mnemonic, conn)
const bpfLoader = new BPFLoader(wallet)
const programAccount = await bpfLoader.load(programBinary)
console.log(`deploy success, program id: ${color(programAccount.publicKey.toBase58(), "blue")}`)
fs.writeFileSync(deployedPath, programAccount.publicKey.toBase58())
log(`deploy success, program id: ${color(programAccount.publicKey.toBase58(), "blue")}`)
fs.writeFileSync(deployedPath, JSON.stringify({
network,
programId: programAccount.publicKey.toBase58()
}))
})
cli
.command("init-aggregator")
.description("initialize aggregator to the program")
.option(
"-n,--network <network_name>",
"deploy on which network (local|devnet|mainnet), default is localnet",
"local"
)
.action(async (opts) => {
const { network } = opts
const res = checkRole("aggregator")
if (!res || !res.exist) {
return console.error(`role ${color("aggregator", "blue")} not created`)
.command("add-aggregator")
.description("add an aggregator")
.action(async () => {
// 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")
}
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,
minSubmissionValue: BigInt(minSubmissionValue),
maxSubmissionValue: BigInt(maxSubmissionValue),
description,
owner: aggregatorOwnerWallet.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()
}])
}))
})
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")
.action(async () => {
// 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")
}
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))
log("add oracle...")
const oracle = await program.addOracle({
owner: new PublicKey(oracleOwner.pubkey),
description: oracleName.substr(0,32).padEnd(32),
aggregator: new PublicKey(aggregator),
aggregatorOwner: aggregatorOwnerWallet.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()
}])
}))
})
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("aggregatorInfo")
.description("show aggregatorInfo")
.action(async () => {
// 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")
}
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))
})
cli
.command("feed")
.description("oracle feeds to aggregator")
.action(async () => {
// 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")
}
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)
feed.start({
oracle: new PublicKey(oracle),
oracleOwner: oracleOwnerWallet.account,
aggregator: new PublicKey(aggregator),
pair,
payerWallet,
programId: new PublicKey(deployed.programId)
})
})
cli.parse(process.argv)

1
src/deployed.json Normal file
View File

@ -0,0 +1 @@
{"network":"local","programId":"95mRqquh7vGi31i87g2tnozV9ngap9GtnkpsHp31kFt1","pairs":[{"pairName":"ETH/USD","aggregator":"GNUKT4Ug3574s6tuWXtPhC9u1VPWgx4XXVCcD8usXHGW"}],"oracles":[{"name":"Solink","aggregator":"GNUKT4Ug3574s6tuWXtPhC9u1VPWgx4XXVCcD8usXHGW","pubkey":"5fs1WPxopQfgRWujfShfh6qLuPGkHjyMYbgxtzZyNtrp"}]}

90
src/feed.ts Normal file
View File

@ -0,0 +1,90 @@
import { PublicKey, Account, Wallet } from "solray"
import WebSocket from "ws"
import { decodeOracleInfo } 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;
payerWallet: Wallet;
programId: PublicKey;
}
export async function start(params: StartParams) {
const {
oracle,
oracleOwner,
aggregator,
pair,
payerWallet,
programId,
} = params
console.log("ready to feeds...")
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`)
ws.send(JSON.stringify({
"type": "subscribe",
"product_ids": [
pair.replace("/", "-").toUpperCase(),
],
"channels": [
"ticker"
]
}))
})
ws.on("message", (data) => {
const json = JSON.parse(data)
if (!json || !json.price) {
return console.log(data)
}
if (submiting) return false
console.log("new price:", json.price)
let now = new Date().getTime()
if (now < nextSubmitTime) {
console.log("submit cooling...")
return false
}
submiting = true
program.submit({
aggregator,
oracle,
submission: BigInt(parseInt((json.price * 100) as any)),
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)
})
}

View File

@ -1,114 +0,0 @@
import dotenv from "dotenv"
import {
Wallet, solana, NetworkName, BPFLoader,
PublicKey, Deployer, Account,
} from "solray"
import { promises as fs } from "fs"
import { calculatePayfees, decodeAggregatorInfo, sleep } from "./utils"
import FluxAggregator, { AggregatorLayout } from "./FluxAggregator"
dotenv.config()
const { NETWORK, SOLANA_PAYER_MNEMONIC } = process.env
const args = process.argv.splice(2)
const cmd = args[0]
const params = args[1]
// so file path
const soPath = "../build/flux_aggregator.so"
async function main() {
if (!SOLANA_PAYER_MNEMONIC || !NETWORK) {
throw new Error("Config error.")
}
const conn = solana.connect(NETWORK as NetworkName)
const wallet = await Wallet.fromMnemonic(SOLANA_PAYER_MNEMONIC, conn)
console.log("using wallet", wallet.address)
let walletBalance = await conn.getBalance(wallet.pubkey)
console.log("wallet banalce:", walletBalance)
const deployer = await Deployer.open("deploy.json")
const programBinary = await fs.readFile(soPath)
let fees = await calculatePayfees(programBinary.length, conn)
const programAccount = await deployer.ensure("programAccount", async () => {
console.log("loading program... Network:", NETWORK)
if (walletBalance < fees) {
// throw new Error("Insufficient balance to pay fees");
// get airdrop
console.log("insufficient balance to pay fees, request airdrop...")
await conn.requestAirdrop(wallet.pubkey, fees)
await sleep(1000)
}
const bpfLoader = new BPFLoader(wallet)
const account = await bpfLoader.load(programBinary)
return account
})
console.log("program loaded:", programAccount.publicKey.toBase58())
const program = new FluxAggregator(wallet, programAccount.publicKey)
const aggregatorOwner = new Account()
console.log("initialize aggregator to owner:", aggregatorOwner.publicKey.toBase58())
walletBalance = await conn.getBalance(wallet.pubkey)
fees = await calculatePayfees(AggregatorLayout.span, conn)
if (walletBalance < fees) {
console.log("insufficient balance to pay fees, request airdrop...")
await conn.requestAirdrop(wallet.pubkey, fees)
await sleep(1000)
}
const aggregator = await program.initialize({
submitInterval: 6,
minSubmissionValue: BigInt(1),
maxSubmissionValue: BigInt(99999),
description: "ETH/USDT".padEnd(32),
owner: aggregatorOwner
})
console.log("aggregator initialized, pubkey:", aggregator.toBase58())
const oracleOwner = new Account()
console.log("add an oracle...")
const oracle = await program.addOracle({
owner: oracleOwner.publicKey,
description: "Solink".padEnd(32),
aggregator,
aggregatorOwner,
})
console.log("oracle added, pubkey:", oracle.toBase58(), ", owner: ", oracleOwner.publicKey.toBase58())
console.log("oracle submiting...")
await program.submit({
aggregator,
oracle,
submission: BigInt(123),
owner: oracleOwner,
})
console.log("submit success! get aggregator info...")
const accountInfo = await conn.getAccountInfo(aggregator)
console.log("aggregator info:", decodeAggregatorInfo(accountInfo))
}
main().catch(err => console.log({ err }))

View File

View File

@ -1,6 +1,6 @@
import { Connection, BpfLoader, PublicKey } from "@solana/web3.js"
import { AggregatorLayout, SubmissionLayout } from "./FluxAggregator"
import { AggregatorLayout, SubmissionLayout, OracleLayout } from "./FluxAggregator"
import { solana, Wallet, NetworkName } from "solray"
@ -92,6 +92,22 @@ export function decodeAggregatorInfo(accountInfo) {
}
}
export function decodeOracleInfo(accountInfo) {
const data = Buffer.from(accountInfo.data)
const oracle = OracleLayout.decode(data)
oracle.submission = oracle.submission.readBigUInt64LE().toString()
oracle.nextSubmitTime = oracle.nextSubmitTime.readBigUInt64LE().toString()
oracle.description = oracle.description.toString()
oracle.isInitialized = oracle.isInitialized != 0
oracle.withdrawable = oracle.withdrawable.readBigUInt64LE().toString()
oracle.aggregator = new PublicKey(oracle.aggregator).toBase58()
oracle.owner = new PublicKey(oracle.owner).toBase58()
return oracle
}
export async function connectTo(network: NetworkName): Promise<Connection> {
const conn = solana.connect(network as NetworkName)
return conn

View File

@ -0,0 +1 @@
{"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"}

View File

@ -0,0 +1 @@
{"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
src/wallets/payer.json Normal file
View File

@ -0,0 +1 @@
{"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"}