chore: pull monorepo changes (#111)
* Project import generated by Copybara. GitOrigin-RevId: 8a02570f92a45d5127c2437ea4ceb1867f1da725 * chore: Update pnpm lockfile --------- Co-authored-by: Copybara <copybara@example.com> Co-authored-by: gallynaut <gallynaut@users.noreply.github.com>
This commit is contained in:
parent
0b5e0911a1
commit
84c2abc1f7
|
@ -4227,9 +4227,9 @@ checksum = "ab16ced94dbd8a46c82fd81e3ed9a8727dac2977ea869d217bcc4ea1f122e81f"
|
|||
|
||||
[[package]]
|
||||
name = "switchboard-common"
|
||||
version = "0.8.5"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c5a03303674fb73b1dbe6cfc83aa8faa0e99264472a47177a1803f7475b318d1"
|
||||
checksum = "1ba2cc1d6055e0989756adc4f54fa1e27d684ce08d6164da23900e93ac0b13b6"
|
||||
dependencies = [
|
||||
"getrandom 0.2.10",
|
||||
"hex",
|
||||
|
@ -4249,7 +4249,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "switchboard-solana"
|
||||
version = "0.9.1"
|
||||
version = "0.8.4"
|
||||
dependencies = [
|
||||
"anchor-client",
|
||||
"anchor-lang",
|
||||
|
@ -4258,7 +4258,6 @@ dependencies = [
|
|||
"bytemuck",
|
||||
"chrono",
|
||||
"cron",
|
||||
"hex",
|
||||
"rust_decimal",
|
||||
"sgx-quote",
|
||||
"solana-address-lookup-table-program",
|
||||
|
@ -4266,7 +4265,6 @@ dependencies = [
|
|||
"solana-program",
|
||||
"superslice",
|
||||
"switchboard-common",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -17,5 +17,5 @@ cpi = ["no-entrypoint"]
|
|||
|
||||
[dependencies]
|
||||
# switchboard-solana = "0.8.4"
|
||||
switchboard-solana = { version = "0.9.1", path = "../../../rust/switchboard-solana" }
|
||||
switchboard-solana = { version = "0.8.4", path = "../../../rust/switchboard-solana" }
|
||||
bytemuck = "1.13.1"
|
||||
|
|
|
@ -8,11 +8,9 @@
|
|||
"directory": "examples/feeds/01_feed_client"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint --ext .js,.json,.ts 'src/**' --fix",
|
||||
"build:old": "node ../build.js anchor-vrf-parser 4hfkS97f5P6zauWeJF5dcZDFaJwYwbMPvKrANVAuKN4p",
|
||||
"build": "anchor build",
|
||||
"test": "npm run localnet && npm run network:create && npm run network:start & sleep 60 && anchor test --skip-local-validator",
|
||||
"test:dev": "npm run localnet && npm run network:create && npm run network:start:dev & sleep 15 && anchor test --skip-local-validator"
|
||||
"build:cargo": "anchor build",
|
||||
"fix": "cargo fmt && pnpm exec prettier ./tests/*.ts -w",
|
||||
"clean": "pnpm exec rimraf node_modules .anchor .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coral-xyz/anchor": "^0.28.0",
|
||||
|
@ -20,9 +18,9 @@
|
|||
"@project-serum/borsh": "^0.2.5",
|
||||
"@solana/spl-token": "^0.3.6",
|
||||
"@solana/web3.js": "^1.77.3",
|
||||
"@switchboard-xyz/common": "^2.2.4",
|
||||
"@switchboard-xyz/oracle": "^2.1.13",
|
||||
"@switchboard-xyz/solana.js": "workspace:*",
|
||||
"@switchboard-xyz/common": "latest",
|
||||
"@switchboard-xyz/oracle": "latest",
|
||||
"@switchboard-xyz/solana.js": "latest",
|
||||
"chalk": "^4.1.2",
|
||||
"dotenv": "^16.0.1",
|
||||
"yargs": "^17.5.1"
|
||||
|
@ -30,18 +28,10 @@
|
|||
"devDependencies": {
|
||||
"@types/chai": "^4.3.0",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/node": "^17.0.45",
|
||||
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
||||
"@typescript-eslint/parser": "^5.44.0",
|
||||
"chai": "^4.3.6",
|
||||
"eslint": "^8.28.0",
|
||||
"mocha": "^9.0.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prettier": "^2.4.1",
|
||||
"prettier-plugin-organize-imports": "^2.3.4",
|
||||
"shx": "^0.3.4",
|
||||
"ts-mocha": "^9.0.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.3"
|
||||
"ts-mocha": "^9.0.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,14 +5,8 @@
|
|||
}
|
||||
},
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"mocha",
|
||||
"chai",
|
||||
"node"
|
||||
],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
],
|
||||
"types": ["mocha", "chai", "node"],
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
"module": "commonjs",
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
|
@ -20,23 +14,14 @@
|
|||
"strictNullChecks": false,
|
||||
"target": "es6",
|
||||
"paths": {
|
||||
"@switchboard-xyz/solana.js": [
|
||||
"../../../javascript/solana.js"
|
||||
]
|
||||
"@switchboard-xyz/solana.js": ["../../../javascript/solana.js"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"tests/**/*",
|
||||
"./cli.ts",
|
||||
"./client/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"target",
|
||||
"lib"
|
||||
],
|
||||
"include": ["tests/**/*", "./cli.ts", "./client/**/*"],
|
||||
"exclude": ["target", "lib"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../javascript/solana.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,4 +12,4 @@ no-entrypoint = []
|
|||
|
||||
[dependencies]
|
||||
# switchboard-solana = "0.8.4"
|
||||
switchboard-solana = { version = "0.9.1", path = "../../../rust/switchboard-solana" }
|
||||
switchboard-solana = { version = "0.8.4", path = "../../../rust/switchboard-solana" }
|
||||
|
|
|
@ -8,25 +8,24 @@
|
|||
"directory": "programs/native-feed-parser"
|
||||
},
|
||||
"scripts": {
|
||||
"build:bpf": "cargo-build-sbf",
|
||||
"build:cargo": "cargo-build-sbf",
|
||||
"fix": "cargo fmt && pnpm exec prettier ./tests/*.ts -w",
|
||||
"clean": "pnpm exec rimraf node_modules .anchor .turbo",
|
||||
"deploy": "solana program deploy target/deploy/native_feed_parser.so",
|
||||
"test": "echo \"For workspace native-feed-parser, use the anchor:test script\" && exit 0"
|
||||
"test:program": "echo \"For workspace native-feed-parser, use the anchor:test script\" && exit 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coral-xyz/anchor": "^0.28.0",
|
||||
"@solana/web3.js": "^1.77.3",
|
||||
"@switchboard-xyz/common": "^2.2.4",
|
||||
"@switchboard-xyz/oracle": "^2.1.13",
|
||||
"@switchboard-xyz/solana.js": "workspace:*"
|
||||
"@switchboard-xyz/common": "latest",
|
||||
"@switchboard-xyz/oracle": "latest",
|
||||
"@switchboard-xyz/solana.js": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^4.3.0",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/node": "^17.0.45",
|
||||
"chai": "^4.3.6",
|
||||
"mocha": "^9.0.3",
|
||||
"ts-mocha": "^9.0.2",
|
||||
"ts-node": "^10.4.0",
|
||||
"typescript": "^4.7"
|
||||
"ts-mocha": "^9.0.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +1,20 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"types": [
|
||||
"mocha",
|
||||
"chai"
|
||||
],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
],
|
||||
"lib": [
|
||||
"es2015"
|
||||
],
|
||||
"types": ["mocha", "chai"],
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
"lib": ["es2015"],
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"esModuleInterop": true,
|
||||
"noEmit": true,
|
||||
"paths": {
|
||||
"@switchboard-xyz/solana.js": [
|
||||
"../../../javascript/solana.js"
|
||||
]
|
||||
"@switchboard-xyz/solana.js": ["../../../javascript/solana.js"]
|
||||
}
|
||||
},
|
||||
"exclude": [
|
||||
"target"
|
||||
],
|
||||
"exclude": ["target"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../javascript/solana.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,4 +3,8 @@ Makefile
|
|||
README.md
|
||||
node_modules
|
||||
.turbo
|
||||
measurement.txt
|
||||
measurement.txt
|
||||
.anchor
|
||||
scripts
|
||||
.prettierignore
|
||||
.gitignore
|
||||
|
|
|
@ -6,14 +6,18 @@ seeds = false
|
|||
skip-lint = false
|
||||
|
||||
[programs.localnet]
|
||||
basic_oracle = "8GHcguBXZEfKaLxRrBvis7LqPcFjBszv4ZYBgKwCUipS"
|
||||
basic_oracle = "BkTMjFhosJ1xKtLMV2xchGtnTDBABLJ45aXzs7x9FdeX"
|
||||
|
||||
[programs.devnet]
|
||||
basic_oracle = "BkTMjFhosJ1xKtLMV2xchGtnTDBABLJ45aXzs7x9FdeX"
|
||||
|
||||
[provider]
|
||||
# cluster = "https://switchbo-switchbo-6225.devnet.rpcpool.com/f6fb9f02-0777-498b-b8f5-67cbb1fc0d14"
|
||||
# cluster = "https://api.devnet.solana.com"
|
||||
# wallet = "~/switchboard_environments_v2/devnet/upgrade_authority/upgrade_authority.json"
|
||||
cluster = "Localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
# cluster = "devnet"
|
||||
[scripts]
|
||||
test = "pnpm exec ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
|
||||
|
||||
|
@ -42,4 +46,4 @@ address = "sbattyXrzedoNATfc4L31wC9Mhxsi1BmFhTiN8gDshx"
|
|||
address = "5ExuoQR69trmKQfB95fDsUGsUrrChbGq9PFgt8qouncz"
|
||||
|
||||
[[test.validator.clone]] # sb devnet attestation State
|
||||
address = "BzqtGXZPiDSinP4xMFgPf6FLgSa6iPufK4m4JJFgMnTK"
|
||||
address = "5MFs7RGTjLi1wtKNBFRtuLipCkkjs4YQwRRU9sjnbQbS"
|
||||
|
|
|
@ -18,7 +18,7 @@ default = []
|
|||
|
||||
[dependencies]
|
||||
switchboard-solana = { version = "=0.9.1" }
|
||||
# switchboard-solana = { version = "0.9.1", path = "../../../../../rust/switchboard-solana" }
|
||||
# switchboard-solana = { version = "0.9.1", path = "../../../rust/switchboard-solana" }
|
||||
bytemuck = "^1"
|
||||
anchor-lang = { version = "0.28.0", features = [
|
||||
"init-if-needed",
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<div align="center">
|
||||
|
||||
![Switchboard Logo](https://github.com/switchboard-xyz/sbv2-core/raw/main/website/static/img/icons/switchboard/avatar.png)
|
||||
|
||||
# anchor-vrf-lite-parser
|
||||
|
||||
> An example program written in Anchor demonstrating how to integrate Switchboard Functions and verify attestation on-chain.
|
||||
|
||||
[![Anchor Test Status](https://github.com/switchboard-xyz/sbv2-solana/actions/workflows/anchor-test.yml/badge.svg)](https://github.com/switchboard-xyz/sbv2-solana/actions/workflows/anchor-test.yml)
|
||||
|
||||
</div>
|
||||
|
||||
<!-- install -->
|
||||
|
||||
<!-- installstop -->
|
||||
|
||||
## Usage
|
||||
|
||||
Build the example program
|
||||
|
||||
```bash
|
||||
anchor build
|
||||
```
|
||||
|
||||
Get your program ID and update `Anchor.toml` and `src/lib.rs` with your pubkey
|
||||
|
||||
```bash
|
||||
export ANCHOR_VRF_LITE_PARSER_PUBKEY=$(solana-keygen pubkey target/deploy/anchor_vrf_lite_parser-keypair.json)
|
||||
sed -i '' s/5Hhm5xKDiThfidbpqjJpKmMJEcKmjj5tEUNFpi2DzSvb/"$ANCHOR_VRF_LITE_PARSER_PUBKEY"/g Anchor.toml
|
||||
sed -i '' s/5Hhm5xKDiThfidbpqjJpKmMJEcKmjj5tEUNFpi2DzSvb/"$ANCHOR_VRF_LITE_PARSER_PUBKEY"/g src/lib.rs
|
||||
```
|
||||
|
||||
Then run Anchor test
|
||||
|
||||
```bash
|
||||
anchor test
|
||||
```
|
File diff suppressed because it is too large
Load Diff
|
@ -1,3 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
curl https://api.binance.com/api/v3/exchangeInfo
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "basic-oracle",
|
||||
"name": "solana-basic-oracle",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -7,16 +7,17 @@
|
|||
"directory": "examples/functions/01_basic_oracle"
|
||||
},
|
||||
"scripts": {
|
||||
"build:anchor": "anchor build",
|
||||
"clean": "pnpm exec rimraf node_modules .anchor lib .turbo"
|
||||
"build:cargo": "anchor build",
|
||||
"fix": "cargo fmt && pnpm exec prettier ./tests/*.ts -w",
|
||||
"clean": "pnpm exec rimraf node_modules .anchor .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coral-xyz/anchor": "^0.28.0",
|
||||
"@solana/spl-token": "^0.3.6",
|
||||
"@solana/web3.js": "^1.78.0",
|
||||
"@switchboard-xyz/common": "*",
|
||||
"@switchboard-xyz/oracle": "*",
|
||||
"@switchboard-xyz/solana.js": "workspace:*"
|
||||
"@switchboard-xyz/common": "latest",
|
||||
"@switchboard-xyz/oracle": "latest",
|
||||
"@switchboard-xyz/solana.js": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bn.js": "^5.1.0",
|
||||
|
|
|
@ -18,4 +18,4 @@ target/
|
|||
|
||||
/target
|
||||
|
||||
measurement.txt
|
||||
*measurement.txt
|
File diff suppressed because it is too large
Load Diff
|
@ -14,4 +14,4 @@ futures = "0.3"
|
|||
serde = "^1"
|
||||
serde_json = "^1"
|
||||
switchboard-utils = { version = "0.8.0" }
|
||||
switchboard-solana = { version = "=0.9.1" }
|
||||
switchboard-solana = { version = "0.9.1", path = "../../../rust/switchboard-solana" }
|
||||
|
|
|
@ -1,19 +1,25 @@
|
|||
# syntax=docker/dockerfile:1.4
|
||||
FROM switchboardlabs/sgx-function AS builder
|
||||
FROM switchboardlabs/sgx-function:main AS builder
|
||||
|
||||
ARG CARGO_NAME=switchboard-function
|
||||
ENV CARGO_NAME=$CARGO_NAME
|
||||
|
||||
WORKDIR /home/root/switchboard-function
|
||||
COPY . .
|
||||
COPY ./Anchor.toml ./Cargo.lock ./Cargo.toml ./
|
||||
COPY ./src ./src
|
||||
|
||||
WORKDIR /home/root/switchboard-function/sgx-function
|
||||
COPY ./sgx-function/Cargo.lock ./sgx-function/Cargo.toml ./
|
||||
COPY ./sgx-function/src ./src
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry --mount=type=cache,target=/home/root/switchboard-function/sgx-function/target \
|
||||
RUN --mount=target=/home/root/.cargo/git,type=cache \
|
||||
--mount=target=/home/root/.cargo/registry,type=cache \
|
||||
--mount=type=cache,target=/home/root/switchboard-function/sgx-function/target \
|
||||
cargo build --release && \
|
||||
cargo strip && \
|
||||
mv target/release/basic-oracle-function /sgx/app
|
||||
|
||||
FROM switchboardlabs/sgx-function
|
||||
FROM switchboardlabs/sgx-function:main
|
||||
|
||||
# Copy the binary
|
||||
WORKDIR /sgx
|
||||
|
@ -22,7 +28,4 @@ COPY --from=builder /sgx/app /sgx
|
|||
# Get the measurement from the enclave
|
||||
RUN /get_measurement.sh
|
||||
|
||||
RUN cp /app.manifest.sgx /sgx/app.manifest.sgx
|
||||
RUN cp /app.sig /sgx/app.sig
|
||||
|
||||
ENTRYPOINT ["bash", "/boot.sh"]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
# Variables
|
||||
CARGO_NAME=basic-oracle-function
|
||||
DOCKER_IMAGE_NAME=switchboardlabs/basic-oracle-function
|
||||
DOCKER_IMAGE_NAME=gallynaut/binance-oracle
|
||||
|
||||
DOCKER_BUILD_COMMAND=DOCKER_BUILDKIT=1 docker buildx build --platform linux/amd64 --build-arg CARGO_NAME=${CARGO_NAME}
|
||||
|
||||
|
@ -10,14 +10,14 @@ DOCKER_BUILD_COMMAND=DOCKER_BUILDKIT=1 docker buildx build --platform linux/amd6
|
|||
all: build
|
||||
|
||||
docker_build:
|
||||
${DOCKER_BUILD_COMMAND} -f Dockerfile -t ${DOCKER_IMAGE_NAME} --load ../
|
||||
${DOCKER_BUILD_COMMAND} --pull -f Dockerfile -t ${DOCKER_IMAGE_NAME}:dev --load ../
|
||||
docker_publish:
|
||||
${DOCKER_BUILD_COMMAND} -f Dockerfile -t ${DOCKER_IMAGE_NAME} --push ../
|
||||
${DOCKER_BUILD_COMMAND} --pull -f Dockerfile -t ${DOCKER_IMAGE_NAME} --push ../
|
||||
|
||||
dev_docker_build:
|
||||
${DOCKER_BUILD_COMMAND} -f Dockerfile.dev -t ${DOCKER_IMAGE_NAME} --load ../../../../
|
||||
${DOCKER_BUILD_COMMAND} --pull -f Dockerfile.dev -t ${DOCKER_IMAGE_NAME}:dev --load ../../../../
|
||||
dev_docker_publish:
|
||||
${DOCKER_BUILD_COMMAND} -f Dockerfile.dev -t ${DOCKER_IMAGE_NAME} --push ../../../../
|
||||
${DOCKER_BUILD_COMMAND} --pull -f Dockerfile.dev -t ${DOCKER_IMAGE_NAME} --push ../../../../
|
||||
|
||||
build: docker_build measurement
|
||||
|
||||
|
@ -25,12 +25,22 @@ dev: dev_docker_build measurement
|
|||
|
||||
publish: docker_publish measurement
|
||||
|
||||
latest_mr_enclave:
|
||||
@docker run -d --pull=always --platform=linux/amd64 --name=latest-my-switchboard-function ${DOCKER_IMAGE_NAME}:latest > /dev/null
|
||||
@docker cp latest-my-switchboard-function:/measurement.txt latest-measurement.txt
|
||||
@docker stop latest-my-switchboard-function > /dev/null
|
||||
@docker rm latest-my-switchboard-function > /dev/null
|
||||
echo "latest MrEnclave: $(cat measurement.txt)"
|
||||
|
||||
measurement:
|
||||
@docker run -d --name my-switchboard-function $(DOCKER_IMAGE_NAME) > /dev/null
|
||||
@docker run -d --platform=linux/amd64 --name=my-switchboard-function ${DOCKER_IMAGE_NAME}:dev > /dev/null
|
||||
@docker cp my-switchboard-function:/measurement.txt measurement.txt
|
||||
@docker stop my-switchboard-function > /dev/null
|
||||
@docker rm my-switchboard-function > /dev/null
|
||||
|
||||
simulate: docker_build
|
||||
docker run -it --platform=linux/amd64 --entrypoint=/bin/bash ${DOCKER_IMAGE_NAME}:dev /boot.sh --test
|
||||
|
||||
# Task to clean up the compiled rust application
|
||||
clean:
|
||||
cargo clean
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
{
|
||||
"name": "basic-oracle-function",
|
||||
"scripts": {
|
||||
"build:function": "make"
|
||||
"build:cargo": "make",
|
||||
"fix": "cargo fmt"
|
||||
},
|
||||
"dependencies": {
|
||||
"basic-oracle": "workspace:*"
|
||||
"solana-basic-oracle": "latest"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@ use crate::*;
|
|||
|
||||
pub use switchboard_utils::reqwest;
|
||||
|
||||
use basic_oracle::{OracleDataBorsh, TradingSymbol};
|
||||
use serde::Deserialize;
|
||||
use basic_oracle::{TradingSymbol, OracleDataBorsh};
|
||||
|
||||
const ONE: i128 = 1000000000;
|
||||
|
||||
|
@ -153,12 +153,12 @@ impl Binance {
|
|||
data: self.eth_usdt.clone().into(),
|
||||
},
|
||||
// OracleDataWithTradingSymbol {
|
||||
// symbol: TradingSymbol::Sol,
|
||||
// data: self.sol_usdt.clone().into(),
|
||||
// symbol: TradingSymbol::Sol,
|
||||
// data: self.sol_usdt.clone().into(),
|
||||
// },
|
||||
// OracleDataWithTradingSymbol {
|
||||
// symbol: TradingSymbol::Doge,
|
||||
// data: self.doge_usdt.clone().into(),
|
||||
// symbol: TradingSymbol::Doge,
|
||||
// data: self.doge_usdt.clone().into(),
|
||||
// },
|
||||
];
|
||||
|
||||
|
@ -189,7 +189,11 @@ impl Binance {
|
|||
is_writable: false,
|
||||
},
|
||||
],
|
||||
data: [ix_discriminator("refresh_oracles").to_vec(), params.try_to_vec().unwrap()].concat(),
|
||||
data: [
|
||||
ix_discriminator("refresh_oracles").to_vec(),
|
||||
params.try_to_vec().unwrap(),
|
||||
]
|
||||
.concat(),
|
||||
};
|
||||
|
||||
vec![ixn]
|
||||
|
@ -201,4 +205,4 @@ pub fn parse_string_value(value: &str) -> i128 {
|
|||
let f64_value = value.parse::<f64>().unwrap();
|
||||
let sb_decimal = SwitchboardDecimal::from_f64(f64_value);
|
||||
sb_decimal.scale_to(9)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,4 +50,4 @@ impl Initialize<'_> {
|
|||
oracle.bump = *ctx.bumps.get("oracle").unwrap();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,4 +8,4 @@ pub mod set_function;
|
|||
pub use set_function::*;
|
||||
|
||||
pub mod trigger_function;
|
||||
pub use trigger_function::*;
|
||||
pub use trigger_function::*;
|
||||
|
|
|
@ -45,4 +45,4 @@ impl RefreshPrices<'_> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ pub struct SetFunction<'info> {
|
|||
}
|
||||
|
||||
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct SetFunctionParams { }
|
||||
pub struct SetFunctionParams {}
|
||||
|
||||
impl SetFunction<'_> {
|
||||
pub fn validate(
|
||||
|
@ -34,4 +34,4 @@ impl SetFunction<'_> {
|
|||
program.function = ctx.accounts.function.key();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ pub struct TriggerFunction<'info> {
|
|||
}
|
||||
|
||||
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct TriggerFunctionParams { }
|
||||
pub struct TriggerFunctionParams {}
|
||||
|
||||
impl TriggerFunction<'_> {
|
||||
pub fn validate(
|
||||
|
@ -32,12 +32,16 @@ impl TriggerFunction<'_> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn actuate(ctx: &Context<Self>, _params: &TriggerFunctionParams) -> anchor_lang::Result<()> {
|
||||
pub fn actuate(
|
||||
ctx: &Context<Self>,
|
||||
_params: &TriggerFunctionParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
FunctionTrigger {
|
||||
function: ctx.accounts.function.clone(),
|
||||
authority: ctx.accounts.authority.clone(),
|
||||
attestation_queue: ctx.accounts.attestation_queue.clone(),
|
||||
}.invoke(ctx.accounts.attestation_program.clone())?;
|
||||
}
|
||||
.invoke(ctx.accounts.attestation_program.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
pub use switchboard_solana::prelude::*;
|
||||
|
||||
pub mod actions;
|
||||
pub use actions::*;
|
||||
pub use switchboard_solana::prelude::*;
|
||||
declare_id!("EF68PJkRqQu2VthTSy19kg6TWynMtRmLpxcMDKEdLC8t");
|
||||
|
||||
pub mod error;
|
||||
pub use error::*;
|
||||
|
@ -12,7 +12,8 @@ pub use model::*;
|
|||
pub mod utils;
|
||||
pub use utils::*;
|
||||
|
||||
|
||||
// IDL 51F8RoK1RcduTxD8KsFGn4LUuHFnPTCf2PdAF5qEYoMU
|
||||
declare_id!("BkTMjFhosJ1xKtLMV2xchGtnTDBABLJ45aXzs7x9FdeX");
|
||||
|
||||
pub const PROGRAM_SEED: &[u8] = b"BASICORACLE";
|
||||
|
||||
|
@ -53,4 +54,4 @@ pub mod basic_oracle {
|
|||
) -> anchor_lang::Result<()> {
|
||||
TriggerFunction::actuate(&ctx, ¶ms)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
target/
|
||||
Makefile
|
||||
README.md
|
||||
node_modules
|
||||
.turbo
|
||||
measurement.txt
|
||||
.anchor
|
||||
scripts
|
||||
.prettierignore
|
||||
.gitignore
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
.anchor
|
||||
.DS_Store
|
||||
target
|
||||
**/*.rs.bk
|
||||
node_modules
|
||||
test-ledger
|
||||
.yarn
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
.anchor
|
||||
.DS_Store
|
||||
target
|
||||
node_modules
|
||||
dist
|
||||
build
|
||||
test-ledger
|
|
@ -0,0 +1,49 @@
|
|||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[features]
|
||||
seeds = false
|
||||
skip-lint = false
|
||||
|
||||
[programs.localnet]
|
||||
basic_oracle = "BkTMjFhosJ1xKtLMV2xchGtnTDBABLJ45aXzs7x9FdeX"
|
||||
|
||||
[programs.devnet]
|
||||
basic_oracle = "BkTMjFhosJ1xKtLMV2xchGtnTDBABLJ45aXzs7x9FdeX"
|
||||
|
||||
[provider]
|
||||
# cluster = "https://api.devnet.solana.com"
|
||||
# wallet = "~/switchboard_environments_v2/devnet/upgrade_authority/upgrade_authority.json"
|
||||
cluster = "Localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
# cluster = "devnet"
|
||||
[scripts]
|
||||
test = "pnpm exec ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
|
||||
|
||||
[test]
|
||||
startup_wait = 15000
|
||||
|
||||
[test.validator]
|
||||
url = "https://api.devnet.solana.com"
|
||||
|
||||
[[test.validator.clone]] # sb devnet oracle programID
|
||||
address = "SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f"
|
||||
|
||||
[[test.validator.clone]] # sb devnet oracle IDL
|
||||
address = "Fi8vncGpNKbq62gPo56G4toCehWNy77GgqGkTaAF5Lkk"
|
||||
|
||||
[[test.validator.clone]] # sb devnet oracle SbState
|
||||
address = "CyZuD7RPDcrqCGbNvLCyqk6Py9cEZTKmNKujfPi3ynDd"
|
||||
|
||||
[[test.validator.clone]] # sb devnet oracle tokenVault
|
||||
address = "7hkp1xfPBcD2t1vZMoWWQPzipHVcXeLAAaiGXdPSfDie"
|
||||
|
||||
[[test.validator.clone]] # sb devnet attestation programID
|
||||
address = "sbattyXrzedoNATfc4L31wC9Mhxsi1BmFhTiN8gDshx"
|
||||
|
||||
[[test.validator.clone]] # sb devnet attestation IDL
|
||||
address = "5ExuoQR69trmKQfB95fDsUGsUrrChbGq9PFgt8qouncz"
|
||||
|
||||
[[test.validator.clone]] # sb devnet attestation State
|
||||
address = "BzqtGXZPiDSinP4xMFgPf6FLgSa6iPufK4m4JJFgMnTK"
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "basic_oracle"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "basic_oracle"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
switchboard-solana = { version = "=0.9.1" }
|
||||
# switchboard-solana = { version = "0.9.1", path = "../../../rust/switchboard-solana" }
|
||||
bytemuck = "^1"
|
||||
anchor-lang = { version = "0.28.0", features = [
|
||||
"init-if-needed",
|
||||
"allow-missing-optionals"
|
||||
] }
|
|
@ -0,0 +1,37 @@
|
|||
<div align="center">
|
||||
|
||||
![Switchboard Logo](https://github.com/switchboard-xyz/sbv2-core/raw/main/website/static/img/icons/switchboard/avatar.png)
|
||||
|
||||
# anchor-vrf-lite-parser
|
||||
|
||||
> An example program written in Anchor demonstrating how to integrate Switchboard Functions and verify attestation on-chain.
|
||||
|
||||
[![Anchor Test Status](https://github.com/switchboard-xyz/sbv2-solana/actions/workflows/anchor-test.yml/badge.svg)](https://github.com/switchboard-xyz/sbv2-solana/actions/workflows/anchor-test.yml)
|
||||
|
||||
</div>
|
||||
|
||||
<!-- install -->
|
||||
|
||||
<!-- installstop -->
|
||||
|
||||
## Usage
|
||||
|
||||
Build the example program
|
||||
|
||||
```bash
|
||||
anchor build
|
||||
```
|
||||
|
||||
Get your program ID and update `Anchor.toml` and `src/lib.rs` with your pubkey
|
||||
|
||||
```bash
|
||||
export ANCHOR_VRF_LITE_PARSER_PUBKEY=$(solana-keygen pubkey target/deploy/anchor_vrf_lite_parser-keypair.json)
|
||||
sed -i '' s/5Hhm5xKDiThfidbpqjJpKmMJEcKmjj5tEUNFpi2DzSvb/"$ANCHOR_VRF_LITE_PARSER_PUBKEY"/g Anchor.toml
|
||||
sed -i '' s/5Hhm5xKDiThfidbpqjJpKmMJEcKmjj5tEUNFpi2DzSvb/"$ANCHOR_VRF_LITE_PARSER_PUBKEY"/g src/lib.rs
|
||||
```
|
||||
|
||||
Then run Anchor test
|
||||
|
||||
```bash
|
||||
anchor test
|
||||
```
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "solana-liquidity-oracle",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/switchboard-xyz/sbv2-solana",
|
||||
"directory": "examples/functions/01_basic_oracle"
|
||||
},
|
||||
"scripts": {
|
||||
"build:anchor": "anchor build",
|
||||
"clean": "pnpm exec rimraf node_modules .anchor lib .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coral-xyz/anchor": "^0.28.0",
|
||||
"@solana/spl-token": "^0.3.6",
|
||||
"@solana/web3.js": "^1.78.0",
|
||||
"@switchboard-xyz/common": "latest",
|
||||
"@switchboard-xyz/oracle": "latest",
|
||||
"@switchboard-xyz/solana.js": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bn.js": "^5.1.0",
|
||||
"@types/chai": "^4.3.0",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/node": "^20.4.0",
|
||||
"chai": "^4.3.4",
|
||||
"mocha": "^9.0.3",
|
||||
"ts-mocha": "^10.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env tsx
|
||||
import * as anchor from "@coral-xyz/anchor";
|
||||
import { BasicOracle } from "../target/types/basic_oracle";
|
||||
|
||||
async function main() {
|
||||
const program = anchor.workspace.BasicOracle as anchor.Program<BasicOracle>;
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
const getStringArg = (arg: string): string => {
|
||||
const args = process.argv.slice(2);
|
||||
const argIdx = args.findIndex((v) => v === arg || v === `--${arg}`);
|
||||
if (argIdx === -1) {
|
||||
return "";
|
||||
}
|
||||
if (argIdx + 1 > args.length) {
|
||||
throw new Error(`Failed to find arg`);
|
||||
}
|
||||
return args[argIdx + 1];
|
||||
};
|
||||
|
||||
const getFlag = (arg: string): boolean => {
|
||||
const args = process.argv.slice(2);
|
||||
const argIdx = args.findIndex((v) => v === arg || v === `--${arg}`);
|
||||
if (argIdx === -1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
|
@ -0,0 +1,52 @@
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
import { BasicOracle } from "../target/types/basic_oracle";
|
||||
import {
|
||||
Connection,
|
||||
PublicKey,
|
||||
Keypair,
|
||||
sendAndConfirmRawTransaction,
|
||||
SystemProgram,
|
||||
LAMPORTS_PER_SOL,
|
||||
} from "@solana/web3.js";
|
||||
|
||||
(async function main() {
|
||||
const walletKeypair = Keypair.fromSecretKey(
|
||||
new Uint8Array(
|
||||
JSON.parse(require("fs").readFileSync("<YOUR_KEYPAIR_FILE>", "utf8"))
|
||||
)
|
||||
);
|
||||
const programId = new PublicKey(
|
||||
"EF68PJkRqQu2VthTSy19kg6TWynMtRmLpxcMDKEdLC8t"
|
||||
);
|
||||
const commitment = "processed";
|
||||
const connection = new Connection("https://api.devnet.solana.com", {
|
||||
commitment,
|
||||
});
|
||||
const wallet = new anchor.Wallet(walletKeypair);
|
||||
const provider = new anchor.AnchorProvider(connection, wallet, {
|
||||
commitment,
|
||||
preflightCommitment: commitment,
|
||||
});
|
||||
const idl = await anchor.Program.fetchIdl(programId, provider);
|
||||
const program = new anchor.Program(idl!, programId!, provider!);
|
||||
const [state] = PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("BASICORACLE")],
|
||||
program.programId
|
||||
);
|
||||
const [oracle] = PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("ORACLE_V1_SEED")],
|
||||
program.programId
|
||||
);
|
||||
const initSig = await program.methods
|
||||
.initialize({})
|
||||
.accounts({
|
||||
program: state,
|
||||
oracle,
|
||||
authority: walletKeypair.publicKey,
|
||||
payer: walletKeypair.publicKey,
|
||||
systemProgram: SystemProgram.programId,
|
||||
})
|
||||
.signers([walletKeypair])
|
||||
.rpc();
|
||||
console.log(initSig);
|
||||
})();
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
# Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
|
||||
# Added by cargo
|
||||
|
||||
/target
|
||||
|
||||
*measurement.txt
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "basic-oracle-function"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "basic-oracle-function"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
basic_oracle = { path = "../", features = ["no-entrypoint"] }
|
||||
tokio = "^1"
|
||||
futures = "0.3"
|
||||
serde = "^1"
|
||||
serde_json = "^1"
|
||||
switchboard-utils = { version = "0.8.0" }
|
||||
switchboard-solana = { version = "=0.9.1" }
|
||||
bytemuck = "1.13.1"
|
||||
rust_decimal = "1.30.0"
|
|
@ -0,0 +1,31 @@
|
|||
# syntax=docker/dockerfile:1.4
|
||||
FROM switchboardlabs/sgx-function:main AS builder
|
||||
|
||||
ARG CARGO_NAME=switchboard-function
|
||||
ENV CARGO_NAME=$CARGO_NAME
|
||||
|
||||
WORKDIR /home/root/switchboard-function
|
||||
COPY ./Anchor.toml ./Cargo.lock ./Cargo.toml ./
|
||||
COPY ./src ./src
|
||||
|
||||
WORKDIR /home/root/switchboard-function/sgx-function
|
||||
COPY ./sgx-function/Cargo.lock ./sgx-function/Cargo.toml ./
|
||||
COPY ./sgx-function/src ./src
|
||||
|
||||
RUN --mount=target=/home/root/.cargo/git,type=cache \
|
||||
--mount=target=/home/root/.cargo/registry,type=cache \
|
||||
--mount=type=cache,target=/home/root/switchboard-function/sgx-function/target \
|
||||
cargo build --release && \
|
||||
cargo strip && \
|
||||
mv target/release/basic-oracle-function /sgx/app
|
||||
|
||||
FROM switchboardlabs/sgx-function:main
|
||||
|
||||
# Copy the binary
|
||||
WORKDIR /sgx
|
||||
COPY --from=builder /sgx/app /sgx
|
||||
|
||||
# Get the measurement from the enclave
|
||||
RUN /get_measurement.sh
|
||||
|
||||
ENTRYPOINT ["bash", "/boot.sh"]
|
|
@ -0,0 +1,44 @@
|
|||
# syntax=docker/dockerfile:1.4
|
||||
FROM switchboardlabs/sgx-function AS builder
|
||||
|
||||
ARG CARGO_NAME=switchboard-function
|
||||
|
||||
WORKDIR /home/root/solana-sdk
|
||||
|
||||
COPY ./rust/switchboard-solana/Cargo.toml \
|
||||
./rust/switchboard-solana/Cargo.lock \
|
||||
./rust/switchboard-solana/
|
||||
|
||||
COPY ./examples/functions/01_basic_oracle/Cargo.toml \
|
||||
./examples/functions/01_basic_oracle/Cargo.lock \
|
||||
./examples/functions/01_basic_oracle/
|
||||
|
||||
COPY ./examples/functions/01_basic_oracle/sgx-function/Cargo.toml \
|
||||
./examples/functions/01_basic_oracle/sgx-function/Cargo.lock \
|
||||
./examples/functions/01_basic_oracle/sgx-function/
|
||||
|
||||
COPY ./rust/switchboard-solana/src \
|
||||
./rust/switchboard-solana/src/
|
||||
|
||||
COPY ./examples/functions/01_basic_oracle/src \
|
||||
./examples/functions/01_basic_oracle/src/
|
||||
|
||||
COPY ./examples/functions/01_basic_oracle/sgx-function/src \
|
||||
./examples/functions/01_basic_oracle/sgx-function/src/
|
||||
|
||||
WORKDIR /home/root/solana-sdk/examples/functions/01_basic_oracle/sgx-function
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry,id=${TARGETPLATFORM} --mount=type=cache,target=target,id=${TARGETPLATFORM} \
|
||||
cargo build --release && \
|
||||
cargo strip && \
|
||||
mv /home/root/solana-sdk/examples/functions/01_basic_oracle/sgx-function/target/release/${CARGO_NAME} /sgx
|
||||
|
||||
FROM switchboardlabs/sgx-function
|
||||
|
||||
# Copy the binary
|
||||
WORKDIR /sgx
|
||||
COPY --from=builder /sgx/${CARGO_NAME} /sgx/app
|
||||
|
||||
# Get the measurement from the enclave
|
||||
RUN /get_measurement.sh
|
||||
ENTRYPOINT ["bash", "/boot.sh"]
|
|
@ -0,0 +1,46 @@
|
|||
.PHONY: build clean publish
|
||||
|
||||
# Variables
|
||||
CARGO_NAME=basic-oracle-function
|
||||
DOCKER_IMAGE_NAME=gallynaut/binance-oracle
|
||||
|
||||
DOCKER_BUILD_COMMAND=DOCKER_BUILDKIT=1 docker buildx build --platform linux/amd64 --build-arg CARGO_NAME=${CARGO_NAME}
|
||||
|
||||
# Default make task
|
||||
all: build
|
||||
|
||||
docker_build:
|
||||
${DOCKER_BUILD_COMMAND} --pull -f Dockerfile -t ${DOCKER_IMAGE_NAME}:dev --load ../
|
||||
docker_publish:
|
||||
${DOCKER_BUILD_COMMAND} --pull -f Dockerfile -t ${DOCKER_IMAGE_NAME} --push ../
|
||||
|
||||
dev_docker_build:
|
||||
${DOCKER_BUILD_COMMAND} --pull -f Dockerfile.dev -t ${DOCKER_IMAGE_NAME}:dev --load ../../../../
|
||||
dev_docker_publish:
|
||||
${DOCKER_BUILD_COMMAND} --pull -f Dockerfile.dev -t ${DOCKER_IMAGE_NAME} --push ../../../../
|
||||
|
||||
build: docker_build measurement
|
||||
|
||||
dev: dev_docker_build measurement
|
||||
|
||||
publish: docker_publish measurement
|
||||
|
||||
latest_mr_enclave:
|
||||
@docker run -d --pull=always --platform=linux/amd64 --name=latest-my-switchboard-function ${DOCKER_IMAGE_NAME}:latest > /dev/null
|
||||
@docker cp latest-my-switchboard-function:/measurement.txt latest-measurement.txt
|
||||
@docker stop latest-my-switchboard-function > /dev/null
|
||||
@docker rm latest-my-switchboard-function > /dev/null
|
||||
echo "latest MrEnclave: $(cat measurement.txt)"
|
||||
|
||||
measurement:
|
||||
@docker run -d --platform=linux/amd64 --name=my-switchboard-function ${DOCKER_IMAGE_NAME}:dev > /dev/null
|
||||
@docker cp my-switchboard-function:/measurement.txt measurement.txt
|
||||
@docker stop my-switchboard-function > /dev/null
|
||||
@docker rm my-switchboard-function > /dev/null
|
||||
|
||||
simulate: docker_build
|
||||
docker run -it --platform=linux/amd64 --entrypoint=/bin/bash ${DOCKER_IMAGE_NAME}:dev /boot.sh --test
|
||||
|
||||
# Task to clean up the compiled rust application
|
||||
clean:
|
||||
cargo clean
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "solana-liquidity-oracle-function",
|
||||
"scripts": {
|
||||
"build:function": "make"
|
||||
},
|
||||
"dependencies": {
|
||||
"solana-liquidity-oracle": "latest"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
// Note: Binance API requires a non-US IP address
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub use switchboard_utils::reqwest;
|
||||
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Deserialize, Default, Clone, Debug)]
|
||||
pub struct BinanceBook {
|
||||
pub bids: Vec<(String, String)>,
|
||||
pub asks: Vec<(String, String)>,
|
||||
}
|
||||
impl Into<NormalizedBook> for BinanceBook {
|
||||
fn into(self) -> NormalizedBook {
|
||||
let book = self;
|
||||
let mut res = NormalizedBook::default();
|
||||
for bid in book.bids.iter() {
|
||||
res.bids.push(NormalizedOrdersRow {
|
||||
price: Decimal::try_from(bid.0.as_str()).unwrap(),
|
||||
amount: Decimal::try_from(bid.1.as_str()).unwrap(),
|
||||
});
|
||||
}
|
||||
for ask in book.asks.iter() {
|
||||
res.asks.push(NormalizedOrdersRow {
|
||||
price: Decimal::try_from(ask.0.as_str()).unwrap(),
|
||||
amount: Decimal::try_from(ask.1.as_str()).unwrap(),
|
||||
});
|
||||
}
|
||||
res.price = res.bids[0]
|
||||
.price
|
||||
.checked_add(res.asks[0].price)
|
||||
.unwrap()
|
||||
.checked_div(2.into())
|
||||
.unwrap();
|
||||
res
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
// Note: Binance API requires a non-US IP address
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub use switchboard_utils::reqwest;
|
||||
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Deserialize, Default, Clone, Debug)]
|
||||
pub struct BitfinexOrdersRow {
|
||||
price: String,
|
||||
amount: String,
|
||||
timestamp: String,
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Deserialize, Default, Clone, Debug)]
|
||||
pub struct BitfinexBook {
|
||||
pub bids: Vec<BitfinexOrdersRow>,
|
||||
pub asks: Vec<BitfinexOrdersRow>,
|
||||
}
|
||||
impl Into<NormalizedBook> for BitfinexBook {
|
||||
fn into(self) -> NormalizedBook {
|
||||
let book = self;
|
||||
let mut res = NormalizedBook::default();
|
||||
for bid in book.bids.iter() {
|
||||
res.bids.push(NormalizedOrdersRow {
|
||||
price: Decimal::try_from(bid.price.as_str()).unwrap(),
|
||||
amount: Decimal::try_from(bid.amount.as_str()).unwrap(),
|
||||
});
|
||||
}
|
||||
for ask in book.asks.iter() {
|
||||
res.asks.push(NormalizedOrdersRow {
|
||||
price: Decimal::try_from(ask.price.as_str()).unwrap(),
|
||||
amount: Decimal::try_from(ask.amount.as_str()).unwrap(),
|
||||
});
|
||||
}
|
||||
res.price = res.bids[0]
|
||||
.price
|
||||
.checked_add(res.asks[0].price)
|
||||
.unwrap()
|
||||
.checked_div(2.into())
|
||||
.unwrap();
|
||||
res
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
// Note: Binance API requires a non-US IP address
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub use switchboard_utils::reqwest;
|
||||
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Deserialize, Default, Clone, Debug)]
|
||||
pub struct CoinbaseBook {
|
||||
pub bids: Vec<(String, String, i64)>,
|
||||
pub asks: Vec<(String, String, i64)>,
|
||||
}
|
||||
impl Into<NormalizedBook> for CoinbaseBook {
|
||||
fn into(self) -> NormalizedBook {
|
||||
let book = self;
|
||||
let mut res = NormalizedBook::default();
|
||||
for bid in book.bids.iter() {
|
||||
res.bids.push(NormalizedOrdersRow {
|
||||
price: Decimal::try_from(bid.0.as_str()).unwrap(),
|
||||
amount: Decimal::try_from(bid.1.as_str()).unwrap(),
|
||||
});
|
||||
}
|
||||
for ask in book.asks.iter() {
|
||||
res.asks.push(NormalizedOrdersRow {
|
||||
price: Decimal::try_from(ask.0.as_str()).unwrap(),
|
||||
amount: Decimal::try_from(ask.1.as_str()).unwrap(),
|
||||
});
|
||||
}
|
||||
res.price = res.bids[0]
|
||||
.price
|
||||
.checked_add(res.asks[0].price)
|
||||
.unwrap()
|
||||
.checked_div(2.into())
|
||||
.unwrap();
|
||||
res
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// Note: Binance API requires a non-US IP address
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub use switchboard_utils::reqwest;
|
||||
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Deserialize, Default, Clone, Debug)]
|
||||
pub struct KrakenBookInternal {
|
||||
pub bids: Vec<(String, String, i64)>,
|
||||
pub asks: Vec<(String, String, i64)>,
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Deserialize, Default, Clone, Debug)]
|
||||
pub struct KrakenBook {
|
||||
pub result: HashMap<String, KrakenBookInternal>,
|
||||
}
|
||||
impl Into<NormalizedBook> for KrakenBook {
|
||||
fn into(self) -> NormalizedBook {
|
||||
let book = self.result.values().next().unwrap();
|
||||
let mut res = NormalizedBook::default();
|
||||
for bid in book.bids.iter() {
|
||||
res.bids.push(NormalizedOrdersRow {
|
||||
price: Decimal::try_from(bid.0.as_str()).unwrap(),
|
||||
amount: Decimal::try_from(bid.1.as_str()).unwrap(),
|
||||
});
|
||||
}
|
||||
for ask in book.asks.iter() {
|
||||
res.asks.push(NormalizedOrdersRow {
|
||||
price: Decimal::try_from(ask.0.as_str()).unwrap(),
|
||||
amount: Decimal::try_from(ask.1.as_str()).unwrap(),
|
||||
});
|
||||
}
|
||||
res.price = res.bids[0]
|
||||
.price
|
||||
.checked_add(res.asks[0].price)
|
||||
.unwrap()
|
||||
.checked_div(2.into())
|
||||
.unwrap();
|
||||
res
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
pub mod coinbase;
|
||||
pub use coinbase::*;
|
||||
pub mod binance;
|
||||
pub use binance::*;
|
||||
pub mod bitfinex;
|
||||
pub use bitfinex::*;
|
||||
pub mod kraken;
|
||||
pub use kraken::*;
|
||||
pub use switchboard_solana::prelude::*;
|
||||
|
||||
use rust_decimal::Decimal;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
pub use switchboard_utils::reqwest;
|
||||
|
||||
pub use basic_oracle::{
|
||||
self, OracleData, OracleDataWithTradingSymbol, RefreshPrices, RefreshPricesParams,
|
||||
SwitchboardDecimal, TradingSymbol, ID as PROGRAM_ID,
|
||||
};
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Deserialize, Default, Clone, Debug)]
|
||||
pub struct NormalizedOrdersRow {
|
||||
price: Decimal,
|
||||
amount: Decimal,
|
||||
}
|
||||
#[allow(non_snake_case)]
|
||||
#[derive(Deserialize, Default, Clone, Debug)]
|
||||
pub struct NormalizedBook {
|
||||
pub bids: Vec<NormalizedOrdersRow>,
|
||||
pub asks: Vec<NormalizedOrdersRow>,
|
||||
pub price: Decimal,
|
||||
}
|
||||
|
||||
#[tokio::main(worker_threads = 12)]
|
||||
async fn main() {
|
||||
// First, initialize the runner instance with a freshly generated Gramine keypair
|
||||
let _runner: FunctionRunner = FunctionRunner::new_from_cluster(Cluster::Devnet, None).unwrap();
|
||||
|
||||
let _binance_book: NormalizedBook =
|
||||
reqwest::get("https://api.binance.com/api/v3/depth?symbol=BTCUSDT&limit=1000")
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<BinanceBook>()
|
||||
.await
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
let _coinbase_book: NormalizedBook =
|
||||
reqwest::get("https://api.pro.coinbase.com/products/BTC-USD/book?level=2")
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<CoinbaseBook>()
|
||||
.await
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
let _kraken_book: NormalizedBook =
|
||||
reqwest::get("https://api.kraken.com/0/public/Depth?pair=BTCUSD")
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<KrakenBook>()
|
||||
.await
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
let _bitfinex_book: NormalizedBook = reqwest::get("https://api.bitfinex.com/v1/book/btcusd")
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<BitfinexBook>()
|
||||
.await
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
// let ixs: Vec<Instruction> = vec![];
|
||||
// // Finally, emit the signed quote and partially signed transaction to the functionRunner oracle
|
||||
// // The functionRunner oracle will use the last outputted word to stdout as the serialized result. This is what gets executed on-chain.
|
||||
// runner.emit(ixs).await.unwrap();
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(params: InitializeParams)] // rpc parameters hint
|
||||
pub struct Initialize<'info> {
|
||||
#[account(
|
||||
init,
|
||||
space = 8 + std::mem::size_of::<MyProgramState>(),
|
||||
payer = payer,
|
||||
seeds = [PROGRAM_SEED],
|
||||
bump
|
||||
)]
|
||||
pub program: AccountLoader<'info, MyProgramState>,
|
||||
|
||||
#[account(
|
||||
init,
|
||||
space = 8 + std::mem::size_of::<MyOracleState>(),
|
||||
payer = payer,
|
||||
seeds = [ORACLE_SEED],
|
||||
bump
|
||||
)]
|
||||
pub oracle: AccountLoader<'info, MyOracleState>,
|
||||
|
||||
pub authority: Signer<'info>,
|
||||
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
|
||||
pub system_program: Program<'info, System>,
|
||||
}
|
||||
|
||||
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct InitializeParams {}
|
||||
|
||||
impl Initialize<'_> {
|
||||
pub fn validate(
|
||||
&self,
|
||||
_ctx: &Context<Self>,
|
||||
_params: &InitializeParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn actuate(ctx: &Context<Self>, _params: &InitializeParams) -> anchor_lang::Result<()> {
|
||||
let program = &mut ctx.accounts.program.load_init()?;
|
||||
program.bump = *ctx.bumps.get("program").unwrap();
|
||||
program.authority = ctx.accounts.authority.key();
|
||||
|
||||
let oracle = &mut ctx.accounts.oracle.load_init()?;
|
||||
oracle.bump = *ctx.bumps.get("oracle").unwrap();
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
pub mod initialize;
|
||||
pub use initialize::*;
|
||||
|
||||
pub mod refresh_prices;
|
||||
pub use refresh_prices::*;
|
||||
|
||||
pub mod set_function;
|
||||
pub use set_function::*;
|
||||
|
||||
pub mod trigger_function;
|
||||
pub use trigger_function::*;
|
|
@ -0,0 +1,48 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct RefreshPrices<'info> {
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [ORACLE_SEED],
|
||||
bump = oracle.load()?.bump
|
||||
)]
|
||||
pub oracle: AccountLoader<'info, MyOracleState>,
|
||||
|
||||
// We use this to verify the functions enclave state
|
||||
#[account(
|
||||
constraint =
|
||||
function.load()?.validate(
|
||||
&enclave_signer.to_account_info()
|
||||
)? @ BasicOracleError::FunctionValidationFailed
|
||||
// FunctionAccountData::validate(
|
||||
// &function.to_account_info(),
|
||||
// &enclave_signer.to_account_info()
|
||||
// )? @ BasicOracleError::FunctionValidationFailed
|
||||
)]
|
||||
pub function: AccountLoader<'info, FunctionAccountData>,
|
||||
pub enclave_signer: Signer<'info>,
|
||||
}
|
||||
|
||||
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct RefreshPricesParams {
|
||||
pub rows: Vec<OracleDataWithTradingSymbol>,
|
||||
}
|
||||
|
||||
impl RefreshPrices<'_> {
|
||||
pub fn validate(
|
||||
&self,
|
||||
_ctx: &Context<Self>,
|
||||
_params: &RefreshPricesParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn actuate(ctx: &Context<Self>, params: &RefreshPricesParams) -> anchor_lang::Result<()> {
|
||||
let oracle = &mut ctx.accounts.oracle.load_mut()?;
|
||||
msg!("saving oracle data");
|
||||
oracle.save_rows(¶ms.rows)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(params: SetFunctionParams)] // rpc parameters hint
|
||||
pub struct SetFunction<'info> {
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [PROGRAM_SEED],
|
||||
bump = program.load()?.bump,
|
||||
has_one = authority
|
||||
)]
|
||||
pub program: AccountLoader<'info, MyProgramState>,
|
||||
|
||||
pub function: AccountLoader<'info, FunctionAccountData>,
|
||||
|
||||
/// CHECK:
|
||||
pub authority: Signer<'info>,
|
||||
}
|
||||
|
||||
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct SetFunctionParams { }
|
||||
|
||||
impl SetFunction<'_> {
|
||||
pub fn validate(
|
||||
&self,
|
||||
_ctx: &Context<Self>,
|
||||
_params: &SetFunctionParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn actuate(ctx: &Context<Self>, _params: &SetFunctionParams) -> anchor_lang::Result<()> {
|
||||
let program = &mut ctx.accounts.program.load_init()?;
|
||||
program.function = ctx.accounts.function.key();
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
use crate::*;
|
||||
use switchboard_solana::attestation_program::instructions::function_trigger::FunctionTrigger;
|
||||
use switchboard_solana::SWITCHBOARD_ATTESTATION_PROGRAM_ID;
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(params: TriggerFunctionParams)] // rpc parameters hint
|
||||
pub struct TriggerFunction<'info> {
|
||||
#[account(
|
||||
has_one = authority,
|
||||
has_one = attestation_queue,
|
||||
)]
|
||||
pub function: AccountLoader<'info, FunctionAccountData>,
|
||||
|
||||
pub attestation_queue: AccountLoader<'info, AttestationQueueAccountData>,
|
||||
|
||||
pub authority: Signer<'info>,
|
||||
|
||||
/// CHECK: address is explicit
|
||||
#[account(address = SWITCHBOARD_ATTESTATION_PROGRAM_ID)]
|
||||
pub attestation_program: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct TriggerFunctionParams { }
|
||||
|
||||
impl TriggerFunction<'_> {
|
||||
pub fn validate(
|
||||
&self,
|
||||
_ctx: &Context<Self>,
|
||||
_params: &TriggerFunctionParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn actuate(ctx: &Context<Self>, _params: &TriggerFunctionParams) -> anchor_lang::Result<()> {
|
||||
FunctionTrigger {
|
||||
function: ctx.accounts.function.clone(),
|
||||
authority: ctx.accounts.authority.clone(),
|
||||
attestation_queue: ctx.accounts.attestation_queue.clone(),
|
||||
}.invoke(ctx.accounts.attestation_program.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
use crate::*;
|
||||
|
||||
#[error_code]
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub enum BasicOracleError {
|
||||
#[msg("Invalid authority account")]
|
||||
InvalidAuthority,
|
||||
#[msg("Array overflow")]
|
||||
ArrayOverflow,
|
||||
#[msg("Stale data")]
|
||||
StaleData,
|
||||
#[msg("Invalid trusted signer")]
|
||||
InvalidTrustedSigner,
|
||||
#[msg("Invalid MRENCLAVE")]
|
||||
InvalidMrEnclave,
|
||||
#[msg("Failed to find a valid trading symbol for this price")]
|
||||
InvalidSymbol,
|
||||
#[msg("FunctionAccount pubkey did not match program_state.function")]
|
||||
IncorrectSwitchboardFunction,
|
||||
#[msg("FunctionAccount pubkey did not match program_state.function")]
|
||||
InvalidSwitchboardFunction,
|
||||
#[msg("FunctionAccount was not validated successfully")]
|
||||
FunctionValidationFailed,
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
pub use switchboard_solana::prelude::*;
|
||||
|
||||
pub mod actions;
|
||||
pub use actions::*;
|
||||
|
||||
pub mod error;
|
||||
pub use error::*;
|
||||
|
||||
pub mod model;
|
||||
pub use model::*;
|
||||
|
||||
pub mod utils;
|
||||
pub use utils::*;
|
||||
|
||||
// IDL 51F8RoK1RcduTxD8KsFGn4LUuHFnPTCf2PdAF5qEYoMU
|
||||
declare_id!("BkTMjFhosJ1xKtLMV2xchGtnTDBABLJ45aXzs7x9FdeX");
|
||||
|
||||
pub const PROGRAM_SEED: &[u8] = b"BASICORACLE";
|
||||
|
||||
pub const ORACLE_SEED: &[u8] = b"ORACLE_V1_SEED";
|
||||
|
||||
#[program]
|
||||
pub mod basic_oracle {
|
||||
use super::*;
|
||||
|
||||
#[access_control(ctx.accounts.validate(&ctx, ¶ms))]
|
||||
pub fn initialize(
|
||||
ctx: Context<Initialize>,
|
||||
params: InitializeParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
Initialize::actuate(&ctx, ¶ms)
|
||||
}
|
||||
|
||||
#[access_control(ctx.accounts.validate(&ctx, ¶ms))]
|
||||
pub fn refresh_oracles(
|
||||
ctx: Context<RefreshPrices>,
|
||||
params: RefreshPricesParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
RefreshPrices::actuate(&ctx, ¶ms)
|
||||
}
|
||||
|
||||
#[access_control(ctx.accounts.validate(&ctx, ¶ms))]
|
||||
pub fn set_function(
|
||||
ctx: Context<SetFunction>,
|
||||
params: SetFunctionParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
SetFunction::actuate(&ctx, ¶ms)
|
||||
}
|
||||
|
||||
#[access_control(ctx.accounts.validate(&ctx, ¶ms))]
|
||||
pub fn trigger_function(
|
||||
ctx: Context<TriggerFunction>,
|
||||
params: TriggerFunctionParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
TriggerFunction::actuate(&ctx, ¶ms)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
use crate::*;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
#[account(zero_copy(unsafe))]
|
||||
pub struct MyProgramState {
|
||||
pub bump: u8,
|
||||
pub authority: Pubkey,
|
||||
pub function: Pubkey,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[zero_copy(unsafe)]
|
||||
pub struct OracleData {
|
||||
pub oracle_timestamp: i64,
|
||||
pub price: i128,
|
||||
pub volume_1hr: i128,
|
||||
pub volume_24hr: i128,
|
||||
pub twap_1hr: i128,
|
||||
pub twap_24hr: i128,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct OracleDataBorsh {
|
||||
pub oracle_timestamp: i64,
|
||||
pub price: i128,
|
||||
pub volume_1hr: i128,
|
||||
pub volume_24hr: i128,
|
||||
pub twap_1hr: i128,
|
||||
pub twap_24hr: i128,
|
||||
}
|
||||
impl From<OracleDataBorsh> for OracleData {
|
||||
fn from(value: OracleDataBorsh) -> Self {
|
||||
Self {
|
||||
oracle_timestamp: value.oracle_timestamp,
|
||||
price: value.price.clone(),
|
||||
volume_1hr: value.volume_1hr.clone(),
|
||||
volume_24hr: value.volume_24hr.clone(),
|
||||
twap_1hr: value.twap_1hr.clone(),
|
||||
twap_24hr: value.twap_24hr.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct OracleDataWithTradingSymbol {
|
||||
pub symbol: TradingSymbol,
|
||||
pub data: OracleDataBorsh,
|
||||
}
|
||||
|
||||
impl OracleData {
|
||||
pub fn get_fair_price(&self) -> anchor_lang::Result<f64> {
|
||||
// Check the price was updated in the last 10 seconds
|
||||
|
||||
// Do some logic here based on the twap
|
||||
|
||||
let price: f64 = SwitchboardDecimal {
|
||||
mantissa: self.price,
|
||||
scale: 9,
|
||||
}
|
||||
.try_into()?;
|
||||
|
||||
Ok(price)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[account(zero_copy(unsafe))]
|
||||
pub struct MyOracleState {
|
||||
pub bump: u8,
|
||||
pub btc: OracleData,
|
||||
pub usdc: OracleData,
|
||||
pub eth: OracleData,
|
||||
pub sol: OracleData,
|
||||
pub doge: OracleData,
|
||||
// can always re-allocate to add more
|
||||
// pub reserved: [u8; 2400],
|
||||
}
|
||||
|
||||
impl MyOracleState {
|
||||
pub fn save_rows(
|
||||
&mut self,
|
||||
rows: &Vec<OracleDataWithTradingSymbol>,
|
||||
) -> anchor_lang::Result<()> {
|
||||
for row in rows.iter() {
|
||||
match row.symbol {
|
||||
TradingSymbol::Btc => {
|
||||
msg!("saving BTC price, {}", { row.data.price });
|
||||
self.btc = row.data.into();
|
||||
}
|
||||
TradingSymbol::Usdc => {
|
||||
msg!("saving USDC price, {}", { row.data.price });
|
||||
self.usdc = row.data.into();
|
||||
}
|
||||
TradingSymbol::Eth => {
|
||||
msg!("saving ETH price, {}", { row.data.price });
|
||||
self.eth = row.data.into();
|
||||
}
|
||||
TradingSymbol::Sol => {
|
||||
msg!("saving SOL price, {}", { row.data.price });
|
||||
self.sol = row.data.into();
|
||||
}
|
||||
TradingSymbol::Doge => {
|
||||
msg!("saving DOGE price, {}", { row.data.price });
|
||||
self.doge = row.data.into();
|
||||
}
|
||||
_ => {
|
||||
msg!("no trading symbol found for {:?}", row.symbol);
|
||||
// TODO: emit an event so we can detect and fix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, AnchorSerialize, AnchorDeserialize)]
|
||||
pub enum TradingSymbol {
|
||||
#[default]
|
||||
Unknown = 0,
|
||||
Btc = 1,
|
||||
Usdc = 2,
|
||||
Eth = 3,
|
||||
Sol = 4,
|
||||
Doge = 5,
|
||||
}
|
||||
|
||||
unsafe impl Pod for TradingSymbol {}
|
||||
unsafe impl Zeroable for TradingSymbol {}
|
||||
|
||||
impl From<TradingSymbol> for u8 {
|
||||
fn from(value: TradingSymbol) -> Self {
|
||||
match value {
|
||||
TradingSymbol::Btc => 1,
|
||||
TradingSymbol::Usdc => 2,
|
||||
TradingSymbol::Eth => 3,
|
||||
TradingSymbol::Sol => 4,
|
||||
TradingSymbol::Doge => 5,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<u8> for TradingSymbol {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
1 => TradingSymbol::Btc,
|
||||
2 => TradingSymbol::Usdc,
|
||||
3 => TradingSymbol::Eth,
|
||||
4 => TradingSymbol::Sol,
|
||||
5 => TradingSymbol::Doge,
|
||||
_ => TradingSymbol::default(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
pub use crate::*;
|
||||
|
||||
pub fn parse_mr_enclaves(enclaves: &Vec<[u8; 32]>) -> anchor_lang::Result<[[u8; 32]; 32]> {
|
||||
// enclaves
|
||||
// .clone()
|
||||
// .try_into()
|
||||
// .map_err(|_| error!(BasicOracleError::ArrayOverflow))
|
||||
if enclaves.len() > 32 {
|
||||
return Err(error!(BasicOracleError::ArrayOverflow));
|
||||
}
|
||||
let mut result: [[u8; 32]; 32] = [[0; 32]; 32];
|
||||
|
||||
for (i, enclave) in enclaves.iter().enumerate() {
|
||||
result[i] = *enclave;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_mr_enclaves_success() {
|
||||
let enclaves: Vec<[u8; 32]> = vec![[1; 32]; 10];
|
||||
let result = parse_mr_enclaves(&enclaves).unwrap();
|
||||
|
||||
// Check first 10 elements are [1; 32]
|
||||
for i in 0..10 {
|
||||
assert_eq!(result[i], [1; 32]);
|
||||
}
|
||||
|
||||
// Check the remaining elements are [0; 32] (default)
|
||||
for i in 10..32 {
|
||||
assert_eq!(result[i], [0; 32]);
|
||||
}
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_parse_mr_enclaves_overflow() {
|
||||
// let enclaves: Vec<[u8; 32]> = vec![[1; 32]; 33];
|
||||
// match parse_mr_enclaves(&enclaves) {
|
||||
// Err(BasicOracleError::ArrayOverflow) => {} // test passes
|
||||
// _ => panic!("Unexpected result"), // test fails
|
||||
// };
|
||||
// }
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
// eslint-disable-next-line node/no-unpublished-import
|
||||
import type { BasicOracle } from "../target/types/basic_oracle";
|
||||
|
||||
import { printLogs } from "./utils";
|
||||
|
||||
import type { Program } from "@coral-xyz/anchor";
|
||||
import * as anchor from "@coral-xyz/anchor";
|
||||
import { sleep } from "@switchboard-xyz/common";
|
||||
import type { FunctionAccount, MrEnclave } from "@switchboard-xyz/solana.js";
|
||||
import { SwitchboardWallet } from "@switchboard-xyz/solana.js";
|
||||
import {
|
||||
AttestationProgramStateAccount,
|
||||
AttestationQueueAccount,
|
||||
attestationTypes,
|
||||
type BootstrappedAttestationQueue,
|
||||
parseMrEnclave,
|
||||
SwitchboardProgram,
|
||||
types,
|
||||
} from "@switchboard-xyz/solana.js";
|
||||
|
||||
const unixTimestamp = () => Math.floor(Date.now() / 1000);
|
||||
|
||||
// vv1gTnfuUiroqgJHS4xsRASsRQqqixCv1su85VWvcP9
|
||||
|
||||
const MRENCLAVE = parseMrEnclave(
|
||||
Buffer.from("Y6keo0uTCiWDNcWwGjZ2jfTd4VFhrr6LC/6Mk1aiNCA=", "base64")
|
||||
);
|
||||
const emptyEnclave: number[] = new Array(32).fill(0);
|
||||
|
||||
function has_mr_enclave(
|
||||
enclaves: Array<MrEnclave>,
|
||||
unknown_enclave: MrEnclave
|
||||
) {
|
||||
return enclaves.includes(unknown_enclave);
|
||||
}
|
||||
|
||||
describe("basic_oracle", () => {
|
||||
// Configure the client to use the local cluster.
|
||||
anchor.setProvider(anchor.AnchorProvider.env());
|
||||
|
||||
const program = anchor.workspace.BasicOracle as Program<BasicOracle>;
|
||||
|
||||
const payer = (program.provider as anchor.AnchorProvider).publicKey;
|
||||
|
||||
const programStatePubkey = anchor.web3.PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("BASICORACLE")],
|
||||
program.programId
|
||||
)[0];
|
||||
|
||||
const oraclePubkey = anchor.web3.PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("ORACLE_V1_SEED")],
|
||||
program.programId
|
||||
)[0];
|
||||
|
||||
let switchboard: BootstrappedAttestationQueue;
|
||||
let wallet: SwitchboardWallet;
|
||||
let functionAccount: FunctionAccount;
|
||||
|
||||
before(async () => {
|
||||
const switchboardProgram = await SwitchboardProgram.fromProvider(
|
||||
program.provider as anchor.AnchorProvider
|
||||
);
|
||||
|
||||
await AttestationProgramStateAccount.getOrCreate(switchboardProgram);
|
||||
|
||||
switchboard = await AttestationQueueAccount.bootstrapNewQueue(
|
||||
switchboardProgram
|
||||
);
|
||||
|
||||
console.log(`programStatePubkey: ${programStatePubkey}`);
|
||||
|
||||
[wallet] = await SwitchboardWallet.create(
|
||||
switchboard.program,
|
||||
switchboard.attestationQueue.publicKey,
|
||||
payer,
|
||||
"MySharedWallet",
|
||||
16
|
||||
);
|
||||
|
||||
console.log(`wallet: ${wallet.publicKey}`);
|
||||
|
||||
[functionAccount] =
|
||||
await switchboard.attestationQueue.account.createFunction(
|
||||
{
|
||||
name: "test function",
|
||||
metadata: "this function handles XYZ for my protocol",
|
||||
schedule: "15 * * * * *",
|
||||
container: "switchboardlabs/basic-oracle-function",
|
||||
version: "latest",
|
||||
mrEnclave: MRENCLAVE,
|
||||
authority: programStatePubkey,
|
||||
},
|
||||
wallet
|
||||
);
|
||||
|
||||
console.log(`functionAccount: ${functionAccount.publicKey}`);
|
||||
});
|
||||
|
||||
it("Is initialized!", async () => {
|
||||
// Add your test here.
|
||||
const tx = await program.methods
|
||||
.initialize({})
|
||||
.accounts({
|
||||
program: programStatePubkey,
|
||||
oracle: oraclePubkey,
|
||||
authority: payer,
|
||||
payer: payer,
|
||||
// function: functionAccount.publicKey,
|
||||
})
|
||||
.rpc()
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
throw err;
|
||||
});
|
||||
console.log("Your transaction signature", tx);
|
||||
});
|
||||
|
||||
// it("Adds an enclave measurement", async () => {
|
||||
// // Add your test here.
|
||||
// const tx = await program.methods
|
||||
// .setEnclaves({ mrEnclaves: [Array.from(MRENCLAVE)] })
|
||||
// .accounts({
|
||||
// program: programStatePubkey,
|
||||
// authority: payer,
|
||||
// })
|
||||
// .rpc()
|
||||
// .catch((err) => {
|
||||
// console.error(err);
|
||||
// throw err;
|
||||
// });
|
||||
// console.log("Your transaction signature", tx);
|
||||
// const programState = await program.account.myProgramState.fetch(
|
||||
// programStatePubkey
|
||||
// );
|
||||
// });
|
||||
|
||||
it("Oracle refreshes the prices", async () => {
|
||||
const securedSigner = anchor.web3.Keypair.generate();
|
||||
|
||||
const rewardAddress =
|
||||
await switchboard.program.mint.getOrCreateAssociatedUser(payer);
|
||||
|
||||
const functionState = await functionAccount.loadData();
|
||||
|
||||
// TODO: generate function verify ixn
|
||||
const functionVerifyIxn = attestationTypes.functionVerify(
|
||||
switchboard.program,
|
||||
{
|
||||
params: {
|
||||
observedTime: new anchor.BN(unixTimestamp()),
|
||||
nextAllowedTimestamp: new anchor.BN(unixTimestamp() + 100),
|
||||
isFailure: false,
|
||||
mrEnclave: Array.from(MRENCLAVE),
|
||||
},
|
||||
},
|
||||
{
|
||||
function: functionAccount.publicKey,
|
||||
functionEnclaveSigner: securedSigner.publicKey,
|
||||
verifier: switchboard.verifier.publicKey,
|
||||
verifierSigner: switchboard.verifier.signer.publicKey,
|
||||
attestationQueue: switchboard.attestationQueue.publicKey,
|
||||
escrowWallet: functionState.escrowWallet,
|
||||
escrowTokenWallet: functionState.escrowTokenWallet,
|
||||
receiver: rewardAddress,
|
||||
verifierPermission: switchboard.verifier.permissionAccount.publicKey,
|
||||
tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID,
|
||||
}
|
||||
);
|
||||
|
||||
// Add your test here.
|
||||
const tx = await program.methods
|
||||
.refreshOracles({
|
||||
rows: [
|
||||
{
|
||||
symbol: { btc: {} },
|
||||
data: {
|
||||
oracleTimestamp: new anchor.BN(unixTimestamp()),
|
||||
price: new anchor.BN("25225000000000"), // 25225
|
||||
volume1hr: new anchor.BN("25225000000000"), // 1337000
|
||||
volume24hr: new anchor.BN("25225000000000"), // 1337000
|
||||
twap1hr: new anchor.BN("25225000000000"), // 25550
|
||||
twap24hr: new anchor.BN("25225000000000"), // 25550
|
||||
},
|
||||
},
|
||||
{
|
||||
symbol: { eth: {} },
|
||||
data: {
|
||||
oracleTimestamp: new anchor.BN(unixTimestamp()),
|
||||
price: new anchor.BN("1750000000000"), // 1750
|
||||
volume1hr: new anchor.BN("420000000000"), // 420000
|
||||
volume24hr: new anchor.BN("420000000000"), // 420000
|
||||
twap1hr: new anchor.BN("1750000000000"), // 1750
|
||||
twap24hr: new anchor.BN("1750000000000"), // 1750
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
.accounts({
|
||||
oracle: oraclePubkey,
|
||||
function: functionAccount.publicKey,
|
||||
enclaveSigner: securedSigner.publicKey,
|
||||
})
|
||||
.preInstructions([functionVerifyIxn])
|
||||
.signers([switchboard.verifier.signer, securedSigner])
|
||||
.rpc({ skipPreflight: true });
|
||||
|
||||
console.log("Your transaction signature", tx);
|
||||
|
||||
await printLogs(switchboard.program.connection, tx ? tx : "");
|
||||
|
||||
await sleep(5000);
|
||||
|
||||
const oracleState = await program.account.myOracleState.fetch(oraclePubkey);
|
||||
|
||||
console.log(oracleState);
|
||||
|
||||
console.log(`BTC\n`);
|
||||
printData(oracleState.btc);
|
||||
console.log(`ETH\n`);
|
||||
printData(oracleState.eth);
|
||||
console.log(`SOL\n`);
|
||||
printData(oracleState.sol);
|
||||
});
|
||||
});
|
||||
|
||||
function normalizeDecimals(value: anchor.BN) {
|
||||
return (value ?? new anchor.BN(0))
|
||||
.div(new anchor.BN(10).pow(new anchor.BN(9)))
|
||||
.toNumber();
|
||||
}
|
||||
|
||||
function printData(obj: {
|
||||
oracleTimestamp: anchor.BN;
|
||||
price: anchor.BN;
|
||||
volume1hr: anchor.BN;
|
||||
volume24hr: anchor.BN;
|
||||
twap1hr: anchor.BN;
|
||||
twap24hr: anchor.BN;
|
||||
}) {
|
||||
console.log(`\tprice: ${normalizeDecimals(obj.price)}`);
|
||||
console.log(`\ttimestamp: ${obj.oracleTimestamp.toNumber()}`);
|
||||
console.log(`\t1Hr Volume: ${normalizeDecimals(obj.volume1hr)}`);
|
||||
console.log(`\t24Hr Volume: ${normalizeDecimals(obj.volume24hr)}`);
|
||||
console.log(`\t1Hr Twap: ${normalizeDecimals(obj.twap1hr)}`);
|
||||
console.log(`\t24Hr Twap: ${normalizeDecimals(obj.twap24hr)}`);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import type { Connection } from "@solana/web3.js";
|
||||
import { sleep } from "@switchboard-xyz/common";
|
||||
|
||||
export async function printLogs(
|
||||
connection: Connection,
|
||||
tx: string,
|
||||
v0Txn?: boolean,
|
||||
delay = 3000
|
||||
) {
|
||||
await sleep(delay);
|
||||
const parsed = await connection.getParsedTransaction(tx, {
|
||||
commitment: "confirmed",
|
||||
maxSupportedTransactionVersion: v0Txn ? 0 : undefined,
|
||||
});
|
||||
console.log(parsed?.meta?.logMessages?.join("\n"));
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "commonjs"
|
||||
}
|
||||
},
|
||||
"compilerOptions": {
|
||||
"types": ["mocha", "chai", "node"],
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
"module": "commonjs",
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": false,
|
||||
"strictNullChecks": false,
|
||||
"target": "es6",
|
||||
"paths": {
|
||||
"@switchboard-xyz/solana.js": ["../../../javascript/solana.js"]
|
||||
}
|
||||
},
|
||||
"include": ["tests/**/*", "target/types/*.ts"],
|
||||
"exclude": ["target", "lib"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../../../javascript/solana.js"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
target/
|
||||
Makefile
|
||||
README.md
|
||||
node_modules
|
||||
.turbo
|
||||
measurement.txt
|
||||
.anchor
|
||||
scripts
|
||||
.prettierignore
|
||||
.gitignore
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
.anchor
|
||||
.DS_Store
|
||||
target
|
||||
**/*.rs.bk
|
||||
node_modules
|
||||
test-ledger
|
||||
.yarn
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
.anchor
|
||||
.DS_Store
|
||||
target
|
||||
node_modules
|
||||
dist
|
||||
build
|
||||
test-ledger
|
|
@ -0,0 +1,49 @@
|
|||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[features]
|
||||
seeds = false
|
||||
skip-lint = false
|
||||
|
||||
[programs.localnet]
|
||||
basic_oracle = "BkTMjFhosJ1xKtLMV2xchGtnTDBABLJ45aXzs7x9FdeX"
|
||||
|
||||
[programs.devnet]
|
||||
basic_oracle = "BkTMjFhosJ1xKtLMV2xchGtnTDBABLJ45aXzs7x9FdeX"
|
||||
|
||||
[provider]
|
||||
# cluster = "https://api.devnet.solana.com"
|
||||
# wallet = "~/switchboard_environments_v2/devnet/upgrade_authority/upgrade_authority.json"
|
||||
cluster = "Localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
# cluster = "devnet"
|
||||
[scripts]
|
||||
test = "pnpm exec ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
|
||||
|
||||
[test]
|
||||
startup_wait = 15000
|
||||
|
||||
[test.validator]
|
||||
url = "https://api.devnet.solana.com"
|
||||
|
||||
[[test.validator.clone]] # sb devnet oracle programID
|
||||
address = "SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f"
|
||||
|
||||
[[test.validator.clone]] # sb devnet oracle IDL
|
||||
address = "Fi8vncGpNKbq62gPo56G4toCehWNy77GgqGkTaAF5Lkk"
|
||||
|
||||
[[test.validator.clone]] # sb devnet oracle SbState
|
||||
address = "CyZuD7RPDcrqCGbNvLCyqk6Py9cEZTKmNKujfPi3ynDd"
|
||||
|
||||
[[test.validator.clone]] # sb devnet oracle tokenVault
|
||||
address = "7hkp1xfPBcD2t1vZMoWWQPzipHVcXeLAAaiGXdPSfDie"
|
||||
|
||||
[[test.validator.clone]] # sb devnet attestation programID
|
||||
address = "sbattyXrzedoNATfc4L31wC9Mhxsi1BmFhTiN8gDshx"
|
||||
|
||||
[[test.validator.clone]] # sb devnet attestation IDL
|
||||
address = "5ExuoQR69trmKQfB95fDsUGsUrrChbGq9PFgt8qouncz"
|
||||
|
||||
[[test.validator.clone]] # sb devnet attestation State
|
||||
address = "BzqtGXZPiDSinP4xMFgPf6FLgSa6iPufK4m4JJFgMnTK"
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "basic_oracle"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "basic_oracle"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
switchboard-solana = { version = "=0.9.1" }
|
||||
# switchboard-solana = { version = "0.9.1", path = "../../../rust/switchboard-solana" }
|
||||
bytemuck = "^1"
|
||||
anchor-lang = { version = "0.28.0", features = [
|
||||
"init-if-needed",
|
||||
"allow-missing-optionals"
|
||||
] }
|
|
@ -0,0 +1,37 @@
|
|||
<div align="center">
|
||||
|
||||
![Switchboard Logo](https://github.com/switchboard-xyz/sbv2-core/raw/main/website/static/img/icons/switchboard/avatar.png)
|
||||
|
||||
# anchor-vrf-lite-parser
|
||||
|
||||
> An example program written in Anchor demonstrating how to integrate Switchboard Functions and verify attestation on-chain.
|
||||
|
||||
[![Anchor Test Status](https://github.com/switchboard-xyz/sbv2-solana/actions/workflows/anchor-test.yml/badge.svg)](https://github.com/switchboard-xyz/sbv2-solana/actions/workflows/anchor-test.yml)
|
||||
|
||||
</div>
|
||||
|
||||
<!-- install -->
|
||||
|
||||
<!-- installstop -->
|
||||
|
||||
## Usage
|
||||
|
||||
Build the example program
|
||||
|
||||
```bash
|
||||
anchor build
|
||||
```
|
||||
|
||||
Get your program ID and update `Anchor.toml` and `src/lib.rs` with your pubkey
|
||||
|
||||
```bash
|
||||
export ANCHOR_VRF_LITE_PARSER_PUBKEY=$(solana-keygen pubkey target/deploy/anchor_vrf_lite_parser-keypair.json)
|
||||
sed -i '' s/5Hhm5xKDiThfidbpqjJpKmMJEcKmjj5tEUNFpi2DzSvb/"$ANCHOR_VRF_LITE_PARSER_PUBKEY"/g Anchor.toml
|
||||
sed -i '' s/5Hhm5xKDiThfidbpqjJpKmMJEcKmjj5tEUNFpi2DzSvb/"$ANCHOR_VRF_LITE_PARSER_PUBKEY"/g src/lib.rs
|
||||
```
|
||||
|
||||
Then run Anchor test
|
||||
|
||||
```bash
|
||||
anchor test
|
||||
```
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "solana-candles-oracle-example",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/switchboard-xyz/sbv2-solana",
|
||||
"directory": "examples/functions/01_basic_oracle"
|
||||
},
|
||||
"scripts": {
|
||||
"build:anchor": "anchor build",
|
||||
"clean": "pnpm exec rimraf node_modules .anchor lib .turbo"
|
||||
},
|
||||
"dependencies": {
|
||||
"@coral-xyz/anchor": "^0.28.0",
|
||||
"@solana/spl-token": "^0.3.6",
|
||||
"@solana/web3.js": "^1.78.0",
|
||||
"@switchboard-xyz/common": "latest",
|
||||
"@switchboard-xyz/oracle": "latest",
|
||||
"@switchboard-xyz/solana.js": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bn.js": "^5.1.0",
|
||||
"@types/chai": "^4.3.0",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/node": "^20.4.0",
|
||||
"chai": "^4.3.4",
|
||||
"mocha": "^9.0.3",
|
||||
"ts-mocha": "^10.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env tsx
|
||||
import * as anchor from "@coral-xyz/anchor";
|
||||
import { BasicOracle } from "../target/types/basic_oracle";
|
||||
|
||||
async function main() {
|
||||
const program = anchor.workspace.BasicOracle as anchor.Program<BasicOracle>;
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
const getStringArg = (arg: string): string => {
|
||||
const args = process.argv.slice(2);
|
||||
const argIdx = args.findIndex((v) => v === arg || v === `--${arg}`);
|
||||
if (argIdx === -1) {
|
||||
return "";
|
||||
}
|
||||
if (argIdx + 1 > args.length) {
|
||||
throw new Error(`Failed to find arg`);
|
||||
}
|
||||
return args[argIdx + 1];
|
||||
};
|
||||
|
||||
const getFlag = (arg: string): boolean => {
|
||||
const args = process.argv.slice(2);
|
||||
const argIdx = args.findIndex((v) => v === arg || v === `--${arg}`);
|
||||
if (argIdx === -1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
|
@ -0,0 +1,52 @@
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
import { BasicOracle } from "../target/types/basic_oracle";
|
||||
import {
|
||||
Connection,
|
||||
PublicKey,
|
||||
Keypair,
|
||||
sendAndConfirmRawTransaction,
|
||||
SystemProgram,
|
||||
LAMPORTS_PER_SOL,
|
||||
} from "@solana/web3.js";
|
||||
|
||||
(async function main() {
|
||||
const walletKeypair = Keypair.fromSecretKey(
|
||||
new Uint8Array(
|
||||
JSON.parse(require("fs").readFileSync("<YOUR_KEYPAIR_FILE>", "utf8"))
|
||||
)
|
||||
);
|
||||
const programId = new PublicKey(
|
||||
"EF68PJkRqQu2VthTSy19kg6TWynMtRmLpxcMDKEdLC8t"
|
||||
);
|
||||
const commitment = "processed";
|
||||
const connection = new Connection("https://api.devnet.solana.com", {
|
||||
commitment,
|
||||
});
|
||||
const wallet = new anchor.Wallet(walletKeypair);
|
||||
const provider = new anchor.AnchorProvider(connection, wallet, {
|
||||
commitment,
|
||||
preflightCommitment: commitment,
|
||||
});
|
||||
const idl = await anchor.Program.fetchIdl(programId, provider);
|
||||
const program = new anchor.Program(idl!, programId!, provider!);
|
||||
const [state] = PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("BASICORACLE")],
|
||||
program.programId
|
||||
);
|
||||
const [oracle] = PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("ORACLE_V1_SEED")],
|
||||
program.programId
|
||||
);
|
||||
const initSig = await program.methods
|
||||
.initialize({})
|
||||
.accounts({
|
||||
program: state,
|
||||
oracle,
|
||||
authority: walletKeypair.publicKey,
|
||||
payer: walletKeypair.publicKey,
|
||||
systemProgram: SystemProgram.programId,
|
||||
})
|
||||
.signers([walletKeypair])
|
||||
.rpc();
|
||||
console.log(initSig);
|
||||
})();
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
# Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
|
||||
# Added by cargo
|
||||
|
||||
/target
|
||||
|
||||
*measurement.txt
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "basic-oracle-function"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "basic-oracle-function"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
basic_oracle = { path = "../", features = ["no-entrypoint"] }
|
||||
tokio = "^1"
|
||||
futures = "0.3"
|
||||
serde = "^1"
|
||||
serde_json = "^1"
|
||||
switchboard-utils = { version = "0.8.0" }
|
||||
switchboard-solana = { version = "=0.9.1" }
|
||||
bytemuck = "1.13.1"
|
||||
rust_decimal = "1.30.0"
|
|
@ -0,0 +1,31 @@
|
|||
# syntax=docker/dockerfile:1.4
|
||||
FROM switchboardlabs/sgx-function:main AS builder
|
||||
|
||||
ARG CARGO_NAME=switchboard-function
|
||||
ENV CARGO_NAME=$CARGO_NAME
|
||||
|
||||
WORKDIR /home/root/switchboard-function
|
||||
COPY ./Anchor.toml ./Cargo.lock ./Cargo.toml ./
|
||||
COPY ./src ./src
|
||||
|
||||
WORKDIR /home/root/switchboard-function/sgx-function
|
||||
COPY ./sgx-function/Cargo.lock ./sgx-function/Cargo.toml ./
|
||||
COPY ./sgx-function/src ./src
|
||||
|
||||
RUN --mount=target=/home/root/.cargo/git,type=cache \
|
||||
--mount=target=/home/root/.cargo/registry,type=cache \
|
||||
--mount=type=cache,target=/home/root/switchboard-function/sgx-function/target \
|
||||
cargo build --release && \
|
||||
cargo strip && \
|
||||
mv target/release/basic-oracle-function /sgx/app
|
||||
|
||||
FROM switchboardlabs/sgx-function:main
|
||||
|
||||
# Copy the binary
|
||||
WORKDIR /sgx
|
||||
COPY --from=builder /sgx/app /sgx
|
||||
|
||||
# Get the measurement from the enclave
|
||||
RUN /get_measurement.sh
|
||||
|
||||
ENTRYPOINT ["bash", "/boot.sh"]
|
|
@ -0,0 +1,44 @@
|
|||
# syntax=docker/dockerfile:1.4
|
||||
FROM switchboardlabs/sgx-function AS builder
|
||||
|
||||
ARG CARGO_NAME=switchboard-function
|
||||
|
||||
WORKDIR /home/root/solana-sdk
|
||||
|
||||
COPY ./rust/switchboard-solana/Cargo.toml \
|
||||
./rust/switchboard-solana/Cargo.lock \
|
||||
./rust/switchboard-solana/
|
||||
|
||||
COPY ./examples/functions/01_basic_oracle/Cargo.toml \
|
||||
./examples/functions/01_basic_oracle/Cargo.lock \
|
||||
./examples/functions/01_basic_oracle/
|
||||
|
||||
COPY ./examples/functions/01_basic_oracle/sgx-function/Cargo.toml \
|
||||
./examples/functions/01_basic_oracle/sgx-function/Cargo.lock \
|
||||
./examples/functions/01_basic_oracle/sgx-function/
|
||||
|
||||
COPY ./rust/switchboard-solana/src \
|
||||
./rust/switchboard-solana/src/
|
||||
|
||||
COPY ./examples/functions/01_basic_oracle/src \
|
||||
./examples/functions/01_basic_oracle/src/
|
||||
|
||||
COPY ./examples/functions/01_basic_oracle/sgx-function/src \
|
||||
./examples/functions/01_basic_oracle/sgx-function/src/
|
||||
|
||||
WORKDIR /home/root/solana-sdk/examples/functions/01_basic_oracle/sgx-function
|
||||
|
||||
RUN --mount=type=cache,target=/usr/local/cargo/registry,id=${TARGETPLATFORM} --mount=type=cache,target=target,id=${TARGETPLATFORM} \
|
||||
cargo build --release && \
|
||||
cargo strip && \
|
||||
mv /home/root/solana-sdk/examples/functions/01_basic_oracle/sgx-function/target/release/${CARGO_NAME} /sgx
|
||||
|
||||
FROM switchboardlabs/sgx-function
|
||||
|
||||
# Copy the binary
|
||||
WORKDIR /sgx
|
||||
COPY --from=builder /sgx/${CARGO_NAME} /sgx/app
|
||||
|
||||
# Get the measurement from the enclave
|
||||
RUN /get_measurement.sh
|
||||
ENTRYPOINT ["bash", "/boot.sh"]
|
|
@ -0,0 +1,46 @@
|
|||
.PHONY: build clean publish
|
||||
|
||||
# Variables
|
||||
CARGO_NAME=basic-oracle-function
|
||||
DOCKER_IMAGE_NAME=gallynaut/binance-oracle
|
||||
|
||||
DOCKER_BUILD_COMMAND=DOCKER_BUILDKIT=1 docker buildx build --platform linux/amd64 --build-arg CARGO_NAME=${CARGO_NAME}
|
||||
|
||||
# Default make task
|
||||
all: build
|
||||
|
||||
docker_build:
|
||||
${DOCKER_BUILD_COMMAND} --pull -f Dockerfile -t ${DOCKER_IMAGE_NAME}:dev --load ../
|
||||
docker_publish:
|
||||
${DOCKER_BUILD_COMMAND} --pull -f Dockerfile -t ${DOCKER_IMAGE_NAME} --push ../
|
||||
|
||||
dev_docker_build:
|
||||
${DOCKER_BUILD_COMMAND} --pull -f Dockerfile.dev -t ${DOCKER_IMAGE_NAME}:dev --load ../../../../
|
||||
dev_docker_publish:
|
||||
${DOCKER_BUILD_COMMAND} --pull -f Dockerfile.dev -t ${DOCKER_IMAGE_NAME} --push ../../../../
|
||||
|
||||
build: docker_build measurement
|
||||
|
||||
dev: dev_docker_build measurement
|
||||
|
||||
publish: docker_publish measurement
|
||||
|
||||
latest_mr_enclave:
|
||||
@docker run -d --pull=always --platform=linux/amd64 --name=latest-my-switchboard-function ${DOCKER_IMAGE_NAME}:latest > /dev/null
|
||||
@docker cp latest-my-switchboard-function:/measurement.txt latest-measurement.txt
|
||||
@docker stop latest-my-switchboard-function > /dev/null
|
||||
@docker rm latest-my-switchboard-function > /dev/null
|
||||
echo "latest MrEnclave: $(cat measurement.txt)"
|
||||
|
||||
measurement:
|
||||
@docker run -d --platform=linux/amd64 --name=my-switchboard-function ${DOCKER_IMAGE_NAME}:dev > /dev/null
|
||||
@docker cp my-switchboard-function:/measurement.txt measurement.txt
|
||||
@docker stop my-switchboard-function > /dev/null
|
||||
@docker rm my-switchboard-function > /dev/null
|
||||
|
||||
simulate: docker_build
|
||||
docker run -it --platform=linux/amd64 --entrypoint=/bin/bash ${DOCKER_IMAGE_NAME}:dev /boot.sh --test
|
||||
|
||||
# Task to clean up the compiled rust application
|
||||
clean:
|
||||
cargo clean
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "solana-candles-oracle-function",
|
||||
"scripts": {
|
||||
"build:function": "make"
|
||||
},
|
||||
"dependencies": {
|
||||
"solana-candles-oracle-example": "latest"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// Note: Binance API requires a non-US IP address
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub use switchboard_utils::reqwest;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Default, Clone, Debug)]
|
||||
pub struct BinanceCandle(
|
||||
i64, // Kline open time
|
||||
String, // Open price
|
||||
String, // High price
|
||||
String, // Low price
|
||||
String, // Close price
|
||||
String, // Volume
|
||||
i64, // Kline Close time
|
||||
String, // Quote asset volume
|
||||
i64, // Number of trades
|
||||
String, // Taker buy base asset volume
|
||||
String, // Taker buy quote asset volume
|
||||
String, // - Unused -
|
||||
);
|
||||
|
||||
impl Into<NormalizedCandle> for BinanceCandle {
|
||||
fn into(self) -> NormalizedCandle {
|
||||
NormalizedCandle {
|
||||
open_time: self.0,
|
||||
open_price: Decimal::try_from(self.1.as_str()).unwrap(),
|
||||
high_price: Decimal::try_from(self.2.as_str()).unwrap(),
|
||||
low_price: Decimal::try_from(self.3.as_str()).unwrap(),
|
||||
close_price: Decimal::try_from(self.4.as_str()).unwrap(),
|
||||
volume: Decimal::try_from(self.5.as_str()).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
use crate::*;
|
||||
|
||||
pub use switchboard_utils::reqwest;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Default, Clone, Debug)]
|
||||
pub struct BitfinexCandle(
|
||||
i64, // open time
|
||||
String, // Open price
|
||||
String, // High price
|
||||
String, // Low price
|
||||
String, // Close price
|
||||
String, // Volume
|
||||
);
|
||||
|
||||
impl Into<NormalizedCandle> for BitfinexCandle {
|
||||
fn into(self) -> NormalizedCandle {
|
||||
NormalizedCandle {
|
||||
open_time: self.0,
|
||||
open_price: Decimal::try_from(self.1.as_str()).unwrap(),
|
||||
high_price: Decimal::try_from(self.2.as_str()).unwrap(),
|
||||
low_price: Decimal::try_from(self.3.as_str()).unwrap(),
|
||||
close_price: Decimal::try_from(self.4.as_str()).unwrap(),
|
||||
volume: Decimal::try_from(self.5.as_str()).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
use crate::*;
|
||||
|
||||
pub use switchboard_utils::reqwest;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Default, Clone, Debug)]
|
||||
pub struct CoinbaseCandle(
|
||||
i64, // open time
|
||||
f64, // Low price
|
||||
f64, // High price
|
||||
f64, // Open price
|
||||
f64, // Close price
|
||||
f64, // Volume
|
||||
);
|
||||
|
||||
impl Into<NormalizedCandle> for CoinbaseCandle {
|
||||
fn into(self) -> NormalizedCandle {
|
||||
NormalizedCandle {
|
||||
open_time: self.0,
|
||||
low_price: Decimal::try_from(self.1).unwrap(),
|
||||
high_price: Decimal::try_from(self.2).unwrap(),
|
||||
open_price: Decimal::try_from(self.3).unwrap(),
|
||||
close_price: Decimal::try_from(self.4).unwrap(),
|
||||
volume: Decimal::try_from(self.5).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
use crate::*;
|
||||
|
||||
pub use switchboard_utils::reqwest;
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Default, Clone, Debug)]
|
||||
pub struct KrakenCandle(
|
||||
String, // Open time
|
||||
String, // Low price
|
||||
String, // High price
|
||||
String, // Open price
|
||||
String, // Close price
|
||||
String, // VWAP // TODO: surface this
|
||||
String, // Volume
|
||||
String, // Count
|
||||
);
|
||||
|
||||
#[derive(Deserialize, Default, Clone, Debug)]
|
||||
pub struct KrakenCandleResponse {
|
||||
pub result: HashMap<String, Vec<KrakenCandle>>,
|
||||
}
|
||||
|
||||
impl Into<Vec<NormalizedCandle>> for KrakenCandleResponse {
|
||||
fn into(self) -> Vec<NormalizedCandle> {
|
||||
let candles = self.result.values().next().unwrap();
|
||||
let mut res = vec![];
|
||||
for candle in candles {
|
||||
res.push(NormalizedCandle {
|
||||
open_time: candle.0.parse().unwrap(),
|
||||
low_price: Decimal::try_from(candle.1.as_str()).unwrap(),
|
||||
high_price: Decimal::try_from(candle.2.as_str()).unwrap(),
|
||||
open_price: Decimal::try_from(candle.3.as_str()).unwrap(),
|
||||
close_price: Decimal::try_from(candle.4.as_str()).unwrap(),
|
||||
volume: Decimal::try_from(candle.5.as_str()).unwrap(),
|
||||
});
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
pub mod coinbase;
|
||||
pub use coinbase::*;
|
||||
pub mod binance;
|
||||
pub use binance::*;
|
||||
pub mod bitfinex;
|
||||
pub use bitfinex::*;
|
||||
pub mod kraken;
|
||||
pub use kraken::*;
|
||||
pub use switchboard_solana::prelude::*;
|
||||
|
||||
use rust_decimal::Decimal;
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
pub use switchboard_utils::reqwest;
|
||||
|
||||
pub use basic_oracle::{
|
||||
self, OracleData, OracleDataWithTradingSymbol, RefreshPrices, RefreshPricesParams,
|
||||
SwitchboardDecimal, TradingSymbol, ID as PROGRAM_ID,
|
||||
};
|
||||
|
||||
pub fn ix_discriminator(name: &str) -> [u8; 8] {
|
||||
let preimage = format!("global:{}", name);
|
||||
let mut sighash = [0u8; 8];
|
||||
sighash.copy_from_slice(
|
||||
&anchor_lang::solana_program::hash::hash(preimage.as_bytes()).to_bytes()[..8],
|
||||
);
|
||||
sighash
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Clone, Debug)]
|
||||
pub struct NormalizedCandle {
|
||||
pub open_time: i64,
|
||||
pub open_price: Decimal,
|
||||
pub high_price: Decimal,
|
||||
pub low_price: Decimal,
|
||||
pub close_price: Decimal,
|
||||
pub volume: Decimal,
|
||||
}
|
||||
|
||||
#[tokio::main(worker_threads = 12)]
|
||||
async fn main() {
|
||||
// First, initialize the runner instance with a freshly generated Gramine keypair
|
||||
let _runner: FunctionRunner = FunctionRunner::new_from_cluster(Cluster::Devnet, None).unwrap();
|
||||
|
||||
let _binance_candles: Vec<NormalizedCandle> =
|
||||
reqwest::get("https://api.binance.com/api/v3/klines?symbol=BTCUSDT&interval=1m&limit=1")
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<Vec<BinanceCandle>>()
|
||||
.await
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|c| c.clone().into())
|
||||
.collect();
|
||||
|
||||
let _coinbase_candles: Vec<NormalizedCandle> =
|
||||
reqwest::get("https://api.pro.coinbase.com/products/BTC-USD/candles?granularity=60")
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<Vec<CoinbaseCandle>>()
|
||||
.await
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|c| c.clone().into())
|
||||
.collect();
|
||||
|
||||
let _kraken_candle: Vec<NormalizedCandle> =
|
||||
reqwest::get("https://api.kraken.com/0/public/OHLC?pair=XBTUSD&interval=1")
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<KrakenCandleResponse>()
|
||||
.await
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
let _bitfinex_candle: Vec<NormalizedCandle> = reqwest::get("https://api-pub.bitfinex.com/v2/candles/trade:1m:tBTCUSD/hist?limit=1")
|
||||
.await
|
||||
.unwrap()
|
||||
.json::<Vec<BitfinexCandle>>()
|
||||
.await
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|c| c.clone().into())
|
||||
.collect();
|
||||
|
||||
// let ixs: Vec<Instruction> = vec![];
|
||||
// // Finally, emit the signed quote and partially signed transaction to the functionRunner oracle
|
||||
// // The functionRunner oracle will use the last outputted word to stdout as the serialized result. This is what gets executed on-chain.
|
||||
// runner.emit(ixs).await.unwrap();
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(params: InitializeParams)] // rpc parameters hint
|
||||
pub struct Initialize<'info> {
|
||||
#[account(
|
||||
init,
|
||||
space = 8 + std::mem::size_of::<MyProgramState>(),
|
||||
payer = payer,
|
||||
seeds = [PROGRAM_SEED],
|
||||
bump
|
||||
)]
|
||||
pub program: AccountLoader<'info, MyProgramState>,
|
||||
|
||||
#[account(
|
||||
init,
|
||||
space = 8 + std::mem::size_of::<MyOracleState>(),
|
||||
payer = payer,
|
||||
seeds = [ORACLE_SEED],
|
||||
bump
|
||||
)]
|
||||
pub oracle: AccountLoader<'info, MyOracleState>,
|
||||
|
||||
pub authority: Signer<'info>,
|
||||
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
|
||||
pub system_program: Program<'info, System>,
|
||||
}
|
||||
|
||||
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct InitializeParams {}
|
||||
|
||||
impl Initialize<'_> {
|
||||
pub fn validate(
|
||||
&self,
|
||||
_ctx: &Context<Self>,
|
||||
_params: &InitializeParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn actuate(ctx: &Context<Self>, _params: &InitializeParams) -> anchor_lang::Result<()> {
|
||||
let program = &mut ctx.accounts.program.load_init()?;
|
||||
program.bump = *ctx.bumps.get("program").unwrap();
|
||||
program.authority = ctx.accounts.authority.key();
|
||||
|
||||
let oracle = &mut ctx.accounts.oracle.load_init()?;
|
||||
oracle.bump = *ctx.bumps.get("oracle").unwrap();
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
pub mod initialize;
|
||||
pub use initialize::*;
|
||||
|
||||
pub mod refresh_prices;
|
||||
pub use refresh_prices::*;
|
||||
|
||||
pub mod set_function;
|
||||
pub use set_function::*;
|
||||
|
||||
pub mod trigger_function;
|
||||
pub use trigger_function::*;
|
|
@ -0,0 +1,48 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct RefreshPrices<'info> {
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [ORACLE_SEED],
|
||||
bump = oracle.load()?.bump
|
||||
)]
|
||||
pub oracle: AccountLoader<'info, MyOracleState>,
|
||||
|
||||
// We use this to verify the functions enclave state
|
||||
#[account(
|
||||
constraint =
|
||||
function.load()?.validate(
|
||||
&enclave_signer.to_account_info()
|
||||
)? @ BasicOracleError::FunctionValidationFailed
|
||||
// FunctionAccountData::validate(
|
||||
// &function.to_account_info(),
|
||||
// &enclave_signer.to_account_info()
|
||||
// )? @ BasicOracleError::FunctionValidationFailed
|
||||
)]
|
||||
pub function: AccountLoader<'info, FunctionAccountData>,
|
||||
pub enclave_signer: Signer<'info>,
|
||||
}
|
||||
|
||||
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct RefreshPricesParams {
|
||||
pub rows: Vec<OracleDataWithTradingSymbol>,
|
||||
}
|
||||
|
||||
impl RefreshPrices<'_> {
|
||||
pub fn validate(
|
||||
&self,
|
||||
_ctx: &Context<Self>,
|
||||
_params: &RefreshPricesParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn actuate(ctx: &Context<Self>, params: &RefreshPricesParams) -> anchor_lang::Result<()> {
|
||||
let oracle = &mut ctx.accounts.oracle.load_mut()?;
|
||||
msg!("saving oracle data");
|
||||
oracle.save_rows(¶ms.rows)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
use crate::*;
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(params: SetFunctionParams)] // rpc parameters hint
|
||||
pub struct SetFunction<'info> {
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [PROGRAM_SEED],
|
||||
bump = program.load()?.bump,
|
||||
has_one = authority
|
||||
)]
|
||||
pub program: AccountLoader<'info, MyProgramState>,
|
||||
|
||||
pub function: AccountLoader<'info, FunctionAccountData>,
|
||||
|
||||
/// CHECK:
|
||||
pub authority: Signer<'info>,
|
||||
}
|
||||
|
||||
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct SetFunctionParams { }
|
||||
|
||||
impl SetFunction<'_> {
|
||||
pub fn validate(
|
||||
&self,
|
||||
_ctx: &Context<Self>,
|
||||
_params: &SetFunctionParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn actuate(ctx: &Context<Self>, _params: &SetFunctionParams) -> anchor_lang::Result<()> {
|
||||
let program = &mut ctx.accounts.program.load_init()?;
|
||||
program.function = ctx.accounts.function.key();
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
use crate::*;
|
||||
use switchboard_solana::attestation_program::instructions::function_trigger::FunctionTrigger;
|
||||
use switchboard_solana::SWITCHBOARD_ATTESTATION_PROGRAM_ID;
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(params: TriggerFunctionParams)] // rpc parameters hint
|
||||
pub struct TriggerFunction<'info> {
|
||||
#[account(
|
||||
has_one = authority,
|
||||
has_one = attestation_queue,
|
||||
)]
|
||||
pub function: AccountLoader<'info, FunctionAccountData>,
|
||||
|
||||
pub attestation_queue: AccountLoader<'info, AttestationQueueAccountData>,
|
||||
|
||||
pub authority: Signer<'info>,
|
||||
|
||||
/// CHECK: address is explicit
|
||||
#[account(address = SWITCHBOARD_ATTESTATION_PROGRAM_ID)]
|
||||
pub attestation_program: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct TriggerFunctionParams { }
|
||||
|
||||
impl TriggerFunction<'_> {
|
||||
pub fn validate(
|
||||
&self,
|
||||
_ctx: &Context<Self>,
|
||||
_params: &TriggerFunctionParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn actuate(ctx: &Context<Self>, _params: &TriggerFunctionParams) -> anchor_lang::Result<()> {
|
||||
FunctionTrigger {
|
||||
function: ctx.accounts.function.clone(),
|
||||
authority: ctx.accounts.authority.clone(),
|
||||
attestation_queue: ctx.accounts.attestation_queue.clone(),
|
||||
}.invoke(ctx.accounts.attestation_program.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
use crate::*;
|
||||
|
||||
#[error_code]
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub enum BasicOracleError {
|
||||
#[msg("Invalid authority account")]
|
||||
InvalidAuthority,
|
||||
#[msg("Array overflow")]
|
||||
ArrayOverflow,
|
||||
#[msg("Stale data")]
|
||||
StaleData,
|
||||
#[msg("Invalid trusted signer")]
|
||||
InvalidTrustedSigner,
|
||||
#[msg("Invalid MRENCLAVE")]
|
||||
InvalidMrEnclave,
|
||||
#[msg("Failed to find a valid trading symbol for this price")]
|
||||
InvalidSymbol,
|
||||
#[msg("FunctionAccount pubkey did not match program_state.function")]
|
||||
IncorrectSwitchboardFunction,
|
||||
#[msg("FunctionAccount pubkey did not match program_state.function")]
|
||||
InvalidSwitchboardFunction,
|
||||
#[msg("FunctionAccount was not validated successfully")]
|
||||
FunctionValidationFailed,
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
pub use switchboard_solana::prelude::*;
|
||||
|
||||
pub mod actions;
|
||||
pub use actions::*;
|
||||
|
||||
pub mod error;
|
||||
pub use error::*;
|
||||
|
||||
pub mod model;
|
||||
pub use model::*;
|
||||
|
||||
pub mod utils;
|
||||
pub use utils::*;
|
||||
|
||||
// IDL 51F8RoK1RcduTxD8KsFGn4LUuHFnPTCf2PdAF5qEYoMU
|
||||
declare_id!("BkTMjFhosJ1xKtLMV2xchGtnTDBABLJ45aXzs7x9FdeX");
|
||||
|
||||
pub const PROGRAM_SEED: &[u8] = b"BASICORACLE";
|
||||
|
||||
pub const ORACLE_SEED: &[u8] = b"ORACLE_V1_SEED";
|
||||
|
||||
#[program]
|
||||
pub mod basic_oracle {
|
||||
use super::*;
|
||||
|
||||
#[access_control(ctx.accounts.validate(&ctx, ¶ms))]
|
||||
pub fn initialize(
|
||||
ctx: Context<Initialize>,
|
||||
params: InitializeParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
Initialize::actuate(&ctx, ¶ms)
|
||||
}
|
||||
|
||||
#[access_control(ctx.accounts.validate(&ctx, ¶ms))]
|
||||
pub fn refresh_oracles(
|
||||
ctx: Context<RefreshPrices>,
|
||||
params: RefreshPricesParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
RefreshPrices::actuate(&ctx, ¶ms)
|
||||
}
|
||||
|
||||
#[access_control(ctx.accounts.validate(&ctx, ¶ms))]
|
||||
pub fn set_function(
|
||||
ctx: Context<SetFunction>,
|
||||
params: SetFunctionParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
SetFunction::actuate(&ctx, ¶ms)
|
||||
}
|
||||
|
||||
#[access_control(ctx.accounts.validate(&ctx, ¶ms))]
|
||||
pub fn trigger_function(
|
||||
ctx: Context<TriggerFunction>,
|
||||
params: TriggerFunctionParams,
|
||||
) -> anchor_lang::Result<()> {
|
||||
TriggerFunction::actuate(&ctx, ¶ms)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,155 @@
|
|||
use crate::*;
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
|
||||
#[account(zero_copy(unsafe))]
|
||||
pub struct MyProgramState {
|
||||
pub bump: u8,
|
||||
pub authority: Pubkey,
|
||||
pub function: Pubkey,
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[zero_copy(unsafe)]
|
||||
pub struct OracleData {
|
||||
pub oracle_timestamp: i64,
|
||||
pub price: i128,
|
||||
pub volume_1hr: i128,
|
||||
pub volume_24hr: i128,
|
||||
pub twap_1hr: i128,
|
||||
pub twap_24hr: i128,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct OracleDataBorsh {
|
||||
pub oracle_timestamp: i64,
|
||||
pub price: i128,
|
||||
pub volume_1hr: i128,
|
||||
pub volume_24hr: i128,
|
||||
pub twap_1hr: i128,
|
||||
pub twap_24hr: i128,
|
||||
}
|
||||
impl From<OracleDataBorsh> for OracleData {
|
||||
fn from(value: OracleDataBorsh) -> Self {
|
||||
Self {
|
||||
oracle_timestamp: value.oracle_timestamp,
|
||||
price: value.price.clone(),
|
||||
volume_1hr: value.volume_1hr.clone(),
|
||||
volume_24hr: value.volume_24hr.clone(),
|
||||
twap_1hr: value.twap_1hr.clone(),
|
||||
twap_24hr: value.twap_24hr.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default, AnchorSerialize, AnchorDeserialize)]
|
||||
pub struct OracleDataWithTradingSymbol {
|
||||
pub symbol: TradingSymbol,
|
||||
pub data: OracleDataBorsh,
|
||||
}
|
||||
|
||||
impl OracleData {
|
||||
pub fn get_fair_price(&self) -> anchor_lang::Result<f64> {
|
||||
// Check the price was updated in the last 10 seconds
|
||||
|
||||
// Do some logic here based on the twap
|
||||
|
||||
let price: f64 = SwitchboardDecimal {
|
||||
mantissa: self.price,
|
||||
scale: 9,
|
||||
}
|
||||
.try_into()?;
|
||||
|
||||
Ok(price)
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(packed)]
|
||||
#[account(zero_copy(unsafe))]
|
||||
pub struct MyOracleState {
|
||||
pub bump: u8,
|
||||
pub btc: OracleData,
|
||||
pub usdc: OracleData,
|
||||
pub eth: OracleData,
|
||||
pub sol: OracleData,
|
||||
pub doge: OracleData,
|
||||
// can always re-allocate to add more
|
||||
// pub reserved: [u8; 2400],
|
||||
}
|
||||
|
||||
impl MyOracleState {
|
||||
pub fn save_rows(
|
||||
&mut self,
|
||||
rows: &Vec<OracleDataWithTradingSymbol>,
|
||||
) -> anchor_lang::Result<()> {
|
||||
for row in rows.iter() {
|
||||
match row.symbol {
|
||||
TradingSymbol::Btc => {
|
||||
msg!("saving BTC price, {}", { row.data.price });
|
||||
self.btc = row.data.into();
|
||||
}
|
||||
TradingSymbol::Usdc => {
|
||||
msg!("saving USDC price, {}", { row.data.price });
|
||||
self.usdc = row.data.into();
|
||||
}
|
||||
TradingSymbol::Eth => {
|
||||
msg!("saving ETH price, {}", { row.data.price });
|
||||
self.eth = row.data.into();
|
||||
}
|
||||
TradingSymbol::Sol => {
|
||||
msg!("saving SOL price, {}", { row.data.price });
|
||||
self.sol = row.data.into();
|
||||
}
|
||||
TradingSymbol::Doge => {
|
||||
msg!("saving DOGE price, {}", { row.data.price });
|
||||
self.doge = row.data.into();
|
||||
}
|
||||
_ => {
|
||||
msg!("no trading symbol found for {:?}", row.symbol);
|
||||
// TODO: emit an event so we can detect and fix
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Copy, Clone, Default, Debug, Eq, PartialEq, AnchorSerialize, AnchorDeserialize)]
|
||||
pub enum TradingSymbol {
|
||||
#[default]
|
||||
Unknown = 0,
|
||||
Btc = 1,
|
||||
Usdc = 2,
|
||||
Eth = 3,
|
||||
Sol = 4,
|
||||
Doge = 5,
|
||||
}
|
||||
|
||||
unsafe impl Pod for TradingSymbol {}
|
||||
unsafe impl Zeroable for TradingSymbol {}
|
||||
|
||||
impl From<TradingSymbol> for u8 {
|
||||
fn from(value: TradingSymbol) -> Self {
|
||||
match value {
|
||||
TradingSymbol::Btc => 1,
|
||||
TradingSymbol::Usdc => 2,
|
||||
TradingSymbol::Eth => 3,
|
||||
TradingSymbol::Sol => 4,
|
||||
TradingSymbol::Doge => 5,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<u8> for TradingSymbol {
|
||||
fn from(value: u8) -> Self {
|
||||
match value {
|
||||
1 => TradingSymbol::Btc,
|
||||
2 => TradingSymbol::Usdc,
|
||||
3 => TradingSymbol::Eth,
|
||||
4 => TradingSymbol::Sol,
|
||||
5 => TradingSymbol::Doge,
|
||||
_ => TradingSymbol::default(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
pub use crate::*;
|
||||
|
||||
pub fn parse_mr_enclaves(enclaves: &Vec<[u8; 32]>) -> anchor_lang::Result<[[u8; 32]; 32]> {
|
||||
// enclaves
|
||||
// .clone()
|
||||
// .try_into()
|
||||
// .map_err(|_| error!(BasicOracleError::ArrayOverflow))
|
||||
if enclaves.len() > 32 {
|
||||
return Err(error!(BasicOracleError::ArrayOverflow));
|
||||
}
|
||||
let mut result: [[u8; 32]; 32] = [[0; 32]; 32];
|
||||
|
||||
for (i, enclave) in enclaves.iter().enumerate() {
|
||||
result[i] = *enclave;
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_parse_mr_enclaves_success() {
|
||||
let enclaves: Vec<[u8; 32]> = vec![[1; 32]; 10];
|
||||
let result = parse_mr_enclaves(&enclaves).unwrap();
|
||||
|
||||
// Check first 10 elements are [1; 32]
|
||||
for i in 0..10 {
|
||||
assert_eq!(result[i], [1; 32]);
|
||||
}
|
||||
|
||||
// Check the remaining elements are [0; 32] (default)
|
||||
for i in 10..32 {
|
||||
assert_eq!(result[i], [0; 32]);
|
||||
}
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn test_parse_mr_enclaves_overflow() {
|
||||
// let enclaves: Vec<[u8; 32]> = vec![[1; 32]; 33];
|
||||
// match parse_mr_enclaves(&enclaves) {
|
||||
// Err(BasicOracleError::ArrayOverflow) => {} // test passes
|
||||
// _ => panic!("Unexpected result"), // test fails
|
||||
// };
|
||||
// }
|
||||
}
|
|
@ -0,0 +1,246 @@
|
|||
// eslint-disable-next-line node/no-unpublished-import
|
||||
import type { BasicOracle } from "../target/types/basic_oracle";
|
||||
|
||||
import { printLogs } from "./utils";
|
||||
|
||||
import type { Program } from "@coral-xyz/anchor";
|
||||
import * as anchor from "@coral-xyz/anchor";
|
||||
import { sleep } from "@switchboard-xyz/common";
|
||||
import type { FunctionAccount, MrEnclave } from "@switchboard-xyz/solana.js";
|
||||
import { SwitchboardWallet } from "@switchboard-xyz/solana.js";
|
||||
import {
|
||||
AttestationProgramStateAccount,
|
||||
AttestationQueueAccount,
|
||||
attestationTypes,
|
||||
type BootstrappedAttestationQueue,
|
||||
parseMrEnclave,
|
||||
SwitchboardProgram,
|
||||
types,
|
||||
} from "@switchboard-xyz/solana.js";
|
||||
|
||||
const unixTimestamp = () => Math.floor(Date.now() / 1000);
|
||||
|
||||
// vv1gTnfuUiroqgJHS4xsRASsRQqqixCv1su85VWvcP9
|
||||
|
||||
const MRENCLAVE = parseMrEnclave(
|
||||
Buffer.from("Y6keo0uTCiWDNcWwGjZ2jfTd4VFhrr6LC/6Mk1aiNCA=", "base64")
|
||||
);
|
||||
const emptyEnclave: number[] = new Array(32).fill(0);
|
||||
|
||||
function has_mr_enclave(
|
||||
enclaves: Array<MrEnclave>,
|
||||
unknown_enclave: MrEnclave
|
||||
) {
|
||||
return enclaves.includes(unknown_enclave);
|
||||
}
|
||||
|
||||
describe("basic_oracle", () => {
|
||||
// Configure the client to use the local cluster.
|
||||
anchor.setProvider(anchor.AnchorProvider.env());
|
||||
|
||||
const program = anchor.workspace.BasicOracle as Program<BasicOracle>;
|
||||
|
||||
const payer = (program.provider as anchor.AnchorProvider).publicKey;
|
||||
|
||||
const programStatePubkey = anchor.web3.PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("BASICORACLE")],
|
||||
program.programId
|
||||
)[0];
|
||||
|
||||
const oraclePubkey = anchor.web3.PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("ORACLE_V1_SEED")],
|
||||
program.programId
|
||||
)[0];
|
||||
|
||||
let switchboard: BootstrappedAttestationQueue;
|
||||
let wallet: SwitchboardWallet;
|
||||
let functionAccount: FunctionAccount;
|
||||
|
||||
before(async () => {
|
||||
const switchboardProgram = await SwitchboardProgram.fromProvider(
|
||||
program.provider as anchor.AnchorProvider
|
||||
);
|
||||
|
||||
await AttestationProgramStateAccount.getOrCreate(switchboardProgram);
|
||||
|
||||
switchboard = await AttestationQueueAccount.bootstrapNewQueue(
|
||||
switchboardProgram
|
||||
);
|
||||
|
||||
console.log(`programStatePubkey: ${programStatePubkey}`);
|
||||
|
||||
[wallet] = await SwitchboardWallet.create(
|
||||
switchboard.program,
|
||||
switchboard.attestationQueue.publicKey,
|
||||
payer,
|
||||
"MySharedWallet",
|
||||
16
|
||||
);
|
||||
|
||||
console.log(`wallet: ${wallet.publicKey}`);
|
||||
|
||||
[functionAccount] =
|
||||
await switchboard.attestationQueue.account.createFunction(
|
||||
{
|
||||
name: "test function",
|
||||
metadata: "this function handles XYZ for my protocol",
|
||||
schedule: "15 * * * * *",
|
||||
container: "switchboardlabs/basic-oracle-function",
|
||||
version: "latest",
|
||||
mrEnclave: MRENCLAVE,
|
||||
authority: programStatePubkey,
|
||||
},
|
||||
wallet
|
||||
);
|
||||
|
||||
console.log(`functionAccount: ${functionAccount.publicKey}`);
|
||||
});
|
||||
|
||||
it("Is initialized!", async () => {
|
||||
// Add your test here.
|
||||
const tx = await program.methods
|
||||
.initialize({})
|
||||
.accounts({
|
||||
program: programStatePubkey,
|
||||
oracle: oraclePubkey,
|
||||
authority: payer,
|
||||
payer: payer,
|
||||
// function: functionAccount.publicKey,
|
||||
})
|
||||
.rpc()
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
throw err;
|
||||
});
|
||||
console.log("Your transaction signature", tx);
|
||||
});
|
||||
|
||||
// it("Adds an enclave measurement", async () => {
|
||||
// // Add your test here.
|
||||
// const tx = await program.methods
|
||||
// .setEnclaves({ mrEnclaves: [Array.from(MRENCLAVE)] })
|
||||
// .accounts({
|
||||
// program: programStatePubkey,
|
||||
// authority: payer,
|
||||
// })
|
||||
// .rpc()
|
||||
// .catch((err) => {
|
||||
// console.error(err);
|
||||
// throw err;
|
||||
// });
|
||||
// console.log("Your transaction signature", tx);
|
||||
// const programState = await program.account.myProgramState.fetch(
|
||||
// programStatePubkey
|
||||
// );
|
||||
// });
|
||||
|
||||
it("Oracle refreshes the prices", async () => {
|
||||
const securedSigner = anchor.web3.Keypair.generate();
|
||||
|
||||
const rewardAddress =
|
||||
await switchboard.program.mint.getOrCreateAssociatedUser(payer);
|
||||
|
||||
const functionState = await functionAccount.loadData();
|
||||
|
||||
// TODO: generate function verify ixn
|
||||
const functionVerifyIxn = attestationTypes.functionVerify(
|
||||
switchboard.program,
|
||||
{
|
||||
params: {
|
||||
observedTime: new anchor.BN(unixTimestamp()),
|
||||
nextAllowedTimestamp: new anchor.BN(unixTimestamp() + 100),
|
||||
isFailure: false,
|
||||
mrEnclave: Array.from(MRENCLAVE),
|
||||
},
|
||||
},
|
||||
{
|
||||
function: functionAccount.publicKey,
|
||||
functionEnclaveSigner: securedSigner.publicKey,
|
||||
verifier: switchboard.verifier.publicKey,
|
||||
verifierSigner: switchboard.verifier.signer.publicKey,
|
||||
attestationQueue: switchboard.attestationQueue.publicKey,
|
||||
escrowWallet: functionState.escrowWallet,
|
||||
escrowTokenWallet: functionState.escrowTokenWallet,
|
||||
receiver: rewardAddress,
|
||||
verifierPermission: switchboard.verifier.permissionAccount.publicKey,
|
||||
tokenProgram: anchor.utils.token.TOKEN_PROGRAM_ID,
|
||||
}
|
||||
);
|
||||
|
||||
// Add your test here.
|
||||
const tx = await program.methods
|
||||
.refreshOracles({
|
||||
rows: [
|
||||
{
|
||||
symbol: { btc: {} },
|
||||
data: {
|
||||
oracleTimestamp: new anchor.BN(unixTimestamp()),
|
||||
price: new anchor.BN("25225000000000"), // 25225
|
||||
volume1hr: new anchor.BN("25225000000000"), // 1337000
|
||||
volume24hr: new anchor.BN("25225000000000"), // 1337000
|
||||
twap1hr: new anchor.BN("25225000000000"), // 25550
|
||||
twap24hr: new anchor.BN("25225000000000"), // 25550
|
||||
},
|
||||
},
|
||||
{
|
||||
symbol: { eth: {} },
|
||||
data: {
|
||||
oracleTimestamp: new anchor.BN(unixTimestamp()),
|
||||
price: new anchor.BN("1750000000000"), // 1750
|
||||
volume1hr: new anchor.BN("420000000000"), // 420000
|
||||
volume24hr: new anchor.BN("420000000000"), // 420000
|
||||
twap1hr: new anchor.BN("1750000000000"), // 1750
|
||||
twap24hr: new anchor.BN("1750000000000"), // 1750
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
.accounts({
|
||||
oracle: oraclePubkey,
|
||||
function: functionAccount.publicKey,
|
||||
enclaveSigner: securedSigner.publicKey,
|
||||
})
|
||||
.preInstructions([functionVerifyIxn])
|
||||
.signers([switchboard.verifier.signer, securedSigner])
|
||||
.rpc({ skipPreflight: true });
|
||||
|
||||
console.log("Your transaction signature", tx);
|
||||
|
||||
await printLogs(switchboard.program.connection, tx ? tx : "");
|
||||
|
||||
await sleep(5000);
|
||||
|
||||
const oracleState = await program.account.myOracleState.fetch(oraclePubkey);
|
||||
|
||||
console.log(oracleState);
|
||||
|
||||
console.log(`BTC\n`);
|
||||
printData(oracleState.btc);
|
||||
console.log(`ETH\n`);
|
||||
printData(oracleState.eth);
|
||||
console.log(`SOL\n`);
|
||||
printData(oracleState.sol);
|
||||
});
|
||||
});
|
||||
|
||||
function normalizeDecimals(value: anchor.BN) {
|
||||
return (value ?? new anchor.BN(0))
|
||||
.div(new anchor.BN(10).pow(new anchor.BN(9)))
|
||||
.toNumber();
|
||||
}
|
||||
|
||||
function printData(obj: {
|
||||
oracleTimestamp: anchor.BN;
|
||||
price: anchor.BN;
|
||||
volume1hr: anchor.BN;
|
||||
volume24hr: anchor.BN;
|
||||
twap1hr: anchor.BN;
|
||||
twap24hr: anchor.BN;
|
||||
}) {
|
||||
console.log(`\tprice: ${normalizeDecimals(obj.price)}`);
|
||||
console.log(`\ttimestamp: ${obj.oracleTimestamp.toNumber()}`);
|
||||
console.log(`\t1Hr Volume: ${normalizeDecimals(obj.volume1hr)}`);
|
||||
console.log(`\t24Hr Volume: ${normalizeDecimals(obj.volume24hr)}`);
|
||||
console.log(`\t1Hr Twap: ${normalizeDecimals(obj.twap1hr)}`);
|
||||
console.log(`\t24Hr Twap: ${normalizeDecimals(obj.twap24hr)}`);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import type { Connection } from "@solana/web3.js";
|
||||
import { sleep } from "@switchboard-xyz/common";
|
||||
|
||||
export async function printLogs(
|
||||
connection: Connection,
|
||||
tx: string,
|
||||
v0Txn?: boolean,
|
||||
delay = 3000
|
||||
) {
|
||||
await sleep(delay);
|
||||
const parsed = await connection.getParsedTransaction(tx, {
|
||||
commitment: "confirmed",
|
||||
maxSupportedTransactionVersion: v0Txn ? 0 : undefined,
|
||||
});
|
||||
console.log(parsed?.meta?.logMessages?.join("\n"));
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"ts-node": {
|
||||
"compilerOptions": {
|
||||
"module": "commonjs"
|
||||
}
|
||||
},
|
||||
"compilerOptions": {
|
||||
"types": ["mocha", "chai", "node"],
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
"module": "commonjs",
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": false,
|
||||
"strictNullChecks": false,
|
||||
"target": "es6",
|
||||
"paths": {
|
||||
"@switchboard-xyz/solana.js": ["../../../javascript/solana.js"]
|
||||
}
|
||||
},
|
||||
"include": ["tests/**/*", "target/types/*.ts"],
|
||||
"exclude": ["target", "lib"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../../../../../javascript/solana.js"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
.anchor
|
||||
.DS_Store
|
||||
target
|
||||
**/*.rs.bk
|
||||
node_modules
|
||||
test-ledger
|
||||
.yarn
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
.anchor
|
||||
.DS_Store
|
||||
target
|
||||
node_modules
|
||||
dist
|
||||
build
|
||||
test-ledger
|
|
@ -0,0 +1,47 @@
|
|||
[features]
|
||||
seeds = false
|
||||
skip-lint = false
|
||||
|
||||
[programs.localnet]
|
||||
custom_randomness_request = "Csx5AU83fPiaSChJUBZg2cW9GcCVVwZ4rwFqDA2pomX2"
|
||||
|
||||
[programs.devnet]
|
||||
custom_randomness_request = "Csx5AU83fPiaSChJUBZg2cW9GcCVVwZ4rwFqDA2pomX2"
|
||||
|
||||
[registry]
|
||||
url = "https://api.apr.dev"
|
||||
|
||||
[provider]
|
||||
# cluster = "Localnet"
|
||||
cluster = "https://api.devnet.solana.com"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
[scripts]
|
||||
test = "pnpm exec ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
|
||||
|
||||
[test.validator]
|
||||
url = "https://api.devnet.solana.com"
|
||||
|
||||
[test]
|
||||
startup_wait = 15000
|
||||
|
||||
[[test.validator.clone]] # sb devnet oracle programID
|
||||
address = "SW1TCH7qEPTdLsDHRgPuMQjbQxKdH2aBStViMFnt64f"
|
||||
|
||||
[[test.validator.clone]] # sb devnet oracle IDL
|
||||
address = "Fi8vncGpNKbq62gPo56G4toCehWNy77GgqGkTaAF5Lkk"
|
||||
|
||||
[[test.validator.clone]] # sb devnet oracle SbState
|
||||
address = "CyZuD7RPDcrqCGbNvLCyqk6Py9cEZTKmNKujfPi3ynDd"
|
||||
|
||||
[[test.validator.clone]] # sb devnet oracle tokenVault
|
||||
address = "7hkp1xfPBcD2t1vZMoWWQPzipHVcXeLAAaiGXdPSfDie"
|
||||
|
||||
[[test.validator.clone]] # sb devnet attestation programID
|
||||
address = "sbattyXrzedoNATfc4L31wC9Mhxsi1BmFhTiN8gDshx"
|
||||
|
||||
[[test.validator.clone]] # sb devnet attestation IDL
|
||||
address = "5ExuoQR69trmKQfB95fDsUGsUrrChbGq9PFgt8qouncz"
|
||||
|
||||
[[test.validator.clone]] # sb devnet programState
|
||||
address = "5MFs7RGTjLi1wtKNBFRtuLipCkkjs4YQwRRU9sjnbQbS"
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue